Collision Shapes¶
A circle and a box body falling onto a static floor.
▶ Run in browserTags: 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())