@spearwolf/twopoint5d
Version:
Create 2.5D realtime graphics and pixelart with WebGL and three.js
383 lines • 12.9 kB
JavaScript
var _a;
import { emit, eventize, once } from '@spearwolf/eventize';
import { texture } from 'three/tsl';
import { Color, RenderTarget } from 'three/webgpu';
import { isWebGLRenderer } from '../display/isWebGLRenderer.js';
import { OnAddToParent, OnRemoveFromParent, OnStageAdded, OnStageRemoved, } from '../events.js';
import { RootRenderPipeline } from './RootRenderPipeline.js';
const hasAsPassNode = (s) => typeof s?.asPassNode === 'function';
export class StageRenderer {
#parent;
#clearColor;
get clearColor() {
return this.#clearColor;
}
set clearColor(color) {
if (color == null) {
this.#clearColor = null;
}
else {
this.#clearColor = color;
this.clear = true;
}
}
setClearColor(color, alpha = 1) {
this.#clearColor = color;
this.clearAlpha = alpha;
this.clear = true;
return this;
}
#oldClearColor;
#renderOrder;
#orderedStages;
set renderOrder(order) {
order = order || '*';
if (this.#renderOrder !== order) {
this.#renderOrder = order;
this.#renderOrderArray = undefined;
this.#orderedStages = undefined;
this.#outputDirty = true;
this.onRenderOrderChanged();
}
}
onRenderOrderChanged() {
}
get renderOrder() {
return this.#renderOrder;
}
#renderOrderArray;
get renderOrderArray() {
if (!this.#renderOrderArray) {
this.#renderOrderArray = this.renderOrder
.split(',')
.map((item) => item.trim())
.filter(Boolean);
}
return this.#renderOrderArray;
}
get parent() {
return this.#parent;
}
set parent(parent) {
if (this.#parent !== parent) {
this.#removeFromParent();
this.#parent = parent;
if (this.#parent) {
this.#addToParent();
}
}
}
#removeFromParent() {
if (this.#parent == null)
return;
emit(this, OnRemoveFromParent);
if (this.#parent instanceof _a) {
this.#parent.remove(this);
}
}
#addToParent() {
if (this.#parent instanceof _a) {
this.#parent.add(this);
}
else {
this.#addToHost(this.#parent);
}
emit(this, OnAddToParent);
}
#addToHost(host) {
once(this, OnRemoveFromParent, host.onResize(({ width, height }) => {
this.resize(width, height);
}));
once(this, OnRemoveFromParent, host.onRenderFrame(({ renderer, now, deltaTime, frameNo }) => {
this.updateFrame(now, deltaTime, frameNo);
this.renderTo(renderer);
}));
}
constructor(parent) {
this.name = 'StageRenderer';
this.width = 0;
this.height = 0;
this.clear = false;
this.#clearColor = null;
this.clearAlpha = 1;
this.clearColorBuffer = true;
this.clearDepthBuffer = true;
this.clearStencilBuffer = true;
this.#oldClearColor = new Color(0x000000);
this.stages = [];
this.#renderOrder = '*';
this.#renderOrderArray = [];
this.#outputDirty = true;
eventize(this);
if (parent) {
this.parent = parent;
}
}
attach(parent) {
this.parent = parent;
return this;
}
detach() {
this.parent = undefined;
return this;
}
resize(width, height) {
if (this.width === width && this.height === height)
return;
this.width = width;
this.height = height;
if (this.#internalRT)
this.#internalRT.setSize(Math.max(1, width), Math.max(1, height));
if (this.#asPassNodeRT)
this.#asPassNodeRT.setSize(Math.max(1, width), Math.max(1, height));
for (const stage of this.stages) {
this.resizeStage(stage, width, height);
}
}
resizeStage(stageItem, width, height) {
if (stageItem.width !== width || stageItem.height !== height) {
stageItem.width = width;
stageItem.height = height;
stageItem.stage.resize(width, height);
}
}
updateFrame(now, deltaTime, frameNo) {
for (const { stage } of this.orderedStages) {
stage.updateFrame(now, deltaTime, frameNo);
}
}
#internalRT;
#asPassNodeRT;
#outputDirty;
invalidateOutputNode() {
this.#outputDirty = true;
}
renderTo(renderer) {
if (isWebGLRenderer(renderer)) {
throw new TypeError('The WebGLRenderer renderer is not supported anymore');
}
if (this.outputRenderTarget) {
const prev = renderer.getRenderTarget();
renderer.setRenderTarget(this.outputRenderTarget);
try {
this.#renderToCurrentTarget(renderer);
}
finally {
renderer.setRenderTarget(prev);
}
}
else {
this.#renderToCurrentTarget(renderer);
}
}
#renderToCurrentTarget(renderer) {
if (this.pipeline) {
if (this.buildOutputNode || this.pipeline instanceof RootRenderPipeline) {
this.#renderPipelineComposed(renderer);
}
else {
this.#renderPipelineSimple(renderer);
}
}
else {
this.#renderStagesInline(renderer);
}
}
#renderStagesInline(renderer) {
const wasPreviouslyAutoClear = renderer.autoClear;
if (this.clear)
this.#applyClear(renderer);
renderer.autoClear = false;
for (const stageItem of this.orderedStages) {
this.renderStage(stageItem, renderer);
}
renderer.autoClear = wasPreviouslyAutoClear;
}
#renderPipelineSimple(renderer) {
const rt = this.#ensureInternalRT(renderer);
const prev = renderer.getRenderTarget();
renderer.setRenderTarget(rt);
try {
this.#clearForInternalRT(renderer);
const wasPreviouslyAutoClear = renderer.autoClear;
renderer.autoClear = false;
for (const stageItem of this.orderedStages)
this.renderStage(stageItem, renderer);
renderer.autoClear = wasPreviouslyAutoClear;
}
finally {
renderer.setRenderTarget(prev);
}
if (this.#outputDirty) {
this.pipeline.outputNode = texture(rt.texture);
this.pipeline.needsUpdate = true;
this.#outputDirty = false;
}
if (this.clear)
this.#applyClear(renderer);
this.pipeline.render();
}
#clearForInternalRT(renderer) {
if (this.clear) {
this.#applyClear(renderer);
}
else {
const oldClearAlpha = renderer.getClearAlpha();
renderer.setClearAlpha(0);
renderer.clear(true, true, false);
renderer.setClearAlpha(oldClearAlpha);
}
}
#renderPipelineComposed(renderer) {
for (const stageItem of this.orderedStages) {
const stage = stageItem.stage;
if (stage instanceof _a) {
const childRT = stage.#ensureAsPassNodeRT(renderer);
const prev = renderer.getRenderTarget();
renderer.setRenderTarget(childRT);
try {
stage.#renderToCurrentTarget(renderer);
}
finally {
renderer.setRenderTarget(prev);
}
}
}
if (this.#outputDirty) {
const passes = this.orderedStages.map((s) => this.#getStagePass(s, renderer));
const compose = this.buildOutputNode ?? RootRenderPipeline.buildOutputNode;
this.pipeline.outputNode = compose(passes);
this.pipeline.needsUpdate = true;
this.#outputDirty = false;
}
if (this.clear)
this.#applyClear(renderer);
this.pipeline.render();
}
#getStagePass(stageItem, renderer) {
const stage = stageItem.stage;
if (!hasAsPassNode(stage)) {
throw new TypeError(`StageRenderer.buildOutputNode: stage ${JSON.stringify(stage.name)} does not implement asPassNode() — incompatible with the buildOutputNode composition path`);
}
return stage.asPassNode(renderer);
}
asPassNode(renderer) {
const rt = this.#ensureAsPassNodeRT(renderer);
return texture(rt.texture);
}
#ensureInternalRT(renderer) {
return (this.#internalRT = this.#ensureRT(this.#internalRT, renderer));
}
#ensureAsPassNodeRT(renderer) {
return (this.#asPassNodeRT = this.#ensureRT(this.#asPassNodeRT, renderer));
}
#ensureRT(rt, renderer) {
const pixelRatio = renderer.getPixelRatio?.() ?? 1;
const w = Math.max(1, Math.floor(this.width * pixelRatio));
const h = Math.max(1, Math.floor(this.height * pixelRatio));
if (!rt) {
return new RenderTarget(w, h);
}
if (rt.width !== w || rt.height !== h) {
rt.setSize(w, h);
}
return rt;
}
#applyClear(renderer) {
const oldClearAlpha = renderer.getClearAlpha();
let colorWasOverridden = false;
if (this.#clearColor != null) {
renderer.getClearColor(this.#oldClearColor);
renderer.setClearColor(this.#clearColor, this.clearAlpha);
colorWasOverridden = true;
}
else {
renderer.setClearAlpha(this.clearAlpha);
}
renderer.clear(this.clearColorBuffer, this.clearDepthBuffer, this.clearStencilBuffer);
if (colorWasOverridden) {
renderer.setClearColor(this.#oldClearColor, oldClearAlpha);
}
else {
renderer.setClearAlpha(oldClearAlpha);
}
}
dispose() {
this.#internalRT?.dispose();
this.#internalRT = undefined;
this.#asPassNodeRT?.dispose();
this.#asPassNodeRT = undefined;
this.pipeline?.dispose();
this.pipeline = undefined;
}
renderStage(stageItem, renderer) {
stageItem.stage.renderTo(renderer);
}
get orderedStages() {
if (this.#orderedStages)
return this.#orderedStages;
const renderOrder = this.renderOrderArray;
if (renderOrder.length === 0 || (renderOrder.length === 1 && (renderOrder[0] === '' || renderOrder[0] === '*'))) {
return this.stages;
}
const explicitlyNamedStages = new Map();
const otherStages = this.stages.slice();
renderOrder.forEach((name) => {
if (name !== '*') {
const index = otherStages.findIndex((stage) => stage.stage.name === name);
if (index !== -1) {
const stage = otherStages.splice(index, 1)[0];
explicitlyNamedStages.set(name, stage);
}
}
});
const orderedStages = renderOrder
.map((name) => {
if (name === '*') {
return otherStages;
}
return explicitlyNamedStages.get(name);
})
.flat()
.filter(Boolean);
explicitlyNamedStages.clear();
this.#orderedStages = orderedStages;
return orderedStages;
}
#getIndex(stage) {
return this.stages.findIndex((item) => item.stage === stage);
}
hasStage(stage) {
return this.#getIndex(stage) !== -1;
}
add(stage) {
if (!this.hasStage(stage)) {
if (this.#renderOrder !== '*' && this.stages.some((item) => item.stage.name === stage.name)) {
console.warn(`StageRenderer: a stage named ${JSON.stringify(stage.name)} is already present and renderOrder=${JSON.stringify(this.#renderOrder)} cannot disambiguate. Set unique names on your stages.`);
}
const si = {
stage,
width: 0,
height: 0,
};
this.stages.push(si);
this.#orderedStages = undefined;
this.#outputDirty = true;
this.resizeStage(si, this.width, this.height);
emit(this, OnStageAdded, { stage, renderer: this });
}
return this;
}
remove(stage) {
const index = this.#getIndex(stage);
if (index !== -1) {
this.stages.splice(index, 1);
this.#orderedStages = undefined;
this.#outputDirty = true;
emit(this, OnStageRemoved, { stage, renderer: this });
}
return this;
}
}
_a = StageRenderer;
//# sourceMappingURL=StageRenderer.js.map