polygonjs-engine
Version:
node-based webgl 3D engine https://polygonjs.com
308 lines (290 loc) • 9.9 kB
text/typescript
import {Constructor} from '../../../../types/GlobalTypes';
import {BaseController} from './_BaseController';
import {Material} from 'three/src/materials/Material';
import {Texture} from 'three/src/textures/Texture';
import {BaseMatNodeType} from '../_Base';
import {ParamConfig} from '../../utils/params/ParamsConfig';
import {NodeContext} from '../../../poly/NodeContext';
import {BaseCopNodeType} from '../../cop/_Base';
import {OperatorPathParam} from '../../../params/OperatorPath';
import {BooleanParam} from '../../../params/Boolean';
import {BaseNodeType} from '../../_Base';
import {BaseParamType} from '../../../params/_Base';
import {ShaderMaterial} from 'three/src/materials/ShaderMaterial';
import {IUniform} from 'three/src/renderers/shaders/UniformsLib';
import {IUniforms} from '../../../../core/geometry/Material';
import {NODE_PATH_DEFAULT} from '../../../../core/Walker';
export function TextureMapParamConfig<TBase extends Constructor>(Base: TBase) {
return class Mixin extends Base {
useMap = ParamConfig.BOOLEAN(0);
map = ParamConfig.OPERATOR_PATH(NODE_PATH_DEFAULT.NODE.UV, {visibleIf: {useMap: 1}});
};
}
// class TextureMapMaterial<T extends string> extends Material {
// [T]!: Texture | null;
// }
// class TextureMapParamsConfig extends TextureMapParamConfig(NodeParamsConfig) {}
// class TextureMapMatNode extends TypedMatNode<TextureMapMaterial, TextureMapParamsConfig> {
// create_material() {
// return new TextureMapMaterial();
// }
// }
type FilterFlags<Base, Condition> = {
[Key in keyof Base]: Base[Key] extends Condition ? Key : never;
};
type AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];
type SubType<Base, Condition> = Pick<Base, AllowedNames<Base, Condition>>;
// type test = FilterFlags<MeshLambertMaterial, Texture|null>
// type test2 = AllowedNames<MeshLambertMaterial, Texture|null>
// type test3 = SubType<MeshLambertMaterial, Texture|null>
export function BooleanParamOptions(controller_class: typeof BaseTextureMapController) {
return {
cook: false,
callback: (node: BaseNodeType, param: BaseParamType) => {
controller_class.update(node as BaseMatNodeType);
},
};
}
export function OperatorPathOptions(controller: typeof BaseTextureMapController, use_map_name: string) {
return {
visibleIf: {[use_map_name]: 1},
nodeSelection: {context: NodeContext.COP},
cook: false,
callback: (node: BaseNodeType, param: BaseParamType) => {
controller.update(node as BaseMatNodeType);
},
};
}
type TextureUpdateCallback<O extends Object> = (
material: Material,
object: O,
mat_attrib_name: keyof SubType<O, Texture | null>,
texture: Texture
) => void;
type TextureRemoveCallback<O extends Object> = (
material: Material,
object: O,
mat_attrib_name: keyof SubType<O, Texture | null>
) => void;
type CurrentMaterial = Material | ShaderMaterial;
export interface UpdateOptions {
direct_params?: boolean;
uniforms?: boolean;
// define?: boolean;
// define_uv?: boolean;
}
export class BaseTextureMapController extends BaseController {
constructor(protected node: BaseMatNodeType, protected _update_options: UpdateOptions) {
super(node);
// if (this._update_options.define == null) {
// this._update_options.define = true;
// }
// if (this._update_options.define_uv == null) {
// this._update_options.define_uv = true;
// }
}
protected add_hooks(use_map_param: BooleanParam, path_param: OperatorPathParam) {
use_map_param.addPostDirtyHook('TextureController', () => {
this.update();
});
path_param.addPostDirtyHook('TextureController', () => {
this.update();
});
}
static update(node: BaseNodeType) {}
async _update<M extends CurrentMaterial>(
material: M,
mat_attrib_name: string,
use_map_param: BooleanParam,
path_param: OperatorPathParam
) {
if (this._update_options.uniforms) {
const shader_material = material as ShaderMaterial;
const attr_name = mat_attrib_name as keyof SubType<IUniforms, Texture | null>;
await this._update_texture_on_uniforms(shader_material, attr_name, use_map_param, path_param);
}
if (this._update_options.direct_params) {
const mat = material as Material;
const attr_name = mat_attrib_name as keyof SubType<Material, Texture | null>;
await this._update_texture_on_material(mat, attr_name, use_map_param, path_param);
}
}
//
//
// FOR CASES WHERE THE TEXTURE IS ON THE UNIFORMS
//
//
async _update_texture_on_uniforms<O extends IUniform>(
material: ShaderMaterial,
mat_attrib_name: keyof SubType<O, Texture | null>,
use_map_param: BooleanParam,
path_param: OperatorPathParam
) {
this._update_required_attribute(
material,
material.uniforms,
mat_attrib_name as never,
use_map_param,
path_param,
this._apply_texture_on_uniforms.bind(this),
this._remove_texture_from_uniforms.bind(this)
);
}
private _apply_texture_on_uniforms<O extends IUniforms>(
material: Material,
uniforms: O,
mat_attrib_name: keyof SubType<O, Texture | null>,
texture: Texture
) {
const has_texture = uniforms[mat_attrib_name] != null && uniforms[mat_attrib_name].value != null;
let new_texture_is_different = false;
if (has_texture) {
const current_texture: Texture = (<unknown>uniforms[mat_attrib_name].value) as Texture;
if (current_texture.uuid != texture.uuid) {
new_texture_is_different = true;
}
}
if (!has_texture || new_texture_is_different) {
uniforms[mat_attrib_name].value = texture as any;
// currently removing the settings of defines USE_MAP or USE_UV
// as this seems to conflict with setting .map on the material itself.
// ideally I should test if .alphaMap and .envMap still work
// if (this._do_update_define()) {
// if (material.defines) {
// const define_name = this._define_name(`${mat_attrib_name}`);
// material.defines[define_name] = 3;
// }
// }
// if (this._update_options.define_uv) {
// if (material.defines) {
// material.defines['USE_UV'] = 5;
// }
// }
this._apply_texture_on_material(material, material, mat_attrib_name as any, texture);
material.needsUpdate = true;
}
}
private _remove_texture_from_uniforms<U extends IUniforms>(
material: Material,
uniforms: U,
mat_attrib_name: keyof SubType<U, Texture | null>
) {
if (uniforms[mat_attrib_name].value) {
uniforms[mat_attrib_name].value = null;
// if (this._do_update_define()) {
// if (material.defines) {
// // const define_name = this._define_name(`${mat_attrib_name}`);
// // delete material.defines[define_name];
// }
// }
this._remove_texture_from_material(material, material, mat_attrib_name as any);
material.needsUpdate = true;
}
}
// private _define_name(mat_attrib_name: string): string {
// return 'USE_' + mat_attrib_name.replace('_', '').toUpperCase();
// }
//
//
// FOR CASES WHERE THE TEXTURE IS ON THE MATERIAL
//
//
async _update_texture_on_material<M extends Material>(
material: M,
mat_attrib_name: keyof SubType<M, Texture | null>,
use_map_param: BooleanParam,
path_param: OperatorPathParam
) {
this._update_required_attribute(
material,
material,
mat_attrib_name,
use_map_param,
path_param,
this._apply_texture_on_material.bind(this),
this._remove_texture_from_material.bind(this)
);
}
private _apply_texture_on_material<M extends Material>(
material: Material,
texture_owner: M,
mat_attrib_name: keyof SubType<M, Texture | null>,
texture: Texture
) {
const has_texture = texture_owner[mat_attrib_name] != null;
let new_texture_is_different = false;
if (has_texture) {
const current_texture: Texture = (<unknown>texture_owner[mat_attrib_name]) as Texture;
if (current_texture.uuid != texture.uuid) {
new_texture_is_different = true;
}
}
if (!has_texture || new_texture_is_different) {
texture_owner[mat_attrib_name] = texture as any;
material.needsUpdate = true;
}
}
private _remove_texture_from_material<M extends Material>(
material: Material,
texture_owner: M,
mat_attrib_name: keyof SubType<M, Texture | null>
) {
if (texture_owner[mat_attrib_name]) {
texture_owner[mat_attrib_name] = null as any;
material.needsUpdate = true;
}
}
//
//
// MAIN ALGO to decide if texture should be updated
//
//
private async _update_required_attribute<O extends Object>(
material: Material,
texture_owner: O,
mat_attrib_name: keyof SubType<O, Texture | null>,
use_map_param: BooleanParam,
path_param: OperatorPathParam,
update_callback: TextureUpdateCallback<O>,
remove_callback: TextureRemoveCallback<O>
) {
if (use_map_param.isDirty()) {
await use_map_param.compute();
}
const use_map: boolean = use_map_param.value;
if (use_map) {
if (path_param.isDirty()) {
await path_param.compute();
}
const found_node = path_param.found_node();
if (found_node) {
if (found_node.nodeContext() == NodeContext.COP) {
const texture_node = found_node as BaseCopNodeType;
const container = await texture_node.requestContainer();
const texture = container.texture();
if (texture) {
update_callback(material, texture_owner, mat_attrib_name, texture);
return;
} else {
this.node.states.error.set(`found node has no texture`);
}
} else {
this.node.states.error.set(`found map node is not a COP node`);
}
} else {
this.node.states.error.set(
`could not find map node ${path_param.name()} with path ${path_param.value}`
);
}
}
// this is not wrapped in an else clause after the "if (use_map) {"
// as we should come here after any of the errors above, if any is triggered
remove_callback(material, texture_owner, mat_attrib_name);
}
// private _do_update_define(): boolean {
// if (this._update_options.define == null) {
// return true;
// }
// return this._update_options.define;
// }
}