Skip to content

Commit 2974a4f

Browse files
committed
feat: add missing shapes of chainloop and edge
1 parent 872943b commit 2974a4f

4 files changed

Lines changed: 64 additions & 15 deletions

File tree

pkg/plugin/physics2d/component/collider.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@ const (
2020
ShapeTypeBox
2121
// ShapeTypeConvexPolygon uses Vertices as a convex polygon in the shape's local frame.
2222
ShapeTypeConvexPolygon
23-
// ShapeTypeStaticChain uses ChainPoints for open chain segments (static/terrain-style
24-
// geometry only; not for moving dynamic shapes).
23+
// ShapeTypeStaticChain uses ChainPoints for open chain segments (static or kinematic
24+
// bodies only; not for dynamic bodies which require mass).
2525
ShapeTypeStaticChain
26+
// ShapeTypeStaticChainLoop uses ChainPoints for closed chain loops (static or kinematic
27+
// bodies only; not for dynamic bodies). Unlike ShapeTypeStaticChain, the last vertex
28+
// automatically connects back to the first, creating a sealed boundary.
29+
ShapeTypeStaticChainLoop
30+
// ShapeTypeEdge uses EdgeVertices (exactly 2 points) for a single line segment
31+
// (static or kinematic bodies only). Lighter than a 2-point chain for isolated barriers
32+
// or triggers.
33+
ShapeTypeEdge
2634
)
2735

