use serde::{Deserialize, Serialize};
pub mod recovery;
pub use recovery::{
FloorBoundaryLoop, FloorBoundarySource, GeometryDiagnostic, LevelElevation,
OpeningHostRelationship, OpeningKind, RecoveryOutcome, WallLocationCurve, WallLocationSource,
recover_door_host, recover_floor_boundary, recover_floor_boundary_from_floor,
recover_level_elevation, recover_level_elevations, recover_wall_location_curve,
recover_wall_location_curve_from_arc_wall, recover_wall_location_curve_from_wall,
recover_window_host,
};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Point3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Point3 {
pub const ORIGIN: Self = Self::new(0.0, 0.0, 0.0);
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Vector3 {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Vector3 {
pub const X_AXIS: Self = Self::new(1.0, 0.0, 0.0);
pub const Y_AXIS: Self = Self::new(0.0, 1.0, 0.0);
pub const Z_AXIS: Self = Self::new(0.0, 0.0, 1.0);
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Transform3 {
pub origin: Point3,
pub x_axis: Vector3,
pub y_axis: Vector3,
pub z_axis: Vector3,
pub scale: f64,
}
impl Transform3 {
pub const IDENTITY: Self = Self {
origin: Point3::ORIGIN,
x_axis: Vector3::X_AXIS,
y_axis: Vector3::Y_AXIS,
z_axis: Vector3::Z_AXIS,
scale: 1.0,
};
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Curve {
Line { start: Point3, end: Point3 },
Arc {
center: Point3,
radius: f64,
normal: Vector3,
start_angle: f64,
end_angle: f64,
},
Circle {
center: Point3,
radius: f64,
normal: Vector3,
},
Ellipse {
center: Point3,
major_axis: Vector3,
minor_axis: Vector3,
start_angle: f64,
end_angle: f64,
},
HermiteSpline {
control_points: Vec<Point3>,
tangents: Vec<Vector3>,
},
NurbsCurve {
degree: u8,
knots: Vec<f64>,
control_points: Vec<(Point3, f64)>,
},
CylindricalHelix {
center: Point3,
axis: Vector3,
radius: f64,
pitch: f64,
turns: f64,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CurveLoop {
pub curves: Vec<Curve>,
pub closed: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Face {
Planar {
origin: Point3,
normal: Vector3,
outer_loop: CurveLoop,
holes: Vec<CurveLoop>,
},
Cylindrical {
axis_origin: Point3,
axis_direction: Vector3,
radius: f64,
u_range: (f64, f64),
v_range: (f64, f64),
},
Conical {
apex: Point3,
axis_direction: Vector3,
half_angle_radians: f64,
u_range: (f64, f64),
v_range: (f64, f64),
},
Revolved {
generator: Curve,
axis_origin: Point3,
axis_direction: Vector3,
angle_radians: f64,
},
Ruled { curve_a: Curve, curve_b: Curve },
HermitePatch {
control_net: Vec<Vec<Point3>>,
u_tangents: Vec<Vec<Vector3>>,
v_tangents: Vec<Vec<Vector3>>,
},
NurbsSurface {
u_degree: u8,
v_degree: u8,
u_knots: Vec<f64>,
v_knots: Vec<f64>,
control_net: Vec<Vec<(Point3, f64)>>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Solid {
Extrusion {
profile: CurveLoop,
direction: Vector3,
distance: f64,
placement: Transform3,
},
Blend {
profile_a: CurveLoop,
profile_b: CurveLoop,
alignment: Transform3,
},
Revolve {
profile: CurveLoop,
axis_origin: Point3,
axis_direction: Vector3,
angle_radians: f64,
},
Sweep {
profile: CurveLoop,
path: Curve,
placement: Transform3,
},
SweptBlend {
profile_a: CurveLoop,
profile_b: CurveLoop,
path: Curve,
},
Boolean {
op: BooleanOp,
operand_a: Box<Solid>,
operand_b: Box<Solid>,
},
Void {
host: Box<Solid>,
void_shape: Box<Solid>,
},
Mesh(Mesh),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BooleanOp {
Union,
Difference,
Intersection,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Mesh {
pub vertices: Vec<Point3>,
pub triangles: Vec<[u32; 3]>,
pub normals: Vec<Vector3>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PointCloud {
pub points: Vec<Point3>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct BoundingBox {
pub min: Point3,
pub max: Point3,
}
impl BoundingBox {
pub fn empty() -> Self {
Self {
min: Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY),
max: Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
}
}
pub fn is_empty(&self) -> bool {
self.min.x > self.max.x || self.min.y > self.max.y || self.min.z > self.max.z
}
pub fn expand_point(&mut self, p: Point3) {
if p.x < self.min.x {
self.min.x = p.x;
}
if p.y < self.min.y {
self.min.y = p.y;
}
if p.z < self.min.z {
self.min.z = p.z;
}
if p.x > self.max.x {
self.max.x = p.x;
}
if p.y > self.max.y {
self.max.y = p.y;
}
if p.z > self.max.z {
self.max.z = p.z;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn point3_origin_is_zero() {
assert_eq!(Point3::ORIGIN.x, 0.0);
assert_eq!(Point3::ORIGIN.y, 0.0);
assert_eq!(Point3::ORIGIN.z, 0.0);
}
#[test]
fn transform_identity_has_unit_axes_at_origin() {
let t = Transform3::IDENTITY;
assert_eq!(t.origin, Point3::ORIGIN);
assert_eq!(t.x_axis, Vector3::X_AXIS);
assert_eq!(t.y_axis, Vector3::Y_AXIS);
assert_eq!(t.z_axis, Vector3::Z_AXIS);
assert_eq!(t.scale, 1.0);
}
#[test]
fn curve_line_stores_endpoints() {
let c = Curve::Line {
start: Point3::new(0.0, 0.0, 0.0),
end: Point3::new(1.0, 2.0, 3.0),
};
match c {
Curve::Line { start, end } => {
assert_eq!(start.x, 0.0);
assert_eq!(end.z, 3.0);
}
_ => panic!("expected Line"),
}
}
#[test]
fn solid_extrusion_builds() {
let profile = CurveLoop {
curves: vec![Curve::Line {
start: Point3::ORIGIN,
end: Point3::new(10.0, 0.0, 0.0),
}],
closed: false,
};
let s = Solid::Extrusion {
profile,
direction: Vector3::Z_AXIS,
distance: 5.0,
placement: Transform3::IDENTITY,
};
match s {
Solid::Extrusion { distance, .. } => assert_eq!(distance, 5.0),
_ => panic!("expected Extrusion"),
}
}
#[test]
fn boolean_op_variants_are_distinct() {
assert_ne!(BooleanOp::Union, BooleanOp::Difference);
assert_ne!(BooleanOp::Difference, BooleanOp::Intersection);
}
#[test]
fn bbox_empty_starts_inverted() {
let b = BoundingBox::empty();
assert!(b.is_empty());
}
#[test]
fn bbox_expand_tracks_extent() {
let mut b = BoundingBox::empty();
b.expand_point(Point3::new(1.0, 2.0, 3.0));
b.expand_point(Point3::new(-5.0, 10.0, 0.0));
assert!(!b.is_empty());
assert_eq!(b.min.x, -5.0);
assert_eq!(b.max.y, 10.0);
}
#[test]
fn mesh_and_pointcloud_serialize() {
let m = Mesh {
vertices: vec![Point3::ORIGIN, Point3::new(1.0, 0.0, 0.0)],
triangles: vec![[0, 1, 0]],
normals: vec![],
};
let json = serde_json::to_string(&m).unwrap();
assert!(json.contains("\"triangles\""));
let pc = PointCloud {
points: vec![Point3::ORIGIN],
};
let json = serde_json::to_string(&pc).unwrap();
assert!(json.contains("\"points\""));
}
}