threepipe
Version:
A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.
194 lines (127 loc) • 6.28 kB
text/typescript
import {Euler, EulerOrder, EventDispatcher, MathUtils, Object3D, Quaternion, Vector3} from 'three'
import {IEvent, now, serialize} from 'ts-browser-helpers'
import {uiButton, uiPanelContainer, uiSlider} from 'uiconfig.js'
import {ICameraControls, ICameraControlsEventMap} 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
export class DeviceOrientationControls2 extends EventDispatcher<ICameraControlsEventMap> implements ICameraControls {
object: Object3D
enabled = false // do not serialize this as it signifies weather this is active.
deviceOrientation?: DeviceOrientationEvent
screenOrientation?: ScreenOrientation
lastOrder: EulerOrder = 'XYZ'
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()
resetView() {
(this._initQuaternionDest as any).__init = false
}
connect() {
if (this.enabled) return
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() {
if (!this.enabled) return
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
// debugger
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
}
}