Add Area and Centroid nodes (#1749)

* initial attempt for area node

* allow node preview for more types

* make AreaNode sync and add CentroidNode

* cargo fmt

* preview of DVec2

* make the nodes async again

* use segment domain instead of region domain

* modify the check for linearity

* create a limit for area in centroid calculation

* cargo fmt

* reverse unnecessary changes

* add threshold to area calculation too.

* handle zero area edge case

* add todo comment

* implement 1D centroid and use it as fallback

* formatting floats to skip last zero

* add Centroid Type radio button to Centroid Node

* rename docs to use area and perimeter centroid

* add web demos for perimeter centroid

* add tests for perimeter centroid

* add fallback to use average of points

* Fix for broken area

* missing fixes

* Code review and rename Perimeter Centroid to Length Centroid

* Use dummy footprint in Area and Centroid nodes

* add doc and todo to clarify when `is_linear` fails

* use epsilon instead of zero

---------

Co-authored-by: 0hypercube <0hypercube@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Elbert Ronnie
2024-05-22 13:11:11 -07:00
committed by GitHub
co-authored by 0hypercube Keavon Chambers Dennis Kobert
parent 4587457bfa
commit 5a1c171fc3
18 changed files with 443 additions and 43 deletions
@@ -76,6 +76,18 @@ impl WasmSubpath {
subpath_svg
}
fn to_filled_svg(&self) -> String {
let mut subpath_svg = String::new();
self.0.to_svg(
&mut subpath_svg,
CURVE_FILLED_ATTRIBUTES.to_string(),
ANCHOR_ATTRIBUTES.to_string(),
HANDLE_ATTRIBUTES.to_string(),
HANDLE_LINE_ATTRIBUTES.to_string(),
);
subpath_svg
}
pub fn insert(&self, t: f64, t_variant: String) -> String {
let mut subpath = self.0.clone();
let t = parse_t_variant(&t_variant, t);
@@ -92,14 +104,35 @@ impl WasmSubpath {
wrap_svg_tag(format!("{}{}", self.to_default_svg(), length_text))
}
pub fn area(&self, error: f64, minimum_separation: f64) -> String {
let area_text = draw_text(format!("Area: {}", self.0.area(Some(error), Some(minimum_separation))), 5., 193., BLACK);
wrap_svg_tag(format!("{}{}", self.to_default_svg(), area_text))
pub fn length_centroid(&self) -> String {
let centroid = self.0.length_centroid(None, true).unwrap();
let point_text = draw_circle(centroid, 4., RED, 1.5, WHITE);
wrap_svg_tag(format!("{}{}", self.to_default_svg(), point_text))
}
pub fn centroid(&self, error: f64, minimum_separation: f64) -> String {
let point_text = draw_circle(self.0.centroid(Some(error), Some(minimum_separation)).unwrap(), 4., RED, 1.5, WHITE);
wrap_svg_tag(format!("{}{}", self.to_default_svg(), point_text))
pub fn area(&self, error: f64, minimum_separation: f64) -> String {
let area_text = draw_text(format!("Area: {}", self.0.area(Some(error), Some(minimum_separation))), 5., 193., BLACK);
wrap_svg_tag(format!("{}{}", self.to_filled_svg(), area_text))
}
pub fn area_centroid(&self, error: f64, minimum_separation: f64) -> String {
let point_text = draw_circle(self.0.area_centroid(Some(error), Some(minimum_separation), None).unwrap(), 4., RED, 1.5, WHITE);
wrap_svg_tag(format!("{}{}", self.to_filled_svg(), point_text))
}
pub fn poisson_disk_points(&self, separation_disk_diameter: f64) -> String {
let r = separation_disk_diameter / 2.;
let subpath_svg = self.to_default_svg();
let points = self.0.poisson_disk_points(separation_disk_diameter, Math::random);
let points_style = format!("<style class=\"poisson\">style.poisson ~ circle {{ fill: {RED}; opacity: 0.25; }}</style>");
let content = points
.iter()
.map(|point| format!("<circle cx=\"{}\" cy=\"{}\" r=\"{r}\" />", point.x, point.y))
.collect::<Vec<_>>()
.join("");
wrap_svg_tag(format!("{subpath_svg}{points_style}{content}"))
}
pub fn evaluate(&self, t: f64, t_variant: String) -> String {
@@ -193,21 +226,6 @@ impl WasmSubpath {
}
}
pub fn poisson_disk_points(&self, separation_disk_diameter: f64) -> String {
let r = separation_disk_diameter / 2.;
let subpath_svg = self.to_default_svg();
let points = self.0.poisson_disk_points(separation_disk_diameter, Math::random);
let points_style = format!("<style class=\"poisson\">style.poisson ~ circle {{ fill: {RED}; opacity: 0.25; }}</style>");
let content = points
.iter()
.map(|point| format!("<circle cx=\"{}\" cy=\"{}\" r=\"{r}\" />", point.x, point.y))
.collect::<Vec<_>>()
.join("");
wrap_svg_tag(format!("{subpath_svg}{points_style}{content}"))
}
pub fn inflections(&self) -> String {
let inflections: Vec<f64> = self.0.inflections();