| { | { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.22", | |||||
| "version": "0.0.23", | |||||
| "lockfileVersion": 3, | "lockfileVersion": 3, | ||||
| "requires": true, | "requires": true, | ||||
| "packages": { | "packages": { | ||||
| "": { | "": { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.22", | |||||
| "version": "0.0.23", | |||||
| "license": "Apache-2.0", | "license": "Apache-2.0", | ||||
| "dependencies": { | "dependencies": { | ||||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1020/package.tgz", | "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1020/package.tgz", | ||||
| "rollup-plugin-glsl": "^1.3.0", | "rollup-plugin-glsl": "^1.3.0", | ||||
| "rollup-plugin-license": "^3.0.1", | "rollup-plugin-license": "^3.0.1", | ||||
| "rollup-plugin-postcss": "^4.0.2", | "rollup-plugin-postcss": "^4.0.2", | ||||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2020/package.tgz", | |||||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2021/package.tgz", | |||||
| "tslib": "^2.5.0", | "tslib": "^2.5.0", | ||||
| "typedoc": "^0.25.7", | "typedoc": "^0.25.7", | ||||
| "typescript": "^5.3.3", | "typescript": "^5.3.3", | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/three": { | "node_modules/three": { | ||||
| "version": "0.152.2020", | |||||
| "resolved": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2020/package.tgz", | |||||
| "integrity": "sha512-XpB018kSE/FZG5xmh6XG22lXTS9bParloATQ952tZaY3p7PsdiP3skXR49GYiLjG2Lui46wA18aq5aXJ9oabGA==", | |||||
| "version": "0.152.2021", | |||||
| "resolved": "https://github.com/repalash/three.js-modded/releases/download/v0.152.2021/package.tgz", | |||||
| "integrity": "sha512-va2FQU/ES07GLdflqysT82rh8x+cwx0sCYB8w+JGjDCAcwYDYqvjWafWzWP0UrDR8JM+8HoCLwh+rHtGU5Eoug==", | |||||
| "dev": true, | "dev": true, | ||||
| "license": "MIT" | "license": "MIT" | ||||
| }, | }, |
| { | { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.22", | |||||
| "version": "0.0.23", | |||||
| "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | ||||
| "main": "dist/index.js", | "main": "dist/index.js", | ||||
| "module": "dist/index.mjs", | "module": "dist/index.mjs", |
| import {Euler, EulerOrder, EventDispatcher, MathUtils, Object3D, Quaternion, Vector3} from 'three' | |||||
| import {IEvent, now, serialize} from 'ts-browser-helpers' | |||||
| import {uiPanelContainer, uiSlider} from 'uiconfig.js' | |||||
| import {ICameraControls} from '../../core' | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _zee = new Vector3(0, 0, 1) | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _euler = new Euler() | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _q0 = new Quaternion() | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)) // - PI/2 around the x-axis | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _q2 = new Quaternion() // - PI/2 around the x-axis | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _changeEvent: IEvent<'change'> = {type: 'change'} | |||||
| const EPS = 0.000001 | |||||
| @uiPanelContainer('Device Orientation Controls') | |||||
| export class DeviceOrientationControls2 extends EventDispatcher implements ICameraControls<'change'> { | |||||
| object: Object3D | |||||
| enabled = false // do not serialize this as it signifies weather this is active. | |||||
| deviceOrientation?: DeviceOrientationEvent | |||||
| screenOrientation?: ScreenOrientation | |||||
| lastOrder: EulerOrder = 'XYZ' | |||||
| @serialize() | |||||
| @uiSlider('Damping', [0, 1], 0.01) | |||||
| dampingFactor = 0.05 | |||||
| lastQuaternion = new Quaternion() | |||||
| constructor(object: Object3D) { | |||||
| super() | |||||
| if (window.isSecureContext === false) { | |||||
| console.error('DeviceOrientationControls2: DeviceOrientationEvent is only available in secure contexts (https)') | |||||
| } | |||||
| this.object = object | |||||
| this.lastOrder = this.object.rotation.order | |||||
| this.object.rotation.reorder('YXZ') | |||||
| // this.enabled = true | |||||
| this.connect() | |||||
| } | |||||
| onDeviceOrientationChangeEvent = (event: DeviceOrientationEvent) => { | |||||
| this.deviceOrientation = event | |||||
| } | |||||
| onScreenOrientationChangeEvent = () => { | |||||
| this.screenOrientation = screen.orientation | |||||
| } | |||||
| private _initQuaternion = new Quaternion() | |||||
| private _initQuaternionInvert = new Quaternion() | |||||
| private _initQuaternionDest = new Quaternion() | |||||
| connect() { | |||||
| this.onScreenOrientationChangeEvent() // run once on load | |||||
| // iOS 13+ | |||||
| if (window.DeviceOrientationEvent !== undefined && typeof (window.DeviceOrientationEvent as any).requestPermission === 'function') { | |||||
| (window.DeviceOrientationEvent as any).requestPermission().then((response: string)=>{ | |||||
| if (response == 'granted') { | |||||
| window.addEventListener('orientationchange', this.onScreenOrientationChangeEvent) | |||||
| window.addEventListener('deviceorientation', this.onDeviceOrientationChangeEvent) | |||||
| } | |||||
| }).catch((error: any)=>{ | |||||
| console.error('DeviceOrientationControls2: Unable to use DeviceOrientation API:', error) | |||||
| }) | |||||
| } else { | |||||
| window.addEventListener('orientationchange', this.onScreenOrientationChangeEvent) | |||||
| window.addEventListener('deviceorientation', this.onDeviceOrientationChangeEvent) | |||||
| } | |||||
| this.enabled = true | |||||
| this._initQuaternion.copy(this.object.quaternion) | |||||
| this._initQuaternionInvert.copy(this.object.quaternion).invert() | |||||
| } | |||||
| disconnect() { | |||||
| window.removeEventListener('orientationchange', this.onScreenOrientationChangeEvent) | |||||
| window.removeEventListener('deviceorientation', this.onDeviceOrientationChangeEvent) | |||||
| this._initQuaternion.identity() | |||||
| this._initQuaternionInvert.identity() | |||||
| this._initQuaternionDest = new Quaternion() // need to set a new instance here. | |||||
| this.object.rotation.reorder(this.lastOrder) | |||||
| this.lastOrder = 'XYZ' | |||||
| this.enabled = false | |||||
| } | |||||
| update() { | |||||
| if (!this.enabled) return | |||||
| const device = this.deviceOrientation | |||||
| if (device) { | |||||
| const alpha = device.alpha !== null ? MathUtils.degToRad(device.alpha) : 0 // Z | |||||
| const beta = device.beta !== null ? MathUtils.degToRad(device.beta) : 0 // X' | |||||
| const gamma = device.gamma !== null ? MathUtils.degToRad(device.gamma) : 0 // Y'' | |||||
| const orient = this.screenOrientation ? MathUtils.degToRad(this.screenOrientation.angle) : 0 // O | |||||
| this.setObjectQuaternion(alpha, beta, gamma, orient) | |||||
| if (8 * (1 - this.lastQuaternion.dot(this.object.quaternion)) > EPS) { | |||||
| this.lastQuaternion.copy(this.object.quaternion) | |||||
| this.dispatchEvent(_changeEvent) | |||||
| } | |||||
| } | |||||
| } | |||||
| dispose() { | |||||
| this.disconnect() | |||||
| } | |||||
| private _lastTime = -1 | |||||
| // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y'' | |||||
| setObjectQuaternion(alpha: number, beta: number, gamma: number, orient: number): void { | |||||
| // if(_lastTime < 0) | |||||
| const time = now() / 1000 | |||||
| _euler.set(beta, alpha, -gamma, 'YXZ') // 'ZXY' for the device, but 'YXZ' for us | |||||
| _q2.setFromEuler(_euler) // orient the device | |||||
| _q2.multiply(_q1) // camera looks out the back of the device, not the top | |||||
| _q2.multiply(_q0.setFromAxisAngle(_zee, -orient)) // adjust for screen orientation | |||||
| if (!(this._initQuaternionDest as any).__init) { | |||||
| this._initQuaternionDest.copy(_q2).invert() | |||||
| ;(this._initQuaternionDest as any).__init = true | |||||
| } | |||||
| _q2.premultiply(this._initQuaternionDest) | |||||
| const mTime = 1 / 60 | |||||
| // this.object.quaternion.multiply(this._initQuaternionInvert) | |||||
| this.object.quaternion.slerp(_q2, this.dampingFactor / (Math.min(1, time - this._lastTime) / mTime)) | |||||
| // this.object.quaternion.multiply(this._initQuaternion) | |||||
| // console.log(time - this._lastTime, mTime) | |||||
| this._lastTime = time | |||||
| } | |||||
| } |
| import {MathUtils, Object3D, Spherical, Vector3} from 'three' | |||||
| import {IEvent, now, serialize, SimpleEventDispatcher} from 'ts-browser-helpers' | |||||
| import {uiFolderContainer, uiInput, uiToggle} from 'uiconfig.js' | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _lookDirection = new Vector3() | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _spherical = new Spherical() | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _target = new Vector3() | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _changeEvent: IEvent<'change'> = {type: 'change'} | |||||
| @uiFolderContainer('First Person Controls') | |||||
| export class FirstPersonControls2 extends SimpleEventDispatcher<'change'> { | |||||
| readonly object: Object3D | |||||
| readonly domElement: HTMLElement | Document | |||||
| // API | |||||
| @serialize() @uiToggle() enabled = true | |||||
| @serialize() @uiToggle() enableKeys = true | |||||
| @serialize() @uiInput() movementSpeed = 1.0 | |||||
| @serialize() @uiInput()lookSpeed = 0.005 | |||||
| @serialize() @uiToggle() lookVertical = true | |||||
| @serialize() @uiToggle() autoForward = false | |||||
| @serialize() @uiToggle() activeLook = true | |||||
| @serialize() @uiToggle() heightSpeed = false | |||||
| @serialize() @uiInput()heightCoef = 1.0 | |||||
| @serialize() @uiInput()heightMin = 0.0 | |||||
| @serialize() @uiInput()heightMax = 1.0 | |||||
| @serialize() @uiToggle() constrainVertical = false | |||||
| @serialize() @uiInput() verticalMin = 0 | |||||
| @serialize() @uiInput() verticalMax = Math.PI | |||||
| @serialize() @uiToggle() mouseDragOn = false | |||||
| // internals | |||||
| autoSpeedFactor = 0.0 | |||||
| pointerX = 0 | |||||
| pointerY = 0 | |||||
| moveForward = false | |||||
| moveBackward = false | |||||
| moveLeft = false | |||||
| moveRight = false | |||||
| moveUp = false | |||||
| moveDown = false | |||||
| viewHalfX = 0 | |||||
| viewHalfY = 0 | |||||
| // private variables | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| private lat = 0 | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| private lon = 0 | |||||
| constructor(object: Object3D, domElement: HTMLElement|Document) { | |||||
| super() | |||||
| this.object = object | |||||
| this.domElement = domElement | |||||
| this.onPointerMove = this.onPointerMove.bind(this) | |||||
| this.onPointerDown = this.onPointerDown.bind(this) | |||||
| this.onPointerUp = this.onPointerUp.bind(this) | |||||
| this.onKeyDown = this.onKeyDown.bind(this) | |||||
| this.onKeyUp = this.onKeyUp.bind(this) | |||||
| this.onContextMenu = this.onContextMenu.bind(this) | |||||
| this.domElement.addEventListener('contextmenu', this.onContextMenu) | |||||
| ;(this.domElement as HTMLElement).addEventListener('pointermove', this.onPointerMove) | |||||
| ;(this.domElement as HTMLElement).addEventListener('pointerdown', this.onPointerDown) | |||||
| ;(this.domElement as HTMLElement).addEventListener('pointerup', this.onPointerUp) | |||||
| window.addEventListener('keydown', this.onKeyDown) | |||||
| window.addEventListener('keyup', this.onKeyUp) | |||||
| this.handleResize() | |||||
| this.setOrientation() | |||||
| } | |||||
| setOrientation() { | |||||
| const quaternion = this.object.quaternion | |||||
| _lookDirection.set(0, 0, -1).applyQuaternion(quaternion) | |||||
| _spherical.setFromVector3(_lookDirection) | |||||
| this.lat = 90 - MathUtils.radToDeg(_spherical.phi) | |||||
| this.lon = MathUtils.radToDeg(_spherical.theta) | |||||
| } | |||||
| handleResize() { | |||||
| if (this.domElement === document) { | |||||
| this.viewHalfX = window.innerWidth / 2 | |||||
| this.viewHalfY = window.innerHeight / 2 | |||||
| } else { | |||||
| this.viewHalfX = (this.domElement as HTMLElement).offsetWidth / 2 | |||||
| this.viewHalfY = (this.domElement as HTMLElement).offsetHeight / 2 | |||||
| } | |||||
| } | |||||
| onPointerDown(event: PointerEvent) { | |||||
| if (this.domElement !== document) { | |||||
| (this.domElement as HTMLElement).focus() | |||||
| } | |||||
| if (this.activeLook) { | |||||
| switch (event.button) { | |||||
| case 0: this.moveForward = true; break | |||||
| case 2: this.moveBackward = true; break | |||||
| default: break | |||||
| } | |||||
| } | |||||
| this.mouseDragOn = true | |||||
| } | |||||
| onPointerUp(event: PointerEvent) { | |||||
| if (this.activeLook) { | |||||
| switch (event.button) { | |||||
| case 0: this.moveForward = false; break | |||||
| case 2: this.moveBackward = false; break | |||||
| default: break | |||||
| } | |||||
| } | |||||
| this.mouseDragOn = false | |||||
| } | |||||
| onPointerMove(event: PointerEvent) { | |||||
| if (this.domElement === document) { | |||||
| this.pointerX = event.pageX - this.viewHalfX | |||||
| this.pointerY = event.pageY - this.viewHalfY | |||||
| } else { | |||||
| this.pointerX = event.pageX - (this.domElement as HTMLElement).offsetLeft - this.viewHalfX | |||||
| this.pointerY = event.pageY - (this.domElement as HTMLElement).offsetTop - this.viewHalfY | |||||
| } | |||||
| } | |||||
| onKeyDown(event: KeyboardEvent) { | |||||
| if (!this.enableKeys) return | |||||
| switch (event.code) { | |||||
| case 'ArrowUp': | |||||
| case 'KeyW': this.moveForward = true; break | |||||
| case 'ArrowLeft': | |||||
| case 'KeyA': this.moveLeft = true; break | |||||
| case 'ArrowDown': | |||||
| case 'KeyS': this.moveBackward = true; break | |||||
| case 'ArrowRight': | |||||
| case 'KeyD': this.moveRight = true; break | |||||
| case 'KeyR': this.moveUp = true; break | |||||
| case 'KeyF': this.moveDown = true; break | |||||
| default: break | |||||
| } | |||||
| } | |||||
| onKeyUp(event: KeyboardEvent) { | |||||
| if (!this.enableKeys) return | |||||
| switch (event.code) { | |||||
| case 'ArrowUp': | |||||
| case 'KeyW': this.moveForward = false; break | |||||
| case 'ArrowLeft': | |||||
| case 'KeyA': this.moveLeft = false; break | |||||
| case 'ArrowDown': | |||||
| case 'KeyS': this.moveBackward = false; break | |||||
| case 'ArrowRight': | |||||
| case 'KeyD': this.moveRight = false; break | |||||
| case 'KeyR': this.moveUp = false; break | |||||
| case 'KeyF': this.moveDown = false; break | |||||
| default: break | |||||
| } | |||||
| } | |||||
| lookAt(x: number|Vector3, y?: number, z?: number) { | |||||
| if ((x as Vector3).isVector3) { | |||||
| _target.copy(x as Vector3) | |||||
| } else { | |||||
| if (y === undefined || z === undefined) console.error('FirstPersonControls2.lookAt: y and z parameters are required') | |||||
| else _target.set(x as number, y, z) | |||||
| } | |||||
| this.object.lookAt(_target) | |||||
| this.setOrientation() | |||||
| return this | |||||
| } | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| private targetPosition = new Vector3() | |||||
| private _lastTime = -1 // in ms | |||||
| update() { | |||||
| const time = now() // in ms | |||||
| const delta = (this._lastTime < 0 ? 16 : Math.min(time - this._lastTime, 1000)) / 1000 // in secs | |||||
| this._lastTime = time | |||||
| // console.log(delta) | |||||
| if (!this.enabled) return | |||||
| if (this.heightSpeed) { | |||||
| const y = MathUtils.clamp(this.object.position.y, this.heightMin, this.heightMax) | |||||
| const heightDelta = y - this.heightMin | |||||
| this.autoSpeedFactor = delta * (heightDelta * this.heightCoef) | |||||
| } else { | |||||
| this.autoSpeedFactor = 0.0 | |||||
| } | |||||
| const actualMoveSpeed = delta * this.movementSpeed | |||||
| if (this.moveForward || this.autoForward && !this.moveBackward) this.object.translateZ(-(actualMoveSpeed + this.autoSpeedFactor)) | |||||
| if (this.moveBackward) this.object.translateZ(actualMoveSpeed) | |||||
| if (this.moveLeft) this.object.translateX(-actualMoveSpeed) | |||||
| if (this.moveRight) this.object.translateX(actualMoveSpeed) | |||||
| if (this.moveUp) this.object.translateY(actualMoveSpeed) | |||||
| if (this.moveDown) this.object.translateY(-actualMoveSpeed) | |||||
| let actualLookSpeed = delta * this.lookSpeed | |||||
| if (!this.activeLook) { | |||||
| actualLookSpeed = 0 | |||||
| } | |||||
| let verticalLookRatio = 1 | |||||
| if (this.constrainVertical) { | |||||
| verticalLookRatio = Math.PI / (this.verticalMax - this.verticalMin) | |||||
| } | |||||
| this.lon -= this.pointerX * actualLookSpeed | |||||
| if (this.lookVertical) this.lat -= this.pointerY * actualLookSpeed * verticalLookRatio | |||||
| this.lat = Math.max(-85, Math.min(85, this.lat)) | |||||
| let phi = MathUtils.degToRad(90 - this.lat) | |||||
| const theta = MathUtils.degToRad(this.lon) | |||||
| if (this.constrainVertical) { | |||||
| phi = MathUtils.mapLinear(phi, 0, Math.PI, this.verticalMin, this.verticalMax) | |||||
| } | |||||
| const position = this.object.position | |||||
| this.targetPosition.setFromSphericalCoords(1, phi, theta).add(position) | |||||
| this.object.lookAt(this.targetPosition) | |||||
| this.dispatchEvent(_changeEvent) | |||||
| } | |||||
| dispose() { | |||||
| this.domElement.removeEventListener('contextmenu', this.onContextMenu) | |||||
| ;(this.domElement as HTMLElement).removeEventListener('pointerdown', this.onPointerDown) | |||||
| ;(this.domElement as HTMLElement).removeEventListener('pointermove', this.onPointerMove) | |||||
| ;(this.domElement as HTMLElement).removeEventListener('pointerup', this.onPointerUp) | |||||
| window.removeEventListener('keydown', this.onKeyDown) | |||||
| window.removeEventListener('keyup', this.onKeyUp) | |||||
| } | |||||
| onContextMenu(event: Event) { | |||||
| if (!this.enableKeys) return | |||||
| event.preventDefault() | |||||
| } | |||||
| } | |||||
| import {Camera, PerspectiveCamera, Vector3} from 'three' | |||||
| import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js' | |||||
| const offset2 = new Vector3() | |||||
| const targetDeltaX = new Vector3() | |||||
| const targetDeltaY = new Vector3() | |||||
| const targetDeltaZ = new Vector3() | |||||
| const targetDelta = new Vector3() | |||||
| const panOffset2 = new Vector3() | |||||
| let scaleOffset = 1 | |||||
| const upVec = new Vector3(0, 1, 0) | |||||
| export class OrbitControls2 extends OrbitControls { | |||||
| throttleUpdate = 60 | |||||
| constructor(object: Camera, domElement: HTMLElement) { | |||||
| super(object, domElement) | |||||
| const sup = this.update | |||||
| this.update = ()=>this._update(sup) | |||||
| } | |||||
| readonly targetOffset = new Vector3(0, 0, 0) | |||||
| private _update(sup: ()=>boolean): boolean { | |||||
| this.target.add(this.targetOffset) | |||||
| offset2.copy(this.object.position).sub(this.target) | |||||
| scaleOffset = offset2.length() | |||||
| panOffset2.copy(this.target) | |||||
| const ret = sup() | |||||
| panOffset2.sub(this.target) // get the panOffset of this frame from OrbitControls | |||||
| // if (panOffset2.length() > 0.0001) | |||||
| // console.log(panOffset2.toArray()) | |||||
| // console.log(offset2.clone().normalize().cross(upVec)) | |||||
| offset2.copy(this.object.position).sub(this.target) | |||||
| // panOffset2.multiplyScalar(-1) | |||||
| // panOffset3.x = panOffset3.z | |||||
| // console.log(panOffset3.z) | |||||
| scaleOffset /= offset2.length() | |||||
| this.target.add(panOffset2) | |||||
| this.object.position.copy(this.target).add(offset2) | |||||
| offset2.normalize() | |||||
| targetDeltaX.crossVectors(upVec, offset2).normalize() | |||||
| targetDeltaY.crossVectors(offset2, targetDeltaX).normalize() | |||||
| targetDeltaZ.crossVectors(targetDeltaX, targetDeltaY).normalize().negate() | |||||
| if (targetDeltaX.length() > 0.1) // check if not 0 | |||||
| this.object.up.crossVectors(offset2.clone().normalize(), targetDeltaX) | |||||
| if (this.enablePan) { | |||||
| targetDelta.set(0, 0, 0) | |||||
| .addScaledVector(targetDeltaX, panOffset2.x) | |||||
| .addScaledVector(targetDeltaY, panOffset2.y) | |||||
| .addScaledVector(targetDeltaZ, panOffset2.z) | |||||
| this.targetOffset.add(targetDelta) | |||||
| this.targetOffset.multiplyScalar(1. / scaleOffset) | |||||
| } | |||||
| targetDelta.set(0, 0, 0) | |||||
| .addScaledVector(targetDeltaX, -this.targetOffset.x) | |||||
| .addScaledVector(targetDeltaY, -this.targetOffset.y) | |||||
| .addScaledVector(targetDeltaZ, -this.targetOffset.z) | |||||
| // console.log(targetDelta) | |||||
| this.object.lookAt(targetDelta.add(this.target)) | |||||
| this.object.updateMatrixWorld() | |||||
| if ((this.object as PerspectiveCamera).isCamera) { | |||||
| (this.object as PerspectiveCamera).updateProjectionMatrix() | |||||
| } | |||||
| this.target.sub(this.targetOffset) | |||||
| return ret | |||||
| } | |||||
| } |
| import {Euler, EventDispatcher, Object3D, Vector3} from 'three' | |||||
| import {IEvent, serialize} from 'ts-browser-helpers' | |||||
| import {uiInput, uiPanelContainer, uiToggle} from 'uiconfig.js' | |||||
| import {ICameraControls} from '../../core' | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _euler = new Euler(0, 0, 0, 'YXZ') | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _vector = new Vector3() | |||||
| export type TPointerLockEvents = 'change'|'lock'|'unlock' | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _changeEvent: IEvent<TPointerLockEvents> = {type: 'change'} | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _lockEvent: IEvent<TPointerLockEvents> = {type: 'lock'} | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _unlockEvent: IEvent<TPointerLockEvents> = {type: 'unlock'} | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| const _PI_2 = Math.PI / 2 | |||||
| @uiPanelContainer('Pointer Lock Controls') | |||||
| export class PointerLockControls2 extends EventDispatcher implements ICameraControls<TPointerLockEvents> { | |||||
| readonly domElement: HTMLElement | |||||
| readonly object: Object3D | |||||
| isLocked = false | |||||
| @uiToggle() @serialize() enabled = true | |||||
| // Set to constrain the pitch of the camera | |||||
| // Range is 0 to Math.PI radians | |||||
| @uiInput() @serialize() minPolarAngle = 0 // radians | |||||
| @uiInput() @serialize() maxPolarAngle = Math.PI // radians | |||||
| @uiInput() @serialize() pointerSpeed = 1.0 | |||||
| @uiToggle() @serialize() autoLockOnClick = true | |||||
| constructor(camera: Object3D, domElement: HTMLElement) { | |||||
| super() | |||||
| this.domElement = domElement | |||||
| this.object = camera | |||||
| this.onElementClick = this.onElementClick.bind(this) | |||||
| this.onMouseMove = this.onMouseMove.bind(this) | |||||
| this.onPointerlockChange = this.onPointerlockChange.bind(this) | |||||
| this.onPointerlockError = this.onPointerlockError.bind(this) | |||||
| this.connect() | |||||
| } | |||||
| onElementClick(event: Event) { | |||||
| if (this.isLocked) return | |||||
| if (!this.autoLockOnClick) return | |||||
| event.preventDefault() | |||||
| this.lock() | |||||
| } | |||||
| private _movementX = 0 | |||||
| private _movementY = 0 | |||||
| onMouseMove(event: MouseEvent) { | |||||
| if (!this.isLocked) return | |||||
| this._movementX += event.movementX || (event as any).mozMovementX || (event as any).webkitMovementX || 0 | |||||
| this._movementY += event.movementY || (event as any).mozMovementY || (event as any).webkitMovementY || 0 | |||||
| } | |||||
| onPointerlockChange() { | |||||
| if (this.domElement.ownerDocument.pointerLockElement === this.domElement) { | |||||
| this.dispatchEvent(_lockEvent) | |||||
| this.isLocked = true | |||||
| } else { | |||||
| this.dispatchEvent(_unlockEvent) | |||||
| this.isLocked = false | |||||
| } | |||||
| } | |||||
| onPointerlockError() { | |||||
| console.error('THREE.PointerLockControls: Unable to use Pointer Lock API') | |||||
| } | |||||
| connect() { | |||||
| this.domElement.ownerDocument.addEventListener('mousemove', this.onMouseMove) | |||||
| this.domElement.ownerDocument.addEventListener('pointerlockchange', this.onPointerlockChange) | |||||
| this.domElement.ownerDocument.addEventListener('pointerlockerror', this.onPointerlockError) | |||||
| this.domElement.addEventListener('click', this.onElementClick) | |||||
| } | |||||
| disconnect() { | |||||
| this.domElement.ownerDocument.removeEventListener('mousemove', this.onMouseMove) | |||||
| this.domElement.ownerDocument.removeEventListener('pointerlockchange', this.onPointerlockChange) | |||||
| this.domElement.ownerDocument.removeEventListener('pointerlockerror', this.onPointerlockError) | |||||
| this.domElement.removeEventListener('click', this.onElementClick) | |||||
| } | |||||
| dispose() { | |||||
| this.disconnect() | |||||
| } | |||||
| // getObject() { // retaining this method for backward compatibility | |||||
| // | |||||
| // return this.object | |||||
| // | |||||
| // } | |||||
| private _forwardDirection = new Vector3(0, 0, -1) | |||||
| getDirection(v: Vector3) { | |||||
| return v.copy(this._forwardDirection).applyQuaternion(this.object.quaternion) | |||||
| } | |||||
| moveForward(distance: number) { | |||||
| // move forward parallel to the xz-plane | |||||
| // assumes camera.up is y-up | |||||
| _vector.setFromMatrixColumn(this.object.matrix, 0) | |||||
| _vector.crossVectors(this.object.up, _vector) | |||||
| this.object.position.addScaledVector(_vector, distance) | |||||
| } | |||||
| moveRight(distance: number) { | |||||
| _vector.setFromMatrixColumn(this.object.matrix, 0) | |||||
| this.object.position.addScaledVector(_vector, distance) | |||||
| } | |||||
| lock() { | |||||
| this.domElement.requestPointerLock() | |||||
| } | |||||
| unlock() { | |||||
| this.domElement.ownerDocument.exitPointerLock() | |||||
| } | |||||
| update() { | |||||
| if (Math.abs(this._movementX) < 0.0001 && Math.abs(this._movementY) < 0.0001) return | |||||
| _euler.setFromQuaternion(this.object.quaternion) | |||||
| _euler.y -= this._movementX * 0.002 * this.pointerSpeed | |||||
| _euler.x -= this._movementY * 0.002 * this.pointerSpeed | |||||
| this._movementX = 0 | |||||
| this._movementY = 0 | |||||
| _euler.x = Math.max(_PI_2 - this.maxPolarAngle, Math.min(_PI_2 - this.minPolarAngle, _euler.x)) | |||||
| this.object.quaternion.setFromEuler(_euler) | |||||
| this.dispatchEvent(_changeEvent) | |||||
| } | |||||
| } |
| export {OrbitControls3, type TOrbitControlsEvents} from './controls/OrbitControls3' | export {OrbitControls3, type TOrbitControlsEvents} from './controls/OrbitControls3' | ||||
| export {TransformControls2} from './controls/TransformControls2' | export {TransformControls2} from './controls/TransformControls2' | ||||
| export {TransformControls, TransformControlsGizmo, TransformControlsPlane} from './controls/TransformControls' | export {TransformControls, TransformControlsGizmo, TransformControlsPlane} from './controls/TransformControls' | ||||
| export {FirstPersonControls2} from './controls/FirstPersonControls2' | |||||
| export {PointerLockControls2, type TPointerLockEvents} from './controls/PointerLockControls2' | |||||
| export {DeviceOrientationControls2} from './controls/DeviceOrientationControls2' | |||||
| export {OrbitControls2} from './controls/OrbitControls2' | |||||
| export {Box3B} from './math/Box3B' | export {Box3B} from './math/Box3B' | ||||
| export * from './utils/index' | export * from './utils/index' | ||||
| export * from './widgets/index' | export * from './widgets/index' |
| import {Color, Vector4} from 'three' | |||||
| // todo: move these to ts-browser-helpers maybe | |||||
| // reference: http://iwasbeingirony.blogspot.ca/2010/06/difference-between-rgbm-and-rgbd.html | |||||
| export function vRGBMToLinear(value: Vector4, maxRange: number): Vector4 { | |||||
| value.multiplyScalar(value.w * maxRange) | |||||
| value.w = 1.0 | |||||
| return value | |||||
| } | |||||
| export function cRGBMToLinear(value: Vector4, maxRange: number): Color { | |||||
| vRGBMToLinear(value, maxRange) | |||||
| return new Color(value.x, value.y, value.z) | |||||
| } | |||||
| export function vLinearToRGBM(value: Vector4, maxRange: number): Vector4 { | |||||
| const maxRGB = Math.max(value.x, Math.max(value.y, value.z)) | |||||
| let M = Math.max(Math.min(maxRGB / maxRange, 1.0), 0.0) | |||||
| M = Math.ceil(M * 255.0) / 255.0 | |||||
| value.divideScalar(M * maxRange) | |||||
| value.w = M | |||||
| return value | |||||
| } | |||||
| export function cLinearToRGBM(value: Color, maxRange: number): Vector4 { | |||||
| return vLinearToRGBM(new Vector4(value.r, value.g, value.b, 1.0), maxRange) | |||||
| } |
| export {makeGLBFile} from './gltf' | export {makeGLBFile} from './gltf' | ||||
| export {animateCameraToViewLinear, animateCameraToViewSpherical, sphericalFromCameraView} from './camera-anim' | export {animateCameraToViewLinear, animateCameraToViewSpherical, sphericalFromCameraView} from './camera-anim' | ||||
| export {animateAsync, animateTarget, EasingFunctions, makeSetterFor, animate, lerp, lerpAngle} from './animation' | export {animateAsync, animateTarget, EasingFunctions, makeSetterFor, animate, lerp, lerpAngle} from './animation' | ||||
| export {cLinearToRGBM, vLinearToRGBM, cRGBMToLinear, vRGBMToLinear} from './color-encodings' | |||||
| export {CanvasSnapshot, type CanvasSnapshotOptions, type CanvasSnapshotRect} from './canvas-snapshot' | |||||
| export type {Easing, KeyframeOptions, AnimationOptions, EasingFunctionType, AnimateResult} from './animation' | export type {Easing, KeyframeOptions, AnimationOptions, EasingFunctionType, AnimateResult} from './animation' | ||||
| export const VERSION = '0.0.22' | |||||
| export const VERSION = '0.0.23' |