mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08: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:
@@ -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::*;
|
||||
|
||||
@@ -77,6 +77,7 @@ pub enum TaggedValue {
|
||||
Footprint(graphene_core::transform::Footprint),
|
||||
RenderOutput(RenderOutput),
|
||||
Palette(Vec<Color>),
|
||||
CentroidType(graphene_core::vector::misc::CentroidType),
|
||||
}
|
||||
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
@@ -156,6 +157,7 @@ impl Hash for TaggedValue {
|
||||
Self::Footprint(x) => x.hash(state),
|
||||
Self::RenderOutput(x) => x.hash(state),
|
||||
Self::Palette(x) => x.hash(state),
|
||||
Self::CentroidType(x) => x.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,6 +224,7 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::Footprint(x) => Box::new(x),
|
||||
TaggedValue::RenderOutput(x) => Box::new(x),
|
||||
TaggedValue::Palette(x) => Box::new(x),
|
||||
TaggedValue::CentroidType(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +302,7 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::Footprint(_) => concrete!(graphene_core::transform::Footprint),
|
||||
TaggedValue::RenderOutput(_) => concrete!(RenderOutput),
|
||||
TaggedValue::Palette(_) => concrete!(Vec<Color>),
|
||||
TaggedValue::CentroidType(_) => concrete!(graphene_core::vector::misc::CentroidType),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +370,7 @@ impl<'a> TaggedValue {
|
||||
}
|
||||
x if x == TypeId::of::<graphene_core::transform::Footprint>() => Ok(TaggedValue::Footprint(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<Vec<Color>>() => Ok(TaggedValue::Palette(*downcast(input).unwrap())),
|
||||
x if x == TypeId::of::<graphene_core::vector::misc::CentroidType>() => Ok(TaggedValue::CentroidType(*downcast(input).unwrap())),
|
||||
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,12 +578,18 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [String]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [Option<Color>]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [Vec<Color>]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [DVec2]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => VectorData]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ImageFrame<Color>]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Option<Color>]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec<Color>]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Artboard]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => f32]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => f64]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => bool]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => String]),
|
||||
async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => DVec2]),
|
||||
async_node!(
|
||||
graphene_core::memo::EndLetNode<_, _>,
|
||||
input: WasmEditorApi,
|
||||
@@ -774,6 +780,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
register_node!(graphene_core::vector::PoissonDiskPoints<_>, input: VectorData, params: [f64]),
|
||||
register_node!(graphene_core::vector::LengthsOfSegmentsOfSubpaths, input: VectorData, params: []),
|
||||
register_node!(graphene_core::vector::SplinesFromPointsNode, input: VectorData, params: []),
|
||||
async_node!(graphene_core::vector::AreaNode<_>, input: (), output: f64, fn_params: [Footprint => VectorData]),
|
||||
async_node!(graphene_core::vector::CentroidNode<_, _>, input: (), output: DVec2, fn_params: [Footprint => VectorData, () => graphene_core::vector::misc::CentroidType]),
|
||||
async_node!(graphene_core::vector::MorphNode<_, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, Footprint => VectorData, () => u32, () => f64]),
|
||||
register_node!(graphene_core::vector::generator_nodes::CircleGenerator<_>, input: (), params: [f64]),
|
||||
register_node!(graphene_core::vector::generator_nodes::EllipseGenerator<_, _>, input: (), params: [f64, f64]),
|
||||
|
||||
Reference in New Issue
Block a user