Minor improvements xd

This commit is contained in:
Andras Schmelczer 2023-05-21 19:19:12 +01:00
parent f3f2547724
commit 0e97e54ffe
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
21 changed files with 189 additions and 184 deletions

View file

@ -3,7 +3,8 @@
## todo ## todo
- add info page - add info page
- generate starting shape on the gpu
- add cancer
- graceful error messages when no support
- settings page - settings page
- shareable settings
- query max agent count
- graceful error messages when no support

View file

@ -6,4 +6,6 @@ export interface GameLoopSettings {
aggressionFactor: number; aggressionFactor: number;
nextGenerationSpawnRadius: number; nextGenerationSpawnRadius: number;
nextGenerationSpawnInterval: number; nextGenerationSpawnInterval: number;
startColorHue: number;
} }

View file

@ -1,5 +1,4 @@
import { AgentGenerationPipeline } from '../pipelines/agents/agent-generation/agent-generation-pipeline'; import { AgentGenerationPipeline } from '../pipelines/agents/agent-generation/agent-generation-pipeline';
import { GenerationCounts } from '../pipelines/agents/agent-generation/generation-counts';
import { AgentPipeline } from '../pipelines/agents/agent-pipeline'; import { AgentPipeline } from '../pipelines/agents/agent-pipeline';
import { BrushPipeline } from '../pipelines/brush/brush-pipeline'; import { BrushPipeline } from '../pipelines/brush/brush-pipeline';
import { CommonState } from '../pipelines/common-state/common-state'; import { CommonState } from '../pipelines/common-state/common-state';
@ -11,6 +10,7 @@ import { DeltaTimeCalculator } from '../utils/delta-time-calculator';
import { initializeContext } from '../utils/graphics/initialize-context'; import { initializeContext } from '../utils/graphics/initialize-context';
import { ResizableTexture } from '../utils/graphics/resizable-texture'; import { ResizableTexture } from '../utils/graphics/resizable-texture';
import { sleep } from '../utils/sleep'; import { sleep } from '../utils/sleep';
import { GamePresentation } from './game-presentation';
import { GameRules } from './game-rules'; import { GameRules } from './game-rules';
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
@ -27,8 +27,6 @@ export default class GameLoop {
private readonly brushPipeline: BrushPipeline; private readonly brushPipeline: BrushPipeline;
private readonly diffusionPipeline: DiffusionPipeline; private readonly diffusionPipeline: DiffusionPipeline;
private readonly gameRules = new GameRules(performance.now() / 1000);
private hasFinished = false; private hasFinished = false;
private readonly hasFinishedPromise: Promise<void> = new Promise( private readonly hasFinishedPromise: Promise<void> = new Promise(
(resolve) => (this.resolveHasFinished = resolve) (resolve) => (this.resolveHasFinished = resolve)
@ -40,7 +38,8 @@ export default class GameLoop {
public constructor( public constructor(
private readonly canvas: HTMLCanvasElement, private readonly canvas: HTMLCanvasElement,
private readonly device: GPUDevice, private readonly device: GPUDevice,
private readonly deltaTimeCalculator: DeltaTimeCalculator private readonly deltaTimeCalculator: DeltaTimeCalculator,
private readonly gameRules: GameRules
) { ) {
const context = initializeContext({ device, canvas }); const context = initializeContext({ device, canvas });
@ -131,10 +130,19 @@ export default class GameLoop {
return; return;
} }
const accentColor = GamePresentation.getGenerationColor(
this.gameRules.nextGenerationId - 1
);
document.documentElement.style.setProperty(
'--accent-color',
`rgb(${accentColor.map((v) => v * 255).join(',')})`
);
const deltaTime = this.deltaTimeCalculator.calculateDeltaTimeInSeconds(time); const deltaTime = this.deltaTimeCalculator.calculateDeltaTimeInSeconds(time);
time *= settings.renderSpeed; time *= settings.renderSpeed;
const timeInSeconds = time / 1000; const timeInSeconds = time / 1000;
const spawnAction = this.gameRules.getSpawnAction(timeInSeconds, this.canvasSize);
[ [
this.commonState, this.commonState,
@ -156,20 +164,28 @@ export default class GameLoop {
nextGenerationId: this.gameRules.nextGenerationId, nextGenerationId: this.gameRules.nextGenerationId,
deltaTime, deltaTime,
canvasSize: this.canvasSize, canvasSize: this.canvasSize,
brushColor: GamePresentation.getGenerationColor(
this.gameRules.nextGenerationId - 1
),
evenGenerationColor: GamePresentation.getGenerationColor(
this.gameRules.nextGenerationId % 2 == 0
? this.gameRules.nextGenerationId
: this.gameRules.nextGenerationId - 1
),
oddGenerationColor: GamePresentation.getGenerationColor(
this.gameRules.nextGenerationId % 2 == 1
? this.gameRules.nextGenerationId
: this.gameRules.nextGenerationId - 1
),
...settings, ...settings,
center: spawnAction.position,
radius: spawnAction.radius,
}) })
); );
for (let i = 0; i < settings.renderSpeed; i++) { for (let i = 0; i < settings.renderSpeed; i++) {
const commandEncoder = this.device.createCommandEncoder(); const commandEncoder = this.device.createCommandEncoder();
const spawnAction = this.gameRules.getSpawnAction(timeInSeconds, this.canvasSize);
this.agentGenerationPipeline.spawnNextGeneration(
spawnAction.position,
spawnAction.radius,
spawnAction.generation
);
this.copyPipeline.execute( this.copyPipeline.execute(
commandEncoder, commandEncoder,
this.trailMapA.getTextureView(), this.trailMapA.getTextureView(),

View file

@ -1,11 +1,22 @@
import { settings } from '../settings';
import { hsl } from '../utils/colors/hsl'; import { hsl } from '../utils/colors/hsl';
import { hash } from '../utils/hash'; import { last } from '../utils/last';
import { Random } from '../utils/random';
import { vec3 } from 'gl-matrix'; import { vec3 } from 'gl-matrix';
const hues = [settings.startColorHue];
for (let i = 0; i < 100; i++) {
hues.push((last(hues) + Random.randomBetween(90, 240)) % 360);
}
const colors = hues.map((hue) =>
hsl(hue, Random.randomBetween(80, 90), Random.randomBetween(20, 30))
);
export class GamePresentation { export class GamePresentation {
public getGenerationColor(generation: number): vec3 { public static getGenerationColor(generation: number): vec3 {
const hue = Math.round(hash(generation) * 360); return colors[generation % colors.length];
return hsl(hue, 100, 50);
} }
} }

View file

@ -8,7 +8,6 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
color: var(--very-light-text-color);
@media (prefers-reduced-motion) { @media (prefers-reduced-motion) {
transition: none !important; transition: none !important;
@ -78,7 +77,7 @@ html {
} }
> aside { > aside {
@include blurred-background(#777); @include blurred-background(#fff);
position: absolute; position: absolute;
top: 50%; top: 50%;
@ -97,19 +96,19 @@ html {
margin: var(--small-margin); margin: var(--small-margin);
> button.info { > button.info {
@include image-button(url('../assets/icons/info.svg')); @include image-button(url('../assets/icons/info.svg'), var(--accent-color));
} }
> button.maximize-full-screen { > button.maximize-full-screen {
@include image-button(url('../assets/icons/maximize.svg')); @include image-button(url('../assets/icons/maximize.svg'), var(--accent-color));
} }
> button.minimize-full-screen { > button.minimize-full-screen {
@include image-button(url('../assets/icons/minimize.svg')); @include image-button(url('../assets/icons/minimize.svg'), var(--accent-color));
} }
> button.restart { > button.restart {
@include image-button(url('../assets/icons/restart.svg')); @include image-button(url('../assets/icons/restart.svg'), var(--accent-color));
} }
} }

View file

@ -1,5 +1,6 @@
import '../assets/icons/info.svg'; import '../assets/icons/info.svg';
import GameLoop from './game-loop/game-loop'; import GameLoop from './game-loop/game-loop';
import { GameRules } from './game-loop/game-rules';
import './index.scss'; import './index.scss';
import { FullScreenHandler } from './page/full-screen-handler'; import { FullScreenHandler } from './page/full-screen-handler';
import { InfoPageHandler } from './page/info-page-handler'; import { InfoPageHandler } from './page/info-page-handler';
@ -49,7 +50,7 @@ const main = async () => {
let shouldStop = false; let shouldStop = false;
let game: GameLoop | null = null; let game: GameLoop | null = null;
ErrorHandler.addOnErrorListener((error, metadata) => { ErrorHandler.addOnErrorListener((error, _metadata) => {
elements.errorContainer.innerHTML += ` elements.errorContainer.innerHTML += `
<pre class="${error.severity}">${error.message}</div> <pre class="${error.severity}">${error.message}</div>
`; `;
@ -73,7 +74,9 @@ const main = async () => {
elements.restartButton.addEventListener('click', () => game?.destroy()); elements.restartButton.addEventListener('click', () => game?.destroy());
const deltaTimeCalculator = new DeltaTimeCalculator(); const deltaTimeCalculator = new DeltaTimeCalculator();
const gameRules = new GameRules(performance.now() / 1000);
console.log(gameRules.nextGenerationId);
const updateCounters = () => { const updateCounters = () => {
elements.counters.innerHTML = `FPS: ${deltaTimeCalculator.fps.toFixed(2)} elements.counters.innerHTML = `FPS: ${deltaTimeCalculator.fps.toFixed(2)}
current gen: ${game?.aliveAgentCounts.currentGenerationCount ?? 0} current gen: ${game?.aliveAgentCounts.currentGenerationCount ?? 0}
@ -83,7 +86,7 @@ next gen: ${game?.aliveAgentCounts.nextGenerationCount ?? 0}`;
updateCounters(); updateCounters();
while (!shouldStop) { while (!shouldStop) {
game = new GameLoop(elements.canvas, gpu, deltaTimeCalculator); game = new GameLoop(elements.canvas, gpu, deltaTimeCalculator, gameRules);
await game.start(); await game.start();
} }
} catch (e) { } catch (e) {

View file

@ -1,15 +1,12 @@
import random from '../../../utils/graphics/random.wgsl'; import random from '../../../utils/graphics/random.wgsl';
import { smartCompile } from '../../../utils/graphics/smart-compile'; import { smartCompile } from '../../../utils/graphics/smart-compile';
import { CommonState } from '../../common-state/common-state'; import { CommonState } from '../../common-state/common-state';
import { AGENT_SIZE_IN_BYTES, Agent } from './agent'; import { AGENT_SIZE_IN_BYTES } from './agent';
import countingShader from './agent-counting.wgsl'; import countingShader from './agent-counting.wgsl';
import firstGenerationShader from './agent-first-generation.wgsl'; import firstGenerationShader from './agent-first-generation.wgsl';
import agentGenerationShader from './agent-generation.wgsl';
import agentSchema from './agent-schema.wgsl'; import agentSchema from './agent-schema.wgsl';
import { GenerationCounts } from './generation-counts'; import { GenerationCounts } from './generation-counts';
import { vec2 } from 'gl-matrix';
export class AgentGenerationPipeline { export class AgentGenerationPipeline {
private static readonly WORKGROUP_SIZE = 64; private static readonly WORKGROUP_SIZE = 64;
private static readonly UNIFORM_COUNT = 4; private static readonly UNIFORM_COUNT = 4;
@ -20,7 +17,6 @@ export class AgentGenerationPipeline {
private readonly bindGroup: GPUBindGroup; private readonly bindGroup: GPUBindGroup;
private readonly firstGenerationPipeline: GPUComputePipeline; private readonly firstGenerationPipeline: GPUComputePipeline;
private readonly nextGenerationPipeline: GPUComputePipeline;
private readonly countingPipeline: GPUComputePipeline; private readonly countingPipeline: GPUComputePipeline;
public readonly agentsBuffer: GPUBuffer; public readonly agentsBuffer: GPUBuffer;
@ -122,22 +118,6 @@ export class AgentGenerationPipeline {
}, },
}); });
this.nextGenerationPipeline = device.createComputePipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [commonState.bindGroupLayout, this.bindGroupLayout],
}),
compute: {
module: smartCompile(
device,
CommonState.shaderCode,
random,
agentSchema,
agentGenerationShader
),
entryPoint: 'main',
},
});
this.countingPipeline = device.createComputePipeline({ this.countingPipeline = device.createComputePipeline({
layout: device.createPipelineLayout({ layout: device.createPipelineLayout({
bindGroupLayouts: [commonState.bindGroupLayout, this.bindGroupLayout], bindGroupLayouts: [commonState.bindGroupLayout, this.bindGroupLayout],
@ -170,27 +150,6 @@ export class AgentGenerationPipeline {
this.device.queue.submit([commandEncoder.finish()]); this.device.queue.submit([commandEncoder.finish()]);
} }
public spawnNextGeneration(center: vec2, radius: number, generationId: number): void {
this.device.queue.writeBuffer(
this.uniforms,
0,
new Float32Array([...center, radius, generationId])
);
const commandEncoder = this.device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass();
this.commonState.execute(passEncoder);
passEncoder.setPipeline(this.nextGenerationPipeline);
passEncoder.setBindGroup(1, this.bindGroup);
passEncoder.dispatchWorkgroups(
Math.ceil(this.agentCount / AgentGenerationPipeline.WORKGROUP_SIZE)
);
passEncoder.end();
this.device.queue.submit([commandEncoder.finish()]);
}
public async countAgents(): Promise<GenerationCounts> { public async countAgents(): Promise<GenerationCounts> {
this.device.queue.writeBuffer(this.countersBuffer, 0, new Int32Array([0, 0])); this.device.queue.writeBuffer(this.countersBuffer, 0, new Int32Array([0, 0]));

View file

@ -1,20 +0,0 @@
struct Settings {
center: vec2<f32>,
radius: f32,
nextGenerationId: f32,
};
@group(1) @binding(0) var<uniform> settings: Settings;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let id = global_id.x;
if id >= arrayLength(&agents) {
return;
}
if length(settings.center - agents[id].position) < settings.radius {
agents[id].generation = settings.nextGenerationId;
}
}

View file

@ -1,19 +1,20 @@
import random from '../../utils/graphics/random.wgsl'; import random from '../../utils/graphics/random.wgsl';
import { smartCompile } from '../../utils/graphics/smart-compile'; import { smartCompile } from '../../utils/graphics/smart-compile';
import { CommonState } from '../common-state/common-state'; import { CommonState } from '../common-state/common-state';
import { AGENT_SIZE_IN_BYTES, Agent } from './agent-generation/agent'; import { AGENT_SIZE_IN_BYTES } from './agent-generation/agent';
import agentSchme from './agent-generation/agent-schema.wgsl'; import agentSchme from './agent-generation/agent-schema.wgsl';
import { AgentSettings } from './agent-settings'; import { AgentSettings } from './agent-settings';
import shader from './agent.wgsl'; import shader from './agent.wgsl';
import { vec2 } from 'gl-matrix';
export class AgentPipeline { export class AgentPipeline {
private static readonly WORKGROUP_SIZE = 64; private static readonly WORKGROUP_SIZE = 64;
private static readonly UNIFORM_COUNT = 8; private static readonly UNIFORM_COUNT = 16;
private readonly bindGroupLayout: GPUBindGroupLayout; private readonly bindGroupLayout: GPUBindGroupLayout;
private readonly pipeline: GPUComputePipeline; private readonly pipeline: GPUComputePipeline;
private readonly uniforms: GPUBuffer; private readonly uniforms: GPUBuffer;
private bindGroup?: GPUBindGroup; private bindGroup?: GPUBindGroup;
private previousTrailMapIn?: GPUTextureView; private previousTrailMapIn?: GPUTextureView;
private previousTrailMapOut?: GPUTextureView; private previousTrailMapOut?: GPUTextureView;
@ -50,10 +51,18 @@ export class AgentPipeline {
evenGenerationAggression, evenGenerationAggression,
oddGenerationAggression, oddGenerationAggression,
nextGenerationId, nextGenerationId,
center,
radius,
turnWhenGoingInTheRightDirection,
turnWhenLost,
individualTrailWeight,
deinfectionProbability,
}: AgentSettings & { }: AgentSettings & {
evenGenerationAggression: number; evenGenerationAggression: number;
oddGenerationAggression: number; oddGenerationAggression: number;
nextGenerationId: number; nextGenerationId: number;
center: vec2;
radius: number;
}) { }) {
this.device.queue.writeBuffer( this.device.queue.writeBuffer(
this.uniforms, this.uniforms,
@ -67,6 +76,12 @@ export class AgentPipeline {
evenGenerationAggression, evenGenerationAggression,
oddGenerationAggression, oddGenerationAggression,
nextGenerationId, nextGenerationId,
...center,
radius,
turnWhenGoingInTheRightDirection,
turnWhenLost,
individualTrailWeight,
deinfectionProbability,
]) ])
); );
} }

View file

@ -4,4 +4,8 @@ export interface AgentSettings {
turnSpeed: number; turnSpeed: number;
sensorOffsetAngle: number; sensorOffsetAngle: number;
sensorOffsetDistance: number; sensorOffsetDistance: number;
turnWhenGoingInTheRightDirection: number;
turnWhenLost: number;
individualTrailWeight: number;
deinfectionProbability: number;
} }

View file

@ -1,15 +1,32 @@
struct Settings { struct Settings {
brushTrailWeight: f32, brushTrailWeight: f32,
moveRate: f32, moveRate: f32,
turnRate: f32, turnRate: f32,
sensorAngle: f32, sensorAngle: f32,
sensorOffset: f32, sensorOffset: f32,
evenGenerationAggression: f32, evenGenerationAggression: f32,
oddGenerationAggression: f32, oddGenerationAggression: f32,
nextGenerationId: f32 nextGenerationId: f32,
center: vec2<f32>,
radius: f32,
turnWhenGoingInTheRightDirection: f32,
turnWhenLost: f32,
individualTrailWeight: f32,
deinfectionProbability: f32
}; };
@group(1) @binding(0) var<uniform> settings: Settings; @group(1) @binding(0) var<uniform> settings: Settings;
// even generation's trail -> red channel
// odd generation's trail -> green channel
// unused -> blue channel
// brush -> alpha channel
@group(1) @binding(2) var trailMapIn: texture_2d<f32>; @group(1) @binding(2) var trailMapIn: texture_2d<f32>;
@group(1) @binding(3) var trailMapOut: texture_storage_2d<rgba16float, write>; @group(1) @binding(3) var trailMapOut: texture_storage_2d<rgba16float, write>;
@ -22,24 +39,38 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
} }
var agent = agents[id]; var agent = agents[id];
var trailBelow = textureLoad(trailMapIn, vec2<i32>(agent.position), 0);
if settings.radius > 0 && length(settings.center - agent.position) < settings.radius {
agents[id].generation = settings.nextGenerationId;
// clear trail map below so the agent won't die immediately
if (settings.nextGenerationId % 2 == 0) {
trailBelow.r += trailBelow.g;
trailBelow.g = 0;
} else {
trailBelow.g += trailBelow.r;
trailBelow.r = 0;
}
textureStore(trailMapOut, vec2<i32>(agent.position), trailBelow);
return;
}
let random = hash(id + u32(state.time % 107 * 1673.7)); let random = hash(id + u32(state.time % 107 * 1673.7));
let trailCurrent = textureLoad(trailMapIn, vec2<i32>(agent.position), 0);
var weight: f32;
// even generation id -> red channel
// odd generation id -> green channel
let isFromEvenGeneration = agent.generation % 2 == 0; let isFromEvenGeneration = agent.generation % 2 == 0;
let isFromNextGeneration = agent.generation == settings.nextGenerationId;
let trailForward = sense(agent.position, agent.angle, settings.sensorOffset, 0); let isFromCurrentGeneration = !isFromNextGeneration;
let trailForward = sense(agent.position, agent.angle, settings.sensorOffset , 0);
let trailLeft = sense(agent.position, agent.angle, settings.sensorOffset, settings.sensorAngle); let trailLeft = sense(agent.position, agent.angle, settings.sensorOffset, settings.sensorAngle);
let trailRight = sense(agent.position, agent.angle, settings.sensorOffset, -settings.sensorAngle); let trailRight = sense(agent.position, agent.angle, settings.sensorOffset, -settings.sensorAngle);
var weightForward: f32 = trailForward.a * settings.brushTrailWeight; var weightForward: f32 = f32(isFromCurrentGeneration) * trailForward.a * settings.brushTrailWeight;
var weightLeft: f32 = trailLeft.a * settings.brushTrailWeight; var weightLeft: f32 = f32(isFromCurrentGeneration) * trailLeft.a * settings.brushTrailWeight;
var weightRight: f32 = trailRight.a * settings.brushTrailWeight; var weightRight: f32 = f32(isFromCurrentGeneration) * trailRight.a * settings.brushTrailWeight;
if (isFromEvenGeneration) { if (isFromEvenGeneration) {
weightForward += trailForward.r + settings.evenGenerationAggression * trailForward.g; weightForward += trailForward.r + settings.evenGenerationAggression * trailForward.g;
weightLeft += trailLeft.r + settings.evenGenerationAggression * trailLeft.g; weightLeft += trailLeft.r + settings.evenGenerationAggression * trailLeft.g;
@ -51,38 +82,37 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
} }
var rotation: f32 = 0; var rotation: f32 = 0;
if (weightForward < weightLeft && weightForward < weightRight) { if weightForward > weightLeft && weightForward > weightRight {
rotation = (random - 0.5) * 2. * settings.turnRate * state.deltaTime; rotation = (random - 0.5) * settings.turnWhenGoingInTheRightDirection * settings.turnRate * state.deltaTime;
} else if (weightLeft < weightRight) { } else if weightLeft < weightRight {
rotation = random * -settings.turnRate * state.deltaTime; rotation = -min(settings.sensorAngle, settings.turnRate * state.deltaTime);
} else if (weightRight < weightLeft) { } else if weightRight < weightLeft {
rotation = random * settings.turnRate * state.deltaTime; rotation = min(settings.sensorAngle, settings.turnRate * state.deltaTime);
} else {
rotation = (random - 0.5) * settings.turnWhenLost * settings.turnRate * state.deltaTime;
} }
var nextAngle = agent.angle + rotation;
let direction = vec2(cos(agent.angle), sin(agent.angle)); let direction = vec2(cos(agent.angle), sin(agent.angle));
var nextPosition = agent.position + direction * settings.moveRate * state.deltaTime; var nextPosition = agent.position + direction * settings.moveRate * state.deltaTime;
nextPosition = clamp(nextPosition, vec2<f32>(0, 0), state.size); nextPosition = clamp(nextPosition, vec2<f32>(0, 0), state.size);
if nextPosition.x == 0 || nextPosition.x == state.size.x || nextPosition.y == 0 || nextPosition.y == state.size.y { if nextPosition.x == 0 || nextPosition.x == state.size.x || nextPosition.y == 0 || nextPosition.y == state.size.y {
rotation = 3.14159265359 + random - 0.5; rotation = 3.14159265359 + random - 0.5;
nextAngle = agent.angle + rotation;
} }
var trail = vec4<f32>(0, 0.1, 0, 0); var trail = vec4<f32>(0, settings.individualTrailWeight, 0, 0);
if (isFromEvenGeneration) { if isFromEvenGeneration {
trail = vec4(0.1, 0, 0, 0); trail = vec4(settings.individualTrailWeight, 0, 0, 0);
} }
let current = textureLoad(trailMapIn, vec2<i32>(nextPosition), 0); let next = vec4(trail.rgb + trailBelow.rgb, trailBelow.a);
let next = vec4(trail.rgb + current.rgb, current.a);
textureStore(trailMapOut, vec2<i32>(nextPosition), next); textureStore(trailMapOut, vec2<i32>(nextPosition), next);
if(isFromEvenGeneration) { if isFromEvenGeneration {
if next.r < next.g { if next.r < next.g {
if agent.generation == settings.nextGenerationId { if agent.generation == settings.nextGenerationId {
// agent.generation -= 1; if random < settings.deinfectionProbability {
agent.generation -= 1;
}
} else { } else {
agent.generation += 1; agent.generation += 1;
} }
@ -90,7 +120,9 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
} else { } else {
if next.g < next.r { if next.g < next.r {
if agent.generation == settings.nextGenerationId { if agent.generation == settings.nextGenerationId {
// agent.generation -= 1; if random < settings.deinfectionProbability {
agent.generation -= 1;
}
} else { } else {
agent.generation += 1; agent.generation += 1;
} }
@ -98,7 +130,7 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
} }
agent.position = nextPosition; agent.position = nextPosition;
agent.angle = nextAngle; agent.angle += rotation;
agents[id] = agent; agents[id] = agent;
} }

View file

@ -1,4 +1,3 @@
import { setUpFullScreenQuad } from '../../utils/graphics/full-screen-quad/full-screen-quad';
import { smartCompile } from '../../utils/graphics/smart-compile'; import { smartCompile } from '../../utils/graphics/smart-compile';
import shader from './copy.wgsl'; import shader from './copy.wgsl';

View file

@ -33,7 +33,7 @@ fn fragment(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let decayed = clamp(vec4( let decayed = clamp(vec4(
current.rgb * settings.decayRateTrails, current.rgb * settings.decayRateTrails,
current.a * settings.decayRateBrush max(0, current.a - settings.decayRateBrush)
), vec4(0), vec4(1)); ), vec4(0), vec4(1));
return decayed; return decayed;

View file

@ -1,10 +1,11 @@
import { generateFbmNoise } from '../../utils/graphics/fbm-noise/fbm-noise';
import { setUpFullScreenQuad } from '../../utils/graphics/full-screen-quad/full-screen-quad'; import { setUpFullScreenQuad } from '../../utils/graphics/full-screen-quad/full-screen-quad';
import { smartCompile } from '../../utils/graphics/smart-compile'; import { smartCompile } from '../../utils/graphics/smart-compile';
import { CommonState } from '../common-state/common-state'; import { CommonState } from '../common-state/common-state';
import { RenderSettings } from './render-settings'; import { RenderSettings } from './render-settings';
import shader from './render.wgsl'; import shader from './render.wgsl';
import { vec3 } from 'gl-matrix';
export class RenderPipeline { export class RenderPipeline {
private static readonly UNIFORM_COUNT = 13; private static readonly UNIFORM_COUNT = 13;
@ -56,7 +57,11 @@ export class RenderPipeline {
evenGenerationColor, evenGenerationColor,
oddGenerationColor, oddGenerationColor,
clarity, clarity,
}: RenderSettings) { }: RenderSettings & {
brushColor: vec3;
evenGenerationColor: vec3;
oddGenerationColor: vec3;
}) {
this.device.queue.writeBuffer( this.device.queue.writeBuffer(
this.uniforms, this.uniforms,
0, 0,

View file

@ -1,8 +1,3 @@
import { vec3 } from 'gl-matrix';
export interface RenderSettings { export interface RenderSettings {
brushColor: vec3;
evenGenerationColor: vec3;
oddGenerationColor: vec3;
clarity: number; clarity: number;
} }

View file

@ -16,15 +16,19 @@ fn fragment(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let backgroundColor = vec3(0.9) + 0.075 * random.r; let backgroundColor = vec3(0.9) + 0.075 * random.r;
let evenGenerationStrength = clamp(pow(traces.r, settings.clarity), 0, 1); let evenGenerationStrength = pow(traces.r, settings.clarity);
let oddGenerationStrength = clamp(pow(traces.g, settings.clarity), 0, 1); let oddGenerationStrength = pow(traces.g, settings.clarity);
let brushStrength = traces.a; let brushStrength = traces.a;
let agentColor = step(evenGenerationStrength, oddGenerationStrength) * settings.oddGenerationColor * oddGenerationStrength + step(oddGenerationStrength, evenGenerationStrength) * settings.evenGenerationColor * evenGenerationStrength; let color = max(
let agentStrength = evenGenerationStrength + oddGenerationStrength; mix(
evenGenerationStrength * settings.evenGenerationColor,
oddGenerationStrength * settings.oddGenerationColor,
oddGenerationStrength / (evenGenerationStrength + oddGenerationStrength + 0.000001)
),
brushStrength * settings.brushColor);
let rgbColor = sqrt( let strength = max(evenGenerationStrength, max(oddGenerationStrength, brushStrength));
mix(agentColor, settings.brushColor * brushStrength, clamp(brushStrength - agentStrength, 0, 1))
); return vec4(mix(backgroundColor, color, strength), 1);
return vec4(backgroundColor - rgbColor, 1);
} }

View file

@ -3,16 +3,6 @@ import { AgentSettings } from './pipelines/agents/agent-settings';
import { BrushSettings } from './pipelines/brush/brush-settings'; import { BrushSettings } from './pipelines/brush/brush-settings';
import { DiffusionSettings } from './pipelines/diffusion/diffusion-settings'; import { DiffusionSettings } from './pipelines/diffusion/diffusion-settings';
import { RenderSettings } from './pipelines/render/render-settings'; import { RenderSettings } from './pipelines/render/render-settings';
import { rgb255 } from './utils/colors/rgb255';
const palette = {
blue: rgb255(0, 110, 202),
red: rgb255(232, 141, 122),
green: rgb255(90, 188, 94),
purple: rgb255(161, 90, 188),
yellow: rgb255(255, 204, 0),
beige: rgb255(229, 204, 175),
};
export const settings: GameLoopSettings & export const settings: GameLoopSettings &
AgentSettings & AgentSettings &
@ -21,29 +11,31 @@ export const settings: GameLoopSettings &
RenderSettings = { RenderSettings = {
agentCount: 4_000_000, // requires restart agentCount: 4_000_000, // requires restart
aggressionFactor: 0.5, // requires restart aggressionFactor: 3,
nextGenerationSpawnRadius: 1, nextGenerationSpawnRadius: 5,
nextGenerationSpawnInterval: 2, nextGenerationSpawnInterval: 1,
renderSpeed: 10, renderSpeed: 2,
simulatedDelayMs: 0, simulatedDelayMs: 0,
brushWidth: 20, brushWidth: 12,
brushWidthRandomness: 8, brushWidthRandomness: 5.5,
brushTrailWeight: 5, brushTrailWeight: 5,
moveSpeed: 40, moveSpeed: 80,
turnSpeed: 20, turnSpeed: 550,
sensorOffsetAngle: 30, sensorOffsetAngle: 30,
sensorOffsetDistance: 60, sensorOffsetDistance: 30,
turnWhenGoingInTheRightDirection: 0.05,
turnWhenLost: 0.2,
individualTrailWeight: 0.5,
deinfectionProbability: 0.001,
diffusionRateTrails: 0.4, // inverse diffusionRateTrails: 2, // inverse
decayRateTrails: 0.9, // inverse decayRateTrails: 0.9, // inverse
diffusionRateBrush: 4, // inverse diffusionRateBrush: 4, // inverse
decayRateBrush: 0.995, // inverse decayRateBrush: 0.003,
brushColor: palette.blue, clarity: 2,
evenGenerationColor: palette.yellow, startColorHue: 200,
oddGenerationColor: palette.purple,
clarity: 1,
}; };

View file

@ -33,18 +33,17 @@ $breakpoint-width: 700px !default;
} }
} }
@mixin image-button($background-image) { @mixin image-button($background-image, $background-color) {
@include square(var(--icon-size)); @include square(var(--icon-size));
border: none; border: none;
cursor: pointer; cursor: pointer;
background-color: transparent; background-color: $background-color;
background-image: $background-image; mask-image: $background-image;
background-size: contain; -webkit-mask-image: $background-image;
background-repeat: no-repeat; mask-repeat: no-repeat;
background-position: center;
transition: transform var(--transition-time); transition: transform var(--transition-time), background-color var(--transition-time);
&:hover { &:hover {
transform: scale(1.15); transform: scale(1.15);
} }

View file

@ -1,13 +1,11 @@
@use 'mixins' as *; @use 'mixins' as *;
$accent-color: #b7455e;
:root { :root {
--transition-time: 200ms; --transition-time: 200ms;
--transition-time-long: 350ms; --transition-time-long: 350ms;
--line-width: 4px; --line-width: 4px;
--line-height: 1.125rem; --line-height: 1.125rem;
--accent-color: $accent-color; --accent-color: #ff6b6b;
--sun-color: #f7f78c; --sun-color: #f7f78c;
--very-light-text-color: #ffffff; --very-light-text-color: #ffffff;
--background: #ffffff; --background: #ffffff;

View file

@ -6,7 +6,7 @@ export const hsl = (hue: number, saturation: number, lightness: number): vec3 =>
hue /= 360; hue /= 360;
saturation /= 100; saturation /= 100;
lightness /= 100; lightness /= 100;
let r, g, b; let r: number, g: number, b: number;
if (saturation == 0) { if (saturation == 0) {
r = g = b = lightness; r = g = b = lightness;

View file

@ -1,9 +0,0 @@
export const hash = (state: number): number => {
state ^= 2747636419;
state *= 2654435769;
state ^= state >> 16;
state *= 2654435769;
state ^= state >> 16;
state *= 2654435769;
return state / 4294967295.0;
};