Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions packages/melonjs/src/input/pointerevent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ function dispatchEvent(normalizedEvents: Pointer[]): boolean {
lastTimeStamp = pointer.event!.timeStamp;
}

const worldOffset = _app.world.pos;
const absoluteWorldX = pointer.gameWorldX + worldOffset.x;
const absoluteWorldY = pointer.gameWorldY + worldOffset.y;

currentPointer.pos.set(pointer.gameWorldX, pointer.gameWorldY);
currentPointer.setSize(pointer.width, pointer.height);

Expand All @@ -323,13 +327,38 @@ function dispatchEvent(normalizedEvents: Pointer[]): boolean {
emit(POINTERMOVE, pointer);
}

// fetch valid candiates from the game world container
// Fetch candidates in level-local coordinates. Floating regions are indexed
// in this coordinate space because Camera2d.localToWorld removes the root
// world offset.
let candidates = _app.world.broadphase.retrieve(
currentPointer,
(a: any, b: any) => _app.world._sortReverseZ(a, b),
undefined,
);

// Non-floating renderable bounds include the root world offset. This offset
// is introduced when a level is centered by flex-height or flex-width, so a
// second query is required to find their absolute broadphase entries.
if (worldOffset.x !== 0 || worldOffset.y !== 0) {
currentPointer.pos.set(absoluteWorldX, absoluteWorldY);
const absoluteCandidates = _app.world.broadphase.retrieve(
currentPointer,
(a: any, b: any) => _app.world._sortReverseZ(a, b),
undefined,
);
Comment on lines +343 to +348
for (const candidate of candidates) {
if (
candidate.isFloating === true &&
!absoluteCandidates.includes(candidate)
) {
absoluteCandidates.push(candidate);
}
}
candidates = absoluteCandidates.sort((a, b) =>
_app.world._sortReverseZ(a, b),
);
}

// add the main game viewport to the list of candidates
candidates = candidates.concat([_app.viewport]);

Expand All @@ -355,11 +384,19 @@ function dispatchEvent(normalizedEvents: Pointer[]): boolean {
// within the region ancestor container
if (typeof ancestor !== "undefined") {
const parentBounds = ancestor.getBounds();
pointer.gameLocalX = pointer.gameX - parentBounds.x;
pointer.gameLocalY = pointer.gameY - parentBounds.y;
if (region.isFloating === true) {
pointer.gameLocalX = pointer.gameX - parentBounds.x;
pointer.gameLocalY = pointer.gameY - parentBounds.y;
} else {
pointer.gameLocalX = absoluteWorldX - parentBounds.x;
pointer.gameLocalY = absoluteWorldY - parentBounds.y;
}
}

const eventInBounds = bounds.contains(pointer.gameX, pointer.gameY);
const eventInBounds =
region.isFloating === true
? bounds.contains(pointer.gameX, pointer.gameY)
: bounds.contains(absoluteWorldX, absoluteWorldY);

switch (pointer.type) {
case POINTER_MOVE[0]:
Expand Down
73 changes: 73 additions & 0 deletions packages/melonjs/tests/input.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,82 @@ describe("input", () => {
afterEach(() => {
// clean up the game world
app.world.reset();
app.world.pos.set(0, 0, 0);
app.world.broadphase.clear();
});

it.each([
{
name: "vertical world offset used by flex-height",
worldOffset: [0, 120],
renderablePosition: [50, 100],
screenPosition: [70, 240],
expectedWorldPosition: [70, 120],
},
{
name: "horizontal world offset used by flex-width",
worldOffset: [120, 0],
renderablePosition: [100, 50],
screenPosition: [240, 70],
expectedWorldPosition: [120, 70],
},
])("should trigger pointerdown with a $name", ({
worldOffset,
renderablePosition,
screenPosition,
expectedWorldPosition,
}) => {
const renderable = new Renderable(
renderablePosition[0],
renderablePosition[1],
40,
40,
);
renderable.anchorPoint.set(0, 0);
renderable.isKinematic = false;

app.world.addChild(renderable);
app.world.pos.set(worldOffset[0], worldOffset[1], 0);
renderable.updateBounds(true);
app.world.broadphase.clear();
app.world.broadphase.insertContainer(app.world);

let receivedPointer;
input.registerPointerEvent("pointerdown", renderable, (pointer) => {
receivedPointer = pointer;
});

try {
const canvas = app.renderer.getCanvas();
const canvasBounds = canvas.getBoundingClientRect();
canvas.dispatchEvent(
new PointerEvent("pointerdown", {
clientX:
canvasBounds.left +
(screenPosition[0] * canvasBounds.width) / canvas.width,
clientY:
canvasBounds.top +
(screenPosition[1] * canvasBounds.height) / canvas.height,
pointerId: 1,
width: 1,
height: 1,
isPrimary: true,
bubbles: true,
}),
);

expect(receivedPointer).toBeDefined();
expect(receivedPointer.gameWorldX).toBeCloseTo(
expectedWorldPosition[0],
);
expect(receivedPointer.gameWorldY).toBeCloseTo(
expectedWorldPosition[1],
);
} finally {
input.releasePointerEvent("pointerdown", renderable);
}
});

it("should register and trigger a pointerdown event", () => {
return new Promise((resolve) => {
const renderable = new Renderable(0, 0, 100, 100);
Expand Down
Loading