# HG changeset patch # User Lin Jiang # Date 1724262840 25200 # Wed Aug 21 10:54:00 2024 -0700 # Node ID 3d1dcda1995e28400dbba7525f58cb80b9fe84e6 # Parent 8a73e1d140bf3c40daec3a3a4e0e39c8034e10a2 add intersect functionality diff --git a/examples/centroid.et b/examples/centroid.et --- a/examples/centroid.et +++ b/examples/centroid.et @@ -3,9 +3,15 @@ (setq C (point 7 3)) (setq triangle_a (triangle A B C)) (setq O (centroid triangle_a)) +(setq line_A (lineseg A O)) +(setq line_B (lineseg B O)) +(setq line_C (lineseg C O)) triangle_a O -(lineseg A O) -(lineseg B O) -(lineseg C O) +line_A +line_B +line_C +(intersect line_A (lineseg B C)) +(intersect line_B (lineseg A C)) +(intersect line_C (lineseg A B)) diff --git a/examples/intersect.et b/examples/intersect.et new file mode 100644 --- /dev/null +++ b/examples/intersect.et @@ -0,0 +1,7 @@ +(setq C (circle (point 0 0) 5)) +(setq L (lineseg (point 0 0) (point 3 0))) + +C +L +(intersect L C 0) +(intersect L C 1) diff --git a/src/lang/functions.rs b/src/lang/functions.rs --- a/src/lang/functions.rs +++ b/src/lang/functions.rs @@ -397,6 +397,119 @@ */ #[derive(Clone)] +pub struct FnIntersect; + +impl FnIntersect { + /// Case 1: Two line segments + fn from_linesegs(&self, args: &[Value]) -> Result { + // check for 2 arguments + if args.len() != 2 { + return Err("Intersect requires exactly 2 arguments".to_string()); + } + + // check for 2 line segments + let lineseg1 = match &args[0] { + Value::Lineseg(l) => l.clone(), + _ => return Err("Invalid types for line segment".to_string()), + }; + let lineseg2 = match &args[1] { + Value::Lineseg(l) => l.clone(), + _ => return Err("Invalid types for line segment".to_string()), + }; + + // check if line segments are parallel + if lineseg1.slope() == lineseg2.slope() { + return Err("Line segments are parallel".to_string()); + } + + // handle vertical line segments + if lineseg1.slope().abs() == f64::INFINITY { + let x = lineseg1.start.x; + let y = lineseg2.slope() * x + lineseg2.y_intercept(); + return Ok(Value::Point(Point { x, y })); + } else if lineseg2.slope().abs() == f64::INFINITY { + let x = lineseg2.start.x; + let y = lineseg1.slope() * x + lineseg1.y_intercept(); + return Ok(Value::Point(Point { x, y })); + } + + // otherwise, find the intersection point + let x = (lineseg2.y_intercept() - lineseg1.y_intercept()) + / (lineseg1.slope() - lineseg2.slope()); + let y = lineseg1.slope() * x + lineseg1.y_intercept(); + + Ok(Value::Point(Point { x, y })) + } + + /// Case 2: One line segment and one circle + fn from_lineseg_circle(&self, args: &[Value]) -> Result { + // check for 3 arguments + if args.len() != 3 { + return Err("Intersect requires exactly 3 arguments".to_string()); + } + + // check for 1 line segment, 1 circle, and 1 index either 0 or 1 + let lineseg = match &args[0] { + Value::Lineseg(l) => l.clone(), + _ => return Err("Invalid types for line segment".to_string()), + }; + let circle = match &args[1] { + Value::Circle(c) => c.clone(), + _ => return Err("Invalid types for circle".to_string()), + }; + let index = match &args[2] { + Value::Int(i) => *i, + _ => return Err("Invalid types for index".to_string()), + }; + if index != 0 && index != 1 { + return Err("Index must be either 0 or 1".to_string()); + } + + // calculate the intersection points without methods + let a = lineseg.start.y; + let b = lineseg.end.y; + let c = circle.center.x; + let d = circle.center.y; + let r = circle.radius; + let m = (b - a) / (lineseg.start.x - lineseg.end.x); + let n = (a * lineseg.end.y - b * lineseg.start.y) / (lineseg.end.x - lineseg.start.x); + let A = 1.0 + m * m; + let B = 2.0 * (m * n - m * d - c); + let C = c * c + d * d + n * n - 2.0 * n * d - r * r; + let D = B * B - 4.0 * A * C; + if D < 0.0 { + return Err("No intersection points".to_string()); + } + let x1 = (-B + D.sqrt()) / (2.0 * A); + let x2 = (-B - D.sqrt()) / (2.0 * A); + let y1 = m * x1 + n; + let y2 = m * x2 + n; + + // return the intersection point + if index == 0 { + Ok(Value::Point(Point { x: x1, y: y1 })) + } else { + Ok(Value::Point(Point { x: x2, y: y2 })) + } + } +} + +impl Operation for FnIntersect { + clone_impl!(FnIntersect); + fn call(&self, args: &[Value]) -> Result { + match self.from_linesegs(args) { + Ok(point) => return Ok(point), + _ => {} + } + + match self.from_lineseg_circle(args) { + Ok(point) => Ok(point), + Err(e) => Err(e), + } + } +} + +#[derive(Clone)] pub struct FnInradius; impl Operation for FnInradius { clone_impl!(FnInradius); diff --git a/src/lang/types.rs b/src/lang/types.rs --- a/src/lang/types.rs +++ b/src/lang/types.rs @@ -73,6 +73,18 @@ pub end: Point, } +impl Lineseg { + /// Return the slope of the lineseg + pub fn slope(&self) -> f64 { + (self.end.y - self.start.y) / (self.end.x - self.start.x) + } + + /// Return the y intercept of the lineseg + pub fn y_intercept(&self) -> f64 { + self.start.y - self.slope() * self.start.x + } +} + impl Element for Lineseg { /// Turn lineseg into a SVG element fn to_svg(&self) -> Vec> { @@ -95,7 +107,8 @@ fn to_svg(&self) -> Vec> { vec![Box::new(SvgCircle { center: *self, - radius: 0.01, + radius: 0.05, + fill: true, })] } } @@ -134,6 +147,7 @@ vec![Box::new(SvgCircle { center: self.center, radius: self.radius, + fill: false, })] } } diff --git a/src/lexer.rs b/src/lexer.rs --- a/src/lexer.rs +++ b/src/lexer.rs @@ -1,7 +1,6 @@ use crate::lang::functions; use crate::lang::types::{Operation, Value}; use std::fmt::{Debug, Error, Formatter}; -use std::rc::Weak; #[derive(Clone, Debug, PartialEq)] pub enum Token { @@ -131,6 +130,11 @@ }, // functions that return properties + "intersect" => Function { + name, + args: Vec::new(), + function: Box::new(functions::FnIntersect), + }, "inradius" => Function { name, args: Vec::new(), diff --git a/src/renderer.rs b/src/renderer.rs --- a/src/renderer.rs +++ b/src/renderer.rs @@ -306,14 +306,20 @@ pub struct SvgCircle { pub center: Point, pub radius: f64, + pub fill: bool, } impl Render for SvgCircle { impl_as_any!(SvgCircle); fn render(&self) -> String { + let mut fill_value = "none"; + if self.fill { + fill_value = "black"; + } + format!( - "\t\n", - self.center.x, self.center.y, self.radius + "\t\n", + self.center.x, self.center.y, self.radius, fill_value ) }