gradient arrows
This commit is contained in:
parent
b198b61bb7
commit
1a168698a5
130
flat.js
130
flat.js
@ -28,25 +28,69 @@ function smoothing(radius, distance)
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FlatRenderer {
|
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)
|
createParticles(device)
|
||||||
{
|
{
|
||||||
this.maxDim = 32;
|
this.maxDim = 32;
|
||||||
this.maxParticles = this.maxDim * this.maxDim;
|
this.maxParticles = this.maxDim * this.maxDim;
|
||||||
this.particleStride = 4 * 2; // in elements
|
this.particleStride = 4 * 2; // in elements
|
||||||
this.particles = new Float32Array(this.maxParticles * this.particleStride);
|
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.particleBuffers = [];
|
||||||
|
this.gradientBuffers = [];
|
||||||
this.particleBindGroups = [];
|
this.particleBindGroups = [];
|
||||||
|
|
||||||
this.particleConfiguration = new Float32Array(3 * 4);
|
this.particleConfiguration = new Float32Array(3 * 4);
|
||||||
this.particleConfigurationBuffers = [];
|
this.particleConfigurationBuffers = [];
|
||||||
|
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
const buffer = device.createBuffer({
|
const particleBuffer = device.createBuffer({
|
||||||
label: `particle buffer ${i}`,
|
label: `particle buffer ${i}`,
|
||||||
size: this.particles.byteLength,
|
size: this.particles.byteLength,
|
||||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
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({
|
const configBuffer = device.createBuffer({
|
||||||
label: `particle configuration buffer ${i}`,
|
label: `particle configuration buffer ${i}`,
|
||||||
@ -63,26 +107,24 @@ class FlatRenderer {
|
|||||||
resource: { buffer: configBuffer },
|
resource: { buffer: configBuffer },
|
||||||
}, {
|
}, {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
resource: { buffer: buffer },
|
resource: { buffer: particleBuffer },
|
||||||
|
}, {
|
||||||
|
binding: 2,
|
||||||
|
resource: { buffer: gradientBuffer },
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
this.particleBindGroups.push(bindGroup);
|
this.particleBindGroups.push(bindGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let y = 0; y < this.maxDim; y++) {
|
for (let i = 0; i < this.maxParticles; i++) {
|
||||||
for (let x = 0; x < this.maxDim; x++) {
|
// position
|
||||||
const index = y * this.maxDim + x;
|
const position = [Math.random(), Math.random()];
|
||||||
//this.particles[index * this.particleStride + 0] = (x / this.maxDim) + 0.5 / this.maxDim;
|
this.particles[i * this.particleStride + 0] = position[0];
|
||||||
//this.particles[index * this.particleStride + 1] = (y / this.maxDim) + 0.5 / this.maxDim;
|
this.particles[i * this.particleStride + 1] = position[1];
|
||||||
// position
|
this.particles[i * this.particleStride + 2] = 0.0;
|
||||||
const position = [Math.random(), Math.random()];
|
this.particles[i * this.particleStride + 3] = 1.0;
|
||||||
this.particles[index * this.particleStride + 0] = position[0];
|
// value.x
|
||||||
this.particles[index * this.particleStride + 1] = position[1];
|
this.particles[i * this.particleStride + 4] = test(position);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +175,10 @@ class FlatRenderer {
|
|||||||
binding: 1,
|
binding: 1,
|
||||||
visibility: GPUShaderStage.FRAGMENT,
|
visibility: GPUShaderStage.FRAGMENT,
|
||||||
buffer: { type: "read-only-storage" }
|
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;
|
const radius = configuration.circleRadius;
|
||||||
for (var i = 0; i < particleCount; i++) {
|
const count = configuration.particleCount;
|
||||||
const particlePosition = [this.particles[i * this.particleStride + 0],
|
|
||||||
this.particles[i * this.particleStride + 1]];
|
// calculate particle gradients
|
||||||
const distance = length(sub(position, particlePosition));
|
for (let y = 0; y < this.maxGradientsDim; y++) {
|
||||||
const influence = smoothing(circleRadius, distance);
|
for (let x = 0; x < this.maxGradientsDim; x++) {
|
||||||
density += influence;
|
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)
|
updateParticles(device, frameNumber, configuration)
|
||||||
@ -209,7 +275,7 @@ class FlatRenderer {
|
|||||||
for (var i = 0; i < configuration.particleCount; i++) {
|
for (var i = 0; i < configuration.particleCount; i++) {
|
||||||
const position = [this.particles[i * this.particleStride + 0],
|
const position = [this.particles[i * this.particleStride + 0],
|
||||||
this.particles[i * this.particleStride + 1]];
|
this.particles[i * this.particleStride + 1]];
|
||||||
const density = this.positionDensity(position,
|
const density = this.particleDensity(position,
|
||||||
configuration.circleRadius,
|
configuration.circleRadius,
|
||||||
configuration.particleCount);
|
configuration.particleCount);
|
||||||
// value.y
|
// value.y
|
||||||
@ -218,7 +284,10 @@ class FlatRenderer {
|
|||||||
|
|
||||||
device.queue.writeBuffer(this.particleBuffers[frameNumber], 0,
|
device.queue.writeBuffer(this.particleBuffers[frameNumber], 0,
|
||||||
this.particles, 0, this.particles.length);
|
this.particles, 0, this.particles.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfiguration(device, frameNumber, configuration)
|
||||||
|
{
|
||||||
const PARTICLE = 0;
|
const PARTICLE = 0;
|
||||||
const CIRCLE = 1;
|
const CIRCLE = 1;
|
||||||
const MOUSE = 2;
|
const MOUSE = 2;
|
||||||
@ -236,8 +305,8 @@ class FlatRenderer {
|
|||||||
|
|
||||||
config[MOUSE * 4 + 0] = configuration.mousePosition[0];
|
config[MOUSE * 4 + 0] = configuration.mousePosition[0];
|
||||||
config[MOUSE * 4 + 1] = configuration.mousePosition[1];
|
config[MOUSE * 4 + 1] = configuration.mousePosition[1];
|
||||||
config[MOUSE * 4 + 2] = configuration.mousePosition[2];
|
config[MOUSE * 4 + 2] = configuration.lineThickness;
|
||||||
config[MOUSE * 4 + 3] = configuration.mousePosition[3];
|
config[MOUSE * 4 + 3] = configuration.lineLength;
|
||||||
|
|
||||||
device.queue.writeBuffer(this.particleConfigurationBuffers[frameNumber], 0,
|
device.queue.writeBuffer(this.particleConfigurationBuffers[frameNumber], 0,
|
||||||
this.particleConfiguration, 0, this.particleConfiguration.length)
|
this.particleConfiguration, 0, this.particleConfiguration.length)
|
||||||
@ -245,7 +314,10 @@ class FlatRenderer {
|
|||||||
|
|
||||||
render(device, renderPass, frameNumber, configuration)
|
render(device, renderPass, frameNumber, configuration)
|
||||||
{
|
{
|
||||||
|
// particle update must be before gradient update
|
||||||
this.updateParticles(device, frameNumber, configuration);
|
this.updateParticles(device, frameNumber, configuration);
|
||||||
|
this.updateGradients(device, frameNumber, configuration);
|
||||||
|
this.updateConfiguration(device, frameNumber, configuration);
|
||||||
|
|
||||||
renderPass.setPipeline(this.renderPipeline);
|
renderPass.setPipeline(this.renderPipeline);
|
||||||
renderPass.setIndexBuffer(this.buffer, "uint16");
|
renderPass.setIndexBuffer(this.buffer, "uint16");
|
||||||
|
|||||||
33
flat.wgsl
33
flat.wgsl
@ -16,9 +16,15 @@ struct ParticleConfiguration {
|
|||||||
mouse: vec4f,
|
mouse: vec4f,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Gradient {
|
||||||
|
position: vec4f,
|
||||||
|
delta: vec4f,
|
||||||
|
};
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> config: Configuration;
|
@group(0) @binding(0) var<uniform> config: Configuration;
|
||||||
@group(1) @binding(0) var<uniform> particleConfiguration: ParticleConfiguration;
|
@group(1) @binding(0) var<uniform> particleConfiguration: ParticleConfiguration;
|
||||||
@group(1) @binding(1) var<storage> particle: array<Particle>;
|
@group(1) @binding(1) var<storage> particle: array<Particle>;
|
||||||
|
@group(1) @binding(2) var<storage> gradient: array<Gradient>;
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
@builtin(vertex_index) vertex_index: u32,
|
@builtin(vertex_index) vertex_index: u32,
|
||||||
@ -74,6 +80,14 @@ fn positionDensity(position: vec2f, circleRadius: f32, particleCount: u32) -> f3
|
|||||||
return density;
|
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
|
@fragment
|
||||||
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
|
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
|
||||||
{
|
{
|
||||||
@ -89,7 +103,9 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
|
|||||||
let testIntensity = particleConfiguration.circle.z;
|
let testIntensity = particleConfiguration.circle.z;
|
||||||
let mass12Mix = particleConfiguration.circle.w;
|
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;
|
var property: f32 = 0.0;
|
||||||
|
|
||||||
@ -111,14 +127,19 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
|
|||||||
}
|
}
|
||||||
color.z = property * mix(particleMass1, particleMass2, mass12Mix);
|
color.z = property * mix(particleMass1, particleMass2, mass12Mix);
|
||||||
|
|
||||||
if (circlePosition.w != 0) {
|
for (var i: u32 = 0; i < 1024; i++) {
|
||||||
let thickness = abs(length(input.texture.xy - circlePosition.xy) - circleRadius);
|
let a = gradient[i].position.xy;
|
||||||
if (thickness < circleThickness) {
|
let b = a + gradient[i].delta.xy * lineLength;
|
||||||
//color.y = 1.0;
|
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);
|
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);
|
//return vec4(color.x, 0, 0, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,6 +129,10 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
|
<div class="row">
|
||||||
|
<span class="label">eye</span>
|
||||||
|
<input type="color" class="value">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
6
index.js
6
index.js
@ -283,6 +283,8 @@ const sliders = {
|
|||||||
circleThickness: new Slider("circle thickness", 0.0, 0.01),
|
circleThickness: new Slider("circle thickness", 0.0, 0.01),
|
||||||
testIntensity: new Slider("test intensity", 0.0, 2.0),
|
testIntensity: new Slider("test intensity", 0.0, 2.0),
|
||||||
mass12Mix: new Slider("mass12 mix", 0.0, 1.0, 1),
|
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]);
|
const mousePosition = new Float32Array([0, 0, 0, 0]);
|
||||||
@ -299,6 +301,8 @@ function updateSliders()
|
|||||||
circleThickness: sliders.circleThickness.update(),
|
circleThickness: sliders.circleThickness.update(),
|
||||||
testIntensity: sliders.testIntensity.update(),
|
testIntensity: sliders.testIntensity.update(),
|
||||||
mass12Mix: sliders.mass12Mix.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[0] = e.clientX / canvas.width;
|
||||||
mousePosition[1] = 1.0 - (e.clientY / canvas.height);
|
mousePosition[1] = 1.0 - (e.clientY / canvas.height);
|
||||||
mousePosition[2] = 0;
|
|
||||||
mousePosition[3] = 1;
|
|
||||||
}
|
}
|
||||||
canvas.addEventListener("click", onClick);
|
canvas.addEventListener("click", onClick);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user