simvx.core.physics.shapes

Shape resources for 3D physics.

A Shape is a plain collision-geometry resource (a value), not a scene node. It is held by a :class:~simvx.core.physics.nodes.CollisionShape3D node via a Property. A body never branches on shape kind: it calls shape.build(world) and each subclass dispatches to the backend’s matching world.create_* factory. This keeps body nodes open/closed over new shape kinds.

The concrete classes are dimension-suffixed SphereShape3D / BoxShape3D (the abstract Shape base stays un-suffixed, being dimensionless). The 2D siblings live in shapes2d.py. All of these are exported from simvx.core.

Module Contents

Classes

Shape

Abstract collision-geometry resource that OWNS its backend shape.

SphereShape3D

A sphere collision shape of a given radius.

BoxShape3D

An axis-aligned box collision shape centred at the origin.

CapsuleShape3D

A Y-axis capsule collision shape (a segment swept by a sphere).

CylinderShape3D

A Y-axis cylinder collision shape.

ConvexHullShape3D

A convex-hull collision shape defined by a point cloud (Tier-1).

ConcaveMeshShape3D

A static triangle-mesh collision shape (level geometry, Tier-1 static-only).

Data

API

simvx.core.physics.shapes.__all__

[‘Shape’, ‘SphereShape3D’, ‘BoxShape3D’, ‘CapsuleShape3D’, ‘CylinderShape3D’, ‘ConvexHullShape3D’, ‘…

class simvx.core.physics.shapes.Shape[source]

Bases: abc.ABC

Abstract collision-geometry resource that OWNS its backend shape.

A Shape is the unit of collision geometry and the unit of its lifetime, the same model meshes and materials follow. :meth:build hands back the one backend handle this resource owns in a given world, building it on the first ask; when the resource is collected, every handle it owns is released. A resource shared by a thousand bodies is therefore one backend record, and a collider rebuilt every frame from fresh geometry costs one record at a time rather than one per distinct size for the life of the world.

Bodies call shape.build(world) and never inspect the concrete kind, so adding a new shape requires no body changes: subclasses implement

Meth:

create and dispatch to the matching world.create_* factory.

Geometry is IMMUTABLE, by contract rather than by enforcement. Nothing here reads a radius or a half-extent twice: :meth:build runs once per world and the handle is remembered, so writing to one of those attributes after the fact changes the Python object and nothing else. The world keeps the geometry it was given, a later :meth:build hands back the same handle, and only

Attr:

bounding_radius (which is computed on each read, and drives CPU picking) follows the new number – a collider that picks at one size and collides at another. A resource is shared, so there is no other answer available: a setter would have to re-enter every world the resource is live in and change the collider under every body holding it. Changing a collider means assigning a NEW resource, which is what the node layer does.

Per-node SCALE is not part of a shape. A resource is shared, so it cannot bake in one node’s scale: scale travels with the BODY (create_body(scale=...) and the pose write), and each backend applies it to its own instance of this geometry.

build(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle[source]

Return this resource’s backend shape handle in world.

Built once per world by :meth:create and remembered, so asking twice hands back the same handle and every body built from this resource shares one backend record. The handle is released when this resource is collected; a body already built on it keeps its geometry (see

Meth:

~simvx.core.physics.world.PhysicsWorld.destroy_shape).

The handle’s lifetime is THIS RESOURCE’s lifetime, so a caller holds the resource, not the handle: a node keeps the resource it built from, and a query takes the resource as its argument. SphereShape3D(0.5).build(world) as a throwaway expression hands back a handle whose owner is already gone.

Args: world: The :class:~simvx.core.physics.world.PhysicsWorld whose shape factory builds the opaque handle.

Returns: An opaque ShapeHandle for use with world.create_body.

abstractmethod create(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle[source]

Build a fresh backend shape for this geometry (called once per world).

Subclasses dispatch to the matching world.create_* factory. Callers want :meth:build, which memoises this per world and owns the result; calling create directly mints a handle nothing will ever release.

abstract property bounding_radius: float[source]

Radius of the shape’s origin-centred bounding sphere, in local units.

A conservative, rotation-invariant size used for cheap broad-phase / CPU picking (a sphere test against the shape’s extent). Each subclass returns the radius of the smallest sphere centred on the shape’s local origin that contains it.

__slots__

()

class simvx.core.physics.shapes.SphereShape3D(radius: float = 0.5)[source]

Bases: simvx.core.physics.shapes.Shape

A sphere collision shape of a given radius.

Args: radius: Sphere radius in world units (must be > 0).

Initialization

create(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle[source]
property bounding_radius: float[source]
build(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle
__slots__

()

class simvx.core.physics.shapes.BoxShape3D(half_extents: simvx.core.math.Vec3 | tuple[float, float, float] = (0.5, 0.5, 0.5))[source]

Bases: simvx.core.physics.shapes.Shape

An axis-aligned box collision shape centred at the origin.

Args: half_extents: Half-sizes along x/y/z. Coerced to Vec3 (float32); every component must be > 0.

Initialization

create(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle[source]
property bounding_radius: float[source]
build(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle
__slots__

()

class simvx.core.physics.shapes.CapsuleShape3D(radius: float = 0.5, height: float = 2.0)[source]

Bases: simvx.core.physics.shapes.Shape

A Y-axis capsule collision shape (a segment swept by a sphere).

height is the TOTAL extent along Y, including the two hemispherical caps, so the central segment half-length is max(0.0, height / 2 - radius) and the segment endpoints are centre +- [0, half_len, 0]. When height <= 2 * radius the segment collapses to a point and the capsule degenerates to a sphere of the given radius: that is a valid, documented case (matching Godot’s capsule and the segment-reduction maths in the builtin backend), NOT an error. Accordingly height >= 2 * radius is NOT validated.

Args: radius: Capsule radius in world units (must be > 0). height: Total extent along Y including both caps (must be > 0).

Initialization

create(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle[source]
property bounding_radius: float[source]
build(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle
__slots__

()

class simvx.core.physics.shapes.CylinderShape3D(radius: float = 0.5, height: float = 2.0)[source]

Bases: simvx.core.physics.shapes.Shape

A Y-axis cylinder collision shape.

height is the TOTAL extent along Y, with flat circular top/bottom caps at +-height / 2.

Args: radius: Cylinder radius in world units (must be > 0). height: Total extent along Y (must be > 0).

Initialization

create(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle[source]
property bounding_radius: float[source]
build(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle
__slots__

()

class simvx.core.physics.shapes.ConvexHullShape3D(points: collections.abc.Sequence[simvx.core.math.Vec3 | tuple[float, float, float]])[source]

Bases: simvx.core.physics.shapes.Shape

A convex-hull collision shape defined by a point cloud (Tier-1).

The hull is the convex hull of points; the resource stores the raw cloud and the backend computes its own representation. At least 4 points are required (a 3D hull needs a tetrahedron). A coplanar / collinear cloud is accepted (it is finite and has >= 4 points) but yields a degenerate hull whose penetration depth is approximate: a full coplanarity test is deliberately NOT done at the resource layer (the backend tolerates degenerate clouds).

Basic-tier honesty (see builtin/world.py): the BuiltinPhysics backend does GJK for overlap (exact) and EPA-lite for penetration depth/normal (bounded iteration, documented approximation); hull rotation is ignored. The Jolt backend does a proper hull.

Args: points: Iterable of >= 4 points (Vec3 or (x, y, z) tuples). Coerced to a single (N, 3) float32 array. Must be finite.

Initialization

classmethod from_mesh(vertices: collections.abc.Sequence[simvx.core.math.Vec3 | tuple[float, float, float]], indices: collections.abc.Sequence[int] | None = None) simvx.core.physics.shapes.ConvexHullShape3D[source]

Build a hull from a mesh’s VERTEX CLOUD (opt-in).

The convex hull of a mesh equals the convex hull of its vertex cloud, so indices are ignored: this is a cheap, honest hull-from-mesh with no convex decomposition. Pass the mesh vertices and the topology is irrelevant to the result.

Args: vertices: The mesh vertex cloud ((N, 3) after coercion). indices: Ignored (the hull is independent of triangle topology).

Returns: A ConvexHullShape3D over the vertex cloud.

create(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle[source]
property bounding_radius: float[source]
build(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle
__slots__

()

class simvx.core.physics.shapes.ConcaveMeshShape3D(vertices: collections.abc.Sequence[simvx.core.math.Vec3 | tuple[float, float, float]], indices: collections.abc.Sequence[int])[source]

Bases: simvx.core.physics.shapes.Shape

A static triangle-mesh collision shape (level geometry, Tier-1 static-only).

Holds a triangle-list mesh as a vertex array plus a flat index array (three indices per triangle). This collider is STATIC-ONLY: placing it on a non-STATIC body is an error raised at body creation (every serious engine enforces this, including Jolt). It has no inertia / mass.

The Shape resource itself is a pure value with no static-mode awareness; the static-only contract is enforced by the physics world (create_body / set_body_mode), not here.

Args: vertices: Vertex positions, coerced to a (N, 3) float32 array; finite. indices: Flat triangle-list indices, coerced to a (3 * T,) int64 array. len(indices) must be a non-zero multiple of 3 and every index in [0, N).

Initialization

create(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle[source]
property bounding_radius: float[source]
build(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle
__slots__

()