reckfellENGINE

SoftBody3D: ткань, закрепления и деформация

Полный пример ткани 9×9: физическая сетка, закреплённый край, импульс и освобождение вершин.

На этой странице

Запустить ткань

Добавьте SoftBodyLab3D.cs в C#-проект. Прикрепите к корню Entity3D отдельного уровня, соберите и запустите F6. Оставьте корень без масштаба/поворота; физика — 60 шагов/с, гравитация 9.8 вниз. Код создаёт сетку, материал, пол и камеру. Выберите реальный backend 3D, не Dummy.

Изначально прямоугольник лежит горизонтально на высоте 3. Один край закреплён, остальные вершины провисают под гравитацией. Space толкает ткань по Z, R освобождает край. Для первоначального состояния перезапустите уровень. PinsReady означает завершённую начальную настройку, а не обещание, что после R точки всё ещё закреплены.

Деформированная ткань с закреплённым краем

SoftBodyLab3D.cs
C#
using Reckfell;

public partial class SoftBodyLab3D : Entity3D
{
    public const int Columns = 9;
    public const int Rows = 9;
    public SoftBody3D Cloth { get; private set; } = null!;
    public bool PinsReady { get; private set; }
    public override void _Ready()
    {
        var vertices = new Vector3[Columns * Rows];
        var normals = new Vector3[vertices.Length];
        for (int row = 0; row < Rows; row++)
            for (int col = 0; col < Columns; col++)
            {
                int i = row * Columns + col;
                vertices[i] = new Vector3(-1 + 2f * col / (Columns - 1), 3, 2f * row / (Rows - 1));
                normals[i] = Vector3.Up;
            }
        var indices = new int[(Columns - 1) * (Rows - 1) * 6]; int index = 0;
        for (int row = 0; row < Rows - 1; row++)
            for (int col = 0; col < Columns - 1; col++)
            {
                int a = row * Columns + col, b = a + 1, c = a + Columns, d = c + 1;
                foreach (int point in new[] { a, c, b, b, c, d }) indices[index++] = point;
            }
        var arrays = new Reckfell.Collections.Array(); arrays.Resize((int)Mesh.ArrayType.Max);
        arrays[(int)Mesh.ArrayType.Vertex] = vertices; arrays[(int)Mesh.ArrayType.Normal] = normals; arrays[(int)Mesh.ArrayType.Index] = indices;
        var mesh = new ArrayMesh(); mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, arrays, flags: Mesh.ArrayFormat.FlagUseDynamicUpdate);
        Cloth = new SoftBody3D { Name = "Cloth", Mesh = mesh, TotalMass = 1, LinearStiffness = .5f,
            SimulationPrecision = 5, PressureCoefficient = 0, CollisionLayer = 4, CollisionMask = 1,
            MaterialOverride = new StandardMaterial3D { AlbedoColor = new Color(.25f,.45f,1), ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded, CullMode = BaseMaterial3D.CullModeEnum.Disabled } };
        AddChild(Cloth);
        var floor = new StaticBody3D { Position = new Vector3(0,-.2f,0), CollisionLayer = 1, CollisionMask = 0 };
        floor.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = new Vector3(8,.4f,6) } });
        floor.AddChild(new MeshInstance3D { Mesh = new BoxMesh { Size = new Vector3(8,.4f,6) }, MaterialOverride =
            new StandardMaterial3D { AlbedoColor = new Color(.55f,.55f,.6f), ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded } }); AddChild(floor);
        var camera = new Camera3D { Position = new Vector3(4,3,6), Current = true }; AddChild(camera); camera.LookAt(ToGlobal(new Vector3(0,1.5f,0)));
    }
    public override void _PhysicsProcess(double delta)
    {
        if (PinsReady || Cloth.PointCount != Columns * Rows) return;
        for (int col = 0; col < Columns; col++) Cloth.SetPointPinned(col, true, new EntityPath(""), -1);
        PinsReady = true;
    }
    public void ReleaseTop()
    {
        if (!PinsReady) return;
        for (int col = 0; col < Columns; col++) Cloth.SetPointPinned(col, false, new EntityPath(""), -1);
    }
    public override void _UnhandledKeyInput(InputEvent @event)
    {
        if (@event is InputEventKey { Pressed: true, Echo: false } key)
        {
            if (key.PhysicalKeycode == Key.Space && PinsReady) Cloth.ApplyCentralImpulse(new Vector3(0,0,1));
            if (key.PhysicalKeycode == Key.R) ReleaseTop();
        }
    }
}

Сетка и индексы

SoftBody3D использует треугольную сетку, а не набор дочерних CollisionShape3D. В примере один ArrayMesh с одной поверхностью: 81 уникальная вершина, 128 треугольников, первый ряд 0–8 закрепляется в _PhysicsProcess после добавления тела в мир. PointCount проверяет число вершин первой поверхности; сам по себе он не доказывает готовность произвольного тела к симуляции. GetPointTransform возвращает мировую позицию физической точки.

У импортированной модели порядок вершин может измениться после оптимизации, переимпорта, объединения поверхностей или смены LOD. Не копируйте индексы 0–8 на произвольный меш. Сначала проверьте подходящую поверхность, плотность треугольников, дубликаты на швах и реальные точки закрепления. Очень подробная сетка повышает стоимость; начните с упрощённой равномерной геометрии.

В текущей реализации SoftBody делает собственную изменяемую копию первой поверхности и обновляет данные рендера из симуляции. Не рассчитывайте, что общий импортированный ресурс будет одновременно удобной независимой тканью и неизменяемой моделью для всех пользователей. После начала симуляции вершины обновляются в мировой системе; постоянное перемещение обычным transform не заменяет управление физической тканью.

Закрепить плащ к персонажу

SetPointPinned связывает индекс точки с закреплением; attachmentPath может задавать сущность привязки в иерархии. Для плаща создайте устойчивые точки крепления у плеч, убедитесь в правильной системе координат и проверьте движение/поворот персонажа. Закрепить все вершины — значит почти убрать возможность деформации; отсутствие закреплений не создаёт привязку к скелету автоматически.

Отдельно настройте Layer/Mask, форму коллизии персонажа и при необходимости ParentCollisionIgnore. Это не взаимозаменяемые настройки. Проверьте пересечение у плеч, быстрый разворот, телепорт, паузу и удаление персонажа. Пример закрепляет край в мире; он не является готовым плащом с импортированной моделью и сложными креплениями.

Параметры и ограничения

ПараметрНазначение
TotalMassОбщая масса; не задавайте ноль ради закрепления
LinearStiffnessСопротивление изменению формы; значения не гарантируют одну жёсткость для любой сетки/backend
SimulationPrecisionТочность расчёта мягкого тела; при повышении проверяйте нагрузку на CPU
PressureCoefficientДавление; для открытого листа оставлено 0, объёмная оболочка требует подходящей замкнутой топологии
DampingCoefficient / DragCoefficientСопротивление; высокая величина может скрыть движение, но не исправляет топологию
CollisionLayer / CollisionMaskФильтрация взаимодействий, отдельно от закреплений

Проверены ReckfellPhysics3D и Jolt Physics: точки создаются, край закрепляется, свободные точки двигаются, импульс деформирует ткань, после снятия закрепления край падает. Изображение снято с ReckfellPhysics3D. Это не подтверждает все контакты со сложными импортированными мешами, все Area-поля, self-collision или экспорт на каждое устройство. Используйте проверку backend и измеряйте свою сцену.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.