diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index 2ab1a615ee..09e36df7f6 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -27,6 +27,7 @@ import { RenderState } from "./shader/state/RenderState"; import { Texture2D, TextureCubeFace, TextureCubeMap, TextureFormat } from "./texture"; import { ModelMesh, PrimitiveMesh } from "./mesh"; import { CompareFunction } from "./shader"; +import { InputManager } from "./input/InputManager"; import { IPhysics } from "@oasis-engine/design"; import { PhysicsManager } from "./physics"; @@ -65,6 +66,8 @@ export class Engine extends EventDispatcher { _shaderProgramPools: ShaderProgramPool[] = []; /** @internal */ _spriteMaskManager: SpriteMaskManager; + /** @internal */ + _inputManager: InputManager; protected _canvas: Canvas; private _resourceManager: ResourceManager = new ResourceManager(this); @@ -177,6 +180,8 @@ export class Engine extends EventDispatcher { this._spriteDefaultMaterial = this._createSpriteMaterial(); this._spriteMaskDefaultMaterial = this._createSpriteMaskMaterial(); + this._inputManager = new InputManager(this); + const whitePixel = new Uint8Array([255, 255, 255, 255]); const whiteTexture2D = new Texture2D(this, 1, 1, TextureFormat.R8G8B8A8, false); @@ -248,7 +253,10 @@ export class Engine extends EventDispatcher { const scene = this._sceneManager._activeScene; const componentsManager = this._componentsManager; if (scene) { + scene._activeCameras.sort((camera1, camera2) => camera1.priority - camera2.priority); + componentsManager.callScriptOnStart(); + this._inputManager._update(); if (this.physicsManager) { componentsManager.callColliderOnUpdate(); this.physicsManager._update(deltaTime); @@ -283,7 +291,7 @@ export class Engine extends EventDispatcher { if (this._sceneManager) { this._whiteTexture2D.destroy(true); this._whiteTextureCube.destroy(true); - + this._inputManager._destroy(); this.trigger(new Event("shutdown", this)); engineFeatureManager.callFeatureMethod(this, "shutdown", [this]); @@ -339,9 +347,6 @@ export class Engine extends EventDispatcher { scene._updateShaderData(); if (cameras.length > 0) { - // Sort on priority - //@ts-ignore - cameras.sort((camera1, camera2) => camera1.priority - camera2.priority); for (let i = 0, l = cameras.length; i < l; i++) { const camera = cameras[i]; const cameraEntity = camera.entity; diff --git a/packages/core/src/input/InputManager.ts b/packages/core/src/input/InputManager.ts new file mode 100644 index 0000000000..58a75d337b --- /dev/null +++ b/packages/core/src/input/InputManager.ts @@ -0,0 +1,50 @@ +import { Engine } from "../Engine"; +import { Pointer } from "./pointer/Pointer"; +import { PointerManager } from "./pointer/PointerManager"; + +/** + * InputManager manages device input such as mouse, touch, keyboard, etc. + */ +export class InputManager { + private _pointerManager: PointerManager; + + /** + * Pointer List. + */ + get pointers(): Readonly { + return this._pointerManager._pointers; + } + + /** + * Whether to handle multi-pointer. + */ + get multiPointerEnabled(): boolean { + return this._pointerManager._multiPointerEnabled; + } + + set multiPointerEnabled(enabled: boolean) { + this._pointerManager._multiPointerEnabled = enabled; + } + + /** + * @internal + */ + constructor(engine: Engine) { + // @ts-ignore + this._pointerManager = new PointerManager(engine, engine.canvas._webCanvas); + } + + /** + * @internal + */ + _update(): void { + this._pointerManager._update(); + } + + /** + * @internal + */ + _destroy(): void { + this._pointerManager._destroy(); + } +} diff --git a/packages/core/src/input/enums/PointerPhase.ts b/packages/core/src/input/enums/PointerPhase.ts new file mode 100644 index 0000000000..8638b44a14 --- /dev/null +++ b/packages/core/src/input/enums/PointerPhase.ts @@ -0,0 +1,13 @@ +/** + * The current phase of the pointer. + */ +export enum PointerPhase { + /** A Pointer pressed on the screen. */ + Down, + /** A pointer moved on the screen. */ + Move, + /** A pointer was lifted from the screen. */ + Up, + /** The system cancelled tracking for the pointer. */ + Leave +} diff --git a/packages/core/src/input/pointer/Pointer.ts b/packages/core/src/input/pointer/Pointer.ts new file mode 100644 index 0000000000..3539654b80 --- /dev/null +++ b/packages/core/src/input/pointer/Pointer.ts @@ -0,0 +1,29 @@ +import { Vector2 } from "@oasis-engine/math"; +import { PointerPhase } from "../enums/PointerPhase"; + +/** + * Pointer. + */ +export class Pointer { + /** + * Unique id. + * @remark Start from 0. + */ + readonly id: number; + /** The phase of pointer. */ + phase: PointerPhase = PointerPhase.Leave; + /** The position of the pointer in screen space pixel coordinates. */ + position: Vector2 = new Vector2(); + + /** @internal */ + _uniqueID: number; + /** @internal */ + _needUpdate: boolean = true; + + /** + * @internal + */ + constructor(id: number) { + this.id = id; + } +} diff --git a/packages/core/src/input/pointer/PointerManager.ts b/packages/core/src/input/pointer/PointerManager.ts new file mode 100644 index 0000000000..dbbd320380 --- /dev/null +++ b/packages/core/src/input/pointer/PointerManager.ts @@ -0,0 +1,313 @@ +import { Ray, Vector2 } from "@oasis-engine/math"; +import { Canvas } from "../../Canvas"; +import { Engine } from "../../Engine"; +import { Entity } from "../../Entity"; +import { CameraClearFlags } from "../../enums/CameraClearFlags"; +import { HitResult } from "../../physics"; +import { PointerPhase } from "../enums/PointerPhase"; +import { Pointer } from "./Pointer"; + +/** + * Pointer Manager. + * @internal + */ +export class PointerManager { + private static _tempRay: Ray = new Ray(); + private static _tempPoint: Vector2 = new Vector2(); + private static _tempHitResult: HitResult = new HitResult(); + + /** @internal */ + _pointers: Pointer[] = []; + /** @internal */ + _multiPointerEnabled: boolean = true; + + private _engine: Engine; + private _canvas: Canvas; + private _nativeEvents: PointerEvent[] = []; + private _pointerPool: Pointer[]; + private _keyEventList: number[] = []; + private _keyEventCount: number = 0; + private _needOverallPointers: boolean = false; + private _currentPosition: Vector2 = new Vector2(); + private _currentPressedEntity: Entity; + private _currentEnteredEntity: Entity; + + /** + * Create a PointerManager. + * @param engine - The current engine instance + */ + constructor(engine: Engine) { + this._engine = engine; + this._canvas = engine.canvas; + // @ts-ignore + const htmlCanvas = this._canvas._webCanvas as HTMLCanvasElement; + htmlCanvas.style.touchAction = "none"; + // prettier-ignore + htmlCanvas.onpointerdown = htmlCanvas.onpointerup = htmlCanvas.onpointerout = htmlCanvas.onpointermove = (evt:PointerEvent)=>{ + this._nativeEvents.push(evt); + }; + // MaxTouchCount + MouseCount(1) + this._pointerPool = new Array(navigator.maxTouchPoints + 1); + } + + /** + * @internal + */ + _update(): void { + this._needOverallPointers && this._overallPointers(); + if (this._nativeEvents.length > 0) { + this._handlePointerEvent(this._nativeEvents); + const rayCastEntity = this._pointerRayCast(); + const { _keyEventCount: keyEventCount, _keyEventList: keyEventList } = this; + if (keyEventCount > 0) { + this._firePointerExitAndEnter(rayCastEntity); + for (let i = 0; i < keyEventCount; i++) { + switch (keyEventList[i]) { + case PointerKeyEvent.Down: + this._firePointerDown(rayCastEntity); + break; + case PointerKeyEvent.Up: + this._firePointerUpAndClick(rayCastEntity); + break; + } + } + if (keyEventList[keyEventCount - 1] === PointerKeyEvent.Leave) { + this._firePointerExitAndEnter(null); + this._currentPressedEntity = null; + } + this._keyEventCount = 0; + } else { + this._firePointerDrag(); + this._firePointerExitAndEnter(rayCastEntity); + } + } else { + this._firePointerDrag(); + this._firePointerExitAndEnter(this._pointerRayCast()); + } + } + + /** + * @internal + */ + _destroy(): void { + // @ts-ignore + const htmlCanvas = this._canvas._webCanvas as HTMLCanvasElement; + htmlCanvas.onpointerdown = htmlCanvas.onpointerup = htmlCanvas.onpointerout = htmlCanvas.onpointermove = null; + this._nativeEvents.length = 0; + this._pointerPool.length = 0; + this._pointers.length = 0; + this._currentPosition = null; + this._currentEnteredEntity = null; + this._currentPressedEntity = null; + this._engine = null; + this._canvas = null; + } + + 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--) { + if (pointers[i]._uniqueID === pointerId) { + return i; + } + } + return -1; + } + + public _addPointer(pointerId: number, x: number, y: number, phase: PointerPhase): void { + 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.setValue(x, y); + pointer._needUpdate = true; + pointer.phase = phase; + pointers.splice(i, 0, pointer); + } + } + + private _removePointer(pointerIndex: number): void { + this._pointers[pointerIndex].phase = PointerPhase.Leave; + } + + private _updatePointer(pointerIndex: number, x: number, y: number, phase: PointerPhase): void { + const updatedPointer = this._pointers[pointerIndex]; + updatedPointer.position.setValue(x, y); + updatedPointer._needUpdate = true; + updatedPointer.phase = phase; + } + + private _handlePointerEvent(nativeEvents: PointerEvent[]): void { + const { _pointers: pointers, _keyEventList: keyEventList } = this; + let activePointerCount = pointers.length; + for (let i = 0, n = nativeEvents.length; i < n; i++) { + const evt = nativeEvents[i]; + let pointerIndex = this._getIndexByPointerID(evt.pointerId); + switch (evt.type) { + case "pointerdown": + if (pointerIndex === -1) { + this._addPointer(evt.pointerId, evt.offsetX, evt.offsetY, PointerPhase.Down); + activePointerCount++; + } else { + this._updatePointer(pointerIndex, evt.offsetX, evt.offsetY, PointerPhase.Down); + } + activePointerCount === 1 && (keyEventList[this._keyEventCount++] = PointerKeyEvent.Down); + break; + case "pointerup": + if (pointerIndex >= 0) { + this._updatePointer(pointerIndex, evt.offsetX, evt.offsetY, PointerPhase.Up); + activePointerCount === 1 && (keyEventList[this._keyEventCount++] = PointerKeyEvent.Up); + } + break; + case "pointermove": + if (pointerIndex === -1) { + this._addPointer(evt.pointerId, evt.offsetX, evt.offsetY, PointerPhase.Move); + activePointerCount++; + } else { + this._updatePointer(pointerIndex, evt.offsetX, evt.offsetY, PointerPhase.Move); + } + break; + case "pointerout": + if (pointerIndex >= 0) { + this._removePointer(pointerIndex); + --activePointerCount === 0 && (keyEventList[this._keyEventCount++] = PointerKeyEvent.Leave); + this._needOverallPointers = true; + } + break; + } + } + nativeEvents.length = 0; + const pointerCount = pointers.length; + if (pointerCount > 0) { + const { _canvas: canvas, _currentPosition: currentPosition } = this; + // @ts-ignore + const pixelRatioWidth = canvas.width / (canvas._webCanvas as HTMLCanvasElement).clientWidth; + // @ts-ignore + const pixelRatioHeight = canvas.height / (canvas._webCanvas as HTMLCanvasElement).clientWidth; + currentPosition.setValue(0, 0); + for (let i = 0; i < pointerCount; i++) { + const pointer = pointers[i]; + const { position } = pointer; + if (pointer._needUpdate) { + position.setValue(position.x * pixelRatioWidth, position.y * pixelRatioHeight); + pointer._needUpdate = false; + } + currentPosition.add(position); + } + currentPosition.scale(1 / pointerCount); + } + } + + private _pointerRayCast(): Entity { + if (this._pointers.length > 0) { + let x = this._currentPosition.x / this._canvas.width; + let y = this._currentPosition.y / this._canvas.height; + const cameras = this._engine.sceneManager.activeScene._activeCameras; + const { _tempPoint, _tempRay, _tempHitResult } = PointerManager; + 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) { + PointerManager._tempPoint.setValue((x - vpX) / vpW, (y - vpY) / vpH); + // TODO: Only check which colliders have listened to the input. + if (this._engine.physicsManager.raycast(camera.viewportPointToRay(_tempPoint, _tempRay), _tempHitResult)) { + return PointerManager._tempHitResult.entity; + } else if (camera.clearFlags === CameraClearFlags.DepthColor) { + return null; + } + } + } + } + return null; + } + + private _firePointerDrag(): void { + if (this._currentPressedEntity) { + const scripts = this._currentPressedEntity._scripts; + for (let i = scripts.length - 1; i >= 0; i--) { + scripts.get(i).onPointerDrag(); + } + } + } + + 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--) { + scripts.get(i).onPointerExit(); + } + } + if (rayCastEntity) { + const scripts = rayCastEntity._scripts; + for (let i = scripts.length - 1; i >= 0; i--) { + scripts.get(i).onPointerEnter(); + } + } + this._currentEnteredEntity = rayCastEntity; + } + } + + private _firePointerDown(rayCastEntity: Entity): void { + if (rayCastEntity) { + const scripts = rayCastEntity._scripts; + for (let i = scripts.length - 1; i >= 0; i--) { + scripts.get(i).onPointerDown(); + } + } + this._currentPressedEntity = rayCastEntity; + } + + private _firePointerUpAndClick(rayCastEntity: Entity): void { + if (this._currentPressedEntity) { + const sameTarget = this._currentPressedEntity === rayCastEntity; + const scripts = rayCastEntity._scripts; + for (let i = scripts.length - 1; i >= 0; i--) { + const script = scripts.get(i); + sameTarget && script.onPointerClick(); + script.onPointerUp(); + } + this._currentPressedEntity = null; + } + } +} + +/** + * @internal + */ +enum PointerKeyEvent { + Down, + Up, + Leave +} diff --git a/packages/physics-physx/src/PhysXPhysics.ts b/packages/physics-physx/src/PhysXPhysics.ts index c0a518d59e..d0b6218a7a 100644 --- a/packages/physics-physx/src/PhysXPhysics.ts +++ b/packages/physics-physx/src/PhysXPhysics.ts @@ -62,9 +62,9 @@ export class PhysXPhysics { } if (runtimeMode == PhysXRuntimeMode.JavaScript) { - script.src = "http://30.50.24.12:8000/physx.release.js"; + script.src = "https://lg-2fw0hhsc-1256786476.cos.ap-shanghai.myqcloud.com/phy/physx.release.js"; } else if (runtimeMode == PhysXRuntimeMode.WebAssembly) { - script.src = "http://30.50.24.12:8000/physx.release.js"; + script.src = "https://lg-2fw0hhsc-1256786476.cos.ap-shanghai.myqcloud.com/phy/physx.release.js"; } }); diff --git a/packages/rhi-webgl/src/WebCanvas.ts b/packages/rhi-webgl/src/WebCanvas.ts index 8ae74ceb6d..00cefe6e44 100644 --- a/packages/rhi-webgl/src/WebCanvas.ts +++ b/packages/rhi-webgl/src/WebCanvas.ts @@ -69,10 +69,8 @@ export class WebCanvas implements Canvas { resizeByClientSize(pixelRatio: number = window.devicePixelRatio): void { const webCanvas = this._webCanvas; if (webCanvas instanceof HTMLCanvasElement) { - const width = webCanvas.clientWidth; - const height = webCanvas.clientHeight; - this.width = width * pixelRatio; - this.height = height * pixelRatio; + this.width = webCanvas.clientWidth * pixelRatio; + this.height = webCanvas.clientHeight * pixelRatio; } }