Гайд по динамичной деформации мешей в Godot

Результат магии
Результат магии

Если вы хотите, чтобы ваш меш при столкновении или при касании руками игрока вминался, гнулся или, проще говоря, деформировался, — этот гайд поможет это реализовать. Но прежде чем начать познавать магию Godot, прошу обратить внимание на игру Fullitro — разрабатываемую мною игру про гонки в открытом мире. Рекомендую посетить канал в ТГ и поддержать разработку:

Fullitro
Fullitro

Итак, предположим, есть точка столкновения/взаимодействия и импульс. Для удобства создадим функцию:

func deformation_nextgen(deform_pos, impulse): deform_point.global_position = deform_pos

Для того, чтобы взаимодействовать с мешем нам понадобится специальный инструмент MeshDataTool, или сокращенно MDT. С помощью него можно редактировать вершины меша, цвет, положение, нормали и так далее. Создадим MDT:

func deformation_nextgen(deform_pos, impulse): deform_point.global_position = deform_pos var mdt = MeshDataTool.new()

Далее мы будем загружать в него surfaces, деформировать и загружать их в меш.

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

Дальнейший план работы программы:
- очищаем старые данные
- проходимся по каждой surface
- загружаем surface и редактируем вертексы
- отправляем изменения в наш меш-дубликат
- очищаем изначальный меш и загружаем меш из меша-дубликата

В случае, если у вас только одна поверхность, можете пропустить 2 и 4 пункты и сразу записывать изменения в исходный меш

Итак, назовем наш второй меш mdt_result. Перед началом деформации, нужно очистить данные этого меша:

func deformation(deform_pos, impulse): deform_point.global_position = deform_pose var mdt = MeshDataTool.new() mdt_result.mesh.clear_surfaces()

Теперь будем проходить по каждой поверхности, загружать её в наш магический MDT и редактировать его там:

func deformation(deform_pos, impulse): deform_point.global_position = deform_pos var mdt = MeshDataTool.new() mdt_result.mesh.clear_surfaces() for surface in range(main_mesh_body.mesh.get_surface_count()): mdt.create_from_surface(main_mesh_body.mesh, surface)

mdt.create_from_surface загружает поверхность меша в редактор MDT

Теперь проходимся по каждой вершине и вычитаем из неё импульс:

for i in range(mdt.get_vertex_count()): vertex = mdt.get_vertex(i) if vertex.distance_to(deform_point.position): mdt.set_vertex(i, vertex-to_local(impulse)/(impulse_coef*vertex.distance_to(deform_point.position)))

mdt.get_vertex(i) возвращает локальную позицию вершины в пространстве, impulse_coef - переменная, отвечающая на силу деформации, умножение на дистанцию до точки столкновения - смягчение деформации.
Также вы можете изменить цвет вертексов, нормали и прочие параметры (см. док. MDT Годота).

После редактирования необходимо отправить новый меш на исходный (если у вас одна surface) или на наш mdt_result (если у вас несколько surfaces):

for surface in range(main_mesh_body.mesh.get_surface_count()): mdt.create_from_surface(main_mesh_body.mesh, surface) for i in range(mdt.get_vertex_count()): vertex = mdt.get_vertex(i) if vertex.distance_to(deform_point.position) < 1.7: mdt.set_vertex(i, vertex-to_local(impulse)/(impulse_coef *vertex.distance_to(deform_point.position))) mdt.commit_to_surface(mdt_result .mesh)

Мы создали новый меш на основе исходного, "помяли" его, и теперь его нужно загрузить его вместо старого, "немятого":

for surface in range(main_mesh_body.mesh.get_surface_count()): mdt.create_from_surface(main_mesh_body.mesh, surface) for i in range(mdt.get_vertex_count()): vertex = mdt.get_vertex(i) if vertex.distance_to(deform_point.position) < 1.7: mdt.set_vertex(i, vertex-to_local(impulse)/(impulse_coef *vertex.distance_to(deform_point.position))) mdt.commit_to_surface(mdt_result .mesh) main_mesh_body.mesh.clear_surfaces() for surface in range(mdt_result .mesh.get_surface_count()): mdt.create_from_surface(mdt_result .mesh, surface) mdt.commit_to_surface(main_mesh_body.mesh)

Полный код функции выглядит так:

func deformation(deform_pos, impulse): deform_point.global_position = deform_pos var mdt = MeshDataTool.new() mdt_result.mesh.clear_surfaces() for surface in range(main_mesh_body.mesh.get_surface_count()): mdt.create_from_surface(main_mesh_body.mesh, surface) for i in range(mdt.get_vertex_count()): vertex = mdt.get_vertex(i) if vertex.distance_to(deform_point.position) < 1.7: mdt.set_vertex(i, vertex-to_local(impulse)/(impulse_coef*vertex.distance_to(deform_point.position))) mdt.commit_to_surface(mdt_result .mesh) main_mesh_body.mesh.clear_surfaces() for surface in range(mdt_result .mesh.get_surface_count()): mdt.create_from_surface(mdt_result .mesh, surface) mdt.commit_to_surface(main_mesh_body.mesh)

Для вызова функции рекомендую использовать мониторинг столкновений у RigidBody тел (Solver->Contact Monitor = true) и функции _integrate_forces.

Также, если потребуется добавить деформируемому мешу форму столкновений, используйте:

collider.shape = main_mesh_body.mesh.create_convex_shape()

Удачи в воспроизведении этой магии!

Не судите строго, это мой первый пост на DTF.

5
2
Начать дискуссию