Table of Contents

Shapes

Shapes define how the rigid body collides with other objects. Shapes implement the ISupportMappable interface and are always convex. They can be passed to static methods defined in the NarrowPhase class for collision detection. Shapes also implement the IDynamicTreeProxy interface and can be added to the DynamicTree class. When a shape is added to a rigid body this is done automatically (world.DynamicTree).

Note

Concave Shapes A concave shape can be represented by combining multiple convex shapes on a single rigid body. Third-party libraries for 'convex decomposition' can be used to generate convex hulls from arbitrary meshes.

Changing an attached shape

Changing a shape's dimensions or local transformation after it has been added to a body does not automatically refresh the dynamic tree, the body's mass properties, or existing contact points. Make these changes between calls to world.Step, then update the affected state explicitly:

box.Size = new JVector(2, 1, 1);
world.DynamicTree.Update(box); // Refresh this shape's bounding box and broadphase pairs.
body.ClearContactCache();      // Discard contact points based on the old geometry.
body.SetMassInertia();          // Only if the body uses mass properties derived from its shapes.

world.DynamicTree.Update(shape) updates one registered shape immediately. A regular world step updates active proxies, but does not track changes to inactive proxies. If the change affects a sleeping dynamic body, activate that body explicitly with body.SetActivationState(true). Updating a static shape's proxy does not wake sleeping bodies that newly overlap it; activate the affected dynamic bodies yourself.

A shape change can also leave bodies interpenetrating. The engine cannot decide how they should be separated, so the application must choose whether to move them, wake them, or allow the solver to resolve the new contacts. If mass or inertia was set explicitly, retain or recalculate it according to the application's mass policy instead of calling the parameterless SetMassInertia().

Default types

The inheritance hierarchy for the default shapes:

Shape
├── RigidBodyShape
│   ├── BoxShape
│   ├── CapsuleShape
│   ├── ConeShape
│   ├── ConvexHullShape
│   ├── CylinderShape
│   ├── PointCloudShape
│   ├── TransformedShape
│   └── TriangleShape
└── SoftBodyShape
    ├── SoftBodyTetrahedron
    └── SoftBodyTriangle

Most shapes are self-explanatory (additional details are in the API documentation), while some specifics are outlined below.

ConvexHullShape

The constructor of the ConvexHullShape takes a list of triangles.

public ConvexHullShape(List<JTriangle> triangles)

The triangles provided must form a convex hull. Apart from rejecting zero-mass hulls, the validity of the convex shape is not checked. Invalid shapes can lead to glitched collisions and/or non-terminating algorithms during collision detection. The triangles are used to construct an internal acceleration structure that speeds up collision detection for this shape through hill-climbing.

The convexHullShape.Clone() method can be used to clone the shape: The internal data structure is then used for both shapes.

PointCloudShape

The PointCloudShape is very similar to the ConvexHullShape. The constructor takes a list of vertices.

public PointCloudShape(List<JVector> vertices)

The vertices do not need to form a convex hull; however, collision detection will 'shrink-wrap' these vertices, so the final collision shape is convex. For example, passing the 8 vertices of a cube to the constructor generates a cube shape; adding a 9th vertex at the cube's center has no effect.

Warning

Number of vertices PointCloudShapes should only be used for a small to moderate number of vertices (\(\approx{}300\)). Larger numbers of vertices can negatively impact performance. ConvexHullShapes are the better choice for more complex hulls.

TransformedShape

The TransformedShape takes another shape as input and transforms it.

public TransformedShape(RigidBodyShape shape, in JVector translation, in JMatrix transform)

Any affine transformation is possible. The wrapped shape might be translated, rotated, scaled and sheared. For example, a sphere shape could be transformed into an ellipsoid.

TriangleShape

The TriangleShape has no volume. It is mostly used for static geometry, although it can be added to non-static bodies. The TriangleShape is constructed with a TriangleMesh and an index.

public TriangleShape(TriangleMesh mesh, int index)

A TriangleMesh.Triangle stores information about neighbour triangles. This information is used in the TriangleEdgeCollisionFilter (enabled by default) to resolve collision artifacts that occur when shapes slide over the edges between connected triangles. These edges are often referred to as 'internal edges' and can cause major problems when adding level geometry to a game.

SoftBodyShape

The vertices of the SoftBodyShape are represented by rigid bodies. The shapes (triangle and tetrahedron) are dynamically defined by the position of the vertices. A SoftBodyShape is not added to a body.

Debug drawing

Shapes can be drawn individually:

var shape = new SphereShape(radius: 1);
shape.DebugDraw(drawer);

Attached shapes follow their body's position and rotation. The sphere above appears at the world origin until it is attached to a body. See debugging shapes on a body for setting up the drawer and adjusting color and smoothness.

Your drawer's DrawTriangle implementation chooses filled triangles or wireframes. See IDebugDrawer for the drawing methods.

Warning

Debug drawing can be slow. Point clouds and custom shapes may appear with gaps or overlapping faces. Higher detail settings increase the cost and do not guarantee a more accurate drawing.

Custom shapes

Custom shapes can easily be implemented. A shape is defined by its support function—which can be looked up or derived.

The following example demonstrates implementing a half-sphere (symmetry axis aligned with the y-axis) with a radius of one:

public class HalfSphereShape : RigidBodyShape
{
    public override void SupportMap(in JVector direction, out JVector result)
    {
        const float centerOfMassOffset = 3.0f / 8.0f;

        if (direction.Y >= 0.0f)
        {
            result = JVector.Normalize(direction);
        }
        else
        {
            JVector pDir = new JVector(direction.X, 0.0f, direction.Z);
            float pDirSq = pDir.LengthSquared();

            if (pDirSq < 1e-12f) result = JVector.Zero;
            else result = (1.0f / MathF.Sqrt(pDirSq)) * pDir;
        }

        // shift, such that (0, 0, 0) is the center of mass
        result.Y -= centerOfMassOffset;
    }

    public override void GetCenter(out JVector point)
    {
        point = JVector.Zero;
    }
}

Mass properties and bounding boxes are automatically calculated from the support map using methods in the ShapeHelper class. Performance can be optimized by providing overrides directly in the shape class:

public override void CalculateBoundingBox(in JQuaternion orientation, in JVector position, out JBoundingBox box)

public override void CalculateMassInertia(out JSymmetricMatrix inertia, out JVector com, out float mass)

public override bool LocalRayCast(in JVector origin, in JVector direction, out JVector normal, out float lambda)

The ShapeHelper class can also be used to generate a triangle mesh representation of the shape (or any class implementing ISupportMappable):

public static List<JTriangle> Tessellate(ISupportMappable support, int subdivisions = 3)