UNPKG

polygonjs-engine

Version:

node-based webgl 3D engine https://polygonjs.com

308 lines (290 loc) 9.9 kB
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; // } }