Alex Mikhalev
6 years ago
6 changed files with 335 additions and 336 deletions
@ -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<T: Clone, TRegion: Region<T>> { |
||||||
|
value: T, |
||||||
|
constraints: TRegion, |
||||||
|
} |
||||||
|
|
||||||
|
impl<T: Clone, TRegion: Region<T>> Var<T, TRegion> { |
||||||
|
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<Scalar, Region1>; |
||||||
|
type PointVar = Var<Point2, Region2>; |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct Point { |
||||||
|
pub pos: PointVar, |
||||||
|
} |
||||||
|
|
||||||
|
pub type PointRef = Rc<RefCell<Point>>; |
||||||
|
|
||||||
|
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, |
||||||
|
} |
@ -0,0 +1,180 @@ |
|||||||
|
pub type Scalar = f64; |
||||||
|
|
||||||
|
pub const EPSILON: Scalar = std::f64::EPSILON * 100.; |
||||||
|
|
||||||
|
pub type Vec2 = nalgebra::Vector2<Scalar>; |
||||||
|
pub type Point2 = nalgebra::Point2<Scalar>; |
||||||
|
|
||||||
|
pub type Rot2 = nalgebra::UnitComplex<Scalar>; |
||||||
|
|
||||||
|
pub trait Region<T> { |
||||||
|
fn full() -> Self; |
||||||
|
fn singleton(value: T) -> Self; |
||||||
|
|
||||||
|
fn nearest(&self, value: &T) -> Option<T>; |
||||||
|
fn contains(&self, value: &T) -> bool; |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Clone, Debug)] |
||||||
|
pub enum Region1 { |
||||||
|
Empty, |
||||||
|
Singleton(Scalar), |
||||||
|
Range(Scalar, Scalar), |
||||||
|
Union(Box<Region1>, Box<Region1>), |
||||||
|
Full, |
||||||
|
} |
||||||
|
|
||||||
|
impl Region<Scalar> 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<Scalar> { |
||||||
|
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<Point2> { |
||||||
|
// 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<Region2>, Box<Region2>), |
||||||
|
Full, |
||||||
|
} |
||||||
|
|
||||||
|
impl Region<Point2> 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<Point2> { |
||||||
|
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!(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue