Clean up node catalog by adding missing units, more tooltips; fix 'Line' node missing parameters (#2813)

* Fix unit usages

* Add node and parameter doc comments

* Fix the parameters panel for the 'Line' node when added from the graph

* Clean up nodes

* Fix tests

* Update the demo artwork
This commit is contained in:
Keavon Chambers 2025-07-01 07:47:54 -07:00 committed by GitHub
parent 0febfaf142
commit 8c5accc069
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 366 additions and 89 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1016,7 +1016,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
description: Cow::Borrowed(
"Decomposes the X and Y components of a 2D coordinate.\n\nThe inverse of this node is \"Coordinate Value\", which can have either or both its X and Y exposed as graph inputs.",
),
properties: None,
},
// TODO: Remove this and just use the proto node definition directly
@ -1856,7 +1858,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
NodeInput::network(concrete!(f64), 2),
NodeInput::network(concrete!(f64), 3),
NodeInput::network(concrete!(u32), 3),
NodeInput::network(concrete!(f64), 4),
NodeInput::network(concrete!(f64), 5),
NodeInput::network(concrete!(bool), 6),
@ -1895,7 +1897,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
NodeInput::value(TaggedValue::F64(100.), false),
NodeInput::value(TaggedValue::F64(100.), false),
NodeInput::value(TaggedValue::U32(100), false),
NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::Bool(false), false),

View file

@ -1427,6 +1427,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
} else {
NumberInput::default()
.value(Some(uniform_val))
.unit(" px")
.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, CornerRadiusInput::<f64>::INDEX))
.on_commit(commit_value)
.widget_holder()

View file

@ -744,17 +744,62 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
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), new_spacing_value, network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
}
// Make the "Quantity" parameter a u32 instead of f64
if reference == "Sample Polyline" {
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
let new_node_template = node_definition.default_node_template();
let document_node = new_node_template.document_node;
// Get the inputs, obtain the quantity value, and put the inputs back
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
let quantity_value = old_inputs.get(3).cloned();
let _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path);
if let Some(NodeInput::Value { tagged_value, exposed }) = quantity_value {
if let TaggedValue::F64(value) = *tagged_value {
let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), exposed);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
}
}
}
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
if reference == "Grid" && inputs_count == 6 {
let node_definition = resolve_document_node_type(reference).unwrap();
let new_node_template = node_definition.default_node_template();
let document_node = new_node_template.document_node;
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
let index_3_value = old_inputs.get(3).cloned();
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
if matches!(*tagged_value, TaggedValue::DVec2(_)) {
// Move index 3 to the end
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), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
} else {
// Swap it back if we're not changing anything
let _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path);
}
}
}
}
// Ensure layers are positioned as stacks if they are upstream siblings of another layer

View file

@ -1,5 +1,14 @@
use crate::raster_types::{CPU, RasterDataTable};
use crate::vector::VectorDataTable;
use crate::{Color, Ctx};
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"), name("Log to Console"))]
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
log::debug!("{:#?}", value);
value
}
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
#[node_macro::node(category("Debug"))]

View file

