mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 05:18:19 +00:00
Replace 'Generate Handles' and 'Remove Handles' nodes with 'Auto-Tangents' node; rename vector2 data type to coordinate
This commit is contained in:
parent
f72263f4f8
commit
96a0dada92
7 changed files with 171 additions and 101 deletions
2
demo-artwork/parametric-dunescape.graphite
generated
2
demo-artwork/parametric-dunescape.graphite
generated
File diff suppressed because one or more lines are too long
|
@ -951,7 +951,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
properties: None,
|
properties: None,
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Split Vector2",
|
identifier: "Split Coordinate",
|
||||||
category: "Math: Vector",
|
category: "Math: Vector",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
|
@ -982,7 +982,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
input_properties: vec![("Vector2", "TODO").into()],
|
input_properties: vec![("Coordinate", "TODO").into()],
|
||||||
output_names: vec!["X".to_string(), "Y".to_string()],
|
output_names: vec!["X".to_string(), "Y".to_string()],
|
||||||
has_primary_output: false,
|
has_primary_output: false,
|
||||||
network_metadata: Some(NodeNetworkMetadata {
|
network_metadata: Some(NodeNetworkMetadata {
|
||||||
|
@ -2913,7 +2913,7 @@ fn static_input_properties() -> InputProperties {
|
||||||
.input_metadata(&node_id, index, "min", context.selection_network_path)
|
.input_metadata(&node_id, index, "min", context.selection_network_path)
|
||||||
.and_then(|value| value.as_f64());
|
.and_then(|value| value.as_f64());
|
||||||
|
|
||||||
Ok(vec![node_properties::vector2_widget(
|
Ok(vec![node_properties::coordinate_widget(
|
||||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
@ -3190,7 +3190,7 @@ fn static_input_properties() -> InputProperties {
|
||||||
Box::new(|node_id, index, context| {
|
Box::new(|node_id, index, context| {
|
||||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||||
Ok(vec![LayoutGroup::Row {
|
Ok(vec![LayoutGroup::Row {
|
||||||
widgets: node_properties::array_of_vector2_widget(
|
widgets: node_properties::array_of_coordinates_widget(
|
||||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||||
TextInput::default().centered(true),
|
TextInput::default().centered(true),
|
||||||
),
|
),
|
||||||
|
|
|
@ -162,8 +162,8 @@ pub(crate) fn property_from_type(
|
||||||
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
|
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
|
||||||
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
|
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
|
||||||
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||||
Some("Resolution") => vector2_widget(default_info, "W", "H", " px", Some(64.)),
|
Some("Resolution") => coordinate_widget(default_info, "W", "H", " px", Some(64.)),
|
||||||
Some("PixelSize") => vector2_widget(default_info, "X", "Y", " px", None),
|
Some("PixelSize") => coordinate_widget(default_info, "X", "Y", " px", None),
|
||||||
|
|
||||||
// For all other types, use TypeId-based matching
|
// For all other types, use TypeId-based matching
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -177,14 +177,14 @@ pub(crate) fn property_from_type(
|
||||||
Some(x) if x == TypeId::of::<u64>() => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
Some(x) if x == TypeId::of::<u64>() => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||||
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
|
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
|
||||||
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
|
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
|
||||||
Some(x) if x == TypeId::of::<DVec2>() => vector2_widget(default_info, "X", "Y", "", None),
|
Some(x) if x == TypeId::of::<DVec2>() => coordinate_widget(default_info, "X", "Y", "", None),
|
||||||
Some(x) if x == TypeId::of::<UVec2>() => vector2_widget(default_info, "X", "Y", "", Some(0.)),
|
Some(x) if x == TypeId::of::<UVec2>() => coordinate_widget(default_info, "X", "Y", "", Some(0.)),
|
||||||
Some(x) if x == TypeId::of::<IVec2>() => vector2_widget(default_info, "X", "Y", "", None),
|
Some(x) if x == TypeId::of::<IVec2>() => coordinate_widget(default_info, "X", "Y", "", None),
|
||||||
// ==========================
|
// ==========================
|
||||||
// PRIMITIVE COLLECTION TYPES
|
// PRIMITIVE COLLECTION TYPES
|
||||||
// ==========================
|
// ==========================
|
||||||
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
|
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
|
||||||
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_vector2_widget(default_info, TextInput::default()).into(),
|
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_coordinates_widget(default_info, TextInput::default()).into(),
|
||||||
// ====================
|
// ====================
|
||||||
// GRAPHICAL DATA TYPES
|
// GRAPHICAL DATA TYPES
|
||||||
// ====================
|
// ====================
|
||||||
|
@ -498,7 +498,7 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
||||||
last.clone()
|
last.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vector2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
|
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||||
|
@ -641,7 +641,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
||||||
widgets
|
widgets
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn array_of_vector2_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||||
|
@ -1181,7 +1181,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||||
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
|
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
|
||||||
match grid_type {
|
match grid_type {
|
||||||
GridType::Rectangular => {
|
GridType::Rectangular => {
|
||||||
let spacing = vector2_widget(ParameterWidgetsInfo::from_index(document_node, node_id, spacing_index, true, context), "W", "H", " px", Some(0.));
|
let spacing = coordinate_widget(ParameterWidgetsInfo::from_index(document_node, node_id, spacing_index, true, context), "W", "H", " px", Some(0.));
|
||||||
widgets.push(spacing);
|
widgets.push(spacing);
|
||||||
}
|
}
|
||||||
GridType::Isometric => {
|
GridType::Isometric => {
|
||||||
|
@ -1191,7 +1191,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||||
NumberInput::default().label("H").min(0.).unit(" px"),
|
NumberInput::default().label("H").min(0.).unit(" px"),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let angles = vector2_widget(ParameterWidgetsInfo::from_index(document_node, node_id, angles_index, true, context), "", "", "°", None);
|
let angles = coordinate_widget(ParameterWidgetsInfo::from_index(document_node, node_id, angles_index, true, context), "", "", "°", None);
|
||||||
widgets.extend([spacing, angles]);
|
widgets.extend([spacing, angles]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -467,7 +467,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const REPLACEMENTS: [(&str, &str); 37] = [
|
const REPLACEMENTS: [(&str, &str); 40] = [
|
||||||
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
|
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
|
||||||
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
|
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
|
||||||
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
|
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
|
||||||
|
@ -475,7 +475,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"),
|
("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"),
|
||||||
("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"),
|
("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"),
|
||||||
("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"),
|
("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"),
|
||||||
("graphene_core::ops::ConstructVector2", "graphene_core::ops::Vector2ValueNode"),
|
("graphene_core::ops::ConstructVector2", "graphene_core::ops::CoordinateValueNode"),
|
||||||
|
("graphene_core::ops::Vector2ValueNode", "graphene_core::ops::CoordinateValueNode"),
|
||||||
("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"),
|
("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"),
|
||||||
("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"),
|
("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"),
|
||||||
("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"),
|
("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"),
|
||||||
|
@ -484,6 +485,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"),
|
("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"),
|
||||||
("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"),
|
("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"),
|
||||||
("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"),
|
("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"),
|
||||||
|
("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||||
|
("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||||
("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"),
|
("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||||
("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"),
|
("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||||
("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"),
|
("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"),
|
||||||
|
@ -963,6 +966,48 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
|
|
||||||
document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string());
|
document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if reference == "Remove Handles" {
|
||||||
|
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
|
||||||
|
let new_node_template = node_definition.default_node_template();
|
||||||
|
let document_node = new_node_template.document_node;
|
||||||
|
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||||
|
document
|
||||||
|
.network_interface
|
||||||
|
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||||
|
|
||||||
|
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||||
|
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||||
|
document
|
||||||
|
.network_interface
|
||||||
|
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path);
|
||||||
|
document
|
||||||
|
.network_interface
|
||||||
|
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
|
||||||
|
|
||||||
|
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if reference == "Generate Handles" {
|
||||||
|
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
|
||||||
|
let new_node_template = node_definition.default_node_template();
|
||||||
|
let document_node = new_node_template.document_node;
|
||||||
|
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||||
|
document
|
||||||
|
.network_interface
|
||||||
|
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||||
|
|
||||||
|
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||||
|
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||||
|
document
|
||||||
|
.network_interface
|
||||||
|
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path);
|
||||||
|
|
||||||
|
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this document upgrade code
|
// TODO: Eventually remove this document upgrade code
|
||||||
|
|
|
@ -230,7 +230,7 @@ fn cosine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f6
|
||||||
|
|
||||||
/// The inverse tangent trigonometric function (atan or atan2, depending on input type) calculates:
|
/// The inverse tangent trigonometric function (atan or atan2, depending on input type) calculates:
|
||||||
/// atan: the angle whose tangent is the specified scalar number.
|
/// atan: the angle whose tangent is the specified scalar number.
|
||||||
/// atan2: the angle of a ray from the origin to the specified vector2 point.
|
/// atan2: the angle of a ray from the origin to the specified coordinate.
|
||||||
#[node_macro::node(category("Math: Trig"))]
|
#[node_macro::node(category("Math: Trig"))]
|
||||||
fn tangent_inverse<U: TangentInverse>(_: impl Ctx, #[implementations(f64, f32, DVec2)] value: U, radians: bool) -> U::Output {
|
fn tangent_inverse<U: TangentInverse>(_: impl Ctx, #[implementations(f64, f32, DVec2)] value: U, radians: bool) -> U::Output {
|
||||||
value.atan(radians)
|
value.atan(radians)
|
||||||
|
@ -434,8 +434,8 @@ fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a two-dimensional vector value which may be set to any XY coordinate.
|
/// Constructs a two-dimensional vector value which may be set to any XY coordinate.
|
||||||
#[node_macro::node(name("Vector2 Value"), category("Value"))]
|
#[node_macro::node(category("Value"))]
|
||||||
fn vector2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 {
|
fn coordinate_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 {
|
||||||
DVec2::new(x, y)
|
DVec2::new(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -524,7 +524,7 @@ fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 {
|
||||||
vector_a.dot(vector_b)
|
vector_a.dot(vector_b)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain the X or Y component of a vector2.
|
/// Obtain the X or Y component of a coordinate.
|
||||||
#[node_macro::node(name("Extract XY"), category("Math: Vector"))]
|
#[node_macro::node(name("Extract XY"), category("Math: Vector"))]
|
||||||
fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 {
|
fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 {
|
||||||
match axis {
|
match axis {
|
||||||
|
@ -533,7 +533,7 @@ fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The X or Y component of a vector2.
|
/// The X or Y component of a coordinate.
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType)]
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub mod types {
|
||||||
pub type IntegerCount = u32;
|
pub type IntegerCount = u32;
|
||||||
/// Unsigned integer to be used for random seeds
|
/// Unsigned integer to be used for random seeds
|
||||||
pub type SeedValue = u32;
|
pub type SeedValue = u32;
|
||||||
/// Non-negative integer vector2 with px unit
|
/// Non-negative integer coordinate with px unit
|
||||||
pub type Resolution = glam::UVec2;
|
pub type Resolution = glam::UVec2;
|
||||||
/// DVec2 with px unit
|
/// DVec2 with px unit
|
||||||
pub type PixelSize = glam::DVec2;
|
pub type PixelSize = glam::DVec2;
|
||||||
|
|
|
@ -9,9 +9,9 @@ use crate::raster_types::{CPU, RasterDataTable};
|
||||||
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
|
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
|
||||||
use crate::renderer::GraphicElementRendered;
|
use crate::renderer::GraphicElementRendered;
|
||||||
use crate::transform::{Footprint, ReferencePoint, Transform};
|
use crate::transform::{Footprint, ReferencePoint, Transform};
|
||||||
use crate::vector::PointDomain;
|
|
||||||
use crate::vector::misc::dvec2_to_point;
|
use crate::vector::misc::dvec2_to_point;
|
||||||
use crate::vector::style::{LineCap, LineJoin};
|
use crate::vector::style::{LineCap, LineJoin};
|
||||||
|
use crate::vector::{FillId, PointDomain, RegionId};
|
||||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
||||||
use bezier_rs::{Join, ManipulatorGroup, Subpath};
|
use bezier_rs::{Join, ManipulatorGroup, Subpath};
|
||||||
use core::f64::consts::PI;
|
use core::f64::consts::PI;
|
||||||
|
@ -763,69 +763,25 @@ fn bilinear_interpolate(t: DVec2, quad: &[DVec2; 4]) -> DVec2 {
|
||||||
tl * (1. - t.x) * (1. - t.y) + tr * t.x * (1. - t.y) + br * t.x * t.y + bl * (1. - t.x) * t.y
|
tl * (1. - t.x) * (1. - t.y) + tr * t.x * (1. - t.y) + br * t.x * t.y + bl * (1. - t.x) * t.y
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
/// Automatically constructs tangents (Bézier handles) for anchor points in a vector path.
|
||||||
async fn remove_handles(
|
#[node_macro::node(category("Vector"), name("Auto-Tangents"), path(graphene_core::vector))]
|
||||||
_: impl Ctx,
|
async fn auto_tangents(
|
||||||
vector_data: VectorDataTable,
|
|
||||||
#[default(10.)]
|
|
||||||
#[soft_min(0.)]
|
|
||||||
max_handle_distance: f64,
|
|
||||||
) -> VectorDataTable {
|
|
||||||
let mut result_table = VectorDataTable::default();
|
|
||||||
|
|
||||||
for mut vector_data_instance in vector_data.instance_iter() {
|
|
||||||
let mut vector_data = vector_data_instance.instance;
|
|
||||||
|
|
||||||
for (_, handles, start, end) in vector_data.segment_domain.handles_mut() {
|
|
||||||
// Only convert to linear if handles are within the threshold distance
|
|
||||||
match *handles {
|
|
||||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
|
||||||
let start_pos = vector_data.point_domain.positions()[start];
|
|
||||||
let end_pos = vector_data.point_domain.positions()[end];
|
|
||||||
|
|
||||||
let start_handle_distance = (handle_start - start_pos).length();
|
|
||||||
let end_handle_distance = (handle_end - end_pos).length();
|
|
||||||
|
|
||||||
// If handles are close enough to their anchor points, make the segment linear
|
|
||||||
if start_handle_distance <= max_handle_distance && end_handle_distance <= max_handle_distance {
|
|
||||||
*handles = bezier_rs::BezierHandles::Linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
|
||||||
let start_pos = vector_data.point_domain.positions()[start];
|
|
||||||
let end_pos = vector_data.point_domain.positions()[end];
|
|
||||||
|
|
||||||
// Use average distance from handle to both points
|
|
||||||
let avg_distance = ((handle - start_pos).length() + (handle - end_pos).length()) / 2.;
|
|
||||||
|
|
||||||
if avg_distance <= max_handle_distance {
|
|
||||||
*handles = bezier_rs::BezierHandles::Linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vector_data_instance.instance = vector_data;
|
|
||||||
vector_data_instance.source_node_id = None;
|
|
||||||
result_table.push(vector_data_instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
result_table
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
|
||||||
async fn generate_handles(
|
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
source: VectorDataTable,
|
source: VectorDataTable,
|
||||||
#[default(0.4)]
|
/// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread).
|
||||||
|
#[default(0.5)]
|
||||||
#[range((0., 1.))]
|
#[range((0., 1.))]
|
||||||
curvature: f64,
|
spread: f64,
|
||||||
|
/// If active, existing non-zero handles won't be affected.
|
||||||
|
#[default(true)]
|
||||||
|
preserve_existing: bool,
|
||||||
) -> VectorDataTable {
|
) -> VectorDataTable {
|
||||||
let mut result_table = VectorDataTable::default();
|
let mut result_table = VectorDataTable::default();
|
||||||
|
|
||||||
for source in source.instance_ref_iter() {
|
for source in source.instance_ref_iter() {
|
||||||
let source_transform = *source.transform;
|
let transform = *source.transform;
|
||||||
|
let alpha_blending = *source.alpha_blending;
|
||||||
|
let source_node_id = *source.source_node_id;
|
||||||
let source = source.instance;
|
let source = source.instance;
|
||||||
|
|
||||||
let mut result = VectorData {
|
let mut result = VectorData {
|
||||||
|
@ -834,11 +790,11 @@ async fn generate_handles(
|
||||||
};
|
};
|
||||||
|
|
||||||
for mut subpath in source.stroke_bezier_paths() {
|
for mut subpath in source.stroke_bezier_paths() {
|
||||||
subpath.apply_transform(source_transform);
|
subpath.apply_transform(transform);
|
||||||
|
|
||||||
let groups = subpath.manipulator_groups();
|
let groups = subpath.manipulator_groups();
|
||||||
if groups.len() < 2 {
|
if groups.len() < 2 {
|
||||||
// Not enough points for softening
|
// Not enough points for softening or handle removal
|
||||||
result.append_subpath(subpath, true);
|
result.append_subpath(subpath, true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -849,16 +805,30 @@ async fn generate_handles(
|
||||||
for i in 0..groups.len() {
|
for i in 0..groups.len() {
|
||||||
let curr = &groups[i];
|
let curr = &groups[i];
|
||||||
|
|
||||||
// Check if this point has handles
|
if preserve_existing {
|
||||||
let has_handles =
|
// Check if this point has handles that are meaningfully different from the anchor
|
||||||
(curr.in_handle.is_some() && !curr.in_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5)) || (curr.out_handle.is_some() && !curr.out_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5));
|
let has_handles = (curr.in_handle.is_some() && !curr.in_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5))
|
||||||
|
|| (curr.out_handle.is_some() && !curr.out_handle.unwrap().abs_diff_eq(curr.anchor, 1e-5));
|
||||||
|
|
||||||
if has_handles || (!is_closed && (i == 0 || i == groups.len() - 1)) {
|
// If the point already has handles, or if it's an endpoint of an open path, keep it as is.
|
||||||
new_groups.push(*curr);
|
if has_handles || (!is_closed && (i == 0 || i == groups.len() - 1)) {
|
||||||
|
new_groups.push(*curr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If spread is 0, remove handles for this point, making it a sharp corner.
|
||||||
|
if spread == 0. {
|
||||||
|
new_groups.push(ManipulatorGroup {
|
||||||
|
anchor: curr.anchor,
|
||||||
|
in_handle: None,
|
||||||
|
out_handle: None,
|
||||||
|
id: curr.id,
|
||||||
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get previous and next points
|
// Get previous and next points for auto-tangent calculation
|
||||||
let prev_idx = if i == 0 { if is_closed { groups.len() - 1 } else { i } } else { i - 1 };
|
let prev_idx = if i == 0 { if is_closed { groups.len() - 1 } else { i } } else { i - 1 };
|
||||||
let next_idx = if i == groups.len() - 1 { if is_closed { 0 } else { i } } else { i + 1 };
|
let next_idx = if i == groups.len() - 1 { if is_closed { 0 } else { i } } else { i + 1 };
|
||||||
|
|
||||||
|
@ -866,25 +836,33 @@ async fn generate_handles(
|
||||||
let curr_pos = curr.anchor;
|
let curr_pos = curr.anchor;
|
||||||
let next = groups[next_idx].anchor;
|
let next = groups[next_idx].anchor;
|
||||||
|
|
||||||
// Calculate directions to adjacent points
|
// Calculate directions from current point to adjacent points
|
||||||
let dir_prev = (prev - curr_pos).normalize_or_zero();
|
let dir_prev = (prev - curr_pos).normalize_or_zero();
|
||||||
let dir_next = (next - curr_pos).normalize_or_zero();
|
let dir_next = (next - curr_pos).normalize_or_zero();
|
||||||
|
|
||||||
// Check if we have valid directions
|
// Check if we have valid directions (e.g., points are not coincident)
|
||||||
if dir_prev.length_squared() < 1e-5 || dir_next.length_squared() < 1e-5 {
|
if dir_prev.length_squared() < 1e-5 || dir_next.length_squared() < 1e-5 {
|
||||||
|
// Fallback: keep the original manipulator group (which has no active handles here)
|
||||||
new_groups.push(*curr);
|
new_groups.push(*curr);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate handle direction (perpendicular to the angle bisector)
|
// Calculate handle direction (colinear, pointing along the line from prev to next)
|
||||||
let handle_dir = (dir_prev - dir_next).try_normalize().unwrap_or(dir_prev.perp());
|
// Original logic: (dir_prev - dir_next) is equivalent to (prev - curr) - (next - curr) = prev - next
|
||||||
let handle_dir = if dir_prev.dot(handle_dir) < 0. { -handle_dir } else { handle_dir };
|
// The handle_dir will be along the line connecting prev and next, or perpendicular if they are coincident.
|
||||||
|
let mut handle_dir = (dir_prev - dir_next).try_normalize().unwrap_or_else(|| dir_prev.perp());
|
||||||
|
|
||||||
// Calculate handle lengths - 1/3 of distance to adjacent points, scaled by curvature
|
// Ensure consistent orientation of the handle_dir
|
||||||
let in_length = (curr_pos - prev).length() / 3. * curvature;
|
// This makes the `+ handle_dir` for in_handle and `- handle_dir` for out_handle consistent
|
||||||
let out_length = (next - curr_pos).length() / 3. * curvature;
|
if dir_prev.dot(handle_dir) < 0. {
|
||||||
|
handle_dir = -handle_dir;
|
||||||
|
}
|
||||||
|
|
||||||
// Create new manipulator group with handles
|
// Calculate handle lengths: 1/3 of distance to adjacent points, scaled by spread
|
||||||
|
let in_length = (curr_pos - prev).length() / 3. * spread;
|
||||||
|
let out_length = (next - curr_pos).length() / 3. * spread;
|
||||||
|
|
||||||
|
// Create new manipulator group with calculated auto-tangents
|
||||||
new_groups.push(ManipulatorGroup {
|
new_groups.push(ManipulatorGroup {
|
||||||
anchor: curr_pos,
|
anchor: curr_pos,
|
||||||
in_handle: Some(curr_pos + handle_dir * in_length),
|
in_handle: Some(curr_pos + handle_dir * in_length),
|
||||||
|
@ -894,15 +872,15 @@ async fn generate_handles(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut softened_subpath = Subpath::new(new_groups, is_closed);
|
let mut softened_subpath = Subpath::new(new_groups, is_closed);
|
||||||
softened_subpath.apply_transform(source_transform.inverse());
|
softened_subpath.apply_transform(transform.inverse());
|
||||||
result.append_subpath(softened_subpath, true);
|
result.append_subpath(softened_subpath, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
result_table.push(Instance {
|
result_table.push(Instance {
|
||||||
instance: result,
|
instance: result,
|
||||||
transform: source_transform,
|
transform,
|
||||||
alpha_blending: Default::default(),
|
alpha_blending,
|
||||||
source_node_id: None,
|
source_node_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1058,6 +1036,53 @@ async fn dimensions(_: impl Ctx, vector_data: VectorDataTable) -> DVec2 {
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a coordinate value into a vector anchor point.
|
||||||
|
///
|
||||||
|
/// This is useful in conjunction with nodes that repeat it, followed by the "Points to Polyline" node to string together a path of the points.
|
||||||
|
#[node_macro::node(category("Vector"), name("Coordinate to Point"), path(graphene_core::vector))]
|
||||||
|
async fn position_to_point(_: impl Ctx, coordinate: DVec2) -> VectorDataTable {
|
||||||
|
let mut result_table = VectorDataTable::default();
|
||||||
|
|
||||||
|
let mut point_domain = PointDomain::new();
|
||||||
|
point_domain.push(PointId::generate(), coordinate);
|
||||||
|
|
||||||
|
result_table.push(Instance {
|
||||||
|
instance: VectorData { point_domain, ..Default::default() },
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
result_table
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist.
|
||||||
|
#[node_macro::node(category("Vector"), name("Points to Polyline"), path(graphene_core::vector))]
|
||||||
|
async fn points_to_polyline(_: impl Ctx, mut points: VectorDataTable, #[default(true)] closed: bool) -> VectorDataTable {
|
||||||
|
for instance in points.instance_mut_iter() {
|
||||||
|
let mut segment_domain = SegmentDomain::new();
|
||||||
|
|
||||||
|
let points_count = instance.instance.point_domain.ids().len();
|
||||||
|
|
||||||
|
if points_count > 2 {
|
||||||
|
(0..points_count - 1).for_each(|i| {
|
||||||
|
segment_domain.push(SegmentId::generate(), i, i + 1, bezier_rs::BezierHandles::Linear, StrokeId::generate());
|
||||||
|
});
|
||||||
|
|
||||||
|
if closed {
|
||||||
|
segment_domain.push(SegmentId::generate(), points_count - 1, 0, bezier_rs::BezierHandles::Linear, StrokeId::generate());
|
||||||
|
|
||||||
|
instance
|
||||||
|
.instance
|
||||||
|
.region_domain
|
||||||
|
.push(RegionId::generate(), segment_domain.ids()[0]..=*segment_domain.ids().last().unwrap(), FillId::generate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.instance.segment_domain = segment_domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
points
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
|
||||||
async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable {
|
async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable {
|
||||||
let mut result_table = VectorDataTable::default();
|
let mut result_table = VectorDataTable::default();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue