simvx.core.physics.nodes¶
Body + shape-carrier nodes for 3D physics.
The user-facing 3D body/shape node taxonomy:
- class:
CollisionShape3D– aNode3Dthat carries a :class:Shaperesource. Bodies discover their geometry by scanning their direct children for the firstCollisionShape3Dand building its shape.
- class:
PhysicsObject3D– the abstract base of every node that owns a body in the physics world: it holds the backend body handle, the layer/mask pair, the shape resolution, the collision Signals and the teardown. Annotate with this wherever the meaning is “a node with a body in the world”.
- class:
PhysicsBody3D– the one concrete body node. Its motion mode is a user-facing, inspectable, serialized :class:BodyModeProperty (STATIC | KINEMATIC | DYNAMIC), not fixed per class: one body node with a runtime-mutablemodeknob, mirroring every backend’s native model (JoltEMotionType, pymunkbody_type, Box2Db2BodyType). On enter-tree it resolves its world (:func:resolve_world), builds its shape, creates a backend body, and (for non-static bodies) registers with the tree’s handle->node sync registry. On exit-tree it unregisters, destroys the backend body, and clears its state.
- class:
CharacterBody3D– aKINEMATICbody with a swept movement helper (:meth:CharacterBody3D.move_and_slide), which is the only thing that distinguishes it from any other kinematic body.
The 2D siblings live in nodes2d.py. All of these are exported from
simvx.core.
Module Contents¶
Classes¶
Node-level collision-event payload for |
|
A |
|
Abstract base of every 3D node that owns a body in the physics world. |
|
A 3D physics body whose motion mode is a Property. |
|
A |
|
A pure sensor zone (trigger): broadphase-driven overlap detection. |
|
A force-field zone: an ADDITIVE gravity effector over the bodies it overlaps. |
|
Base class for the constraint nodes (node-agnostic carriers). |
|
Weld two bodies: lock their full relative transform (position + orientation). |
|
Pin two bodies at a single point (ball / point-to-point), rotation free. |
|
Hinge two bodies: pin at :attr: |
|
Soft distance-spring between the two body centres (compliant, not rigid). |
Data¶
API¶
- simvx.core.physics.nodes.log¶
‘getLogger(…)’
- simvx.core.physics.nodes.__all__¶
[‘BodyMode’, ‘Contact’, ‘CollisionShape3D’, ‘PhysicsObject3D’, ‘PhysicsBody3D’, ‘CharacterBody3D’, ‘…
- class simvx.core.physics.nodes.Contact[source]¶
Node-level collision-event payload for
collided/separated.Distinct from the world-level :class:
~simvx.core.physics.world.SweepHit(a query/sweep result keyed by handle): this is node-typed and built by the tree’s dispatch from a node-agnosticContactEvent. The tree fillsotherwith the peer node and reorientsnormal/velocityso they always point toward the RECEIVING body.Attributes: other: The OTHER physics object involved in the collision. point: World-space contact point. normal: Unit normal oriented TOWARD the receiving body (the separating direction). Degenerate (
Vec3(0)) onseparated(no live manifold on exit). impulse: Normal impulse magnitude the solver applied this step.0.0onseparatedand on acollidedthe solver did not push apart.Noneiff the resolved backend does not advertise :attr:~simvx.core.physics.capability.Capability.CONTACT_IMPULSE(both Jolt backends): it cannot read its solver’s applied lambda, so it reports no number rather than a guess. Check the capability once at setup rather than testing forNonemid-collision. impulse_estimate: The portable stand-in forimpulse, one formula on every backend: the impulse it would take to arrest the approach, from the pair’s masses and the difference of the two bodies’ LINEAR velocities. Always a number, and0.0onseparatedand for a pair that is not approaching. This is what a game reads where the backend cannot measure. It is deliberately not computed fromvelocitybelow, whose at-point meaning would put each backend’s choice of contact point into the number. See :attr:~simvx.core.physics.world.ContactEvent.impulse_estimate. velocity: Relative velocity ofotherw.r.t. the receiver AT THE CONTACT POINT, pre-solve. Spin counts: a wheel skidding on the ground reports the speed of its tread, which its centre is not moving at. Degenerate (Vec3(0)) onseparated. For the receiver’s own linear velocity, read it off the body.- other: PhysicsObject3D¶
None
- point: simvx.core.math.Vec3¶
None
- normal: simvx.core.math.Vec3¶
None
- impulse: float | None¶
None
- impulse_estimate: float¶
None
- velocity: simvx.core.math.Vec3¶
None
- class simvx.core.physics.nodes.CollisionShape3D(**kwargs: object)[source]¶
Bases:
simvx.core.nodes_3d.node3d.Node3DA
Node3Dthat carries a :class:Shapecollision-geometry resource.Geometry lives in a single :class:
Shaperesource (SphereShape3D/BoxShape3D) rather than in loosekind/radius/extentsfields, which keeps the node open for new shape kinds. The node is a pure data carrier: it runs no overlap or penetration tests of its own (narrowphase belongs to the physics world). It exists so a body can discover its geometry as a child node.Initialization
- shape: simvx.core.physics.shapes.Shape¶
‘Property(…)’
- pickable: bool¶
‘Property(…)’
- build_shape(world: simvx.core.physics.world.PhysicsWorld) simvx.core.physics.world.ShapeHandle[source]¶
Build this node’s shape into an opaque backend shape handle.
- property collider_scale: simvx.core.math.Vec3[source]¶
The world scale the SIMULATION applies to this collider’s geometry.
Scale lives on the BODY: a body has one scale and the seam gives a body one, so a collider child’s own local scale does not resize the collider it carries. This resolves the scale that actually reaches the physics world – the owning body’s when this node has one, and this node’s own when it does not, because a free collider (a pick target, an editor gizmo) has no body to take a scale from.
Read this, not :attr:
world_scale, anywhere a collider’s SIMULATED size matters. The two differ exactly when a collider child carries a local scale, which is the case a user provokes by trying to resize a collider by scaling the collider node.
- property pick_radius: float[source]¶
Bounding-sphere radius used for CPU picking, in WORLD units.
The shape’s local :attr:
~simvx.core.physics.shapes.Shape.bounding_radiusscaled by the largest axis of :attr:collider_scale, so a uniformly or non-uniformly scaled collider still has a conservative pick sphere and one that matches the collider the simulation actually holds.
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_enter_tree() None¶
- on_exit_tree() None¶
- on_update(dt: float) None¶
- on_fixed_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶
- class simvx.core.physics.nodes.PhysicsObject3D(**kwargs: object)[source]¶
Bases:
simvx.core.physics._pose_reconcile._PhysicsPoseReconcile,simvx.core.nodes_3d.node3d.Node3DAbstract base of every 3D node that owns a body in the physics world.
Not instantiable in practice (it creates nothing on enter-tree): it exists so that “a node with a body in the physics world” has ONE name. Every physics node family is one of these – :class:
PhysicsBody3D(which addsmode/mass/materialand the live velocity), :class:CharacterBody3D(which adds the collide-and-slide movement helper) and :class:Area3D(which adds the sensor flag and the overlap sets) – so a payload or an annotation that means “the peer body node” names this class rather than a union or anisinstancetuple.What it owns: the backend
handleand itsworld, the- Attr:
collision_layer/ :attr:collision_maskpair, the :attr:shapeconvenience collider and its resolution, the initial-pose build, the node-pose-to-body teleport of :class:_PhysicsPoseReconcile, the- Attr:
collided/ :attr:separatedSignals, and the exit-tree teardown. Subclasses own body CREATION only, because what they create differs (a typed-mode body, a kinematic body, a static sensor).
Initialization
- collision_layer: int¶
‘Bitmask(…)’
- collision_mask: int¶
‘Bitmask(…)’
- shape: simvx.core.physics.shapes.Shape | None¶
‘Property(…)’
- collided¶
‘Signal(…)’
- separated¶
‘Signal(…)’
- property handle: simvx.core.physics.world.BodyHandle | None[source]¶
This object’s opaque backend body handle, or
Noneif none was created.
- property world: simvx.core.physics.world.PhysicsWorld | None[source]¶
The :class:
PhysicsWorldthis object’s body was created in, orNone.
- on_exit_tree() None[source]¶
Retire from the tree’s handle->node map and destroy the backend body.
Retired, not dropped: destroying the body ends every contact and sensor overlap it was in, and the seam reports those closing
EXITs on the first drain after the next step. The tree holds this node’s handle->node entry until that dispatch has run, so a peer’sseparated/body_exitedcan still name the body that went away. By then this node is detached (tree,handleandworldare allNone), which is exactly what a handler needs in order to tell “it left” from “it moved away”.
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_enter_tree() None¶
- on_update(dt: float) None¶
- on_fixed_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶
- class simvx.core.physics.nodes.PhysicsBody3D(**kwargs: object)[source]¶
Bases:
simvx.core.physics.nodes.PhysicsObject3DA 3D physics body whose motion mode is a Property.
One concrete body node with a runtime-mutable :class:
BodyModemodeknob (STATIC | KINEMATIC | DYNAMIC) rather than a Static/Rigid/Kinematic class split. This mirrors every backend’s native model (JoltEMotionType, pymunkbody_type, Box2Db2BodyType): one body + a mode, runtime-flippable (sleep->static, ragdoll toggle).Per mode:
STATIC: immovable collider (floors, walls). Simulated as infinite-mass and never integrated, so it is intentionally NOT registered for scatter read-back; :attr:massis retained for a later flip toDYNAMIC.DYNAMIC: force-simulated; responds to gravity, impulses, contacts. Uses :attr:mass.KINEMATIC: code-moved via :meth:move_and_collide; immune to gravity/forces. Simulated as infinite-mass, with :attr:massretained for a later flip toDYNAMIC.
- Class:
PhysicsObject3Downs the rest of the body lifecycle: resolve world, build shape, unregister + destroy on exit._handle/_worldare cleared on exit and rebuilt on enter, so a body that re-enters the tree (re-parenting / change_scene) gets a fresh backend body, re-resolved against whateverPhysicsRootit now sits under.
Parenting, and which mode follows a moving ancestor. A STATIC body follows: move any ancestor and the collider goes with it, because nothing simulates a STATIC body and its pose is authored data. That is the mode to parent under a node whose motion is authoring rather than gameplay – an editor drag, a group positioned once at setup, a level chunk assembled under a common origin.
A DYNAMIC or KINEMATIC body does NOT follow: the simulation owns its pose, and an ancestor’s move would be erased by the next write-back anyway. So for a platform that must carry or push bodies at RUNTIME, do not parent them to it. Make the platform a KINEMATIC body and move that body itself, because a STATIC pose write carries no derived velocity and would slide riders off rather than transport them. This is a deliberate difference from engines that push the composed transform into every body type regardless of mode.
Initialization
- mode: simvx.core.physics.world.BodyMode¶
‘Property(…)’
- mass: float¶
‘Property(…)’
- material: simvx.core.physics.material.PhysicsMaterial¶
‘Property(…)’
- linear_damping: float¶
‘Property(…)’
- angular_damping: float¶
‘Property(…)’
- gravity_scale: float¶
‘Property(…)’
- continuous: bool¶
‘Property(…)’
- can_sleep: bool¶
‘Property(…)’
- property is_sleeping: bool[source]¶
True if the simulated body is asleep. False when inert.
A DYNAMIC body whose speed stays sub-threshold settles to sleep (skipped by integrate + the contact velocity solve) until a disturbance wakes it. STATIC / KINEMATIC bodies are never ‘asleep’ (they were never awake) and return False, as does an inert node (not in tree / no body).
- wake() None[source]¶
Wake this body now, whatever its sleep timer had reached.
Rarely needed, because every write that changes what the solver reads already wakes the body. Reach for it when a game knows something the solver cannot see: a scripted force is coming next frame, or a script is about to read a velocity that must be current. A no-op when the body is awake, when it is STATIC or KINEMATIC (never asleep), and when inert (not in tree / no body).
- sleep() None[source]¶
Put this body to sleep now, without waiting for it to settle.
Freezes it where it is, still a full collider, until something wakes it. Use it to park a pile a game knows is finished rather than paying for it to come to rest first. A no-op when the body is STATIC or KINEMATIC, when
- Attr:
can_sleepis False, when inert, and on a backend that does not advertise :attr:~simvx.core.physics.capability.Capability.SLEEP.
- property velocity: simvx.core.math.Vec3[source]¶
Live linear velocity (
Vec3), read/written straight to the physics world.This is runtime sim state, NOT a serialized :class:
Property: each access delegates to the physics world, so reads are current and writes take effect immediately. ReturnsVec3()(zero) when inert (not in tree / no body). A mass-free instant change is justself.velocity += dv(the getter returns a freshVec3,+=writes it back through the setter). The setter preserves the current angular velocity (spin).
- property spin: simvx.core.math.Vec3[source]¶
Live angular velocity (
Vec3, radians/s), read/written to the physics world.Companion to :attr:
velocity; same live-state, never-serialized rules. ReturnsVec3()when inert. The setter preserves linear velocity.
- push(impulse: simvx.core.math.Vec3 | collections.abc.Sequence[float], *, at: simvx.core.math.Vec3 | collections.abc.Sequence[float] | None = None) None[source]¶
Apply an instantaneous linear impulse (
v += impulse * inv_mass) NOW.Meaningful only for
mode == DYNAMIC(forces on STATIC / KINEMATIC are physically inert:inverse_mass == 0); a no-op otherwise, and a no-op when inert (not in tree / no body).atis a world-space point; the offsetr = at - centreadds an angular impulse contribution (cross(r, impulse)), replacing a separate central-vs-offset split.Args: impulse: Linear impulse (
Vec3/ sequence), N*s. at: Optional world-space application point.Noneapplies the impulse through the centre of mass (no spin).
- spin_up(angular_impulse: simvx.core.math.Vec3 | collections.abc.Sequence[float]) None[source]¶
Apply an instantaneous angular impulse (
omega += angular * inv_mass).DYNAMIC-only, inert otherwise. The built-in backend has no inertia tensor and scales by
inverse_massas a stand-in for the inverse inertia, so spin results there are physically approximate.Args: angular_impulse: Angular impulse (
Vec3/ sequence).
- add_force(force: simvx.core.math.Vec3 | collections.abc.Sequence[float], *, at: simvx.core.math.Vec3 | collections.abc.Sequence[float] | None = None) None[source]¶
Accumulate a continuous force, applied during the NEXT fixed step.
Auto-cleared each step, so to sustain a force re-call this every
on_fixed_update. DYNAMIC-only, inert otherwise.atis a world-space point; its offset adds a torque (cross(r, force)).Args: force: Linear force (
Vec3/ sequence), N. at: Optional world-space application point.Noneapplies the force through the centre of mass (no torque).
- add_torque(torque: simvx.core.math.Vec3 | collections.abc.Sequence[float]) None[source]¶
Accumulate a continuous torque, applied during the NEXT fixed step.
Auto-cleared each step like :meth:
add_force; re-call peron_fixed_updateto sustain. DYNAMIC-only, inert otherwise. On the built-in backend this uses theinverse_massinverse-inertia stand-in.Args: torque: Torque (
Vec3/ sequence), N*m.
- move_and_collide(velocity: simvx.core.math.Vec3 | collections.abc.Sequence[float], dt: float = 1.0) simvx.core.physics.world.SweepHit | None[source]¶
Move by
velocity * dt, stop at the first contact, sync, return it.Meaningful for
mode == KINEMATIC(code-driven movement); it sweeps the body’s shape and reports the first blocker.velocityis a world-space velocity (units/s); the node multiplies bydtto form the displacement handed to the physics world. After the sweep the node’s transform is synced SYNCHRONOUSLY from the simulated body, so the pose is correct the instant this call returns.Returns a :class:
~simvx.core.physics.world.SweepHitif the sweep stopped early, elseNone. AlsoNoneif the body is inert (noCollisionShape3Dchild). A sweep is a geometric query, not a solver pass, so the result carries the blocker’s handle, contact point, separating normal and guaranteed-clear distance, and no impulse.
- collision_layer: int¶
‘Bitmask(…)’
- collision_mask: int¶
‘Bitmask(…)’
- shape: simvx.core.physics.shapes.Shape | None¶
‘Property(…)’
- collided¶
‘Signal(…)’
- separated¶
‘Signal(…)’
- property handle: simvx.core.physics.world.BodyHandle | None¶
- property world: simvx.core.physics.world.PhysicsWorld | None¶
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_update(dt: float) None¶
- on_fixed_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶
- class simvx.core.physics.nodes.CharacterBody3D(**kwargs: object)[source]¶
Bases:
simvx.core.physics.nodes.PhysicsObject3DA
KINEMATICphysics body with a swept movement helper.Raycasts, shape queries and areas see it, dynamic bodies collide with it and rest on it, it produces contact events against dynamic bodies, and two characters block each other when their layers and masks mutually opt in.
- Meth:
move_and_slideis the only thing that distinguishes it from any other kinematic body. It never carries a body resting on it, but it does shove one: at the default :attr:push_factorof1.0a blocking dynamic body takes the arrest impulse of the pair, so it leaves at no more than the speed the character was walking, scaled by the mass ratio. Set the factor to0.0and a dynamic body stops the sweep exactly as a static one does. The contacts it resolved are handed back in :attr:collisionseither way, so game code can apply its own impulses.
Geometry follows the standard order (:attr:
shapeProperty if set, else the first direct-child :class:CollisionShape3D, else inert). It is deliberately NOT registered for the dynamic auto bulk-sync; :meth:move_and_slidesyncs the node transform synchronously. :attr:velocityis set by game logic each frame and written back (deflected) after each move.- Attr:
velocityis a plain instance attribute, deliberately NOT the world-backed :attr:PhysicsBody3D.velocityproperty: the simulated body’s velocity must stay zero, or the world’s integrator would re-apply the motion- Meth:
move_and_slidehas already performed.
Initialization
- slope_limit: float¶
‘Property(…)’
- step_height: float¶
‘Property(…)’
- max_slides: int¶
‘Property(…)’
- skin_width: float¶
‘Property(…)’
- mass: float¶
‘Property(…)’
- push_factor: float¶
‘Property(…)’
- move_and_slide(dt: float) None[source]¶
Collide-and-slide by
self.velocity * dt, then sync the transform.Runs the shared policy in :mod:
simvx.core.physics.slideagainst the simulated body. Writes the deflected post-slide velocity back to :attr:velocity, caches floor/wall/ceiling state + :attr:floor_normal+- Attr:
collisions, shoves each non-floor blocking contact if- Attr:
push_factoris non-zero, and syncs the node’s transform SYNCHRONOUSLY from the move (characters are not part of the auto bulk-sync). No-op if the body is inert (no collider).
- collision_layer: int¶
‘Bitmask(…)’
- collision_mask: int¶
‘Bitmask(…)’
- shape: simvx.core.physics.shapes.Shape | None¶
‘Property(…)’
- collided¶
‘Signal(…)’
- separated¶
‘Signal(…)’
- property handle: simvx.core.physics.world.BodyHandle | None¶
- property world: simvx.core.physics.world.PhysicsWorld | None¶
- on_exit_tree() None¶
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_update(dt: float) None¶
- on_fixed_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶
- class simvx.core.physics.nodes.Area3D(**kwargs: object)[source]¶
Bases:
simvx.core.physics.nodes.PhysicsObject3DA pure sensor zone (trigger): broadphase-driven overlap detection.
Owns a SENSOR body in the physics world (a flag on the body, not a separate class). A sensor participates in the broadphase but is excluded from collision resolution, so an Area3D detects bodies/areas passing through it without pushing them. Detection is ONE-DIRECTIONAL: the area sees another body iff
area.collision_mask & other.collision_layer(the observer decides; the other body’s mask is irrelevant), checked per detector independently for sensor-vs-sensor.Detection is never a per-frame
find_alltree scan: overlap edges arrive as a buffered, deferred event stream drained by the tree after each step, and the live overlap sets are maintained from those edges, so :meth:get_overlapping_bodies/ :meth:get_overlapping_areasand the Signals are always consistent with the last dispatched step.Geometry follows the resolution order: the :attr:
shapeconvenience Property if set (it WINS over a child), else the first direct child :class:CollisionShape3D, else inert. The sensor body is STATIC (a zone is typically fixed); a sensor is excluded from response regardless of mode.What it detects is the same everywhere apart from one exception. An awake DYNAMIC or KINEMATIC body, a SLEEPING one and a STATIC one are all reported on every backend, the last of them where
- Attr:
~simvx.core.physics.capability.Capability.SENSOR_DETECTS_STATICis advertised, which today is every backend. The exception is an area whose geometry is a :class:~simvx.core.physics.shapes.ConcaveMeshShape3Don Jolt: Jolt requires a mesh collider to sit on a static body, and a static body never drives the pair search, so such an area sees only what is awake and movable. Trigger volumes are boxes and spheres in practice; give the area convex geometry – at creation or by swapping it in later – and the exception is gone.
shape/collision_layer/collision_maskare live on an area exactly as they are on a body. :attr:monitoringis the one create-time knob: it decides whether a sensor body exists at all, so switching it after enter needs a re-enter (remove and re-add the node).Initialization
- monitoring: bool¶
‘Property(…)’
- shape: simvx.core.physics.shapes.Shape | None¶
‘Property(…)’
- body_entered¶
‘Signal(…)’
- body_exited¶
‘Signal(…)’
- area_entered¶
‘Signal(…)’
- area_exited¶
‘Signal(…)’
- get_overlapping_bodies(*, group=None, type=None)[source]¶
Bodies currently overlapping this area (live, as of the last step).
A peer destroyed / removed from the tree mid-overlap is dropped from the maintained set by the overlap
EXITthe seam reports for it, on the step after it goes (:attr:body_exitedfires with it at the same moment). Detached peers (handle is None) are also filtered here, which covers the one step before that event lands and a body carried out with a world that this tree will not step again.The optional filters narrow the result without a tree scan: they test the already-maintained overlap set. Both are ANDed when given:
Args: group: When set, keep only bodies that belong to this SceneTree group (
body.is_in_group(group)). The canonical “what of kind X am I touching?” query (e.g.area.get_overlapping_bodies(group="mobs")). type: When set, keep only bodies that are instances of this :class:PhysicsObject3Dsubclass (isinstance(body, type)), and the result is typed as a list of THAT class. A character is an ordinary kinematic body, so it appears here like any other; passtype=PhysicsBody3Dwhen the caller needs the force/mass API a character does not have.Returns: The overlapping bodies (live, detached peers filtered) matching every supplied filter, in arbitrary order.
- get_overlapping_areas() list[simvx.core.physics.nodes.Area3D][source]¶
Other areas currently overlapping this area (live, as of the last step).
Detached peers (a destroyed / removed area,
handle is None) are filtered here for the same reason as :meth:get_overlapping_bodies.
- collision_layer: int¶
‘Bitmask(…)’
- collision_mask: int¶
‘Bitmask(…)’
- collided¶
‘Signal(…)’
- separated¶
‘Signal(…)’
- property handle: simvx.core.physics.world.BodyHandle | None¶
- property world: simvx.core.physics.world.PhysicsWorld | None¶
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_update(dt: float) None¶
- on_fixed_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶
- class simvx.core.physics.nodes.GravityArea3D(**kwargs: object)[source]¶
Bases:
simvx.core.physics.nodes.Area3DA force-field zone: an ADDITIVE gravity effector over the bodies it overlaps.
Following the Unreal
PhysicsVolumesplit, gravity is NOT baked into- Class:
Area3D: a plainArea3Dstays a zero-cost pure sensor, and this separate effector node consumes its overlap set.GravityArea3Dinherits the entire sensor mechanism unchanged (the STATIC sensor body, the broadphase-driven buffered overlap stream, the maintained overlap sets,- Meth:
get_overlapping_bodies/ :meth:get_overlapping_areas, thebody_entered/body_exited/area_entered/area_exitedSignals, and thecollision_layer/collision_mask/monitoring/shapeProperties). It does NOT override the lifecycle: it is still a sensor that detects without colliding.
What it adds is an :meth:
on_fixed_updatehandler that, each fixed step, reads :meth:get_overlapping_bodiesand applies a field to every DYNAMIC body inside it. The field is additive on top of world gravity, never a replacement for it: the world still applies its own gravity insidestep, and this re-applied force composes with it. Two independent components that SUM when both are configured:Directional (:attr:
gravity): a uniform acceleration vector applied regardless of body mass, exactly like world gravity (e.g.Vec3(0, 9.81, 0)for an anti-gravity lift, or a sideways wind-as-accel).Point (:attr:
point_gravity/ :attr:point_strength): a CONSTANT (distance-independent) acceleration of magnitude :attr:point_strengthtoward the area centre (:attr:world_position). There is no inverse-distance / inverse-square falloff, no per-area damping override and no gravity-replacement mode.
The acceleration is converted to a force via
add_force(mass * accel): the integrator divides by mass, so the net effect is mass-INDEPENDENT, exactly like gravity.add_forceis auto-cleared each step, so the field is freshly re-applied every fixed step and consumed exactly once by the immediately followingworld.step(no carry-over, no double-apply). When the area is empty the loop is a no-op (zero cost);monitoring=Falsemakes the area inert (empty overlap set) so the field naturally turns off.Moving-emitter caveat (shared with :class:
Area3D): the sensor body pose is read at enter-tree, so moving aGravityArea3Dat runtime does not move the zone it detects and pulls in.Initialization
- gravity: simvx.core.math.Vec3¶
‘Property(…)’
- point_gravity: bool¶
‘Property(…)’
- point_strength: float¶
‘Property(…)’
- on_fixed_update(dt: float) None[source]¶
Apply the additive gravity field to every DYNAMIC overlapping body.
Runs BEFORE
world.step(the tree drives nodeon_fixed_updatefirst, then steps the worlds), accumulating a fresh force consumed by that step.dtis accepted to match the hook signature but is NOT used to scale:add_forceis a continuous force the integrator consumes over the step, not an impulse.A :class:
CharacterBody3Doverlapping the area IS in the overlap set (it is an ordinary kinematic body), but it is never force-driven: it is not a- Class:
PhysicsBody3Dand so has no :meth:~PhysicsBody3D.add_force, and the :attr:~CharacterBody3D.massit does carry is read only by its own push arithmetic, never by an integrator. It is position-driven by- Meth:
CharacterBody3D.move_and_slideinstead, and a game that wants a gravity zone to move one applies the field to itsvelocityitself. Thetype=filter is what excludes it, so a character in a gravity zone is simply ignored rather than an error.
- monitoring: bool¶
‘Property(…)’
- shape: simvx.core.physics.shapes.Shape | None¶
‘Property(…)’
- body_entered¶
‘Signal(…)’
- body_exited¶
‘Signal(…)’
- area_entered¶
‘Signal(…)’
- area_exited¶
‘Signal(…)’
- get_overlapping_bodies(*, group=None, type=None)¶
- get_overlapping_areas() list[simvx.core.physics.nodes.Area3D]¶
- on_enter_tree() None¶
- on_exit_tree() None¶
- collision_layer: int¶
‘Bitmask(…)’
- collision_mask: int¶
‘Bitmask(…)’
- collided¶
‘Signal(…)’
- separated¶
‘Signal(…)’
- property handle: simvx.core.physics.world.BodyHandle | None¶
- property world: simvx.core.physics.world.PhysicsWorld | None¶
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶
- class simvx.core.physics.nodes.Joint3D(*, body_a: simvx.core.physics.nodes.PhysicsBody3D | None = None, body_b: simvx.core.physics.nodes.PhysicsBody3D | None = None, **kwargs: object)[source]¶
Bases:
simvx.core.nodes_3d.node3d.Node3DBase class for the constraint nodes (node-agnostic carriers).
A
Joint3Dconstrains two :class:PhysicsBody3Dinstances in the physics world. It is a thin carrier: it holds the two body references plus its per-joint Properties and owns the constraint lifecycle (create on enter-tree, remove on exit-tree), exactly mirroring how :class:PhysicsBody3Downs its body.Body references (:attr:
body_a/ :attr:body_b) are PLAIN instance attributes, NOT :class:Propertydescriptors: constraints are keyed by body handle rather than by node, and cross-scene references are plain Python imports. Set them programmatically, e.g.joint.body_a = self.crate; joint.body_b = self.anchor, or via the constructor:PinJoint3D(body_a=..., body_b=..., anchor=...).Resolution at :meth:
on_enter_tree:If either body reference is
Nonethe joint stays INERT (no constraint is created), logs at debug, and returns: a one-body joint is a no-op, not a crash (mirrors the body’s no-shape inert path).Each body’s handle (
body.handle) is resolved. If EITHER isNone(the body is not yet in the tree, or is inert with no collider) the joint stays inert: it never half-creates.SAME-WORLD requirement:
body_a.worldandbody_b.worldMUST be the identical :class:PhysicsWorld(a joint is a within-world constraint; body handles are world-local). A mismatch (or eitherNoneafter handles resolved) raises :class:ValueError: this is a genuine programming error, unlike a missing body (a transient tree-order condition), which is the soft-inert case above.
LIFECYCLE LIMITATION (same as body shape / mass / layers): a joint reads its Properties and resolves its bodies ONCE at enter-tree. Changing a parameter or a body reference after enter requires a re-enter. A joint declared BEFORE its bodies resolves to inert and silently never constrains, so ALWAYS add a joint AFTER both of its bodies (or re-enter it).
Initialization
- property joint: simvx.core.physics.world.JointHandle | None[source]¶
This joint’s opaque backend handle, or
Noneif inert / not created.
- property world: simvx.core.physics.world.PhysicsWorld | None[source]¶
The :class:
PhysicsWorldthis joint was created in, orNone.
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_update(dt: float) None¶
- on_fixed_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶
- class simvx.core.physics.nodes.FixedJoint3D(*, body_a: simvx.core.physics.nodes.PhysicsBody3D | None = None, body_b: simvx.core.physics.nodes.PhysicsBody3D | None = None, **kwargs: object)[source]¶
Bases:
simvx.core.physics.nodes.Joint3DWeld two bodies: lock their full relative transform (position + orientation).
Captures the current relative pose of
body_binbody_a’s frame at enter-tree and holds it in that frame, so the two bodies move as one rigid assembly and the assembly swings round whenbody_aturns. No anchor / axis Properties: the constraint captures the current relative pose at create.Built-in backend caveat: it has NO inertia tensor, so the angular lock uses
inverse_massas the inverse-inertia scalar (a long thin or off-centre weld rotates too easily), and convergence is a few iterations (a long weld chain sags slightly). Use near-uniform masses, or the Jolt backend for precise articulated mechanisms.Initialization
- property joint: simvx.core.physics.world.JointHandle | None¶
- property world: simvx.core.physics.world.PhysicsWorld | None¶
- on_enter_tree() None¶
- on_exit_tree() None¶
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_update(dt: float) None¶
- on_fixed_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶
- class simvx.core.physics.nodes.PinJoint3D(*, body_a: simvx.core.physics.nodes.PhysicsBody3D | None = None, body_b: simvx.core.physics.nodes.PhysicsBody3D | None = None, **kwargs: object)[source]¶
Bases:
simvx.core.physics.nodes.Joint3DPin two bodies at a single point (ball / point-to-point), rotation free.
The two bodies cannot separate at :attr:
anchorbut rotate freely about it. The anchor is captured into each body’s own frame, so a pin on a spinning body orbits with it.Built-in backend caveat: angular cross-coupling uses the
inverse_massinverse-inertia scalar (there is no inertia tensor), and the solver runs only a few iterations, so a long chain sags slightly.Initialization
- anchor: simvx.core.math.Vec3¶
‘Property(…)’
- property joint: simvx.core.physics.world.JointHandle | None¶
- property world: simvx.core.physics.world.PhysicsWorld | None¶
- on_enter_tree() None¶
- on_exit_tree() None¶
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_update(dt: float) None¶
- on_fixed_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶
- class simvx.core.physics.nodes.HingeJoint3D(*, body_a: simvx.core.physics.nodes.PhysicsBody3D | None = None, body_b: simvx.core.physics.nodes.PhysicsBody3D | None = None, **kwargs: object)[source]¶
Bases:
simvx.core.physics.nodes.Joint3DHinge two bodies: pin at :attr:
anchor+ one free rotational DOF about :attr:axis.A point constraint at the anchor PLUS an angular lock removing the two off-axis rotational DOF, leaving free rotation only about
axis. The axis is captured into each body’s own frame, so a door hung on a post that is itself turning keeps swinging about the post. There are no motors and no angular limits.Built-in backend caveat: the same
inverse_massinverse-inertia-scalar stand-in as :class:PinJoint3D, and only a few solver iterations.Initialization
- anchor: simvx.core.math.Vec3¶
‘Property(…)’
- axis: simvx.core.math.Vec3¶
‘Property(…)’
- property joint: simvx.core.physics.world.JointHandle | None¶
- property world: simvx.core.physics.world.PhysicsWorld | None¶
- on_enter_tree() None¶
- on_exit_tree() None¶
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_update(dt: float) None¶
- on_fixed_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶
- class simvx.core.physics.nodes.SpringJoint3D(*, body_a: simvx.core.physics.nodes.PhysicsBody3D | None = None, body_b: simvx.core.physics.nodes.PhysicsBody3D | None = None, **kwargs: object)[source]¶
Bases:
simvx.core.physics.nodes.Joint3DSoft distance-spring between the two body centres (compliant, not rigid).
Pulls the two body centres toward :attr:
rest_lengthapart with- Attr:
stiffness(N/m) and :attr:damping(N*s/m). Intentionally compliant: no position correction.
Acts between the two centres of mass rather than between anchors as Pin and Hinge do. On the built-in backend the explicit soft-impulse integration can oscillate / overshoot when
stiffnessis large relative to the fixeddt; nothing is silently clamped. A stiff spring needs a smallerdtor the Jolt backend.Initialization
- rest_length: float¶
‘Property(…)’
- stiffness: float¶
‘Property(…)’
- damping: float¶
‘Property(…)’
- property joint: simvx.core.physics.world.JointHandle | None¶
- property world: simvx.core.physics.world.PhysicsWorld | None¶
- on_enter_tree() None¶
- on_exit_tree() None¶
- position¶
‘_SpatialVecProperty(…)’
- rotation¶
‘Property(…)’
- scale¶
‘_SpatialVecProperty(…)’
- render_layer¶
‘Property(…)’
- property rotation_degrees: simvx.core.math.types.Vec3¶
- property world_position: simvx.core.math.types.Vec3¶
- property world_rotation: simvx.core.math.types.Quat¶
- property world_scale: simvx.core.math.types.Vec3¶
- property forward: simvx.core.math.types.Vec3¶
- property right: simvx.core.math.types.Vec3¶
- property up: simvx.core.math.types.Vec3¶
- translate(offset: tuple[float, float, float] | numpy.ndarray)¶
- translate_global(offset: tuple[float, float, float] | numpy.ndarray)¶
- rotate(axis: tuple[float, float, float] | numpy.ndarray, angle: float)¶
- rotate_x(angle: float)¶
- rotate_y(angle: float)¶
- rotate_z(angle: float)¶
- look_at(target: tuple[float, float, float] | numpy.ndarray, up=None)¶
- face_along(forward: tuple[float, float, float] | numpy.ndarray, up: tuple[float, float, float] | numpy.ndarray | None = None) None¶
- set_render_layer(index: int, enabled: bool = True) None¶
- is_on_render_layer(index: int) bool¶
- wrap_bounds(bounds: tuple[float, float, float] | numpy.ndarray, margin: float = 1.0)¶
- strict_errors: ClassVar[bool]¶
True
- dev_checks: ClassVar[bool]¶
None
- script_error_raised¶
‘Signal(…)’
- dynamic: bool¶
False
- visible¶
‘Property(…)’
- update_mode¶
‘Property(…)’
- __properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]¶
None
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property visible_in_tree: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.T) simvx.core.node.T¶
- remove_child(node: simvx.core.node.Node) None¶
- reparent(new_parent: simvx.core.node.Node)¶
- node_at(path, default=_NO_DEFAULT)¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- expect(target, *, direct: bool = False)¶
- ancestor(target)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- property is_scene_root: bool¶
- add_to_group(group: str)¶
- remove_from_group(group: str)¶
- is_in_group(group: str) bool¶
- on_ready() None¶
- on_update(dt: float) None¶
- on_fixed_update(dt: float) None¶
- on_draw(renderer) None¶
- on_picked(event: simvx.core.events.InputEvent) None¶
- on_unhandled_input(event: simvx.core.events.TreeInputEvent) None¶
- start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle¶
- stop_coroutine(gen_or_handle)¶
- queue_redraw() None¶
- property render_dirty: bool¶
- clear_children()¶
- destroy()¶
- property destroying: bool¶
- call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None¶
- property app¶
- property tree: simvx.core.scene_tree.SceneTree¶
- property physics¶
- property physics_2d¶
- __getitem__(key: str)¶
- classmethod get_properties() dict[str, simvx.core.descriptors.Property]¶
- __repr__()¶