| if (!data.metadata) { | if (!data.metadata) { | ||||
| console.warn('Invalid data(no metadata)', data) | console.warn('Invalid data(no metadata)', data) | ||||
| } | } | ||||
| console.log(data, event) | |||||
| if (event.material) { | if (event.material) { | ||||
| if (data.metadata?.type !== 'Material') { | if (data.metadata?.type !== 'Material') { | ||||
| console.warn('Invalid material data', data) | console.warn('Invalid material data', data) |
| IMaterialTemplate, | IMaterialTemplate, | ||||
| ITexture, | ITexture, | ||||
| ITextureEvent, | ITextureEvent, | ||||
| LegacyPhongMaterial, | |||||
| LineMaterial2, | |||||
| PhysicalMaterial, | PhysicalMaterial, | ||||
| UnlitLineMaterial, | |||||
| UnlitMaterial, | UnlitMaterial, | ||||
| } from '../core' | } from '../core' | ||||
| import {downloadFile} from 'ts-browser-helpers' | import {downloadFile} from 'ts-browser-helpers' | ||||
| import {MaterialExtension} from '../materials' | import {MaterialExtension} from '../materials' | ||||
| import {generateUUID, isInScene} from '../three' | import {generateUUID, isInScene} from '../three' | ||||
| import {LegacyPhongMaterial} from '../core/material/LegacyPhongMaterial' | |||||
| /** | /** | ||||
| * Material Manager | * Material Manager | ||||
| readonly templates: IMaterialTemplate[] = [ | readonly templates: IMaterialTemplate[] = [ | ||||
| PhysicalMaterial.MaterialTemplate, | PhysicalMaterial.MaterialTemplate, | ||||
| UnlitMaterial.MaterialTemplate, | UnlitMaterial.MaterialTemplate, | ||||
| UnlitLineMaterial.MaterialTemplate, | |||||
| LineMaterial2.MaterialTemplate, | |||||
| LegacyPhongMaterial.MaterialTemplate, | LegacyPhongMaterial.MaterialTemplate, | ||||
| ] | ] | ||||
| } | } | ||||
| return applied | return applied | ||||
| } | } | ||||
| } | } | ||||
| inverseAlphaMap?: boolean // only for physical material right now | inverseAlphaMap?: boolean // only for physical material right now | ||||
| [key: string]: any // commented for noe | |||||
| [key: string]: any | |||||
| // legacy, to be removed | // legacy, to be removed |
| isCamera?: boolean | isCamera?: boolean | ||||
| isMesh?: boolean | isMesh?: boolean | ||||
| isLine?: boolean | isLine?: boolean | ||||
| isLineSegments?: boolean | |||||
| // isGroup?: boolean | // isGroup?: boolean | ||||
| isScene?: boolean | isScene?: boolean | ||||
| // isHelper?: boolean | // isHelper?: boolean |
| export {PhysicalMaterial, type PhysicalMaterialEventTypes, MeshStandardMaterial2} from './material/PhysicalMaterial' | export {PhysicalMaterial, type PhysicalMaterialEventTypes, MeshStandardMaterial2} from './material/PhysicalMaterial' | ||||
| export {ShaderMaterial2} from './material/ShaderMaterial2' | export {ShaderMaterial2} from './material/ShaderMaterial2' | ||||
| export {UnlitMaterial, type UnlitMaterialEventTypes, MeshBasicMaterial2} from './material/UnlitMaterial' | export {UnlitMaterial, type UnlitMaterialEventTypes, MeshBasicMaterial2} from './material/UnlitMaterial' | ||||
| export {UnlitLineMaterial, type UnlitLineMaterialEventTypes, LineBasicMaterial2} from './material/UnlitLineMaterial' | |||||
| export {LineMaterial2, type LineMaterial2EventTypes} from './material/LineMaterial2' | |||||
| export {LegacyPhongMaterial, type PhongMaterialEventTypes} from './material/LegacyPhongMaterial' | export {LegacyPhongMaterial, type PhongMaterialEventTypes} from './material/LegacyPhongMaterial' | ||||
| export {iObjectCommons} from './object/iObjectCommons' | export {iObjectCommons} from './object/iObjectCommons' | ||||
| export {iCameraCommons} from './object/iCameraCommons' | export {iCameraCommons} from './object/iCameraCommons' |
| WebGLRenderer, | WebGLRenderer, | ||||
| } from 'three' | } from 'three' | ||||
| import {shaderReplaceString} from '../../utils' | import {shaderReplaceString} from '../../utils' | ||||
| import {IMaterialUserData} from '../IMaterial' | |||||
| export class ExtendedShaderMaterial extends ShaderMaterial2 { | export class ExtendedShaderMaterial extends ShaderMaterial2 { | ||||
| declare ['constructor']: (typeof ExtendedShaderMaterial) & (typeof ShaderMaterial2) | declare ['constructor']: (typeof ExtendedShaderMaterial) & (typeof ShaderMaterial2) | ||||
| textures: {colorSpace: ColorSpace, id: string}[] = [] | textures: {colorSpace: ColorSpace, id: string}[] = [] | ||||
| userData: IMaterialUserData | |||||
| constructor(parameters: ShaderMaterialParameters, textureIds: string[], isRawShaderMaterial = false) { | constructor(parameters: ShaderMaterialParameters, textureIds: string[], isRawShaderMaterial = false) { | ||||
| super(parameters, isRawShaderMaterial) | super(parameters, isRawShaderMaterial) | ||||
| this.setTextureIds(textureIds) | this.setTextureIds(textureIds) |
| } from 'three' | } from 'three' | ||||
| import {downloadBlob, uploadFile} from 'ts-browser-helpers' | import {downloadBlob, uploadFile} from 'ts-browser-helpers' | ||||
| import {PhysicalMaterial} from './PhysicalMaterial' | import {PhysicalMaterial} from './PhysicalMaterial' | ||||
| import {getEmptyMeta} from '../../utils/serialization' | |||||
| import {getEmptyMeta} from '../../utils' | |||||
| import {LegacyPhongMaterial} from './LegacyPhongMaterial' | import {LegacyPhongMaterial} from './LegacyPhongMaterial' | ||||
| import {generateUUID} from '../../three' | |||||
| declare module '../IMaterial' { | |||||
| interface IMaterial { | |||||
| __matExtUiConfigs?: Record<string, UiObjectConfig|undefined> | |||||
| } | |||||
| } | |||||
| export const iMaterialUI = { | export const iMaterialUI = { | ||||
| base: (material: IMaterial): UiObjectConfig[] => [ | base: (material: IMaterial): UiObjectConfig[] => [ | ||||
| value: value[1], | value: value[1], | ||||
| })), | })), | ||||
| }, | }, | ||||
| { | |||||
| material.alphaMap !== undefined ? { | |||||
| type: 'image', | type: 'image', | ||||
| property: [material, 'alphaMap'], | property: [material, 'alphaMap'], | ||||
| }, | |||||
| makeSamplerUi(material, 'alphaMap'), | |||||
| } : {}, | |||||
| material.alphaMap !== undefined ? makeSamplerUi(material, 'alphaMap') : {}, | |||||
| { | { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| label: 'Render to Gbuffer', | label: 'Render to Gbuffer', | ||||
| } | } | ||||
| ), | ), | ||||
| misc: (material: IMaterial): UiObjectConfig[] => [ | misc: (material: IMaterial): UiObjectConfig[] => [ | ||||
| ()=>material.materialExtensions?.map(v=>{ | |||||
| v.uuid = v.uuid || generateUUID() | |||||
| material.__matExtUiConfigs = material.__matExtUiConfigs || {} | |||||
| if (!material.__matExtUiConfigs[v.uuid]) material.__matExtUiConfigs[v.uuid] = v.getUiConfig?.(material, material.uiConfig?.uiRefresh) | |||||
| return material.__matExtUiConfigs[v.uuid] | |||||
| }).filter(v=>v), | |||||
| { | { | ||||
| type: 'dropdown', | type: 'dropdown', | ||||
| label: 'Side', | label: 'Side', | ||||
| }) | }) | ||||
| }, | }, | ||||
| }, | }, | ||||
| ()=>material.materialExtensions?.map(v=>v.getUiConfig?.(material, material.uiConfig?.uiRefresh)).filter(v=>v), | |||||
| ], | ], | ||||
| roughMetal: (material: PhysicalMaterial): UiObjectConfig => ( | roughMetal: (material: PhysicalMaterial): UiObjectConfig => ( | ||||
| { | { | ||||
| children: [ | children: [ | ||||
| { | { | ||||
| type: 'slider', | type: 'slider', | ||||
| bounds: [0, 0.2], | |||||
| bounds: [-0.2, 0.2], | |||||
| stepSize: 0.001, | stepSize: 0.001, | ||||
| property: [material, 'bumpScale'], | property: [material, 'bumpScale'], | ||||
| hidden: ()=>!material.bumpMap, | hidden: ()=>!material.bumpMap, |
| IMaterialGenerator, | IMaterialGenerator, | ||||
| IMaterialParameters, | IMaterialParameters, | ||||
| IMaterialTemplate, | IMaterialTemplate, | ||||
| IMaterialUserData, | |||||
| } from '../IMaterial' | } from '../IMaterial' | ||||
| import {MaterialExtension} from '../../materials' | import {MaterialExtension} from '../../materials' | ||||
| import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | ||||
| public static readonly TYPE = 'LegacyPhongMaterial' // not using .type because it is used by three.js | public static readonly TYPE = 'LegacyPhongMaterial' // not using .type because it is used by three.js | ||||
| assetType = 'material' as const | assetType = 'material' as const | ||||
| userData: IMaterialUserData | |||||
| public readonly isLegacyPhongMaterial = true | public readonly isLegacyPhongMaterial = true | ||||
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() |
| import {generateUiConfig, uiColor, uiInput, uiNumber, UiObjectConfig, uiToggle, uiVector} from 'uiconfig.js' | |||||
| import {Color, IUniform, Material, Shader, Vector2, WebGLRenderer} from 'three' | |||||
| import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | |||||
| import { | |||||
| IMaterial, | |||||
| IMaterialEvent, | |||||
| IMaterialEventTypes, | |||||
| IMaterialGenerator, | |||||
| IMaterialParameters, | |||||
| IMaterialTemplate, | |||||
| } from '../IMaterial' | |||||
| import {MaterialExtension} from '../../materials' | |||||
| import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons' | |||||
| import {IObject3D} from '../IObject' | |||||
| import {iMaterialUI} from './IMaterialUi' | |||||
| import {LineMaterial, type LineMaterialParameters} from 'three/examples/jsm/lines/LineMaterial.js' | |||||
| export type LineMaterial2EventTypes = IMaterialEventTypes | '' | |||||
| export class LineMaterial2 extends LineMaterial<IMaterialEvent, LineMaterial2EventTypes> implements IMaterial<IMaterialEvent, LineMaterial2EventTypes> { | |||||
| declare ['constructor']: typeof LineMaterial2 | |||||
| public static readonly TypeSlug = 'lmat' | |||||
| public static readonly TYPE = 'LineMaterial2' // not using .type because it is used by three.js | |||||
| assetType = 'material' as const | |||||
| public readonly isLineMaterial2 = true | |||||
| readonly appliedMeshes: Set<IObject3D> = new Set() | |||||
| readonly setDirty = iMaterialCommons.setDirty | |||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | |||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | |||||
| dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | |||||
| generator?: IMaterialGenerator | |||||
| constructor({customMaterialExtensions, ...parameters}: LineMaterialParameters & IMaterialParameters = {}) { | |||||
| super(parameters) | |||||
| this.fog = false | |||||
| this.setDirty = this.setDirty.bind(this) | |||||
| if (customMaterialExtensions) this.registerMaterialExtensions(customMaterialExtensions) | |||||
| iMaterialCommons.upgradeMaterial.call(this) | |||||
| } | |||||
| // region Material Extension | |||||
| materialExtensions: MaterialExtension[] = [] | |||||
| extraUniformsToUpload: Record<string, IUniform> = {} | |||||
| registerMaterialExtensions = iMaterialCommons.registerMaterialExtensions | |||||
| unregisterMaterialExtensions = iMaterialCommons.unregisterMaterialExtensions | |||||
| customProgramCacheKey(): string { | |||||
| return super.customProgramCacheKey() + iMaterialCommons.customProgramCacheKey.call(this) | |||||
| } | |||||
| onBeforeCompile(shader: Shader, renderer: WebGLRenderer): void { // shader is not Shader but WebglUniforms.getParameters return value type so includes defines | |||||
| const f = [ | |||||
| ['vec4 diffuseColor = ', 'beforeAccumulation'], | |||||
| ['#include <clipping_planes_fragment>', 'mainStart'], | |||||
| ] | |||||
| const v = [ | |||||
| ['#ifdef USE_COLOR', 'mainStart'], | |||||
| ] | |||||
| for (const vElement of v) shader.vertexShader = shaderReplaceString(shader.vertexShader, vElement[0], '#glMarker ' + vElement[1] + '\n' + vElement[0]) | |||||
| for (const fElement of f) shader.fragmentShader = shaderReplaceString(shader.fragmentShader, fElement[0], '#glMarker ' + fElement[1] + '\n' + fElement[0]) | |||||
| iMaterialCommons.onBeforeCompile.call(this, shader, renderer) | |||||
| super.onBeforeCompile(shader, renderer) | |||||
| } | |||||
| onAfterRender = iMaterialCommons.onAfterRenderOverride(super.onAfterRender) | |||||
| // endregion | |||||
| // region UI Config | |||||
| @uiInput() name: string | |||||
| @uiColor() color: Color | |||||
| @uiToggle() dashed: boolean | |||||
| @uiNumber() dashScale: number | |||||
| @uiNumber() dashSize: number | |||||
| @uiNumber() dashOffset: number | |||||
| @uiNumber() gapSize: number | |||||
| @uiNumber() linewidth: number | |||||
| @uiVector() resolution: Vector2 | |||||
| @uiToggle() alphaToCoverage: boolean | |||||
| @uiToggle() worldUnits: boolean | |||||
| // @uiToggle() fog = true | |||||
| // todo dispose ui config | |||||
| uiConfig: UiObjectConfig = { | |||||
| type: 'folder', | |||||
| label: 'Line 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: [ | |||||
| ...generateUiConfig(this), | |||||
| iMaterialUI.blending(this), | |||||
| iMaterialUI.polygonOffset(this), | |||||
| ...iMaterialUI.misc(this), | |||||
| ], | |||||
| } | |||||
| // endregion UI Config | |||||
| // region Serialization | |||||
| /** | |||||
| * Sets the values of this material based on the values of the passed material or an object with material properties | |||||
| * The input is expected to be a valid material or a deserialized material parameters object(including the deserialized userdata) | |||||
| * @param parameters - material or material parameters object | |||||
| * @param allowInvalidType - if true, the type of the oldMaterial is not checked. Objects without type are always allowed. | |||||
| * @param clearCurrentUserData - if undefined, then depends on material.isMaterial. if true, the current userdata is cleared before setting the new values, because it can have data which wont be overwritten if not present in the new material. | |||||
| */ | |||||
| setValues(parameters: Material|(LineMaterialParameters&{type?:string}), allowInvalidType = true, clearCurrentUserData: boolean|undefined = undefined): this { | |||||
| if (!parameters) return this | |||||
| if (parameters.type && !allowInvalidType && !['LineMaterial', this.constructor.TYPE].includes(parameters.type)) { | |||||
| console.error('Material type is not supported:', parameters.type) | |||||
| return this | |||||
| } | |||||
| if (clearCurrentUserData === undefined) clearCurrentUserData = (<Material>parameters).isMaterial | |||||
| if (clearCurrentUserData) this.userData = {} | |||||
| iMaterialCommons.setValues(super.setValues).call(this, parameters) | |||||
| this.userData.uuid = this.uuid | |||||
| return this | |||||
| } | |||||
| copy(source: Material|any): this { | |||||
| return this.setValues(source, false) | |||||
| } | |||||
| /** | |||||
| * Serializes this material to JSON. | |||||
| * @param meta - metadata for serialization | |||||
| * @param _internal - Calls only super.toJSON, does internal three.js serialization and @serialize tags. Set it to true only if you know what you are doing. This is used in Serialization->serializer->material | |||||
| */ | |||||
| toJSON(meta?: SerializationMetaType, _internal = false): any { | |||||
| if (_internal) return { | |||||
| ...super.toJSON(meta), | |||||
| ...ThreeSerialization.Serialize(this, meta, true), // this will serialize the properties of this class(like defined with @serialize and @serialize attribute) | |||||
| } | |||||
| return ThreeSerialization.Serialize(this, meta, false) // this will call toJSON again, but with baseOnly=true, that's why we set isThis to false. | |||||
| } | |||||
| /** | |||||
| * Deserializes the material from JSON. | |||||
| * 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 | |||||
| */ | |||||
| fromJSON(data: any, meta?: SerializationMetaType, _internal = false): this | null { | |||||
| if (_internal) { | |||||
| ThreeSerialization.Deserialize(data, this, meta, true) | |||||
| return this.setValues(data) // todo remove this and add @serialize decorator to properties | |||||
| } | |||||
| this.dispatchEvent({type: 'beforeDeserialize', data, meta, bubbleToObject: true, bubbleToParent: true}) | |||||
| return this | |||||
| } | |||||
| // endregion | |||||
| // used for serialization and used in setValues | |||||
| static readonly MaterialProperties = { | |||||
| // keep updated with properties in LineMaterial.js | |||||
| ...threeMaterialPropList, | |||||
| color: new Color(0xffffff), | |||||
| dashed: false, | |||||
| dashScale: 1, | |||||
| dashSize: 1, | |||||
| dashOffset: 0, | |||||
| gapSize: 1, | |||||
| linewidth: 1, | |||||
| resolution: new Vector2(1, 1), | |||||
| alphaToCoverage: false, | |||||
| worldUnits: false, | |||||
| uniforms: {}, | |||||
| defines: {}, | |||||
| extensions: {}, | |||||
| clipping: false, | |||||
| fog: true, | |||||
| fragmentShader: '', | |||||
| vertexShader: '', | |||||
| } | |||||
| static MaterialTemplate: IMaterialTemplate<LineMaterial2, Partial<typeof LineMaterial2.MaterialProperties>> = { | |||||
| materialType: LineMaterial2.TYPE, | |||||
| name: 'line', | |||||
| typeSlug: LineMaterial2.TypeSlug, | |||||
| alias: ['line', 'line_physical', LineMaterial2.TYPE, LineMaterial2.TypeSlug, 'LineMaterial'], | |||||
| params: { | |||||
| color: new Color(1, 1, 1), | |||||
| }, | |||||
| generator: (params) => { | |||||
| return new LineMaterial2(params) | |||||
| }, | |||||
| } | |||||
| } |
| Vector2, | Vector2, | ||||
| WebGLRenderer, | WebGLRenderer, | ||||
| } from 'three' | } from 'three' | ||||
| import {shaderReplaceString} from '../../utils/shader-helpers' | |||||
| import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | |||||
| import { | import { | ||||
| IMaterial, | IMaterial, | ||||
| IMaterialEvent, | IMaterialEvent, | ||||
| IMaterialGenerator, | IMaterialGenerator, | ||||
| IMaterialParameters, | IMaterialParameters, | ||||
| IMaterialTemplate, | IMaterialTemplate, | ||||
| IMaterialUserData, | |||||
| } from '../IMaterial' | } from '../IMaterial' | ||||
| import {SerializationMetaType, ThreeSerialization} from '../../utils/serialization' | |||||
| import {MaterialExtension} from '../../materials' | import {MaterialExtension} from '../../materials' | ||||
| import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons' | import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons' | ||||
| import {IObject3D} from '../IObject' | import {IObject3D} from '../IObject' | ||||
| public static readonly TYPE = 'PhysicalMaterial' // not using .type because it is used by three.js | public static readonly TYPE = 'PhysicalMaterial' // not using .type because it is used by three.js | ||||
| assetType = 'material' as const | assetType = 'material' as const | ||||
| userData: IMaterialUserData | |||||
| public readonly isPhysicalMaterial = true | public readonly isPhysicalMaterial = true | ||||
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() |
| ShaderMaterialParameters, | ShaderMaterialParameters, | ||||
| WebGLRenderer, | WebGLRenderer, | ||||
| } from 'three' | } from 'three' | ||||
| import {IMaterial, IMaterialEvent, IMaterialEventTypes, IMaterialParameters} from '../IMaterial' | |||||
| import {IMaterial, IMaterialEvent, IMaterialEventTypes, IMaterialParameters, IMaterialUserData} from '../IMaterial' | |||||
| import {MaterialExtension} from '../../materials' | import {MaterialExtension} from '../../materials' | ||||
| import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons' | import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons' | ||||
| } | } | ||||
| assetType = 'material' as const | assetType = 'material' as const | ||||
| userData: IMaterialUserData | |||||
| public readonly isAShaderMaterial = true | public readonly isAShaderMaterial = true | ||||
| readonly appliedMeshes: Set<any> = new Set() | readonly appliedMeshes: Set<any> = new Set() |
| import {Color, IUniform, LineBasicMaterial, LineBasicMaterialParameters, Material, Shader, WebGLRenderer} from 'three' | |||||
| import {UiObjectConfig} from 'uiconfig.js' | |||||
| import { | |||||
| IMaterial, | |||||
| IMaterialEvent, | |||||
| IMaterialEventTypes, | |||||
| IMaterialGenerator, | |||||
| IMaterialParameters, | |||||
| IMaterialTemplate, | |||||
| IMaterialUserData, | |||||
| } from '../IMaterial' | |||||
| import {MaterialExtension} from '../../materials' | |||||
| import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | |||||
| import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons' | |||||
| import {IObject3D} from '../IObject' | |||||
| import {makeSamplerUi} from '../../ui/image-ui' | |||||
| import {iMaterialUI} from './IMaterialUi' | |||||
| export type UnlitLineMaterialEventTypes = IMaterialEventTypes | '' | |||||
| export class UnlitLineMaterial extends LineBasicMaterial<IMaterialEvent, UnlitLineMaterialEventTypes> implements IMaterial<IMaterialEvent, UnlitLineMaterialEventTypes> { | |||||
| declare ['constructor']: typeof UnlitLineMaterial | |||||
| public static readonly TypeSlug = 'blmat' | |||||
| public static readonly TYPE = 'UnlitLineMaterial' // not using .type because it is used by three.js | |||||
| assetType = 'material' as const | |||||
| userData: IMaterialUserData | |||||
| public readonly isUnlitLineMaterial = true | |||||
| readonly appliedMeshes: Set<IObject3D> = new Set() | |||||
| readonly setDirty = iMaterialCommons.setDirty | |||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | |||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | |||||
| dispatchEvent(event: IMaterialEvent): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | |||||
| generator?: IMaterialGenerator | |||||
| constructor({customMaterialExtensions, ...parameters}: LineBasicMaterialParameters & IMaterialParameters = {}) { | |||||
| super(parameters) | |||||
| !this.defines && (this.defines = {}) | |||||
| this.fog = false | |||||
| this.setDirty = this.setDirty.bind(this) | |||||
| if (customMaterialExtensions) this.registerMaterialExtensions(customMaterialExtensions) | |||||
| iMaterialCommons.upgradeMaterial.call(this) | |||||
| } | |||||
| // region Material Extension | |||||
| materialExtensions: MaterialExtension[] = [] | |||||
| extraUniformsToUpload: Record<string, IUniform> = {} | |||||
| registerMaterialExtensions = iMaterialCommons.registerMaterialExtensions | |||||
| unregisterMaterialExtensions = iMaterialCommons.unregisterMaterialExtensions | |||||
| customProgramCacheKey(): string { | |||||
| return super.customProgramCacheKey() + iMaterialCommons.customProgramCacheKey.call(this) | |||||
| } | |||||
| onBeforeCompile(shader: Shader, renderer: WebGLRenderer): void { // shader is not Shader but WebglUniforms.getParameters return value type so includes defines | |||||
| const f = [ | |||||
| ['vec3 outgoingLight = ', 'afterModulation'], // added markers before found substring | |||||
| ['#include <aomap_fragment>', 'beforeModulation'], | |||||
| ['ReflectedLight reflectedLight = ', 'beforeAccumulation'], | |||||
| ['#include <clipping_planes_fragment>', 'mainStart'], | |||||
| ] | |||||
| const v = [ | |||||
| ['#include <uv_vertex>', 'mainStart'], | |||||
| ] | |||||
| for (const vElement of v) shader.vertexShader = shaderReplaceString(shader.vertexShader, vElement[0], '#glMarker ' + vElement[1] + '\n' + vElement[0]) | |||||
| for (const vElement of f) shader.fragmentShader = shaderReplaceString(shader.fragmentShader, vElement[0], '#glMarker ' + vElement[1] + '\n' + vElement[0]) | |||||
| iMaterialCommons.onBeforeCompile.call(this, shader, renderer) | |||||
| super.onBeforeCompile(shader, renderer) | |||||
| } | |||||
| onBeforeRender = iMaterialCommons.onBeforeRenderOverride(super.onBeforeRender) | |||||
| onAfterRender = iMaterialCommons.onAfterRenderOverride(super.onAfterRender) | |||||
| // endregion | |||||
| // region Serialization | |||||
| /** | |||||
| * Sets the values of this material based on the values of the passed material or an object with material properties | |||||
| * The input is expected to be a valid material or a deserialized material parameters object(including the deserialized userdata) | |||||
| * @param parameters - material or material parameters object | |||||
| * @param allowInvalidType - if true, the type of the oldMaterial is not checked. Objects without type are always allowed. | |||||
| * @param clearCurrentUserData - if undefined, then depends on material.isMaterial. if true, the current userdata is cleared before setting the new values, because it can have data which wont be overwritten if not present in the new material. | |||||
| */ | |||||
| setValues(parameters: Material|(LineBasicMaterialParameters&{type?:string}), allowInvalidType = true, clearCurrentUserData: boolean|undefined = undefined): this { | |||||
| if (!parameters) return this | |||||
| if (parameters.type && !allowInvalidType && !['LineBasicMaterial', 'LineBasicMaterial2', this.constructor.TYPE].includes(parameters.type)) { | |||||
| console.error('Material type is not supported:', parameters.type) | |||||
| return this | |||||
| } | |||||
| if (clearCurrentUserData === undefined) clearCurrentUserData = (<Material>parameters).isMaterial | |||||
| if (clearCurrentUserData) this.userData = {} | |||||
| iMaterialCommons.setValues(super.setValues).call(this, parameters) | |||||
| this.userData.uuid = this.uuid | |||||
| return this | |||||
| } | |||||
| copy(source: Material|any): this { | |||||
| return this.setValues(source, false) | |||||
| } | |||||
| /** | |||||
| * Serializes this material to JSON. | |||||
| * @param meta - metadata for serialization | |||||
| * @param _internal - Calls only super.toJSON, does internal three.js serialization and @serialize tags. Set it to true only if you know what you are doing. This is used in Serialization->serializer->material | |||||
| */ | |||||
| toJSON(meta?: SerializationMetaType, _internal = false): any { | |||||
| if (_internal) return { | |||||
| ...super.toJSON(meta), | |||||
| ...ThreeSerialization.Serialize(this, meta, true), // this will serialize the properties of this class(like defined with @serialize and @serialize attribute) | |||||
| } | |||||
| return ThreeSerialization.Serialize(this, meta, false) // this will call toJSON again, but with baseOnly=true, that's why we set isThis to false. | |||||
| } | |||||
| /** | |||||
| * Deserializes the material from JSON. | |||||
| * Textures should be loaded and in meta.textures before calling this method. | |||||
| * todo - needs to be tested | |||||
| * @param data | |||||
| * @param meta | |||||
| * @param _internal | |||||
| */ | |||||
| fromJSON(data: any, meta?: SerializationMetaType, _internal = false): this | null { | |||||
| if (_internal) { | |||||
| ThreeSerialization.Deserialize(data, this, meta, true) | |||||
| return this.setValues(data) | |||||
| } | |||||
| this.dispatchEvent({type: 'beforeDeserialize', data, meta, bubbleToObject: true, bubbleToParent: true}) | |||||
| return this | |||||
| } | |||||
| // endregion | |||||
| // region UI Config | |||||
| // todo dispose ui config | |||||
| uiConfig: UiObjectConfig = { | |||||
| type: 'folder', | |||||
| label: 'Unlit Line Material', | |||||
| uuid: 'MBLM2_' + this.uuid, | |||||
| expanded: true, | |||||
| children: [ | |||||
| { | |||||
| type: 'input', | |||||
| property: [this, 'name'], | |||||
| }, | |||||
| // { | |||||
| // type: 'monitor', | |||||
| // property: [this, 'uuid'], | |||||
| // }, | |||||
| { | |||||
| type: 'checkbox', | |||||
| property: [this, 'vertexColors'], | |||||
| }, | |||||
| { | |||||
| type: 'color', | |||||
| property: [this, 'color'], | |||||
| }, | |||||
| makeSamplerUi(this, 'map'), | |||||
| { | |||||
| type: 'number', | |||||
| property: [this, 'linewidth'], | |||||
| }, | |||||
| { | |||||
| type: 'dropdown', | |||||
| property: [this, 'linecap'], | |||||
| children: ['butt', 'round', 'square'].map(label => ({label})), | |||||
| }, | |||||
| { | |||||
| type: 'dropdown', | |||||
| property: [this, 'linejoin'], | |||||
| children: ['bevel', 'round', 'miter'].map(label => ({label})), | |||||
| }, | |||||
| // { | |||||
| // type: 'checkbox', | |||||
| // property: [this, 'fog'], | |||||
| // }, | |||||
| iMaterialUI.blending(this), | |||||
| iMaterialUI.polygonOffset(this), | |||||
| ...iMaterialUI.misc(this), | |||||
| ], | |||||
| } | |||||
| // endregion UI Config | |||||
| // Class properties can also be listed with annotations like @serialize or @property | |||||
| // used for serialization | |||||
| static readonly MaterialProperties = { | |||||
| ...threeMaterialPropList, | |||||
| color: new Color(0xffffff), | |||||
| map: null, | |||||
| linewidth: 1, | |||||
| linecap: 'round', | |||||
| linejoin: 'round', | |||||
| fog: true, | |||||
| } | |||||
| static MaterialTemplate: IMaterialTemplate<UnlitLineMaterial, Partial<typeof UnlitLineMaterial.MaterialProperties>> = { | |||||
| materialType: UnlitLineMaterial.TYPE, | |||||
| name: 'unlit_line', | |||||
| typeSlug: UnlitLineMaterial.TypeSlug, | |||||
| alias: ['line_basic', 'unlit_line', UnlitLineMaterial.TYPE, UnlitLineMaterial.TypeSlug, 'LineBasicMaterial', 'LineBasicMaterial2'], | |||||
| params: { | |||||
| color: new Color(1, 1, 1), | |||||
| }, | |||||
| generator: (params) => { | |||||
| return new UnlitLineMaterial(params) | |||||
| }, | |||||
| } | |||||
| } | |||||
| export class LineBasicMaterial2 extends UnlitLineMaterial { | |||||
| constructor(parameters?: LineBasicMaterialParameters) { | |||||
| super(parameters) | |||||
| console.error('LineBasicMaterial2 is deprecated, use UnlitLineMaterial instead') | |||||
| } | |||||
| } |
| IMaterialGenerator, | IMaterialGenerator, | ||||
| IMaterialParameters, | IMaterialParameters, | ||||
| IMaterialTemplate, | IMaterialTemplate, | ||||
| IMaterialUserData, | |||||
| } from '../IMaterial' | } from '../IMaterial' | ||||
| import {MaterialExtension} from '../../materials' | import {MaterialExtension} from '../../materials' | ||||
| import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | ||||
| public static readonly TYPE = 'UnlitMaterial' // not using .type because it is used by three.js | public static readonly TYPE = 'UnlitMaterial' // not using .type because it is used by three.js | ||||
| assetType = 'material' as const | assetType = 'material' as const | ||||
| userData: IMaterialUserData | |||||
| public readonly isUnlitMaterial = true | public readonly isUnlitMaterial = true | ||||
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() |
| export const iMaterialCommons = { | export const iMaterialCommons = { | ||||
| threeMaterialPropList, | threeMaterialPropList, | ||||
| setDirty: function(this: IMaterial, options?: IMaterialSetDirtyOptions): void { | setDirty: function(this: IMaterial, options?: IMaterialSetDirtyOptions): void { | ||||
| this.needsUpdate = true | |||||
| if (options?.needsUpdate !== false) this.needsUpdate = true | |||||
| this.dispatchEvent({bubbleToObject: true, bubbleToParent: true, ...options, type: 'materialUpdate'}) // 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) | ||||
| }, | }, |
| } : {}, | } : {}, | ||||
| ], | ], | ||||
| } | } | ||||
| if (this.isMesh && isMesh !== false) { | |||||
| if ((this.isLine || this.isMesh) && isMesh !== false) { | |||||
| // todo: move to make mesh ui function? | // todo: move to make mesh ui function? | ||||
| const ui = [ | const ui = [ | ||||
| // morph targets | // morph targets |
| }) | }) | ||||
| // this is called initially in Material manager from process model below, not required here... | // 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 | // 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') | |||||
| } | |||||
| // if (currentMaterial && !Array.isArray(currentMaterial) && !currentMaterial.assetType) { | |||||
| // console.error('todo: initMaterial: material not upgraded') | |||||
| // } | |||||
| this.material = currentMaterial | this.material = currentMaterial | ||||
| // Legacy | // Legacy | ||||
| // const mat = material?.materialObject | // const mat = material?.materialObject | ||||
| if (!mat) continue | if (!mat) continue | ||||
| if (!mat.assetType) { | if (!mat.assetType) { | ||||
| console.error('Material not upgraded') | |||||
| console.warn('Upgrading Material', mat) | |||||
| iMaterialCommons.upgradeMaterial.call(mat) | iMaterialCommons.upgradeMaterial.call(mat) | ||||
| } | } | ||||
| materials.push(mat) | materials.push(mat) |
| export * from './postprocessing/index' | export * from './postprocessing/index' | ||||
| export * from './materials/index' | export * from './materials/index' | ||||
| export * from './rendering/index' | export * from './rendering/index' | ||||
| export * from './ui/image-ui' | |||||
| // testing | // testing | ||||
| export {_testStart, _testFinish} from './testing/testing' | export {_testStart, _testFinish} from './testing/testing' |
| import {IMaterial, IMaterialUserData} from '../core' | import {IMaterial, IMaterialUserData} from '../core' | ||||
| import {getOrCall, objectMap} from 'ts-browser-helpers' | import {getOrCall, objectMap} from 'ts-browser-helpers' | ||||
| import {shaderReplaceString} from '../utils/shader-helpers' | |||||
| import {Object3D, Shader, WebGLRenderer} from 'three' | |||||
| import {shaderReplaceString, shaderUtils} from '../utils' | |||||
| import {Object3D, Shader, ShaderChunk, WebGLRenderer} from 'three' | |||||
| import {MaterialExtension} from './MaterialExtension' | import {MaterialExtension} from './MaterialExtension' | ||||
| import {generateUUID} from '../three/utils/misc' | |||||
| import {generateUUID} from '../three' | |||||
| export class MaterialExtender { | export class MaterialExtender { | ||||
| static { | |||||
| Object.assign(ShaderChunk, shaderUtils) // for #include in the shaders | |||||
| } | |||||
| static VoidMain = 'void main()' | static VoidMain = 'void main()' | ||||
| static ApplyMaterialExtensions(material: IMaterial, shader: Shader, materialExtensions: MaterialExtension[], renderer: WebGLRenderer) { | static ApplyMaterialExtensions(material: IMaterial, shader: Shader, materialExtensions: MaterialExtension[], renderer: WebGLRenderer) { | ||||
| } | } | ||||
| } | } | ||||
| function updateMaterialDefines(defines: MaterialExtension['extraDefines'], material: IMaterial) { | |||||
| export function updateMaterialDefines(defines: MaterialExtension['extraDefines'], material: IMaterial) { | |||||
| if (!defines || !material) return | if (!defines || !material) return | ||||
| if (material.defines === undefined || material.defines === null) { // required for some three.js materials | if (material.defines === undefined || material.defines === null) { // required for some three.js materials | ||||
| material.defines = {} | material.defines = {} | ||||
| flag = true | flag = true | ||||
| } | } | ||||
| } else if (material.defines[key] !== val) { | } else if (material.defines[key] !== val) { | ||||
| material.defines[key] = val | |||||
| material.defines[key] = typeof val === 'boolean' ? +val : val | |||||
| flag = true | flag = true | ||||
| } | } | ||||
| } | } |
| /** | /** | ||||
| * Extra defines to copy to material | * Extra defines to copy to material | ||||
| * Note: boolean are converted to 0 and 1 | |||||
| */ | */ | ||||
| extraDefines?: Record<string, ValOrFunc<number|string|undefined>>; | |||||
| extraDefines?: Record<string, ValOrFunc<number|string|undefined|boolean>>; | |||||
| /** | /** | ||||
| * Custom callback to extend/modify/replace shader code and other shader properties | * Custom callback to extend/modify/replace shader code and other shader properties | ||||
| * This is called once when the material extension is registered. | * This is called once when the material extension is registered. | ||||
| * @param material | * @param material | ||||
| */ | */ | ||||
| getUiConfig?: (material: IMaterial, refreshUi: UiObjectConfig['uiRefresh']) => UiObjectConfig | undefined | |||||
| getUiConfig?: (material: IMaterial, refreshUi?: UiObjectConfig['uiRefresh']) => UiObjectConfig | undefined | |||||
| /** | /** | ||||
| * Higher priority extensions are applied first. | * Higher priority extensions are applied first. |
| export {MaterialExtender} from './MaterialExtender' | |||||
| export {MaterialExtender, updateMaterialDefines} from './MaterialExtender' | |||||
| export type {MaterialExtension, IShaderPropertiesUpdater} from './MaterialExtension' | export type {MaterialExtension, IShaderPropertiesUpdater} from './MaterialExtension' |
| import {Group, Sphere, Vector2} from 'three' | import {Group, Sphere, Vector2} from 'three' | ||||
| import {LineMaterial} from 'three/examples/jsm/lines/LineMaterial.js' | |||||
| import {AnyOptions} from 'ts-browser-helpers' | import {AnyOptions} from 'ts-browser-helpers' | ||||
| import {Box3B} from '../math/Box3B' | import {Box3B} from '../math/Box3B' | ||||
| import {IObject3D, IWidget} from '../../core' | import {IObject3D, IWidget} from '../../core' | ||||
| import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' | import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' | ||||
| import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' | import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' | ||||
| import {LineMaterial2} from '../../core/material/LineMaterial2' | |||||
| export class SelectionWidget extends Group implements IWidget { | export class SelectionWidget extends Group implements IWidget { | ||||
| isWidget = true as const | isWidget = true as const | ||||
| export class BoxSelectionWidget extends SelectionWidget { | export class BoxSelectionWidget extends SelectionWidget { | ||||
| constructor() { | constructor() { | ||||
| super() | super() | ||||
| const matLine = new LineMaterial({ | |||||
| const matLine = new LineMaterial2({ | |||||
| color: '#ff2222' as any, transparent: true, opacity: 0.9, | color: '#ff2222' as any, transparent: true, opacity: 0.9, | ||||
| linewidth: 5, // in pixels | linewidth: 5, // in pixels | ||||
| resolution: new Vector2(1024, 1024), // to be set by renderer, eventually | resolution: new Vector2(1024, 1024), // to be set by renderer, eventually | ||||
| const ls = new LineSegmentsGeometry() | const ls = new LineSegmentsGeometry() | ||||
| ls.setPositions([1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1].map(v=>v - 0.5)) | ls.setPositions([1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1].map(v=>v - 0.5)) | ||||
| const wireframe = new LineSegments2(ls, matLine) | |||||
| const wireframe = new LineSegments2(ls, matLine as any) | |||||
| wireframe.computeLineDistances() | wireframe.computeLineDistances() | ||||
| wireframe.scale.set(1, 1, 1) | wireframe.scale.set(1, 1, 1) | ||||
| wireframe.visible = true | wireframe.visible = true |
| export * from './browser-helpers' | export * from './browser-helpers' | ||||
| export * from './shaders' | |||||
| export {windowDialogWrapper, type IDialogWrapper} from './DialogWrapper' | export {windowDialogWrapper, type IDialogWrapper} from './DialogWrapper' | ||||
| export {GLStatsJS} from './GLStatsJS' | export {GLStatsJS} from './GLStatsJS' | ||||
| export {CustomContextMenu} from './CustomContextMenu' | export {CustomContextMenu} from './CustomContextMenu' |
| import simpleCameraHelpers from './shaders/simpleCameraHelpers.glsl' | |||||
| import randomHelpers from './shaders/randomHelpers.glsl' | |||||
| import voronoiNoise from './shaders/voronoiNoise.glsl' | |||||
| export const shaderUtils = { | |||||
| simpleCameraHelpers, randomHelpers, voronoiNoise, | |||||
| } |
| #ifndef BASIC_RANDOM_HELPERS | |||||
| #define BASIC_RANDOM_HELPERS | |||||
| uniform float frameCount; | |||||
| float random(float n){return fract(sin(n) * 43758.5453123);} | |||||
| float random2(vec2 n,float x){n+=x;return fract(sin(dot(n.xy,vec2(12.9898, 78.233)))*43758.5453);} | |||||
| float random3(vec3 v) { | |||||
| v = fract(v * 443.8975); | |||||
| v += dot(v, v.yzx + 19.19); | |||||
| return fract((v.x + v.y) * v.z); | |||||
| } | |||||
| // https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Shaders/Private/Random.ush#L27 | |||||
| float interleavedGradientNoise(const in vec2 fragCoord, const in float seed) { | |||||
| vec3 magic = vec3(0.06711056, 0.00583715, 52.9829189); | |||||
| return fract(magic.z * fract(dot(fragCoord.xy + seed * vec2(2.083, 4.867), magic.xy))); | |||||
| } | |||||
| vec3 hash3( vec2 p ) | |||||
| { | |||||
| vec3 q = vec3( dot(p,vec2(127.1,311.7)), | |||||
| dot(p,vec2(269.5,183.3)), | |||||
| dot(p,vec2(419.2,371.9)) ); | |||||
| return fract(sin(q)*43758.5453); | |||||
| } | |||||
| #endif |
| #ifndef SIMPLE_CAMERA_HELPERS | |||||
| #define SIMPLE_CAMERA_HELPERS | |||||
| #ifndef USE_TRANSMISSION | |||||
| uniform mat4 projectionMatrix; | |||||
| #endif | |||||
| vec3 viewToScreen(const in vec3 pos) { | |||||
| vec4 projected = projectionMatrix * vec4(pos, 1.0); | |||||
| return vec3(0.5 + 0.5 * projected.xy / projected.w, projected.w); | |||||
| } | |||||
| #endif |
| #ifndef VORONOI_HELPER | |||||
| #define VORONOI_HELPER | |||||
| float voronoi_distance(vec2 a, vec2 b, float metric) { | |||||
| return distance(a, b); | |||||
| } | |||||
| // Blender port of the original voronoise function | |||||
| float voronoi_f1_2d(in vec2 coord, in float randomness, in float flakeClamp, in float flakeRadius, inout vec3 outColor) { | |||||
| vec2 cellPosition = floor(coord); | |||||
| vec2 localPosition = coord - cellPosition; | |||||
| float minDistance = 8.0; | |||||
| vec2 targetOffset, targetPosition; | |||||
| for (int j = -1; j <= 1; j++) { | |||||
| for (int i = -1; i <= 1; i++) { | |||||
| vec2 cellOffset = vec2(i, j); | |||||
| vec2 pointPosition = cellOffset + hash3(cellPosition + cellOffset).xy * randomness; | |||||
| float distanceToPoint = voronoi_distance(pointPosition, localPosition, 1.); | |||||
| if (distanceToPoint < minDistance) { | |||||
| targetOffset = cellOffset; | |||||
| minDistance = distanceToPoint; | |||||
| targetPosition = pointPosition; | |||||
| } | |||||
| } | |||||
| } | |||||
| float outDistance = minDistance; | |||||
| float dist = step(flakeRadius, outDistance); | |||||
| outColor = hash3(cellPosition + hash3(cellPosition + targetOffset).xy * randomness + targetOffset); | |||||
| vec3 outColor1 = minDistance < flakeRadius ? outColor : vec3(0.5, 0.5, 1.); | |||||
| outDistance = mix(dist, minDistance, flakeClamp); | |||||
| outColor = mix(outColor1, outColor, flakeClamp); | |||||
| return outDistance; | |||||
| } | |||||
| #endif |