diff --git a/src/animation/ProceduralKeyframeAnimator.ts b/src/animation/ProceduralKeyframeAnimator.ts index dcda70e7..14d5e004 100644 --- a/src/animation/ProceduralKeyframeAnimator.ts +++ b/src/animation/ProceduralKeyframeAnimator.ts @@ -498,7 +498,7 @@ export type OnframeCallback = (target: T, percent: number) => void; export type AnimationPropGetter = (target: T, key: string) => InterpolatableType; export type AnimationPropSetter = (target: T, key: string, value: InterpolatableType) => void; -export default class ProceduralKeyframeAnimator implements Animator { +export default class ProceduralKeyframeAnimator implements Animator { timeline?: Timeline; targetName?: string; @@ -846,7 +846,7 @@ export default class ProceduralKeyframeAnimator implements Animator { * 添加动画每一帧的回调函数 * @param callback */ - during(cb: OnframeCallback) { + during(cb?: OnframeCallback) { if (cb) { this._onframeCbs.push(cb); } @@ -856,14 +856,14 @@ export default class ProceduralKeyframeAnimator implements Animator { * Add callback for animation end * @param cb */ - done(cb: DoneCallback) { + done(cb?: DoneCallback) { if (cb) { this._doneCbs.push(cb); } return this; } - aborted(cb: AbortCallback) { + aborted(cb?: AbortCallback) { if (cb) { this._abortedCbs.push(cb); } diff --git a/src/camera/Perspective.ts b/src/camera/Perspective.ts index 8f7b7ef5..1207c037 100644 --- a/src/camera/Perspective.ts +++ b/src/camera/Perspective.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import Camera, { CameraOpts } from '../Camera'; export interface PerspectiveCameraOpts extends CameraOpts { diff --git a/src/math/Matrix2.ts b/src/math/Matrix2.ts index 93837c86..0a1346a4 100644 --- a/src/math/Matrix2.ts +++ b/src/math/Matrix2.ts @@ -132,7 +132,7 @@ class Matrix2 { } toArray() { - return Array.prototype.slice.call(this.array); + return this.array.slice() as mat2.Mat2Array; } /** diff --git a/src/math/Matrix2d.ts b/src/math/Matrix2d.ts index 6dc7d392..2c1b6b2b 100644 --- a/src/math/Matrix2d.ts +++ b/src/math/Matrix2d.ts @@ -122,7 +122,7 @@ class Matrix2d { } toArray() { - return Array.prototype.slice.call(this.array); + return this.array.slice() as mat2d.Mat2dArray; } /** diff --git a/src/math/Matrix3.ts b/src/math/Matrix3.ts index c592b278..1571ff9e 100644 --- a/src/math/Matrix3.ts +++ b/src/math/Matrix3.ts @@ -179,7 +179,7 @@ class Matrix3 { } toArray() { - return Array.prototype.slice.call(this.array); + return this.array.slice() as mat3.Mat3Array; } /** diff --git a/src/math/Matrix4.ts b/src/math/Matrix4.ts index 423ad2a9..97d681c0 100644 --- a/src/math/Matrix4.ts +++ b/src/math/Matrix4.ts @@ -379,7 +379,7 @@ class Matrix4 { } toArray() { - return Array.prototype.slice.call(this.array); + return this.array.slice() as mat4.Mat4Array; } /** diff --git a/src/math/Quaternion.ts b/src/math/Quaternion.ts index 7fc82430..abc4180a 100644 --- a/src/math/Quaternion.ts +++ b/src/math/Quaternion.ts @@ -332,7 +332,7 @@ class Quaternion { } toArray() { - return Array.prototype.slice.call(this.array); + return this.array.slice() as quat.QuatArray; } /** diff --git a/src/math/Vector2.ts b/src/math/Vector2.ts index c4a1fdcb..0e96cd65 100644 --- a/src/math/Vector2.ts +++ b/src/math/Vector2.ts @@ -332,7 +332,7 @@ class Vector2 { } toArray() { - return Array.prototype.slice.call(this.array); + return this.array.slice() as vec2.Vec2Array; } // Supply methods that are not in place diff --git a/src/math/Vector3.ts b/src/math/Vector3.ts index 51f1551d..f17faf6d 100644 --- a/src/math/Vector3.ts +++ b/src/math/Vector3.ts @@ -402,7 +402,7 @@ class Vector3 { } toArray() { - return Array.prototype.slice.call(this.array); + return this.array.slice() as vec3.Vec3Array; } /** diff --git a/src/math/Vector4.ts b/src/math/Vector4.ts index 51819c5b..a98daa1a 100644 --- a/src/math/Vector4.ts +++ b/src/math/Vector4.ts @@ -328,7 +328,7 @@ class Vector4 { } toArray() { - return Array.prototype.slice.call(this.array); + return this.array as vec4.Vec4Array; } /** * @param out diff --git a/src/plugin/FreeControl.ts b/src/plugin/FreeControl.ts index cbb23030..90a08e0a 100644 --- a/src/plugin/FreeControl.ts +++ b/src/plugin/FreeControl.ts @@ -1,12 +1,55 @@ -// @ts-nocheck -import Base from '../core/Base'; import Vector3 from '../math/Vector3'; import vendor from '../core/vendor'; +import ClayNode from '../Node'; +import Timeline from '../Timeline'; +import Notifier from '../core/Notifier'; -const doc = typeof document === 'undefined' ? {} : document; +const addEvent = vendor.addEventListener; +const removeEvent = vendor.removeEventListener; +const doc = document; +interface FreeControlOpts { + /** + * Scene node to control, mostly it is a camera + */ + target?: ClayNode; + /** + * Target dom to bind with mouse events + */ + domElement: HTMLElement; + + /** + * Mouse move sensitivity + * @type {number} + */ + sensitivity: number; + + /** + * Target move speed + * @type {number} + */ + speed: number; + + /** + * Up axis + * @type {clay.Vector3} + */ + up: Vector3; + + /** + * If lock vertical movement + * @type {boolean} + */ + verticalMoveLock: boolean; + + /** + * @type {clay.Timeline} + */ + timeline?: Timeline; +} + +interface FreeControl extends FreeControlOpts {} /** - * @constructor clay.plugin.FreeControl * @example * const control = new clay.plugin.FreeControl({ * target: camera, @@ -18,60 +61,28 @@ const doc = typeof document === 'undefined' ? {} : document; * renderer.render(scene, camera); * }); */ -const FreeControl = Base.extend( - function () { - return /** @lends clay.plugin.FreeControl# */ { - /** - * Scene node to control, mostly it is a camera - * @type {clay.Node} - */ - target: null, +class FreeControl extends Notifier { + private _moveForward = false; + private _moveBackward = false; + private _moveLeft = false; + private _moveRight = false; - /** - * Target dom to bind with mouse events - * @type {HTMLElement} - */ - domElement: null, + private _offsetPitch = 0; + private _offsetRoll = 0; - /** - * Mouse move sensitivity - * @type {number} - */ - sensitivity: 1, + constructor(opts?: Partial) { + super(); + Object.assign( + this, + { + sensitivity: 1, + speed: 0.4, + up: new Vector3(0, 1, 0), + verticalMoveLock: false + }, + opts + ); - /** - * Target move speed - * @type {number} - */ - speed: 0.4, - - /** - * Up axis - * @type {clay.Vector3} - */ - up: new Vector3(0, 1, 0), - - /** - * If lock vertical movement - * @type {boolean} - */ - verticalMoveLock: false, - - /** - * @type {clay.Timeline} - */ - timeline: null, - - _moveForward: false, - _moveBackward: false, - _moveLeft: false, - _moveRight: false, - - _offsetPitch: 0, - _offsetRoll: 0 - }; - }, - function () { this._lockChange = this._lockChange.bind(this); this._keyDown = this._keyDown.bind(this); this._keyUp = this._keyUp.bind(this); @@ -80,184 +91,178 @@ const FreeControl = Base.extend( if (this.domElement) { this.init(); } - }, - /** @lends clay.plugin.FreeControl.prototype */ - { - /** - * init control - */ - init: function () { - // Use pointer lock - // http://www.html5rocks.com/en/tutorials/pointerlock/intro/ - const el = this.domElement; + } + /** + * init control + */ + init() { + // Use pointer lock + // http://www.html5rocks.com/en/tutorials/pointerlock/intro/ + const el = this.domElement; - //Must request pointer lock after click event, can't not do it directly - //Why ? ? - vendor.addEventListener(el, 'click', this._requestPointerLock); + //Must request pointer lock after click event, can't not do it directly + //Why ? ? + addEvent(el, 'click', this._requestPointerLock); - vendor.addEventListener(doc, 'pointerlockchange', this._lockChange); - vendor.addEventListener(doc, 'mozpointerlockchange', this._lockChange); - vendor.addEventListener(doc, 'webkitpointerlockchange', this._lockChange); + addEvent(doc, 'pointerlockchange', this._lockChange); + addEvent(doc, 'mozpointerlockchange', this._lockChange); + addEvent(doc, 'webkitpointerlockchange', this._lockChange); - vendor.addEventListener(doc, 'keydown', this._keyDown); - vendor.addEventListener(doc, 'keyup', this._keyUp); + addEvent(doc, 'keydown', this._keyDown); + addEvent(doc, 'keyup', this._keyUp); - if (this.timeline) { - this.timeline.on('frame', this._detectMovementChange, this); - } - }, - - /** - * Dispose control - */ - dispose: function () { - const el = this.domElement; - - el.exitPointerLock = el.exitPointerLock || el.mozExitPointerLock || el.webkitExitPointerLock; - - if (el.exitPointerLock) { - el.exitPointerLock(); - } - - vendor.removeEventListener(el, 'click', this._requestPointerLock); - - vendor.removeEventListener(doc, 'pointerlockchange', this._lockChange); - vendor.removeEventListener(doc, 'mozpointerlockchange', this._lockChange); - vendor.removeEventListener(doc, 'webkitpointerlockchange', this._lockChange); - - vendor.removeEventListener(doc, 'keydown', this._keyDown); - vendor.removeEventListener(doc, 'keyup', this._keyUp); - - if (this.timeline) { - this.timeline.off('frame', this._detectMovementChange); - } - }, - - _requestPointerLock: function () { - const el = this; - el.requestPointerLock = - el.requestPointerLock || el.mozRequestPointerLock || el.webkitRequestPointerLock; - - el.requestPointerLock(); - }, - - /** - * Control update. Should be invoked every frame - * @param {number} frameTime Frame time - */ - update: function (frameTime) { - const target = this.target; - - const position = this.target.position; - let xAxis = target.localTransform.x.normalize(); - const zAxis = target.localTransform.z.normalize(); - - if (this.verticalMoveLock) { - zAxis.y = 0; - zAxis.normalize(); - } - - const speed = (this.speed * frameTime) / 20; - - if (this._moveForward) { - // Opposite direction of z - position.scaleAndAdd(zAxis, -speed); - } - if (this._moveBackward) { - position.scaleAndAdd(zAxis, speed); - } - if (this._moveLeft) { - position.scaleAndAdd(xAxis, -speed / 2); - } - if (this._moveRight) { - position.scaleAndAdd(xAxis, speed / 2); - } - - target.rotateAround( - target.position, - this.up, - (-this._offsetPitch * frameTime * Math.PI) / 360 - ); - xAxis = target.localTransform.x; - target.rotateAround(target.position, xAxis, (-this._offsetRoll * frameTime * Math.PI) / 360); - - this._offsetRoll = this._offsetPitch = 0; - }, - - _lockChange: function () { - if ( - doc.pointerLockElement === this.domElement || - doc.mozPointerLockElement === this.domElement || - doc.webkitPointerLockElement === this.domElement - ) { - vendor.addEventListener(doc, 'mousemove', this._mouseMove, false); - } else { - vendor.removeEventListener(doc, 'mousemove', this._mouseMove); - } - }, - - _mouseMove: function (e) { - const dx = e.movementX || e.mozMovementX || e.webkitMovementX || 0; - const dy = e.movementY || e.mozMovementY || e.webkitMovementY || 0; - - this._offsetPitch += (dx * this.sensitivity) / 200; - this._offsetRoll += (dy * this.sensitivity) / 200; - - // Trigger change event to remind renderer do render - this.trigger('change'); - }, - - _detectMovementChange: function (frameTime) { - if (this._moveForward || this._moveBackward || this._moveLeft || this._moveRight) { - this.trigger('change'); - } - this.update(frameTime); - }, - - _keyDown: function (e) { - switch (e.keyCode) { - case 87: //w - case 38: //up arrow - this._moveForward = true; - break; - case 83: //s - case 40: //down arrow - this._moveBackward = true; - break; - case 65: //a - case 37: //left arrow - this._moveLeft = true; - break; - case 68: //d - case 39: //right arrow - this._moveRight = true; - break; - } - // Trigger change event to remind renderer do render - this.trigger('change'); - }, - - _keyUp: function (e) { - switch (e.keyCode) { - case 87: //w - case 38: //up arrow - this._moveForward = false; - break; - case 83: //s - case 40: //down arrow - this._moveBackward = false; - break; - case 65: //a - case 37: //left arrow - this._moveLeft = false; - break; - case 68: //d - case 39: //right arrow - this._moveRight = false; - break; - } + if (this.timeline) { + this.timeline.on('frame', this._detectMovementChange, this); } } -); + + /** + * Dispose control + */ + dispose() { + const el = this.domElement; + + doc.exitPointerLock = doc.exitPointerLock || (doc as any).mozExitPointerLock; + + if (doc.exitPointerLock) { + doc.exitPointerLock(); + } + + removeEvent(el, 'click', this._requestPointerLock); + + removeEvent(doc, 'pointerlockchange', this._lockChange); + removeEvent(doc, 'mozpointerlockchange', this._lockChange); + removeEvent(doc, 'webkitpointerlockchange', this._lockChange); + + removeEvent(doc, 'keydown', this._keyDown); + removeEvent(doc, 'keyup', this._keyUp); + + if (this.timeline) { + this.timeline.off('frame', this._detectMovementChange); + } + } + + _requestPointerLock() { + const el = this.domElement; + el.requestPointerLock = el.requestPointerLock || (el as any).mozRequestPointerLock; + + el.requestPointerLock(); + } + + /** + * Control update. Should be invoked every frame + * @param {number} frameTime Frame time + */ + update(frameTime: number) { + const target = this.target; + if (!target) { + return; + } + + const position = target.position; + let xAxis = target.localTransform.x.normalize(); + const zAxis = target.localTransform.z.normalize(); + + if (this.verticalMoveLock) { + zAxis.y = 0; + zAxis.normalize(); + } + + const speed = (this.speed * frameTime) / 20; + + if (this._moveForward) { + // Opposite direction of z + position.scaleAndAdd(zAxis, -speed); + } + if (this._moveBackward) { + position.scaleAndAdd(zAxis, speed); + } + if (this._moveLeft) { + position.scaleAndAdd(xAxis, -speed / 2); + } + if (this._moveRight) { + position.scaleAndAdd(xAxis, speed / 2); + } + + target.rotateAround(target.position, this.up, (-this._offsetPitch * frameTime * Math.PI) / 360); + xAxis = target.localTransform.x; + target.rotateAround(target.position, xAxis, (-this._offsetRoll * frameTime * Math.PI) / 360); + + this._offsetRoll = this._offsetPitch = 0; + } + + _lockChange() { + if ( + doc.pointerLockElement === this.domElement || + (doc as any).mozPointerLockElement === this.domElement + ) { + addEvent(doc, 'mousemove', this._mouseMove, false); + } else { + removeEvent(doc, 'mousemove', this._mouseMove); + } + } + + _mouseMove(e: MouseEvent) { + const dx = e.movementX || (e as any).mozMovementX || 0; + const dy = e.movementY || (e as any).mozMovementY || 0; + + this._offsetPitch += (dx * this.sensitivity) / 200; + this._offsetRoll += (dy * this.sensitivity) / 200; + + // Trigger change event to remind renderer do render + this.trigger('change'); + } + + _detectMovementChange(frameTime: number) { + if (this._moveForward || this._moveBackward || this._moveLeft || this._moveRight) { + this.trigger('change'); + } + this.update(frameTime); + } + + _keyDown(e: KeyboardEvent) { + switch (e.keyCode) { + case 87: //w + case 38: //up arrow + this._moveForward = true; + break; + case 83: //s + case 40: //down arrow + this._moveBackward = true; + break; + case 65: //a + case 37: //left arrow + this._moveLeft = true; + break; + case 68: //d + case 39: //right arrow + this._moveRight = true; + break; + } + // Trigger change event to remind renderer do render + this.trigger('change'); + } + + _keyUp(e: KeyboardEvent) { + switch (e.keyCode) { + case 87: //w + case 38: //up arrow + this._moveForward = false; + break; + case 83: //s + case 40: //down arrow + this._moveBackward = false; + break; + case 65: //a + case 37: //left arrow + this._moveLeft = false; + break; + case 68: //d + case 39: //right arrow + this._moveRight = false; + break; + } + } +} export default FreeControl; diff --git a/src/plugin/GamepadControl.ts b/src/plugin/GamepadControl.ts index a98c9468..ffd5b869 100644 --- a/src/plugin/GamepadControl.ts +++ b/src/plugin/GamepadControl.ts @@ -1,93 +1,92 @@ -// @ts-nocheck -import Base from '../core/Base'; import Vector3 from '../math/Vector3'; import vendor from '../core/vendor'; +import Notifier from '../core/Notifier'; +import type ClayNode from '../Node'; +import type Timeline from '../Timeline'; +const addEvent = vendor.addEventListener; +const removeEvent = vendor.removeEventListener; + +interface GamepadControlOpts { + /* + * Scene node to control, mostly it is a camera. + * + */ + target?: ClayNode; + + /** + * Move speed. + */ + moveSpeed: number; + + /** + * Look around speed. + */ + lookAroundSpeed: number; + + /** + * Up axis. + */ + up: Vector3; + + /** + * Timeline. + */ + timeline?: Timeline; + + /** + * Function to be called when a standard gamepad is ready to use. + */ + onStandardGamepadReady?: (gamepad) => void; + + /** + * Function to be called when a gamepad is disconnected. + */ + onGamepadDisconnected?: (gamepad) => void; +} + +interface GamepadControl extends GamepadControlOpts {} /** * Gamepad Control plugin. * - * @constructor clay.plugin.GamepadControl - * * @example - * init: function(app) { + * init(app) { * this._gamepadControl = new clay.plugin.GamepadControl({ * target: camera, * onStandardGamepadReady: customCallback * }); * }, * - * loop: function(app) { + * loop(app) { * this._gamepadControl.update(app.frameTime); * } */ -const GamepadControl = Base.extend( - function () { - return /** @lends clay.plugin.GamepadControl# */ { - /** - * Scene node to control, mostly it is a camera. - * - * @type {clay.Node} - */ - target: null, - /** - * Move speed. - * - * @type {number} - */ - moveSpeed: 0.1, +class GamepadControl extends Notifier { + private _moveForward = false; + private _moveBackward = false; + private _moveLeft = false; + private _moveRight = false; - /** - * Look around speed. - * - * @type {number} - */ - lookAroundSpeed: 0.1, + private _offsetPitch = 0; + private _offsetRoll = 0; - /** - * Up axis. - * - * @type {clay.Vector3} - */ - up: new Vector3(0, 1, 0), + private _standardGamepadIndex = 0; + private _standardGamepadAvailable = false; + private _gamepadAxisThreshold = 0.3; - /** - * Timeline. - * - * @type {clay.Timeline} - */ - timeline: null, + constructor(opts?: Partial) { + super(); + Object.assign( + this, + { + moveSpeed: 0.1, + lookAroundSpeed: 0.1, + up: Vector3.UP + }, + opts + ); - /** - * Function to be called when a standard gamepad is ready to use. - * - * @type {function} - */ - onStandardGamepadReady: function (gamepad) {}, - - /** - * Function to be called when a gamepad is disconnected. - * - * @type {function} - */ - onGamepadDisconnected: function (gamepad) {}, - - // Private properties: - - _moveForward: false, - _moveBackward: false, - _moveLeft: false, - _moveRight: false, - - _offsetPitch: 0, - _offsetRoll: 0, - - _connectedGamepadIndex: 0, - _standardGamepadAvailable: false, - _gamepadAxisThreshold: 0.3 - }; - }, - function () { this._checkGamepadCompatibility = this._checkGamepadCompatibility.bind(this); this._disconnectGamepad = this._disconnectGamepad.bind(this); this._getStandardGamepad = this._getStandardGamepad.bind(this); @@ -100,203 +99,207 @@ const GamepadControl = Base.extend( if (typeof navigator.getGamepads === 'function') { this.init(); } - }, - /** @lends clay.plugin.GamepadControl.prototype */ - { + } + + /** + * Init. control. + */ + init() { /** - * Init. control. - */ - init: function () { - /** - * When user begins to interact with connected gamepad: - * - * @see https://w3c.github.io/gamepad/#dom-gamepadevent - */ - vendor.addEventListener(window, 'gamepadconnected', this._checkGamepadCompatibility); - - if (this.timeline) { - this.timeline.on('frame', this.update); - } - - vendor.addEventListener(window, 'gamepaddisconnected', this._disconnectGamepad); - }, - - /** - * Dispose control. - */ - dispose: function () { - vendor.removeEventListener(window, 'gamepadconnected', this._checkGamepadCompatibility); - - if (this.timeline) { - this.timeline.off('frame', this.update); - } - - vendor.removeEventListener(window, 'gamepaddisconnected', this._disconnectGamepad); - }, - - /** - * Control's update. Should be invoked every frame. + * When user begins to interact with connected gamepad: * - * @param {number} frameTime Frame time. + * @see https://w3c.github.io/gamepad/#dom-gamepadevent */ - update: function (frameTime) { - if (!this._standardGamepadAvailable) { - return; - } + addEvent(window, 'gamepadconnected', this._checkGamepadCompatibility); - this._scanPressedGamepadButtons(); - this._scanInclinedGamepadAxes(); + if (this.timeline) { + this.timeline.on('frame', this.update); + } - // Update target depending on user input. + addEvent(window, 'gamepaddisconnected', this._disconnectGamepad); + } - const target = this.target; + /** + * Dispose control. + */ + dispose() { + removeEvent(window, 'gamepadconnected', this._checkGamepadCompatibility); - const position = this.target.position; - let xAxis = target.localTransform.x.normalize(); - const zAxis = target.localTransform.z.normalize(); + if (this.timeline) { + this.timeline.off('frame', this.update); + } - const moveSpeed = (this.moveSpeed * frameTime) / 20; + removeEvent(window, 'gamepaddisconnected', this._disconnectGamepad); + } - if (this._moveForward) { - // Opposite direction of z. - position.scaleAndAdd(zAxis, -moveSpeed); - } - if (this._moveBackward) { - position.scaleAndAdd(zAxis, moveSpeed); - } - if (this._moveLeft) { - position.scaleAndAdd(xAxis, -moveSpeed); - } - if (this._moveRight) { - position.scaleAndAdd(xAxis, moveSpeed); - } + /** + * Control's update. Should be invoked every frame. + * + * @param {number} frameTime Frame time. + */ + update(frameTime: number) { + if (!this._standardGamepadAvailable) { + return; + } - target.rotateAround( - target.position, - this.up, - (-this._offsetPitch * frameTime * Math.PI) / 360 - ); - xAxis = target.localTransform.x; - target.rotateAround(target.position, xAxis, (-this._offsetRoll * frameTime * Math.PI) / 360); + this._scanPressedGamepadButtons(); + this._scanInclinedGamepadAxes(); - /* - * If necessary: trigger `update` event. - * XXX This can economize rendering OPs. - */ - if ( - this._moveForward === true || - this._moveBackward === true || - this._moveLeft === true || - this._moveRight === true || - this._offsetPitch !== 0 || - this._offsetRoll !== 0 - ) { - this.trigger('update'); - } + // Update target depending on user input. - // Reset values to avoid lost of control. + const target = this.target; + if (!target) { + return; + } - this._moveForward = this._moveBackward = this._moveLeft = this._moveRight = false; - this._offsetPitch = this._offsetRoll = 0; - }, + const position = target.position; + let xAxis = target.localTransform.x.normalize(); + const zAxis = target.localTransform.z.normalize(); - // Private methods: + const moveSpeed = (this.moveSpeed * frameTime) / 20; - _checkGamepadCompatibility: function (event) { - /** - * If connected gamepad has a **standard** layout: - * - * @see https://w3c.github.io/gamepad/#remapping about standard. - */ - if (event.gamepad.mapping === 'standard') { - this._standardGamepadIndex = event.gamepad.index; - this._standardGamepadAvailable = true; + if (this._moveForward) { + // Opposite direction of z. + position.scaleAndAdd(zAxis, -moveSpeed); + } + if (this._moveBackward) { + position.scaleAndAdd(zAxis, moveSpeed); + } + if (this._moveLeft) { + position.scaleAndAdd(xAxis, -moveSpeed); + } + if (this._moveRight) { + position.scaleAndAdd(xAxis, moveSpeed); + } - this.onStandardGamepadReady(event.gamepad); - } - }, + target.rotateAround(target.position, this.up, (-this._offsetPitch * frameTime * Math.PI) / 360); + xAxis = target.localTransform.x; + target.rotateAround(target.position, xAxis, (-this._offsetRoll * frameTime * Math.PI) / 360); - _disconnectGamepad: function (event) { - this._standardGamepadAvailable = false; + /* + * If necessary: trigger `update` event. + * XXX This can economize rendering OPs. + */ + if ( + this._moveForward === true || + this._moveBackward === true || + this._moveLeft === true || + this._moveRight === true || + this._offsetPitch !== 0 || + this._offsetRoll !== 0 + ) { + this.trigger('update'); + } - this.onGamepadDisconnected(event.gamepad); - }, + // Reset values to avoid lost of control. - _getStandardGamepad: function () { - return navigator.getGamepads()[this._standardGamepadIndex]; - }, + this._moveForward = this._moveBackward = this._moveLeft = this._moveRight = false; + this._offsetPitch = this._offsetRoll = 0; + } - _scanPressedGamepadButtons: function () { - const gamepadButtons = this._getStandardGamepad().buttons; + // Private methods: - // For each gamepad button: - for (let gamepadButtonId = 0; gamepadButtonId < gamepadButtons.length; gamepadButtonId++) { - // Get user input. - const gamepadButton = gamepadButtons[gamepadButtonId]; + _checkGamepadCompatibility(event: GamepadEvent) { + /** + * If connected gamepad has a **standard** layout: + * + * @see https://w3c.github.io/gamepad/#remapping about standard. + */ + if (event.gamepad.mapping === 'standard') { + this._standardGamepadIndex = event.gamepad.index; + this._standardGamepadAvailable = true; - if (gamepadButton.pressed) { - switch (gamepadButtonId) { - // D-pad Up - case 12: - this._moveForward = true; - break; + this.onStandardGamepadReady && this.onStandardGamepadReady(event.gamepad); + } + } - // D-pad Down - case 13: - this._moveBackward = true; - break; + _disconnectGamepad(event: GamepadEvent) { + this._standardGamepadAvailable = false; - // D-pad Left - case 14: - this._moveLeft = true; - break; + this.onGamepadDisconnected && this.onGamepadDisconnected(event.gamepad); + } - // D-pad Right - case 15: - this._moveRight = true; - break; - } - } - } - }, + _getStandardGamepad() { + return navigator.getGamepads()[this._standardGamepadIndex]; + } - _scanInclinedGamepadAxes: function () { - const gamepadAxes = this._getStandardGamepad().axes; + _scanPressedGamepadButtons() { + const gamepad = this._getStandardGamepad(); + const gamepadButtons = gamepad && gamepad.buttons; + if (!gamepadButtons) { + return; + } - // For each gamepad axis: - for (let gamepadAxisId = 0; gamepadAxisId < gamepadAxes.length; gamepadAxisId++) { - // Get user input. - const gamepadAxis = gamepadAxes[gamepadAxisId]; + // For each gamepad button: + for (let gamepadButtonId = 0; gamepadButtonId < gamepadButtons.length; gamepadButtonId++) { + // Get user input. + const gamepadButton = gamepadButtons[gamepadButtonId]; - // XXX We use a threshold because axes are never neutral. - if (Math.abs(gamepadAxis) > this._gamepadAxisThreshold) { - switch (gamepadAxisId) { - // Left stick X± - case 0: - this._moveLeft = gamepadAxis < 0; - this._moveRight = gamepadAxis > 0; - break; + if (gamepadButton.pressed) { + switch (gamepadButtonId) { + // D-pad Up + case 12: + this._moveForward = true; + break; - // Left stick Y± - case 1: - this._moveForward = gamepadAxis < 0; - this._moveBackward = gamepadAxis > 0; - break; + // D-pad Down + case 13: + this._moveBackward = true; + break; - // Right stick X± - case 2: - this._offsetPitch += gamepadAxis * this.lookAroundSpeed; - break; + // D-pad Left + case 14: + this._moveLeft = true; + break; - // Right stick Y± - case 3: - this._offsetRoll += gamepadAxis * this.lookAroundSpeed; - break; - } + // D-pad Right + case 15: + this._moveRight = true; + break; } } } } -); + _scanInclinedGamepadAxes() { + const gamepad = this._getStandardGamepad(); + const gamepadAxes = gamepad && gamepad.axes; + if (!gamepadAxes) { + return; + } + + // For each gamepad axis: + for (let gamepadAxisId = 0; gamepadAxisId < gamepadAxes.length; gamepadAxisId++) { + // Get user input. + const gamepadAxis = gamepadAxes[gamepadAxisId]; + + // XXX We use a threshold because axes are never neutral. + if (Math.abs(gamepadAxis) > this._gamepadAxisThreshold) { + switch (gamepadAxisId) { + // Left stick X± + case 0: + this._moveLeft = gamepadAxis < 0; + this._moveRight = gamepadAxis > 0; + break; + + // Left stick Y± + case 1: + this._moveForward = gamepadAxis < 0; + this._moveBackward = gamepadAxis > 0; + break; + + // Right stick X± + case 2: + this._offsetPitch += gamepadAxis * this.lookAroundSpeed; + break; + + // Right stick Y± + case 3: + this._offsetRoll += gamepadAxis * this.lookAroundSpeed; + break; + } + } + } + } +} export default GamepadControl; diff --git a/src/plugin/GestureMgr.ts b/src/plugin/GestureMgr.ts index ae174ff9..a1de59ae 100644 --- a/src/plugin/GestureMgr.ts +++ b/src/plugin/GestureMgr.ts @@ -1,31 +1,42 @@ -// @ts-nocheck import * as util from '../core/util'; -const GestureMgr = function () { - this._track = []; -}; +type Target = any; +interface TrackItem { + points: number[][]; + touches: Touch[]; + target: Target; + event: TouchEvent; +} -GestureMgr.prototype = { - constructor: GestureMgr, +export interface PinchEvent extends TouchEvent { + pinchScale: number; + pinchX: number; + pinchY: number; +} - recognize: function (event, target, root) { +export class GestureMgr { + private _track: TrackItem[] = []; + + constructor() {} + + recognize(event: TouchEvent, target: Target, root: HTMLElement) { this._doTrack(event, target, root); return this._recognize(event); - }, + } - clear: function () { + clear() { this._track.length = 0; return this; - }, + } - _doTrack: function (event, target, root) { - const touches = event.targetTouches; + _doTrack(event: TouchEvent, target: Target, root: HTMLElement) { + const touches = event.touches; if (!touches) { return; } - const trackItem = { + const trackItem: TrackItem = { points: [], touches: [], target: target, @@ -39,9 +50,9 @@ GestureMgr.prototype = { } this._track.push(trackItem); - }, + } - _recognize: function (event) { + _recognize(event: TouchEvent) { for (const eventName in recognizers) { if (util.hasOwn(recognizers, eventName)) { const gestureInfo = recognizers[eventName](this._track, event); @@ -51,47 +62,59 @@ GestureMgr.prototype = { } } } -}; +} -function dist(pointPair) { +function dist(pointPair: number[][]): number { const dx = pointPair[1][0] - pointPair[0][0]; const dy = pointPair[1][1] - pointPair[0][1]; return Math.sqrt(dx * dx + dy * dy); } -function center(pointPair) { +function center(pointPair: number[][]): number[] { return [(pointPair[0][0] + pointPair[1][0]) / 2, (pointPair[0][1] + pointPair[1][1]) / 2]; } -const recognizers = { - pinch: function (track, event) { - const trackLen = track.length; +type Recognizer = ( + tracks: TrackItem[], + event: TouchEvent +) => + | { + type: 'pinch'; + target: Target; + event: PinchEvent; + } + | undefined; + +const recognizers: Record = { + pinch(tracks: TrackItem[], event: TouchEvent) { + const trackLen = tracks.length; if (!trackLen) { return; } - const pinchEnd = (track[trackLen - 1] || {}).points; - const pinchPre = (track[trackLen - 2] || {}).points || pinchEnd; + const pinchEnd = (tracks[trackLen - 1] || {}).points; + const pinchPre = (tracks[trackLen - 2] || {}).points || pinchEnd; if (pinchPre && pinchPre.length > 1 && pinchEnd && pinchEnd.length > 1) { let pinchScale = dist(pinchEnd) / dist(pinchPre); !isFinite(pinchScale) && (pinchScale = 1); - event.pinchScale = pinchScale; + (event as PinchEvent).pinchScale = pinchScale; const pinchCenter = center(pinchEnd); - event.pinchX = pinchCenter[0]; - event.pinchY = pinchCenter[1]; + (event as PinchEvent).pinchX = pinchCenter[0]; + (event as PinchEvent).pinchY = pinchCenter[1]; return { type: 'pinch', - target: track[0].target, - event: event + target: tracks[0].target, + event: event as PinchEvent }; } } -}; + // Only pinch currently. +}; export default GestureMgr; diff --git a/src/plugin/OrbitControl.ts b/src/plugin/OrbitControl.ts index b5e095ec..233f9372 100644 --- a/src/plugin/OrbitControl.ts +++ b/src/plugin/OrbitControl.ts @@ -1,10 +1,18 @@ -// @ts-nocheck -import Base from '../core/Base'; import Vector2 from '../math/Vector2'; import Vector3 from '../math/Vector3'; -import GestureMgr from './GestureMgr'; +import GestureMgr, { PinchEvent } from './GestureMgr'; import vendor from '../core/vendor'; import PerspectiveCamera from '../camera/Perspective'; +import Notifier from '../core/Notifier'; +import Timeline from '../Timeline'; +import ClayNode from '../Node'; +import type { vec3 } from '../glmatrix'; +import type { AnimationEasing } from '../animation/easing'; +import type ProceduralKeyframeAnimator from '../animation/ProceduralKeyframeAnimator'; +import OrthographicCamera from '../camera/Orthographic'; + +const addEvent = vendor.addEventListener; +const removeEvent = vendor.removeEventListener; const MOUSE_BUTTON_KEY_MAP = { left: 0, @@ -12,168 +20,187 @@ const MOUSE_BUTTON_KEY_MAP = { right: 2 }; -function convertToArray(val) { +function convertToArray(val: number | number[]): number[] { if (!Array.isArray(val)) { val = [val, val]; } return val; } -/** - * @constructor - * @alias clay.plugin.OrbitControl - * @extends clay.core.Base - */ -const OrbitControl = Base.extend( - function () { - return /** @lends clay.plugin.OrbitControl# */ { - /** - * @type {clay.Timeline} - */ - timeline: null, +type AnimatableControlOpts = Pick< + OrbitControlOpts, + 'distance' | 'orthographicSize' | 'alpha' | 'beta' | 'center' +>; - /** - * @type {HTMLElement} - */ - domElement: null, +interface OrbitControlOpts { + timeline?: Timeline; + domElement: HTMLElement; - /** - * @type {clay.Node} - */ - target: null, - /** - * @type {clay.Vector3} - */ - _center: new Vector3(), + /** + * States of orbit + */ + distance: number; + orthographicSize: number; + alpha: number; + beta: number; + center: vec3.Vec3Array; - /** - * Minimum distance to the center - * @type {number} - * @default 0.5 - */ - minDistance: 0.1, + /** + * Minimum distance to the center + * @type {number} + * @default 0.5 + */ + minDistance: number; - /** - * Maximum distance to the center - * @type {number} - * @default 2 - */ - maxDistance: 1000, + /** + * Maximum distance to the center + * @type {number} + * @default 2 + */ + maxDistance: number; - /** - * Only available when camera is orthographic - */ - maxOrthographicSize: 300, + /** + * Only available when camera is orthographic + */ + maxOrthographicSize: number; - /** - * Only available when camera is orthographic - */ - minOrthographicSize: 30, + /** + * Only available when camera is orthographic + */ + minOrthographicSize: number; - /** - * Aspect of orthographic camera - * Only available when camera is orthographic - */ - orthographicAspect: 1, + /** + * Aspect of orthographic camera + * Only available when camera is orthographic + */ + orthographicAspect: number; - /** - * Minimum alpha rotation - */ - minAlpha: -90, + /** + * Minimum alpha rotation + */ + minAlpha: number; - /** - * Maximum alpha rotation - */ - maxAlpha: 90, + /** + * Maximum alpha rotation + */ + maxAlpha: number; - /** - * Minimum beta rotation - */ - minBeta: -Infinity, - /** - * Maximum beta rotation - */ - maxBeta: Infinity, + /** + * Minimum beta rotation + */ + minBeta: number; + /** + * Maximum beta rotation + */ + maxBeta: number; - /** - * Start auto rotating after still for the given time - */ - autoRotateAfterStill: 0, + /** + * Start auto rotating after still for the given time + */ + autoRotateAfterStill: number; - /** - * Direction of autoRotate. cw or ccw when looking top down. - */ - autoRotateDirection: 'cw', + /** + * Direction of autoRotate. cw or ccw when looking top down. + */ + autoRotateDirection: 'cw' | 'ccw'; - /** - * Degree per second - */ - autoRotateSpeed: 60, + /** + * Degree per second + */ + autoRotateSpeed: number; - panMouseButton: 'middle', - rotateMouseButton: 'left', + panMouseButton: 'middle' | 'left' | 'right'; + rotateMouseButton: 'left' | 'middle' | 'right'; - /** - * Pan or rotate - * @type {String} - */ - _mode: 'rotate', + damping: number; - /** - * @param {number} - */ - damping: 0.8, + rotateSensitivity: number; - /** - * @param {number} - */ - rotateSensitivity: 1, + zoomSensitivity: number; - /** - * @param {number} - */ - zoomSensitivity: 1, + /** + * Invert zoom direction? + */ + invertZoomDirection: false; - /** - * Invert zoom direction? - * - * @type {boolean} - */ - invertZoomDirection: false, + panSensitivity: number; - /** - * @param {number} - */ - panSensitivity: 1, + autoRotate: boolean; + target: ClayNode; +} - _needsUpdate: false, +interface OrbitControl + extends Omit< + OrbitControlOpts, + 'autoRotate' | 'target' | 'distance' | 'orthographicSize' | 'alpha' | 'beta' | 'center' + > {} - _rotating: false, +class OrbitControl extends Notifier { + private _autoRotate: boolean = false; + private _target?: ClayNode; + private _center = new Vector3(); - // Rotation around yAxis - _phi: 0, - // Rotation around xAxis - _theta: 0, + private _orthoSize?: number; - _mouseX: 0, - _mouseY: 0, + private _mode?: 'rotate' | 'pan' = 'rotate'; - _rotateVelocity: new Vector2(), + private _needsUpdate = false; - _panVelocity: new Vector2(), + private _rotating = false; - _distance: 20, + // Rotation around yAxis + private _phi = 0; + // Rotation around xAxis + private _theta = 0; - _zoomSpeed: 0, + private _mouseX = 0; + private _mouseY = 0; - _stillTimeout: 0, + private _rotateVelocity = new Vector2(); - _animators: [], + private _panVelocity = new Vector2(); + + private _distance = 20; + + private _zoomSpeed = 0; + + private _stillTimeout = 0; + + private _animators: ProceduralKeyframeAnimator[] = []; + + private _gestureMgr = new GestureMgr(); + + constructor(opts?: Partial) { + super(); + + this.setOption( + Object.assign( + { + autoRotate: false, + autoRotateAfterStill: 0, + autoRotateDirection: 'cw', + autoRotateSpeed: 60, + damping: 0.8, + minDistance: 0.1, + maxDistance: 1000, + maxOrthographicSize: 300, + minOrthographicSize: 30, + orthographicAspect: 1, + minAlpha: -90, + maxAlpha: 90, + minBeta: -Infinity, + maxBeta: Infinity, + panMouseButton: 'middle', + rotateMouseButton: 'left', + invertZoomDirection: false, + rotateSensitivity: 1, + zoomSensitivity: 1, + panSensitivity: 1 + }, + opts + ) + ); - _gestureMgr: new GestureMgr() - }; - }, - function () { // Each OrbitControl has it's own handler this._mouseDownHandler = this._mouseDownHandler.bind(this); this._mouseWheelHandler = this._mouseWheelHandler.bind(this); @@ -182,146 +209,171 @@ const OrbitControl = Base.extend( this._pinchHandler = this._pinchHandler.bind(this); this.init(); - }, - /** @lends clay.plugin.OrbitControl# */ { - /** - * Initialize. - * Mouse event binding - */ - init: function () { - const dom = this.domElement; + } - vendor.addEventListener(dom, 'touchstart', this._mouseDownHandler); + /** + * If auto rotate the target + */ + get autoRotate() { + return this._autoRotate; + } - vendor.addEventListener(dom, 'mousedown', this._mouseDownHandler); - vendor.addEventListener(dom, 'wheel', this._mouseWheelHandler); + set autoRotate(val: boolean) { + this._autoRotate = val; + this._rotating = val; + } - if (this.timeline) { - this.timeline.on('frame', this.update, this); - } - if (this.target) { - this.decomposeTransform(); - } - }, + get target(): ClayNode | undefined { + return this._target; + } - /** - * Dispose. - * Mouse event unbinding - */ - dispose: function () { - const dom = this.domElement; + set target(val: ClayNode | undefined) { + if (val && val.target) { + this.setCenter(val.target.toArray()); + } + this._target = val; + this.decomposeTransform(); + } - vendor.removeEventListener(dom, 'touchstart', this._mouseDownHandler); - vendor.removeEventListener(dom, 'touchmove', this._mouseMoveHandler); - vendor.removeEventListener(dom, 'touchend', this._mouseUpHandler); + /** + * Initialize. + * Mouse event binding + */ + init() { + const dom = this.domElement; - vendor.removeEventListener(dom, 'mousedown', this._mouseDownHandler); - vendor.removeEventListener(dom, 'mousemove', this._mouseMoveHandler); - vendor.removeEventListener(dom, 'mouseup', this._mouseUpHandler); - vendor.removeEventListener(dom, 'wheel', this._mouseWheelHandler); - vendor.removeEventListener(dom, 'mouseout', this._mouseUpHandler); + addEvent(dom, 'touchstart', this._mouseDownHandler); - if (this.timeline) { - this.timeline.off('frame', this.update); - } - this.stopAllAnimation(); - }, + addEvent(dom, 'mousedown', this._mouseDownHandler); + addEvent(dom, 'wheel', this._mouseWheelHandler); - /** - * Get distance - * @return {number} - */ - getDistance: function () { - return this._distance; - }, + if (this.timeline) { + this.timeline.on('frame', this.update, this); + } + if (this.target) { + this.decomposeTransform(); + } + } - /** - * Set distance - * @param {number} distance - */ - setDistance: function (distance) { - this._distance = distance; - this._needsUpdate = true; - }, + /** + * Dispose. + * Mouse event unbinding + */ + dispose() { + const dom = this.domElement; - /** - * Get size of orthographic viewing volume - * @return {number} - */ - getOrthographicSize: function () { - return this._orthoSize; - }, + removeEvent(dom, 'touchstart', this._mouseDownHandler); + removeEvent(dom, 'touchmove', this._mouseMoveHandler); + removeEvent(dom, 'touchend', this._mouseUpHandler); - /** - * Set size of orthographic viewing volume - * @param {number} size - */ - setOrthographicSize: function (size) { - this._orthoSize = size; - this._needsUpdate = true; - }, + removeEvent(dom, 'mousedown', this._mouseDownHandler); + removeEvent(dom, 'mousemove', this._mouseMoveHandler); + removeEvent(dom, 'mouseup', this._mouseUpHandler); + removeEvent(dom, 'wheel', this._mouseWheelHandler); + removeEvent(dom, 'mouseout', this._mouseUpHandler); - /** - * Get alpha rotation - * Alpha angle for top-down rotation. Positive to rotate to top. - * - * Which means camera rotation around x axis. - */ - getAlpha: function () { - return (this._theta / Math.PI) * 180; - }, + if (this.timeline) { + this.timeline.off('frame', this.update); + } + this.stopAllAnimation(); + } - /** - * Get beta rotation - * Beta angle for left-right rotation. Positive to rotate to right. - * - * Which means camera rotation around y axis. - */ - getBeta: function () { - return (-this._phi / Math.PI) * 180; - }, + /** + * Get distance + * @return {number} + */ + getDistance() { + return this._distance; + } - /** - * Get control center - * @return {Array.} - */ - getCenter: function () { - return this._center.toArray(); - }, + /** + * Set distance + * @param {number} distance + */ + setDistance(distance: number) { + this._distance = distance; + this._needsUpdate = true; + } - /** - * Set alpha rotation angle - * @param {number} alpha - */ - setAlpha: function (alpha) { - alpha = Math.max(Math.min(this.maxAlpha, alpha), this.minAlpha); + /** + * Get size of orthographic viewing volume + * @return {number} + */ + getOrthographicSize() { + return this._orthoSize; + } - this._theta = (alpha / 180) * Math.PI; - this._needsUpdate = true; - }, + /** + * Set size of orthographic viewing volume + * @param {number} size + */ + setOrthographicSize(size: number) { + this._orthoSize = size; + this._needsUpdate = true; + } - /** - * Set beta rotation angle - * @param {number} beta - */ - setBeta: function (beta) { - beta = Math.max(Math.min(this.maxBeta, beta), this.minBeta); + /** + * Get alpha rotation + * Alpha angle for top-down rotation. Positive to rotate to top. + * + * Which means camera rotation around x axis. + */ + getAlpha() { + return (this._theta / Math.PI) * 180; + } - this._phi = (-beta / 180) * Math.PI; - this._needsUpdate = true; - }, + /** + * Get beta rotation + * Beta angle for left-right rotation. Positive to rotate to right. + * + * Which means camera rotation around y axis. + */ + getBeta() { + return (-this._phi / Math.PI) * 180; + } - /** - * Set control center - * @param {Array.} center - */ - setCenter: function (centerArr) { - this._center.setArray(centerArr); - }, + /** + * Get control center + * @return {Array.} + */ + getCenter() { + return this._center.toArray(); + } - setOption: function (opts) { - opts = opts || {}; + /** + * Set alpha rotation angle + * @param {number} alpha + */ + setAlpha(alpha: number) { + alpha = Math.max(Math.min(this.maxAlpha, alpha), this.minAlpha); + this._theta = (alpha / 180) * Math.PI; + this._needsUpdate = true; + } + + /** + * Set beta rotation angle + * @param {number} beta + */ + setBeta(beta: number) { + beta = Math.max(Math.min(this.maxBeta, beta), this.minBeta); + + this._phi = (-beta / 180) * Math.PI; + this._needsUpdate = true; + } + + /** + * Set control center + * @param {Array.} center + */ + setCenter(centerArr: vec3.Vec3Array) { + this._center.setArray(centerArr); + } + + setOption(opts?: Partial) { + opts = opts || {}; + + ( [ 'autoRotate', 'autoRotateAfterStill', @@ -337,496 +389,473 @@ const OrbitControl = Base.extend( 'maxAlpha', 'minBeta', 'maxBeta', + 'panMouseButton', + 'rotateMouseButton', + 'invertZoomDirection', 'rotateSensitivity', 'zoomSensitivity', 'panSensitivity' - ].forEach(function (key) { - if (opts[key] != null) { - this[key] = opts[key]; - } - }, this); - - if (opts.distance != null) { - this.setDistance(opts.distance); - } - if (opts.orthographicSize != null) { - this.setOrthographicSize(opts.orthographicSize); + ] as const + ).forEach((key) => { + if (opts![key] != null) { + (this as any)[key] = opts![key]; } + }); - if (opts.alpha != null) { - this.setAlpha(opts.alpha); - } - if (opts.beta != null) { - this.setBeta(opts.beta); - } + if (opts.distance != null) { + this.setDistance(opts.distance); + } + if (opts.orthographicSize != null) { + this.setOrthographicSize(opts.orthographicSize); + } - if (opts.center) { - this.setCenter(opts.center); - } + if (opts.alpha != null) { + this.setAlpha(opts.alpha); + } + if (opts.beta != null) { + this.setBeta(opts.beta); + } - if (this.target) { - this._updateTransform(); - this.target.update(); - } - }, - - /** - * @param {Object} opts - * @param {number} opts.distance - * @param {number} opts.orthographicSize - * @param {number} opts.alpha - * @param {number} opts.beta - * @param {Array.} opts.center - * @param {number} [opts.duration=1000] - * @param {number} [opts.easing='linear'] - * @param {number} [opts.done] - */ - animateTo: function (opts) { - const self = this; - - const obj = {}; - const target = {}; - const timeline = this.timeline; - if (!timeline) { - return; - } - if (opts.distance != null) { - obj.distance = this.getDistance(); - target.distance = opts.distance; - } - if (opts.orthographicSize != null) { - obj.orthographicSize = this.getOrthographicSize(); - target.orthographicSize = opts.orthographicSize; - } - if (opts.alpha != null) { - obj.alpha = this.getAlpha(); - target.alpha = opts.alpha; - } - if (opts.beta != null) { - obj.beta = this.getBeta(); - target.beta = opts.beta; - } - if (opts.center != null) { - obj.center = this.getCenter(); - target.center = opts.center; - } - - return this._addAnimator( - timeline - .animate(obj) - .when(opts.duration || 1000, target) - .during(function () { - if (obj.alpha != null) { - self.setAlpha(obj.alpha); - } - if (obj.beta != null) { - self.setBeta(obj.beta); - } - if (obj.distance != null) { - self.setDistance(obj.distance); - } - if (obj.orthographicSize != null) { - self.setOrthographicSize(obj.orthographicSize); - } - if (obj.center != null) { - self.setCenter(obj.center); - } - self._needsUpdate = true; - }) - .done(opts.done) - ).start(opts.easing || 'linear'); - }, - - /** - * Stop all animations - */ - stopAllAnimation: function () { - for (let i = 0; i < this._animators.length; i++) { - this._animators[i].stop(); - } - this._animators.length = 0; - }, - - _isAnimating: function () { - return this._animators.length > 0; - }, - /** - * Call update each frame - * @param {number} deltaTime Frame time - */ - update: function (deltaTime) { - deltaTime = deltaTime || 16; - - if (this._rotating) { - const radian = - (((this.autoRotateDirection === 'cw' ? 1 : -1) * this.autoRotateSpeed) / 180) * Math.PI; - this._phi -= (radian * deltaTime) / 1000; - this._needsUpdate = true; - } else if (this._rotateVelocity.len() > 0) { - this._needsUpdate = true; - } - - if (Math.abs(this._zoomSpeed) > 0.01 || this._panVelocity.len() > 0) { - this._needsUpdate = true; - } - - if (!this._needsUpdate) { - return; - } - - // Fixed deltaTime - this._updateDistanceOrSize(Math.min(deltaTime, 50)); - this._updatePan(Math.min(deltaTime, 50)); - - this._updateRotate(Math.min(deltaTime, 50)); - - this._updateTransform(); + if (opts.center) { + this.setCenter(opts.center); + } + this._updateTransform(); + if (this.target) { this.target.update(); + } + } - this.trigger('update'); - - this._needsUpdate = false; - }, - - _updateRotate: function (deltaTime) { - const velocity = this._rotateVelocity; - this._phi = (velocity.y * deltaTime) / 20 + this._phi; - this._theta = (velocity.x * deltaTime) / 20 + this._theta; - - this.setAlpha(this.getAlpha()); - this.setBeta(this.getBeta()); - - this._vectorDamping(velocity, this.damping); - }, - - _updateDistanceOrSize: function (deltaTime) { - this._setDistance(this._distance + (this._zoomSpeed * deltaTime) / 20); - if (!(this.target instanceof PerspectiveCamera)) { - this._setOrthoSize(this._orthoSize + (this._zoomSpeed * deltaTime) / 20); + animateTo( + opts: Partial< + AnimatableControlOpts & { + duration: number; + easing: AnimationEasing; + done: () => void; } + > + ) { + const self = this; - this._zoomSpeed *= Math.pow(this.damping, deltaTime / 16); - }, + const obj = {} as AnimatableControlOpts; + const target = {} as AnimatableControlOpts; + const timeline = this.timeline; + if (!timeline) { + return; + } + if (opts.distance != null) { + obj.distance = this.getDistance(); + target.distance = opts.distance; + } + if (opts.orthographicSize != null) { + obj.orthographicSize = this.getOrthographicSize() as number; + target.orthographicSize = opts.orthographicSize; + } + if (opts.alpha != null) { + obj.alpha = this.getAlpha(); + target.alpha = opts.alpha; + } + if (opts.beta != null) { + obj.beta = this.getBeta(); + target.beta = opts.beta; + } + if (opts.center != null) { + obj.center = this.getCenter(); + target.center = opts.center; + } - _setDistance: function (distance) { - this._distance = Math.max(Math.min(distance, this.maxDistance), this.minDistance); - }, + return this._addAnimator( + timeline + .animate(obj) + .when(opts.duration || 1000, target) + .during(function () { + if (obj.alpha != null) { + self.setAlpha(obj.alpha); + } + if (obj.beta != null) { + self.setBeta(obj.beta); + } + if (obj.distance != null) { + self.setDistance(obj.distance); + } + if (obj.orthographicSize != null) { + self.setOrthographicSize(obj.orthographicSize); + } + if (obj.center != null) { + self.setCenter(obj.center); + } + self._needsUpdate = true; + }) + .done(opts.done) + ).start(opts.easing || 'linear'); + } - _setOrthoSize: function (size) { - this._orthoSize = Math.max( - Math.min(size, this.maxOrthographicSize), - this.minOrthographicSize - ); - const camera = this.target; - const cameraHeight = this._orthoSize; - // TODO - const cameraWidth = cameraHeight * this.orthographicAspect; - camera.left = -cameraWidth / 2; - camera.right = cameraWidth / 2; - camera.top = cameraHeight / 2; - camera.bottom = -cameraHeight / 2; - }, + /** + * Stop all animations + */ + stopAllAnimation() { + for (let i = 0; i < this._animators.length; i++) { + this._animators[i].stop(); + } + this._animators.length = 0; + } - _updatePan: function (deltaTime) { - const velocity = this._panVelocity; - const len = this._distance; + _isAnimating() { + return this._animators.length > 0; + } + /** + * Call update each frame + * @param {number} deltaTime Frame time + */ + update(deltaTime?: number) { + deltaTime = deltaTime || 16; - const target = this.target; - const yAxis = target.worldTransform.y; - const xAxis = target.worldTransform.x; + if (this._rotating) { + const radian = + (((this.autoRotateDirection === 'cw' ? 1 : -1) * this.autoRotateSpeed) / 180) * Math.PI; + this._phi -= (radian * deltaTime) / 1000; + this._needsUpdate = true; + } else if (this._rotateVelocity.len() > 0) { + this._needsUpdate = true; + } - // PENDING - this._center - .scaleAndAdd(xAxis, (-velocity.x * len) / 200) - .scaleAndAdd(yAxis, (-velocity.y * len) / 200); + if (Math.abs(this._zoomSpeed) > 0.01 || this._panVelocity.len() > 0) { + this._needsUpdate = true; + } - this._vectorDamping(velocity, 0); + if (!this._needsUpdate) { + return; + } - velocity.x = velocity.y = 0; - }, + // Fixed deltaTime + this._updateDistanceOrSize(Math.min(deltaTime, 50)); + this._updatePan(Math.min(deltaTime, 50)); - _updateTransform: function () { - const camera = this.target; + this._updateRotate(Math.min(deltaTime, 50)); - const dir = new Vector3(); - const theta = this._theta + Math.PI / 2; - const phi = this._phi + Math.PI / 2; - const r = Math.sin(theta); + this._updateTransform(); - dir.x = r * Math.cos(phi); - dir.y = -Math.cos(theta); - dir.z = r * Math.sin(phi); + if (this.target) { + this.target.update(); + } - camera.position.copy(this._center).scaleAndAdd(dir, this._distance); - camera.rotation - .identity() - // First around y, then around x - .rotateY(-this._phi) - .rotateX(-this._theta); - }, + this.trigger('update'); - _startCountingStill: function () { - clearTimeout(this._stillTimeout); + this._needsUpdate = false; + } - const time = this.autoRotateAfterStill; - const self = this; - if (!isNaN(time) && time > 0) { - this._stillTimeout = setTimeout(function () { - self._rotating = true; - }, time * 1000); - } - }, + _updateRotate(deltaTime: number) { + const velocity = this._rotateVelocity; + this._phi = (velocity.y * deltaTime) / 20 + this._phi; + this._theta = (velocity.x * deltaTime) / 20 + this._theta; - _vectorDamping: function (v, damping) { - let speed = v.len(); - speed = speed * damping; - if (speed < 1e-4) { - speed = 0; - } - v.normalize().scale(speed); - }, + this.setAlpha(this.getAlpha()); + this.setBeta(this.getBeta()); - decomposeTransform: function () { - if (!this.target) { - return; - } + this._vectorDamping(velocity, this.damping); + } - this.target.updateWorldTransform(); + _updateDistanceOrSize(deltaTime: number) { + this._setDistance(this._distance + (this._zoomSpeed * deltaTime) / 20); + if (!(this.target instanceof PerspectiveCamera)) { + this._setOrthoSize((this._orthoSize as number) + (this._zoomSpeed * deltaTime) / 20); + } - const forward = this.target.worldTransform.z; - const alpha = Math.asin(forward.y); - const beta = Math.atan2(forward.x, forward.z); + this._zoomSpeed *= Math.pow(this.damping, deltaTime / 16); + } - this._theta = alpha; - this._phi = -beta; + _setDistance(distance: number) { + this._distance = Math.max(Math.min(distance, this.maxDistance), this.minDistance); + } - this.setBeta(this.getBeta()); - this.setAlpha(this.getAlpha()); + _setOrthoSize(size: number) { + this._orthoSize = Math.max(Math.min(size, this.maxOrthographicSize), this.minOrthographicSize); + const camera = this.target as OrthographicCamera; + const cameraHeight = this._orthoSize; + // TODO + const cameraWidth = cameraHeight * this.orthographicAspect; + camera.left = -cameraWidth / 2; + camera.right = cameraWidth / 2; + camera.top = cameraHeight / 2; + camera.bottom = -cameraHeight / 2; + } - this._setDistance(this.target.position.dist(this._center)); - if (!(this.target instanceof PerspectiveCamera)) { - this._setOrthoSize(this.target.top - this.target.bottom); - } - }, + _updatePan(deltaTime: number) { + const velocity = this._panVelocity; + const len = this._distance; - _mouseDownHandler: function (e) { - if (this._isAnimating()) { - return; - } - let x = e.clientX; - let y = e.clientY; - // Touch - if (e.targetTouches) { - const touch = e.targetTouches[0]; - x = touch.clientX; - y = touch.clientY; + const target = this.target; + if (!target) { + return; + } + const yAxis = target.worldTransform.y; + const xAxis = target.worldTransform.x; + // PENDING + this._center + .scaleAndAdd(xAxis, (-velocity.x * len) / 200) + .scaleAndAdd(yAxis, (-velocity.y * len) / 200); + + this._vectorDamping(velocity, 0); + + velocity.x = velocity.y = 0; + } + + _updateTransform() { + const camera = this.target; + if (!camera) { + return; + } + + const dir = new Vector3(); + const theta = this._theta + Math.PI / 2; + const phi = this._phi + Math.PI / 2; + const r = Math.sin(theta); + + dir.x = r * Math.cos(phi); + dir.y = -Math.cos(theta); + dir.z = r * Math.sin(phi); + + camera.position.copy(this._center).scaleAndAdd(dir, this._distance); + camera.rotation + .identity() + // First around y, then around x + .rotateY(-this._phi) + .rotateX(-this._theta); + } + + _startCountingStill() { + clearTimeout(this._stillTimeout); + + const time = this.autoRotateAfterStill; + const self = this; + if (!isNaN(time) && time > 0) { + this._stillTimeout = setTimeout(function () { + self._rotating = true; + }, time * 1000) as any; + } + } + + _vectorDamping(v: Vector2, damping: number) { + let speed = v.len(); + speed = speed * damping; + if (speed < 1e-4) { + speed = 0; + } + v.normalize().scale(speed); + } + + decomposeTransform() { + if (!this.target) { + return; + } + + this.target.updateWorldTransform(); + + const forward = this.target.worldTransform.z; + const alpha = Math.asin(forward.y); + const beta = Math.atan2(forward.x, forward.z); + + this._theta = alpha; + this._phi = -beta; + + this.setBeta(this.getBeta()); + this.setAlpha(this.getAlpha()); + + this._setDistance(this.target.position.dist(this._center)); + if (this.target instanceof OrthographicCamera) { + this._setOrthoSize(this.target.top - this.target.bottom); + } + } + + _mouseDownHandler(e: MouseEvent | TouchEvent) { + if (this._isAnimating()) { + return; + } + let x = (e as MouseEvent).clientX; + let y = (e as MouseEvent).clientY; + // Touch + if ((e as TouchEvent).targetTouches) { + const touch = (e as TouchEvent).targetTouches[0]; + x = touch.clientX; + y = touch.clientY; + + this._mode = 'rotate'; + + this._processGesture(e as TouchEvent, 'start'); + } else { + if ((e as MouseEvent).button === MOUSE_BUTTON_KEY_MAP[this.rotateMouseButton]) { this._mode = 'rotate'; + } else if ((e as MouseEvent).button === MOUSE_BUTTON_KEY_MAP[this.panMouseButton]) { + this._mode = 'pan'; - this._processGesture(e, 'start'); + /** + * Vendors like Mozilla provide a mouse-driven panning feature + * that is activated when the middle mouse button is pressed. + * + * @see https://w3c.github.io/uievents/#event-type-mousedown + */ + e.preventDefault(); } else { - if (e.button === MOUSE_BUTTON_KEY_MAP[this.rotateMouseButton]) { - this._mode = 'rotate'; - } else if (e.button === MOUSE_BUTTON_KEY_MAP[this.panMouseButton]) { - this._mode = 'pan'; - - /** - * Vendors like Mozilla provide a mouse-driven panning feature - * that is activated when the middle mouse button is pressed. - * - * @see https://w3c.github.io/uievents/#event-type-mousedown - */ - e.preventDefault(); - } else { - this._mode = null; - } + this._mode = undefined; } + } - const dom = this.domElement; - vendor.addEventListener(dom, 'touchmove', this._mouseMoveHandler); - vendor.addEventListener(dom, 'touchend', this._mouseUpHandler); + const dom = this.domElement; + addEvent(dom, 'touchmove', this._mouseMoveHandler); + addEvent(dom, 'touchend', this._mouseUpHandler); - vendor.addEventListener(dom, 'mousemove', this._mouseMoveHandler); - vendor.addEventListener(dom, 'mouseup', this._mouseUpHandler); - vendor.addEventListener(dom, 'mouseout', this._mouseUpHandler); + addEvent(dom, 'mousemove', this._mouseMoveHandler); + addEvent(dom, 'mouseup', this._mouseUpHandler); + addEvent(dom, 'mouseout', this._mouseUpHandler); - // Reset rotate velocity - this._rotateVelocity.set(0, 0); - this._rotating = false; - if (this.autoRotate) { - this._startCountingStill(); + // Reset rotate velocity + this._rotateVelocity.set(0, 0); + this._rotating = false; + if (this.autoRotate) { + this._startCountingStill(); + } + + this._mouseX = x; + this._mouseY = y; + } + + _mouseMoveHandler(e: MouseEvent | TouchEvent) { + if (this._isAnimating()) { + return; + } + let x = (e as MouseEvent).clientX; + let y = (e as MouseEvent).clientY; + + let haveGesture; + // Touch + if ((e as TouchEvent).targetTouches) { + const touch = (e as TouchEvent).targetTouches[0]; + x = touch.clientX; + y = touch.clientY; + + haveGesture = this._processGesture(e as TouchEvent, 'change'); + } + + const panSensitivity = convertToArray(this.panSensitivity); + const rotateSensitivity = convertToArray(this.rotateSensitivity); + + if (!haveGesture) { + if (this._mode === 'rotate') { + this._rotateVelocity.y += + ((x - this._mouseX) / this.domElement.clientWidth) * 2 * rotateSensitivity[0]; + this._rotateVelocity.x += + ((y - this._mouseY) / this.domElement.clientHeight) * 2 * rotateSensitivity[1]; + } else if (this._mode === 'pan') { + this._panVelocity.x += + ((x - this._mouseX) / this.domElement.clientWidth) * panSensitivity[0] * 400; + this._panVelocity.y += + ((-y + this._mouseY) / this.domElement.clientHeight) * panSensitivity[1] * 400; } + } - this._mouseX = x; - this._mouseY = y; - }, + this._mouseX = x; + this._mouseY = y; - _mouseMoveHandler: function (e) { - if (this._isAnimating()) { - return; - } - let x = e.clientX; - let y = e.clientY; + e.preventDefault && e.preventDefault(); + } - let haveGesture; - // Touch - if (e.targetTouches) { - const touch = e.targetTouches[0]; - x = touch.clientX; - y = touch.clientY; + _mouseWheelHandler(e: WheelEvent) { + if (this._isAnimating()) { + return; + } + const delta = e.deltaY; + if (delta === 0) { + return; + } - haveGesture = this._processGesture(e, 'change'); - } - - const panSensitivity = convertToArray(this.panSensitivity); - const rotateSensitivity = convertToArray(this.rotateSensitivity); - - if (!haveGesture) { - if (this._mode === 'rotate') { - this._rotateVelocity.y += - ((x - this._mouseX) / this.domElement.clientWidth) * 2 * rotateSensitivity[0]; - this._rotateVelocity.x += - ((y - this._mouseY) / this.domElement.clientHeight) * 2 * rotateSensitivity[1]; - } else if (this._mode === 'pan') { - this._panVelocity.x += - ((x - this._mouseX) / this.domElement.clientWidth) * panSensitivity[0] * 400; - this._panVelocity.y += - ((-y + this._mouseY) / this.domElement.clientHeight) * panSensitivity[1] * 400; - } - } - - this._mouseX = x; - this._mouseY = y; - - e.preventDefault && e.preventDefault(); - }, - - _mouseWheelHandler: function (e) { - if (this._isAnimating()) { - return; - } - const delta = e.deltaY; - if (delta === 0) { - return; - } - - if (this.invertZoomDirection) { - this._zoomHandler(e, delta > 0 ? -1 : 1); - } else { - this._zoomHandler(e, delta > 0 ? 1 : -1); - } - }, - - _pinchHandler: function (e) { - if (this._isAnimating()) { - return; - } - this._zoomHandler(e, e.pinchScale > 1 ? 0.4 : -0.4); - }, - - _zoomHandler: function (e, delta) { - let speed; - if (this.target instanceof PerspectiveCamera) { - speed = Math.max( - Math.max(Math.min(this._distance - this.minDistance, this.maxDistance - this._distance)) / - 20, - 0.5 - ); - } else { - speed = Math.max( - Math.max( - Math.min( - this._orthoSize - this.minOrthographicSize, - this.maxOrthographicSize - this._orthoSize - ) - ) / 20, - 0.5 - ); - } - - this._zoomSpeed = (delta > 0 ? -1 : 1) * speed * this.zoomSensitivity; - - this._rotating = false; - - if (this.autoRotate && this._mode === 'rotate') { - this._startCountingStill(); - } - - e.preventDefault && e.preventDefault(); - }, - - _mouseUpHandler: function (event) { - const dom = this.domElement; - vendor.removeEventListener(dom, 'touchmove', this._mouseMoveHandler); - vendor.removeEventListener(dom, 'touchend', this._mouseUpHandler); - vendor.removeEventListener(dom, 'mousemove', this._mouseMoveHandler); - vendor.removeEventListener(dom, 'mouseup', this._mouseUpHandler); - vendor.removeEventListener(dom, 'mouseout', this._mouseUpHandler); - - this._processGesture(event, 'end'); - }, - - _addAnimator: function (animator) { - const animators = this._animators; - animators.push(animator); - animator.done(function () { - const idx = animators.indexOf(animator); - if (idx >= 0) { - animators.splice(idx, 1); - } - }); - return animator; - }, - - _processGesture: function (event, stage) { - const gestureMgr = this._gestureMgr; - - stage === 'start' && gestureMgr.clear(); - - const gestureInfo = gestureMgr.recognize(event, null, this.domElement); - - stage === 'end' && gestureMgr.clear(); - - // Do not do any preventDefault here. Upper application do that if necessary. - if (gestureInfo) { - const type = gestureInfo.type; - event.gestureEvent = type; - - this._pinchHandler(gestureInfo.event); - } - - return gestureInfo; + if (this.invertZoomDirection) { + this._zoomHandler(e, delta > 0 ? -1 : 1); + } else { + this._zoomHandler(e, delta > 0 ? 1 : -1); } } -); -/** - * If auto rotate the target - * @type {boolean} - * @default false - */ -Object.defineProperty(OrbitControl.prototype, 'autoRotate', { - get: function () { - return this._autoRotate; - }, - set: function (val) { - this._autoRotate = val; - this._rotating = val; - } -}); - -Object.defineProperty(OrbitControl.prototype, 'target', { - get: function () { - return this._target; - }, - set: function (val) { - if (val && val.target) { - this.setCenter(val.target.toArray()); + _pinchHandler(e: PinchEvent) { + if (this._isAnimating()) { + return; } - this._target = val; - this.decomposeTransform(); + this._zoomHandler(e as any, e.pinchScale > 1 ? 0.4 : -0.4); } -}); + + _zoomHandler(e: WheelEvent, delta: number) { + let speed; + if (this.target instanceof PerspectiveCamera) { + speed = Math.max( + Math.max(Math.min(this._distance - this.minDistance, this.maxDistance - this._distance)) / + 20, + 0.5 + ); + } else { + speed = Math.max( + Math.max( + Math.min( + (this._orthoSize as number) - this.minOrthographicSize, + this.maxOrthographicSize - (this._orthoSize as number) + ) + ) / 20, + 0.5 + ); + } + + this._zoomSpeed = (delta > 0 ? -1 : 1) * speed * this.zoomSensitivity; + + this._rotating = false; + + if (this.autoRotate && this._mode === 'rotate') { + this._startCountingStill(); + } + + e.preventDefault && e.preventDefault(); + } + + _mouseUpHandler(event: MouseEvent | TouchEvent) { + const dom = this.domElement; + removeEvent(dom, 'touchmove', this._mouseMoveHandler); + removeEvent(dom, 'touchend', this._mouseUpHandler); + removeEvent(dom, 'mousemove', this._mouseMoveHandler); + removeEvent(dom, 'mouseup', this._mouseUpHandler); + removeEvent(dom, 'mouseout', this._mouseUpHandler); + + this._processGesture(event as TouchEvent, 'end'); + } + + _addAnimator(animator: ProceduralKeyframeAnimator) { + const animators = this._animators; + animators.push(animator); + animator.done(function () { + const idx = animators.indexOf(animator); + if (idx >= 0) { + animators.splice(idx, 1); + } + }); + return animator; + } + + _processGesture(event: TouchEvent, stage: 'start' | 'end' | 'change') { + const gestureMgr = this._gestureMgr; + + stage === 'start' && gestureMgr.clear(); + + const gestureInfo = gestureMgr.recognize(event, null, this.domElement); + + stage === 'end' && gestureMgr.clear(); + + // Do not do any preventDefault here. Upper application do that if necessary. + if (gestureInfo) { + const type = gestureInfo.type; + (event as any).gestureEvent = type; + + this._pinchHandler(gestureInfo.event); + } + + return gestureInfo; + } +} export default OrbitControl; diff --git a/src/plugin/Skydome.ts b/src/plugin/Skydome.ts index c3ee428e..5000ca19 100644 --- a/src/plugin/Skydome.ts +++ b/src/plugin/Skydome.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import Skybox from './Skybox'; export default Skybox;