§ О чем речь

Это продолжение той же самой темы, что была раннее описана мной, но я теперь немного расширю сферу применения. В прошлый раз я вывел формулы, которые пересекают линию с плоскостью, но так, что линия исходит из точки (0,0,0), а теперь я поправлю формулы так, чтобы можно было рассчитать любую исходящую линию для расчета пересечений.

§ Вывод формулы

Задаем снова параметрические уравнения для плоскости и прямой:
  • Плоскость M = \vec{OA} + u\vec{AB} + v\vec{AC}
  • Прямая M = \vec{OP} + t\vec{PN}
Здесь нет ничего необычного. Вектор AB и вектор AC рассчитываются путем вычитания точек B-A и C-A по всем трем компонентам координат.
\vec{PN} = N - P
\vec{AP} = \vec{OP} - \vec{OA}
Требуется найти u,v,t. Здесь (u,v) будет найденной текстурной позицией, а t - расстоянием от точки P до точки M. Прямая задается вектором PN и позицией P. То есть при t=0, M будет равно P.
Теперь, для того, чтобы параметры, необходимо приравнять оба уравнения между собой, отчего получится:
u\vec{AB} + v\vec{AC} - t\vec{PN} = \vec{AP}
Здесь вектор AP это разность между точкой P (начала прямой) и точкой A (треугольник). Если разложить покомпонентно, то получим вот такую систему линейных алгебраических уравнений (СЛАУ):
uAB_x + vAC_x - tPN_x = AP_x
uAB_y + vAC_y - tPN_y = AP_y
uAB_z + vAC_z - tPN_z = AP_z

§ Нахождение решений

Чтобы найти u,v,t необходимо применить формулу Крамера, которую уже неоднократно применяли. Для начала, надо вычислить детерминант:
\Delta = \begin{vmatrix}AB_x & AC_x & -PN_x \\\ AB_y & AC_y & -PN_y \\\ AB_z & AC_z & -PN_z \end{vmatrix}
Он будет равен:
\Delta = PN_x(AB_zAC_y - AB_yAC_z) + PN_y(AB_xAC_z - AB_zAC_x) + PN_z(AB_yAC_x - AB_xAC_y)
Вообще, каждый раз коэффициенты для треугольника для этого детерминанта не надо высчитывать, они могут быть рассчитаны только один раз на всю сцену. Меняется только лишь PN. И потому их достаточно высчитать лишь единожды. Пересчитывание этих коэффициентов может быть только тогда, когда как-то меняется положение треугольника в пространстве.
Теперь ищется u:
u = \frac{1}{\Delta} \begin{vmatrix}AP_x & AC_x & -PN_x \\\ AP_y & AC_y & -PN_y \\\ AP_z & AC_z & -PN_z \end{vmatrix} , v = \frac{1}{\Delta} \begin{vmatrix}AB_x & AP_x & -PN_x \\\ AB_y & AP_y & -PN_y \\\ AB_z & AP_z & -PN_z \end{vmatrix} , t = \frac{1}{\Delta} \begin{vmatrix}AB_x & AC_x & AP_x \\\ AB_y & AC_y & AP_y \\\ AB_z & AC_z & AP_z \end{vmatrix}
Получается
u \Delta = PN_x(AC_yAP_z-AP_yAC_z) + PN_y(AP_xAC_z-AC_xAP_z) + PN_z(AC_xAP_y-AP_xAC_y)
v \Delta = PN_x(AP_yAB_z-AB_yAP_z) + PN_y(AB_xAP_z-AP_xAB_z) + PN_z(AP_xAB_y-AB_xAP_y)
t \Delta = AP_x(AB_yAC_z-AC_yAB_z) + AP_y(AC_xAB_z-AB_xAC_z) + AP_z(AB_xAC_y-AC_xAB_y)
В данном случае, для t менять коэффициенты требуется только при перемещении точки вида относительно треугольника. То есть, фактически, один раз на фрейм.
Стоит обратить внимание, что вектор \vec{AP} это разница между точкой исходящего луча и точкой A в треугольнике.
Внимательно отмечу что коэффициенты для t являются вектором, перпендикулярным плоскости, то есть, из этого вектора можно найти вектор нормали:
N = (AB_yAC_z-AC_yAB_z, AC_xAB_z-AB_xAC_z, AB_xAC_y-AC_xAB_y)

