diff --git a/license.md b/license.md deleted file mode 100644 index f443c4f..0000000 --- a/license.md +++ /dev/null @@ -1,9 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. - -In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 887524d..97d4c27 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,10 @@ import './index.scss'; import Renderer from './renderer'; import './utils/mulberry32'; -const canvas = document.querySelector('canvas') as HTMLCanvasElement; -canvas.width = canvas.height = 640; -const renderer = new Renderer(canvas); -renderer.start(); +const main = () => { + const canvas = document.querySelector('canvas'); + const renderer = new Renderer(canvas); + renderer.start(); +}; + +main(); diff --git a/src/pipelines/diffusion/diffuse.wgsl b/src/pipelines/diffusion/diffuse.wgsl index f117540..334472d 100644 --- a/src/pipelines/diffusion/diffuse.wgsl +++ b/src/pipelines/diffusion/diffuse.wgsl @@ -1,8 +1,14 @@ struct Settings { size : vec2, + swipePrevious : vec2, + swipeCurrent : vec2, diffusionRate : f32, decayRate : f32, deltaTime : f32, + time : f32, + swipeRadius : f32, + swipeBlur : f32, + isSwipeActive : f32 }; @group(0) @binding(0) var settings : Settings; @@ -11,18 +17,30 @@ struct Settings { @fragment fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { - let current = textureSample(trailMap, Sampler, uv).rgb; - - let neighbours: vec3 = ( - textureSample(trailMap, Sampler, uv + vec2(0, 1) / settings.size).rgb - + textureSample(trailMap, Sampler, uv + vec2(0, -1) / settings.size).rgb - + textureSample(trailMap, Sampler, uv + vec2(-1, 0) / settings.size).rgb - + textureSample(trailMap, Sampler, uv + vec2(1, 0) / settings.size).rgb + var current = textureSample(trailMap, Sampler, uv); + + let neighbours: vec4 = ( + textureSample(trailMap, Sampler, uv + vec2(0, 1) / settings.size) + + 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) ); - return vec4(mix( + if (settings.isSwipeActive == 1.0) { + let pa = (uv - settings.swipePrevious) * normalize(settings.size); + let direction = (settings.swipeCurrent - settings.swipePrevious) * normalize(settings.size); + let q = clamp(dot(pa, direction) / dot(direction, direction), 0, 1); + let distance = length(pa - direction * q) - settings.swipeRadius; + + if(distance < 0) { + let opacity = -distance / settings.swipeBlur; + return clamp(vec4(1), current, vec4(1)); + } + } + + return mix( current, neighbours / 4.0, settings.diffusionRate - ) * (1.0 - settings.decayRate), 1.0); + ) * (1.0 - settings.decayRate); } diff --git a/src/pipelines/diffusion/diffusion-pipeline.ts b/src/pipelines/diffusion/diffusion-pipeline.ts index 02b1d29..a491bea 100644 --- a/src/pipelines/diffusion/diffusion-pipeline.ts +++ b/src/pipelines/diffusion/diffusion-pipeline.ts @@ -1,13 +1,19 @@ import { setUpFullScreenQuad } from '../../utils/full-screen-quad'; import shader from './diffuse.wgsl'; +import { vec2 } from 'gl-matrix'; + export class DiffusionPipeline { - private static readonly UNIFORM_COUNT = 6; + private static readonly UNIFORM_COUNT = 14; private readonly pipeline: GPURenderPipeline; private readonly uniforms: GPUBuffer; private readonly quadVertexBuffer: GPUBuffer; + private swipes: Array = [ + vec2.fromValues(Number.NaN, Number.NaN), + vec2.fromValues(Number.NaN, Number.NaN), + ]; private bindGroup?: GPUBindGroup; private previousTrailMapIn?: GPUTexture; @@ -46,17 +52,42 @@ export class DiffusionPipeline { diffusionRate, decayRate, deltaTime, + time, + swipe, + swipeRadius, + swipeBlur, + isSwipeActive, }: { width: number; height: number; + swipe: vec2; diffusionRate: number; decayRate: number; deltaTime: number; + time: number; + swipeRadius: number; + swipeBlur: number; + isSwipeActive: boolean; }) { + if (swipe) { + this.swipes = [...this.swipes.slice(-1), swipe]; + } + this.device.queue.writeBuffer( this.uniforms, 0, - new Float32Array([width, height, diffusionRate, decayRate, deltaTime]) + new Float32Array([ + width, + height, + ...this.swipes.flatMap((s) => [s[0], s[1]]), + diffusionRate, + decayRate, + deltaTime, + time, + swipeRadius, + swipeBlur, + isSwipeActive ? 1.0 : 0.0, + ]) ); } diff --git a/src/pipelines/render/render.wgsl b/src/pipelines/render/render.wgsl index 71518ed..de8d0a4 100644 --- a/src/pipelines/render/render.wgsl +++ b/src/pipelines/render/render.wgsl @@ -4,5 +4,5 @@ @fragment fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { - return textureSample(TargetTexture, mySampler, uv) * 10.0; + return vec4(textureSample(TargetTexture, mySampler, uv).rgb * 1.0, 1); } diff --git a/src/pipelines/swipe/swipe-pipeline.ts b/src/pipelines/swipe/swipe-pipeline.ts deleted file mode 100644 index 5a14406..0000000 --- a/src/pipelines/swipe/swipe-pipeline.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { setUpFullScreenQuad } from '../../utils/full-screen-quad'; -import shader from './swipe.wgsl'; - -import { vec2 } from 'gl-matrix'; - -export class SwipePipeline { - private static readonly UNIFORM_COUNT = 8; - - private readonly pipeline: GPURenderPipeline; - private readonly uniforms: GPUBuffer; - private readonly quadVertexBuffer: GPUBuffer; - - private bindGroup?: GPUBindGroup; - private previousTrailMapIn?: GPUTexture; - - public constructor(private readonly device: GPUDevice) { - const { buffer, vertex } = setUpFullScreenQuad(device); - this.quadVertexBuffer = buffer; - - this.pipeline = device.createRenderPipeline({ - layout: 'auto', - vertex, - fragment: { - module: device.createShaderModule({ - code: shader, - }), - entryPoint: 'fragment', - targets: [ - { - format: 'rgba16float', - }, - ], - }, - primitive: { - topology: 'triangle-strip', - }, - }); - - this.uniforms = this.device.createBuffer({ - size: SwipePipeline.UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT, - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, - }); - } - - public setParameters({ - width, - height, - isSwipeActive, - swipe, - swipeRadius, - }: { - width: number; - height: number; - isSwipeActive: boolean; - swipe: vec2; - swipeRadius: number; - }) { - this.device.queue.writeBuffer( - this.uniforms, - 0, - new Float32Array([ - width, - height, - swipe ? swipe[0] : 0, - swipe ? swipe[1] : 0, - swipeRadius, - isSwipeActive ? 1 : 0, - ]) - ); - } - - public execute( - commandEncoder: GPUCommandEncoder, - trailMapIn: GPUTexture, - trailMapOut: GPUTexture - ) { - this.ensureBindGroupExists(trailMapIn); - - const renderPassDescriptor: GPURenderPassDescriptor = { - colorAttachments: [ - { - view: trailMapOut.createView(), - clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, - loadOp: 'clear', - storeOp: 'store', - }, - ], - }; - - const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); - passEncoder.setPipeline(this.pipeline); - passEncoder.setVertexBuffer(0, this.quadVertexBuffer); - passEncoder.setBindGroup(0, this.bindGroup); - passEncoder.draw(4, 1); - passEncoder.end(); - } - - private ensureBindGroupExists(trailMapIn: GPUTexture) { - if (this.previousTrailMapIn !== trailMapIn) { - this.bindGroup = this.device.createBindGroup({ - layout: this.pipeline.getBindGroupLayout(0), - entries: [ - { - binding: 0, - resource: { - buffer: this.uniforms, - }, - }, - { - binding: 1, - resource: this.device.createSampler({ - magFilter: 'linear', - minFilter: 'linear', - }), - }, - { - binding: 2, - resource: trailMapIn.createView(), - }, - ], - }); - - this.previousTrailMapIn = trailMapIn; - } - } -} diff --git a/src/pipelines/swipe/swipe.wgsl b/src/pipelines/swipe/swipe.wgsl deleted file mode 100644 index 347bd98..0000000 --- a/src/pipelines/swipe/swipe.wgsl +++ /dev/null @@ -1,24 +0,0 @@ -struct Settings { - size : vec2, - swipe : vec2, - swipeRadius : f32, - isSwipeActive : f32, -}; - -@group(0) @binding(0) var settings : Settings; -@group(0) @binding(1) var Sampler: sampler; -@group(0) @binding(2) var trailMap : texture_2d; - -@fragment -fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { - var current = textureSample(trailMap, Sampler, uv); - - if ( - settings.isSwipeActive == 1.0 && - length((uv - settings.swipe) * normalize(settings.size)) < settings.swipeRadius - ) { - current = vec4(1); - } - - return current; -} diff --git a/src/renderer.ts b/src/renderer.ts index 412015a..4d3b523 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -2,9 +2,9 @@ import { Agent } from './pipelines/agents/agent'; import { AgentPipeline } from './pipelines/agents/agent-pipeline'; import { DiffusionPipeline } from './pipelines/diffusion/diffusion-pipeline'; import { RenderPipeline } from './pipelines/render/render-pipeline'; -import { SwipePipeline } from './pipelines/swipe/swipe-pipeline'; import { settings } from './settings'; import { randomBetween } from './utils/random-between'; +import { sleep } from './utils/sleep'; import { vec2 } from 'gl-matrix'; @@ -17,7 +17,6 @@ export default class Renderer { private agentPipeline: AgentPipeline; private renderPipeline: RenderPipeline; private diffusionPipeline: DiffusionPipeline; - private swipePipeline: SwipePipeline; private preferredCanvasFormat: GPUTextureFormat; private trailMapA?: GPUTexture; @@ -38,16 +37,15 @@ export default class Renderer { window.addEventListener('mousedown', (_) => (this.isSwipeActive = true)); window.addEventListener('mouseup', (_) => (this.isSwipeActive = false)); - requestAnimationFrame(this.render.bind(this)); - this.agentPipeline = new AgentPipeline(this.device, this.spawnAgents()); this.renderPipeline = new RenderPipeline( this.context, this.device, this.preferredCanvasFormat ); - this.swipePipeline = new SwipePipeline(this.device); this.diffusionPipeline = new DiffusionPipeline(this.device); + + requestAnimationFrame(this.render.bind(this)); } private onSwipe(event: MouseEvent) { @@ -130,45 +128,37 @@ export default class Renderer { }); } - private render(time: DOMHighResTimeStamp) { + private async render(time: DOMHighResTimeStamp) { const deltaTime = this.calculateDeltaTime(time); this.agentPipeline.setParameters({ + ...settings, width: this.canvas.width, height: this.canvas.height, time, deltaTime, - ...settings, - }); - this.swipePipeline.setParameters({ - width: this.canvas.width, - height: this.canvas.height, - isSwipeActive: this.isSwipeActive, - swipe: this.swipeLocation, - ...settings, }); this.diffusionPipeline.setParameters({ + ...settings, width: this.canvas.width, height: this.canvas.height, deltaTime, - ...settings, + time, + isSwipeActive: this.isSwipeActive, + swipe: this.swipeLocation, }); const commandEncoder = this.device.createCommandEncoder(); for (let i = 0; i < settings.renderSpeed; i++) { this.agentPipeline.execute(commandEncoder, this.trailMapA, this.trailMapB); this.diffusionPipeline.execute(commandEncoder, this.trailMapB, this.trailMapA); - if (this.isSwipeActive) { - this.swipePipeline.execute(commandEncoder, this.trailMapA, this.trailMapB); - this.renderPipeline.execute(commandEncoder, this.trailMapB); - } else { - this.renderPipeline.execute(commandEncoder, this.trailMapA); - [this.trailMapA, this.trailMapB] = [this.trailMapB, this.trailMapA]; - } + this.renderPipeline.execute(commandEncoder, this.trailMapA); + [this.trailMapA, this.trailMapB] = [this.trailMapB, this.trailMapA]; } this.queue.submit([commandEncoder.finish()]); + // await sleep(1000); requestAnimationFrame(this.render.bind(this)); } diff --git a/src/settings.ts b/src/settings.ts index e593341..6ed08ca 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -10,20 +10,23 @@ interface Settings { sensorAngleDegrees: number; sensorOffsetDst: number; swipeRadius: number; + swipeBlur: number; } export const settings: Settings = { - agentCount: 1_000_000, + agentCount: 1_000, renderSpeed: 1, startingRadius: 0.15, - trailWeight: 5, decayRate: 0.02, diffusionRate: 0.8, + trailWeight: 5, moveSpeed: 0.025, turnSpeed: 6, sensorAngleDegrees: 30, sensorOffsetDst: 0.025, - swipeRadius: 0.005, + + swipeRadius: 0.003, + swipeBlur: 0.002, }; diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts new file mode 100644 index 0000000..b358bfa --- /dev/null +++ b/src/utils/sleep.ts @@ -0,0 +1,3 @@ +export const sleep = (ms: number): Promise => { + return new Promise((resolve, _) => setTimeout(resolve, ms)); +};