Skip to content

Commit d8552e2

Browse files
committed
[upstream] Added b2ContactId for events and access functions (erincatto/box2d@d3d2b92)
`b2ContactBeginTouchEvent` now contains a `b2ContactId` you can hold onto safely and query for the manifold. This lets you get the contact impulses. You can use the end touch event to clear the handle.
1 parent 1c28ecb commit d8552e2

15 files changed

+288
-23
lines changed

src/Box2D.NET.Samples/Samples/Events/FootSensor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Box2D.NET.Samples.Samples.Events;
1919

2020
public class FootSensor : Sample
2121
{
22-
private static readonly int SampleCharacterSensor = SampleFactory.Shared.RegisterSample("Events", "Foot Sensor", Create);
22+
private static readonly int SampleFootSensor = SampleFactory.Shared.RegisterSample("Events", "Foot Sensor", Create);
2323

2424
public const uint GROUND = 0x00000001;
2525
public const uint PLAYER = 0x00000002;

src/Box2D.NET.Samples/Samples/Joints/JointEvent.cs renamed to src/Box2D.NET.Samples/Samples/Events/JointEvent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
using static Box2D.NET.B2Diagnostics;
1313
using static Box2D.NET.B2Worlds;
1414

15-
namespace Box2D.NET.Samples.Samples.Joints;
15+
namespace Box2D.NET.Samples.Samples.Events;
1616

1717
// This sample shows how to break joints when the internal reaction force becomes large. Instead of polling, this uses events.
1818
public class JointEvent : Sample
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// SPDX-FileCopyrightText: 2025 Erin Catto
2+
// SPDX-FileCopyrightText: 2025 Ikpil Choi([email protected])
3+
// SPDX-License-Identifier: MIT
4+
5+
using static Box2D.NET.B2Types;
6+
using static Box2D.NET.B2Bodies;
7+
using static Box2D.NET.B2Shapes;
8+
using static Box2D.NET.B2Ids;
9+
using static Box2D.NET.B2Worlds;
10+
using static Box2D.NET.B2Contacts;
11+
12+
namespace Box2D.NET.Samples.Samples.Events;
13+
14+
public class PersistentContact : Sample
15+
{
16+
private static int SamplePersistentContact = SampleFactory.Shared.RegisterSample("Events", "Persistent Contact", Create);
17+
18+
private B2ContactId m_contactId;
19+
20+
private static Sample Create(SampleContext context)
21+
{
22+
return new PersistentContact(context);
23+
}
24+
25+
public PersistentContact(SampleContext context) : base(context)
26+
{
27+
if (m_context.settings.restart == false)
28+
{
29+
m_context.camera.m_center = new B2Vec2(0.0f, 6.0f);
30+
m_context.camera.m_zoom = 7.5f;
31+
}
32+
33+
{
34+
B2BodyDef bodyDef = b2DefaultBodyDef();
35+
B2BodyId groundId = b2CreateBody(m_worldId, ref bodyDef);
36+
37+
B2Vec2[] points = new B2Vec2[22];
38+
float x = 10.0f;
39+
for (int i = 0; i < 20; ++i)
40+
{
41+
points[i] = new B2Vec2(x, 0.0f);
42+
x -= 1.0f;
43+
}
44+
45+
points[20] = new B2Vec2(-9.0f, 10.0f);
46+
points[21] = new B2Vec2(10.0f, 10.0f);
47+
48+
B2ChainDef chainDef = b2DefaultChainDef();
49+
chainDef.points = points;
50+
chainDef.count = 22;
51+
chainDef.isLoop = true;
52+
53+
b2CreateChain(groundId, ref chainDef);
54+
}
55+
56+
{
57+
B2BodyDef bodyDef = b2DefaultBodyDef();
58+
bodyDef.type = B2BodyType.b2_dynamicBody;
59+
bodyDef.position = new B2Vec2(-8.0f, 1.0f);
60+
bodyDef.linearVelocity = new B2Vec2(2.0f, 0.0f);
61+
62+
B2BodyId bodyId = b2CreateBody(m_worldId, ref bodyDef);
63+
64+
B2ShapeDef shapeDef = b2DefaultShapeDef();
65+
shapeDef.enableContactEvents = true;
66+
B2Circle circle = new B2Circle(new B2Vec2(0.0f, 0.0f), 0.5f);
67+
b2CreateCircleShape(bodyId, ref shapeDef, ref circle);
68+
}
69+
70+
m_contactId = b2_nullContactId;
71+
}
72+
73+
public override void Step()
74+
{
75+
base.Step();
76+
77+
B2ContactEvents events = b2World_GetContactEvents(m_worldId);
78+
for (int i = 0; i < events.beginCount && i < 1; ++i)
79+
{
80+
B2ContactBeginTouchEvent @event = events.beginEvents[i];
81+
m_contactId = events.beginEvents[i].contactId;
82+
}
83+
84+
for (int i = 0; i < events.endCount; ++i)
85+
{
86+
if (B2_ID_EQUALS(m_contactId, events.endEvents[i].contactId))
87+
{
88+
m_contactId = b2_nullContactId;
89+
break;
90+
}
91+
}
92+
93+
if (B2_IS_NON_NULL(m_contactId) && b2Contact_IsValid(m_contactId))
94+
{
95+
B2Manifold manifold = b2Contact_GetManifold(m_contactId);
96+
97+
for (int i = 0; i < manifold.pointCount; ++i)
98+
{
99+
ref readonly B2ManifoldPoint manifoldPoint = ref manifold.points[i];
100+
B2Vec2 p1 = manifoldPoint.point;
101+
B2Vec2 p2 = p1 + manifoldPoint.totalNormalImpulse * manifold.normal;
102+
m_draw.DrawSegment(p1, p2, B2HexColor.b2_colorCrimson);
103+
m_draw.DrawPoint(p1, 6.0f, B2HexColor.b2_colorCrimson);
104+
m_draw.DrawString(p1, $"{manifoldPoint.totalNormalImpulse:F2}");
105+
}
106+
}
107+
else
108+
{
109+
m_contactId = b2_nullContactId;
110+
}
111+
}
112+
}

src/Box2D.NET/B2Contact.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ public class B2Contact
3434

3535
// b2ContactFlags
3636
public uint flags;
37+
38+
// This is monotonically advanced when a contact is allocated in this slot
39+
// Used to check for invalid b2ContactId
40+
public uint generation;
3741

3842
public bool isMarked;
3943
}

