threepipe
Version:
A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.
137 lines (127 loc) • 6.43 kB
text/typescript
import {iObjectCommons} from './iObjectCommons'
import {Camera, Vector3} from 'three'
import type {ICamera, ICameraEvent, ICameraSetDirtyOptions} from '../ICamera'
export const iCameraCommons = {
setDirty: function(this: ICamera, options?: ICameraSetDirtyOptions): void {
// console.log('target', target, this._controls, this._camera)
if (this.controls && this.controls.target && this.target !== this.controls.target) {
this.controls.target.copy(this.target)
// this.controls.update() // this should be done automatically postFrame
}
if (!this.controls || !this.controls.enabled) {
if (this.userData.autoLookAtTarget) {
this.lookAt(this.target)
}
}
this.dispatchEvent({...options, type: 'update'}) // does not bubble
this.dispatchEvent({...options, type: 'cameraUpdate', bubbleToParent: true}) // this sets dirty in the viewer
iObjectCommons.setDirty.call(this, {refreshScene: false, ...options})
},
activateMain: function(this: ICamera, options: Partial<ICameraEvent> = {}, _internal = false, _refresh = true): void {
if (!_internal) {
if (options.camera === null) return this.deactivateMain(options, _internal, _refresh)
return this.dispatchEvent({
type: 'activateMain', ...options,
camera: this,
bubbleToParent: true,
})
} // this will be used by RootScene to deactivate other cameras and activate this one
if (this.userData.__isMainCamera) return
this.userData.__isMainCamera = true
this.userData.__lastScale = this.scale.clone()
this.scale.divide(this.getWorldScale(new Vector3())) // make unit scale, for near far and all
if (_refresh) {
this.refreshCameraControls(false)
}
this.setDirty({change: 'activateMain', ...options})
// console.log({...this._camera.modelObject.position})
},
deactivateMain: function(this: ICamera, options: Partial<ICameraEvent> = {}, _internal = false, _refresh = true): void {
if (!_internal) return this.dispatchEvent({
type: 'activateMain', ...options,
camera: null,
bubbleToParent: true,
}) // this will be used by RootScene to deactivate other cameras and activate this one
if (!this.userData.__isMainCamera) return
this.userData.__isMainCamera = false // or delete?
if (this.userData.__lastScale) {
this.scale.copy(this.userData.__lastScale)
delete this.userData.__lastScale
}
if (_refresh) {
this.refreshCameraControls(false)
}
this.setDirty({change: 'deactivateMain', ...options})
},
refreshUi: function(this: ICamera) {
// todo
this.uiConfig?.uiRefresh?.(true, 'postFrame', 1)
},
refreshTarget: function(this: ICamera, distanceFromTarget = 4, setDirty = true) {
if (this.controls?.enabled && this.controls.target) {
if (this.controls.target !== this.target) this.target.copy(this.controls.target)
} else {
// this.cameraObject.updateWorldMatrix(true, false)
this.getWorldDirection(this.target)
// .transformDirection(this.cameraObject.matrixWorldInverse)
// .multiplyScalar(distanceFromTarget).add(this._position)
.multiplyScalar(distanceFromTarget).add(this.getWorldPosition(new Vector3()))
// if (this.cameraObject.parent) this.cameraObject.parent.worldToLocal(this._target)
}
if (setDirty) this.setDirty({change: 'target'})
},
upgradeCamera: upgradeCamera,
copy: (superCopy: ICamera['copy']): ICamera['copy'] =>
function(this: ICamera, camera: ICamera | Camera, recursive?, distanceFromTarget?, worldSpace?, ...args): ICamera {
if (!camera.isCamera) {
console.error('ICamera.copy: camera is not a Camera', camera)
return this
}
superCopy.call(this, camera, recursive, ...args)
// moved to setView in ThreeViewer
// const worldPos = camera.getWorldPosition(this.position)
// camera.getWorldQuaternion(this.quaternion)
// if (this.parent) {
// this.position.copy(this.parent.worldToLocal(worldPos))
// this.quaternion.premultiply(this.parent.quaternion.clone().invert())
// }
if ((<ICamera>camera).target?.isVector3) this.target.copy((<ICamera>camera).target)
else {
const minDistance = (this.controls as any)?.minDistance ?? distanceFromTarget ?? 4
camera.getWorldDirection(this.target).multiplyScalar(minDistance).add(this.getWorldPosition(new Vector3()))
}
if (worldSpace) { // default = false
const worldPos = camera.getWorldPosition(this.position)
// this.getWorldQuaternion(this.quaternion) // todo: do if autoLookAtTarget is false
// todo up vector
if (this.parent) {
this.position.copy(this.parent.worldToLocal(worldPos))
// this.quaternion.premultiply(this.parent.quaternion.clone().invert())
}
}
this.updateMatrixWorld(true)
this.updateProjectionMatrix()
this.refreshAspect(false)
this.setDirty()
return this
},
}
function upgradeCamera(this: ICamera) {
if (!this.isCamera) {
console.error('Object is not a camera', this)
return
}
if (this.userData.__cameraSetup) return
this.userData.__cameraSetup = true
iObjectCommons.upgradeObject3D.call(this)
this.copy = iCameraCommons.copy(this.copy)
if (!this.target) this.target = new Vector3()
if (!this.refreshTarget) this.refreshTarget = iCameraCommons.refreshTarget
if (!this.activateMain) this.activateMain = iCameraCommons.activateMain
if (!this.deactivateMain) this.deactivateMain = iCameraCommons.deactivateMain
if (!this.refreshUi) this.refreshUi = iCameraCommons.refreshUi
if (!this.setDirty) this.setDirty = iCameraCommons.setDirty
// if (!this.controlsMode) this.controlsMode = ''
this.assetType = 'camera'
// todo uiconfig, anything else?
}