forked from 1b-agent/opencadstudio-upstream
refactor(scene): group modules into role-based subfolders
Move the 30 flat modules under src/scene/ into six folders by role: convert (entity→geometry), text (fonts/shaping), model (render models), pick (hit-test/selection/grips/xclip), view (camera/transform/render), cache (block + property caches). pipeline/ is unchanged. Full path migration — all `scene::X` references become `scene::<group>::X`, no compatibility re-exports. render.rs items that were `pub(super)` (parent was scene) become `pub(in crate::scene)` to keep their original scene-wide visibility. No behavior change; build and tests are green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
cd048165a9
commit
7fe7e0230d
160 files changed
+732
-715
No files matched your search
@@ -0,0 +1,235 @@
|
||||
/// Tag for pre-baked snap candidates stored inside a WireModel.
|
||||
/// Kept separate from `snap::SnapType` to avoid circular module dependencies.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum SnapHint {
|
||||
/// Geometric center of a circle, arc, or ellipse.
|
||||
Center,
|
||||
/// Point entity location.
|
||||
Node,
|
||||
/// 0 / 90 / 180 / 270 ° point on a circle/arc (within arc span).
|
||||
Quadrant,
|
||||
/// Insertion point of text or block.
|
||||
Insertion,
|
||||
/// Midpoint of a curve that has one well-defined midpoint (an arc's
|
||||
/// arc-length centre, a spline's `t = 0.5`). Lines / polylines do
|
||||
/// not use this — their midpoints are derived from `key_vertices`.
|
||||
Midpoint,
|
||||
}
|
||||
|
||||
/// Geometric primitive used by the tangent-snap engine.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TangentGeom {
|
||||
/// Infinite line through these two world-space points.
|
||||
Line { p1: [f32; 3], p2: [f32; 3] },
|
||||
/// Circle/arc.
|
||||
Circle { center: [f32; 3], radius: f32 },
|
||||
}
|
||||
|
||||
/// A 1-D entity (line, arc, polyline) represented as an ordered set of
|
||||
/// world-space points rendered as a quad strip (TriangleList).
|
||||
///
|
||||
/// Linetype is encoded as a GPU-side dash pattern so the CPU never needs to
|
||||
/// split wires into per-dash segments. `pattern_length = 0.0` means solid.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WireModel {
|
||||
/// Unique identifier — the handle value as a decimal string.
|
||||
pub name: String,
|
||||
/// Ordered world-space positions forming a strip of quads.
|
||||
pub points: Vec<[f32; 3]>,
|
||||
/// RGBA colour in [0, 1].
|
||||
pub color: [f32; 4],
|
||||
/// Whether this wire is currently selected.
|
||||
#[allow(dead_code)]
|
||||
pub selected: bool,
|
||||
/// Total length of one pattern repeat (world units). 0 = solid line.
|
||||
pub pattern_length: f32,
|
||||
/// Up to 8 pattern elements: positive = dash length, negative = gap length.
|
||||
/// Unused slots must be 0.0 (acts as end-of-pattern sentinel in shader).
|
||||
pub pattern: [f32; 8],
|
||||
/// Rendered line width in screen pixels (half-width = line_weight_px / 2).
|
||||
pub line_weight_px: f32,
|
||||
/// ACI color index (1-255). 0 means true-color or unknown (no CTB lookup).
|
||||
pub aci: u8,
|
||||
/// Pre-baked snap candidates (Center, Node, Quadrant, Insertion).
|
||||
pub snap_pts: Vec<(glam::Vec3, SnapHint)>,
|
||||
/// Per-segment tangent geometry for Tangent snap.
|
||||
/// Line/Arc entities: 1 entry. LwPolyline: 1 entry per segment.
|
||||
pub tangent_geoms: Vec<TangentGeom>,
|
||||
/// True polyline vertices used for Endpoint/Midpoint snap.
|
||||
/// Non-empty only for entities with distinct vertex positions (Line, LwPolyline).
|
||||
/// Empty for tessellated curves (Circle, Arc, Ellipse) which use snap_pts instead.
|
||||
pub key_vertices: Vec<[f32; 3]>,
|
||||
/// World-space 2-D bounding box [min_x, min_y, max_x, max_y].
|
||||
/// Set from acadrust `bounding_box()` in `tessellate_entity()`.
|
||||
/// Preview / interim wires use `UNBOUNDED_AABB` so they are never pre-rejected
|
||||
/// by the snap world-space filter.
|
||||
pub aabb: [f32; 4],
|
||||
/// When false the linetype pattern restarts at each NaN-separated segment
|
||||
/// (DXF PLINEGEN=0). When true the pattern runs continuously (PLINEGEN=1).
|
||||
pub plinegen: bool,
|
||||
/// Paper-space bounding box [x0, y0, x1, y1] for GPU scissor clipping.
|
||||
/// Set only for viewport-projected wires in paper-space layouts.
|
||||
pub vp_scissor: Option<[f32; 4]>,
|
||||
/// Pre-triangulated solid fill: flat vertex list, 3 per triangle (world-offset applied).
|
||||
/// Non-empty only for PolyfaceMesh / PolygonMesh entities.
|
||||
pub fill_tris: Vec<[f32; 3]>,
|
||||
}
|
||||
|
||||
impl WireModel {
|
||||
pub const WHITE: [f32; 4] = [1.00, 1.00, 1.00, 1.0];
|
||||
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];
|
||||
/// Civil 3D-style object acquisition highlight (structure / catchment hover).
|
||||
pub const PICK_HIGHLIGHT: [f32; 4] = [0.95, 0.50, 0.08, 1.0];
|
||||
pub const PICK_HIGHLIGHT_DIM: [f32; 4] = [0.95, 0.50, 0.08, 0.45];
|
||||
/// Sentinel AABB that never rejects any snap query.
|
||||
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 {
|
||||
Self {
|
||||
name,
|
||||
points,
|
||||
color,
|
||||
selected,
|
||||
aci: 0,
|
||||
pattern_length: 0.0,
|
||||
pattern: [0.0; 8],
|
||||
line_weight_px: 1.0,
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
aabb: Self::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a clone with every point translated by `delta`.
|
||||
pub fn translated(&self, delta: glam::Vec3) -> Self {
|
||||
let mut out = self.clone();
|
||||
out.name = format!("preview_{}", self.name);
|
||||
out.color = Self::CYAN;
|
||||
out.selected = false;
|
||||
for p in &mut out.points {
|
||||
p[0] += delta.x;
|
||||
p[1] += delta.y;
|
||||
p[2] += delta.z;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Return a clone with every point rotated around `center` by `angle_rad`.
|
||||
pub fn rotated(&self, center: glam::Vec3, angle_rad: f32) -> Self {
|
||||
let (s, c) = angle_rad.sin_cos();
|
||||
let mut out = self.clone();
|
||||
out.name = format!("preview_{}", self.name);
|
||||
out.color = Self::CYAN;
|
||||
out.selected = false;
|
||||
for p in &mut out.points {
|
||||
let dx = p[0] - center.x;
|
||||
let dy = p[1] - center.y;
|
||||
p[0] = center.x + dx * c - dy * s;
|
||||
p[1] = center.y + dx * s + dy * c;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Return a clone with every point uniformly scaled from `center` by `factor`.
|
||||
pub fn scaled(&self, center: glam::Vec3, factor: f32) -> Self {
|
||||
let mut out = self.clone();
|
||||
out.name = format!("preview_{}", self.name);
|
||||
out.color = Self::CYAN;
|
||||
out.selected = false;
|
||||
for p in &mut out.points {
|
||||
p[0] = center.x + (p[0] - center.x) * factor;
|
||||
p[1] = center.y + (p[1] - center.y) * factor;
|
||||
p[2] = center.z + (p[2] - center.z) * factor;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Return a clone for a stretch preview: every point whose XY lies inside
|
||||
/// the crossing window `[win_min, win_max]` is translated by `delta`; points
|
||||
/// outside stay put. Exact for line/polyline vertices (the primary stretch
|
||||
/// targets); curve tessellation points may deform where a window edge cuts
|
||||
/// through them, matching the per-vertex nature of the operation.
|
||||
pub fn stretched(&self, win_min: glam::Vec3, win_max: glam::Vec3, delta: glam::Vec3) -> Self {
|
||||
let mut out = self.clone();
|
||||
out.name = format!("preview_{}", self.name);
|
||||
out.color = Self::CYAN;
|
||||
out.selected = false;
|
||||
for p in &mut out.points {
|
||||
if p[0] >= win_min.x && p[0] <= win_max.x && p[1] >= win_min.y && p[1] <= win_max.y {
|
||||
p[0] += delta.x;
|
||||
p[1] += delta.y;
|
||||
p[2] += delta.z;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Return a clone mirrored across the line through `p1`→`p2`.
|
||||
pub fn mirrored(&self, p1: glam::Vec3, p2: glam::Vec3) -> Self {
|
||||
let ax = p2.x - p1.x;
|
||||
let ay = p2.y - p1.y;
|
||||
let len2 = ax * ax + ay * ay;
|
||||
let mut out = self.clone();
|
||||
out.name = format!("preview_{}", self.name);
|
||||
out.color = Self::CYAN;
|
||||
out.selected = false;
|
||||
if len2 < 1e-12 {
|
||||
return out;
|
||||
}
|
||||
for p in &mut out.points {
|
||||
let dx = p[0] - p1.x;
|
||||
let dy = p[1] - p1.y;
|
||||
let t = (dx * ax + dy * ay) / len2;
|
||||
p[0] = p1.x + 2.0 * t * ax - dx;
|
||||
p[1] = p1.y + 2.0 * t * ay - dy;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Total arc-length of this wire (sum of segment lengths).
|
||||
#[allow(dead_code)]
|
||||
pub fn length(&self) -> f32 {
|
||||
self.points
|
||||
.windows(2)
|
||||
.map(|w| {
|
||||
let dx = w[1][0] - w[0][0];
|
||||
let dy = w[1][1] - w[0][1];
|
||||
let dz = w[1][2] - w[0][2];
|
||||
(dx * dx + dy * dy + dz * dz).sqrt()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WireModel {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: String::new(),
|
||||
points: Vec::new(),
|
||||
color: Self::WHITE,
|
||||
selected: false,
|
||||
pattern_length: 0.0,
|
||||
pattern: [0.0; 8],
|
||||
line_weight_px: 1.0,
|
||||
aci: 0,
|
||||
snap_pts: Vec::new(),
|
||||
tangent_geoms: Vec::new(),
|
||||
key_vertices: Vec::new(),
|
||||
aabb: Self::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user