Skip to content

Commit

Permalink
fix: [#3078] Pointer lastWorldPosition updates when the camera does (#…
Browse files Browse the repository at this point in the history
…3245)


Closes #3078
  • Loading branch information
eonarheim authored Nov 3, 2024
1 parent 2b9be51 commit e14f866
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ are doing mtv adjustments during precollision.

### Fixed

- Fixed issue where the pointer `lastWorldPos` was not updated when the current `Camera` moved
- Fixed issue where `cancel()`'d events still bubbled to the top level input handlers
- Fixed issue where unexpected html HTML content from an image would silently hang the loader
- Fixed issue where Collision events ahd inconsistent targets, sometimes they were Colliders and sometimes they were Entities
Expand Down
21 changes: 16 additions & 5 deletions src/engine/Camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { PreUpdateEvent, PostUpdateEvent, InitializeEvent } from './Events';
import { BoundingBox } from './Collision/BoundingBox';
import { Logger } from './Util/Log';
import { ExcaliburGraphicsContext } from './Graphics/Context/ExcaliburGraphicsContext';
import { watchAny } from './Util/Watch';
import { AffineMatrix } from './Math/affine-matrix';
import { EventEmitter, EventKey, Handler, Subscription } from './EventEmitter';
import { pixelSnapEpsilon } from './Graphics';
import { sign } from './Math/util';
import { WatchVector } from './Math/watch-vector';

/**
* Interface that describes a custom camera strategy for tracking targets
Expand Down Expand Up @@ -312,17 +312,28 @@ export class Camera implements CanUpdate, CanInitialize {
this._angularVelocity = value;
}

private _posChanged = false;
private _pos: Vector = new WatchVector(Vector.Zero, () => {
this._posChanged = true;
});
/**
* Get or set the camera's position
*/
private _posChanged = false;
private _pos: Vector = watchAny(Vector.Zero, () => (this._posChanged = true));
public get pos(): Vector {
return this._pos;
}
public set pos(vec: Vector) {
this._pos = watchAny(vec, () => (this._posChanged = true));
this._posChanged = true;
this._pos = new WatchVector(vec, () => {
this._posChanged = true;
});
}

/**
* Has the position changed since the last update
*/
public hasChanged(): boolean {
return this._posChanged;
}
/**
* Interpolated camera position if more draws are running than updates
Expand Down Expand Up @@ -782,8 +793,8 @@ export class Camera implements CanUpdate, CanInitialize {
// It's important to update the camera after strategies
// This prevents jitter
this.updateTransform(this.pos);

this._postupdate(engine, elapsedMs);
this._posChanged = false;
}

private _snapPos = vec(0, 0);
Expand Down
13 changes: 13 additions & 0 deletions src/engine/Input/PointerAbstraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Vector } from '../Math/vector';
import { PointerEvent } from './PointerEvent';
import { EventEmitter, EventKey, Handler, Subscription } from '../EventEmitter';
import { PointerEvents } from './PointerEventReceiver';
import { GlobalCoordinates } from '../Math/global-coordinates';
import { Engine } from '../Engine';

export class PointerAbstraction {
public events = new EventEmitter<PointerEvents>();
Expand Down Expand Up @@ -50,6 +52,17 @@ export class PointerAbstraction {
this.events.off(eventName, handler);
}

/**
* Called internally by excalibur to keep pointers up to date
* @internal
* @param pos

Check warning on line 58 in src/engine/Input/PointerAbstraction.ts

View workflow job for this annotation

GitHub Actions / build / build

Expected @param names to be "engine". Got "pos"

Check warning on line 58 in src/engine/Input/PointerAbstraction.ts

View workflow job for this annotation

GitHub Actions / build

Expected @param names to be "engine". Got "pos"
*/
public _updateWorldPosition(engine: Engine) {
const coord = GlobalCoordinates.fromPagePosition(this.lastPagePos, engine);
this.lastScreenPos = coord.screenPos;
this.lastWorldPos = coord.worldPos;
}

