From 3be8db1fdc3c96f2bf7b11030367740f4ef12a28 Mon Sep 17 00:00:00 2001 From: Emi Simpson Date: Sat, 13 Nov 2021 15:18:08 -0500 Subject: [PATCH] Basic Delaunay triangulation --- .editorconfig | 8 + .gitignore | 1 + Cargo.lock | 431 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 10 ++ src/main.rs | 283 +++++++++++++++++++++++++++++++++ src/util.rs | 0 6 files changed, 733 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/util.rs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..abeebc1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*.rs] +indent_style = tab +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = false +max_line_length = 90 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c89b266 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,431 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + +[[package]] +name = "audir-sles" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea47348666a8edb7ad80cbee3940eb2bccf70df0e6ce09009abe1a836cb779f5" + +[[package]] +name = "audrey" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b92a84e89497e3cd25d3672cd5d1c288abaac02c18ff21283f17d118b889b8" +dependencies = [ + "dasp_frame", + "dasp_sample", + "hound", + "lewton", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "bytemuck" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "dasp_frame" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a3937f5fe2135702897535c8d4a5553f8b116f76c1529088797f2eee7c5cd6" +dependencies = [ + "dasp_sample", +] + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "fontdue" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75712fff1702bac51b7eaa5a5ca9f9853b8055ef5906088a32f4fe196595a1d" +dependencies = [ + "hashbrown", + "ttf-parser", +] + +[[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" + +[[package]] +name = "glow" +version = "0.1.0" +dependencies = [ + "macroquad", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] + +[[package]] +name = "hound" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-iter", + "num-rational", + "num-traits", + "png", +] + +[[package]] +name = "lewton" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d542c1a317036c45c2aa1cf10cc9d403ca91eb2d333ef1a4917e5cb10628bd0" +dependencies = [ + "byteorder", + "ogg", + "smallvec", +] + +[[package]] +name = "libc" +version = "0.2.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" + +[[package]] +name = "macroquad" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54a31d3996831b27b429f8e9d7b948aa589d3571305eba39f57b4600bd2cd01" +dependencies = [ + "bumpalo", + "fontdue", + "glam", + "image", + "macroquad_macro", + "miniquad", + "quad-rand", + "quad-snd", +] + +[[package]] +name = "macroquad_macro" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5cecfede1e530599c8686f7f2d609489101d3d63741a6dc423afc997ce3fcc8" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "miniquad" +version = "0.3.0-alpha.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6793a3ef846953fc7c01302093abf8749be22742c63db05c66ef0a2c889a7fbb" +dependencies = [ + "sapp-android", + "sapp-darwin", + "sapp-dummy", + "sapp-ios", + "sapp-linux", + "sapp-wasm", + "sapp-windows", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "ndk-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ogg" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e571c3517af9e1729d4c63571a27edd660ade0667973bfc74a67c660c2b651" +dependencies = [ + "byteorder", +] + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide", +] + +[[package]] +name = "quad-alsa-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66c2f04a6946293477973d85adc251d502da51c57b08cd9c997f0cfd8dcd4b5" +dependencies = [ + "libc", +] + +[[package]] +name = "quad-rand" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658fa1faf7a4cc5f057c9ee5ef560f717ad9d8dc66d975267f709624d6e1ab88" + +[[package]] +name = "quad-snd" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e0b4259cfd6a317a46df7b7cb4c09a08ba150642e6f6fb7df5a6b3450a0a29" +dependencies = [ + "audir-sles", + "audrey", + "libc", + "quad-alsa-sys", + "winapi", +] + +[[package]] +name = "sapp-android" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a4a81f462ba2783213978528560aa138adf2f94da1ac940c1b5c854c03e1724" +dependencies = [ + "libc", + "ndk-sys", +] + +[[package]] +name = "sapp-darwin" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0310e2445f307468aa13f1cde94d6fba6b8fd329afbb642dedbe3faf1a145f31" +dependencies = [ + "cc", +] + +[[package]] +name = "sapp-dummy" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66f1ad26a5b6c682b9ca27c66db9aa91002b8d98a82ac7101ded57285215a478" +dependencies = [ + "libc", +] + +[[package]] +name = "sapp-ios" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "081e6e5261c9ac2e938979b6a854a53b439f065fc3c897205ce7e69d3028b4a9" +dependencies = [ + "cc", +] + +[[package]] +name = "sapp-linux" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbdb2f8011955c62544d9e626a58333e788810d00bd7411d52b81611b92af142" +dependencies = [ + "libc", +] + +[[package]] +name = "sapp-wasm" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e859e8645a3bcb85aecd40bab883438e4105f21b21bccbeac2348760f508bb" + +[[package]] +name = "sapp-windows" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8faec983cb54ce5e9529815fc0aae6c36bab9fba9cd0ae5590dfa17bc0719fa" +dependencies = [ + "winapi", +] + +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "ttf-parser" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..91862de --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "glow" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +macroquad = "0.3" +#itertools = "0.10.1" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..417fb27 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,283 @@ +use std::collections::HashSet; +use core::f32::consts::PI; +use macroquad::prelude::draw_circle_lines; +use macroquad::prelude::mouse_position; +use macroquad::prelude::is_mouse_button_pressed; +use macroquad::prelude::Vec2; +use core::ops::DerefMut; +use core::ops::Deref; +use macroquad::prelude::draw_line; +use core::hash::Hasher; +use core::hash::Hash; +use macroquad::prelude::screen_height; +use macroquad::prelude::next_frame; +use macroquad::prelude::screen_width; +use macroquad::prelude::draw_circle; +use macroquad::prelude::clear_background; +use macroquad::prelude::Color; +use macroquad::input::MouseButton; +use std::sync::Arc; + +const BG: Color = Color::new(0.16863, 0.23922, 0.21176, 1.0); +const FG: Color = Color::new(0.91569, 0.71765, 0.00000, 1.0); +const DIM_FG: Color = Color::new(0.29412, 0.27451, 0.13333, 1.0); +const HIL_FG: Color = Color::new(0.25806, 0.61290, 1.09032, 1.0); + +#[macroquad::main("BasicShapes")] +async fn main() { + let mut dd = DelaunayDemo::default(); + loop { + if is_mouse_button_pressed(MouseButton::Left) { + dd.click(mouse_position().into()) + } + dd.draw(); + next_frame().await; + } +} + +pub struct DelaunayDemo { + pub nodes: Vec>, + pub triangles: Vec, + pub edges: Vec, +} + +impl Default for DelaunayDemo { + fn default() -> Self { + let nodes: Vec> = vec![ + Arc::new((0.0, 0.0).into()), + Arc::new((0.0, screen_height()).into()), + Arc::new((screen_width(), 0.0).into()), + Arc::new((screen_width(), screen_height()).into()), + ]; + let edges = vec![ + Edge(nodes[2].clone(), nodes[0].clone()), + Edge(nodes[0].clone(), nodes[1].clone()), + Edge(nodes[1].clone(), nodes[2].clone()), + Edge(nodes[2].clone(), nodes[3].clone()), + Edge(nodes[3].clone(), nodes[1].clone()), + ]; + let triangles = vec![ + Triangle::new( + [edges[0].clone(), edges[1].clone(), edges[2].clone()], + ), + Triangle::new( + [edges[2].clone(), edges[3].clone(), edges[4].clone()], + ), + ]; + DelaunayDemo { + nodes, edges, triangles, + } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Node(Vec2); + +impl Deref for Node { + type Target = Vec2; + + fn deref(&self) -> &Vec2 { + &self.0 + } +} + +impl DerefMut for Node { + fn deref_mut(&mut self) -> &mut Vec2 { + &mut self.0 + } +} + +impl From<(f32, f32)> for Node { + fn from((x, y): (f32, f32)) -> Node { + Node(Vec2::new(x, y)) + } +} + +#[derive(Clone, Debug)] +pub struct Edge(Arc, Arc); + +impl PartialEq for Edge { + fn eq(&self, other: &Edge) -> bool { + let mut our_ptrs = [ + Arc::as_ptr(&self.0), + Arc::as_ptr(&self.1), + ]; + let mut other_ptrs = [ + Arc::as_ptr(&other.0), + Arc::as_ptr(&other.1), + ]; + our_ptrs.sort_unstable(); + other_ptrs.sort_unstable(); + + our_ptrs[0] == other_ptrs[0] && our_ptrs[1] == other_ptrs[1] + } +} + +impl Eq for Edge {} + +impl Hash for Edge { + fn hash(&self, state: &mut H) { + let mut ptrs = [ + Arc::as_ptr(&self.0), + Arc::as_ptr(&self.1), + ]; + ptrs.sort_unstable(); + ptrs.hash(state); + } +} + +pub struct Triangle { + nodes: [Arc; 3], + edges: [Edge; 3], + circumcenter: Vec2, + radius: f32, +} + +impl Triangle { + pub fn new(edges: [Edge; 3]) -> Triangle { + let nodes = [ + edges[0].0.clone(), + edges[0].1.clone(), + if Arc::ptr_eq(&edges[1].0, &edges[0].0) || Arc::ptr_eq(&edges[1].0, &edges[0].1) { + edges[1].1.clone() + } else { + edges[1].0.clone() + } + ]; + let circumcenter = Self::circumcenter(**nodes[0], **nodes[1], **nodes[2]); + let radius = circumcenter.distance(**nodes[0]); + Triangle { + nodes, + edges, + circumcenter, + radius, + } + } + +fn circumcenter(a: Vec2, b: Vec2, c: Vec2) -> Vec2 { + let len_a = b.distance(c); + let len_b = c.distance(a); + let len_c = a.distance(b); + + let cos_a = (len_b.powf(2.0) + len_c.powf(2.0) - len_a.powf(2.0)) / (2.0 * len_b * len_c ); + let cos_b = (len_c.powf(2.0) + len_a.powf(2.0) - len_b.powf(2.0)) / (2.0 * len_c * len_a ); + + let ang_a = f32::acos(cos_a); + let ang_b = f32::acos(cos_b); + let ang_c = PI - ang_a - ang_b; + + let sin_2a = f32::sin(2.0 * ang_a); + let sin_2b = f32::sin(2.0 * ang_b); + let sin_2c = f32::sin(2.0 * ang_c); + + Vec2::new( + (a.x * sin_2a + b.x * sin_2b + c.x * sin_2c) / (sin_2a + sin_2b + sin_2c), + (a.y * sin_2a + b.y * sin_2b + c.y * sin_2c) / (sin_2a + sin_2b + sin_2c), + ) +} + + pub fn invalidated_by_point(&self, point: Vec2) -> bool { + self.circumcenter.distance(point) < self.radius + } +} + +impl DelaunayDemo { + fn draw(&self) { + clear_background(BG); + let mut danger_lines = Vec::with_capacity(3); + for triangle in &self.triangles { + let c = triangle.circumcenter; + let color = if c.distance(mouse_position().into()) < triangle.radius { + danger_lines.extend(&triangle.edges); + DIM_FG //HIL_FG + } else { + DIM_FG + }; + draw_circle_lines(c.x, c.y, triangle.radius, 1.0, color); + } + for edge in &self.edges { + let color = if danger_lines.contains(&edge) { + FG //HIL_FG + } else { + FG + }; + draw_line(edge.0.x, edge.0.y, edge.1.x, edge.1.y, 3.0, color); + } + for node in &self.nodes { + draw_circle(node.x, node.y, 10.0, FG); + } + } + + fn click(&mut self, pos: Vec2) { + self.add_point(Node(pos)); + } + + fn add_point(&mut self, node: Node) { + + // Remove invalid triangles and coalesce their edges + let mut bad_triangle_sides: Vec<_> = Vec::with_capacity(self.triangles.len() * 3); + let mut i = 0; + while i < self.triangles.len() { + let triangle = &self.triangles[i]; + if triangle.invalidated_by_point(*node) { + bad_triangle_sides.extend( + self.triangles.remove(i).edges + ); + } else { + i = i + 1; + } + } + + // For every edge + + // If it has a duplicate later in the list + + // Remove the edge from existance + + // Otherwise + + // Form a new triangle with that edge and this point + + let node = Arc::new(node); + let mut new_edges = HashSet::with_capacity(bad_triangle_sides.len()); + i = 0; + while i < bad_triangle_sides.len() { + let edge = &bad_triangle_sides[i]; + let other_edge_i = bad_triangle_sides[(i + 1)..].iter() + .position(|other_edge| edge == other_edge); + + if let Some(other_edge_i) = other_edge_i { + bad_triangle_sides.remove(other_edge_i + i + 1); + i = i + 1; + } else { + let edge = bad_triangle_sides.remove(i); + let new_edge_a = Edge(edge.0.clone(), node.clone()); + let new_edge_b = Edge(edge.1.clone(), node.clone()); + self.triangles.push(Triangle::new([ + edge, + new_edge_a.clone(), + new_edge_b.clone(), + ])); + new_edges.extend([new_edge_a, new_edge_b]); + } + } + self.edges.retain(|e| !bad_triangle_sides.iter().any(|bad_e| e == bad_e)); + self.edges.extend(new_edges); + self.nodes.push(node); + } + + /*fn move_point(&mut self, node: Arc) { + + // Update position + + // Check for broken triangles + + // Remove triange + + // Remove triangle consisting of common points + self + + // Form triangle consisting of common point A + self + opposite + + // Form triangle consisting of common point B + self + opposite + }*/ +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..e69de29