Support Tiled 1.12 capsule shape, refactor RoundRect and TMXObject#1326
Support Tiled 1.12 capsule shape, refactor RoundRect and TMXObject#1326
Conversation
…ject RoundRect: - Extend Polygon directly instead of Rect for proper SAT collision - Generate polygon vertices approximating rounded corners (8 segments/corner) - Precompute cos/sin lookup table, reuse vertex objects on size/radius changes - Own width/height/left/right/top/bottom/centerX/centerY getters TMXObject: - Refactor shape detection into detectShape() returning a string type - Replace multiple is* boolean flags with single shapeType property (booleans still set for backward compatibility) - Refactor parseTMXShapes() from nested if/else to clean switch - Add capsule case creating RoundRect with radius = min(w,h)/2 SAT collision: - Add RoundRect and Rectangle entries to SAT_LOOKUP table in detector.js Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds support for Tiled 1.12 “capsule” object shapes by representing them as polygon-approximated RoundRect shapes, and updates collision detection dispatch to recognize RoundRect/Rectangle type combinations.
Changes:
- Refactors
RoundRectto extendPolygonand generate vertices approximating rounded corners (enabling SAT-based collision). - Refactors
TMXObjectshape detection/parsing to use ashapeTypestring and adds capsule parsing (capsule →RoundRectwithradius = min(w,h)/2). - Extends the SAT dispatch lookup to handle
RoundRect*andRectangle*shape type pairings; adds/extends tests for capsule parsing and RoundRect behavior.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/melonjs/src/geometries/roundrect.ts | Reimplements RoundRect as a Polygon with rounded-corner vertex approximation and updated geometry API. |
| packages/melonjs/src/level/tiled/TMXObject.js | Refactors shape detection/parsing and adds capsule → RoundRect shape creation. |
| packages/melonjs/src/physics/detector.js | Adds SAT dispatch entries for RoundRect/Rectangle type combinations. |
| packages/melonjs/tests/roundrect.spec.ts | Adds extensive RoundRect tests (getters, contains, vertex generation/reuse). |
| packages/melonjs/tests/tmxobject.spec.js | Adds new TMXObject tests for shape detection and shape parsing (including capsule). |
| packages/melonjs/tests/tmxutils.spec.js | Adds XML capsule marker parsing tests (<capsule/>). |
| packages/melonjs/CHANGELOG.md | Documents capsule support and RoundRect collision behavior change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| set width(value) { | ||
| this._width = value; | ||
| this._updateVertices(); | ||
| } | ||
|
|
||
| /** | ||
| * height of the RoundRect | ||
| */ | ||
| get height() { | ||
| return this._height; | ||
| } | ||
| set height(value) { | ||
| this._height = value; | ||
| this._updateVertices(); | ||
| } |
There was a problem hiding this comment.
width/height setters call _updateVertices() without re-clamping radius to the new dimensions. Since updateRoundRectVertices assumes the radius is already clamped, shrinking the shape via these setters can leave _radius > min(width,height)/2, producing invalid vertices/bounds and incorrect contains() results. Consider routing these setters through setSize() (or reapplying this.radius = this._radius after updating _width/_height) so radius is always clamped on dimension changes.
| get radius() { | ||
| return this._radius; | ||
| } | ||
| set radius(value) { | ||
| // verify the rectangle is at least as wide and tall as the rounded corners. | ||
| if (this.width < 2 * value) { | ||
| value = this.width / 2; | ||
| // clamp radius to half the shorter side | ||
| if (this._width < 2 * value) { | ||
| value = this._width / 2; | ||
| } | ||
| if (this.height < 2 * value) { | ||
| value = this.height / 2; | ||
| if (this._height < 2 * value) { | ||
| value = this._height / 2; | ||
| } | ||
| this._radius = value; | ||
| this._updateVertices(); | ||
| } |
There was a problem hiding this comment.
radius can be set to a negative number and stored in _radius (no lower-bound clamp). This violates the stated “corner radius” semantics and also breaks the assumption that the value passed into updateRoundRectVertices is a clamped radius. Suggest clamping to >= 0 in the setter (e.g., value = Math.max(0, value)) before the size-based clamping.
| /** | ||
| * Returns true if the rounded rectangle contains the given rectangle | ||
| * @param rectangle - rectangle to test | ||
| * @returns true if contained | ||
| */ | ||
| containsRectangle(rectangle: RoundRect) { | ||
| return ( | ||
| rectangle.left >= this.left && | ||
| rectangle.right <= this.right && | ||
| rectangle.top >= this.top && | ||
| rectangle.bottom <= this.bottom | ||
| ); |
There was a problem hiding this comment.
containsRectangle is typed as containsRectangle(rectangle: RoundRect). Since RoundRect previously inherited Rect.containsRectangle(rectangle: Rect) (when it extended Rect), this narrows the TypeScript API and forces callers to cast when they have a Rect. Consider accepting Rect (or a structural type with left/right/top/bottom) to preserve the prior API surface.
Summary
RoundRect — extends Polygon directly
Polygoninstead ofRectfor proper SAT collision support (previously treated as a plain rectangle)Vector2dinstances when vertex count is unchanged (size/radius changes)width/height/left/right/top/bottom/centerX/centerYgetterscontext.roundRect()viashape.type === "RoundRect"TMXObject — refactored shape detection
detectShape()function returns a string type instead of setting multiple boolean flagsshapeTypeproperty replaces scatteredis*flags (booleans still set for backward compatibility)parseTMXShapes()refactored from nested if/else to clean switch statementRoundRectwithradius = min(width, height) / 2SAT collision
RoundRect*andRectangle*entries toSAT_LOOKUPtable indetector.jsCapsule object shape (Tiled 1.12)
<capsule/>marker on<object>(same pattern as<ellipse/>)"capsule": trueboolean propertyTest plan
{}), parseTMXShapes (all shapes: rectangle, ellipse, capsule, point, convex polygon, concave polygon → triangles, polyline), rotation on all applicable shapes, basic properties (position, rotation, class/type, opacity, visible)<capsule/>, with rotation, not on rectangle, not on ellipse🤖 Generated with Claude Code