// пример аффинного текстурирования с билинейной фильтрацией

#include <math.h>   // для floor(), ceil()

// подключим пару своих библиотек
#include "video.h"
#include "3d.h"

// процедура рисования грани в видеобуфер
void drawBilinearAffineTexturedFace(face *f) {
  vertex *a, *b, *c, *tmp_vertex;
  int current_sx, current_sy, iu, iv, offs;
  float tmp, k, x_start, x_end, u_start, u_end, v_start, v_end;
  float u, v, du, dv, fu, fv, c1, c2;
  char *dest;

  a = f->v1;
  b = f->v2;
  c = f->v3;

  // спроецируем вершины грани
  projectVertex(a);
  projectVertex(b);
  projectVertex(c);

  // отсортируем вершины грани по 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;

  // посчитаем du/dsx и dv/dsx
  // считаем по самой длинной линии (т.е. проходящей через вершину B)
  k = (b->sy - a->sy) / (c->sy - a->sy);
  x_start = a->sx + (c->sx - a->sx) * k;
  u_start = a->u + (c->u - a->u) * k;
  v_start = a->v + (c->v - a->v) * k;
  x_end = b->sx;
  u_end = b->u;
  v_end = b->v;
  du = (u_start - u_end) / (x_start - x_end);
  dv = (v_start - v_end) / (x_start - x_end);

  // построчная отрисовка грани
  for (current_sy = (int)a->sy; current_sy <= (int)c->sy; current_sy++) {
    // считаем x/u/v для стороны AC
    k = (current_sy - a->sy) / (c->sy - a->sy);
    x_start = a->sx + (c->sx - a->sx) * k;
    u_start = a->u + (c->u - a->u) * k;
    v_start = a->v + (c->v - a->v) * k;

    if (current_sy >= b->sy) {
      // мы находимся ниже вершины B, считаем x/u/v для стороны BC
      k = (current_sy - b->sy) / (c->sy - b->sy);
      x_end = b->sx + (c->sx - b->sx) * k;
      u_end = b->u + (c->u - b->u) * k;
      v_end = b->v + (c->v - b->v) * k;
    } else {
      // мы находимся выше вершины B, считаем x/u/v для стороны AC
      k = (current_sy - a->sy) / (b->sy - a->sy);
      x_end = a->sx + (b->sx - a->sx) * k;
      u_end = a->u + (b->u - a->u) * k;
      v_end = a->v + (b->v - a->v) * k;
    }

    // x_start должен находиться левее x_end
    if (x_start > x_end) {
      tmp = x_start; x_start = x_end; x_end = tmp;
      tmp = u_start; u_start = u_end; u_end = tmp;
      tmp = v_start; v_start = v_end; v_end = tmp;
    }

    // считаем адрес начала строки в видеопамяти
    dest = videoBuffer;
    dest += current_sy * XSIZE + (int)x_start;

    // готовим u, v к текстурированию
    u = u_start;
    v = v_start;

    // текстурируем строку
    for (current_sx = (int)x_start; current_sx <= (int)x_end; current_sx++) {
      iu = floor(u);
      iv = floor(v);
      fu = u - iu;
      fv = v - iv;
      offs = iv * 256 + iu;
      c1 = f->texture[offs] + (f->texture[offs + 256] - f->texture[offs]) * fv;
      c2 = f->texture[offs + 1] + (f->texture[offs + 257] - f->texture[offs + 1]) * fv;
      *dest++ = c1 + (c2 - c1) * fu;
      u += du;
      v += dv;
    }
  }
}

void makeBiTexture() {
  int i, j;
  char *d = exampleTexture;

  for (i = 0; i < 256; i++)
    for (j = 0; j < 256; j++)
      *d++ = ((i & 1) + (j & 1)) * 0xFF;
}


void main() {
  vertex v1, v2, v3;
  face f;

  initVideo();        // инициализируем видеосистему
  initPalette();      // поставим палитру
  makeBiTexture();    // сгенерируем текстуру
  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 = 4;
  v3.u = 4;
  v3.v = 0;

  // нарисуем грань в видеобуфер
  drawBilinearAffineTexturedFace(&f);

  // скопируем видеобуфер на экран
  showVideoBuffer();

  // подождем нажатия клавиши
  getch();

  // вернемся в текстовый видеорежим
  doneVideo();
}
