simvx.core.physics.nodes¶
Role¶
This is the user-facing 3D body/shape node taxonomy on the new transport seam:
- class:
CollisionShape3D– aNode3Dthat carries a :class:Shaperesource. Bodies discover their geometry by scanning their direct children for the firstCollisionShape3Dand building its shape.
- 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 seam (JoltEMotionType, pymunkbody_type, Box2Db2BodyType). On enter-tree it resolves its world (:func:resolve_world), builds its shape, creates a seam body, and (for non-static bodies) registers with the tree’s handle->node sync registry. On exit-tree it unregisters, destroys the seam body, and clears its state.
- class:
CharacterBody3D– a swept-collision controller (NOT a body), distinct from the :class:PhysicsBody3Dfamily.
These nodes deliberately reuse the canonical names CollisionShape3D /
PhysicsBody3D / CharacterBody3D but live ONLY here. They are NOT wired
into any facade (simvx.core / simvx.core.physics still export the OLD
body system); tests import them via this module path. The facade flip + removal
of the old nodes is a later stage; 2D is later still.
Body + shape-carrier nodes for the new physics seam (3D only).
Module Contents¶
Classes¶
Node-level collision-event payload for |
|
A |
|
A 3D physics body on the new seam; its motion mode is a Property. |
|
A character controller (CharacterVirtual-style), distinct from a rigid body. |
|
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 four T1c 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’, ‘PhysicsBody3D’, ‘CharacterBody3D’, ‘Area3D’, ‘GravityAr…
- class simvx.core.physics.nodes.Contact[source]¶
Node-level collision-event payload for
collided/separated.Distinct from the seam-level
world.Contact(a query/sweep TOI result): 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 body 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 applied this step.0onseparatedand on anenterthe solver did not push apart. velocity: Relative velocity ofotherw.r.t. the receiver at the contact, pre-solve. Degenerate (Vec3(0)) onseparated.- other: PhysicsBody3D¶
None
- point: simvx.core.math.Vec3¶
None
- normal: simvx.core.math.Vec3¶
None
- impulse: float¶
None
- velocity: simvx.core.math.Vec3¶
None
- class simvx.core.physics.nodes.CollisionShape3D(**kwargs)[source]¶
Bases:
simvx.core.nodes_3d.node3d.Node3DA
Node3Dthat carries a :class:Shapecollision-geometry resource.Unlike the old
physics_nodes.CollisionShape3D(which storedkind/radius/extentsfields), geometry here lives in a single- Class:
Shaperesource (SphereShape3D/BoxShape3D), which keeps the node open for new shape kinds. This stage it is a pure data carrier: it has no overlap/penetration tests (the seam does narrowphase). 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 seam shape handle.
- 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 node’s largest world-scale axis, so a uniformly or non-uniformly scaled collider still has a conservative pick sphere.
- 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
- script_error_raised¶
‘Signal(…)’
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property update_mode: simvx.core.descriptors.UpdateMode¶
- property visible: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.Node) simvx.core.node.Node¶
- remove_child(node: simvx.core.node.Node)¶
- reparent(new_parent: simvx.core.node.Node)¶
- get_node(path: str) simvx.core.node.Node¶
- get_node_or_none(path: str) simvx.core.node.Node | None¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- 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)¶
- clear_children()¶
- destroy()¶
- 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.nodes_3d.node3d.Node3DA 3D physics body on the new seam; its motion mode is a Property.
One concrete body node with a runtime-mutable :class:
BodyModemodeknob (STATIC | KINEMATIC | DYNAMIC), replacing the old Static/Rigid/Kinematic class split. This mirrors every backend’s seam (JoltEMotionType, pymunkbody_type, Box2Db2BodyType): one body + a mode, runtime-flippable (sleep->static, ragdoll toggle).Per mode:
STATIC: immovable collider (floors, walls). Infinite mass; never integrated, so it is intentionally NOT registered for scatter read-back.DYNAMIC: force-simulated; responds to gravity, impulses, contacts. Uses :attr:mass.KINEMATIC: code-moved via :meth:move_and_collide; immune to gravity/forces, pushes dynamic bodies.massis ignored (infinite).
The base owns the seam lifecycle: resolve world, build shape, create body on enter; 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 seam body, re-resolved against whateverPhysicsRootit now sits under.Initialization
- mode: simvx.core.physics.world.BodyMode¶
‘Property(…)’
- mass: float¶
‘Property(…)’
- collision_layer: int¶
‘Bitmask(…)’
- collision_mask: int¶
‘Bitmask(…)’
- shape: simvx.core.physics.shapes.Shape | None¶
‘Property(…)’
- material: simvx.core.physics.material.PhysicsMaterial¶
‘Property(…)’
- continuous: bool¶
‘Property(…)’
- collided¶
‘Signal(…)’
- separated¶
‘Signal(…)’
- property handle: simvx.core.physics.world.BodyHandle | None[source]¶
This body’s opaque seam handle, or
Noneif no body was created.
- property world: simvx.core.physics.world.PhysicsWorld | None[source]¶
The :class:
PhysicsWorldthis body was created in, orNone.
- property is_sleeping: bool[source]¶
True if the seam body is asleep (basic-tier sleeping). 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 seam body).
- property velocity: simvx.core.math.Vec3[source]¶
Live linear velocity (
Vec3), read/written straight to the seam.This is runtime sim state, NOT a serialized :class:
Property: each access delegates to the seam, so reads are current and writes take effect immediately. ReturnsVec3()(zero) when inert (not in tree / no seam 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 seam.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 seam 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. Basic tier scales by
inverse_massas a stand-in for the inverse inertia tensor (the basic backend has no inertia tensor), so results are physically approximate: real inertia is a Tier-2 / Jolt concern.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(design S7). 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. Basic tier 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.nodes.Contact | None[source]¶
Move by
velocity * dt, stop at the first contact, sync, return it.Meaningful for
mode == KINEMATIC(code-driven movement); it sweeps the seam 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 seam. After the sweep the node’s transform is synced SYNCHRONOUSLY from the seam, so the pose is correct the instant this call returns.Returns the :class:
Contactif the sweep stopped early, elseNone. ReturnsNoneif the body is inert (noCollisionShape3Dchild).
- 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
- script_error_raised¶
‘Signal(…)’
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property update_mode: simvx.core.descriptors.UpdateMode¶
- property visible: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.Node) simvx.core.node.Node¶
- remove_child(node: simvx.core.node.Node)¶
- reparent(new_parent: simvx.core.node.Node)¶
- get_node(path: str) simvx.core.node.Node¶
- get_node_or_none(path: str) simvx.core.node.Node | None¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- 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)¶
- clear_children()¶
- destroy()¶
- 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.nodes_3d.node3d.Node3DA character controller (CharacterVirtual-style), distinct from a rigid body.
Unlike a :class:
PhysicsBody3Dit holds aCharacterHandle(not aBodyHandle), mirroring that a character collides against the world but is never force-simulated. It manages its own seam lifecycle and is deliberately NOT registered for the dynamic auto bulk-sync; :meth:move_and_slidesyncs the node transform synchronously.Geometry is supplied by a direct-child :class:
CollisionShape3D, matching the Stage 3a body pattern.velocityis set by game logic each frame and is written back (deflected) after each :meth:move_and_slide.Initialization
- slope_limit: float¶
‘Property(…)’
- step_height: float¶
‘Property(…)’
- max_slides: int¶
‘Property(…)’
- skin_width: float¶
‘Property(…)’
- collision_layer: int¶
‘Bitmask(…)’
- collision_mask: int¶
‘Bitmask(…)’
- shape: simvx.core.physics.shapes.Shape | None¶
‘Property(…)’
- property character: simvx.core.physics.world.CharacterHandle | None[source]¶
This node’s opaque character handle, or
Noneif inert.
- property world: simvx.core.physics.world.PhysicsWorld | None[source]¶
The :class:
PhysicsWorldthis character was created in, orNone.
- move_and_slide(dt: float) None[source]¶
Collide-and-slide by
self.velocity * dt, then sync the transform.Writes the deflected post-slide velocity back to :attr:
velocity, caches floor/wall/ceiling state + :attr:floor_normal, and syncs the node’s transform SYNCHRONOUSLY from the seam (characters are not part of the auto bulk-sync). No-op if the body is inert (noCollisionShape3Dchild).
- 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
- script_error_raised¶
‘Signal(…)’
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property update_mode: simvx.core.descriptors.UpdateMode¶
- property visible: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.Node) simvx.core.node.Node¶
- remove_child(node: simvx.core.node.Node)¶
- reparent(new_parent: simvx.core.node.Node)¶
- get_node(path: str) simvx.core.node.Node¶
- get_node_or_none(path: str) simvx.core.node.Node | None¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- 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)¶
- clear_children()¶
- destroy()¶
- 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.nodes_3d.node3d.Node3DA pure sensor zone (trigger): broadphase-driven overlap detection.
Owns a SENSOR body on the new seam (a flag on the body, not a separate class: Design S3/S4). 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 (Design S6): 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.Unlike the old
Area3Dthis is NEVER a per-framefind_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 Design S3 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.Lifecycle limitations (this stage, parity with :class:
PhysicsBody3D):monitoring/shape/collision_layer/collision_maskare read at enter-tree, so changing them after enter requires a re-enter.Initialization
- collision_layer: int¶
‘Bitmask(…)’
- collision_mask: int¶
‘Bitmask(…)’
- monitoring: bool¶
‘Property(…)’
- shape: simvx.core.physics.shapes.Shape | None¶
‘Property(…)’
- body_entered¶
‘Signal(…)’
- body_exited¶
‘Signal(…)’
- area_entered¶
‘Signal(…)’
- area_exited¶
‘Signal(…)’
- property handle: simvx.core.physics.world.BodyHandle | None[source]¶
This area’s opaque sensor-body handle, or
Noneif inert.
- property world: simvx.core.physics.world.PhysicsWorld | None[source]¶
The :class:
PhysicsWorldthis area’s sensor was created in, orNone.
- get_overlapping_bodies(*, group: str | None = None, type: simvx.core.physics.nodes.Area3D.get_overlapping_bodies.type[simvx.core.physics.nodes.PhysicsBody3D] | None = None) list[simvx.core.physics.nodes.PhysicsBody3D][source]¶
Bodies currently overlapping this area (live, as of the last step).
A peer destroyed / removed from the tree mid-overlap is purged silently by the backend (no dangling EXIT, parity with the contact stream), so the tree never discards it from the maintained set. Filter such off-seam peers (
handle is None) here so polling never reports a body that has left the simulation, matching the backend’s own purge condition.The optional filters narrow the result without a tree scan (they test the already-maintained overlap set): this is the durable, broadphase-backed replacement for the old arcade
CharacterBody.get_overlapping(group=, body_type=)poll. 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:PhysicsBody3Dsubclass (isinstance(body, type)).Returns: The overlapping bodies (live, off-seam 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).
Off-seam peers (a destroyed / removed area,
handle is None) are filtered here for the same reason as :meth:get_overlapping_bodies.
- 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
- script_error_raised¶
‘Signal(…)’
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property update_mode: simvx.core.descriptors.UpdateMode¶
- property visible: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.Node) simvx.core.node.Node¶
- remove_child(node: simvx.core.node.Node)¶
- reparent(new_parent: simvx.core.node.Node)¶
- get_node(path: str) simvx.core.node.Node¶
- get_node_or_none(path: str) simvx.core.node.Node | None¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- 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)¶
- clear_children()¶
- destroy()¶
- 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.
Per Design S4 (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 an override this stage): 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). Inverse-distance / inverse-square falloff, independently-overridable damping, and a full gravity-OVERRIDE (replace) mode are explicit later refinements.
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 (parity with :class:
Area3D, not a regression): the sensor body pose is read at enter-tree, so aGravityArea3Dmoved at runtime is out of scope this stage.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.
- collision_layer: int¶
‘Bitmask(…)’
- collision_mask: int¶
‘Bitmask(…)’
- monitoring: bool¶
‘Property(…)’
- shape: simvx.core.physics.shapes.Shape | None¶
‘Property(…)’
- body_entered¶
‘Signal(…)’
- body_exited¶
‘Signal(…)’
- area_entered¶
‘Signal(…)’
- area_exited¶
‘Signal(…)’
- property handle: simvx.core.physics.world.BodyHandle | None¶
- property world: simvx.core.physics.world.PhysicsWorld | None¶
- get_overlapping_bodies(*, group: str | None = None, type: simvx.core.physics.nodes.Area3D.get_overlapping_bodies.type[simvx.core.physics.nodes.PhysicsBody3D] | None = None) list[simvx.core.physics.nodes.PhysicsBody3D]¶
- get_overlapping_areas() list[simvx.core.physics.nodes.Area3D]¶
- 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
- script_error_raised¶
‘Signal(…)’
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property update_mode: simvx.core.descriptors.UpdateMode¶
- property visible: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.Node) simvx.core.node.Node¶
- remove_child(node: simvx.core.node.Node)¶
- reparent(new_parent: simvx.core.node.Node)¶
- get_node(path: str) simvx.core.node.Node¶
- get_node_or_none(path: str) simvx.core.node.Node | None¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- 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)¶
- clear_children()¶
- destroy()¶
- 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 four T1c constraint nodes (node-agnostic carriers).
A
Joint3Dconstrains two :class:PhysicsBody3Dinstances via the physics seam. It is a thin carrier: it holds the two body references plus its per-joint Properties and owns the seam-constraint lifecycle (create on enter-tree, remove on exit-tree), exactly mirroring how :class:PhysicsBody3Downs its seam body.Body references (:attr:
body_a/ :attr:body_b) are PLAIN instance attributes, NOT :class:Propertydescriptors: a serialised scene-graph node reference is out of scope for this seam stage (the seam is handle-keyed, not node-keyed; scene-to-scene refs are plain Python imports per the codebase rule). 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 seam joint), 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 seam 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; the seam 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 (parity with body shape / mass / layers): a joint reads its Properties and resolves its bodies ONCE at enter-tree. Changing a param or a body reference after enter requires a re-enter (an
on_changere-create hook is a follow-on, not T1c). 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). A robust deferred-resolve is a follow-on.Initialization
- property joint: simvx.core.physics.world.JointHandle | None[source]¶
This joint’s opaque seam 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
- script_error_raised¶
‘Signal(…)’
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property update_mode: simvx.core.descriptors.UpdateMode¶
- property visible: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.Node) simvx.core.node.Node¶
- remove_child(node: simvx.core.node.Node)¶
- reparent(new_parent: simvx.core.node.Node)¶
- get_node(path: str) simvx.core.node.Node¶
- get_node_or_none(path: str) simvx.core.node.Node | None¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- 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)¶
- clear_children()¶
- destroy()¶
- 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, so the two bodies move as one rigid assembly. No anchor / axis Properties: the constraint captures the current relative pose at create.Basic-tier honesty: the backend 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 versus a real solver), and convergence is a few iterations (a long weld chain sags slightly). Use near-uniform masses; precise articulated mechanisms are a Jolt concern.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
- script_error_raised¶
‘Signal(…)’
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property update_mode: simvx.core.descriptors.UpdateMode¶
- property visible: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.Node) simvx.core.node.Node¶
- remove_child(node: simvx.core.node.Node)¶
- reparent(new_parent: simvx.core.node.Node)¶
- get_node(path: str) simvx.core.node.Node¶
- get_node_or_none(path: str) simvx.core.node.Node | None¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- 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)¶
- clear_children()¶
- destroy()¶
- 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.Basic-tier honesty: the anchor offset does NOT rotate with the bodies (re- derived each step in world axes), so a pin on a spinning body drifts; angular cross-coupling uses the
inverse_massinverse-inertia scalar. A few iterations of convergence.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
- script_error_raised¶
‘Signal(…)’
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property update_mode: simvx.core.descriptors.UpdateMode¶
- property visible: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.Node) simvx.core.node.Node¶
- remove_child(node: simvx.core.node.Node)¶
- reparent(new_parent: simvx.core.node.Node)¶
- get_node(path: str) simvx.core.node.Node¶
- get_node_or_none(path: str) simvx.core.node.Node | None¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- 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)¶
- clear_children()¶
- destroy()¶
- 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. 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 :class:PinJoint3D; a few iterations of convergence.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
- script_error_raised¶
‘Signal(…)’
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property update_mode: simvx.core.descriptors.UpdateMode¶
- property visible: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.Node) simvx.core.node.Node¶
- remove_child(node: simvx.core.node.Node)¶
- reparent(new_parent: simvx.core.node.Node)¶
- get_node(path: str) simvx.core.node.Node¶
- get_node_or_none(path: str) simvx.core.node.Node | None¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- 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)¶
- clear_children()¶
- destroy()¶
- 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.
Basic-tier honesty: uses the two centres of mass (Pin / Hinge use anchors, Spring uses COMs for simplicity). 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
- script_error_raised¶
‘Signal(…)’
- classmethod __init_subclass__(**kwargs)¶
- property name: str¶
- property update_mode: simvx.core.descriptors.UpdateMode¶
- property visible: bool¶
- reset_error() None¶
- add_child(node: simvx.core.node.Node) simvx.core.node.Node¶
- remove_child(node: simvx.core.node.Node)¶
- reparent(new_parent: simvx.core.node.Node)¶
- get_node(path: str) simvx.core.node.Node¶
- get_node_or_none(path: str) simvx.core.node.Node | None¶
- find(target, *, direct: bool = False)¶
- find_all(target, *, direct: bool = False)¶
- walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]¶
- property path: str¶
- 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)¶
- clear_children()¶
- destroy()¶
- 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__()¶