add incenter and incircle functionality
5 files changed, 154 insertions(+), 35 deletions(-)

M examples/iangle.et
A => examples/incenter.et
M src/lang/functions.rs
M src/lang/types.rs
M src/lexer.rs
M examples/iangle.et +1 -1
@@ 1,4 1,4 @@ 
 (setq A (point 5 5))
 (setq C (circle A 5))
 C
-(triangle C)
+(iangle C 90)

          
A => examples/incenter.et +8 -0
@@ 0,0 1,8 @@ 
+(setq A (point 3 3))
+(setq B (point 3 6))
+(setq C (point 7 3))
+(setq triangle_a (triangle A B C))
+(setq I (incenter triangle_a))
+
+triangle_a
+(circle I (inradius triangle_a))

          
M src/lang/functions.rs +74 -28
@@ 217,6 217,80 @@ impl Operation for FnAngle {
     }
 }
 
+#[derive(Clone)]
+pub struct FnIncenter;
+impl Operation for FnIncenter {
+    clone_impl!(FnIncenter);
+    fn call(&self, args: &[Value]) -> Result<Value, String> {
+        // check for 1 argument
+        if args.len() < 1 {
+            return Err("Incenter requires exactly 1 argument".to_string());
+        }
+
+        // check for 1 triangle
+        let triangle = match &args[0] {
+            Value::Triangle(t) => t.clone(),
+            _ => return Err("Invalid types for triangle".to_string()),
+        };
+
+        // try getting the incenter
+        return Ok(Value::Point(triangle.incenter()));
+    }
+}
+
+#[derive(Clone)]
+pub struct FnPoint;
+impl Operation for FnPoint {
+    clone_impl!(FnPoint);
+    fn call(&self, args: &[Value]) -> Result<Value, String> {
+        // check for 2 arguments
+        if args.len() < 2 {
+            return Err("Point requires exactly 2 arguments".to_string());
+        }
+
+        // try forcing the arguments into floats
+        let mut floats = Vec::new();
+        for arg in args {
+            match arg {
+                Value::Int(i) => floats.push(*i as f64),
+                Value::Float(f) => floats.push(*f),
+                _ => return Err("Invalid types for point".to_string()),
+            }
+        }
+
+        // return the point
+        Ok(Value::Point(Point {
+            x: floats[0],
+            y: floats[1],
+        }))
+    }
+}
+
+/*
+Functions that return properties
+*/
+
+#[derive(Clone)]
+pub struct FnInradius;
+impl Operation for FnInradius {
+    clone_impl!(FnInradius);
+    fn call(&self, args: &[Value]) -> Result<Value, String> {
+        // check for 1 argument
+        if args.len() < 1 {
+            return Err("Inradius requires exactly 1 argument".to_string());
+        }
+
+        // check for 1 triangle
+        let triangle = match &args[0] {
+            Value::Triangle(t) => t.clone(),
+            _ => return Err("Invalid types for triangle".to_string()),
+        };
+
+        // try getting the inradius
+        return Ok(Value::Float(triangle.inradius()));
+    }
+}
+
 /*
 Basic geometric shapes
 */

          
@@ 385,31 459,3 @@ impl Operation for FnTriangle {
         }
     }
 }
-
-#[derive(Clone)]
-pub struct FnPoint;
-impl Operation for FnPoint {
-    clone_impl!(FnPoint);
-    fn call(&self, args: &[Value]) -> Result<Value, String> {
-        // check for 2 arguments
-        if args.len() < 2 {
-            return Err("Point requires exactly 2 arguments".to_string());
-        }
-
-        // try forcing the arguments into floats
-        let mut floats = Vec::new();
-        for arg in args {
-            match arg {
-                Value::Int(i) => floats.push(*i as f64),
-                Value::Float(f) => floats.push(*f),
-                _ => return Err("Invalid types for point".to_string()),
-            }
-        }
-
-        // return the point
-        Ok(Value::Point(Point {
-            x: floats[0],
-            y: floats[1],
-        }))
-    }
-}

          
M src/lang/types.rs +54 -1
@@ 76,7 76,7 @@ impl Element for Point {
     fn to_svg(&self) -> Vec<Box<dyn Render>> {
         vec![Box::new(SvgCircle {
             center: *self,
-            radius: 2.0,
+            radius: 0.01,
         })]
     }
 }

          
