diff --git a/packages/core/src/Script.ts b/packages/core/src/Script.ts index 6b3d73ab01..1fded50630 100644 --- a/packages/core/src/Script.ts +++ b/packages/core/src/Script.ts @@ -1,6 +1,7 @@ import { Camera } from "./Camera"; import { ignoreClone } from "./clone/CloneManager"; import { Component } from "./Component"; +import { Pointer } from "./input"; import { ColliderShape } from "./physics"; /** @@ -117,34 +118,40 @@ export class Script extends Component { /** * Called when the pointer is down while over the ColliderShape. + * @param pointer - The pointer that triggered */ - onPointerDown(): void {} + onPointerDown(pointer: Pointer): void {} /** * Called when the pointer is up while over the ColliderShape. + * @param pointer - The pointer that triggered */ - onPointerUp(): void {} + onPointerUp(pointer: Pointer): void {} /** * Called when the pointer is down and up with the same collider. + * @param pointer - The pointer that triggered */ - onPointerClick(): void {} + onPointerClick(pointer: Pointer): void {} /** * Called when the pointer is enters the ColliderShape. + * @param pointer - The pointer that triggered */ - onPointerEnter(): void {} + onPointerEnter(pointer: Pointer): void {} /** * Called when the pointer is no longer over the ColliderShape. + * @param pointer - The pointer that triggered */ - onPointerExit(): void {} + onPointerExit(pointer: Pointer): void {} /** * Called when the pointer is down while over the ColliderShape and is still holding down. + * @param pointer - The pointer that triggered * @remarks onPointerDrag is called every frame while the pointer is down. */ - onPointerDrag(): void {} + onPointerDrag(pointer: Pointer): void {} /** * Called when be disabled. diff --git a/packages/core/src/input/InputManager.ts b/packages/core/src/input/InputManager.ts index 73dbdc884c..8701e594b8 100644 --- a/packages/core/src/input/InputManager.ts +++ b/packages/core/src/input/InputManager.ts @@ -3,7 +3,7 @@ import { KeyboardManager } from "./keyboard/KeyboardManager"; import { Keys } from "./enums/Keys"; import { Pointer } from "./pointer/Pointer"; import { PointerManager } from "./pointer/PointerManager"; -import { PointerButton } from "./enums/PointerButton"; +import { PointerButton, _pointerBin2DecMap } from "./enums/PointerButton"; import { WheelManager } from "./wheel/WheelManager"; import { Vector2, Vector3 } from "@oasis-engine/math"; @@ -19,7 +19,7 @@ export class InputManager { private _keyboardManager: KeyboardManager; /** - * Pointer List. + * Pointer list. */ get pointers(): Readonly { return this._initialized ? this._pointerManager._pointers : null; @@ -44,24 +44,6 @@ export class InputManager { return this._initialized ? this._wheelManager._delta : null; } - /** - * Get the change of the pointer. - * @returns Change value - */ - get pointerMovingDelta(): Readonly { - return this._initialized ? this._pointerManager._movingDelta : null; - } - - /** - * Get the position of the pointer. - * @returns The position of the pointer - */ - get pointerPosition(): Readonly { - return this._initialized && this._pointerManager._pointers.length > 0 - ? this._pointerManager._currentPosition - : null; - } - /** * Whether the key is being held down, if there is no parameter, return whether any key is being held down. * @param key - The keys of the keyboard @@ -123,7 +105,7 @@ export class InputManager { if (pointerButton === undefined) { return this._pointerManager._buttons !== 0; } else { - return (this._pointerManager._buttons & PointerManager.Buttons[pointerButton]) !== 0; + return (this._pointerManager._buttons & pointerButton) !== 0; } } else { return false; @@ -140,7 +122,7 @@ export class InputManager { if (pointerButton === undefined) { return this._pointerManager._downList.length > 0; } else { - return this._pointerManager._downMap[pointerButton] === this._curFrameCount; + return this._pointerManager._downMap[_pointerBin2DecMap[pointerButton]] === this._curFrameCount; } } else { return false; @@ -157,7 +139,7 @@ export class InputManager { if (pointerButton === undefined) { return this._pointerManager._upList.length > 0; } else { - return this._pointerManager._upMap[pointerButton] === this._curFrameCount; + return this._pointerManager._upMap[_pointerBin2DecMap[pointerButton]] === this._curFrameCount; } } else { return false; diff --git a/packages/core/src/input/enums/PointerButton.ts b/packages/core/src/input/enums/PointerButton.ts index 8060aabe7e..55378dbddb 100644 --- a/packages/core/src/input/enums/PointerButton.ts +++ b/packages/core/src/input/enums/PointerButton.ts @@ -1,29 +1,78 @@ /** * Defines values that specify the buttons on a pointer device. - * Refer to the W3C standards.(https://www.w3.org/TR/uievents/#dom-mouseevent-button) + * Refer to the W3C standards: + * (https://www.w3.org/TR/uievents/#dom-mouseevent-button) + * (https://www.w3.org/TR/uievents/#dom-mouseevent-buttons) * Refer to Microsoft's documentation.(https://docs.microsoft.com/en-us/dotnet/api/system.windows.input.mousebutton?view=windowsdesktop-6.0) */ export enum PointerButton { + /** No button. */ + None = 0x0, /** Indicate the primary pointer of the device (in general, the left button or the only button on single-button devices, used to activate a user interface control or select text) or the un-initialized value. */ - Primary = 0, - /** Indicate the auxiliary pointer (in general, the middle button, often combined with a mouse wheel). */ - Auxiliary = 1, + Primary = 0x1, /** Indicate the secondary pointer (in general, the right button, often used to display a context menu). */ - Secondary = 2, + Secondary = 0x2, + /** Indicate the auxiliary pointer (in general, the middle button, often combined with a mouse wheel). */ + Auxiliary = 0x4, /** Indicate the X1 (back) pointer. */ - XButton1 = 3, + XButton1 = 0x8, /** Indicate the X2 (forward) pointer. */ - XButton2 = 4, + XButton2 = 0x10, /** Indicate the X3 pointer. */ - XButton3 = 5, + XButton3 = 0x20, /** Indicate the X4 pointer. */ - XButton4 = 6, + XButton4 = 0x40, /** Indicate the X5 pointer. */ - XButton5 = 7, + XButton5 = 0x80, /** Indicate the X6 pointer. */ - XButton6 = 8, + XButton6 = 0x100, /** Indicate the X7 pointer. */ - XButton7 = 9, + XButton7 = 0x200, /** Indicate the X8 pointer. */ - XButton8 = 10 + XButton8 = 0x400 } + +/** + * @internal + */ +export const _pointerDec2BinMap = [ + PointerButton.Primary, + PointerButton.Auxiliary, + PointerButton.Secondary, + PointerButton.XButton1, + PointerButton.XButton2, + PointerButton.XButton3, + PointerButton.XButton4, + PointerButton.XButton5, + PointerButton.XButton6, + PointerButton.XButton7, + PointerButton.XButton8 +]; + +/** + * @internal + */ +export const _pointerBin2DecMap: Record = { + /** Primary */ + 0x1: 0, + /** Secondary */ + 0x2: 2, + /** Auxiliary */ + 0x4: 1, + /** XButton1 */ + 0x8: 3, + /** XButton2 */ + 0x10: 4, + /** XButton3 */ + 0x20: 5, + /** XButton4 */ + 0x40: 6, + /** XButton5 */ + 0x80: 7, + /** XButton6 */ + 0x100: 8, + /** XButton7 */ + 0x200: 9, + /** XButton8 */ + 0x400: 10 +}; diff --git a/packages/core/src/input/enums/PointerPhase.ts b/packages/core/src/input/enums/PointerPhase.ts index 8638b44a14..3060410165 100644 --- a/packages/core/src/input/enums/PointerPhase.ts +++ b/packages/core/src/input/enums/PointerPhase.ts @@ -6,6 +6,8 @@ export enum PointerPhase { Down, /** A pointer moved on the screen. */ Move, + /** A Pointer pressed on the screen but hasn't moved. */ + Stationary, /** A pointer was lifted from the screen. */ Up, /** The system cancelled tracking for the pointer. */ diff --git a/packages/core/src/input/pointer/Pointer.ts b/packages/core/src/input/pointer/Pointer.ts index 81a5574114..2bccffda88 100644 --- a/packages/core/src/input/pointer/Pointer.ts +++ b/packages/core/src/input/pointer/Pointer.ts @@ -1,4 +1,6 @@ import { Vector2 } from "@oasis-engine/math"; +import { Entity } from "../../Entity"; +import { PointerButton } from "../enums/PointerButton"; import { PointerPhase } from "../enums/PointerPhase"; /** @@ -12,12 +14,83 @@ export class Pointer { readonly id: number; /** The phase of pointer. */ phase: PointerPhase = PointerPhase.Leave; + /** The button that triggers the pointer event. */ + button: PointerButton; + /** The currently pressed buttons for this pointer. */ + pressedButtons: PointerButton; /** The position of the pointer in screen space pixel coordinates. */ position: Vector2 = new Vector2(); - + /** The change of the pointer. */ + deltaPosition: Vector2 = new Vector2(); + /** @internal */ + _events: PointerEvent[] = []; /** @internal */ _uniqueID: number; + private _currentPressedEntity: Entity; + private _currentEnteredEntity: Entity; + + /** @internal */ + _firePointerExitAndEnter(rayCastEntity: Entity): void { + if (this._currentEnteredEntity !== rayCastEntity) { + if (this._currentEnteredEntity) { + const scripts = this._currentEnteredEntity._scripts; + for (let i = scripts.length - 1; i >= 0; i--) { + const script = scripts.get(i); + script._waitHandlingInValid || script.onPointerExit(this); + } + } + if (rayCastEntity) { + const scripts = rayCastEntity._scripts; + for (let i = scripts.length - 1; i >= 0; i--) { + const script = scripts.get(i); + script._waitHandlingInValid || script.onPointerEnter(this); + } + } + this._currentEnteredEntity = rayCastEntity; + } + } + + /** @internal */ + _firePointerDown(rayCastEntity: Entity): void { + if (rayCastEntity) { + const scripts = rayCastEntity._scripts; + for (let i = scripts.length - 1; i >= 0; i--) { + const script = scripts.get(i); + script._waitHandlingInValid || script.onPointerDown(this); + } + } + this._currentPressedEntity = rayCastEntity; + } + + /** @internal */ + _firePointerDrag(): void { + if (this._currentPressedEntity) { + const scripts = this._currentPressedEntity._scripts; + for (let i = scripts.length - 1; i >= 0; i--) { + const script = scripts.get(i); + script._waitHandlingInValid || script.onPointerDrag(this); + } + } + } + + /** @internal */ + _firePointerUpAndClick(rayCastEntity: Entity): void { + const { _currentPressedEntity: pressedEntity } = this; + if (pressedEntity) { + const sameTarget = pressedEntity === rayCastEntity; + const scripts = pressedEntity._scripts; + for (let i = scripts.length - 1; i >= 0; i--) { + const script = scripts.get(i); + if (!script._waitHandlingInValid) { + sameTarget && script.onPointerClick(this); + script.onPointerUp(this); + } + } + this._currentPressedEntity = null; + } + } + /** * @internal */ diff --git a/packages/core/src/input/pointer/PointerManager.ts b/packages/core/src/input/pointer/PointerManager.ts index 56f7c3f021..8e3290668f 100644 --- a/packages/core/src/input/pointer/PointerManager.ts +++ b/packages/core/src/input/pointer/PointerManager.ts @@ -6,7 +6,7 @@ import { Entity } from "../../Entity"; import { CameraClearFlags } from "../../enums/CameraClearFlags"; import { HitResult } from "../../physics"; import { PointerPhase } from "../enums/PointerPhase"; -import { PointerButton } from "../enums/PointerButton"; +import { PointerButton, _pointerBin2DecMap, _pointerDec2BinMap } from "../enums/PointerButton"; import { IInput } from "../interface/IInput"; import { Pointer } from "./Pointer"; @@ -15,45 +15,30 @@ import { Pointer } from "./Pointer"; * @internal */ export class PointerManager implements IInput { - /** Refer to the W3C standards.(https://www.w3.org/TR/uievents/#dom-mouseevent-buttons) */ - public static Buttons = [0x1, 0x4, 0x2, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400]; - private static _tempRay: Ray = new Ray(); private static _tempPoint: Vector2 = new Vector2(); private static _tempHitResult: HitResult = new HitResult(); - /** @internal */ _pointers: Pointer[] = []; /** @internal */ - _movingDelta: Vector2 = new Vector2(); - /** @internal */ _multiPointerEnabled: boolean = true; /** @internal */ - _buttons: number = 0x0; + _buttons: PointerButton = PointerButton.None; /** @internal */ _upMap: number[] = []; /** @internal */ _downMap: number[] = []; /** @internal */ - _downList: DisorderedArray = new DisorderedArray(); - /** @internal */ _upList: DisorderedArray = new DisorderedArray(); /** @internal */ - _currentPosition: Vector2 = new Vector2(); - - private _currentPressedEntity: Entity; - private _currentEnteredEntity: Entity; + _downList: DisorderedArray = new DisorderedArray(); private _engine: Engine; private _canvas: Canvas; private _htmlCanvas: HTMLCanvasElement; private _nativeEvents: PointerEvent[] = []; private _pointerPool: Pointer[]; - private _keyEventList: number[] = []; - private _keyEventCount: number = 0; - private _needOverallPointers: boolean = false; private _hadListener: boolean = false; - private _lastPositionFrameCount: number = 0; /** * Create a PointerManager. @@ -67,12 +52,10 @@ export class PointerManager implements IInput { htmlCanvas.oncontextmenu = (event: UIEvent) => { return false; }; - const onPointerEvent = (this._onPointerEvent = this._onPointerEvent.bind(this)); - htmlCanvas.addEventListener("pointerdown", onPointerEvent); - htmlCanvas.addEventListener("pointerup", onPointerEvent); - htmlCanvas.addEventListener("pointerout", onPointerEvent); - htmlCanvas.addEventListener("pointermove", onPointerEvent); - this._hadListener = true; + this._onPointerEvent = this._onPointerEvent.bind(this); + this._updatePointerWithPhysics = this._updatePointerWithPhysics.bind(this); + this._updatePointerWithoutPhysics = this._updatePointerWithoutPhysics.bind(this); + this._onFocus(); // If there are no compatibility issues, navigator.maxTouchPoints should be used here. this._pointerPool = new Array(11); } @@ -81,33 +64,44 @@ export class PointerManager implements IInput { * @internal */ _update(frameCount: number): void { - this._needOverallPointers && this._overallPointers(); - this._downList.length = 0; - this._upList.length = 0; - this._movingDelta.set(0, 0); - this._nativeEvents.length > 0 && this._handlePointerEvent(this._nativeEvents, frameCount); - this._pointers.length > 0 && (this._lastPositionFrameCount = frameCount); - if (this._engine.physicsManager._initialized) { - const rayCastEntity = this._pointerRayCast(); - const { _keyEventCount: keyEventCount } = this; - if (keyEventCount > 0) { - const { _keyEventList: keyEventList } = this; - for (let i = 0; i < keyEventCount; i++) { - switch (keyEventList[i]) { - case PointerKeyEvent.Down: - this._firePointerDown(rayCastEntity); - break; - case PointerKeyEvent.Up: - this._firePointerUpAndClick(rayCastEntity); - break; + const { _pointers: pointers, _nativeEvents: nativeEvents } = this; + /** Clean up the pointer released in the previous frame. */ + let lastIndex = pointers.length - 1; + if (lastIndex >= 0) { + for (let i = lastIndex; i >= 0; i--) { + if (pointers[i].phase === PointerPhase.Leave) { + if (i !== lastIndex) { + pointers[i] = pointers[lastIndex]; } + --lastIndex; } - this._firePointerExitAndEnter(rayCastEntity); - keyEventList[keyEventCount - 1] === PointerKeyEvent.Leave && (this._currentPressedEntity = null); - this._keyEventCount = 0; - } else { - this._firePointerDrag(); - this._firePointerExitAndEnter(rayCastEntity); + } + pointers.length = lastIndex + 1; + } + + /** Generate the pointer received for this frame. */ + lastIndex = nativeEvents.length - 1; + if (lastIndex >= 0) { + for (let i = 0; i <= lastIndex; i++) { + const evt = nativeEvents[i]; + this._getPointer(evt.pointerId)?._events.push(evt); + } + nativeEvents.length = 0; + } + + /** Pointer handles its own events. */ + lastIndex = pointers.length - 1; + this._buttons = PointerButton.None; + if (lastIndex >= 0) { + const updatePointer = this._engine.physicsManager._initialized + ? this._updatePointerWithPhysics + : this._updatePointerWithoutPhysics; + const { clientWidth, clientHeight } = this._htmlCanvas; + const { width, height } = this._canvas; + for (let i = lastIndex; i >= 0; i--) { + const pointer = pointers[i]; + updatePointer(frameCount, pointer, clientWidth, clientHeight, width, height); + this._buttons |= pointer.pressedButtons; } } } @@ -136,13 +130,14 @@ export class PointerManager implements IInput { htmlCanvas.removeEventListener("pointerup", onPointerEvent); htmlCanvas.removeEventListener("pointerout", onPointerEvent); htmlCanvas.removeEventListener("pointermove", onPointerEvent); - this._nativeEvents.length = 0; - this._pointerPool.length = 0; - this._currentEnteredEntity = null; - this._currentPressedEntity = null; + this._hadListener = false; this._downList.length = 0; this._upList.length = 0; - this._hadListener = false; + const { _pointers: pointers } = this; + for (let i = pointers.length - 1; i >= 0; i--) { + pointers[i].phase = PointerPhase.Leave; + } + pointers.length = 0; } } @@ -159,14 +154,12 @@ export class PointerManager implements IInput { htmlCanvas.removeEventListener("pointermove", onPointerEvent); this._hadListener = false; } - this._nativeEvents.length = 0; this._pointerPool.length = 0; this._pointers.length = 0; - this._currentPosition = null; - this._currentEnteredEntity = null; - this._currentPressedEntity = null; + this._downList.length = 0; + this._upList.length = 0; + this._htmlCanvas = null; this._engine = null; - this._canvas = null; } private _onPointerEvent(evt: PointerEvent) { @@ -175,23 +168,6 @@ export class PointerManager implements IInput { this._nativeEvents.push(evt); } - private _overallPointers(): void { - const { _pointers: pointers } = this; - let deleteCount = 0; - const totalCount = pointers.length; - for (let i = 0; i < totalCount; i++) { - if (pointers[i].phase === PointerPhase.Leave) { - deleteCount++; - } else { - if (deleteCount > 0) { - pointers[i - deleteCount] = pointers[i]; - } - } - } - pointers.length = totalCount - deleteCount; - this._needOverallPointers = false; - } - private _getIndexByPointerID(pointerId: number): number { const { _pointers: pointers } = this; for (let i = pointers.length - 1; i >= 0; i--) { @@ -202,214 +178,169 @@ export class PointerManager implements IInput { return -1; } - private _addPointer(pointerId: number, x: number, y: number, phase: PointerPhase): void { + private _getPointer(pointerId: number): Pointer { const { _pointers: pointers } = this; - const lastCount = pointers.length; - if (lastCount === 0 || this._multiPointerEnabled) { - const { _pointerPool: pointerPool } = this; - // Get Pointer smallest index. - let i = 0; - for (; i < lastCount; i++) { - if (pointers[i].id > i) { - break; - } - } - let pointer = pointerPool[i]; - if (!pointer) { - pointer = pointerPool[i] = new Pointer(i); - } - pointer._uniqueID = pointerId; - pointer.position.set(x, y); - pointer.phase = phase; - pointers.splice(i, 0, pointer); - } - } - - private _removePointer(pointerIndex: number): void { - const leavePointer = this._pointers[pointerIndex]; - leavePointer.phase = PointerPhase.Leave; - } - - private _updatePointer(pointerIndex: number, x: number, y: number, phase: PointerPhase): void { - const updatedPointer = this._pointers[pointerIndex]; - updatedPointer.position.set(x, y); - updatedPointer.phase = phase; - } - - private _handlePointerEvent(nativeEvents: PointerEvent[], frameCount: number): void { - const { - _pointers: pointers, - _keyEventList: keyEventList, - _upMap: upMap, - _downMap: downMap, - _upList: upList, - _downList: downList - } = this; - let activePointerCount = pointers.length; - const pixelRatioW = this._canvas.width / this._htmlCanvas.clientWidth; - const pixelRatioH = this._canvas.height / this._htmlCanvas.clientHeight; - const nativeEventsLen = nativeEvents.length; - for (let i = 0; i < nativeEventsLen; i++) { - const evt = nativeEvents[i]; - const pointerButton: PointerButton = evt.button | PointerButton.Primary; - const pointerIndex = this._getIndexByPointerID(evt.pointerId); - switch (evt.type) { - case "pointerdown": - if (pointerIndex === -1) { - this._addPointer(evt.pointerId, evt.offsetX * pixelRatioW, evt.offsetY * pixelRatioH, PointerPhase.Down); - activePointerCount++; - } else { - this._updatePointer(pointerIndex, evt.offsetX * pixelRatioW, evt.offsetY * pixelRatioH, PointerPhase.Down); - } - activePointerCount === 1 && (keyEventList[this._keyEventCount++] = PointerKeyEvent.Down); - downList.add(pointerButton); - downMap[pointerButton] = frameCount; - break; - case "pointerup": - if (pointerIndex >= 0) { - this._updatePointer(pointerIndex, evt.offsetX * pixelRatioW, evt.offsetY * pixelRatioH, PointerPhase.Up); - activePointerCount === 1 && (keyEventList[this._keyEventCount++] = PointerKeyEvent.Up); - } - upList.add(pointerButton); - upMap[pointerButton] = frameCount; - break; - case "pointermove": - if (pointerIndex === -1) { - this._addPointer(evt.pointerId, evt.offsetX * pixelRatioW, evt.offsetY * pixelRatioH, PointerPhase.Move); - activePointerCount++; - } else { - this._updatePointer(pointerIndex, evt.offsetX * pixelRatioW, evt.offsetY * pixelRatioH, PointerPhase.Move); - } - break; - case "pointerout": - if (pointerIndex >= 0) { - this._removePointer(pointerIndex); - --activePointerCount === 0 && (keyEventList[this._keyEventCount++] = PointerKeyEvent.Leave); - this._needOverallPointers = true; + const index = this._getIndexByPointerID(pointerId); + if (index >= 0) { + return pointers[index]; + } else { + const lastCount = pointers.length; + if (lastCount === 0 || this._multiPointerEnabled) { + const { _pointerPool: pointerPool } = this; + // Get Pointer smallest index. + let i = 0; + for (; i < lastCount; i++) { + if (pointers[i].id > i) { + break; } - break; - } - } - this._buttons = nativeEvents[nativeEventsLen - 1].buttons; - const pointerCount = pointers.length; - if (pointerCount > 0) { - const { _currentPosition: currentPosition } = this; - const { x: lastX, y: lastY } = currentPosition; - if (activePointerCount === 0) { - // Get the pointer coordinates when leaving, and use it to correctly dispatch the click event. - const lastNativeEvent = nativeEvents[nativeEventsLen - 1]; - currentPosition.set(lastNativeEvent.offsetX * pixelRatioW, lastNativeEvent.offsetY * pixelRatioH); - } else { - currentPosition.set(0, 0); - for (let i = 0; i < pointerCount; i++) { - currentPosition.add(pointers[i].position); - } - currentPosition.scale(1 / pointerCount); - } - // Update pointer moving delta. - if (this._lastPositionFrameCount === frameCount - 1) { - this._movingDelta.set(currentPosition.x - lastX, currentPosition.y - lastY); - } - } - nativeEvents.length = 0; - } - - private _pointerRayCast(): Entity { - if (this._pointers.length > 0) { - const { _tempPoint: point, _tempRay: ray, _tempHitResult: hitResult } = PointerManager; - const { _activeCameras: cameras } = this._engine.sceneManager.activeScene; - const x = this._currentPosition.x / this._canvas.width; - const y = this._currentPosition.y / this._canvas.height; - for (let i = cameras.length - 1; i >= 0; i--) { - const camera = cameras[i]; - if (!camera.enabled || camera.renderTarget) { - continue; } - const { x: vpX, y: vpY, z: vpW, w: vpH } = camera.viewport; - if (x >= vpX && y >= vpY && x - vpX <= vpW && y - vpY <= vpH) { - point.set((x - vpX) / vpW, (y - vpY) / vpH); - // TODO: Only check which colliders have listened to the input. - if ( - this._engine.physicsManager.raycast( - camera.viewportPointToRay(point, ray), - Number.MAX_VALUE, - camera.cullingMask, - hitResult - ) - ) { - return hitResult.entity; - } else if (camera.clearFlags & CameraClearFlags.Color) { - return null; - } + let pointer = pointerPool[i]; + if (!pointer) { + pointer = pointerPool[i] = new Pointer(i); } - } - } - return null; - } - - private _firePointerDrag(): void { - if (this._currentPressedEntity) { - const scripts = this._currentPressedEntity._scripts; - for (let i = scripts.length - 1; i >= 0; i--) { - const script = scripts.get(i); - script._waitHandlingInValid || script.onPointerDrag(); + pointer._uniqueID = pointerId; + pointers.splice(i, 0, pointer); + return pointer; + } else { + return null; } } } - private _firePointerExitAndEnter(rayCastEntity: Entity): void { - if (this._currentEnteredEntity !== rayCastEntity) { - if (this._currentEnteredEntity) { - const scripts = this._currentEnteredEntity._scripts; - for (let i = scripts.length - 1; i >= 0; i--) { - const script = scripts.get(i); - script._waitHandlingInValid || script.onPointerExit(); - } + private _pointerRayCast(normalizedX: number, normalizedY: number): Entity { + const { _tempPoint: point, _tempRay: ray, _tempHitResult: hitResult } = PointerManager; + const { _activeCameras: cameras } = this._engine.sceneManager.activeScene; + for (let i = cameras.length - 1; i >= 0; i--) { + const camera = cameras[i]; + if (!camera.enabled || camera.renderTarget) { + continue; } - if (rayCastEntity) { - const scripts = rayCastEntity._scripts; - for (let i = scripts.length - 1; i >= 0; i--) { - const script = scripts.get(i); - script._waitHandlingInValid || script.onPointerEnter(); + const { x: vpX, y: vpY, z: vpW, w: vpH } = camera.viewport; + if (normalizedX >= vpX && normalizedY >= vpY && normalizedX - vpX <= vpW && normalizedY - vpY <= vpH) { + point.set((normalizedX - vpX) / vpW, (normalizedY - vpY) / vpH); + if ( + this._engine.physicsManager.raycast( + camera.viewportPointToRay(point, ray), + Number.MAX_VALUE, + camera.cullingMask, + hitResult + ) + ) { + return hitResult.entity; + } else if (camera.clearFlags & CameraClearFlags.Color) { + return null; } } - this._currentEnteredEntity = rayCastEntity; } } - private _firePointerDown(rayCastEntity: Entity): void { - if (rayCastEntity) { - const scripts = rayCastEntity._scripts; - for (let i = scripts.length - 1; i >= 0; i--) { - const script = scripts.get(i); - script._waitHandlingInValid || script.onPointerDown(); + private _updatePointerWithPhysics( + frameCount: number, + pointer: Pointer, + clientW: number, + clientH: number, + canvasW: number, + canvasH: number + ): void { + const { _events: events, position } = pointer; + const length = events.length; + if (length > 0) { + const { _upList, _upMap, _downList, _downMap } = this; + const latestEvent = events[length - 1]; + const normalizedX = latestEvent.offsetX / clientW; + const normalizedY = latestEvent.offsetY / clientH; + const currX = normalizedX * canvasW; + const currY = normalizedY * canvasH; + position.set(currX, currY); + if (currX === position.x && currY === position.y) { + pointer.deltaPosition.set(currX - position.x, currY - position.y); + pointer.phase = PointerPhase.Move; + } else { + pointer.deltaPosition.set(0, 0); + pointer.phase = PointerPhase.Stationary; + } + pointer._firePointerDrag(); + const rayCastEntity = this._pointerRayCast(normalizedX, normalizedY); + pointer._firePointerExitAndEnter(rayCastEntity); + for (let i = 0; i < length; i++) { + const event = events[i]; + const pointerButton = (pointer.button = _pointerDec2BinMap[event.button]); + pointer.pressedButtons = event.buttons; + switch (event.type) { + case "pointerdown": + _downList.add(pointerButton); + _downMap[pointerButton] = frameCount; + pointer.phase = PointerPhase.Down; + pointer._firePointerDown(rayCastEntity); + break; + case "pointerup": + _upList.add(pointerButton); + _upMap[pointerButton] = frameCount; + pointer.phase = PointerPhase.Up; + pointer._firePointerUpAndClick(rayCastEntity); + break; + case "pointerout": + pointer.phase = PointerPhase.Leave; + pointer._firePointerExitAndEnter(null); + default: + break; + } } + pointer._events.length = 0; + } else { + pointer.deltaPosition.set(0, 0); + pointer.phase = PointerPhase.Stationary; + pointer._firePointerDrag(); + pointer._firePointerExitAndEnter(this._pointerRayCast(position.x / canvasW, position.y / canvasH)); } - this._currentPressedEntity = rayCastEntity; } - private _firePointerUpAndClick(rayCastEntity: Entity): void { - const { _currentPressedEntity: pressedEntity } = this; - if (pressedEntity) { - const sameTarget = pressedEntity === rayCastEntity; - const scripts = pressedEntity._scripts; - for (let i = scripts.length - 1; i >= 0; i--) { - const script = scripts.get(i); - if (!script._waitHandlingInValid) { - sameTarget && script.onPointerClick(); - script.onPointerUp(); + private _updatePointerWithoutPhysics( + frameCount: number, + pointer: Pointer, + clientW: number, + clientH: number, + canvasW: number, + canvasH: number + ): void { + const { _events: events } = pointer; + const length = events.length; + if (length > 0) { + const { position } = pointer; + const latestEvent = events[length - 1]; + const currX = (latestEvent.offsetX / clientW) * canvasW; + const currY = (latestEvent.offsetY / clientH) * canvasH; + pointer.deltaPosition.set(currX - position.x, currY - position.y); + position.set(currX, currY); + pointer.button = _pointerDec2BinMap[latestEvent.button]; + pointer.pressedButtons = latestEvent.buttons; + const { _upList, _upMap, _downList, _downMap } = this; + for (let i = 0; i < length; i++) { + const event = events[i]; + switch (event.type) { + case "pointerdown": + _downList.add(event.button); + _downMap[event.button] = frameCount; + pointer.phase = PointerPhase.Down; + break; + case "pointerup": + _upList.add(event.button); + _upMap[event.button] = frameCount; + pointer.phase = PointerPhase.Up; + break; + case "pointermove": + pointer.phase = PointerPhase.Move; + break; + case "pointerout": + pointer.phase = PointerPhase.Leave; + default: + break; } } - this._currentPressedEntity = null; + pointer._events.length = 0; + } else { + pointer.deltaPosition.set(0, 0); + pointer.phase = PointerPhase.Stationary; } } } - -/** - * @internal - */ -enum PointerKeyEvent { - Down, - Up, - Leave -}