| if (!(camera as PerspectiveCamera).isPerspectiveCamera || !camera.parent) { | if (!(camera as PerspectiveCamera).isPerspectiveCamera || !camera.parent) { | ||||
| iCameraCommons.upgradeCamera.call(camera) | iCameraCommons.upgradeCamera.call(camera) | ||||
| } else { | } else { | ||||
| const newCamera: ICamera = (camera as any).iCamera ?? new PerspectiveCamera2('', this.viewer.canvas).copy(camera) | |||||
| const newCamera: ICamera = (camera as any).iCamera ?? new PerspectiveCamera2('', this.viewer.canvas) | |||||
| if (camera === newCamera) continue | if (camera === newCamera) continue | ||||
| camera.parent.children.splice(camera.parent.children.indexOf(camera), 1, newCamera) | |||||
| newCamera.parent = camera.parent as any | |||||
| newCamera.copy(camera as any) | |||||
| camera.parent = null | |||||
| ;(newCamera as any).uuid = camera.uuid | ;(newCamera as any).uuid = camera.uuid | ||||
| newCamera.userData.uuid = camera.uuid | newCamera.userData.uuid = camera.uuid | ||||
| ;(camera as any).iCamera = newCamera | ;(camera as any).iCamera = newCamera | ||||
| camera.parent.children.splice(camera.parent.children.indexOf(camera), 1, newCamera) | |||||
| // console.log('replacing camera', camera, newCamera) | |||||
| } | } | ||||
| } | } | ||||
| __lastScale?: Vector3, | __lastScale?: Vector3, | ||||
| __isMainCamera?: boolean, | __isMainCamera?: boolean, | ||||
| __cameraSetup?: boolean, | |||||
| // [key: string]: any // commented for noe | // [key: string]: any // commented for noe | ||||
| } | } |
| import {Camera, Event, IUniform, Object3D, PerspectiveCamera, Vector3} from 'three' | import {Camera, Event, IUniform, Object3D, PerspectiveCamera, Vector3} from 'three' | ||||
| import {generateUiConfig, uiInput, UiObjectConfig, uiSlider, uiVector} from 'uiconfig.js' | |||||
| import {generateUiConfig, uiInput, UiObjectConfig, uiSlider, uiToggle, uiVector} from 'uiconfig.js' | |||||
| import {onChange, onChange2, onChange3, serialize} from 'ts-browser-helpers' | import {onChange, onChange2, onChange3, serialize} from 'ts-browser-helpers' | ||||
| import type {ICamera, ICameraEvent, ICameraUserData, TCameraControlsMode} from '../ICamera' | import type {ICamera, ICameraEvent, ICameraUserData, TCameraControlsMode} from '../ICamera' | ||||
| import {ICameraSetDirtyOptions} from '../ICamera' | import {ICameraSetDirtyOptions} from '../ICamera' | ||||
| import {ThreeSerialization} from '../../utils' | import {ThreeSerialization} from '../../utils' | ||||
| import {iCameraCommons} from '../object/iCameraCommons' | import {iCameraCommons} from '../object/iCameraCommons' | ||||
| import {bindToValue} from '../../three/utils/decorators' | import {bindToValue} from '../../three/utils/decorators' | ||||
| import {makeICameraCommonUiConfig} from '../object/IObjectUi' | |||||
| // todo: maybe change domElement to some wrapper/base class of viewer | // todo: maybe change domElement to some wrapper/base class of viewer | ||||
| export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | ||||
| @uiVector('Target') | @uiVector('Target') | ||||
| @serialize() readonly target: Vector3 = new Vector3(0, 0, 0) | @serialize() readonly target: Vector3 = new Vector3(0, 0, 0) | ||||
| /** | |||||
| * Automatically manage aspect ratio based on window/canvas size. | |||||
| * Defaults to `true` if {@link domElement}(canvas) is set. | |||||
| */ | |||||
| @serialize() | @serialize() | ||||
| @onChange2(PerspectiveCamera2.prototype.refreshAspect) | @onChange2(PerspectiveCamera2.prototype.refreshAspect) | ||||
| @uiToggle('Auto Aspect') | |||||
| autoAspect: boolean | autoAspect: boolean | ||||
| /** | /** | ||||
| children: ['', 'orbit', ...this._controlsCtors.keys()].map(v=>({label: v === '' ? 'none' : v, value:v})), | children: ['', 'orbit', ...this._controlsCtors.keys()].map(v=>({label: v === '' ? 'none' : v, value:v})), | ||||
| onChange: () => this.refreshCameraControls(), | onChange: () => this.refreshCameraControls(), | ||||
| }), | }), | ||||
| ()=>makeICameraCommonUiConfig.call(this, this.uiConfig), | |||||
| ] | ] | ||||
| uiConfig: UiObjectConfig = { | uiConfig: UiObjectConfig = { |
| import {ICamera} from '../ICamera' | import {ICamera} from '../ICamera' | ||||
| import {Vector3} from 'three' | import {Vector3} from 'three' | ||||
| export function makeICameraCommonUiConfig(this: IObject3D, config: UiObjectConfig): UiObjectConfig[] { | |||||
| return [ | |||||
| { | |||||
| type: 'button', | |||||
| label: 'Set View', | |||||
| value: ()=>{ | |||||
| // todo: call setView on the camera, which will dispatch the event | |||||
| (this as ICamera).dispatchEvent({type: 'setView', ui: true, camera: this as ICamera}) | |||||
| config.uiRefresh?.(true, 'postFrame') | |||||
| console.log('set view', this) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| type: 'button', | |||||
| label: 'Activate main', | |||||
| hidden: ()=>(this as ICamera)?.isMainCamera, | |||||
| value: ()=>{ | |||||
| // todo: call activateMain on the camera, which will dispatch the event | |||||
| (this as ICamera).dispatchEvent({type: 'activateMain', ui: true, camera: this as ICamera}) | |||||
| config.uiRefresh?.(true, 'postFrame') | |||||
| }, | |||||
| }, | |||||
| { | |||||
| type: 'button', | |||||
| label: 'Deactivate main', | |||||
| hidden: ()=>!(this as ICamera)?.isMainCamera, | |||||
| value: ()=>{ | |||||
| // todo: call activateMain on the camera, which will dispatch the event | |||||
| (this as ICamera).dispatchEvent({type: 'activateMain', ui: true, camera: undefined}) | |||||
| config.uiRefresh?.(true, 'postFrame') | |||||
| }, | |||||
| }, | |||||
| { | |||||
| type: 'checkbox', | |||||
| label: 'Auto LookAt Target', | |||||
| getValue: ()=>(this as ICamera).userData.autoLookAtTarget ?? false, | |||||
| setValue: (v)=>{ | |||||
| (this as ICamera).userData.autoLookAtTarget = v | |||||
| config.uiRefresh?.(true, 'postFrame') | |||||
| }, | |||||
| }, | |||||
| ] | |||||
| } | |||||
| export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjectConfig { | export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjectConfig { | ||||
| if (!this) return {} | if (!this) return {} | ||||
| if (this.uiConfig) return this.uiConfig | if (this.uiConfig) return this.uiConfig | ||||
| } | } | ||||
| // todo: if we are replacing all the cameras in the scene, is this even required? | // todo: if we are replacing all the cameras in the scene, is this even required? | ||||
| if (this.isCamera) { | if (this.isCamera) { | ||||
| // todo: move to make camera ui function? | |||||
| const ui: UiObjectConfig[] = [ | |||||
| { | |||||
| type: 'button', | |||||
| label: 'Set View', | |||||
| value: ()=>{ | |||||
| // todo: call setView on the camera, which will dispatch the event | |||||
| (this as ICamera).dispatchEvent({type: 'setView', ui: true, camera: this as ICamera}) | |||||
| config.uiRefresh?.(true, 'postFrame') | |||||
| }, | |||||
| }, | |||||
| { | |||||
| type: 'button', | |||||
| label: 'Activate main', | |||||
| hidden: ()=>(this as ICamera)?.isMainCamera, | |||||
| value: ()=>{ | |||||
| // todo: call activateMain on the camera, which will dispatch the event | |||||
| (this as ICamera).dispatchEvent({type: 'activateMain', ui: true, camera: this as ICamera}) | |||||
| config.uiRefresh?.(true, 'postFrame') | |||||
| }, | |||||
| }, | |||||
| { | |||||
| type: 'button', | |||||
| label: 'Deactivate main', | |||||
| hidden: ()=>!(this as ICamera)?.isMainCamera, | |||||
| value: ()=>{ | |||||
| // todo: call activateMain on the camera, which will dispatch the event | |||||
| (this as ICamera).dispatchEvent({type: 'activateMain', ui: true, camera: undefined}) | |||||
| config.uiRefresh?.(true, 'postFrame') | |||||
| }, | |||||
| }, | |||||
| { | |||||
| type: 'checkbox', | |||||
| label: 'Auto LookAt Target', | |||||
| getValue: ()=>(this as ICamera).userData.autoLookAtTarget ?? false, | |||||
| setValue: (v)=>{ | |||||
| (this as ICamera).userData.autoLookAtTarget = v | |||||
| config.uiRefresh?.(true, 'postFrame') | |||||
| }, | |||||
| }, | |||||
| ] | |||||
| const ui: UiObjectConfig[] = makeICameraCommonUiConfig.call(this as ICamera, config) | |||||
| ;(config.children as UiObjectConfig[]).push(...ui) | ;(config.children as UiObjectConfig[]).push(...ui) | ||||
| } | } | ||||
| cam.removeEventListener('cameraUpdate', this._mainCameraUpdate) | cam.removeEventListener('cameraUpdate', this._mainCameraUpdate) | ||||
| } | } | ||||
| if (camera) { | if (camera) { | ||||
| camera.activateMain(undefined, true) | |||||
| camera.addEventListener('cameraUpdate', this._mainCameraUpdate) | |||||
| this._mainCamera = camera | this._mainCamera = camera | ||||
| camera.addEventListener('cameraUpdate', this._mainCameraUpdate) | |||||
| camera.activateMain(undefined, true) | |||||
| } else { | } else { | ||||
| this._mainCamera = null | this._mainCamera = null | ||||
| } | } |
| return this | return this | ||||
| } | } | ||||
| superCopy.call(this, camera, recursive, ...args) | superCopy.call(this, camera, recursive, ...args) | ||||
| this.position.copy(this.worldToLocal(camera.getWorldPosition(new Vector3()))) | |||||
| // 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) | if ((<ICamera>camera).target?.isVector3) this.target.copy((<ICamera>camera).target) | ||||
| else { | else { | ||||
| const minDistance = (this.controls as any).minDistance ?? distanceFromTarget ?? 4 | |||||
| const minDistance = (this.controls as any)?.minDistance ?? distanceFromTarget ?? 4 | |||||
| camera.getWorldDirection(this.target).multiplyScalar(minDistance).add(this.getWorldPosition(new Vector3())) | camera.getWorldDirection(this.target).multiplyScalar(minDistance).add(this.getWorldPosition(new Vector3())) | ||||
| } | } | ||||
| this.setDirty() | |||||
| this.updateMatrixWorld(true) | |||||
| this.updateProjectionMatrix() | |||||
| this.refreshAspect(true) | |||||
| return this | return this | ||||
| }, | }, | ||||
| } | } | ||||
| function upgradeCamera(this: ICamera) { | function upgradeCamera(this: ICamera) { | ||||
| if (this.assetType === 'camera') return // already upgraded | |||||
| if (!this.isCamera) { | if (!this.isCamera) { | ||||
| console.error('Object is not a camera', this) | console.error('Object is not a camera', this) | ||||
| return | return | ||||
| } | } | ||||
| if (this.userData.__cameraSetup) return | |||||
| this.userData.__cameraSetup = true | |||||
| iObjectCommons.upgradeObject3D.call(this) | iObjectCommons.upgradeObject3D.call(this) | ||||
| this.copy = iCameraCommons.copy(this.copy) | this.copy = iCameraCommons.copy(this.copy) | ||||
| if (!this.target) this.target = new Vector3() | if (!this.target) this.target = new Vector3() |
| function(this: IObject3D, source: IObject3D, ...args): IObject3D { | function(this: IObject3D, source: IObject3D, ...args): IObject3D { | ||||
| const userData = source.userData | const userData = source.userData | ||||
| source.userData = {} | source.userData = {} | ||||
| const t: any = superCopy.call(this, source, ...args) | |||||
| const selfUserData = this.userData | |||||
| superCopy.call(this, source, ...args) | |||||
| this.userData = selfUserData | |||||
| source.userData = userData | source.userData = userData | ||||
| copyObject3DUserData(this.userData, source) // todo: do same for object.toJSON() | |||||
| return t | |||||
| copyObject3DUserData(this.userData, source.userData) // todo: do same for object.toJSON() | |||||
| return this | |||||
| }, | }, | ||||
| add: (superAdd: IObject3D['add']): IObject3D['add'] => | add: (superAdd: IObject3D['add']): IObject3D['add'] => | ||||
| function(this: IObject3D, ...args): IObject3D { | function(this: IObject3D, ...args): IObject3D { |
| return | return | ||||
| } | } | ||||
| this._scene.mainCamera.copy(event.camera) | this._scene.mainCamera.copy(event.camera) | ||||
| const worldPos = event.camera.getWorldPosition(this._scene.mainCamera.position) | |||||
| // camera.getWorldQuaternion(this.quaternion) // todo: do if autoLookAtTarget is false | |||||
| if (this._scene.mainCamera.parent) { | |||||
| this._scene.mainCamera.position.copy(this._scene.mainCamera.parent.worldToLocal(worldPos)) | |||||
| // this.quaternion.premultiply(this.parent.quaternion.clone().invert()) | |||||
| } | |||||
| this._scene.mainCamera.setDirty() | |||||
| } else if (event.type === 'activateMain') | } else if (event.type === 'activateMain') | ||||
| this._scene.mainCamera = event.camera || undefined // event.camera should have been upgraded when added to the scene. | this._scene.mainCamera = event.camera || undefined // event.camera should have been upgraded when added to the scene. | ||||
| } | } |