@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
366 lines (365 loc) • 12.2 kB
JavaScript
"use strict";
import {
Mesh,
PlaneGeometry,
WebGLRenderer,
WebGLRenderTarget,
ShaderMaterial,
Scene,
FloatType,
HalfFloatType,
RGBAFormat,
ClampToEdgeWrapping,
LinearFilter,
NearestFilter,
NoToneMapping,
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 { DataTextureController } from "./utils/DataTextureController";
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 { CoreUserAgent } from "../../../core/UserAgent";
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 = [256, 256];
function BuilderCopParamConfig(Base) {
return class Mixin extends Base {
constructor() {
super(...arguments);
/** @param texture resolution */
this.resolution = ParamConfig.VECTOR2(RESOLUTION_DEFAULT);
/** @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) => {
BuilderCopNode.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 */
this.useDataTexture = ParamConfig.BOOLEAN(0);
/** @param force Render */
this.render = ParamConfig.BUTTON(null, {
callback: (node) => {
BuilderCopNode.PARAM_CALLBACK_render(node);
}
});
}
};
}
class BuilderCopParamsConfig extends TextureParamConfig(BuilderCopParamConfig(NodeParamsConfig)) {
}
const ParamsConfig = new BuilderCopParamsConfig();
export class BuilderCopNode 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;
}
usedAssembler() {
return AssemblerName.GL_TEXTURE;
}
_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/builder_${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;
}
this._saveRendererState(this._renderer);
this._prepareRenderer(this._renderer);
this._renderer.render(this._textureScene, this._textureCamera);
await this._postRender(updateTextureFromParams);
this._restoreRendererState(this._renderer);
Poly.onSceneUpdatedHooks.runHooks();
}
async _postRender(updateTextureFromParams) {
var _a;
if ((_a = this._renderTarget) == null ? void 0 : _a.texture) {
if (isBooleanTrue(this.pv.useDataTexture) && this._renderTarget && this._renderer) {
this._dataTextureController = this._dataTextureController || new DataTextureController();
const texture = this._dataTextureController.fromRenderTarget(this._renderer, this._renderTarget);
if (updateTextureFromParams) {
await this.textureParamsController.update(texture);
}
this.setTexture(texture);
} else {
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.setRenderTarget(this._renderTarget);
renderer.outputColorSpace = NoColorSpace;
renderer.toneMapping = NoToneMapping;
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", foundRenderer);
}
} 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);
}
async createRenderTargetIfRequired() {
var _a;
if (!this._renderTarget || !this._renderTargetResolutionValid()) {
this._renderTarget = await this._createRenderTarget(this.pv.resolution.x, this.pv.resolution.y);
(_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) {
if (this._renderTarget) {
const image = this._renderTarget.texture.image;
if (image.width == width && image.height == height) {
return this._renderTarget;
}
}
const wrapS = ClampToEdgeWrapping;
const wrapT = ClampToEdgeWrapping;
const minFilter = LinearFilter;
const magFilter = NearestFilter;
const renderTarget = new WebGLRenderTarget(width, height, {
wrapS,
wrapT,
minFilter,
magFilter,
format: RGBAFormat,
type: CoreUserAgent.isiOS() ? HalfFloatType : FloatType,
stencilBuffer: false,
depthBuffer: false
// encoding: LinearEncoding,
});
Poly.warn(`${this.path()}: created WebGLRenderTarget`, this.path(), width, height);
return renderTarget;
}
/*
*
* CALLBACK
*
*/
static PARAM_CALLBACK_render(node) {
node._renderOnTarget(true);
}
static PARAM_CALLBACK_resetRenderer(node) {
node._resetRenderer();
}
}