Add colony stats readback for aggregate ant state

This commit is contained in:
Jared Miller 2026-03-09 10:53:10 -04:00
parent 862cbfc3b7
commit 451e187032
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
4 changed files with 76 additions and 0 deletions

67
src/ColonyStats.ts Normal file
View file

@ -0,0 +1,67 @@
import type * as THREE from "three";
export interface ColonyStatsData {
foragerRatio: number; // fraction of ants currently carrying food
totalAnts: number; // total ant count
}
export default class ColonyStats {
private buffer: Float32Array;
private statsData: ColonyStatsData = {
foragerRatio: 0,
totalAnts: 0,
};
constructor() {
this.buffer = new Float32Array(0);
}
public update(
renderer: THREE.WebGLRenderer,
antTarget: THREE.WebGLRenderTarget,
): ColonyStatsData {
const width = antTarget.width;
const height = antTarget.height;
const pixelCount = width * height;
// resize buffer if needed
if (this.buffer.length !== pixelCount * 4) {
this.buffer = new Float32Array(pixelCount * 4);
}
renderer.readRenderTargetPixels(
antTarget,
0,
0,
width,
height,
this.buffer,
);
let carryingCount = 0;
let activeAnts = 0;
for (let i = 0; i < pixelCount; i++) {
const posX = this.buffer[i * 4];
const posY = this.buffer[i * 4 + 1];
const w = this.buffer[i * 4 + 3]; // alpha = packed state
// skip uninitialized ants (both coordinates at origin)
if (posX === 0 && posY === 0) continue;
activeAnts++;
const isCarrying = Math.trunc(w) & 1;
if (isCarrying) carryingCount++;
}
this.statsData.totalAnts = activeAnts;
this.statsData.foragerRatio =
activeAnts > 0 ? carryingCount / activeAnts : 0;
return this.statsData;
}
public get data(): ColonyStatsData {
return this.statsData;
}
}

View file

@ -1,6 +1,7 @@
import type { WebGLRenderTarget } from "three";
import * as THREE from "three";
import type { SceneCollection } from "./App";
import ColonyStats from "./ColonyStats";
import Config from "./Config";
import {
FOOD_QUALITY_MASK,
@ -22,6 +23,7 @@ interface Resources {
export default class Renderer {
private renderer: THREE.WebGLRenderer;
public resources!: Resources;
private colonyStats = new ColonyStats();
constructor(public canvas: HTMLCanvasElement) {
this.renderer = new THREE.WebGLRenderer({ canvas });
@ -157,6 +159,11 @@ export default class Renderer {
antsComputeTarget.textures[0];
scenes.screen.groundMaterial.uniforms.map.value =
this.resources.worldRenderTargetCopy.texture;
// colony stats readback — read ant state texture, compute aggregate stats on CPU
this.colonyStats.update(this.renderer, antsComputeTarget);
scenes.ants.material.uniforms.uForagerRatio.value =
this.colonyStats.data.foragerRatio;
}
private setViewportFromRT(rt: WebGLRenderTarget) {

View file

@ -22,6 +22,7 @@ export default class AntsComputeScene extends AbstractScene {
tLastExtState: { value: null },
tWorld: { value: null },
tPresence: { value: null },
uForagerRatio: { value: 0 },
},
vertexShader,
fragmentShader,

View file

@ -13,6 +13,7 @@ uniform sampler2D tLastState;
uniform sampler2D tLastExtState;
uniform sampler2D tWorld;
uniform sampler2D tPresence;
uniform float uForagerRatio;
const float sampleDistance = 20.;
const float cellSize = 1. / WORLD_SIZE;