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¶
Abstract collision-geometry resource that OWNS its backend shape. |
|
A sphere collision shape of a given radius. |
|
An axis-aligned box collision shape centred at the origin. |
|
A Y-axis capsule collision shape (a segment swept by a sphere). |
|
A Y-axis cylinder collision shape. |
|
A convex-hull collision shape defined by a point cloud (Tier-1). |
|
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.ABCAbstract collision-geometry resource that OWNS its backend shape.
A
Shapeis the unit of collision geometry and the unit of its lifetime, the same model meshes and materials follow. :meth:buildhands 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:
createand dispatch to the matchingworld.create_*factory.
Geometry is IMMUTABLE, by contract rather than by enforcement. Nothing here reads a radius or a half-extent twice: :meth:
buildruns 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:buildhands 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:
createand 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.PhysicsWorldwhose shape factory builds the opaque handle.Returns: An opaque
ShapeHandlefor use withworld.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; callingcreatedirectly 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.ShapeA sphere collision shape of a given radius.
Args: radius: Sphere radius in world units (must be > 0).
Initialization
- __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.ShapeAn 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
- __slots__¶
()
- class simvx.core.physics.shapes.CapsuleShape3D(radius: float = 0.5, height: float = 2.0)[source]¶
Bases:
simvx.core.physics.shapes.ShapeA Y-axis capsule collision shape (a segment swept by a sphere).
heightis the TOTAL extent along Y, including the two hemispherical caps, so the central segment half-length ismax(0.0, height / 2 - radius)and the segment endpoints arecentre +- [0, half_len, 0]. Whenheight <= 2 * radiusthe 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. Accordinglyheight >= 2 * radiusis NOT validated.Args: radius: Capsule radius in world units (must be > 0). height: Total extent along Y including both caps (must be > 0).
Initialization
- __slots__¶
()
- class simvx.core.physics.shapes.CylinderShape3D(radius: float = 0.5, height: float = 2.0)[source]¶
Bases:
simvx.core.physics.shapes.ShapeA Y-axis cylinder collision shape.
heightis 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
- __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.ShapeA 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 (
Vec3or(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
indicesare 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
ConvexHullShape3Dover the vertex cloud.
- __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.ShapeA 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
Shaperesource 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
- __slots__¶
()