src/Box2D.NET/B2ContactBeginTouchEvent.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@ public struct B2ContactBeginTouchEvent
1313
/// Id of the second shape
1414
public B2ShapeId shapeIdB;
1515

16+
/// The transient contact id. This contact maybe destroyed automatically by Box2D when the world is modified or simulated.
17+
/// Used b2Contact_IsValid before using this id.
18+
public B2ContactId contactId;
19+
1620
/// The initial contact manifold. This is recorded before the solver is called,
17-
/// so all the impulses will be zero.
21+
/// so all the impulses will be zero. You can use the contact id to access the manifold impulses
22+
/// using b2Contact_GetManifold.
1823
public B2Manifold manifold;
1924

20-
public B2ContactBeginTouchEvent(B2ShapeId shapeIdA, B2ShapeId shapeIdB, ref B2Manifold manifold)
25+
public B2ContactBeginTouchEvent(B2ShapeId shapeIdA, B2ShapeId shapeIdB, B2ContactId contactId, ref B2Manifold manifold)
2126
{
2227
this.shapeIdA = shapeIdA;
2328
this.shapeIdB = shapeIdB;
29+
this.contactId = contactId;
2430
this.manifold = manifold;
2531
}
2632
}

src/Box2D.NET/B2ContactEndTouchEvent.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ public struct B2ContactEndTouchEvent
1919
/// @warning this shape may have been destroyed
2020
/// @see b2Shape_IsValid
2121
public B2ShapeId shapeIdB;
22+
23+
/// Id of the contact
24+
public B2ContactId contactId;
2225

23-
public B2ContactEndTouchEvent(B2ShapeId shapeIdA, B2ShapeId shapeIdB)
26+
public B2ContactEndTouchEvent(B2ShapeId shapeIdA, B2ShapeId shapeIdB, B2ContactId contactId)
2427
{
2528
this.shapeIdA = shapeIdA;
2629
this.shapeIdB = shapeIdB;
30+
this.contactId = contactId;
2731
}
2832
}
2933
}

