Files
solidcraft/crates/geom/src/plane.rs
T
JIJINBEIandClaude Opus 5.5 7ad5130cd2 refactor: geom::UnitVec3 for unit axes and normals (#2603)
* geom: UnitVec3; a face's analytic axes and normals are unit by type

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* geom: UnitVec3 adds to and subtracts from a Vec3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* kernel, doc, engine: cylinder axes and measured normals held as UnitVec3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* kernel, engine: functions that take a unit axis or direction say so by type

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* doc: a face's normal goes to Plane::from_normal as is

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* kernel, doc: main's new cylinder helpers take the face's axis as a UnitVec3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:16:31 -04:00

90 lines
3.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use serde::{Deserialize, Serialize};
use crate::{Vec2, Vec3};
/// A sketch plane: an origin and two orthonormal in-plane axes. The normal is `x × y`.
///
/// World point of sketch point `(u, v)` = `origin + u·x + v·y`.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Plane {
pub origin: Vec3,
#[serde(rename = "x_dir")]
pub x: Vec3,
#[serde(rename = "y_dir")]
pub y: Vec3,
}
impl Plane {
/// Top: sketch x = +X, y = +Y, normal +Z.
pub const XY: Plane = Plane { origin: Vec3::ZERO, x: Vec3::X, y: Vec3::Y };
/// Front: sketch x = +X, y = −Z, normal +Y.
pub const XZ: Plane = Plane { origin: Vec3::ZERO, x: Vec3::X, y: Vec3::new(0.0, 0.0, -1.0) };
/// Right: sketch x = +Y, y = +Z, normal +X.
pub const YZ: Plane = Plane { origin: Vec3::ZERO, x: Vec3::Y, y: Vec3::Z };
/// Plane through `origin` with the given axes, re-orthonormalised. `None` if degenerate.
pub fn new(origin: Vec3, x: Vec3, y: Vec3) -> Option<Plane> {
let x = x.normalized()?;
let n = x.cross(y).normalized()?;
let y = n.cross(x).normalized()?;
origin.is_finite().then_some(Plane { origin, x, y })
}
/// Plane through `origin` with normal `n` (in-plane axes chosen deterministically).
pub fn from_normal(origin: Vec3, n: impl Into<Vec3>) -> Option<Plane> {
let n = n.into().normalized()?;
// Horizontal planes keep world X as sketch x; others use the horizontal direction Z × n.
let cand = if n.dot(Vec3::Z).abs() > 0.9 { Vec3::X } else { Vec3::Z.cross(n) };
let x = (cand - n * cand.dot(n)).normalized()?;
Plane::new(origin, x, n.cross(x))
}
/// The same plane seen from its other side: a half turn about the sketch x axis (y
/// reversed), or about the y axis (x reversed) when `about_y`. Still right-handed.
pub fn flipped(&self, about_y: bool) -> Plane {
if about_y { Plane { x: -self.x, ..*self } } else { Plane { y: -self.y, ..*self } }
}
/// Named origin plane: `XY`, `XZ`, `YZ` (case-insensitive).
pub fn named(name: &str) -> Option<Plane> {
match name.to_ascii_uppercase().as_str() {
"XY" | "TOP" => Some(Plane::XY),
"XZ" | "FRONT" => Some(Plane::XZ),
"YZ" | "RIGHT" => Some(Plane::YZ),
_ => None,
}
}
pub fn normal(&self) -> Vec3 {
self.x.cross(self.y)
}
pub fn to_world(&self, p: Vec2) -> Vec3 {
self.origin + self.x * p.x + self.y * p.y
}
pub fn dir_to_world(&self, d: Vec2) -> Vec3 {
self.x * d.x + self.y * d.y
}
pub fn to_local(&self, p: Vec3) -> Vec2 {
let d = p - self.origin;
Vec2::new(d.dot(self.x), d.dot(self.y))
}
/// Signed distance of `p` above the plane (along the normal).
pub fn height(&self, p: Vec3) -> f64 {
(p - self.origin).dot(self.normal())
}
/// The plane moved along its normal by `d`.
pub fn offset(&self, d: f64) -> Plane {
Plane { origin: self.origin + self.normal() * d, ..*self }
}
/// Intersection of the ray `o + t·dir` with the plane (t may be negative).
pub fn intersect_ray(&self, o: Vec3, dir: Vec3) -> Option<Vec3> {
let n = self.normal();
let den = dir.dot(n);
if den.abs() < 1e-12 {
return None;
}
let t = (self.origin - o).dot(n) / den;
t.is_finite().then(|| o + dir * t)
}
}