forked from 1b-agent/opencadstudio-upstream
fix(pipeline): batched-hatch pattern math + invisible-instance clip
Two issues in the step-3 batched path surfaced during testing: 1. WGSL rejects compile-time NaN literals (`0.0 / 0.0`), so the visibility-skip path that emitted a NaN clip position failed `Device::create_shader_module` validation. Replaced with an out-of-frustum clip position `(2, 2, 2, 1)` — the GPU rasterizer clips the primitive and the fragment shader never runs. 2. Pattern hatches rendered with the wrong spacing / direction. The batched builder was rotating `perp_step` / `along_step` into the global frame, but the per-hatch shader (`hatch.wgsl`) expects them in QCAD PAT local-frame convention: `perp_step = family.dy`, `along_step = family.dx`, with the cos_off/sin_off rotation applied inside the shader. Now matches `build_family_batch` from `hatch_gpu.rs` line-for-line. `line_width` also reset to 0 (the shader picks 1 px from a screen-space derivative; the stored field is unused). 3. Padding's `max_spacing` likewise dropped its bogus rotation and now uses `|dy|` per family, matching the new perp_step convention. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
3593e9dabc
commit
cfe53bd789
2 files changed
+21
-13
No files matched your search
@@ -192,11 +192,16 @@ impl HatchBatchedGpu {
|
|||||||
dashes.push(d);
|
dashes.push(d);
|
||||||
}
|
}
|
||||||
let n_dashes = (dashes.len() as u32 - dash_offset).min(u32::MAX);
|
let n_dashes = (dashes.len() as u32 - dash_offset).min(u32::MAX);
|
||||||
let perp_step = -fam.dx * fam.angle_deg.to_radians().sin()
|
// QCAD PAT local-frame convention (mirrors
|
||||||
+ fam.dy * fam.angle_deg.to_radians().cos();
|
// `build_family_batch` in hatch_gpu.rs): `dy` is
|
||||||
let along_step = fam.dx * fam.angle_deg.to_radians().cos()
|
// the perpendicular spacing, `dx` is the along-line
|
||||||
+ fam.dy * fam.angle_deg.to_radians().sin();
|
// phase shift — both in family-local coords. The
|
||||||
let line_width = perp_step.abs() * 0.08;
|
// shader applies cos_off/sin_off to rotate them.
|
||||||
|
let perp_step = fam.dy;
|
||||||
|
let along_step = fam.dx;
|
||||||
|
// Screen-space derivative drives 1-px line width
|
||||||
|
// in the shader; this stored field is unused.
|
||||||
|
let line_width = 0.0_f32;
|
||||||
let period: f32 = fam.dashes.iter().map(|d| d.abs()).sum();
|
let period: f32 = fam.dashes.iter().map(|d| d.abs()).sum();
|
||||||
families.push(LineFamilyGpu {
|
families.push(LineFamilyGpu {
|
||||||
cos_a: fam.angle_deg.to_radians().cos(),
|
cos_a: fam.angle_deg.to_radians().cos(),
|
||||||
@@ -208,7 +213,7 @@ impl HatchBatchedGpu {
|
|||||||
perp_step,
|
perp_step,
|
||||||
along_step,
|
along_step,
|
||||||
line_width,
|
line_width,
|
||||||
period,
|
period: if n_dashes > 0 { period } else { 0.0 },
|
||||||
n_dashes,
|
n_dashes,
|
||||||
dash_offset,
|
dash_offset,
|
||||||
});
|
});
|
||||||
@@ -263,13 +268,13 @@ impl HatchBatchedGpu {
|
|||||||
// family origin. Mirrors the per-hatch shader's quad sizing
|
// family origin. Mirrors the per-hatch shader's quad sizing
|
||||||
// logic — `diag * 0.8 + max_spacing * 2 * scale`.
|
// logic — `diag * 0.8 + max_spacing * 2 * scale`.
|
||||||
let diag = ((max_x - min_x).powi(2) + (max_y - min_y).powi(2)).sqrt();
|
let diag = ((max_x - min_x).powi(2) + (max_y - min_y).powi(2)).sqrt();
|
||||||
|
// `perp_step.abs()` per family — uses the same QCAD local-
|
||||||
|
// frame convention as `LineFamilyGpu.perp_step` above so
|
||||||
|
// the quad padding matches what the shader will sample.
|
||||||
let max_spacing = match &h.pattern {
|
let max_spacing = match &h.pattern {
|
||||||
HatchPattern::Pattern(fs) => fs
|
HatchPattern::Pattern(fs) => fs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| {
|
.map(|f| f.dy.abs())
|
||||||
let a = f.angle_deg.to_radians();
|
|
||||||
(-f.dx * a.sin() + f.dy * a.cos()).abs()
|
|
||||||
})
|
|
||||||
.fold(0.0f32, f32::max),
|
.fold(0.0f32, f32::max),
|
||||||
_ => 5.0,
|
_ => 5.0,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -100,10 +100,13 @@ fn corner_xy(c: u32, aabb: vec4<f32>) -> vec2<f32> {
|
|||||||
var o: VOut;
|
var o: VOut;
|
||||||
let inst = instances[v.instance_index];
|
let inst = instances[v.instance_index];
|
||||||
|
|
||||||
// CPU-driven visibility flag (Phase 4-B frustum skip). NaN clip
|
// CPU-driven visibility flag (Phase 4-B frustum skip). Emit a clip
|
||||||
// position degenerates the triangle so no fragment runs for it.
|
// position whose x/y exceed |w|, so the GPU frustum-culls the
|
||||||
|
// primitive and no fragment runs for it. (WGSL forbids literal NaN
|
||||||
|
// so we can't go that route; pushing the vertex out of the unit
|
||||||
|
// cube is the equivalent degenerate-triangle trick.)
|
||||||
if inst.visible == 0u {
|
if inst.visible == 0u {
|
||||||
o.clip = vec4<f32>(0.0 / 0.0, 0.0 / 0.0, 0.0 / 0.0, 1.0);
|
o.clip = vec4<f32>(2.0, 2.0, 2.0, 1.0);
|
||||||
o.xz = vec2<f32>(0.0, 0.0);
|
o.xz = vec2<f32>(0.0, 0.0);
|
||||||
o.instance_index = v.instance_index;
|
o.instance_index = v.instance_index;
|
||||||
return o;
|
return o;
|
||||||
|
|||||||
Reference in new issue
Block a user