@@ 221,4 221,57 @@ impl Triangle {
         // otherwise, return the triangle
         Ok(Self { a, b, c })
     }
+
+    /// Return the inradius of the triangle
+    pub fn inradius(&self) -> f64 {
+        // calculate the side lengths
+        let a = (self.b.x - self.c.x).hypot(self.b.y - self.c.y);
+        let b = (self.a.x - self.c.x).hypot(self.a.y - self.c.y);
+        let c = (self.a.x - self.b.x).hypot(self.a.y - self.b.y);
+
+        // calculate the semiperimeter
+        let s = (a + b + c) / 2.0;
+
+        // calculate the inradius
+        (s * (s - a) * (s - b) * (s - c)).sqrt() / s
+    }
+
+    /// Return the incenter of the triangle
+    pub fn incenter(&self) -> Point {
+        // calculate the side lengths
+        let a = (self.b.x - self.c.x).hypot(self.b.y - self.c.y);
+        let b = (self.a.x - self.c.x).hypot(self.a.y - self.c.y);
+        let c = (self.a.x - self.b.x).hypot(self.a.y - self.b.y);
+
+        // calculate the incenter
+        let x = (a * self.a.x + b * self.b.x + c * self.c.x) / (a + b + c);
+        let y = (a * self.a.y + b * self.b.y + c * self.c.y) / (a + b + c);
+
+        Point { x, y }
+    }
+
+    /// Return the orthocenter of the triangle
+    pub fn orthocenter(&self) -> Point {
+        // calculate the slopes of the sides
+        let m1 = (self.b.y - self.a.y) / (self.b.x - self.a.x);
+        let m2 = (self.c.y - self.b.y) / (self.c.x - self.b.x);
+        let m3 = (self.a.y - self.c.y) / (self.a.x - self.c.x);
+
+        // calculate the orthocenter
+        let x = (m1 * m2 * (self.a.y - self.c.y)
+            + m2 * m3 * (self.a.y - self.b.y)
+            + m3 * m1 * (self.b.y - self.c.y))
+            / (m1 * m2 + m2 * m3 + m3 * m1);
+        let y = self.a.y - m1 * (x - self.a.x);
+
+        Point { x, y }
+    }
+
+    /// Return the centroid of the triangle
+    pub fn centroid(&self) -> Point {
+        Point {
+            x: (self.a.x + self.b.x + self.c.x) / 3.0,
+            y: (self.a.y + self.b.y + self.c.y) / 3.0,
+        }
+    }
 }

          
M src/lexer.rs +17 -5
@@ 93,6 93,23 @@ fn match_fn(name: String) -> Function {
             args: Vec::new(),
             function: Box::new(functions::FnInscribedAngle),
         },
+        "point" => Function {
+            name,
+            args: Vec::new(),
+            function: Box::new(functions::FnPoint),
+        },
+        "incenter" => Function {
+            name,
+            args: Vec::new(),
+            function: Box::new(functions::FnIncenter),
+        },
+
+        // functions that return properties
+        "inradius" => Function {
+            name,
+            args: Vec::new(),
+            function: Box::new(functions::FnInradius),
+        },
 
         // basic geometric functions
         "circle" => Function {

          
@@ 100,11 117,6 @@ fn match_fn(name: String) -> Function {
             args: Vec::new(),
             function: Box::new(functions::FnCircle),
         },
-        "point" => Function {
-            name,
-            args: Vec::new(),
-            function: Box::new(functions::FnPoint),
-        },
         "triangle" => Function {
             name,
             args: Vec::new(),