Source code for box2d.world

# src/box3d/world.py

from ._box2d import lib, ffi
from .body import BodyBuilder, Body
from .joint import (
    MouseJoint,
    WeldJoint,
    RevoluteJoint,
    PrismaticJoint,
    WheelJoint,
    DistanceJoint,
    MotorJoint,
)
from .math import Vec2, VectorLike, AABB, Transform, to_vec2
from .debug_draw import DebugDraw
from .collision_filter import CollisionFilter
from .shape_def import CircleDef
from .shape import Shape
from dataclasses import dataclass

from dataclasses import dataclass
from .math import Vec2
from ._box2d import ffi, lib


@dataclass
class RayCastResult:
    """Result from a ray cast query."""

    shape: "Shape"  # Shape that was hit
    point: Vec2  # Hit point in world coords
    normal: Vec2  # Surface normal at hit point
    fraction: float  # Fraction along ray where hit occurred (0-1)


@dataclass
class SensorEvent:
    """Event generated by a sensor contact."""

    sensor: "Shape"  # Sensor shape generating the event
    visitor: "Shape"  # Other shape involved in the event
    begin: bool  # True if the sensor is starting to overlap the other shape False if ending


@dataclass
class SensorEvents:
    """Collection of sensor events generated during a time step."""

    begin: list[SensorEvent]
    end: list[SensorEvent]


def make_ray_cast_callback(results: list):
    """Create a ray cast callback function."""

    @ffi.callback("float(b2ShapeId, b2Vec2, b2Vec2, float, void*)")
    def ray_cast_callback(shape_id, point, normal, fraction, _):
        shape = ffi.from_handle(lib.b2Shape_GetUserData(shape_id))
        result = RayCastResult(
            shape=shape,
            point=Vec2(point.x, point.y),
            normal=Vec2(normal.x, normal.y),
            fraction=fraction,
        )
        results.append(result)
        return 1.0  # Continue querying

    return ray_cast_callback


def make_overlap_callback(results: list, max_results: int = None):
    """
    Create an overlap callback that appends detected shapes to the results
    list and stops querying when max_results is reached.

    Args:
        results: A list to collect overlapping shapes.
        max_results: Optional maximum number of shapes to collect. Once reached,
                     the callback returns False to stop further processing.

    Returns:
        A Box2D-compatible callback function.
    """

    @ffi.callback("bool(b2ShapeId, void*)")
    def overlap_callback(shape_id, _):
        shape = ffi.from_handle(lib.b2Shape_GetUserData(shape_id))
        results.append(shape)
        if max_results is not None and len(results) >= max_results:
            return False  # Stop querying further shapes
        return True  # Continue querying

    return overlap_callback


