M examples/centroid.et +9 -3
@@ 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))
A => examples/intersect.et +7 -0
@@ 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)
M src/lang/functions.rs +113 -0
@@ 397,6 397,119 @@ Functions that return properties
*/
#[derive(Clone)]
+pub struct FnIntersect;
+
+impl FnIntersect {
+ /// Case 1: Two line segments
+ fn from_linesegs(&self, args: &[Value]) -> Result<Value, String> {
+ // 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<Value, String> {
+ // 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<Value, String> {
+ 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);
M src/lang/types.rs +15 -1
@@ 73,6 73,18 @@ pub struct Lineseg {
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<Box<dyn Render>> {
@@ 95,7 107,8 @@ impl Element for Point {
fn to_svg(&self) -> Vec<Box<dyn Render>> {
vec![Box::new(SvgCircle {
center: *self,
- radius: 0.01,
+ radius: 0.05,
+ fill: true,
})]
}
}
@@ 134,6 147,7 @@ impl Element for Circle {
vec![Box::new(SvgCircle {
center: self.center,
radius: self.radius,
+ fill: false,
})]
}
}
M src/lexer.rs +5 -1
@@ 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 @@ fn match_fn(name: String) -> Function {
},
// functions that return properties
+ "intersect" => Function {
+ name,
+ args: Vec::new(),
+ function: Box::new(functions::FnIntersect),
+ },
"inradius" => Function {
name,
args: Vec::new(),
M src/renderer.rs +8 -2
@@ 306,14 306,20 @@ impl Render for SvgLine {
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<circle cx=\"{}\" cy=\"{}\" r=\"{}\" fill=\"none\" stroke=\"black\" stroke-width=\"0.02\"/>\n",
- self.center.x, self.center.y, self.radius
+ "\t<circle cx=\"{}\" cy=\"{}\" r=\"{}\" fill=\"{}\" stroke=\"black\" stroke-width=\"0.02\"/>\n",
+ self.center.x, self.center.y, self.radius, fill_value
)
}