struct Settings { eraserRadiusSquared: f32, lineDistanceEpsilon: f32, clearRed: f32, clearGreen: f32, clearBlue: f32, clearAlpha: f32, eraserRadius: f32, padding1: f32, }; @group(1) @binding(0) var settings: Settings; struct VertexOutput { @builtin(position) position: vec4, @location(0) screenPosition: vec2, @location(1) @interpolate(flat) start: vec2, @location(2) @interpolate(flat) direction: vec2, @location(3) @interpolate(flat) inverseLengthSquared: f32, } struct EraserTextureTargets { @location(0) source: vec4, @location(1) influence: vec4, @location(2) trail: vec4, } struct EraserCombinedTargets { @location(0) mask: vec4, @location(1) source: vec4, @location(2) influence: vec4, @location(3) trail: vec4, } @vertex fn vertex( @builtin(vertex_index) vertexIndex: u32, @location(0) start: vec2, @location(1) end: vec2 ) -> VertexOutput { let direction = end - start; let denominator = dot(direction, direction); var inverseLengthSquared = 0.0; if denominator > settings.lineDistanceEpsilon { inverseLengthSquared = 1.0 / denominator; } let screenPosition = segment_vertex_position(vertexIndex, start, end, settings.eraserRadius); 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, direction, inverseLengthSquared); } @fragment fn fragment( @location(0) screenPosition: vec2, @location(1) @interpolate(flat) start: vec2, @location(2) @interpolate(flat) direction: vec2, @location(3) @interpolate(flat) inverseLengthSquared: f32 ) -> @location(0) vec4 { if shouldDiscardEraserFragment(screenPosition, start, direction, inverseLengthSquared) { discard; } return getEraserClearValue(); } @fragment fn fragmentMrt( @location(0) screenPosition: vec2, @location(1) @interpolate(flat) start: vec2, @location(2) @interpolate(flat) direction: vec2, @location(3) @interpolate(flat) inverseLengthSquared: f32 ) -> EraserTextureTargets { if shouldDiscardEraserFragment(screenPosition, start, direction, inverseLengthSquared) { discard; } let cleared = getEraserClearValue(); return EraserTextureTargets(cleared, cleared, cleared); } @fragment fn fragmentCombined( @location(0) screenPosition: vec2, @location(1) @interpolate(flat) start: vec2, @location(2) @interpolate(flat) direction: vec2, @location(3) @interpolate(flat) inverseLengthSquared: f32 ) -> EraserCombinedTargets { if shouldDiscardEraserFragment(screenPosition, start, direction, inverseLengthSquared) { discard; } let cleared = getEraserClearValue(); return EraserCombinedTargets(getEraserMaskValue(), cleared, cleared, cleared); } fn getEraserMaskValue() -> vec4 { return vec4(settings.clearAlpha, 0.0, 0.0, 1.0); } fn getEraserClearValue() -> vec4 { return vec4( settings.clearRed, settings.clearGreen, settings.clearBlue, settings.clearAlpha ); } fn shouldDiscardEraserFragment( screenPosition: vec2, start: vec2, direction: vec2, inverseLengthSquared: f32 ) -> bool { return distanceSquaredFromLine(screenPosition, start, direction, inverseLengthSquared) > settings.eraserRadiusSquared; } fn distanceSquaredFromLine( position: vec2, start: vec2, direction: vec2, inverseLengthSquared: f32 ) -> f32 { let pa = position - start; if inverseLengthSquared <= 0.0 { return dot(pa, pa); } let q = clamp(dot(pa, direction) * inverseLengthSquared, 0.0, 1.0); let nearestOffset = pa - direction * q; return dot(nearestOffset, nearestOffset); } fn segment_vertex_position( vertexIndex: u32, start: vec2, end: vec2, radius: f32 ) -> vec2 { let directionVector = end - start; let segmentLength = length(directionVector); var direction = vec2(1.0, 0.0); if segmentLength > 0.0 { direction = directionVector / segmentLength; } let perpendicular = vec2(direction.y, -direction.x); let corner = segment_vertex_corner(vertexIndex % 6u); let center = mix(start, end, (corner.x + 1.0) * 0.5); return center + direction * corner.x * radius + perpendicular * corner.y * radius; } fn segment_vertex_corner(index: u32) -> vec2 { if index == 0u { return vec2(-1.0, 1.0); } if index == 1u || index == 3u { return vec2(-1.0, -1.0); } if index == 2u || index == 4u { return vec2(1.0, 1.0); } return vec2(1.0, -1.0); }