WIP
This commit is contained in:
parent
34ac200437
commit
39b0160064
136 changed files with 7129 additions and 1950 deletions
244
src/pipelines/eraser/eraser-agent-pipeline.ts
Normal file
244
src/pipelines/eraser/eraser-agent-pipeline.ts
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
|
||||
import { appConfig } from '../../config';
|
||||
import {
|
||||
createCachedFloat32BufferWrite,
|
||||
writeFloat32BufferIfChanged,
|
||||
} from '../../utils/graphics/cached-buffer-write';
|
||||
import { getWorkgroupCounts } from '../../utils/graphics/get-workgroup-counts';
|
||||
import { smartCompile } from '../../utils/graphics/smart-compile';
|
||||
import agentSchema from '../agents/agent-generation/agent-schema.wgsl?raw';
|
||||
import { CommonState } from '../common-state/common-state';
|
||||
import shader from './eraser-agent.wgsl?raw';
|
||||
|
||||
interface LineSegment {
|
||||
from: vec2;
|
||||
to: vec2;
|
||||
}
|
||||
|
||||
const shaderWithConfig = shader.replace(
|
||||
'const MAX_SEGMENT_COUNT = 384u;',
|
||||
`const MAX_SEGMENT_COUNT = ${Math.round(appConfig.pipelines.eraser.maxSegmentCount)}u;`
|
||||
);
|
||||
|
||||
export class EraserAgentPipeline {
|
||||
private static readonly WORKGROUP_SIZE = appConfig.pipelines.eraser.workgroupSize;
|
||||
private static readonly UNIFORM_COUNT = 4;
|
||||
private static readonly MAX_SEGMENT_COUNT = appConfig.pipelines.eraser.maxSegmentCount;
|
||||
private static readonly SEGMENT_FLOAT_COUNT =
|
||||
appConfig.pipelines.eraser.segmentFloatCount;
|
||||
|
||||
private readonly bindGroupLayout: GPUBindGroupLayout;
|
||||
private readonly bindGroup: GPUBindGroup;
|
||||
private readonly pipeline: GPUComputePipeline;
|
||||
private readonly uniforms: GPUBuffer;
|
||||
private readonly uniformValues = new Float32Array(EraserAgentPipeline.UNIFORM_COUNT);
|
||||
private readonly uniformCache = createCachedFloat32BufferWrite(
|
||||
EraserAgentPipeline.UNIFORM_COUNT
|
||||
);
|
||||
private readonly segmentsBuffer: GPUBuffer;
|
||||
private readonly segmentUploadData = new Float32Array(
|
||||
EraserAgentPipeline.MAX_SEGMENT_COUNT * EraserAgentPipeline.SEGMENT_FLOAT_COUNT
|
||||
);
|
||||
|
||||
private linePoints: Array<vec2> = [];
|
||||
private lineSegments: Array<LineSegment> = [];
|
||||
private actualSegments: Array<LineSegment> = [];
|
||||
private segmentCount = 0;
|
||||
private agentCount = 0;
|
||||
|
||||
public constructor(
|
||||
private readonly device: GPUDevice,
|
||||
private readonly commonState: CommonState,
|
||||
private readonly agentsBuffer: GPUBuffer
|
||||
) {
|
||||
this.bindGroupLayout = device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'uniform',
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'storage',
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
visibility: GPUShaderStage.COMPUTE,
|
||||
buffer: {
|
||||
type: 'read-only-storage',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
this.uniforms = this.device.createBuffer({
|
||||
size: EraserAgentPipeline.UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
this.segmentsBuffer = this.device.createBuffer({
|
||||
size:
|
||||
EraserAgentPipeline.MAX_SEGMENT_COUNT *
|
||||
EraserAgentPipeline.SEGMENT_FLOAT_COUNT *
|
||||
Float32Array.BYTES_PER_ELEMENT,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
this.bindGroup = this.device.createBindGroup({
|
||||
layout: this.bindGroupLayout,
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: this.uniforms,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 1,
|
||||
resource: {
|
||||
buffer: this.agentsBuffer,
|
||||
},
|
||||
},
|
||||
{
|
||||
binding: 2,
|
||||
resource: {
|
||||
buffer: this.segmentsBuffer,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
this.pipeline = device.createComputePipeline({
|
||||
layout: device.createPipelineLayout({
|
||||
bindGroupLayouts: [commonState.bindGroupLayout, this.bindGroupLayout],
|
||||
}),
|
||||
compute: {
|
||||
module: smartCompile(
|
||||
device,
|
||||
CommonState.shaderCode,
|
||||
agentSchema,
|
||||
shaderWithConfig
|
||||
),
|
||||
entryPoint: 'main',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public addSwipe(position: vec2): void {
|
||||
const previousPosition = this.linePoints[this.linePoints.length - 1] ?? position;
|
||||
this.addSwipeSegment(previousPosition, position);
|
||||
this.linePoints.push(vec2.clone(position));
|
||||
}
|
||||
|
||||
public addSwipeSegment(from: vec2, to: vec2): void {
|
||||
this.lineSegments.push({
|
||||
from: vec2.clone(from),
|
||||
to: vec2.clone(to),
|
||||
});
|
||||
}
|
||||
|
||||
public clearSwipes(): void {
|
||||
this.linePoints.length = 0;
|
||||
this.lineSegments.length = 0;
|
||||
this.actualSegments.length = 0;
|
||||
this.segmentCount = 0;
|
||||
}
|
||||
|
||||
public setParameters({
|
||||
agentCount,
|
||||
eraserSize,
|
||||
}: {
|
||||
agentCount: number;
|
||||
eraserSize: number;
|
||||
}): void {
|
||||
this.agentCount = agentCount;
|
||||
this.actualSegments = this.lineSegments.slice();
|
||||
this.lineSegments.length = 0;
|
||||
|
||||
if (this.actualSegments.length > EraserAgentPipeline.MAX_SEGMENT_COUNT) {
|
||||
this.actualSegments = EraserAgentPipeline.subsampleSegments(this.actualSegments);
|
||||
}
|
||||
|
||||
this.segmentCount = Math.max(0, this.actualSegments.length);
|
||||
|
||||
const eraserRadius = eraserSize / 2;
|
||||
this.uniformValues[0] = eraserRadius;
|
||||
this.uniformValues[1] = this.segmentCount;
|
||||
this.uniformValues[2] = agentCount;
|
||||
this.uniformValues[3] = eraserRadius * eraserRadius;
|
||||
writeFloat32BufferIfChanged(
|
||||
this.device,
|
||||
this.uniforms,
|
||||
this.uniformValues,
|
||||
this.uniformCache
|
||||
);
|
||||
|
||||
if (this.segmentCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.segmentCount; i++) {
|
||||
const { from, to } = this.actualSegments[i];
|
||||
const offset = i * EraserAgentPipeline.SEGMENT_FLOAT_COUNT;
|
||||
this.segmentUploadData[offset] = from[0];
|
||||
this.segmentUploadData[offset + 1] = from[1];
|
||||
this.segmentUploadData[offset + 2] = to[0];
|
||||
this.segmentUploadData[offset + 3] = to[1];
|
||||
}
|
||||
|
||||
this.device.queue.writeBuffer(
|
||||
this.segmentsBuffer,
|
||||
0,
|
||||
this.segmentUploadData,
|
||||
0,
|
||||
this.segmentCount * EraserAgentPipeline.SEGMENT_FLOAT_COUNT
|
||||
);
|
||||
}
|
||||
|
||||
public execute(commandEncoder: GPUCommandEncoder): void {
|
||||
if (this.segmentCount === 0 || this.agentCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const passEncoder = commandEncoder.beginComputePass();
|
||||
passEncoder.setPipeline(this.pipeline);
|
||||
this.commonState.execute(passEncoder);
|
||||
passEncoder.setBindGroup(1, this.bindGroup);
|
||||
passEncoder.dispatchWorkgroups(
|
||||
...getWorkgroupCounts(
|
||||
this.device,
|
||||
this.agentCount,
|
||||
EraserAgentPipeline.WORKGROUP_SIZE
|
||||
)
|
||||
);
|
||||
passEncoder.end();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.uniforms.destroy();
|
||||
this.segmentsBuffer.destroy();
|
||||
}
|
||||
|
||||
private static subsampleSegments(segments: Array<LineSegment>): Array<LineSegment> {
|
||||
if (segments.length <= EraserAgentPipeline.MAX_SEGMENT_COUNT) {
|
||||
return segments;
|
||||
}
|
||||
|
||||
const result: Array<LineSegment> = [];
|
||||
for (let i = 0; i < EraserAgentPipeline.MAX_SEGMENT_COUNT; i++) {
|
||||
const index = Math.round(
|
||||
(i * (segments.length - 1)) / (EraserAgentPipeline.MAX_SEGMENT_COUNT - 1)
|
||||
);
|
||||
result.push(segments[index]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
63
src/pipelines/eraser/eraser-agent.wgsl
Normal file
63
src/pipelines/eraser/eraser-agent.wgsl
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
struct Settings {
|
||||
eraserRadius: f32,
|
||||
segmentCount: f32,
|
||||
agentCount: f32,
|
||||
eraserRadiusSquared: f32,
|
||||
};
|
||||
|
||||
const MAX_SEGMENT_COUNT = 384u;
|
||||
|
||||
@group(1) @binding(0) var<uniform> settings: Settings;
|
||||
@group(1) @binding(2) var<storage, read> segments: array<vec4<f32>>;
|
||||
|
||||
@compute @workgroup_size(64)
|
||||
fn main(
|
||||
@builtin(global_invocation_id) global_id: vec3<u32>,
|
||||
@builtin(num_workgroups) workgroup_count: vec3<u32>
|
||||
) {
|
||||
let id = get_id(global_id, workgroup_count);
|
||||
|
||||
if id >= u32(settings.agentCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
var agent = agents[id];
|
||||
if agent.colorIndex < 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0u; i < MAX_SEGMENT_COUNT; i++) {
|
||||
if i >= u32(settings.segmentCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
let segment = segments[i];
|
||||
let distanceSquared = distanceSquaredFromLine(
|
||||
agent.position,
|
||||
segment.xy,
|
||||
segment.zw
|
||||
);
|
||||
|
||||
if distanceSquared <= settings.eraserRadiusSquared {
|
||||
agent.position = vec2<f32>(-1.0, -1.0);
|
||||
agent.targetPosition = vec2<f32>(-1.0, -1.0);
|
||||
agent.colorIndex = -1.0;
|
||||
agents[id] = agent;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn distanceSquaredFromLine(position: vec2<f32>, start: vec2<f32>, end: vec2<f32>) -> f32 {
|
||||
let pa = position - start;
|
||||
let direction = end - start;
|
||||
let denominator = dot(direction, direction);
|
||||
|
||||
if denominator <= 0.0001 {
|
||||
return dot(pa, pa);
|
||||
}
|
||||
|
||||
let q = clamp(dot(pa, direction) / denominator, 0.0, 1.0);
|
||||
let nearestOffset = pa - direction * q;
|
||||
return dot(nearestOffset, nearestOffset);
|
||||
}
|
||||
333
src/pipelines/eraser/eraser-texture-pipeline.ts
Normal file
333
src/pipelines/eraser/eraser-texture-pipeline.ts
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
|
||||
import { appConfig } from '../../config';
|
||||
import { clamp } from '../../utils/clamp';
|
||||
import {
|
||||
createCachedFloat32BufferWrite,
|
||||
writeFloat32BufferIfChanged,
|
||||
} from '../../utils/graphics/cached-buffer-write';
|
||||
import { smartCompile } from '../../utils/graphics/smart-compile';
|
||||
import { CommonState } from '../common-state/common-state';
|
||||
import shader from './eraser-texture.wgsl?raw';
|
||||
|
||||
interface LineSegment {
|
||||
from: vec2;
|
||||
to: vec2;
|
||||
}
|
||||
|
||||
export class EraserTexturePipeline {
|
||||
private static readonly UNIFORM_COUNT = 4;
|
||||
private static readonly MAX_LINE_COUNT = appConfig.pipelines.eraser.maxTextureLineCount;
|
||||
private static readonly VERTICES_PER_LINE_SEGMENT = 6;
|
||||
private static readonly ATTRIBUTES_PER_LINE_SEGMENT = 6;
|
||||
|
||||
private readonly bindGroupLayout: GPUBindGroupLayout;
|
||||
private readonly bindGroup: GPUBindGroup;
|
||||
private readonly pipeline: GPURenderPipeline;
|
||||
private readonly uniforms: GPUBuffer;
|
||||
private readonly uniformValues = new Float32Array(EraserTexturePipeline.UNIFORM_COUNT);
|
||||
private readonly uniformCache = createCachedFloat32BufferWrite(
|
||||
EraserTexturePipeline.UNIFORM_COUNT
|
||||
);
|
||||
private readonly vertexBuffer: GPUBuffer;
|
||||
private readonly vertexUploadData = new Float32Array(
|
||||
EraserTexturePipeline.MAX_LINE_COUNT *
|
||||
EraserTexturePipeline.VERTICES_PER_LINE_SEGMENT *
|
||||
EraserTexturePipeline.ATTRIBUTES_PER_LINE_SEGMENT
|
||||
);
|
||||
|
||||
private linePoints: Array<vec2> = [];
|
||||
private lineSegments: Array<LineSegment> = [];
|
||||
private actualSegments: Array<LineSegment> = [];
|
||||
|
||||
public constructor(
|
||||
private readonly device: GPUDevice,
|
||||
private readonly commonState: CommonState
|
||||
) {
|
||||
this.bindGroupLayout = device.createBindGroupLayout({
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.FRAGMENT,
|
||||
buffer: {
|
||||
type: 'uniform',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
this.vertexBuffer = device.createBuffer({
|
||||
size:
|
||||
EraserTexturePipeline.MAX_LINE_COUNT *
|
||||
EraserTexturePipeline.VERTICES_PER_LINE_SEGMENT *
|
||||
EraserTexturePipeline.ATTRIBUTES_PER_LINE_SEGMENT *
|
||||
Float32Array.BYTES_PER_ELEMENT,
|
||||
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
this.pipeline = device.createRenderPipeline({
|
||||
layout: device.createPipelineLayout({
|
||||
bindGroupLayouts: [commonState.bindGroupLayout, this.bindGroupLayout],
|
||||
}),
|
||||
vertex: {
|
||||
module: smartCompile(device, CommonState.shaderCode, shader),
|
||||
entryPoint: 'vertex',
|
||||
buffers: [
|
||||
{
|
||||
arrayStride: Float32Array.BYTES_PER_ELEMENT * 6,
|
||||
attributes: [
|
||||
{
|
||||
shaderLocation: 0,
|
||||
format: 'float32x2',
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
shaderLocation: 1,
|
||||
format: 'float32x2',
|
||||
offset: Float32Array.BYTES_PER_ELEMENT * 2,
|
||||
},
|
||||
{
|
||||
shaderLocation: 2,
|
||||
format: 'float32x2',
|
||||
offset: Float32Array.BYTES_PER_ELEMENT * 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
fragment: {
|
||||
module: smartCompile(device, CommonState.shaderCode, shader),
|
||||
entryPoint: 'fragment',
|
||||
targets: [
|
||||
{
|
||||
format: 'rgba16float',
|
||||
},
|
||||
],
|
||||
},
|
||||
primitive: {
|
||||
topology: 'triangle-list',
|
||||
},
|
||||
});
|
||||
|
||||
this.uniforms = this.device.createBuffer({
|
||||
size: EraserTexturePipeline.UNIFORM_COUNT * Float32Array.BYTES_PER_ELEMENT,
|
||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
this.bindGroup = this.device.createBindGroup({
|
||||
layout: this.bindGroupLayout,
|
||||
entries: [
|
||||
{
|
||||
binding: 0,
|
||||
resource: {
|
||||
buffer: this.uniforms,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
public addSwipe(position: vec2): void {
|
||||
const previousPosition = this.linePoints[this.linePoints.length - 1] ?? position;
|
||||
this.addSwipeSegment(previousPosition, position);
|
||||
this.linePoints.push(vec2.clone(position));
|
||||
}
|
||||
|
||||
public addSwipeSegment(from: vec2, to: vec2): void {
|
||||
this.lineSegments.push({
|
||||
from: vec2.clone(from),
|
||||
to: vec2.clone(to),
|
||||
});
|
||||
}
|
||||
|
||||
public clearSwipes(): void {
|
||||
this.linePoints.length = 0;
|
||||
this.lineSegments.length = 0;
|
||||
this.actualSegments.length = 0;
|
||||
}
|
||||
|
||||
public setParameters({ eraserSize }: { eraserSize: number }): void {
|
||||
const eraserRadius = eraserSize / 2;
|
||||
|
||||
this.uniformValues[0] = eraserRadius;
|
||||
this.uniformValues[1] = eraserRadius * eraserRadius;
|
||||
this.uniformValues[2] = 0;
|
||||
this.uniformValues[3] = 0;
|
||||
writeFloat32BufferIfChanged(
|
||||
this.device,
|
||||
this.uniforms,
|
||||
this.uniformValues,
|
||||
this.uniformCache
|
||||
);
|
||||
|
||||
this.actualSegments = this.lineSegments.slice();
|
||||
this.lineSegments.length = 0;
|
||||
|
||||
if (this.actualSegments.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.actualSegments.length > EraserTexturePipeline.MAX_LINE_COUNT) {
|
||||
this.actualSegments = EraserTexturePipeline.subsampleSegments(this.actualSegments);
|
||||
}
|
||||
|
||||
const lineCount = this.lineCount;
|
||||
let floatOffset = 0;
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const segment = this.actualSegments[i];
|
||||
floatOffset = this.writeSegmentVertices(
|
||||
this.vertexUploadData,
|
||||
floatOffset,
|
||||
segment.from,
|
||||
segment.to,
|
||||
eraserRadius
|
||||
);
|
||||
}
|
||||
|
||||
this.device.queue.writeBuffer(
|
||||
this.vertexBuffer,
|
||||
0,
|
||||
this.vertexUploadData,
|
||||
0,
|
||||
floatOffset
|
||||
);
|
||||
}
|
||||
|
||||
public execute(commandEncoder: GPUCommandEncoder, textureOut: GPUTextureView): void {
|
||||
if (this.lineCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renderPassDescriptor: GPURenderPassDescriptor = {
|
||||
colorAttachments: [
|
||||
{
|
||||
view: textureOut,
|
||||
loadOp: 'load',
|
||||
storeOp: 'store',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
|
||||
passEncoder.setPipeline(this.pipeline);
|
||||
this.commonState.execute(passEncoder);
|
||||
passEncoder.setBindGroup(1, this.bindGroup);
|
||||
passEncoder.setVertexBuffer(0, this.vertexBuffer);
|
||||
passEncoder.draw(EraserTexturePipeline.VERTICES_PER_LINE_SEGMENT * this.lineCount, 1);
|
||||
passEncoder.end();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.vertexBuffer.destroy();
|
||||
this.uniforms.destroy();
|
||||
}
|
||||
|
||||
private static subsampleSegments(segments: Array<LineSegment>): Array<LineSegment> {
|
||||
if (segments.length <= EraserTexturePipeline.MAX_LINE_COUNT) {
|
||||
return segments;
|
||||
}
|
||||
|
||||
const result: Array<LineSegment> = [];
|
||||
for (let i = 0; i < EraserTexturePipeline.MAX_LINE_COUNT; i++) {
|
||||
const index = Math.round(
|
||||
(i * (segments.length - 1)) / (EraserTexturePipeline.MAX_LINE_COUNT - 1)
|
||||
);
|
||||
result.push(segments[index]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private writeSegmentVertices(
|
||||
target: Float32Array,
|
||||
offset: number,
|
||||
from: vec2,
|
||||
to: vec2,
|
||||
width: number
|
||||
): number {
|
||||
const dx = to[0] - from[0];
|
||||
const dy = to[1] - from[1];
|
||||
const length = Math.hypot(dx, dy);
|
||||
const directionX = length > 0 ? dx / length : 1;
|
||||
const directionY = length > 0 ? dy / length : 0;
|
||||
const scaledDirectionX = directionX * width;
|
||||
const scaledDirectionY = directionY * width;
|
||||
const perpendicularX = directionY * width;
|
||||
const perpendicularY = -directionX * width;
|
||||
|
||||
const startX = from[0] - scaledDirectionX;
|
||||
const startY = from[1] - scaledDirectionY;
|
||||
const endX = to[0] + scaledDirectionX;
|
||||
const endY = to[1] + scaledDirectionY;
|
||||
|
||||
offset = this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
startX + perpendicularX,
|
||||
startY + perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
offset = this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
startX - perpendicularX,
|
||||
startY - perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
offset = this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
endX + perpendicularX,
|
||||
endY + perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
offset = this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
startX - perpendicularX,
|
||||
startY - perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
offset = this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
endX + perpendicularX,
|
||||
endY + perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
return this.writeVertex(
|
||||
target,
|
||||
offset,
|
||||
endX - perpendicularX,
|
||||
endY - perpendicularY,
|
||||
from,
|
||||
to
|
||||
);
|
||||
}
|
||||
|
||||
private writeVertex(
|
||||
target: Float32Array,
|
||||
offset: number,
|
||||
screenX: number,
|
||||
screenY: number,
|
||||
from: vec2,
|
||||
to: vec2
|
||||
): number {
|
||||
target[offset++] = screenX;
|
||||
target[offset++] = screenY;
|
||||
target[offset++] = from[0];
|
||||
target[offset++] = from[1];
|
||||
target[offset++] = to[0];
|
||||
target[offset++] = to[1];
|
||||
return offset;
|
||||
}
|
||||
|
||||
private get lineCount(): number {
|
||||
return clamp(this.actualSegments.length, 0, EraserTexturePipeline.MAX_LINE_COUNT);
|
||||
}
|
||||
}
|
||||
53
src/pipelines/eraser/eraser-texture.wgsl
Normal file
53
src/pipelines/eraser/eraser-texture.wgsl
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
struct Settings {
|
||||
eraserRadius: f32,
|
||||
eraserRadiusSquared: f32,
|
||||
padding1: f32,
|
||||
padding2: f32,
|
||||
};
|
||||
|
||||
@group(1) @binding(0) var<uniform> settings: Settings;
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
@location(1) start: vec2<f32>,
|
||||
@location(2) end: vec2<f32>
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vertex(
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
@location(1) @interpolate(flat) start: vec2<f32>,
|
||||
@location(2) @interpolate(flat) end: vec2<f32>
|
||||
) -> VertexOutput {
|
||||
let uv = screenPosition / state.size;
|
||||
let position = vec2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
|
||||
return VertexOutput(vec4(position, 0.0, 1.0), screenPosition, start, end);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
@location(0) screenPosition: vec2<f32>,
|
||||
@location(1) start: vec2<f32>,
|
||||
@location(2) end: vec2<f32>
|
||||
) -> @location(0) vec4<f32> {
|
||||
if distanceSquaredFromLine(screenPosition, start, end) > settings.eraserRadiusSquared {
|
||||
discard;
|
||||
}
|
||||
|
||||
return vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
fn distanceSquaredFromLine(position: vec2<f32>, start: vec2<f32>, end: vec2<f32>) -> f32 {
|
||||
let pa = position - start;
|
||||
let direction = end - start;
|
||||
let denominator = dot(direction, direction);
|
||||
|
||||
if denominator <= 0.0001 {
|
||||
return dot(pa, pa);
|
||||
}
|
||||
|
||||
let q = clamp(dot(pa, direction) / denominator, 0.0, 1.0);
|
||||
let nearestOffset = pa - direction * q;
|
||||
return dot(nearestOffset, nearestOffset);
|
||||
}
|
||||
Loading…
Reference in a new issue