WIP
This commit is contained in:
parent
34ac200437
commit
39b0160064
136 changed files with 7144 additions and 1965 deletions
181
src/game-loop/agent-population.ts
Normal file
181
src/game-loop/agent-population.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
|
||||
import { appConfig } from '../config';
|
||||
import { AGENT_FLOAT_COUNT } from '../pipelines/agents/agent-generation/agent';
|
||||
import { AgentGenerationPipeline } from '../pipelines/agents/agent-generation/agent-generation-pipeline';
|
||||
import { settings } from '../settings';
|
||||
import { createIntroTitleAgents } from './intro-title-agents';
|
||||
|
||||
export const GLOBAL_AGENT_CAP = appConfig.simulation.globalAgentCap;
|
||||
|
||||
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;
|
||||
|
||||
export class AgentPopulation {
|
||||
private activeCount = 0;
|
||||
private targetBudget = appConfig.simulation.budget.initialTargetAgentBudget;
|
||||
private replacementCursor = 0;
|
||||
private shouldCompactAfterErase = false;
|
||||
private isCompacting = false;
|
||||
private readonly strokeAgentData = new Float32Array(
|
||||
MAX_STROKE_AGENT_COUNT * AGENT_FLOAT_COUNT
|
||||
);
|
||||
|
||||
public constructor(private readonly pipeline: AgentGenerationPipeline) {}
|
||||
|
||||
public get activeAgentCount(): number {
|
||||
return this.activeCount;
|
||||
}
|
||||
|
||||
public get targetAgentBudget(): number {
|
||||
return this.targetBudget;
|
||||
}
|
||||
|
||||
public get maxAgentCount(): number {
|
||||
return this.pipeline.maxAgentCount;
|
||||
}
|
||||
|
||||
public initializeIntroAgents(canvasSize: vec2): void {
|
||||
this.targetBudget = Math.min(
|
||||
this.pipeline.maxAgentCount,
|
||||
settings.agentBudgetMax,
|
||||
INITIAL_AGENT_COUNT
|
||||
);
|
||||
this.writeAgentBatch(
|
||||
createIntroTitleAgents({
|
||||
count: this.targetBudget,
|
||||
width: canvasSize[0],
|
||||
height: canvasSize[1],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public onVibeChanged(): void {
|
||||
this.targetBudget = Math.min(
|
||||
this.targetBudget,
|
||||
settings.agentBudgetMax,
|
||||
this.pipeline.maxAgentCount
|
||||
);
|
||||
}
|
||||
|
||||
public growBudget(
|
||||
deltaTime: number,
|
||||
smoothedFps: number,
|
||||
refreshTargetFps: number
|
||||
): void {
|
||||
const cap = Math.min(settings.agentBudgetMax, this.pipeline.maxAgentCount);
|
||||
if (
|
||||
this.targetBudget < cap &&
|
||||
smoothedFps > refreshTargetFps * appConfig.simulation.budget.fpsHeadroom
|
||||
) {
|
||||
this.targetBudget = Math.min(
|
||||
cap,
|
||||
this.targetBudget +
|
||||
Math.ceil(appConfig.simulation.budget.rampAgentsPerSecond * deltaTime)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public resizeAgents(scale: vec2): void {
|
||||
this.pipeline.resizeAgents(this.activeCount, scale);
|
||||
}
|
||||
|
||||
public requestCompactionAfterErase(): void {
|
||||
this.shouldCompactAfterErase = true;
|
||||
}
|
||||
|
||||
public async compactAfterErase(isSwipeActive: boolean): Promise<void> {
|
||||
if (!this.shouldCompactAfterErase || this.isCompacting || isSwipeActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.shouldCompactAfterErase = false;
|
||||
if (this.activeCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isCompacting = true;
|
||||
try {
|
||||
const compactedAgentCount = await this.pipeline.compactAgents(this.activeCount);
|
||||
this.activeCount = compactedAgentCount;
|
||||
this.replacementCursor =
|
||||
compactedAgentCount === 0 ? 0 : this.replacementCursor % compactedAgentCount;
|
||||
this.targetBudget = Math.max(this.targetBudget, compactedAgentCount);
|
||||
} finally {
|
||||
this.isCompacting = false;
|
||||
}
|
||||
}
|
||||
|
||||
public spawnStrokeAgents(from: vec2, to: vec2): void {
|
||||
const length = Math.max(1, vec2.dist(from, to));
|
||||
const count = Math.max(
|
||||
MIN_STROKE_AGENT_COUNT,
|
||||
Math.min(
|
||||
MAX_STROKE_AGENT_COUNT,
|
||||
Math.ceil(length * settings.spawnPerPixel * STROKE_AGENT_DENSITY_MULTIPLIER)
|
||||
)
|
||||
);
|
||||
const direction = vec2.sub(vec2.create(), to, from);
|
||||
const baseAngle = Math.atan2(direction[1], direction[0]);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const t = count === 1 ? 1 : i / (count - 1);
|
||||
const x = from[0] + (to[0] - from[0]) * t;
|
||||
const y = from[1] + (to[1] - from[1]) * t;
|
||||
const angle =
|
||||
(Number.isFinite(baseAngle) ? baseAngle : Math.random() * Math.PI * 2) +
|
||||
(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;
|
||||
this.strokeAgentData[base + 2] = angle;
|
||||
this.strokeAgentData[base + 3] = settings.selectedColorIndex;
|
||||
this.strokeAgentData[base + 4] = -1;
|
||||
this.strokeAgentData[base + 5] = -1;
|
||||
this.strokeAgentData[base + 6] = angle;
|
||||
this.strokeAgentData[base + 7] = 0;
|
||||
}
|
||||
|
||||
this.writeAgentBatch(this.strokeAgentData.subarray(0, count * AGENT_FLOAT_COUNT));
|
||||
}
|
||||
|
||||
private writeAgentBatch(data: Float32Array): void {
|
||||
if (data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const count = data.length / AGENT_FLOAT_COUNT;
|
||||
const available = Math.max(0, this.targetBudget - this.activeCount);
|
||||
const appendCount = Math.min(count, available);
|
||||
|
||||
if (appendCount > 0) {
|
||||
this.pipeline.writeAgents(
|
||||
this.activeCount,
|
||||
data.subarray(0, appendCount * AGENT_FLOAT_COUNT)
|
||||
);
|
||||
this.activeCount += appendCount;
|
||||
}
|
||||
|
||||
let sourceAgentOffset = appendCount;
|
||||
while (sourceAgentOffset < count && this.activeCount > 0) {
|
||||
const targetAgentOffset = this.replacementCursor % this.activeCount;
|
||||
const chunkAgentCount = Math.min(
|
||||
count - sourceAgentOffset,
|
||||
this.activeCount - targetAgentOffset
|
||||
);
|
||||
|
||||
this.pipeline.writeAgents(
|
||||
targetAgentOffset,
|
||||
data.subarray(
|
||||
sourceAgentOffset * AGENT_FLOAT_COUNT,
|
||||
(sourceAgentOffset + chunkAgentCount) * AGENT_FLOAT_COUNT
|
||||
)
|
||||
);
|
||||
|
||||
sourceAgentOffset += chunkAgentCount;
|
||||
this.replacementCursor = (targetAgentOffset + chunkAgentCount) % this.activeCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue