UNPKG

@polygonjs/polygonjs

Version:

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

373 lines (372 loc) 12.3 kB
"use strict"; import { Mesh, PlaneGeometry, WebGLRenderer, ShaderMaterial, Scene, ClampToEdgeWrapping, RGBAFormat, LinearFilter, NearestFilter, NoToneMapping, WebGLArrayRenderTarget, NoColorSpace, OrthographicCamera } from "three"; import { TypedCopNode } from "./_Base"; import { GlobalsGeometryHandler } from "../gl/code/globals/Geometry"; import { GlNodeFinder } from "../gl/code/utils/NodeFinder"; import { NodeContext } from "../../poly/NodeContext"; import { NodeParamsConfig, ParamConfig } from "../utils/params/ParamsConfig"; import { CopRendererController } from "./utils/RendererController"; import { AssemblerName } from "../../poly/registers/assemblers/_BaseRegister"; import { Poly } from "../../Poly"; import { TexturePersistedConfig } from "../gl/code/assemblers/textures/TexturePersistedConfig"; import { CopType } from "../../poly/registers/nodes/types/Cop"; import { TextureParamsController, TextureParamConfig } from "./utils/TextureParamsController"; import { isBooleanTrue } from "../../../core/Type"; import FRAGMENT_SHADER from "../gl/code/templates/textures/Default.frag.glsl"; import VERTEX_SHADER from "../gl/code/templates/textures/Default.vert.glsl"; import { handleCopBuilderDependencies } from "./utils/BuilderUtils"; const RESOLUTION_DEFAULT = [128, 128]; function Builder2DArrayCopParamConfig(Base) { return class Mixin extends Base { constructor() { super(...arguments); /** @param textures resolution */ this.resolution = ParamConfig.VECTOR2(RESOLUTION_DEFAULT); /** @param layers */ this.layers = ParamConfig.INTEGER(4, { range: [2, 32], rangeLocked: [true, false] }); /** @param use the main camera renderer. This can save memory, but can also lead to colors being affected by the renderer's output color space */ this.useCameraRenderer = ParamConfig.BOOLEAN(1, { callback: (node) => { Builder2DArrayCopNode.PARAM_CALLBACK_render(node); } }); /** @param use a data texture instead of a render target, which can be useful when using that texture as and envMap */ // useDataTexture = ParamConfig.BOOLEAN(0); /** @param force Render */ this.render = ParamConfig.BUTTON(null, { callback: (node) => { Builder2DArrayCopNode.PARAM_CALLBACK_render(node); } }); } }; } class Builder2DArrayCopParamsConfig extends TextureParamConfig(Builder2DArrayCopParamConfig(NodeParamsConfig)) { } const ParamsConfig = new Builder2DArrayCopParamsConfig(); export class Builder2DArrayCopNode extends TypedCopNode { constructor() { super(...arguments); this.paramsConfig = ParamsConfig; this.persisted_config = new TexturePersistedConfig(this); this._assemblerController = this._createAssemblerController(); this._textureMesh = new Mesh(new PlaneGeometry(2, 2)); this.textureMaterial = new ShaderMaterial({ uniforms: {}, vertexShader: VERTEX_SHADER, fragmentShader: FRAGMENT_SHADER }); this._textureScene = new Scene(); this._textureCamera = new OrthographicCamera(); this.textureParamsController = new TextureParamsController(this); this._childrenControllerContext = NodeContext.GL; this._cook_main_without_inputs_when_dirty_bound = this._cook_main_without_inputs_when_dirty.bind(this); // // // RENDER + RENDER TARGET // // this.boundRenderOnTarget = this.renderOnTargetWithoutUpdatingTextureFromParams.bind(this); this._prevTarget = null; this._prevOutputColorSpace = NoColorSpace; this._prevToneMapping = NoToneMapping; } static type() { return CopType.BUILDER_2D_ARRAY; } usedAssembler() { return AssemblerName.GL_TEXTURE_2D_ARRAY; } _createAssemblerController() { const assemblerController = Poly.assemblersRegister.assembler(this, this.usedAssembler()); if (assemblerController) { const globalsHandler = new GlobalsGeometryHandler(); assemblerController.setAssemblerGlobalsHandler(globalsHandler); return assemblerController; } } assemblerController() { return this._assemblerController; } initializeNode() { this._textureMesh.material = this.textureMaterial; this._textureMesh.scale.multiplyScalar(0.25); this._textureScene.add(this._textureMesh); this._textureCamera.position.z = 1; this.addPostDirtyHook("_cook_main_without_inputs_when_dirty", () => { setTimeout(this._cook_main_without_inputs_when_dirty_bound, 0); }); } createNode(node_class, options) { return super.createNode(node_class, options); } children() { return super.children(); } nodesByType(type) { return super.nodesByType(type); } childrenAllowed() { if (this.assemblerController()) { return super.childrenAllowed(); } return false; } sceneReadonly() { return this.assemblerController() == null; } async _cook_main_without_inputs_when_dirty() { await this.cookController.cookMainWithoutInputs(); } // private _reset_if_resolution_changed(trigger?: CoreGraphNode) { // if (trigger && trigger.graphNodeId() == this.p.resolution.graphNodeId()) { // this._reset(); // } // } async cook() { this.compileIfRequired(); await this._renderOnTarget(true); } shaders_by_name() { return { fragment: this._fragmentShader }; } compileIfRequired() { var _a; if ((_a = this.assemblerController()) == null ? void 0 : _a.compileRequired()) { try { this.compile(); } catch (err) { const message = err.message || "failed to compile"; this.states.error.set(message); } } } compile() { const assemblerController = this.assemblerController(); if (!assemblerController) { return; } const outputNodes = GlNodeFinder.findOutputNodes(this); if (outputNodes.length == 0) { this.states.error.set("one output node is required"); return; } if (outputNodes.length > 1) { this.states.error.set("only one output node allowed"); return; } const outputNode = outputNodes[0]; if (outputNode) { const rootNodes = outputNodes; assemblerController.assembler.set_root_nodes(rootNodes); assemblerController.assembler.updateFragmentShader(); const fragmentShader = assemblerController.assembler.fragment_shader(); const uniforms = assemblerController.assembler.uniforms(); if (fragmentShader && uniforms) { this._fragmentShader = fragmentShader; this._uniforms = uniforms; } handleCopBuilderDependencies({ node: this, timeDependent: assemblerController.assembler.uniformsTimeDependent(), uniforms: void 0 }); } if (this._fragmentShader && this._uniforms) { this.textureMaterial.fragmentShader = this._fragmentShader; this.textureMaterial.uniforms = this._uniforms; this.textureMaterial.needsUpdate = true; this.textureMaterial.uniforms.resolution = { value: this.pv.resolution }; } assemblerController.post_compile(); } callbackName() { return `cop/builder3D_${this.graphNodeId()}`; } // private _uniformCallbackName() { // return `cop/builder_uniforms_${this.graphNodeId()}`; // } dispose() { var _a, _b; super.dispose(); (_a = this._renderTarget) == null ? void 0 : _a.dispose(); (_b = this._renderer) == null ? void 0 : _b.dispose(); this.removeCallbacks(); } removeCallbacks() { const scene = this.scene(); scene.unRegisterOnBeforeTick(this.callbackName()); } async renderOnTargetWithoutUpdatingTextureFromParams() { this._renderOnTarget(false); } async _renderOnTarget(updateTextureFromParams) { await this.createRenderTargetIfRequired(); await this._createRendererIfRequired(); if (this.states.error.active()) { return; } if (!this._renderer) { console.warn("no renderer"); return; } if (!this._uniforms) { return; } this._saveRendererState(this._renderer); this._prepareRenderer(this._renderer); const layersCount = this.pv.layers; for (let i = 0; i < layersCount; i++) { this._uniforms.uLayer.value = i; this._setRenderLayer(this._renderer, i); this._renderer.render(this._textureScene, this._textureCamera); } await this._postRender(updateTextureFromParams); this._restoreRendererState(this._renderer); } async _postRender(updateTextureFromParams) { var _a; if ((_a = this._renderTarget) == null ? void 0 : _a.texture) { const texture = this._renderTarget.texture; if (updateTextureFromParams) { } this.setTexture(texture); } else { this.cookController.endCook(); } } _saveRendererState(renderer) { this._prevTarget = renderer.getRenderTarget(); this._prevOutputColorSpace = renderer.outputColorSpace; this._prevToneMapping = renderer.toneMapping; } _prepareRenderer(renderer) { if (!this._renderTarget) { console.warn("no render target"); return; } renderer.outputColorSpace = NoColorSpace; renderer.toneMapping = NoToneMapping; } _setRenderLayer(renderer, layer) { if (!this._renderTarget) { console.warn("no render target"); return; } renderer.setRenderTarget(this._renderTarget, layer); renderer.clear(); } _restoreRendererState(renderer) { renderer.setRenderTarget(this._prevTarget); renderer.outputColorSpace = this._prevOutputColorSpace; renderer.toneMapping = this._prevToneMapping; } async _createRendererIfRequired() { if (this._renderer) { return; } if (isBooleanTrue(this.pv.useCameraRenderer)) { this._rendererController = this._rendererController || new CopRendererController(this); const foundRenderer = await this._rendererController.waitForRenderer(); if (foundRenderer instanceof WebGLRenderer) { this._renderer = foundRenderer; } else { console.warn("found renderer is not a WebGLRenderer"); } } else { this._renderer = Poly.renderersController.linearRenderer(); } } _resetRenderer() { this._renderer = void 0; } renderer() { return this._renderer; } /* * * RENDER TARGET * */ async renderTarget() { return this._renderTarget = this._renderTarget || await this._createRenderTarget(this.pv.resolution.x, this.pv.resolution.y, this.pv.layers); } async createRenderTargetIfRequired() { var _a; if (!this._renderTarget || !this._renderTargetResolutionValid()) { this._renderTarget = await this._createRenderTarget( this.pv.resolution.x, this.pv.resolution.y, this.pv.layers ); (_a = this._dataTextureController) == null ? void 0 : _a.reset(); } } _renderTargetResolutionValid() { if (this._renderTarget) { const image = this._renderTarget.texture.image; if (image.width != this.pv.resolution.x || image.height != this.pv.resolution.y) { return false; } else { return true; } } else { return false; } } async _createRenderTarget(width, height, depth) { if (this._renderTarget) { const image = this._renderTarget.texture.image; console.log(image); if (image.width == width && image.height == height && image.depth == depth) { return this._renderTarget; } } const wrapS = ClampToEdgeWrapping; const wrapT = ClampToEdgeWrapping; const minFilter = LinearFilter; const magFilter = NearestFilter; const renderTarget = new WebGLArrayRenderTarget(width, height, depth); renderTarget.texture.wrapS = wrapS; renderTarget.texture.wrapT = wrapT; renderTarget.texture.minFilter = minFilter; renderTarget.texture.magFilter = magFilter; renderTarget.texture.format = RGBAFormat; renderTarget.stencilBuffer = false; renderTarget.depthBuffer = false; Poly.warn(`${this.path()}: created WebGLArrayRenderTarget`, this.path(), width, height, depth); return renderTarget; } /* * * CALLBACK * */ static PARAM_CALLBACK_render(node) { node._renderOnTarget(true); } static PARAM_CALLBACK_resetRenderer(node) { node._resetRenderer(); } }