Procházet zdrojové kódy

Handle material bubbleToObject, implement physical, unlit material fromJSON, some type changes and refactor

master
Palash Bansal před 3 roky
rodič
revize
3605b0448a
Žádný účet není propojen s e-mailovou adresou tvůrce revize

+ 1
- 1
package.json Zobrazit soubor

@@ -15,7 +15,7 @@
"build-examples": "tsc --project examples/tsconfig.build.json",
"dev-examples": "tsc --project examples/tsconfig.build.json -w",
"serve-docs": "ws -d docs -p 8080",
"serve": "ws -d . -p 8000",
"serve": "ws -d . -p 9229",
"docs": "npx typedoc && markdown-to-html",
"prepare": "npm run build && npm run build-examples && npm run docs"
},

+ 18
- 0
src/assetmanager/AssetManager.ts Zobrazit soubor

@@ -89,6 +89,7 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
this.viewer = viewer
this.viewer.scene.addEventListener('addSceneObject', this._sceneUpdated)
this.viewer.scene.addEventListener('materialChanged', this._sceneUpdated)
this.viewer.scene.addEventListener('beforeDeserialize', this._sceneUpdated)
this._initCacheStorage(simpleCache, storage ?? true)

this.importer.addEventListener('processRaw', (event)=>{
@@ -278,6 +279,23 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult}
for (const t of targets) {
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 {
console.error('Unexpected')
}

+ 8
- 3
src/assetmanager/import/JSONMaterialLoader.ts Zobrazit soubor

@@ -1,6 +1,7 @@
import {SimpleJSONLoader} from './SimpleJSONLoader'
import {ThreeViewer} from '../../viewer'
import {getEmptyMeta, SerializationMetaType, ThreeSerialization} from '../../utils/serialization'
import {IMaterial} from '../../core'

export class JSONMaterialLoader extends SimpleJSONLoader {

@@ -10,7 +11,11 @@ export class JSONMaterialLoader extends SimpleJSONLoader {
if (!this.viewer) throw 'Viewer not set in JSONMaterialLoader.'

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}
if (json.images) {
if (Array.isArray(json.images)) meta.images = Object.fromEntries(json.images.map((i: any) => [i.uuid, i]))
@@ -27,7 +32,7 @@ export class JSONMaterialLoader extends SimpleJSONLoader {
else meta.materials = json.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)
}
}

+ 3
- 3
src/core/ICamera.ts Zobrazit soubor

@@ -90,9 +90,9 @@ export interface ICamera<E extends ICameraEvent = ICameraEvent, ET extends ICame
traverse(callback: (object: IObject3D) => void): void
traverseVisible(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
clone(recursive?: boolean): this
add(...object: IObject3D[]): this

+ 26
- 5
src/core/IMaterial.ts Zobrazit soubor

@@ -1,23 +1,44 @@
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 {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 {ITexture} from './ITexture'
import type {IImportResultUserData} from '../assetmanager'

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 & {
type: T
bubbleToObject?: boolean
bubbleToParent?: boolean
material?: IMaterial

texture?: 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{
uuid?: string // adding to userdata also, so that its saved in gltf


+ 5
- 7
src/core/IObject.ts Zobrazit soubor

@@ -1,5 +1,5 @@
import {IDisposable} from 'ts-browser-helpers'
import {IMaterial, IMaterialEvent} from './IMaterial'
import {IMaterial} from './IMaterial'
import {Event, Object3D} from 'three'
import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js'
import {IGeometry, IGeometryEvent} from './IGeometry'
@@ -7,7 +7,7 @@ import {IImportResultUserData} from '../assetmanager'
import {GLTF} from 'three/examples/jsm/loaders/GLTFLoader.js'

export type IObject3DEventTypes = 'dispose' | 'materialUpdate' | 'objectUpdate' | 'geometryChanged' |
'materialChanged' | 'geometryUpdate' | 'added' | 'removed' | 'select' |
'materialChanged' | 'geometryUpdate' | 'added' | 'removed' | 'select' | 'beforeDeserialize' |
'setView' | 'activateMain' | 'cameraUpdate' // from camera
// | string
export interface IObject3DEvent<T extends string = IObject3DEventTypes> extends Event {
@@ -168,8 +168,6 @@ export interface IObject3D<E extends Event = IObject3DEvent, ET = IObject3DEvent
modelObject: this


// eslint-disable-next-line @typescript-eslint/naming-convention
_onMaterialUpdate?: (e: IMaterialEvent<'materialUpdate'>) => void
// eslint-disable-next-line @typescript-eslint/naming-convention
_onGeometryUpdate?: (e: IGeometryEvent<'geometryUpdate'>) => void

@@ -183,9 +181,9 @@ export interface IObject3D<E extends Event = IObject3DEvent, ET = IObject3DEvent
traverse(callback: (object: IObject3D) => void): void
traverseVisible(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
clone(recursive?: boolean): this
add(...object: IObject3D[]): this

+ 3
- 3
src/core/IScene.ts Zobrazit soubor

@@ -127,9 +127,9 @@ export interface IScene<E extends ISceneEvent = ISceneEvent, ET extends ISceneEv
traverse(callback: (object: IObject3D) => void): void
traverseVisible(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
clone(recursive?: boolean): this
add(...object: IObject3D[]): this

+ 3
- 3
src/core/camera/PerspectiveCamera2.ts Zobrazit soubor

@@ -453,9 +453,9 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera {
traverse: (callback: (object: IObject3D) => void) => void
traverseVisible: (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
clone: (recursive?: boolean) => this
add: (...object: IObject3D[]) => this

+ 19
- 6
src/core/material/IMaterialUi.ts Zobrazit soubor

@@ -25,8 +25,9 @@ import {
SubtractiveBlending,
TangentSpaceNormalMap,
} from 'three'
import {downloadBlob} from 'ts-browser-helpers'
import {downloadBlob, uploadFile} from 'ts-browser-helpers'
import {PhysicalMaterial} from './PhysicalMaterial'
import {getEmptyMeta} from '../../utils/serialization'

export const iMaterialUI = {
base: (material: IMaterial): UiObjectConfig[] => [
@@ -73,7 +74,7 @@ export const iMaterialUI = {
{
type: 'checkbox',
property: [material, 'transparent'],
onChange: material.setDirty,
onChange: (ev)=>material.setDirty({uiChangeEvent: ev}),
},
{
type: 'dropdown',
@@ -95,12 +96,12 @@ export const iMaterialUI = {
{
type: 'checkbox',
property: [material, 'depthTest'],
onChange: material.setDirty,
onChange: (ev)=>material.setDirty({uiChangeEvent: ev}),
},
{
type: 'checkbox',
property: [material, 'depthWrite'],
onChange: material.setDirty,
onChange: (ev)=>material.setDirty({uiChangeEvent: ev}),
},
{
type: 'slider',
@@ -229,10 +230,22 @@ export const iMaterialUI = {
},
{
type: 'button',
label: `Download ${material.constructor.TypeSlug}}`,
label: `Download ${material.constructor.TypeSlug}`,
value: ()=>{
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),

+ 8
- 3
src/core/material/PhysicalMaterial.ts Zobrazit soubor

@@ -45,6 +45,7 @@ export class PhysicalMaterial extends MeshPhysicalMaterial<IMaterialEvent, Physi
readonly appliedMeshes: Set<IObject3D> = new Set()
readonly setDirty = iMaterialCommons.setDirty
clone(): this {return iMaterialCommons.clone(super.clone).call(this)}
dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)}

generator?: IMaterialGenerator

@@ -123,6 +124,10 @@ export class PhysicalMaterial extends MeshPhysicalMaterial<IMaterialEvent, Physi
label: 'Physical Material',
uuid: 'MPM2_' + this.uuid,
expanded: true,
onChange: (ev)=>{
if (!ev.config || ev.config.onChange) return
this.setDirty({uiChangeEvent: ev, needsUpdate: false, refreshUi: true})
},
children: [
...iMaterialUI.base(this),
iMaterialUI.blending(this),
@@ -188,8 +193,8 @@ export class PhysicalMaterial extends MeshPhysicalMaterial<IMaterialEvent, Physi

/**
* 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 meta
* @param _internal
@@ -199,7 +204,7 @@ export class PhysicalMaterial extends MeshPhysicalMaterial<IMaterialEvent, Physi
ThreeSerialization.Deserialize(data, this, meta, true)
return this.setValues(data)
}
ThreeSerialization.Deserialize(data, this, meta, false)
this.dispatchEvent({type: 'beforeDeserialize', data, meta, bubbleToObject: true, bubbleToParent: true})
return this
}


+ 2
- 1
src/core/material/UnlitMaterial.ts Zobrazit soubor

@@ -45,6 +45,7 @@ export class UnlitMaterial extends MeshBasicMaterial<IMaterialEvent, UnlitMateri
readonly appliedMeshes: Set<IObject3D> = new Set()
readonly setDirty = iMaterialCommons.setDirty
clone(): this {return iMaterialCommons.clone(super.clone).call(this)}
dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)}

generator?: IMaterialGenerator

@@ -159,7 +160,7 @@ export class UnlitMaterial extends MeshBasicMaterial<IMaterialEvent, UnlitMateri
ThreeSerialization.Deserialize(data, this, meta, true)
return this.setValues(data)
}
ThreeSerialization.Deserialize(data, this, meta, false)
this.dispatchEvent({type: 'beforeDeserialize', data, meta, bubbleToObject: true, bubbleToParent: true})
return this
}


+ 13
- 2
src/core/material/iMaterialCommons.ts Zobrazit soubor

@@ -18,7 +18,7 @@ import {copyProps} from 'ts-browser-helpers'
import {copyMaterialUserData} from '../../utils/serialization'
import {MaterialExtender, MaterialExtension} from '../../materials'
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
@@ -75,7 +75,7 @@ export const iMaterialCommons = {
threeMaterialPropList,
setDirty: function(this: IMaterial, options?: IMaterialSetDirtyOptions): void {
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)
},
setValues: (superSetValues: Material['setValues']): IMaterial['setValues'] =>
@@ -123,6 +123,16 @@ export const iMaterialCommons = {

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 {
MaterialExtender.RegisterExtensions(this, customMaterialExtensions)
@@ -193,6 +203,7 @@ export function upgradeMaterial(this: IMaterial): IMaterial {
this.assetType = 'material'
this.setValues = iMaterialCommons.setValues(this.setValues)
this.clone = iMaterialCommons.clone(this.clone)
this.dispatchEvent = iMaterialCommons.dispatchEvent(this.dispatchEvent)

// todo: add uiconfig, serialization, other stuff from UnlitMaterial?
// dispose uiconfig etc. on dispose

+ 5
- 5
src/core/object/RootScene.ts Zobrazit soubor

@@ -160,7 +160,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I
}

/**
* 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 options
*/
@@ -512,10 +512,10 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I
traverse: (callback: (object: IObject3D) => void) => void
traverseVisible: (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
remove: (...object: IObject3D[]) => this
dispatchEvent: (event: ISceneEvent) => void

+ 1
- 9
src/core/object/iObjectCommons.ts Zobrazit soubor

@@ -1,5 +1,5 @@
import {Event, Mesh, Vector3} from 'three'
import {IMaterial, IMaterialEvent} from '../IMaterial'
import {IMaterial} from '../IMaterial'
import {objectHasOwn} from 'ts-browser-helpers'
import {IObject3D, IObject3DEvent, IObjectProcessor, IObjectSetDirtyOptions} from '../IObject'
import {copyObject3DUserData} from '../../utils/serialization'
@@ -95,10 +95,6 @@ export const iObjectCommons = {
})
}
},
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 {
if (!e.bubbleToObject) return
this.dispatchEvent({bubbleToParent: true, ...e, object: this, geometry: e.geometry})
@@ -160,7 +156,6 @@ export const iObjectCommons = {
const mats = Array.isArray(this.material) ? [...(this.material as IMaterial[])] : [this.material!]
for (const mat of mats) {
if (!mat) continue
this._onMaterialUpdate && mat.removeEventListener('materialUpdate', this._onMaterialUpdate)
if (mat.appliedMeshes) {
mat.appliedMeshes.delete(this)
if (mat.userData && mat.appliedMeshes?.size === 0 && mat.userData.disposeOnIdle !== false)
@@ -178,7 +173,6 @@ export const iObjectCommons = {
}
materials.push(mat)
if (mat) {
this._onMaterialUpdate && mat.addEventListener('materialUpdate', this._onMaterialUpdate)
mat.appliedMeshes.add(this)
}
}
@@ -385,7 +379,6 @@ function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectPr
if ((this.isMesh || this.isLine) && !this.userData.__meshSetup) {
this.userData.__meshSetup = true

this._onMaterialUpdate = (e: IMaterialEvent) => iObjectCommons.eventCallbacks.onMaterialUpdate.call(this, e)
this._onGeometryUpdate = (e: IGeometryEvent) => iObjectCommons.eventCallbacks.onGeometryUpdate.call(this, e)

// Material, Geometry prop init
@@ -415,7 +408,6 @@ function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectPr
// if (oldGeom && oldGeom.userData && oldGeom.appliedMeshes?.size === 0 && oldGeom.userData.disposeOnIdle !== false) oldGeom.dispose()
}

delete this._onMaterialUpdate
delete this._onGeometryUpdate
})


+ 2
- 2
src/utils/serialization.ts Zobrazit soubor

@@ -232,8 +232,8 @@ export class ThreeSerialization {

// 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
}
// if obj is not null

Načítá se…
Zrušit
Uložit