src/Box2D.NET/B2ContactId.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Box2D.NET
2+
{
3+
/// Contact id references a contact instance. This should be treated as an opaque handled.
4+
public readonly struct B2ContactId
5+
{
6+
public readonly int index1;
7+
public readonly ushort world0;
8+
public readonly short padding;
9+
public readonly uint generation;
10+
11+
public B2ContactId(int index1, ushort world0, short padding, uint generation)
12+
{
13+
this.index1 = index1;
14+
this.world0 = world0;
15+
this.padding = padding;
16+
this.generation = generation;
17+
}
18+
}
19+
}

src/Box2D.NET/B2Contacts.cs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
using static Box2D.NET.B2Collisions;
1515
using static Box2D.NET.B2Islands;
1616
using static Box2D.NET.B2ConstraintGraphs;
17-
17+
using static Box2D.NET.B2Worlds;
1818

1919
namespace Box2D.NET
2020
{
@@ -201,6 +201,7 @@ public static void b2CreateContact(B2World world, B2Shape shapeA, B2Shape shapeB
201201

202202
B2Contact contact = b2Array_Get(ref world.contacts, contactId);
203203
contact.contactId = contactId;
204+
contact.generation += 1;
204205
contact.setIndex = setIndex;
205206
contact.colorIndex = B2_NULL_INDEX;
206207
contact.localIndex = set.contactSims.count;
@@ -326,7 +327,18 @@ public static void b2DestroyContact(B2World world, B2Contact contact, bool wakeB
326327
B2ShapeId shapeIdA = new B2ShapeId(shapeA.id + 1, worldId, shapeA.generation);
327328
B2ShapeId shapeIdB = new B2ShapeId(shapeB.id + 1, worldId, shapeB.generation);
328329

329-
B2ContactEndTouchEvent @event = new B2ContactEndTouchEvent(shapeIdA, shapeIdB);
330+
B2ContactId contactId1 = new B2ContactId(
331+
index1: contact.contactId + 1,
332+
world0: world.worldId,
333+
padding: 0,
334+
generation: contact.generation
335+
);
336+
337+
B2ContactEndTouchEvent @event = new B2ContactEndTouchEvent(
338+
shapeIdA: shapeIdA,
339+
shapeIdB: shapeIdB,
340+
contactId: contactId1
341+
);
330342
b2Array_Push(ref world.contactEndEvents[world.endEventArrayIndex], @event);
331343
}
332344

@@ -405,11 +417,11 @@ public static void b2DestroyContact(B2World world, B2Contact contact, bool wakeB
405417
}
406418
}
407419

420+
// Free contact and id (preserve generation)
408421
contact.contactId = B2_NULL_INDEX;
409422
contact.setIndex = B2_NULL_INDEX;
410423
contact.colorIndex = B2_NULL_INDEX;
411424
contact.localIndex = B2_NULL_INDEX;
412-
413425
b2FreeId(world.contactIdPool, contactId);
414426

415427
if (wakeBodies && touching)
@@ -605,5 +617,44 @@ public static B2Manifold b2ComputeManifold(B2Shape shapeA, B2Transform transform
605617
B2SimplexCache cache = new B2SimplexCache();
606618
return fcn(shapeA, transformA, shapeB, transformB, ref cache);
607619
}
620+
621+
public static B2Contact b2GetContactFullId(B2World world, B2ContactId contactId)
622+
{
623+
int id = contactId.index1 - 1;
624+
B2Contact contact = b2Array_Get(ref world.contacts, id);
625+
B2_ASSERT(contact.contactId == id && contact.generation == contactId.generation);
626+
return contact;
627+
}
628+
629+
630+
/// Get manifold for a contact. The manifold may have no points if the contact is not touching.
631+
public static B2Manifold b2Contact_GetManifold(B2ContactId contactId)
632+
{
633+
B2World world = b2GetWorld(contactId.world0);
634+
B2Contact contact = b2GetContactFullId(world, contactId);
635+
B2ContactSim contactSim = b2GetContactSim(world, contact);
636+
return contactSim.manifold;
637+
}
638+
639+
/// Get the shapes associated with a contact.
640+
public static void b2Contact_GetShapeIds(B2ContactId contactId, out B2ShapeId shapeIdA, out B2ShapeId shapeIdB)
641+
{
642+
B2World world = b2GetWorld(contactId.world0);
643+
B2Contact contact = b2GetContactFullId(world, contactId);
644+
B2Shape shapeA = b2Array_Get(ref world.shapes, contact.shapeIdA);
645+
B2Shape shapeB = b2Array_Get(ref world.shapes, contact.shapeIdB);
646+
shapeIdA = new B2ShapeId(
647+
index1: shapeA.id + 1,
648+
world0: contactId.world0,
649+
generation: shapeA.generation
650+
);
651+
shapeIdB = new B2ShapeId(
652+
index1: shapeB.id + 1,
653+
world0: contactId.world0,
654+
generation: shapeB.generation
655+
);
656+
}
657+
658+
/**@}*/
608659
}
609660
}

