Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: [#3078] Pointer lastWorldPosition updates when the camera does #3245

Merged
merged 12 commits into from
Nov 3, 2024
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 { 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 @@
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

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
Loading