[docs] class World: """2D physics world containing bodies, joints, and simulation parameters. Manages Box2D world state and provides body creation through a builder pattern. Wraps Box2D's b2World functionality with Pythonic interfaces. Example: >>> world = World(gravity=(0, -9.81)) >>> body = world.new_body().dynamic().position(0, 5).box(1, 1).build() >>> for _ in range(60): ... world.step(1/60, 4) """
[docs] def __init__(self, gravity: VectorLike = (0, -10), threads: int = 1): """Initialize physics world with specified gravity vector. Args: gravity: Initial gravitational acceleration (x,y) in m/s². Example: >>> world = World(gravity=(0, -9.8)) >>> world.gravity Vec2(0.0, -9.8) """ world_def = lib.b2DefaultWorldDef() world_def.gravity.x, world_def.gravity.y = gravity self._threads = threads if threads > 1: # Use the C-level task scheduler lib.setup_threadpool(threads) self._use_c_scheduler = True world_def.workerCount = threads world_def.enqueueTask = lib.c_enqueue_tasks world_def.finishTask = lib.c_finish_tasks self._user_task_ctx = ffi.new_handle(self) world_def.userTaskContext = self._user_task_ctx else: self._use_c_scheduler = False self._world_id = lib.b2CreateWorld(ffi.addressof(world_def)) # Store default simulation parameters self._enable_sleep = world_def.enableSleep self._enable_continuous = world_def.enableContinuous self._restitution_threshold = world_def.restitutionThreshold self._hit_event_threshold = world_def.hitEventThreshold self._contact_hertz = world_def.contactHertz self._contact_damping_ratio = world_def.contactDampingRatio self._contact_push_velocity = world_def.contactPushMaxSpeed self._bodies = {}
@property def gravity(self): """World gravity vector (m/s²). Example: >>> world = World() >>> world.gravity = (0, -9.81) >>> world.gravity Vec2(0.0, -9.81) """ g = lib.b2World_GetGravity(self._world_id) return Vec2(g.x, g.y) @gravity.setter def gravity(self, value: VectorLike): """Set world gravity vector""" value = to_vec2(value) lib.b2World_SetGravity(self._world_id, value.b2Vec2[0])
[docs] def step(self, time_step, substep_count=4): """Advance simulation by time step. Args: time_step: Time to simulate (seconds) substep_count: Number of solver iterations (default 4) Higher values improve stability at cost of performance Example: >>> world = World() >>> world.step(1/60) # Default 4 substeps >>> world.step(0.016, 6) # Custom substep count """ lib.b2World_Step(self._world_id, time_step, substep_count)
[docs] def new_body(self): """Create BodyBuilder for constructing bodies. Entry point for body creation. Returns: BodyBuilder: Fluent interface for body configuration Example: >>> world = World() >>> builder = world.new_body() >>> body = builder.dynamic().position(2,3).circle(1).build() """ return BodyBuilder(self)
[docs] def add_mouse_joint( self, body, target, max_force=1000.0, damping_ratio=0.7, hertz=5, ) -> MouseJoint: """Create a mouse joint for interactive dragging between bodies Args: body: Body to drag target: Initial target position in world coordinates max_force: Maximum constraint force (default 1000.0) damping_ratio: Response damping ratio (0-1, default 0.7) herts: Spring stiffness in Hz (higher = stiffer movement) Example: >>> world = World() >>> box = world.new_body().dynamic().position(0,5).build() >>> mouse_joint = world.add_mouse_joint(box, (0,5)) """ if body._body_id not in self._bodies: raise ValueError("Bodies must belong to this world") return MouseJoint(self, body, target, max_force, damping_ratio, hertz)
[docs] def add_weld_joint( self, body_a, body_b, local_anchor_a=None, local_anchor_b=None, anchor=None, collide_connected=False, linear_hertz=None, linear_damping_ratio=None, angular_hertz=None, angular_damping_ratio=None, reference_angle=None, ) -> WeldJoint: """Create a weld joint that rigidly connects two bodies. Args: body_a: The first body to connect (must belong to this world) body_b: The second body to connect (must belong to this world) local_anchor_a (tuple): Local coordinates (x, y) on body_a where the joint attaches. local_anchor_b (tuple): Local coordinates (x, y) on body_b where the joint attaches. anchor: World coordinates (x, y) where the joint attaches. (Used without local anchors) collide_connected (bool, optional): If True, the connected bodies will collide. linear_hertz (float, optional): Linear spring stiffness in Hertz (0 means rigid). linear_damping_ratio (float, optional): Linear damping ratio (non-dimensional). angular_hertz (float, optional): Angular spring stiffness in Hertz (0 means rigid). angular_damping_ratio (float, optional): Angular damping ratio (non-dimensional). reference_angle (float, optional): The body_b angle minus body_a angle in the reference state (radians) Returns: WeldJoint: The created weld joint connecting the two bodies. Example: >>> world = World() >>> body_a = world.new_body().dynamic().position(0, 0).build() >>> body_b = world.new_body().dynamic().position(1, 1).build() >>> weld_joint = world.add_weld_joint(body_a, body_b, anchor=(0.5, 0.5)) """ if (body_a._body_id not in self._bodies) or ( body_b._body_id not in self._bodies ): raise ValueError("Both bodies must belong to this world") if anchor is not None: if local_anchor_a is not None or local_anchor_b is not None: raise ValueError( "You can't set local anchors when setting a world anchor" ) local_anchor_a = body_a.transform.inverse(anchor) local_anchor_b = body_b.transform.inverse(anchor) return WeldJoint( self, body_a, body_b, local_anchor_a, local_anchor_b, collide_connected, linear_hertz, linear_damping_ratio, angular_hertz, angular_damping_ratio, reference_angle, )
[docs] def add_revolute_joint( self, body_a, body_b, local_anchor_a=None, local_anchor_b=None, anchor=None, collide_connected=False, lower_angle=None, upper_angle=None, enable_limit=None, motor_speed=None, max_motor_torque=None, enable_motor=None, reference_angle=None, ) -> RevoluteJoint: """ Create a revolute joint connecting two bodies, allowing relative rotation about an anchor point. Args: body_a: The first body to connect (must belong to this world). body_b: The second body to connect (must belong to this world). local_anchor_a (tuple): Local coordinates (x, y) on body_a for the joint. local_anchor_b (tuple): Local coordinates (x, y) on body_b for the joint. collide_connected (bool, optional): Whether the connected bodies should collide with each other. Defaults to False. lower_angle (float, optional): Lower joint limit in radians. upper_angle (float, optional): Upper joint limit in radians. enable_limit (bool, optional): Enable joint limits if True. motor_speed (float, optional): Desired motor speed in radians per second. max_motor_torque (float, optional): Maximum motor torque in newton-meters. enable_motor (bool, optional): Enable the joint motor if True. reference_angle (float, optional): Reference angle between the two bodies. Returns: RevoluteJoint: The created revolute joint connecting body_a and body_b. Example: >>> world = World() >>> body_a = world.new_body().dynamic().position(0, 0).build() >>> body_b = world.new_body().dynamic().position(1, 0).build() >>> revolute_joint = world.add_revolute_joint( ... body_a, body_b, (0, 0), (0, 0), ... collide_connected=False, ... enable_limit=True, ... lower_angle=-0.5, ... upper_angle=0.5 ... ) """ if (body_a._body_id not in self._bodies) or ( body_b._body_id not in self._bodies ): raise ValueError("Both bodies must belong to this world") if anchor is not None: if local_anchor_a is not None or local_anchor_b is not None: raise ValueError( "You can't set local anchors when setting a world anchor" ) local_anchor_a = body_a.transform.inverse(anchor) local_anchor_b = body_b.transform.inverse(anchor) return RevoluteJoint( self, body_a, body_b, local_anchor_a, local_anchor_b, collide_connected, lower_angle, upper_angle, enable_limit, motor_speed, max_motor_torque, enable_motor, reference_angle, )
[docs] def add_prismatic_joint( self, body_a, body_b, local_anchor_a=None, local_anchor_b=None, axis=(1, 0), anchor=None, collide_connected=False, lower_limit=None, upper_limit=None, enable_limit=None, motor_speed=None, max_motor_force=None, enable_motor=None, reference_angle=None, enable_spring=None, hertz=None, damping_ratio=None, ) -> "PrismaticJoint": """Create a prismatic joint that allows sliding motion along an axis. Args: body_a: First body to connect (must belong to this world) body_b: Second body to connect (must belong to this world) local_anchor_a (tuple): Local coordinates (x,y) on body_a where joint attaches local_anchor_b (tuple): Local coordinates (x,y) on body_b where joint attaches axis (tuple): The axis defining allowed translation (x,y) in body A's frame anchor: World coordinates (x,y) where joint attaches (alternative to local anchors) collide_connected (bool): Whether bodies can collide with each other lower_limit (float): Lower translation limit upper_limit (float): Upper translation limit enable_limit (bool): Whether to enable joint limits motor_speed (float): Desired motor speed in meters/sec max_motor_force (float): Maximum motor force in Newtons enable_motor (bool): Whether to enable the joint motor reference_angle (float): Reference angle between bodies in radians enable_spring (bool): Enable spring behavior hertz (float): Spring stiffness frequency in Hz damping_ratio (float): Spring damping ratio Returns: PrismaticJoint: The created prismatic joint Example: >>> world = World() >>> body_a = world.new_body().dynamic().position(0,0).build() >>> body_b = world.new_body().dynamic().position(1,0).build() >>> # Create sliding joint along x-axis >>> joint = world.add_prismatic_joint( ... body_a, body_b, ... anchor=(0.5,0), ... axis=(1,0), ... enable_limit=True, ... lower_limit=-1, ... upper_limit=1 ... ) """ if (body_a._body_id not in self._bodies) or ( body_b._body_id not in self._bodies ): raise ValueError("Both bodies must belong to this world") if anchor is not None: if local_anchor_a is not None or local_anchor_b is not None: raise ValueError("Can't set local anchors when setting a world anchor") local_anchor_a = body_a.transform.inverse(anchor) local_anchor_b = body_b.transform.inverse(anchor) return PrismaticJoint( self, body_a, body_b, local_anchor_a, local_anchor_b, axis, collide_connected, lower_limit, upper_limit, enable_limit, motor_speed, max_motor_force, enable_motor, reference_angle, enable_spring, hertz, damping_ratio, )
[docs] def add_wheel_joint( self, body_a, body_b, local_anchor_a=None, local_anchor_b=None, axis=(1, 0), anchor=None, collide_connected=False, enable_limit=False, lower_translation=0.0, upper_translation=0.0, enable_motor=False, motor_speed=0.0, max_motor_torque=0.0, enable_spring=False, spring_hertz=0.0, spring_damping_ratio=0.0, ) -> "WheelJoint": """Create a wheel joint for vehicle suspension simulation. Args: body_a: First body to connect (must belong to this world) body_b: Second body to connect (must belong to this world) local_anchor_a (tuple): Local coordinates (x,y) on body_a where joint attaches local_anchor_b (tuple): Local coordinates (x,y) on body_b where joint attaches axis (tuple): The axis defining translation (x,y) in body A's frame anchor: World coordinates (x,y) where joint attaches (alternative to local anchors) collide_connected (bool): Whether bodies can collide with each other enable_limit (bool): Enable translation limits lower_translation (float): Lower translation limit upper_translation (float): Upper translation limit enable_motor (bool): Enable the joint motor motor_speed (float): Desired motor speed in radians/sec max_motor_torque (float): Maximum motor torque in N-m enable_spring (bool): Enable spring behavior spring_hertz (float): Spring oscillation frequency in Hz spring_damping_ratio (float): Spring damping ratio (non-dimensional) Returns: WheelJoint: The created wheel joint Example: >>> world = World() >>> chassis = world.new_body().dynamic().position(0,1).box(2,0.5).build() >>> wheel = world.new_body().dynamic().position(1,0).circle(0.4).build() >>> # Create wheel suspension >>> joint = world.add_wheel_joint( ... chassis, wheel, ... anchor=(1,0), ... axis=(0,1), # Vertical suspension movement ... enable_spring=True, ... spring_hertz=4.0, ... spring_damping_ratio=0.7 ... ) """ if (body_a._body_id not in self._bodies) or ( body_b._body_id not in self._bodies ): raise ValueError("Both bodies must belong to this world") if anchor is not None: if local_anchor_a is not None or local_anchor_b is not None: raise ValueError("Can't set local anchors when setting a world anchor") local_anchor_a = body_a.transform.inverse(anchor) local_anchor_b = body_b.transform.inverse(anchor) return WheelJoint( self, body_a, body_b, local_anchor_a, local_anchor_b, axis, collide_connected, enable_limit, lower_translation, upper_translation, enable_motor, motor_speed, max_motor_torque, enable_spring, spring_hertz, spring_damping_ratio, )
[docs] def add_distance_joint( self, body_a, body_b, local_anchor_a=None, local_anchor_b=None, collide_connected=False, length=None, min_length=None, max_length=None, enable_limit=False, enable_spring=False, hertz=None, damping_ratio=None, enable_motor=False, motor_speed=None, max_motor_force=None, ) -> "DistanceJoint": """Create a distance joint that maintains or limits distance between points on two bodies. Args: body_a: First body to connect (must belong to this world) body_b: Second body to connect (must belong to this world) local_anchor_a (tuple): Local coordinates (x,y) on body_a where joint attaches local_anchor_b (tuple): Local coordinates (x,y) on body_b where joint attaches collide_connected (bool): Whether bodies can collide with each other length (float): Rest length. Calculated from anchors if None. min_length (float): Minimum allowed length when using limits max_length (float): Maximum allowed length when using limits enable_limit (bool): Whether to enable length limits enable_spring (bool): Enable spring behavior hertz (float): Spring oscillation frequency in Hz damping_ratio (float): Spring damping ratio enable_motor (bool): Enable the joint motor motor_speed (float): Desired motor speed in meters/second max_motor_force (float): Maximum motor force in Newtons Returns: DistanceJoint: The created distance joint Example: >>> world = World() >>> body_a = world.new_body().dynamic().position(0,0).build() >>> body_b = world.new_body().dynamic().position(2,0).build() >>> # Create spring joint >>> spring = world.add_distance_joint( ... body_a, body_b, ... anchor_a=(0,0), ... anchor_b=(2,0), ... enable_spring=True, ... hertz=4.0, ... damping_ratio=0.5 ... ) >>> # Create rope joint >>> rope = world.add_distance_joint( ... body_a, body_b, ... anchor_a=(0,1), ... anchor_b=(2,1), ... enable_limit=True, ... min_length=1.0, ... max_length=3.0 ... ) """ if (body_a._body_id not in self._bodies) or ( body_b._body_id not in self._bodies ): raise ValueError("Both bodies must belong to this world") return DistanceJoint( self, body_a, body_b, local_anchor_a, local_anchor_b, collide_connected, length, min_length, max_length, enable_limit, enable_spring, hertz, damping_ratio, enable_motor, motor_speed, max_motor_force, )
[docs] def add_motor_joint( self, body_a, body_b, linear_offset=None, angular_offset=None, max_force=None, max_torque=None, correction_factor=None, collide_connected=False, ) -> "MotorJoint": """Create a motor joint to control relative motion between two bodies. Args: body_a: First body to connect body_b: Second body to connect linear_offset (tuple): Desired position of bodyB in bodyA's frame angular_offset (float): Desired angle between bodies in radians max_force (float): Maximum force in Newtons max_torque (float): Maximum torque in Newton-meters correction_factor (float): Position correction factor [0,1] collide_connected (bool): Whether bodies can collide Returns: MotorJoint: The created motor joint Example: >>> world = World() >>> ground = world.new_body().build() >>> body = world.new_body().dynamic().position(0, 4).build() >>> motor = world.add_motor_joint( ... ground, body, ... linear_offset=(1, 0), # Move body 1m right ... max_force=100 ... ) """ return MotorJoint( self, body_a, body_b, linear_offset, angular_offset, max_force, max_torque, correction_factor, collide_connected, )
def _track_body(self, body: "Body"): """Internal method to track body references. Called automatically during body creation. Args: body: Body instance to register in the world """ self._bodies[body._body_id] = body @property def bodies(self): """Get list of all active bodies in the world. Returns: list[Body]: Copies of registered body references Example: >>> world = World() >>> box = world.new_body().dynamic().build() >>> len(world.bodies) 1 """ return list(self._bodies.values())
[docs] def draw(self, debug_draw: DebugDraw): """Render world state using debug drawing interface. Args: debug_draw: Configured DebugDraw instance for visualization Example: >>> world = World() >>> debug_draw = DebugDraw() >>> world.draw(debug_draw) """ lib.b2World_Draw(self._world_id, ffi.addressof(debug_draw._debug_draw))
[docs] def query_aabb( self, aabb: AABB, collision_filter: "CollisionFilter" = None, max_results: int = None, ) -> list: """Find shapes overlapping an axis-aligned bounding box using an optional collision filter. Args: aabb: Axis-aligned bounding box to query. collision_filter: Optional CollisionFilter instance for filtering. If None, a default CollisionFilter is used. max_results: Optional maximum number of shapes to return. The callback will return False when this limit is reached. Returns: list: Shapes with overlapping fixtures. Example: >>> world = World() >>> box = world.new_body().dynamic().position(0, 0).box(1, 1).build() >>> aabb = AABB(lower=Vec2(-1, -1), upper=Vec2(1, 1)) >>> overlaps = world.query_aabb(aabb, collision_filter=CollisionFilter(), max_results=10) >>> len(overlaps) <= 10 True """ if collision_filter is None: collision_filter = CollisionFilter() results = [] overlap_callback = make_overlap_callback(results, max_results) # Convert the CollisionFilter to a Box2D c_filter. c_filter = collision_filter.b2QueryFilter filter_dict = { "categoryBits": c_filter.categoryBits, "maskBits": c_filter.maskBits, } lib.b2World_OverlapAABB( self._world_id, aabb.b2AABB[0], c_filter[0], overlap_callback, ffi.NULL, ) return results
[docs] def query_circle( self, position: VectorLike, radius: float, collision_filter: "CollisionFilter" = None, max_results: int = None, ) -> list: """Find shapes overlapping a circle. Args: position: Center of the query circle in world coordinates. radius: Radius of the query circle. collision_filter: Optional CollisionFilter instance for filtering. If None, a default CollisionFilter is used. max_results: Optional maximum number of shapes to return. The callback will return False when this limit is reached. Returns: list: Shapes with overlapping fixtures. Example: >>> world = World() >>> # Create a body with a circle fixture >>> box = world.new_body().dynamic().circle(1).build() >>> overlaps = world.query_circle((0, 0), 1.5, collision_filter=CollisionFilter(), max_results=10) >>> len(overlaps) <= 10 True """ if collision_filter is None: collision_filter = CollisionFilter() results = [] overlap_callback = make_overlap_callback(results, max_results) circle = CircleDef(radius).circle transform = Transform(position=position).b2Transform c_filter = collision_filter.b2QueryFilter lib.b2World_OverlapCircle( self._world_id, circle, transform[0], c_filter[0], overlap_callback, ffi.NULL, ) return results
[docs] def ray_cast( self, origin: VectorLike, translation: VectorLike, collision_filter: "CollisionFilter" = None, first_hit_only: bool = False, ) -> list[RayCastResult]: """Cast a ray and find intersecting shapes. Args: origin: Starting point of the ray (x,y) translation: The translation of the ray from the start point to the end point collision_filter: Optional filter to control which shapes are tested first_hit_only: If True, only return the first hit encountered Returns: List of RayCastResult objects containing hit information Example: >>> world = World() >>> box = world.new_body().dynamic().position(5,0).box(1,1).build() >>> hits = world.ray_cast((0,0), (10,0)) >>> len(hits) > 0 True >>> hits[0].shape # First intersected shape <Shape> """ if collision_filter is None: collision_filter = CollisionFilter() results = [] p1 = to_vec2(origin).b2Vec2[0] p2 = to_vec2(translation).b2Vec2[0] c_filter = collision_filter.b2QueryFilter if first_hit_only: result = lib.b2World_CastRayClosest(self._world_id, p1, p2, c_filter[0]) if result.hit: results.append( RayCastResult( shape=ffi.from_handle(lib.b2Shape_GetUserData(result.shapeId)), point=Vec2(result.point.x, result.point.y), normal=Vec2(result.normal.x, result.normal.y), fraction=result.fraction, ) ) else: callback = make_ray_cast_callback(results) lib.b2World_CastRay(self._world_id, p1, p2, c_filter[0], callback, ffi.NULL) results.sort(key=lambda r: r.fraction) return results
[docs] def get_sensor_events(self) -> list: """Retrieve all sensor events that occurred during the last time step. Sensors are shapes marked with is_sensor=True. They detect overlap with other shapes but don't generate collision responses. This method returns all begin and end overlap events for sensors from the last simulation step. Returns: list: A list of SensorEvent objects with the following attributes: - sensor: The sensor Shape that detected overlap - visitor: The Shape that overlapped with the sensor - begin: True if overlap began, False if overlap ended Example: >>> world = World() >>> body = world.new_body().dynamic().position(0,0).build() >>> # Create a sensor fixture >>> sensor = body.add_circle(radius=1.0, is_sensor=True) >>> # Run simulation >>> world.step(1/60) >>> # Get events >>> events = world.get_sensor_events() >>> for event in events: ... if event['begin']: ... print(f"Sensor began contact with object") ... else: ... print(f"Sensor ended contact with object") """ events = [] # Get the events from Box2D sensor_events = lib.b2World_GetSensorEvents(self._world_id) # Process the events if there are any if sensor_events: # Determine how many events we have begin_count = sensor_events.beginCount end_count = sensor_events.endCount # Process each event begin_ev = [sensor_events.beginEvents[i] for i in range(begin_count)] end_ev = [sensor_events.endEvents[i] for i in range(end_count)] begin_events = [ SensorEvent( sensor=ffi.from_handle( lib.b2Shape_GetUserData(event.sensorShapeId) ), visitor=ffi.from_handle( lib.b2Shape_GetUserData(event.visitorShapeId) ), begin=True, ) for event in begin_ev ] end_events = [ SensorEvent( sensor=ffi.from_handle( lib.b2Shape_GetUserData(event.sensorShapeId) ), visitor=ffi.from_handle( lib.b2Shape_GetUserData(event.visitorShapeId) ), begin=False, ) for event in end_ev ] return SensorEvents(begin=begin_events, end=end_events)
[docs] def destroy(self): """Destroy the world. Example: >>> world = World() >>> world.destroy() """ # If using the C-level scheduler, clean it up. if getattr(self, "_use_c_scheduler", False): lib.cleanup_threadpool() self._use_c_scheduler = False if hasattr(self, "_world_id"): lib.b2DestroyWorld(self._world_id) del self._world_id
def __del__(self): """Clean up world resources. Automatically called when instance is garbage collected. Destroys Box2D world instance. """ self.destroy() @property def enable_sleep(self) -> bool: """Control whether bodies can enter sleep state to save computation. When enabled, inactive bodies will stop simulating until awakened. Example: >>> world = World() >>> world.enable_sleep = False # Disable sleeping entirely """ return self._enable_sleep @enable_sleep.setter def enable_sleep(self, value: bool): self._enable_sleep = bool(value) lib.b2World_EnableSleeping(self._world_id, self._enable_sleep) @property def enable_continuous(self) -> bool: """Toggle continuous collision detection for dynamic vs static bodies. Helps prevent fast-moving objects from tunneling through static geometry. Example: >>> world = World() >>> world.enable_continuous = False # Disable CCD for static """ return self._enable_continuous @enable_continuous.setter def enable_continuous(self, value: bool): self._enable_continuous = bool(value) lib.b2World_EnableContinuous(self._world_id, self._enable_continuous) @property def restitution_threshold(self) -> float: """Minimum collision speed for restitution effects (m/s). Collisions slower than this threshold will have zero restitution. Example: >>> world = World() >>> world.restitution_threshold = 2.0 # Only apply restitution above 2m/s """ return self._restitution_threshold @restitution_threshold.setter def restitution_threshold(self, value: float): self._restitution_threshold = float(value) lib.b2World_SetRestitutionThreshold(self._world_id, self._restitution_threshold) @property def hit_event_threshold(self) -> float: """Minimum collision speed to trigger hit events (m/s). Collisions slower than this won't generate collision events. Example: >>> world = World() >>> world.hit_event_threshold = 0.5 # Get events for slower impacts """ return self._hit_event_threshold @hit_event_threshold.setter def hit_event_threshold(self, value: float): self._hit_event_threshold = float(value) lib.b2World_SetHitEventThreshold(self._world_id, self._hit_event_threshold) @property def contact_hertz(self) -> float: """Contact constraint stiffness frequency (Hz). Higher values make contacts stiffer/more rigid. Example: >>> world = World() >>> world.contact_hertz = 30.0 # Softer contacts """ return self._contact_hertz @contact_hertz.setter def contact_hertz(self, value: float): self._contact_hertz = float(value) lib.b2World_SetContactTuning( self._world_id, self._contact_hertz, self._contact_damping_ratio, self._contact_push_velocity, ) @property def contact_damping_ratio(self) -> float: """Contact constraint damping ratio (0-1). 1.0 = critical damping (fastest oscillation reduction) Example: >>> world = World() >>> world.contact_damping_ratio = 0.2 # Add some energy absorption """ return self._contact_damping_ratio @contact_damping_ratio.setter def contact_damping_ratio(self, value: float): self._contact_damping_ratio = float(value) lib.b2World_SetContactTuning( self._world_id, self._contact_hertz, self._contact_damping_ratio, self._contact_push_velocity, ) @property def contact_push_velocity(self) -> float: """Maximum velocity for pushing objects out of penetration (m/s). Limits how fast contacts can separate penetrating bodies. Example: >>> world = World() >>> world.contact_push_velocity = 2.0 # Allow faster separation """ return self._contact_push_velocity @contact_push_velocity.setter def contact_push_velocity(self, value: float): self._contact_push_velocity = float(value) lib.b2World_SetContactTuning( self._world_id, self._contact_hertz, self._contact_damping_ratio, self._contact_push_velocity, )