forked from 1b-agent/opencadstudio-upstream
Host wide polyline quads on their 3D plane instead of billboarding in screen space
This commit is contained in:
1 parent
76e1610dcc
commit
ed177cbf25
6 files changed
+376
-180
No files matched your search
@@ -662,6 +662,25 @@ fn test_tilted_3d_donut_and_thick_arc_gpu_rendering() {
|
||||
assert_eq!(circles[0].instance_count, 2);
|
||||
pipeline.gpu_circles = std::sync::Arc::new(circles);
|
||||
|
||||
// Wide straight polyline segment on the 3D plane
|
||||
let mut straight_wide = WireModel::default();
|
||||
straight_wide.name = "wide_straight".into();
|
||||
straight_wide.points = vec![[0.0, 0.0, 0.0], [50.0, 50.0, 0.0]];
|
||||
straight_wide.world_width = 10.0;
|
||||
straight_wide.color = [0.9, 0.2, 0.3, 1.0];
|
||||
|
||||
let wide_wires = vec![straight_wide];
|
||||
let gpu_wires = wire_gpu::WireGpu::from_run(
|
||||
&device,
|
||||
&queue,
|
||||
&wide_wires,
|
||||
&depth_map,
|
||||
false,
|
||||
pipeline.wire_const_bgl.as_ref(),
|
||||
);
|
||||
assert!(!gpu_wires.is_empty());
|
||||
pipeline.gpu_wires = std::sync::Arc::new(gpu_wires);
|
||||
|
||||
pipeline.ensure_depth_texture(&device, iced::Size::new(512, 512));
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("tilted_donut_target"),
|
||||
|
||||
@@ -532,7 +532,35 @@ fn finite3(p: [f32; 3]) -> bool {
|
||||
|
||||
fn marker_metadata(wire: &WireModel) -> ([f32; 4], [f32; 4], [f32; 4]) {
|
||||
let Some(marker) = wire.point_marker else {
|
||||
return ([0.0; 4], [0.0; 4], [0.0; 4]);
|
||||
for tg in &wire.tangent_geoms {
|
||||
match tg {
|
||||
crate::scene::model::wire_model::TangentGeom::Arc { axis_x, axis_y, .. }
|
||||
| crate::scene::model::wire_model::TangentGeom::PlanarCircle { axis_x, axis_y, .. } => {
|
||||
let ax = glam::DVec3::from_array(*axis_x);
|
||||
let ay = glam::DVec3::from_array(*axis_y);
|
||||
let n = ax.cross(ay).normalize_or(glam::DVec3::Z);
|
||||
return ([0.0; 4], [0.0; 4], [n.x as f32, n.y as f32, n.z as f32, 0.0]);
|
||||
}
|
||||
crate::scene::model::wire_model::TangentGeom::PlanarEllipse { normal, .. } => {
|
||||
let n = glam::DVec3::from_array(*normal).normalize_or(glam::DVec3::Z);
|
||||
return ([0.0; 4], [0.0; 4], [n.x as f32, n.y as f32, n.z as f32, 0.0]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if wire.points.len() >= 3 {
|
||||
let p0 = glam::Vec3::from_array(wire.points[0]);
|
||||
for i in 1..wire.points.len() - 1 {
|
||||
let v1 = glam::Vec3::from_array(wire.points[i]) - p0;
|
||||
let v2 = glam::Vec3::from_array(wire.points[i + 1]) - p0;
|
||||
let cross = v1.cross(v2);
|
||||
if cross.length_squared() > 1e-6 {
|
||||
let n = cross.normalize();
|
||||
return ([0.0; 4], [0.0; 4], [n.x, n.y, n.z, 0.0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ([0.0; 4], [0.0; 4], [0.0, 0.0, 1.0, 0.0]);
|
||||
};
|
||||
let origin = marker.origin;
|
||||
let (hx, lx) = WireModel::split_ds(origin.x);
|
||||
|
||||
+79
-37
@@ -121,36 +121,83 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32, in: VertexIn) -> VertexOut
|
||||
|
||||
let rel_a = marker_relative(in.pos_a, in.pos_a_low, in.translation, in.translation_low);
|
||||
let rel_b = marker_relative(in.pos_b, in.pos_b_low, in.translation, in.translation_low);
|
||||
let clip_a = u.view_rot * vec4<f32>(rel_a, 1.0);
|
||||
let clip_b = u.view_rot * vec4<f32>(rel_b, 1.0);
|
||||
let screen_a = clip_a.xy / clip_a.w * u.viewport_size * 0.5;
|
||||
let screen_b = clip_b.xy / clip_b.w * u.viewport_size * 0.5;
|
||||
let segment = screen_b - screen_a;
|
||||
let segment_length = length(segment);
|
||||
var direction = vec2<f32>(1.0, 0.0);
|
||||
if segment_length > 1e-4 {
|
||||
direction = segment / segment_length;
|
||||
}
|
||||
let perpendicular = vec2<f32>(-direction.y, direction.x);
|
||||
let clip_position = mix(clip_a, clip_b, which_end);
|
||||
let half_width_a = resolve_hw(
|
||||
in.taper_ratio.x,
|
||||
wire_const.world_half_width,
|
||||
wire_const.half_width,
|
||||
);
|
||||
let half_width_b = resolve_hw(
|
||||
in.taper_ratio.y,
|
||||
wire_const.world_half_width,
|
||||
wire_const.half_width,
|
||||
);
|
||||
let half_width = mix(half_width_a, half_width_b, which_end);
|
||||
let is_world = wire_const.world_half_width > 0.0 || in.taper_ratio.x > 0.0 || in.taper_ratio.y > 0.0;
|
||||
let extension = select(which_end * 2.0 - 1.0, 0.0, is_world);
|
||||
let offset_px = perpendicular * half_width * side
|
||||
+ direction * half_width * extension;
|
||||
let ndc_offset = offset_px / (u.viewport_size * 0.5);
|
||||
let final_clip = clip_position
|
||||
+ vec4<f32>(ndc_offset * clip_position.w, 0.0, 0.0);
|
||||
|
||||
var final_clip: vec4<f32>;
|
||||
var out_dist: f32;
|
||||
var out_cap: vec2<f32>;
|
||||
var out_cap_ends: vec3<f32>;
|
||||
|
||||
if is_world {
|
||||
// Wide polylines have physical world-unit width (world_half_width or taper > 0).
|
||||
// Expand the quad in 3D world space on the entity's plane (perpendicular to
|
||||
// the segment and the plane normal), so the rectangle remains hosted rigidly
|
||||
// on the 3D plane when the camera rotates, orbits, or tilts, matching CAD
|
||||
// behavior and circle.wgsl planar arcs.
|
||||
let world_hw_a = select(wire_const.world_half_width, in.taper_ratio.x * wire_const.world_half_width, in.taper_ratio.x > 0.0);
|
||||
let world_hw_b = select(wire_const.world_half_width, in.taper_ratio.y * wire_const.world_half_width, in.taper_ratio.y > 0.0);
|
||||
let cur_world_hw = mix(world_hw_a, world_hw_b, which_end);
|
||||
let eff_hw = max(cur_world_hw, 0.5 * u.world_per_pixel);
|
||||
|
||||
let world_delta = rel_b - rel_a;
|
||||
let world_len = length(world_delta);
|
||||
var world_dir = vec3<f32>(1.0, 0.0, 0.0);
|
||||
if world_len > 1e-6 {
|
||||
world_dir = world_delta / world_len;
|
||||
}
|
||||
|
||||
var norm = vec3<f32>(0.0, 0.0, 1.0);
|
||||
if length(wire_const.marker_normal_scale.xyz) > 1e-4 {
|
||||
norm = normalize(wire_const.marker_normal_scale.xyz);
|
||||
}
|
||||
|
||||
var perp_world = cross(norm, world_dir);
|
||||
if length(perp_world) < 1e-4 {
|
||||
perp_world = cross(vec3<f32>(0.0, 1.0, 0.0), world_dir);
|
||||
if length(perp_world) < 1e-4 {
|
||||
perp_world = cross(vec3<f32>(1.0, 0.0, 0.0), world_dir);
|
||||
}
|
||||
}
|
||||
perp_world = normalize(perp_world);
|
||||
|
||||
let pos_rel = mix(rel_a, rel_b, which_end);
|
||||
let world_pos = pos_rel + perp_world * (eff_hw * side);
|
||||
var clip_pos = u.view_rot * vec4<f32>(world_pos, 1.0);
|
||||
clip_pos.z = clip_pos.z - in.depth.x * DRAW_ORDER_BIAS * clip_pos.w;
|
||||
|
||||
final_clip = clip_pos;
|
||||
out_dist = mix(in.distances.x, in.distances.y, which_end);
|
||||
out_cap = vec2<f32>(which_end * world_len, eff_hw * side);
|
||||
out_cap_ends = vec3<f32>(world_len, world_hw_a, world_hw_b);
|
||||
} else {
|
||||
// Thin wires expand in screen pixels facing the camera with rounded end caps / joints.
|
||||
let clip_a = u.view_rot * vec4<f32>(rel_a, 1.0);
|
||||
let clip_b = u.view_rot * vec4<f32>(rel_b, 1.0);
|
||||
let screen_a = clip_a.xy / clip_a.w * u.viewport_size * 0.5;
|
||||
let screen_b = clip_b.xy / clip_b.w * u.viewport_size * 0.5;
|
||||
let segment = screen_b - screen_a;
|
||||
let segment_length = length(segment);
|
||||
var direction = vec2<f32>(1.0, 0.0);
|
||||
if segment_length > 1e-4 {
|
||||
direction = segment / segment_length;
|
||||
}
|
||||
let perpendicular = vec2<f32>(-direction.y, direction.x);
|
||||
let clip_position = mix(clip_a, clip_b, which_end);
|
||||
|
||||
let half_width = resolve_hw(0.0, 0.0, wire_const.half_width);
|
||||
let extension = which_end * 2.0 - 1.0;
|
||||
let offset_px = perpendicular * half_width * side
|
||||
+ direction * half_width * extension;
|
||||
let ndc_offset = offset_px / (u.viewport_size * 0.5);
|
||||
var clip_pos_out = clip_position + vec4<f32>(ndc_offset * clip_position.w, 0.0, 0.0);
|
||||
clip_pos_out.z = clip_pos_out.z - in.depth.x * DRAW_ORDER_BIAS * clip_pos_out.w;
|
||||
|
||||
final_clip = clip_pos_out;
|
||||
out_dist = mix(in.distances.x, in.distances.y, which_end)
|
||||
+ extension * half_width * u.world_per_pixel;
|
||||
out_cap = vec2<f32>(which_end * segment_length + extension * half_width, half_width * side);
|
||||
out_cap_ends = vec3<f32>(segment_length, half_width, half_width);
|
||||
}
|
||||
|
||||
let scale = u.linetype_scale;
|
||||
var min_element = wire_const.pattern_length * scale;
|
||||
@@ -173,21 +220,16 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32, in: VertexIn) -> VertexOut
|
||||
|
||||
var out: VertexOut;
|
||||
out.clip_pos = final_clip;
|
||||
out.clip_pos.z -= in.depth.x * DRAW_ORDER_BIAS * out.clip_pos.w;
|
||||
out.color = wire_const.color;
|
||||
out.distance = mix(in.distances.x, in.distances.y, which_end)
|
||||
+ extension * half_width * u.world_per_pixel;
|
||||
out.distance = out_dist;
|
||||
out.cap = out_cap;
|
||||
out.cap_ends = out_cap_ends;
|
||||
out.pattern_length = wire_const.pattern_length * scale;
|
||||
out.pat0 = wire_const.pat0 * scale;
|
||||
out.pat1 = wire_const.pat1 * scale;
|
||||
out.min_elem = min_element;
|
||||
out.align_end = wire_const.align_end * scale;
|
||||
out.align_total = wire_const.align_total;
|
||||
out.cap = vec2<f32>(
|
||||
which_end * segment_length + extension * half_width,
|
||||
half_width * side,
|
||||
);
|
||||
out.cap_ends = vec3<f32>(segment_length, half_width_a, half_width_b);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -152,36 +152,83 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32, in: VertexIn) -> VertexOut
|
||||
|
||||
let rel_a = marker_relative(seg_pos_a, seg_pos_a_low, in.translation, in.translation_low);
|
||||
let rel_b = marker_relative(seg_pos_b, seg_pos_b_low, in.translation, in.translation_low);
|
||||
let clip_a = u.view_rot * vec4<f32>(rel_a, 1.0);
|
||||
let clip_b = u.view_rot * vec4<f32>(rel_b, 1.0);
|
||||
let screen_a = clip_a.xy / clip_a.w * u.viewport_size * 0.5;
|
||||
let screen_b = clip_b.xy / clip_b.w * u.viewport_size * 0.5;
|
||||
let segment = screen_b - screen_a;
|
||||
let segment_length = length(segment);
|
||||
var direction = vec2<f32>(1.0, 0.0);
|
||||
if segment_length > 1e-4 {
|
||||
direction = segment / segment_length;
|
||||
}
|
||||
let perpendicular = vec2<f32>(-direction.y, direction.x);
|
||||
let clip_position = mix(clip_a, clip_b, which_end);
|
||||
let half_width_a = resolve_hw(
|
||||
seg_taper_ratio.x,
|
||||
wire_const.world_half_width,
|
||||
wire_const.half_width,
|
||||
);
|
||||
let half_width_b = resolve_hw(
|
||||
seg_taper_ratio.y,
|
||||
wire_const.world_half_width,
|
||||
wire_const.half_width,
|
||||
);
|
||||
let half_width = mix(half_width_a, half_width_b, which_end);
|
||||
let is_world = wire_const.world_half_width > 0.0 || seg_taper_ratio.x > 0.0 || seg_taper_ratio.y > 0.0;
|
||||
let extension = select(which_end * 2.0 - 1.0, 0.0, is_world);
|
||||
let offset_px = perpendicular * half_width * side
|
||||
+ direction * half_width * extension;
|
||||
let ndc_offset = offset_px / (u.viewport_size * 0.5);
|
||||
let final_clip = clip_position
|
||||
+ vec4<f32>(ndc_offset * clip_position.w, 0.0, 0.0);
|
||||
|
||||
var final_clip: vec4<f32>;
|
||||
var out_dist: f32;
|
||||
var out_cap: vec2<f32>;
|
||||
var out_cap_ends: vec3<f32>;
|
||||
|
||||
if is_world {
|
||||
// Wide polylines have physical world-unit width (world_half_width or taper > 0).
|
||||
// Expand the quad in 3D world space on the entity's plane (perpendicular to
|
||||
// the segment and the plane normal), so the rectangle remains hosted rigidly
|
||||
// on the 3D plane when the camera rotates, orbits, or tilts, matching CAD
|
||||
// behavior and circle.wgsl planar arcs.
|
||||
let world_hw_a = select(wire_const.world_half_width, seg_taper_ratio.x * wire_const.world_half_width, seg_taper_ratio.x > 0.0);
|
||||
let world_hw_b = select(wire_const.world_half_width, seg_taper_ratio.y * wire_const.world_half_width, seg_taper_ratio.y > 0.0);
|
||||
let cur_world_hw = mix(world_hw_a, world_hw_b, which_end);
|
||||
let eff_hw = max(cur_world_hw, 0.5 * u.world_per_pixel);
|
||||
|
||||
let world_delta = rel_b - rel_a;
|
||||
let world_len = length(world_delta);
|
||||
var world_dir = vec3<f32>(1.0, 0.0, 0.0);
|
||||
if world_len > 1e-6 {
|
||||
world_dir = world_delta / world_len;
|
||||
}
|
||||
|
||||
var norm = vec3<f32>(0.0, 0.0, 1.0);
|
||||
if length(wire_const.marker_normal_scale.xyz) > 1e-4 {
|
||||
norm = normalize(wire_const.marker_normal_scale.xyz);
|
||||
}
|
||||
|
||||
var perp_world = cross(norm, world_dir);
|
||||
if length(perp_world) < 1e-4 {
|
||||
perp_world = cross(vec3<f32>(0.0, 1.0, 0.0), world_dir);
|
||||
if length(perp_world) < 1e-4 {
|
||||
perp_world = cross(vec3<f32>(1.0, 0.0, 0.0), world_dir);
|
||||
}
|
||||
}
|
||||
perp_world = normalize(perp_world);
|
||||
|
||||
let pos_rel = mix(rel_a, rel_b, which_end);
|
||||
let world_pos = pos_rel + perp_world * (eff_hw * side);
|
||||
var clip_pos = u.view_rot * vec4<f32>(world_pos, 1.0);
|
||||
clip_pos.z = clip_pos.z - in.depth.x * DRAW_ORDER_BIAS * clip_pos.w;
|
||||
|
||||
final_clip = clip_pos;
|
||||
out_dist = mix(seg_distances.x, seg_distances.y, which_end);
|
||||
out_cap = vec2<f32>(which_end * world_len, eff_hw * side);
|
||||
out_cap_ends = vec3<f32>(world_len, world_hw_a, world_hw_b);
|
||||
} else {
|
||||
// Thin wires expand in screen pixels facing the camera with rounded end caps / joints.
|
||||
let clip_a = u.view_rot * vec4<f32>(rel_a, 1.0);
|
||||
let clip_b = u.view_rot * vec4<f32>(rel_b, 1.0);
|
||||
let screen_a = clip_a.xy / clip_a.w * u.viewport_size * 0.5;
|
||||
let screen_b = clip_b.xy / clip_b.w * u.viewport_size * 0.5;
|
||||
let segment = screen_b - screen_a;
|
||||
let segment_length = length(segment);
|
||||
var direction = vec2<f32>(1.0, 0.0);
|
||||
if segment_length > 1e-4 {
|
||||
direction = segment / segment_length;
|
||||
}
|
||||
let perpendicular = vec2<f32>(-direction.y, direction.x);
|
||||
let clip_position = mix(clip_a, clip_b, which_end);
|
||||
|
||||
let half_width = resolve_hw(0.0, 0.0, wire_const.half_width);
|
||||
let extension = which_end * 2.0 - 1.0;
|
||||
let offset_px = perpendicular * half_width * side
|
||||
+ direction * half_width * extension;
|
||||
let ndc_offset = offset_px / (u.viewport_size * 0.5);
|
||||
var clip_pos_out = clip_position + vec4<f32>(ndc_offset * clip_position.w, 0.0, 0.0);
|
||||
clip_pos_out.z = clip_pos_out.z - in.depth.x * DRAW_ORDER_BIAS * clip_pos_out.w;
|
||||
|
||||
final_clip = clip_pos_out;
|
||||
out_dist = mix(seg_distances.x, seg_distances.y, which_end)
|
||||
+ extension * half_width * u.world_per_pixel;
|
||||
out_cap = vec2<f32>(which_end * segment_length + extension * half_width, half_width * side);
|
||||
out_cap_ends = vec3<f32>(segment_length, half_width, half_width);
|
||||
}
|
||||
|
||||
let scale = u.linetype_scale;
|
||||
var min_element = wire_const.pattern_length * scale;
|
||||
@@ -204,21 +251,16 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32, in: VertexIn) -> VertexOut
|
||||
|
||||
var out: VertexOut;
|
||||
out.clip_pos = final_clip;
|
||||
out.clip_pos.z -= in.depth.x * DRAW_ORDER_BIAS * out.clip_pos.w;
|
||||
out.color = wire_const.color;
|
||||
out.distance = mix(seg_distances.x, seg_distances.y, which_end)
|
||||
+ extension * half_width * u.world_per_pixel;
|
||||
out.distance = out_dist;
|
||||
out.cap = out_cap;
|
||||
out.cap_ends = out_cap_ends;
|
||||
out.pattern_length = wire_const.pattern_length * scale;
|
||||
out.pat0 = wire_const.pat0 * scale;
|
||||
out.pat1 = wire_const.pat1 * scale;
|
||||
out.min_elem = min_element;
|
||||
out.align_end = wire_const.align_end * scale;
|
||||
out.align_total = wire_const.align_total;
|
||||
out.cap = vec2<f32>(
|
||||
which_end * segment_length + extension * half_width,
|
||||
half_width * side,
|
||||
);
|
||||
out.cap_ends = vec3<f32>(segment_length, half_width_a, half_width_b);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
+85
-54
@@ -156,54 +156,89 @@ fn marker_relative(position_high: vec3<f32>, position_low: vec3<f32>, instance:
|
||||
// UTM-scale coordinates and after a cross-drawing paste, with no jitter.
|
||||
let rel_a = marker_relative(in.pos_a, in.pos_a_low, in);
|
||||
let rel_b = marker_relative(in.pos_b, in.pos_b_low, in);
|
||||
let clip_a = u.view_rot * vec4<f32>(rel_a, 1.0);
|
||||
let clip_b = u.view_rot * vec4<f32>(rel_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 direction / perpendicular of the segment.
|
||||
let seg = screen_b - screen_a;
|
||||
let seg_len = length(seg);
|
||||
var dir: vec2<f32>;
|
||||
if seg_len > 1e-4 {
|
||||
dir = seg / seg_len;
|
||||
} else {
|
||||
dir = vec2<f32>(1.0, 0.0);
|
||||
}
|
||||
let perp = vec2<f32>(-dir.y, dir.x);
|
||||
|
||||
// Select the clip-space position for this vertex's endpoint.
|
||||
let clip_pos = mix(clip_a, clip_b, which_end);
|
||||
|
||||
// A wide polyline carries its band width in world units: expand the quad by
|
||||
// `world_half_width / world_per_pixel` (pixels) so the band tracks zoom. A
|
||||
// normal wire (world_half_width == 0) uses the screen-pixel half-width,
|
||||
// honouring the LWDISPLAY toggle (off → collapse to a 1-pixel line).
|
||||
// A tapered band interpolates a per-endpoint world half-width across the
|
||||
// segment; a constant band uses `world_half_width`. Both clamp to a
|
||||
// half-pixel so a zoomed-out band stays a hairline instead of vanishing.
|
||||
let hw_a = resolve_hw(in.taper.x, in.misc.w, in.dists.z);
|
||||
let hw_b = resolve_hw(in.taper.y, in.misc.w, in.dists.z);
|
||||
let hw = mix(hw_a, hw_b, which_end);
|
||||
|
||||
// Extend the quad longitudinally by the end half-width and let the
|
||||
// fragment stage round the overhang off: adjoining segments then meet in
|
||||
// overlapping round joints, closing the wedge gaps a perpendicular-only
|
||||
// expansion leaves on the outside of corners and along tessellated arcs.
|
||||
// Wide polylines have physical world-unit width (in.misc.w or in.taper > 0);
|
||||
// in CAD, wide polyline segments are flat/square-ended 2D rectangles (or
|
||||
// trapezoids), not rounded pill shapes.
|
||||
let is_world = in.misc.w > 0.0 || in.taper.x > 0.0 || in.taper.y > 0.0;
|
||||
let ext = select(which_end * 2.0 - 1.0, 0.0, is_world);
|
||||
let offset_px = perp * hw * side + dir * hw * ext;
|
||||
let ndc_offset = offset_px / (u.viewport_size * 0.5);
|
||||
let final_clip = clip_pos + vec4<f32>(ndc_offset * clip_pos.w, 0.0, 0.0);
|
||||
|
||||
var final_clip: vec4<f32>;
|
||||
var out_dist: f32;
|
||||
var out_cap: vec2<f32>;
|
||||
var out_cap_ends: vec3<f32>;
|
||||
|
||||
if is_world {
|
||||
// Wide polylines have physical world-unit width (in.misc.w or in.taper > 0).
|
||||
// Expand the quad in 3D world space on the entity's plane (perpendicular to
|
||||
// the segment and the plane normal), so the rectangle remains hosted rigidly
|
||||
// on the 3D plane when the camera rotates, orbits, or tilts, matching CAD
|
||||
// behavior and circle.wgsl planar arcs.
|
||||
let world_hw_a = select(in.misc.w, in.taper.x, in.taper.x > 0.0);
|
||||
let world_hw_b = select(in.misc.w, in.taper.y, in.taper.y > 0.0);
|
||||
let cur_world_hw = mix(world_hw_a, world_hw_b, which_end);
|
||||
let eff_hw = max(cur_world_hw, 0.5 * u.world_per_pixel);
|
||||
|
||||
let world_delta = rel_b - rel_a;
|
||||
let world_len = length(world_delta);
|
||||
var world_dir = vec3<f32>(1.0, 0.0, 0.0);
|
||||
if world_len > 1e-6 {
|
||||
world_dir = world_delta / world_len;
|
||||
}
|
||||
|
||||
var norm = vec3<f32>(0.0, 0.0, 1.0);
|
||||
if length(in.marker_normal_scale.xyz) > 1e-4 {
|
||||
norm = normalize(in.marker_normal_scale.xyz);
|
||||
}
|
||||
|
||||
var perp_world = cross(norm, world_dir);
|
||||
if length(perp_world) < 1e-4 {
|
||||
perp_world = cross(vec3<f32>(0.0, 1.0, 0.0), world_dir);
|
||||
if length(perp_world) < 1e-4 {
|
||||
perp_world = cross(vec3<f32>(1.0, 0.0, 0.0), world_dir);
|
||||
}
|
||||
}
|
||||
perp_world = normalize(perp_world);
|
||||
|
||||
let pos_rel = mix(rel_a, rel_b, which_end);
|
||||
let world_pos = pos_rel + perp_world * (eff_hw * side);
|
||||
var clip_pos = u.view_rot * vec4<f32>(world_pos, 1.0);
|
||||
clip_pos.z = clip_pos.z - in.misc.x * DRAW_ORDER_BIAS * clip_pos.w;
|
||||
|
||||
final_clip = clip_pos;
|
||||
out_dist = mix(in.dists.x, in.dists.y, which_end);
|
||||
out_cap = vec2<f32>(which_end * world_len, eff_hw * side);
|
||||
out_cap_ends = vec3<f32>(world_len, world_hw_a, world_hw_b);
|
||||
} else {
|
||||
// Thin wires expand in screen pixels facing the camera with rounded end caps / joints.
|
||||
let clip_a = u.view_rot * vec4<f32>(rel_a, 1.0);
|
||||
let clip_b = u.view_rot * vec4<f32>(rel_b, 1.0);
|
||||
|
||||
let ndc_a = clip_a.xy / clip_a.w;
|
||||
let ndc_b = clip_b.xy / clip_b.w;
|
||||
|
||||
let screen_a = ndc_a * u.viewport_size * 0.5;
|
||||
let screen_b = ndc_b * u.viewport_size * 0.5;
|
||||
|
||||
let seg = screen_b - screen_a;
|
||||
let seg_len = length(seg);
|
||||
var dir: vec2<f32>;
|
||||
if seg_len > 1e-4 {
|
||||
dir = seg / seg_len;
|
||||
} else {
|
||||
dir = vec2<f32>(1.0, 0.0);
|
||||
}
|
||||
let perp = vec2<f32>(-dir.y, dir.x);
|
||||
|
||||
let clip_pos = mix(clip_a, clip_b, which_end);
|
||||
|
||||
let hw = resolve_hw(0.0, 0.0, in.dists.z);
|
||||
let ext = which_end * 2.0 - 1.0;
|
||||
let offset_px = perp * hw * side + dir * hw * ext;
|
||||
let ndc_offset = offset_px / (u.viewport_size * 0.5);
|
||||
var clip_pos_out = clip_pos + vec4<f32>(ndc_offset * clip_pos.w, 0.0, 0.0);
|
||||
clip_pos_out.z = clip_pos_out.z - in.misc.x * DRAW_ORDER_BIAS * clip_pos_out.w;
|
||||
|
||||
final_clip = clip_pos_out;
|
||||
out_dist = mix(in.dists.x, in.dists.y, which_end) + ext * hw * u.world_per_pixel;
|
||||
out_cap = vec2<f32>(which_end * seg_len + ext * hw, hw * side);
|
||||
out_cap_ends = vec3<f32>(seg_len, hw, hw);
|
||||
}
|
||||
|
||||
// Smallest non-zero dash / gap element, in world units. Used by
|
||||
// the fragment stage to decide when the pattern's finest feature
|
||||
@@ -223,14 +258,10 @@ fn marker_relative(position_high: vec3<f32>, position_low: vec3<f32>, instance:
|
||||
|
||||
var out: VertexOut;
|
||||
out.clip_pos = final_clip;
|
||||
out.clip_pos.z = out.clip_pos.z - in.misc.x * DRAW_ORDER_BIAS * out.clip_pos.w;
|
||||
out.color = in.color;
|
||||
// Dash arc-length, extrapolated over the cap overhang so the pattern
|
||||
// stays continuous through a joint.
|
||||
out.distance = mix(in.dists.x, in.dists.y, which_end)
|
||||
+ ext * hw * u.world_per_pixel;
|
||||
out.cap = vec2<f32>(which_end * seg_len + ext * hw, hw * side);
|
||||
out.cap_ends = vec3<f32>(seg_len, hw_a, hw_b);
|
||||
out.distance = out_dist;
|
||||
out.cap = out_cap;
|
||||
out.cap_ends = out_cap_ends;
|
||||
out.pattern_length = in.dists.w * lt_scale;
|
||||
out.pat0 = in.pat0 * lt_scale;
|
||||
out.pat1 = in.pat1 * lt_scale;
|
||||
|
||||
@@ -128,51 +128,89 @@ fn marker_relative(position_high: vec3<f32>, position_low: vec3<f32>, c: WireCon
|
||||
|
||||
let rel_a = marker_relative(in.pos_a, in.pos_a_low, c);
|
||||
let rel_b = marker_relative(in.pos_b, in.pos_b_low, c);
|
||||
let clip_a = u.view_rot * vec4<f32>(rel_a, 1.0);
|
||||
let clip_b = u.view_rot * vec4<f32>(rel_b, 1.0);
|
||||
|
||||
let ndc_a = clip_a.xy / clip_a.w;
|
||||
let ndc_b = clip_b.xy / clip_b.w;
|
||||
|
||||
let screen_a = ndc_a * u.viewport_size * 0.5;
|
||||
let screen_b = ndc_b * u.viewport_size * 0.5;
|
||||
|
||||
let seg = screen_b - screen_a;
|
||||
let seg_len = length(seg);
|
||||
var dir: vec2<f32>;
|
||||
if seg_len > 1e-4 {
|
||||
dir = seg / seg_len;
|
||||
} else {
|
||||
dir = vec2<f32>(1.0, 0.0);
|
||||
}
|
||||
let perp = vec2<f32>(-dir.y, dir.x);
|
||||
|
||||
let clip_pos = mix(clip_a, clip_b, which_end);
|
||||
|
||||
// A wide polyline carries its band width in world units: expand the quad
|
||||
// by `world_half_width / world_per_pixel` (screen pixels) so the band grows
|
||||
// and shrinks with zoom. A normal wire (world_half_width == 0) uses the
|
||||
// screen-pixel half-width, honouring the LWDISPLAY toggle.
|
||||
// A tapered band carries normalized endpoint widths on the instance:
|
||||
// interpolate across the segment so the band narrows/widens smoothly. A
|
||||
// constant band uses the per-wire `world_half_width`. Both clamp to a
|
||||
// half-pixel so a zoomed-out band stays a hairline instead of vanishing.
|
||||
let hw_a = resolve_hw(in.taper_ratio.x, c.world_half_width, c.half_width);
|
||||
let hw_b = resolve_hw(in.taper_ratio.y, c.world_half_width, c.half_width);
|
||||
let hw = mix(hw_a, hw_b, which_end);
|
||||
|
||||
// Extend the quad longitudinally by the end half-width and let the
|
||||
// fragment stage round the overhang off: adjoining segments then meet in
|
||||
// overlapping round joints, closing the wedge gaps a perpendicular-only
|
||||
// expansion leaves on the outside of corners and along tessellated arcs.
|
||||
// Wide polylines have physical world-unit width (world_half_width or taper > 0);
|
||||
// in CAD, wide polyline segments are flat/square-ended 2D rectangles (or
|
||||
// trapezoids), not rounded pill shapes.
|
||||
let is_world = c.world_half_width > 0.0 || in.taper_ratio.x > 0.0 || in.taper_ratio.y > 0.0;
|
||||
let ext = select(which_end * 2.0 - 1.0, 0.0, is_world);
|
||||
let offset_px = perp * hw * side + dir * hw * ext;
|
||||
let ndc_offset = offset_px / (u.viewport_size * 0.5);
|
||||
let final_clip = clip_pos + vec4<f32>(ndc_offset * clip_pos.w, 0.0, 0.0);
|
||||
|
||||
var final_clip: vec4<f32>;
|
||||
var out_dist: f32;
|
||||
var out_cap: vec2<f32>;
|
||||
var out_cap_ends: vec3<f32>;
|
||||
|
||||
if is_world {
|
||||
// Wide polylines have physical world-unit width (world_half_width or taper > 0).
|
||||
// Expand the quad in 3D world space on the entity's plane (perpendicular to
|
||||
// the segment and the plane normal), so the rectangle remains hosted rigidly
|
||||
// on the 3D plane when the camera rotates, orbits, or tilts, matching CAD
|
||||
// behavior and circle.wgsl planar arcs.
|
||||
let world_hw_a = select(c.world_half_width, in.taper_ratio.x * c.world_half_width, in.taper_ratio.x > 0.0);
|
||||
let world_hw_b = select(c.world_half_width, in.taper_ratio.y * c.world_half_width, in.taper_ratio.y > 0.0);
|
||||
let cur_world_hw = mix(world_hw_a, world_hw_b, which_end);
|
||||
let eff_hw = max(cur_world_hw, 0.5 * u.world_per_pixel);
|
||||
|
||||
let world_delta = rel_b - rel_a;
|
||||
let world_len = length(world_delta);
|
||||
var world_dir = vec3<f32>(1.0, 0.0, 0.0);
|
||||
if world_len > 1e-6 {
|
||||
world_dir = world_delta / world_len;
|
||||
}
|
||||
|
||||
var norm = vec3<f32>(0.0, 0.0, 1.0);
|
||||
if length(c.marker_normal_scale.xyz) > 1e-4 {
|
||||
norm = normalize(c.marker_normal_scale.xyz);
|
||||
}
|
||||
|
||||
var perp_world = cross(norm, world_dir);
|
||||
if length(perp_world) < 1e-4 {
|
||||
perp_world = cross(vec3<f32>(0.0, 1.0, 0.0), world_dir);
|
||||
if length(perp_world) < 1e-4 {
|
||||
perp_world = cross(vec3<f32>(1.0, 0.0, 0.0), world_dir);
|
||||
}
|
||||
}
|
||||
perp_world = normalize(perp_world);
|
||||
|
||||
let pos_rel = mix(rel_a, rel_b, which_end);
|
||||
let world_pos = pos_rel + perp_world * (eff_hw * side);
|
||||
var clip_pos = u.view_rot * vec4<f32>(world_pos, 1.0);
|
||||
clip_pos.z = clip_pos.z - c.draw_depth * DRAW_ORDER_BIAS * clip_pos.w;
|
||||
|
||||
final_clip = clip_pos;
|
||||
out_dist = mix(in.distance_a, in.distance_b, which_end);
|
||||
out_cap = vec2<f32>(which_end * world_len, eff_hw * side);
|
||||
out_cap_ends = vec3<f32>(world_len, world_hw_a, world_hw_b);
|
||||
} else {
|
||||
// Thin wires expand in screen pixels facing the camera with rounded end caps / joints.
|
||||
let clip_a = u.view_rot * vec4<f32>(rel_a, 1.0);
|
||||
let clip_b = u.view_rot * vec4<f32>(rel_b, 1.0);
|
||||
|
||||
let ndc_a = clip_a.xy / clip_a.w;
|
||||
let ndc_b = clip_b.xy / clip_b.w;
|
||||
|
||||
let screen_a = ndc_a * u.viewport_size * 0.5;
|
||||
let screen_b = ndc_b * u.viewport_size * 0.5;
|
||||
|
||||
let seg = screen_b - screen_a;
|
||||
let seg_len = length(seg);
|
||||
var dir: vec2<f32>;
|
||||
if seg_len > 1e-4 {
|
||||
dir = seg / seg_len;
|
||||
} else {
|
||||
dir = vec2<f32>(1.0, 0.0);
|
||||
}
|
||||
let perp = vec2<f32>(-dir.y, dir.x);
|
||||
|
||||
let clip_pos = mix(clip_a, clip_b, which_end);
|
||||
|
||||
let hw = resolve_hw(0.0, 0.0, c.half_width);
|
||||
let ext = which_end * 2.0 - 1.0;
|
||||
let offset_px = perp * hw * side + dir * hw * ext;
|
||||
let ndc_offset = offset_px / (u.viewport_size * 0.5);
|
||||
var clip_pos_out = clip_pos + vec4<f32>(ndc_offset * clip_pos.w, 0.0, 0.0);
|
||||
clip_pos_out.z = clip_pos_out.z - c.draw_depth * DRAW_ORDER_BIAS * clip_pos_out.w;
|
||||
|
||||
final_clip = clip_pos_out;
|
||||
out_dist = mix(in.distance_a, in.distance_b, which_end) + ext * hw * u.world_per_pixel;
|
||||
out_cap = vec2<f32>(which_end * seg_len + ext * hw, hw * side);
|
||||
out_cap_ends = vec3<f32>(seg_len, hw, hw);
|
||||
}
|
||||
|
||||
let lt_scale = u.linetype_scale;
|
||||
var min_elem: f32 = c.pattern_length * lt_scale;
|
||||
@@ -189,14 +227,10 @@ fn marker_relative(position_high: vec3<f32>, position_low: vec3<f32>, c: WireCon
|
||||
|
||||
var out: VertexOut;
|
||||
out.clip_pos = final_clip;
|
||||
out.clip_pos.z = out.clip_pos.z - c.draw_depth * DRAW_ORDER_BIAS * out.clip_pos.w;
|
||||
out.color = c.color;
|
||||
// Dash arc-length, extrapolated over the cap overhang so the pattern
|
||||
// stays continuous through a joint.
|
||||
out.distance = mix(in.distance_a, in.distance_b, which_end)
|
||||
+ ext * hw * u.world_per_pixel;
|
||||
out.cap = vec2<f32>(which_end * seg_len + ext * hw, hw * side);
|
||||
out.cap_ends = vec3<f32>(seg_len, hw_a, hw_b);
|
||||
out.distance = out_dist;
|
||||
out.cap = out_cap;
|
||||
out.cap_ends = out_cap_ends;
|
||||
out.pattern_length = c.pattern_length * lt_scale;
|
||||
out.pat0 = c.pat0 * lt_scale;
|
||||
out.pat1 = c.pat1 * lt_scale;
|
||||
|
||||
Reference in new issue
Block a user