| @@ -92,6 +92,16 @@ declare module 'three'{ | |||
| bubbleToParent?: boolean | |||
| uiChangeEvent?: ChangeEvent | |||
| } | |||
| select: { // todo remove? | |||
| ui?: boolean | |||
| // focusCamera?: boolean // todo ? | |||
| bubbleToObject?: boolean | |||
| bubbleToParent?: boolean | |||
| material: IMaterial | |||
| value?: /* IObject3D | */ IMaterial | null // todo is this required? | |||
| source?: string // who is triggering the event. so that recursive events can be prevented | |||
| } /* & IObjectSetDirtyOptions*/ | |||
| } | |||
| } | |||
| @@ -105,6 +115,8 @@ export interface IMaterialSetDirtyOptions extends ISetDirtyCommonOptions{ | |||
| */ | |||
| needsUpdate?: boolean, | |||
| change?: string | keyof IMaterial | |||
| [key: string]: any | |||
| } | |||
| export interface IMaterialUserData extends IImportResultUserData{ | |||
| @@ -1,7 +1,7 @@ | |||
| import {IMaterial, IMaterialEventMap} from './IMaterial' | |||
| import {IMaterial, IMaterialEventMap, IMaterialSetDirtyOptions} from './IMaterial' | |||
| import {Box3, EventListener2, Object3D, Object3DEventMap, Sphere, Vector3} from 'three' | |||
| import {ChangeEvent, IUiConfigContainer, UiObjectConfig} from 'uiconfig.js' | |||
| import {IGeometry, IGeometryEventMap} from './IGeometry' | |||
| import {IGeometry, IGeometryEventMap, IGeometrySetDirtyOptions} from './IGeometry' | |||
| import {IImportResultUserData} from '../assetmanager' | |||
| import {GLTF} from 'three/examples/jsm/loaders/GLTFLoader.js' | |||
| import {ICamera, type ICameraSetDirtyOptions} from './ICamera' | |||
| @@ -24,12 +24,12 @@ import {ICamera, type ICameraSetDirtyOptions} from './ICamera' | |||
| declare module 'three'{ | |||
| export interface Object3DEventMap{ | |||
| select: { // todo | |||
| select: { // todo remove? | |||
| ui?: boolean | |||
| focusCamera?: boolean | |||
| bubbleToParent?: boolean | |||
| object: IObject3D | |||
| value?: IObject3D /* | Material*/ // todo is this required? | |||
| value?: IObject3D|null /* | Material*/ // todo is this required? | |||
| source?: string // who is triggering the event. so that recursive events can be prevented | |||
| } /* & IObjectSetDirtyOptions*/ | |||
| @@ -47,13 +47,12 @@ export interface IObject3DEventMap extends Object3DEventMap{ | |||
| materialUpdate: { | |||
| // object: IObject3D | |||
| material: IMaterial|IMaterial[] | |||
| } | |||
| } & IMaterialSetDirtyOptions | |||
| objectUpdate: { | |||
| object: IObject3D | |||
| change?: string | |||
| args?: any[] | |||
| bubbleToParent: boolean | |||
| } | |||
| } & Omit<IObjectSetDirtyOptions, 'bubbleToParent'> | |||
| textureUpdate: { | |||
| // object: IObject3D | |||
| // todo | |||
| @@ -75,7 +74,7 @@ export interface IObject3DEventMap extends Object3DEventMap{ | |||
| geometry: IGeometry | |||
| // oldGeometry: IGeometry | |||
| bubbleToParent: boolean | |||
| } | |||
| } & IGeometrySetDirtyOptions | |||
| added: { | |||
| // object: IObject3D | |||
| // todo | |||
| @@ -100,7 +99,6 @@ export interface IObject3DEventMap extends Object3DEventMap{ | |||
| camera?: ICamera | null | |||
| bubbleToParent: boolean | |||
| // object: IObject3D | |||
| } | |||
| cameraUpdate: { | |||
| ui?: boolean | |||
| @@ -108,7 +106,7 @@ export interface IObject3DEventMap extends Object3DEventMap{ | |||
| // object: IObject3D | |||
| bubbleToParent: boolean | |||
| // todo | |||
| } & ICameraSetDirtyOptions | |||
| } & Omit<ICameraSetDirtyOptions, 'bubbleToParent'> | |||
| } | |||
| // Record<keyof IObject3DEventMap0, IObject3DEventMap0[keyof IObject3DEventMap0] & { | |||
| // // bubbleToParent?: boolean | |||
| @@ -304,6 +302,7 @@ export interface IObject3D<TE extends IObject3DEventMap = IObject3DEventMap> ext | |||
| isScene?: boolean | |||
| // isHelper?: boolean | |||
| isWidget?: boolean | |||
| isPoints?: boolean | |||
| readonly isObject3D: true | |||
| material?: IMaterial | IMaterial[] | |||
| @@ -97,7 +97,7 @@ export const iMaterialUI = { | |||
| { | |||
| type: 'checkbox', | |||
| property: [material, 'transparent'], | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev}), | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev, change: 'transparent'}), | |||
| }, | |||
| { | |||
| type: 'dropdown', | |||
| @@ -119,17 +119,17 @@ export const iMaterialUI = { | |||
| { | |||
| type: 'checkbox', | |||
| property: [material, 'depthTest'], | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev}), | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev, change: 'depthTest'}), | |||
| }, | |||
| { | |||
| type: 'checkbox', | |||
| property: [material, 'depthWrite'], | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev}), | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev, change: 'depthWrite'}), | |||
| }, | |||
| { | |||
| type: 'checkbox', | |||
| property: [material, 'colorWrite'], | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev}), | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev, change: 'colorWrite'}), | |||
| }, | |||
| { | |||
| type: 'slider', | |||
| @@ -270,7 +270,7 @@ export const iMaterialUI = { | |||
| setValue: (v: boolean)=>{ | |||
| if (!v && !material.userData.renderToGBuffer) return | |||
| material.userData.renderToGBuffer = v | |||
| material.setDirty() | |||
| material.setDirty({change: 'userData', key: 'renderToGBuffer'}) | |||
| }, | |||
| }, | |||
| { | |||
| @@ -281,7 +281,7 @@ export const iMaterialUI = { | |||
| setValue: (v: boolean)=>{ | |||
| if (!v && !material.userData.renderToDepth) return | |||
| material.userData.renderToDepth = v | |||
| material.setDirty() | |||
| material.setDirty({change: 'userData', key: 'renderToDepth'}) | |||
| }, | |||
| }, | |||
| material.isPhysicalMaterial ? { | |||
| @@ -291,7 +291,7 @@ export const iMaterialUI = { | |||
| getValue: ()=>material.userData.inverseAlphaMap === true, | |||
| setValue: (v: boolean)=>{ | |||
| material.userData.inverseAlphaMap = v ? v : undefined | |||
| material.setDirty() | |||
| material.setDirty({change: 'userData', key: 'inverseAlphaMap'}) | |||
| }, | |||
| } : {}, | |||
| ], | |||
| @@ -382,7 +382,7 @@ export const iMaterialUI = { | |||
| setValue: (v: string)=>{ | |||
| material.userData.envMapSlotKey = v | |||
| if (!v) delete material.userData.envMapSlotKey | |||
| material.setDirty() | |||
| material.setDirty({change: 'userData', key: 'envMapSlotKey'}) | |||
| }, | |||
| }, | |||
| ], | |||
| @@ -654,14 +654,14 @@ export const iMaterialUI = { | |||
| bounds: [0, 500], | |||
| label: 'Thickness0', | |||
| property: [material.iridescenceThicknessRange, '0'], | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev}), | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev, change: 'iridescenceThicknessRange', key: '0'}), | |||
| }, | |||
| { | |||
| type: 'slider', | |||
| bounds: [0, 500], | |||
| label: 'Thickness1', | |||
| property: [material.iridescenceThicknessRange, '1'], | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev}), | |||
| onChange: (ev)=>material.setDirty({uiChangeEvent: ev, change: 'iridescenceThicknessRange', key: '1'}), | |||
| }, | |||
| { | |||
| type: 'image', | |||
| @@ -171,8 +171,10 @@ export class LegacyPhongMaterial<TE extends IMaterialEventMap = IMaterialEventMa | |||
| expanded: true, | |||
| onChange: (ev)=>{ | |||
| if (!ev.config || ev.config.onChange) return | |||
| let key = Array.isArray(ev.config.property) ? ev.config.property[1] : ev.config.property | |||
| key = typeof key === 'string' ? key : undefined | |||
| // todo set needsUpdate true only for properties that require it like maps. | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last}) | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last, change: key}) | |||
| }, | |||
| children: [ | |||
| ...iMaterialUI.base(this), | |||
| @@ -134,8 +134,10 @@ export class LineMaterial2<TE extends IMaterialEventMap = IMaterialEventMap> ext | |||
| // m.computeLineDistances() | |||
| // } | |||
| // }) | |||
| let key = Array.isArray(ev.config.property) ? ev.config.property[1] : ev.config.property | |||
| key = typeof key === 'string' ? key : undefined | |||
| // todo set needsUpdate true only for properties that require it like maps. | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last}) | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last, change: key}) | |||
| }, | |||
| children: [ | |||
| ...generateUiConfig(this) || [], | |||
| @@ -158,8 +158,10 @@ export class ObjectShaderMaterial<TE extends IMaterialEventMap = IMaterialEventM | |||
| expanded: true, | |||
| onChange: (ev)=>{ | |||
| if (!ev.config || ev.config.onChange) return | |||
| let key = Array.isArray(ev.config.property) ? ev.config.property[1] : ev.config.property | |||
| key = typeof key === 'string' ? key : undefined | |||
| // todo set needsUpdate true only for properties that require it like maps. | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last}) | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last, change: key}) | |||
| }, | |||
| children: [ | |||
| ...generateUiConfig(this), | |||
| @@ -133,8 +133,10 @@ export class PhysicalMaterial<TE extends IMaterialEventMap = IMaterialEventMap> | |||
| onChange: (ev)=>{ | |||
| if (!ev.config || ev.config.onChange) return | |||
| // todo frameFade | |||
| let key = Array.isArray(ev.config.property) ? ev.config.property[1] : ev.config.property | |||
| key = typeof key === 'string' ? key : undefined | |||
| // todo set needsUpdate true only for properties that require it like maps. | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last}) | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last, change: key}) | |||
| }, | |||
| children: [ | |||
| ...iMaterialUI.base(this), | |||
| @@ -161,8 +161,10 @@ export class UnlitLineMaterial<TE extends IMaterialEventMap = IMaterialEventMap> | |||
| expanded: true, | |||
| onChange: (ev)=>{ | |||
| if (!ev.config || ev.config.onChange) return | |||
| let key = Array.isArray(ev.config.property) ? ev.config.property[1] : ev.config.property | |||
| key = typeof key === 'string' ? key : undefined | |||
| // todo set needsUpdate true only for properties that require it like maps. | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last}) | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last, change: key}) | |||
| }, | |||
| children: [ | |||
| { | |||
| @@ -176,8 +176,10 @@ export class UnlitMaterial<TE extends IMaterialEventMap = IMaterialEventMap> ext | |||
| expanded: true, | |||
| onChange: (ev)=>{ | |||
| if (!ev.config || ev.config.onChange) return | |||
| let key = Array.isArray(ev.config.property) ? ev.config.property[1] : ev.config.property | |||
| key = typeof key === 'string' ? key : undefined | |||
| // todo set needsUpdate true only for properties that require it like maps. | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last}) | |||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last, change: key}) | |||
| }, | |||
| children: [ | |||
| ...iMaterialUI.base(this), | |||
| @@ -80,7 +80,7 @@ export const iMaterialCommons = { | |||
| setDirty: function(this: IMaterial, options?: IMaterialSetDirtyOptions): void { | |||
| if (options?.needsUpdate !== false) this.needsUpdate = true | |||
| this.dispatchEvent({bubbleToObject: true, bubbleToParent: true, ...options, type: 'materialUpdate'}) // this sets sceneUpdate in root scene | |||
| if (options?.last !== false) this.uiConfig?.uiRefresh?.(true, 'postFrame', 1) | |||
| if (options?.last !== false && options?.refreshUi !== false) this.uiConfig?.uiRefresh?.(true, 'postFrame', 1) | |||
| }, | |||
| setValues: (superSetValues: Material['setValues']): IMaterial['setValues'] => | |||
| function(this: IMaterial, parameters: Material | (MaterialParameters & {type?: string})): IMaterial { | |||
| @@ -152,7 +152,7 @@ export const iMaterialCommons = { | |||
| superDispatchEvent.call(this, event) | |||
| const type = event.type | |||
| if ((event as IMaterialEventMap['materialUpdate']).bubbleToObject && ( | |||
| type === 'beforeDeserialize' || type === 'materialUpdate' || type === 'textureUpdate' // todo - add more events | |||
| type === 'beforeDeserialize' || type === 'materialUpdate' || type === 'textureUpdate' || type === 'select' // todo - add more events | |||
| )) { | |||
| this.appliedMeshes.forEach(m => m.dispatchEvent({...event, material: this, type})) | |||
| } | |||
| @@ -69,7 +69,9 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| expanded: true, | |||
| onChange: (ev)=>{ | |||
| if (!ev.config || ev.config.onChange) return | |||
| this.setDirty({uiChangeEvent: ev, refreshScene: false, refreshUi: true}) | |||
| let key = Array.isArray(ev.config.property) ? ev.config.property[1] : ev.config.property | |||
| key = typeof key === 'string' ? key : undefined | |||
| this.setDirty({uiChangeEvent: ev, refreshScene: false, refreshUi: !!ev.last, change: key}) | |||
| }, | |||
| children: [ | |||
| { | |||
| @@ -104,7 +106,7 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| label: 'Name', | |||
| property: [this, 'name'], | |||
| onChange: (e)=>{ | |||
| if (e.last) this.setDirty?.({uiChangeEvent: e, refreshScene: true, frameFade: false, refreshUi: true}) | |||
| if (e.last) this.setDirty?.({uiChangeEvent: e, refreshScene: true, frameFade: false, refreshUi: true, change: 'name'}) | |||
| }, | |||
| }, | |||
| { | |||
| @@ -183,12 +185,33 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| { | |||
| type: 'button', | |||
| label: 'Pivot to Node Center', | |||
| tags: ['context-menu', 'interaction'], | |||
| value: async()=>{ | |||
| const res = await ThreeViewer.Dialog.confirm('Pivot to Center: Adjust the pivot to bounding box center. The object will rotate around the new pivot, are you sure you want to proceed?') | |||
| if (!res) return | |||
| return this.pivotToBoundsCenter?.(true) // return value is the undo function | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Duplicate Object', | |||
| tags: ['context-menu'], | |||
| value: async()=>{ | |||
| const parent = this.parent | |||
| const clone = this.clone(true) as IObject3D | |||
| clone.name = this.name + ' (copy)' | |||
| return { | |||
| action: ()=>{ | |||
| if (parent && !clone.parent) | |||
| parent.add(clone) // todo same index? | |||
| }, | |||
| undo: ()=>{ | |||
| if (clone.parent === parent) | |||
| clone.removeFromParent() | |||
| }, | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Delete Object', | |||
| @@ -86,9 +86,11 @@ export class ScreenPass extends ExtendedShaderPass implements IPipelinePass<'scr | |||
| @uiToggle() clipBackground = false | |||
| beforeRender(_: IScene, _1: ICamera, renderManager: ViewerRenderManager) { | |||
| this.material.uniforms.tTransparent.value = renderManager.renderPass.preserveTransparentTarget ? renderManager.renderPass.transparentTarget?.texture || null : null | |||
| this.material.defines.HAS_TRANSPARENT_TARGET = this.material.uniforms.tTransparent.value ? 1 : undefined | |||
| if (!this.material.defines.HAS_TRANSPARENT_TARGET) delete this.material.defines.HAS_TRANSPARENT_TARGET | |||
| if (this.material.uniforms.tTransparent) { | |||
| this.material.uniforms.tTransparent.value = renderManager.renderPass.preserveTransparentTarget ? renderManager.renderPass.transparentTarget?.texture || null : null | |||
| this.material.defines.HAS_TRANSPARENT_TARGET = this.material.uniforms.tTransparent.value ? 1 : undefined | |||
| if (!this.material.defines.HAS_TRANSPARENT_TARGET) delete this.material.defines.HAS_TRANSPARENT_TARGET | |||
| } | |||
| } | |||
| setDirty() { | |||