Collision Shapes

A circle and a box body falling onto a static floor.

▶ Run in browser

Tags: 2d physics collision rigidbody

Two dynamic PhysicsBody2D nodes (one with a CircleShape2D collider, one with a RectangleShape2D collider) fall under gravity and come to rest on a STATIC PhysicsBody2D floor. The Physics2DWorld resolves the contacts automatically, so each body settles exactly on top of the floor.

What it demonstrates

  • PhysicsBody2D(DYNAMIC) with two collider kinds: CircleShape2D and RectangleShape2D.

  • PhysicsBody2D(STATIC) as an immovable floor the dynamic bodies rest against.

  • Automatic gravity + impulse-based contact response from the Physics2DWorld.

  • Different shapes settling at the correct height (floor top minus their extent).

Controls: R - Reset the bodies back to their drop positions ESC - Quit

Source

  1"""Collision Shapes: A circle and a box body falling onto a static floor.
  2
  3Two dynamic PhysicsBody2D nodes (one with a CircleShape2D collider, one with a
  4RectangleShape2D collider) fall under gravity and come to rest on a STATIC
  5PhysicsBody2D floor. The Physics2DWorld resolves the contacts automatically, so
  6each body settles exactly on top of the floor.
  7
  8# /// simvx
  9# tags = ["2d", "physics", "collision", "rigidbody"]
 10# web = { root = "CollisionShapesDemo", width = 800, height = 600, responsive = true }
 11# ///
 12
 13## What it demonstrates
 14- PhysicsBody2D(DYNAMIC) with two collider kinds: CircleShape2D and RectangleShape2D.
 15- PhysicsBody2D(STATIC) as an immovable floor the dynamic bodies rest against.
 16- Automatic gravity + impulse-based contact response from the Physics2DWorld.
 17- Different shapes settling at the correct height (floor top minus their extent).
 18
 19Controls:
 20  R   - Reset the bodies back to their drop positions
 21  ESC - Quit
 22"""
 23
 24from simvx.core import (
 25    BodyMode,
 26    CircleShape2D,
 27    CollisionShape2D,
 28    Input,
 29    InputMap,
 30    Key,
 31    Node2D,
 32    PhysicsBody2D,
 33    PhysicsMaterial,
 34    RectangleShape2D,
 35    Vec2,
 36)
 37from simvx.core.physics.root import PhysicsRoot2D
 38from simvx.graphics import App
 39
 40WIDTH, HEIGHT = 800, 600
 41
 42FLOOR_Y = 500.0
 43FLOOR_HALF_W = 320.0
 44FLOOR_HALF_H = 20.0
 45FLOOR_TOP = FLOOR_Y - FLOOR_HALF_H
 46
 47BALL_RADIUS = 30.0
 48BOX_HALF = 35.0
 49
 50# 2D physics here runs in screen pixels, Y-down (gravity = +Y, falling reads as
 51# falling on screen). Continuous collision keeps the box from tunnelling the floor
 52# at the fall speeds this gravity produces.
 53GRAVITY = Vec2(0.0, 1600.0)
 54
 55BALL_DROP = Vec2(WIDTH / 2 - 110, 100)
 56BOX_DROP = Vec2(WIDTH / 2 + 110, 100)
 57
 58
 59class CollisionShapesDemo(Node2D):
 60    """Dynamic circle + box bodies resting on a static floor."""
 61
 62    def on_ready(self):
 63        InputMap.add_action("reset", [Key.R])
 64        InputMap.add_action("quit", [Key.ESCAPE])
 65
 66        # One isolated Y-down 2D world; every physics node below resolves to it.
 67        self._root = self.add_child(PhysicsRoot2D(name="World", gravity=GRAVITY))
 68
 69        # Static floor: a wide, thin box. Immovable, infinite effective mass.
 70        floor = PhysicsBody2D(
 71            name="Floor", mode=BodyMode.STATIC, position=Vec2(WIDTH / 2, FLOOR_Y),
 72            material=PhysicsMaterial(friction=0.7),
 73        )
 74        floor.add_child(CollisionShape2D(shape=RectangleShape2D(half_extents=Vec2(FLOOR_HALF_W, FLOOR_HALF_H))))
 75        self._root.add_child(floor)
 76
 77        # Dynamic ball: a circular collider. Continuous CCD avoids tunnelling.
 78        self._ball = PhysicsBody2D(
 79            name="Ball", mode=BodyMode.DYNAMIC, mass=1.0, continuous=True,
 80            position=Vec2(BALL_DROP.x, BALL_DROP.y),
 81            material=PhysicsMaterial(friction=0.5),
 82        )
 83        self._ball.add_child(CollisionShape2D(shape=CircleShape2D(BALL_RADIUS)))
 84        self._root.add_child(self._ball)
 85
 86        # Dynamic box: a rectangular collider.
 87        self._box = PhysicsBody2D(
 88            name="Box", mode=BodyMode.DYNAMIC, mass=1.0, continuous=True,
 89            position=Vec2(BOX_DROP.x, BOX_DROP.y),
 90            material=PhysicsMaterial(friction=0.5),
 91        )
 92        self._box.add_child(CollisionShape2D(shape=RectangleShape2D(half_extents=Vec2(BOX_HALF, BOX_HALF))))
 93        self._root.add_child(self._box)
 94
 95    def _reset(self):
 96        self._ball.position = Vec2(BALL_DROP.x, BALL_DROP.y)
 97        self._ball.velocity = Vec2()
 98        self._box.position = Vec2(BOX_DROP.x, BOX_DROP.y)
 99        self._box.velocity = Vec2()
100
101    def on_update(self, dt: float):
102        if Input.is_action_just_pressed("quit"):
103            self.app.quit()
104        elif Input.is_action_just_pressed("reset"):
105            self._reset()
106
107    def on_draw(self, renderer):
108        # Floor.
109        renderer.draw_rect(
110            (WIDTH / 2 - FLOOR_HALF_W, FLOOR_Y - FLOOR_HALF_H),
111            (FLOOR_HALF_W * 2, FLOOR_HALF_H * 2),
112            colour=(0.4, 0.4, 0.45, 1.0), filled=True,
113        )
114
115        # Ball.
116        bp = self._ball.world_position
117        renderer.draw_circle((bp.x, bp.y), BALL_RADIUS, colour=(0.95, 0.45, 0.3, 1.0), filled=True)
118
119        # Box (axis-aligned; the demo bodies stay upright as they drop).
120        xp = self._box.world_position
121        renderer.draw_rect(
122            (xp.x - BOX_HALF, xp.y - BOX_HALF), (BOX_HALF * 2, BOX_HALF * 2),
123            colour=(0.4, 0.7, 0.95, 1.0), filled=True,
124        )
125
126        # HUD.
127        renderer.draw_text("Collision Shapes -- Circle + Box on a Floor", (10, 10), colour=(1.0, 1.0, 1.0), scale=2)
128        renderer.draw_text("R: reset   ESC: quit", (10, 40), colour=(0.75, 0.75, 0.75))
129
130
131if __name__ == "__main__":
132    App(title="2D Collision Shapes", width=WIDTH, height=HEIGHT).run(CollisionShapesDemo())