first commit

This commit is contained in:
Hakan Seven committed 2026-03-22 14:56:27 +03:00
1 parent d52c6440ff
commit d2fdac7f1e
274 files changed
+89330

No files matched your search

+191
View File
@@ -0,0 +1,191 @@
// Hatch shader — GPU-driven hatch fill over an arbitrary polygon boundary.
//
// Vertex shader : projects a bounding-box quad over the hatch region.
// Fragment shader: three stages —
// 1. Point-in-polygon (ray casting against boundary vertices in world XY).
// 2. Pattern test:
// mode 0 — N line families (PAT format), each with optional dash pattern.
// mode 1 — solid fill.
// mode 2 — linear gradient.
//
// GPU limits: MAX_FAMILIES = 16, MAX_DASHES = 128.
// ── Group 0: frame uniforms (shared) ──────────────────────────────────────
struct Uniforms {
view_proj: mat4x4<f32>,
camera_pos: vec4<f32>,
viewport_size: vec2<f32>,
_pad: vec2<f32>,
}
@group(0) @binding(0) var<uniform> u: Uniforms;
// ── Group 1: per-hatch data ────────────────────────────────────────────────
//
// mode encoding:
// 0 → Pattern (evaluate FamilyBatch)
// 1 → Solid (return h.color immediately)
// 2 → Gradient (mix h.color → h.color2 along grad_cos/grad_sin)
struct HatchUniforms {
color: vec4<f32>, // 0: primary RGBA
color2: vec4<f32>, // 16: gradient end color
mode: u32, // 32: 0=pattern, 1=solid, 2=gradient
vcount: u32, // 36: boundary vertex count
angle_offset: f32, // 40: pattern rotation (radians, added to each family)
scale: f32, // 44: pattern scale multiplier
grad_cos: f32, // 48: gradient direction cos
grad_sin: f32, // 52: gradient direction sin
grad_min: f32, // 56: gradient proj_min
grad_range: f32, // 60: gradient proj_range
}
@group(1) @binding(0) var<uniform> h: HatchUniforms;
struct Boundary {
verts: array<vec4<f32>, 64>,
}
@group(1) @binding(1) var<uniform> b: Boundary;
// One line family (48 bytes, matches LineFamilyGpu in hatch_gpu.rs).
struct LineFamily {
cos_a: f32, // base cos(angle)
sin_a: f32, // base sin(angle)
x0: f32, // family origin x
y0: f32, // family origin y
dx: f32, // step vector x
dy: f32, // step vector y
perp_step: f32, // -dx*sin_a + dy*cos_a (perpendicular spacing)
along_step: f32, // dx*cos_a + dy*sin_a (phase shift per step)
line_width: f32, // half-width of each line (|perp_step| × 0.08)
period: f32, // sum(|dashes|) — 0 means solid
n_dashes: u32, // number of dash entries
dash_off: u32, // start index in dash_values
}
struct FamilyBatch {
families: array<LineFamily, 16>,
dash_values: array<vec4<f32>, 32>, // 128 f32s packed as 32×vec4 (uniform stride=16)
n_families: u32,
_pad0: u32, _pad1: u32, _pad2: u32,
}
@group(1) @binding(2) var<uniform> f: FamilyBatch;
// ── Vertex shader ──────────────────────────────────────────────────────────
struct VIn { @location(0) pos: vec3<f32> }
struct VOut {
@builtin(position) clip: vec4<f32>,
@location(0) xz: vec2<f32>,
}
@vertex fn vs_main(v: VIn) -> VOut {
var o: VOut;
o.clip = u.view_proj * vec4<f32>(v.pos, 1.0);
o.xz = vec2<f32>(v.pos.x, v.pos.y);
return o;
}
// ── Point-in-polygon (ray casting) ────────────────────────────────────────
fn in_polygon(p: vec2<f32>) -> bool {
var inside = false;
let n = h.vcount;
var j = n - 1u;
for (var i = 0u; i < n; i++) {
let vi = b.verts[i].xy;
let vj = b.verts[j].xy;
if (vi.y > p.y) != (vj.y > p.y) {
let x_int = (vj.x - vi.x) * (p.y - vi.y) / (vj.y - vi.y) + vi.x;
if p.x < x_int { inside = !inside; }
}
j = i;
}
return inside;
}
// ── Per-family hatch test ─────────────────────────────────────────────────
//
// Returns true if world point `xz` falls on a hatch line of `fam`.
// `cos_off` / `sin_off` are cos/sin of `h.angle_offset` (precomputed once).
// `scale` is `h.scale`.
fn check_family(
xz: vec2<f32>,
fam: LineFamily,
cos_off: f32,
sin_off: f32,
scale: f32,
) -> bool {
// Rotate family direction by angle_offset.
let cos_a = fam.cos_a * cos_off - fam.sin_a * sin_off;
let sin_a = fam.sin_a * cos_off + fam.cos_a * sin_off;
// Rotate and scale the family origin.
let ox = (fam.x0 * cos_off - fam.y0 * sin_off) * scale;
let oz = (fam.x0 * sin_off + fam.y0 * cos_off) * scale;
let px = xz.x - ox;
let pz = xz.y - oz;
// Perpendicular distance from the nearest parallel line.
let perp_step = fam.perp_step * scale;
let line_w = abs(fam.line_width * scale);
let perp = -px * sin_a + pz * cos_a;
let k = round(perp / perp_step);
let d = abs(perp - k * perp_step);
let half_px = length(vec2<f32>(dpdx(perp), dpdy(perp))) * 0.5;
if d > half_px { return false; }
// Solid line family — no dash check needed.
if fam.n_dashes == 0u { return true; }
// Dash pattern test: position along line k.
let along_step = fam.along_step * scale;
let period = fam.period * scale;
let along = px * cos_a + pz * sin_a;
let t = along - k * along_step;
let t_mod = ((t % period) + period) % period;
var pos = 0.0;
for (var j = 0u; j < fam.n_dashes; j++) {
let idx = fam.dash_off + j;
let sv = f.dash_values[idx / 4u][idx % 4u] * scale; // scale dash lengths
if sv > 0.0 {
if t_mod >= pos && t_mod < pos + sv { return true; }
pos = pos + sv;
} else {
pos = pos - sv;
}
}
return false;
}
// ── Fragment shader ────────────────────────────────────────────────────────
@fragment fn fs_main(v: VOut) -> @location(0) vec4<f32> {
// 1. Boundary test.
if !in_polygon(v.xz) { discard; }
// 2. Mode dispatch.
if h.mode == 1u {
// Solid fill.
return h.color;
} else if h.mode == 2u {
// Linear gradient.
let proj = v.xz.x * h.grad_cos + v.xz.y * h.grad_sin;
let t = clamp((proj - h.grad_min) / h.grad_range, 0.0, 1.0);
return mix(h.color, h.color2, t);
}
// 3. Pattern: evaluate each line family.
let cos_off = cos(h.angle_offset);
let sin_off = sin(h.angle_offset);
for (var i = 0u; i < f.n_families; i++) {
if check_family(v.xz, f.families[i], cos_off, sin_off, h.scale) {
return h.color;
}
}
discard;
}
+47
View File
@@ -0,0 +1,47 @@
// Mesh shader — renders triangle meshes (truck Shell/Solid tessellation).
//
// Vertex layout: position [f32;3], normal [f32;3], color [f32;4] (40 bytes)
//
// Lighting: simple half-Lambert with a fixed directional light so solids look
// solid regardless of camera angle. Selection highlight overrides color.
struct Uniforms {
view_proj: mat4x4<f32>,
viewport: vec2<f32>,
_pad: vec2<f32>,
};
@group(0) @binding(0)
var<uniform> u: Uniforms;
struct VertexIn {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) color: vec4<f32>,
};
struct VertexOut {
@builtin(position) clip_pos: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) normal: vec3<f32>,
};
@vertex
fn vs_main(v: VertexIn) -> VertexOut {
var out: VertexOut;
out.clip_pos = u.view_proj * vec4<f32>(v.position, 1.0);
out.color = v.color;
out.normal = v.normal;
return out;
}
@fragment
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
// Fixed directional light (world space, tilted toward the viewer).
let light_dir = normalize(vec3<f32>(0.4, 0.8, 0.6));
let n = normalize(in.normal);
// Half-Lambert: [0.5, 1.0] so the dark side isn't fully black.
let diff = clamp(dot(n, light_dir), 0.0, 1.0) * 0.5 + 0.5;
let rgb = in.color.rgb * diff;
return vec4<f32>(rgb, in.color.a);
}
+32
View File
@@ -0,0 +1,32 @@
// ViewCube shader — Phong lighting + hover highlight.
struct Uniforms {
view_proj: mat4x4<f32>,
rotation: mat4x4<f32>,
hover_region: vec4<f32>,
}
@group(0) @binding(0) var<uniform> u: Uniforms;
struct VIn { @location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>, @location(3) region_f: f32 }
struct VOut { @builtin(position) clip: vec4<f32>, @location(0) color: vec3<f32>, @location(1) world_n: vec3<f32>, @location(2) region_f: f32 }
@vertex
fn vs_main(in: VIn) -> VOut {
var out: VOut;
let rotated = (u.rotation * vec4<f32>(in.pos, 1.0)).xyz;
out.clip = u.view_proj * vec4<f32>(rotated, 1.0);
out.color = in.color;
out.world_n = normalize((u.rotation * vec4<f32>(in.normal, 0.0)).xyz);
out.region_f = in.region_f;
return out;
}
@fragment
fn fs_main(in: VOut) -> @location(0) vec4<f32> {
let is_hovered = abs(in.region_f - u.hover_region.x) < 0.001 && u.hover_region.x >= 0.0;
var final_rgb = in.color;
if is_hovered {
final_rgb = mix(final_rgb, vec3<f32>(0.55, 0.78, 1.0), 0.45);
}
return vec4<f32>(final_rgb, 1.0);
}
+39
View File
@@ -0,0 +1,39 @@
struct Uniforms {
screen: vec2<f32>,
_pad: vec2<f32>,
};
@group(0) @binding(0) var<uniform> u: Uniforms;
@group(0) @binding(1) var samp: sampler;
@group(0) @binding(2) var tex: texture_2d<f32>;
struct VsIn {
@location(0) pos: vec2<f32>,
@location(1) uv: vec2<f32>,
@location(2) color: vec4<f32>,
};
struct VsOut {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) color: vec4<f32>,
};
@vertex
fn vs_main(input: VsIn) -> VsOut {
var out: VsOut;
let ndc = vec2<f32>(
(input.pos.x / u.screen.x) * 2.0 - 1.0,
1.0 - (input.pos.y / u.screen.y) * 2.0
);
out.pos = vec4<f32>(ndc, 0.0, 1.0);
out.uv = input.uv;
out.color = input.color;
return out;
}
@fragment
fn fs_main(input: VsOut) -> @location(0) vec4<f32> {
let a = textureSample(tex, samp, input.uv).r;
return vec4<f32>(input.color.rgb, input.color.a * a);
}
+109
View File
@@ -0,0 +1,109 @@
// Wire shader — renders 1-D CAD entities as screen-aligned quads.
// Topology: TriangleList (6 vertices per segment).
//
// Each vertex carries both segment endpoints (pos_a, pos_b) plus which_end
// (0=A, 1=B) and side (±1). The vertex shader expands the quad by half_width
// pixels perpendicular to the segment direction in screen space.
//
// Linetype is applied entirely on the GPU:
// • distance = cumulative arc-length from wire start (interpolated by GPU).
// • pattern_length > 0 enables the dash test; 0 = solid (no discard).
// • pat0/pat1 encode up to 8 elements: positive=dash, negative=gap.
struct Uniforms {
view_proj: mat4x4<f32>,
camera_pos: vec4<f32>,
viewport_size: vec2<f32>,
_pad: vec2<f32>,
}
@group(0) @binding(0) var<uniform> u: Uniforms;
struct VertexIn {
@location(0) pos_a: vec3<f32>,
@location(1) pos_b: vec3<f32>,
@location(2) which_end: f32,
@location(3) side: f32,
@location(4) color: vec4<f32>,
@location(5) distance: f32,
@location(6) half_width: f32,
@location(7) pattern_length: f32,
// location 8 = _pad (not needed in shader)
@location(8) pat0: vec4<f32>,
@location(9) pat1: vec4<f32>,
}
struct VertexOut {
@builtin(position) clip_pos: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) distance: f32,
@location(2) pattern_length: f32,
@location(3) pat0: vec4<f32>,
@location(4) pat1: vec4<f32>,
}
@vertex fn vs_main(in: VertexIn) -> VertexOut {
let clip_a = u.view_proj * vec4<f32>(in.pos_a, 1.0);
let clip_b = u.view_proj * vec4<f32>(in.pos_b, 1.0);
// NDC of both endpoints.
let ndc_a = clip_a.xy / clip_a.w;
let ndc_b = clip_b.xy / clip_b.w;
// Screen-space pixel positions.
let screen_a = ndc_a * u.viewport_size * 0.5;
let screen_b = ndc_b * u.viewport_size * 0.5;
// Screen-space perpendicular to segment direction.
let seg = screen_b - screen_a;
let seg_len = length(seg);
var perp: vec2<f32>;
if seg_len > 1e-4 {
let dir = seg / seg_len;
perp = vec2<f32>(-dir.y, dir.x);
} else {
perp = vec2<f32>(0.0, 1.0);
}
// Convert perpendicular from screen pixels to NDC offset.
let perp_ndc = perp / (u.viewport_size * 0.5);
// Select the clip-space position for this vertex's endpoint.
let clip_pos = mix(clip_a, clip_b, in.which_end);
// Offset in clip space (multiply by w to un-apply perspective division).
let ndc_offset = perp_ndc * in.half_width * in.side;
let final_clip = clip_pos + vec4<f32>(ndc_offset * clip_pos.w, 0.0, 0.0);
var out: VertexOut;
out.clip_pos = final_clip;
out.color = in.color;
out.distance = in.distance;
out.pattern_length = in.pattern_length;
out.pat0 = in.pat0;
out.pat1 = in.pat1;
return out;
}
// Returns true if arc-length `dist` falls inside a dash segment.
fn in_dash(dist: f32, pat_len: f32, p0: vec4<f32>, p1: vec4<f32>) -> bool {
let d = ((dist % pat_len) + pat_len) % pat_len;
var pos = 0.0f;
let elems = array<f32, 8>(p0.x, p0.y, p0.z, p0.w, p1.x, p1.y, p1.z, p1.w);
for (var i = 0u; i < 8u; i++) {
let elem = elems[i];
if elem == 0.0 { break; }
let len = abs(elem);
if d < pos + len { return elem > 0.0; }
pos += len;
}
return false;
}
@fragment fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
if in.pattern_length > 0.0 {
if !in_dash(in.distance, in.pattern_length, in.pat0, in.pat1) {
discard;
}
}
return in.color;
}