cargo fmt
This commit is contained in:
parent
468cbde5e1
commit
ca07268c7c
204 changed files with 13988 additions and 5516 deletions
|
|
@ -12,7 +12,7 @@ use crate::scene::wire_model::{SnapHint, TangentGeom};
|
|||
/// world offset can be subtracted with f64 precision in tessellate.rs.
|
||||
pub struct TextStroke {
|
||||
pub strokes: Vec<Vec<[f32; 2]>>,
|
||||
pub origin: [f64; 2],
|
||||
pub origin: [f64; 2],
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ impl Camera {
|
|||
let (ray_origin, ray_dir) = match self.projection {
|
||||
Projection::Perspective => {
|
||||
let near_pt = inv.project_point3(Vec3::new(ndc_x, ndc_y, 0.0));
|
||||
let far_pt = inv.project_point3(Vec3::new(ndc_x, ndc_y, 1.0));
|
||||
let far_pt = inv.project_point3(Vec3::new(ndc_x, ndc_y, 1.0));
|
||||
let dir = (far_pt - near_pt).normalize();
|
||||
(near_pt, dir)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
//! Coordinate convention: 2D entities live in the **XZ** plane (Y = elevation).
|
||||
//! Shape X → along the linetype direction; Shape Y → perpendicular in XZ.
|
||||
|
||||
use crate::entities::text_support::resolve_dxf_special_chars;
|
||||
use crate::linetypes::{ComplexLt, LtSegment};
|
||||
use crate::scene::cxf;
|
||||
use crate::scene::wire_model::WireModel;
|
||||
use crate::entities::text_support::resolve_dxf_special_chars;
|
||||
|
||||
// ── Public entry point ────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -166,10 +166,8 @@ pub fn apply_along(
|
|||
);
|
||||
for stroke in &text_strokes {
|
||||
if stroke.len() >= 2 {
|
||||
let pts: Vec<[f32; 3]> = stroke
|
||||
.iter()
|
||||
.map(|&[sx, sy]| [sx, sy, insert[2]])
|
||||
.collect();
|
||||
let pts: Vec<[f32; 3]> =
|
||||
stroke.iter().map(|&[sx, sy]| [sx, sy, insert[2]]).collect();
|
||||
strokes.push(pts);
|
||||
}
|
||||
}
|
||||
|
|
@ -239,14 +237,27 @@ fn scale_segments(segs: &[LtSegment], scale: f32) -> Vec<LtSeg> {
|
|||
LtSegment::Dash(l) => LtSeg::Dash(l * scale),
|
||||
LtSegment::Space(l) => LtSeg::Space(l * scale),
|
||||
LtSegment::Dot => LtSeg::Dot,
|
||||
LtSegment::Shape { name, x, y, scale: sh_scale, rot_deg } => LtSeg::Shape {
|
||||
LtSegment::Shape {
|
||||
name,
|
||||
x,
|
||||
y,
|
||||
scale: sh_scale,
|
||||
rot_deg,
|
||||
} => LtSeg::Shape {
|
||||
name: name.clone(),
|
||||
x: x * scale,
|
||||
y: y * scale,
|
||||
scale: *sh_scale * scale,
|
||||
rot_deg: *rot_deg,
|
||||
},
|
||||
LtSegment::Text { text, style, x, y, scale: tx_scale, rot_deg } => LtSeg::Text {
|
||||
LtSegment::Text {
|
||||
text,
|
||||
style,
|
||||
x,
|
||||
y,
|
||||
scale: tx_scale,
|
||||
rot_deg,
|
||||
} => LtSeg::Text {
|
||||
text: text.clone(),
|
||||
style: style.clone(),
|
||||
x: x * scale,
|
||||
|
|
|
|||
|
|
@ -85,11 +85,16 @@ static FONTS: OnceLock<HashMap<String, CxfFile>> = OnceLock::new();
|
|||
static WARNED_GLYPHS: OnceLock<Mutex<HashSet<(String, char)>>> = OnceLock::new();
|
||||
|
||||
fn warn_missing_glyph(font_name: &str, ch: char) {
|
||||
if ch.is_ascii() { return; }
|
||||
if ch.is_ascii() {
|
||||
return;
|
||||
}
|
||||
let set = WARNED_GLYPHS.get_or_init(|| Mutex::new(HashSet::new()));
|
||||
if let Ok(mut guard) = set.lock() {
|
||||
if guard.insert((font_name.to_string(), ch)) {
|
||||
eprintln!("cxf: glyph U+{:04X} ('{}') not found in font '{font_name}'", ch as u32, ch);
|
||||
eprintln!(
|
||||
"cxf: glyph U+{:04X} ('{}') not found in font '{font_name}'",
|
||||
ch as u32, ch
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -265,7 +270,9 @@ pub fn tessellate_text_ex(
|
|||
match chars.peek().copied() {
|
||||
Some('L') => {
|
||||
chars.next();
|
||||
if underline.is_none() { underline = Some(cursor_x); }
|
||||
if underline.is_none() {
|
||||
underline = Some(cursor_x);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Some('l') => {
|
||||
|
|
@ -277,7 +284,9 @@ pub fn tessellate_text_ex(
|
|||
}
|
||||
Some('O') => {
|
||||
chars.next();
|
||||
if overline.is_none() { overline = Some(cursor_x); }
|
||||
if overline.is_none() {
|
||||
overline = Some(cursor_x);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Some('o') => {
|
||||
|
|
@ -289,13 +298,18 @@ pub fn tessellate_text_ex(
|
|||
}
|
||||
Some('K') => {
|
||||
chars.next();
|
||||
if strikethrough.is_none() { strikethrough = Some(cursor_x); }
|
||||
if strikethrough.is_none() {
|
||||
strikethrough = Some(cursor_x);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Some('k') => {
|
||||
chars.next();
|
||||
if let Some(s) = strikethrough.take() {
|
||||
out.push(vec![xform(s, STRIKE_Y, 0.0), xform(cursor_x, STRIKE_Y, 0.0)]);
|
||||
out.push(vec![
|
||||
xform(s, STRIKE_Y, 0.0),
|
||||
xform(cursor_x, STRIKE_Y, 0.0),
|
||||
]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
@ -307,15 +321,30 @@ pub fn tessellate_text_ex(
|
|||
let render_ch: char = if ch == '%' && chars.peek() == Some(&'%') {
|
||||
chars.next(); // consume second '%'
|
||||
match chars.peek().map(|c| c.to_ascii_lowercase()) {
|
||||
Some('d') => { chars.next(); '°' }
|
||||
Some('p') => { chars.next(); '±' }
|
||||
Some('c') => { chars.next(); '⌀' }
|
||||
Some('%') => { chars.next(); '%' }
|
||||
Some('d') => {
|
||||
chars.next();
|
||||
'°'
|
||||
}
|
||||
Some('p') => {
|
||||
chars.next();
|
||||
'±'
|
||||
}
|
||||
Some('c') => {
|
||||
chars.next();
|
||||
'⌀'
|
||||
}
|
||||
Some('%') => {
|
||||
chars.next();
|
||||
'%'
|
||||
}
|
||||
Some('u') => {
|
||||
chars.next();
|
||||
underline = match underline.take() {
|
||||
Some(start) => {
|
||||
out.push(vec![xform(start, UNDER_Y, 0.0), xform(cursor_x, UNDER_Y, 0.0)]);
|
||||
out.push(vec![
|
||||
xform(start, UNDER_Y, 0.0),
|
||||
xform(cursor_x, UNDER_Y, 0.0),
|
||||
]);
|
||||
None
|
||||
}
|
||||
None => Some(cursor_x),
|
||||
|
|
@ -326,7 +355,10 @@ pub fn tessellate_text_ex(
|
|||
chars.next();
|
||||
overline = match overline.take() {
|
||||
Some(start) => {
|
||||
out.push(vec![xform(start, OVER_Y, 0.0), xform(cursor_x, OVER_Y, 0.0)]);
|
||||
out.push(vec![
|
||||
xform(start, OVER_Y, 0.0),
|
||||
xform(cursor_x, OVER_Y, 0.0),
|
||||
]);
|
||||
None
|
||||
}
|
||||
None => Some(cursor_x),
|
||||
|
|
@ -338,7 +370,9 @@ pub fn tessellate_text_ex(
|
|||
let mut digits = String::with_capacity(3);
|
||||
for _ in 0..3 {
|
||||
match chars.peek() {
|
||||
Some(&c) if c.is_ascii_digit() => { digits.push(chars.next().unwrap()); }
|
||||
Some(&c) if c.is_ascii_digit() => {
|
||||
digits.push(chars.next().unwrap());
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
|
@ -346,15 +380,21 @@ pub fn tessellate_text_ex(
|
|||
if let Ok(n) = digits.parse::<u32>() {
|
||||
if let Some(c) = char::from_u32(n) {
|
||||
c
|
||||
} else { continue; }
|
||||
} else { continue; }
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Partial digit sequence — advance as unknown glyph and move on
|
||||
cursor_x += (6.0 + font.letter_spacing) * wf;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => { continue; } // unknown %%x — skip silently
|
||||
_ => {
|
||||
continue;
|
||||
} // unknown %%x — skip silently
|
||||
}
|
||||
} else {
|
||||
ch
|
||||
|
|
@ -388,13 +428,22 @@ pub fn tessellate_text_ex(
|
|||
|
||||
// Close any decoration spans that weren't explicitly closed.
|
||||
if let Some(start) = underline {
|
||||
out.push(vec![xform(start, UNDER_Y, 0.0), xform(cursor_x, UNDER_Y, 0.0)]);
|
||||
out.push(vec![
|
||||
xform(start, UNDER_Y, 0.0),
|
||||
xform(cursor_x, UNDER_Y, 0.0),
|
||||
]);
|
||||
}
|
||||
if let Some(start) = overline {
|
||||
out.push(vec![xform(start, OVER_Y, 0.0), xform(cursor_x, OVER_Y, 0.0)]);
|
||||
out.push(vec![
|
||||
xform(start, OVER_Y, 0.0),
|
||||
xform(cursor_x, OVER_Y, 0.0),
|
||||
]);
|
||||
}
|
||||
if let Some(start) = strikethrough {
|
||||
out.push(vec![xform(start, STRIKE_Y, 0.0), xform(cursor_x, STRIKE_Y, 0.0)]);
|
||||
out.push(vec![
|
||||
xform(start, STRIKE_Y, 0.0),
|
||||
xform(cursor_x, STRIKE_Y, 0.0),
|
||||
]);
|
||||
}
|
||||
|
||||
out
|
||||
|
|
|
|||
|
|
@ -54,8 +54,10 @@ pub fn grips_to_screen(
|
|||
/// Parameters match the `to_px` closure in `paper_canvas.rs`.
|
||||
pub fn grips_to_screen_paper(
|
||||
grips: &[GripDef],
|
||||
tx: f32, ty: f32,
|
||||
half_w: f32, half_h: f32,
|
||||
tx: f32,
|
||||
ty: f32,
|
||||
half_w: f32,
|
||||
half_h: f32,
|
||||
bounds: Rectangle,
|
||||
) -> Vec<(usize, Point, bool, GripShape)> {
|
||||
grips
|
||||
|
|
@ -74,8 +76,10 @@ pub fn grips_to_screen_paper(
|
|||
pub fn find_hit_grip_paper(
|
||||
cursor: Point,
|
||||
grips: &[GripDef],
|
||||
tx: f32, ty: f32,
|
||||
half_w: f32, half_h: f32,
|
||||
tx: f32,
|
||||
ty: f32,
|
||||
half_w: f32,
|
||||
half_h: f32,
|
||||
bounds: Rectangle,
|
||||
) -> Option<(usize, bool, Vec3)> {
|
||||
let mut best_dist = GRIP_THRESHOLD_PX;
|
||||
|
|
|
|||
|
|
@ -109,7 +109,9 @@ pub fn box_hit<'a>(
|
|||
}
|
||||
let sp = world_to_screen(Vec3::new(px, py, pz), view_proj, bounds);
|
||||
if crossing {
|
||||
if inside(sp) { hit = true; }
|
||||
if inside(sp) {
|
||||
hit = true;
|
||||
}
|
||||
if let Some(p0) = prev {
|
||||
if !hit {
|
||||
hit = segments_intersect(p0, sp, box_tl, box_tr)
|
||||
|
|
@ -119,13 +121,23 @@ pub fn box_hit<'a>(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if !inside(sp) { all_inside = false; }
|
||||
if !inside(sp) {
|
||||
all_inside = false;
|
||||
}
|
||||
}
|
||||
prev = Some(sp);
|
||||
}
|
||||
|
||||
let result = if crossing { hit } else { all_inside && prev.is_some() };
|
||||
if result { Some(wire.name.as_str()) } else { None }
|
||||
let result = if crossing {
|
||||
hit
|
||||
} else {
|
||||
all_inside && prev.is_some()
|
||||
};
|
||||
if result {
|
||||
Some(wire.name.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -167,20 +179,34 @@ pub fn poly_hit<'a>(
|
|||
}
|
||||
let sp = world_to_screen(Vec3::new(px, py, pz), view_proj, bounds);
|
||||
if crossing {
|
||||
if point_in_polygon(sp, poly) { hit = true; }
|
||||
if point_in_polygon(sp, poly) {
|
||||
hit = true;
|
||||
}
|
||||
if !hit {
|
||||
if let Some(p0) = prev {
|
||||
if segment_crosses_polygon(p0, sp, poly) { hit = true; }
|
||||
if segment_crosses_polygon(p0, sp, poly) {
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !point_in_polygon(sp, poly) { all_inside = false; }
|
||||
if !point_in_polygon(sp, poly) {
|
||||
all_inside = false;
|
||||
}
|
||||
}
|
||||
prev = Some(sp);
|
||||
}
|
||||
|
||||
let result = if crossing { hit } else { all_inside && prev.is_some() };
|
||||
if result { Some(wire.name.as_str()) } else { None }
|
||||
let result = if crossing {
|
||||
hit
|
||||
} else {
|
||||
all_inside && prev.is_some()
|
||||
};
|
||||
if result {
|
||||
Some(wire.name.as_str())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,14 @@ impl ImageModel {
|
|||
let opacity = 1.0 - img.fade as f32 / 100.0;
|
||||
|
||||
let (pixels, width, height) = load_pixels(&img.file_path)?;
|
||||
Some(Self { file_path: img.file_path.clone(), pixels: Arc::new(pixels), width, height, opacity, corners })
|
||||
Some(Self {
|
||||
file_path: img.file_path.clone(),
|
||||
pixels: Arc::new(pixels),
|
||||
width,
|
||||
height,
|
||||
opacity,
|
||||
corners,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
1291
src/scene/mod.rs
1291
src/scene/mod.rs
File diff suppressed because it is too large
Load diff
|
|
@ -65,13 +65,20 @@ impl<'a> canvas::Program<Message> for PaperCanvas<'a> {
|
|||
let mut frame = canvas::Frame::new(renderer, bounds.size());
|
||||
|
||||
// ── Desk background (area outside the paper sheet) ────────────────────
|
||||
const DESK: Color = Color { r: 0.22, g: 0.24, b: 0.28, a: 1.0 };
|
||||
const DESK: Color = Color {
|
||||
r: 0.22,
|
||||
g: 0.24,
|
||||
b: 0.28,
|
||||
a: 1.0,
|
||||
};
|
||||
frame.fill_rectangle(Point::ORIGIN, bounds.size(), DESK);
|
||||
|
||||
// ── White paper area — use layout paper limits (actual paper size).
|
||||
if let Some(((px0, py0), (px1, py1))) = self.scene.paper_limits().map(
|
||||
|((x0, y0), (x1, y1))| ((x0 as f32, y0 as f32), (x1 as f32, y1 as f32)),
|
||||
) {
|
||||
if let Some(((px0, py0), (px1, py1))) = self
|
||||
.scene
|
||||
.paper_limits()
|
||||
.map(|((x0, y0), (x1, y1))| ((x0 as f32, y0 as f32), (x1 as f32, y1 as f32)))
|
||||
{
|
||||
let tl = to_px(px0, py1);
|
||||
let br = to_px(px1, py0);
|
||||
let pw = br.x - tl.x;
|
||||
|
|
@ -156,11 +163,7 @@ impl<'a> canvas::Program<Message> for PaperCanvas<'a> {
|
|||
|
||||
/// Draw one HatchModel (solid, gradient, or pattern) onto `frame`.
|
||||
/// Patterns and gradients are approximated as solid fills in 2-D canvas mode.
|
||||
fn draw_hatch(
|
||||
frame: &mut canvas::Frame,
|
||||
hatch: &HatchModel,
|
||||
to_px: &impl Fn(f32, f32) -> Point,
|
||||
) {
|
||||
fn draw_hatch(frame: &mut canvas::Frame, hatch: &HatchModel, to_px: &impl Fn(f32, f32) -> Point) {
|
||||
if hatch.boundary.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -204,4 +207,3 @@ fn draw_hatch(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ use iced::wgpu::util::DeviceExt;
|
|||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Face3DVertex {
|
||||
pub position: [f32; 3],
|
||||
pub color: [f32; 4],
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
||||
impl Face3DVertex {
|
||||
|
|
@ -58,7 +58,11 @@ impl Face3DGpu {
|
|||
/// emits 2 triangles per face.
|
||||
/// - `all_wires`: all entity wires — `fill_tris` holds pre-triangulated
|
||||
/// fill data for PolyfaceMesh / PolygonMesh.
|
||||
pub fn from_wires(device: &wgpu::Device, face3d_wires: &[WireModel], all_wires: &[WireModel]) -> Self {
|
||||
pub fn from_wires(
|
||||
device: &wgpu::Device,
|
||||
face3d_wires: &[WireModel],
|
||||
all_wires: &[WireModel],
|
||||
) -> Self {
|
||||
let mut vertices: Vec<Face3DVertex> = Vec::with_capacity(face3d_wires.len() * 6);
|
||||
|
||||
// Face3D quads (4 key_vertices → 2 triangles).
|
||||
|
|
@ -69,7 +73,10 @@ impl Face3DGpu {
|
|||
let [r, g, b, a] = wire.color;
|
||||
let fill_color = [r * 0.45, g * 0.45, b * 0.45, a];
|
||||
let p = &wire.key_vertices;
|
||||
let v = |i: usize| Face3DVertex { position: p[i], color: fill_color };
|
||||
let v = |i: usize| Face3DVertex {
|
||||
position: p[i],
|
||||
color: fill_color,
|
||||
};
|
||||
vertices.push(v(0));
|
||||
vertices.push(v(1));
|
||||
vertices.push(v(2));
|
||||
|
|
@ -86,7 +93,10 @@ impl Face3DGpu {
|
|||
let [r, g, b, a] = wire.color;
|
||||
let fill_color = [r * 0.45, g * 0.45, b * 0.45, a];
|
||||
for &position in &wire.fill_tris {
|
||||
vertices.push(Face3DVertex { position, color: fill_color });
|
||||
vertices.push(Face3DVertex {
|
||||
position,
|
||||
color: fill_color,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@
|
|||
// binding 1 — BoundaryData (16384 bytes) boundary polygon vertices
|
||||
// binding 2 — FamilyBatchData (1296 bytes) up to 16 line families + 128 dash values
|
||||
|
||||
use crate::scene::hatch_model::{
|
||||
HatchModel, HatchPattern, PatFamily, MAX_HATCH_BOUNDARY_VERTS,
|
||||
};
|
||||
use crate::scene::hatch_model::{HatchModel, HatchPattern, PatFamily, MAX_HATCH_BOUNDARY_VERTS};
|
||||
use iced::wgpu;
|
||||
use iced::wgpu::util::DeviceExt;
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,10 @@ impl ImageGpu {
|
|||
});
|
||||
|
||||
// ── Opacity uniform ───────────────────────────────────────────────
|
||||
let params = ImageParams { opacity: model.opacity.clamp(0.0, 1.0), _pad: [0.0; 3] };
|
||||
let params = ImageParams {
|
||||
opacity: model.opacity.clamp(0.0, 1.0),
|
||||
_pad: [0.0; 3],
|
||||
};
|
||||
let _params_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("image.params"),
|
||||
contents: bytemuck::bytes_of(¶ms),
|
||||
|
|
@ -146,12 +149,30 @@ impl ImageGpu {
|
|||
// UV: BL=(0,1), BR=(1,1), TR=(1,0), TL=(0,0)
|
||||
let [p0, p1, p2, p3] = model.corners;
|
||||
let verts = [
|
||||
ImageVertex { pos: p0, uv: [0.0, 1.0] },
|
||||
ImageVertex { pos: p1, uv: [1.0, 1.0] },
|
||||
ImageVertex { pos: p2, uv: [1.0, 0.0] },
|
||||
ImageVertex { pos: p0, uv: [0.0, 1.0] },
|
||||
ImageVertex { pos: p2, uv: [1.0, 0.0] },
|
||||
ImageVertex { pos: p3, uv: [0.0, 0.0] },
|
||||
ImageVertex {
|
||||
pos: p0,
|
||||
uv: [0.0, 1.0],
|
||||
},
|
||||
ImageVertex {
|
||||
pos: p1,
|
||||
uv: [1.0, 1.0],
|
||||
},
|
||||
ImageVertex {
|
||||
pos: p2,
|
||||
uv: [1.0, 0.0],
|
||||
},
|
||||
ImageVertex {
|
||||
pos: p0,
|
||||
uv: [0.0, 1.0],
|
||||
},
|
||||
ImageVertex {
|
||||
pos: p2,
|
||||
uv: [1.0, 0.0],
|
||||
},
|
||||
ImageVertex {
|
||||
pos: p3,
|
||||
uv: [0.0, 0.0],
|
||||
},
|
||||
];
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("image.vbuf"),
|
||||
|
|
|
|||
|
|
@ -609,7 +609,11 @@ impl Pipeline {
|
|||
self.gpu_wires = wires
|
||||
.iter()
|
||||
.filter(|w| {
|
||||
let segs = w.points.len().saturating_sub(1).min(wire_gpu::MAX_SEGS_PER_WIRE);
|
||||
let segs = w
|
||||
.points
|
||||
.len()
|
||||
.saturating_sub(1)
|
||||
.min(wire_gpu::MAX_SEGS_PER_WIRE);
|
||||
if total_segs + segs > MAX_TOTAL_SEGS {
|
||||
skipped += 1;
|
||||
false
|
||||
|
|
@ -641,31 +645,42 @@ impl Pipeline {
|
|||
pub fn compute_wire_scissors(&mut self, view_proj: glam::Mat4, clip_w: u32, clip_h: u32) {
|
||||
let w = clip_w as f32;
|
||||
let h = clip_h as f32;
|
||||
self.wire_pixel_scissors = self.gpu_wires.iter().map(|wire| {
|
||||
let [x0, y0, x1, y1] = wire.vp_scissor?;
|
||||
let corners = [
|
||||
view_proj.project_point3(glam::Vec3::new(x0, y0, 0.0)),
|
||||
view_proj.project_point3(glam::Vec3::new(x1, y0, 0.0)),
|
||||
view_proj.project_point3(glam::Vec3::new(x0, y1, 0.0)),
|
||||
view_proj.project_point3(glam::Vec3::new(x1, y1, 0.0)),
|
||||
];
|
||||
let px: Vec<f32> = corners.iter().map(|c| (c.x + 1.0) * 0.5 * w).collect();
|
||||
let py: Vec<f32> = corners.iter().map(|c| (1.0 - c.y) * 0.5 * h).collect();
|
||||
let sx0 = px.iter().cloned().fold(f32::INFINITY, f32::min).max(0.0) as u32;
|
||||
let sy0 = py.iter().cloned().fold(f32::INFINITY, f32::min).max(0.0) as u32;
|
||||
let sx1 = (px.iter().cloned().fold(f32::NEG_INFINITY, f32::max) as u32).min(clip_w);
|
||||
let sy1 = (py.iter().cloned().fold(f32::NEG_INFINITY, f32::max) as u32).min(clip_h);
|
||||
if sx1 <= sx0 || sy1 <= sy0 { return None; }
|
||||
Some([sx0, sy0, sx1 - sx0, sy1 - sy0])
|
||||
}).collect();
|
||||
self.wire_pixel_scissors = self
|
||||
.gpu_wires
|
||||
.iter()
|
||||
.map(|wire| {
|
||||
let [x0, y0, x1, y1] = wire.vp_scissor?;
|
||||
let corners = [
|
||||
view_proj.project_point3(glam::Vec3::new(x0, y0, 0.0)),
|
||||
view_proj.project_point3(glam::Vec3::new(x1, y0, 0.0)),
|
||||
view_proj.project_point3(glam::Vec3::new(x0, y1, 0.0)),
|
||||
view_proj.project_point3(glam::Vec3::new(x1, y1, 0.0)),
|
||||
];
|
||||
let px: Vec<f32> = corners.iter().map(|c| (c.x + 1.0) * 0.5 * w).collect();
|
||||
let py: Vec<f32> = corners.iter().map(|c| (1.0 - c.y) * 0.5 * h).collect();
|
||||
let sx0 = px.iter().cloned().fold(f32::INFINITY, f32::min).max(0.0) as u32;
|
||||
let sy0 = py.iter().cloned().fold(f32::INFINITY, f32::min).max(0.0) as u32;
|
||||
let sx1 = (px.iter().cloned().fold(f32::NEG_INFINITY, f32::max) as u32).min(clip_w);
|
||||
let sy1 = (py.iter().cloned().fold(f32::NEG_INFINITY, f32::max) as u32).min(clip_h);
|
||||
if sx1 <= sx0 || sy1 <= sy0 {
|
||||
return None;
|
||||
}
|
||||
Some([sx0, sy0, sx1 - sx0, sy1 - sy0])
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Upload all 3DFACE entities as two batched GPU objects:
|
||||
/// - `gpu_face3d_fill`: filled triangles (1 buffer, 1 draw call)
|
||||
/// - `gpu_face3d_edges`: merged edge wires (1 buffer, 1 draw call)
|
||||
pub fn upload_face3d(&mut self, device: &wgpu::Device, face3d_wires: &[WireModel], all_wires: &[WireModel]) {
|
||||
let has_fills = !face3d_wires.is_empty()
|
||||
|| all_wires.iter().any(|w| !w.fill_tris.is_empty());
|
||||
pub fn upload_face3d(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
face3d_wires: &[WireModel],
|
||||
all_wires: &[WireModel],
|
||||
) {
|
||||
let has_fills =
|
||||
!face3d_wires.is_empty() || all_wires.iter().any(|w| !w.fill_tris.is_empty());
|
||||
if !has_fills {
|
||||
self.gpu_face3d_fill = None;
|
||||
self.gpu_face3d_edges = vec![];
|
||||
|
|
@ -725,7 +740,12 @@ impl Pipeline {
|
|||
let vp = clip_bounds;
|
||||
let msaa = &self.msaa_view;
|
||||
let [r, g, b, a] = bg_color;
|
||||
let clear_color = wgpu::Color { r: r as f64, g: g as f64, b: b as f64, a: a as f64 };
|
||||
let clear_color = wgpu::Color {
|
||||
r: r as f64,
|
||||
g: g as f64,
|
||||
b: b as f64,
|
||||
a: a as f64,
|
||||
};
|
||||
|
||||
// ── Pass 1: hatch fills ────────────────────────────────────────────
|
||||
{
|
||||
|
|
|
|||
|
|
@ -90,18 +90,18 @@ impl CubeRegion {
|
|||
11 => (PI, -FRAC_PI_4), // EDGE_BOT_BACK
|
||||
12 => (FRAC_PI_2, -FRAC_PI_4),
|
||||
13 => (-FRAC_PI_2, -FRAC_PI_4),
|
||||
14 => (FRAC_PI_4, 0.0), // EDGE_FRONT_RIGHT (between 0 and PI/2)
|
||||
15 => (-FRAC_PI_4, 0.0), // EDGE_FRONT_LEFT
|
||||
16 => (PI * 0.75, 0.0), // EDGE_BACK_RIGHT (between PI and PI/2)
|
||||
17 => (-PI * 0.75, 0.0), // EDGE_BACK_LEFT
|
||||
18 => (FRAC_PI_4, iso), // CORNER_TPF_R (FRONT+RIGHT)
|
||||
19 => (-FRAC_PI_4, iso), // CORNER_TPF_L (FRONT+LEFT)
|
||||
20 => (PI * 0.75, iso), // CORNER_TBK_R (BACK+RIGHT)
|
||||
21 => (-PI * 0.75, iso), // CORNER_TBK_L (BACK+LEFT)
|
||||
22 => (FRAC_PI_4, -iso), // CORNER_BTF_R
|
||||
23 => (-FRAC_PI_4, -iso), // CORNER_BTF_L
|
||||
24 => (PI * 0.75, -iso), // CORNER_BBK_R
|
||||
25 => (-PI * 0.75, -iso), // CORNER_BBK_L
|
||||
14 => (FRAC_PI_4, 0.0), // EDGE_FRONT_RIGHT (between 0 and PI/2)
|
||||
15 => (-FRAC_PI_4, 0.0), // EDGE_FRONT_LEFT
|
||||
16 => (PI * 0.75, 0.0), // EDGE_BACK_RIGHT (between PI and PI/2)
|
||||
17 => (-PI * 0.75, 0.0), // EDGE_BACK_LEFT
|
||||
18 => (FRAC_PI_4, iso), // CORNER_TPF_R (FRONT+RIGHT)
|
||||
19 => (-FRAC_PI_4, iso), // CORNER_TPF_L (FRONT+LEFT)
|
||||
20 => (PI * 0.75, iso), // CORNER_TBK_R (BACK+RIGHT)
|
||||
21 => (-PI * 0.75, iso), // CORNER_TBK_L (BACK+LEFT)
|
||||
22 => (FRAC_PI_4, -iso), // CORNER_BTF_R
|
||||
23 => (-FRAC_PI_4, -iso), // CORNER_BTF_L
|
||||
24 => (PI * 0.75, -iso), // CORNER_BBK_R
|
||||
25 => (-PI * 0.75, -iso), // CORNER_BBK_L
|
||||
_ => (0.0, 0.0),
|
||||
}
|
||||
}
|
||||
|
|
@ -909,11 +909,11 @@ pub fn build_geometry() -> (Vec<CubeVertex>, Vec<u32>) {
|
|||
&mut is,
|
||||
);
|
||||
for &([sx, sy, sz], region) in &[
|
||||
([1.0f32, 1.0, 1.0], CORNER_TBK_R), // sy=+1 = BACK yönü
|
||||
([1.0f32, 1.0, 1.0], CORNER_TBK_R), // sy=+1 = BACK yönü
|
||||
([-1.0, 1.0, 1.0], CORNER_TBK_L),
|
||||
([1.0, 1.0, -1.0], CORNER_BBK_R),
|
||||
([-1.0, 1.0, -1.0], CORNER_BBK_L),
|
||||
([1.0, -1.0, 1.0], CORNER_TPF_R), // sy=-1 = FRONT yönü
|
||||
([1.0, -1.0, 1.0], CORNER_TPF_R), // sy=-1 = FRONT yönü
|
||||
([-1.0, -1.0, 1.0], CORNER_TPF_L),
|
||||
([1.0, -1.0, -1.0], CORNER_BTF_R),
|
||||
([-1.0, -1.0, -1.0], CORNER_BTF_L),
|
||||
|
|
@ -934,32 +934,32 @@ pub fn build_geometry() -> (Vec<CubeVertex>, Vec<u32>) {
|
|||
pub fn region_centroids() -> [[f32; 3]; NUM_REGIONS] {
|
||||
let m = (F + E) * 0.5;
|
||||
[
|
||||
[0.0, 0.0, E], // FACE_TOP
|
||||
[0.0, 0.0, -E], // FACE_BOTTOM
|
||||
[0.0, -E, 0.0], // FACE_FRONT (geometry Y=-E)
|
||||
[0.0, E, 0.0], // FACE_BACK (geometry Y=+E)
|
||||
[E, 0.0, 0.0], // FACE_RIGHT
|
||||
[-E, 0.0, 0.0], // FACE_LEFT
|
||||
[0.0, -m, m], // EDGE_TOP_FRONT
|
||||
[0.0, m, m], // EDGE_TOP_BACK
|
||||
[m, 0.0, m], // EDGE_TOP_RIGHT
|
||||
[-m, 0.0, m], // EDGE_TOP_LEFT
|
||||
[0.0, -m, -m], // EDGE_BOT_FRONT
|
||||
[0.0, m, -m], // EDGE_BOT_BACK
|
||||
[m, 0.0, -m], // EDGE_BOT_RIGHT
|
||||
[-m, 0.0, -m], // EDGE_BOT_LEFT
|
||||
[m, -m, 0.0], // EDGE_FRONT_RIGHT
|
||||
[-m, -m, 0.0], // EDGE_FRONT_LEFT
|
||||
[m, m, 0.0], // EDGE_BACK_RIGHT
|
||||
[-m, m, 0.0], // EDGE_BACK_LEFT
|
||||
[m, -m, m], // CORNER_TPF_R (geometry sy=-1 = FRONT)
|
||||
[-m, -m, m], // CORNER_TPF_L
|
||||
[m, m, m], // CORNER_TBK_R (geometry sy=+1 = BACK)
|
||||
[-m, m, m], // CORNER_TBK_L
|
||||
[m, -m, -m], // CORNER_BTF_R (geometry sy=-1 = FRONT)
|
||||
[-m, -m, -m], // CORNER_BTF_L
|
||||
[m, m, -m], // CORNER_BBK_R (geometry sy=+1 = BACK)
|
||||
[-m, m, -m], // CORNER_BBK_L
|
||||
[0.0, 0.0, E], // FACE_TOP
|
||||
[0.0, 0.0, -E], // FACE_BOTTOM
|
||||
[0.0, -E, 0.0], // FACE_FRONT (geometry Y=-E)
|
||||
[0.0, E, 0.0], // FACE_BACK (geometry Y=+E)
|
||||
[E, 0.0, 0.0], // FACE_RIGHT
|
||||
[-E, 0.0, 0.0], // FACE_LEFT
|
||||
[0.0, -m, m], // EDGE_TOP_FRONT
|
||||
[0.0, m, m], // EDGE_TOP_BACK
|
||||
[m, 0.0, m], // EDGE_TOP_RIGHT
|
||||
[-m, 0.0, m], // EDGE_TOP_LEFT
|
||||
[0.0, -m, -m], // EDGE_BOT_FRONT
|
||||
[0.0, m, -m], // EDGE_BOT_BACK
|
||||
[m, 0.0, -m], // EDGE_BOT_RIGHT
|
||||
[-m, 0.0, -m], // EDGE_BOT_LEFT
|
||||
[m, -m, 0.0], // EDGE_FRONT_RIGHT
|
||||
[-m, -m, 0.0], // EDGE_FRONT_LEFT
|
||||
[m, m, 0.0], // EDGE_BACK_RIGHT
|
||||
[-m, m, 0.0], // EDGE_BACK_LEFT
|
||||
[m, -m, m], // CORNER_TPF_R (geometry sy=-1 = FRONT)
|
||||
[-m, -m, m], // CORNER_TPF_L
|
||||
[m, m, m], // CORNER_TBK_R (geometry sy=+1 = BACK)
|
||||
[-m, m, m], // CORNER_TBK_L
|
||||
[m, -m, -m], // CORNER_BTF_R (geometry sy=-1 = FRONT)
|
||||
[-m, -m, -m], // CORNER_BTF_L
|
||||
[m, m, -m], // CORNER_BBK_R (geometry sy=+1 = BACK)
|
||||
[-m, m, -m], // CORNER_BBK_L
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,8 +146,18 @@ impl WireGpu {
|
|||
|
||||
for wire in wires {
|
||||
let color = wire.color;
|
||||
let pat0 = [wire.pattern[0], wire.pattern[1], wire.pattern[2], wire.pattern[3]];
|
||||
let pat1 = [wire.pattern[4], wire.pattern[5], wire.pattern[6], wire.pattern[7]];
|
||||
let pat0 = [
|
||||
wire.pattern[0],
|
||||
wire.pattern[1],
|
||||
wire.pattern[2],
|
||||
wire.pattern[3],
|
||||
];
|
||||
let pat1 = [
|
||||
wire.pattern[4],
|
||||
wire.pattern[5],
|
||||
wire.pattern[6],
|
||||
wire.pattern[7],
|
||||
];
|
||||
let half_width = wire.line_weight_px * 0.5;
|
||||
let n = wire.points.len();
|
||||
let raw_segs = n.saturating_sub(1);
|
||||
|
|
@ -183,8 +193,12 @@ impl WireGpu {
|
|||
for i in 0..capped_segs {
|
||||
let a = wire.points[i];
|
||||
let b = wire.points[i + 1];
|
||||
if !a[0].is_finite() || !a[1].is_finite() || !a[2].is_finite()
|
||||
|| !b[0].is_finite() || !b[1].is_finite() || !b[2].is_finite()
|
||||
if !a[0].is_finite()
|
||||
|| !a[1].is_finite()
|
||||
|| !a[2].is_finite()
|
||||
|| !b[0].is_finite()
|
||||
|| !b[1].is_finite()
|
||||
|| !b[2].is_finite()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -193,18 +207,25 @@ impl WireGpu {
|
|||
let make = |which_end: f32, side: f32| -> WireVertex {
|
||||
let dist = if which_end < 0.5 { dist_a } else { dist_b };
|
||||
WireVertex {
|
||||
pos_a: a, pos_b: b, which_end, side, color,
|
||||
distance: dist, half_width,
|
||||
pos_a: a,
|
||||
pos_b: b,
|
||||
which_end,
|
||||
side,
|
||||
color,
|
||||
distance: dist,
|
||||
half_width,
|
||||
pattern_length: wire.pattern_length,
|
||||
_pad: 0.0, pat0, pat1,
|
||||
_pad: 0.0,
|
||||
pat0,
|
||||
pat1,
|
||||
}
|
||||
};
|
||||
vertices.push(make(0.0, -1.0));
|
||||
vertices.push(make(1.0, -1.0));
|
||||
vertices.push(make(1.0, 1.0));
|
||||
vertices.push(make(1.0, 1.0));
|
||||
vertices.push(make(0.0, -1.0));
|
||||
vertices.push(make(1.0, 1.0));
|
||||
vertices.push(make(0.0, 1.0));
|
||||
vertices.push(make(1.0, 1.0));
|
||||
vertices.push(make(0.0, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ impl shader::Primitive for PaperViewportPrimitive {
|
|||
bounds: &Rectangle,
|
||||
viewport: &Viewport,
|
||||
) {
|
||||
self.0.prepare(&mut pipeline.0, device, queue, bounds, viewport);
|
||||
self.0
|
||||
.prepare(&mut pipeline.0, device, queue, bounds, viewport);
|
||||
}
|
||||
|
||||
fn render(
|
||||
|
|
@ -163,10 +164,13 @@ impl Scene {
|
|||
/// Returns (entity_color, pattern_length, pattern, line_weight_px, aci).
|
||||
pub(super) fn render_style(&self, e: &EntityType) -> ([f32; 4], f32, [f32; 8], f32, u8) {
|
||||
let (color, pl, pat, lw, aci) = render_style_for(&self.document, e);
|
||||
let bg = if self.current_layout == "Model" { self.bg_color } else { self.paper_bg_color };
|
||||
let bg = if self.current_layout == "Model" {
|
||||
self.bg_color
|
||||
} else {
|
||||
self.paper_bg_color
|
||||
};
|
||||
(adapt_to_bg(color, bg), pl, pat, lw, aci)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ── Document-only render-style helpers (no &self, safe to call from parallel contexts) ──
|
||||
|
|
@ -338,7 +342,6 @@ impl Scene {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Build a Primitive that renders model-space content through a specific
|
||||
/// paper-space viewport's camera, applying its layer-freeze list.
|
||||
pub(super) fn build_viewport_primitive(
|
||||
|
|
@ -415,10 +418,7 @@ impl Scene {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn viewcube_mouse_interaction(
|
||||
&self,
|
||||
state: &CameraState,
|
||||
) -> mouse::Interaction {
|
||||
pub(super) fn viewcube_mouse_interaction(&self, state: &CameraState) -> mouse::Interaction {
|
||||
if state.hover_region.is_some() {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
|
|
@ -479,7 +479,9 @@ fn split_face3d_wires(
|
|||
let mut face3d = Vec::new();
|
||||
let mut others = Vec::new();
|
||||
for w in wires {
|
||||
let is_face3d = w.name.parse::<u64>()
|
||||
let is_face3d = w
|
||||
.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.and_then(|v| document.get_entity(Handle::new(v)))
|
||||
.map(|e| matches!(e, EntityType::Face3D(_)))
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@
|
|||
use std::collections::HashSet;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
use acadrust::entities::acis::{
|
||||
SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatFace, SatLoop,
|
||||
SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface, SatTorusSurface, SatVertex,
|
||||
};
|
||||
use acadrust::entities::acis::types::Sense;
|
||||
use acadrust::entities::acis::{
|
||||
SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatFace, SatLoop, SatPlaneSurface,
|
||||
SatPoint, SatPointer, SatSphereSurface, SatTorusSurface, SatVertex,
|
||||
};
|
||||
use acadrust::entities::{Body, Region, Solid3D};
|
||||
|
||||
use crate::scene::mesh_model::MeshModel;
|
||||
|
|
@ -38,7 +38,9 @@ fn tessellate_sat(sat: &SatDocument, name: String, color: [f32; 4]) -> Option<Me
|
|||
|
||||
for face in sat.faces() {
|
||||
let surf_ptr = face.surface();
|
||||
let Some(surf_rec) = sat.resolve(surf_ptr) else { continue };
|
||||
let Some(surf_rec) = sat.resolve(surf_ptr) else {
|
||||
continue;
|
||||
};
|
||||
match surf_rec.entity_type.as_str() {
|
||||
"plane-surface" => {
|
||||
if let Some(plane) = SatPlaneSurface::from_record(surf_rec) {
|
||||
|
|
@ -63,11 +65,24 @@ fn tessellate_sat(sat: &SatDocument, name: String, color: [f32; 4]) -> Option<Me
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
if indices.is_empty() { return None; }
|
||||
Some(MeshModel { name, verts, normals, indices, color, selected: false })
|
||||
if indices.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(MeshModel {
|
||||
name,
|
||||
verts,
|
||||
normals,
|
||||
indices,
|
||||
color,
|
||||
selected: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_acis(sat_fn: impl FnOnce() -> Option<SatDocument>, is_binary: bool, sab_data: &[u8]) -> Option<SatDocument> {
|
||||
fn parse_acis(
|
||||
sat_fn: impl FnOnce() -> Option<SatDocument>,
|
||||
is_binary: bool,
|
||||
sab_data: &[u8],
|
||||
) -> Option<SatDocument> {
|
||||
if let Some(doc) = sat_fn() {
|
||||
return Some(doc);
|
||||
}
|
||||
|
|
@ -79,14 +94,22 @@ fn parse_acis(sat_fn: impl FnOnce() -> Option<SatDocument>, is_binary: bool, sab
|
|||
|
||||
/// Tessellate a `Region` entity (2D planar ACIS body) into a `MeshModel`.
|
||||
pub fn tessellate_region(region: &Region, color: [f32; 4]) -> Option<MeshModel> {
|
||||
let sat = parse_acis(|| region.parse_sat(), region.acis_data.is_binary, ®ion.acis_data.sab_data)?;
|
||||
let sat = parse_acis(
|
||||
|| region.parse_sat(),
|
||||
region.acis_data.is_binary,
|
||||
®ion.acis_data.sab_data,
|
||||
)?;
|
||||
let name = region.common.handle.value().to_string();
|
||||
tessellate_sat(&sat, name, color)
|
||||
}
|
||||
|
||||
/// Tessellate a `Body` entity (3D ACIS body) into a `MeshModel`.
|
||||
pub fn tessellate_body(body: &Body, color: [f32; 4]) -> Option<MeshModel> {
|
||||
let sat = parse_acis(|| body.parse_sat(), body.acis_data.is_binary, &body.acis_data.sab_data)?;
|
||||
let sat = parse_acis(
|
||||
|| body.parse_sat(),
|
||||
body.acis_data.is_binary,
|
||||
&body.acis_data.sab_data,
|
||||
)?;
|
||||
let name = body.common.handle.value().to_string();
|
||||
tessellate_sat(&sat, name, color)
|
||||
}
|
||||
|
|
@ -96,7 +119,11 @@ pub fn tessellate_body(body: &Body, color: [f32; 4]) -> Option<MeshModel> {
|
|||
/// Returns `None` when the entity has no parseable SAT data or produces no
|
||||
/// triangles (e.g. the solid uses only unsupported surface types).
|
||||
pub fn tessellate_solid3d(solid: &Solid3D, color: [f32; 4]) -> Option<MeshModel> {
|
||||
let sat = parse_acis(|| solid.parse_sat(), solid.acis_data.is_binary, &solid.acis_data.sab_data)?;
|
||||
let sat = parse_acis(
|
||||
|| solid.parse_sat(),
|
||||
solid.acis_data.is_binary,
|
||||
&solid.acis_data.sab_data,
|
||||
)?;
|
||||
let name = solid.common.handle.value().to_string();
|
||||
tessellate_sat(&sat, name, color)
|
||||
}
|
||||
|
|
@ -261,7 +288,11 @@ fn tess_cone_face(
|
|||
let segs_u = CIRC_SEGS;
|
||||
let segs_v = segs_u / 4; // height subdivisions
|
||||
|
||||
let theta_span = if full_circle { TAU } else { theta_max - theta_min };
|
||||
let theta_span = if full_circle {
|
||||
TAU
|
||||
} else {
|
||||
theta_max - theta_min
|
||||
};
|
||||
let h_span = h_max - h_min;
|
||||
|
||||
if h_span.abs() < 1e-10 || theta_span.abs() < 1e-10 {
|
||||
|
|
@ -317,7 +348,9 @@ fn tess_cone_face(
|
|||
/// Compute a point on a cone/cylinder surface.
|
||||
#[inline]
|
||||
fn cone_pt(
|
||||
cx: f64, cy: f64, cz: f64,
|
||||
cx: f64,
|
||||
cy: f64,
|
||||
cz: f64,
|
||||
axis: [f64; 3],
|
||||
u_dir: [f64; 3],
|
||||
v_dir: [f64; 3],
|
||||
|
|
@ -338,7 +371,9 @@ fn cone_pt(
|
|||
/// `full_circle` is true when there are no boundary vertices (e.g. a sphere or
|
||||
/// a cylinder with no seam edge).
|
||||
fn angular_range(
|
||||
cx: f64, cy: f64, cz: f64,
|
||||
cx: f64,
|
||||
cy: f64,
|
||||
cz: f64,
|
||||
axis: [f64; 3],
|
||||
u_dir: [f64; 3],
|
||||
v_dir: [f64; 3],
|
||||
|
|
@ -397,7 +432,7 @@ fn tess_sphere_face(
|
|||
let nv = GRID_V;
|
||||
|
||||
for j in 0..nv {
|
||||
let phi0 = std::f64::consts::PI * (j as f64 / nv as f64); // 0..π
|
||||
let phi0 = std::f64::consts::PI * (j as f64 / nv as f64); // 0..π
|
||||
let phi1 = std::f64::consts::PI * ((j + 1) as f64 / nv as f64);
|
||||
|
||||
for i in 0..nu {
|
||||
|
|
@ -429,13 +464,7 @@ fn tess_sphere_face(
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn sphere_dir(
|
||||
pole: [f64; 3],
|
||||
u_dir: [f64; 3],
|
||||
v_dir: [f64; 3],
|
||||
theta: f64,
|
||||
phi: f64,
|
||||
) -> [f64; 3] {
|
||||
fn sphere_dir(pole: [f64; 3], u_dir: [f64; 3], v_dir: [f64; 3], theta: f64, phi: f64) -> [f64; 3] {
|
||||
let sin_phi = phi.sin();
|
||||
let cos_phi = phi.cos();
|
||||
let cos_theta = theta.cos();
|
||||
|
|
@ -477,10 +506,18 @@ fn tess_torus_face(
|
|||
let theta1 = TAU * ((i + 1) as f64 / nu as f64);
|
||||
|
||||
let p = [
|
||||
torus_pt(cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta0, phi0),
|
||||
torus_pt(cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta0, phi1),
|
||||
torus_pt(cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta1, phi1),
|
||||
torus_pt(cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta1, phi0),
|
||||
torus_pt(
|
||||
cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta0, phi0,
|
||||
),
|
||||
torus_pt(
|
||||
cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta0, phi1,
|
||||
),
|
||||
torus_pt(
|
||||
cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta1, phi1,
|
||||
),
|
||||
torus_pt(
|
||||
cx, cy, cz, axis, u_dir, v_dir, major_r, minor_r, theta1, phi0,
|
||||
),
|
||||
];
|
||||
|
||||
// Outward tube normal.
|
||||
|
|
@ -505,14 +542,16 @@ fn tess_torus_face(
|
|||
|
||||
#[inline]
|
||||
fn torus_pt(
|
||||
cx: f64, cy: f64, cz: f64,
|
||||
cx: f64,
|
||||
cy: f64,
|
||||
cz: f64,
|
||||
axis: [f64; 3],
|
||||
u_dir: [f64; 3],
|
||||
v_dir: [f64; 3],
|
||||
major_r: f64,
|
||||
minor_r: f64,
|
||||
theta: f64, // tube angle
|
||||
phi: f64, // revolution angle
|
||||
theta: f64, // tube angle
|
||||
phi: f64, // revolution angle
|
||||
) -> [f64; 3] {
|
||||
// Ring center at angle phi.
|
||||
let ring = [
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@
|
|||
// Entities not handled by acad_to_truck (Viewport, Hatch, …) are tessellated
|
||||
// by the legacy geometry() path so nothing regresses.
|
||||
|
||||
use acadrust::entities::{Dimension, Leader, LeaderContentType, MultiLeader, MultiLeaderPathType, Text};
|
||||
use crate::entities::multileader::catmull_rom_pts;
|
||||
use acadrust::entities::{
|
||||
Dimension, Leader, LeaderContentType, MultiLeader, MultiLeaderPathType, Text,
|
||||
};
|
||||
use acadrust::types::{Color as AcadColor, Vector3};
|
||||
use acadrust::{CadDocument, EntityType, Handle};
|
||||
use glam::Vec3;
|
||||
|
|
@ -76,18 +78,35 @@ pub fn tessellate(
|
|||
} else {
|
||||
xdata.get_record("AcAnnoPO").is_some()
|
||||
};
|
||||
if is_annotative { anno_scale } else { 1.0 }
|
||||
if is_annotative {
|
||||
anno_scale
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
};
|
||||
|
||||
// MultiLeader/Leader need anno_scale for arrow/dogleg/text — handle before generic path.
|
||||
if let EntityType::MultiLeader(ml) = entity {
|
||||
return tessellate_multileader_single(
|
||||
document, handle, ml, selected, entity_color, line_weight_px, world_offset, anno_scale,
|
||||
document,
|
||||
handle,
|
||||
ml,
|
||||
selected,
|
||||
entity_color,
|
||||
line_weight_px,
|
||||
world_offset,
|
||||
anno_scale,
|
||||
);
|
||||
}
|
||||
if let EntityType::Leader(leader) = entity {
|
||||
return tessellate_leader_single(
|
||||
handle, leader, selected, entity_color, line_weight_px, world_offset, anno_scale,
|
||||
handle,
|
||||
leader,
|
||||
selected,
|
||||
entity_color,
|
||||
line_weight_px,
|
||||
world_offset,
|
||||
anno_scale,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +126,10 @@ pub fn tessellate(
|
|||
// Annotation scale: scale glyph strokes relative to the text
|
||||
// insertion point (first group's origin) so multi-line MText
|
||||
// lines spread apart correctly as well as growing in size.
|
||||
let ref_origin = stroke_groups.first().map(|g| g.origin).unwrap_or([0.0, 0.0]);
|
||||
let ref_origin = stroke_groups
|
||||
.first()
|
||||
.map(|g| g.origin)
|
||||
.unwrap_or([0.0, 0.0]);
|
||||
let ref_lx = (ref_origin[0] - ox) as f32;
|
||||
let ref_ly = (ref_origin[1] - oy) as f32;
|
||||
for group in &stroke_groups {
|
||||
|
|
@ -141,11 +163,11 @@ pub fn tessellate(
|
|||
snap_pts,
|
||||
tangent_geoms: te.tangent_geoms,
|
||||
aci: 0,
|
||||
key_vertices: te.key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
key_vertices: te.key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -174,9 +196,9 @@ pub fn tessellate(
|
|||
aci: 0,
|
||||
key_vertices: te.key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -187,7 +209,9 @@ pub fn tessellate(
|
|||
if let TruckTessResult::Lines(points) = tessellate_edge(&e, world_offset) {
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
|
||||
let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter()
|
||||
let key_vertices: Vec<[f32; 3]> = te
|
||||
.key_vertices
|
||||
.into_iter()
|
||||
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
|
||||
.collect();
|
||||
return WireModel {
|
||||
|
|
@ -203,9 +227,9 @@ pub fn tessellate(
|
|||
aci: 0,
|
||||
key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -214,7 +238,9 @@ pub fn tessellate(
|
|||
if let TruckTessResult::Lines(points) = tessellate_wire(&w, world_offset) {
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
|
||||
let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter()
|
||||
let key_vertices: Vec<[f32; 3]> = te
|
||||
.key_vertices
|
||||
.into_iter()
|
||||
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
|
||||
.collect();
|
||||
return WireModel {
|
||||
|
|
@ -230,9 +256,9 @@ pub fn tessellate(
|
|||
aci: 0,
|
||||
key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -242,15 +268,25 @@ pub fn tessellate(
|
|||
// leader, mesh, solid2d, etc.). Subtract world_offset so the
|
||||
// geometry lands in local space alongside Line/Arc/Circle.
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let local_pts: Vec<[f32; 3]> = points.into_iter().map(|[x, y, z]| {
|
||||
if x.is_nan() { [x, y, z] }
|
||||
else { [x - ox as f32, y - oy as f32, z - oz as f32] }
|
||||
}).collect();
|
||||
let local_pts: Vec<[f32; 3]> = points
|
||||
.into_iter()
|
||||
.map(|[x, y, z]| {
|
||||
if x.is_nan() {
|
||||
[x, y, z]
|
||||
} else {
|
||||
[x - ox as f32, y - oy as f32, z - oz as f32]
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
|
||||
let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter()
|
||||
let key_vertices: Vec<[f32; 3]> = te
|
||||
.key_vertices
|
||||
.into_iter()
|
||||
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
|
||||
.collect();
|
||||
let fill_tris: Vec<[f32; 3]> = te.fill_tris.into_iter()
|
||||
let fill_tris: Vec<[f32; 3]> = te
|
||||
.fill_tris
|
||||
.into_iter()
|
||||
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
|
||||
.collect();
|
||||
return WireModel {
|
||||
|
|
@ -274,12 +310,20 @@ pub fn tessellate(
|
|||
|
||||
TruckObject::SegmentedLines(points) => {
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let local_pts: Vec<[f32; 3]> = points.into_iter().map(|[x, y, z]| {
|
||||
if x.is_nan() { [x, y, z] }
|
||||
else { [x - ox as f32, y - oy as f32, z - oz as f32] }
|
||||
}).collect();
|
||||
let local_pts: Vec<[f32; 3]> = points
|
||||
.into_iter()
|
||||
.map(|[x, y, z]| {
|
||||
if x.is_nan() {
|
||||
[x, y, z]
|
||||
} else {
|
||||
[x - ox as f32, y - oy as f32, z - oz as f32]
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let snap_pts = offset_snap_pts(te.snap_pts, world_offset);
|
||||
let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter()
|
||||
let key_vertices: Vec<[f32; 3]> = te
|
||||
.key_vertices
|
||||
.into_iter()
|
||||
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
|
||||
.collect();
|
||||
return WireModel {
|
||||
|
|
@ -295,7 +339,7 @@ pub fn tessellate(
|
|||
aci: 0,
|
||||
key_vertices,
|
||||
plinegen: false,
|
||||
vp_scissor: None,
|
||||
vp_scissor: None,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
fill_tris: vec![],
|
||||
};
|
||||
|
|
@ -317,11 +361,7 @@ pub fn tessellate(
|
|||
_ => None,
|
||||
};
|
||||
if let Some(p) = por {
|
||||
let sp = Vec3::new(
|
||||
(p.x - ox) as f32,
|
||||
(p.y - oy) as f32,
|
||||
(p.z - oz) as f32,
|
||||
);
|
||||
let sp = Vec3::new((p.x - ox) as f32, (p.y - oy) as f32, (p.z - oz) as f32);
|
||||
wm.snap_pts.push((sp, SnapHint::Insertion));
|
||||
}
|
||||
return wm;
|
||||
|
|
@ -344,9 +384,9 @@ pub fn tessellate(
|
|||
tangent_geoms,
|
||||
key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -370,8 +410,10 @@ pub fn tessellate_dimension(
|
|||
let (arrow_size, dimexo, dimexe) = document
|
||||
.dim_styles
|
||||
.iter()
|
||||
.find(|s| s.name.eq_ignore_ascii_case(style_name)
|
||||
|| (style_name.trim().is_empty() && s.name.eq_ignore_ascii_case("Standard")))
|
||||
.find(|s| {
|
||||
s.name.eq_ignore_ascii_case(style_name)
|
||||
|| (style_name.trim().is_empty() && s.name.eq_ignore_ascii_case("Standard"))
|
||||
})
|
||||
.map(|s| {
|
||||
let scale = (if s.dimscale > 1e-6 { s.dimscale } else { 1.0 }) * anno_scale as f64;
|
||||
(
|
||||
|
|
@ -403,9 +445,9 @@ pub fn tessellate_dimension(
|
|||
tangent_geoms: vec![],
|
||||
key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
}];
|
||||
|
||||
if let Some(text) = dimension_text_entity(dim) {
|
||||
|
|
@ -437,12 +479,15 @@ fn tessellate_leader_single(
|
|||
world_offset: [f64; 3],
|
||||
anno_scale: f32,
|
||||
) -> WireModel {
|
||||
let color = if selected { WireModel::SELECTED } else { entity_color };
|
||||
let color = if selected {
|
||||
WireModel::SELECTED
|
||||
} else {
|
||||
entity_color
|
||||
};
|
||||
let name = handle.value().to_string();
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let p3 = |v: &Vector3| -> [f32; 3] {
|
||||
[(v.x - ox) as f32, (v.y - oy) as f32, (v.z - oz) as f32]
|
||||
};
|
||||
let p3 =
|
||||
|v: &Vector3| -> [f32; 3] { [(v.x - ox) as f32, (v.y - oy) as f32, (v.z - oz) as f32] };
|
||||
let nan = [f32::NAN; 3];
|
||||
|
||||
let verts = &leader.vertices;
|
||||
|
|
@ -472,7 +517,10 @@ fn tessellate_leader_single(
|
|||
let key_vertices: Vec<[f32; 3]> = verts.iter().map(|v| p3(v)).collect();
|
||||
|
||||
for i in 0..verts.len().saturating_sub(1) {
|
||||
tangents.push(TangentGeom::Line { p1: p3(&verts[i]), p2: p3(&verts[i + 1]) });
|
||||
tangents.push(TangentGeom::Line {
|
||||
p1: p3(&verts[i]),
|
||||
p2: p3(&verts[i + 1]),
|
||||
});
|
||||
}
|
||||
|
||||
if leader.arrow_enabled {
|
||||
|
|
@ -487,15 +535,27 @@ fn tessellate_leader_single(
|
|||
let (s, c) = a.sin_cos();
|
||||
let tip_f = p3(tip);
|
||||
points.push(nan);
|
||||
points.push([tip_f[0] + (dx*c - dy*s)*sz, tip_f[1] + (dx*s + dy*c)*sz, tip_f[2]]);
|
||||
points.push([
|
||||
tip_f[0] + (dx * c - dy * s) * sz,
|
||||
tip_f[1] + (dx * s + dy * c) * sz,
|
||||
tip_f[2],
|
||||
]);
|
||||
points.push(tip_f);
|
||||
points.push([tip_f[0] + (dx*c + dy*s)*sz, tip_f[1] + (-dx*s + dy*c)*sz, tip_f[2]]);
|
||||
points.push([
|
||||
tip_f[0] + (dx * c + dy * s) * sz,
|
||||
tip_f[1] + (-dx * s + dy * c) * sz,
|
||||
tip_f[2],
|
||||
]);
|
||||
}
|
||||
|
||||
if leader.hookline_enabled {
|
||||
let last = verts.last().unwrap();
|
||||
let prev = &verts[verts.len() - 2];
|
||||
let sign = if (last.x - prev.x) >= 0.0 { 1.0_f32 } else { -1.0_f32 };
|
||||
let sign = if (last.x - prev.x) >= 0.0 {
|
||||
1.0_f32
|
||||
} else {
|
||||
-1.0_f32
|
||||
};
|
||||
let land_len = leader.text_height as f32 * 1.5 * anno_scale;
|
||||
let last_f = p3(last);
|
||||
points.push(nan);
|
||||
|
|
@ -532,7 +592,11 @@ fn tessellate_multileader_single(
|
|||
world_offset: [f64; 3],
|
||||
anno_scale: f32,
|
||||
) -> WireModel {
|
||||
let color = if selected { WireModel::SELECTED } else { entity_color };
|
||||
let color = if selected {
|
||||
WireModel::SELECTED
|
||||
} else {
|
||||
entity_color
|
||||
};
|
||||
let name = handle.value().to_string();
|
||||
let nan = [f32::NAN; 3];
|
||||
let [ox, oy, oz] = world_offset;
|
||||
|
|
@ -556,35 +620,52 @@ fn tessellate_multileader_single(
|
|||
snap_pts.push((Vec3::from(cp_f), SnapHint::Node));
|
||||
|
||||
for line in &root.lines {
|
||||
if line.points.is_empty() { continue; }
|
||||
if line.points.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !invisible {
|
||||
if !first { points.push(nan); }
|
||||
if !first {
|
||||
points.push(nan);
|
||||
}
|
||||
first = false;
|
||||
|
||||
let mut ctrl: Vec<[f32; 3]> = line.points.iter().map(|p| p3(p)).collect();
|
||||
let last_f = *ctrl.last().unwrap_or(&cp_f);
|
||||
let dist = ((last_f[0]-cp_f[0]).powi(2) + (last_f[1]-cp_f[1]).powi(2)).sqrt();
|
||||
if dist > 1e-9 { ctrl.push(cp_f); }
|
||||
let dist = ((last_f[0] - cp_f[0]).powi(2) + (last_f[1] - cp_f[1]).powi(2)).sqrt();
|
||||
if dist > 1e-9 {
|
||||
ctrl.push(cp_f);
|
||||
}
|
||||
for &c in &ctrl {
|
||||
key_verts.push(c);
|
||||
snap_pts.push((Vec3::from(c), SnapHint::Node));
|
||||
}
|
||||
|
||||
if ml.path_type == MultiLeaderPathType::Spline && ctrl.len() >= 2 {
|
||||
for pt in catmull_rom_pts(&ctrl, 8) { points.push(pt); }
|
||||
for pt in catmull_rom_pts(&ctrl, 8) {
|
||||
points.push(pt);
|
||||
}
|
||||
} else {
|
||||
for &c in &ctrl { points.push(c); }
|
||||
for &c in &ctrl {
|
||||
points.push(c);
|
||||
}
|
||||
}
|
||||
for i in 0..ctrl.len().saturating_sub(1) {
|
||||
tangents.push(TangentGeom::Line { p1: ctrl[i], p2: ctrl[i + 1] });
|
||||
tangents.push(TangentGeom::Line {
|
||||
p1: ctrl[i],
|
||||
p2: ctrl[i + 1],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if draw_arrow {
|
||||
let tip = &line.points[0];
|
||||
let tip_f = p3(tip);
|
||||
let next = if line.points.len() >= 2 { line.points[1] } else { *cp };
|
||||
let next = if line.points.len() >= 2 {
|
||||
line.points[1]
|
||||
} else {
|
||||
*cp
|
||||
};
|
||||
let dx = (next.x - tip.x) as f32;
|
||||
let dy = (next.y - tip.y) as f32;
|
||||
let dl = (dx * dx + dy * dy).sqrt().max(1e-9);
|
||||
|
|
@ -592,9 +673,17 @@ fn tessellate_multileader_single(
|
|||
let a = std::f32::consts::PI / 6.0;
|
||||
let (s, c) = a.sin_cos();
|
||||
points.push(nan);
|
||||
points.push([tip_f[0] + (dx*c - dy*s)*arrow_size, tip_f[1] + (dx*s + dy*c)*arrow_size, tip_f[2]]);
|
||||
points.push([
|
||||
tip_f[0] + (dx * c - dy * s) * arrow_size,
|
||||
tip_f[1] + (dx * s + dy * c) * arrow_size,
|
||||
tip_f[2],
|
||||
]);
|
||||
points.push(tip_f);
|
||||
points.push([tip_f[0] + (dx*c + dy*s)*arrow_size, tip_f[1] + (-dx*s + dy*c)*arrow_size, tip_f[2]]);
|
||||
points.push([
|
||||
tip_f[0] + (dx * c + dy * s) * arrow_size,
|
||||
tip_f[1] + (-dx * s + dy * c) * arrow_size,
|
||||
tip_f[2],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -604,16 +693,27 @@ fn tessellate_multileader_single(
|
|||
let d = ml.dogleg_length * anno_scale as f64;
|
||||
points.push(nan);
|
||||
points.push(cp_f);
|
||||
points.push([(cp.x + dir.x/dl*d - ox) as f32, (cp.y + dir.y/dl*d - oy) as f32, cp_f[2]]);
|
||||
points.push([
|
||||
(cp.x + dir.x / dl * d - ox) as f32,
|
||||
(cp.y + dir.y / dl * d - oy) as f32,
|
||||
cp_f[2],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Text strokes — scale height, keep insertion point fixed.
|
||||
if ml.content_type == LeaderContentType::MText && !ml.context.text_string.is_empty() {
|
||||
let height = if ml.context.text_height > 0.0 { ml.context.text_height } else { ml.text_height };
|
||||
let height = if ml.context.text_height > 0.0 {
|
||||
ml.context.text_height
|
||||
} else {
|
||||
ml.text_height
|
||||
};
|
||||
let ins = &ml.context.text_location;
|
||||
let z = (ins.z - oz) as f32;
|
||||
snap_pts.push((Vec3::new((ins.x - ox) as f32, (ins.y - oy) as f32, z), SnapHint::Node));
|
||||
snap_pts.push((
|
||||
Vec3::new((ins.x - ox) as f32, (ins.y - oy) as f32, z),
|
||||
SnapHint::Node,
|
||||
));
|
||||
let style = crate::entities::text_support::resolve_text_style("STANDARD", document);
|
||||
let strokes = crate::scene::cxf::tessellate_text_ex(
|
||||
[ins.x as f32, ins.y as f32],
|
||||
|
|
@ -625,7 +725,9 @@ fn tessellate_multileader_single(
|
|||
&ml.context.text_string,
|
||||
);
|
||||
for stroke in &strokes {
|
||||
if stroke.len() < 2 { continue; }
|
||||
if stroke.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
points.push(nan);
|
||||
for &[x, y] in stroke {
|
||||
points.push([x - ox as f32, y - oy as f32, z]);
|
||||
|
|
@ -732,9 +834,8 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
|
|||
let normal = (h.normal.x, h.normal.y, h.normal.z);
|
||||
// Convert a 2D OCS hatch boundary point to WCS, then subtract world_offset.
|
||||
let to_wcs = |x: f64, y: f64| -> [f32; 3] {
|
||||
let (wx, wy, wz) = crate::scene::transform::ocs_point_to_wcs(
|
||||
(x, y, h.elevation), normal,
|
||||
);
|
||||
let (wx, wy, wz) =
|
||||
crate::scene::transform::ocs_point_to_wcs((x, y, h.elevation), normal);
|
||||
[(wx - ox) as f32, (wy - oy) as f32, (wz - oz) as f32]
|
||||
};
|
||||
let mut pts: Vec<[f32; 3]> = Vec::new();
|
||||
|
|
@ -757,7 +858,9 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
|
|||
acadrust::entities::BoundaryEdge::Line(ln) => {
|
||||
let p0 = to_wcs(ln.start.x, ln.start.y);
|
||||
let p1 = to_wcs(ln.end.x, ln.end.y);
|
||||
if !pts.is_empty() { pts.push([f32::NAN; 3]); }
|
||||
if !pts.is_empty() {
|
||||
pts.push([f32::NAN; 3]);
|
||||
}
|
||||
pts.push(p0);
|
||||
pts.push(p1);
|
||||
key_verts.push(p0);
|
||||
|
|
@ -767,16 +870,12 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
|
|||
let sa = arc.start_angle;
|
||||
let ea = arc.end_angle;
|
||||
let ccw = arc.counter_clockwise;
|
||||
let (sa, ea) = if ccw {
|
||||
(sa, ea)
|
||||
} else {
|
||||
(TAU - sa, TAU - ea)
|
||||
};
|
||||
let (sa, ea) = if ccw { (sa, ea) } else { (TAU - sa, TAU - ea) };
|
||||
let span = ea - sa;
|
||||
let segs = ((span / TAU) * 32.0)
|
||||
.ceil()
|
||||
.max(4.0) as u32;
|
||||
if !pts.is_empty() { pts.push([f32::NAN; 3]); }
|
||||
let segs = ((span / TAU) * 32.0).ceil().max(4.0) as u32;
|
||||
if !pts.is_empty() {
|
||||
pts.push([f32::NAN; 3]);
|
||||
}
|
||||
for i in 0..=segs {
|
||||
let t = sa + span * (i as f64 / segs as f64);
|
||||
let p = to_wcs(
|
||||
|
|
@ -784,9 +883,14 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
|
|||
arc.center.y + arc.radius * t.sin() as f64,
|
||||
);
|
||||
pts.push(p);
|
||||
if i == 0 || i == segs { key_verts.push(p); }
|
||||
if i == 0 || i == segs {
|
||||
key_verts.push(p);
|
||||
}
|
||||
}
|
||||
snap_pts.push((Vec3::from(to_wcs(arc.center.x, arc.center.y)), SnapHint::Center));
|
||||
snap_pts.push((
|
||||
Vec3::from(to_wcs(arc.center.x, arc.center.y)),
|
||||
SnapHint::Center,
|
||||
));
|
||||
}
|
||||
acadrust::entities::BoundaryEdge::EllipticArc(ell) => {
|
||||
let r_maj = ((ell.major_axis_endpoint.x * ell.major_axis_endpoint.x
|
||||
|
|
@ -805,10 +909,11 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
|
|||
} else {
|
||||
ea - sa + std::f32::consts::TAU
|
||||
};
|
||||
let segs = ((span / std::f32::consts::TAU) * 32.0)
|
||||
.ceil()
|
||||
.max(4.0) as u32;
|
||||
if !pts.is_empty() { pts.push([f32::NAN; 3]); }
|
||||
let segs =
|
||||
((span / std::f32::consts::TAU) * 32.0).ceil().max(4.0) as u32;
|
||||
if !pts.is_empty() {
|
||||
pts.push([f32::NAN; 3]);
|
||||
}
|
||||
for i in 0..=segs {
|
||||
let t = sa + span * (i as f32 / segs as f32);
|
||||
let lx = r_maj * t.cos();
|
||||
|
|
@ -817,9 +922,14 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
|
|||
let ocs_y = ell.center.y + (lx * rot.sin() + ly * rot.cos()) as f64;
|
||||
let p = to_wcs(ocs_x, ocs_y);
|
||||
pts.push(p);
|
||||
if i == 0 || i == segs { key_verts.push(p); }
|
||||
if i == 0 || i == segs {
|
||||
key_verts.push(p);
|
||||
}
|
||||
}
|
||||
snap_pts.push((Vec3::from(to_wcs(ell.center.x, ell.center.y)), SnapHint::Center));
|
||||
snap_pts.push((
|
||||
Vec3::from(to_wcs(ell.center.x, ell.center.y)),
|
||||
SnapHint::Center,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -837,7 +947,7 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
|
|||
let y0 = (ole.lower_right_corner.y - oy) as f32;
|
||||
let x1 = (ole.lower_right_corner.x - ox) as f32;
|
||||
let y1 = (ole.upper_left_corner.y - oy) as f32;
|
||||
let z = (ole.upper_left_corner.z - oz) as f32;
|
||||
let z = (ole.upper_left_corner.z - oz) as f32;
|
||||
if (x1 - x0).abs() < 1e-6 && (y1 - y0).abs() < 1e-6 {
|
||||
// Degenerate / unknown size — show a small cross.
|
||||
let s = 0.5_f32;
|
||||
|
|
@ -845,12 +955,20 @@ fn legacy_geometry(entity: &EntityType, world_offset: [f64; 3]) -> Geometry {
|
|||
}
|
||||
let pts = vec![
|
||||
// Outer rectangle
|
||||
[x0, y0, z], [x1, y0, z], [x1, y0, z], [x1, y1, z],
|
||||
[x1, y1, z], [x0, y1, z], [x0, y1, z], [x0, y0, z],
|
||||
[x0, y0, z],
|
||||
[x1, y0, z],
|
||||
[x1, y0, z],
|
||||
[x1, y1, z],
|
||||
[x1, y1, z],
|
||||
[x0, y1, z],
|
||||
[x0, y1, z],
|
||||
[x0, y0, z],
|
||||
// Diagonal X
|
||||
[x0, y0, z], [x1, y1, z],
|
||||
[x0, y0, z],
|
||||
[x1, y1, z],
|
||||
[f32::NAN, f32::NAN, f32::NAN],
|
||||
[x1, y0, z], [x0, y1, z],
|
||||
[x1, y0, z],
|
||||
[x0, y1, z],
|
||||
];
|
||||
(pts, vec![], vec![], vec![[x0, y0, z], [x1, y1, z]])
|
||||
}
|
||||
|
|
@ -870,8 +988,8 @@ fn solid_wire_fallback(entity: &EntityType, world_offset: [f64; 3]) -> Vec<[f32;
|
|||
let [ox, oy, oz] = world_offset;
|
||||
let wires: &[acadrust::entities::Wire] = match entity {
|
||||
EntityType::Solid3D(s) => &s.wires,
|
||||
EntityType::Region(r) => &r.wires,
|
||||
EntityType::Body(b) => &b.wires,
|
||||
EntityType::Region(r) => &r.wires,
|
||||
EntityType::Body(b) => &b.wires,
|
||||
_ => return vec![],
|
||||
};
|
||||
|
||||
|
|
@ -908,14 +1026,32 @@ fn dimension_geometry(
|
|||
let second = lv(d.second_point);
|
||||
let def = lv(d.definition_point);
|
||||
let axis = normalized_or(second - first, Vec3::X);
|
||||
append_linear_dimension(&mut points, first, second, def, axis, arrow_size, dimexo, dimexe);
|
||||
append_linear_dimension(
|
||||
&mut points,
|
||||
first,
|
||||
second,
|
||||
def,
|
||||
axis,
|
||||
arrow_size,
|
||||
dimexo,
|
||||
dimexe,
|
||||
);
|
||||
}
|
||||
Dimension::Linear(d) => {
|
||||
let first = lv(d.first_point);
|
||||
let second = lv(d.second_point);
|
||||
let def = lv(d.definition_point);
|
||||
let axis = Vec3::new(d.rotation.cos() as f32, d.rotation.sin() as f32, 0.0);
|
||||
append_linear_dimension(&mut points, first, second, def, normalized_or(axis, Vec3::X), arrow_size, dimexo, dimexe);
|
||||
append_linear_dimension(
|
||||
&mut points,
|
||||
first,
|
||||
second,
|
||||
def,
|
||||
normalized_or(axis, Vec3::X),
|
||||
arrow_size,
|
||||
dimexo,
|
||||
dimexe,
|
||||
);
|
||||
}
|
||||
Dimension::Radius(d) => {
|
||||
let center = lv(d.angle_vertex);
|
||||
|
|
@ -923,7 +1059,12 @@ fn dimension_geometry(
|
|||
let text = dimension_text_position(dim, world_offset);
|
||||
add_segment(&mut points, center, point);
|
||||
add_segment(&mut points, point, text);
|
||||
append_arrow(&mut points, point, normalized_or(center - point, Vec3::X), arrow_size);
|
||||
append_arrow(
|
||||
&mut points,
|
||||
point,
|
||||
normalized_or(center - point, Vec3::X),
|
||||
arrow_size,
|
||||
);
|
||||
}
|
||||
Dimension::Diameter(d) => {
|
||||
let p1 = lv(d.angle_vertex);
|
||||
|
|
@ -982,9 +1123,9 @@ fn append_linear_dimension(
|
|||
let sign1 = if offset1 >= 0.0 { 1.0_f32 } else { -1.0 };
|
||||
let sign2 = if offset2 >= 0.0 { 1.0_f32 } else { -1.0 };
|
||||
let ext1_start = first + perp * (sign1 * dimexo);
|
||||
let ext1_end = d1 + perp * (sign1 * dimexe);
|
||||
let ext1_end = d1 + perp * (sign1 * dimexe);
|
||||
let ext2_start = second + perp * (sign2 * dimexo);
|
||||
let ext2_end = d2 + perp * (sign2 * dimexe);
|
||||
let ext2_end = d2 + perp * (sign2 * dimexe);
|
||||
add_segment(points, ext1_start, ext1_end);
|
||||
add_segment(points, ext2_start, ext2_end);
|
||||
add_segment(points, d1, d2);
|
||||
|
|
@ -1081,13 +1222,35 @@ fn dimension_snap_pts(dim: &Dimension, world_offset: [f64; 3]) -> Vec<(Vec3, Sna
|
|||
};
|
||||
let node = |v: acadrust::types::Vector3| (lv(v), SnapHint::Node);
|
||||
match dim {
|
||||
Dimension::Linear(d) => vec![node(d.first_point), node(d.second_point), node(d.definition_point)],
|
||||
Dimension::Aligned(d) => vec![node(d.first_point), node(d.second_point), node(d.definition_point)],
|
||||
Dimension::Linear(d) => vec![
|
||||
node(d.first_point),
|
||||
node(d.second_point),
|
||||
node(d.definition_point),
|
||||
],
|
||||
Dimension::Aligned(d) => vec![
|
||||
node(d.first_point),
|
||||
node(d.second_point),
|
||||
node(d.definition_point),
|
||||
],
|
||||
Dimension::Radius(d) => vec![node(d.angle_vertex), node(d.definition_point)],
|
||||
Dimension::Diameter(d) => vec![node(d.angle_vertex), node(d.definition_point)],
|
||||
Dimension::Angular2Ln(d) => vec![node(d.angle_vertex), node(d.first_point), node(d.second_point), node(d.definition_point)],
|
||||
Dimension::Angular3Pt(d) => vec![node(d.angle_vertex), node(d.first_point), node(d.second_point), node(d.definition_point)],
|
||||
Dimension::Ordinate(d) => vec![node(d.definition_point), node(d.feature_location), node(d.leader_endpoint)],
|
||||
Dimension::Angular2Ln(d) => vec![
|
||||
node(d.angle_vertex),
|
||||
node(d.first_point),
|
||||
node(d.second_point),
|
||||
node(d.definition_point),
|
||||
],
|
||||
Dimension::Angular3Pt(d) => vec![
|
||||
node(d.angle_vertex),
|
||||
node(d.first_point),
|
||||
node(d.second_point),
|
||||
node(d.definition_point),
|
||||
],
|
||||
Dimension::Ordinate(d) => vec![
|
||||
node(d.definition_point),
|
||||
node(d.feature_location),
|
||||
node(d.leader_endpoint),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1172,17 +1335,30 @@ fn dimension_text_position(dim: &Dimension, world_offset: [f64; 3]) -> Vec3 {
|
|||
|
||||
fn dimension_text_height(dim: &Dimension) -> f64 {
|
||||
let scale = (dim.measurement().abs() * 0.12).clamp(0.25, 2.0);
|
||||
if scale.is_finite() { scale } else { 0.25 }
|
||||
if scale.is_finite() {
|
||||
scale
|
||||
} else {
|
||||
0.25
|
||||
}
|
||||
}
|
||||
|
||||
fn vec3_local(v: Vector3, off: [f64; 3]) -> Vec3 {
|
||||
Vec3::new((v.x - off[0]) as f32, (v.y - off[1]) as f32, (v.z - off[2]) as f32)
|
||||
Vec3::new(
|
||||
(v.x - off[0]) as f32,
|
||||
(v.y - off[1]) as f32,
|
||||
(v.z - off[2]) as f32,
|
||||
)
|
||||
}
|
||||
|
||||
fn offset_snap_pts(pts: Vec<(Vec3, SnapHint)>, off: [f64; 3]) -> Vec<(Vec3, SnapHint)> {
|
||||
let [ox, oy, oz] = off;
|
||||
pts.into_iter()
|
||||
.map(|(p, h)| (Vec3::new(p.x - ox as f32, p.y - oy as f32, p.z - oz as f32), h))
|
||||
.map(|(p, h)| {
|
||||
(
|
||||
Vec3::new(p.x - ox as f32, p.y - oy as f32, p.z - oz as f32),
|
||||
h,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,10 +98,7 @@ pub fn ocs_axes(normal: (f64, f64, f64)) -> ((f64, f64, f64), (f64, f64, f64)) {
|
|||
|
||||
/// Transform a single OCS point to WCS using the given normal vector.
|
||||
#[inline]
|
||||
pub fn ocs_point_to_wcs(
|
||||
ocs: (f64, f64, f64),
|
||||
normal: (f64, f64, f64),
|
||||
) -> (f64, f64, f64) {
|
||||
pub fn ocs_point_to_wcs(ocs: (f64, f64, f64), normal: (f64, f64, f64)) -> (f64, f64, f64) {
|
||||
let (ax, ay) = ocs_axes(normal);
|
||||
let (ox, oy, oz) = ocs;
|
||||
let (nx, ny, nz) = normal;
|
||||
|
|
|
|||
|
|
@ -44,7 +44,11 @@ pub enum TruckTessResult {
|
|||
/// raw world coordinates are in the millions (e.g. Turkish UTM).
|
||||
#[inline]
|
||||
fn to_local(x: f64, y: f64, z: f64, off: [f64; 3]) -> [f32; 3] {
|
||||
[(x - off[0]) as f32, (y - off[1]) as f32, (z - off[2]) as f32]
|
||||
[
|
||||
(x - off[0]) as f32,
|
||||
(y - off[1]) as f32,
|
||||
(z - off[2]) as f32,
|
||||
]
|
||||
}
|
||||
|
||||
// ── Vertex ────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -24,14 +24,22 @@ pub struct ViewportPane<'a> {
|
|||
|
||||
impl<'a> ViewportPane<'a> {
|
||||
pub fn model(scene: &'a Scene, show_viewcube: bool) -> Self {
|
||||
Self { scene, mode: ViewportPaneMode::Model, show_viewcube }
|
||||
Self {
|
||||
scene,
|
||||
mode: ViewportPaneMode::Model,
|
||||
show_viewcube,
|
||||
}
|
||||
}
|
||||
|
||||
/// One paper-space viewport: model content rendered through its own camera.
|
||||
/// See [`ViewportPaneMode::Paper`] for why this is currently unused.
|
||||
#[allow(dead_code)]
|
||||
pub fn paper(scene: &'a Scene, handle: Handle) -> Self {
|
||||
Self { scene, mode: ViewportPaneMode::Paper { handle }, show_viewcube: false }
|
||||
Self {
|
||||
scene,
|
||||
mode: ViewportPaneMode::Paper { handle },
|
||||
show_viewcube: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +70,8 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for PaperViewportPan
|
|||
_cursor: mouse::Cursor,
|
||||
bounds: Rectangle,
|
||||
) -> Self::Primitive {
|
||||
self.scene.build_active_viewport_primitive(self.handle, state.hover_region, bounds)
|
||||
self.scene
|
||||
.build_active_viewport_primitive(self.handle, state.hover_region, bounds)
|
||||
}
|
||||
|
||||
fn update(
|
||||
|
|
@ -101,10 +110,12 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for ViewportPane<'a>
|
|||
) -> Self::Primitive {
|
||||
match &self.mode {
|
||||
ViewportPaneMode::Model => {
|
||||
self.scene.build_primitive(state.hover_region, bounds, self.show_viewcube)
|
||||
self.scene
|
||||
.build_primitive(state.hover_region, bounds, self.show_viewcube)
|
||||
}
|
||||
ViewportPaneMode::Paper { handle } => {
|
||||
self.scene.build_viewport_primitive(*handle, state.hover_region, bounds)
|
||||
self.scene
|
||||
.build_viewport_primitive(*handle, state.hover_region, bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,8 +76,12 @@ impl WireModel {
|
|||
pub const CYAN: [f32; 4] = [0.25, 0.85, 1.00, 1.0];
|
||||
pub const SELECTED: [f32; 4] = [0.15, 0.55, 1.00, 1.0];
|
||||
/// Sentinel AABB that never rejects any snap query.
|
||||
pub const UNBOUNDED_AABB: [f32; 4] =
|
||||
[f32::NEG_INFINITY, f32::NEG_INFINITY, f32::INFINITY, f32::INFINITY];
|
||||
pub const UNBOUNDED_AABB: [f32; 4] = [
|
||||
f32::NEG_INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
f32::INFINITY,
|
||||
f32::INFINITY,
|
||||
];
|
||||
|
||||
/// Create a solid wire (no dash pattern, 1px weight).
|
||||
pub fn solid(name: String, points: Vec<[f32; 3]>, color: [f32; 4], selected: bool) -> Self {
|
||||
|
|
|
|||
Loading…
Reference in a new issue