simvx.core.physics.world¶
Role¶
This module defines PhysicsWorld, the abstract interface every physics
backend (BuiltinPhysics now, JoltPhysics later) implements. It is a
transport abstraction, not a semantics one: all backends run the same kind
of rigid-body simulation, so the contract is about moving body state across
the Python<->native boundary efficiently, not about defining solver behaviour.
The load-bearing part of the contract is the bulk-array transfer: per-frame
body state is exchanged as a single numpy buffer (one transfer per world per
frame), in a fixed body->row order, by filling a caller-preallocated array
in place. Per-body crossings are reserved for setup/teardown and are never
used on the hot path. See :meth:PhysicsWorld.register_bodies,
- meth:
PhysicsWorld.read_transforms, and :meth:PhysicsWorld.read_velocities.
Stage 1 status¶
This is additive, non-breaking scaffolding. It defines the interface only
(full signatures, docstrings, @abstractmethod); there is no implementation
here, and nothing in this module is wired into SceneTree or the old
simvx.core.physics system. The seam is testable standalone by constructing
a concrete backend and calling :meth:step manually.
PhysicsWorld: the backend transport seam (Stage 1 additive scaffolding).
Module Contents¶
Classes¶
Motion mode of a body, mirroring every serious solver’s seam. |
|
Result of a successful raycast against the world. |
|
Result of a kinematic / character shape-sweep stopping against a body. |
|
Edge phase of a body-pair contact, diffed by the broadphase each step. |
|
A node-agnostic body-pair collision event emitted by the seam. |
|
A node-agnostic sensor-overlap event emitted by the seam. |
|
Returned struct of a character collide-and-slide move. |
|
Abstract backend seam: one isolated simulation world. |
Data¶
API¶
- simvx.core.physics.world.BodyHandle¶
None
- simvx.core.physics.world.ShapeHandle¶
None
- simvx.core.physics.world.CharacterHandle¶
None
- simvx.core.physics.world.JointHandle¶
None
- class simvx.core.physics.world.BodyMode[source]¶
Bases:
enum.EnumMotion mode of a body, mirroring every serious solver’s seam.
STATIC: immovable collider. Never integrated; infinite mass.DYNAMIC: force-simulated; responds to gravity, impulses, contacts.KINEMATIC: code-moved; pushes dynamic bodies, immune to forces.
- STATIC¶
‘static’
- DYNAMIC¶
‘dynamic’
- KINEMATIC¶
‘kinematic’
- __new__(value)¶
- __repr__()¶
- __str__()¶
- __dir__()¶
- __format__(format_spec)¶
- __hash__()¶
- __reduce_ex__(proto)¶
- __deepcopy__(memo)¶
- __copy__()¶
- name()¶
- value()¶
- class simvx.core.physics.world.RaycastHit[source]¶
Result of a successful raycast against the world.
Attributes: body: Handle of the body the ray hit. point: World-space contact point (
Vec3). normal: World-space surface normal at the hit (Vec3, unit length). distance: Distance from the ray origin topointalong the ray.- body: simvx.core.physics.world.BodyHandle¶
None
- point: simvx.core.math.Vec3¶
None
- normal: simvx.core.math.Vec3¶
None
- distance: float¶
None
- class simvx.core.physics.world.Contact[source]¶
Result of a kinematic / character shape-sweep stopping against a body.
Mirrors :class:
RaycastHitexactly (a single “other” body, like a query result): the swept body is implicit (the caller). Maps cleanly onto Jolt (body<- hitBodyID,normal<- contact normal,distance<-fraction * |motion|).Attributes: body: Handle of the OTHER body that was hit. point: World-space contact point (
Vec3). normal: World-space surface normal (Vec3, unit), pointing AWAY from the other body toward the moving body, i.e. the direction that separates the mover. distance: Distance the mover actually travelled alongmotionbefore contact (TOI distance,0..|motion|).- body: simvx.core.physics.world.BodyHandle¶
None
- point: simvx.core.math.Vec3¶
None
- normal: simvx.core.math.Vec3¶
None
- distance: float¶
None
- class simvx.core.physics.world.ContactPhase[source]¶
Bases:
enum.EnumEdge phase of a body-pair contact, diffed by the broadphase each step.
Only the two transitions are reported (no per-frame “stay”): a pair fires
ENTERthe step it begins overlapping andEXITthe step it stops.- ENTER¶
‘enter’
- EXIT¶
‘exit’
- __new__(value)¶
- __repr__()¶
- __str__()¶
- __dir__()¶
- __format__(format_spec)¶
- __hash__()¶
- __reduce_ex__(proto)¶
- __deepcopy__(memo)¶
- __copy__()¶
- name()¶
- value()¶
- class simvx.core.physics.world.ContactEvent[source]¶
A node-agnostic body-pair collision event emitted by the seam.
Keyed by body HANDLES only: the seam never names a node. The tree maps
a/bback to nodes and fires the node-levelcollided/separatedSignals. Both static-dynamic and dynamic-dynamic pairs are reported, and both bodies are notified (the tree reorients per side).Orientation convention is fixed here as
a -> b(mirroring the internal narrow-phase_Contact.normal); the tree negatesnormal/rel_velocityfor thebside so each body sees the separating direction pointing toward itself.Attributes: a: Handle of the first body of the pair (canonical order). b: Handle of the second body of the pair. phase: :class:
ContactPhase(ENTER/EXIT). point: World contact point (Vec3). Meaningful onENTER; degenerate (Vec3(0)) onEXIT(no live manifold). normal: Unit contact normal orienteda -> b(Vec3). Degenerate (Vec3(0)) onEXIT. impulse: Normal impulse magnitude applied to the pair this step.0onEXITand0on anENTERwhere the solver applied no impulse (e.g. separating velocity). rel_velocity: Pre-solve velocity ofbw.r.t.aat the contact (Vec3). Degenerate (Vec3(0)) onEXIT.- phase: simvx.core.physics.world.ContactPhase¶
None
- point: simvx.core.math.Vec3¶
None
- normal: simvx.core.math.Vec3¶
None
- impulse: float¶
None
- rel_velocity: simvx.core.math.Vec3¶
None
- class simvx.core.physics.world.OverlapEvent[source]¶
A node-agnostic sensor-overlap event emitted by the seam.
A SECOND, independent edge-diffed stream, parallel to :class:
ContactEventbut never mixed with it: a sensor pair produces NO collision response and NO manifold, so there is no point / normal / impulse / rel_velocity to carry.Keyed by body HANDLES only (node-agnostic, like :class:
ContactEvent), but DIRECTEDsensor -> otherrather than a canonical unordered pair: the detection is one-directional (the observing sensor decides via its mask), so a sensor-vs-sensor overlap can fire on one side without the other. The tree maps both handles to nodes and routesbody_enteredvsarea_enteredby the OTHER node’s type.Attributes: sensor: Handle of the detecting sensor body (the observer). other: Handle of the detected body (a normal body OR another sensor). phase: :class:
ContactPhase(ENTER/EXIT); reused, no second phase enum.- sensor: simvx.core.physics.world.BodyHandle¶
None
- other: simvx.core.physics.world.BodyHandle¶
None
- phase: simvx.core.physics.world.ContactPhase¶
None
- class simvx.core.physics.world.CharacterMoveResult[source]¶
Returned struct of a character collide-and-slide move.
Returning a struct (rather than mutating caller state) keeps the seam node-agnostic and gives Jolt a clean place to surface
CharacterVirtual::GetGroundState()/GetGroundNormal(). Floor/wall/ceiling are computed seam-side from contact normals vsup(and the character’sslope_limit) so every backend exposes identical semantics.Attributes: velocity: Post-slide velocity (deflected along contact normals); the caller writes this back as its new velocity. on_floor: True if a contact this move was classified as floor. on_wall: True if a contact this move was classified as wall. on_ceiling: True if a contact this move was classified as ceiling. floor_normal: Normal of the floor contact this move (unit), or
+upif there was no floor contact.- velocity: simvx.core.math.Vec3¶
None
- on_floor: bool¶
None
- on_wall: bool¶
None
- on_ceiling: bool¶
None
- floor_normal: simvx.core.math.Vec3¶
None
- class simvx.core.physics.world.PhysicsWorld(*, gravity: simvx.core.math.Vec3)[source]¶
Bases:
abc.ABCAbstract backend seam: one isolated simulation world.
A
PhysicsWorldowns a set of bodies, advances them as a unit at a fixed timestep via :meth:step, and exchanges per-frame state in bulk. Concrete backends (BuiltinPhysics, laterJoltPhysics) implement every method.Bulk-array contract (the keystone)
Call :meth:
register_bodiesonce (or whenever membership changes) to fix the body->row order used by the bulk readers.Each frame, after :meth:
step, call :meth:read_transformsand/or- meth:
read_velocities, passing a caller-preallocated, C-contiguousfloat32numpy array of the documented shape. The backend fills it in place; it must not allocate or return a new array on the hot path.
The array shapes/dtypes/contiguity are part of the contract and MUST be asserted by subclasses (see
_check_transforms_out/_check_velocities_out).Initialization
Initialise the world.
Args: gravity: World gravity acceleration vector (
Vec3), metres/s^2.- property gravity: simvx.core.math.Vec3[source]¶
World gravity acceleration vector (
Vec3), metres/s^2.
- capabilities() frozenset[simvx.core.physics.capability.Capability][source]¶
Return the set of Tier-3 :class:
Capabilityfeatures this backend honours.The strict Tier-1 rigid-body surface is the parity contract every backend implements identically; this method is the ONE place backends advertise the few features that genuinely cannot be faked (cross-platform determinism, vehicles, soft bodies). A Tier-3 node checks
Capability.X in world.capabilities()and degrades / refuses if absent.The default (this base implementation) is the empty set: a backend advertises nothing it cannot honour. Backends override to list only the capabilities they actually support. Not abstract: the empty default is the honest answer for any plain rigid-body backend.
- abstractmethod create_sphere(radius: float) simvx.core.physics.world.ShapeHandle[source]¶
Create a sphere collision shape and return an opaque handle.
Args: radius: Sphere radius, world units (> 0).
Returns: An opaque shape handle for use with :meth:
create_body.
- abstractmethod create_box(half_extents: simvx.core.math.Vec3) simvx.core.physics.world.ShapeHandle[source]¶
Create an axis-aligned box collision shape (centred at the origin).
Args: half_extents: Half-sizes along x/y/z (
Vec3, all > 0).Returns: An opaque shape handle for use with :meth:
create_body.
- abstractmethod create_capsule(radius: float, height: float) simvx.core.physics.world.ShapeHandle[source]¶
Create a Y-axis capsule collision shape and return an opaque handle.
Args: radius: Capsule radius, world units (> 0). height: Total extent along Y including the two hemispherical caps (> 0). The central segment half-length is
max(0, height / 2 - radius); whenheight <= 2 * radiusthe segment collapses to a point and the capsule behaves as a sphere ofradius.Returns: An opaque shape handle for use with :meth:
create_body.
- abstractmethod create_cylinder(radius: float, height: float) simvx.core.physics.world.ShapeHandle[source]¶
Create a Y-axis cylinder collision shape and return an opaque handle.
Args: radius: Cylinder radius, world units (> 0). height: Total extent along Y with flat caps at
+-height / 2(> 0).Returns: An opaque shape handle for use with :meth:
create_body.
- abstractmethod create_convex_hull(points: numpy.ndarray) simvx.core.physics.world.ShapeHandle[source]¶
Create a convex-hull collision shape from a point cloud.
Args: points:
(N, 3)float32 array of >= 4 finite points. The backend computes its own internal hull representation from the cloud. Orientation is supported via the body transform like other shapes, EXCEPT the basicBuiltinPhysicsbackend, which IGNORES hull rotation (the cloud is treated in world axes offset by the body position); the Jolt backend rotates properly.Returns: An opaque shape handle for use with :meth:
create_body. The basic backend’s penetration depth/normal for a hull is an EPA-lite approximation (GJK overlap is exact); seebuiltin/world.py.
- abstractmethod create_mesh(vertices: numpy.ndarray, indices: numpy.ndarray) simvx.core.physics.world.ShapeHandle[source]¶
Create a STATIC triangle-mesh collision shape (level geometry).
Args: vertices:
(N, 3)float32 vertex positions. indices:(3 * T,)int64 flat triangle-list indices (three per triangle), each in[0, N).Returns: An opaque shape handle for use with :meth:
create_body. A mesh shape is a STATIC-ONLY collider: placing it on a non-STATIC body is an error (rejected at :meth:create_bodyand :meth:set_body_mode). It carries no inertia / mass and cannot be used as a moving query shape (:meth:shapecast/ :meth:overlapreject a mesh probe).
- abstractmethod create_body(shape: simvx.core.physics.world.ShapeHandle, body_type: simvx.core.physics.world.BodyMode, transform: Any, *, 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]¶
Create a body in the world and return its handle.
Args: shape: An opaque shape handle from :meth:
create_sphere, :meth:create_box, :meth:create_capsule, :meth:create_cylinder, :meth:create_convex_hull, or :meth:create_mesh. A mesh shape on a non-STATIC body is an error (mesh colliders are STATIC-only), rejected here and in :meth:set_body_mode. body_type: One of :class:BodyMode. transform: Initial world transform (aTransform3Dor anything a backend accepts as an initial pose; position + orientation). mass: Body mass in kg, used only forDYNAMICbodies. Ignored forSTATIC/KINEMATIC(treated as infinite). collision_layer: 32-bit layer membership of this body (which layers it lives on). Stored verbatim; defaults to layer 1. collision_mask: 32-bit mask of layers this body scans for collisions. Defaults to all (0xFFFFFFFF) so the bare API collides every pair (non-breaking). A pair (a, b) collides iff(a.mask & b.layer) or (b.mask & a.layer)(bidirectional OR). is_sensor: When True, this body is a SENSOR (trigger). It is created / destroyed / teleported exactly like a normal body and participates in the broadphase, but is EXCLUDED from collision resolution (it skips the solver, applies no impulse, and never appears in the contact-event stream) and instead generates a SEPARATE overlap-event stream (see :meth:drain_overlap_events) using the ONE-DIRECTIONAL filtersensor.mask & other.layer(the observer decides; the other body’s mask is irrelevant), never the AND body-body rule. A sensor is an ordinary body with a flag, not a separate kind of handle. friction: Coulomb friction coefficientmu(>= 0). The contact solver clamps the tangential impulse bymutimes the normal impulse. Defaults to0.5(matching :class:PhysicsMaterial). Node-agnostic primitive: the node unpacks itsPhysicsMaterialresource into this and the next three params (the seam never sees the resource or the node). restitution: Bounciness in[0, 1](0= inelastic, the default, so the bare seam API does not bounce). Combined per-contact and gated by a small velocity rest-threshold (see the builtin solver). friction_combine: How this body’sfrictioncombines with the other body’s at a contact (:class:CombineMode); defaults to AVERAGE. restitution_combine: How this body’srestitutioncombines with the other body’s, INDEPENDENT offriction_combine(defaults to AVERAGE). continuous: When True, this body uses continuous collision detection: each step its centre displacement is swept against STATIC geometry and clamped to the time-of-impact so a fast small body cannot tunnel through thin static colliders. Defaults False (discrete). Basic-tier honesty: a CENTRE ray / shapecast sweep vs STATIC bodies only, no rotational sweep and no dynamic-vs-dynamic CCD; the Jolt backend honours the flag faithfully viaEMotionQuality::LinearCast.Returns: An opaque body handle, stable until :meth:
destroy_body.
- abstractmethod destroy_body(handle: simvx.core.physics.world.BodyHandle) None[source]¶
Remove a body from the world.
After destruction the handle is invalid. Callers that use the bulk readers must re-call :meth:
register_bodiesto re-establish row order.Args: handle: A handle previously returned by :meth:
create_body.
- abstractmethod set_body_transform(handle: simvx.core.physics.world.BodyHandle, transform: Any) None[source]¶
Teleport a body to a new world transform.
Args: handle: Body handle. transform: New world transform (position + orientation).
- abstractmethod set_body_velocity(handle: simvx.core.physics.world.BodyHandle, linear: simvx.core.math.Vec3, angular: simvx.core.math.Vec3 | None = None) None[source]¶
Set a body’s linear and angular velocity directly.
Args: handle: Body handle. linear: Linear velocity (
Vec3), world units/s. angular: Angular velocity (Vec3), radians/s about each axis.None(default) means zero angular velocity.
- abstractmethod set_body_mode(handle: simvx.core.physics.world.BodyHandle, mode: simvx.core.physics.world.BodyMode) None[source]¶
Change a live body’s motion mode in place (no destroy/recreate).
Flips the body between STATIC / KINEMATIC / DYNAMIC, updating its effective (inverse) mass: STATIC and KINEMATIC are infinite-mass (inv_mass 0), DYNAMIC uses the body’s stored mass. Maps onto Jolt’s Body::SetMotionType; the builtin backend flips body_type + inverse_mass.
- abstractmethod body_velocity(handle: simvx.core.physics.world.BodyHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Vec3][source]¶
Read a body’s current
(linear, angular)velocity, per-body.Cold per-body read parallel to :meth:
body_transform. The bulk- Meth:
read_velocitiesstays the hot scatter path; this is the accessor used byPhysicsBody3D.velocity/.spinfor a single synchronous read-back (e.g.self.velocity += dv).
Args: handle: Body handle.
Returns:
(linear, angular)velocity (Vec3,Vec3); angular in radians/s. Returns zero velocities for an infinite-mass body that was never moved.
- abstractmethod sleeping(handle: simvx.core.physics.world.BodyHandle) bool[source]¶
True if the body is asleep (skipped by integrate + solve until woken).
STATIC / KINEMATIC bodies are never ‘asleep’ (they were never awake): returns False for them. A sleeping body stays a full collider and still reads back its (frozen) transform / velocity through the bulk readers.
- abstractmethod 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 an instantaneous velocity change to a body NOW.
Unlike :meth:
apply_forcethis takes effect immediately (it mutates velocity, not an accumulator) and is NOT cleared by :meth:step. Inert on non-DYNAMIC bodies (inverse mass 0).Args: handle: Body handle. impulse: Linear impulse (
Vec3), N*s. Addsimpulse * inv_massto the linear velocity. at: Optional world-space application point. When given, the offsetr = at - positioncontributes an angular impulsecross(r, impulse)(basic tier scales it byinv_massas a stand-in for the inverse inertia tensor, which the basic backend does not model).Noneapplies the impulse purely through the centre of mass (no torque). angular: Optional explicit angular impulse (Vec3), forspin_up. Addsangular * inv_mass(basic-tier inverse inertia stand-in) to the angular velocity, independent ofat.
- abstractmethod apply_force(handle: simvx.core.physics.world.BodyHandle, force: simvx.core.math.Vec3, *, at: simvx.core.math.Vec3 | None = None) None[source]¶
Accumulate a continuous force, applied during the NEXT :meth:
step.The force is integrated as acceleration (
force * inv_mass) before position integration, then auto-cleared at the end of the step. To sustain a force the caller must re-add it every fixed step (per design S7); a single call affects exactly one step. Inert on non-DYNAMIC.Args: handle: Body handle. force: Linear force (
Vec3), N. at: Optional world-space application point. When given, the offsetr = at - positionadds a torquecross(r, force)to the torque accumulator.Noneapplies the force through the COM.
- abstractmethod apply_torque(handle: simvx.core.physics.world.BodyHandle, torque: simvx.core.math.Vec3) None[source]¶
Accumulate a continuous torque, applied during the NEXT :meth:
step.Auto-cleared after the step like :meth:
apply_force: re-add each fixed step to sustain it. Inert on non-DYNAMIC. Basic tier applies it astorque * inv_mass(inverse inertia stand-in).Args: handle: Body handle. torque: Torque (
Vec3), N*m.
- abstractmethod create_fixed_joint(a: simvx.core.physics.world.BodyHandle, b: simvx.core.physics.world.BodyHandle) simvx.core.physics.world.JointHandle[source]¶
Weld two bodies: lock their full relative transform.
Captures the CURRENT relative pose of
bina’s frame at create time (relative position AND relative orientation) and holds it: the two bodies thereafter move as one rigid assembly.Basic-tier honesty: the builtin backend has NO inertia tensor, so the angular lock uses
inverse_massas the inverse-inertia scalar (per T1a); a long thin body or an off-centre weld will rotate too easily versus a real solver. Convergence is a few sequential-impulse iterations, so a long weld chain sags slightly. Precise articulated mechanisms are a Jolt concern.Args: a: First body handle (the reference frame). b: Second body handle (welded into
a’s frame).Returns: An opaque :data:
JointHandle, valid until :meth:remove_joint(or until either body is destroyed, which silently drops the joint).
- abstractmethod 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]¶
Pin two bodies at a single world-space point (ball / point-to-point).
Constrains the two bodies so the world point
anchorstays coincident on both (they cannot separate there) while leaving all three rotational DOF free. The anchor is stored as a per-body offset (r_a = anchor - pos_a,r_b = anchor - pos_b) captured at create.Basic-tier honesty: the stored anchor offset does NOT rotate with the body (re-derived each step as
pos + rin WORLD axes, same limitation as the hull-rotation-ignored narrowphase), so a pin on a spinning body drifts. Angular cross-coupling usesinverse_massas the inverse-inertia scalar. A few iterations of convergence.Args: a: First body handle. b: Second body handle. anchor: World-space pivot point shared by both bodies (
Vec3).Returns: An opaque :data:
JointHandle.
- abstractmethod 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]¶
Hinge two bodies: pin at
anchor+ one free rotational DOF aboutaxis.A point constraint at
anchor(like :meth:create_pin_joint) PLUS an angular constraint that locks the two off-axis rotational DOF, leaving free rotation only about the world-spaceaxis(normalised and captured at create). Motors and angular limits are explicitly OUT of scope this stage (a deliberate follow-on).Basic-tier honesty: same anchor-does-not-rotate and
inverse_massinverse-inertia-scalar caveats as :meth:create_pin_joint; a few iterations of convergence.Args: a: First body handle. b: Second body handle. anchor: World-space hinge pivot point (
Vec3). axis: World-space hinge axis (Vec3, normalised at create).Returns: An opaque :data:
JointHandle.
- abstractmethod 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]¶
Soft distance-spring between the two body centres (compliant, not rigid).
A soft constraint that pulls the two body centres of mass toward
rest_lengthapart with spring constantstiffness(N/m) and dampingdamping(N*s/m). Unlike the rigid joints it is intentionally compliant: it applies a soft velocity impulse with ak*xbias and ac*vdamping term, and is NEVER position-corrected.Basic-tier honesty: the builtin backend uses the two COMs, NOT per-body anchors (Pin / Hinge use anchors, Spring uses centres for simplicity). The explicit soft-impulse form can oscillate or overshoot when
stiffnessis large relative to the fixeddt; a stiff spring needs a smallerdtor the Jolt backend. Nothing is silently clamped.Args: a: First body handle. b: Second body handle. rest_length: Target centre-to-centre separation (world units, >= 0). stiffness: Spring constant k (N/m). damping: Damping coefficient c (N*s/m).
Returns: An opaque :data:
JointHandle.
- abstractmethod remove_joint(handle: simvx.core.physics.world.JointHandle) None[source]¶
Remove a constraint; the handle is invalid afterwards.
A no-op if
handleis unknown (already removed, or silently dropped because one of its bodies was destroyed): this is the SAME silent-drop contract :meth:destroy_bodyalready uses for touching / overlap pairs, not an error-swallowing shim. A joint whose body was freed is the expected case, so removing it twice (once by the body-purge, once by the joint node’s own teardown) must be safe in either teardown order.Args: handle: A handle previously returned by a
create_*_jointcall.
- abstract property body_count: int[source]¶
Number of bodies currently in the world.
Read-only. Used by
SceneTree.physics_tickto skip stepping empty worlds for zero overhead, mirroringPhysicsServer.body_count.
- abstractmethod clear() None[source]¶
Remove every body, character, and joint, emptying the world.
The seam equivalent of the old global
PhysicsServer.reset(): a level teardown / restart-the-scene primitive that returns the world to an empty state (body_count == 0) WITHOUT discarding the world object, its configured :attr:gravity, or its backend. Per-step edge-diff buffers (contacts / overlaps) and any warm-start cache are reset so the next step starts from a clean broadphase. Cached shape handles stay valid (shapes are reusable resources), and handle counters keep advancing so a freed handle is never re-issued to a new body. After :meth:clear, callers using the bulk readers must re-:meth:register_bodies.
- abstractmethod step(dt: float) None[source]¶
Advance the whole world once by a fixed timestep.
This integrates every body, resolves collisions, and updates internal state for the entire world as a unit. It is designed to be driven by a fixed-step accumulator (Stage 2 wires it to
SceneTree.physics_tick); in Stage 1 it is called manually by tests.Args: dt: Fixed timestep in seconds. Callers must pass a constant value.
- abstractmethod drain_contact_events() list[simvx.core.physics.world.ContactEvent][source]¶
Return and CLEAR this step’s buffered enter/exit contact events.
Edge-only and broadphase-driven: the backend diffs the touching-pair set each :meth:
stepand buffers a :class:ContactEventfor every pair that began (ENTER) or stopped (EXIT) overlapping. Returns[]when nothing changed. Node-agnostic: events are keyed by body handles only; the tree maps handles to nodes and fires the Signals. Both static-dynamic AND dynamic-dynamic pairs are reported, filtered by the same layer/mask rule the simulation uses.
- abstractmethod drain_overlap_events() list[simvx.core.physics.world.OverlapEvent][source]¶
Return and CLEAR this step’s buffered sensor-overlap enter/exit events.
Edge-only, broadphase-driven, DIRECTED (keyed
sensor -> other), filtered by the one-directional sensor rule (sensor.mask & other.layer), node-agnostic. Distinct from- Meth:
drain_contact_events: a sensor pair produces NO collision response and NO manifold, so the event carries only the two handles plus the :class:ContactPhase. Returns[]when nothing changed.
- abstractmethod register_bodies(handles: list[simvx.core.physics.world.BodyHandle]) None[source]¶
Fix the body->row order used by the bulk readers.
Establishes the mapping from each body handle to a row index. After this call, :meth:
read_transforms/ :meth:read_velocitiesfill rowiwith the state ofhandles[i]. Call again whenever membership or desired ordering changes.Args: handles: Ordered list of body handles.
len(handles)is the row countNexpected by the bulk readers.
- abstractmethod read_transforms(out: numpy.ndarray) None[source]¶
Fill
outwith current body transforms, in place.Bulk hot-path read. The backend writes into the caller-owned buffer and allocates nothing.
Args: out: Pre-allocated array of shape
(N, 7), dtypefloat32, C-contiguous, whereNmatches the most recent :meth:register_bodies. Each row is[px, py, pz, qx, qy, qz, qw]: position xyz followed by an orientation quaternion in xyzw order (scalar-last). Rowicorresponds tohandles[i].
- abstractmethod read_velocities(out: numpy.ndarray) None[source]¶
Fill
outwith current body velocities, in place.Bulk hot-path read. The backend writes into the caller-owned buffer and allocates nothing.
Args: out: Pre-allocated array of shape
(N, 6), dtypefloat32, C-contiguous, whereNmatches the most recent :meth:register_bodies. Each row is[lx, ly, lz, ax, ay, az]: linear velocity xyz followed by angular velocity xyz (radians/s). Rowicorresponds tohandles[i].
- abstractmethod raycast(origin: simvx.core.math.Vec3, direction: simvx.core.math.Vec3, max_dist: float, *, mask: int = 4294967295) simvx.core.physics.world.RaycastHit | None[source]¶
Cast a ray and return the nearest hit, or
None.Args: origin: Ray origin in world space (
Vec3). direction: Ray direction (Vec3); need not be normalised. max_dist: Maximum distance alongdirectionto test. mask: Query layer mask. Only bodies whosecollision_layer & maskis non-zero are considered. Defaults to all layers. This is the single query-mask convention (one query mask vs each body’s layer), distinct from the bidirectional body-pair rule used by the simulation.Returns: A :class:
RaycastHitfor the closest body intersected withinmax_distwhose layer matchesmask, orNoneif the ray hits nothing.
- abstractmethod 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]¶
Cast a ray and return EVERY hit within
max_dist, sorted by distance.Like :meth:
raycastbut collects all intersected bodies (whosecollision_layer & maskis set) instead of only the nearest, returned ascending by :attr:RaycastHit.distance. Empty list on no hit. BacksPhysicsQuery.raycast_alland theexclude=filter path ofraycast(which must skip excluded nearer hits).Args: origin: Ray origin in world space (
Vec3). direction: Ray direction (Vec3); need not be normalised. max_dist: Maximum distance alongdirectionto test. mask: Query layer mask (single query-mask vs body-layer convention).Returns: All :class:
RaycastHit\ s withinmax_dist, sorted ascending by distance (empty if the ray hits nothing).
- abstractmethod 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]¶
Sweep a shape along a ray and return the earliest-TOI contact, or
None.Sweeps
shapefromoriginalongdirection(need not be normalised) up tomax_distagainst world bodies, returning the earliest time-of-impact contact among bodies whosecollision_layer & maskis set, elseNone. Reuses- Class:
Contact: the swept shape is the implicit caller,bodyis the hit body,normalis the separating normal pointing back toward the cast origin, anddistanceis the TOI distance alongdirection.
Basic-tier honesty: this is a substepped sweep, not a true continuous cast, so fast casts vs very thin colliders can tunnel and box orientation is ignored (AABB), matching :meth:
move_and_collide.Args: shape: An opaque shape handle from :meth:
create_sphere, :meth:create_box, :meth:create_capsule, or :meth:create_cylinder. origin: Cast origin in world space (Vec3). direction: Cast direction (Vec3); need not be normalised. max_dist: Maximum sweep distance alongdirection. mask: Query layer mask (single query-mask vs body-layer convention).Returns: The earliest-TOI :class:
Contact, orNoneif nothing was hit.
- abstractmethod overlap(shape: simvx.core.physics.world.ShapeHandle, transform: Any, *, mask: int = 4294967295) list[simvx.core.physics.world.BodyHandle][source]¶
Return all bodies a static shape overlaps at
transform.Places
shapeattransform(same flexible forms as- Meth:
create_body) and returns the handles of every body it overlaps whosecollision_layer & maskis set, sorted by handle for determinism. Basic-tier honesty: AABB-ish narrowphase, box orientation ignored, like :meth:move_and_collide.
Args: shape: An opaque shape handle from :meth:
create_sphere, :meth:create_box, :meth:create_capsule, or :meth:create_cylinder. transform: World pose to place the shape at (same flexible forms as :meth:create_body). mask: Query layer mask (single query-mask vs body-layer convention).Returns: Sorted list of overlapping body handles (empty if none).
- abstractmethod move_and_collide(handle: simvx.core.physics.world.BodyHandle, motion: simvx.core.math.Vec3) simvx.core.physics.world.Contact | None[source]¶
Move a kinematic body by
motion, stopping at the first contact.Shape-casts the body’s shape from its current pose along world-space
motion(alreadyvelocity * dt: the seam takes a displacement, not a velocity, sodtlives in the node) against every OTHER body and finds the earliest time-of-impact. Sets the body’s stored transform to the position it actually reached (origin + motion * toiminus a tiny safe margin). Does NOT slide and does NOT integrate gravity: one sweep, stop at first contact.Args: handle: A body created with :attr:
BodyMode.KINEMATIC. motion: World-space displacement (Vec3).Returns: A :class:
Contact(other body, world point, separating normal, distance travelled) if the sweep stopped early, elseNoneafter moving the fullmotion.
- abstractmethod body_transform(handle: simvx.core.physics.world.BodyHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Quat][source]¶
Read a body’s current pose as
(position, orientation).Parallel to :meth:
character_transform; returns plainVec3/Quatso callers (e.g.KinematicBody3D) get a clean synchronous read-back after a user-driven :meth:move_and_collidewithout the bulk path.Args: handle: Body handle.
Returns:
(position, orientation).
- abstractmethod create_character(shape: simvx.core.physics.world.ShapeHandle, transform: Any, *, 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]¶
Create a character controller bound to this world.
The character collides against world bodies but is NOT added to the dynamic-body set: it is never force-integrated and never appears in
- Meth:
register_bodies/ :meth:read_transforms. This mirrors a JoltCharacterVirtual(distinct from a rigidBody).
Args: shape: An opaque shape handle from :meth:
create_sphere, :meth:create_box, :meth:create_capsule, or :meth:create_cylinder. transform: Initial world pose (position + orientation), same flexible forms accepted by :meth:create_body. up: World up vector (Vec3), engine Y-up convention. slope_limit: Maximum walkable slope, in RADIANS (engine convention). step_height: Maximum step-up height in world units (0 disables). skin_width: Collision safe margin in world units. collision_layer: 32-bit layer membership of this character. Defaults to layer 1. collision_mask: 32-bit mask of layers the character collides with. Defaults to all (0xFFFFFFFF). Character sweeps use the same bidirectional OR rule as bodies (char.mask & body.layerorbody.mask & char.layer).Returns: An opaque :data:
CharacterHandle, distinct from any body handle.
- abstractmethod destroy_character(handle: simvx.core.physics.world.CharacterHandle) None[source]¶
Remove a character controller from the world.
Args: handle: A handle previously returned by :meth:
create_character.
- abstractmethod set_character_transform(handle: simvx.core.physics.world.CharacterHandle, transform: Any) None[source]¶
Teleport a character to a new pose (no collision).
For sync-down on re-parent / explicit position writes.
Args: handle: Character handle. transform: New world pose (position + orientation).
- abstractmethod character_transform(handle: simvx.core.physics.world.CharacterHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Quat][source]¶
Read a character’s current pose as
(position, orientation).For synchronous node sync after a move.
Args: handle: Character handle.
Returns:
(position, orientation).
- abstractmethod 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]¶
Collide-and-slide a character by
velocity * dtagainst the world.Deflects velocity along contact normals over up to
max_slidesiterations, classifies floor/wall/ceiling from contact normals vsup(and the character’sslope_limit), and updates the character’s stored pose.upis passed per-call (the node owns the up vector); the fixed collider config (slope_limit/step_height/skin_width) is set at create time, like Jolt’sCharacterVirtualSettings.Floor/wall/ceiling state is returned per-move in
- Class:
CharacterMoveResult(not a separate query method), so it is valid only right after this call, matching Jolt’sGetGroundState().
Args: handle: Character handle. velocity: Desired world-space velocity (
Vec3), units/s. dt: Timestep in seconds. up: World up vector (Vec3). max_slides: Maximum collide-and-slide iterations.Returns: A :class:
CharacterMoveResult.
- __slots__¶
()
- simvx.core.physics.world.__all__¶
[‘BodyMode’, ‘Capability’, ‘CombineMode’, ‘RaycastHit’, ‘Contact’, ‘ContactPhase’, ‘ContactEvent’, ‘…