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

в 1:34, , рубрики: C#, MESH, unity3d, unity3d уроки, разработка игр

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

Сегодня мы будем заниматься деформацией, основанной, на коллизии. Ну и, разумеется, всем, кто заинтересован, добро пожаловать под кат.
Unity3D. Балуемся с мешем. Часть 3 — Деформация меша, основанная на коллизиях - 1
Кадр из старого советского мультика "Брэк"

Содержание

  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 — за вектор под которым столкновение происходит.

И ещё один немаловажный момент: чтобы коллизия была зафиксирована, как минимум одно тело должно иметь компонент 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:
Unity3D. Балуемся с мешем. Часть 3 — Деформация меша, основанная на коллизиях - 2

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

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

Автор: KitScribe

Источник

Поделиться

* - обязательные к заполнению поля