UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

393 lines (392 loc) 14.9 kB
"use strict"; import { Spherical, Vector3, Vector2, Euler } from "three"; import { CoreMath } from "../../../core/math/_Module"; import { disableContextMenu, enableContextMenu } from "../../../core/DomUtils"; import { BaseCollisionHandler } from "./BaseCollisionHandler"; import { isBooleanTrue } from "../../../core/Type"; export const DEFAULT_PARAMS = { rotateSpeed: 1, rotationRange: { min: -Math.PI * 0.25, max: Math.PI * 0.25 } // translateSpeed: 0.1, }; const EVENT_CHANGE = { type: "change" }; const tmpCameraUnproject = new Vector3(); const spherical = new Spherical(); export class MobileJoystickControls extends BaseCollisionHandler { constructor(_camera, domElement, options, player) { super(); this._camera = _camera; this.domElement = domElement; this.options = options; this.player = player; this.translationData = { direction: new Vector3() }; this.rotationData = { direction: { x: 0, y: 0 } }; this._boundMethods = { onRotateStart: this._onRotateStart.bind(this), onRotateMove: this._onRotateMove.bind(this), onRotateEnd: this._onRotateEnd.bind(this), onTranslateStart: this._onTranslateStart.bind(this), onTranslateMove: this._onTranslateMove.bind(this), onTranslateEnd: this._onTranslateEnd.bind(this), onJump: this._onJump.bind(this), onRunToggle: this._onRunToggle.bind(this) }; this._startCameraRotation = new Euler(); // private _velocity = new Vector3(); // private _element: HTMLElement; // private _translationSpeed = 4; this._rotationSpeed = DEFAULT_PARAMS.rotateSpeed; this._rotationRange = { min: DEFAULT_PARAMS.rotationRange.min, max: DEFAULT_PARAMS.rotationRange.max }; // private _translationSpeed = DEFAULT_PARAMS.translateSpeed; this._azimuthalAngle = 0; // setTranslationSpeed(speed: number) { // this._translationSpeed = speed; // } // // // ROTATE // // this.vLeft = new Vector3(); this.vRight = new Vector3(); this.vTop = new Vector3(); this.vBottom = new Vector3(); this.angleY = 0; this.angleX = 0; this._rotationStartPosition = new Vector2(); this._rotationMovePosition = new Vector2(); this._rotationDelta = new Vector2(); // // // TRANSLATE // // this._startCameraPosition = new Vector3(); this._translationStartPosition = new Vector2(); this._translationMovePosition = new Vector2(); this._translationDelta = new Vector2(); this._camera.rotation.order = "ZYX"; this._translateDomElement = this.options.translateDomElement || this._createTranslateDomElement(); this._runDomElement = this.options.runDomElement || this._createRunDomElement(); this._jumpDomElement = this.options.jumpDomElement || this._createJumpDomElement(); this._translateDomElementRect = this._translateDomElement.getBoundingClientRect(); this._addElements(); this._addEvents(); } dispose() { this._removeEvents(); this._removeElements(); this.updateElements(); } _createTranslateDomElement() { const rect = this.domElement.getBoundingClientRect(); const minDim = Math.min(rect.width, rect.height); const size = Math.round(0.4 * minDim); const margin = Math.round(0.1 * minDim); const element = document.createElement("div"); element.id = "MobileJoystickControls-translate"; element.style.width = `${size}px`; element.style.height = element.style.width; element.style.border = "1px solid black"; element.style.borderRadius = `${size}px`; element.style.position = "absolute"; element.style.bottom = `${margin}px`; element.style.left = `${margin}px`; return element; } _jumpDomElementSize() { const rect = this.domElement.getBoundingClientRect(); const minDim = Math.min(rect.width, rect.height); const size = Math.round(0.2 * minDim); const margin = Math.round(0.05 * minDim); return { size, margin }; } _createJumpDomElement() { const { size, margin } = this._jumpDomElementSize(); const element = document.createElement("div"); element.id = "MobileJoystickControls-jump"; element.style.width = `${size}px`; const height = Math.floor(size); element.style.height = `${height}px`; element.style.border = "1px solid black"; element.style.position = "absolute"; element.style.bottom = `${2 * margin + parseInt(this._runDomElement.style.height)}px`; element.style.right = `${margin}px`; element.style.borderRadius = `${height}px`; return element; } _createRunDomElement() { const element = document.createElement("div"); const rect = this.domElement.getBoundingClientRect(); const minDim = Math.min(rect.width, rect.height); const size = Math.round(0.2 * minDim); const margin = Math.round(0.05 * minDim); element.id = "MobileJoystickControls-run"; element.style.width = `${size}px`; element.style.height = `${Math.floor(size)}px`; element.style.border = "1px solid black"; element.style.position = "absolute"; element.style.bottom = `${margin}px`; element.style.right = `${margin}px`; return element; } _addElements() { var _a, _b, _c; (_a = this.domElement.parentElement) == null ? void 0 : _a.append(this._translateDomElement); (_b = this.domElement.parentElement) == null ? void 0 : _b.append(this._jumpDomElement); (_c = this.domElement.parentElement) == null ? void 0 : _c.append(this._runDomElement); } _removeElements() { function _removeElement(element) { var _a; (_a = element.parentElement) == null ? void 0 : _a.removeChild(element); } if (!this.options.translateDomElement) { _removeElement(this._translateDomElement); } if (!this.options.runDomElement) { _removeElement(this._runDomElement); } if (!this.options.jumpDomElement) { _removeElement(this._jumpDomElement); } } updateElements() { if (!this.player) { return; } this._jumpDomElement.style.display = isBooleanTrue(this.player.jumpAllowed) ? "block" : "none"; this._runDomElement.style.display = isBooleanTrue(this.player.runAllowed) ? "block" : "none"; } _addEvents() { disableContextMenu(); this.domElement.addEventListener("touchstart", this._boundMethods.onRotateStart); this.domElement.addEventListener("touchmove", this._boundMethods.onRotateMove); this.domElement.addEventListener("touchend", this._boundMethods.onRotateEnd); this._translateDomElement.addEventListener("touchstart", this._boundMethods.onTranslateStart); this._translateDomElement.addEventListener("touchmove", this._boundMethods.onTranslateMove); this._translateDomElement.addEventListener("touchend", this._boundMethods.onTranslateEnd); this._jumpDomElement.addEventListener("pointerdown", this._boundMethods.onJump); this._runDomElement.addEventListener("pointerdown", this._boundMethods.onRunToggle); } _removeEvents() { enableContextMenu(); this.domElement.removeEventListener("touchstart", this._boundMethods.onRotateStart); this.domElement.removeEventListener("touchmove", this._boundMethods.onRotateMove); this.domElement.removeEventListener("touchend", this._boundMethods.onRotateEnd); this._translateDomElement.removeEventListener("touchstart", this._boundMethods.onTranslateStart); this._translateDomElement.removeEventListener("touchmove", this._boundMethods.onTranslateMove); this._translateDomElement.removeEventListener("touchend", this._boundMethods.onTranslateEnd); this._jumpDomElement.removeEventListener("pointerdown", this._boundMethods.onJump); this._runDomElement.removeEventListener("pointerdown", this._boundMethods.onRunToggle); } setRotationSpeed(speed) { this._rotationSpeed = speed; } setRotationRange(range) { this._rotationRange.min = range.min; this._rotationRange.max = range.max; } _onRotateStart(event) { this._startCameraRotation.copy(this._camera.rotation); const touch = this._getTouch(event, this.domElement); if (!touch) { return; } this._rotationStartPosition.set(touch.clientX, touch.clientY); this.vLeft.set(-1, 0, 0.5); this.vRight.set(1, 0, 0.5); [this.vLeft, this.vRight].forEach((v) => { v.unproject(this._camera); this._camera.worldToLocal(v); }); this.angleY = this.vLeft.angleTo(this.vRight); this.vTop.set(0, 1, 0.5); this.vBottom.set(0, -1, 0.5); [this.vTop, this.vBottom].forEach((v) => { v.unproject(this._camera); this._camera.worldToLocal(v); }); this.angleX = this.vTop.angleTo(this.vBottom); } _onRotateMove(event) { const touch = this._getTouch(event, this.domElement); if (!touch) { return; } this._rotationMovePosition.set(touch.clientX, touch.clientY); this._rotationDelta.copy(this._rotationMovePosition).sub(this._rotationStartPosition); this.rotationData.direction.x = this._rotationDelta.x / this.domElement.clientWidth; this.rotationData.direction.y = this._rotationDelta.y / this.domElement.clientHeight; this._rotateCamera(this.rotationData); } _onRotateEnd() { this.rotationData.direction.x = 0; this.rotationData.direction.y = 0; } _rotateCamera(rotationData) { const INVERT_Y = true; const INVERT_X = INVERT_Y; let angleY = this.angleY * rotationData.direction.x * this._rotationSpeed; this._camera.rotation.y = this._startCameraRotation.y + (INVERT_Y ? -angleY : angleY); let angleX = this.angleX * rotationData.direction.y * this._rotationSpeed; this._camera.rotation.x = CoreMath.clamp( this._startCameraRotation.x + (INVERT_X ? -angleX : angleX), this._rotationRange.min, this._rotationRange.max ); this._computeAzimuthalAngle(); this.dispatchEvent(EVENT_CHANGE); } _computeAzimuthalAngle() { this._camera.updateMatrixWorld(); tmpCameraUnproject.set(0, 0, 1); this._camera.localToWorld(tmpCameraUnproject); tmpCameraUnproject.sub(this._camera.position); spherical.setFromVector3(tmpCameraUnproject); this._azimuthalAngle = spherical.theta; } _onTranslateStart(event) { this._startCameraPosition.copy(this._camera.position); const touch = this._getTouch(event, this._translateDomElement); if (!touch) { return; } this._translateDomElementRect = this._translateDomElement.getBoundingClientRect(); const elementCenterX = this._translateDomElementRect.left + this._translateDomElementRect.width * 0.5; const elementCenterY = this._translateDomElementRect.top + this._translateDomElementRect.height * 0.5; this._translationStartPosition.set(elementCenterX, elementCenterY); } _onTranslateMove(event) { const touch = this._getTouch(event, this._translateDomElement); if (!touch) { return; } this._translationMovePosition.set(touch.clientX, touch.clientY); this._translationDelta.copy(this._translationMovePosition).sub(this._translationStartPosition); this.translationData.direction.x = this._translationDelta.x / this._translateDomElementRect.width * 0.5; this.translationData.direction.z = -this._translationDelta.y / this._translateDomElementRect.height * 0.5; this._updatePlayerTranslate(); this.dispatchEvent(EVENT_CHANGE); } _onTranslateEnd() { this.translationData.direction.x = 0; this.translationData.direction.z = 0; this._updatePlayerTranslate(); } _updatePlayerTranslate() { if (!this.player) { return; } const direction = this.translationData.direction; this.player.setForward(false); this.player.setBackward(false); this.player.setLeft(false); this.player.setRight(false); const absx = Math.abs(direction.x); const absz = Math.abs(direction.z); const delta = absz - absx; function checkZ(player) { if (direction.z > 0) { player.setForward(true); } if (direction.z < 0) { player.setBackward(true); } } function checkX(player) { if (direction.x > 0) { player.setRight(true); } if (direction.x < 0) { player.setLeft(true); } } if (delta > 0) { checkZ(this.player); if (delta < absz * 0.5) { checkX(this.player); } } else { checkX(this.player); if (delta < absx * 0.5) { checkZ(this.player); } } } _onJump() { var _a; (_a = this.player) == null ? void 0 : _a.jump(); } _onRunToggle() { if (!this.player) { return; } const runState = this.player.running(); this.player.setRun(!runState); const borderSize = this.player.running() ? 3 : 1; this._runDomElement.style.border = `${borderSize}px solid black`; } update(delta) { if (this.player) { this.player.setAzimuthalAngle(this._azimuthalAngle); this.player.update(delta); } } // private _camTmpPost = new Vector3(); // private _camWorldDir = new Vector3(); // private _up = new Vector3(0, 1, 0); // private _camSideVector = new Vector3(); // private _translateCamera(data: TranslationData, deltaTime: number) { // this._camera.getWorldDirection(this._camWorldDir); // this._camWorldDir.y = 0; // this._camWorldDir.normalize(); // this._camSideVector.crossVectors(this._up, this._camWorldDir); // this._camSideVector.normalize(); // this._camSideVector.multiplyScalar(-data.direction.x); // this._camWorldDir.multiplyScalar(data.direction.y); // this._velocity.copy(this._camWorldDir); // this._velocity.add(this._camSideVector); // const initialHeight = this._camera.position.y; // this._camTmpPost.copy(this._camera.position); // if (this._playerCollisionController) { // // damping // const damping = 1; //Math.exp(-3 * deltaTime) - 1; // this._velocity.addScaledVector(this._velocity, damping); // const deltaPosition = this._velocity.clone().multiplyScalar(deltaTime); // this._camTmpPost.add(deltaPosition); // const result = this._playerCollisionController.testPosition(this._camTmpPost); // if (result) { // // playerCollider.translate( result.normal.multiplyScalar( result.depth ) ); // this._camTmpPost.add(result.normal.multiplyScalar(result.depth)); // } // this._camera.position.copy(this._camTmpPost); // } else { // this._camTmpPost.add(this._camSideVector); // this._camTmpPost.add(this._camWorldDir); // this._camera.position.copy(this._camTmpPost); // } // // ensure that the camera never changes y. // this._camera.position.y = initialHeight; // } // // // UTILS // // _getTouch(event, element) { for (let i = 0; i < event.touches.length; i++) { const touch = event.touches[i]; if (touch.target === element) { return touch; } } } }