From 42d87fc2a310cd01bcd3ae5ec98e5dfeaa9b71ae Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sat, 29 Apr 2023 22:24:15 +0100 Subject: [PATCH] Experiment with cancer cells --- src/game-loop/game-loop.ts | 7 +-- src/pipelines/agents/agent.wgsl | 59 ++++++++++++++++--- src/pipelines/diffusion/diffuse.wgsl | 24 +++++--- src/pipelines/diffusion/diffusion-pipeline.ts | 14 +++-- src/pipelines/diffusion/diffusion-settings.ts | 6 +- src/settings.ts | 8 ++- 6 files changed, 87 insertions(+), 31 deletions(-) diff --git a/src/game-loop/game-loop.ts b/src/game-loop/game-loop.ts index 3f3bc84..0221917 100644 --- a/src/game-loop/game-loop.ts +++ b/src/game-loop/game-loop.ts @@ -12,7 +12,6 @@ import { vec2 } from 'gl-matrix'; export default class GameLoop { private context: GPUCanvasContext; - private adapter: GPUAdapter; private device: GPUDevice; private agentPipeline: AgentPipeline; @@ -81,7 +80,7 @@ export default class GameLoop { position, angle: angle + Math.PI, species: 0, - timeToLive: Random.randomBetween(2, 10), + timeToLive: Random.randomBetween(10, 15000), }; }); } @@ -119,8 +118,8 @@ export default class GameLoop { throw new Error('WebGPU is not supported'); } - this.adapter = await gpu.requestAdapter(); - this.device = await this.adapter.requestDevice(); // could request more resources + const adapter = await gpu.requestAdapter(); + this.device = await adapter.requestDevice(); // could request more resources this.context = this.canvas.getContext('webgpu') as any; this.context.configure({ diff --git a/src/pipelines/agents/agent.wgsl b/src/pipelines/agents/agent.wgsl index 8aec868..34ace4a 100644 --- a/src/pipelines/agents/agent.wgsl +++ b/src/pipelines/agents/agent.wgsl @@ -14,7 +14,7 @@ struct Settings { moveRate: f32, turnRate: f32, sensorAngle: f32, - sensorOffsetDst: f32, + sensorOffset: f32, }; @group(0) @binding(0) var settings: Settings; @@ -32,11 +32,46 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { var agent = agents[id]; + if (agent.timeToLive <= 0.) { + agent.position = vec2(random(id + u32(settings.time * 10 + agent.position.x * 10000)), + random(id + u32(settings.time * 10 + agent.position.y * 10000))); + agent.angle = random(id + u32(settings.time)) * 3.14159265359 * 2.; + agent.species = 1; + agent.timeToLive = 1000; + agents[id] = agent; + return; + } + 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); - let weightRight: f32 = sense(agent, -settings.sensorAngle); + let trailCurrent = sense(agent, 0, 0); + var weight: f32; + if(agent.species == 0) { + weight = trailCurrent.r - trailCurrent.g; + } else { + weight = trailCurrent.g - trailCurrent.r; + } + if (weight < 0) { + agent.timeToLive = 0; + return; + } + + let trailForward = sense(agent, settings.sensorOffset, 0); + let trailLeft = sense(agent, settings.sensorOffset, settings.sensorAngle); + let trailRight = sense(agent, settings.sensorOffset, -settings.sensorAngle); + + var weightForward: f32 = trailForward.a; + var weightLeft: f32 = trailLeft.a; + var weightRight: f32 = trailRight.a; + if (agent.species == 0) { + weightForward += trailForward.r - trailForward.g; + weightLeft += trailLeft.r - trailLeft.g; + weightRight += trailRight.r - trailRight.g; + } else { + weightForward += trailForward.g - trailForward.r; + weightLeft += trailLeft.g - trailLeft.r; + weightRight += trailRight.g - trailRight.r; + } if (weightForward < weightLeft && weightForward < weightRight) { agent.angle += (random - 0.5) * 2. * settings.turnRate; @@ -53,17 +88,23 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { agent.angle += 3.14159265359 + random - 0.5; } - textureStore(TrailMapOut, vec2(newPos * settings.size), vec4(1, 0, 0, 0)); + var trail = vec4(0, 1, 0, 0); + if (agent.species == 0) { + trail = vec4(1, 0, 0, 0); + } + textureStore(TrailMapOut, vec2(newPos * settings.size), trail); agent.position = newPos; + agent.timeToLive -= settings.deltaTime; agents[id] = agent; } -fn sense(agent: Agent, sensorAngleOffset: f32) -> f32 { - let sensorAngle: f32 = agent.angle + sensorAngleOffset; +fn sense(agent: Agent, sensorOffset: f32, sensorOffsetAngle: f32) -> vec4 { + let sensorAngle = agent.angle + sensorOffsetAngle; + let sensorDir: vec2 = vec2(cos(sensorAngle), sin(sensorAngle)) / normalize(settings.size); - let sensorPos: vec2 = agent.position + sensorDir * settings.sensorOffsetDst; - return length(textureLoad(TrailMapIn, vec2(sensorPos * settings.size), 0)); + let sensorPos: vec2 = agent.position + sensorDir * sensorOffset; + return textureLoad(TrailMapIn, vec2(sensorPos * settings.size), 0); } fn random(state0: u32) -> f32 { diff --git a/src/pipelines/diffusion/diffuse.wgsl b/src/pipelines/diffusion/diffuse.wgsl index 6fac26d..34c5193 100644 --- a/src/pipelines/diffusion/diffuse.wgsl +++ b/src/pipelines/diffusion/diffuse.wgsl @@ -3,8 +3,10 @@ struct Settings { deltaTime: f32, time: f32, - diffusionRate: f32, - decayRate: f32, + diffusionRateTrails: f32, + decayRateTrails: f32, + diffusionRateBrush: f32, + decayRateBrush: f32, }; @group(0) @binding(0) var settings: Settings; @@ -22,11 +24,17 @@ fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { + textureSample(trailMap, Sampler, uv + vec2(1, 0) / settings.size) ) / 4; - let mixed = mix( - current, - neighbours, - settings.diffusionRate - ) * (1.0 - settings.decayRate); + let mixedTrails = mix( + current.rgb, + neighbours.rgb, + settings.diffusionRateTrails + ) * (1.0 - settings.decayRateTrails); - return clamp(mixed, vec4(0), vec4(1)); + let mixedBrush = mix( + current.a, + neighbours.a, + settings.diffusionRateBrush + ) * (1.0 - settings.decayRateBrush); + + return clamp(vec4(mixedTrails, mixedBrush), vec4(0), vec4(1)); } diff --git a/src/pipelines/diffusion/diffusion-pipeline.ts b/src/pipelines/diffusion/diffusion-pipeline.ts index 96a9881..a7f85db 100644 --- a/src/pipelines/diffusion/diffusion-pipeline.ts +++ b/src/pipelines/diffusion/diffusion-pipeline.ts @@ -5,7 +5,7 @@ import shader from './diffuse.wgsl'; import { DiffusionSettings } from './diffusion-settings'; export class DiffusionPipeline { - private static readonly UNIFORM_COUNT = 16; + private static readonly UNIFORM_COUNT = 18; private readonly pipeline: GPURenderPipeline; private readonly uniforms: GPUBuffer; @@ -45,8 +45,10 @@ export class DiffusionPipeline { canvasSize, deltaTime, time, - diffusionRate, - decayRate, + diffusionRateTrails, + decayRateTrails, + diffusionRateBrush, + decayRateBrush, }: CommonParameters & DiffusionSettings) { this.device.queue.writeBuffer( this.uniforms, @@ -56,8 +58,10 @@ export class DiffusionPipeline { canvasSize[1], deltaTime, time, - diffusionRate, - decayRate, + diffusionRateTrails, + decayRateTrails, + diffusionRateBrush, + decayRateBrush, ]) ); } diff --git a/src/pipelines/diffusion/diffusion-settings.ts b/src/pipelines/diffusion/diffusion-settings.ts index 3b91e37..909101b 100644 --- a/src/pipelines/diffusion/diffusion-settings.ts +++ b/src/pipelines/diffusion/diffusion-settings.ts @@ -1,4 +1,6 @@ export interface DiffusionSettings { - diffusionRate: number; - decayRate: number; + diffusionRateTrails: number; + decayRateTrails: number; + diffusionRateBrush: number; + decayRateBrush: number; } diff --git a/src/settings.ts b/src/settings.ts index 057a637..86628ae 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -32,10 +32,12 @@ export const settings: GameLoopSettings & sensorAngleDegrees: 30, sensorOffsetDst: 0.025, - decayRate: 0.005, - diffusionRate: 0.9, + diffusionRateTrails: 0.8, + decayRateTrails: 0.03, + diffusionRateBrush: 0.9, + decayRateBrush: 0.003, - brushColor: palette.beige, + brushColor: palette.yellow, speciesColorA: palette.yellow, speciesColorB: palette.green, };