| "build-examples": "tsc --project examples/tsconfig.build.json", | "build-examples": "tsc --project examples/tsconfig.build.json", | ||||
| "dev-examples": "tsc --project examples/tsconfig.build.json -w", | "dev-examples": "tsc --project examples/tsconfig.build.json -w", | ||||
| "serve-docs": "ws -d docs -p 8080", | "serve-docs": "ws -d docs -p 8080", | ||||
| "serve": "ws -d . -p 8000", | |||||
| "serve": "ws -d . -p 9229", | |||||
| "docs": "npx typedoc && markdown-to-html", | "docs": "npx typedoc && markdown-to-html", | ||||
| "prepare": "npm run build && npm run build-examples && npm run docs" | "prepare": "npm run build && npm run build-examples && npm run docs" | ||||
| }, | }, |
| this.viewer = viewer | this.viewer = viewer | ||||
| this.viewer.scene.addEventListener('addSceneObject', this._sceneUpdated) | this.viewer.scene.addEventListener('addSceneObject', this._sceneUpdated) | ||||
| this.viewer.scene.addEventListener('materialChanged', this._sceneUpdated) | this.viewer.scene.addEventListener('materialChanged', this._sceneUpdated) | ||||
| this.viewer.scene.addEventListener('beforeDeserialize', this._sceneUpdated) | |||||
| this._initCacheStorage(simpleCache, storage ?? true) | this._initCacheStorage(simpleCache, storage ?? true) | ||||
| this.importer.addEventListener('processRaw', (event)=>{ | this.importer.addEventListener('processRaw', (event)=>{ | ||||
| for (const t of targets) { | for (const t of targets) { | ||||
| this.materials.registerMaterial(t) | this.materials.registerMaterial(t) | ||||
| } | } | ||||
| } else if (event.type === 'beforeDeserialize') { | |||||
| // object/material/texture to be deserialized | |||||
| const data = event.data | |||||
| const meta = event.meta | |||||
| if (!data.metadata) { | |||||
| console.warn('Invalid data(no metadata)', data) | |||||
| } | |||||
| console.log(data, event) | |||||
| if (event.material) { | |||||
| if (data.metadata?.type !== 'Material') { | |||||
| console.warn('Invalid material data', data) | |||||
| } | |||||
| JSONMaterialLoader.DeserializeMaterialJSON(data, this.viewer, meta, event.material).then(()=>{ | |||||
| // | |||||
| }) | |||||
| } | |||||
| } else { | } else { | ||||
| console.error('Unexpected') | console.error('Unexpected') | ||||
| } | } |
| import {SimpleJSONLoader} from './SimpleJSONLoader' | import {SimpleJSONLoader} from './SimpleJSONLoader' | ||||
| import {ThreeViewer} from '../../viewer' | import {ThreeViewer} from '../../viewer' | ||||
| import {getEmptyMeta, SerializationMetaType, ThreeSerialization} from '../../utils/serialization' | import {getEmptyMeta, SerializationMetaType, ThreeSerialization} from '../../utils/serialization' | ||||
| import {IMaterial} from '../../core' | |||||
| export class JSONMaterialLoader extends SimpleJSONLoader { | export class JSONMaterialLoader extends SimpleJSONLoader { | ||||
| if (!this.viewer) throw 'Viewer not set in JSONMaterialLoader.' | if (!this.viewer) throw 'Viewer not set in JSONMaterialLoader.' | ||||
| const json = await super.loadAsync(url, onProgress) as any | const json = await super.loadAsync(url, onProgress) as any | ||||
| const meta: SerializationMetaType = getEmptyMeta() | |||||
| return await JSONMaterialLoader.DeserializeMaterialJSON(json, this.viewer) | |||||
| } | |||||
| static async DeserializeMaterialJSON(json: any, viewer: ThreeViewer, meta?: SerializationMetaType, obj?: IMaterial|IMaterial[]) { | |||||
| meta = meta || getEmptyMeta() | |||||
| const json2 = {...json} | const json2 = {...json} | ||||
| if (json.images) { | if (json.images) { | ||||
| if (Array.isArray(json.images)) meta.images = Object.fromEntries(json.images.map((i: any) => [i.uuid, i])) | if (Array.isArray(json.images)) meta.images = Object.fromEntries(json.images.map((i: any) => [i.uuid, i])) | ||||
| else meta.materials = json.materials | else meta.materials = json.materials | ||||
| delete json2.materials | delete json2.materials | ||||
| } | } | ||||
| const resources = await this.viewer.loadConfigResources(meta) | |||||
| return ThreeSerialization.Deserialize(json2, undefined, resources) | |||||
| const resources = await viewer.loadConfigResources(meta) | |||||
| return ThreeSerialization.Deserialize(json2, obj || undefined, resources) | |||||
| } | } | ||||
| } | } |
| traverse(callback: (object: IObject3D) => void): void | traverse(callback: (object: IObject3D) => void): void | ||||
| traverseVisible(callback: (object: IObject3D) => void): void | traverseVisible(callback: (object: IObject3D) => void): void | ||||
| traverseAncestors(callback: (object: IObject3D) => void): void | traverseAncestors(callback: (object: IObject3D) => void): void | ||||
| getObjectById(id: number): IObject3D | undefined | |||||
| getObjectByName(name: string): IObject3D | undefined | |||||
| getObjectByProperty(name: string, value: string): IObject3D | undefined | |||||
| getObjectById<T extends IObject3D = IObject3D>(id: number): T | undefined | |||||
| getObjectByName<T extends IObject3D = IObject3D>(name: string): T | undefined | |||||
| getObjectByProperty<T extends IObject3D = IObject3D>(name: string, value: string): T | undefined | |||||
| copy(source: this, recursive?: boolean, distanceFromTarget?: number, ...args: any[]): this | copy(source: this, recursive?: boolean, distanceFromTarget?: number, ...args: any[]): this | ||||
| clone(recursive?: boolean): this | clone(recursive?: boolean): this | ||||
| add(...object: IObject3D[]): this | add(...object: IObject3D[]): this |
| import type {Event, IUniform, Material, MaterialParameters, Shader} from 'three' | import type {Event, IUniform, Material, MaterialParameters, Shader} from 'three' | ||||
| import type {AnyOptions, IDisposable, IJSONSerializable} from 'ts-browser-helpers' | |||||
| import type {IDisposable, IJSONSerializable} from 'ts-browser-helpers' | |||||
| import type {MaterialExtension} from '../materials' | import type {MaterialExtension} from '../materials' | ||||
| import type {IUiConfigContainer} from 'uiconfig.js' | |||||
| import type {SerializationMetaType} from '../utils/serialization' | |||||
| import type {ChangeEvent, IUiConfigContainer} from 'uiconfig.js' | |||||
| import type {SerializationMetaType} from '../utils' | |||||
| import type {IObject3D} from './IObject' | import type {IObject3D} from './IObject' | ||||
| import type {ITexture} from './ITexture' | import type {ITexture} from './ITexture' | ||||
| import type {IImportResultUserData} from '../assetmanager' | import type {IImportResultUserData} from '../assetmanager' | ||||
| export type IMaterialParameters = MaterialParameters & {customMaterialExtensions?: MaterialExtension[]} | export type IMaterialParameters = MaterialParameters & {customMaterialExtensions?: MaterialExtension[]} | ||||
| export type IMaterialEventTypes = 'dispose' | 'materialUpdate' | 'beforeRender' | 'beforeCompile' | 'afterRender' | 'textureChanged' | |||||
| export type IMaterialEventTypes = 'dispose' | 'materialUpdate' | 'beforeRender' | 'beforeCompile' | 'afterRender' | 'textureChanged' | 'beforeDeserialize' | |||||
| export type IMaterialEvent<T extends string = IMaterialEventTypes> = Event & { | export type IMaterialEvent<T extends string = IMaterialEventTypes> = Event & { | ||||
| type: T | type: T | ||||
| bubbleToObject?: boolean | bubbleToObject?: boolean | ||||
| bubbleToParent?: boolean | |||||
| material?: IMaterial | material?: IMaterial | ||||
| texture?: ITexture | texture?: ITexture | ||||
| oldTexture?: ITexture | oldTexture?: ITexture | ||||
| uiChangeEvent?: ChangeEvent | |||||
| } | |||||
| export interface IMaterialSetDirtyOptions { | |||||
| /** | |||||
| * @default true | |||||
| */ | |||||
| bubbleToObject?: boolean, | |||||
| /** | |||||
| * @default true | |||||
| */ | |||||
| refreshUi?: boolean, | |||||
| /** | |||||
| * @default true | |||||
| */ | |||||
| needsUpdate?: boolean, | |||||
| /** | |||||
| * Event from uiconfig.js | |||||
| */ | |||||
| uiChangeEvent?: ChangeEvent, | |||||
| [key: string]: any | |||||
| } | } | ||||
| export type IMaterialSetDirtyOptions = AnyOptions & {bubbleToObject?: boolean} | |||||
| export interface IMaterialUserData extends IImportResultUserData{ | export interface IMaterialUserData extends IImportResultUserData{ | ||||
| uuid?: string // adding to userdata also, so that its saved in gltf | uuid?: string // adding to userdata also, so that its saved in gltf | ||||
| import {IDisposable} from 'ts-browser-helpers' | import {IDisposable} from 'ts-browser-helpers' | ||||
| import {IMaterial, IMaterialEvent} from './IMaterial' | |||||
| import {IMaterial} from './IMaterial' | |||||
| import {Event, Object3D} from 'three' | import {Event, Object3D} from 'three' | ||||
| import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js' | import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js' | ||||
| import {IGeometry, IGeometryEvent} from './IGeometry' | import {IGeometry, IGeometryEvent} from './IGeometry' | ||||
| import {GLTF} from 'three/examples/jsm/loaders/GLTFLoader.js' | import {GLTF} from 'three/examples/jsm/loaders/GLTFLoader.js' | ||||
| export type IObject3DEventTypes = 'dispose' | 'materialUpdate' | 'objectUpdate' | 'geometryChanged' | | export type IObject3DEventTypes = 'dispose' | 'materialUpdate' | 'objectUpdate' | 'geometryChanged' | | ||||
| 'materialChanged' | 'geometryUpdate' | 'added' | 'removed' | 'select' | | |||||
| 'materialChanged' | 'geometryUpdate' | 'added' | 'removed' | 'select' | 'beforeDeserialize' | | |||||
| 'setView' | 'activateMain' | 'cameraUpdate' // from camera | 'setView' | 'activateMain' | 'cameraUpdate' // from camera | ||||
| // | string | // | string | ||||
| export interface IObject3DEvent<T extends string = IObject3DEventTypes> extends Event { | export interface IObject3DEvent<T extends string = IObject3DEventTypes> extends Event { | ||||
| modelObject: this | modelObject: this | ||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||||
| _onMaterialUpdate?: (e: IMaterialEvent<'materialUpdate'>) => void | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | // eslint-disable-next-line @typescript-eslint/naming-convention | ||||
| _onGeometryUpdate?: (e: IGeometryEvent<'geometryUpdate'>) => void | _onGeometryUpdate?: (e: IGeometryEvent<'geometryUpdate'>) => void | ||||
| traverse(callback: (object: IObject3D) => void): void | traverse(callback: (object: IObject3D) => void): void | ||||
| traverseVisible(callback: (object: IObject3D) => void): void | traverseVisible(callback: (object: IObject3D) => void): void | ||||
| traverseAncestors(callback: (object: IObject3D) => void): void | traverseAncestors(callback: (object: IObject3D) => void): void | ||||
| getObjectById(id: number): IObject3D | undefined | |||||
| getObjectByName(name: string): IObject3D | undefined | |||||
| getObjectByProperty(name: string, value: string): IObject3D | undefined | |||||
| getObjectById<T extends IObject3D = IObject3D>(id: number): T | undefined | |||||
| getObjectByName<T extends IObject3D = IObject3D>(name: string): T | undefined | |||||
| getObjectByProperty<T extends IObject3D = IObject3D>(name: string, value: string): T | undefined | |||||
| copy(source: this, recursive?: boolean, ...args: any[]): this | copy(source: this, recursive?: boolean, ...args: any[]): this | ||||
| clone(recursive?: boolean): this | clone(recursive?: boolean): this | ||||
| add(...object: IObject3D[]): this | add(...object: IObject3D[]): this |
| traverse(callback: (object: IObject3D) => void): void | traverse(callback: (object: IObject3D) => void): void | ||||
| traverseVisible(callback: (object: IObject3D) => void): void | traverseVisible(callback: (object: IObject3D) => void): void | ||||
| traverseAncestors(callback: (object: IObject3D) => void): void | traverseAncestors(callback: (object: IObject3D) => void): void | ||||
| getObjectById(id: number): IObject3D | undefined | |||||
| getObjectByName(name: string): IObject3D | undefined | |||||
| getObjectByProperty(name: string, value: string): IObject3D | undefined | |||||
| getObjectById<T extends IObject3D = IObject3D>(id: number): T | undefined | |||||
| getObjectByName<T extends IObject3D = IObject3D>(name: string): T | undefined | |||||
| getObjectByProperty<T extends IObject3D = IObject3D>(name: string, value: string): T | undefined | |||||
| copy(source: this, recursive?: boolean): this | copy(source: this, recursive?: boolean): this | ||||
| clone(recursive?: boolean): this | clone(recursive?: boolean): this | ||||
| add(...object: IObject3D[]): this | add(...object: IObject3D[]): this |
| traverse: (callback: (object: IObject3D) => void) => void | traverse: (callback: (object: IObject3D) => void) => void | ||||
| traverseVisible: (callback: (object: IObject3D) => void) => void | traverseVisible: (callback: (object: IObject3D) => void) => void | ||||
| traverseAncestors: (callback: (object: IObject3D) => void) => void | traverseAncestors: (callback: (object: IObject3D) => void) => void | ||||
| getObjectById: (id: number) => IObject3D | undefined | |||||
| getObjectByName: (name: string) => IObject3D | undefined | |||||
| getObjectByProperty: (name: string, value: string) => IObject3D | undefined | |||||
| getObjectById: <T extends IObject3D = IObject3D>(id: number) => T | undefined | |||||
| getObjectByName: <T extends IObject3D = IObject3D>(name: string) => T | undefined | |||||
| getObjectByProperty: <T extends IObject3D = IObject3D>(name: string, value: string) => T | undefined | |||||
| copy: (source: ICamera|Camera, recursive?: boolean, distanceFromTarget?: number) => this | copy: (source: ICamera|Camera, recursive?: boolean, distanceFromTarget?: number) => this | ||||
| clone: (recursive?: boolean) => this | clone: (recursive?: boolean) => this | ||||
| add: (...object: IObject3D[]) => this | add: (...object: IObject3D[]) => this |
| SubtractiveBlending, | SubtractiveBlending, | ||||
| TangentSpaceNormalMap, | TangentSpaceNormalMap, | ||||
| } from 'three' | } from 'three' | ||||
| import {downloadBlob} from 'ts-browser-helpers' | |||||
| import {downloadBlob, uploadFile} from 'ts-browser-helpers' | |||||
| import {PhysicalMaterial} from './PhysicalMaterial' | import {PhysicalMaterial} from './PhysicalMaterial' | ||||
| import {getEmptyMeta} from '../../utils/serialization' | |||||
| export const iMaterialUI = { | export const iMaterialUI = { | ||||
| base: (material: IMaterial): UiObjectConfig[] => [ | base: (material: IMaterial): UiObjectConfig[] => [ | ||||
| { | { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| property: [material, 'transparent'], | property: [material, 'transparent'], | ||||
| onChange: material.setDirty, | |||||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev}), | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'dropdown', | type: 'dropdown', | ||||
| { | { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| property: [material, 'depthTest'], | property: [material, 'depthTest'], | ||||
| onChange: material.setDirty, | |||||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev}), | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| property: [material, 'depthWrite'], | property: [material, 'depthWrite'], | ||||
| onChange: material.setDirty, | |||||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev}), | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'slider', | type: 'slider', | ||||
| }, | }, | ||||
| { | { | ||||
| type: 'button', | type: 'button', | ||||
| label: `Download ${material.constructor.TypeSlug}}`, | |||||
| label: `Download ${material.constructor.TypeSlug}`, | |||||
| value: ()=>{ | value: ()=>{ | ||||
| const blob = new Blob([JSON.stringify(material.toJSON(), null, 2)], {type: 'application/json'}) | const blob = new Blob([JSON.stringify(material.toJSON(), null, 2)], {type: 'application/json'}) | ||||
| downloadBlob(blob, `unlit-material.${material.constructor.TypeSlug}`) | |||||
| downloadBlob(blob, `material.${material.constructor.TypeSlug}`) | |||||
| }, | |||||
| }, | |||||
| { | |||||
| type: 'button', | |||||
| label: `Select ${material.constructor.TypeSlug}`, | |||||
| value: ()=>{ | |||||
| uploadFile(false, false, material.constructor.TypeSlug).then(async(files)=>files?.[0]?.text()).then((text)=>{ | |||||
| if (!text) return | |||||
| const json = JSON.parse(text) | |||||
| if (json.uuid) delete json.uuid // just copy the material properties | |||||
| material.fromJSON(json, getEmptyMeta()) | |||||
| }) | |||||
| }, | }, | ||||
| }, | }, | ||||
| ()=>material.materialExtensions?.map(v=>v.getUiConfig?.(material, material.uiConfig?.uiRefresh)).filter(v=>v), | ()=>material.materialExtensions?.map(v=>v.getUiConfig?.(material, material.uiConfig?.uiRefresh)).filter(v=>v), |
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | ||||
| dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | |||||
| generator?: IMaterialGenerator | generator?: IMaterialGenerator | ||||
| label: 'Physical Material', | label: 'Physical Material', | ||||
| uuid: 'MPM2_' + this.uuid, | uuid: 'MPM2_' + this.uuid, | ||||
| expanded: true, | expanded: true, | ||||
| onChange: (ev)=>{ | |||||
| if (!ev.config || ev.config.onChange) return | |||||
| this.setDirty({uiChangeEvent: ev, needsUpdate: false, refreshUi: true}) | |||||
| }, | |||||
| children: [ | children: [ | ||||
| ...iMaterialUI.base(this), | ...iMaterialUI.base(this), | ||||
| iMaterialUI.blending(this), | iMaterialUI.blending(this), | ||||
| /** | /** | ||||
| * Deserializes the material from JSON. | * Deserializes the material from JSON. | ||||
| * Textures should be loaded and in meta.textures before calling this method. | |||||
| * todo - needs to be tested | |||||
| * Note: some properties that are not serialized in Material.toJSON when they are default values (like side, alphaTest, blending, maps), they wont be reverted back if not present in JSON | |||||
| * If _internal = true, Textures should be loaded and in meta.textures before calling this method. | |||||
| * @param data | * @param data | ||||
| * @param meta | * @param meta | ||||
| * @param _internal | * @param _internal | ||||
| ThreeSerialization.Deserialize(data, this, meta, true) | ThreeSerialization.Deserialize(data, this, meta, true) | ||||
| return this.setValues(data) | return this.setValues(data) | ||||
| } | } | ||||
| ThreeSerialization.Deserialize(data, this, meta, false) | |||||
| this.dispatchEvent({type: 'beforeDeserialize', data, meta, bubbleToObject: true, bubbleToParent: true}) | |||||
| return this | return this | ||||
| } | } | ||||
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | ||||
| dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | |||||
| generator?: IMaterialGenerator | generator?: IMaterialGenerator | ||||
| ThreeSerialization.Deserialize(data, this, meta, true) | ThreeSerialization.Deserialize(data, this, meta, true) | ||||
| return this.setValues(data) | return this.setValues(data) | ||||
| } | } | ||||
| ThreeSerialization.Deserialize(data, this, meta, false) | |||||
| this.dispatchEvent({type: 'beforeDeserialize', data, meta, bubbleToObject: true, bubbleToParent: true}) | |||||
| return this | return this | ||||
| } | } | ||||
| import {copyMaterialUserData} from '../../utils/serialization' | import {copyMaterialUserData} from '../../utils/serialization' | ||||
| import {MaterialExtender, MaterialExtension} from '../../materials' | import {MaterialExtender, MaterialExtension} from '../../materials' | ||||
| import {IScene} from '../IScene' | import {IScene} from '../IScene' | ||||
| import {IMaterial, IMaterialSetDirtyOptions} from '../IMaterial' | |||||
| import {IMaterial, IMaterialEvent, IMaterialSetDirtyOptions} from '../IMaterial' | |||||
| /** | /** | ||||
| * Map of all material properties and their default values in three.js - Material.js | * Map of all material properties and their default values in three.js - Material.js | ||||
| threeMaterialPropList, | threeMaterialPropList, | ||||
| setDirty: function(this: IMaterial, options?: IMaterialSetDirtyOptions): void { | setDirty: function(this: IMaterial, options?: IMaterialSetDirtyOptions): void { | ||||
| this.needsUpdate = true | this.needsUpdate = true | ||||
| this.dispatchEvent({bubbleToObject: true, ...options, type: 'materialUpdate', material: this}) // this sets sceneUpdate in root scene | |||||
| this.dispatchEvent({bubbleToObject: true, bubbleToParent: true, ...options, type: 'materialUpdate'}) // this sets sceneUpdate in root scene | |||||
| this.uiConfig?.uiRefresh?.(true, 'postFrame', 1) | this.uiConfig?.uiRefresh?.(true, 'postFrame', 1) | ||||
| }, | }, | ||||
| setValues: (superSetValues: Material['setValues']): IMaterial['setValues'] => | setValues: (superSetValues: Material['setValues']): IMaterial['setValues'] => | ||||
| return material | return material | ||||
| }, | }, | ||||
| dispatchEvent: (superDispatchEvent: Material['dispatchEvent']): IMaterial['dispatchEvent'] => | |||||
| function(this: IMaterial, event: IMaterialEvent): void { | |||||
| superDispatchEvent.call(this, event) | |||||
| const type = event.type | |||||
| if (event.bubbleToObject && ( | |||||
| type === 'beforeDeserialize' || type === 'materialUpdate' // todo - add more events | |||||
| )) { | |||||
| this.appliedMeshes.forEach(m => m.dispatchEvent({...event, material: this, type})) | |||||
| } | |||||
| }, | |||||
| registerMaterialExtensions: function(this: IMaterial, customMaterialExtensions: MaterialExtension[]): void { | registerMaterialExtensions: function(this: IMaterial, customMaterialExtensions: MaterialExtension[]): void { | ||||
| MaterialExtender.RegisterExtensions(this, customMaterialExtensions) | MaterialExtender.RegisterExtensions(this, customMaterialExtensions) | ||||
| this.assetType = 'material' | this.assetType = 'material' | ||||
| this.setValues = iMaterialCommons.setValues(this.setValues) | this.setValues = iMaterialCommons.setValues(this.setValues) | ||||
| this.clone = iMaterialCommons.clone(this.clone) | this.clone = iMaterialCommons.clone(this.clone) | ||||
| this.dispatchEvent = iMaterialCommons.dispatchEvent(this.dispatchEvent) | |||||
| // todo: add uiconfig, serialization, other stuff from UnlitMaterial? | // todo: add uiconfig, serialization, other stuff from UnlitMaterial? | ||||
| // dispose uiconfig etc. on dispose | // dispose uiconfig etc. on dispose |
| } | } | ||||
| /** | /** | ||||
| * Load model root scene exported to GLTF format. Used internally by {@link ThreeViewer.addSceneObject}. | |||||
| Load model root scene exported to GLTF format. Used internally by {@link ThreeViewer.addSceneObject}. | |||||
| * @param obj | * @param obj | ||||
| * @param options | * @param options | ||||
| */ | */ | ||||
| traverse: (callback: (object: IObject3D) => void) => void | traverse: (callback: (object: IObject3D) => void) => void | ||||
| traverseVisible: (callback: (object: IObject3D) => void) => void | traverseVisible: (callback: (object: IObject3D) => void) => void | ||||
| traverseAncestors: (callback: (object: IObject3D) => void) => void | traverseAncestors: (callback: (object: IObject3D) => void) => void | ||||
| getObjectById: (id: number) => IObject3D | undefined | |||||
| getObjectByName: (name: string) => IObject3D | undefined | |||||
| getObjectByProperty: (name: string, value: string) => IObject3D | undefined | |||||
| copy: (source: IObject3D, recursive?: boolean) => this | |||||
| getObjectById: <T extends IObject3D = IObject3D>(id: number) => T | undefined | |||||
| getObjectByName: <T extends IObject3D = IObject3D>(name: string) => T | undefined | |||||
| getObjectByProperty: <T extends IObject3D = IObject3D>(name: string, value: string) => T | undefined | |||||
| copy: (source: this, recursive?: boolean, ...args: any[]) => this | |||||
| clone: (recursive?: boolean) => this | clone: (recursive?: boolean) => this | ||||
| remove: (...object: IObject3D[]) => this | remove: (...object: IObject3D[]) => this | ||||
| dispatchEvent: (event: ISceneEvent) => void | dispatchEvent: (event: ISceneEvent) => void |
| import {Event, Mesh, Vector3} from 'three' | import {Event, Mesh, Vector3} from 'three' | ||||
| import {IMaterial, IMaterialEvent} from '../IMaterial' | |||||
| import {IMaterial} from '../IMaterial' | |||||
| import {objectHasOwn} from 'ts-browser-helpers' | import {objectHasOwn} from 'ts-browser-helpers' | ||||
| import {IObject3D, IObject3DEvent, IObjectProcessor, IObjectSetDirtyOptions} from '../IObject' | import {IObject3D, IObject3DEvent, IObjectProcessor, IObjectSetDirtyOptions} from '../IObject' | ||||
| import {copyObject3DUserData} from '../../utils/serialization' | import {copyObject3DUserData} from '../../utils/serialization' | ||||
| }) | }) | ||||
| } | } | ||||
| }, | }, | ||||
| onMaterialUpdate: function(this: IObject3D, e: IMaterialEvent<'materialUpdate'>): void { | |||||
| if (!e.bubbleToObject) return | |||||
| this.dispatchEvent({bubbleToParent: true, ...e, object: this, material: e.target}) | |||||
| }, | |||||
| onGeometryUpdate: function(this: IObject3D, e: IGeometryEvent<'geometryUpdate'>): void { | onGeometryUpdate: function(this: IObject3D, e: IGeometryEvent<'geometryUpdate'>): void { | ||||
| if (!e.bubbleToObject) return | if (!e.bubbleToObject) return | ||||
| this.dispatchEvent({bubbleToParent: true, ...e, object: this, geometry: e.geometry}) | this.dispatchEvent({bubbleToParent: true, ...e, object: this, geometry: e.geometry}) | ||||
| const mats = Array.isArray(this.material) ? [...(this.material as IMaterial[])] : [this.material!] | const mats = Array.isArray(this.material) ? [...(this.material as IMaterial[])] : [this.material!] | ||||
| for (const mat of mats) { | for (const mat of mats) { | ||||
| if (!mat) continue | if (!mat) continue | ||||
| this._onMaterialUpdate && mat.removeEventListener('materialUpdate', this._onMaterialUpdate) | |||||
| if (mat.appliedMeshes) { | if (mat.appliedMeshes) { | ||||
| mat.appliedMeshes.delete(this) | mat.appliedMeshes.delete(this) | ||||
| if (mat.userData && mat.appliedMeshes?.size === 0 && mat.userData.disposeOnIdle !== false) | if (mat.userData && mat.appliedMeshes?.size === 0 && mat.userData.disposeOnIdle !== false) | ||||
| } | } | ||||
| materials.push(mat) | materials.push(mat) | ||||
| if (mat) { | if (mat) { | ||||
| this._onMaterialUpdate && mat.addEventListener('materialUpdate', this._onMaterialUpdate) | |||||
| mat.appliedMeshes.add(this) | mat.appliedMeshes.add(this) | ||||
| } | } | ||||
| } | } | ||||
| if ((this.isMesh || this.isLine) && !this.userData.__meshSetup) { | if ((this.isMesh || this.isLine) && !this.userData.__meshSetup) { | ||||
| this.userData.__meshSetup = true | this.userData.__meshSetup = true | ||||
| this._onMaterialUpdate = (e: IMaterialEvent) => iObjectCommons.eventCallbacks.onMaterialUpdate.call(this, e) | |||||
| this._onGeometryUpdate = (e: IGeometryEvent) => iObjectCommons.eventCallbacks.onGeometryUpdate.call(this, e) | this._onGeometryUpdate = (e: IGeometryEvent) => iObjectCommons.eventCallbacks.onGeometryUpdate.call(this, e) | ||||
| // Material, Geometry prop init | // Material, Geometry prop init | ||||
| // if (oldGeom && oldGeom.userData && oldGeom.appliedMeshes?.size === 0 && oldGeom.userData.disposeOnIdle !== false) oldGeom.dispose() | // if (oldGeom && oldGeom.userData && oldGeom.appliedMeshes?.size === 0 && oldGeom.userData.disposeOnIdle !== false) oldGeom.dispose() | ||||
| } | } | ||||
| delete this._onMaterialUpdate | |||||
| delete this._onGeometryUpdate | delete this._onGeometryUpdate | ||||
| }) | }) | ||||
| // data has deserialized textures and userData, assuming the rest can be deserialized by material.fromJSON | // data has deserialized textures and userData, assuming the rest can be deserialized by material.fromJSON | ||||
| if (!obj || !obj.isMaterial || obj.type !== type) { | |||||
| if (obj && Object.keys(obj).length) console.warn('Material type mismatch during deserialize, creating a new material', obj, data) | |||||
| if (!obj || !obj.isMaterial || obj.type !== type && obj.constructor?.TYPE !== type) { | |||||
| if (obj && Object.keys(obj).length) console.warn('Material type mismatch during deserialize, creating a new material', obj, data, type, obj.constructor?.type) | |||||
| obj = null | obj = null | ||||
| } | } | ||||
| // if obj is not null | // if obj is not null |