forked from 1b-agent/opencadstudio-upstream
refactor(tess): move solid sampling to kernel
Remove local face, edge, spline, sweep, loft, and silhouette samplers. Meshes and overlays share the kernel tolerance and edge schedule.
This commit is contained in:
1 parent
60f5bca0d4
commit
dc998fe46b
15 files changed
+410
-3938
No files matched your search
+8
-180
@@ -168,9 +168,7 @@ pub struct Pipeline {
|
||||
/// it back into the base set (issue #316).
|
||||
text_preview_vbuf: Option<wgpu::Buffer>,
|
||||
text_preview_vcount: u32,
|
||||
/// Per-frame DISPSILH silhouette line list — rebuilt every prepare() from
|
||||
/// the mesh sets' curved-face generators and the current view direction, so
|
||||
/// the outline tracks the camera. Reuses the mesh vertex format / pipeline.
|
||||
/// Per-frame silhouette line list from the kernel mesh and current view.
|
||||
silhouette_vbuf: Option<wgpu::Buffer>,
|
||||
silhouette_vcount: u32,
|
||||
/// Last requested render size (the full viewport rect, in pixels). The
|
||||
@@ -2290,35 +2288,17 @@ impl Pipeline {
|
||||
// The math lives outside the GPU method so it can be unit-tested; see the
|
||||
// module test below.
|
||||
|
||||
/// Rebuild the per-frame DISPSILH silhouette line list from the mesh sets'
|
||||
/// curved-face generators and the current eye. For each cone/cylinder face
|
||||
/// the silhouette runs at the two angles where the surface turns edge-on to
|
||||
/// the view — `θ = φ ± acos(-tanα·(view·axis) / |view⊥|)`, which reduces to
|
||||
/// `φ ± π/2` for a cylinder. Segments are uploaded in the mesh vertex format
|
||||
/// so they draw through the existing wireframe pipeline.
|
||||
/// Rebuild view-dependent silhouette lines through the kernel.
|
||||
pub fn upload_silhouettes(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
sets: &[crate::scene::model::mesh_model::MeshLodSet],
|
||||
view_dir: glam::Vec3,
|
||||
) {
|
||||
// Silhouettes follow the view *angle* only — a single parallel direction
|
||||
// for the whole scene, not the eye-to-surface vector — so the outline
|
||||
// stays put under pan and doesn't foreshorten. This is the orthographic
|
||||
// silhouette a CAD wireframe expects.
|
||||
let view = glam::DVec3::new(view_dir.x as f64, view_dir.y as f64, view_dir.z as f64)
|
||||
.normalize_or(glam::DVec3::NEG_Z);
|
||||
use crate::scene::model::mesh_model::CurvedGen;
|
||||
use crate::scene::pipeline::mesh_gpu::MeshVertex;
|
||||
let mut verts: Vec<MeshVertex> = Vec::new();
|
||||
let d3 = |a: [f32; 3]| glam::DVec3::new(a[0] as f64, a[1] as f64, a[2] as f64);
|
||||
let lo = |c: [f32; 3], l: [f32; 3]| {
|
||||
glam::DVec3::new(
|
||||
c[0] as f64 + l[0] as f64,
|
||||
c[1] as f64 + l[1] as f64,
|
||||
c[2] as f64 + l[2] as f64,
|
||||
)
|
||||
};
|
||||
for set in sets {
|
||||
let color = set.lods.first().map(|m| m.color).unwrap_or([0.0, 0.0, 0.0, 1.0]);
|
||||
let mk = |w: glam::DVec3| -> MeshVertex {
|
||||
@@ -2346,141 +2326,12 @@ impl Pipeline {
|
||||
uv_normal: [0.0; 2],
|
||||
}
|
||||
};
|
||||
for g in &set.curved_gens {
|
||||
match g {
|
||||
CurvedGen::Cone {
|
||||
base, base_low, axis, u_dir, v_dir, radius, tan_a,
|
||||
h_max, theta_min, theta_span, full,
|
||||
} => {
|
||||
let base = lo(*base, *base_low);
|
||||
let (axis, u, v) = (d3(*axis), d3(*u_dir), d3(*v_dir));
|
||||
let Some((t0, t1)) =
|
||||
silhouette_thetas(view.dot(u), view.dot(v), view.dot(axis), *tan_a as f64)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let r0 = *radius as f64;
|
||||
let r1 = *radius as f64 + *h_max as f64 * *tan_a as f64;
|
||||
for theta in [t0, t1] {
|
||||
if !full {
|
||||
let off = (theta - *theta_min as f64).rem_euclid(std::f64::consts::TAU);
|
||||
if off > *theta_span as f64 {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let (c, s) = (theta.cos(), theta.sin());
|
||||
let radial = u * c + v * s;
|
||||
verts.push(mk(base + radial * r0));
|
||||
verts.push(mk(base + radial * r1 + axis * *h_max as f64));
|
||||
}
|
||||
}
|
||||
CurvedGen::Sphere {
|
||||
center, center_low, pole, u_dir, v_dir, radius,
|
||||
theta_min, theta_span, full, phi_min, phi_max,
|
||||
} => {
|
||||
let c = lo(*center, *center_low);
|
||||
let (pole, u, v) = (d3(*pole), d3(*u_dir), d3(*v_dir));
|
||||
let r = *radius as f64;
|
||||
// Great circle in the plane perpendicular to the view.
|
||||
let mut e1 = view.cross(pole);
|
||||
if e1.length_squared() < 1e-12 {
|
||||
e1 = view.cross(u);
|
||||
}
|
||||
let e1 = e1.normalize();
|
||||
let e2 = view.cross(e1).normalize();
|
||||
const N: usize = 64;
|
||||
let mut prev: Option<glam::DVec3> = None;
|
||||
for i in 0..=N {
|
||||
let a = std::f64::consts::TAU * (i as f64 / N as f64);
|
||||
let dir = e1 * a.cos() + e2 * a.sin();
|
||||
// Keep only the arc that lies on the actual face.
|
||||
// `full` is a *longitude* wrap flag: a dish cap sits
|
||||
// on the pole and so covers every longitude while
|
||||
// still ending at its seam, so the colatitude test
|
||||
// always applies. A whole ball reports phi 0..π and
|
||||
// passes it regardless.
|
||||
let phi = dir.dot(pole).clamp(-1.0, 1.0).acos();
|
||||
let in_phi =
|
||||
phi >= *phi_min as f64 && phi <= *phi_max as f64;
|
||||
let in_theta = *full || {
|
||||
let th = dir.dot(v).atan2(dir.dot(u));
|
||||
let toff = (th - *theta_min as f64).rem_euclid(std::f64::consts::TAU);
|
||||
toff <= *theta_span as f64
|
||||
};
|
||||
let on_face = in_phi && in_theta;
|
||||
let p = if on_face { Some(c + dir * r) } else { None };
|
||||
if let (Some(a), Some(b)) = (prev, p) {
|
||||
verts.push(mk(a));
|
||||
verts.push(mk(b));
|
||||
}
|
||||
prev = p;
|
||||
}
|
||||
}
|
||||
CurvedGen::Torus {
|
||||
center, center_low, axis, u_dir, v_dir, major, minor,
|
||||
phi_min, phi_span, full, theta_min, theta_span, theta_full,
|
||||
} => {
|
||||
let ctr = lo(*center, *center_low);
|
||||
let (axis, u, v) = (d3(*axis), d3(*u_dir), d3(*v_dir));
|
||||
let (major, minor) = (*major as f64, *minor as f64);
|
||||
// True silhouette: at each revolution angle the tube is a
|
||||
// circle; the two points where its normal turns edge-on
|
||||
// trace two curves around the ring. Sample the revolution
|
||||
// and connect consecutive edge-on points.
|
||||
const N: usize = 72;
|
||||
let span = if *full { std::f64::consts::TAU } else { *phi_span as f64 };
|
||||
let mut prev: [Option<glam::DVec3>; 2] = [None, None];
|
||||
for i in 0..=N {
|
||||
let phi = *phi_min as f64 + span * (i as f64 / N as f64);
|
||||
let radial = u * phi.cos() + v * phi.sin();
|
||||
let ring = ctr + radial * major;
|
||||
let (rv, av) = (radial.dot(view), axis.dot(view));
|
||||
if rv.abs() < 1e-9 && av.abs() < 1e-9 {
|
||||
prev = [None, None];
|
||||
continue;
|
||||
}
|
||||
// tube normal(θ) = radial·cosθ + axis·sinθ; ⟂ view at
|
||||
// θ = atan2(-rv, av) and +π.
|
||||
let th = (-rv).atan2(av);
|
||||
let cur = [th, th + std::f64::consts::PI];
|
||||
for k in 0..2 {
|
||||
let t = cur[k];
|
||||
let theta_offset =
|
||||
(t - *theta_min as f64).rem_euclid(std::f64::consts::TAU);
|
||||
let on_face = *theta_full || theta_offset <= *theta_span as f64;
|
||||
let p = on_face.then(|| {
|
||||
ring + (radial * t.cos() + axis * t.sin()) * minor
|
||||
});
|
||||
if let (Some(pp), Some(p)) = (prev[k], p) {
|
||||
verts.push(mk(pp));
|
||||
verts.push(mk(p));
|
||||
}
|
||||
prev[k] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if set.curved_gens.is_empty() || !set.complete {
|
||||
let best = set.stored_silhouettes.iter().max_by(|left, right| {
|
||||
let score = |silhouette: &crate::scene::model::mesh_model::StoredSilhouette| {
|
||||
let direction = d3(silhouette.view_direction)
|
||||
.normalize_or(glam::DVec3::NEG_Z);
|
||||
direction.dot(view).abs()
|
||||
};
|
||||
score(left)
|
||||
.partial_cmp(&score(right))
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
if let Some(silhouette) = best {
|
||||
for (index, high) in silhouette.edge_verts.iter().copied().enumerate() {
|
||||
let low = silhouette
|
||||
.edge_verts_low
|
||||
.get(index)
|
||||
.copied()
|
||||
.unwrap_or([0.0; 3]);
|
||||
verts.push(mk(lo(high, low)));
|
||||
}
|
||||
for generator in &set.curved_gens {
|
||||
for point in acadrust::kernel::brep::mesh::silhouette(
|
||||
&generator.source,
|
||||
[view.x, view.y, view.z],
|
||||
) {
|
||||
verts.push(mk(glam::DVec3::from_array(point)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4678,26 +4529,3 @@ impl iced::widget::shader::Pipeline for MultiPipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The two silhouette angles of a cone/cylinder face for a view direction,
|
||||
/// expressed in the face's `(u, v, axis)` frame via the view's components on
|
||||
/// each: `du = view·u`, `dv = view·v`, `da = view·axis`. `tan_a` is the cone
|
||||
/// taper (0 for a cylinder).
|
||||
///
|
||||
/// The outward normal is edge-on to the view where `du·cosθ + dv·sinθ =
|
||||
/// -tanα·da`, i.e. `θ = φ ± acos(-tanα·da / |view⊥|)` with `φ = atan2(dv, du)`.
|
||||
/// `None` when the view runs down the axis (no outline) or the whole cone faces
|
||||
/// toward/away (`|arg| > 1`).
|
||||
fn silhouette_thetas(du: f64, dv: f64, da: f64, tan_a: f64) -> Option<(f64, f64)> {
|
||||
let r_perp = (du * du + dv * dv).sqrt();
|
||||
if r_perp < 1e-6 {
|
||||
return None;
|
||||
}
|
||||
let arg = -tan_a * da / r_perp;
|
||||
if arg.abs() > 1.0 {
|
||||
return None;
|
||||
}
|
||||
let phi = dv.atan2(du);
|
||||
let delta = arg.acos();
|
||||
Some((phi + delta, phi - delta))
|
||||
}
|
||||
Reference in new issue
Block a user