UNPKG

threepipe

Version:

A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.

313 lines 12.8 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { onChange, serialize } from 'ts-browser-helpers'; import { AViewerPluginSync } from '../../viewer'; import { BoxSelectionWidget, ObjectPicker } from '../../three'; import { FrameFadePlugin } from '../pipeline/FrameFadePlugin'; export class PickingPlugin extends AViewerPluginSync { get picker() { return this._picker; } get hoverEnabled() { return this._picker?.hoverEnabled ?? false; } set hoverEnabled(v) { if (!this._picker) return; this._picker.hoverEnabled = v; this.uiConfig && this.uiConfig.uiRefresh?.(); } _widgetEnabledChange() { if (this.widgetEnabled && this._picker?.selectedObject) this._widget?.attach(this._picker.selectedObject); else this._widget?.detach(); this.uiConfig?.uiRefresh?.(true); } setDirty() { if (!this._viewer) return; if (this.isDisabled()) this.setSelectedObject(undefined); this._viewer.setDirty(); } constructor(selection = BoxSelectionWidget, pickUi = true, autoFocus = false) { super(); this.enabled = true; // @serialize() // todo this.autoFocusHover = false; /** * Note: this is for runtime use only, not serialized */ this.widgetEnabled = true; this._mainCameraChange = () => { if (!this._picker || !this._viewer) return; this._picker.camera = this._viewer.scene.mainCamera; }; this._onSceneUpdate = (e) => { if (!e.hierarchyChanged) return; const s = this.getSelectedObject(); let inScene = false; s?.traverseAncestors((o) => { if (o === this._viewer?.scene) inScene = true; }); if (!inScene) this.setSelectedObject(undefined); }; this._onObjectSelectEvent = (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'); else this.setSelectedObject(e.object || e.value, this.autoFocus || e.focusCamera); }; this._selectedObjectChanged = (e) => { if (!this._viewer) return; this.dispatchEvent(e); const selected = this._picker?.selectedObject || undefined; // or use e.object. doing this so that listeners can change the selected object in dispatch above const frameFade = this._viewer.getPlugin(FrameFadePlugin); if (frameFade) { if (selected) frameFade.disable(this); else frameFade.enable(this); } 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?.uiConfig; const ui = this.uiConfig; ui.children = [...this._uiConfigChildren]; if (sUiConfig) ui.children.push(sUiConfig); ui.uiRefresh?.(); } const widget = this._widget; if (widget && this.widgetEnabled) { if (selected) widget.attach(selected); else widget.detach(); } // if (selected) selected.dispatchEvent({type: 'selected', source: PickingPlugin.PluginType, object: selected}) this._viewer.setDirty(); if (this.autoFocus) { // this._viewer.resetCamera({rootObject: selected, centerOffset: new Vector3(4, 4, 4)}) this.focusObject(selected); } }; this._hoverObjectChanged = (e) => { this.dispatchEvent(e); const selected = this._picker?.hoverObject || undefined; const widget = this._hoverWidget; if (widget && this.widgetEnabled) { if (selected) widget.attach(selected); else widget.detach(); } // if (selected) selected.dispatchEvent({type: 'selected', source: PickingPlugin.PluginType, object: selected}) this._viewer?.setDirty(); if (this.autoFocusHover) { // this._viewer?.resetCamera({rootObject: selected, centerOffset: new Vector3(4, 4, 4)}) this.focusObject(selected); } }; this._onObjectHit = (e) => { if (!this._viewer) return; if (this.isDisabled()) { e.intersects.selectedObject = null; return; } this.dispatchEvent(e); }; this._uiConfigChildren = [ { label: 'Enabled', type: 'checkbox', property: [this, 'enabled'], }, { label: 'Hover Enabled', type: 'checkbox', property: [this, 'hoverEnabled'], onChange: () => this.uiConfig.uiRefresh?.(true), // for autoFocusHover }, { label: 'Auto Focus', type: 'checkbox', property: [this, 'autoFocus'], onChange: () => { const o = this.getSelectedObject(); if (this.autoFocus && o) this.setSelectedObject(o, true); }, }, { label: 'Auto Focus on Hover', type: 'checkbox', hidden: () => !this.hoverEnabled, property: [this, 'autoFocusHover'], }, { label: 'Widget Enabled', type: 'checkbox', property: [this, 'widgetEnabled'], }, ]; this.uiConfig = { type: 'panel', label: 'Picker', expanded: true, children: [ ...this._uiConfigChildren, ], }; if (selection) { this._widget = new selection(); this._hoverWidget = new selection(); if (this._hoverWidget.lineMaterial) { this._hoverWidget.lineMaterial.linewidth /= 2; this._hoverWidget.lineMaterial.color.set('#aa2222'); } } this._pickUi = pickUi; this.autoFocus = autoFocus; this.dispatchEvent = this.dispatchEvent.bind(this); } getSelectedObject() { if (this.isDisabled()) return; return this._picker?.selectedObject || undefined; } setSelectedObject(object, focusCamera = false) { const disabled = this.isDisabled(); if (disabled && !object) return; if (!this._picker) return; const t = this.autoFocus; this.autoFocus = false; this._picker.selectedObject = object || null; this.autoFocus = t; if (!disabled && object && (t || focusCamera)) this.focusObject(object); } onAdded(viewer) { super.onAdded(viewer); this.setDirty(); this._picker = new ObjectPicker(viewer.scene.modelRoot, viewer.canvas, viewer.scene.mainCamera, (obj) => { const hasMat = obj.material; if (!hasMat) return false; let o = obj; let ret = false; while (o) { if (!o.visible) return false; if (o.assetType === 'model' || o.assetType === 'light') ret = true; if (o.assetType === 'widget') return false; if (o.userData.userSelectable === false) return false; if (o.userData.bboxVisible === false) return false; o = o.parent; } return ret; }); if (this._widget) viewer.scene.addObject(this._widget, { addToRoot: true }); if (this._hoverWidget) viewer.scene.addObject(this._hoverWidget, { addToRoot: true }); this._picker.addEventListener('selectedObjectChanged', this._selectedObjectChanged); this._picker.addEventListener('hoverObjectChanged', this._hoverObjectChanged); this._picker.addEventListener('hitObject', this._onObjectHit); // on material drop on selected object // viewer.scene.addEventListener('addSceneObject', async(e) => { // const obj = e.object // const selected: IModel<Mesh> = this.getSelectedObject()! as any // if (selected // && obj?.assetType === 'material' // && typeof selected?.setMaterial === 'function' // && selected?.modelObject?.isMesh // && await viewer.confirm('Applying material: Apply material to the selected object?') // ) { // const oldMat = selected.material // if (Array.isArray(oldMat)) { // console.warn('Dropping on material array not yet fully supported.') // selected.setMaterial(obj) // } else { // let meshes: IModel<Mesh>[] = Array.from(oldMat?.userData.__appliedMeshes ?? []) // const c = meshes.length > 1 ? !await viewer.confirm('Applying material: Apply to all objects using this material?') : meshes.length < 1 // if (c) meshes = [selected] // for (const mesh of meshes) { // if (mesh) mesh.setMaterial?.(obj) // } // } // } // }) viewer.scene.addEventListener('select', this._onObjectSelectEvent); viewer.scene.addEventListener('sceneUpdate', this._onSceneUpdate); viewer.scene.addEventListener('mainCameraChange', this._mainCameraChange); viewer.forPlugin('UndoManagerPlugin', (um) => { if (!this._picker) return; this._picker.undoManager = um.undoManager; }, () => { if (!this._picker) return; this._picker.undoManager = undefined; }); } onRemove(viewer) { viewer.scene.removeEventListener('select', this._onObjectSelectEvent); viewer.scene.removeEventListener('sceneUpdate', this._onSceneUpdate); viewer.scene.removeEventListener('mainCameraChange', this._mainCameraChange); this._widget?.removeFromParent(); this._hoverWidget?.removeFromParent(); if (this._picker) { this._picker.removeEventListener('selectedObjectChanged', this._selectedObjectChanged); this._picker.removeEventListener('hoverObjectChanged', this._hoverObjectChanged); this._picker.removeEventListener('hitObject', this._onObjectHit); this._picker.dispose(); this._picker.undoManager = undefined; // because setting above this._picker = undefined; } super.onRemove(viewer); } dispose() { super.dispose(); this._widget?.dispose(); this._hoverWidget?.dispose(); } async focusObject(selected) { this._viewer?.fitToView(selected, 1.25, 1000, 'easeOut'); } get widget() { return this._widget; } } PickingPlugin.PluginType = 'Picking'; PickingPlugin.OldPluginType = 'PickingPlugin'; // todo: swap __decorate([ serialize(), onChange(PickingPlugin.prototype.setDirty) ], PickingPlugin.prototype, "enabled", void 0); __decorate([ serialize() ], PickingPlugin.prototype, "autoFocus", void 0); __decorate([ onChange(PickingPlugin.prototype._widgetEnabledChange) ], PickingPlugin.prototype, "widgetEnabled", void 0); //# sourceMappingURL=PickingPlugin.js.map