| @@ -303,7 +303,6 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data: ImportResult} | |||
| 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) | |||
| @@ -7,13 +7,15 @@ import { | |||
| IMaterialTemplate, | |||
| ITexture, | |||
| ITextureEvent, | |||
| LegacyPhongMaterial, | |||
| LineMaterial2, | |||
| PhysicalMaterial, | |||
| UnlitLineMaterial, | |||
| UnlitMaterial, | |||
| } from '../core' | |||
| import {downloadFile} from 'ts-browser-helpers' | |||
| import {MaterialExtension} from '../materials' | |||
| import {generateUUID, isInScene} from '../three' | |||
| import {LegacyPhongMaterial} from '../core/material/LegacyPhongMaterial' | |||
| /** | |||
| * Material Manager | |||
| @@ -26,6 +28,8 @@ export class MaterialManager<T = ''> extends EventDispatcher<BaseEvent, T> { | |||
| readonly templates: IMaterialTemplate[] = [ | |||
| PhysicalMaterial.MaterialTemplate, | |||
| UnlitMaterial.MaterialTemplate, | |||
| UnlitLineMaterial.MaterialTemplate, | |||
| LineMaterial2.MaterialTemplate, | |||
| LegacyPhongMaterial.MaterialTemplate, | |||
| ] | |||
| @@ -334,5 +338,6 @@ export class MaterialManager<T = ''> extends EventDispatcher<BaseEvent, T> { | |||
| } | |||
| return applied | |||
| } | |||
| } | |||
| @@ -58,7 +58,7 @@ export interface IMaterialUserData extends IImportResultUserData{ | |||
| inverseAlphaMap?: boolean // only for physical material right now | |||
| [key: string]: any // commented for noe | |||
| [key: string]: any | |||
| // legacy, to be removed | |||
| @@ -153,6 +153,7 @@ export interface IObject3D<E extends Event = IObject3DEvent, ET = IObject3DEvent | |||
| isCamera?: boolean | |||
| isMesh?: boolean | |||
| isLine?: boolean | |||
| isLineSegments?: boolean | |||
| // isGroup?: boolean | |||
| isScene?: boolean | |||
| // isHelper?: boolean | |||
| @@ -4,6 +4,8 @@ export {ExtendedShaderMaterial} from './material/ExtendedShaderMaterial' | |||
| export {PhysicalMaterial, type PhysicalMaterialEventTypes, MeshStandardMaterial2} from './material/PhysicalMaterial' | |||
| export {ShaderMaterial2} from './material/ShaderMaterial2' | |||
| 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 {iObjectCommons} from './object/iObjectCommons' | |||
| export {iCameraCommons} from './object/iCameraCommons' | |||
| @@ -14,12 +14,15 @@ import { | |||
| WebGLRenderer, | |||
| } from 'three' | |||
| import {shaderReplaceString} from '../../utils' | |||
| import {IMaterialUserData} from '../IMaterial' | |||
| export class ExtendedShaderMaterial extends ShaderMaterial2 { | |||
| declare ['constructor']: (typeof ExtendedShaderMaterial) & (typeof ShaderMaterial2) | |||
| textures: {colorSpace: ColorSpace, id: string}[] = [] | |||
| userData: IMaterialUserData | |||
| constructor(parameters: ShaderMaterialParameters, textureIds: string[], isRawShaderMaterial = false) { | |||
| super(parameters, isRawShaderMaterial) | |||
| this.setTextureIds(textureIds) | |||
| @@ -27,8 +27,15 @@ import { | |||
| } from 'three' | |||
| import {downloadBlob, uploadFile} from 'ts-browser-helpers' | |||
| import {PhysicalMaterial} from './PhysicalMaterial' | |||
| import {getEmptyMeta} from '../../utils/serialization' | |||
| import {getEmptyMeta} from '../../utils' | |||
| import {LegacyPhongMaterial} from './LegacyPhongMaterial' | |||
| import {generateUUID} from '../../three' | |||
| declare module '../IMaterial' { | |||
| interface IMaterial { | |||
| __matExtUiConfigs?: Record<string, UiObjectConfig|undefined> | |||
| } | |||
| } | |||
| export const iMaterialUI = { | |||
| base: (material: IMaterial): UiObjectConfig[] => [ | |||
| @@ -129,11 +136,11 @@ export const iMaterialUI = { | |||
| value: value[1], | |||
| })), | |||
| }, | |||
| { | |||
| material.alphaMap !== undefined ? { | |||
| type: 'image', | |||
| property: [material, 'alphaMap'], | |||
| }, | |||
| makeSamplerUi(material, 'alphaMap'), | |||
| } : {}, | |||
| material.alphaMap !== undefined ? makeSamplerUi(material, 'alphaMap') : {}, | |||
| { | |||
| type: 'checkbox', | |||
| label: 'Render to Gbuffer', | |||
| @@ -211,6 +218,12 @@ export const iMaterialUI = { | |||
| } | |||
| ), | |||
| 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', | |||
| label: 'Side', | |||
| @@ -250,7 +263,6 @@ export const iMaterialUI = { | |||
| }) | |||
| }, | |||
| }, | |||
| ()=>material.materialExtensions?.map(v=>v.getUiConfig?.(material, material.uiConfig?.uiRefresh)).filter(v=>v), | |||
| ], | |||
| roughMetal: (material: PhysicalMaterial): UiObjectConfig => ( | |||
| { | |||
| @@ -287,7 +299,7 @@ export const iMaterialUI = { | |||
| children: [ | |||
| { | |||
| type: 'slider', | |||
| bounds: [0, 0.2], | |||
| bounds: [-0.2, 0.2], | |||
| stepSize: 0.001, | |||
| property: [material, 'bumpScale'], | |||
| hidden: ()=>!material.bumpMap, | |||
| @@ -17,6 +17,7 @@ import { | |||
| IMaterialGenerator, | |||
| IMaterialParameters, | |||
| IMaterialTemplate, | |||
| IMaterialUserData, | |||
| } from '../IMaterial' | |||
| import {MaterialExtension} from '../../materials' | |||
| import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | |||
| @@ -35,6 +36,8 @@ export class LegacyPhongMaterial extends MeshPhongMaterial<IMaterialEvent, Phong | |||
| public static readonly TYPE = 'LegacyPhongMaterial' // not using .type because it is used by three.js | |||
| assetType = 'material' as const | |||
| userData: IMaterialUserData | |||
| public readonly isLegacyPhongMaterial = true | |||
| readonly appliedMeshes: Set<IObject3D> = new Set() | |||
| @@ -0,0 +1,210 @@ | |||
| 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) | |||
| }, | |||
| } | |||
| } | |||
| @@ -14,7 +14,7 @@ import { | |||
| Vector2, | |||
| WebGLRenderer, | |||
| } from 'three' | |||
| import {shaderReplaceString} from '../../utils/shader-helpers' | |||
| import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | |||
| import { | |||
| IMaterial, | |||
| IMaterialEvent, | |||
| @@ -22,8 +22,8 @@ import { | |||
| IMaterialGenerator, | |||
| IMaterialParameters, | |||
| IMaterialTemplate, | |||
| IMaterialUserData, | |||
| } from '../IMaterial' | |||
| import {SerializationMetaType, ThreeSerialization} from '../../utils/serialization' | |||
| import {MaterialExtension} from '../../materials' | |||
| import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons' | |||
| import {IObject3D} from '../IObject' | |||
| @@ -38,6 +38,8 @@ export class PhysicalMaterial extends MeshPhysicalMaterial<IMaterialEvent, Physi | |||
| public static readonly TYPE = 'PhysicalMaterial' // not using .type because it is used by three.js | |||
| assetType = 'material' as const | |||
| userData: IMaterialUserData | |||
| public readonly isPhysicalMaterial = true | |||
| readonly appliedMeshes: Set<IObject3D> = new Set() | |||
| @@ -10,7 +10,7 @@ import { | |||
| ShaderMaterialParameters, | |||
| WebGLRenderer, | |||
| } from 'three' | |||
| import {IMaterial, IMaterialEvent, IMaterialEventTypes, IMaterialParameters} from '../IMaterial' | |||
| import {IMaterial, IMaterialEvent, IMaterialEventTypes, IMaterialParameters, IMaterialUserData} from '../IMaterial' | |||
| import {MaterialExtension} from '../../materials' | |||
| import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons' | |||
| @@ -38,6 +38,9 @@ export class ShaderMaterial2<E extends IMaterialEvent = IMaterialEvent, ET = IMa | |||
| } | |||
| assetType = 'material' as const | |||
| userData: IMaterialUserData | |||
| public readonly isAShaderMaterial = true | |||
| readonly appliedMeshes: Set<any> = new Set() | |||
| @@ -0,0 +1,227 @@ | |||
| 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') | |||
| } | |||
| } | |||
| @@ -16,6 +16,7 @@ import { | |||
| IMaterialGenerator, | |||
| IMaterialParameters, | |||
| IMaterialTemplate, | |||
| IMaterialUserData, | |||
| } from '../IMaterial' | |||
| import {MaterialExtension} from '../../materials' | |||
| import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | |||
| @@ -33,6 +34,8 @@ export class UnlitMaterial extends MeshBasicMaterial<IMaterialEvent, UnlitMateri | |||
| public static readonly TYPE = 'UnlitMaterial' // not using .type because it is used by three.js | |||
| assetType = 'material' as const | |||
| userData: IMaterialUserData | |||
| public readonly isUnlitMaterial = true | |||
| readonly appliedMeshes: Set<IObject3D> = new Set() | |||
| @@ -75,7 +75,7 @@ export const threeMaterialPropList = { | |||
| export const iMaterialCommons = { | |||
| threeMaterialPropList, | |||
| 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.uiConfig?.uiRefresh?.(true, 'postFrame', 1) | |||
| }, | |||
| @@ -174,7 +174,7 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| } : {}, | |||
| ], | |||
| } | |||
| if (this.isMesh && isMesh !== false) { | |||
| if ((this.isLine || this.isMesh) && isMesh !== false) { | |||
| // todo: move to make mesh ui function? | |||
| const ui = [ | |||
| // morph targets | |||
| @@ -117,9 +117,9 @@ export const iObjectCommons = { | |||
| }) | |||
| // this is called initially in Material manager from process model below, not required here... | |||
| // todo: shouldnt be called from there. maybe check if material is upgraded before | |||
| if (currentMaterial && !Array.isArray(currentMaterial) && !currentMaterial.assetType) { | |||
| console.error('todo: initMaterial: material not upgraded') | |||
| } | |||
| // if (currentMaterial && !Array.isArray(currentMaterial) && !currentMaterial.assetType) { | |||
| // console.error('todo: initMaterial: material not upgraded') | |||
| // } | |||
| this.material = currentMaterial | |||
| // Legacy | |||
| @@ -168,7 +168,7 @@ export const iObjectCommons = { | |||
| // const mat = material?.materialObject | |||
| if (!mat) continue | |||
| if (!mat.assetType) { | |||
| console.error('Material not upgraded') | |||
| console.warn('Upgrading Material', mat) | |||
| iMaterialCommons.upgradeMaterial.call(mat) | |||
| } | |||
| materials.push(mat) | |||
| @@ -8,6 +8,7 @@ export * from './plugins/index' | |||
| export * from './postprocessing/index' | |||
| export * from './materials/index' | |||
| export * from './rendering/index' | |||
| export * from './ui/image-ui' | |||
| // testing | |||
| export {_testStart, _testFinish} from './testing/testing' | |||
| @@ -1,11 +1,16 @@ | |||
| import {IMaterial, IMaterialUserData} from '../core' | |||
| 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 {generateUUID} from '../three/utils/misc' | |||
| import {generateUUID} from '../three' | |||
| export class MaterialExtender { | |||
| static { | |||
| Object.assign(ShaderChunk, shaderUtils) // for #include in the shaders | |||
| } | |||
| static VoidMain = 'void main()' | |||
| static ApplyMaterialExtensions(material: IMaterial, shader: Shader, materialExtensions: MaterialExtension[], renderer: WebGLRenderer) { | |||
| @@ -96,7 +101,7 @@ export class MaterialExtender { | |||
| } | |||
| } | |||
| function updateMaterialDefines(defines: MaterialExtension['extraDefines'], material: IMaterial) { | |||
| export function updateMaterialDefines(defines: MaterialExtension['extraDefines'], material: IMaterial) { | |||
| if (!defines || !material) return | |||
| if (material.defines === undefined || material.defines === null) { // required for some three.js materials | |||
| material.defines = {} | |||
| @@ -111,7 +116,7 @@ function updateMaterialDefines(defines: MaterialExtension['extraDefines'], mater | |||
| flag = true | |||
| } | |||
| } else if (material.defines[key] !== val) { | |||
| material.defines[key] = val | |||
| material.defines[key] = typeof val === 'boolean' ? +val : val | |||
| flag = true | |||
| } | |||
| } | |||
| @@ -15,8 +15,9 @@ export interface MaterialExtension{ | |||
| /** | |||
| * 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 | |||
| @@ -85,7 +86,7 @@ export interface MaterialExtension{ | |||
| * This is called once when the material extension is registered. | |||
| * @param material | |||
| */ | |||
| getUiConfig?: (material: IMaterial, refreshUi: UiObjectConfig['uiRefresh']) => UiObjectConfig | undefined | |||
| getUiConfig?: (material: IMaterial, refreshUi?: UiObjectConfig['uiRefresh']) => UiObjectConfig | undefined | |||
| /** | |||
| * Higher priority extensions are applied first. | |||
| @@ -1,2 +1,2 @@ | |||
| export {MaterialExtender} from './MaterialExtender' | |||
| export {MaterialExtender, updateMaterialDefines} from './MaterialExtender' | |||
| export type {MaterialExtension, IShaderPropertiesUpdater} from './MaterialExtension' | |||
| @@ -1,10 +1,10 @@ | |||
| import {Group, Sphere, Vector2} from 'three' | |||
| import {LineMaterial} from 'three/examples/jsm/lines/LineMaterial.js' | |||
| import {AnyOptions} from 'ts-browser-helpers' | |||
| import {Box3B} from '../math/Box3B' | |||
| import {IObject3D, IWidget} from '../../core' | |||
| import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' | |||
| import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' | |||
| import {LineMaterial2} from '../../core/material/LineMaterial2' | |||
| export class SelectionWidget extends Group implements IWidget { | |||
| isWidget = true as const | |||
| @@ -79,7 +79,7 @@ export class SelectionWidget extends Group implements IWidget { | |||
| export class BoxSelectionWidget extends SelectionWidget { | |||
| constructor() { | |||
| super() | |||
| const matLine = new LineMaterial({ | |||
| const matLine = new LineMaterial2({ | |||
| color: '#ff2222' as any, transparent: true, opacity: 0.9, | |||
| linewidth: 5, // in pixels | |||
| resolution: new Vector2(1024, 1024), // to be set by renderer, eventually | |||
| @@ -90,7 +90,7 @@ export class BoxSelectionWidget extends SelectionWidget { | |||
| 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)) | |||
| const wireframe = new LineSegments2(ls, matLine) | |||
| const wireframe = new LineSegments2(ls, matLine as any) | |||
| wireframe.computeLineDistances() | |||
| wireframe.scale.set(1, 1, 1) | |||
| wireframe.visible = true | |||
| @@ -1,4 +1,5 @@ | |||
| export * from './browser-helpers' | |||
| export * from './shaders' | |||
| export {windowDialogWrapper, type IDialogWrapper} from './DialogWrapper' | |||
| export {GLStatsJS} from './GLStatsJS' | |||
| export {CustomContextMenu} from './CustomContextMenu' | |||
| @@ -0,0 +1,7 @@ | |||
| import simpleCameraHelpers from './shaders/simpleCameraHelpers.glsl' | |||
| import randomHelpers from './shaders/randomHelpers.glsl' | |||
| import voronoiNoise from './shaders/voronoiNoise.glsl' | |||
| export const shaderUtils = { | |||
| simpleCameraHelpers, randomHelpers, voronoiNoise, | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| #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 | |||
| @@ -0,0 +1,10 @@ | |||
| #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 | |||
| @@ -0,0 +1,36 @@ | |||
| #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 | |||