| @@ -110,8 +110,8 @@ export class AssimpJsPlugin extends AViewerPluginSync { | |||
| return | |||
| } | |||
| const initing = this.init() | |||
| const selected = this.exportSelected ? this._viewer.getPlugin(PickingPlugin)?.getSelectedObject() : undefined | |||
| object = object || selected || this._viewer.scene.modelRoot | |||
| const selected = this.exportSelected ? this._viewer.getPlugin(PickingPlugin)?.getSelectedObject<IObject3D>() : undefined | |||
| object = object || (selected?.isObject3D ? selected : this._viewer.scene.modelRoot) | |||
| // export to glb | |||
| const blob = await this._viewer.export(object, options) | |||
| @@ -2,7 +2,7 @@ import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {PickingPlugin} from '../interaction/PickingPlugin' | |||
| import {imageBitmapToBase64, makeColorSvgCircle, serialize} from 'ts-browser-helpers' | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| import {IMaterial, PhysicalMaterial} from '../../core' | |||
| import {IMaterial, IObject3D, PhysicalMaterial} from '../../core' | |||
| import {MaterialPreviewGenerator} from '../../three' | |||
| import {Color} from 'three' | |||
| @@ -160,7 +160,16 @@ export class MaterialConfiguratorBasePlugin extends AViewerPluginSync { | |||
| @serialize() | |||
| variations: MaterialVariations[] = [] | |||
| private _selectedMaterial = () => (this._picking?.getSelectedObject()?.material || undefined) as IMaterial | undefined | |||
| private _selectedMaterial = () => { | |||
| const selected = this._picking?.getSelectedObject() | |||
| if (!selected) return undefined | |||
| if ((selected as IMaterial).isMaterial) return selected as IMaterial | |||
| else { | |||
| const mat = ((selected as IObject3D)?.material || undefined) as IMaterial | undefined | |||
| if (Array.isArray(mat)) return mat[0] | |||
| return mat | |||
| } | |||
| } | |||
| uiConfig: UiObjectConfig = { | |||
| label: 'Material Configurator', | |||
| type: 'folder', | |||
| @@ -4,6 +4,7 @@ import {PickingPlugin} from '../interaction/PickingPlugin' | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| import {serialize} from 'ts-browser-helpers' | |||
| import {snapObject} from '../../three' | |||
| import {IObject3D} from '../../core' | |||
| /** | |||
| * Switch Node Plugin (Base) | |||
| @@ -131,8 +132,8 @@ export class SwitchNodeBasePlugin extends AViewerPluginSync { | |||
| @serialize() variations: ObjectSwitchNode[] = [] | |||
| protected _selectedSwitchNode = (): Object3D | undefined => { | |||
| const obj = this._picking?.getSelectedObject() // (?.material || undefined) as IMaterial | undefined | |||
| if (!obj) return undefined | |||
| const obj = this._picking?.getSelectedObject<IObject3D>() // (?.material || undefined) as IMaterial | undefined | |||
| if (!obj?.isObject3D) return undefined | |||
| const nodes = this.variations.map(v => v.name) | |||
| let found: Object3D | undefined = undefined | |||
| obj.traverseAncestors(a => { | |||
| @@ -77,11 +77,12 @@ export class AssetExporterPlugin extends AViewerPluginSync { | |||
| async exportSelected(options?: ExportAssetOptions, download = true) { | |||
| const selected = this._viewer?.getPlugin<PickingPlugin>('PickingPlugin')?.getSelectedObject() as any | |||
| if (!selected) { | |||
| alert('Nothing selected') | |||
| this._viewer?.dialog.alert('Export Selected: Nothing selected') | |||
| return | |||
| } | |||
| const name = selected.name || 'selected' | |||
| const blob = await this._viewer!.assetManager.exporter.exportObject(selected, options ?? this.exportOptions) | |||
| if (blob && download) await this._viewer?.exportBlob(blob, 'object.' + blob.ext) | |||
| if (blob && download) await this._viewer?.exportBlob(blob, name + '.' + blob.ext) | |||
| return blob | |||
| } | |||
| @@ -170,7 +170,7 @@ export class CanvasSnapshotPlugin extends AViewerPluginSync { | |||
| /** | |||
| * Only for {@link downloadSnapshot} and functions using that | |||
| */ | |||
| @uiConfig() | |||
| @uiConfig(undefined, {label: 'Options'}) | |||
| @serialize() | |||
| defaultOptions: CanvasSnapshotPluginOptions = { | |||
| waitForProgressive: true, | |||
| @@ -61,7 +61,8 @@ export abstract class SimplifyModifierPlugin extends AViewerPluginSync { | |||
| simplifyGeometries(geometry?: ValOrArr<IGeometry>, options?: SimplifyOptions) { | |||
| if (!geometry) { | |||
| const selected = this._pickingPlugin?.getSelectedObject() | |||
| const selected = this._pickingPlugin?.getSelectedObject<IObject3D>() | |||
| if (!selected?.isObject3D) return | |||
| const geom: IGeometry[] = [] | |||
| selected?.traverse((o) => { | |||
| if (o.geometry && !geom.includes(o.geometry)) geom.push(o.geometry) | |||
| @@ -84,7 +85,7 @@ export abstract class SimplifyModifierPlugin extends AViewerPluginSync { | |||
| disposeOnReplace = false, | |||
| }: SimplifyOptions = {}): IGeometry|undefined { | |||
| if (!geometry) { | |||
| const selected = this._pickingPlugin?.getSelectedObject() | |||
| const selected = this._pickingPlugin?.getSelectedObject<IObject3D>() | |||
| geometry = selected?.geometry | |||
| if (!geometry) return undefined | |||
| } | |||
| @@ -184,9 +185,9 @@ export abstract class SimplifyModifierPlugin extends AViewerPluginSync { | |||
| return | |||
| } | |||
| } | |||
| const selected = this._pickingPlugin?.getSelectedObject() | |||
| if (!selected) { | |||
| await this._viewer.dialog.alert('Simplify: Nothing Selected') | |||
| const selected = this._pickingPlugin?.getSelectedObject<IObject3D>() | |||
| if (!selected?.isObject3D) { | |||
| await this._viewer.dialog.alert('Simplify: No Object Selected') | |||
| return | |||
| } | |||
| let doAll = false | |||
| @@ -1,7 +1,7 @@ | |||
| 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 {bindToValue, BoxSelectionWidget, ObjectPicker, SelectionWidget} from '../../three' | |||
| import {IMaterial, IObject3D, IScene, ISceneEventMap} from '../../core' | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| import {FrameFadePlugin} from '../pipeline/FrameFadePlugin' | |||
| @@ -9,10 +9,7 @@ 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} | |||
| hoverObjectChanged: {object: IObject3D|null} | |||
| hitObject: {intersects: {selectedObject: IObject3D|null}} | |||
| export interface PickingPluginEventMap extends AViewerPluginEventMap, ObjectPickerEventMap{ | |||
| } | |||
| export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| @@ -41,6 +38,9 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| this.uiConfig && this.uiConfig.uiRefresh?.() | |||
| } | |||
| @bindToValue({obj: '_picker', key: 'selectionMode'}) | |||
| selectionMode: 'object' | 'material' = 'object' | |||
| @serialize() | |||
| autoFocus | |||
| @@ -54,10 +54,11 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| widgetEnabled = true | |||
| protected _widgetEnabledChange() { | |||
| if (this.widgetEnabled && this._picker?.selectedObject) | |||
| this._widget?.attach(this._picker.selectedObject) | |||
| if (!this._widget) return | |||
| if (this.widgetEnabled && (this._picker?.selectedObject as IObject3D)?.isObject3D) | |||
| this._widget.attach(this._picker!.selectedObject as IObject3D) | |||
| else | |||
| this._widget?.detach() | |||
| this._widget.detach() | |||
| this.uiConfig?.uiRefresh?.(true) | |||
| } | |||
| @@ -81,12 +82,12 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| this.dispatchEvent = this.dispatchEvent.bind(this) | |||
| } | |||
| getSelectedObject<T extends IObject3D = IObject3D>(): T|undefined { | |||
| getSelectedObject<T extends IObject3D|IMaterial = IObject3D|IMaterial>(): T|undefined { | |||
| if (this.isDisabled()) return | |||
| return this._picker?.selectedObject as T || undefined | |||
| } | |||
| setSelectedObject(object: IObject3D|undefined, focusCamera = false, trackUndo = true) { // todo: listen to object disposed | |||
| setSelectedObject(object: IObject3D|IMaterial|undefined, focusCamera = false, trackUndo = true) { // todo: listen to object disposed | |||
| const disabled = this.isDisabled() | |||
| if (disabled && !object) return | |||
| if (!this._picker) return | |||
| @@ -94,7 +95,7 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| this.autoFocus = false | |||
| this._picker.setSelected(object || null, trackUndo) | |||
| this.autoFocus = t | |||
| if (!disabled && object && (t || focusCamera)) this.focusObject(object) | |||
| if (!disabled && object && this.selectionMode === 'object' && (t || focusCamera)) this.focusObject(object as IObject3D) | |||
| } | |||
| onAdded(viewer: ThreeViewer): void { | |||
| @@ -121,6 +122,7 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| this._picker.addEventListener('selectedObjectChanged', this._selectedObjectChanged) | |||
| this._picker.addEventListener('hoverObjectChanged', this._hoverObjectChanged) | |||
| this._picker.addEventListener('hitObject', this._onObjectHit) | |||
| this._picker.addEventListener('selectionModeChanged', this._selectionModeChanged) | |||
| // on material drop on selected object | |||
| // viewer.scene.addEventListener('addSceneObject', async(e) => { | |||
| @@ -172,6 +174,7 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| this._picker.removeEventListener('selectedObjectChanged', this._selectedObjectChanged) | |||
| this._picker.removeEventListener('hoverObjectChanged', this._hoverObjectChanged) | |||
| this._picker.removeEventListener('hitObject', this._onObjectHit) | |||
| this._picker.removeEventListener('selectionModeChanged', this._selectionModeChanged) | |||
| this._picker.dispose() | |||
| this._picker.undoManager = undefined // because setting above | |||
| this._picker = undefined | |||
| @@ -197,11 +200,13 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| } | |||
| private _checkSelectedInScene() { | |||
| if (this.isDisabled()) return | |||
| if (this.isDisabled() || !this._viewer) return | |||
| const s = this.getSelectedObject() | |||
| if (!s || !(s as IObject3D).isObject3D) return // ignoring checking for materials in scene | |||
| let inScene = false | |||
| s?.traverseAncestors((o) => { | |||
| if (o === this._viewer?.scene) inScene = true | |||
| ;(s as IObject3D).traverseAncestors((o) => { | |||
| if (inScene || o !== this._viewer!.scene) return | |||
| inScene = true | |||
| }) | |||
| if (!inScene) this.setSelectedObject(undefined, false, false) | |||
| } | |||
| @@ -218,7 +223,7 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| private _onObjectSelectEvent: EventListener2<'select', ISceneEventMap, IScene> = (e)=>{ | |||
| if (e.source === PickingPlugin.PluginType) return | |||
| if (e.object === undefined && e.value === undefined) console.error('e.object or e.value must be set for picking, can be null to unselect') | |||
| if (e.object === undefined && e.value === undefined) console.error('PickingPlugin - Error handling object/material `select` event `e.object` or `e.value` must be set for picking, `value` can be null to unselect') | |||
| else this.setSelectedObject(e.object || e.value, this.autoFocus || e.focusCamera, true) | |||
| } | |||
| @@ -240,38 +245,54 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| const ui = this.uiConfig | |||
| ui.children = [...this._uiConfigChildren] | |||
| 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}) | |||
| if ((selected as IObject3D).isObject3D) { | |||
| const obj = (selected as IObject3D) | |||
| ui.children.push( | |||
| { | |||
| type: 'button', | |||
| label: 'Focus', | |||
| value: () => { | |||
| if (!obj.isObject3D) return | |||
| // const selected = this.getSelectedObject() | |||
| if (selected.assetType && obj.parentRoot) // todo also check if acceptChildEvents is set on some parent? | |||
| obj.dispatchEvent({ | |||
| type: 'select', | |||
| ui: true, | |||
| object: obj, | |||
| bubbleToParent: true, | |||
| focusCamera: true, | |||
| }) | |||
| else | |||
| this.setSelectedObject(parent, false) | |||
| } | |||
| this.setSelectedObject(obj, true) | |||
| }, | |||
| }, | |||
| }, | |||
| ) | |||
| { | |||
| type: 'button', | |||
| label: 'Select Parent', | |||
| hidden: () => !obj.parent, | |||
| value: () => { | |||
| if (!obj.isObject3D) return | |||
| const parent = obj.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] | |||
| const mats = (selected as IObject3D).materials ?? [(selected as IObject3D).material as IMaterial] | |||
| for (const m of mats) { | |||
| c = m?.uiConfig | |||
| if (c) ui.children.push(c) | |||
| @@ -284,7 +305,7 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| const widget = this._widget | |||
| if (widget && this.widgetEnabled) { | |||
| if (selected) widget.attach(selected) | |||
| if ((selected as IObject3D)?.isObject3D) widget.attach((selected as IObject3D)) | |||
| else widget.detach() | |||
| } | |||
| @@ -292,20 +313,21 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| this._viewer.setDirty() | |||
| if (this.autoFocus) { | |||
| if (this.autoFocus && this.selectionMode === 'object') { | |||
| // this._viewer.resetCamera({rootObject: selected, centerOffset: new Vector3(4, 4, 4)}) | |||
| this.focusObject(selected) | |||
| this.focusObject(selected as IObject3D | undefined) | |||
| } | |||
| } | |||
| private _hoverObjectChanged = (e: any) => { | |||
| if (!this._viewer) return | |||
| this.dispatchEvent(e) | |||
| const selected = this._picker?.hoverObject || undefined | |||
| const widget = this._hoverWidget | |||
| if (widget && this.widgetEnabled) { | |||
| if (selected) widget.attach(selected) | |||
| if ((selected as IObject3D)?.isObject3D) widget.attach((selected as IObject3D)) | |||
| else widget.detach() | |||
| } | |||
| @@ -313,9 +335,9 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| this._viewer?.setDirty() | |||
| if (this.autoFocusHover) { | |||
| if (this.autoFocusHover && this.selectionMode === 'object') { | |||
| // this._viewer?.resetCamera({rootObject: selected, centerOffset: new Vector3(4, 4, 4)}) | |||
| this.focusObject(selected) | |||
| this.focusObject(selected as IObject3D | undefined) | |||
| } | |||
| @@ -329,9 +351,15 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| } | |||
| this.dispatchEvent(e) | |||
| } | |||
| private _selectionModeChanged = (e: any)=>{ | |||
| if (!this._viewer) return | |||
| this.dispatchEvent(e) | |||
| if (this.isDisabled()) return | |||
| this.uiConfig?.uiRefresh?.(true, 'postFrame', 1) | |||
| } | |||
| public async focusObject(selected?: Object3D) { | |||
| this._viewer?.fitToView(selected, 1.25, 1000, 'easeOut') | |||
| public async focusObject(selected?: Object3D|null) { | |||
| this._viewer?.fitToView(selected ?? undefined, 1.25, 1000, 'easeOut') | |||
| } | |||
| private _uiConfigChildren: UiObjectConfig[] = [ | |||
| @@ -346,6 +374,12 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> { | |||
| property: [this, 'hoverEnabled'], | |||
| onChange: ()=>this.uiConfig.uiRefresh?.(true), // for autoFocusHover | |||
| }, | |||
| // { | |||
| // label: 'Selection Mode', | |||
| // type: 'dropdown', | |||
| // children: ['object', 'material'].map(v=>({label: v, value: v})), | |||
| // onChange: ()=>this.uiConfig.uiRefresh?.(true), | |||
| // }, | |||
| { | |||
| label: 'Auto Focus', | |||
| type: 'checkbox', | |||
| @@ -4,7 +4,7 @@ import {OrbitControls3, TransformControls2} from '../../three' | |||
| import {PickingPlugin} from './PickingPlugin' | |||
| import {JSUndoManager, onChange} from 'ts-browser-helpers' | |||
| import {TransformControls} from '../../three/controls/TransformControls' | |||
| import {UnlitLineMaterial, UnlitMaterial} from '../../core' | |||
| import {IObject3D, UnlitLineMaterial, UnlitMaterial} from '../../core' | |||
| import {Euler, Object3D, Vector3} from 'three' | |||
| import type {UndoManagerPlugin} from './UndoManagerPlugin' | |||
| @@ -29,7 +29,7 @@ export class TransformControlsPlugin extends AViewerPluginSync { | |||
| this._pickingWidgetDisabled = false | |||
| } | |||
| if (this.transformControls) { | |||
| if (enabled && picking.getSelectedObject()) this.transformControls.attach(picking.getSelectedObject()!) | |||
| if (enabled && picking.getSelectedObject<IObject3D>()?.isObject3D) this.transformControls.attach(picking.getSelectedObject<IObject3D>()!) | |||
| else this.transformControls.detach() | |||
| } | |||
| this._viewer.setDirty() | |||
| @@ -1,17 +1,21 @@ | |||
| import {EventDispatcher, Intersection, Raycaster, Vector2} from 'three' | |||
| import {JSUndoManager, now} from 'ts-browser-helpers' | |||
| import {ICamera, IObject3D} from '../../core' | |||
| import {JSUndoManager, now, onChangeDispatchEvent} from 'ts-browser-helpers' | |||
| import {ICamera, IMaterial, IObject3D} from '../../core' | |||
| export interface ObjectPickerEventMap{ | |||
| hoverObjectChanged: {object: IObject3D | null} | |||
| selectedObjectChanged: {object: IObject3D | null} | |||
| hoverObjectChanged: {object: IObject3D | null, material: IMaterial | null, value: IObject3D | IMaterial | null}, | |||
| selectedObjectChanged: {object: IObject3D | null, material: IMaterial | null, value: IObject3D | IMaterial | null}, | |||
| hitObject: {time: number, intersects: {selectedObject: IObject3D | null, intersect: Intersection<IObject3D> | null, intersects: Intersection<IObject3D>[]}} | |||
| selectionModeChanged: {detail: {key: 'selectionMode', value: 'object' | 'material', oldValue: 'object' | 'material'}} | |||
| } | |||
| export class ObjectPicker extends EventDispatcher<ObjectPickerEventMap> { | |||
| private _firstHit: IObject3D | undefined | |||
| hoverEnabled = false | |||
| @onChangeDispatchEvent('selectionModeChanged') | |||
| selectionMode: 'object' | 'material' = 'object' | |||
| /** | |||
| * Time threshold for a pointer click event | |||
| */ | |||
| @@ -31,8 +35,8 @@ export class ObjectPicker extends EventDispatcher<ObjectPickerEventMap> { | |||
| public selectionCondition: (o: IObject3D) => boolean | |||
| public raycaster: Raycaster | |||
| public mouse: Vector2 | |||
| private _selected: IObject3D[] | |||
| private _hovering: IObject3D[] | |||
| private _selected: IObject3D[] | IMaterial[] | |||
| private _hovering: IObject3D[] | IMaterial[] | |||
| public cursorStyles: {default: string; down: string} | |||
| public domElement: HTMLElement | |||
| constructor(root: IObject3D, domElement: HTMLElement, camera: ICamera, selectionCondition?: (o:IObject3D)=>boolean) { | |||
| @@ -74,7 +78,7 @@ export class ObjectPicker extends EventDispatcher<ObjectPickerEventMap> { | |||
| } | |||
| dispose() { | |||
| this.selectedObject = null | |||
| this.setSelected(null) | |||
| this.hoverObject = null | |||
| this.domElement.removeEventListener('pointermove', this._onPointerMove) | |||
| @@ -94,33 +98,52 @@ export class ObjectPicker extends EventDispatcher<ObjectPickerEventMap> { | |||
| this._camera = value | |||
| } | |||
| get selectedObject(): IObject3D | null { | |||
| get selectedObject(): IObject3D | IMaterial | null { | |||
| return this._selected.length > 0 ? this._selected[0] : null | |||
| } | |||
| set selectedObject(object) { | |||
| this.setSelected(object) | |||
| } | |||
| // set selectedObject(object) { | |||
| // this.setSelected(object) | |||
| // } | |||
| setSelected(object: IObject3D|null, record = true) { | |||
| setSelected(object: IObject3D | IMaterial | null, record = true) { | |||
| if ((object as IObject3D)?.isObject3D && this.selectionMode === 'material' || | |||
| (object as IMaterial)?.isMaterial && this.selectionMode === 'object') { | |||
| this.selectionMode = (object as IMaterial)?.isMaterial ? 'material' : 'object' | |||
| } | |||
| if (!this._selected.length && !object || this._selected.length === 1 && this._selected[0] === object) return | |||
| const current = [...this._selected] | |||
| this._selected = object ? Array.isArray(object) ? [...object] : [object] : [] | |||
| this.dispatchEvent({type: 'selectedObjectChanged', object: this.selectedObject}) | |||
| const obj = this.selectedObject | |||
| this.dispatchEvent({ | |||
| type: 'selectedObjectChanged', | |||
| object: (obj as IObject3D)?.isObject3D ? (obj as IObject3D) : null, | |||
| material: (obj as IMaterial)?.isMaterial ? (obj as IMaterial) : null, | |||
| value: obj, | |||
| }) | |||
| record && this.undoManager?.record({ | |||
| undo: () => this.setSelected(current.length ? current[0] : null, false), | |||
| redo: () => this.setSelected(object, false), | |||
| }) | |||
| } | |||
| get hoverObject(): IObject3D | null { | |||
| get hoverObject(): IObject3D | IMaterial | null { | |||
| return this._hovering.length > 0 ? this._hovering[0] : null | |||
| } | |||
| set hoverObject(object: IObject3D | IObject3D[] | null) { | |||
| set hoverObject(object: IObject3D | IObject3D[] | IMaterial | IMaterial[] | null) { | |||
| if (!this._hovering.length && !object || this._hovering.length === 1 && this._hovering[0] === object) return | |||
| this._hovering = object ? Array.isArray(object) ? [...object] : [object] : [] | |||
| this.dispatchEvent({type: 'hoverObjectChanged', object: this.hoverObject}) | |||
| this._hovering = (object ? Array.isArray(object) ? [...object] : [object] : []) as (IObject3D[] | IMaterial[]) | |||
| const obj = this.hoverObject | |||
| this.dispatchEvent({ | |||
| type: 'hoverObjectChanged', | |||
| object: (obj as IObject3D)?.isObject3D ? (obj as IObject3D) : null, | |||
| material: (obj as IMaterial)?.isMaterial ? (obj as IMaterial) : null, | |||
| value: obj, | |||
| }) | |||
| } | |||
| get time() { | |||
| @@ -202,7 +225,12 @@ export class ObjectPicker extends EventDispatcher<ObjectPickerEventMap> { | |||
| const intersects = this.checkIntersection() | |||
| if (intersects) this.dispatchEvent({type: 'hitObject', time: this._mouseUpTime, intersects}) | |||
| else this.dispatchEvent({type: 'hitObject', time: this._mouseUpTime, intersects: {selectedObject: null, intersect: null, intersects: []}}) | |||
| this.selectedObject = intersects?.selectedObject || null | |||
| let obj: IObject3D|IMaterial|null = intersects?.selectedObject || null | |||
| if (this.selectionMode === 'material' && obj && obj.material) { | |||
| obj = Array.isArray(obj.material) ? obj.material[0] : obj.material | |||
| } | |||
| this.setSelected(obj) | |||
| } | |||
| checkIntersection() { | |||