@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
307 lines (278 loc) • 11.8 kB
text/typescript
import {BasePersistedConfig, PersistedConfigWithShaders} from '../../../../utils/BasePersistedConfig';
import {BaseBuilderMatNodeType} from '../../../../mat/_BaseBuilder';
import {
ShaderMaterialWithCustomMaterials,
MaterialWithCustomMaterials,
CustomMaterialName,
} from '../../../../../../core/geometry/Material';
import {PolyDictionary} from '../../../../../../types/GlobalTypes';
import {ShaderMaterial} from 'three';
import {
assignOnBeforeCompileDataAndFunction,
OnBeforeCompileDataConverter,
OnBeforeCompileDataJSON,
} from './OnBeforeCompile';
// from https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
type RemoveShaders<Type> = {
[Property in keyof Type as Exclude<Property, 'fragmentShader' | 'vertexShader'>]: Type[Property];
};
export interface OnBeforeCompileDataJSONWithoutShaders extends RemoveShaders<OnBeforeCompileDataJSON> {}
export interface PersistedConfigBaseMaterialData {
material: object;
// param_uniform_pairs: [string, string][];
// uniforms_time_dependent?: boolean;
// uniforms_resolution_dependent?: boolean;
onBeforeCompileDataJSONWithoutShaders: OnBeforeCompileDataJSONWithoutShaders;
customMaterials?: PolyDictionary<PersistedConfigBaseMaterialData>;
}
export interface PersistedConfigBaseMaterialDataWithShaders
extends PersistedConfigBaseMaterialData,
PersistedConfigWithShaders {}
function _removeShaders(data: OnBeforeCompileDataJSON): OnBeforeCompileDataJSONWithoutShaders {
const onBeforeCompileDataJSONWithoutShaders: OnBeforeCompileDataJSONWithoutShaders = {
paramConfigs: data.paramConfigs,
timeDependent: data.timeDependent,
resolutionDependent: data.resolutionDependent,
raymarchingLightsWorldCoordsDependent: data.raymarchingLightsWorldCoordsDependent,
};
return onBeforeCompileDataJSONWithoutShaders;
}
interface Options {
vertex: string;
fragment: string;
}
function _addShaders(data: OnBeforeCompileDataJSONWithoutShaders, options: Options): OnBeforeCompileDataJSON {
const onBeforeCompileDataJSON: OnBeforeCompileDataJSON = {
paramConfigs: data.paramConfigs,
timeDependent: data.timeDependent,
resolutionDependent: data.resolutionDependent,
raymarchingLightsWorldCoordsDependent: data.raymarchingLightsWorldCoordsDependent,
fragmentShader: options.fragment,
vertexShader: options.vertex,
};
return onBeforeCompileDataJSON;
}
function _shaderKey(shaderType: 'vertex' | 'fragment', customMaterialName?: string) {
return customMaterialName ? `${customMaterialName}.${shaderType}` : shaderType;
}
// potential bug with Material Loader
// - 1. a uniform with a mat3, such as uvTransform, will be reloaded with a mat4
// - 2. the boolean lights property is not saved
// - 3. if a color property is added on the material itself, it should not be saved
// - 4. for the volume shader, a uniform with an array of vector can be saved, but not loaded again as a vector (but only as an {x,y,z} object)
export class MaterialPersistedConfig extends BasePersistedConfig {
private _material: ShaderMaterialWithCustomMaterials | undefined;
constructor(protected override node: BaseBuilderMatNodeType) {
super(node);
}
override async toData(): Promise<PersistedConfigBaseMaterialDataWithShaders | undefined> {
const assemblerController = this.node.assemblerController();
if (!assemblerController) {
return;
}
const assembler = assemblerController.assembler;
const onBeforeCompileData = assembler.onBeforeCompileData();
if (!onBeforeCompileData) {
return;
}
const onBeforeCompileDataJSON = OnBeforeCompileDataConverter.toJSON(onBeforeCompileData);
const onBeforeCompileDataJSONWithoutShaders = _removeShaders(onBeforeCompileDataJSON);
const shaders: PolyDictionary<string> = {
[_shaderKey('vertex')]: onBeforeCompileDataJSON.vertexShader,
[_shaderKey('fragment')]: onBeforeCompileDataJSON.fragmentShader,
};
// custom materials
const customMaterialsData: PolyDictionary<PersistedConfigBaseMaterialData> = {};
const material = await this.node.material();
if (!material) {
return;
}
const customMaterials = material.customMaterials;
if (customMaterials) {
assembler.traverseCustomAssemblers((customAssembler, customMaterialName) => {
const customOnBeforeCompileData = customAssembler.onBeforeCompileData();
if (customOnBeforeCompileData) {
const customMaterial = customMaterials[customMaterialName] as ShaderMaterial;
if (customMaterial) {
const customMaterialData = this._materialToJson(customMaterial, {
node: this.node,
suffix: customMaterialName,
});
if (customMaterialData) {
const data = OnBeforeCompileDataConverter.toJSON(customOnBeforeCompileData);
const dataWithoutShaders = _removeShaders(data);
customMaterialsData[customMaterialName] = {
material: customMaterialData,
onBeforeCompileDataJSONWithoutShaders: dataWithoutShaders,
};
shaders[_shaderKey('vertex', customMaterialName)] = data.vertexShader;
shaders[_shaderKey('fragment', customMaterialName)] = data.fragmentShader;
}
}
}
});
// const customMaterialNames: CustomMaterialName[] = Object.keys(customMaterials) as CustomMaterialName[];
// for (let customMaterialName of customMaterialNames) {
// }
}
// params updating uniforms
// const param_uniform_pairs: [string, string][] = [];
// const param_configs = assemblerController.assembler.param_configs();
// for (let param_config of param_configs) {
// param_uniform_pairs.push([param_config.name(), param_config.uniformName()]);
// }
const materialData = this._materialToJson(material as ShaderMaterialWithCustomMaterials, {
node: this.node,
suffix: 'main',
});
if (!materialData) {
console.warn('failed to save material from node', this.node.path());
}
const data: PersistedConfigBaseMaterialDataWithShaders = {
material: materialData || {},
onBeforeCompileDataJSONWithoutShaders,
// uniforms_time_dependent: assemblerController.assembler.uniformsTimeDependent(),
// uniforms_resolution_dependent: assemblerController.assembler.uniformsResolutionDependent(),
// param_uniform_pairs: param_uniform_pairs,
customMaterials: customMaterialsData,
shaders,
};
return data;
}
override load(data: PersistedConfigBaseMaterialDataWithShaders) {
const assemblerController = this.node.assemblerController();
if (assemblerController) {
return;
}
this._material = this._loadMaterial(data.material);
if (!this._material) {
return;
}
const shaders = data.shaders;
if (!shaders) {
console.warn(`${this.node.path()}: persisted config has no shaders`);
return;
}
// const shaderMaterial = this._material as ShaderMaterial;
const onBeforeCompileDataJSON: OnBeforeCompileDataJSON = _addShaders(
data.onBeforeCompileDataJSONWithoutShaders,
{
vertex: shaders[_shaderKey('vertex')],
fragment: shaders[_shaderKey('fragment')],
}
);
const onBeforeCompileData = OnBeforeCompileDataConverter.fromJSON(onBeforeCompileDataJSON);
const material = this._material;
assignOnBeforeCompileDataAndFunction(this.node.scene(), material, onBeforeCompileData);
for (let paramConfig of onBeforeCompileData.paramConfigs) {
paramConfig.applyToNode(this.node);
}
// material.onBeforeCompile = (shader: Shader) => {
// onBeforeCompile(shader);
// if (data.onBeforeCompileDataJSON.paramConfigs) {
// for (let paramConfigJSON of data.onBeforeCompileDataJSON.paramConfigs) {
// const paramName = paramConfigJSON.name;
// const uniformName = paramConfigJSON.uniformName;
// const param = this.node.params.get(paramName);
// const uniform = shader.uniforms[uniformName];
// const customMatNames: CustomMaterialName[] = Object.keys(
// material.customMaterials
// ) as CustomMaterialName[];
// let customUniforms: IUniform[] | undefined;
// for (let customMatName of customMatNames) {
// const customMat = material.customMaterials[customMatName] as ShaderMaterial;
// const customUniform = customMat?.uniforms[uniformName];
// if (customUniform) {
// customUniforms = customUniforms || [];
// customUniforms.push(customUniform);
// }
// }
// if (param && (uniform || customUniforms)) {
// const callback = () => {
// if (uniform) {
// GlParamConfig.callback(param, uniform);
// }
// if (customUniforms) {
// for (let customUniform of customUniforms) {
// GlParamConfig.callback(param, customUniform);
// }
// }
// };
// param.options.setOption('callback', callback);
// // it's best to execute the callback directly
// // as it may otherwise be prevented if the scene is loading for instance
// // and this is currently necessary for ramp params, when no assembler is loaded
// callback();
// // param.options.executeCallback();
// }
// }
// }
// };
// load custom materials
this._material.customMaterials = this._material.customMaterials || {};
if (data.customMaterials) {
const customMatNames: CustomMaterialName[] = Object.keys(data.customMaterials) as CustomMaterialName[];
for (const customMatName of customMatNames) {
const customMatData = data.customMaterials[customMatName];
const customMat = this._loadMaterial(customMatData.material);
if (customMat) {
const onBeforeCompileDataJSON: OnBeforeCompileDataJSON = _addShaders(
customMatData.onBeforeCompileDataJSONWithoutShaders,
{
vertex: shaders[_shaderKey('vertex', customMatName)],
fragment: shaders[_shaderKey('fragment', customMatName)],
}
);
const customOnBeforeCompileData = OnBeforeCompileDataConverter.fromJSON(onBeforeCompileDataJSON);
customOnBeforeCompileData.paramConfigs = onBeforeCompileData.paramConfigs;
assignOnBeforeCompileDataAndFunction(this.node.scene(), customMat, customOnBeforeCompileData);
this._material.customMaterials[customMatName] = customMat;
// console.log('=============', customMatName);
// We could link the customMaterial's uniform here
// and get them in sync,
// but this seems to have unexpected side-effects
// and some assigned textures do not get assigned as expected.
// Therefore, the syncing is done later in this method,
// when setting the param callback
// const uniformNames = Object.keys(this._material.uniforms);
// for (let uniformName of uniformNames) {
// const customMatUniform = custom_mat.uniforms[uniformName];
// if (customMatUniform) {
// console.log(
// uniformName,
// custom_mat.uniforms[uniformName].value,
// this._material.uniforms[uniformName].value
// );
// // if the uniform exists in the customMat, replace it completely (not just its value)
// // so that it is garantied to be in sync with the parent material
// // custom_mat.uniforms[uniformName] = this._material.uniforms[uniformName];
// }
// }
}
}
}
// if (shaderMaterial.uniforms) {
// if (data.uniforms_time_dependent) {
// this.node
// .scene()
// .uniformsController.addTimeDependentUniformOwner(
// this._material.uuid,
// this._material.uniforms as IUniformsWithTime
// );
// }
// if (data.uniforms_resolution_dependent) {
// this.node
// .scene()
// .uniformsController.addResolutionDependentUniformOwner(
// this._material.uuid,
// this._material.uniforms as IUniformsWithResolution
// );
// }
}
material(): MaterialWithCustomMaterials | undefined {
// do not check playerMode, but if the node has an assembler instead
// if (Poly.playerMode()) {
return this._material;
// }
}
}