import { getPath } from "./common.js"; function test(position) { const x = position[0] * 4 * Math.PI; const y = position[1] * 4 * Math.PI; return Math.cos(y - 3 + Math.sin(x)) * 0.5 + 0.5; } function length(vector) { return Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); } function sub(a, b) { return [ a[0] - b[0], a[1] - b[1], ]; } function smoothing(radius, distance) { const volume = Math.PI * Math.pow(radius, 8) / 4; const value = Math.max(0, radius * radius - distance * distance); return value * value * value / volume; } class FlatRenderer { 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.particleBuffers = []; this.particleBindGroups = []; this.particleConfiguration = new Float32Array(3 * 4); this.particleConfigurationBuffers = []; for (let i = 0; i < 2; i++) { const buffer = device.createBuffer({ label: `particle buffer ${i}`, size: this.particles.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, }); this.particleBuffers.push(buffer); const configBuffer = device.createBuffer({ label: `particle configuration buffer ${i}`, size: this.particleConfiguration.byteLength, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }); this.particleConfigurationBuffers.push(configBuffer); const bindGroup = device.createBindGroup({ label: `particle buffer bind group ${i}`, layout: this.bindGroupLayout.particle, entries: [{ binding: 0, resource: { buffer: configBuffer }, }, { binding: 1, resource: { buffer: buffer }, }], }); 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); } } } constructor(device, canvasFormat, viewUniformBuffer, shaderModule) { const label = "flat"; ////////////////////////////////////////////////////////////////////// // buffer ////////////////////////////////////////////////////////////////////// const array = new Uint16Array(6); array[0] = 0; array[1] = 1; array[2] = 2; array[3] = 0; array[4] = 3; array[5] = 1; this.buffer = device.createBuffer({ label: `${label} renderer buffer`, size: array.byteLength, usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, }); device.queue.writeBuffer(this.buffer, 0, array, 0, array.length); ////////////////////////////////////////////////////////////////////// // pipeline ////////////////////////////////////////////////////////////////////// this.bindGroupLayout = { view: device.createBindGroupLayout({ label: `${label} bind group layout view`, entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } }] }), particle: device.createBindGroupLayout({ label: `${label} bind group layout particle`, entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } }, { binding: 1, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "read-only-storage" } }] }), }; const pipelineLayout = device.createPipelineLayout({ label: `${label} pipeline layout`, bindGroupLayouts: [ this.bindGroupLayout.view, this.bindGroupLayout.particle ], }); this.renderPipeline = device.createRenderPipeline({ label: `${label} pipeline`, layout: pipelineLayout, vertex: { module: shaderModule, entryPoint: "vertexMain", }, fragment: { module: shaderModule, entryPoint: "fragmentMain", targets: [{ format: canvasFormat, }] }, primitive: { topology: "triangle-list", }, depthStencil: { depthWriteEnabled: true, depthCompare: "less", format: "depth24plus", }, }); ////////////////////////////////////////////////////////////////////// // particles ////////////////////////////////////////////////////////////////////// this.createParticles(device); ////////////////////////////////////////////////////////////////////// // bind group ////////////////////////////////////////////////////////////////////// this.bindGroups = [ device.createBindGroup({ label: `${label} bind group`, layout: this.bindGroupLayout.view, entries: [{ binding: 0, resource: { buffer: viewUniformBuffer }, }], }), ]; } positionDensity(position, circleRadius, particleCount) { 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; } return density; } updateParticles(device, frameNumber, configuration) { 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, configuration.circleRadius, configuration.particleCount); // value.y this.particles[i * this.particleStride + 5] = density; } device.queue.writeBuffer(this.particleBuffers[frameNumber], 0, this.particles, 0, this.particles.length); const PARTICLE = 0; const CIRCLE = 1; const MOUSE = 2; const config = this.particleConfiguration; config[PARTICLE * 4 + 0] = configuration.particleCount; config[PARTICLE * 4 + 1] = configuration.particleSize; config[PARTICLE * 4 + 2] = configuration.particleMass1; config[PARTICLE * 4 + 3] = configuration.particleMass2; config[CIRCLE * 4 + 0] = configuration.circleRadius; config[CIRCLE * 4 + 1] = configuration.circleThickness; config[CIRCLE * 4 + 2] = configuration.testIntensity; config[CIRCLE * 4 + 3] = configuration.mass12Mix; 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]; device.queue.writeBuffer(this.particleConfigurationBuffers[frameNumber], 0, this.particleConfiguration, 0, this.particleConfiguration.length) } render(device, renderPass, frameNumber, configuration) { this.updateParticles(device, frameNumber, configuration); renderPass.setPipeline(this.renderPipeline); renderPass.setIndexBuffer(this.buffer, "uint16"); renderPass.setBindGroup(0, this.bindGroups[0]); renderPass.setBindGroup(1, this.particleBindGroups[frameNumber]); renderPass.drawIndexed(6); } } async function loadFlat(device, canvasFormat, viewUniformBuffer) { const flatWgsl = await getPath("flat.wgsl"); const shaderModule = device.createShaderModule({ label: "flag shader", code: flatWgsl, }); const renderer = new FlatRenderer(device, canvasFormat, viewUniformBuffer, shaderModule); return renderer; } export { loadFlat };