Многие ждали продолжение серии, и наверняка соскучились по новой статье. Поэтому я думаю, что сейчас самое время продолжить наши игры с мешем в Unity3D, а также расширить свой багаж знаний и навыков.
Сегодня мы будем заниматься деформацией, основанной, на коллизии. Ну и, разумеется, всем, кто заинтересован, добро пожаловать под кат.

Кадр из старого советского мультика "Брэк"
Содержание
- Unity3D. Балуемся с мешем. Часть 1 — Генерация меша с помощью карты высот
- Unity3D. Балуемся с мешем. Часть 2 — Деформация меша с помощью карты высот
- Unity3D. Балуемся с мешем. Часть 3 — Деформация меша, основанная на коллизиях
Предупреждение: картинок будет мало.
Закрываем хвосты
В предыдущих статьях я был технически неточным в одном месте, что заметили в комментариях к предыдущей части и за что меня поругал один товарищ. Почему я это делал? Для упрощения понимания что и как работает и устроено. Но теперь, когда у нас есть понимание, я надеюсь, мы можем избавиться от невежества и стать технически грамотными:
- Нормаль (normal) не перпендикулярна вершине (vertex).
В принципе, перпендикуляр не может быть задан к точке, и, я надеюсь, это всем понятно. Более точная и технически грамотная формулировка — из вершин складывается полигон, нормали вершин которого перпендикулярны поверхности этого полигона.
Теория коллизий
Для деформации меша на основе коллизий нам потребуется понять что такое коллизия. И на самом деле в этом нет ничего сложного. Коллизия — это столкновение тел, которые имеют возможность столкнуться. То есть имеют коллайдеры, как минимум.
Для обработки коллизий в Unity есть 3 метода c обязательным аргументом Collision: OnCollisionEnter(Collision), OnCollisionStay(Collision), OnCollisionExit(Collision)
Мы будем задействовать один из них. Как думаете какой?
Почти! 🙂 Этот метод срабатывает при первом столкновении. Но есть вероятность, что коллайдеры не прекратят контакт, а позиция объектов будет меняться.
В точку! Это то, что нам надо. Этот метод срабатывает при каждом столкновении коллайдеров.
Не совсем то, что нам надо. Данный метод сработает после того, как контакт коллайдеров будет прекращён.
С методом разобрались, давайте поймём чем полезна коллизия и какими атрибутами она владеет.
В подробности вдаваться не будем, и опишем полезные для нашей реализации вещи. Коллизия имеет массив точек соприкосновения — ContacPoint, которые в свою очередь обладают весьма полезными атрибутами: point и normal (да-да, и тут есть нормаль). point — отвечает за координаты столкновения, а normal — за вектор под которым столкновение происходит.
Теория реализации
С коллизией вроде всё понятно. Осталось подумать как сделать деформацию.
Всё, что нам нужно это узнать точки столкновения и сместить вершины меша в этих точках по вектору нормали коллизии, помноженную на какую-нибудь константу. Нам необходимо будет всего 2 параметра — радиус для поиска вершин и константа для смещения вершины по вектору нормали коллизии. Радиус нам нужен будет для того, чтобы найти нужные вершины, ибо на мой взгляд наилучший способ сделать это — проверка на дистанцию.
Какие могут быть сложности? Меш всегда находится локально, то есть в нулевых координатах. Но данная сложность решается встроенными методами Unity.
Приступаем к написанию решения
Создадим класс DeformableGO и добавим в нужные атрибуты, а также несколько нужных компонентов.
using UnityEngine; public class DeformableGO : MonoBehaviour { public float maxDeformDelta = 1f; // константа смещения public float radius = 0.5f; // радиус поиска MeshFilter mf; Vector3[] vertices; Transform trans; }
Нам важно сохранить оригинальный меш, так что до старта сцены получим все необходимые ссылки, а также скопируем наш меш
using UnityEngine; public class DeformableGO : MonoBehaviour { public float maxDeformDelta = 1f; // константа смещения public float radius = 0.5f; // радиус поиска MeshFilter mf; Vector3[] vertices; Transform trans; void Awake() { trans = GetComponent<Transform>(); mf = gameObject.GetComponent<MeshFilter>(); Mesh mesh = CopyMesh(mf.sharedMesh); mf.sharedMesh = mesh; vertices = mesh.vertices; } Mesh CopyMesh(Mesh oldmesh) { Mesh newmesh = new Mesh(); newmesh.vertices = oldmesh.vertices; newmesh.triangles = oldmesh.triangles; newmesh.uv = oldmesh.uv; newmesh.normals = oldmesh.normals; newmesh.tangents = oldmesh.tangents; newmesh.colors = oldmesh.colors; newmesh.bounds = oldmesh.bounds; newmesh.MarkDynamic(); // новый метод return newmesh; } }
Вы могли увидеть комментарий "новый метод" напротив метода, который мы пока ещё не использовали. Unity Techologies очень советовали его использовать на одной из конференций, если мы работаем с динамически изменяемым мешем, потому что данный метод оптимизирует меш под частые изменения. Документацию можно найти тут.
Давайте добавим обработку OnCollisionStay(Collision) и создадим заглушку для метода, деформирующего меш
// Как мы помним, нам нужно найти нужные вершины, // основываясь на радиусе, а затем сместить нашу вершину // по направлению нормали. Для направления мы будем передавать // аргумент `Vector3 dir`, для точки соприкосновения - `Vector3 point` void PressMesh(Vector3 point, Vector3 dir) { } void OnCollisionStay(Collision collision) { for (int i = 0; i < collision.contacts.Length; i++) { PressMesh(collision.contacts[i].point, collision.contacts[i].normal); } mf.sharedMesh.vertices = vertices; mf.sharedMesh.RecalculateNormals(); mf.sharedMesh.RecalculateBounds(); }
Давайте реализуем логику метода PressMesh
void PressMesh(Vector3 point, Vector3 dir) { // преобразовываем мировые координаты в локальные // и сохраняем их в отдельную переменную var localPos = trans.InverseTransformPoint(point); for (int i = 0; i < vertices.Length; i++) { float distance = (localPos - vertices[i]).magnitude; if (distance <= radius) { vertices[i] += dir * maxDeformDelta; } } }
Полный скрипт должен выглядеть следующим образом
using UnityEngine; public class DeformableGO : MonoBehaviour { public float maxDeformDelta = 1f; public float radius = 0.5f; MeshFilter mf; Vector3[] vertices; Transform trans; void Awake() { trans = GetComponent<Transform>(); mf = gameObject.GetComponent<MeshFilter>(); Mesh mesh = CopyMesh(mf.sharedMesh); mf.sharedMesh = mesh; vertices = mesh.vertices; } void PressMesh(Vector3 point, Vector3 dir) { var localPos = trans.InverseTransformPoint(point); for (int i = 0; i < vertices.Length; i++) { float distance = (localPos - vertices[i]).magnitude; if (distance <= radius) { vertices[i] += dir * maxDeformDelta; } } } void OnCollisionStay(Collision collision) { for (int i = 0; i < collision.contacts.Length; i++) { PressMesh(collision.contacts[i].point, collision.contacts[i].normal); } mf.sharedMesh.vertices = vertices; mf.sharedMesh.RecalculateNormals(); mf.sharedMesh.RecalculateBounds(); } Mesh CopyMesh(Mesh oldmesh) { Mesh newmesh = new Mesh(); newmesh.vertices = oldmesh.vertices; newmesh.triangles = oldmesh.triangles; newmesh.uv = oldmesh.uv; newmesh.normals = oldmesh.normals; newmesh.tangents = oldmesh.tangents; newmesh.colors = oldmesh.colors; newmesh.bounds = oldmesh.bounds; newmesh.MarkDynamic(); return newmesh; } }
Тестируем
Я повесил скрипт на сферу из прошлой части, разместив её в координатах (0, 7, -10), и скинул на неё несколько обычных сфер с Rigidbody:

ссылка на оригинал статьи https://habrahabr.ru/post/329146/
Добавить комментарий