forked from 1b-agent/opencadstudio-upstream
Expand GPU analytical curves to interactive previews and transformations
This commit is contained in:
1 parent
84e95e4781
commit
ec3b6b89f6
8 files changed
+464
-19
No files matched your search
@@ -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<WireModel> {
|
||||
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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<f64> {
|
||||
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<WireGpu>,
|
||||
gpu_preview_circles: Vec<CircleGpu>,
|
||||
gpu_preview_ellipses: Vec<EllipseGpu>,
|
||||
/// Wipeout masks — solid fills rendered after wires in a separate pass via
|
||||
/// the legacy per-primitive `WipeoutGpu` renderer.
|
||||
gpu_wipeouts: Vec<WipeoutGpu>,
|
||||
@@ -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<u64, [f32; 2]>,
|
||||
) {
|
||||
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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user