From 8ce9b97cf21fcdb2d0dd6346abede9e4dc2ddb98 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 18 Apr 2023 20:46:53 +0100 Subject: [PATCH] Use UV coords for agents --- src/pipelines/agents/agent-pipeline.ts | 16 +++-- src/pipelines/agents/agent.wgsl | 95 ++++++++------------------ src/renderer.ts | 20 ++++-- src/settings.ts | 18 ++--- 4 files changed, 59 insertions(+), 90 deletions(-) diff --git a/src/pipelines/agents/agent-pipeline.ts b/src/pipelines/agents/agent-pipeline.ts index 7d10988..ef0e430 100644 --- a/src/pipelines/agents/agent-pipeline.ts +++ b/src/pipelines/agents/agent-pipeline.ts @@ -37,9 +37,12 @@ export class AgentPipeline { new ArrayBuffer(agents.length * AGENT_SIZE_IN_BYTES) ); agents.forEach((agent, i) => { - serializedAgents[i * 4 + 0] = agent.position[0]; - serializedAgents[i * 4 + 1] = agent.position[1]; - serializedAgents[i * 4 + 2] = agent.angle; + 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({ @@ -80,11 +83,10 @@ export class AgentPipeline { width, height, trailWeight, - deltaTime, time, - moveSpeed, - turnSpeed, - sensorAngleDegrees, + moveSpeed * deltaTime, + turnSpeed * deltaTime, + (sensorAngleDegrees * Math.PI) / 180, sensorOffsetDst, sensorSize, ]) diff --git a/src/pipelines/agents/agent.wgsl b/src/pipelines/agents/agent.wgsl index d6f76fc..a0b1c4a 100644 --- a/src/pipelines/agents/agent.wgsl +++ b/src/pipelines/agents/agent.wgsl @@ -4,15 +4,14 @@ struct Agent { } struct Settings { - width : i32, - height : i32, + size: vec2, trailWeight : f32, - deltaTime : f32, time : f32, - moveSpeed : f32, - turnSpeed : f32, - sensorAngleDegrees : f32, + moveRate : f32, + turnRate : f32, + + sensorAngle : f32, sensorOffsetDst : f32, sensorSize : f32, }; @@ -24,8 +23,7 @@ struct Settings { // Hash function www.cs.ubc.ca/~rbridson/docs/schechter-sca08-turbulence.pdf -fn hash(state0 : u32) -> u32 -{ +fn hash(state0 : u32) -> u32 { var state : u32 = state0; state = state ^ 2747636419u; state = state * 2654435769u; @@ -46,78 +44,41 @@ fn main(@builtin(global_invocation_id) global_id : vec3) { var agent = agents[id]; - var random = f32(hash( - u32( - agent.position.y * f32(settings.width) + agent.position.x - ) - + hash( - id + u32(settings.time * 100000.) - ) - )) / 4294967295.0; + let random = f32(hash(id + u32(settings.time * 10000 + agent.position.y * 10 + agent.position.x))) / 4294967295.0; - // Steer based on sensory data - let sensorAngleRad : f32 = settings.sensorAngleDegrees * (3.1415 / 180.); let weightForward : f32 = sense(agent, 0.); - let weightLeft : f32 = sense(agent, sensorAngleRad); - let weightRight : f32 = sense(agent, -sensorAngleRad); + let weightLeft : f32 = sense(agent, settings.sensorAngle); + let weightRight : f32 = sense(agent, -settings.sensorAngle); - let randomSteerStrength : f32 = random; - let turnSpeed : f32 = settings.turnSpeed * 2. * 3.1415; - - // choose random direction if (weightForward < weightLeft && weightForward < weightRight) { - agent.angle = agent.angle + (randomSteerStrength - 0.5) * 2. * turnSpeed * settings.deltaTime; - } - // Turn right - else if (weightRight > weightLeft) { - agent.angle = agent.angle - randomSteerStrength * turnSpeed * settings.deltaTime; - } - // Turn left - else if (weightLeft > weightRight) { - agent.angle = agent.angle + randomSteerStrength * turnSpeed * settings.deltaTime; + agent.angle += (random - 0.5) * 2. * settings.turnRate; + } else if (weightLeft < weightRight) { + agent.angle -= random * settings.turnRate; + } else if (weightRight < weightLeft) { + agent.angle += random * settings.turnRate; } - // Update position - let direction : vec2 = vec2(cos(agent.angle), sin(agent.angle)); - var newPos : vec2 = agent.position + direction * settings.deltaTime * settings.moveSpeed; + let direction = vec2(cos(agent.angle), sin(agent.angle)); + var newPos = agent.position + direction / normalize(settings.size) * settings.moveRate; - // Clamp position to map boundaries, and pick new random move dir if hit boundary - if (newPos.x < 0. || newPos.x >= f32(settings.width) || newPos.y < 0. || newPos.y >= f32(settings.height)) { - // random = hash(random); - let randomAngle : f32 = random * 2. * 3.1415; - - newPos.x = min(f32(settings.width - 1), max(0., newPos.x)); - newPos.y = min(f32(settings.height - 1), max(0., newPos.y)); - agent.angle = randomAngle; - } else { - let offset : i32 = i32() * settings.width * 4 + i32() * 4; - textureStore(TrailMapOut, vec2(i32(newPos.x), i32(newPos.y)), vec4(vec3(1.) * settings.trailWeight * settings.deltaTime, 1.)); + newPos = clamp(newPos, vec2(0, 0), vec2(1, 1)); + if (newPos.x == 0. || newPos.x == 1. || newPos.y == 0. || newPos.y == 1.) { + agent.angle = random * 2. * 3.1415; } - agent.position = newPos; + textureStore( + TrailMapOut, + vec2(newPos * settings.size), + vec4(vec3(1.) * settings.trailWeight * 0.02, 1.) + ); + + agent.position = newPos; agents[id] = agent; } fn sense(agent : Agent, sensorAngleOffset : f32) -> f32 { let sensorAngle : f32 = agent.angle + sensorAngleOffset; - let sensorDir : vec2 = vec2(cos(sensorAngle), sin(sensorAngle)); - + let sensorDir : vec2 = vec2(cos(sensorAngle), sin(sensorAngle)) / normalize(settings.size); let sensorPos : vec2 = agent.position + sensorDir * settings.sensorOffsetDst; - let sensorCentreX : i32 = i32(sensorPos.x); - let sensorCentreY : i32 = i32(sensorPos.y); - - var sum : f32 = 0.; - - let senseWeight : vec4 = vec4(2, 2, 2, 2) - vec4(1, 1, 1, 1); - - let sensorSize : i32 = i32(settings.sensorSize); - for (var offsetX : i32 = -sensorSize; offsetX <= sensorSize; offsetX = offsetX + 1) { - for (var offsetY : i32 = -sensorSize; offsetY <= sensorSize; offsetY = offsetY + 1) { - let sampleX : i32 = min(settings.width - 1, max(0, sensorCentreX + offsetX)); - let sampleY : i32 = min(settings.height - 1, max(0, sensorCentreY + offsetY)); - sum = sum + dot(vec4(senseWeight), textureLoad(TrailMapIn, vec2(sampleX, sampleY), 1)); - } - } - - return sum; + return textureLoad(TrailMapIn, vec2(sensorPos * settings.size), 0).x; } diff --git a/src/renderer.ts b/src/renderer.ts index 2d01fa3..bb9e7b7 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -21,6 +21,8 @@ export default class Renderer { private trailMapA?: GPUTexture; private trailMapB?: GPUTexture; + private previousTime?: DOMHighResTimeStamp = null; + public constructor(private canvas: HTMLCanvasElement) {} async start() { @@ -35,7 +37,7 @@ export default class Renderer { window.addEventListener('resize', this.resize.bind(this)); const agents: Array = new Array(settings.agentCount).fill(0).map(() => ({ - position: vec2.fromValues(randomBetween(0, 1000), randomBetween(0, 1000)), + position: vec2.fromValues(randomBetween(1 / 3, 2 / 3), randomBetween(1 / 3, 2 / 3)), angle: randomBetween(0, Math.PI * 2), })); @@ -95,18 +97,19 @@ export default class Renderer { } private render(time: DOMHighResTimeStamp) { + const deltaTime = this.calculateDeltaTime(time); + this.agentPipeline.setParameters({ width: this.canvas.width, height: this.canvas.height, time, - deltaTime: 0.016, - sensorAngleDegrees: 45, + deltaTime, ...settings, }); this.diffusionPipeline.setParameters({ width: this.canvas.width, height: this.canvas.height, - deltaTime: 0.016, + deltaTime, ...settings, }); const commandEncoder = this.device.createCommandEncoder(); @@ -120,4 +123,13 @@ export default class Renderer { requestAnimationFrame(this.render.bind(this)); } + + private calculateDeltaTime(time: DOMHighResTimeStamp): number { + if (this.previousTime === null) { + this.previousTime = time; + } + const deltaTime = time - this.previousTime; + this.previousTime = time; + return deltaTime / 1000; + } } diff --git a/src/settings.ts b/src/settings.ts index 1fd7241..6b8ff0d 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,31 +1,25 @@ -const SpawnMode = { Random: 0, Point: 1, InwardCircle: 2, RandomCircle: 3 }; - interface Settings { - stepsPerFrame: number; agentCount: number; - spawnMode: number; trailWeight: number; decayRate: number; diffusionRate: number; moveSpeed: number; turnSpeed: number; - sensorAngleSpacing: number; + sensorAngleDegrees: number; sensorOffsetDst: number; sensorSize: number; } export const settings: Settings = { - stepsPerFrame: 2, - agentCount: 500000, - spawnMode: SpawnMode.InwardCircle, + agentCount: 50000, trailWeight: 5, decayRate: 0.05, - diffusionRate: 0.1, + diffusionRate: 0.2, - moveSpeed: 20, + moveSpeed: 0.03, turnSpeed: 2, - sensorAngleSpacing: 30, - sensorOffsetDst: 35, + sensorAngleDegrees: 45, + sensorOffsetDst: 35 / 1000, sensorSize: 1, };