| @@ -5,7 +5,7 @@ export interface ICameraControlsEventMap { | |||
| change: object | |||
| } | |||
| export interface ICameraControls<TE extends ICameraControlsEventMap = ICameraControlsEventMap> extends IUiConfigContainer<void, 'panel'>, EventDispatcher<TE> { | |||
| export interface ICameraControls<TE extends ICameraControlsEventMap = ICameraControlsEventMap> extends IUiConfigContainer<void, 'panel'|'folder'>, EventDispatcher<TE> { | |||
| object: Object3D | |||
| enabled: boolean | |||
| domElement?: HTMLElement | Document; | |||
| @@ -80,24 +80,25 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| this.setDirty?.({uiChangeEvent: e, refreshScene: true, refreshUi: true, change: 'visible'}) | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Pick/Focus', // todo: move to the plugin that does the picking | |||
| value: ()=>{ | |||
| this.dispatchEvent({type: 'select', ui: true, object: this, bubbleToParent: true, focusCamera: true}) | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Pick Parent', // todo: move to the plugin that does the picking | |||
| hidden: ()=>!this.parent, | |||
| value: ()=>{ | |||
| const parent = this.parent | |||
| if (parent) { | |||
| parent.dispatchEvent({type: 'select', ui: true, bubbleToParent: true, object: parent}) | |||
| } | |||
| }, | |||
| }, | |||
| // moved to PickingPlugin | |||
| // { | |||
| // type: 'button', | |||
| // label: 'Pick/Focus', // todo: move to the plugin that does the picking | |||
| // value: ()=>{ | |||
| // this.dispatchEvent({type: 'select', ui: true, object: this, bubbleToParent: true, focusCamera: true}) | |||
| // }, | |||
| // }, | |||
| // { | |||
| // type: 'button', | |||
| // label: 'Pick Parent', // todo: move to the plugin that does the picking | |||
| // hidden: ()=>!this.parent, | |||
| // value: ()=>{ | |||
| // const parent = this.parent | |||
| // if (parent) { | |||
| // parent.dispatchEvent({type: 'select', ui: true, bubbleToParent: true, object: parent}) | |||
| // } | |||
| // }, | |||
| // }, | |||
| { | |||
| type: 'input', | |||
| label: 'Name', | |||
| @@ -10,7 +10,7 @@ export const iCameraCommons = { | |||
| // this.controls.update() // this should be done automatically postFrame | |||
| } | |||
| // if (!this.controls || !this.controls.enabled) { | |||
| if (this.userData.autoLookAtTarget) { | |||
| else if (this.userData.autoLookAtTarget) { | |||
| this.lookAt(this.target) | |||
| } | |||
| // } | |||
| @@ -1,5 +1,6 @@ | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {TControlsCtor} from '../../core' | |||
| import {IScene, ISceneEventMap, TControlsCtor} from '../../core' | |||
| import {Event2, EventListener2} from 'three' | |||
| export abstract class ACameraControlsPlugin extends AViewerPluginSync { | |||
| readonly enabled = true | |||
| @@ -9,17 +10,19 @@ export abstract class ACameraControlsPlugin extends AViewerPluginSync { | |||
| onAdded(viewer: ThreeViewer): void { | |||
| super.onAdded(viewer) | |||
| this._cameraChanged({camera: viewer.scene.mainCamera}) | |||
| // @ts-expect-error hack | |||
| this._cameraChanged({camera: viewer.scene.mainCamera, lastCamera: undefined}) | |||
| viewer.scene.addEventListener('mainCameraChange', this._cameraChanged) | |||
| } | |||
| onRemove(viewer: ThreeViewer): void { | |||
| this._cameraChanged({lastCamera: viewer.scene.mainCamera}) | |||
| // @ts-expect-error hack | |||
| this._cameraChanged({lastCamera: viewer.scene.mainCamera, camera: undefined}) | |||
| viewer.scene.removeEventListener('mainCameraChange', this._cameraChanged) | |||
| super.onRemove(viewer) | |||
| } | |||
| private _cameraChanged = (e: any) => { | |||
| private _cameraChanged: EventListener2<'mainCameraChange', ISceneEventMap, IScene> = (e: Partial<Event2<'mainCameraChange', ISceneEventMap, IScene>>) => { | |||
| e.lastCamera?.removeControlsCtor?.(this.controlsKey) | |||
| e.camera?.setControlsCtor?.(this.controlsKey, this._controlsCtor) | |||
| } | |||
| @@ -2,11 +2,12 @@ import {EventListener2, Object3D} from 'three' | |||
| import {Class, onChange, serialize} from 'ts-browser-helpers' | |||
| import {AViewerPluginEventMap, AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {BoxSelectionWidget, ObjectPicker, SelectionWidget} from '../../three' | |||
| import {IObject3D, IScene, ISceneEventMap} from '../../core' | |||
| import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js' | |||
| import {IMaterial, IObject3D, IScene, ISceneEventMap} from '../../core' | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| import {FrameFadePlugin} from '../pipeline/FrameFadePlugin' | |||
| import {type UndoManagerPlugin} from './UndoManagerPlugin' | |||
| import {ObjectPickerEventMap} from '../../three/utils/ObjectPicker' | |||
| import {CameraViewPlugin} from '../animation/CameraViewPlugin' | |||
| export interface PickingPluginEventMap extends AViewerPluginEventMap{ | |||
| selectedObjectChanged: {object: IObject3D|null} | |||
| @@ -29,6 +30,8 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| private _hoverWidget?: SelectionWidget | |||
| private _pickUi: boolean | |||
| dependencies = [CameraViewPlugin] | |||
| get hoverEnabled() { | |||
| return this._picker?.hoverEnabled ?? false | |||
| } | |||
| @@ -217,10 +220,48 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| this._viewer.scene.autoNearFarEnabled = !selected // for widgets etc, this can be removed when they are rendered in a separate pass | |||
| if (this._pickUi) { | |||
| const sUiConfig = (selected as IUiConfigContainer)?.uiConfig | |||
| const ui = this.uiConfig | |||
| ui.children = [...this._uiConfigChildren] | |||
| if (sUiConfig) ui.children.push(sUiConfig) | |||
| if (selected) { | |||
| ui.children.push( | |||
| { | |||
| type: 'button', | |||
| label: 'Focus', | |||
| value: ()=>{ | |||
| // const selected = this.getSelectedObject() | |||
| if (selected.assetType && selected.parentRoot) // todo also check if acceptChildEvents is set on some parent? | |||
| selected.dispatchEvent({type: 'select', ui: true, object: selected, bubbleToParent: true, focusCamera: true}) | |||
| else | |||
| this.setSelectedObject(selected, true) | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Select Parent', | |||
| hidden: ()=>!selected.parent, | |||
| value: ()=>{ | |||
| const parent = selected.parent | |||
| if (parent) { | |||
| if (parent.assetType && parent.parentRoot) // todo also check if acceptChildEvents is set on some parent? | |||
| parent.dispatchEvent({type: 'select', ui: true, bubbleToParent: true, object: parent}) | |||
| else | |||
| this.setSelectedObject(parent, false) | |||
| } | |||
| }, | |||
| }, | |||
| ) | |||
| let c = selected.uiConfig | |||
| if (c) ui.children.push(c) | |||
| else { | |||
| // check materials | |||
| const mats = selected.materials ?? [selected.material as IMaterial] | |||
| for (const m of mats) { | |||
| c = m?.uiConfig | |||
| if (c) ui.children.push(c) | |||
| } | |||
| } | |||
| } | |||
| ui.uiRefresh?.() | |||
| } | |||
| @@ -34,10 +34,10 @@ export class OrbitControls3 extends OrbitControls implements IUiConfigContainer, | |||
| @uiInput() @serialize() autoPushTarget = false | |||
| @uiInput() @serialize() autoPullTarget = false | |||
| @uiInput() @serialize() minDistance = 0.35 | |||
| @uiInput() @serialize() maxDistance = 1000 | |||
| @uiInput() @serialize() maxDistance = 1e6 // should be Infinity but this breaks the UI | |||
| @uiInput() @serialize() minZoom = 0.01 | |||
| @uiInput() @serialize() maxZoom = 1000 | |||
| @uiInput() @serialize() maxZoom = 1e6 // should be Infinity but this breaks the UI | |||
| @uiInput() @serialize() minPolarAngle = 0 | |||
| @uiInput() @serialize() maxPolarAngle = Math.PI | |||