diff --git a/src/game-loop/game-loop.ts b/src/game-loop/game-loop.ts index 2d5a60f..3f3bc84 100644 --- a/src/game-loop/game-loop.ts +++ b/src/game-loop/game-loop.ts @@ -80,6 +80,8 @@ export default class GameLoop { return { position, angle: angle + Math.PI, + species: 0, + timeToLive: Random.randomBetween(2, 10), }; }); } diff --git a/src/pipelines/agents/agent-pipeline.ts b/src/pipelines/agents/agent-pipeline.ts index 00994c6..42e5d01 100644 --- a/src/pipelines/agents/agent-pipeline.ts +++ b/src/pipelines/agents/agent-pipeline.ts @@ -34,24 +34,23 @@ export class AgentPipeline { usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); - const serializedAgents = new Float32Array( - new ArrayBuffer(agents.length * AGENT_SIZE_IN_BYTES) - ); - agents.forEach((agent, i) => { - serializedAgents[(i * AGENT_SIZE_IN_BYTES) / Float32Array.BYTES_PER_ELEMENT + 0] = - agent.position[0]; - serializedAgents[(i * AGENT_SIZE_IN_BYTES) / Float32Array.BYTES_PER_ELEMENT + 1] = - agent.position[1]; - serializedAgents[(i * AGENT_SIZE_IN_BYTES) / Float32Array.BYTES_PER_ELEMENT + 2] = - agent.angle; - }); - this.agentsBuffer = device.createBuffer({ size: agents.length * AGENT_SIZE_IN_BYTES, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, + usage: GPUBufferUsage.STORAGE, + mappedAtCreation: true, }); - device.queue.writeBuffer(this.agentsBuffer, 0, serializedAgents.buffer); + new Float32Array(this.agentsBuffer.getMappedRange()).set( + agents.flatMap((agent) => [ + agent.position[0], + agent.position[1], + agent.angle, + agent.species, + agent.timeToLive, + 0, // padding + ]) + ); + this.agentsBuffer.unmap(); } public setParameters({ diff --git a/src/pipelines/agents/agent.ts b/src/pipelines/agents/agent.ts index 007c3e7..2f4cd1a 100644 --- a/src/pipelines/agents/agent.ts +++ b/src/pipelines/agents/agent.ts @@ -3,6 +3,8 @@ import { vec2 } from 'gl-matrix'; export interface Agent { position: vec2; angle: number; + species: number; + timeToLive: number; } -export const AGENT_SIZE_IN_BYTES = 4 * 4; +export const AGENT_SIZE_IN_BYTES = 6 * Float32Array.BYTES_PER_ELEMENT; diff --git a/src/pipelines/agents/agent.wgsl b/src/pipelines/agents/agent.wgsl index e547a67..884abe2 100644 --- a/src/pipelines/agents/agent.wgsl +++ b/src/pipelines/agents/agent.wgsl @@ -1,6 +1,8 @@ struct Agent { position: vec2, angle: f32, + species: f32, + timeToLive: f32 } struct Settings { @@ -30,7 +32,7 @@ fn main(@builtin(global_invocation_id) global_id : vec3) { var agent = agents[id]; - let random = f32(hash(id + u32(settings.time * 10000 + agent.position.y * 10 + agent.position.x))) / 4294967295.0; + let random = random(id + u32(settings.time * 10000 + agent.position.y * 10 + agent.position.x)); let weightForward : f32 = sense(agent, 0.); let weightLeft : f32 = sense(agent, settings.sensorAngle); @@ -54,28 +56,27 @@ fn main(@builtin(global_invocation_id) global_id : vec3) { textureStore( TrailMapOut, vec2(newPos * settings.size), - vec4(vec3(1.) * settings.trailWeight * 0.02, 1.) + vec4(1) ); agent.position = newPos; agents[id] = agent; } -fn hash(state0 : u32) -> u32 { - // Hash function www.cs.ubc.ca/~rbridson/docs/schechter-sca08-turbulence.pdf - var state : u32 = state0; - state = state ^ 2747636419u; - state = state * 2654435769u; - state = state ^ (state >> 16u); - state = state * 2654435769u; - state = state ^ (state >> 16u); - state = state * 2654435769u; - return state; -} - fn sense(agent : Agent, sensorAngleOffset : f32) -> f32 { let sensorAngle : f32 = agent.angle + sensorAngleOffset; let sensorDir : vec2 = vec2(cos(sensorAngle), sin(sensorAngle)) / normalize(settings.size); let sensorPos : vec2 = agent.position + sensorDir * settings.sensorOffsetDst; return textureLoad(TrailMapIn, vec2(sensorPos * settings.size), 0).x; } + +fn random(state0 : u32) -> f32 { + var state : u32 = state0; + state = state ^ 2747636419u; + state = state * 2654435769u; + state = state ^ (state >> 16u); + state = state * 2654435769u; + state = state ^ (state >> 16u); + state = state * 2654435769u; + return f32(state) / 4294967295.0; +} diff --git a/src/pipelines/diffusion/diffuse.wgsl b/src/pipelines/diffusion/diffuse.wgsl index 30e7d2c..d3a830d 100644 --- a/src/pipelines/diffusion/diffuse.wgsl +++ b/src/pipelines/diffusion/diffuse.wgsl @@ -20,11 +20,11 @@ fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { + textureSample(trailMap, Sampler, uv + vec2(0, -1) / settings.size) + textureSample(trailMap, Sampler, uv + vec2(-1, 0) / settings.size) + textureSample(trailMap, Sampler, uv + vec2(1, 0) / settings.size) - ); + ) / 4; let mixed = mix( current, - neighbours / 4.0, + neighbours, settings.diffusionRate ) * (1.0 - settings.decayRate); diff --git a/src/settings.ts b/src/settings.ts index 21f9b04..d51adb2 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -3,18 +3,19 @@ import { AgentSettings } from './pipelines/agents/agent-settings'; import { BrushSettings } from './pipelines/brush/brush-settings'; import { DiffusionSettings } from './pipelines/diffusion/diffusion-settings'; import { RenderSettings } from './pipelines/render/render-settings'; +import { rgb255 } from './utils/colors/rgb255'; export const settings: GameLoopSettings & AgentSettings & BrushSettings & DiffusionSettings & RenderSettings = { - agentCount: 1_000, + agentCount: 1_000_000, renderSpeed: 1, startingRadius: 0.15, - brushWidth: 20, - brushBlurWidth: 5, + brushWidth: 30, + brushBlurWidth: 8, trailWeight: 5, moveSpeed: 0.025, @@ -22,6 +23,15 @@ export const settings: GameLoopSettings & sensorAngleDegrees: 30, sensorOffsetDst: 0.025, - decayRate: 0.005, + decayRate: 0.01, diffusionRate: 0.8, }; + +const paletteMap = { + 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), +};