Unity3D. Балуемся с мешем. Часть 3 — Деформация меша, основанная на коллизиях

от автора

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

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

Кадр из старого советского мультика "Брэк"

Содержание

  1. Unity3D. Балуемся с мешем. Часть 1 — Генерация меша с помощью карты высот
  2. Unity3D. Балуемся с мешем. Часть 2 — Деформация меша с помощью карты высот
  3. Unity3D. Балуемся с мешем. Часть 3 — Деформация меша, основанная на коллизиях

Предупреждение: картинок будет мало.

Закрываем хвосты

В предыдущих статьях я был технически неточным в одном месте, что заметили в комментариях к предыдущей части и за что меня поругал один товарищ. Почему я это делал? Для упрощения понимания что и как работает и устроено. Но теперь, когда у нас есть понимание, я надеюсь, мы можем избавиться от невежества и стать технически грамотными:

  • Нормаль (normal) не перпендикулярна вершине (vertex).

В принципе, перпендикуляр не может быть задан к точке, и, я надеюсь, это всем понятно. Более точная и технически грамотная формулировка — из вершин складывается полигон, нормали вершин которого перпендикулярны поверхности этого полигона.

Теория коллизий

Для деформации меша на основе коллизий нам потребуется понять что такое коллизия. И на самом деле в этом нет ничего сложного. Коллизия — это столкновение тел, которые имеют возможность столкнуться. То есть имеют коллайдеры, как минимум.

Для обработки коллизий в Unity есть 3 метода c обязательным аргументом Collision: OnCollisionEnter(Collision), OnCollisionStay(Collision), OnCollisionExit(Collision)

Мы будем задействовать один из них. Как думаете какой?

OnCollisionEnter

Почти! 🙂 Этот метод срабатывает при первом столкновении. Но есть вероятность, что коллайдеры не прекратят контакт, а позиция объектов будет меняться.

OnCollisionStay

В точку! Это то, что нам надо. Этот метод срабатывает при каждом столкновении коллайдеров.

OnCollisionExit

Не совсем то, что нам надо. Данный метод сработает после того, как контакт коллайдеров будет прекращён.

С методом разобрались, давайте поймём чем полезна коллизия и какими атрибутами она владеет.

В подробности вдаваться не будем, и опишем полезные для нашей реализации вещи. Коллизия имеет массив точек соприкосновения — ContacPoint, которые в свою очередь обладают весьма полезными атрибутами: point и normal (да-да, и тут есть нормаль). point — отвечает за координаты столкновения, а normal — за вектор под которым столкновение происходит.

Теория реализации

С коллизией вроде всё понятно. Осталось подумать как сделать деформацию.

Всё, что нам нужно это узнать точки столкновения и сместить вершины меша в этих точках по вектору нормали коллизии, помноженную на какую-нибудь константу. Нам необходимо будет всего 2 параметра — радиус для поиска вершин и константа для смещения вершины по вектору нормали коллизии. Радиус нам нужен будет для того, чтобы найти нужные вершины, ибо на мой взгляд наилучший способ сделать это — проверка на дистанцию.

Какие могут быть сложности? Меш всегда находится локально, то есть в нулевых координатах. Но данная сложность решается встроенными методами Unity.

Приступаем к написанию решения

Создадим класс DeformableGO и добавим в нужные атрибуты, а также несколько нужных компонентов.

DeformableGO

using UnityEngine;  public class DeformableGO : MonoBehaviour {     public float maxDeformDelta = 1f; // константа смещения     public float radius = 0.5f; // радиус поиска     MeshFilter mf;     Vector3[] vertices;     Transform trans; }

Нам важно сохранить оригинальный меш, так что до старта сцены получим все необходимые ссылки, а также скопируем наш меш

DeformableGO

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) и создадим заглушку для метода, деформирующего меш

OnCollisionStay + PressMesh()

 // Как мы помним, нам нужно найти нужные вершины, // основываясь на радиусе, а затем сместить нашу вершину // по направлению нормали. Для направления мы будем передавать // аргумент `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

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;             }         }     }

Полный скрипт должен выглядеть следующим образом

DeformableGO.cs

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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *