facett-flow/src/flow_ext.wgsl
// Composed after flow.wgsl. Plain entry points and group 0 stay byte-for-byte intact.
struct Extra { ok:vec4<f32>, slow:vec4<f32>, error:vec4<f32>, options:vec4<f32> }
@group(1) @binding(0) var<uniform> extra:Extra;
@group(1) @binding(1) var<storage,read> node_attr:array<vec4<f32>>;
struct EdgeAttr {speed:f32, packed:u32}
@group(1) @binding(2) var<storage,read> edge_attr:array<EdgeAttr>;
fn edge_status(i:u32)->f32 {return f32((edge_attr[i].packed>>8u)&3u);}
fn status_colour(code:f32, plain:vec4<f32>)->vec4<f32> {
if !STATUS_ENABLED {return plain;}
if code==1.0 {return extra.ok;} if code==2.0 {return extra.slow;} if code==3.0 {return extra.error;} return plain;
}
fn end_x(index:u32, other:u32)->vec2<f32> {
let p=points[index].pos; let delta=points[other].pos-p; var size=vec2(0.0);if BOXES_ENABLED {size=node_attr[index].xy;}
if abs(delta.y)>abs(delta.x) {return project(p+vec2(0.0,sign(delta.y)*size.y*0.5));}
return project(p+vec2(sign(delta.x)*size.x*0.5,0.0));
}
struct NodeVary { @builtin(position) pos:vec4<f32>, @location(0) local:vec2<f32>, @location(1) colour:vec4<f32>, @location(2) shape:vec3<f32> }
fn node_hidden()->NodeVary {return NodeVary(vec4(2.0,2.0,0.0,1.0),vec2(0.0),vec4(0.0),vec3(0.0));}
@vertex fn node_vs_x(@builtin(vertex_index) v:u32,@builtin(instance_index) i:u32)->NodeVary {
let n=points[i]; var attr=node_attr[i];if !BOXES_ENABLED {attr.x=0.0;attr.y=0.0;}
if n.kind>2.5 && attr.x==0.0 {return node_hidden();}
let p=project(n.pos); var extent=vec2(clamp(n.radius*u.motion.x,1.2,14.0));
if attr.x>0.0 {extent=attr.xy*u.motion.x*0.5;}
if outside(p-extent,p+extent) {return node_hidden();}
var col=u.middle_col; if n.kind<0.5 {col=u.input_col;} else if n.kind>1.5 {col=u.output_col;}
var local=corner(v);var shape=vec3(0.0);if attr.x>0.0 {local*=extent;shape=vec3(extent,1.0);}
return NodeVary(clip(p+corner(v)*extent),local,status_colour(attr.z,col),shape);
}
@fragment fn node_fs_x(v:NodeVary)->@location(0) vec4<f32> {
if BOXES_ENABLED && v.shape.z>0.5 {
let radius=min(4.0*extra.options.z,min(v.shape.x,v.shape.y));
let q=abs(v.local)-v.shape.xy+vec2(radius);
let distance=length(max(q,vec2(0.0)))+min(max(q.x,q.y),0.0)-radius;
return vec4(v.colour.rgb,v.colour.a*(1.0-smoothstep(-0.5,0.5,distance)));
}
return vec4(v.colour.rgb,v.colour.a*(1.0-smoothstep(0.65,1.0,length(v.local))));
}
struct EdgeVary { @builtin(position) pos:vec4<f32>, @location(0) local:vec2<f32>, @location(1) colour:vec4<f32>, @location(2) motion:vec3<f32> }
@vertex fn edge_vs_x(@builtin(vertex_index) v:u32,@builtin(instance_index) i:u32)->EdgeVary {
let e=links[i]; let a=end_x(e.a_index,e.b_index); let b=end_x(e.b_index,e.a_index); let attr=rate_attr(i);
if outside(a,b) {return EdgeVary(vec4(2.0,2.0,0.0,1.0),vec2(0.0),vec4(0.0),vec3(0.0));}
let segment=v/6u; let c=corner(v%6u);
let p=curve(a,b,f32(segment)/u.detail.x); let q=curve(a,b,f32(segment+1u)/u.detail.x);
let direction=normalize(q-p+vec2(0.0001,0.0));let normal=vec2(-direction.y,direction.x);
let pos=mix(p,q,(c.x+1.0)*0.5)+normal*c.y*0.65*extra.options.z;
var col=status_colour(attr.z,u.edge_col);col.a*=u.motion.w;
var count=attr.y;if LOD_ENABLED && distance(a,b)<extra.options.w*extra.options.z {count=0.0;}
return EdgeVary(clip(pos),vec2((f32(segment)+(c.x+1.0)*0.5)/u.detail.x,e.phase),col,vec3(attr.x,count,attr.w));
}
@fragment fn edge_fs_x(v:EdgeVary)->@location(0) vec4<f32> {
var phase=v.local.y;if v.motion.z>0.5 {phase=0.0;}
let head=fract(u.motion.y*v.motion.x+phase);let behind=fract(head-v.local.x);
var trail=(1.0-smoothstep(0.0,0.16,behind))*u.motion.z;if v.motion.y<0.5 {trail=0.0;}
return vec4(mix(v.colour.rgb,u.pulse_col.rgb,trail),v.colour.a*(0.5+2.0*trail));
}
@vertex fn pulse_vs_x(@builtin(vertex_index) v:u32,@builtin(instance_index) instance:u32)->Vary {
let stride=min(MAX_PACKETS,u32(extra.options.y));let i=instance/stride;let j=instance%stride;let attr=rate_attr(i);
if f32(j)>=attr.y || u.motion.z<0.5 {return hidden();}
let e=links[i];let a=end_x(e.a_index,e.b_index);let b=end_x(e.b_index,e.a_index);if outside(a,b) || (LOD_ENABLED && distance(a,b)<extra.options.w*extra.options.z) {return hidden();}
var phase=e.phase;if attr.w>0.5 {phase=0.0;}
let t=fract(f32(j)/max(attr.y,1.0)+u.motion.y*attr.x+phase);let p=curve(a,b,t);let c=corner(v);
return Vary(clip(p+c*2.8*extra.options.z),c,u.pulse_col);
}
@vertex fn arrow_vs_x(@builtin(vertex_index) v:u32,@builtin(instance_index) i:u32)->Vary {
let e=links[i];let a=end_x(e.a_index,e.b_index);let b=end_x(e.b_index,e.a_index);
if !ARROWS_ENABLED || outside(a,b) {return hidden();}
let d=normalize(b-curve(a,b,0.95)+vec2(0.0001,0.0));let n=vec2(-d.y,d.x);
let triangle=array<vec2<f32>,3>(b,b-d*8.0*extra.options.z+n*4.0*extra.options.z,b-d*8.0*extra.options.z-n*4.0*extra.options.z);
return Vary(clip(triangle[v]),vec2(0.0),status_colour(edge_status(i),u.edge_col));
}
@fragment fn arrow_fs_x(v:Vary)->@location(0) vec4<f32> {return v.colour;}