Skip to content

Commit 94f4b5d

Browse files
obiotclaude
andcommitted
Decompose concave collision polygons into convex triangles
Instead of throwing an error when a concave polygon is encountered in a Tiled map, automatically decompose it into convex triangles using earcut triangulation and warn in the console. This allows maps with concave collision shapes to load and function correctly with the SAT collision algorithm. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 588db28 commit 94f4b5d

3 files changed

Lines changed: 23 additions & 111 deletions

File tree

packages/examples/public/assets/tiledMapLoader/map/orthogonal-outside.tmx

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -227,59 +227,8 @@
227227
</data>
228228
</layer>
229229
<objectgroup id="3" name="Objects">
230-
<object id="1" name="maggots" type="Location" x="435" y="74" width="155" height="99">
231-
<properties>
232-
<property name="spawncount" type="int" value="5"/>
233-
<property name="spawntype" value="maggot"/>
234-
</properties>
235-
</object>
236-
<object id="2" name="discover chest" type="Trigger" x="201" y="200" width="127" height="127">
237-
<properties>
238-
<property name="script" type="file" value="chest-discovered.lua"/>
239-
</properties>
240-
<ellipse/>
241-
</object>
242-
<object id="3" name="unreachable" type="Fixture" x="2" y="158">
243-
<properties>
244-
<property name="static" type="bool" value="true"/>
245-
</properties>
230+
<object id="3" x="2" y="158">
246231
<polygon points="0,0 55,-23 96,-117 110,-61 104,-42 119,-33 116,6 104,9 100,36 60,43 53,58 43,58 34,74 21,69 18,90 0,89"/>
247232
</object>
248-
<object id="5" name="guard" type="NPC" x="22" y="361">
249-
<polyline points="-3,120 87,91 154,96 181,16 273,-1"/>
250-
</object>
251-
<object id="6" name="guard" type="NPC" x="277" y="18">
252-
<polyline points="0,0 75,78 133,82 176,179 274,183"/>
253-
</object>
254-
<object id="10" gid="282" x="413.333" y="225.333" width="16" height="16"/>
255-
<object id="11" gid="282" x="421.667" y="218" width="16" height="16"/>
256-
<object id="12" gid="2147483930" x="423" y="235.333" width="16" height="16"/>
257-
<object id="13" gid="282" x="5" y="70" width="16" height="16"/>
258-
<object id="14" gid="282" x="-3.66667" y="80.3333" width="16" height="16"/>
259-
<object id="16" gid="283" x="538" y="418.333" width="16" height="16"/>
260-
<object id="17" gid="283" x="407.667" y="462" width="16" height="16"/>
261-
<object id="18" gid="283" x="417" y="473.667" width="16" height="16"/>
262-
<object id="19" gid="283" x="402.667" y="469" width="16" height="16"/>
263-
<object id="21" gid="2147483930" x="683.333" y="260.5" width="16" height="16"/>
264-
<object id="22" gid="282" x="692.167" y="269.167" width="16" height="16"/>
265-
<object id="23" gid="282" x="701.667" y="247.833" width="16" height="16"/>
266-
<object id="24" gid="282" x="688.5" y="242" width="16" height="16"/>
267-
<object id="25" gid="282" x="670.5" y="263.5" width="16" height="16"/>
268-
<object id="26" gid="282" x="680" y="284" width="16" height="16"/>
269-
<object id="27" gid="282" x="643.833" y="283.667" width="16" height="16"/>
270-
<object id="28" gid="282" x="63.4165" y="386" width="16" height="16"/>
271-
<object id="29" gid="282" x="9.0835" y="356.167" width="16" height="16"/>
272-
<object id="30" gid="282" x="11.9165" y="385" width="16" height="16"/>
273-
<object id="31" gid="282" x="54.2495" y="378.5" width="16" height="16"/>
274-
<object id="32" gid="2147483930" x="2.4165" y="364.5" width="16" height="16"/>
275-
<object id="33" gid="2147483930" x="41.5835" y="382.833" width="16" height="16"/>
276-
<object id="34" type="Sign" gid="257" x="670.667" y="87" width="16" height="16">
277-
<properties>
278-
<property name="text" value="East West"/>
279-
</properties>
280-
</object>
281-
<object id="37" name="player-start" type="Location" x="192" y="160">
282-
<point/>
283-
</object>
284233
</objectgroup>
285234
</map>

