Лисья Нора

Оглавление


§ О чем речь

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

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

Задаем снова параметрические уравнения для плоскости и прямой:
Здесь нет ничего необычного. Вектор 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)

§ Код

Ниже представлена функция для расчета параметров треугольника:
struct vec2 { float x, y; };
struct vec3 { float x, y, z; };
struct vec3uv { float x, y, z, u, v; };
 
struct trig
{
vec3uv a, b, c;
vec3 D, U, V, T;
vec3 tu, tv;
vec3 AP;
};
 
void calc_triangle(trig& T, vec3 P)
{
vec3 AB = {T.b.x - T.a.x, T.b.y - T.a.y, T.b.z - T.a.z};
vec3 AC = {T.c.x - T.a.x, T.c.y - T.a.y, T.c.z - T.a.z};
vec3 AP = {P.x - T.a.x, P.y - T.a.y, P.z - T.a.z};
 
T.D = {
AB.z*AC.y - AB.y*AC.z,
AB.x*AC.z - AB.z*AC.x,
AB.y*AC.x - AB.x*AC.y
};
 
T.U = {
AC.y*AP.z - AP.y*AC.z,
AP.x*AC.z - AC.x*AP.z,
AC.x*AP.y - AP.x*AC.y
};
 
T.V = {
AP.y*AB.z - AB.y*AP.z,
AB.x*AP.z - AP.x*AB.z,
AP.x*AB.y - AB.x*AP.y
};
 
T.T = {
AB.y*AC.z - AC.y*AB.z,
AC.x*AB.z - AB.x*AC.z,
AB.x*AC.y - AC.x*AB.y
};
 
// Текстуры
T.tu = {T.a.u, T.b.u - T.a.u, T.c.u - T.a.u};
T.tv = {T.a.v, T.b.v - T.a.v, T.c.v - T.a.v};
T.AP = AP;
}
Так можно задать треугольник
trig T;
vec3 org = {0, 0, 0};
 
T.a = {-1, 1, 0, 0, 0};
T.b = { 1, 1, 0, 255, 0};
T.c = { 1, -1, 0, 255, 255};
 
calc_triangle(T, org);
Здесь org – это точка, откуда смотрит камера.
Таким образом вычисляются точки текстуры и растеризуются:
for (int y = -100; y < 100; y++)
for (int x = -160; x < 160; x++) {
 
int cl = 0;
 
// Вектор вида
vec3 vec = { (float)x / 100, (float)y / 100, 1.0};
 
// Поворот вектора
float dx = vec.x;
float dy = vec.y;
float dz = vec.z;
 
// Тест пересечения u, v, t
float u = T.U.x*dx + T.U.y*dy + T.U.z*dz;
float v = T.V.x*dx + T.V.y*dy + T.V.z*dz;
float d = T.D.x*dx + T.D.y*dy + T.D.z*dz;
float t = T.T.x*T.AP.x + T.T.y*T.AP.y + T.T.z*T.AP.z;
 
if (u + v < d && u >= 0 && v >= 0 && t < 0) {
 
u /= d;
v /= d;
 
// Рассчитать позицию текстуры
float tx = T.tu.x + T.tu.y*u + T.tu.z*v;
float ty = T.tv.x + T.tv.y*u + T.tv.z*v;
 
cl = 256*((int)tx^(int)ty);
}
 
pset(160 + x, 100 - y, cl);
}