2836
// ColliderShape is one child shape inside a compound Collider2D.
@@ -33,17 +41,20 @@ const (
3341
// - ShapeTypeBox → HalfExtents (half-width on X, half-height on Y, axis-aligned before LocalOffset/LocalRotation)
3442
// - ShapeTypeConvexPolygon → Vertices (convex polygon, respect backend limits)
3543
// - ShapeTypeStaticChain → ChainPoints (open polyline in local space)
44+
// - ShapeTypeStaticChainLoop → ChainPoints (closed loop in local space)
45+
// - ShapeTypeEdge → EdgeVertices (exactly 2 points in local space)
3646
type ColliderShape struct {
3747
ShapeType ShapeType `json:"shape_type"`
3848
LocalOffset Vec2 `json:"local_offset"`
3949
LocalRotation float64 `json:"local_rotation"`
4050
IsSensor bool `json:"is_sensor"`
4151

4252
// Geometry (use fields matching ShapeType).
43-
Radius float64 `json:"radius,omitempty"`
44-
HalfExtents Vec2 `json:"half_extents,omitempty"`
45-
Vertices []Vec2 `json:"vertices,omitempty"`
46-
ChainPoints []Vec2 `json:"chain_points,omitempty"`
53+
Radius float64 `json:"radius,omitempty"`
54+
HalfExtents Vec2 `json:"half_extents,omitempty"`
55+
Vertices []Vec2 `json:"vertices,omitempty"`
56+
ChainPoints []Vec2 `json:"chain_points,omitempty"`
57+
EdgeVertices [2]Vec2 `json:"edge_vertices,omitempty"`
4758

4859
// Material and per-shape collision filtering (fixture-level in Box2D).
4960
Friction float64 `json:"friction"`
@@ -117,9 +128,15 @@ func (s ColliderShape) Validate() error {
117128
return err
118129
}
119130
}
131+
for i, v := range s.EdgeVertices {
132+
if err := validateVec2(fmt.Sprintf("edge_vertices[%d]", i), v); err != nil {
133+
return err
134+
}
135+
}
120136

121137
switch s.ShapeType {
122-
case ShapeTypeCircle, ShapeTypeBox, ShapeTypeConvexPolygon, ShapeTypeStaticChain:
138+
case ShapeTypeCircle, ShapeTypeBox, ShapeTypeConvexPolygon, ShapeTypeStaticChain,
139+
ShapeTypeStaticChainLoop, ShapeTypeEdge:
123140
default:
124141
return fmt.Errorf("shape_type: unknown value %d", s.ShapeType)
125142
}

pkg/plugin/physics2d/internal/create.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,12 @@ func attachFixture(
121121
sh component.ColliderShape,
122122
bodyType uint8,
123123
) error {
124-
if sh.ShapeType == component.ShapeTypeStaticChain && bodyType != box2d.B2BodyType.B2_staticBody {
125-
return errors.New("static chain colliders require a static body")
124+
//nolint:exhaustive // We only care about static chain, static chain loop, and edge shapes
125+
switch sh.ShapeType {
126+
case component.ShapeTypeStaticChain, component.ShapeTypeStaticChainLoop, component.ShapeTypeEdge:
127+
if bodyType == box2d.B2BodyType.B2_dynamicBody {
128+
return fmt.Errorf("%d shape type cannot be used on dynamic bodies (zero mass)", sh.ShapeType)
129+
}
126130
}
127131

128132
shape, err := buildShape(sh)
@@ -155,6 +159,10 @@ func buildShape(sh component.ColliderShape) (box2d.B2ShapeInterface, error) {
155159
return buildPolygonShape(sh)
156160
case component.ShapeTypeStaticChain:
157161
return buildChainShape(sh)
162+
case component.ShapeTypeStaticChainLoop:
163+
return buildChainLoopShape(sh)
164+
case component.ShapeTypeEdge:
165+
return buildEdgeShape(sh)
158166
default:
159167
return nil, fmt.Errorf("unknown shape_type %d", sh.ShapeType)
160168
}
@@ -194,6 +202,24 @@ func buildChainShape(sh component.ColliderShape) (box2d.B2ShapeInterface, error)
194202
return &chain, nil
195203
}
196204

205+
func buildChainLoopShape(sh component.ColliderShape) (box2d.B2ShapeInterface, error) {
206+
pts := make([]box2d.B2Vec2, len(sh.ChainPoints))
207+
for i := range sh.ChainPoints {
208+
pts[i] = shapePointToBodySpace(sh.ChainPoints[i], sh.LocalOffset, sh.LocalRotation)
209+
}
210+
chain := box2d.MakeB2ChainShape()
211+
chain.CreateLoop(pts, len(sh.ChainPoints))
212+
return &chain, nil
213+
}
214+
215+
func buildEdgeShape(sh component.ColliderShape) (box2d.B2ShapeInterface, error) {
216+
v1 := shapePointToBodySpace(sh.EdgeVertices[0], sh.LocalOffset, sh.LocalRotation)
217+
v2 := shapePointToBodySpace(sh.EdgeVertices[1], sh.LocalOffset, sh.LocalRotation)
218+
edge := box2d.MakeB2EdgeShape()
219+
edge.Set(v1, v2)
220+
return &edge, nil
221+
}
222+
197223
// shapePointToBodySpace maps a point from shape-local space into body-local space using
198224
// LocalOffset and LocalRotation (radians, CCW +Y up) on the ColliderShape.
199225
func shapePointToBodySpace(p, offset component.Vec2, localRot float64) box2d.B2Vec2 {

pkg/plugin/physics2d/internal/shadow.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func deepCopyColliderShape(s component.ColliderShape) component.ColliderShape {
4949
HalfExtents: s.HalfExtents,
5050
Vertices: cloneVec2Slice(s.Vertices),
5151
ChainPoints: cloneVec2Slice(s.ChainPoints),
52+
EdgeVertices: s.EdgeVertices,
5253
Friction: s.Friction,
5354
Restitution: s.Restitution,
5455
Density: s.Density,
@@ -140,7 +141,9 @@ func colliderShapeDeepEqual(a, b component.ColliderShape) bool {
140141
a.GroupIndex != b.GroupIndex {
141142
return false
142143
}
143-
return vec2SliceEqual(a.Vertices, b.Vertices) && vec2SliceEqual(a.ChainPoints, b.ChainPoints)
144+
return vec2SliceEqual(a.Vertices, b.Vertices) &&
145+
vec2SliceEqual(a.ChainPoints, b.ChainPoints) &&
146+
a.EdgeVertices == b.EdgeVertices
144147
}
145148

146149
// Collider2DStructuralEqual reports whether two colliders match for Box2D fixture shape
@@ -166,7 +169,8 @@ func colliderShapeStructuralEqual(a, b component.ColliderShape) bool {
166169
a.Radius == b.Radius &&
167170
vec2Equal(a.HalfExtents, b.HalfExtents) &&
168171
vec2SliceEqual(a.Vertices, b.Vertices) &&
169-
vec2SliceEqual(a.ChainPoints, b.ChainPoints)
172+
vec2SliceEqual(a.ChainPoints, b.ChainPoints) &&
173+
a.EdgeVertices == b.EdgeVertices
170174
}
171175

172176
// ColliderShapeMutableFieldsEqual compares per-shape fields that Box2D can update without

pkg/plugin/physics2d/plugin.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@ const (
5050

5151
// Collider shape kinds (ColliderShape).
5252
const (
53-
ShapeTypeCircle = component.ShapeTypeCircle
54-
ShapeTypeBox = component.ShapeTypeBox
55-
ShapeTypeConvexPolygon = component.ShapeTypeConvexPolygon
56-
ShapeTypeStaticChain = component.ShapeTypeStaticChain
53+
ShapeTypeCircle = component.ShapeTypeCircle
54+
ShapeTypeBox = component.ShapeTypeBox
55+
ShapeTypeConvexPolygon = component.ShapeTypeConvexPolygon
56+
ShapeTypeStaticChain = component.ShapeTypeStaticChain
57+
ShapeTypeStaticChainLoop = component.ShapeTypeStaticChainLoop
58+
ShapeTypeEdge = component.ShapeTypeEdge
5759
)
5860

5961
// Contact / trigger system events (implement ecs.SystemEvent; register with WithSystemEventEmitter).

0 commit comments

Comments
 (0)