§ Код

Ниже представлена функция для расчета параметров треугольника:
1struct vec2   { float x, y; };
2struct vec3   { float x, y, z; };
3struct vec3uv { float x, y, z, u, v; };
4
5struct trig {
6
7    vec3uv  a, b, c;
8    vec3    D, U, V, T;
9    vec3    tu, tv;
10    vec3    AP;
11};
12
13void calc_triangle(trig& T, vec3 P) {
14
15    vec3 AB = {T.b.x - T.a.x, T.b.y - T.a.y, T.b.z - T.a.z};
16    vec3 AC = {T.c.x - T.a.x, T.c.y - T.a.y, T.c.z - T.a.z};
17    vec3 AP = {P.x   - T.a.x, P.y   - T.a.y, P.z   - T.a.z};
18
19    T.D = {
20        AB.z*AC.y - AB.y*AC.z,
21        AB.x*AC.z - AB.z*AC.x,
22        AB.y*AC.x - AB.x*AC.y
23    };
24
25    T.U = {
26        AC.y*AP.z - AP.y*AC.z,
27        AP.x*AC.z - AC.x*AP.z,
28        AC.x*AP.y - AP.x*AC.y
29    };
30
31    T.V = {
32        AP.y*AB.z - AB.y*AP.z,
33        AB.x*AP.z - AP.x*AB.z,
34        AP.x*AB.y - AB.x*AP.y
35    };
36
37    T.T = {
38        AB.y*AC.z - AC.y*AB.z,
39        AC.x*AB.z - AB.x*AC.z,
40        AB.x*AC.y - AC.x*AB.y
41    };
42
43    // Текстуры
44    T.tu = {T.a.u, T.b.u - T.a.u, T.c.u - T.a.u};
45    T.tv = {T.a.v, T.b.v - T.a.v, T.c.v - T.a.v};
46    T.AP = AP;
47}
Так можно задать треугольник
1trig T;
2vec3 org = {0, 0, 0};
3
4T.a = {-1,  1, 0,   0,   0};
5T.b = { 1,  1, 0, 255,   0};
6T.c = { 1, -1, 0, 255, 255};
7
8calc_triangle(T, org);
Здесь org - это точка, откуда смотрит камера.
Таким образом вычисляются точки текстуры и растеризуется
1for (int y = -100; y < 100; y++)
2for (int x = -160; x < 160; x++) {
3
4    int cl = 0;
5
6    // Вектор вида
7    vec3  vec = { (float)x / 100, (float)y / 100, 1.0};
8
9    // Поворот вектора
10    float dx = vec.x;
11    float dy = vec.y;
12    float dz = vec.z;
13
14    // Тест пересечения u, v, t
15    float u = T.U.x*dx + T.U.y*dy + T.U.z*dz;
16    float v = T.V.x*dx + T.V.y*dy + T.V.z*dz;
17    float d = T.D.x*dx + T.D.y*dy + T.D.z*dz;
18    float t = T.T.x*T.AP.x + T.T.y*T.AP.y + T.T.z*T.AP.z;
19
20    if (u + v < d && u >= 0 && v >= 0 && t < 0) {
21
22        u /= d;
23        v /= d;
24
25        // Рассчитать позицию текстуры
26        float tx = T.tu.x + T.tu.y*u + T.tu.z*v;
27        float ty = T.tv.x + T.tv.y*u + T.tv.z*v;
28
29        cl = 256*((int)tx^(int)ty);
30    }
31
32    pset(160 + x, 100 - y, cl);
33}