diff --git a/flat.js b/flat.js index b20f985..c96d21c 100644 --- a/flat.js +++ b/flat.js @@ -28,25 +28,69 @@ function smoothing(radius, distance) } class FlatRenderer { + positionProperty(position, circleRadius, particleCount) + { + var property = 0.0; + for (var i = 0; i < particleCount; i++) { + const particlePosition = [this.particles[i * this.particleStride + 0], + this.particles[i * this.particleStride + 1]]; + const particleProperty = this.particles[i * this.particleStride + 4]; + const particleDensity = this.particles[i * this.particleStride + 5]; + + const distance = length(sub(position, particlePosition)); + const influence = smoothing(circleRadius, distance); + + property += particleProperty * influence / particleDensity; + } + return property; + } + + particleDensity(aPosition, circleRadius, particleCount) + { + var density = 0.0; + for (var i = 0; i < particleCount; i++) { + const bPosition = [this.particles[i * this.particleStride + 0], + this.particles[i * this.particleStride + 1]]; + const distance = length(sub(aPosition, bPosition)); + const influence = smoothing(circleRadius, distance); + density += influence; + } + return density; + } + createParticles(device) { this.maxDim = 32; this.maxParticles = this.maxDim * this.maxDim; this.particleStride = 4 * 2; // in elements this.particles = new Float32Array(this.maxParticles * this.particleStride); + + this.maxGradientsDim = 32; + this.maxGradients = this.maxGradientsDim * this.maxGradientsDim; + this.gradientStride = 4 * 2; + this.gradients = new Float32Array(this.maxGradients * this.gradientStride); + this.particleBuffers = []; + this.gradientBuffers = []; this.particleBindGroups = []; this.particleConfiguration = new Float32Array(3 * 4); this.particleConfigurationBuffers = []; for (let i = 0; i < 2; i++) { - const buffer = device.createBuffer({ + const particleBuffer = device.createBuffer({ label: `particle buffer ${i}`, size: this.particles.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, }); - this.particleBuffers.push(buffer); + this.particleBuffers.push(particleBuffer); + + const gradientBuffer = device.createBuffer({ + label: `gradient buffer ${i}`, + size: this.gradients.byteLength, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + }); + this.gradientBuffers.push(gradientBuffer); const configBuffer = device.createBuffer({ label: `particle configuration buffer ${i}`, @@ -63,26 +107,24 @@ class FlatRenderer { resource: { buffer: configBuffer }, }, { binding: 1, - resource: { buffer: buffer }, + resource: { buffer: particleBuffer }, + }, { + binding: 2, + resource: { buffer: gradientBuffer }, }], }); this.particleBindGroups.push(bindGroup); } - for (let y = 0; y < this.maxDim; y++) { - for (let x = 0; x < this.maxDim; x++) { - const index = y * this.maxDim + x; - //this.particles[index * this.particleStride + 0] = (x / this.maxDim) + 0.5 / this.maxDim; - //this.particles[index * this.particleStride + 1] = (y / this.maxDim) + 0.5 / this.maxDim; - // position - const position = [Math.random(), Math.random()]; - this.particles[index * this.particleStride + 0] = position[0]; - this.particles[index * this.particleStride + 1] = position[1]; - this.particles[index * this.particleStride + 2] = 0.0; - this.particles[index * this.particleStride + 3] = 1.0; - // value.x - this.particles[index * this.particleStride + 4] = test(position); - } + for (let i = 0; i < this.maxParticles; i++) { + // position + const position = [Math.random(), Math.random()]; + this.particles[i * this.particleStride + 0] = position[0]; + this.particles[i * this.particleStride + 1] = position[1]; + this.particles[i * this.particleStride + 2] = 0.0; + this.particles[i * this.particleStride + 3] = 1.0; + // value.x + this.particles[i * this.particleStride + 4] = test(position); } } @@ -133,6 +175,10 @@ class FlatRenderer { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "read-only-storage" } + }, { + binding: 2, + visibility: GPUShaderStage.FRAGMENT, + buffer: { type: "read-only-storage" } }] }), }; @@ -191,17 +237,37 @@ class FlatRenderer { ]; } - positionDensity(position, circleRadius, particleCount) + updateGradients(device, frameNumber, configuration) { - var density = 0.0; - for (var i = 0; i < particleCount; i++) { - const particlePosition = [this.particles[i * this.particleStride + 0], - this.particles[i * this.particleStride + 1]]; - const distance = length(sub(position, particlePosition)); - const influence = smoothing(circleRadius, distance); - density += influence; + const radius = configuration.circleRadius; + const count = configuration.particleCount; + + // calculate particle gradients + for (let y = 0; y < this.maxGradientsDim; y++) { + for (let x = 0; x < this.maxGradientsDim; x++) { + const i = y * this.maxGradientsDim + x; + const position = [(x / this.maxGradientsDim) + 0.5 / this.maxGradientsDim, + (y / this.maxGradientsDim) + 0.5 / this.maxGradientsDim]; + + const stepSize = 0.001; + const px = [position[0] + stepSize, position[1]]; + const py = [position[0], position[1] + stepSize]; + + const property = this.positionProperty(position, radius, count); + + const dx = this.positionProperty(px, radius, count) - property; + const dy = this.positionProperty(py, radius, count) - property; + + this.gradients[i * this.gradientStride + 0] = position[0]; + this.gradients[i * this.gradientStride + 1] = position[1]; + + this.gradients[i * this.gradientStride + 4] = dx / stepSize; + this.gradients[i * this.gradientStride + 5] = dy / stepSize; + } } - return density; + + device.queue.writeBuffer(this.gradientBuffers[frameNumber], 0, + this.gradients, 0, this.gradients.length); } updateParticles(device, frameNumber, configuration) @@ -209,7 +275,7 @@ class FlatRenderer { for (var i = 0; i < configuration.particleCount; i++) { const position = [this.particles[i * this.particleStride + 0], this.particles[i * this.particleStride + 1]]; - const density = this.positionDensity(position, + const density = this.particleDensity(position, configuration.circleRadius, configuration.particleCount); // value.y @@ -218,7 +284,10 @@ class FlatRenderer { device.queue.writeBuffer(this.particleBuffers[frameNumber], 0, this.particles, 0, this.particles.length); + } + updateConfiguration(device, frameNumber, configuration) + { const PARTICLE = 0; const CIRCLE = 1; const MOUSE = 2; @@ -236,8 +305,8 @@ class FlatRenderer { config[MOUSE * 4 + 0] = configuration.mousePosition[0]; config[MOUSE * 4 + 1] = configuration.mousePosition[1]; - config[MOUSE * 4 + 2] = configuration.mousePosition[2]; - config[MOUSE * 4 + 3] = configuration.mousePosition[3]; + config[MOUSE * 4 + 2] = configuration.lineThickness; + config[MOUSE * 4 + 3] = configuration.lineLength; device.queue.writeBuffer(this.particleConfigurationBuffers[frameNumber], 0, this.particleConfiguration, 0, this.particleConfiguration.length) @@ -245,7 +314,10 @@ class FlatRenderer { render(device, renderPass, frameNumber, configuration) { + // particle update must be before gradient update this.updateParticles(device, frameNumber, configuration); + this.updateGradients(device, frameNumber, configuration); + this.updateConfiguration(device, frameNumber, configuration); renderPass.setPipeline(this.renderPipeline); renderPass.setIndexBuffer(this.buffer, "uint16"); diff --git a/flat.wgsl b/flat.wgsl index 9865fed..4316cac 100644 --- a/flat.wgsl +++ b/flat.wgsl @@ -16,9 +16,15 @@ struct ParticleConfiguration { mouse: vec4f, }; +struct Gradient { + position: vec4f, + delta: vec4f, +}; + @group(0) @binding(0) var config: Configuration; @group(1) @binding(0) var particleConfiguration: ParticleConfiguration; @group(1) @binding(1) var particle: array; +@group(1) @binding(2) var gradient: array; struct VertexInput { @builtin(vertex_index) vertex_index: u32, @@ -74,6 +80,14 @@ fn positionDensity(position: vec2f, circleRadius: f32, particleCount: u32) -> f3 return density; } +fn signedDistanceSegment(p: vec2f, a: vec2f, b: vec2f, r: f32) -> f32 +{ + let ba = b - a; + let pa = p - a; + let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return length(pa - h * ba) - r; +} + @fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4f { @@ -89,7 +103,9 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f let testIntensity = particleConfiguration.circle.z; let mass12Mix = particleConfiguration.circle.w; - let circlePosition = particleConfiguration.mouse; + let circlePosition = particleConfiguration.mouse.xy; + let lineThickness = particleConfiguration.mouse.z; + let lineLength = particleConfiguration.mouse.w; var property: f32 = 0.0; @@ -111,14 +127,19 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f } color.z = property * mix(particleMass1, particleMass2, mass12Mix); - if (circlePosition.w != 0) { - let thickness = abs(length(input.texture.xy - circlePosition.xy) - circleRadius); - if (thickness < circleThickness) { - //color.y = 1.0; + for (var i: u32 = 0; i < 1024; i++) { + let a = gradient[i].position.xy; + let b = a + gradient[i].delta.xy * lineLength; + let distance = signedDistanceSegment(input.texture.xy, a, b, 0); + let headDistance = length(input.texture.xy - b); + if ((distance < lineThickness) || (headDistance < lineThickness * 3.5)) { + color.y = 1.0; } } let testColor = test(input.texture.xy); - return vec4(color.xz, testColor * testIntensity, 1.0); + color.z += testColor * testIntensity; + + return vec4(color.xyz, 1.0); //return vec4(color.x, 0, 0, 1.0); } diff --git a/index.html b/index.html index ff2534e..605c28f 100644 --- a/index.html +++ b/index.html @@ -129,6 +129,10 @@ --> +
+ eye + +
diff --git a/index.js b/index.js index 1dffee1..9d0eebe 100644 --- a/index.js +++ b/index.js @@ -283,6 +283,8 @@ const sliders = { circleThickness: new Slider("circle thickness", 0.0, 0.01), testIntensity: new Slider("test intensity", 0.0, 2.0), mass12Mix: new Slider("mass12 mix", 0.0, 1.0, 1), + lineThickness: new Slider("line thickness", 0.0, 0.005), + lineLength: new Slider("line length", 0.0, 0.025), }; const mousePosition = new Float32Array([0, 0, 0, 0]); @@ -299,6 +301,8 @@ function updateSliders() circleThickness: sliders.circleThickness.update(), testIntensity: sliders.testIntensity.update(), mass12Mix: sliders.mass12Mix.update(), + lineThickness: sliders.lineThickness.update(), + lineLength: sliders.lineLength.update(), }; } @@ -306,8 +310,6 @@ function onClick(e) { mousePosition[0] = e.clientX / canvas.width; mousePosition[1] = 1.0 - (e.clientY / canvas.height); - mousePosition[2] = 0; - mousePosition[3] = 1; } canvas.addEventListener("click", onClick);