@ -2,7 +2,9 @@ use crate::Ctx;
use dyn_any::DynAny;
use glam::{DVec2, IVec2, UVec2};
/// Obtain the X or Y component of a coordinate.
/// Obtains the X or Y component of a coordinate point.
///
/// The inverse of this node is "Coordinate Value", which can have either or both its X and Y exposed as graph inputs.
#[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 {
match axis {

View file

@ -1,14 +1,13 @@
use crate::ArtboardGroupTable;
use crate::Color;
use crate::GraphicElement;
use crate::GraphicGroupTable;
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, GPU, RasterDataTable};
use crate::vector::VectorDataTable;
use crate::{Color, Context, Ctx};
use crate::{Context, Ctx};
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"), name("Log to Console"))]
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
log::debug!("{:#?}", value);
value
}
#[node_macro::node(category("Text"))]
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
format!("{:?}", value)
@ -45,24 +44,42 @@ async fn switch<T, C: Send + 'n + Clone>(
#[implementations(
Context -> String,
Context -> bool,
Context -> f32,
Context -> f64,
Context -> u32,
Context -> u64,
Context -> DVec2,
Context -> VectorDataTable,
Context -> DAffine2,
Context -> ArtboardGroupTable,
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> GraphicElement,
Context -> Color,
Context -> Option<Color>,
Context -> GradientStops,
)]
if_true: impl Node<C, Output = T>,
#[expose]
#[implementations(
Context -> String,
Context -> bool,
Context -> f32,
Context -> f64,
Context -> u32,
Context -> u64,
Context -> DVec2,
Context -> VectorDataTable,
Context -> DAffine2,
Context -> ArtboardGroupTable,
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> GraphicElement,
Context -> Color,
Context -> Option<Color>,
Context -> GradientStops,
)]
if_false: impl Node<C, Output = T>,
) -> T {

View file

@ -73,6 +73,14 @@ pub trait Convert<T>: Sized {
fn convert(self) -> T;
}
impl<T: ToString> Convert<String> for T {
/// Converts this type into a `String` using its `ToString` implementation.
#[inline]
fn convert(self) -> String {
self.to_string()
}
}
/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types.
macro_rules! impl_convert {
($from:ty, $to:ty) => {

View file

@ -70,6 +70,7 @@ async fn boundless_footprint<T: 'n + 'static>(
transform_target.eval(ctx.into_context()).await
}
#[node_macro::node(category("Debug"))]
async fn freeze_real_time<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,

View file

@ -86,6 +86,7 @@ async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
Default::default()
}
// TODO: Make this return a u32 instead of an f64, but we ned to improve math-related compatibility with integer types first.
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_index(ctx: impl Ctx + ExtractIndex) -> f64 {
match ctx.try_index() {

View file

@ -37,7 +37,13 @@ impl CornerRadius for [f64; 4] {
}
#[node_macro::node(category("Vector: Shape"))]
fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
fn circle(
_: impl Ctx,
_primary: (),
#[unit(" px")]
#[default(50.)]
radius: f64,
) -> VectorDataTable {
let radius = radius.abs();
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
}
@ -46,7 +52,9 @@ fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataT
fn arc(
_: impl Ctx,
_primary: (),
#[default(50.)] radius: f64,
#[unit(" px")]
#[default(50.)]
radius: f64,
start_angle: Angle,
#[default(270.)]
#[range((0., 360.))]
@ -66,7 +74,16 @@ fn arc(
}
#[node_macro::node(category("Vector: Shape"))]
fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable {
fn ellipse(
_: impl Ctx,
_primary: (),
#[unit(" px")]
#[default(50)]
radius_x: f64,
#[unit(" px")]
#[default(25)]
radius_y: f64,
) -> VectorDataTable {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius;
let corner2 = radius;
@ -87,8 +104,12 @@ fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25
fn rectangle<T: CornerRadius>(
_: impl Ctx,
_primary: (),
#[default(100)] width: f64,
#[default(100)] height: f64,
#[unit(" px")]
#[default(100)]
width: f64,
#[unit(" px")]
#[default(100)]
height: f64,
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
#[implementations(f64, [f64; 4])] corner_radius: T,
#[default(true)] clamped: bool,
@ -104,7 +125,9 @@ fn regular_polygon<T: AsU64>(
#[hard_min(3.)]
#[implementations(u32, u64, f64)]
sides: T,
#[default(50)] radius: f64,
#[unit(" px")]
#[default(50)]
radius: f64,
) -> VectorDataTable {
let points = sides.as_u64();
let radius: f64 = radius * 2.;
@ -119,8 +142,12 @@ fn star<T: AsU64>(
#[hard_min(2.)]
#[implementations(u32, u64, f64)]
sides: T,
#[default(50)] radius_1: f64,
#[default(25)] radius_2: f64,
#[unit(" px")]
#[default(50)]
radius_1: f64,
#[unit(" px")]
#[default(25)]
radius_2: f64,
) -> VectorDataTable {
let points = sides.as_u64();
let diameter: f64 = radius_1 * 2.;
@ -130,7 +157,7 @@ fn star<T: AsU64>(
}
#[node_macro::node(category("Vector: Shape"))]
fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: PixelSize, #[default((0., 50.))] end: PixelSize) -> VectorDataTable {
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> VectorDataTable {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
}
@ -153,13 +180,14 @@ fn grid<T: GridSpacing>(
_: impl Ctx,
_primary: (),
grid_type: GridType,
#[unit(" px")]
#[hard_min(0.)]
#[default(10)]
#[implementations(f64, DVec2)]
spacing: T,
#[default(30., 30.)] angles: DVec2,
#[default(10)] columns: u32,
#[default(10)] rows: u32,
#[default(30., 30.)] angles: DVec2,
) -> VectorDataTable {
let (x_spacing, y_spacing) = spacing.as_dvec2().into();
let (angle_a, angle_b) = angles.into();
@ -251,11 +279,11 @@ mod tests {
#[test]
fn isometric_grid_test() {
// Doesn't crash with weird angles
grid((), (), GridType::Isometric, 0., (0., 0.).into(), 5, 5);
grid((), (), GridType::Isometric, 90., (90., 90.).into(), 5, 5);
grid((), (), GridType::Isometric, 0., 5, 5, (0., 0.).into());
grid((), (), GridType::Isometric, 90., 5, 5, (90., 90.).into());
// Works properly
let grid = grid((), (), GridType::Isometric, 10., (30., 30.).into(), 5, 5);
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into());
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
@ -270,7 +298,7 @@ mod tests {
#[test]
fn skew_isometric_grid_test() {
let grid = grid((), (), GridType::Isometric, 10., (40., 30.).into(), 5, 5);
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into());
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {

View file

@ -65,6 +65,7 @@ async fn assign_colors<T>(
randomize: bool,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")]
/// The seed used for randomization.
/// Seed to determine unique variations on the randomized color selection.
seed: SeedValue,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")]
/// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.
@ -165,6 +166,7 @@ async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
#[default(Color::BLACK)]
/// The stroke color.
color: C,
#[unit(" px")]
#[default(2.)]
/// The stroke weight.
weight: f64,
@ -183,6 +185,7 @@ async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.
dash_lengths: Vec<f64>,
/// The phase offset distance from the starting point of the dash pattern.
#[unit(" px")]
dash_offset: f64,
) -> Instances<V>
where
@ -253,7 +256,9 @@ async fn circular_repeat<I: 'n + Send + Clone>(
// TODO: Implement other GraphicElementRendered types.
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
angle_offset: Angle,
#[default(5)] radius: f64,
#[unit(" px")]
#[default(5)]
radius: f64,
#[default(5)] instances: IntegerCount,
) -> Instances<I> {
let count = instances.max(1);
@ -363,7 +368,7 @@ async fn mirror<I: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
offset: f64,
#[unit(" px")] offset: f64,
#[range((-90., 90.))] angle: Angle,
#[default(true)] keep_original: bool,
) -> Instances<I>
@ -1139,10 +1144,10 @@ async fn sample_polyline(
_: impl Ctx,
vector_data: VectorDataTable,
spacing: PointSpacingType,
separation: f64,
quantity: f64,
start_offset: f64,
stop_offset: f64,
#[unit(" px")] separation: f64,
quantity: u32,
#[unit(" px")] start_offset: f64,
#[unit(" px")] stop_offset: f64,
adaptive_spacing: bool,
subpath_segment_lengths: Vec<f64>,
) -> VectorDataTable {
@ -1182,7 +1187,7 @@ async fn sample_polyline(
let amount = match spacing {
PointSpacingType::Separation => separation,
PointSpacingType::Quantity => quantity,
PointSpacingType::Quantity => quantity as f64,
};
let Some(mut sample_bezpath) = sample_polyline_on_bezpath(bezpath, spacing, amount, start_offset, stop_offset, adaptive_spacing, current_bezpath_segments_length) else {
continue;
@ -1388,6 +1393,7 @@ async fn tangent_on_path(
async fn poisson_disk_points(
_: impl Ctx,
vector_data: VectorDataTable,
#[unit(" px")]
#[default(10.)]
#[hard_min(0.01)]
separation_disk_diameter: f64,
@ -1498,7 +1504,14 @@ async fn spline(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
}
#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))]
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
async fn jitter_points(
_: impl Ctx,
vector_data: VectorDataTable,
#[unit(" px")]
#[default(5.)]
amount: f64,
seed: SeedValue,
) -> VectorDataTable {
let mut result_table = VectorDataTable::default();
for mut vector_data_instance in vector_data.instance_iter() {
@ -2080,7 +2093,7 @@ mod test {
#[tokio::test]
async fn sample_polyline() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 30., 0., 0., 0., false, vec![100.]).await;
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 30., 0, 0., 0., false, vec![100.]).await;
let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance;
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
@ -2090,7 +2103,7 @@ mod test {
#[tokio::test]
async fn sample_polyline_adaptive_spacing() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 18., 0., 45., 10., true, vec![100.]).await;
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 18., 0, 45., 10., true, vec![100.]).await;
let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance;
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {

View file

@ -78,8 +78,12 @@ fn math<U: num_traits::float::Float>(
#[node_macro::node(category("Math: Arithmetic"))]
fn add<U: Add<T>, T>(
_: impl Ctx,
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] augend: U,
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] addend: T,
/// The left-hand side of the addition operation.
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
augend: U,
/// The right-hand side of the addition operation.
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
addend: T,
) -> <U as Add<T>>::Output {
augend + addend
}
@ -88,8 +92,12 @@ fn add<U: Add<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))]
fn subtract<U: Sub<T>, T>(
_: impl Ctx,
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U,
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T,
/// The left-hand side of the subtraction operation.
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
minuend: U,
/// The right-hand side of the subtraction operation.
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
subtrahend: T,
) -> <U as Sub<T>>::Output {
minuend - subtrahend
}
@ -98,9 +106,12 @@ fn subtract<U: Sub<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))]
fn multiply<U: Mul<T>, T>(
_: impl Ctx,
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U,
/// The left-hand side of the multiplication operation.
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
multiplier: U,
/// The right-hand side of the multiplication operation.
#[default(1.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)]
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
multiplicand: T,
) -> <U as Mul<T>>::Output {
multiplier * multiplicand
@ -112,7 +123,10 @@ fn multiply<U: Mul<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))]
fn divide<U: Div<T> + Default + PartialEq, T: Default + PartialEq>(
_: impl Ctx,
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, DVec2, f64)] numerator: U,
/// The left-hand side of the division operation.
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, DVec2, f64)]
numerator: U,
/// The right-hand side of the division operation.
#[default(1.)]
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, f64, DVec2)]
denominator: T,
@ -130,10 +144,15 @@ where
#[node_macro::node(category("Math: Arithmetic"))]
fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy>(
_: impl Ctx,
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U,
/// The left-hand side of the modulo operation.
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
numerator: U,
/// The right-hand side of the modulo operation.
#[default(2.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)]
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
modulus: T,
/// Ensures the result will always be positive, even if the numerator is negative.
#[default(true)]
always_positive: bool,
) -> <U as Rem<T>>::Output {
if always_positive { (numerator % modulus + modulus) % modulus } else { numerator % modulus }
@ -143,9 +162,12 @@ fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy
#[node_macro::node(category("Math: Arithmetic"))]
fn exponent<U: Pow<T>, T>(
_: impl Ctx,
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U,
/// The base number that will be raised to the power.
#[implementations(f64, f32, u32)]
base: U,
/// The power to which the base number will be raised.
#[default(2.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
#[implementations(f64, f32, u32)]
power: T,
) -> <U as num_traits::Pow<T>>::Output {
base.pow(power)
@ -155,9 +177,11 @@ fn exponent<U: Pow<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))]
fn root<U: num_traits::float::Float>(
_: impl Ctx,
/// The number for which the nth root will be calculated.
#[default(2.)]
#[implementations(f64, f32)]
radicand: U,
/// The degree of the root to be calculated. Square root is 2, cube root is 3, and so on.
#[default(2.)]
#[implementations(f64, f32)]
degree: U,
@ -175,7 +199,10 @@ fn root<U: num_traits::float::Float>(
#[node_macro::node(category("Math: Arithmetic"))]
fn logarithm<U: num_traits::float::Float>(
_: impl Ctx,
#[implementations(f64, f32)] value: U,
/// The number for which the logarithm will be calculated.
#[implementations(f64, f32)]
value: U,
/// The base of the logarithm, such as 2 (binary), 10 (decimal), and e (natural logarithm).
#[default(2.)]
#[implementations(f64, f32)]
base: U,
@ -193,39 +220,83 @@ fn logarithm<U: num_traits::float::Float>(
/// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length.
#[node_macro::node(category("Math: Trig"))]
fn sine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
fn sine<U: num_traits::float::Float>(
_: impl Ctx,
/// The given angle.
#[implementations(f64, f32)]
theta: U,
/// Whether the given angle should be interpreted as radians instead of degrees.
radians: bool,
) -> U {
if radians { theta.sin() } else { theta.to_radians().sin() }
}
/// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
#[node_macro::node(category("Math: Trig"))]
fn cosine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
fn cosine<U: num_traits::float::Float>(
_: impl Ctx,
/// The given angle.
#[implementations(f64, f32)]
theta: U,
/// Whether the given angle should be interpreted as radians instead of degrees.
radians: bool,
) -> U {
if radians { theta.cos() } else { theta.to_radians().cos() }
}
/// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length.
#[node_macro::node(category("Math: Trig"))]
fn tangent<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
fn tangent<U: num_traits::float::Float>(
_: impl Ctx,
/// The given angle.
#[implementations(f64, f32)]
theta: U,
/// Whether the given angle should be interpreted as radians instead of degrees.
radians: bool,
) -> U {
if radians { theta.tan() } else { theta.to_radians().tan() }
}
/// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value.
#[node_macro::node(category("Math: Trig"))]
fn sine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
fn sine_inverse<U: num_traits::float::Float>(
_: impl Ctx,
/// The given value for which the angle will be calculated. Must be in the range [-1, 1] or else the result will be NaN.
#[implementations(f64, f32)]
value: U,
/// Whether the resulting angle should be given in as radians instead of degrees.
radians: bool,
) -> U {
if radians { value.asin() } else { value.asin().to_degrees() }
}
/// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value.
#[node_macro::node(category("Math: Trig"))]
fn cosine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
fn cosine_inverse<U: num_traits::float::Float>(
_: impl Ctx,
/// The given value for which the angle will be calculated. Must be in the range [-1, 1] or else the result will be NaN.
#[implementations(f64, f32)]
value: U,
/// Whether the resulting angle should be given in as radians instead of degrees.
radians: bool,
) -> U {
if radians { value.acos() } else { value.acos().to_degrees() }
}
/// The inverse tangent trigonometric function (atan or atan2, depending on input type) calculates:
/// atan: the angle whose tangent is the specified scalar number.
/// atan2: the angle of a ray from the origin to the specified coordinate.
///
/// The resulting angle is always in the range [0°, 180°] or, in radians, [-π/2, π/2].
#[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,
/// The given value for which the angle will be calculated.
#[implementations(f64, f32, DVec2)]
value: U,
/// Whether the resulting angle should be given in as radians instead of degrees.
radians: bool,
) -> U::Output {
value.atan(radians)
}
@ -257,10 +328,13 @@ impl TangentInverse for DVec2 {
fn random<U: num_traits::float::Float>(
_: impl Ctx,
_primary: (),
/// Seed to determine the unique variation of which number will be generated.
seed: u64,
/// The smaller end of the range within which the random number will be generated.
#[implementations(f64, f32)]
#[default(0.)]
min: U,
/// The larger end of the range within which the random number will be generated.
#[implementations(f64, f32)]
#[default(1.)]
max: U,
@ -294,37 +368,73 @@ fn to_f64<U: num_traits::int::PrimInt>(_: impl Ctx, #[implementations(u32, u64)]
/// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero.
#[node_macro::node(category("Math: Numeric"))]
fn round<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
fn round<U: num_traits::float::Float>(
_: impl Ctx,
/// The number which will be rounded.
#[implementations(f64, f32)]
value: U,
) -> U {
value.round()
}
/// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole.
/// The floor function (floor) rounds down an input value to the nearest whole number, unless the input number is already whole.
#[node_macro::node(category("Math: Numeric"))]
fn floor<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
fn floor<U: num_traits::float::Float>(
_: impl Ctx,
/// The number which will be rounded down.
#[implementations(f64, f32)]
value: U,
) -> U {
value.floor()
}
/// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole.
/// The ceiling function (ceil) rounds up an input value to the nearest whole number, unless the input number is already whole.
#[node_macro::node(category("Math: Numeric"))]
fn ceiling<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
fn ceiling<U: num_traits::float::Float>(
_: impl Ctx,
/// The number which will be rounded up.
#[implementations(f64, f32)]
value: U,
) -> U {
value.ceil()
}
/// The absolute value function (abs) removes the negative sign from an input value, if present.
#[node_macro::node(category("Math: Numeric"))]
fn absolute_value<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
fn absolute_value<U: num_traits::float::Float>(
_: impl Ctx,
/// The number which will be made positive.
#[implementations(f64, f32)]
value: U,
) -> U {
value.abs()
}
/// The minimum function (min) picks the smaller of two numbers.
#[node_macro::node(category("Math: Numeric"))]
fn min<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
fn min<T: std::cmp::PartialOrd>(
_: impl Ctx,
/// One of the two numbers, of which the lesser will be returned.
#[implementations(f64, f32, u32, &str)]
value: T,
/// The other of the two numbers, of which the lesser will be returned.
#[implementations(f64, f32, u32, &str)]
other_value: T,
) -> T {
if value < other_value { value } else { other_value }
}
/// The maximum function (max) picks the larger of two numbers.
#[node_macro::node(category("Math: Numeric"))]
fn max<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
fn max<T: std::cmp::PartialOrd>(
_: impl Ctx,
/// One of the two numbers, of which the greater will be returned.
#[implementations(f64, f32, u32, &str)]
value: T,
/// The other of the two numbers, of which the greater will be returned.
#[implementations(f64, f32, u32, &str)]
other_value: T,
) -> T {
if value > other_value { value } else { other_value }
}
@ -332,9 +442,15 @@ fn max<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &
#[node_macro::node(category("Math: Numeric"))]
fn clamp<T: std::cmp::PartialOrd>(
_: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T,
/// The number to be clamped, which will be restricted to the range between the minimum and maximum values.
#[implementations(f64, f32, u32, &str)]
value: T,
/// The left (smaller) side of the range. The output will never be less than this number.
#[implementations(f64, f32, u32, &str)]
min: T,
/// The right (greater) side of the range. The output will never be greater than this number.
#[implementations(f64, f32, u32, &str)]
max: T,
) -> T {
let (min, max) = if min < max { (min, max) } else { (max, min) };
if value < min {
@ -350,8 +466,12 @@ fn clamp<T: std::cmp::PartialOrd>(
#[node_macro::node(category("Math: Logic"))]
fn equals<U: std::cmp::PartialEq<T>, T>(
_: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] other_value: U,
/// One of the two numbers to compare for equality.
#[implementations(f64, f32, u32, DVec2, &str)]
value: T,
/// The other of the two numbers to compare for equality.
#[implementations(f64, f32, u32, DVec2, &str)]
other_value: U,
) -> bool {
other_value == value
}
@ -360,8 +480,12 @@ fn equals<U: std::cmp::PartialEq<T>, T>(
#[node_macro::node(category("Math: Logic"))]
fn not_equals<U: std::cmp::PartialEq<T>, T>(
_: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] other_value: U,
/// One of the two numbers to compare for inequality.
#[implementations(f64, f32, u32, DVec2, &str)]
value: T,
/// The other of the two numbers to compare for inequality.
#[implementations(f64, f32, u32, DVec2, &str)]
other_value: U,
) -> bool {
other_value != value
}
@ -371,8 +495,13 @@ fn not_equals<U: std::cmp::PartialEq<T>, T>(
#[node_macro::node(category("Math: Logic"))]
fn less_than<T: std::cmp::PartialOrd<T>>(
_: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
/// The number on the left-hand side of the comparison.
#[implementations(f64, f32, u32)]
value: T,
/// The number on the right-hand side of the comparison.
#[implementations(f64, f32, u32)]
other_value: T,
/// Uses the less-than-or-equal operation (<=) instead of the less-than operation (<).
or_equal: bool,
) -> bool {
if or_equal { value <= other_value } else { value < other_value }
@ -383,8 +512,13 @@ fn less_than<T: std::cmp::PartialOrd<T>>(
#[node_macro::node(category("Math: Logic"))]
fn greater_than<T: std::cmp::PartialOrd<T>>(
_: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
/// The number on the left-hand side of the comparison.
#[implementations(f64, f32, u32)]
value: T,
/// The number on the right-hand side of the comparison.
#[implementations(f64, f32, u32)]
other_value: T,
/// Uses the greater-than-or-equal operation (>=) instead of the greater-than operation (>).
or_equal: bool,
) -> bool {
if or_equal { value >= other_value } else { value > other_value }
@ -392,19 +526,35 @@ fn greater_than<T: std::cmp::PartialOrd<T>>(
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
#[node_macro::node(category("Math: Logic"))]
fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool {
fn logical_or(
_: impl Ctx,
/// One of the two boolean values, either of which may be true for the node to output true.
value: bool,
/// The other of the two boolean values, either of which may be true for the node to output true.
other_value: bool,
) -> bool {
value || other_value
}
/// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false.
#[node_macro::node(category("Math: Logic"))]
fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool {
fn logical_and(
_: impl Ctx,
/// One of the two boolean values, both of which must be true for the node to output true.
value: bool,
/// The other of the two boolean values, both of which must be true for the node to output true.
other_value: bool,
) -> bool {
value && other_value
}
/// The logical not operation (!) reverses true and false value of the input.
#[node_macro::node(category("Math: Logic"))]
fn logical_not(_: impl Ctx, input: bool) -> bool {
fn logical_not(
_: impl Ctx,
/// The boolean value to be reversed.
input: bool,
) -> bool {
!input
}

View file

@ -56,7 +56,7 @@ impl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {
}
#[node_macro::node(skip_impl)]
fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
fn brush_stamp_generator(#[unit(" px")] diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
// Diameter
let radius = diameter / 2.;