threepipe
Version:
A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.
113 lines (98 loc) • 3.64 kB
text/typescript
import {UiObjectConfig} from 'uiconfig.js'
import {IScene, ISceneEventMap, IWidget} from '../../core'
import {AViewerPluginSync, ThreeViewer} from '../../viewer'
import {IEvent, onChange} from 'ts-browser-helpers'
import {EventListener2, Object3D} from 'three'
import {CameraHelper2, DirectionalLightHelper2, PointLightHelper2, SpotLightHelper2} from '../../three'
export interface IObject3DHelper<T extends Object3D&IWidget = Object3D&IWidget>{
Create: (o: Object3D)=>T,
Check: (o: Object3D)=>boolean,
}
/**
* Adds light and camera helpers/gizmos in the viewer.
* A helper is automatically created when any supported light or camera is added to the scene.
* @category Plugins
*/
export class Object3DWidgetsPlugin extends AViewerPluginSync {
enabled = true
public static readonly PluginType = 'Object3DWidgetsPlugin'
helpers: IObject3DHelper[] = [
DirectionalLightHelper2,
SpotLightHelper2,
PointLightHelper2,
CameraHelper2,
]
setDirty() {
this.widgets?.forEach(w => w.visible = !this.isDisabled())
this._viewer?.setDirty()
}
toJSON: any = null
constructor(enabled = true) {
super()
this.enabled = enabled
}
private _widgetRoot = new Object3D()
onAdded(viewer: ThreeViewer) {
super.onAdded(viewer)
viewer.scene.addObject(this._widgetRoot, {addToRoot: true, autoScale: false, autoCenter: false})
viewer.scene.addEventListener('addSceneObject', this._addSceneObject)
}
onRemove(viewer: ThreeViewer) {
viewer.scene.removeEventListener('addSceneObject', this._addSceneObject)
this.widgets.forEach(w => w.dispose && w.dispose())
this.widgets = []
this._widgetRoot.removeFromParent()
this._widgetRoot.clear()
super.onRemove(viewer)
}
private _addSceneObject: EventListener2<'addSceneObject', ISceneEventMap, IScene> = (e)=>{
this._createWidgets(e.object)
}
refresh() {
this._createWidgets(this._viewer?.scene.modelRoot)
}
widgets: (IWidget&Object3D)[] = []
private _widgetDisposed = (e: IEvent<any>)=> this._unregisterWidget(e.target)
private _registerWidget(w: IWidget&Object3D) {
this.widgets.push(w)
w.addEventListener('dispose', this._widgetDisposed) // todo: maybe unregister when removed from parent, dispose makes little sense.
}
private _unregisterWidget(w: IWidget&Object3D) {
w.removeEventListener('dispose', this._widgetDisposed)
const i = this.widgets.indexOf(w)
if (i >= 0) this.widgets.splice(i, 1)
}
private _createWidgets(o?: Object3D) {
o?.traverse((l: any) => {
const widget = this.widgets.find(w => w.object === l)
if (widget) {
widget.update && widget.update()
return
}
const helpers = this.helpers.filter(h => h.Check(l))
helpers.forEach(h => {
const w = h.Create(l)
w.visible = !this.isDisabled()
this._widgetRoot.add(w)
this._registerWidget(w)
})
})
}
uiConfig: UiObjectConfig = {
type: 'folder',
label: 'Widgets',
children: [
{
type: 'checkbox',
label: 'Enabled',
property: [this, 'enabled'],
},
{
type: 'button',
label: 'Refresh',
value: ()=>this.refresh(),
},
],
}
}