Я стараюсь изо всех сил, чтобы описать это:
У меня есть трехмерная наклонная поверхность, и я хочу знать ее высоту (Z) в любой заданной точке внутри нее. У меня есть 3D-позиции вершин поверхности и 2D-позиция точки. Как узнать, на какой высоте он столкнется, находясь на поверхности?
Я использую GDScript, создавая 3D/2.5D Doom-подобный движок в 2D-движке Godot. Плоские полы и стены работают идеально, но наклоны? Я пробовал все, что мог придумать в течение нескольких месяцев, и ничего не получалось.
Вот что бы вы сделали:
Мы определяем линию, которая является вертикальной и проходит через вашу 2D-точку.
Допустим, наша 2D-точка равна position
. Затем:
Точка на линии position
дополняется 0
компонентом высоты. Я назову это v0
:
var v0 = Vector3(position.x, position.y, 0.0)
И направление линии вертикальное, поэтому ноль на обоих компонентах 2D и 1
на компоненте высоты. Я назову это dir
:
var dir = Vector3(0.0, 0.0, 1.0)
Мы определяем плоскость тремя точками, которые составляют ваш трехмерный треугольник.
Допустим, наши 3D-точки — это v1
, v2
и v3
.
По определению они все на плоскости.
Мы можем найти нормаль плоскости, используя векторное произведение, например:
var normal := (v2 - v1).cross(v3 - v1)
Для этого варианта использования нам все равно, если мы перевернули нормаль.
Ах, но будет удобно убедиться, что он имеет единичную длину:
var normal := (v2 - v1).cross(v3 - v1).normalized()
Находим пересечение линии и плоскости. Это наше решение.
Хотим найти точку на плоскости, я назову ее r
. Это такая точка, что вектор, идущий от одной из точек плоскости к ней, перпендикулярен нормали к плоскости. Это означает, что (r - v1).dot(normal) == 0
.
Обратите внимание, что я использую ==
, который является оператором сравнения на равенство. Я использую это, потому что это уравнение, а не задание.
Точка r
также должна принадлежать прямой. Мы можем использовать параметрическую форму линии, которая выглядит следующим образом: r = v0 + dir * t
. И нам нужно сначала найти, каким должен быть параметр t
, чтобы мы могли вычислить r
.
Поэтому мы заменяем r
здесь:
(r - v1).dot(normal) == 0
Что дает нам это:
((v0 + dir * t) - v1).dot(normal) == 0
Переставить:
(dir * t + v0 - v1).dot(normal) == 0
По распределительному свойству скалярного произведения:
(dir * t).dot(normal) + (v0 - v1).dot(normal) == 0
Поскольку t
является скаляром, мы можем вынести его из скалярного произведения:
t * dir.dot(normal) + (v0 - v1).dot(normal) == 0
Вычтите (v0 - v1).dot(normal)
с обеих сторон:
t * dir.dot(normal) == - (v0 - v1).dot(normal)
Мы можем переместить этот отрицательный знак внутри скалярного произведения:
t * dir.dot(normal) == (v1 - v0).dot(normal)
И разделите обе части на dir.dot(normal)
:
t == (v1 - v0).dot(normal) / dir.dot(normal)
Таким образом, наша точка r
:
var r := v0 + dir * (v1 - v0).dot(normal) / dir.dot(normal)
Затем вы можете прочитать компонент высоты r
, который является вашим ответом.
Кстати, у Годо есть почти такой метод. Но он пересекает луч, а не прямую: intersect_ray
класса Plane
.
@weg Я изменил ответ.
Большое спасибо! Вот как это выглядит у меня, правильно ли это?: i.imgur.com/9Uh27tW.png Вроде как по возрастанию и по убыванию работает, но конечный результат может быть либо правильным, либо рандомно заниженным. Похоже, что это напрямую связано с порядком расположения вершин. (0,100,50 - 0,0,0 - 100,100,100) работает отлично, (0,0,0 - 100,100,100 - 100,0,50) например -1400 ед.
@weg Я попробовал эти значения с помощью (50, 50, 0)
, и у меня это работает. Изменение порядка перевернет нормаль плоскости, но решение должно быть таким же. Однако, увидев ваш код, я заметил кое-что, что я упустил из виду: использование var d = v1.dot(normal)
может дать вам отрицательный результат, но var d = v1.project(normal).length()
всегда будет положительным. поэтому используйте вместо этого var d = v1.dot(normal)
, я исправляю это в ответе. Обновлено: я решил удалить d
из ответа, чтобы избежать путаницы.
Какое-то время это было отклонение примерно на 20-40 единиц, положительное или отрицательное, но в процессе добавления решения (я собирался получить новую высоту в самой нижней точке и вычесть ее из нового роста игрока) он решил работать из ниоткуда отлично! Не знаю почему. В любом случае, я добавил кредит для вас в моем проекте! Большое вам спасибо за вашу помощь. Ваше решение невероятно лаконично и изысканно. Вы должны быть прекрасным кодером! Итоговый код для интересующихся: i.imgur.com/fJCloUR.png
Простите меня, но мне очень трудно это понять. v0 — позиция игрока, v1, v2 и v3 — вершины, все в порядке. "Точка на прямой дополнена 0 по компоненте высоты. Я назову ее v0."; «И направление линии вертикальное, поэтому ноль на обоих компонентах 2D и 1 на компоненте высоты. Я назову это направление». Что это значит? Как определяется директор? Затем идет r, сначала он равен нулю, потом нет... мне просто использовать последний? Я действительно не был готов к такому сложному ответу! Что я понял + ошибка: i.imgur.com/Hf7cUsV.png