import { describe, expect, it } from 'vitest'; import { CreateAssetCommand } from '../../src/core/commands/assetCommands'; import { SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands'; import { pointerThrough } from '../../src/core/properties/camera'; import { OVERLAY_KEY } from '../../src/core/model/renderLayers'; import { cameraView, isInside, overlayView, screenToWorld } from '../../src/viewport/view'; import { makeSpriteScene, place, startWorld } from '../helpers'; const MAIN = { width: 640, height: 360, zoom: 1 }; describe('the mouse through a camera (#304)', () => { it('lands where the overlay picture #302 draws shows that world point', () => { const main = cameraView({ x: 0, y: 0, props: MAIN }, 1280, 720, 2); const cameras = [ { x: 1000, y: 0, props: { ...MAIN, zoom: 2 } }, { x: 50, y: 7, props: { width: 320, height: 360, zoom: 2 } }, ]; for (const camera of cameras) { const drawn = overlayView(camera, main, { w: 640, h: 360 }); for (const [sx, sy] of [ [640, 360], [840, 460], [100, 700], [330, 10], ] as const) { const onMain = screenToWorld(main, sx, sy); const seen = pointerThrough({ x: onMain.x / 640, y: onMain.y / 360 }, true, MAIN, camera); const expected = screenToWorld(drawn, sx, sy); expect(seen.x).toBeCloseTo(expected.x, 9); expect(seen.y).toBeCloseTo(expected.y, 9); expect(seen.inside).toBe(isInside(drawn, sx, sy)); } } }); it('gives one mouse two world points through the main camera and a zoomed overlay', () => { const f = makeSpriteScene(); const cam = new CreateAssetCommand('camera', 'Cam'); f.stack.execute(cam); const set = (id: string, key: string, value: number | boolean) => f.stack.execute(new SetOverrideCommand(f.sceneId, id, key, value)); const mainId = place(f, cam.asset.id, { x: 0, y: 0 }); const overId = place(f, cam.asset.id, { x: 1000, y: 0 }); const plainId = place(f, cam.asset.id, { x: -1000, y: 0 }); set(overId, OVERLAY_KEY, true); set(overId, 'zoom', 2); const { world } = startWorld(f); const [main, over, plain] = [mainId, overId, plainId].map((id) => world.findByObjectId(id)!); world.movePointer(100, 50, true, { x: 100 / 640, y: 50 / 360 }); expect(world.pointerThrough(main!)).toEqual({ x: 100, y: 50, inside: true }); expect(world.pointerThrough(over!)).toEqual({ x: 1050, y: 25, inside: true }); // A second plain camera is not drawn, so the mouse is never on its picture. expect(world.pointerThrough(plain!).inside).toBe(false); // The main camera moving on under a mouse that stands still does not move it on the overlay. main!.x = 500; expect(world.pointerThrough(over!)).toEqual({ x: 1050, y: 25, inside: true }); world.movePointer(100, 50, false, { x: 100 / 640, y: 50 / 360 }); expect(world.pointerThrough(over!).inside).toBe(false); world.movePointer(100, 50, true, { x: 100 / 640, y: 50 / 360 }); set(overId, 'enabled', false); world.syncFromModel(); expect(world.pointerThrough(over!)).toMatchObject({ x: 1050, y: 25, inside: false }); }); it('hovers a button only the overlay shows through that camera only', () => { const f = makeSpriteScene('Button'); const cam = new CreateAssetCommand('camera', 'Cam'); f.stack.execute(cam); place(f, cam.asset.id, { x: 0, y: 0 }); const overId = place(f, cam.asset.id, { x: 1000, y: 0 }); f.stack.execute(new SetOverrideCommand(f.sceneId, overId, OVERLAY_KEY, true)); f.stack.execute(new SetOverrideCommand(f.sceneId, overId, 'zoom', 2)); const buttonId = place(f, f.spriteId, { x: 1050, y: 25 }); const { world } = startWorld(f); const button = world.findByObjectId(buttonId)!; const over = world.findByObjectId(overId)!; world.movePointer(100, 50, true, { x: 100 / 640, y: 50 / 360 }); const { x, y } = world.pointerThrough(over); expect(world.hitTest(button, x, y)).toBe(true); expect(world.hitTest(button, world.pointer.x, world.pointer.y)).toBe(false); }); });