@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
373 lines (372 loc) • 12.3 kB
JavaScript
"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();
}
}