diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cd12580..f9beb597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,15 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Added new `ex.Debug` static for more convenient debug drawing where you might not have a graphics context accessible to you. This works by batching up all the debug draw requests and flushing them during the debug draw step. + * `ex.Debug.drawRay(ray: Ray, options?: { distance?: number, color?: Color })` + * `ex.Debug.drawBounds(boundingBox: BoundingBox, options?: { color?: Color })` + * `ex.Debug.drawCircle(center: Vector, radius: number, options?: ...)` + * `ex.Debug.drawPolygon(points: Vector[], options?: { color?: Color })` + * `ex.Debug.drawText(text: string, pos: Vector)` + * `ex.Debug.drawLine(start: Vector, end: Vector, options?: LineGraphicsOptions)` + * `ex.Debug.drawLines(points: Vector[], options?: LineGraphicsOptions)` + * `drawPoint(point: Vector, options?: PointGraphicsOptions)` - Experimental `ex.coroutine` for running code that changes over time, useful for modeling complex animation code. Coroutines return a promise when they are complete. You can think of each `yield` as a frame. * The result of a yield is the current elapsed time * You can yield a number in milliseconds and it will wait that long before resuming diff --git a/sandbox/src/game.ts b/sandbox/src/game.ts index e3816517..74e02a45 100644 --- a/sandbox/src/game.ts +++ b/sandbox/src/game.ts @@ -53,9 +53,17 @@ var game = new ex.Engine({ pointerScope: ex.PointerScope.Canvas, displayMode: ex.DisplayMode.FitScreenAndZoom, snapToPixel: false, + // fixedUpdateFps: 30, + pixelRatio: 2, fixedUpdateFps: 60, maxFps: 60, - antialiasing: false, + antialiasing: { + pixelArtSampler: true, + canvasImageRendering: 'auto', + nativeContextAntialiasing: false, + filtering: ex.ImageFiltering.Pixel, + multiSampleAntialiasing: true, + }, uvPadding: 0, physics: { colliders: { @@ -555,9 +563,9 @@ player.onPostUpdate = (engine) => { }); // console.log(hits); } -player.graphics.onPostDraw = (ctx) => { - ctx.drawLine(ex.Vector.Zero, ex.Vector.Down.scale(100), ex.Color.Red, 2); -} +// player.graphics.onPostDraw = (ctx) => { +// ctx.drawLine(ex.Vector.Zero, ex.Vector.Down.scale(100), ex.Color.Red, 2); +// } player.body.canSleep = false; player.graphics.copyGraphics = false; follower.actions @@ -594,6 +602,23 @@ var healthbar = new ex.Actor({ height: 5, color: new ex.Color(0, 255, 0)}); player.addChild(healthbar); +player.onPostUpdate = () => { + ex.Debug.drawLine( + player.pos, + player.pos.add(ex.Vector.Down.scale(100)), { + color: ex.Color.Red + }); + ex.Debug.drawPoint(player.pos, { + size: 1, + color: ex.Color.Violet + }); + ex.Debug.drawCircle(player.pos, 100, { + color: ex.Color.Transparent, + strokeColor: ex.Color.Black, + width: 1 + }); + ex.Debug.drawBounds(player.collider.bounds, { color: ex.Color.Yellow }); +} // player.onPostDraw = (ctx: CanvasRenderingContext2D) => { // ctx.fillStyle = 'red'; // ctx.fillRect(0, 0, 100, 100); diff --git a/sandbox/tests/polygon/index.ts b/sandbox/tests/polygon/index.ts index 854e3ccd..496ed701 100644 --- a/sandbox/tests/polygon/index.ts +++ b/sandbox/tests/polygon/index.ts @@ -91,5 +91,5 @@ var triangulated = shape.triangulate(); actor.collider.set(triangulated); game.add(actor); - +game.currentScene.camera.zoom = .75; game.start(); \ No newline at end of file diff --git a/src/engine/Collision/BoundingBox.ts b/src/engine/Collision/BoundingBox.ts index a673b178..6e711442 100644 --- a/src/engine/Collision/BoundingBox.ts +++ b/src/engine/Collision/BoundingBox.ts @@ -213,13 +213,34 @@ export class BoundingBox { return 2 * (wx + wy); } + + // Cache bounding box point returns + private _points: Vector[] = []; + private _left?: number; + private _right?: number; + private _top?: number; + private _bottom?: number; + + /** + * Returns the world space points that make up the corners of the bounding box as a polygon + */ public getPoints(): Vector[] { - const results = []; - results.push(new Vector(this.left, this.top)); - results.push(new Vector(this.right, this.top)); - results.push(new Vector(this.right, this.bottom)); - results.push(new Vector(this.left, this.bottom)); - return results; + if (this._left !== this.left || + this._right !== this.right || + this._top !== this.top || + this._bottom !== this.bottom + ) { + this._points.length = 0; + this._points.push(new Vector(this.left, this.top)); + this._points.push(new Vector(this.right, this.top)); + this._points.push(new Vector(this.right, this.bottom)); + this._points.push(new Vector(this.left, this.bottom)); + this._left = this.left; + this._right = this.right; + this._top = this.top; + this._bottom = this.bottom; + } + return this._points; } /** diff --git a/src/engine/Collision/Colliders/PolygonCollider.ts b/src/engine/Collision/Colliders/PolygonCollider.ts index 09bb4d83..d235f930 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 { BodyComponent, ExcaliburGraphicsContext, Logger } from '../..'; +import { BodyComponent, Debug, ExcaliburGraphicsContext, Logger } from '../..'; import { CompositeCollider } from './CompositeCollider'; import { Shape } from './Shape'; import { Transform } from '../../Math/transform'; @@ -46,6 +46,7 @@ export class PolygonCollider extends Collider { public flagDirty() { this._localBoundsDirty = true; this._localSidesDirty = true; + this._transformedPointsDirty = true; this._sidesDirty = true; } @@ -701,13 +702,7 @@ export class PolygonCollider extends Collider { } 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, lineWidth); - ex.drawCircle(points[i], pointSize, color); - ex.drawCircle(points[i + 1], pointSize, color); - } + const points = this.getTransformedPoints(); + Debug.drawPolygon(points, { color }); } } diff --git a/src/engine/Debug/Debug.ts b/src/engine/Debug/DebugConfig.ts similarity index 99% rename from src/engine/Debug/Debug.ts rename to src/engine/Debug/DebugConfig.ts index eddf3742..b2bf7ec3 100644 --- a/src/engine/Debug/Debug.ts +++ b/src/engine/Debug/DebugConfig.ts @@ -1,4 +1,4 @@ -import { DebugFlags, ColorBlindFlags } from './DebugFlags'; +import { ColorBlindFlags } from './DebugFlags'; import { Engine } from '../Engine'; import { Color } from '../Color'; import { CollisionContact } from '../Collision/Detection/CollisionContact'; @@ -152,7 +152,7 @@ export interface GraphicsStatistics { * best to do so on the `postupdate` event for [[Engine]], after all values have been * updated during a frame. */ -export class Debug implements DebugFlags { +export class DebugConfig { private _engine: Engine; constructor(engine: Engine) { @@ -309,6 +309,7 @@ export class Debug implements DebugFlags { collisionNormalColor: Color.Cyan, showCollisionContacts: true, + contactSize: 2, collisionContactColor: Color.Red }; diff --git a/src/engine/Debug/DebugFlags.ts b/src/engine/Debug/DebugFlags.ts index c83031bf..bc4d3295 100644 --- a/src/engine/Debug/DebugFlags.ts +++ b/src/engine/Debug/DebugFlags.ts @@ -3,10 +3,6 @@ import { ColorBlindnessPostProcessor } from '../Graphics/PostProcessor/ColorBlin import { Engine } from '../Engine'; import { ExcaliburGraphicsContextWebGL } from '../Graphics/Context/ExcaliburGraphicsContextWebGL'; -export interface DebugFlags { - colorBlindMode: ColorBlindFlags; -} - export class ColorBlindFlags { private _engine: Engine; private _colorBlindPostProcessor: ColorBlindnessPostProcessor; diff --git a/src/engine/Debug/DebugSystem.ts b/src/engine/Debug/DebugSystem.ts index c87a809f..2462f595 100644 --- a/src/engine/Debug/DebugSystem.ts +++ b/src/engine/Debug/DebugSystem.ts @@ -15,6 +15,7 @@ import { GraphicsComponent } from '../Graphics/GraphicsComponent'; import { Particle } from '../Particles'; import { DebugGraphicsComponent } from '../Graphics/DebugGraphicsComponent'; import { CoordPlane } from '../Math/coord-plane'; +import { Debug } from '../Graphics/Debug'; export class DebugSystem extends System { public readonly systemType = SystemType.Draw; @@ -260,7 +261,10 @@ export class DebugSystem extends System { for (const [_, contact] of this._engine.debug.stats.currFrame.physics.contacts) { if (physicsSettings.showAll || physicsSettings.showCollisionContacts) { for (const point of contact.points) { - this._graphicsContext.debug.drawPoint(point, { size: 5, color: physicsSettings.collisionContactColor }); + this._graphicsContext.debug.drawPoint(point, { + size: physicsSettings.contactSize, + color: physicsSettings.collisionContactColor + }); } } @@ -290,6 +294,15 @@ export class DebugSystem extends System { this._graphicsContext.flush(); } + postupdate(engine: Scene, elapsedMs: number): void { + this._graphicsContext.save(); + if (this._camera) { + this._camera.draw(this._graphicsContext); + } + Debug.flush(this._graphicsContext); + this._graphicsContext.restore(); + } + /** * This applies the current entity transform to the graphics context * @param entity diff --git a/src/engine/Debug/index.ts b/src/engine/Debug/index.ts index 51da2d07..fa4a1ec2 100644 --- a/src/engine/Debug/index.ts +++ b/src/engine/Debug/index.ts @@ -1,3 +1,3 @@ -export * from './Debug'; +export * from './DebugConfig'; export * from './DebugFlags'; export * from './DebugSystem'; diff --git a/src/engine/Engine.ts b/src/engine/Engine.ts index fe6ac79c..c6b6776a 100644 --- a/src/engine/Engine.ts +++ b/src/engine/Engine.ts @@ -32,7 +32,7 @@ import { Logger, LogLevel } from './Util/Log'; import { Color } from './Color'; import { Scene, SceneConstructor, isSceneConstructor } from './Scene'; import { Entity } from './EntityComponentSystem/Entity'; -import { Debug, DebugStats } from './Debug/Debug'; +import { DebugConfig, DebugStats } from './Debug/DebugConfig'; import { BrowserEvents } from './Util/Browser'; import { AntialiasOptions, @@ -505,7 +505,7 @@ export class Engine implements CanInitialize, * * Graphics * * Colliders */ - public debug: Debug; + public debug: DebugConfig; /** * Access [[stats]] that holds frame statistics. @@ -932,7 +932,7 @@ O|===|* >________________>\n\ }; } - this.debug = new Debug(this); + this.debug = new DebugConfig(this); this.director = new Director(this, options.scenes); diff --git a/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts b/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts index a564e24f..d49af062 100644 --- a/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts +++ b/src/engine/Graphics/Context/ExcaliburGraphicsContext.ts @@ -130,11 +130,11 @@ export interface ExcaliburGraphicsContextState { material: Material; } export interface LineGraphicsOptions { - color: Color; + color?: Color; } export interface RectGraphicsOptions { - color: Color; + color?: Color; } export interface PointGraphicsOptions { diff --git a/src/engine/Graphics/Context/line-renderer/line-renderer.ts b/src/engine/Graphics/Context/line-renderer/line-renderer.ts index 1b8df8bc..5efd878c 100644 --- a/src/engine/Graphics/Context/line-renderer/line-renderer.ts +++ b/src/engine/Graphics/Context/line-renderer/line-renderer.ts @@ -7,6 +7,11 @@ import { RendererPlugin } from '../renderer'; import { Shader, VertexBuffer, VertexLayout } from '../..'; import { GraphicsDiagnostics } from '../../GraphicsDiagnostics'; +export interface LineOptions { + color?: Color; + width?: number; +} + export class LineRenderer implements RendererPlugin { public readonly type = 'ex.line'; public priority: number = 0; diff --git a/src/engine/Graphics/Debug.ts b/src/engine/Graphics/Debug.ts new file mode 100644 index 00000000..a76400e3 --- /dev/null +++ b/src/engine/Graphics/Debug.ts @@ -0,0 +1,107 @@ +import { Vector } from '../Math/vector'; +import { ExcaliburGraphicsContext, LineGraphicsOptions, PointGraphicsOptions } from './Context/ExcaliburGraphicsContext'; +import { Color } from '../Color'; +import { Ray } from '../Math/ray'; +import { BoundingBox } from '../excalibur'; + +export class Debug { + static _drawCalls: ((ctx: ExcaliburGraphicsContext) => void)[] = []; + static _ctx: ExcaliburGraphicsContext; + static z: number = Infinity; + static registerGraphicsContext(ctx: ExcaliburGraphicsContext) { + Debug._ctx = ctx; + } + static draw(debugDrawCall: (ctx: ExcaliburGraphicsContext) => void) { + this._drawCalls.push(debugDrawCall); + } + + static drawPoint(point: Vector, options?: PointGraphicsOptions) { + Debug.draw(ctx => { + ctx.debug.drawPoint(point, options); + }); + } + + static drawLine(start: Vector, end: Vector, options?: LineGraphicsOptions) { + Debug.draw(ctx => { + ctx.debug.drawLine(start, end, options); + }); + } + + static drawLines(points: Vector[], options?: LineGraphicsOptions) { + if (points.length > 1) { + Debug.draw(ctx => { + for (let i = 0; i < points.length - 1; i++) { + ctx.debug.drawLine(points[i], points[i + 1], options); + } + }); + } + } + + static drawText(text: string, pos: Vector) { + Debug.draw(ctx => { + ctx.debug.drawText(text, pos); + }); + } + + static drawPolygon(points: Vector[], options?: { color?: Color }) { + if (points.length > 1) { + Debug.draw(ctx => { + const firstPoint = points[0]; + const polygon = [...points, firstPoint]; + for (let i = 0; i < polygon.length - 1; i++) { + ctx.debug.drawLine(polygon[i], polygon[i + 1], options); + } + }); + } + } + + static drawCircle(center: Vector, radius: number, options?: { + color?: Color, + strokeColor?: Color, + width?: number + }) { + const { color, strokeColor, width} = { + color: Color.Black, + strokeColor: undefined, + width: undefined, + ...options + }; + Debug.draw(ctx => { + ctx.drawCircle(center, radius, color, strokeColor, width); + }); + } + + static drawBounds(boundingBox: BoundingBox, options?: { color?: Color }): void { + Debug.draw(ctx => { + ctx.debug.drawRect(boundingBox.left, boundingBox.top, boundingBox.width, boundingBox.height, options); + }); + } + + static drawRay(ray: Ray, options?: { distance?: number, color?: Color }) { + const { distance, color } = { + color: Color.Blue, + distance: 10, + ...options + }; + Debug.draw((ctx) => { + const start = ray.pos; + const end = ray.pos.add(ray.dir.scale(distance)); + + ctx.debug.drawLine(start, end, { color }); + }); + } + + static flush(ctx: ExcaliburGraphicsContext) { + ctx.save(); + ctx.z = Debug.z; + for (const drawCall of Debug._drawCalls) { + drawCall(ctx); + } + ctx.restore(); + Debug.clear(); + } + + static clear() { + Debug._drawCalls.length = 0; + } +} \ No newline at end of file diff --git a/src/engine/Graphics/DebugGraphicsComponent.ts b/src/engine/Graphics/DebugGraphicsComponent.ts index 24708165..871d6b93 100644 --- a/src/engine/Graphics/DebugGraphicsComponent.ts +++ b/src/engine/Graphics/DebugGraphicsComponent.ts @@ -1,5 +1,5 @@ import { ExcaliburGraphicsContext } from './Context/ExcaliburGraphicsContext'; -import { Debug } from '../Debug'; +import { DebugConfig } from '../Debug'; import { Component } from '../EntityComponentSystem/Component'; @@ -11,7 +11,7 @@ import { Component } from '../EntityComponentSystem/Component'; */ export class DebugGraphicsComponent extends Component { constructor( - public draw: (ctx: ExcaliburGraphicsContext, debugFlags: Debug) => void, + public draw: (ctx: ExcaliburGraphicsContext, debugFlags: DebugConfig) => void, public useTransform = true) { super(); } diff --git a/src/engine/Graphics/index.ts b/src/engine/Graphics/index.ts index 3f36fb48..2a23e1fa 100644 --- a/src/engine/Graphics/index.ts +++ b/src/engine/Graphics/index.ts @@ -49,6 +49,9 @@ export * from './Context/vertex-layout'; export * from './Context/quad-index-buffer'; export * from './Context/material'; +// Debug +export * from './Debug'; + // Util import * as webgl from './Context/webgl-util'; diff --git a/src/engine/TileMap/IsometricMap.ts b/src/engine/TileMap/IsometricMap.ts index 1a198234..29ae28b6 100644 --- a/src/engine/TileMap/IsometricMap.ts +++ b/src/engine/TileMap/IsometricMap.ts @@ -9,7 +9,7 @@ import { TransformComponent } from '../EntityComponentSystem/Components/Transfor import { Entity } from '../EntityComponentSystem/Entity'; import { DebugGraphicsComponent, ExcaliburGraphicsContext, Graphic, GraphicsComponent } from '../Graphics'; import { IsometricEntityComponent } from './IsometricEntityComponent'; -import { Debug } from '../Debug'; +import { DebugConfig } from '../Debug'; export class IsometricTile extends Entity { /** * Indicates whether this tile is solid @@ -445,7 +445,7 @@ 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, debugFlags: Debug) { + public debug(gfx: ExcaliburGraphicsContext, debugFlags: DebugConfig) { const { showAll, showPosition, diff --git a/src/engine/TileMap/TileMap.ts b/src/engine/TileMap/TileMap.ts index fad270e6..3852b576 100644 --- a/src/engine/TileMap/TileMap.ts +++ b/src/engine/TileMap/TileMap.ts @@ -17,7 +17,7 @@ import { PostDrawEvent, PostUpdateEvent, PreDrawEvent, PreUpdateEvent } from '.. import { EventEmitter, EventKey, Handler, Subscription } from '../EventEmitter'; import { CoordPlane } from '../Math/coord-plane'; import { QuadTree } from '../Collision/Detection/QuadTree'; -import { Debug } from '../Debug'; +import { DebugConfig } from '../Debug'; export interface TileMapOptions { /** @@ -539,7 +539,7 @@ export class TileMap extends Entity { this.emit('postdraw', new PostDrawEvent(ctx as any, delta, this)); } - public debug(gfx: ExcaliburGraphicsContext, debugFlags: Debug) { + public debug(gfx: ExcaliburGraphicsContext, debugFlags: DebugConfig) { const { showAll, showGrid, diff --git a/src/spec/DebugSpec.ts b/src/spec/DebugSpec.ts new file mode 100644 index 00000000..1d2ca5fa --- /dev/null +++ b/src/spec/DebugSpec.ts @@ -0,0 +1,164 @@ +import * as ex from '@excalibur'; +import { ExcaliburAsyncMatchers } from 'excalibur-jasmine'; + +describe('Debug draw static', () => { + beforeAll(() => { + jasmine.addAsyncMatchers(ExcaliburAsyncMatchers); + }); + it('exists', () => { + expect(ex.Debug).toBeDefined(); + }); + + it('can draw a point', async () => { + const canvas = document.createElement('canvas'); + canvas.width = 10; + canvas.height = 10; + const context = new ex.ExcaliburGraphicsContextWebGL({ + canvasElement: canvas, + backgroundColor: ex.Color.Black + }); + context.clear(); + + ex.Debug.drawPoint(ex.vec(5, 5), {size: 5, color: ex.Color.ExcaliburBlue}); + + ex.Debug.flush(context); + context.flush(); + + await expectAsync(canvas).toEqualImage('src/spec/images/DebugSpec/point.png'); + }); + + it('can draw a line', async () => { + const canvas = document.createElement('canvas'); + canvas.width = 10; + canvas.height = 10; + const context = new ex.ExcaliburGraphicsContextWebGL({ + canvasElement: canvas, + backgroundColor: ex.Color.Black + }); + context.clear(); + + ex.Debug.drawLine(ex.vec(0, 0), ex.vec(10, 10), {color: ex.Color.ExcaliburBlue}); + + ex.Debug.flush(context); + context.flush(); + + await expectAsync(canvas).toEqualImage('src/spec/images/DebugSpec/line.png'); + }); + + it('can draw lines', async () => { + const canvas = document.createElement('canvas'); + canvas.width = 10; + canvas.height = 10; + const context = new ex.ExcaliburGraphicsContextWebGL({ + canvasElement: canvas, + backgroundColor: ex.Color.Black + }); + context.clear(); + + ex.Debug.drawLines([ex.vec(0, 0), ex.vec(10, 10), ex.vec(10, 0), ex.vec(0, 10)], {color: ex.Color.ExcaliburBlue}); + + ex.Debug.flush(context); + context.flush(); + + await expectAsync(canvas).toEqualImage('src/spec/images/DebugSpec/lines.png'); + }); + + xit('can draw text', async () => { + const canvas = document.createElement('canvas'); + canvas.width = 100; + canvas.height = 100; + const context = new ex.ExcaliburGraphicsContextWebGL({ + canvasElement: canvas, + backgroundColor: ex.Color.Black + }); + context.clear(); + + ex.Debug.drawText('some text', ex.vec(0, 50)); + + ex.Debug.flush(context); + context.flush(); + + await expectAsync(canvas).toEqualImage('src/spec/images/DebugSpec/text.png'); + }); + + it('can draw a polygon', async () => { + const canvas = document.createElement('canvas'); + canvas.width = 10; + canvas.height = 10; + const context = new ex.ExcaliburGraphicsContextWebGL({ + canvasElement: canvas, + backgroundColor: ex.Color.Black + }); + context.clear(); + + ex.Debug.drawPolygon([ex.vec(2, 2), ex.vec(8, 2), ex.vec(8, 8), ex.vec(2, 8)], {color: ex.Color.ExcaliburBlue}); + + ex.Debug.flush(context); + context.flush(); + + await expectAsync(canvas).toEqualImage('src/spec/images/DebugSpec/polygon.png'); + }); + + it('can draw a circle', async () => { + const canvas = document.createElement('canvas'); + canvas.width = 10; + canvas.height = 10; + const context = new ex.ExcaliburGraphicsContextWebGL({ + canvasElement: canvas, + backgroundColor: ex.Color.Black + }); + context.clear(); + + ex.Debug.drawCircle(ex.vec(5, 5), 5, {color: ex.Color.Transparent, strokeColor: ex.Color.ExcaliburBlue, width: 1}); + + ex.Debug.flush(context); + context.flush(); + + await expectAsync(canvas).toEqualImage('src/spec/images/DebugSpec/circle.png'); + }); + + it('can draw bounds', async () => { + const canvas = document.createElement('canvas'); + canvas.width = 10; + canvas.height = 10; + const context = new ex.ExcaliburGraphicsContextWebGL({ + canvasElement: canvas, + backgroundColor: ex.Color.Black + }); + context.clear(); + + const bounds = new ex.BoundingBox({ + left: 3, + right: 7, + top: 3, + bottom: 7 + }); + + ex.Debug.drawBounds(bounds, { color: ex.Color.ExcaliburBlue }); + + ex.Debug.flush(context); + context.flush(); + + await expectAsync(canvas).toEqualImage('src/spec/images/DebugSpec/bounds.png'); + }); + + it('can draw a ray', async () => { + const canvas = document.createElement('canvas'); + canvas.width = 10; + canvas.height = 10; + const context = new ex.ExcaliburGraphicsContextWebGL({ + canvasElement: canvas, + backgroundColor: ex.Color.Black + }); + context.clear(); + + const ray = new ex.Ray(ex.vec(0, 0), ex.vec(1, 1)); + + ex.Debug.drawRay(ray, { distance: 4, color: ex.Color.ExcaliburBlue }); + + ex.Debug.flush(context); + context.flush(); + + await expectAsync(canvas).toEqualImage('src/spec/images/DebugSpec/ray.png'); + }); +}); \ No newline at end of file diff --git a/src/spec/images/DebugSpec/bounds.png b/src/spec/images/DebugSpec/bounds.png new file mode 100644 index 0000000000000000000000000000000000000000..fef36347245ae8f817e5875c2243ed8cd98a533d GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0y~yU;weXIM^5%7F z7sn8d;N+BqgdgWm96EO3jQX(yYKxB_SeSR3fyq8kF73zHCqjqs`>&Vy_y6b711C-# z`Zhl_XmaCmn>mf4Arl{dpUs}ox<-kMXMguisG@kwvmVD|9|6}Tjsru6TQ#BwqIYWbNF}f*#g&usR2iu{%z%% z->;XJAi|aVpI@kE`z|51ekbnMZOuQAXsg`HT4d{7@!{8z}%B_>DGT%=2wy&nrSHs2|v!Otv&1DVHlx!!po`a zAs45nTVRL3VVhG-*G8=)%OsXNl^m@QRnlf-2s5{!`jV9vR8i>Hb4D^E|(rCa|Ud2+fEyXx$BA3Jd3z=03) zY7J{7B71zJxEBeesBU8E=+EKqkJNBd+>|6BeY50~h)}9&;5o{Kk literal 0 HcmV?d00001 diff --git a/src/spec/images/DebugSpec/point.png b/src/spec/images/DebugSpec/point.png new file mode 100644 index 0000000000000000000000000000000000000000..758ca7cfb561e35d4b6e881ded4a00d916c6df29 GIT binary patch literal 226 zcmeAS@N?(olHy`uVBq!ia0y~yU;weXIM^5%7ylC+Wx6H&Tb+KR1{Bx2N{VffGtP z-{(7Doz>`kwX1Pv_>{)RiJWUDJp6Cpwts%S@t+;FJr9q+UCh%n$7=gIdJ0rwEy#+j|wFU?K<`UzeL>~%X0@bgv`I%-|(_wc*62XR4CW< i;ygvGiVofa24>TUuL>8LPcbksFnGH9xvXC taLC~tNgb}vPdq0+y0G#KK0M37pul3qxHjW#ECT}rgQu&X%Q~loCIA})EusJb literal 0 HcmV?d00001 diff --git a/src/spec/images/DebugSpec/ray.png b/src/spec/images/DebugSpec/ray.png new file mode 100644 index 0000000000000000000000000000000000000000..4a523f3046349847f96d41f26a8b4c5e7245b139 GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0y~yU;weXIM^5%7!{8z}%B_>DGT%=2wy&nrSHs2|v!Otv$;S^rt9G!q532 i*T-9&Q)OmyF`9-*GD@`6H83zRFnGH9xvX`8w&_e_eF?3aCQckbAq`OCtEGyd3WT-^$V5nq<)0oVZPp0yoBTAa}`C^9lW~T(^w}j z_HOJ;=Ti=!^@#hXSJiim>F(r)X&eW3ma^EaK7M!>$2X~P6SMi( zuNH8|#LsPi5}L1M`@>uzx4riH#1Cs{F+>+%jZ3fDl_f4@uH*b`!~Ne!T%Or+XO}b- zyq%j~-*RL}c;%kgr@0^2rz`!RW0#=5$T*E@7GLz`gE3V}f3#cYENj_v|Buct_c}%i zF^g?|4RZIIeL|J}Q~v&T*kLx0^}Kqxc*~7)p2Yv3cTG66GgYPO^fG1Po4qf-SQ_l% z{I^Cml=1M*xzfv16%KX3T2NZ~bN|^R&ssBb6YU~-%H8!hM6D5;cx!p0r}b&CllJlN z(&9QYY};Jjb6zR$KyrJ-=Ns%-H<9d93q~kE}1O{u5Ja3t6I`` znoSBN-aT(L6)ypX{yoacAEDzCWj>i#PqbT5q0dtbf{r_>8$<6%xFgef_Tg;uhHd zveK^a_hs#ezaO|wc_tz@X23E?Yz!ERz}U})Sb)m@A~`J*RwCyyt(qh zH)Hmej{0x=>V<7yuhY<8VzR9y*gTqFTEFt6R!L8#^>qDY<{Zh0|0|tR?x1w}Ydyie(@D$YgQSKl4UelFg{ANQ zR7_^%uEy=qz#O{|$&TjrEm6zD_oxdE6{IfazYw3q|vbXgW(_SZf-e{AZ zYq@yCmd*QH=dEc?{=LD8_pi{Qd44gQW#&GwopIZ_&Qk7XfcB>5e=H^1PUp`wKJI^5 z+v1X@`*y46XS2Ert^UimoHra(s(r9wtKE+x-ZMw0adn=l6TDWDai7aDu==0(%XKPI z&;6b*-}Wc;&fdT81imR)^Sa8<=*`)|YQUiTvVCu%flc=DL;GHLI30Slbx%mg!xwJF ze{*-LzCJ!#pr-IxT>9#nH|(6c)S6cESw6Gg;v07&`_q0y$#v_`>t0FUCigAjcgyn& zwyPTblguuDDd5(TjoZL?ZoXQut7YqS9kYttN2-?e-9KS&b$*xO9^(mH58aY`Ra*2@ zU-9VU4NA4|?{;$_@ub&$UEKjl8P^zw>|G$$}lI| zud)2=*Ar|>8%$-7SfBH;UAfq7@!DU8c4EoTqU!DJD>pU=epFmxv0PK}6FM75G+Qwuk*-y(PSV(+!~tRmTqPu)`P37uPZ!SKzI??Jp;X7f++>;If{$f=}U=%iu(;@4Zw z|1?~8=V+Q^g6r4+Tn`u2=DyhTf_X{bU$L7B+Vf3w{0sk7$?TDjy`7ye*n3ypDI)i! z^Zy6>n`bhYef-Y)kLi1rX^!si=W3k}Z$Ez2U!48rxXr!q?{W*wW1=di$!76~{^9e^t+r>*U)W{d_haq(50AB~Kdy|KVjXwssy)*o zr&;3n4&Gn$S-b-eE0g-4DXQgy61 ztlV&?W!|jtjO<6ZLE3qurQWb_emD8La`a7}#f!M7P2s*)&vx+V=gtE27@oC<@9)=o zv66`~QSpcmAES^``{n{MA*CKKKLyT-ha)YxIR)KR)hD!^c&Ia{wS{BxBw-JyCk0~X zoSa$$Pjad#7FlqoD=HpZ@q}fPfaRRl%@YI?LyDL@J7mrsiuC9((6Ve)ap6mUsH5T{ iF?Ei^P%{1>>)I(ZLO-3qDa^pYz~JfX=d#Wzp$PygQ_}tb literal 0 HcmV?d00001