struct CameraUniforms {
proj_mat: mat4x4<f32>,
view_mat: mat4x4<f32>,
half_frame_size: vec2<f32>,
}
@group(0) @binding(0) var<uniform> cam_uniforms : CameraUniforms;
@group(1) @binding(0) var<storage> points: array<vec4<f32>>;
@group(1) @binding(1) var<storage> fill_rgbas: array<vec4<f32>>;
@group(1) @binding(2) var<storage> stroke_rgbas: array<vec4<f32>>;
@group(1) @binding(3) var<storage> stroke_widths: array<f32>;
struct ClipBox {
min_x: i32,
max_x: i32,
min_y: i32,
max_y: i32,
max_w:i32,
}
@group(1) @binding(4) var<storage> clip_box: ClipBox;
fn point(idx: u32) -> vec2<f32> {
return points[idx].xy;
}
fn is_closed(idx: u32) -> bool {
return bool(points[idx].z);
}
struct SubpathAttr {
end_idx: u32,
nearest_idx: u32,
d: f32, // distance
sgn: f32,
debug: vec4<f32>,
}
fn cross_2d(a: vec2<f32>, b: vec2<f32>) -> f32 {
return a.x * b.y - a.y * b.x;
}
fn blend_color(f: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
let a = f.a + b.a * (1.0 - f.a);
return vec4(
f.r * f.a + b.r * b.a * (1.0 - f.a) / a,
f.g * f.a + b.g * b.a * (1.0 - f.a) / a,
f.b * f.a + b.b * b.a * (1.0 - f.a) / a,
a
);
}
fn solve_cubic(a: f32, b: f32, c: f32) -> vec3<f32> {
let p = b - a * a / 3.0;
let p3 = p * p * p;
let q = a * (2.0 * a * a - 9.0 * b) / 27.0 + c;
let d = q * q + 4.0 * p3 / 27.0;
let offset = -a / 3.0;
if (d >= 0.0) {
let z = sqrt(d);
let x = (vec2(z, -z) - q) / 2.0;
let uv = sign(x) * pow(abs(x), vec2(1.0 / 3.0));
return vec3(offset + uv.x + uv.y);
}
let v = acos(-sqrt(-27.0 / p3) * q / 2.0) / 3.0;
let m = cos(v);
let n = sin(v) * 1.732050808;
return vec3(m + m, -n - m, n - m) * sqrt(-p / 3.0) + offset;
}
fn distance_bezier(pos: vec2<f32>, A: vec2<f32>, _B: vec2<f32>, C: vec2<f32>) -> f32 {
var B = mix(_B + vec2(1e-4), _B, abs(sign(_B * 2.0 - A - C)));
// var B = _B;
let a = B - A;
let b = A - B * 2.0 + C;
let c = a * 2.0;
let d = A - pos;
let k = vec3(3.0 * dot(a, b), 2.0 * dot(a, a) + dot(d, b), dot(d, a)) / dot(b, b);
let solved = solve_cubic(k.x, k.y, k.z);
let t = vec3(
clamp(solved.x, 0.0, 1.0),
clamp(solved.y, 0.0, 1.0),
clamp(solved.z, 0.0, 1.0),
);
var ppos = A + (c + b * t.x) * t.x;
var dis = length(ppos - pos);
ppos = A + (c + b * t.y) * t.y;
dis = min(dis, length(ppos - pos));
ppos = A + (c + b * t.z) * t.z;
dis = min(dis, length(ppos - pos));
return dis;
}
fn distance_line(pos: vec2<f32>, A: vec2<f32>, B: vec2<f32>) -> f32 {
let e = B - A;
let w = pos - A;
let b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
return length(b);
}
fn sign_bezier(p: vec2<f32>, A: vec2<f32>, B: vec2<f32>, C: vec2<f32>) -> f32 {
let a: vec2<f32> = C - A;
let b: vec2<f32> = B - A;
let c: vec2<f32> = p - A;
let denominator: f32 = a.x * b.y - b.x * a.y;
let bary: vec2<f32> = vec2<f32>(cross_2d(c, b), cross_2d(a, c)) / denominator;
let d: vec2<f32> = vec2<f32>(bary.y * 0.5, 0.0) + 1.0 - bary.x - bary.y;
let sign_inside: f32 = select(1.0, sign(d.x * d.x - d.y), d.x > d.y);
let sign_left: f32 = sign_line(p, A, C);
return sign_inside * sign_left;
}
// left -> -1.0
// right -> 1.0
fn sign_line(p: vec2<f32>, A: vec2<f32>, B: vec2<f32>) -> f32 {
let cond: vec3<bool> = vec3(
p.y >= A.y,
p.y < B.y,
cross_2d(B - A, p - A) > 0.0
);
return select(1.0, -1.0, all(cond) || !any(cond));
}
fn get_subpath_attr(pos: vec2<f32>, start_idx: u32) -> SubpathAttr {
let points_len = arrayLength(&points);
var attr: SubpathAttr;
attr.end_idx = points_len;
attr.nearest_idx = 0u;
attr.d = 3.40282346638528859812e38;
attr.sgn = 1.0;
attr.debug = vec4(1.0, 1.0, 1.0, 1.0);
let n = (points_len - 1) / 2 * 2;
for (var i = start_idx; i < n; i += 2u) {
let a = point(i);
let b = point(i + 1u);
let c = point(i + 2u);
if length(b - a) == 0.0 {
attr.end_idx = i;
// attr.debug = vec4(0.0, 0.5, 0.5, 1.0);
break;
}
let v1 = normalize(b - a);
let v2 = normalize(c - b);
let is_line = abs(cross_2d(v1, v2)) < 0.0001 && dot(v1, v2) > 0.0;
// let is_line = true;
let dist = select(distance_bezier(pos, a, b, c), distance_line(pos, a, c), is_line);
if dist < attr.d {
attr.d = dist;
attr.nearest_idx = i;
}
if is_closed(i) {
attr.sgn *= select(sign_bezier(pos, a, b, c), sign_line(pos, a, c), is_line);
}
}
return attr;
}
fn render(pos: vec2<f32>) -> vec4<f32> {
let points_len = arrayLength(&points);
var idx = 0u;
var d = 3.40282346638528859812e38;
var sgn = 1.0;
var start_idx = 0u;
while start_idx < points_len {
let attr = get_subpath_attr(pos, start_idx);
if attr.d < d {
idx = attr.nearest_idx;
d = attr.d;
}
sgn *= attr.sgn;
start_idx = attr.end_idx + 2;
}
let sgn_d = sgn * d;
// return vec4(vec3(d), 1.0);
let e = point(idx + 1u).xy - point(idx).xy;
let w = pos.xy - point(idx).xy;
let ratio = clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
let anchor_index = idx / 2;
// TODO: Antialias
var fill_rgba: vec4<f32> = select(vec4(0.0), mix(fill_rgbas[anchor_index], fill_rgbas[anchor_index + 1], ratio), is_closed(idx));
fill_rgba.a *= smoothstep(1.0, -1.0, (sgn_d) );
let stroke_width = mix(stroke_widths[anchor_index], stroke_widths[anchor_index + 1], ratio);
var stroke_rgba: vec4<f32> = mix(stroke_rgbas[anchor_index], stroke_rgbas[anchor_index + 1], ratio);
stroke_rgba.a *= smoothstep(1.0, -1.0, (d - stroke_width));
var f_color = blend_color(stroke_rgba, fill_rgba);
return f_color;
}
fn render_control_points(pos: vec2<f32>) -> vec4<f32> {
let points_len = arrayLength(&points);
var d = length(pos - point(0u));
for (var i = 1u; i < points_len; i++) {
d = min(d, length(pos - point(i)));
}
return select(vec4(0.0), vec4(1.0), d < 1);
}
@fragment
fn fs_main(@location(0) pos: vec2<f32>) -> @location(0) vec4<f32> {
var f_color: vec4<f32> = vec4(1.0, 0.0, 0.0, 1.0);
f_color = render(pos);
// let attr = get_subpath_attr(pos, 14u);
// f_color = vec4(f32(attr.d));
// f_color = blend_color(f_color, render_control_points(pos));
return f_color;
}
struct VertexOutput {
@builtin(position) frag_pos: vec4<f32>,
@location(0) pos: vec2<f32>,
}
@vertex
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
var out: VertexOutput;
let x = vertex_index & 1u;
let min_x = f32(clip_box.min_x);
let max_x = f32(clip_box.max_x);
let min_y = f32(clip_box.min_y);
let max_y = f32(clip_box.max_y);
let max_w = f32(clip_box.max_w);
// let x_base = min_x - max_w;
// let x_offset = (max_x - min_x + max_w * 2.0) * f32((vertex_index >> 1u) & 1u);
// let y_base = min_y - max_w;
// let y_offset = (max_y - min_y + max_w * 2.0) * f32(vertex_index & 1u);
// let clip_point = vec2(
// x_base + x_offset,
// y_base + y_offset
// );
var clip_point: vec2<f32>;
clip_point.x = select(
max_x + max_w,
min_x - max_w,
(vertex_index & 2u) == 0u
);
clip_point.y = select(
max_y + max_w,
min_y - max_w,
(vertex_index & 1u) == 0u
);
out.frag_pos = vec4(clip_point / cam_uniforms.half_frame_size, 0.0, 1.0);
out.pos = clip_point;
return out;
}