@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
254 lines (253 loc) • 8.8 kB
JavaScript
"use strict";
import {
WebGLRenderer,
WebGLRenderTarget,
OrthographicCamera,
FloatType,
HalfFloatType,
RGBAFormat,
NearestFilter,
LinearFilter,
ClampToEdgeWrapping,
PerspectiveCamera,
Vector2
} from "three";
import { TypedCopNode } from "./_Base";
import { NodeParamsConfig, ParamConfig } from "../utils/params/ParamsConfig";
import { CopRendererController } from "./utils/RendererController";
import { isBooleanTrue } from "../../../core/BooleanValue";
import { DataTextureController, DataTextureControllerBufferType } from "./utils/DataTextureController";
import { TextureParamsController, TextureParamConfig } from "./utils/TextureParamsController";
import { CoreUserAgent } from "../../../core/UserAgent";
import { Poly } from "../../Poly";
import { CoreCameraPerspectiveFrameMode } from "../../../core/camera/frameMode/CoreCameraPerspectiveFrameMode";
import { CoreCameraOrthographicFrameMode } from "../../../core/camera/frameMode/CoreCameraOrthographicFrameMode";
import { CopType } from "../../poly/registers/nodes/types/Cop";
const _v2 = new Vector2();
export function RenderCopNodeParamConfig(Base) {
return class Mixin extends Base {
constructor() {
super(...arguments);
/** @param path to the main camera object that will be used when the scene loads outside of the editor */
this.cameraPath = ParamConfig.STRING("*perspectiveCamera*", {
objectMask: true
});
/** @param transparent background */
this.transparentBackground = ParamConfig.BOOLEAN(1);
/** @param bg Color */
this.backgroundColor = ParamConfig.COLOR([0, 0, 0], {
visibleIf: {
transparentBackground: 0
}
});
/** @param use same resolution as renderer */
this.useRendererRes = ParamConfig.BOOLEAN(1);
/** @param render resolution */
this.resolution = ParamConfig.VECTOR2([1024, 1024], {
visibleIf: {
useRendererRes: 0
}
});
/** @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 autoRender */
this.autoRender = ParamConfig.BOOLEAN(1);
/** @param render button */
this.render = ParamConfig.BUTTON(null, {
callback: (node) => {
RenderCopNode.PARAM_CALLBACK_render(node);
}
});
}
};
}
class RenderCopParamConfig extends TextureParamConfig(RenderCopNodeParamConfig(NodeParamsConfig)) {
}
const ParamsConfig = new RenderCopParamConfig();
export class RenderCopNode extends TypedCopNode {
constructor() {
super(...arguments);
this.paramsConfig = ParamsConfig;
this.textureParamsController = new TextureParamsController(this);
this._renderTargetByRenderer = /* @__PURE__ */ new WeakMap();
this._renderOnTargetBound = this.renderOnTarget.bind(this);
}
static type() {
return CopType.RENDER;
}
async cook() {
if (isBooleanTrue(this.pv.autoRender)) {
this._addOnBeforeTickCallback();
} else {
this._removeOnBeforeTickCallback();
}
const camera = await this._getCamera();
if (camera) {
await this.renderOnTarget();
} else {
this.cookController.endCook();
}
}
//
//
// AUTO RENDER
//
//
_addOnBeforeTickCallback() {
const callbackName = this._onBeforeRenderCallbackName();
if (this.scene().hasBeforeTickCallback(callbackName)) {
return;
}
this.scene().registerOnBeforeTick(callbackName, this._renderOnTargetBound);
}
_removeOnBeforeTickCallback() {
this.scene().unRegisterOnBeforeTick(this._onBeforeRenderCallbackName());
}
_onBeforeRenderCallbackName() {
return `cop/render_onBeforeTickCallback-${this.graphNodeId()}`;
}
dispose() {
super.dispose();
this._removeOnBeforeTickCallback();
}
//
//
// RENDER + RENDER TARGET
//
//
_getCameraSync() {
return this.scene().objectsController.findObjectByMask(this.pv.cameraPath);
}
async _getCamera() {
const camera = this._getCameraSync();
if (camera) {
return camera;
}
return new Promise((resolve) => {
const onCameraUpdated = async () => {
const camera2 = this._getCameraSync();
this.scene().camerasController.removeOnCameraObjectsUpdated(onCameraUpdated);
if (camera2) {
resolve(camera2);
}
};
this.scene().camerasController.onCameraObjectsUpdated(onCameraUpdated);
});
}
async renderOnTarget() {
const camera = await this._getCamera();
if (!camera) {
this.states.error.set(`no camera found`);
return;
}
this._rendererController = this._rendererController || new CopRendererController(this);
const renderer = await this._rendererController.waitForRenderer();
if (!(renderer instanceof WebGLRenderer)) {
this.states.error.set(`no renderer found`);
return;
}
const renderTarget = await this.createRenderTargetIfRequired(renderer);
this._ensureRenderTargetResolutionValid(renderer, renderTarget);
const viewer = this.scene().viewersRegister.lastRenderedViewer();
if (!viewer) {
this.states.error.set(`no viewer found`);
return;
}
const viewerCamera = viewer.camera();
if (!viewerCamera) {
this.states.error.set(`no viewer camera found`);
return;
}
this._requestedResolution(renderer, _v2);
const aspect = _v2.x / _v2.y;
if (camera instanceof PerspectiveCamera && viewerCamera instanceof PerspectiveCamera) {
CoreCameraPerspectiveFrameMode.updateCameraAspect(camera, aspect, { cameraWithAttributes: viewerCamera });
} else {
if (camera instanceof OrthographicCamera && viewerCamera instanceof OrthographicCamera) {
CoreCameraOrthographicFrameMode.updateCameraAspect(camera, aspect, {
cameraWithAttributes: viewerCamera
});
}
}
const scene = this.scene().threejsScene();
const prevTarget = renderer.getRenderTarget();
const prevColorSpace = renderer.outputColorSpace;
const prevBackground = scene.background;
scene.background = this.pv.transparentBackground ? null : this.pv.backgroundColor;
renderer.setRenderTarget(renderTarget);
renderer.outputColorSpace = this.pv.colorSpace;
renderer.clear();
renderer.render(scene, camera);
renderer.setRenderTarget(prevTarget);
renderer.outputColorSpace = prevColorSpace;
scene.background = prevBackground;
if (renderTarget.texture) {
if (isBooleanTrue(this.pv.useDataTexture)) {
this._dataTextureController = this._dataTextureController || new DataTextureController(DataTextureControllerBufferType.Float32Array);
const dataTexture = this._dataTextureController.fromRenderTarget(renderer, renderTarget);
await this.textureParamsController.update(dataTexture);
this.setTexture(dataTexture);
return;
} else {
this.setTexture(renderTarget.texture);
await this.textureParamsController.update(renderTarget.texture);
return;
}
}
}
async renderTarget(renderer) {
return this._renderTargetByRenderer.get(renderer);
}
async createRenderTargetIfRequired(renderer) {
let renderTarget = this._renderTargetByRenderer.get(renderer);
if (!renderTarget) {
renderTarget = await this._createRenderTarget(renderer);
this._renderTargetByRenderer.set(renderer, renderTarget);
}
return renderTarget;
}
_requestedResolution(renderer, target) {
if (isBooleanTrue(this.pv.useRendererRes)) {
renderer.getSize(target);
} else {
target.copy(this.pv.resolution);
}
}
_ensureRenderTargetResolutionValid(renderer, renderTarget) {
this._requestedResolution(renderer, _v2);
const image = renderTarget.texture.image;
if (image.width != _v2.x || image.height != _v2.y) {
renderTarget.setSize(_v2.x, _v2.y);
}
}
async _createRenderTarget(renderer) {
this._requestedResolution(renderer, _v2);
const wrapS = ClampToEdgeWrapping;
const wrapT = ClampToEdgeWrapping;
const minFilter = LinearFilter;
const magFilter = NearestFilter;
const renderTarget = new WebGLRenderTarget(_v2.x, _v2.y, {
wrapS,
wrapT,
minFilter,
magFilter,
format: RGBAFormat,
generateMipmaps: true,
type: CoreUserAgent.isiOS() ? HalfFloatType : FloatType,
samples: renderer.getPixelRatio(),
stencilBuffer: true,
depthBuffer: true
});
await this.textureParamsController.update(renderTarget.texture);
Poly.warn("created render target", this.path(), _v2.x, _v2.y);
return renderTarget;
}
//
//
// CALLBACK
//
//
static PARAM_CALLBACK_render(node) {
node.renderOnTarget();
}
}