src/Box2D.NET/B2Ids.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-FileCopyrightText: 2025 Ikpil Choi([email protected])
33
// SPDX-License-Identifier: MIT
44

5+
using System;
56
using System.Runtime.CompilerServices;
67

78
namespace Box2D.NET
@@ -41,6 +42,8 @@ public static class B2Ids
4142
public static readonly B2ShapeId b2_nullShapeId = new B2ShapeId(0, 0, 0);
4243
public static readonly B2ChainId b2_nullChainId = new B2ChainId(0, 0, 0);
4344
public static readonly B2JointId b2_nullJointId = new B2JointId(0, 0, 0);
45+
public static readonly B2ContactId b2_nullContactId = new B2ContactId(0, 0, 0, 0);
46+
4447

4548
/// Macro to determine if any id is null.
4649
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -74,13 +77,19 @@ public static class B2Ids
7477
[MethodImpl(MethodImplOptions.AggressiveInlining)]
7578
public static bool B2_IS_NON_NULL(B2JointId id) => id.index1 != 0;
7679

77-
/// Compare two ids for equality. Doesn't work for b2WorldId.
80+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
81+
public static bool B2_IS_NON_NULL(B2ContactId id) => id.index1 != 0;
82+
83+
/// Compare two ids for equality. Doesn't work for b2WorldId. Don't mix types.
7884
[MethodImpl(MethodImplOptions.AggressiveInlining)]
7985
public static bool B2_ID_EQUALS(B2BodyId id1, B2BodyId id2) => id1.index1 == id2.index1 && id1.world0 == id2.world0 && id1.generation == id2.generation;
8086

8187
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8288
public static bool B2_ID_EQUALS(B2ShapeId id1, B2ShapeId id2) => id1.index1 == id2.index1 && id1.world0 == id2.world0 && id1.generation == id2.generation;
8389

90+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
91+
public static bool B2_ID_EQUALS(B2ContactId id1, B2ContactId id2) => id1.index1 == id2.index1 && id1.world0 == id2.world0 && id1.generation == id2.generation;
92+
8493
/// Store a world id into a uint32_t.
8594
[MethodImpl(MethodImplOptions.AggressiveInlining)]
8695
public static uint b2StoreWorldId(B2WorldId id)
@@ -156,6 +165,26 @@ public static B2JointId b2LoadJointId(ulong x)
156165
return id;
157166
}
158167

168+
/// Store a contact id into 16 bytes
169+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
170+
public static void b2StoreContactId(B2ContactId id, Span<uint> values)
171+
{
172+
values[0] = (uint)id.index1;
173+
values[1] = (uint)id.world0;
174+
values[2] = (uint)id.generation;
175+
}
176+
177+
/// Load a two uint64_t into a contact id.
178+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
179+
public static B2ContactId b2LoadContactId(Span<uint> values)
180+
{
181+
B2ContactId id = new B2ContactId((int)values[0], (ushort)values[1], 0, values[2]);
182+
// id.index1 = (int32_t)values[0];
183+
// id.world0 = (uint16_t)values[1];
184+
// id.padding = 0;
185+
// id.generation = (uint32_t)values[2];
186+
return id;
187+
}
159188

160189
/**@}*/
161190
}

src/Box2D.NET/B2MotorJointDef.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Box2D.NET
66
{
77
/// A motor joint is used to control the relative motion between two bodies
8-
///
8+
/// You may move local frame A to change the target transform.
99
/// A typical usage is to control the movement of a dynamic body with respect to the ground.
1010
/// @ingroup motor_joint
1111
public struct B2MotorJointDef

0 commit comments

Comments
 (0)