packages/examples/src/examples/tiledMapLoader/assets/map/orthogonal-outside.tmx

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -227,59 +227,8 @@
227227
</data>
228228
</layer>
229229
<objectgroup id="3" name="Objects">
230-
<object id="1" name="maggots" type="Location" x="435" y="74" width="155" height="99">
231-
<properties>
232-
<property name="spawncount" type="int" value="5"/>
233-
<property name="spawntype" value="maggot"/>
234-
</properties>
235-
</object>
236-
<object id="2" name="discover chest" type="Trigger" x="201" y="200" width="127" height="127">
237-
<properties>
238-
<property name="script" type="file" value="chest-discovered.lua"/>
239-
</properties>
240-
<ellipse/>
241-
</object>
242-
<object id="3" name="unreachable" type="Fixture" x="2" y="158">
243-
<properties>
244-
<property name="static" type="bool" value="true"/>
245-
</properties>
230+
<object id="3" x="2" y="158">
246231
<polygon points="0,0 55,-23 96,-117 110,-61 104,-42 119,-33 116,6 104,9 100,36 60,43 53,58 43,58 34,74 21,69 18,90 0,89"/>
247232
</object>
248-
<object id="5" name="guard" type="NPC" x="22" y="361">
249-
<polyline points="-3,120 87,91 154,96 181,16 273,-1"/>
250-
</object>
251-
<object id="6" name="guard" type="NPC" x="277" y="18">
252-
<polyline points="0,0 75,78 133,82 176,179 274,183"/>
253-
</object>
254-
<object id="10" gid="282" x="413.333" y="225.333" width="16" height="16"/>
255-
<object id="11" gid="282" x="421.667" y="218" width="16" height="16"/>
256-
<object id="12" gid="2147483930" x="423" y="235.333" width="16" height="16"/>
257-
<object id="13" gid="282" x="5" y="70" width="16" height="16"/>
258-
<object id="14" gid="282" x="-3.66667" y="80.3333" width="16" height="16"/>
259-
<object id="16" gid="283" x="538" y="418.333" width="16" height="16"/>
260-
<object id="17" gid="283" x="407.667" y="462" width="16" height="16"/>
261-
<object id="18" gid="283" x="417" y="473.667" width="16" height="16"/>
262-
<object id="19" gid="283" x="402.667" y="469" width="16" height="16"/>
263-
<object id="21" gid="2147483930" x="683.333" y="260.5" width="16" height="16"/>
264-
<object id="22" gid="282" x="692.167" y="269.167" width="16" height="16"/>
265-
<object id="23" gid="282" x="701.667" y="247.833" width="16" height="16"/>
266-
<object id="24" gid="282" x="688.5" y="242" width="16" height="16"/>
267-
<object id="25" gid="282" x="670.5" y="263.5" width="16" height="16"/>
268-
<object id="26" gid="282" x="680" y="284" width="16" height="16"/>
269-
<object id="27" gid="282" x="643.833" y="283.667" width="16" height="16"/>
270-
<object id="28" gid="282" x="63.4165" y="386" width="16" height="16"/>
271-
<object id="29" gid="282" x="9.0835" y="356.167" width="16" height="16"/>
272-
<object id="30" gid="282" x="11.9165" y="385" width="16" height="16"/>
273-
<object id="31" gid="282" x="54.2495" y="378.5" width="16" height="16"/>
274-
<object id="32" gid="2147483930" x="2.4165" y="364.5" width="16" height="16"/>
275-
<object id="33" gid="2147483930" x="41.5835" y="382.833" width="16" height="16"/>
276-
<object id="34" type="Sign" gid="257" x="670.667" y="87" width="16" height="16">
277-
<properties>
278-
<property name="text" value="East West"/>
279-
</properties>
280-
</object>
281-
<object id="37" name="player-start" type="Location" x="192" y="160">
282-
<point/>
283-
</object>
284233
</objectgroup>
285234
</map>

packages/melonjs/src/level/tiled/TMXObject.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -226,15 +226,29 @@ export default class TMXObject {
226226
if (this.isPolygon === true) {
227227
const _polygon = polygonPool.get(0, 0, this.points);
228228
const isConvex = _polygon.isConvex();
229-
// make sure it's a convex polygon
230-
if (isConvex === false) {
231-
throw new Error(
232-
"collision polygones in Tiled should be defined as Convex",
229+
230+
if (isConvex === true) {
231+
shapes.push(_polygon.rotate(this.rotation));
232+
} else if (isConvex === false) {
233+
// decompose concave polygon into convex triangles
234+
console.warn(
235+
"melonJS: concave collision polygon detected, decomposing into convex triangles",
233236
);
234-
} else if (isConvex === null) {
235-
throw new Error("invalid polygone");
237+
const indices = _polygon.getIndices();
238+
const pts = _polygon.points;
239+
for (let t = 0; t < indices.length; t += 3) {
240+
const tri = polygonPool.get(0, 0, [
241+
vector2dPool.get(pts[indices[t]].x, pts[indices[t]].y),
242+
vector2dPool.get(pts[indices[t + 1]].x, pts[indices[t + 1]].y),
243+
vector2dPool.get(pts[indices[t + 2]].x, pts[indices[t + 2]].y),
244+
]);
245+
shapes.push(tri.rotate(this.rotation));
246+
}
247+
polygonPool.release(_polygon);
248+
} else {
249+
console.warn("melonJS: invalid polygon definition, skipping");
250+
polygonPool.release(_polygon);
236251
}
237-
shapes.push(_polygon.rotate(this.rotation));
238252
} else if (this.isPolyLine === true) {
239253
const p = this.points;
240254
let p1;

0 commit comments

Comments
 (0)