From 37b13b55cbd6536efb234b774a8ab2cafb8a577d Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Sat, 30 Dec 2023 19:46:24 -0600 Subject: [PATCH] fix: Fixes for Tiled plugin (#2858) * fix: Incorrect type on tilemap getTileByPoint * feat: Add convex poly warning suppression * change: Remove dep on Isometric map * fix: Infinite loop with certain polygon triangulations * Fix for colinear triangles * Pass through debug draw settings in composite * Add base elevation for Isometric map * Implement isometric map visibility and opacity * feat: add tileset offset --- CHANGELOG.md | 10 +- sandbox/tests/polygon/index.html | 12 ++ sandbox/tests/polygon/index.ts | 95 +++++++++ .../Collision/Colliders/CircleCollider.ts | 5 +- src/engine/Collision/Colliders/Collider.ts | 2 +- .../Collision/Colliders/CompositeCollider.ts | 4 +- .../Collision/Colliders/PolygonCollider.ts | 199 ++++++++++++------ src/engine/Collision/Colliders/Shape.ts | 5 +- src/engine/Debug/Debug.ts | 25 ++- src/engine/Debug/DebugSystem.ts | 10 +- .../TileMap/IsometricEntityComponent.ts | 21 +- src/engine/TileMap/IsometricEntitySystem.ts | 2 +- src/engine/TileMap/IsometricMap.ts | 83 ++++++-- src/engine/TileMap/TileMap.ts | 30 ++- src/spec/CollisionShapeSpec.ts | 6 +- src/spec/DebugSystemSpec.ts | 4 + src/spec/IsometricMapSpec.ts | 3 + .../CollisionShapeSpec/circle-debug.png | Bin 1951 -> 1831 bytes .../CompositeColliderSpec/composite.png | Bin 7474 -> 6015 bytes src/spec/images/DebugSystemSpec/body.png | Bin 9913 -> 8779 bytes .../DebugSystemSpec/composite-collider.png | Bin 15717 -> 14686 bytes src/spec/images/DebugSystemSpec/graphics.png | Bin 6538 -> 5745 bytes src/spec/images/DebugSystemSpec/motion.png | Bin 7831 -> 7224 bytes .../IsometricMapSpec/cube-map-debug.png | Bin 12348 -> 11901 bytes 24 files changed, 406 insertions(+), 110 deletions(-) create mode 100644 sandbox/tests/polygon/index.html create mode 100644 sandbox/tests/polygon/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 35692a1f1..f44055a67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Added ability to apply draw offset to `ex.IsometricMap` and `ex.Tilemap` +- Added `visibility` and `opacity` to `ex.IsometricMap` +- Added base elevation for `ex.IsometricMap` so multiple maps can sort correctly +- Added method to suppress convex polygon warning for library code usage +- Added more configuration options to debug draw flags, including isometric map controls - Added `actionstart` and `actioncomplete` events to the Actor that are fired when an action starts and completes ### Fixed +- Fixed infinite loop :bomb: when certain degenerate polygons were attempted to be triangulated! +- Fixed incorrect type on `ex.Tilemap.getTileByPoint()` - Fixed TS type on `GraphicsComponent` and allow `.material` to be null to unset, current workaround is using `.material = null as any` ### Updates @@ -28,7 +35,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed -- +- Tweaked debug draw to be less noisy by default +- Removed dependency on `ex.IsometricMap` in the `ex.IsometricEntityComponent`, this allows for greater flexibility when using the component when a map may not be known or constructed. diff --git a/sandbox/tests/polygon/index.html b/sandbox/tests/polygon/index.html new file mode 100644 index 000000000..f96a40eba --- /dev/null +++ b/sandbox/tests/polygon/index.html @@ -0,0 +1,12 @@ + + + + + + Polygon Triangulate + + + + + + \ No newline at end of file diff --git a/sandbox/tests/polygon/index.ts b/sandbox/tests/polygon/index.ts new file mode 100644 index 000000000..854e3ccdf --- /dev/null +++ b/sandbox/tests/polygon/index.ts @@ -0,0 +1,95 @@ +/// + + +var game = new ex.Engine({ + width: 800, + height: 600, + displayMode: ex.DisplayMode.FitScreenAndFill +}); +game.toggleDebug(); +game.debug.collider.showBounds = false; + +var actor = new ex.Actor({pos: ex.vec(200, 100)}); +// var shape = ex.Shape.Polygon([ +// ex.vec(0,0), +// ex.vec(-1.6875,-25.0625), +// ex.vec(6.3125,-25.0625), +// ex.vec(5.625,-33.125), +// ex.vec(21.5625,-32.8125), +// ex.vec(21.4375,-40.6875), +// ex.vec(29.1875,-41.9375), +// ex.vec(32.8125,-38.3125), +// ex.vec(33.0625,-21.8125), +// ex.vec(25.8125,-21.5625), +// ex.vec(24.125,10.375), +// ex.vec(-8.75,8.5), +// ex.vec(-11.8125,5.5625), +// ex.vec(-10.9375,-0.5625), +// ex.vec(-3.3125,-2.4375) +// ]); + +// var points = [ +// ex.vec(0,0), +// ex.vec(-1.6875,-25.0625), +// ex.vec(6.3125,-25.0625), +// ex.vec(5.625,-33.125), +// ex.vec(21.5625,-32.8125), +// ex.vec(21.4375,-40.6875), +// ex.vec(29.1875,-41.9375), +// ex.vec(32.8125,-38.3125), +// ex.vec(33.0625,-21.8125), +// ex.vec(25.8125,-21.5625), +// ex.vec(24.125,10.375), +// ex.vec(-8.75,8.5), +// ex.vec(-11.8125,5.5625), +// ex.vec(-10.9375,-0.5625), +// ex.vec(-3.3125,-2.4375) +// ] + +// var points = [ +// ex.vec(200,100), +// ex.vec(300,320), +// ex.vec(400,100), +// ex.vec(500,300), +// ex.vec(350,300), +// ex.vec(300,500), +// ex.vec(250,300), +// ex.vec(100,300)]; + +var points = [ + ex.vec(343,392), + ex.vec(475,103), + ex.vec(245,151), + ex.vec(193,323), + ex.vec(91, 279), + ex.vec(51, 301), + ex.vec(25, 381), + ex.vec(80, 334), + ex.vec(142,418), + ex.vec(325,480), + ex.vec(340,564), + ex.vec(468,597) +] + +var colinear = [ + ex.vec(160, 80), + ex.vec(80, 40), + ex.vec(0, 0), +]; + +function triangleArea(a: ex.Vector, b: ex.Vector, c: ex.Vector) { + return Math.abs((a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - c.y)))/2 +} + +console.log('triangle area', triangleArea(ex.vec(160, 80), +ex.vec(80, 40), +ex.vec(0, 0))); + +var shape = ex.Shape.Polygon(points, ex.Vector.Zero, true); + +var triangulated = shape.triangulate(); +actor.collider.set(triangulated); +game.add(actor); + + +game.start(); \ No newline at end of file diff --git a/src/engine/Collision/Colliders/CircleCollider.ts b/src/engine/Collision/Colliders/CircleCollider.ts index 7ec9b6458..bd1f5bcd0 100644 --- a/src/engine/Collision/Colliders/CircleCollider.ts +++ b/src/engine/Collision/Colliders/CircleCollider.ts @@ -253,7 +253,8 @@ export class CircleCollider extends Collider { return new Projection(Math.min.apply(Math, scalars), Math.max.apply(Math, scalars)); } - public debug(ex: ExcaliburGraphicsContext, color: Color) { + public debug(ex: ExcaliburGraphicsContext, color: Color, options?: { lineWidth: number }) { + const { lineWidth } = { ...{ lineWidth: 1 }, ...options }; const tx = this._transform; const scale = tx?.globalScale ?? Vector.One; const rotation = tx?.globalRotation ?? 0; @@ -262,7 +263,7 @@ export class CircleCollider extends Collider { ex.translate(pos.x, pos.y); ex.rotate(rotation); ex.scale(scale.x, scale.y); - ex.drawCircle((this.offset ?? Vector.Zero), this._naturalRadius, Color.Transparent, color, 2); + ex.drawCircle((this.offset ?? Vector.Zero), this._naturalRadius, Color.Transparent, color, lineWidth); ex.restore(); } } diff --git a/src/engine/Collision/Colliders/Collider.ts b/src/engine/Collision/Colliders/Collider.ts index 7b5c1e826..726b4518b 100644 --- a/src/engine/Collision/Colliders/Collider.ts +++ b/src/engine/Collision/Colliders/Collider.ts @@ -113,7 +113,7 @@ export abstract class Collider implements Clonable { abstract update(transform: Transform): void; - abstract debug(ex: ExcaliburGraphicsContext, color: Color): void; + abstract debug(ex: ExcaliburGraphicsContext, color: Color, options?: { lineWidth: number, pointSize: number }): void; abstract clone(): Collider; } diff --git a/src/engine/Collision/Colliders/CompositeCollider.ts b/src/engine/Collision/Colliders/CompositeCollider.ts index 6ed7b7d6e..c8701dd65 100644 --- a/src/engine/Collision/Colliders/CompositeCollider.ts +++ b/src/engine/Collision/Colliders/CompositeCollider.ts @@ -236,10 +236,10 @@ export class CompositeCollider extends Collider { } } - public debug(ex: ExcaliburGraphicsContext, color: Color) { + public debug(ex: ExcaliburGraphicsContext, color: Color, options?: { lineWidth: number, pointSize: number }) { const colliders = this.getColliders(); for (const collider of colliders) { - collider.debug(ex, color); + collider.debug(ex, color, options); } } diff --git a/src/engine/Collision/Colliders/PolygonCollider.ts b/src/engine/Collision/Colliders/PolygonCollider.ts index 26959e044..a9a12f22c 100644 --- a/src/engine/Collision/Colliders/PolygonCollider.ts +++ b/src/engine/Collision/Colliders/PolygonCollider.ts @@ -11,7 +11,7 @@ import { AffineMatrix } from '../../Math/affine-matrix'; import { Ray } from '../../Math/ray'; import { ClosestLineJumpTable } from './ClosestLineJumpTable'; import { Collider } from './Collider'; -import { ExcaliburGraphicsContext, Logger, range } from '../..'; +import { ExcaliburGraphicsContext, Logger } from '../..'; import { CompositeCollider } from './CompositeCollider'; import { Shape } from './Shape'; import { Transform } from '../../Math/transform'; @@ -25,6 +25,11 @@ export interface PolygonColliderOptions { * Points in the polygon in order around the perimeter in local coordinates. These are relative from the body transform position. */ points: Vector[]; + + /** + * Suppresses convexity warning + */ + suppressConvexWarning?: boolean; } /** @@ -72,10 +77,13 @@ export class PolygonCollider extends Collider { if (!counterClockwise) { this.points.reverse(); } + if (!this.isConvex()) { - this._logger.warn( - 'Excalibur only supports convex polygon colliders and will not behave properly.'+ - 'Call PolygonCollider.triangulate() to build a new collider composed of smaller convex triangles'); + if (!options.suppressConvexWarning) { + this._logger.warn( + 'Excalibur only supports convex polygon colliders and will not behave properly.' + + 'Call PolygonCollider.triangulate() to build a new collider composed of smaller convex triangles'); + } } // calculate initial transformation @@ -109,13 +117,13 @@ export class PolygonCollider extends Collider { for (const [i, point] of this.points.entries()) { oldPoint = newPoint; oldDirection = direction; - newPoint = point; + newPoint = point; direction = Math.atan2(newPoint.y - oldPoint.y, newPoint.x - oldPoint.x); if (oldPoint.equals(newPoint)) { return false; // repeat point } let angle = direction - oldDirection; - if (angle <= -Math.PI){ + if (angle <= -Math.PI) { angle += Math.PI * 2; } else if (angle > Math.PI) { angle -= Math.PI * 2; @@ -124,7 +132,7 @@ export class PolygonCollider extends Collider { if (angle === 0.0) { return false; } - orientation = angle > 0 ? 1 : -1; + orientation = angle > 0 ? 1 : -1; } else { if (orientation * angle <= 0) { return false; @@ -158,19 +166,48 @@ export class PolygonCollider extends Collider { throw Error('Invalid polygon'); } + const triangles: [Vector, Vector, Vector][] = []; + // algorithm likes clockwise + const vertices = [...this.points].reverse(); + let vertexCount = vertices.length; + /** - * Helper to get a vertex in the list + * Returns the previous index based on the current vertex */ - function getItem(index: number, list: T[]) { - if (index >= list.length) { - return list[index % list.length]; - } else if (index < 0) { - return list[index % list.length + list.length]; - } else { - return list[index]; + function getPrevIndex(index: number) { + return index === 0 ? vertexCount - 1 : index - 1; + } + + /** + * Retrieves the next index based on the current vertex + */ + function getNextIndex(index: number) { + return index === vertexCount - 1 ? 0 : index + 1; + } + + /** + * Whether or not the angle at this vertex index is convex + */ + function isConvex(index: number) { + const prev = getPrevIndex(index); + const next = getNextIndex(index); + + const va = vertices[prev]; + const vb = vertices[index]; + const vc = vertices[next]; + + // Check convexity + const leftArm = va.sub(vb); + const rightArm = vc.sub(vb); + // Positive cross product is convex + if (leftArm.cross(rightArm) < 0) { + return false; } + return true; } + const convexVertices = vertices.map((_,i) => isConvex(i)); + /** * Quick test for point in triangle */ @@ -193,61 +230,96 @@ export class PolygonCollider extends Collider { return true; } - const triangles: Vector[][] = []; - const vertices = [...this.points]; - const indices = range(0, this.points.length - 1); - - // 1. Loop through vertices clockwise - // if the vertex is convex (interior angle is < 180) (cross product positive) - // if the polygon formed by it's edges doesn't contain the points - // it's an ear add it to our list of triangles, and restart - - while (indices.length > 3) { - for (let i = 0; i < indices.length; i++) { - const a = indices[i]; - const b = getItem(i - 1, indices); - const c = getItem(i + 1, indices); - - const va = vertices[a]; - const vb = vertices[b]; - const vc = vertices[c]; - - // Check convexity - const leftArm = vb.sub(va); - const rightArm = vc.sub(va); - const isConvex = rightArm.cross(leftArm) > 0; // positive cross means convex - if (!isConvex) { - continue; - } + /** + * Calculate the area of the triangle + */ + // function triangleArea(a: Vector, b: Vector, c: Vector) { + // return Math.abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - c.y))/2; + // } - let isEar = true; - // Check that if any vertices are in the triangle a, b, c - for (let j = 0; j < indices.length; j++) { - const vertIndex = indices[j]; - // We can skip these - if (vertIndex === a || vertIndex === b || vertIndex === c) { - continue; + /** + * Find the next suitable ear tip + */ + function findEarTip() { + for (let i = 0; i < vertexCount; i++) { + if (convexVertices[i]) { + + const prev = getPrevIndex(i); + const next = getNextIndex(i); + + const va = vertices[prev]; + const vb = vertices[i]; + const vc = vertices[next]; + + let isEar = true; + // Check that if any vertices are in the triangle a, b, c + for (let j = 0; j < vertexCount; j++) { + // We can skip these verts because they are the triangle we are testing + if (j === i || j === prev || j === next) { + continue; + } + const point = vertices[j]; + if (isPointInTriangle(point, va, vb, vc)) { + isEar = false; + break; + } } - const point = vertices[vertIndex]; - if (isPointInTriangle(point, vb, va, vc)) { - isEar = false; - break; + // Add ear to polygon list and remove from list + if (isEar) { + return i; } } + } - // Add ear to polygon list and remove from list - if (isEar) { - triangles.push([vb, va, vc]); - indices.splice(i, 1); - break; + // Fall back to any convex vertex + for (let i = 0; i < vertexCount; i++) { + if (convexVertices[i]) { + return i; } } + + // bail and return the first one? + return 0; + } + + /** + * Cut the ear and produce a triangle, update internal state + */ + function cutEarTip(index: number) { + const prev = getPrevIndex(index); + const next = getNextIndex(index); + + const va = vertices[prev]; + const vb = vertices[index]; + const vc = vertices[next]; + + // Clockwise winding + // if (triangleArea(va, vb, vc) > 0) { + triangles.push([va, vb, vc]); + // } + vertices.splice(index, 1); + convexVertices.splice(index, 1); + vertexCount--; + } + + // Loop over all the vertices finding ears + while (vertexCount > 3) { + const earIndex = findEarTip(); + cutEarTip(earIndex); + + // reclassify vertices + for (let i = 0; i < vertexCount; i++) { + convexVertices[i] = isConvex(i); + } } - triangles.push([vertices[indices[0]], vertices[indices[1]], vertices[indices[2]]]); + // Last triangle after the loop + triangles.push([vertices[0], vertices[1], vertices[2]]); - return new CompositeCollider(triangles.map(points => Shape.Polygon(points))); + // FIXME: there is a colinear triangle that sneaks in here sometimes + return new CompositeCollider( + triangles.map(points => Shape.Polygon(points, Vector.Zero, true))); } /** @@ -614,13 +686,14 @@ export class PolygonCollider extends Collider { return new Projection(min, max); } - public debug(ex: ExcaliburGraphicsContext, color: Color) { + public debug(ex: ExcaliburGraphicsContext, color: Color, options?: { lineWidth: number, pointSize: number }) { const firstPoint = this.getTransformedPoints()[0]; const points = [firstPoint, ...this.getTransformedPoints(), firstPoint]; + const { lineWidth, pointSize } = { ...{ lineWidth: 1, pointSize: 1 }, ...options }; for (let i = 0; i < points.length - 1; i++) { - ex.drawLine(points[i], points[i + 1], color, 2); - ex.drawCircle(points[i], 2, color); - ex.drawCircle(points[i + 1], 2, color); + ex.drawLine(points[i], points[i + 1], color, lineWidth); + ex.drawCircle(points[i], pointSize, color); + ex.drawCircle(points[i + 1], pointSize, color); } } } diff --git a/src/engine/Collision/Colliders/Shape.ts b/src/engine/Collision/Colliders/Shape.ts index 65b98d234..3ddcb34e3 100644 --- a/src/engine/Collision/Colliders/Shape.ts +++ b/src/engine/Collision/Colliders/Shape.ts @@ -31,10 +31,11 @@ export class Shape { * @param points Points specified in counter clockwise * @param offset Optional offset relative to the collider in local coordinates */ - static Polygon(points: Vector[], offset: Vector = Vector.Zero): PolygonCollider { + static Polygon(points: Vector[], offset: Vector = Vector.Zero, suppressConvexWarning = false): PolygonCollider { return new PolygonCollider({ points: points, - offset: offset + offset: offset, + suppressConvexWarning }); } diff --git a/src/engine/Debug/Debug.ts b/src/engine/Debug/Debug.ts index c2c930e9d..09460943c 100644 --- a/src/engine/Debug/Debug.ts +++ b/src/engine/Debug/Debug.ts @@ -246,7 +246,7 @@ export class Debug implements DebugFlags { */ public entity = { showAll: false, - showId: true, + showId: false, showName: false }; @@ -256,6 +256,7 @@ export class Debug implements DebugFlags { public transform = { showAll: false, + debugZIndex: 10_000_000, showPosition: false, showPositionLabel: false, positionColor: Color.Yellow, @@ -275,7 +276,7 @@ export class Debug implements DebugFlags { public graphics = { showAll: false, - showBounds: true, + showBounds: false, boundsColor: Color.Yellow }; @@ -285,13 +286,15 @@ export class Debug implements DebugFlags { public collider = { showAll: false, - showBounds: true, + showBounds: false, boundsColor: Color.Blue, showOwner: false, showGeometry: true, - geometryColor: Color.Green + geometryColor: Color.Green, + geometryLineWidth: 1, + geometryPointSize: .5 }; /** @@ -359,6 +362,20 @@ export class Debug implements DebugFlags { colliderGeometryColor: Color.Green, showQuadTree: false }; + + public isometric = { + showAll: false, + showPosition: false, + positionColor: Color.Yellow, + positionSize: 1, + showGrid: false, + gridColor: Color.Red, + gridWidth: 1, + showColliders: true, + colliderColor: Color.Green, + colliderLineWidth: 1, + colliderPointSize: .5 + }; } /** diff --git a/src/engine/Debug/DebugSystem.ts b/src/engine/Debug/DebugSystem.ts index 3a4062d43..fcc139321 100644 --- a/src/engine/Debug/DebugSystem.ts +++ b/src/engine/Debug/DebugSystem.ts @@ -91,6 +91,7 @@ export class DebugSystem extends System { this._pushCameraTransform(tx); this._graphicsContext.save(); + this._graphicsContext.z = txSettings.debugZIndex; this._applyTransform(entity); if (tx) { @@ -182,6 +183,9 @@ export class DebugSystem extends System { this._graphicsContext.restore(); + // World space + this._graphicsContext.save(); + this._graphicsContext.z = txSettings.debugZIndex; motion = entity.get(MotionComponent); if (motion) { if (motionSettings.showAll || motionSettings.showVelocity) { @@ -200,7 +204,10 @@ export class DebugSystem extends System { if (colliderComp) { const collider = colliderComp.get(); if ((colliderSettings.showAll || colliderSettings.showGeometry) && collider) { - collider.debug(this._graphicsContext, colliderSettings.geometryColor); + collider.debug(this._graphicsContext, colliderSettings.geometryColor, { + lineWidth: colliderSettings.geometryLineWidth, + pointSize: colliderSettings.geometryPointSize + }); } if (colliderSettings.showAll || colliderSettings.showBounds) { if (collider instanceof CompositeCollider) { @@ -225,6 +232,7 @@ export class DebugSystem extends System { } } + this._graphicsContext.restore(); this._popCameraTransform(tx); } diff --git a/src/engine/TileMap/IsometricEntityComponent.ts b/src/engine/TileMap/IsometricEntityComponent.ts index 45fa336a3..5a6d69c64 100644 --- a/src/engine/TileMap/IsometricEntityComponent.ts +++ b/src/engine/TileMap/IsometricEntityComponent.ts @@ -1,6 +1,13 @@ import { Component } from '../EntityComponentSystem/Component'; import { IsometricMap } from './IsometricMap'; +export interface IsometricEntityComponentOptions { + columns: number; + rows: number; + tileWidth: number; + tileHeight: number; +} + export class IsometricEntityComponent extends Component<'ex.isometricentity'> { public readonly type = 'ex.isometricentity'; /** @@ -8,14 +15,20 @@ export class IsometricEntityComponent extends Component<'ex.isometricentity'> { */ public elevation: number = 0; - public map: IsometricMap; + public readonly columns: number; + public readonly rows: number; + public readonly tileWidth: number; + public readonly tileHeight: number; /** * Specify the isometric map to use to position this entity's z-index - * @param map + * @param mapOrOptions */ - constructor(map: IsometricMap) { + constructor(mapOrOptions: IsometricMap | IsometricEntityComponentOptions) { super(); - this.map = map; + this.columns = mapOrOptions.columns; + this.rows = mapOrOptions.rows; + this.tileWidth = mapOrOptions.tileWidth; + this.tileHeight = mapOrOptions.tileHeight; } } \ No newline at end of file diff --git a/src/engine/TileMap/IsometricEntitySystem.ts b/src/engine/TileMap/IsometricEntitySystem.ts index bb4e4c6d6..9395765c3 100644 --- a/src/engine/TileMap/IsometricEntitySystem.ts +++ b/src/engine/TileMap/IsometricEntitySystem.ts @@ -15,7 +15,7 @@ export class IsometricEntitySystem extends System this.debug(ctx), false) + new DebugGraphicsComponent((ctx, debugFlags) => this.debug(ctx, debugFlags), false) ], options.name); - const { pos, tileWidth, tileHeight, columns: width, rows: height, renderFromTopOfGraphic, graphicsOffset } = options; + const { pos, tileWidth, tileHeight, columns: width, rows: height, renderFromTopOfGraphic, graphicsOffset, elevation } = options; this.transform = this.get(TransformComponent); if (pos) { @@ -292,6 +311,7 @@ export class IsometricMap extends Entity { this.renderFromTopOfGraphic = renderFromTopOfGraphic ?? this.renderFromTopOfGraphic; this.graphicsOffset = graphicsOffset ?? this.graphicsOffset; + this.elevation = elevation ?? this.elevation; this.tileWidth = tileWidth; this.tileHeight = tileHeight; this.columns = width; @@ -305,7 +325,6 @@ export class IsometricMap extends Entity { const tile = new IsometricTile(x, y, this.graphicsOffset, this); this.tiles[x + y * width] = tile; this.addChild(tile); - // TODO row/columns helpers } } } @@ -415,23 +434,47 @@ export class IsometricMap extends Entity { * Debug draw for IsometricMap, called internally by excalibur when debug mode is toggled on * @param gfx */ - public debug(gfx: ExcaliburGraphicsContext) { + public debug(gfx: ExcaliburGraphicsContext, debugFlags: Debug) { + const { + showAll, + showPosition, + positionColor, + positionSize, + showGrid, + gridColor, + gridWidth, + showColliders, + colliderColor, + colliderLineWidth, + colliderPointSize + } = debugFlags.isometric; gfx.save(); gfx.z = this._getMaxZIndex() + 0.5; - for (let y = 0; y < this.rows + 1; y++) { - const left = this.tileToWorld(vec(0, y)); - const right = this.tileToWorld(vec(this.columns, y)); - gfx.drawLine(left, right, Color.Red, 2); - } + if (showAll || showGrid) { + for (let y = 0; y < this.rows + 1; y++) { + const left = this.tileToWorld(vec(0, y)); + const right = this.tileToWorld(vec(this.columns, y)); + gfx.drawLine(left, right, gridColor, gridWidth); + } - for (let x = 0; x < this.columns + 1; x++) { - const top = this.tileToWorld(vec(x, 0)); - const bottom = this.tileToWorld(vec(x, this.rows)); - gfx.drawLine(top, bottom, Color.Red, 2); + for (let x = 0; x < this.columns + 1; x++) { + const top = this.tileToWorld(vec(x, 0)); + const bottom = this.tileToWorld(vec(x, this.rows)); + gfx.drawLine(top, bottom, gridColor, gridWidth); + } } - for (const tile of this.tiles) { - gfx.drawCircle(this.tileToWorld(vec(tile.x, tile.y)), 3, Color.Yellow); + if (showAll || showPosition) { + for (const tile of this.tiles) { + gfx.drawCircle(this.tileToWorld(vec(tile.x, tile.y)), positionSize, positionColor); + } + } + if (showAll || showColliders) { + for (const tile of this.tiles) { + for (const collider of tile.getColliders()) { + collider.debug(gfx, colliderColor, { lineWidth: colliderLineWidth, pointSize: colliderPointSize }); + } + } } gfx.restore(); } diff --git a/src/engine/TileMap/TileMap.ts b/src/engine/TileMap/TileMap.ts index a92ae978c..9acf18982 100644 --- a/src/engine/TileMap/TileMap.ts +++ b/src/engine/TileMap/TileMap.ts @@ -8,7 +8,6 @@ import { BodyComponent } from '../Collision/BodyComponent'; import { CollisionType } from '../Collision/CollisionType'; import { Shape } from '../Collision/Colliders/Shape'; import { ExcaliburGraphicsContext, Graphic, GraphicsComponent, hasGraphicsTick, ParallaxComponent } from '../Graphics'; -import { removeItemFromArray } from '../Util/Util'; import { MotionComponent } from '../EntityComponentSystem/Components/MotionComponent'; import { ColliderComponent } from '../Collision/ColliderComponent'; import { CompositeCollider } from '../Collision/Colliders/CompositeCollider'; @@ -437,7 +436,7 @@ export class TileMap extends Entity { * Returns the [[Tile]] by testing a point in world coordinates, * returns `null` if no Tile was found. */ - public getTileByPoint(point: Vector): Tile { + public getTileByPoint(point: Vector): Tile | null { const x = Math.floor((point.x - this.pos.x) / (this.tileWidth * this.scale.x)); const y = Math.floor((point.y - this.pos.y) / (this.tileHeight * this.scale.y)); const tile = this.getTile(x, y); @@ -507,17 +506,19 @@ export class TileMap extends Entity { for (let i = 0; i < tiles.length; i++) { const tile = tiles[i]; // get non-negative tile sprites + const offsets = tile.getGraphicsOffsets(); graphics = tile.getGraphics(); for (graphicsIndex = 0, graphicsLen = graphics.length; graphicsIndex < graphicsLen; graphicsIndex++) { // draw sprite, warning if sprite doesn't exist const graphic = graphics[graphicsIndex]; + const offset = offsets[graphicsIndex]; if (graphic) { if (hasGraphicsTick(graphic)) { graphic?.tick(delta, this._token); } const offsetY = this.renderFromTopOfGraphic ? 0 : (graphic.height - this.tileHeight); - graphic.draw(ctx, tile.x * this.tileWidth, tile.y * this.tileHeight - offsetY); + graphic.draw(ctx, tile.x * this.tileWidth + offset.x, tile.y * this.tileHeight - offsetY + offset.y); } } } @@ -618,7 +619,6 @@ export class Tile extends Entity { private _geometry: BoundingBox; private _pos: Vector; private _posDirty = false; - // private _transform: TransformComponent; /** * Return the world position of the top left corner of the tile @@ -678,6 +678,7 @@ export class Tile extends Entity { } private _graphics: Graphic[] = []; + private _offsets: Vector[] = []; /** * Current list of graphics for this tile @@ -686,19 +687,35 @@ export class Tile extends Entity { return this._graphics; } + /** + * Current list of offsets for this tile's graphics + */ + public getGraphicsOffsets(): readonly Vector[] { + return this._offsets; + } + /** * Add another [[Graphic]] to this TileMap tile * @param graphic */ - public addGraphic(graphic: Graphic) { + public addGraphic(graphic: Graphic, options?: { offset?: Vector }) { this._graphics.push(graphic); + if (options?.offset) { + this._offsets.push(options.offset); + } else { + this._offsets.push(Vector.Zero); + } } /** * Remove an instance of a [[Graphic]] from this tile */ public removeGraphic(graphic: Graphic) { - removeItemFromArray(graphic, this._graphics); + const index = this._graphics.indexOf(graphic); + if (index > -1) { + this._graphics.splice(index, 1); + this._offsets.splice(index, 1); + } } /** @@ -706,6 +723,7 @@ export class Tile extends Entity { */ public clearGraphics() { this._graphics.length = 0; + this._offsets.length = 0; } /** diff --git a/src/spec/CollisionShapeSpec.ts b/src/spec/CollisionShapeSpec.ts index 20c458fc0..afe785726 100644 --- a/src/spec/CollisionShapeSpec.ts +++ b/src/spec/CollisionShapeSpec.ts @@ -499,9 +499,9 @@ describe('Collision Shape', () => { const colliders = composite.getColliders() as ex.PolygonCollider[]; expect(colliders.length).toBe(3); - expect(colliders[0].points).toEqual([ex.vec(0, 0), ex.vec(10, 0), ex.vec(10, 10)]); - expect(colliders[1].points).toEqual([ex.vec(0, 0), ex.vec(10, 10), ex.vec(0, 10)]); - expect(colliders[2].points).toEqual([ex.vec(0, 0), ex.vec(5, 5), ex.vec(0, 10)]); + expect(colliders[0].points).toEqual([ex.vec(5, 5), ex.vec(0, 0), ex.vec(10, 0)]); + expect(colliders[1].points).toEqual([ex.vec(0, 10), ex.vec(5, 5), ex.vec(10, 0)]); + expect(colliders[2].points).toEqual([ex.vec(10, 0), ex.vec(10, 10), ex.vec(0, 10)]); expect(concave.isConvex()).withContext('Should be concave').toBe(false); }); diff --git a/src/spec/DebugSystemSpec.ts b/src/spec/DebugSystemSpec.ts index 238d88baa..6bb2df744 100644 --- a/src/spec/DebugSystemSpec.ts +++ b/src/spec/DebugSystemSpec.ts @@ -87,6 +87,8 @@ describe('DebugSystem', () => { actor.vel = ex.vec(100, 0); actor.acc = ex.vec(100, -100); engine.debug.motion.showAll = true; + engine.debug.collider.showGeometry = true; + engine.debug.collider.geometryLineWidth = 2; debugSystem.update([actor], 100); engine.graphicsContext.flush(); @@ -124,6 +126,7 @@ describe('DebugSystem', () => { const actor = new ex.Actor({ name: 'thingy', x: -100 + center.x, y: center.y, width: 50, height: 50, color: ex.Color.Yellow }); actor.id = 0; + engine.debug.entity.showId = true; engine.debug.collider.showAll = true; debugSystem.update([actor], 100); @@ -144,6 +147,7 @@ describe('DebugSystem', () => { actor.collider.useCompositeCollider([ex.Shape.Circle(50), ex.Shape.Box(150, 20), ex.Shape.Box(10, 150)]); actor.id = 0; engine.debug.collider.showAll = true; + engine.debug.collider.geometryLineWidth = 3; debugSystem.update([actor], 100); engine.graphicsContext.flush(); diff --git a/src/spec/IsometricMapSpec.ts b/src/spec/IsometricMapSpec.ts index ecee8d6f6..cab7db45c 100644 --- a/src/spec/IsometricMapSpec.ts +++ b/src/spec/IsometricMapSpec.ts @@ -111,6 +111,9 @@ describe('A IsometricMap', () => { engine.debug.entity.showName = false; engine.debug.entity.showId = false; engine.debug.graphics.showBounds = false; + engine.debug.transform.showPosition = true; + engine.debug.isometric.showGrid = true; + engine.debug.isometric.showPosition = true; const clock = engine.clock as ex.TestClock; const image = new ex.ImageSource('src/spec/images/IsometricMapSpec/cube.png'); await image.load(); diff --git a/src/spec/images/CollisionShapeSpec/circle-debug.png b/src/spec/images/CollisionShapeSpec/circle-debug.png index 3f75fd17290ee8036c7ad6b1ac9b24052a447780..58fe244241000db659b70435cafdca2ce2ff67b1 100644 GIT binary patch delta 1804 zcma)!`#;kQ1Atv;9BnSOSI)4pam*0oJ#uU2mfUh%mP>LATduhsv9c-*Tll*r|f4b;O zg7e3$X5wRbPJ9}7*G8XcbWRDR+ea;MqNjl&Hig$T%Mw*&9|)v$DWb|$||-N^Py zh?gq;`*m(0nohu+vPj$|{MnMbv+unPVzuKV+^9~5WK&?9~@g!lWHpEVg$gWSVY8mK{ZQk zLSIw;JOhwjor_4w9F;&{HWOy^6jO~G-B$J9F`^Msqtz)P+M#9VvR||->T@8UW4iu% zU@ns8f}YBQJgFI#E=oj)q+urnZ+0HpwLK z-y)<2*{bC(u4>ZF8TJ1jlWNH|rF#&q$&fWhG(bp|N#T*=)}D}uqj2g4!zy&Y^cfLnpXw>G zhSYtnl{`Bu6Jk?%)?3xM{5igr58Quk9z#7?uWZn~u<5*>$nCZnk<|S>O0;dC5+wlz z4Y|3^nN95Fi&*7-1KL{%rg}-{pqIU zSxPtcv!Laa(kuM))&jtgeoKPWV9}41lg@p!9h4A@88dNAYhig?nR^R#IX70W%mnR9xwb zI_yh=xIVxWS-pr~^fR3yGDDNIbY4m>Kqnh zKqh-JLmj0xm{r~&iZiziA}hH&az)j#wXB^(_#)yTl%!)Xeoa_Rn z{-e#CPrixrSuYAKl*}h>0NoEsdmQ_9SUs#;Pp(?CUtFFwf?MSy2fd`BF*P3Q?}&2! z;tmVPr{M4XUw(fc30ARmT#PN^6&dAAABJbUEcOr&N;T`O8BBQ+b1L46i>Ep8OaE0y zFd2@6+QGZ3Z^R?p0g4t0!JcRiSc%e#nQi*j?jThs;+UgJ;7uAAN~mwMEjq{$<@g(C zZggXOk(L=cAQp-WHnX+F^!4l}f;1?bvIgtuD4ny2?$mjXUn6sV5-dmy^ZnI8OP{MG zg_c5LEI`y`ZIj^D99KKq9c?y>f|l&zJd@p6Auz+LfULIV#6r@loQEPxGB|SB@@7ne zT44&r=8uh=2J6K+wH0aLRS5g8!a=ewL7D1zd&r7ph=;#FiP(D~pxpY}p>4o8H z?uU*5J_^pn{r=pNXtYYf@OSTX0hC61`H(D16qT}p1($L1w`Q!}8$0}J)P%9yMxNTT zS1oj1e!t&h`?sbVa_N%mRgVEv5)_0!g=CMn1iCLt+n7iN@Y4)*-5q%h=|k}0Sxebl z1Ce~Puf#CJ@tR{5>!O2r_{7Dm@*z5S z-I@V)`o{`t(t=6W(;&A#!xsKbjAJnJX$>JCq!9a7N!?`!-@X_?gBA$fcRH!a6DUI* ztgE%RVWA(o1^SI$&wS zGp~?>m&xOJ!xjEjA7YO1xlTTL&Igvx#z=%C&c-n7Yp|Jgz(~f-y9z_c4i|yS7gW0~ zZMNl_PA4uG?wY9uM#zLCGJV8nD@jM7-f{wcxn4nI6X2mwtnGrX5H0D9l#sS+p;sCp Z$~1WK$pzIgC-MUaTNg>2IzmADe*pogMBM-Y literal 1951 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

WQ&$r(cN2sqPDURW(svpQ20 z%U#3%9{|sRiidNw&k=eDg%JhLYGL7+&BN1`vfIwN`XS9)BESH4_x)Wn?F=ausF!8F zGbVPfb^3Cv9QS%@`frmkUC~0Di7YdVx$ggu8SfZ$GnK$L6}goBfd?R8RKAH*{3V3l zqzJ~v>Ia;=DKFih6=4PcYC+^5ETJt_u`P2Z?qV z;(X-bl$hVx*Z1O-?K1E*^-$Jkap86l^hYuC;kCGozp}n~0>U|JOC6C~!XUT|1iS^TCi026 z!^ipAx>8cwg)}Xx*L5hr`JH+;Tw)G)eD;}_+)dO78cLUO{!TtCr?qFK3Fk&ByS6%a zSW4(Zxy(S*b_2nSwQ2R%YMDxi3#t3~3yCO->qhj`q4pl0Slh1IjH(X%f%Jc_BjxNE zda{#m3nP@PmZC2BwM3o^)%>D}!NkExISF{klDLXZjVxYbl4BZo?zODtl+rPM)sgdT2QS855Fy$qlge%;>9nsX;Hu5ZB#=Jlf9|0#DZc@ptA>t zI(6o8OF2-y*)61=+Y9B4RN#fpXdWg{_u6@s3&uJ9PH)f@>Iqi|YtJjqes&8y!N*Kzw&2Dv^FJK>m_lfBbKMl$h__b=NMF zSH6{jKu>DqM^c&H-UlIci{FKJ+KQ-xBrJkIbqS!Fy!VWBC!pBMuA;6k2^3r5Ff1qci`c7>cX2J!O7wW8NnD^PBcZ?Hznn{s;3 zE&YPmBCaJ~x)S|P`<@s-!r=ATa20GxE?7rkPRPIDHmoyX7pdjjwG zR;~7X$j~#lNHxXmChOIg^mz9WxWr*|CcaKfb3L@)*pGIRVrR`VpzB>jV{S?6(K9Ug zLKtsEHDWs>#;a5S0r5ypV^dDgw5(%uHl_xoye|7%O#+aYW+mZyG$_f7VY3~p;&2)2 zn=me<_HP=l9c+0Kx22blFU8*Z8(?G(nd0W=BScmMzZ diff --git a/src/spec/images/DebugSystemSpec/body.png b/src/spec/images/DebugSystemSpec/body.png index f03d6178876d8def3ad4b396147b7bea948d030c..245812ec031802463e759d2af5a5cb04c0a23382 100644 GIT binary patch literal 8779 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 literal 9913 zcmeHN`&*J(*H*$wI}D~%P6w5=#wjyvOf3zi)U+J4vn8qdTIobe9?%2<89Q1^)})n% zWZ6MQDnn93EGsNepayvWY%~=S$#?_-k?)cIiT8THe&7Puv-Y*u+It=DeedVuuAPDN z=Pa3HVq!9XdyxMg6BAQl=41X7C}|Q%vcWIY^mJP7jY`q zbnTzrmpmV`<&VZy(0MSEprD_ZX||tyKhP@gb-cub_0?aMH1JKj>Sy&q_XMXxwz}0k z8hdf>)~yI;m|i-zzsx6aU0P8oyN(1GF(oJbG^3u0G&Lnh(k|{W3Ly~DJmX_B1Oj0rt*_5$ zLZHx^#or&BL7_PEZ>z~OLUZ#Jx9^YVn41HTkbRIDp{3>UapM^CU%;Pcw;n-_X2E$a z#xa(^!{NNS8+Kb5Eke=?jbq@ukVu+MT(OnWBGBMjKMek{!aqXdN7Bs{gdbJnM;$i* z(Kvpz=Km2au|E7YiD1xS47xDGbhM^uYDiM2|Kdtotmp5O5q0b$j(*ykgTou>Z4w`C zf&EK@PUyRX>07uQnOW})g=(EUqf%NWQv-^qltJztm1u~hRS69MgW^qgv<4BN0|cUX z$nlCPgy`o5aAz>yPE#_q(iPUVzXUbL(T%zK{7L|%Fi{ez@%hHzO(9{{mcwDPPp|a3 zVN!e2x2{)?Y!$bxb38mC)(J_wnu%m|wjf>h|NUf#EjjUI`w=4=a&>WZ=%#Z=FBm=zp(4kx+=WTs93vHI@`h$-j}aq$I^CK9(dd@?s$ZQKwEz~ zb(Z*G&@6ZV%(RK*yr%&W=voIj&$azgWz`|0@IaL9c@U;T^vB(H83mQ&d)JymV82)n z!``%<9$vlWU`g&}xBrAf%@6NI(xNIcwP{8#p4R3OuYe=nOv%K`yhk&}z37@;RbWD% z4LUaa-{YH8uEWds1ioAR)CmHuSOw=Xx>%xGqZgkgLn`Lc@@{L}()hP-Wem@P(OrFLYa5p#PMl8u7GoT4NP%hR+>Bnh7I z&d<0NM{h5{J0SaRfqva0^sr?{MNtlr1&+L7VGb1L_+N+L7LMEdYHDG3rV2x+<+}pg zyjt&uOh$v?eH6s;-n@z_$Az;9=AY9PVnSS#POV5guRX-A{+98{+Y};PXgMsDiz@Df z%>~pLMW}^x!QMxTCCBV~(z3FNQ<{E37z3>xi7J=ByL@B|@Za7&8HO~k^ z(j+J0k+#FL)Zs^34e>$T`#}2#j&ZGM2 znK?hwsHnK)&v)GMLfis*k>n8aw+_dbcxdwziA(#g1T) z0~oz$H7&&D{{+*(jB)L34qC{PRq%d4QYlL0<$upKxiKAgEX;Yl{uw5e(xor-FwOd|i57 z7BR>3MAe?cnW)cNJ|ipq$GI8P9(Sw)4Bf){LFbSPg1we^Rh(8#!OpW^<2Hqn>slUi zWpwl%yc8kp;7bV{944M2E?Lt0;%%>YnoLsAjOTXm2?SoE8m2C5mg*KyP%17pN4+^M zO^fSI8&Dsa=6x!SrdP0Am0jI9@vxsZg%oVZuL}?cs28QrJJ~`;N*oR!k(bS4tG6fi z!zocWy;c?x_sQ}oMEP08RIie9A*XX4pd-YHPgie zw7gm^8Pr9}xZUbrSNZyONhBLRBx-*sJ~^RfdZYz&g7eNZBPkB&^4$9)1;;Zk4eg0f z<{mH<4_&Odv{9`UCf&3PqhWT}O?FF&iXn`#aPAxDKlIJesTd@nGkY-0_jbl~#1P$G zi6xCoeTnbGluJwS z^V|cHa5mv636Dnrc+GgIukIVQ#BkkavV~&!ESHOHm?KjP7K`Oeil}oJq?w&4PrVKBJ4X_ay_P^OwPi=keFIO8#lBICIQ#K!)2k(__zH?q|oh=w` zZu4%Az)TloF$72l&OuP9cJE*U$S#kLe^rzqDeHs>_x`~%49Vs4Hmc3A(&XmO zcT^0fM`~0#L3(k4toZM6=2g^@Wlar4(wfqR)$z?;16N!^-35O*Q9TD zzW+I2w~Nb~o>a12iK@4IG1U}Uf->(QzOOsU%!+L$!bjA3KEpStn!V?vD!PRg>l|T^ zVp}EcO$+d2JpYPw54oZqmK%e2@6{4%u)?A%-`kyiQ=)w{a9JOy+7MC6;`D=_N>(T6 z6UfZiqF~rq|9b~U^jbum3*z^AG8R$RO`UzigD&5qMbX+ZAqw3!DqcuxT@vGZ-5^*GDDsm1$tswdC zr(x&1XoUg6pj#(-+!xY{OLJ5+a^fb0PUdPqy zcJwgn1|8vdi9~WSlo=!AhLZD z{Dl)L57>l8OC>u4F0Tbv@GId*q!+i1g!C*UUjvcN_A5X88QG&quti2aR`eQqpuQDg z)3dtY@ST|o1Itp_h_|OHWVtCL=lqSjrx$vBSY^}yAUP+7Mhojhpv9mi*14x{_P*YC zY|$Lx5BffHG7hp;dN5RDtE}w~oePv<%j=2u<34~`cY@W6>#gK8;!kn~azd0YU){Ei zyFxqNrKuLG{_s_|>zYvbQ9|=yve;tx%f8+b991H{BiS~>b6J^Z7=L>0NAKG)GAH|O zLvL!(AQ6TJ9yhN=6=nXS|EG+JMb&v!+SIYzwo^LS&8Hx^ONm6O^=pBT%ZVo18p4wO zirxkegK%G#0m}>ZT1t8lrkwi3Uy`ro>l)hybya{zO%-;fNQa?Esz_izYuGvItPDJ}H;^Ka9GPIPjrN?S)?PviMcaTLz19fZ-5&p{D4( zeoS<};m@L7+o%JVhlqk@<#LSnl5Ju}_bjqCVjq&``;YKdAbB`z$A&)KEBt^doh2;k zc^Q1S=8MJFfe+>G`ziB)>WS0S!*l>4E7s?TlPEr)50+x*O_q3@cTktz$r?Z0QE);) z{lwHJ9-T&#dFm6i9B{3C#5@jam%f=;R zjO!927v+808wgY>Gl~ME{3KvhH+lC@t15c5Z~h{3muo$3`qN*Yt2_2hMqfBQfxA^x zEO2ReoBlwE1QxT>xiMoRN&9{wwLd<6)qi9rFIV&MVISB|VR@t|{2$L!g>loZX9U+C zxVWE*qu@hiL6gl-cGYD*uiB1_sbe z_ER?qbQ*3sKmJIRO^w5kd?=UgkNHtPT*@7H1-T7j)ben1!>h3lrQ!C3BY%LdWv3_^s|qWt;bK;jE#lXP zm_nSud9Bymo;Y;G?vx)AcDGu^{D*$8g%c28H`u5?fMI`;&k?-=*JwMqMkCukvzr?* z(hs`*vMTX6J3|sd3;oZ=ZHiRGr2jkh0UNv2k_D&pKs>r&O0;t++YBr+us$#km{|U< zD7=QIju;ANePfAjU-V2p0uL8;nMlRbLy1?B0AZMqjWKCsrM*)Zuo6U}ZEggN2H>b$ zwJ{6eSNxE)q2a8E@3qwrM1ht?_P-f< z{Y@a*pX>9<%t+CH2gI6a$a+&o9`|;6O+lvQQnJp4*EW%|0V1*dn!mwsOJ5;`=-OH| zhfJ({>}WN)qdqG?8vbNWpSr(SYB@ZF^Xks7uM|i5oV=?hejUCgsEM;h*_S_1#7Ai( z)x((qVki4UZiuK9l7Ly8;#A;L$(&HH(0ZTZVj=q62-238wkha~yD!B~RRO|gp`3nC zeauxES|p=Kp`!<`jXIzQqw4@HOTgA;!@q6Mq<1ZaS>DNNmgP1J@~TO%8p3P#&H++f z9#`KL%-ZD)A0%%&v4)V;b9&_y)y)z5>?UTtJ%^JqSuHN$mJ@{Oz=xQ_HhMA8c;-*W z6`W+BCp}G?`(|=6;Bu`R!^n+e3kVwj=)VqBdcp8xEt2+YNV1(q9vSJ#qi8fzT77q= zN}46eckT4>`*A_L@!o3tVoT~Vu+h)Jgx zufqElb1-;;nf-|b_MbFKjlRvfE{PVpa41TlEk#D*{#|eIc%gy%Qx6}7E!5#7zOtxC zDF&BsNpJF;p0wdNG~s_EQ(2xRsUD;&IT`la?(1S|>?Ge=;1YJDqhS6ZE6|bEd+)Ow zBW==9FnuZkSDQRl%~V{pr&xKo-}y`%bY+6d?**w-K8N)-h7BM;<3E?xXCqd{g7-vtt!dt!CA7 zUYjTO%{e6S;XiipU{)pTCmgT{n9BA}`pBO6z;TBw^|sZBYT(8#k!9p1P>CQjKd#u~e@b8g715mzN zfvkj8s?*9-fA+0qAjBoDF&%vmEiyn169OS$vgrN7$WrVXdi$;|)q1Wu@TTRdJ0zK0 zd$w0J;yw<|1wn%&j+k1B9ANh~Y;k0TqZC+M&rV(QHy4?MqRB2d?Vax95KZZYE1K`i zM7uv!i*=E7Hf;I=;MvVeG;Ad(88_`;JO*M#^Mx-5}QNlQUhLi35wt0Z~X+D+a zUvY)v<$5+f#coQl0N%&wZK=05%TKtbuFFA7u@ZsJ*cACRgdZZ7&AV+Oj;8#BR5XHrDM7fU`jc1#l^?d?m2=3rJ{EK?uYq zf_z6xX?3iKinLd;0JanriKakhROc^(Gw}pDaUP&~ic4FOuU&wxQ1;xlf^b20DbX*` z+)fFY#DA-LlZwKuHD$o(*xYiIE_f`_m(3X#Cw)P{KK<3gcA7(HeVCZ0 zoszx}8gK_ni2mqVyUi##CI0*< z#i#}ayN$_vE8}hPl?bp~Dz4~f0Eh`0ytanHXIMvY(q~})jM_f?e%EaYGE=52W|$C= z;!Fj3Cbpj{(5Uv`>CDEn6>cUbKgG_x3-CYX*biqQKl0~CG5G&m*?ts&|F#0qXE5PQ Y1i6U~@~ diff --git a/src/spec/images/DebugSystemSpec/composite-collider.png b/src/spec/images/DebugSystemSpec/composite-collider.png index de6a6a0343fdee863476cbf0a99cfb33a724138e..9a3ed62d00ba8dddbd46cc52b5d0f71c2ad4f129 100644 GIT binary patch literal 14686 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 diff --git a/src/spec/images/DebugSystemSpec/graphics.png b/src/spec/images/DebugSystemSpec/graphics.png index b60974499596f748fe2943562c73742b1cc2f1b1..aeda6bc2a18129bf0923f000609dbd4287b061d8 100644 GIT binary patch literal 5745 zcmeAS@N?(olHy`uVBq!ia0y~yVEh8Y9Be?5)7S2I0V&4fAa^H*b?0PWGBAi~d%8G= zRK&f#elSqlLBz#TTSS}b`0=|(mUDQpPS=pnb=dWG_A{MTCbsu`zJL1s{q5g&c_tPP z0R@K#8I9eJ_Pgxk=P@t>MQS)CO4=P7Kx`%^Hrs$j9AH%k7VHMe3MdE&7`$i{a#a8; zVY|x)r05P?bG_cLJA)`4}AT4tev58heO(hzX!YGS5^3P z-}!&|_5I_QYw9K*Fl1u;J7ZN%Ak%qHi7(IZ{1I!s%);|4v-jsd7C8}v7yL~>EgGF! zd44TC`tugcKJA1H`?a4Ivz!w%c#;17`EB7JK?fGpdp==i5dxO~fr|xL{^=)Nh-Gi& zWo+Jear2bJOl-9~YA!AZ8X0o;kRF%Bm!sEG;GzN zaJ>H_(u)byr0L+6_)>B3k>C%YeRG)_cQiQmi|fdNn;<#BHc9Wir790vf$fnatUOhF z5+)>m-v2V-zre3$$z?RLXY>N5AiSB>MyQ!{QJ3!{wgmee>p? zd+vFj&*zgXKc3F>@e1^!(P%y?ZzY|e(dZ`E1-TO|{iFJ8#EpLO1n(%V^^ZNzX|$J; zQj!u*iE|bw67qM>?qmo${KpXw#`RC%sLG68?)v6z^}tWlisRo-(jv-!3#(@N%=|2! z8^7u7_qXaq*rlpQJ#s6~X_av&wyx6X00^6tPTTnqVZ7UmopWsUtXs7x;()TDD3ncqFwr zOE8<)HuF+}9D?g;Itf0)zqbmB8wl1xZBg0ypK1d=fQn?+k=}wF+cd@uG@kinpDCo( zHUQZB6n$**ZK66e3S+pV&j4kJZQ*H0rGDkFo~zE-CvES{t!3!Usi>}Cv`mxh4Vij% zatG75vk2m=b(gQGhdJJ+R})@IoGK^w4n9!)S}DSF7u8R>&KUC?=C~7lN6(SO|6siz zJfK%Doz!1PWb;95W+ngr=$W}6+^X6uw z^adh3B^~g50)oXAXBuPCHpc86R%DIiTkNZ8m+>WIexmLC#v!vr|91exqU=0ee?wy@ zTF^cq>2+f)`5Bb)fzIJEzOMnp56es?GLw?gC|ZaL^V-bwNAwrN6=A6)|v<2ouQB|;$2bpp{*Jz-;pMZUK&>E`@D$f zwujeOX?lpGJvIp*$7nS-PYB=R2R^8AgyU(($%_j-`^Tu`uI+2TyPl9K@P4ug7_(e5 z)Q_rXoc=gZ#w>~XU04CCWH@v)1lA6YuiL&9eX!_!=D=D44C`}31PEq;szf;Y5UlOp zm3IWdJfXs7M39XrDx=25pi3ZKxFSd5a|fGVo+=|G(C5S_S!D!O%6Cy)FI+Y>e|pi{+0J(l*`%=xn@RJ9zb_djo8!#!pIjhF89c_=#f%`&N*4IzFPDrxY`nL%z z3m~xUVB|s%n^b9~awG~qpFnEoVE~AW{GLl{+b{xyfc5}V@4}I=cI4r_A9cVb@Tn|O tr9t%rH8rSFN#O$piWGJD&wR<|)mJdTs&)e|5I2T8|1!{{i|f(oO&X diff --git a/src/spec/images/DebugSystemSpec/motion.png b/src/spec/images/DebugSystemSpec/motion.png index af4ab07d750ab6b3c95a50558c1312bdba2545dc..c214dcfc95786924d8999dd02d658fda14c19f8d 100644 GIT binary patch literal 7224 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 diff --git a/src/spec/images/IsometricMapSpec/cube-map-debug.png b/src/spec/images/IsometricMapSpec/cube-map-debug.png index 86a3aee0b60c437608123133b956623896bc3f0e..f71adfbd867eb1611bfb5ef6d6b55d0182d171fe 100644 GIT binary patch literal 11901 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 literal 12348 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