diff --git a/src/modules/draw/draw/arc.rs b/src/modules/draw/draw/arc.rs index 80e8f2da1..c1851af62 100644 --- a/src/modules/draw/draw/arc.rs +++ b/src/modules/draw/draw/arc.rs @@ -7,7 +7,7 @@ use cadkernel::geom2d::{self, Curve as KernelCurve}; use crate::command::{CadCommand, CmdResult, WorkingPlane}; use crate::modules::IconKind; -use crate::scene::model::wire_model::WireModel; +use crate::scene::model::wire_model::{TangentGeom, WireModel}; use glam::DVec3; const TAU: f64 = std::f64::consts::TAU; @@ -71,9 +71,9 @@ fn arc_preview( end_angle: f64, plane: WorkingPlane, ) -> Option { - let center = plane.to_local(center); + let center_local = plane.to_local(center); let arc = geom2d::bounded_arc( - [center.x, center.y], + [center_local.x, center_local.y], radius, start_angle, end_angle, @@ -81,14 +81,23 @@ fn arc_preview( let points = KernelCurve::Arc(arc) .tessellate_angle(TAU / 64.0) .into_iter() - .map(|point| plane.to_world(DVec3::new(point[0], point[1], center.z)).to_array()) + .map(|point| plane.to_world(DVec3::new(point[0], point[1], center_local.z)).to_array()) .collect(); - Some(WireModel::solid_f64( + let mut wire = WireModel::solid_f64( "rubber_band".into(), points, WireModel::CYAN, false, - )) + ); + wire.tangent_geoms.push(TangentGeom::Arc { + center: [center.x, center.y, center.z], + axis_x: plane.x.to_array(), + axis_y: plane.y.to_array(), + radius: arc.radius, + start_angle: arc.start_angle, + end_angle: arc.end_angle, + }); + Some(wire) } fn make_arc( diff --git a/src/modules/draw/draw/circle.rs b/src/modules/draw/draw/circle.rs index 12c684bf5..38dc0731f 100644 --- a/src/modules/draw/draw/circle.rs +++ b/src/modules/draw/draw/circle.rs @@ -11,7 +11,7 @@ use crate::t; use crate::command::{CadCommand, CmdResult, DynField, TangentObject, WorkingPlane}; use crate::modules::draw::defaults; use crate::modules::IconKind; -use crate::scene::model::wire_model::WireModel; +use crate::scene::model::wire_model::{TangentGeom, WireModel}; use glam::DVec3; const TAU: f64 = std::f64::consts::TAU; @@ -56,16 +56,23 @@ pub const ICON: IconKind = ICON_CR; // ── Shared geometry ──────────────────────────────────────────────────────── fn circle_wire(center: DVec3, radius: f64, plane: WorkingPlane) -> WireModel { - let center = plane.to_local(center); + let center_local = plane.to_local(center); let points = KernelCurve::Circle(KernelCircle { - centre: [center.x, center.y], + centre: [center_local.x, center_local.y], radius, }) .tessellate_angle(TAU / 64.0) .into_iter() - .map(|point| plane.to_world(DVec3::new(point[0], point[1], center.z)).to_array()) + .map(|point| plane.to_world(DVec3::new(point[0], point[1], center_local.z)).to_array()) .collect(); - WireModel::solid_f64("rubber_band".into(), points, WireModel::CYAN, false) + let mut wire = WireModel::solid_f64("rubber_band".into(), points, WireModel::CYAN, false); + wire.tangent_geoms.push(TangentGeom::PlanarCircle { + center: [center.x, center.y, center.z], + axis_x: plane.x.to_array(), + axis_y: plane.y.to_array(), + radius, + }); + wire } fn make_circle(center: DVec3, radius: f64, plane: WorkingPlane) -> EntityType { diff --git a/src/modules/draw/draw/ellipse.rs b/src/modules/draw/draw/ellipse.rs index 1b975efde..db8af61c4 100644 --- a/src/modules/draw/draw/ellipse.rs +++ b/src/modules/draw/draw/ellipse.rs @@ -11,7 +11,7 @@ use crate::t; use crate::command::{CadCommand, CmdResult, WorkingPlane}; use crate::modules::IconKind; -use crate::scene::model::wire_model::WireModel; +use crate::scene::model::wire_model::{TangentGeom, WireModel}; use glam::DVec3; fn parse_num(text: &str) -> Option { @@ -75,7 +75,16 @@ fn ellipse_wire( [p.x, p.y, p.z] }) .collect(); - WireModel::solid_f64("rubber_band".into(), pts, WireModel::CYAN, false) + let mut wire = WireModel::solid_f64("rubber_band".into(), pts, WireModel::CYAN, false); + wire.tangent_geoms.push(TangentGeom::PlanarEllipse { + center: [center.x, center.y, center.z], + major_axis: [major.x, major.y, major.z], + normal: plane.z.to_array(), + minor_axis_ratio: ratio, + start_param: t_start, + end_param: t_e, + }); + wire } /// Convert a world point to the parametric angle on the ellipse. diff --git a/src/scene/model/wire_model.rs b/src/scene/model/wire_model.rs index 1f76e7129..30905d7a3 100644 --- a/src/scene/model/wire_model.rs +++ b/src/scene/model/wire_model.rs @@ -58,6 +58,201 @@ pub enum TangentGeom { }, } +impl TangentGeom { + pub fn translated(&self, delta: glam::DVec3) -> Self { + match self { + Self::Line { p1, p2 } => Self::Line { + p1: [p1[0] + delta.x as f32, p1[1] + delta.y as f32, p1[2] + delta.z as f32], + p2: [p2[0] + delta.x as f32, p2[1] + delta.y as f32, p2[2] + delta.z as f32], + }, + Self::Circle { center, radius } => Self::Circle { + center: [center[0] + delta.x as f32, center[1] + delta.y as f32, center[2] + delta.z as f32], + radius: *radius, + }, + Self::PlanarCircle { center, axis_x, axis_y, radius } => Self::PlanarCircle { + center: [center[0] + delta.x, center[1] + delta.y, center[2] + delta.z], + axis_x: *axis_x, + axis_y: *axis_y, + radius: *radius, + }, + Self::Arc { center, axis_x, axis_y, radius, start_angle, end_angle } => Self::Arc { + center: [center[0] + delta.x, center[1] + delta.y, center[2] + delta.z], + axis_x: *axis_x, + axis_y: *axis_y, + radius: *radius, + start_angle: *start_angle, + end_angle: *end_angle, + }, + Self::PlanarEllipse { center, major_axis, normal, minor_axis_ratio, start_param, end_param } => Self::PlanarEllipse { + center: [center[0] + delta.x, center[1] + delta.y, center[2] + delta.z], + major_axis: *major_axis, + normal: *normal, + minor_axis_ratio: *minor_axis_ratio, + start_param: *start_param, + end_param: *end_param, + }, + } + } + + pub fn rotated(&self, center: glam::DVec3, axis: glam::DVec3, angle_rad: f64) -> Self { + let quat = glam::DQuat::from_axis_angle(axis.normalize_or(glam::DVec3::Z), angle_rad); + let rot_pt = |p: [f64; 3]| (center + quat * (glam::DVec3::from_array(p) - center)).to_array(); + let rot_vec = |v: [f64; 3]| (quat * glam::DVec3::from_array(v)).to_array(); + match self { + Self::Line { p1, p2 } => { + let p1_d = [p1[0] as f64, p1[1] as f64, p1[2] as f64]; + let p2_d = [p2[0] as f64, p2[1] as f64, p2[2] as f64]; + let r1 = rot_pt(p1_d); + let r2 = rot_pt(p2_d); + Self::Line { + p1: [r1[0] as f32, r1[1] as f32, r1[2] as f32], + p2: [r2[0] as f32, r2[1] as f32, r2[2] as f32], + } + } + Self::Circle { center: c, radius } => { + let c_d = [c[0] as f64, c[1] as f64, c[2] as f64]; + let rc = rot_pt(c_d); + Self::Circle { + center: [rc[0] as f32, rc[1] as f32, rc[2] as f32], + radius: *radius, + } + } + Self::PlanarCircle { center: c, axis_x, axis_y, radius } => Self::PlanarCircle { + center: rot_pt(*c), + axis_x: rot_vec(*axis_x), + axis_y: rot_vec(*axis_y), + radius: *radius, + }, + Self::Arc { center: c, axis_x, axis_y, radius, start_angle, end_angle } => Self::Arc { + center: rot_pt(*c), + axis_x: rot_vec(*axis_x), + axis_y: rot_vec(*axis_y), + radius: *radius, + start_angle: *start_angle, + end_angle: *end_angle, + }, + Self::PlanarEllipse { center: c, major_axis, normal, minor_axis_ratio, start_param, end_param } => Self::PlanarEllipse { + center: rot_pt(*c), + major_axis: rot_vec(*major_axis), + normal: rot_vec(*normal), + minor_axis_ratio: *minor_axis_ratio, + start_param: *start_param, + end_param: *end_param, + }, + } + } + + pub fn scaled(&self, center: glam::DVec3, factor: f64) -> Self { + let scale_pt = |p: [f64; 3]| (center + (glam::DVec3::from_array(p) - center) * factor).to_array(); + match self { + Self::Line { p1, p2 } => { + let p1_d = [p1[0] as f64, p1[1] as f64, p1[2] as f64]; + let p2_d = [p2[0] as f64, p2[1] as f64, p2[2] as f64]; + let s1 = scale_pt(p1_d); + let s2 = scale_pt(p2_d); + Self::Line { + p1: [s1[0] as f32, s1[1] as f32, s1[2] as f32], + p2: [s2[0] as f32, s2[1] as f32, s2[2] as f32], + } + } + Self::Circle { center: c, radius } => { + let c_d = [c[0] as f64, c[1] as f64, c[2] as f64]; + let sc = scale_pt(c_d); + Self::Circle { + center: [sc[0] as f32, sc[1] as f32, sc[2] as f32], + radius: *radius * factor.abs() as f32, + } + } + Self::PlanarCircle { center: c, axis_x, axis_y, radius } => Self::PlanarCircle { + center: scale_pt(*c), + axis_x: *axis_x, + axis_y: *axis_y, + radius: *radius * factor.abs(), + }, + Self::Arc { center: c, axis_x, axis_y, radius, start_angle, end_angle } => Self::Arc { + center: scale_pt(*c), + axis_x: *axis_x, + axis_y: *axis_y, + radius: *radius * factor.abs(), + start_angle: *start_angle, + end_angle: *end_angle, + }, + Self::PlanarEllipse { center: c, major_axis, normal, minor_axis_ratio, start_param, end_param } => Self::PlanarEllipse { + center: scale_pt(*c), + major_axis: (glam::DVec3::from_array(*major_axis) * factor.abs()).to_array(), + normal: *normal, + minor_axis_ratio: *minor_axis_ratio, + start_param: *start_param, + end_param: *end_param, + }, + } + } + + pub fn mirrored(&self, p1: glam::DVec3, normal: glam::DVec3) -> Self { + let refl_pt = |p: [f64; 3]| { + let pt = glam::DVec3::from_array(p); + (pt - 2.0 * normal.dot(pt - p1) * normal).to_array() + }; + let refl_vec = |v: [f64; 3]| { + let vec = glam::DVec3::from_array(v); + (vec - 2.0 * normal.dot(vec) * normal).to_array() + }; + match self { + Self::Line { p1: lp1, p2: lp2 } => { + let p1_d = [lp1[0] as f64, lp1[1] as f64, lp1[2] as f64]; + let p2_d = [lp2[0] as f64, lp2[1] as f64, lp2[2] as f64]; + let m1 = refl_pt(p1_d); + let m2 = refl_pt(p2_d); + Self::Line { + p1: [m1[0] as f32, m1[1] as f32, m1[2] as f32], + p2: [m2[0] as f32, m2[1] as f32, m2[2] as f32], + } + } + Self::Circle { center: c, radius } => { + let c_d = [c[0] as f64, c[1] as f64, c[2] as f64]; + let mc = refl_pt(c_d); + Self::Circle { + center: [mc[0] as f32, mc[1] as f32, mc[2] as f32], + radius: *radius, + } + } + Self::PlanarCircle { center: c, axis_x, axis_y, radius } => { + let ax = refl_vec(*axis_x); + let ay = refl_vec(*axis_y); + Self::PlanarCircle { + center: refl_pt(*c), + axis_x: ax, + axis_y: [-ay[0], -ay[1], -ay[2]], + radius: *radius, + } + } + Self::Arc { center: c, axis_x, axis_y, radius, start_angle, end_angle } => { + let ax = refl_vec(*axis_x); + let ay = refl_vec(*axis_y); + Self::Arc { + center: refl_pt(*c), + axis_x: ax, + axis_y: [-ay[0], -ay[1], -ay[2]], + radius: *radius, + start_angle: -end_angle, + end_angle: -start_angle, + } + } + Self::PlanarEllipse { center: c, major_axis, normal: n, minor_axis_ratio, start_param, end_param } => { + let norm = refl_vec(*n); + Self::PlanarEllipse { + center: refl_pt(*c), + major_axis: refl_vec(*major_axis), + normal: [-norm[0], -norm[1], -norm[2]], + minor_axis_ratio: *minor_axis_ratio, + start_param: *start_param, + end_param: *end_param, + } + } + } + } +} + /// Viewport-relative display transform for a point marker. #[derive(Clone, Copy, Debug, PartialEq)] pub struct PointMarker { @@ -413,6 +608,11 @@ impl WireModel { out.text_verts = map_text_verts(&self.text_verts, |x, y, z| (x + dx, y + dy, z + dz)); } + out.tangent_geoms = self + .tangent_geoms + .iter() + .map(|tg| tg.translated(delta.as_dvec3())) + .collect(); out } @@ -465,6 +665,11 @@ impl WireModel { (mapped.x, mapped.y, mapped.z) }); } + out.tangent_geoms = self + .tangent_geoms + .iter() + .map(|tg| tg.rotated(center.as_dvec3(), axis.as_dvec3(), angle_rad as f64)) + .collect(); out } @@ -499,6 +704,11 @@ impl WireModel { (cx + (x - cx) * f, cy + (y - cy) * f, cz + (z - cz) * f) }); } + out.tangent_geoms = self + .tangent_geoms + .iter() + .map(|tg| tg.scaled(center.as_dvec3(), factor as f64)) + .collect(); out } @@ -639,6 +849,11 @@ pub fn stretched_windows( (mapped.x, mapped.y, mapped.z) }); } + out.tangent_geoms = self + .tangent_geoms + .iter() + .map(|tg| tg.mirrored(p1.as_dvec3(), plane_normal.as_dvec3())) + .collect(); out } @@ -764,3 +979,104 @@ impl Default for WireModel { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn preview_transformations_preserve_and_update_tangent_geoms() { + let mut wire = WireModel::default(); + wire.tangent_geoms.push(TangentGeom::PlanarCircle { + center: [10.0, 20.0, 0.0], + axis_x: [1.0, 0.0, 0.0], + axis_y: [0.0, 1.0, 0.0], + radius: 5.0, + }); + wire.tangent_geoms.push(TangentGeom::Arc { + center: [10.0, 20.0, 0.0], + axis_x: [1.0, 0.0, 0.0], + axis_y: [0.0, 1.0, 0.0], + radius: 5.0, + start_angle: 0.0, + end_angle: std::f64::consts::FRAC_PI_2, + }); + wire.tangent_geoms.push(TangentGeom::PlanarEllipse { + center: [10.0, 20.0, 0.0], + major_axis: [10.0, 0.0, 0.0], + normal: [0.0, 0.0, 1.0], + minor_axis_ratio: 0.5, + start_param: 0.0, + end_param: std::f64::consts::PI, + }); + + // Test translated + let trans = wire.translated(glam::Vec3::new(5.0, -5.0, 1.0)); + assert_eq!(trans.tangent_geoms.len(), 3); + if let TangentGeom::PlanarCircle { center, radius, .. } = trans.tangent_geoms[0] { + assert!((center[0] - 15.0).abs() < 1e-5); + assert!((center[1] - 15.0).abs() < 1e-5); + assert!((center[2] - 1.0).abs() < 1e-5); + assert!((radius - 5.0).abs() < 1e-5); + } else { + panic!("Expected PlanarCircle"); + } + if let TangentGeom::Arc { center, radius, start_angle, end_angle, .. } = trans.tangent_geoms[1] { + assert!((center[0] - 15.0).abs() < 1e-5); + assert!((center[1] - 15.0).abs() < 1e-5); + assert!((center[2] - 1.0).abs() < 1e-5); + assert!((radius - 5.0).abs() < 1e-5); + assert_eq!(start_angle, 0.0); + assert_eq!(end_angle, std::f64::consts::FRAC_PI_2); + } else { + panic!("Expected Arc"); + } + if let TangentGeom::PlanarEllipse { center, major_axis, minor_axis_ratio, .. } = trans.tangent_geoms[2] { + assert!((center[0] - 15.0).abs() < 1e-5); + assert!((center[1] - 15.0).abs() < 1e-5); + assert!((center[2] - 1.0).abs() < 1e-5); + assert_eq!(major_axis, [10.0, 0.0, 0.0]); + assert_eq!(minor_axis_ratio, 0.5); + } else { + panic!("Expected PlanarEllipse"); + } + + // Test scaled + let scaled = wire.scaled(glam::Vec3::new(10.0, 20.0, 0.0), 2.0); + if let TangentGeom::PlanarCircle { radius, .. } = scaled.tangent_geoms[0] { + assert!((radius - 10.0).abs() < 1e-5); + } else { + panic!("Expected PlanarCircle"); + } + if let TangentGeom::Arc { radius, .. } = scaled.tangent_geoms[1] { + assert!((radius - 10.0).abs() < 1e-5); + } else { + panic!("Expected Arc"); + } + if let TangentGeom::PlanarEllipse { major_axis, .. } = scaled.tangent_geoms[2] { + assert!((major_axis[0] - 20.0).abs() < 1e-5); + } else { + panic!("Expected PlanarEllipse"); + } + + // Test rotated + let rot = wire.rotated(glam::Vec3::ZERO, std::f32::consts::FRAC_PI_2); + if let TangentGeom::PlanarCircle { center, axis_x, axis_y, .. } = rot.tangent_geoms[0] { + assert!((center[0] - -20.0).abs() < 1e-4); + assert!((center[1] - 10.0).abs() < 1e-4); + assert!((axis_x[1] - 1.0).abs() < 1e-4); + assert!((axis_y[0] - -1.0).abs() < 1e-4); + } else { + panic!("Expected PlanarCircle"); + } + + // Test mirrored across Y=0 plane (normal = Y) + let mirr = wire.mirrored(glam::Vec3::new(0.0, 0.0, 0.0), glam::Vec3::new(1.0, 0.0, 0.0)); + if let TangentGeom::PlanarCircle { center, .. } = mirr.tangent_geoms[0] { + assert!((center[0] - 10.0).abs() < 1e-4); + assert!((center[1] - -20.0).abs() < 1e-4); + } else { + panic!("Expected PlanarCircle"); + } + } +} diff --git a/src/scene/pipeline/circle_gpu.rs b/src/scene/pipeline/circle_gpu.rs index 92d7332fb..d62902a64 100644 --- a/src/scene/pipeline/circle_gpu.rs +++ b/src/scene/pipeline/circle_gpu.rs @@ -237,7 +237,7 @@ pub fn extract_circle_instance_from_geom( ), _ => return None, }; - if radius <= 0.0 || !radius.is_finite() || !start_angle.is_finite() || !end_angle.is_finite() { + if radius <= 0.0 || !radius.is_finite() || !start_angle.is_finite() || !end_angle.is_finite() || radius > 1e6 { return None; } diff --git a/src/scene/pipeline/ellipse_gpu.rs b/src/scene/pipeline/ellipse_gpu.rs index 1291be8f5..50bc2e59d 100644 --- a/src/scene/pipeline/ellipse_gpu.rs +++ b/src/scene/pipeline/ellipse_gpu.rs @@ -218,7 +218,7 @@ pub fn extract_ellipse_instance_from_geom( let major_len_sq = major_axis[0] * major_axis[0] + major_axis[1] * major_axis[1] + major_axis[2] * major_axis[2]; let norm_len_sq = normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]; - if major_len_sq <= 1e-12 || !major_len_sq.is_finite() + if major_len_sq <= 1e-12 || !major_len_sq.is_finite() || major_len_sq > 1e12 || norm_len_sq <= 1e-12 || !norm_len_sq.is_finite() || minor_axis_ratio <= 1e-6 || !minor_axis_ratio.is_finite() || !start_param.is_finite() || !end_param.is_finite() diff --git a/src/scene/pipeline/mod.rs b/src/scene/pipeline/mod.rs index 9e27c2a00..c77fa6d2d 100644 --- a/src/scene/pipeline/mod.rs +++ b/src/scene/pipeline/mod.rs @@ -273,6 +273,8 @@ pub struct Pipeline { /// frame they are present (small), drawn on top of the base wire pass — so /// a live drag never re-uploads the resident base buffer. gpu_preview_wires: Vec, + gpu_preview_circles: Vec, + gpu_preview_ellipses: Vec, /// Wipeout masks — solid fills rendered after wires in a separate pass via /// the legacy per-primitive `WipeoutGpu` renderer. gpu_wipeouts: Vec, @@ -2346,6 +2348,8 @@ impl Pipeline { gpu_selected_ellipses: vec![], gpu_selected_block_wires: vec![], gpu_preview_wires: vec![], + gpu_preview_circles: vec![], + gpu_preview_ellipses: vec![], gpu_wipeouts: vec![], wipeout_skip_flags: vec![], gpu_images: vec![], @@ -2940,11 +2944,47 @@ impl Pipeline { wires: &[WireModel], depth_map: &rustc_hash::FxHashMap, ) { - self.gpu_preview_wires = if wires.is_empty() { - vec![] + if wires.is_empty() { + self.gpu_preview_wires.clear(); + self.gpu_preview_circles.clear(); + self.gpu_preview_ellipses.clear(); } else { - WireGpu::from_run(device, queue, wires, depth_map, false, self.wire_const_bgl.as_ref()) - }; + let partitioned = wire_arena::partition_wires(wires, depth_map); + let mut non_analytical = partitioned.regular; + non_analytical.extend(partitioned.mesh); + self.gpu_preview_wires = if non_analytical.is_empty() { + vec![] + } else { + WireGpu::from_run_refs( + device, + queue, + &non_analytical, + depth_map, + false, + self.wire_const_bgl.as_ref(), + ) + }; + self.gpu_preview_circles = if partitioned.circle_instances.is_empty() { + vec![] + } else { + vec![CircleGpu::from_instances( + device, + queue, + "preview.circles", + &partitioned.circle_instances, + )] + }; + self.gpu_preview_ellipses = if partitioned.ellipse_instances.is_empty() { + vec![] + } else { + vec![EllipseGpu::from_instances( + device, + queue, + "preview.ellipses", + &partitioned.ellipse_instances, + )] + }; + } } /// Upload the live grip-drag / command-preview SDF glyph quads. Re-uploaded @@ -4611,6 +4651,24 @@ impl Pipeline { } } } + if self.gpu_preview_circles.iter().any(|cg| cg.instance_count > 0) { + pass.set_pipeline(&self.circle_xray_pipeline); + for cg in &self.gpu_preview_circles { + if cg.instance_count > 0 { + pass.set_vertex_buffer(0, cg.instance_buffer.slice(..)); + pass.draw(0..6, 0..cg.instance_count); + } + } + } + if self.gpu_preview_ellipses.iter().any(|eg| eg.instance_count > 0) { + pass.set_pipeline(&self.ellipse_xray_pipeline); + for eg in &self.gpu_preview_ellipses { + if eg.instance_count > 0 { + pass.set_vertex_buffer(0, eg.instance_buffer.slice(..)); + pass.draw(0..6, 0..eg.instance_count); + } + } + } } // ── Pass 5c: SDF text quads (drawn over wires) ──────────────────── @@ -4970,6 +5028,8 @@ impl Pipeline { self.gpu_selected_circles.clear(); self.gpu_selected_ellipses.clear(); self.gpu_preview_wires.clear(); + self.gpu_preview_circles.clear(); + self.gpu_preview_ellipses.clear(); self.hatch_gpu.clear(); self.gpu_wipeouts.clear(); self.wipeout_skip_flags.clear(); diff --git a/src/scene/pipeline/wire_arena.rs b/src/scene/pipeline/wire_arena.rs index d4e927da4..bbaddb773 100644 --- a/src/scene/pipeline/wire_arena.rs +++ b/src/scene/pipeline/wire_arena.rs @@ -1644,4 +1644,48 @@ mod tests { assert!(blank_const().pattern_length < 0.0); assert!(blank_packed_instance().pattern_length < 0.0); } + + #[test] + fn partition_wires_partitions_preview_analytical_curves() { + use crate::scene::model::wire_model::TangentGeom; + let mut circle_preview = WireModel::default(); + circle_preview.tangent_geoms.push(TangentGeom::PlanarCircle { + center: [1.0, 2.0, 3.0], + axis_x: [1.0, 0.0, 0.0], + axis_y: [0.0, 1.0, 0.0], + radius: 10.0, + }); + + let mut arc_preview = WireModel::default(); + arc_preview.tangent_geoms.push(TangentGeom::Arc { + center: [4.0, 5.0, 6.0], + axis_x: [1.0, 0.0, 0.0], + axis_y: [0.0, 1.0, 0.0], + radius: 8.0, + start_angle: 0.1, + end_angle: 2.0, + }); + + let mut ellipse_preview = WireModel::default(); + ellipse_preview.tangent_geoms.push(TangentGeom::PlanarEllipse { + center: [7.0, 8.0, 9.0], + major_axis: [5.0, 0.0, 0.0], + normal: [0.0, 0.0, 1.0], + minor_axis_ratio: 0.6, + start_param: 0.0, + end_param: std::f64::consts::TAU, + }); + + let mut regular_line = WireModel::default(); + regular_line.points.push([0.0, 0.0, 0.0]); + regular_line.points.push([1.0, 1.0, 1.0]); + + let wires = vec![circle_preview, arc_preview, ellipse_preview, regular_line]; + let depth_map = rustc_hash::FxHashMap::default(); + let partitioned = partition_wires(&wires, &depth_map); + + assert_eq!(partitioned.circle_instances.len(), 2); + assert_eq!(partitioned.ellipse_instances.len(), 1); + assert_eq!(partitioned.regular.len(), 1); + } }