fix(hatch): radial gradients and correct gradient stop colours
A gradient hatch was drawn linear from the entity colour to its second stop, so a red->yellow spherical fill came out as a pale linear wash. Two fixes: - Colour: start from the gradient's first stop, not the entity colour. hatch_model_from_dxf already baked stop-0 in; stop synced_hatch_models from clobbering it with the entity's resolved colour for gradient fills. - Shape: spherical / hemispherical / curved gradient names now render as a radial fill (new shader mode 3, centred on the boundary with radius to the farthest vertex) instead of linear. Native and web shaders both updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
69354eca07
commit
fc29139056
8 changed files with 93 additions and 23 deletions
|
|
@ -225,13 +225,21 @@ impl HatchGpu {
|
|||
|
||||
let (mode, color2, grad_cos, grad_sin, grad_min, grad_range) = match &h.pattern {
|
||||
HatchPattern::Solid => (1u32, [0.0; 4], 0.0, 0.0, 0.0, 1.0),
|
||||
HatchPattern::Gradient { angle_deg, color2 } => {
|
||||
let r = angle_deg.to_radians();
|
||||
// Gradient projection range (proj_min / proj_range) —
|
||||
// computed at upload time, identical to per-hatch path.
|
||||
let (gmin, gmax) = boundary_projection_range(&h.boundary, r);
|
||||
let grange = (gmax - gmin).max(1.0);
|
||||
(2u32, *color2, r.cos(), r.sin(), gmin, grange)
|
||||
HatchPattern::Gradient { angle_deg, color2, radial } => {
|
||||
if *radial {
|
||||
// Radial fill: the boundary is stored relative to its
|
||||
// centre (`world_origin`), so the centre is the local
|
||||
// origin; radius = the farthest boundary vertex. mode 3.
|
||||
let radius = radial_radius(&h.boundary);
|
||||
(3u32, *color2, 0.0, 0.0, 0.0, radius)
|
||||
} else {
|
||||
let r = angle_deg.to_radians();
|
||||
// Gradient projection range (proj_min / proj_range) —
|
||||
// computed at upload time, identical to per-hatch path.
|
||||
let (gmin, gmax) = boundary_projection_range(&h.boundary, r);
|
||||
let grange = (gmax - gmin).max(1.0);
|
||||
(2u32, *color2, r.cos(), r.sin(), gmin, grange)
|
||||
}
|
||||
}
|
||||
HatchPattern::Pattern(fams) => {
|
||||
for fam in fams {
|
||||
|
|
@ -559,6 +567,18 @@ fn boundary_projection_range(boundary: &[[f32; 2]], theta: f32) -> (f32, f32) {
|
|||
(lo, hi)
|
||||
}
|
||||
|
||||
/// Radius of a radial gradient — the distance from the boundary centre (the
|
||||
/// local origin, since vertices are stored relative to it) to the farthest
|
||||
/// vertex, so `t = 1` (the end colour) reaches the corners.
|
||||
fn radial_radius(boundary: &[[f32; 2]]) -> f32 {
|
||||
boundary
|
||||
.iter()
|
||||
.filter(|p| p[0].is_finite() && p[1].is_finite())
|
||||
.map(|p| (p[0] * p[0] + p[1] * p[1]).sqrt())
|
||||
.fold(0.0_f32, f32::max)
|
||||
.max(1.0)
|
||||
}
|
||||
|
||||
// PatFamily is re-exported by hatch_model so we don't need to import
|
||||
// it explicitly anywhere else — but rust needs the type referenced to
|
||||
// confirm the layout assumption above.
|
||||
|
|
|
|||
|
|
@ -110,9 +110,14 @@ impl HatchWebGpu {
|
|||
let (mode, color2, grad_cos, grad_sin) = match &model.pattern {
|
||||
HatchPattern::Solid => (1u32, [0.0f32; 4], 0.0f32, 0.0f32),
|
||||
HatchPattern::Pattern(_) => (0u32, [0.0f32; 4], 0.0f32, 0.0f32),
|
||||
HatchPattern::Gradient { angle_deg, color2 } => {
|
||||
let r = angle_deg.to_radians();
|
||||
(2u32, *color2, r.cos(), r.sin())
|
||||
HatchPattern::Gradient { angle_deg, color2, radial } => {
|
||||
if *radial {
|
||||
// Radial: centre is the local origin; grad_cos/sin unused.
|
||||
(3u32, *color2, 0.0, 0.0)
|
||||
} else {
|
||||
let r = angle_deg.to_radians();
|
||||
(2u32, *color2, r.cos(), r.sin())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -181,6 +186,16 @@ impl HatchWebGpu {
|
|||
// Floor matches the desktop hatch renderer (hatch_gpu.rs).
|
||||
(proj_min, (proj_max - proj_min).max(1.0))
|
||||
}
|
||||
} else if mode == 3 {
|
||||
// Radial: range = the farthest boundary vertex from the centre.
|
||||
let radius = model
|
||||
.boundary
|
||||
.iter()
|
||||
.filter(|v| v[0].is_finite() && v[1].is_finite())
|
||||
.map(|&[x, y]| (x * x + y * y).sqrt())
|
||||
.fold(0.0_f32, f32::max)
|
||||
.max(1.0);
|
||||
(0.0, radius)
|
||||
} else {
|
||||
(0.0, 1.0)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ impl WipeoutGpu {
|
|||
let (mode, color2, grad_cos, grad_sin) = match &model.pattern {
|
||||
HatchPattern::Solid => (1u32, [0.0f32; 4], 0.0f32, 0.0f32),
|
||||
HatchPattern::Pattern(_) => (0u32, [0.0f32; 4], 0.0f32, 0.0f32),
|
||||
HatchPattern::Gradient { angle_deg, color2 } => {
|
||||
HatchPattern::Gradient { angle_deg, color2, .. } => {
|
||||
let r = angle_deg.to_radians();
|
||||
(2u32, *color2, r.cos(), r.sin())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue