252 lines
9.3 KiB
TypeScript
252 lines
9.3 KiB
TypeScript
import type { WebGLRenderTarget } from "three";
|
|
import * as THREE from "three";
|
|
import type { SceneCollection } from "./App";
|
|
import Config from "./Config";
|
|
|
|
interface Resources {
|
|
worldRenderTarget: THREE.WebGLRenderTarget;
|
|
worldRenderTargetCopy: THREE.WebGLRenderTarget;
|
|
worldBlurredRenderTarget: THREE.WebGLRenderTarget;
|
|
antsDataRenderTarget0: THREE.WebGLRenderTarget;
|
|
antsDataRenderTarget1: THREE.WebGLRenderTarget;
|
|
antsDiscreteRenderTarget: THREE.WebGLRenderTarget;
|
|
}
|
|
|
|
export default class Renderer {
|
|
private renderer: THREE.WebGLRenderer;
|
|
public resources!: Resources;
|
|
|
|
constructor(public canvas: HTMLCanvasElement) {
|
|
this.renderer = new THREE.WebGLRenderer({ canvas });
|
|
|
|
this.initResources();
|
|
}
|
|
|
|
private initResources() {
|
|
const antTextureSize = Math.round(Math.sqrt(2 ** Config.antsCount));
|
|
|
|
this.resources = {
|
|
worldRenderTarget: new THREE.WebGLRenderTarget(
|
|
Config.worldSize,
|
|
Config.worldSize,
|
|
{
|
|
format: THREE.RGBAFormat,
|
|
type: THREE.FloatType,
|
|
depthBuffer: false,
|
|
magFilter: THREE.LinearFilter,
|
|
minFilter: THREE.LinearFilter,
|
|
},
|
|
),
|
|
worldRenderTargetCopy: new THREE.WebGLRenderTarget(
|
|
Config.worldSize,
|
|
Config.worldSize,
|
|
{
|
|
format: THREE.RGBAFormat,
|
|
type: THREE.FloatType,
|
|
depthBuffer: false,
|
|
magFilter: THREE.NearestFilter,
|
|
minFilter: THREE.NearestFilter,
|
|
},
|
|
),
|
|
worldBlurredRenderTarget: new THREE.WebGLRenderTarget(
|
|
Config.worldSize,
|
|
Config.worldSize,
|
|
{
|
|
format: THREE.RGBAFormat,
|
|
type: THREE.FloatType,
|
|
depthBuffer: false,
|
|
magFilter: THREE.NearestFilter,
|
|
minFilter: THREE.NearestFilter,
|
|
},
|
|
),
|
|
antsDataRenderTarget0: new THREE.WebGLRenderTarget(
|
|
antTextureSize,
|
|
antTextureSize,
|
|
{
|
|
format: THREE.RGBAFormat,
|
|
type: THREE.FloatType,
|
|
depthBuffer: false,
|
|
magFilter: THREE.NearestFilter,
|
|
minFilter: THREE.NearestFilter,
|
|
},
|
|
),
|
|
antsDataRenderTarget1: new THREE.WebGLRenderTarget(
|
|
antTextureSize,
|
|
antTextureSize,
|
|
{
|
|
format: THREE.RGBAFormat,
|
|
type: THREE.FloatType,
|
|
depthBuffer: false,
|
|
magFilter: THREE.NearestFilter,
|
|
minFilter: THREE.NearestFilter,
|
|
},
|
|
),
|
|
antsDiscreteRenderTarget: new THREE.WebGLRenderTarget(
|
|
Config.worldSize,
|
|
Config.worldSize,
|
|
{
|
|
format: THREE.RGBAFormat,
|
|
type: THREE.UnsignedByteType,
|
|
depthBuffer: false,
|
|
magFilter: THREE.NearestFilter,
|
|
minFilter: THREE.NearestFilter,
|
|
},
|
|
),
|
|
};
|
|
}
|
|
|
|
public renderSimulation(scenes: SceneCollection) {
|
|
const [antsComputeSource, antsComputeTarget] =
|
|
scenes.ants.getRenderTargets();
|
|
|
|
this.setViewportFromRT(this.resources.worldBlurredRenderTarget);
|
|
this.renderer.setRenderTarget(this.resources.worldBlurredRenderTarget);
|
|
scenes.worldBlur.material.uniforms.tWorld.value =
|
|
this.resources.worldRenderTarget.texture;
|
|
this.renderer.render(scenes.worldBlur, scenes.worldBlur.camera);
|
|
|
|
this.setViewportFromRT(antsComputeTarget);
|
|
this.renderer.setRenderTarget(antsComputeTarget);
|
|
scenes.ants.material.uniforms.tLastState.value =
|
|
antsComputeSource.texture;
|
|
scenes.ants.material.uniforms.tWorld.value =
|
|
this.resources.worldBlurredRenderTarget.texture;
|
|
this.renderer.render(scenes.ants, scenes.ants.camera);
|
|
|
|
this.setViewportFromRT(this.resources.antsDiscreteRenderTarget);
|
|
this.renderer.setRenderTarget(this.resources.antsDiscreteRenderTarget);
|
|
scenes.discretize.material.uniforms.tDataCurrent.value =
|
|
antsComputeTarget.texture;
|
|
scenes.discretize.material.uniforms.tDataLast.value =
|
|
antsComputeSource.texture;
|
|
this.renderer.render(scenes.discretize, scenes.discretize.camera);
|
|
|
|
this.setViewportFromRT(this.resources.worldRenderTarget);
|
|
this.renderer.setRenderTarget(this.resources.worldRenderTarget);
|
|
scenes.world.material.uniforms.tLastState.value =
|
|
this.resources.worldBlurredRenderTarget.texture;
|
|
scenes.world.material.uniforms.tDiscreteAnts.value =
|
|
this.resources.antsDiscreteRenderTarget.texture;
|
|
this.renderer.render(scenes.world, scenes.world.camera);
|
|
|
|
scenes.screen.material.uniforms.tData.value = antsComputeTarget.texture;
|
|
scenes.screen.groundMaterial.uniforms.map.value =
|
|
this.resources.worldRenderTargetCopy.texture;
|
|
}
|
|
|
|
private setViewportFromRT(rt: WebGLRenderTarget) {
|
|
this.renderer.setViewport(0, 0, rt.width, rt.height);
|
|
}
|
|
|
|
public renderToScreen(scenes: SceneCollection) {
|
|
this.setViewportFromRT(this.resources.worldRenderTargetCopy);
|
|
this.renderer.setRenderTarget(this.resources.worldRenderTargetCopy);
|
|
scenes.draw.material.uniforms.tWorld.value =
|
|
this.resources.worldRenderTarget.texture;
|
|
scenes.draw.material.uniforms.pointerPosition.value =
|
|
scenes.screen.pointerPosition;
|
|
scenes.draw.material.uniforms.drawMode.value = scenes.screen.drawMode;
|
|
scenes.draw.material.uniforms.brushRadius.value = Config.brushRadius;
|
|
this.renderer.render(scenes.draw, scenes.draw.camera);
|
|
this.renderer.copyFramebufferToTexture(
|
|
this.resources.worldRenderTarget.texture,
|
|
new THREE.Vector2(),
|
|
);
|
|
|
|
this.renderer.setViewport(
|
|
0,
|
|
0,
|
|
scenes.screen.renderWidth,
|
|
scenes.screen.renderHeight,
|
|
);
|
|
this.renderer.setRenderTarget(null);
|
|
this.renderer.render(scenes.screen, scenes.screen.camera);
|
|
}
|
|
|
|
public resizeCanvas(width: number, height: number) {
|
|
this.canvas.width = width;
|
|
this.canvas.height = height;
|
|
}
|
|
|
|
public getCommonMaterialDefines(): Record<string, string> {
|
|
return {
|
|
WORLD_SIZE: Renderer.convertNumberToFloatString(Config.worldSize),
|
|
SCENT_THRESHOLD: Renderer.convertNumberToFloatString(
|
|
Config.scentThreshold,
|
|
),
|
|
SCENT_FADE_OUT_FACTOR: Renderer.convertNumberToFloatString(
|
|
Config.scentFadeOutFactor,
|
|
),
|
|
SCENT_BLUR_RADIUS: Renderer.convertNumberToFloatString(
|
|
Config.scentBlurRadius,
|
|
),
|
|
SCENT_MAX_STORAGE: Renderer.convertNumberToFloatString(
|
|
Config.scentMaxStorage,
|
|
),
|
|
SCENT_PER_MARKER: Renderer.convertNumberToFloatString(
|
|
Config.scentPerMarker,
|
|
),
|
|
SCENT_MAX_PER_CELL: Renderer.convertNumberToFloatString(
|
|
Config.scentMaxPerCell,
|
|
),
|
|
ANT_SPEED: Renderer.convertNumberToFloatString(Config.antSpeed),
|
|
ANT_ROTATION_ANGLE: Renderer.convertNumberToFloatString(
|
|
Config.antRotationAngle,
|
|
),
|
|
};
|
|
}
|
|
|
|
public reset(scenes: SceneCollection) {
|
|
const antTextureSize = Math.round(Math.sqrt(2 ** Config.antsCount));
|
|
|
|
this.resources.worldRenderTarget.setSize(
|
|
Config.worldSize,
|
|
Config.worldSize,
|
|
);
|
|
this.renderer.setRenderTarget(this.resources.worldRenderTarget);
|
|
this.renderer.clear();
|
|
|
|
this.resources.worldRenderTargetCopy.setSize(
|
|
Config.worldSize,
|
|
Config.worldSize,
|
|
);
|
|
this.renderer.setRenderTarget(this.resources.worldRenderTargetCopy);
|
|
this.renderer.clear();
|
|
|
|
this.resources.worldBlurredRenderTarget.setSize(
|
|
Config.worldSize,
|
|
Config.worldSize,
|
|
);
|
|
this.renderer.setRenderTarget(this.resources.worldBlurredRenderTarget);
|
|
this.renderer.clear();
|
|
|
|
this.resources.antsDataRenderTarget0.setSize(
|
|
antTextureSize,
|
|
antTextureSize,
|
|
);
|
|
this.renderer.setRenderTarget(this.resources.antsDataRenderTarget0);
|
|
this.renderer.clear();
|
|
|
|
this.resources.antsDataRenderTarget1.setSize(
|
|
antTextureSize,
|
|
antTextureSize,
|
|
);
|
|
this.renderer.setRenderTarget(this.resources.antsDataRenderTarget1);
|
|
this.renderer.clear();
|
|
|
|
this.resources.antsDiscreteRenderTarget.setSize(
|
|
Config.worldSize,
|
|
Config.worldSize,
|
|
);
|
|
this.renderer.setRenderTarget(this.resources.antsDiscreteRenderTarget);
|
|
this.renderer.clear();
|
|
|
|
for (const scene of Object.values(scenes)) {
|
|
scene.recompileMaterials();
|
|
}
|
|
}
|
|
|
|
static convertNumberToFloatString(n: number): string {
|
|
return n.toFixed(8);
|
|
}
|
|
}
|