reckfellENGINE

SoftBody3D: cloth, pins and deformation

A complete 9×9 cloth example: physics mesh, pinned edge, impulse and released vertices.

On this page

Run the cloth

Add SoftBodyLab3D.cs to a C# project. Attach it to an Entity3D root in a separate level, build and press F6. Keep the root unscaled/unrotated; use 60 ticks/s and downward gravity 9.8. Code creates mesh, material, floor and camera. Choose a real 3D backend rather than Dummy.

The rectangle starts horizontal at height 3. One edge is pinned and other vertices sag under gravity. Space pushes along Z; R releases the edge. Restart the level to restore its initial state. PinsReady means initial configuration completed, not that points remain pinned after R.

Deformed cloth with a pinned edge

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

Mesh and indices

SoftBody3D uses a triangle mesh, not child CollisionShape3D entities. This example has one ArrayMesh surface: 81 unique vertices, 128 triangles and edge indices 0–8 pinned in _PhysicsProcess after the body enters its world. PointCount checks first-surface vertex count; it does not by itself prove an arbitrary body is ready to simulate. GetPointTransform returns a physics point's world position.

An imported model's vertex order can change after optimization, reimport, surface merging or LOD changes. Do not reuse indices 0–8 on arbitrary meshes. Check the intended surface, triangle density, seam duplicates and actual attachment points first. Detailed meshes cost more; start with simplified uniform geometry.

Current SoftBody creates its own mutable copy of the first surface and updates rendering data from simulation. Do not assume a shared imported resource simultaneously serves as independent cloth and an immutable model for every user. Once simulation starts, vertices update in world space; repeatedly moving an ordinary transform is not a substitute for controlling the cloth.

Attach a cloak to a character

SetPointPinned associates a point index with pinning; attachmentPath can identify an attachment entity in the hierarchy. For a cloak, create stable shoulder attachment points, reconcile coordinate spaces and test character translation/rotation. Pinning every vertex nearly removes deformation; leaving every point free does not automatically bind it to a skeleton.

Separately configure Layer/Mask, the character's collision shape and ParentCollisionIgnore if needed. These settings are not interchangeable. Test shoulder intersections, quick turns, teleporting, pausing and character deletion. This example pins an edge in world space; it is not a ready-made imported-character cloak with complex attachments.

Parameters and limits

ParameterPurpose
TotalMassTotal mass; zero is not a pinning mechanism
LinearStiffnessResistance to deformation; values do not imply equal stiffness for every mesh/backend
SimulationPrecisionSoft-body simulation accuracy; measure CPU load when increasing it
PressureCoefficientPressure; this open sheet uses 0, while volumetric shells need suitable closed topology
DampingCoefficient / DragCoefficientResistance; high values can hide motion but do not repair topology
CollisionLayer / CollisionMaskInteraction filtering, separate from pinning

ReckfellPhysics3D and Jolt Physics were checked: points initialize, the edge pins, free points move, an impulse deforms the cloth and the released edge falls. The image uses ReckfellPhysics3D. This does not prove every complex imported-mesh collision, Area field, self-collision or device export. Use backend checks and measure your scene.

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.