This commit is contained in:
Andras Schmelczer 2026-05-17 14:12:01 +01:00
commit 80ed37298b
73 changed files with 3619 additions and 1295 deletions

View file

@ -6,17 +6,6 @@ import { AgentGenerationPipeline } from '../pipelines/agents/agent-generation/ag
import { settings } from '../settings';
import { createIntroTitleAgents } from './intro-title-agents';
const INITIAL_AGENT_COUNT = appConfig.simulation.initialAgentCount;
const MIN_STROKE_AGENT_COUNT = appConfig.simulation.stroke.minAgentCount;
const MAX_STROKE_AGENT_COUNT = appConfig.simulation.stroke.maxAgentCount;
const STROKE_AGENT_DENSITY_MULTIPLIER = appConfig.simulation.stroke.densityMultiplier;
const ADAPTIVE_CAP_MAX = appConfig.simulation.budget.adaptiveCapMax;
const ADAPTIVE_CAP_MIN = appConfig.simulation.budget.adaptiveCapMin;
const ADAPTIVE_CAP_INITIAL = appConfig.simulation.budget.adaptiveCapInitial;
const ADAPTIVE_CAP_DECREASE_AGENTS_PER_SECOND =
appConfig.simulation.budget.adaptiveCapDecreaseAgentsPerSecond;
const ADAPTIVE_REFRESH_TARGET_FPS = 60;
export class AgentPopulation {
private activeCount = 0;
private adaptiveCap: number;
@ -25,11 +14,16 @@ export class AgentPopulation {
private shouldCompactAfterErase = false;
private isCompacting = false;
private readonly strokeAgentData = new Float32Array(
MAX_STROKE_AGENT_COUNT * AGENT_FLOAT_COUNT
appConfig.simulation.stroke.maxAgentCount * AGENT_FLOAT_COUNT
);
public constructor(private readonly pipeline: AgentGenerationPipeline) {
this.adaptiveCap = this.clampAdaptiveCap(ADAPTIVE_CAP_INITIAL);
public constructor(
private readonly pipeline: AgentGenerationPipeline,
private readonly introSeed = Math.floor(Math.random() * 0xffffffff)
) {
this.adaptiveCap = this.clampAdaptiveCap(
appConfig.simulation.budget.adaptiveCapInitial
);
}
public get activeAgentCount(): number {
@ -37,15 +31,30 @@ export class AgentPopulation {
}
public initializeIntroAgents(canvasSize: vec2): void {
this.replaceIntroAgents(canvasSize, 0);
}
public replaceIntroAgents(canvasSize: vec2, progress: number): void {
this.adaptiveCap = this.clampAdaptiveCap(this.adaptiveCap);
const introAgentCount = Math.min(this.adaptiveCap, INITIAL_AGENT_COUNT);
this.writeAgentBatch(
createIntroTitleAgents({
count: introAgentCount,
width: canvasSize[0],
height: canvasSize[1],
})
const introAgentCount = Math.min(
this.adaptiveCap,
appConfig.simulation.initialAgentCount
);
const data = createIntroTitleAgents({
count: introAgentCount,
width: canvasSize[0],
height: canvasSize[1],
progress,
seed: this.introSeed,
});
if (data.length === 0) {
return;
}
this.pipeline.writeAgents(0, data);
this.activeCount = data.length / AGENT_FLOAT_COUNT;
this.replacementCursor = 0;
}
public onVibeChanged(): void {
@ -87,12 +96,18 @@ export class AgentPopulation {
}
public spawnStrokeAgents(from: vec2, to: vec2): void {
const length = Math.max(1, vec2.dist(from, to));
const length = Math.max(
appConfig.simulation.stroke.minSegmentLengthPx,
vec2.dist(from, to)
);
const count = Math.max(
MIN_STROKE_AGENT_COUNT,
appConfig.simulation.stroke.minAgentCount,
Math.min(
MAX_STROKE_AGENT_COUNT,
Math.ceil(length * settings.spawnPerPixel * STROKE_AGENT_DENSITY_MULTIPLIER)
appConfig.simulation.stroke.maxAgentCount,
this.strokeAgentData.length / AGENT_FLOAT_COUNT,
Math.ceil(
length * settings.spawnPerPixel * appConfig.simulation.stroke.densityMultiplier
)
)
);
const direction = vec2.sub(vec2.create(), to, from);
@ -106,8 +121,9 @@ export class AgentPopulation {
baseAngle +
(Math.random() - 0.5) * appConfig.simulation.stroke.angleJitterRadians;
const base = i * AGENT_FLOAT_COUNT;
this.strokeAgentData[base] = x + (Math.random() - 0.5) * settings.brushSize;
this.strokeAgentData[base + 1] = y + (Math.random() - 0.5) * settings.brushSize;
const spread = settings.brushSize * settings.strokeSpawnSpreadBrushSizeMultiplier;
this.strokeAgentData[base] = x + (Math.random() - 0.5) * spread;
this.strokeAgentData[base + 1] = y + (Math.random() - 0.5) * spread;
this.strokeAgentData[base + 2] = angle;
this.strokeAgentData[base + 3] = settings.selectedColorIndex;
this.strokeAgentData[base + 4] = -1;
@ -164,7 +180,8 @@ export class AgentPopulation {
const previousCap = this.clampAdaptiveCap(this.adaptiveCap);
this.canExpandAdaptiveCap =
smoothedFps >=
ADAPTIVE_REFRESH_TARGET_FPS * appConfig.simulation.budget.fpsHeadroom;
appConfig.simulation.budget.adaptiveRefreshTargetFps *
appConfig.simulation.budget.fpsHeadroom;
if (this.canExpandAdaptiveCap) {
this.adaptiveCap = previousCap;
@ -174,7 +191,9 @@ export class AgentPopulation {
const decrease = Math.max(
1,
Math.ceil(ADAPTIVE_CAP_DECREASE_AGENTS_PER_SECOND * deltaTime)
Math.ceil(
appConfig.simulation.budget.adaptiveCapDecreaseAgentsPerSecond * deltaTime
)
);
const nextCap = this.clampAdaptiveCap(previousCap - decrease);
this.adaptiveCap = nextCap;
@ -207,8 +226,8 @@ export class AgentPopulation {
private clampAdaptiveCap(value: number): number {
const pipelineCap = Math.max(0, Math.floor(this.pipeline.maxAgentCount));
const maxCap = Math.min(ADAPTIVE_CAP_MAX, pipelineCap);
const minCap = Math.min(ADAPTIVE_CAP_MIN, maxCap);
const maxCap = Math.min(appConfig.simulation.budget.adaptiveCapMax, pipelineCap);
const minCap = Math.min(appConfig.simulation.budget.adaptiveCapMin, maxCap);
const finiteValue = Number.isFinite(value) ? value : minCap;
return Math.min(maxCap, Math.max(minCap, Math.round(finiteValue)));
}