refactor and whatnot
This commit is contained in:
parent
96606cbac5
commit
82850849e7
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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)",
|
||||
]
|
||||
|
||||
|
@ -5,4 +5,5 @@ authors = ["Alex Mikhalev <alexmikhalevalex@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
nalgebra = "0.16"
|
||||
nalgebra = "0.16"
|
||||
approx = "0.3"
|
61
src/entity.rs
Normal file
61
src/entity.rs
Normal file
@ -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,
|
||||
}
|
340
src/main.rs
340
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<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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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<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,
|
||||
}
|
||||
}
|
||||
|
||||
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};
|
||||
|
180
src/math.rs
Normal file
180
src/math.rs
Normal file
@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
86
src/relation.rs
Normal file
86
src/relation.rs
Normal file
@ -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…
x
Reference in New Issue
Block a user