// пример параболического текстурирования

// подключим пару своих библиотек
#include "video.h"
#include "3d.h"

// процедура рисования грани в видеобуфер
void drawParabolicTexturedFace(face *f) {
  vertex *a, *b, *c, *tmp_vertex;
  int current_sx, current_sy, length, len;
  float tmp, k, x_start, x_end;
  float uz_start, uz_end, vz_start, vz_end, z1_start, z1_end;
  float u_a, v_a, u_b, v_b, u_c, v_c, u, v, du, dv, ddu, ddv;
  float pa, pb, pc;
  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;

  // построчная отрисовка грани
  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;

    // считаем длину рисуемой строки
    length = (int)x_end - (int)x_start;

    // строку нулевой длины рисовать не будем
    if (length == 0) continue;

    // считаем u, v для начала, середины и конца строки
    u_a = uz_start / z1_start;
    v_a = vz_start / z1_start;
    u_b = (uz_start + uz_end) / (z1_start + z1_end);
    v_b = (vz_start + vz_end) / (z1_start + z1_end);
    u_c = uz_end / z1_end;
    v_c = vz_end / z1_end;

    // считаем начальное du и ddu
    pa = 2 * ((u_c - u_a) - 2 * (u_b - u_a)) / (length * length);
    pb = (4 * (u_b - u_a) - (u_c - u_a)) / length;
    pc = u_a;
    u   = pc;
    du  = pa + pb;
    ddu = 2 * pa;

    // считаем начальное dv и ddv
    pa = 2 * ((v_c - v_a) - 2 * (v_b - v_a)) / (length * length);
    pb = (4 * (v_b - v_a) - (v_c - v_a)) / length;
    pc = v_a;
    v   = pc;
    dv  = pa + pb;
    ddv = 2 * pa;

    // рисуем кусок
    while (length--) {
      *dest++ = f->texture[((int)v) * 256 + (int)u];
      u += du;
      v += dv;
      du += ddu;
      dv += ddv;
    }
  }
}

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;

  // нарисуем грань в видеобуфер
  drawParabolicTexturedFace(&f);

  // скопируем видеобуфер на экран
  showVideoBuffer();

  // подождем нажатия клавиши
  getch();

  // вернемся в текстовый видеорежим
  doneVideo();
}
