From 82850849e793c9b5f54f37f0f02c9f238494ecbb Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Sat, 2 Feb 2019 21:05:11 -0800 Subject: [PATCH] refactor and whatnot --- Cargo.lock | 1 + Cargo.toml | 3 +- src/entity.rs | 61 +++++++++ src/main.rs | 340 +----------------------------------------------- src/math.rs | 180 +++++++++++++++++++++++++ src/relation.rs | 86 ++++++++++++ 6 files changed, 335 insertions(+), 336 deletions(-) create mode 100644 src/entity.rs create mode 100644 src/math.rs create mode 100644 src/relation.rs diff --git a/Cargo.lock b/Cargo.lock index 76692e3..1073bd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "cad_rs" version = "0.1.0" dependencies = [ + "approx 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "nalgebra 0.16.13 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index 567c9fb..5d00795 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ authors = ["Alex Mikhalev "] edition = "2018" [dependencies] -nalgebra = "0.16" \ No newline at end of file +nalgebra = "0.16" +approx = "0.3" \ No newline at end of file diff --git a/src/entity.rs b/src/entity.rs new file mode 100644 index 0000000..d210e76 --- /dev/null +++ b/src/entity.rs @@ -0,0 +1,61 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use crate::math::{Line2, Point2, Region, Region1, Region2, Rot2, Scalar}; + +#[derive(Clone, Copy, Debug)] +pub struct Var> { + value: T, + constraints: TRegion, +} + +impl> Var { + pub fn new(value: T, constraints: TRegion) -> Self { + Self { value, constraints } + } + + pub fn new_full(value: T) -> Self { + Self::new(value, TRegion::full()) + } + + pub fn new_single(value: T) -> Self { + Self::new(value.clone(), TRegion::singleton(value)) + } + + pub fn constraints(&self) -> &TRegion { + &self.constraints + } + + pub fn reconstrain(&mut self, new_constraints: TRegion) -> bool { + self.constraints = new_constraints; + if let Some(n) = self.constraints.nearest(&self.value) { + self.value = n; + true + } else { + false + } + } +} + +type ScalarVar = Var; +type PointVar = Var; + +#[derive(Debug)] +pub struct Point { + pub pos: PointVar, +} + +pub type PointRef = Rc>; + +impl Point { + pub fn new_ref(pos: PointVar) -> PointRef { + Rc::new(RefCell::new(Point { pos })) + } +} + +struct Line { + p1: PointRef, + p2: PointRef, + len: ScalarVar, + dir: ScalarVar, +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 24c8127..40ae4a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,340 +1,10 @@ extern crate nalgebra; +#[macro_use] +extern crate approx; -mod math { - - pub type Scalar = f64; - - pub type Vec2 = nalgebra::Vector2; - pub type Point2 = nalgebra::Point2; - - pub type Rot2 = nalgebra::UnitComplex; - - pub trait Region { - fn full() -> Self; - fn singleton(value: T) -> Self; - - fn nearest(&self, value: &T) -> Option; - fn contains(&self, value: &T) -> bool; - } - - #[derive(Clone, Debug)] - pub enum Region1 { - Empty, - Singleton(Scalar), - Range(Scalar, Scalar), - Union(Box, Box), - Full, - } - - impl Region for Region1 { - fn full() -> Self { - Region1::Full - } - - fn singleton(value: Scalar) -> Self { - Region1::Singleton(value) - } - - fn contains(&self, n: &Scalar) -> bool { - use Region1::*; - match self { - Empty => false, - Singleton(n1) => *n1 == *n, - Range(l, u) => *l <= *n && *n <= *u, - Union(r1, r2) => r1.contains(n) || r2.contains(n), - Full => true, - } - } - - fn nearest(&self, n: &Scalar) -> Option { - unimplemented!(); - } - } - - // line starting at start, point at angle dir, with range extent - // ie. start + (cos dir, sin dir) * t for t in extent - #[derive(Clone, Debug)] - pub struct Line2 { - start: Point2, - dir: Rot2, - extent: Region1, - } - - impl Line2 { - pub fn new(start: Point2, dir: Rot2, extent: Region1) -> Self { - Self { start, dir, extent } - } - - pub fn evaluate(&self, t: Scalar) -> Point2 { - self.start + self.dir * Vec2::new(t, 0.) - } - - pub fn intersect(&self, other: &Line2) -> Option { - // if two lines are parallel - // TODO: epsilon? - if (self.dir * other.dir).sin_angle() == 0. { - return None; - } - // TODO: respect extent - let (a, b) = (self, other); - let (a_0, a_v, b_0, b_v) = (a.start, a.dir, b.start, b.dir); - let (a_c, a_s, b_c, b_s) = ( - a_v.cos_angle(), - a_v.sin_angle(), - b_v.cos_angle(), - b_v.sin_angle(), - ); - let t_b = - (a_0.x * a_s - a_0.y * a_c + a_0.x * a_s + b_0.y * a_c) / (a_s * b_c - a_c * b_s); - Some(b.evaluate(t_b)) - } - } - - #[derive(Clone, Debug)] - pub enum Region2 { - Empty, - // single point at 0 - Singleton(Point2), - Line(Line2), - Union(Box, Box), - Full, - } - - impl Region for Region2 { - fn full() -> Self { - Region2::Full - } - - fn singleton(value: Point2) -> Self { - Region2::Singleton(value) - } - - fn contains(&self, p: &Point2) -> bool { - use Region2::*; - self.nearest(p).map_or(false, |n| n == *p) // TODO: epsilon? - - // match self { - // Empty => false, - // Singleton(n1) => *n1 == n, - // Line(_, _, _) => unimplemented!(), - // Union(r1, r2) => r1.contains(n) || r2.contains(n), - // Full => true, - // } - } - - fn nearest(&self, p: &Point2) -> Option { - use Region2::*; - match self { - Empty => None, - Full => Some(*p), - Singleton(n) => Some(*n), - Line(line) => { - // rotate angle 90 degrees - let perp_dir = line.dir * Rot2::from_cos_sin_unchecked(0., 1.); - let perp = Line2::new(*p, perp_dir, Region1::Full); - perp.intersect(line) - } - Union(r1, r2) => { - use nalgebra::distance; - match (r1.nearest(p), r2.nearest(p)) { - (None, None) => None, - (Some(n), None) | (None, Some(n)) => Some(n), - (Some(n1), Some(n2)) => Some({ - if distance(p, &n1) <= distance(p, &n2) { - n1 - } else { - n2 - } - }), - } - } - } - } - } - - impl Region2 { - pub fn intersect(&self, other: &Region2) -> Region2 { - use Region2::*; - match (self, other) { - (Empty, _) | (_, Empty) => Empty, - (Full, r @ _) | (r @ _, Full) => r.clone(), - (Singleton(n1), Singleton(n2)) => { - if n1 == n2 { - Singleton(*n1) - } else { - Empty - } - } - (Singleton(n), o @ _) | (o @ _, Singleton(n)) => { - if o.contains(n) { - Singleton(*n) - } else { - Empty - } - } - (Line(l1), Line(l2)) => match l1.intersect(l2) { - Some(p) => Singleton(p), - None => Empty, - }, - _ => unimplemented!(), - } - } - } - -} - -mod entity { - use std::cell::RefCell; - use std::rc::Rc; - - use crate::math::{Line2, Point2, Region, Region1, Region2, Rot2, Scalar}; - - #[derive(Clone, Copy, Debug)] - pub struct Var> { - value: T, - constraints: TRegion, - } - - impl> Var { - pub fn new(value: T, constraints: TRegion) -> Self { - Self { value, constraints } - } - - pub fn new_full(value: T) -> Self { - Self::new(value, TRegion::full()) - } - - pub fn new_single(value: T) -> Self { - Self::new(value.clone(), TRegion::singleton(value)) - } - - pub fn constraints(&self) -> &TRegion { - &self.constraints - } - - pub fn reconstrain(&mut self, new_constraints: TRegion) -> bool { - self.constraints = new_constraints; - if let Some(n) = self.constraints.nearest(&self.value) { - self.value = n; - true - } else { - false - } - } - } - - type ScalarVar = Var; - type PointVar = Var; - - #[derive(Debug)] - pub struct Point { - pub pos: PointVar, - } - - pub type PointRef = Rc>; - - impl Point { - pub fn new_ref(pos: PointVar) -> PointRef { - Rc::new(RefCell::new(Point { pos })) - } - } - - struct Line { - p1: PointRef, - p2: PointRef, - len: ScalarVar, - dir: ScalarVar, - } -} - -mod relation { - use crate::entity::{Point as PointEntity, PointRef}; - use crate::math::{Line2, Point2, Region, Region1, Region2, Rot2, Scalar}; - - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum ResolveResult { - Underconstrained, - Constrained, - Overconstrained, - } - - impl ResolveResult { - pub fn from_r2(r: &Region2) -> ResolveResult { - use Region2::*; - match r { - Empty => ResolveResult::Overconstrained, - Singleton(_) => ResolveResult::Constrained, - _ => ResolveResult::Constrained, - } - } - } - - pub trait Relation { - fn resolve(&self) -> ResolveResult; - } - - pub struct Coincident { - pub p1: PointRef, - pub p2: PointRef, - } - - impl Relation for Coincident { - fn resolve(&self) -> ResolveResult { - use Region2::*; - let (mut p1, mut p2) = (self.p1.borrow_mut(), self.p2.borrow_mut()); - let r = { p1.pos.constraints().intersect(p2.pos.constraints()) }; - p1.pos.reconstrain(r.clone()); - p2.pos.reconstrain(r.clone()); - ResolveResult::from_r2(&r) - } - } - - pub struct PointAngle { - pub p1: PointRef, - pub p2: PointRef, - pub angle: Rot2, - } - - impl PointAngle { - pub fn new(p1: PointRef, p2: PointRef, angle: Rot2) -> Self { - Self { p1, p2, angle } - } - - pub fn new_horizontal(p1: PointRef, p2: PointRef) -> Self { - Self::new(p1, p2, Rot2::from_cos_sin_unchecked(1., 0.)) - } - - pub fn new_vertical(p1: PointRef, p2: PointRef) -> Self { - Self::new(p1, p2, Rot2::from_cos_sin_unchecked(0., 1.)) - } - } - - impl Relation for PointAngle { - fn resolve(&self) -> ResolveResult { - use Region2::*; - let (mut p1, mut p2) = (self.p1.borrow_mut(), self.p2.borrow_mut()); - let constrain_line = |p1: &Point2, p2: &mut PointEntity| { - let line = Region2::Line(Line2::new(p1.clone(), self.angle, Region1::Full)); - let new_constraint = p2.pos.constraints().intersect(&line); - p2.pos.reconstrain(new_constraint); - ResolveResult::from_r2(p2.pos.constraints()) - }; - match (&mut p1.pos.constraints(), &mut p2.pos.constraints()) { - (Empty, _) | (_, Empty) => ResolveResult::Overconstrained, - (Singleton(p1), Singleton(p2)) => { - if p1.x == p2.x { - ResolveResult::Constrained - } else { - ResolveResult::Overconstrained - } - } - (Singleton(p), _) => constrain_line(p, &mut *p2), - (_, Singleton(p)) => constrain_line(p, &mut *p1), - _ => ResolveResult::Underconstrained, - } - } - } -} +mod math; +mod entity; +mod relation; fn main() { use entity::{Point, PointRef, Var}; diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 0000000..b7dc393 --- /dev/null +++ b/src/math.rs @@ -0,0 +1,180 @@ +pub type Scalar = f64; + +pub const EPSILON: Scalar = std::f64::EPSILON * 100.; + +pub type Vec2 = nalgebra::Vector2; +pub type Point2 = nalgebra::Point2; + +pub type Rot2 = nalgebra::UnitComplex; + +pub trait Region { + fn full() -> Self; + fn singleton(value: T) -> Self; + + fn nearest(&self, value: &T) -> Option; + fn contains(&self, value: &T) -> bool; +} + +#[derive(Clone, Debug)] +pub enum Region1 { + Empty, + Singleton(Scalar), + Range(Scalar, Scalar), + Union(Box, Box), + Full, +} + +impl Region for Region1 { + fn full() -> Self { + Region1::Full + } + + fn singleton(value: Scalar) -> Self { + Region1::Singleton(value) + } + + fn contains(&self, n: &Scalar) -> bool { + use Region1::*; + match self { + Empty => false, + Singleton(n1) => *n1 == *n, + Range(l, u) => *l <= *n && *n <= *u, + Union(r1, r2) => r1.contains(n) || r2.contains(n), + Full => true, + } + } + + fn nearest(&self, n: &Scalar) -> Option { + unimplemented!(); + } +} + +// line starting at start, point at angle dir, with range extent +// ie. start + (cos dir, sin dir) * t for t in extent +#[derive(Clone, Debug)] +pub struct Line2 { + start: Point2, + dir: Rot2, + extent: Region1, +} + +impl Line2 { + pub fn new(start: Point2, dir: Rot2, extent: Region1) -> Self { + Self { start, dir, extent } + } + + pub fn evaluate(&self, t: Scalar) -> Point2 { + self.start + self.dir * Vec2::new(t, 0.) + } + + pub fn intersect(&self, other: &Line2) -> Option { + // if two lines are parallel + // TODO: epsilon? + if (self.dir * other.dir).sin_angle() == 0. { + return None; + } + // TODO: respect extent + let (a, b) = (self, other); + let (a_0, a_v, b_0, b_v) = (a.start, a.dir, b.start, b.dir); + let (a_c, a_s, b_c, b_s) = ( + a_v.cos_angle(), + a_v.sin_angle(), + b_v.cos_angle(), + b_v.sin_angle(), + ); + let t_b = + (a_0.x * a_s - a_0.y * a_c + a_0.x * a_s + b_0.y * a_c) / (a_s * b_c - a_c * b_s); + Some(b.evaluate(t_b)) + } +} + +#[derive(Clone, Debug)] +pub enum Region2 { + Empty, + // single point at 0 + Singleton(Point2), + Line(Line2), + Union(Box, Box), + Full, +} + +impl Region for Region2 { + fn full() -> Self { + Region2::Full + } + + fn singleton(value: Point2) -> Self { + Region2::Singleton(value) + } + + fn contains(&self, p: &Point2) -> bool { + use Region2::*; + self.nearest(p).map_or(false, |n| n == *p) // TODO: epsilon? + + // match self { + // Empty => false, + // Singleton(n1) => *n1 == n, + // Line(_, _, _) => unimplemented!(), + // Union(r1, r2) => r1.contains(n) || r2.contains(n), + // Full => true, + // } + } + + fn nearest(&self, p: &Point2) -> Option { + use Region2::*; + match self { + Empty => None, + Full => Some(*p), + Singleton(n) => Some(*n), + Line(line) => { + // rotate angle 90 degrees + let perp_dir = line.dir * Rot2::from_cos_sin_unchecked(0., 1.); + let perp = Line2::new(*p, perp_dir, Region1::Full); + perp.intersect(line) + } + Union(r1, r2) => { + use nalgebra::distance; + match (r1.nearest(p), r2.nearest(p)) { + (None, None) => None, + (Some(n), None) | (None, Some(n)) => Some(n), + (Some(n1), Some(n2)) => Some({ + if distance(p, &n1) <= distance(p, &n2) { + n1 + } else { + n2 + } + }), + } + } + } + } +} + +impl Region2 { + pub fn intersect(&self, other: &Region2) -> Region2 { + use Region2::*; + match (self, other) { + (Empty, _) | (_, Empty) => Empty, + (Full, r @ _) | (r @ _, Full) => r.clone(), + (Singleton(n1), Singleton(n2)) => { + if n1 == n2 { + Singleton(*n1) + } else { + Empty + } + } + (Singleton(n), o @ _) | (o @ _, Singleton(n)) => { + if o.contains(n) { + Singleton(*n) + } else { + Empty + } + } + (Line(l1), Line(l2)) => match l1.intersect(l2) { + Some(p) => Singleton(p), + None => Empty, + }, + _ => unimplemented!(), + } + } +} \ No newline at end of file diff --git a/src/relation.rs b/src/relation.rs new file mode 100644 index 0000000..65e176c --- /dev/null +++ b/src/relation.rs @@ -0,0 +1,86 @@ +use crate::entity::{Point as PointEntity, PointRef}; +use crate::math::{Line2, Point2, Region, Region1, Region2, Rot2, Scalar}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ResolveResult { + Underconstrained, + Constrained, + Overconstrained, +} + +impl ResolveResult { + pub fn from_r2(r: &Region2) -> ResolveResult { + use Region2::*; + match r { + Empty => ResolveResult::Overconstrained, + Singleton(_) => ResolveResult::Constrained, + _ => ResolveResult::Constrained, + } + } +} + +pub trait Relation { + fn resolve(&self) -> ResolveResult; +} + +pub struct Coincident { + pub p1: PointRef, + pub p2: PointRef, +} + +impl Relation for Coincident { + fn resolve(&self) -> ResolveResult { + use Region2::*; + let (mut p1, mut p2) = (self.p1.borrow_mut(), self.p2.borrow_mut()); + let r = { p1.pos.constraints().intersect(p2.pos.constraints()) }; + p1.pos.reconstrain(r.clone()); + p2.pos.reconstrain(r.clone()); + ResolveResult::from_r2(&r) + } +} + +pub struct PointAngle { + pub p1: PointRef, + pub p2: PointRef, + pub angle: Rot2, +} + +impl PointAngle { + pub fn new(p1: PointRef, p2: PointRef, angle: Rot2) -> Self { + Self { p1, p2, angle } + } + + pub fn new_horizontal(p1: PointRef, p2: PointRef) -> Self { + Self::new(p1, p2, Rot2::from_cos_sin_unchecked(1., 0.)) + } + + pub fn new_vertical(p1: PointRef, p2: PointRef) -> Self { + Self::new(p1, p2, Rot2::from_cos_sin_unchecked(0., 1.)) + } +} + +impl Relation for PointAngle { + fn resolve(&self) -> ResolveResult { + use Region2::*; + let (mut p1, mut p2) = (self.p1.borrow_mut(), self.p2.borrow_mut()); + let constrain_line = |p1: &Point2, p2: &mut PointEntity| { + let line = Region2::Line(Line2::new(p1.clone(), self.angle, Region1::Full)); + let new_constraint = p2.pos.constraints().intersect(&line); + p2.pos.reconstrain(new_constraint); + ResolveResult::from_r2(p2.pos.constraints()) + }; + match (&mut p1.pos.constraints(), &mut p2.pos.constraints()) { + (Empty, _) | (_, Empty) => ResolveResult::Overconstrained, + (Singleton(p1), Singleton(p2)) => { + if p1.x == p2.x { + ResolveResult::Constrained + } else { + ResolveResult::Overconstrained + } + } + (Singleton(p), _) => constrain_line(p, &mut *p2), + (_, Singleton(p)) => constrain_line(p, &mut *p1), + _ => ResolveResult::Underconstrained, + } + } +} \ No newline at end of file