// пример точного текстурирования

// подключим пару своих библиотек
#include "video.h"
#include "3d.h"

// процедура рисования грани в видеобуфер
void drawPerfectTexturedFace(face *f) {
  vertex *a, *b, *c, *tmp_vertex;
  int current_sx, current_sy;
  float tmp, k, x_start, x_end;
  float uz_start, uz_end, vz_start, vz_end, z1_start, z1_end, duz, dvz, dz1;
  float uz, vz, z1;
  char *dest;

  a = f->v1;
  b = f->v2;
  c = f->v3;

  // спроецируем вершины грани
  projectVertex(a);
  projectVertex(b);
  projectVertex(c);

  // посчитаем u/z, v/z, 1/z во всех вершинах
  a->z1 = 1 / (a->rz + DIST);
  a->uz = a->u * a->z1;
  a->vz = a->v * a->z1;
  b->z1 = 1 / (b->rz + DIST);
  b->uz = b->u * b->z1;
  b->vz = b->v * b->z1;
  c->z1 = 1 / (c->rz + DIST);
  c->uz = c->u * c->z1;
  c->vz = c->v * c->z1;

  // отсортируем вершины грани по sy
  if (a->sy > b->sy) { tmp_vertex = a; a = b; b = tmp_vertex; }
  if (a->sy > c->sy) { tmp_vertex = a; a = c; c = tmp_vertex; }
  if (b->sy > c->sy) { tmp_vertex = b; b = c; c = tmp_vertex; }

  // грань нулевой высоты рисовать не будем
  if ((int)c->sy == (int)a->sy) return;

  // посчитаем d(u/z)/dsx, d(v/z)/dsx, d(1/z)/dsx
  // считаем по самой длинной линии (т.е. проходящей через вершину B)
  k = (b->sy - a->sy) / (c->sy - a->sy);
  x_start = a->sx + (c->sx - a->sx) * k;
  uz_start = a->uz + (c->uz - a->uz) * k;
  vz_start = a->vz + (c->vz - a->vz) * k;
  z1_start = a->z1 + (c->z1 - a->z1) * k;
  x_end = b->sx;
  uz_end = b->uz;
  vz_end = b->vz;
  z1_end = b->z1;
  duz = (uz_start - uz_end) / (x_start - x_end);
  dvz = (vz_start - vz_end) / (x_start - x_end);
  dz1 = (z1_start - z1_end) / (x_start - x_end);

  // построчная отрисовка грани
  for (current_sy = (int)a->sy; current_sy <= (int)c->sy; current_sy++) {
    // считаем x, u/z, v/z, 1/z для стороны AC
    k = (current_sy - a->sy) / (c->sy - a->sy);
    x_start = a->sx + (c->sx - a->sx) * k;
    uz_start = a->uz + (c->uz - a->uz) * k;
    vz_start = a->vz + (c->vz - a->vz) * k;
    z1_start = a->z1 + (c->z1 - a->z1) * k;

    if (current_sy >= b->sy) {
      // мы находимся ниже вершины B, считаем x, u/z, v/z, 1/z для стороны BC
      k = (current_sy - b->sy) / (c->sy - b->sy);
      x_end = b->sx + (c->sx - b->sx) * k;
      uz_end = b->uz + (c->uz - b->uz) * k;
      vz_end = b->vz + (c->vz - b->vz) * k;
      z1_end = b->z1 + (c->z1 - b->z1) * k;
    } else {
      // мы находимся выше вершины B, считаем x, u/z, v/z, 1/z для стороны AB
      k = (current_sy - a->sy) / (b->sy - a->sy);
      x_end = a->sx + (b->sx - a->sx) * k;
      uz_end = a->uz + (b->uz - a->uz) * k;
      vz_end = a->vz + (b->vz - a->vz) * k;
      z1_end = a->z1 + (b->z1 - a->z1) * k;
    }

    // x_start должен находиться левее x_end
    if (x_start > x_end) {
      tmp = x_start; x_start = x_end; x_end = tmp;
      tmp = uz_start; uz_start = uz_end; uz_end = tmp;
      tmp = vz_start; vz_start = vz_end; vz_end = tmp;
      tmp = z1_start; z1_start = z1_end; z1_end = tmp;
    }

    // считаем адрес начала строки в видеопамяти
    dest = videoBuffer;
    dest += current_sy * XSIZE + (int)x_start;

    // готовим u, v к текстурированию
    uz = uz_start;
    vz = vz_start;
    z1 = z1_start;

    // текстурируем строку
    for (current_sx = x_start; current_sx <= x_end; current_sx++) {
      *dest++ = f->texture[(int)(vz / z1) * 256 + (int)(uz / z1)];
      uz += duz;
      vz += dvz;
      z1 += dz1;
    }
  }
}

void main() {
  vertex v1, v2, v3;
  face f;

  initVideo();        // инициализируем видеосистему
  initPalette();      // поставим палитру
  makeTexture();      // сгенерируем текстуру
  clearVideoBuffer(); // очистим буфер отрисовки

  // "настроим" грань
  f.v1 = &v1;
  f.v2 = &v2;
  f.v3 = &v3;
  f.texture = exampleTexture;

  // тестовые значения координат вершин
  v1.rx = -120;
  v1.ry = 80;
  v1.rz = 0;
  v2.rx = -120;
  v2.ry = -80;
  v2.rz = 0;
  v3.rx = -80;
  v3.ry = 80;
  v3.rz = 160;

  // тестовые значения координат текстуры в вершинах
  v1.u = 0;
  v1.v = 0;
  v2.u = 0;
  v2.v = 255;
  v3.u = 255;
  v3.v = 0;

  // нарисуем грань в видеобуфер
  drawPerfectTexturedFace(&f);

  // скопируем видеобуфер на экран
  showVideoBuffer();

  // подождем нажатия клавиши
  getch();

  // вернемся в текстовый видеорежим
  doneVideo();
}
