Improve warning in AssetManager, Create MaterialManager.copyMaterialProps, fix camera aspect when canvas has height 0, add undo for some buttons, changes/fixes for ui, handle InteractionPromptPlugin in CameraViewPlugin, support async onStop and onComplete in PopmotionPlugin, allow resetting geometry in BaseGroundPlugin, add pluginListeners, forPlugin in ThreeViewer, add SwitchNodeBasePlugin.snapIcons, fixes in InteractionPromptPlugin, add isEditor, LS_DEFAULT_LOGO in LoadingScreenPlugin, add isEditor in FrameFadePlugin.
1 rok temu Improve warning in AssetManager, Create MaterialManager.copyMaterialProps, fix camera aspect when canvas has height 0, add undo for some buttons, changes/fixes for ui, handle InteractionPromptPlugin in CameraViewPlugin, support async onStop and onComplete in PopmotionPlugin, allow resetting geometry in BaseGroundPlugin, add pluginListeners, forPlugin in ThreeViewer, add SwitchNodeBasePlugin.snapIcons, fixes in InteractionPromptPlugin, add isEditor, LS_DEFAULT_LOGO in LoadingScreenPlugin, add isEditor in FrameFadePlugin.
1 rok temu Improve warning in AssetManager, Create MaterialManager.copyMaterialProps, fix camera aspect when canvas has height 0, add undo for some buttons, changes/fixes for ui, handle InteractionPromptPlugin in CameraViewPlugin, support async onStop and onComplete in PopmotionPlugin, allow resetting geometry in BaseGroundPlugin, add pluginListeners, forPlugin in ThreeViewer, add SwitchNodeBasePlugin.snapIcons, fixes in InteractionPromptPlugin, add isEditor, LS_DEFAULT_LOGO in LoadingScreenPlugin, add isEditor in FrameFadePlugin.
1 rok temu |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- import {Event, Matrix4, Mesh, Vector3} from 'three'
- import {IMaterial} from '../IMaterial'
- import {objectHasOwn} from 'ts-browser-helpers'
- import {IObject3D, IObject3DEvent, IObjectProcessor, IObjectSetDirtyOptions} from '../IObject'
- import {copyObject3DUserData} from '../../utils'
- import {IGeometry, IGeometryEvent} from '../IGeometry'
- import {Box3B} from '../../three'
- import {makeIObject3DUiConfig} from './IObjectUi'
- import {iGeometryCommons} from '../geometry/iGeometryCommons'
- import {iMaterialCommons} from '../material/iMaterialCommons'
- import {ILight} from '../light/ILight'
-
- export const iObjectCommons = {
- setDirty: function(this: IObject3D, options?: IObjectSetDirtyOptions, ...args: any[]): void {
- if (typeof options === 'string') { // just incase called by decorators
- options = {change: options}
- }
- this.dispatchEvent({bubbleToParent: true, ...options, type: 'objectUpdate', object: this, args}) // this sets sceneUpdate in root scene
- if (options?.refreshUi !== false && options?.last !== false) this.refreshUi?.()
- // console.log('object update')
- },
-
- upgradeObject3D: upgradeObject3D,
- makeUiConfig: makeIObject3DUiConfig,
-
- autoCenter: function<T extends IObject3D>(this: T, setDirty = true, undo = false): T {
- if (undo) {
- if (!this.userData.autoCentered || !this.userData._lastCenter) return this
- this.position.add(this.userData._lastCenter)
- delete this.userData.autoCentered
- delete this.userData.isCentered
- delete this.userData._lastCenter
- } else {
- const bb = new Box3B().expandByObject(this, true, true)
- const center = bb.getCenter(new Vector3())
- this.userData._lastCenter = center/* .clone()*/
- this.position.sub(center)
- this.userData.autoCentered = true
- this.userData.isCentered = true
- }
- this.updateMatrix()
- if (setDirty) this.setDirty({change: 'autoCenter', undo})
- return this
- },
-
- autoScale: function<T extends IObject3D>(this: T, autoScaleRadius?: number, isCentered?: boolean, setDirty = true, undo = false): T {
- let scale = 1
- if (undo) { // Note - undo only works for quick undo, not for multiple times
- if (!this.userData.autoScaled || !this.userData._lastScaleRadius) return this
- const rad = this.userData.autoScaleRadius || autoScaleRadius || 1
- scale = this.userData._lastScaleRadius / rad
- if (!isFinite(scale)) return this // NaN when radius is 0
- this.userData.autoScaled = true
- this.userData.autoScaleRadius = autoScaleRadius
- delete this.userData._lastScaleRadius
- } else {
- const bbox = new Box3B().expandByObject(this, true, true)
- const radius = bbox.getSize(new Vector3()).length() * 0.5
- if (autoScaleRadius === undefined) {
- autoScaleRadius = this.userData.autoScaleRadius || 1
- }
- scale = autoScaleRadius / radius
- if (!isFinite(scale)) return this // NaN when radius is 0
- this.userData.autoScaled = true
- this.userData.autoScaleRadius = autoScaleRadius
- this.userData._lastScaleRadius = radius
- }
-
- if (this.userData.pseudoCentered) {
- this.children.forEach(child => {
- child.scale.multiplyScalar(scale)
- })
- } else
- this.scale.multiplyScalar(scale)
- if (isCentered || this.userData.isCentered) this.position.multiplyScalar(scale)
-
- this.traverse((obj) => {
- const l = obj as any
- if (l.isLight && l.shadow?.camera?.right) {
- l.shadow.camera.right *= scale
- l.shadow.camera.left *= scale
- l.shadow.camera.top *= scale
- l.shadow.camera.bottom *= scale
- obj.setDirty()
- }
- if (l.isCamera && l.right) {
- l.right *= scale
- l.left *= scale
- l.top *= scale
- l.bottom *= scale
- obj.setDirty()
- }
- })
-
- if (setDirty) this.setDirty({change: 'autoScale', undo})
-
- return this
- },
-
- pivotToBoundsCenter: function<T extends IObject3D>(this: T, setDirty = true): ()=>void {
- const bb = new Box3B().expandByObject(this, true, true)
- const center = bb.getCenter(new Vector3())
- return iObjectCommons.pivotToPoint.call(this, center, setDirty)
- },
-
- pivotToPoint: function<T extends IObject3D>(this: T, point: Vector3, setDirty = true): ()=>void {
- const worldCenter = new Vector3().copy(point)
- const localCenter = new Vector3().copy(worldCenter)
-
- const worldMatrixInv = new Matrix4().copy(this.matrixWorld).invert()
- const m = this.parent?.matrixWorld
- const parentWorldMatrixInv = new Matrix4()
- if (m !== undefined)
- parentWorldMatrixInv.copy(m).invert()
-
- // Get the center with respect to the parent
- worldCenter.applyMatrix4(parentWorldMatrixInv)
- const lastPosition = this.position.clone()
-
- // Apply the new position
- this.position.copy(worldCenter)
-
- // local center
- localCenter.applyMatrix4(worldMatrixInv).negate()
-
- // Shift the geometry
- if (this.geometry) {
- this.geometry.translate(localCenter.x, localCenter.y, localCenter.z)
- }
- // Add offsets
- this.children.forEach((object)=> {
- object.position.add(localCenter)
- })
- if (setDirty) this.setDirty({change: 'pivotToPoint', undo: false})
-
- return ()=>{
- // undo
- this.position.copy(lastPosition)
- if (this.geometry) {
- this.geometry.translate(-localCenter.x, -localCenter.y, -localCenter.z)
- }
- this.children.forEach((object)=> {
- object.position.sub(localCenter)
- })
- if (setDirty) this.setDirty({change: 'pivotToPoint', undo: true})
- }
- },
-
- eventCallbacks: {
- onAddedToParent: function(this: IObject3D, e: Event): void {
- // added to some parent
- const root = this.parent?.parentRoot ?? this.parent
- if (!this.objectProcessor && root?.objectProcessor) { // this is added so that when an upgraded(not processed) object is added to the scene, it will be processed by the scene processor
- this.traverse(o=>{
- o.objectProcessor = root.objectProcessor
- o.objectProcessor?.processObject(o)
- })
- }
- if (root !== this.parentRoot) {
- this.traverse(o=>{
- o.parentRoot = root
- })
- }
- this.setDirty?.({...e, change: 'addedToParent'})
- },
- onRemovedFromParent: function(this: IObject3D, e: Event): void {
- // removed from some parent
- this.setDirty?.({...e, change: 'removedFromParent'})
- if (this.parentRoot !== undefined) {
- this.traverse(o=>{
- o.parentRoot = undefined
- })
- }
- },
- onGeometryUpdate: function(this: IObject3D, e: IGeometryEvent<'geometryUpdate'>): void {
- if (!e.bubbleToObject) return
- this.dispatchEvent({bubbleToParent: true, ...e, object: this, geometry: e.geometry})
- },
- },
-
- initMaterial: function(this: IObject3D): void {
- if (objectHasOwn(this, '_currentMaterial')) return
- this._currentMaterial = null
-
- const currentMaterial = this.material
- delete this.material
- Object.defineProperty(this, 'material', {
- get: iObjectCommons.getMaterial,
- set: iObjectCommons.setMaterial,
- })
- Object.defineProperty(this, 'materials', {
- get: iObjectCommons.getMaterials,
- set: iObjectCommons.setMaterials,
- })
- // this is called initially in Material manager from process model below, not required here...
- // todo: shouldnt be called from there. maybe check if material is upgraded before
- // if (currentMaterial && !Array.isArray(currentMaterial) && !currentMaterial.assetType) {
- // console.error('todo: initMaterial: material not upgraded')
- // }
- this.material = currentMaterial
-
- // Legacy
- if (!(this as any).setMaterial) {
- (this as any).setMaterial = (m: IMaterial | IMaterial[]| undefined)=>{
- const mats = this.material
- console.error('setMaterial is deprecated, use material property directly')
- this.material = m
- return mats
- }
- }
- // Legacy
- if (this.userData.setMaterial) console.error('userData.setMaterial already defined')
- this.userData.setMaterial = (m: any)=>{
- console.error('userData.setMaterial is deprecated, use setMaterial directly')
- this.material = m
- }
-
- },
-
- getMaterial: function(this: IObject3D): IMaterial | IMaterial[] | undefined {
- return this._currentMaterial || undefined
- },
- getMaterials: function(this: IObject3D): IMaterial[] {
- return !this._currentMaterial ? [] : Array.isArray(this._currentMaterial) ? [...this._currentMaterial] : [this._currentMaterial]
- },
-
- setMaterial: function(this: IObject3D, material: IMaterial | IMaterial[] | undefined) {
- const imats = (Array.isArray(material) ? material : [material]).filter(v=>v)
- if (this.material == imats || imats.length === 1 && this.material === imats[0]) return []
- // todo: check by uuid?
-
- // Remove old material listeners
- const mats = Array.isArray(this.material) ? [...(this.material as IMaterial[])] : [this.material!]
- for (const mat of mats) {
- if (!mat) continue
- if (mat.appliedMeshes) {
- mat.appliedMeshes.delete(this)
- // if (mat.userData && mat.appliedMeshes?.size === 0 && mat.userData.disposeOnIdle !== false)
- mat.dispose(false) // this will dispose textures(if they are idle) if the material is registered in the material manager
- }
- }
-
- const materials = []
- for (const mat of imats) {
- // const mat = material?.materialObject
- if (!mat) continue
- if (!mat.assetType) {
- console.warn('Upgrading Material', mat)
- iMaterialCommons.upgradeMaterial.call(mat)
- }
- materials.push(mat)
- if (mat) {
- mat.appliedMeshes.add(this)
- }
- }
- this._currentMaterial = !materials.length ? null : materials.length !== 1 ? materials : materials[0] || null
-
- this.dispatchEvent({type: 'materialChanged', material, oldMaterial: mats, object: this, bubbleToParent: true})
- this.refreshUi()
- },
- setMaterials: function(this: IObject3D, materials: IMaterial[]) {
- this.material = materials || undefined
- },
-
- initGeometry: function(this: IObject3D): void {
- const currentGeometry = this.geometry
- this._currentGeometry = null
- delete this.geometry
- Object.defineProperty(this, 'geometry', {
- get: iObjectCommons.getGeometry,
- set: iObjectCommons.setGeometry,
- })
- this.geometry = currentGeometry
-
- // Legacy
- if (!(this as any).setGeometry) {
- (this as any).setGeometry = (geometry: IGeometry) =>{
- const geom = this.geometry
- console.error('setGeometry is deprecated, use geometry property directly')
- this.geometry = geometry
- return geom
- }
- }
- // Legacy
- if (this.userData.setGeometry) console.error('userData.setGeometry already defined')
- this.userData.setGeometry = (g: any)=>{
- console.error('userData.setGeometry is deprecated, use setGeometry directly')
- this.geometry = g
- }
-
- },
- getGeometry: function(this: IObject3D&Mesh): IGeometry | undefined {
- return this._currentGeometry || undefined
- },
- setGeometry: function(this: IObject3D&Mesh, geometry: IGeometry | undefined): void {
- const geom = this.geometry || undefined
- // todo: check by uuid?
- if (geom === geometry) return
- if (geom) {
- this._onGeometryUpdate && geom.removeEventListener('geometryUpdate', this._onGeometryUpdate)
- if (geom.appliedMeshes) {
- geom.appliedMeshes.delete(this)
- geom.dispose(false)
- }
- }
- if (geometry) {
- if (!geometry.assetType) {
- // console.error('Geometry not upgraded')
- iGeometryCommons.upgradeGeometry.call(geometry)
- }
- }
- this._currentGeometry = geometry || null
- if (geometry) {
- this.updateMorphTargets()
- this._onGeometryUpdate && geometry.addEventListener('geometryUpdate', this._onGeometryUpdate)
- geometry.appliedMeshes.add(this)
- }
- this.dispatchEvent({type: 'geometryChanged', geometry, oldGeometry: geom, bubbleToParent: true})
- this.refreshUi()
-
- },
-
- refreshUi: function(this: IObject3D): void {
- this.uiConfig?.uiRefresh?.(true, 'postFrame', 1)
- },
-
- dispatchEvent: (superDispatch: IObject3D['dispatchEvent']) =>
- function(this: IObject3D, event: IObject3DEvent): void {
- if (event.bubbleToParent || this.userData?.__autoBubbleToParentEvents?.includes(event.type)) {
- // console.log('parent dispatch', e, this.parentRoot, this.parent)
- const pRoot = this.parentRoot || this.parent
- if (this.parentRoot !== this) pRoot?.dispatchEvent(event)
- }
- superDispatch.call(this, event)
- },
- clone: (superClone: IObject3D['clone']): IObject3D['clone'] =>
- function(this: IObject3D, ...args): IObject3D {
- const userData = this.userData
- this.userData = {}
- const clone: any = superClone.call(this, ...args)
- this.userData = userData
- copyObject3DUserData(clone.userData, userData) // todo: do same for this.toJSON()
- const objParent = this.parentRoot || undefined
- if (objParent && objParent.assetType !== 'model') {
- console.warn('Cloning an IObject with a parent that is not an \'model\' is not supported')
- }
- iObjectCommons.upgradeObject3D.call(clone, objParent, this.objectProcessor)
- clone.userData.cloneParent = this.uuid
- return clone
- },
- copy: (superCopy: IObject3D['copy']): IObject3D['copy'] =>
- function(this: IObject3D|ILight, source: IObject3D, ...args): IObject3D {
- const lightTarget = this.isLight ? (this as ILight).target : null
-
- const userData = source.userData
- source.userData = {}
-
- const selfUserData = this.userData
- superCopy.call(this, source, ...args)
- this.userData = selfUserData
-
- source.userData = userData
- copyObject3DUserData(this.userData, source.userData) // todo: do same for object.toJSON()
-
- if (lightTarget && (this as ILight).target) { // For eg DirectionalLight2
- lightTarget.position.copy((this as ILight).target!.position)
- lightTarget.updateMatrixWorld()
- ;(this as ILight).target = lightTarget // because t is a child and because of UI.
- }
-
- return this
- },
- add: (superAdd: IObject3D['add']): IObject3D['add'] =>
- function(this: IObject3D, ...args): IObject3D {
- for (const a of args) iObjectCommons.upgradeObject3D.call(a, this.parentRoot || this, this.objectProcessor)
- return superAdd.call(this, ...args)
- },
- dispose: (superDispose?: IObject3D['dispose']) =>
- function(this: IObject3D, removeFromParent = true): void {
- if (removeFromParent && this.parent) {
- this.removeFromParent()
- delete this.parentRoot
- }
-
- this.dispatchEvent({type: 'dispose', bubbleToParent: false})
-
- // if (this.__disposed) {
- // console.warn('Object already disposed', this)
- // return
- // }
- // this.__disposed = true
-
- for (const c of [...this.children]) c?.dispose && c.dispose(false) // not removing the children from parent to preserve hierarchy
- // this.children = []
-
- // this.uiConfig?.dispose?.() // todo: make uiConfig.dispose
-
- superDispose?.call(this)
- },
-
-
- }
-
- /**
- * Converts three.js Object3D to IObject3D, setup object events, adds utility methods, and runs objectProcessor.
- * @param parent
- * @param objectProcessor
- */
- function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectProcessor?: IObjectProcessor): void {
- if (!this) return
- // console.log('upgradeObject3D', this, parent, objectProcessor)
- // if (this.__disposed) {
- // console.warn('re-init/re-add disposed object, things might not work as intended', this)
- // delete this.__disposed
- // }
- if (!this.userData) this.userData = {}
- this.userData.uuid = this.uuid
-
- // not checking assetType but custom var __objectSetup because its required in types sometimes, check PerspectiveCamera2
- // if (this.assetType) return
-
- if (this.userData.__objectSetup) return
- this.userData.__objectSetup = true
-
- if (!this.objectProcessor) this.objectProcessor = objectProcessor || this.parent?.objectProcessor || parent?.objectProcessor
-
- if (!this.userData.__autoBubbleToParentEvents) this.userData.__autoBubbleToParentEvents = ['select']
- // Event bubbling. todo: set bubbleToParent in these events when dispatched from child and remove from here?
-
- if (this.isLight) this.assetType = 'light'
- else if (this.isCamera) this.assetType = 'camera'
- else if (this.isWidget) this.assetType = 'widget'
- else this.assetType = 'model'
-
- if (parent) this.parentRoot = parent
-
- // const oldFunctions = {
- // dispatchEvent: this.dispatchEvent,
- // clone: this.clone,
- // copy: this.copy,
- // add: this.add,
- // dispose: this.dispose,
- // }
- // this.addEventListener('dispose', () => Object.assign(this, oldFunctions)) // todo: is this required?
-
- // typed because of type-checking
- this.dispatchEvent = iObjectCommons.dispatchEvent(this.dispatchEvent)
- this.dispose = iObjectCommons.dispose(this.dispose)
- this.clone = iObjectCommons.clone(this.clone)
- this.copy = iObjectCommons.copy(this.copy) // todo: do same for object.toJSON()
- this.add = iObjectCommons.add(this.add)
-
- if (!this.setDirty) this.setDirty = iObjectCommons.setDirty
- if (!this.refreshUi) this.refreshUi = iObjectCommons.refreshUi
- if (!this.autoScale) this.autoScale = iObjectCommons.autoScale.bind(this)
- if (!this.autoCenter) this.autoCenter = iObjectCommons.autoCenter.bind(this)
- if (!this.pivotToBoundsCenter) this.pivotToBoundsCenter = iObjectCommons.pivotToBoundsCenter.bind(this)
- if (!this.pivotToPoint) this.pivotToPoint = iObjectCommons.pivotToPoint.bind(this)
-
- // fired from Object3D.js
- this.addEventListener('added', iObjectCommons.eventCallbacks.onAddedToParent)
- this.addEventListener('removed', iObjectCommons.eventCallbacks.onRemovedFromParent)
-
- // this.addEventListener('dispose', ()=>{
- // this.removeEventListener('added', iObjectCommons.eventCallbacks.onAddedToParent)
- // this.removeEventListener('removed', iObjectCommons.eventCallbacks.onRemovedFromParent)
- // })
-
- if ((this.isMesh || this.isLine) && !this.userData.__meshSetup) {
- this.userData.__meshSetup = true
-
- this._onGeometryUpdate = (e: IGeometryEvent) => iObjectCommons.eventCallbacks.onGeometryUpdate.call(this, e)
-
- // Material, Geometry prop init
- iObjectCommons.initMaterial.call(this)
- iObjectCommons.initGeometry.call(this)
-
- // from GLTFObject3DExtrasExtension
- if (!this.userData.__keepShadowDef) {
- this.castShadow = true
- this.receiveShadow = true
- this.userData.__keepShadowDef = true
- }
-
- this.addEventListener('dispose', ()=>{
-
- (this.materials || [<IMaterial> this.material]).forEach(m => m?.dispose(false))
- this.geometry?.dispose(false)
-
- // if (this.material) {
- // // const oldMats = Array.isArray(this.material) ? [...(this.material as IMaterial[])] : [this.material!]
- // this.material = undefined // this will dispose material if not used by other meshes
- // // delete this.material
- // // for (const oldMat of oldMats) {
- // // if (oldMat && oldMat.userData && oldMat.appliedMeshes?.size === 0 && oldMat.userData.disposeOnIdle !== false) oldMat.dispose()
- // // }
- // }
- // if (this.geometry) {
- // // const oldGeom = this.geometry
- // this.geometry = undefined // this will dispose geometry if not used by other meshes
- // // delete this.geometry
- // // if (oldGeom && oldGeom.userData && oldGeom.appliedMeshes?.size === 0 && oldGeom.userData.disposeOnIdle !== false) oldGeom.dispose()
- // }
- //
- // delete this._onGeometryUpdate
- })
-
- }
-
- if (!this.uiConfig && (this.assetType === 'model' || this.assetType === 'camera')) {
- // todo: lights/other types?
- iObjectCommons.makeUiConfig.call(this)
- }
-
- // todo: serialization?
-
- const children = [...this.children]
- for (const c of children) upgradeObject3D.call(c, this)
-
- // region Legacy
-
- // eslint-disable-next-line deprecation/deprecation
- !this.userData.dispose && (this.userData.dispose = () => {
- console.warn('userData.dispose is deprecated, use dispose directly')
- this.dispose && this.dispose()
- })
- // eslint-disable-next-line deprecation/deprecation
- !this.modelObject && Object.defineProperty(this, 'modelObject', {
- get: ()=>{
- console.error('modelObject is deprecated, use object directly')
- return this
- },
- })
- // eslint-disable-next-line deprecation/deprecation
- !this.userData.setDirty && (this.userData.setDirty = (e: any)=>{
- console.error('object.userData.setDirty is deprecated, use object.setDirty directly')
- this.setDirty?.(e)
- })
-
- // endregion
-
- this.objectProcessor?.processObject(this)
-
- }
|