private _onPointerMove = (ev: PointerEvent): void => {
this.lastPagePos = new Vector(ev.pagePos.x, ev.pagePos.y);
this.lastScreenPos = new Vector(ev.screenPos.x, ev.screenPos.y);
Expand Down
7 changes: 7 additions & 0 deletions src/engine/Input/PointerEventReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export class PointerEventReceiver {
* Updates the current frame pointer info and emits raw pointer events
*
* This does not emit events to entities, see PointerSystem
* @internal
*/
public update() {
this.lastFramePointerDown = new Map(this.currentFramePointerDown);
Expand Down Expand Up @@ -253,6 +254,12 @@ export class PointerEventReceiver {
this.primary.emit('pointerwheel', event);
this.primary.emit('wheel', event);
}

if (this.engine.currentScene.camera.hasChanged()) {
for (const pointer of this._pointers) {
pointer._updateWorldPosition(this.engine);
}
}
}

/**
Expand Down
9 changes: 8 additions & 1 deletion src/engine/Input/PointerSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ export class PointerSystem extends System {
};

public preupdate(): void {
if (this._scene.camera.hasChanged()) {
// if the camera has changed we want to force a transform update so pointers can be correctly calc'd
this._scene.camera.updateTransform(this._scene.camera.pos);
}

// event receiver might change per frame
this._receivers = [this._engine.input.pointers, this._scene.input.pointers];
this._engineReceiver = this._engine.input.pointers;
Expand Down Expand Up @@ -142,8 +147,10 @@ export class PointerSystem extends System {
// Dispatch pointer events on entities
this._dispatchEvents(this._sortedEntities);

// Clear last frame's events
// Dispatch pointer events on top level pointers
this._receivers.forEach((r) => r.update());

// Clear last frame's events
this.lastFrameEntityToPointers.clear();
this.lastFrameEntityToPointers = new Map<number, number[]>(this.currentFrameEntityToPointers);
this.currentFrameEntityToPointers.clear();
Expand Down
5 changes: 5 additions & 0 deletions src/engine/Math/watch-vector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ export class WatchVector extends Vector {
this._y = this.original.y = newY;
}
}

override setTo(x: number, y: number): void {
this.x = x;
this.y = y;
}
}
2 changes: 1 addition & 1 deletion src/spec/CameraSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ describe('A camera', () => {

it('can use built-in elastic around actor strategy', () => {
engine.currentScene.camera = new ex.Camera();
engine.currentScene.camera.pos.setTo(0, 0);
engine.currentScene.camera.pos = ex.vec(0, 0);
const actor = new ex.Actor();

engine.currentScene.camera.strategy.elasticToActor(actor, 0.05, 0.1);
Expand Down
27 changes: 25 additions & 2 deletions src/spec/PointerInputSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,29 @@ describe('A pointer', () => {
expect(spyTopLevelPointerWheel).not.toHaveBeenCalled();
});

it('should update the pointer pos when the camera moves', () => {
const oldMargin = document.body.style.margin;
const oldPadding = document.body.style.padding;
document.body.style.margin = '0';
document.body.style.padding = '0';
executeMouseEvent('pointerdown', <any>document, null, 10, 10);
engine.currentScene.update(engine, 0);
expect(engine.input.pointers.primary.lastWorldPos).toEqual(ex.vec(10, 10));
expect(engine.input.pointers.primary.lastScreenPos).toEqual(ex.vec(10, 10));
expect(engine.input.pointers.primary.lastPagePos).toEqual(ex.vec(10, 10));

expect(engine.currentScene.camera.hasChanged()).toBe(false);
engine.currentScene.camera.pos = ex.vec(1000, 1000);
expect(engine.currentScene.camera.hasChanged()).toBe(true);
engine.currentScene.update(engine, 0);
expect(engine.input.pointers.primary.lastWorldPos).toEqual(ex.vec(760, 760));
expect(engine.input.pointers.primary.lastScreenPos).toEqual(ex.vec(10, 10));
expect(engine.input.pointers.primary.lastPagePos).toEqual(ex.vec(10, 10));

document.body.style.margin = oldMargin;
document.body.style.padding = oldPadding;
});

it('should dispatch point events on screen elements', () => {
const pointerDownSpy = jasmine.createSpy('pointerdown');
const screenElement = new ex.ScreenElement({
Expand Down Expand Up @@ -307,9 +330,9 @@ describe('A pointer', () => {
const actor1 = new ex.Actor({
pos: ex.vec(50, 50),
width: 100,
height: 100
height: 100,
coordPlane: ex.CoordPlane.Screen
});
actor1.transform.coordPlane = ex.CoordPlane.Screen;
actor1.on('pointerdown', clickSpy);
engine.currentScene.camera.pos = ex.vec(1000, 1000);
engine.currentScene.camera.draw(engine.graphicsContext);
Expand Down

0 comments on commit e14f866

Please sign in to comment.