simvx.core.physics.builtin.world¶
Role¶
Concrete :class:~simvx.core.physics.world.PhysicsWorld implementation, pure
Python (numpy only). This is the engine’s default, always-available backend and
the behavioural parity target for the optional native backend.
This is the basic tier: semi-implicit (symplectic) Euler integration, an O(n^2) broad+narrow phase over sphere/box pairs, and a sequential normal-impulse solver with Baumgarte positional correction and a small penetration slop. It is written for clarity over raw throughput: dynamic bodies settle on static ground without sinking or tunnelling at moderate speeds, which is the acceptance bar for the tier. Rotation is integrated for round-tripping but contacts apply a linear impulse at the centre of mass only (no inertia tensor); that is intentional for the basic backend.
Shape set (T1a): sphere, box, capsule, cylinder. Capsules are done properly via segment closest-point reductions (every capsule pairing reduces to a sphere test at the closest segment point(s), reusing the sphere depth/normal maths). Cylinders are the honest weak shape of the tier: cylinder-sphere and the ray queries are exact analytic finite-cylinder maths, but cylinder-box is an AABB-of-cylinder approximation and cylinder-cylinder / capsule-cylinder approximate the cylinder as a capsule (rounding the rims). Each approximate pairing carries an explicit “deferred to Jolt” comment at its call site; none silently fakes a result.
Swept queries (shapecast / overlap / character sweep) for capsule and cylinder inherit the same substepped (non-continuous) tunnelling honesty as the existing box queries: capsules are oriented only by translation (the segment stays Y-axis), and the cylinder-box approximation applies inside sweeps too.
Shape set (T1b): adds convex hull + static triangle mesh.
STATIC TRIANGLE MESH (
"mesh"): the high-value path, done properly. A moving sphere / capsule / box collides against the mesh triangles via closest-point-on-triangle (Ericson RTCD 5.1.5), reduced to a sphere-at-a-point test per candidate triangle; box-vs-triangle uses the box’s bounding sphere (a documented approximation, like cylinder-box). Raycast uses Moller-Trumbore. Meshes are STATIC-ONLY (enforced atcreate_body/set_body_mode) so the mesh is never the MOVING shape. Broadphase is a linear per-triangle-AABB cull (a BVH is deferred to Jolt).CONVEX HULL (
"hull"): basic tier. GJK gives EXACT boolean overlap; penetration depth + normal use EPA-lite (bounded-iteration expanding polytope with a search-direction fallback) and are APPROXIMATE. Hull rotation is IGNORED (cloud treated in world axes offset by body position), like every other basic shape. Hull raycast is the AABB-of-cloud slab test (an explicit approximation; exact hull raycast is deferred to Jolt). A true robust hull / EPA manifold is a Jolt concern. Capsule / cylinder vs hull route through GJK by exposing a capsule support function (cylinder is approximated as a capsule, the existing rim caveat).
BuiltinPhysics world: pure-Python backend implementing the seam.
Module Contents¶
Classes¶
Pure-Python default backend (basic tier). |
Data¶
API¶
- class simvx.core.physics.builtin.world.BuiltinPhysics(*, gravity: simvx.core.math.Vec3)[source]¶
Bases:
simvx.core.physics.world.PhysicsWorldPure-Python default backend (basic tier).
See :class:
~simvx.core.physics.world.PhysicsWorldfor the full contract.Initialization
Initialise the world.
Args: gravity: World gravity acceleration vector (
Vec3), metres/s^2.- capabilities() frozenset[simvx.core.physics.capability.Capability][source]¶
No Tier-3 capabilities: the builtin tier is an honest rigid-body solver.
It advertises NONE of :class:
Capability(no cross-platform determinism, no vehicles, no soft bodies): the Tier-1 rigid-body surface it shares with every backend is its whole offering. A node needing a Tier-3 feature must select a native backend that lists it. Explicit (not inherited) so the claim is a deliberate, tested promise, not an accident of the default.
- clear() None[source]¶
Remove every body, character, and joint, emptying the world.
See :meth:
~simvx.core.physics.world.PhysicsWorld.clear. Resets the body / character / joint tables and the per-step edge-diff + warm-start caches so a re-populated world starts from a clean broadphase state. Gravity, shapes, and the handle counters are intentionally NOT reset (shapes are reusable resources; monotonic handles keep freed handles from aliasing a live one).
- create_sphere(radius: float) simvx.core.physics.world.ShapeHandle[source]¶
- create_box(half_extents: simvx.core.math.Vec3) simvx.core.physics.world.ShapeHandle[source]¶
- create_capsule(radius: float, height: float) simvx.core.physics.world.ShapeHandle[source]¶
- create_cylinder(radius: float, height: float) simvx.core.physics.world.ShapeHandle[source]¶
- create_convex_hull(points: numpy.ndarray) simvx.core.physics.world.ShapeHandle[source]¶
- create_mesh(vertices: numpy.ndarray, indices: numpy.ndarray) simvx.core.physics.world.ShapeHandle[source]¶
- create_body(shape: simvx.core.physics.world.ShapeHandle, body_type: simvx.core.physics.world.BodyMode, transform: object, *, mass: float = 1.0, collision_layer: int = 1, collision_mask: int = 4294967295, is_sensor: bool = False, friction: float = 0.5, restitution: float = 0.0, friction_combine: simvx.core.physics.material.CombineMode = CombineMode.AVERAGE, restitution_combine: simvx.core.physics.material.CombineMode = CombineMode.AVERAGE, continuous: bool = False) simvx.core.physics.world.BodyHandle[source]¶
- destroy_body(handle: simvx.core.physics.world.BodyHandle) None[source]¶
- set_body_transform(handle: simvx.core.physics.world.BodyHandle, transform: object) None[source]¶
- set_body_velocity(handle: simvx.core.physics.world.BodyHandle, linear: simvx.core.math.Vec3, angular: simvx.core.math.Vec3 | None = None) None[source]¶
- set_body_mode(handle: simvx.core.physics.world.BodyHandle, mode: simvx.core.physics.world.BodyMode) None[source]¶
- body_velocity(handle: simvx.core.physics.world.BodyHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Vec3][source]¶
- sleeping(handle: simvx.core.physics.world.BodyHandle) bool[source]¶
- apply_impulse(handle: simvx.core.physics.world.BodyHandle, impulse: simvx.core.math.Vec3, *, at: simvx.core.math.Vec3 | None = None, angular: simvx.core.math.Vec3 | None = None) None[source]¶
- apply_force(handle: simvx.core.physics.world.BodyHandle, force: simvx.core.math.Vec3, *, at: simvx.core.math.Vec3 | None = None) None[source]¶
- apply_torque(handle: simvx.core.physics.world.BodyHandle, torque: simvx.core.math.Vec3) None[source]¶
- create_fixed_joint(a: simvx.core.physics.world.BodyHandle, b: simvx.core.physics.world.BodyHandle) simvx.core.physics.world.JointHandle[source]¶
- create_pin_joint(a: simvx.core.physics.world.BodyHandle, b: simvx.core.physics.world.BodyHandle, anchor: simvx.core.math.Vec3) simvx.core.physics.world.JointHandle[source]¶
- create_hinge_joint(a: simvx.core.physics.world.BodyHandle, b: simvx.core.physics.world.BodyHandle, anchor: simvx.core.math.Vec3, axis: simvx.core.math.Vec3) simvx.core.physics.world.JointHandle[source]¶
- create_spring_joint(a: simvx.core.physics.world.BodyHandle, b: simvx.core.physics.world.BodyHandle, rest_length: float, stiffness: float, damping: float) simvx.core.physics.world.JointHandle[source]¶
- remove_joint(handle: simvx.core.physics.world.JointHandle) None[source]¶
- drain_contact_events() list[simvx.core.physics.world.ContactEvent][source]¶
- drain_overlap_events() list[simvx.core.physics.world.OverlapEvent][source]¶
- register_bodies(handles: list[simvx.core.physics.world.BodyHandle]) None[source]¶
- raycast(origin: simvx.core.math.Vec3, direction: simvx.core.math.Vec3, max_dist: float, *, mask: int = 4294967295) simvx.core.physics.world.RaycastHit | None[source]¶
- raycast_all(origin: simvx.core.math.Vec3, direction: simvx.core.math.Vec3, max_dist: float, *, mask: int = 4294967295) list[simvx.core.physics.world.RaycastHit][source]¶
- move_and_collide(handle: simvx.core.physics.world.BodyHandle, motion: simvx.core.math.Vec3) simvx.core.physics.world.Contact | None[source]¶
Substepped narrow-phase sweep of a kinematic body (basic tier).
Conservative-advancement is out of tier scope (the narrow phase is analytic AABB-ish overlap, not a true continuous TOI shape-cast), so this uses a substepped sweep whose substep count is sized from the mover’s smallest feature: it advances along
motion, and the first substep that penetrates an OTHER body backs the mover off to the previous non-penetrating substep and reports a contact. Fast movers vs very thin colliders can still tunnel between substeps; this matches the solver’s basic-tier honesty and is replaced by a GJK/Jolt pass later.
- shapecast(shape: simvx.core.physics.world.ShapeHandle, origin: simvx.core.math.Vec3, direction: simvx.core.math.Vec3, max_dist: float, *, mask: int = 4294967295) simvx.core.physics.world.Contact | None[source]¶
Substepped shape sweep, earliest-TOI contact (basic tier).
Mirrors :meth:
move_and_collide’s substepped scan but with a transient probe shape (not a registered body) and the single query-mask convention (mask & body.layer). Earliest TOI wins: substeps are the outer loop, bodies the inner, so the first penetrating substep is the earliest hit.
- overlap(shape: simvx.core.physics.world.ShapeHandle, transform: object, *, mask: int = 4294967295) list[simvx.core.physics.world.BodyHandle][source]¶
Static shape-vs-body overlap test, all matches (basic tier).
Places a transient probe shape at
transformand returns the handles of every body it overlaps whoselayer & maskis set, sorted by handle for determinism. Single query-mask convention.
- body_transform(handle: simvx.core.physics.world.BodyHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Quat][source]¶
- create_character(shape: simvx.core.physics.world.ShapeHandle, transform: object, *, up: simvx.core.math.Vec3 = _DEFAULT_UP, slope_limit: float = math.radians(45.0), step_height: float = 0.0, skin_width: float = 0.001, collision_layer: int = 1, collision_mask: int = 4294967295) simvx.core.physics.world.CharacterHandle[source]¶
- destroy_character(handle: simvx.core.physics.world.CharacterHandle) None[source]¶
- set_character_transform(handle: simvx.core.physics.world.CharacterHandle, transform: object) None[source]¶
- character_transform(handle: simvx.core.physics.world.CharacterHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Quat][source]¶
- character_move_and_slide(handle: simvx.core.physics.world.CharacterHandle, velocity: simvx.core.math.Vec3, dt: float, *, up: simvx.core.math.Vec3, max_slides: int = 4) simvx.core.physics.world.CharacterMoveResult[source]¶
Arcade collide-and-slide against world bodies (basic tier).
Sweeps the character along
velocity * dt; on each contact it classifies floor/wall/ceiling from the contact normal vsup(and the storedslope_limit), deflects both the remaining motion and the velocity along the contact normal, and repeats up tomax_slidestimes. An optional single up/forward/down step-up probe handles ledges up tostep_height(basic tier: one step only).
- property gravity: simvx.core.math.Vec3¶
- __slots__¶
()
- simvx.core.physics.builtin.world.__all__¶
[‘BuiltinPhysics’]