1+ // SPDX-FileCopyrightText: 2025 Erin Catto
2+ // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com)
3+ // SPDX-License-Identifier: MIT
4+
5+ using System . Collections . Generic ;
6+ using System . Numerics ;
7+ using System . Runtime . InteropServices ;
8+ using ImGuiNET ;
9+ using static Box2D . NET . B2Types ;
10+ using static Box2D . NET . B2Bodies ;
11+ using static Box2D . NET . B2Shapes ;
12+ using static Box2D . NET . B2Ids ;
13+ using static Box2D . NET . B2Worlds ;
14+ using static Box2D . NET . B2Contacts ;
15+ using static Box2D . NET . Samples . Graphics . Draws ;
16+
17+ namespace Box2D . NET . Samples . Samples . Stackings ;
18+
19+ public class CircleImpulse : Sample
20+ {
21+ private static readonly int SampleCircleImpulse = SampleFactory . Shared . RegisterSample ( "Stacking" , "Circle Impulse" , Create ) ;
22+
23+ public struct Event
24+ {
25+ public float impulse ;
26+ public float totalImpulse ;
27+ public float speed ;
28+ } ;
29+
30+ private float m_mass ;
31+ private List < Event > m_events = new List < Event > ( ) ;
32+ private B2BodyId m_bodyId ;
33+ private float m_gravity ;
34+ private float m_restitution ;
35+ private bool m_useGravity ;
36+ private bool m_useRestitution ;
37+
38+ private static Sample Create ( SampleContext context )
39+ {
40+ return new CircleImpulse ( context ) ;
41+ }
42+
43+ public CircleImpulse ( SampleContext context ) : base ( context )
44+ {
45+ if ( m_context . restart == false )
46+ {
47+ m_context . camera . center = new B2Vec2 ( 0.0f , 2.7f ) ;
48+ m_context . camera . zoom = 3.4f ;
49+ }
50+
51+ {
52+ B2BodyDef bodyDef = b2DefaultBodyDef ( ) ;
53+ B2BodyId groundId = b2CreateBody ( m_worldId , bodyDef ) ;
54+
55+ B2ShapeDef shapeDef = b2DefaultShapeDef ( ) ;
56+
57+ B2Segment segment = new B2Segment ( new B2Vec2 ( - 10.0f , 0.0f ) , new B2Vec2 ( 10.0f , 0.0f ) ) ;
58+ b2CreateSegmentShape ( groundId , shapeDef , segment ) ;
59+ }
60+
61+ m_gravity = 10.0f ;
62+ m_restitution = 0.25f ;
63+ m_useGravity = false ;
64+ m_useRestitution = false ;
65+ m_mass = 1.0f ;
66+ m_bodyId = b2_nullBodyId ;
67+
68+ Spawn ( ) ;
69+ }
70+
71+ void Spawn ( )
72+ {
73+ if ( B2_IS_NON_NULL ( m_bodyId ) )
74+ {
75+ b2DestroyBody ( m_bodyId ) ;
76+ m_bodyId = b2_nullBodyId ;
77+ }
78+
79+ m_events . Clear ( ) ;
80+
81+ B2BodyDef bodyDef = b2DefaultBodyDef ( ) ;
82+ bodyDef . type = B2BodyType . b2_dynamicBody ;
83+ bodyDef . gravityScale = m_useGravity ? 1.0f : 0.0f ;
84+ bodyDef . linearVelocity . Y = - 25.0f ;
85+ bodyDef . position . Y = 5.5f ;
86+
87+ B2Circle circle = new B2Circle ( ) ;
88+ circle . radius = 0.25f ;
89+
90+ B2ShapeDef shapeDef = b2DefaultShapeDef ( ) ;
91+ shapeDef . enableHitEvents = true ;
92+ shapeDef . material . friction = 0.0f ;
93+ shapeDef . material . restitution = m_useRestitution ? m_restitution : 0.0f ;
94+
95+ m_bodyId = b2CreateBody ( m_worldId , bodyDef ) ;
96+
97+ b2CreateCircleShape ( m_bodyId , shapeDef , circle ) ;
98+
99+ // Override mass
100+ B2MassData massData = b2Body_GetMassData ( m_bodyId ) ;
101+ float ratio = m_mass / massData . mass ;
102+ massData . mass = m_mass ;
103+ massData . rotationalInertia *= ratio ;
104+ b2Body_SetMassData ( m_bodyId , massData ) ;
105+ }
106+
107+ public override void UpdateGui ( )
108+ {
109+ float fontSize = ImGui . GetFontSize ( ) ;
110+ float height = 6.0f * fontSize ;
111+ ImGui . SetNextWindowPos ( new Vector2 ( 0.5f * fontSize , m_camera . height - height - 2.0f * fontSize ) , ImGuiCond . Once ) ;
112+ ImGui . SetNextWindowSize ( new Vector2 ( 10.0f * fontSize , height ) ) ;
113+
114+ ImGui . Begin ( "Circle Impulse" , ImGuiWindowFlags . NoMove | ImGuiWindowFlags . NoResize ) ;
115+
116+ if ( ImGui . Checkbox ( "gravity" , ref m_useGravity ) )
117+ {
118+ Spawn ( ) ;
119+ }
120+
121+ if ( ImGui . Checkbox ( "restitution" , ref m_useRestitution ) )
122+ {
123+ Spawn ( ) ;
124+ }
125+
126+ ImGui . End ( ) ;
127+ }
128+
129+ public override void Step ( )
130+ {
131+ base . Step ( ) ;
132+
133+ B2ContactEvents events = b2World_GetContactEvents ( m_worldId ) ;
134+ for ( int i = 0 ; i < events . hitCount ; ++ i )
135+ {
136+ ref readonly B2ContactHitEvent @event = ref events . hitEvents [ i ] ;
137+
138+ DrawPoint ( m_draw , @event . point , 10.0f , B2HexColor . b2_colorWhite ) ;
139+
140+ B2ContactData data = b2Contact_GetData ( @event . contactId ) ;
141+
142+ Event e = new Event ( ) ;
143+ e . speed = @event . approachSpeed ;
144+
145+ if ( data . manifold . pointCount > 0 )
146+ {
147+ e . impulse = data . manifold . points [ 0 ] . normalImpulse ;
148+ e . totalImpulse = data . manifold . points [ 0 ] . totalNormalImpulse ;
149+ }
150+
151+ m_events . Add ( e ) ;
152+ }
153+
154+ DrawTextLine ( $ "mass = { m_mass } , gravity = { ( m_useGravity ? 10.0f : 0.0f ) } , restitution = { ( m_useRestitution ? m_restitution : 0.0f ) } ") ;
155+
156+ int eventCount = m_events . Count ;
157+ var eventsSpan = CollectionsMarshal . AsSpan ( m_events ) ;
158+ for ( int i = 0 ; i < eventCount ; ++ i )
159+ {
160+ ref readonly Event e = ref eventsSpan [ i ] ;
161+ DrawTextLine ( $ "hit speed = { e . speed } , hit momentum = { m_mass * e . speed } , final impulse = { e . impulse } , total impulse = { e . totalImpulse } ") ;
162+ }
163+ }
164+ }
0 commit comments