UNPKG

@polygonjs/polygonjs

Version:

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

202 lines (178 loc) 6.98 kB
import {BaseSopOperation} from './_Base'; import {CoreGroup} from '../../../core/geometry/Group'; import {TypedNodePathParamValue} from '../../../core/Walker'; import {NodeContext} from '../../../engine/poly/NodeContext'; import {BaseBuilderMatNodeType} from '../../../engine/nodes/mat/_BaseBuilder'; import {applyRenderHook, applyCustomMaterials, cloneMaterial} from '../../../core/geometry/Material'; import {Group, Material, Object3D, Mesh, Texture, ShaderMaterial} from 'three'; import {GlobalsGeometryHandler} from '../../../engine/nodes/gl/code/globals/Geometry'; import {InputCloneMode} from '../../../engine/poly/InputCloneMode'; import {isBooleanTrue} from '../../../core/BooleanValue'; import {DefaultOperationParams} from '../../../core/operations/_Base'; import {CoreMask} from '../../../core/geometry/Mask'; import {BaseMatNodeType} from '../../nodes/mat/_Base'; import {MaterialSopNode} from '../../nodes/sop/Material'; interface MaterialSopParams extends DefaultOperationParams { group: string; assignMat: boolean; material: TypedNodePathParamValue; cloneMat: boolean; shareCustomUniforms: boolean; swapCurrentTex: boolean; texSrc0: string; texDest0: string; } // type TraverseCallback = (coreObject: CoreObject) => void; let _nextId = 0; export class MaterialSopOperation extends BaseSopOperation { private _materialSopOperationId = _nextId++; static override readonly DEFAULT_PARAMS: MaterialSopParams = { group: '', assignMat: true, material: new TypedNodePathParamValue(''), cloneMat: false, shareCustomUniforms: true, swapCurrentTex: false, texSrc0: 'emissiveMap', texDest0: 'map', }; static override readonly INPUT_CLONED_STATE = InputCloneMode.FROM_NODE; static override type(): Readonly<'material'> { return 'material'; } private _globalsHandler: GlobalsGeometryHandler = new GlobalsGeometryHandler(); override async cook(inputCoreGroups: CoreGroup[], params: MaterialSopParams) { const coreGroup = inputCoreGroups[0]; this._oldMatByOldNewId.clear(); await this._applyMaterials(coreGroup, params); this._swapTextures(coreGroup, params); return coreGroup; } private async _getMaterial(params: MaterialSopParams) { const materialNode = params.material.nodeWithContext(NodeContext.MAT, this.states?.error); if (materialNode) { this._watchMaterialNode(materialNode); const material = await materialNode.material(); const baseBuilderMatNode = materialNode as BaseBuilderMatNodeType; if (baseBuilderMatNode.assemblerController) { baseBuilderMatNode.assemblerController()?.setAssemblerGlobalsHandler(this._globalsHandler); } if (!material) { this.states?.error.set(`material invalid. (error: '${materialNode.states.error.message()}')`); } return material; } else { this.states?.error.set(`no material node found`); } } private _watchedMaterialNode: BaseMatNodeType | undefined; private _watchedMaterialNodeMaterial: Material | undefined; private _watchMaterialNode(materialNode: BaseMatNodeType) { if (this._watchedMaterialNode == materialNode) { return; } const hookName = this._watchHookName(); materialNode.addPostDirtyHook(hookName, this._onMaterialUpdateBound); materialNode.cookController.registerOnCookEnd(hookName, this._onMaterialUpdateBound); if (this._watchedMaterialNode) { this._watchedMaterialNode.removePostDirtyHook(hookName); this._watchedMaterialNode.cookController.deregisterOnCookEnd(hookName); } this._watchedMaterialNode = materialNode; } private _onMaterialUpdateBound = this._onMaterialUpdate.bind(this); private async _onMaterialUpdate() { if (!this._watchedMaterialNode) { return; } const container = await this._watchedMaterialNode.compute(); const material = container.material(); if (material != this._watchedMaterialNodeMaterial) { this._watchedMaterialNodeMaterial = material; if (this._node) { (this._node as MaterialSopNode).p.material.setDirty(); } } } private _watchHookName() { return `MaterialSopOperationId-${this._materialSopOperationId}`; } private async _applyMaterials(coreGroup: CoreGroup, params: MaterialSopParams) { if (!isBooleanTrue(params.assignMat)) { return; } const material = await this._getMaterial(params); if (!material) { return; } const selectedObjects = CoreMask.filterThreejsObjects(coreGroup, params); for (let selectedObject of selectedObjects) { this._applyMaterial(selectedObject, material, params); } return coreGroup; } private _oldMatByOldNewId: Map<string, Material> = new Map(); private _materialByUuid: Map<string, Material> = new Map(); private _swapTextures(coreGroup: CoreGroup, params: MaterialSopParams) { if (!isBooleanTrue(params.swapCurrentTex)) { return; } this._materialByUuid.clear(); const objects = CoreMask.filterObjects(coreGroup, params, coreGroup.allCoreObjects()); for (let object of objects) { const mat = (object as Mesh).material as Material; this._materialByUuid.set(mat.uuid, mat); } this._materialByUuid.forEach((mat, mat_uuid) => { this._swapTexture(mat, params); }); } private _applyMaterial(object: Object3D, srcMaterial: Material, params: MaterialSopParams) { const usedMaterial = isBooleanTrue(params.cloneMat) ? cloneMaterial(this.scene(), srcMaterial, { shareCustomUniforms: params.shareCustomUniforms, addCustomMaterials: true, }) : srcMaterial; if (srcMaterial instanceof ShaderMaterial && usedMaterial instanceof ShaderMaterial) { for (let uniform_name in srcMaterial.uniforms) { usedMaterial.uniforms[uniform_name] = srcMaterial.uniforms[uniform_name]; } } if ((object as Group).isGroup) { // do not assign material to a group, as this could cause render errors return; } const object_with_material = object as Mesh; // const current_mat = object_with_material.material as Material | undefined; // if (current_mat && params.swapCurrentTex) { // this._swap_texture(used_material, current_mat, params); // } this._oldMatByOldNewId.set(usedMaterial.uuid, object_with_material.material as Material); object_with_material.material = usedMaterial; applyRenderHook(object, usedMaterial); applyCustomMaterials(object, usedMaterial); } private _swapTexture(target_mat: Material, params: MaterialSopParams) { if (params.texSrc0 == '' || params.texDest0 == '') { return; } let src_mat = this._oldMatByOldNewId.get(target_mat.uuid); src_mat = src_mat || target_mat; const src_tex: Texture | null = (src_mat as any)[params.texSrc0]; if (src_tex) { // swap mat param (target_mat as any)[params.texDest0] = src_tex; // (src_mat as any)[params.texSrc0] = null; // swap uniforms const uniforms = (target_mat as any).uniforms; if (uniforms) { const uniforms_map = uniforms[params.texDest0]; if (uniforms_map) { uniforms[params.texDest0] = {value: src_tex}; // uniforms[params.texSrc0] = {value: null}; } } } } }