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.
125 lines (104 loc) • 3.8 kB
text/typescript
import {IObject3D} from '../../../core'
import {UiObjectConfig} from 'uiconfig.js'
import {ComponentCtx, ComponentDefn, ComponentJSON} from './componentTypes'
import {refreshAllStateProperties} from './setupComponent'
import {EntityComponentPlugin} from '../EntityComponentPlugin'
import {ViewerEventMap} from '../../../viewer/ThreeViewer'
export type TObject3DComponent = typeof Object3DComponent
export class Object3DComponent {
declare ['constructor']: TObject3DComponent & ComponentDefn
// enabled = true
static StateProperties: ComponentDefn['StateProperties'] = [/* 'enabled'*/]
static ComponentType = 'Object3DComponent'
readonly isObject3DComponent = true
protected _object: IObject3D | null = null
get object() {
if (!this._object) {
throw new Error('Cannot access object at this point.')
}
return this._object
}
set object(_: IObject3D) {
console.error('Object3DComponent: object is read-only')
}
// name: string
// uuid = generateUUID()
uuid = Math.random().toString(32).slice(2, 10)
declare uiConfig?: UiObjectConfig
declare state: never // so that this name can never be used
protected _state: ComponentJSON['state']
get stateRef() {
return this._state
}
// getState() {
// return {...this._state}
// }
setState(state: ComponentJSON['state']) {
const oldState = this._state
if (state === oldState) return
if (state && typeof state === 'object') {
this._state = state
} else {
this._state = {}
}
for (const key in oldState) {
if (!(key in this._state)) {
this._state[key] = oldState[key]
}
}
refreshAllStateProperties(this, oldState)
return this
}
declare ctx: ComponentCtx
constructor() {
this._state = {
// ...stateFuncs,
}
// todo this has to be done after the subclass constructor, not before. move to the plugin
// this.name = this.type
}
// hooks
start() {
// called when component is added to an object, usually when the scene is played
}
stop() {
// called when scene is stopped or component is removed from an object when scene is playing
}
update(_e: ViewerEventMap['preFrame']): boolean|void {
// called every frame
}
preFrame(_e: ViewerEventMap['preFrame']): boolean|void {
// called every frame, before update, even when not running(paused/edit mode)
}
init(object: IObject3D, state: ComponentJSON['state']) {
if (!object) throw new Error('Object3DComponent: no object')
this._object = object
this.setState(state)
}
destroy() {
this._object = null as any
this.stateChangeHandlers = {}
const state = this._state
this._state = {}
return state
}
stateChangeHandlers: Record<string, ((value: any, oldValue: any, key?: string) => void)[]> = {}
onStateChange<K extends keyof this & string>(
key: K,
fn: (value: this[K], oldValue: this[K], key?: K) => void
) {
if (!this.stateChangeHandlers[key]) this.stateChangeHandlers[key] = []
this.stateChangeHandlers[key].push(fn)
}
/**
* @internal
*/
['_sType']?: string
getComponent<T extends TObject3DComponent>(type: T | string, self = false) {
const obj = this._object
if (!obj) return self ? null : this.ctx.ecp.getComponentOfType(type)
if (self) return EntityComponentPlugin.GetComponent(obj, type)
return EntityComponentPlugin.GetComponentInParent(obj, type) ||
this.ctx.ecp.getComponentOfType(type)
}
}