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

  • Tutorial

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


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



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


Содержание


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

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


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


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


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


OnCollisionEnter(Collision), OnCollisionStay(Collision), OnCollisionExit(Collision)


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


OnCollisionEnter

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


OnCollisionStay

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


OnCollisionExit

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


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


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


!!! Чтобы коллизия была зафиксирована, как минимум одно тело должно иметь компонент Rigidbody!!!


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


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


Всё, что нам нужно это узнать точки столкновения и сместить вершины меша в этих точках по вектору нормали коллизии, помноженную на какую-нибудь константу. Нам необходимо будет всего 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, обладающими гравитацией:



Плюсы и минусы


  • Не обязателен mesh collider
  • Тратится не так много ресурсов на вычисления
  • При желании можно снизить траты ресурсов на деформацию
  • Для детекта коллизий обязателен Rigidbody
  • +26
  • 14.3k
  • 9
Share post

Similar posts

Comments 9

    0

    Mesh.MarkDynamic() существует уже лет 5, начиная с версии 4.0.

      0

      Мало того скажу, он существует ровно с версии 4.0.0


      Но к чему этот комментарий?

      0
      Уважаемый, а в дальнейших уроках ничего похожего на https://www.youtube.com/watch?v=pN-NDikbseY не будет?
      Динамическая деформация меша, подобие упругости и т.д.?
        0

        А надо?


        Честно говоря, я уже начал уставать немного. Выхлоп не такой большой от статей, как хотелось бы.

          0
          Я был бы рад.
          А какого рода выхлоп ожидается?
            0

            Хотя бы чтобы не минусовали в карму) На 4 плюса у меня выходит 2,5 минуса.


            Кто это делает и за что мне не очень понятно, но смею предположить, что какие-нибудь разработчики ассетов, у которых я, можно сказать, хлеб ворую)


            Видимо, Unity Community не очень расположено к Open Source. Хоть вроде в переводе и "Общество Единства"

        0

        Ждем клона spin tires, спасибо за статью

          0

          Пожалуйста! Но я не собираюсь делать туториалы по проектам.


          Стоит по-вашему?

            0
            почему нет?

        Only users with full accounts can post comments. Log in, please.