mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-19 05:35:01 +00:00
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:
parent
4587457bfa
commit
5a1c171fc3
18 changed files with 443 additions and 43 deletions
|
@ -595,6 +595,7 @@ impl Primitive for String {}
|
|||
impl Primitive for bool {}
|
||||
impl Primitive for f32 {}
|
||||
impl Primitive for f64 {}
|
||||
impl Primitive for DVec2 {}
|
||||
|
||||
fn text_attributes(attributes: &mut SvgRenderAttrs) {
|
||||
attributes.push("fill", "white");
|
||||
|
|
11
node-graph/gcore/src/vector/misc.rs
Normal file
11
node-graph/gcore/src/vector/misc.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use dyn_any::{DynAny, StaticType};
|
||||
|
||||
/// Represents different ways of calculating the centroid.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
|
||||
pub enum CentroidType {
|
||||
/// The center of mass for the area of a solid shape's interior, as if made out of an infinitely flat material.
|
||||
#[default]
|
||||
Area,
|
||||
/// The center of mass for the arc length of a curved shape's perimeter, as if made out of an infinitely thin wire.
|
||||
Length,
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
pub mod brush_stroke;
|
||||
pub mod generator_nodes;
|
||||
pub mod misc;
|
||||
|
||||
pub mod style;
|
||||
pub use style::PathStyle;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use super::misc::CentroidType;
|
||||
use super::style::{Fill, FillType, Gradient, GradientType, Stroke};
|
||||
use super::{PointId, SegmentId, StrokeId, VectorData};
|
||||
use crate::renderer::GraphicElementRendered;
|
||||
|
@ -530,6 +531,75 @@ async fn morph<SourceFuture: Future<Output = VectorData>, TargetFuture: Future<O
|
|||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AreaNode<VectorData> {
|
||||
vector_data: VectorData,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(AreaNode)]
|
||||
async fn area_node<Fut: Future<Output = VectorData>>(empty: (), vector_data: impl Node<Footprint, Output = Fut>) -> f64 {
|
||||
let vector_data = self.vector_data.eval(Footprint::default()).await;
|
||||
|
||||
let mut area = 0.;
|
||||
let scale = vector_data.transform.decompose_scale();
|
||||
for subpath in vector_data.stroke_bezier_paths() {
|
||||
area += subpath.area(Some(1e-3), Some(1e-3));
|
||||
}
|
||||
area * scale[0] * scale[1]
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CentroidNode<VectorData, CentroidType> {
|
||||
vector_data: VectorData,
|
||||
centroid_type: CentroidType,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(CentroidNode)]
|
||||
async fn centroid_node<Fut: Future<Output = VectorData>>(empty: (), vector_data: impl Node<Footprint, Output = Fut>, centroid_type: CentroidType) -> DVec2 {
|
||||
let vector_data = self.vector_data.eval(Footprint::default()).await;
|
||||
|
||||
if centroid_type == CentroidType::Area {
|
||||
let mut area = 0.;
|
||||
let mut centroid = DVec2::ZERO;
|
||||
for subpath in vector_data.stroke_bezier_paths() {
|
||||
if let Some((subpath_centroid, subpath_area)) = subpath.area_centroid_and_area(Some(1e-3), Some(1e-3)) {
|
||||
if subpath_area == 0. {
|
||||
continue;
|
||||
}
|
||||
area += subpath_area;
|
||||
centroid += subpath_area * subpath_centroid;
|
||||
}
|
||||
}
|
||||
|
||||
if area != 0. {
|
||||
centroid /= area;
|
||||
return vector_data.transform().transform_point2(centroid);
|
||||
}
|
||||
}
|
||||
|
||||
let mut length = 0.;
|
||||
let mut centroid = DVec2::ZERO;
|
||||
for subpath in vector_data.stroke_bezier_paths() {
|
||||
if let Some((subpath_centroid, subpath_length)) = subpath.length_centroid_and_length(None, true) {
|
||||
length += subpath_length;
|
||||
centroid += subpath_length * subpath_centroid;
|
||||
}
|
||||
}
|
||||
|
||||
if length != 0. {
|
||||
centroid /= length;
|
||||
return vector_data.transform().transform_point2(centroid);
|
||||
}
|
||||
|
||||
let positions = vector_data.point_domain.positions();
|
||||
if !positions.is_empty() {
|
||||
let centroid = positions.iter().sum::<DVec2>() / (positions.len() as f64);
|
||||
return vector_data.transform().transform_point2(centroid);
|
||||
}
|
||||
|
||||
DVec2::ZERO
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue