From 2cd1d36661c5eb8557dd69224b4a247b3404b731 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 2 Sep 2026 23:28:34 +0300 Subject: [PATCH] fix(render): match model lineweight preview curve --- src/scene/pipeline/uniforms.rs | 5 +++-- src/scene/view/render.rs | 2 +- src/shaders/block_wire.wgsl | 14 +++++++++++++- src/shaders/hatch.wgsl | 20 +++++++++++++++----- src/shaders/hatch_texture.wgsl | 21 ++++++++++++++++----- src/shaders/wire.wgsl | 14 +++++++++++++- src/shaders/wire_indexed.wgsl | 14 +++++++++++++- 7 files changed, 74 insertions(+), 16 deletions(-) diff --git a/src/scene/pipeline/uniforms.rs b/src/scene/pipeline/uniforms.rs index d3dd549d0..06cb0cc1e 100644 --- a/src/scene/pipeline/uniforms.rs +++ b/src/scene/pipeline/uniforms.rs @@ -25,8 +25,9 @@ pub struct Uniforms { /// zooming inside MSPACE changes one scalar instead of re-tessellating and /// re-uploading every dashed wire. pub linetype_scale: f32, - /// Multiplier for screen-space lineweights. Model space uses the user's - /// preview scale; paper layouts derive it from the current paper zoom. + /// Positive values scale paper-space lineweights directly. Negative values + /// carry the model-space preview scale; lineweight shaders then apply the + /// model preview curve before multiplying by its magnitude. pub lineweight_scale: f32, // ── Relative-to-eye (double-single) additions ─────────────────────────── diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index 34ea633f0..e04809efc 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -3848,7 +3848,7 @@ impl Scene { self.document.header.lineweight_display || display_plot_lineweights, ); if self.current_layout == "Model" { - uniforms.lineweight_scale = self.model_lineweight_scale; + uniforms.lineweight_scale = -self.model_lineweight_scale; } else { let paper_per_pixel = 2.0 * self.camera.borrow().ortho_size() / canvas.1.max(1.0); let paper_units_per_mm = self.paper_space_unit_factor() as f32; diff --git a/src/shaders/block_wire.wgsl b/src/shaders/block_wire.wgsl index 5a12c20cb..a8500bddc 100644 --- a/src/shaders/block_wire.wgsl +++ b/src/shaders/block_wire.wgsl @@ -45,6 +45,8 @@ struct VertexIn { } const DRAW_ORDER_BIAS: f32 = 0.001; +const MODEL_LINEWEIGHT_BOOST: f32 = 2.0; +const MODEL_LINEWEIGHT_MAX_PX: f32 = 10.0; struct VertexOut { @builtin(position) clip_pos: vec4, @@ -67,7 +69,17 @@ fn resolve_hw(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { if world_hw > 0.0 { return max(world_hw / u.world_per_pixel, 0.5); } - return select(0.5, max(px_hw * u.lineweight_scale, 0.5), u.lwdisplay_enable > 0.5); + var display_hw = max(px_hw * u.lineweight_scale, 0.5); + if u.lineweight_scale < 0.0 { + let scale = -u.lineweight_scale; + let base_hw = select( + min(px_hw * MODEL_LINEWEIGHT_BOOST, MODEL_LINEWEIGHT_MAX_PX * 0.5), + 0.5, + px_hw <= 0.5, + ); + display_hw = max(base_hw * scale, 0.5); + } + return select(0.5, display_hw, u.lwdisplay_enable > 0.5); } fn marker_relative( diff --git a/src/shaders/hatch.wgsl b/src/shaders/hatch.wgsl index dba8b4309..bf34140bd 100644 --- a/src/shaders/hatch.wgsl +++ b/src/shaders/hatch.wgsl @@ -50,6 +50,8 @@ struct HatchInstance { // Draw-order depth bias (see wire.wgsl). Higher draw_depth → smaller z → // drawn on top, ordering this fill against other entity types. const DRAW_ORDER_BIAS: f32 = 0.001; +const MODEL_LINEWEIGHT_BOOST: f32 = 2.0; +const MODEL_LINEWEIGHT_MAX_PX: f32 = 10.0; struct LineFamily { cos_a: f32, @@ -133,6 +135,18 @@ struct VOut { // `ddx_xz`/`ddy_xz` are screen-space derivatives of `xz`, taken once in // fs_main: derivative builtins must run in uniform control flow, and the // per-family loop's early return makes later iterations non-uniform. +fn display_lineweight(width: f32) -> f32 { + if u.lineweight_scale < 0.0 { + let base = select( + min(width * MODEL_LINEWEIGHT_BOOST, MODEL_LINEWEIGHT_MAX_PX), + 1.0, + width <= 1.0, + ); + return max(base * -u.lineweight_scale, 1.0); + } + return max(width * u.lineweight_scale, 1.0); +} + fn check_family( xz: vec2, ddx_xz: vec2, @@ -163,11 +177,7 @@ fn check_family( -ddx_xz.x * sin_a + ddx_xz.y * cos_a, -ddy_xz.x * sin_a + ddy_xz.y * cos_a, )) * 0.5; - let width_px = select( - 1.0, - max(fam.line_width * u.lineweight_scale, 1.0), - u.lwdisplay_enable > 0.5, - ); + let width_px = select(1.0, display_lineweight(fam.line_width), u.lwdisplay_enable > 0.5); let half_line = half_px * width_px; // World units per screen pixel on each axis — used to light exactly the diff --git a/src/shaders/hatch_texture.wgsl b/src/shaders/hatch_texture.wgsl index 515921d5d..237ad8341 100644 --- a/src/shaders/hatch_texture.wgsl +++ b/src/shaders/hatch_texture.wgsl @@ -23,6 +23,9 @@ struct Uniforms { } @group(0) @binding(0) var u: Uniforms; +const MODEL_LINEWEIGHT_BOOST: f32 = 2.0; +const MODEL_LINEWEIGHT_MAX_PX: f32 = 10.0; + // ── Group 1: per-hatch data ──────────────────────────────────────────────── struct HatchUniforms { @@ -117,6 +120,18 @@ struct VOut { // `ddx_xz`/`ddy_xz` are screen-space derivatives of `xz`, taken once in // fs_main: derivative builtins must run in uniform control flow, and the // per-family loop's early return makes later iterations non-uniform. +fn display_lineweight(width: f32) -> f32 { + if u.lineweight_scale < 0.0 { + let base = select( + min(width * MODEL_LINEWEIGHT_BOOST, MODEL_LINEWEIGHT_MAX_PX), + 1.0, + width <= 1.0, + ); + return max(base * -u.lineweight_scale, 1.0); + } + return max(width * u.lineweight_scale, 1.0); +} + fn check_family( xz: vec2, ddx_xz: vec2, @@ -147,11 +162,7 @@ fn check_family( -ddx_xz.x * sin_a + ddx_xz.y * cos_a, -ddy_xz.x * sin_a + ddy_xz.y * cos_a, )) * 0.5; - let width_px = select( - 1.0, - max(fam.line_width * u.lineweight_scale, 1.0), - u.lwdisplay_enable > 0.5, - ); + let width_px = select(1.0, display_lineweight(fam.line_width), u.lwdisplay_enable > 0.5); let half_line = half_px * width_px; let wpx = length(vec2(ddx_xz.x, ddy_xz.x)); diff --git a/src/shaders/wire.wgsl b/src/shaders/wire.wgsl index 68995cc5b..c41abd617 100644 --- a/src/shaders/wire.wgsl +++ b/src/shaders/wire.wgsl @@ -72,6 +72,8 @@ struct InstanceIn { // draw_depth is signed (-1,1): front → positive → smaller z → drawn on top; // 0.0 = neutral (real depth). Depth32Float gives ample precision. const DRAW_ORDER_BIAS: f32 = 0.001; +const MODEL_LINEWEIGHT_BOOST: f32 = 2.0; +const MODEL_LINEWEIGHT_MAX_PX: f32 = 10.0; struct VertexOut { @builtin(position) clip_pos: vec4, @@ -102,7 +104,17 @@ struct VertexOut { fn resolve_hw(taper: f32, world_hw: f32, px_hw: f32) -> f32 { if taper > 0.0 { return max(taper / u.world_per_pixel, 0.5); } if world_hw > 0.0 { return max(world_hw / u.world_per_pixel, 0.5); } - return select(0.5, max(px_hw * u.lineweight_scale, 0.5), u.lwdisplay_enable > 0.5); + var display_hw = max(px_hw * u.lineweight_scale, 0.5); + if u.lineweight_scale < 0.0 { + let scale = -u.lineweight_scale; + let base_hw = select( + min(px_hw * MODEL_LINEWEIGHT_BOOST, MODEL_LINEWEIGHT_MAX_PX * 0.5), + 0.5, + px_hw <= 0.5, + ); + display_hw = max(base_hw * scale, 0.5); + } + return select(0.5, display_hw, u.lwdisplay_enable > 0.5); } fn marker_relative(position_high: vec3, position_low: vec3, instance: InstanceIn) -> vec3 { diff --git a/src/shaders/wire_indexed.wgsl b/src/shaders/wire_indexed.wgsl index 241e3abe8..c62b3e189 100644 --- a/src/shaders/wire_indexed.wgsl +++ b/src/shaders/wire_indexed.wgsl @@ -54,6 +54,8 @@ struct InstanceIn { } const DRAW_ORDER_BIAS: f32 = 0.001; +const MODEL_LINEWEIGHT_BOOST: f32 = 2.0; +const MODEL_LINEWEIGHT_MAX_PX: f32 = 10.0; struct VertexOut { @builtin(position) clip_pos: vec4, @@ -81,7 +83,17 @@ fn resolve_hw(taper_ratio: f32, world_hw: f32, px_hw: f32) -> f32 { return max((taper_ratio * world_hw) / u.world_per_pixel, 0.5); } if world_hw > 0.0 { return max(world_hw / u.world_per_pixel, 0.5); } - return select(0.5, max(px_hw * u.lineweight_scale, 0.5), u.lwdisplay_enable > 0.5); + var display_hw = max(px_hw * u.lineweight_scale, 0.5); + if u.lineweight_scale < 0.0 { + let scale = -u.lineweight_scale; + let base_hw = select( + min(px_hw * MODEL_LINEWEIGHT_BOOST, MODEL_LINEWEIGHT_MAX_PX * 0.5), + 0.5, + px_hw <= 0.5, + ); + display_hw = max(base_hw * scale, 0.5); + } + return select(0.5, display_hw, u.lwdisplay_enable > 0.5); } fn marker_relative(position_high: vec3, position_low: vec3, c: WireConst) -> vec3 {