mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Refactor the node macro and simply most of the node implementations (#1942)
* Add support structure for new node macro to gcore * Fix compile issues and code generation * Implement new node_fn macro * Implement property translation * Fix NodeIO type generation * Start translating math nodes * Move node implementation to outer scope to allow usage of local imports * Add expose attribute to allow controlling the parameter exposure * Add rust analyzer support for #[implementations] attribute * Migrate logic nodes * Handle where clause properly * Implement argument ident pattern preservation * Implement adjustment layer mapping * Fix node registry types * Fix module paths * Improve demo artwork comptibility * Improve macro error reporting * Fix handling of impl node implementations * Fix nodeio type computation * Fix opacity node and graph type resolution * Fix loading of demo artworks * Fix eslint * Fix typo in macro test * Remove node definitions for Adjustment Nodes * Fix type alias property generation and make adjustments footprint aware * Convert vector nodes * Implement path overrides * Fix stroke node * Fix painted dreams * Implement experimental type level specialization * Fix poisson disk sampling -> all demo artworks should work again * Port text node + make node macro more robust by implementing lifetime substitution * Fix vector node tests * Fix red dress demo + ci * Fix clippy warnings * Code review * Fix primary input issues * Improve math nodes and audit others * Set no_properties when no automatic properties are derived * Port vector generator nodes (could not derive all definitions yet) * Various QA changes and add min/max/mode_range to number parameters * Add min and max for f64 and u32 * Convert gpu nodes and clean up unused nodes * Partially port transform node * Allow implementations on call arg * Port path modify node * Start porting graphic element nodes * Transform nodes in graphic_element.rs * Port brush node * Port nodes in wasm_executior * Rename node macro * Fix formatting * Fix Mandelbrot node * Formatting * Fix Load Image and Load Resource nodes, add scope input to node macro * Remove unnecessary underscores * Begin attemping to make nodes resolution-aware * Infer a generic manual compositon type on generic call arg * Various fixes and work towards merging * Final changes for merge! * Fix tests, probably * More free line removals! --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::{Node, NodeMut};
|
||||
use crate::Node;
|
||||
pub struct FnNode<T: Fn(I) -> O, I, O>(T, PhantomData<(I, O)>);
|
||||
|
||||
impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode<T, I, O> {
|
||||
@@ -15,31 +15,3 @@ impl<T: Fn(I) -> O, I, O> FnNode<T, I, O> {
|
||||
FnNode(f, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnMutNode<T: FnMut(I) -> O, I, O>(T, PhantomData<(I, O)>);
|
||||
|
||||
impl<'i, T: FnMut(I) -> O + 'i, O: 'i, I: 'i> NodeMut<'i, I> for FnMutNode<T, I, O> {
|
||||
type MutOutput = O;
|
||||
fn eval_mut(&'i mut self, input: I) -> Self::MutOutput {
|
||||
self.0(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, T: FnMut(I) -> O + 'i, I: 'i, O: 'i> FnMutNode<T, I, O> {
|
||||
pub fn new(f: T) -> Self {
|
||||
FnMutNode(f, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FnNodeWithState<'i, T: Fn(I, &'i State) -> O, I, O, State: 'i>(T, State, PhantomData<(&'i O, I)>);
|
||||
impl<'i, I: 'i, O: 'i, State, T: Fn(I, &'i State) -> O + 'i> Node<'i, I> for FnNodeWithState<'i, T, I, O, State> {
|
||||
type Output = O;
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
(self.0)(input, &self.1)
|
||||
}
|
||||
}
|
||||
impl<'i, I, O, State, T: Fn(I, &'i State) -> O> FnNodeWithState<'i, T, I, O, State> {
|
||||
pub fn new(f: T, state: State) -> Self {
|
||||
FnNodeWithState(f, state, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use crate::application_io::TextureFrame;
|
||||
use crate::raster::{BlendMode, ImageFrame};
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut};
|
||||
use crate::uuid::NodeId;
|
||||
use crate::vector::VectorData;
|
||||
use crate::{Color, Node};
|
||||
use crate::Color;
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use node_macro::node_fn;
|
||||
use dyn_any::DynAny;
|
||||
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use glam::{DAffine2, IVec2};
|
||||
@@ -227,27 +226,20 @@ impl ArtboardGroup {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn add_artboard(&mut self, artboard: Artboard, node_id: Option<NodeId>) {
|
||||
fn append_artboard(&mut self, artboard: Artboard, node_id: Option<NodeId>) {
|
||||
self.artboards.push((artboard, node_id));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstructLayerNode<Stack, GraphicElement, NodePath> {
|
||||
stack: Stack,
|
||||
graphic_element: GraphicElement,
|
||||
node_path: NodePath,
|
||||
}
|
||||
|
||||
#[node_fn(ConstructLayerNode)]
|
||||
async fn construct_layer<Data: Into<GraphicElement> + Send>(
|
||||
footprint: crate::transform::Footprint,
|
||||
mut stack: impl Node<crate::transform::Footprint, Output = GraphicGroup>,
|
||||
graphic_element: impl Node<crate::transform::Footprint, Output = Data>,
|
||||
#[node_macro::node(category(""))]
|
||||
async fn layer<F: 'n + Copy + Send>(
|
||||
#[implementations((), Footprint)] footprint: F,
|
||||
#[implementations(((), GraphicGroup), (Footprint, GraphicGroup))] stack: impl Node<F, Output = GraphicGroup>,
|
||||
#[implementations(((), GraphicElement), (Footprint, GraphicElement))] graphic_element: impl Node<F, Output = GraphicElement>,
|
||||
node_path: Vec<NodeId>,
|
||||
) -> GraphicGroup {
|
||||
let graphic_element = self.graphic_element.eval(footprint).await;
|
||||
let mut stack = self.stack.eval(footprint).await;
|
||||
let mut element: GraphicElement = graphic_element.into();
|
||||
let mut element = graphic_element.eval(footprint).await;
|
||||
let mut stack = stack.eval(footprint).await;
|
||||
if stack.transform.matrix2.determinant() != 0. {
|
||||
*element.transform_mut() = stack.transform.inverse() * element.transform();
|
||||
} else {
|
||||
@@ -261,41 +253,54 @@ async fn construct_layer<Data: Into<GraphicElement> + Send>(
|
||||
stack
|
||||
}
|
||||
|
||||
pub struct ToGraphicElementNode {}
|
||||
|
||||
#[node_fn(ToGraphicElementNode)]
|
||||
fn to_graphic_element<Data: Into<GraphicElement>>(data: Data) -> GraphicElement {
|
||||
data.into()
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn to_element<F: 'n + Send, Data: Into<GraphicElement> + 'n>(
|
||||
#[implementations((), (), (), (), Footprint)] footprint: F,
|
||||
#[implementations(
|
||||
((), VectorData),
|
||||
((), ImageFrame<Color>),
|
||||
((), GraphicGroup),
|
||||
((), TextureFrame),
|
||||
(Footprint, VectorData),
|
||||
(Footprint, ImageFrame<Color>),
|
||||
(Footprint, GraphicGroup),
|
||||
(Footprint, TextureFrame),
|
||||
)]
|
||||
data: impl Node<F, Output = Data>,
|
||||
) -> GraphicElement {
|
||||
data.eval(footprint).await.into()
|
||||
}
|
||||
|
||||
pub struct ToGraphicGroupNode {}
|
||||
|
||||
#[node_fn(ToGraphicGroupNode)]
|
||||
fn to_graphic_group<Data: Into<GraphicGroup>>(data: Data) -> GraphicGroup {
|
||||
data.into()
|
||||
#[node_macro::node(category("General"))]
|
||||
async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
|
||||
#[implementations((), (), (), (), Footprint)] footprint: F,
|
||||
#[implementations(
|
||||
((), VectorData),
|
||||
((), ImageFrame<Color>),
|
||||
((), GraphicGroup),
|
||||
((), TextureFrame),
|
||||
(Footprint, VectorData),
|
||||
(Footprint, ImageFrame<Color>),
|
||||
(Footprint, GraphicGroup),
|
||||
(Footprint, TextureFrame),
|
||||
)]
|
||||
element: impl Node<F, Output = Data>,
|
||||
) -> GraphicGroup {
|
||||
element.eval(footprint).await.into()
|
||||
}
|
||||
|
||||
pub struct ConstructArtboardNode<Contents, Label, Location, Dimensions, Background, Clip> {
|
||||
contents: Contents,
|
||||
label: Label,
|
||||
location: Location,
|
||||
dimensions: Dimensions,
|
||||
background: Background,
|
||||
clip: Clip,
|
||||
}
|
||||
|
||||
#[node_fn(ConstructArtboardNode)]
|
||||
async fn construct_artboard(
|
||||
mut footprint: Footprint,
|
||||
contents: impl Node<Footprint, Output = GraphicGroup>,
|
||||
#[node_macro::node(category(""))]
|
||||
async fn to_artboard<F: 'n + Copy + Send + ApplyTransform>(
|
||||
#[implementations((), Footprint)] mut footprint: F,
|
||||
#[implementations(((), GraphicGroup), (Footprint, GraphicGroup))] contents: impl Node<F, Output = GraphicGroup>,
|
||||
label: String,
|
||||
location: IVec2,
|
||||
dimensions: IVec2,
|
||||
background: Color,
|
||||
clip: bool,
|
||||
) -> Artboard {
|
||||
footprint.transform *= DAffine2::from_translation(location.as_dvec2());
|
||||
let graphic_group = self.contents.eval(footprint).await;
|
||||
footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2()));
|
||||
let graphic_group = contents.eval(footprint).await;
|
||||
|
||||
Artboard {
|
||||
graphic_group,
|
||||
@@ -306,25 +311,19 @@ async fn construct_artboard(
|
||||
clip,
|
||||
}
|
||||
}
|
||||
pub struct AddArtboardNode<ArtboardGroup, Artboard, NodePath> {
|
||||
artboards: ArtboardGroup,
|
||||
artboard: Artboard,
|
||||
node_path: NodePath,
|
||||
}
|
||||
|
||||
#[node_fn(AddArtboardNode)]
|
||||
async fn add_artboard<Data: Into<Artboard> + Send>(
|
||||
footprint: Footprint,
|
||||
artboards: impl Node<Footprint, Output = ArtboardGroup>,
|
||||
artboard: impl Node<Footprint, Output = Data>,
|
||||
#[node_macro::node(category(""))]
|
||||
async fn append_artboard<F: 'n + Copy + Send>(
|
||||
#[implementations((), Footprint)] footprint: F,
|
||||
#[implementations(((), ArtboardGroup), (Footprint, ArtboardGroup))] artboards: impl Node<F, Output = ArtboardGroup>,
|
||||
#[implementations(((), Artboard), (Footprint, Artboard))] artboard: impl Node<F, Output = Artboard>,
|
||||
node_path: Vec<NodeId>,
|
||||
) -> ArtboardGroup {
|
||||
let artboard = self.artboard.eval(footprint).await;
|
||||
let mut artboards = self.artboards.eval(footprint).await;
|
||||
let artboard = artboard.eval(footprint).await;
|
||||
let mut artboards = artboards.eval(footprint).await;
|
||||
|
||||
// Get the penultimate element of the node path, or None if the path is too short
|
||||
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||
artboards.add_artboard(artboard.into(), encapsulating_node_id);
|
||||
artboards.append_artboard(artboard, encapsulating_node_id);
|
||||
|
||||
artboards
|
||||
}
|
||||
@@ -369,6 +368,7 @@ trait ToGraphicElement: Into<GraphicElement> {}
|
||||
|
||||
impl ToGraphicElement for VectorData {}
|
||||
impl ToGraphicElement for ImageFrame<Color> {}
|
||||
impl ToGraphicElement for TextureFrame {}
|
||||
|
||||
impl<T> From<T> for GraphicGroup
|
||||
where
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::Raster;
|
||||
use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup};
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
|
||||
use base64::Engine;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
#[cfg(feature = "alloc")]
|
||||
use core::future::Future;
|
||||
|
||||
#[cfg_attr(feature = "log", macro_use)]
|
||||
#[cfg(feature = "log")]
|
||||
extern crate log;
|
||||
pub use crate as graphene_core;
|
||||
|
||||
#[cfg(feature = "reflections")]
|
||||
pub use ctor;
|
||||
|
||||
pub mod consts;
|
||||
pub mod generic;
|
||||
pub mod logic;
|
||||
@@ -24,7 +29,6 @@ pub mod gpu;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod memo;
|
||||
pub mod storage;
|
||||
|
||||
pub mod raster;
|
||||
#[cfg(feature = "alloc")]
|
||||
@@ -40,7 +44,8 @@ pub mod vector;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub mod application_io;
|
||||
|
||||
pub mod quantization;
|
||||
#[cfg(feature = "reflections")]
|
||||
pub mod registry;
|
||||
|
||||
use core::any::TypeId;
|
||||
pub use memo::MemoHash;
|
||||
@@ -68,32 +73,6 @@ pub trait Node<'i, Input: 'i>: 'i {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NodeMut<'i, Input: 'i>: 'i {
|
||||
type MutOutput: 'i;
|
||||
fn eval_mut(&'i mut self, input: Input) -> Self::MutOutput;
|
||||
}
|
||||
|
||||
pub trait NodeOnce<'i, Input>
|
||||
where
|
||||
Input: 'i,
|
||||
{
|
||||
type OnceOutput: 'i;
|
||||
fn eval_once(self, input: Input) -> Self::OnceOutput;
|
||||
}
|
||||
|
||||
impl<'i, T: Node<'i, I>, I: 'i> NodeOnce<'i, I> for &'i T {
|
||||
type OnceOutput = T::Output;
|
||||
fn eval_once(self, input: I) -> Self::OnceOutput {
|
||||
(self).eval(input)
|
||||
}
|
||||
}
|
||||
impl<'i, T: Node<'i, I> + ?Sized, I: 'i> NodeMut<'i, I> for &'i T {
|
||||
type MutOutput = T::Output;
|
||||
fn eval_mut(&'i mut self, input: I) -> Self::MutOutput {
|
||||
(*self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
mod types;
|
||||
#[cfg(feature = "alloc")]
|
||||
@@ -124,6 +103,19 @@ where
|
||||
parameters,
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
fn to_async_node_io(&self, parameters: Vec<Type>) -> NodeIOTypes
|
||||
where
|
||||
<Self::Output as Future>::Output: StaticTypeSized,
|
||||
Self::Output: Future,
|
||||
{
|
||||
NodeIOTypes {
|
||||
input: concrete!(<Input as StaticTypeSized>::Static),
|
||||
// TODO return actual future type
|
||||
output: concrete!(<<Self::Output as Future>::Output as StaticTypeSized>::Static),
|
||||
parameters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N
|
||||
|
||||
@@ -1,45 +1,12 @@
|
||||
use crate::Node;
|
||||
|
||||
pub struct LogToConsoleNode;
|
||||
|
||||
#[node_macro::node_fn(LogToConsoleNode)]
|
||||
fn log_to_console<T: core::fmt::Debug>(value: T) -> T {
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn log_to_console<T: core::fmt::Debug>(
|
||||
_: (),
|
||||
#[default("Not connected to value yet")]
|
||||
#[implementations(String, bool, f64, f64, u32, u64, glam::DVec2, crate::vector::VectorData, glam::DAffine2)]
|
||||
value: T,
|
||||
) -> T {
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
debug!("{value:#?}");
|
||||
value
|
||||
}
|
||||
|
||||
pub struct LogicOrNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(LogicOrNode)]
|
||||
fn logic_or(first: bool, second: bool) -> bool {
|
||||
first || second
|
||||
}
|
||||
|
||||
pub struct LogicAndNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(LogicAndNode)]
|
||||
fn logic_and(first: bool, second: bool) -> bool {
|
||||
first && second
|
||||
}
|
||||
|
||||
pub struct LogicXorNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(LogicXorNode)]
|
||||
fn logic_xor(first: bool, second: bool) -> bool {
|
||||
first ^ second
|
||||
}
|
||||
|
||||
pub struct LogicNotNode;
|
||||
|
||||
#[node_macro::node_fn(LogicNotNode)]
|
||||
fn logic_not(first: bool) -> bool {
|
||||
!first
|
||||
}
|
||||
|
||||
@@ -1,324 +1,308 @@
|
||||
use crate::registry::types::Percentage;
|
||||
use crate::Node;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::{Add, Div, Mul, Rem, Sub};
|
||||
use num_traits::Pow;
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
#[cfg(target_arch = "spirv")]
|
||||
use spirv_std::num_traits::float::Float;
|
||||
|
||||
// Add Pair
|
||||
// TODO: Delete this redundant (two-argument version of the) add node. It's only used in tests.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct AddPairNode;
|
||||
impl<'i, L: Add<R, Output = O> + 'i, R: 'i, O: 'i> Node<'i, (L, R)> for AddPairNode {
|
||||
type Output = <L as Add<R>>::Output;
|
||||
fn eval(&'i self, input: (L, R)) -> Self::Output {
|
||||
input.0 + input.1
|
||||
}
|
||||
}
|
||||
impl AddPairNode {
|
||||
pub const fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
// Add
|
||||
pub struct AddNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
#[node_macro::node_fn(AddNode)]
|
||||
fn add_parameter<U, T>(first: U, second: T) -> <U as Add<T>>::Output
|
||||
where
|
||||
U: Add<T>,
|
||||
{
|
||||
first + second
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn add<U: Add<T>, T>(
|
||||
_: (),
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, glam::DVec2)] augend: U,
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, glam::DVec2)] addend: T,
|
||||
) -> <U as Add<T>>::Output {
|
||||
augend + addend
|
||||
}
|
||||
|
||||
// Subtract
|
||||
pub struct SubtractNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
#[node_macro::node_fn(SubtractNode)]
|
||||
fn sub<U, T>(first: U, second: T) -> <U as Sub<T>>::Output
|
||||
where
|
||||
U: Sub<T>,
|
||||
{
|
||||
first - second
|
||||
}
|
||||
|
||||
// Divide
|
||||
pub struct DivideNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
#[node_macro::node_fn(DivideNode)]
|
||||
fn div<U, T>(first: U, second: T) -> <U as Div<T>>::Output
|
||||
where
|
||||
U: Div<T>,
|
||||
{
|
||||
first / second
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn subtract<U: Sub<T>, T>(
|
||||
_: (),
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, glam::DVec2)] minuend: U,
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, glam::DVec2)] subtrahend: T,
|
||||
) -> <U as Sub<T>>::Output {
|
||||
minuend - subtrahend
|
||||
}
|
||||
|
||||
// Multiply
|
||||
pub struct MultiplyNode<Second> {
|
||||
second: Second,
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn multiply<U: Mul<T>, T>(
|
||||
_: (),
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, glam::DVec2, f64)] multiplier: U,
|
||||
#[default(1.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, glam::DVec2, glam::DVec2)]
|
||||
multiplicand: T,
|
||||
) -> <U as Mul<T>>::Output {
|
||||
multiplier * multiplicand
|
||||
}
|
||||
#[node_macro::node_fn(MultiplyNode)]
|
||||
fn mul<U, T>(first: U, second: T) -> <U as Mul<T>>::Output
|
||||
where
|
||||
U: Mul<T>,
|
||||
{
|
||||
first * second
|
||||
|
||||
// Divide
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn divide<U: Div<T>, T>(
|
||||
_: (),
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, glam::DVec2, glam::DVec2)] numerator: U,
|
||||
#[default(1.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, glam::DVec2, f64)]
|
||||
denominator: T,
|
||||
) -> <U as Div<T>>::Output {
|
||||
numerator / denominator
|
||||
}
|
||||
|
||||
// Modulo
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn modulo<U: Rem<T>, T>(
|
||||
_: (),
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] numerator: U,
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
|
||||
modulus: T,
|
||||
) -> <U as Rem<T>>::Output {
|
||||
numerator % modulus
|
||||
}
|
||||
|
||||
// Exponent
|
||||
pub struct ExponentNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
#[node_macro::node_fn(ExponentNode)]
|
||||
fn exp<U, T>(first: U, second: T) -> <U as Pow<T>>::Output
|
||||
where
|
||||
U: Pow<T>,
|
||||
{
|
||||
first.pow(second)
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn exponent<U: Pow<T>, T>(
|
||||
_: (),
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, )] base: U,
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
|
||||
power: T,
|
||||
) -> <U as num_traits::Pow<T>>::Output {
|
||||
base.pow(power)
|
||||
}
|
||||
|
||||
// Floor
|
||||
pub struct FloorNode;
|
||||
#[node_macro::node_fn(FloorNode)]
|
||||
fn floor(input: f64) -> f64 {
|
||||
input.floor()
|
||||
}
|
||||
|
||||
// Ceil
|
||||
pub struct CeilingNode;
|
||||
#[node_macro::node_fn(CeilingNode)]
|
||||
fn ceil(input: f64) -> f64 {
|
||||
input.ceil()
|
||||
}
|
||||
|
||||
// Round
|
||||
pub struct RoundNode;
|
||||
#[node_macro::node_fn(RoundNode)]
|
||||
fn round(input: f64) -> f64 {
|
||||
input.round()
|
||||
}
|
||||
|
||||
// Absolute Value
|
||||
pub struct AbsoluteValue;
|
||||
#[node_macro::node_fn(AbsoluteValue)]
|
||||
fn abs(input: f64) -> f64 {
|
||||
input.abs()
|
||||
}
|
||||
|
||||
// Log
|
||||
pub struct LogarithmNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
#[node_macro::node_fn(LogarithmNode)]
|
||||
fn ln<U: num_traits::float::Float>(first: U, second: U) -> U {
|
||||
first.log(second)
|
||||
}
|
||||
|
||||
// Natural Log
|
||||
pub struct NaturalLogarithmNode;
|
||||
#[node_macro::node_fn(NaturalLogarithmNode)]
|
||||
fn ln(input: f64) -> f64 {
|
||||
input.ln()
|
||||
}
|
||||
|
||||
// Sine
|
||||
pub struct SineNode;
|
||||
#[node_macro::node_fn(SineNode)]
|
||||
fn ln(input: f64) -> f64 {
|
||||
input.sin()
|
||||
}
|
||||
|
||||
// Cosine
|
||||
pub struct CosineNode;
|
||||
#[node_macro::node_fn(CosineNode)]
|
||||
fn ln(input: f64) -> f64 {
|
||||
input.cos()
|
||||
}
|
||||
|
||||
// Tangent
|
||||
pub struct TangentNode;
|
||||
#[node_macro::node_fn(TangentNode)]
|
||||
fn ln(input: f64) -> f64 {
|
||||
input.tan()
|
||||
}
|
||||
|
||||
// Min
|
||||
pub struct MinimumNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
#[node_macro::node_fn(MinimumNode)]
|
||||
fn min<T: core::cmp::PartialOrd>(first: T, second: T) -> T {
|
||||
match first < second {
|
||||
true => first,
|
||||
false => second,
|
||||
// Root
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn root<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
radicand: U,
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
degree: U,
|
||||
) -> U {
|
||||
if degree == U::from(2.).unwrap() {
|
||||
radicand.sqrt()
|
||||
} else if degree == U::from(3.).unwrap() {
|
||||
radicand.cbrt()
|
||||
} else {
|
||||
radicand.powf(U::from(1.).unwrap() / degree)
|
||||
}
|
||||
}
|
||||
|
||||
// Maxi
|
||||
pub struct MaximumNode<Second> {
|
||||
second: Second,
|
||||
// Logarithm
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn logarithm<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
#[implementations(f64, f32)] value: U,
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
base: U,
|
||||
) -> U {
|
||||
if base == U::from(2.).unwrap() {
|
||||
value.log2()
|
||||
} else if base == U::from(10.).unwrap() {
|
||||
value.log10()
|
||||
} else if base - U::from(std::f64::consts::E).unwrap() < U::epsilon() * U::from(1e6).unwrap() {
|
||||
value.ln()
|
||||
} else {
|
||||
value.log(base)
|
||||
}
|
||||
}
|
||||
#[node_macro::node_fn(MaximumNode)]
|
||||
fn max<T: core::cmp::PartialOrd>(first: T, second: T) -> T {
|
||||
match first > second {
|
||||
true => first,
|
||||
false => second,
|
||||
|
||||
// Sine
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn sine(_: (), theta: f64) -> f64 {
|
||||
theta.sin()
|
||||
}
|
||||
|
||||
// Cosine
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn cosine(_: (), theta: f64) -> f64 {
|
||||
theta.cos()
|
||||
}
|
||||
|
||||
// Tangent
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn tangent(_: (), theta: f64) -> f64 {
|
||||
theta.tan()
|
||||
}
|
||||
|
||||
// Random
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn random(_: (), _primary: (), seed: u64, min: f64, #[default(1.)] max: f64) -> f64 {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
|
||||
let result = rng.gen::<f64>();
|
||||
let (min, max) = if min < max { (min, max) } else { (max, min) };
|
||||
result * (max - min) + min
|
||||
}
|
||||
|
||||
// Round
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn round(_: (), value: f64) -> f64 {
|
||||
value.round()
|
||||
}
|
||||
|
||||
// Floor
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn floor(_: (), value: f64) -> f64 {
|
||||
value.floor()
|
||||
}
|
||||
|
||||
// Ceiling
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn ceiling(_: (), value: f64) -> f64 {
|
||||
value.ceil()
|
||||
}
|
||||
|
||||
// Absolute Value
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn absolute_value(_: (), value: f64) -> f64 {
|
||||
value.abs()
|
||||
}
|
||||
|
||||
// Min
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn min<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
|
||||
match value < other_value {
|
||||
true => value,
|
||||
false => other_value,
|
||||
}
|
||||
}
|
||||
|
||||
// Max
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn max<T: core::cmp::PartialOrd>(_: (), #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
|
||||
match value > other_value {
|
||||
true => value,
|
||||
false => other_value,
|
||||
}
|
||||
}
|
||||
|
||||
// Equals
|
||||
pub struct EqualsNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
#[node_macro::node_fn(EqualsNode)]
|
||||
fn eq<T: core::cmp::PartialEq>(first: T, second: T) -> bool {
|
||||
first == second
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn equals<U: core::cmp::PartialEq<T>, T>(
|
||||
_: (),
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)]
|
||||
#[min(100.)]
|
||||
#[max(200.)]
|
||||
other_value: U,
|
||||
) -> bool {
|
||||
other_value == value
|
||||
}
|
||||
|
||||
// Modulo
|
||||
pub struct ModuloNode<Second> {
|
||||
second: Second,
|
||||
}
|
||||
#[node_macro::node_fn(ModuloNode)]
|
||||
fn modulo<U, T>(first: U, second: T) -> <U as Rem<T>>::Output
|
||||
where
|
||||
U: Rem<T>,
|
||||
{
|
||||
first % second
|
||||
// Logical Or
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_or(_: (), value: bool, other_value: bool) -> bool {
|
||||
value || other_value
|
||||
}
|
||||
|
||||
pub struct ConstructVector2<X, Y> {
|
||||
x: X,
|
||||
y: Y,
|
||||
// Logical And
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_and(_: (), value: bool, other_value: bool) -> bool {
|
||||
value && other_value
|
||||
}
|
||||
#[node_macro::node_fn(ConstructVector2)]
|
||||
fn construct_vector2(_primary: (), x: f64, y: f64) -> glam::DVec2 {
|
||||
|
||||
// Logical Xor
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_xor(_: (), value: bool, other_value: bool) -> bool {
|
||||
value ^ other_value
|
||||
}
|
||||
|
||||
// Logical Not
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_not(_: (), input: bool) -> bool {
|
||||
!input
|
||||
}
|
||||
|
||||
// Bool Value
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn bool_value(_: (), _primary: (), #[name("Bool")] bool_value: bool) -> bool {
|
||||
bool_value
|
||||
}
|
||||
|
||||
// Number Value
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn number_value(_: (), _primary: (), number: f64) -> f64 {
|
||||
number
|
||||
}
|
||||
|
||||
// Percentage Value
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn percentage_value(_: (), _primary: (), percentage: Percentage) -> f64 {
|
||||
percentage
|
||||
}
|
||||
|
||||
// Vector2 Value
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> glam::DVec2 {
|
||||
glam::DVec2::new(x, y)
|
||||
}
|
||||
|
||||
// TODO: Make it possible to give Color::BLACK instead of 000000ff as the default
|
||||
// Color Value
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn color_value(_: (), _primary: (), #[default(000000ff)] color: crate::Color) -> crate::Color {
|
||||
color
|
||||
}
|
||||
|
||||
// Gradient Value
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn gradient_value(_: (), _primary: (), gradient: crate::vector::style::GradientStops) -> crate::vector::style::GradientStops {
|
||||
gradient
|
||||
}
|
||||
|
||||
// Color Channel Value
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn color_channel_value(_: (), _primary: (), color_channel: crate::raster::adjustments::RedGreenBlue) -> crate::raster::adjustments::RedGreenBlue {
|
||||
color_channel
|
||||
}
|
||||
|
||||
// Blend Mode Value
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn blend_mode_value(_: (), _primary: (), blend_mode: crate::raster::BlendMode) -> crate::raster::BlendMode {
|
||||
blend_mode
|
||||
}
|
||||
|
||||
// Size Of
|
||||
#[cfg(feature = "std")]
|
||||
pub struct SizeOfNode;
|
||||
#[cfg(feature = "std")]
|
||||
#[node_macro::node_fn(SizeOfNode)]
|
||||
fn flat_map(ty: crate::Type) -> Option<usize> {
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn size_of(_: (), ty: crate::Type) -> Option<usize> {
|
||||
ty.size()
|
||||
}
|
||||
|
||||
// Some
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct SomeNode;
|
||||
#[node_macro::node_fn(SomeNode)]
|
||||
fn some<T>(input: T) -> Option<T> {
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn some<T>(_: (), #[implementations(f64, f32, u32, u64, String, crate::Color)] input: T) -> Option<T> {
|
||||
Some(input)
|
||||
}
|
||||
|
||||
// Unwrap
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct UnwrapNode;
|
||||
#[node_macro::node_fn(UnwrapNode)]
|
||||
fn some<T: Default>(input: Option<T>) -> T {
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn unwrap<T: Default>(_: (), #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>, Option<crate::Color>)] input: Option<T>) -> T {
|
||||
input.unwrap_or_default()
|
||||
}
|
||||
|
||||
// Clone
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct CloneNode<O>(PhantomData<O>);
|
||||
impl<'i, 'n: 'i, O: Clone + 'i> Node<'i, &'n O> for CloneNode<O> {
|
||||
type Output = O;
|
||||
fn eval(&'i self, input: &'i O) -> Self::Output {
|
||||
input.clone()
|
||||
}
|
||||
}
|
||||
impl<O> CloneNode<O> {
|
||||
pub const fn new() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
// First of Pair
|
||||
/// Return the first element of a 2-tuple
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct FirstOfPairNode;
|
||||
impl<'i, L: 'i, R: 'i> Node<'i, (L, R)> for FirstOfPairNode {
|
||||
type Output = L;
|
||||
fn eval(&'i self, input: (L, R)) -> Self::Output {
|
||||
input.0
|
||||
}
|
||||
}
|
||||
impl FirstOfPairNode {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
// Second of Pair
|
||||
/// Return the second element of a 2-tuple
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct SecondOfPairNode;
|
||||
impl<'i, L: 'i, R: 'i> Node<'i, (L, R)> for SecondOfPairNode {
|
||||
type Output = R;
|
||||
fn eval(&'i self, input: (L, R)) -> Self::Output {
|
||||
input.1
|
||||
}
|
||||
}
|
||||
impl SecondOfPairNode {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
// Swap Pair
|
||||
/// Return a new 2-tuple with the elements reversed
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct SwapPairNode;
|
||||
impl<'i, L: 'i, R: 'i> Node<'i, (L, R)> for SwapPairNode {
|
||||
type Output = (R, L);
|
||||
fn eval(&'i self, input: (L, R)) -> Self::Output {
|
||||
(input.1, input.0)
|
||||
}
|
||||
}
|
||||
impl SwapPairNode {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
// Make Pair
|
||||
/// Return a 2-tuple with two duplicates of the input argument
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct MakePairNode;
|
||||
impl<'i, O: Clone + 'i> Node<'i, O> for MakePairNode {
|
||||
type Output = (O, O);
|
||||
fn eval(&'i self, input: O) -> Self::Output {
|
||||
(input.clone(), input)
|
||||
}
|
||||
}
|
||||
impl MakePairNode {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&crate::raster::ImageFrame<crate::Color>)] value: &'i T) -> T {
|
||||
value.clone()
|
||||
}
|
||||
|
||||
// Identity
|
||||
/// Return the input argument unchanged
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct IdentityNode;
|
||||
impl<'i, O: 'i> Node<'i, O> for IdentityNode {
|
||||
type Output = O;
|
||||
fn eval(&'i self, input: O) -> Self::Output {
|
||||
input
|
||||
}
|
||||
}
|
||||
impl IdentityNode {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
// TODO: Rename to "Passthrough"
|
||||
/// The identity function returns the input argument unchanged.
|
||||
#[node_macro::node(skip_impl)]
|
||||
fn identity<'i, T: 'i>(value: T) -> T {
|
||||
value
|
||||
}
|
||||
|
||||
// Type
|
||||
@@ -332,6 +316,14 @@ where
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
self.0.eval(input)
|
||||
}
|
||||
|
||||
fn reset(&self) {
|
||||
self.0.reset();
|
||||
}
|
||||
|
||||
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
|
||||
self.0.serialize()
|
||||
}
|
||||
}
|
||||
impl<'i, N: for<'a> Node<'a, I>, I: 'i> TypeNode<N, I, <N as Node<'i, I>>::Output> {
|
||||
pub fn new(node: N) -> Self {
|
||||
@@ -345,62 +337,15 @@ impl<'i, N: for<'a> Node<'a, I> + Clone, I: 'i> Clone for TypeNode<N, I, <N as N
|
||||
}
|
||||
impl<'i, N: for<'a> Node<'a, I> + Copy, I: 'i> Copy for TypeNode<N, I, <N as Node<'i, I>>::Output> {}
|
||||
|
||||
// Map Option
|
||||
pub struct MapOptionNode<I, Mn> {
|
||||
node: Mn,
|
||||
_i: PhantomData<I>,
|
||||
}
|
||||
#[node_macro::node_fn(MapOptionNode<_I>)]
|
||||
fn map_option_node<_I, N>(input: Option<_I>, node: &'input N) -> Option<<N as Node<'input, _I>>::Output>
|
||||
where
|
||||
N: for<'a> Node<'a, _I>,
|
||||
{
|
||||
input.map(|x| node.eval(x))
|
||||
}
|
||||
|
||||
// Map Result
|
||||
pub struct MapResultNode<I, E, Mn> {
|
||||
node: Mn,
|
||||
_i: PhantomData<I>,
|
||||
_e: PhantomData<E>,
|
||||
}
|
||||
#[node_macro::node_fn(MapResultNode<_I, _E>)]
|
||||
fn map_result_node<_I, _E, N>(input: Result<_I, _E>, node: &'input N) -> Result<<N as Node<'input, _I>>::Output, _E>
|
||||
where
|
||||
N: for<'a> Node<'a, _I>,
|
||||
{
|
||||
input.map(|x| node.eval(x))
|
||||
}
|
||||
|
||||
// Flat Map Result
|
||||
pub struct FlatMapResultNode<I, O, E, Mn> {
|
||||
node: Mn,
|
||||
_i: PhantomData<I>,
|
||||
_o: PhantomData<O>,
|
||||
_e: PhantomData<E>,
|
||||
}
|
||||
#[node_macro::node_fn(FlatMapResultNode<_I, _O, _E>)]
|
||||
fn flat_map_node<_I, _O, _E, N>(input: Result<_I, _E>, node: &'input N) -> Result<_O, _E>
|
||||
where
|
||||
N: for<'a> Node<'a, _I, Output = Result<_O, _E>>,
|
||||
{
|
||||
match input.map(|x| node.eval(x)) {
|
||||
Ok(Ok(x)) => Ok(x),
|
||||
Ok(Err(e)) => Err(e),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
// Into
|
||||
pub struct IntoNode<I, O> {
|
||||
_i: PhantomData<I>,
|
||||
pub struct IntoNode<O> {
|
||||
_o: PhantomData<O>,
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
#[node_macro::node_fn(IntoNode<_I, _O>)]
|
||||
async fn into<_I, _O>(input: _I) -> _O
|
||||
#[node_macro::old_node_fn(IntoNode<_O>)]
|
||||
async fn into<I, _O>(input: I) -> _O
|
||||
where
|
||||
_I: Into<_O> + Sync + Send,
|
||||
I: Into<_O> + Sync + Send,
|
||||
{
|
||||
input.into()
|
||||
}
|
||||
@@ -410,86 +355,14 @@ mod test {
|
||||
use super::*;
|
||||
use crate::{generic::*, structural::*, value::*};
|
||||
|
||||
#[test]
|
||||
pub fn duplicate_node() {
|
||||
let value = ValueNode(4u32);
|
||||
let pair = ComposeNode::new(value, MakePairNode::new());
|
||||
assert_eq!(pair.eval(()), (&4, &4));
|
||||
}
|
||||
#[test]
|
||||
pub fn identity_node() {
|
||||
let value = ValueNode(4u32).then(IdentityNode::new());
|
||||
assert_eq!(value.eval(()), &4);
|
||||
}
|
||||
#[test]
|
||||
pub fn clone_node() {
|
||||
let cloned = ValueNode(4u32).then(CloneNode::new());
|
||||
assert_eq!(cloned.eval(()), 4);
|
||||
let type_erased = &CloneNode::new() as &dyn for<'a> Node<'a, &'a u32, Output = u32>;
|
||||
assert_eq!(type_erased.eval(&4), 4);
|
||||
let type_erased = &cloned as &dyn for<'a> Node<'a, (), Output = u32>;
|
||||
assert_eq!(type_erased.eval(()), 4);
|
||||
}
|
||||
#[test]
|
||||
pub fn first_node() {
|
||||
let first_of_pair = ValueNode((4u32, "a")).then(CloneNode::new()).then(FirstOfPairNode::new());
|
||||
assert_eq!(first_of_pair.eval(()), 4);
|
||||
}
|
||||
#[test]
|
||||
pub fn second_node() {
|
||||
let second_of_pair = ValueNode((4u32, "a")).then(CloneNode::new()).then(SecondOfPairNode::new());
|
||||
assert_eq!(second_of_pair.eval(()), "a");
|
||||
}
|
||||
#[test]
|
||||
pub fn object_safe() {
|
||||
let second_of_pair = ValueNode((4u32, "a")).then(CloneNode::new()).then(SecondOfPairNode::new());
|
||||
let foo = &second_of_pair as &dyn Node<(), Output = &str>;
|
||||
assert_eq!(foo.eval(()), "a");
|
||||
}
|
||||
#[test]
|
||||
pub fn map_result() {
|
||||
let value: ClonedNode<Result<&u32, ()>> = ClonedNode(Ok(&4u32));
|
||||
assert_eq!(value.eval(()), Ok(&4u32));
|
||||
// let type_erased_clone = clone as &dyn for<'a> Node<'a, &'a u32, Output = u32>;
|
||||
let map_result = MapResultNode::new(ValueNode::new(FnNode::new(|x: &u32| *x)));
|
||||
// let type_erased = &map_result as &dyn for<'a> Node<'a, Result<&'a u32, ()>, Output = Result<u32, ()>>;
|
||||
assert_eq!(map_result.eval(Ok(&4u32)), Ok(4u32));
|
||||
let fst = value.then(map_result);
|
||||
// let type_erased = &fst as &dyn for<'a> Node<'a, (), Output = Result<u32, ()>>;
|
||||
assert_eq!(fst.eval(()), Ok(4u32));
|
||||
}
|
||||
#[test]
|
||||
pub fn flat_map_result() {
|
||||
let fst = ValueNode(Ok(&4u32)).then(CloneNode::new());
|
||||
let fn_node: FnNode<_, &u32, Result<&u32, _>> = FnNode::new(|_| Err(8u32));
|
||||
assert_eq!(fn_node.eval(&4u32), Err(8u32));
|
||||
let flat_map = FlatMapResultNode::new(ValueNode::new(fn_node));
|
||||
let fst = fst.then(flat_map);
|
||||
assert_eq!(fst.eval(()), Err(8u32));
|
||||
}
|
||||
#[test]
|
||||
pub fn add_node() {
|
||||
let a = ValueNode(42u32);
|
||||
let b = ValueNode(6u32);
|
||||
let cons_a = ConsNode::new(a);
|
||||
let tuple = b.then(cons_a);
|
||||
|
||||
let sum = tuple.then(AddPairNode::new());
|
||||
|
||||
assert_eq!(sum.eval(()), 48);
|
||||
}
|
||||
#[test]
|
||||
pub fn foo() {
|
||||
fn int(_: (), state: &u32) -> u32 {
|
||||
*state
|
||||
}
|
||||
fn swap(input: (u32, u32)) -> (u32, u32) {
|
||||
(input.1, input.0)
|
||||
}
|
||||
let fnn = FnNode::new(&swap);
|
||||
let fns = FnNodeWithState::new(int, 42u32);
|
||||
let fnn = FnNode::new(|(a, b)| (b, a));
|
||||
assert_eq!(fnn.eval((1u32, 2u32)), (2, 1));
|
||||
let result: u32 = fns.eval(());
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
use crate::raster::{Color, Pixel};
|
||||
use crate::Node;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
|
||||
#[cfg(target_arch = "spirv")]
|
||||
use spirv_std::num_traits::Float;
|
||||
|
||||
#[derive(Clone, Copy, DynAny, PartialEq, Pod, Zeroable)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(C, align(16))]
|
||||
pub struct Quantization {
|
||||
pub a: f32,
|
||||
pub b: f32,
|
||||
pub bits: u32,
|
||||
_padding: u32,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Quantization {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Quantization").field("a", &self.a).field("b", &self.b()).field("bits", &self.bits()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Quantization {
|
||||
pub fn new(a: f32, b: f32, bits: u32) -> Self {
|
||||
Self { a, b, bits, _padding: 0 }
|
||||
}
|
||||
|
||||
pub fn a(&self) -> f32 {
|
||||
self.a
|
||||
}
|
||||
|
||||
pub fn b(&self) -> f32 {
|
||||
self.b
|
||||
}
|
||||
|
||||
pub fn bits(&self) -> u32 {
|
||||
self.bits
|
||||
}
|
||||
}
|
||||
|
||||
impl core::hash::Hash for Quantization {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.bits().hash(state);
|
||||
self.a().to_bits().hash(state);
|
||||
self.b().to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Quantization {
|
||||
fn default() -> Self {
|
||||
Self::new(1., 0., 8)
|
||||
}
|
||||
}
|
||||
|
||||
pub type QuantizationChannels = [Quantization; 4];
|
||||
#[repr(transparent)]
|
||||
#[derive(DynAny, Clone, Copy, Debug, PartialEq, Eq, Pod, Zeroable)]
|
||||
pub struct PackedPixel(pub u32);
|
||||
|
||||
impl Pixel for PackedPixel {}
|
||||
|
||||
/*
|
||||
#[inline(always)]
|
||||
fn quantize(value: f32, offset: u32, quantization: Quantization) -> u32 {
|
||||
let a = quantization.a();
|
||||
let bits = quantization.bits();
|
||||
let b = quantization.b();
|
||||
let value = (((a * value) * ((1 << bits) - 1) as f32) as i32 + b) as u32;
|
||||
value.checked_shl(32 - bits - offset).unwrap_or(0)
|
||||
}*/
|
||||
|
||||
#[inline(always)]
|
||||
fn quantize(value: f32, offset: u32, quantization: Quantization) -> u32 {
|
||||
let a = quantization.a();
|
||||
let b = quantization.b();
|
||||
let bits = quantization.bits();
|
||||
|
||||
// Calculate the quantized value
|
||||
// Scale the value by 'a' and the maximum quantization range
|
||||
let scaled_value = ((a * value) + b) * ((1 << bits) - 1) as f32;
|
||||
// Round the scaled value to the nearest integer
|
||||
let rounded_value = scaled_value.clamp(0., (1 << bits) as f32 - 1.) as u32;
|
||||
|
||||
// Shift the quantized value to the appropriate position based on the offset
|
||||
|
||||
rounded_value.checked_shl(32 - bits - offset).unwrap()
|
||||
}
|
||||
/*
|
||||
#[inline(always)]
|
||||
fn decode(value: u32, offset: u32, quantization: Quantization) -> f32 {
|
||||
let a = quantization.a();
|
||||
let bits = quantization.bits();
|
||||
let b = quantization.b();
|
||||
let value = (value << offset) >> (31 - bits);
|
||||
let value = value as i32 - b;
|
||||
(value as f32 / ((1 << bits) - 1) as f32) / a
|
||||
}*/
|
||||
|
||||
#[inline(always)]
|
||||
fn decode(value: u32, offset: u32, quantization: Quantization) -> f32 {
|
||||
let a = quantization.a();
|
||||
let bits = quantization.bits();
|
||||
let b = quantization.b();
|
||||
|
||||
// Shift the value to the appropriate position based on the offset
|
||||
let shifted_value = value.checked_shr(32 - bits - offset).unwrap();
|
||||
|
||||
// Unpack the quantized value
|
||||
let unpacked_value = shifted_value & ((1 << bits) - 1); // Mask out the unnecessary bits
|
||||
let normalized_value = unpacked_value as f32 / ((1 << bits) - 1) as f32; // Normalize the value based on the quantization range
|
||||
let decoded_value = normalized_value - b;
|
||||
|
||||
decoded_value / a
|
||||
}
|
||||
|
||||
pub struct QuantizeNode<Quantization> {
|
||||
quantization: Quantization,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(QuantizeNode)]
|
||||
fn quantize_fn<'a>(color: Color, quantization: [Quantization; 4]) -> PackedPixel {
|
||||
let quant = quantization;
|
||||
quantize_color(color, quant)
|
||||
}
|
||||
|
||||
pub fn quantize_color(color: Color, quant: [Quantization; 4]) -> PackedPixel {
|
||||
let mut offset = 0;
|
||||
let r = quantize(color.r(), offset, quant[0]);
|
||||
offset += quant[0].bits();
|
||||
let g = quantize(color.g(), offset, quant[1]);
|
||||
offset += quant[1].bits();
|
||||
let b = quantize(color.b(), offset, quant[2]);
|
||||
offset += quant[2].bits();
|
||||
let a = quantize(color.a(), offset, quant[3]);
|
||||
|
||||
PackedPixel(r | g | b | a)
|
||||
}
|
||||
|
||||
pub struct DeQuantizeNode<Quantization> {
|
||||
quantization: Quantization,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(DeQuantizeNode)]
|
||||
fn dequantize_fn<'a>(color: PackedPixel, quantization: [Quantization; 4]) -> Color {
|
||||
let quant = quantization;
|
||||
dequantize_color(color, quant)
|
||||
}
|
||||
|
||||
pub fn dequantize_color(color: PackedPixel, quant: [Quantization; 4]) -> Color {
|
||||
let mut offset = 0;
|
||||
let mut r = decode(color.0, offset, quant[0]);
|
||||
offset += quant[0].bits();
|
||||
let mut g = decode(color.0, offset, quant[1]);
|
||||
offset += quant[1].bits();
|
||||
let mut b = decode(color.0, offset, quant[2]);
|
||||
offset += quant[2].bits();
|
||||
let mut a = decode(color.0, offset, quant[3]);
|
||||
if a.is_nan() {
|
||||
a = 0.;
|
||||
}
|
||||
|
||||
if r.is_nan() {
|
||||
r = 0.;
|
||||
}
|
||||
|
||||
if g.is_nan() {
|
||||
g = 0.;
|
||||
}
|
||||
if b.is_nan() {
|
||||
b = 0.;
|
||||
}
|
||||
|
||||
Color::from_rgbaf32_unchecked(r, g, b, a)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn quantize() {
|
||||
let quant = Quantization::new(1., 0., 8);
|
||||
let color = Color::from_rgbaf32_unchecked(0.5, 0.5, 0.5, 0.5);
|
||||
let quantized = quantize_color(color, [quant; 4]);
|
||||
assert_eq!(quantized.0, 0x7f7f7f7f);
|
||||
let _dequantized = dequantize_color(quantized, [quant; 4]);
|
||||
// assert_eq!(color, dequantized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quantize_black() {
|
||||
let quant = Quantization::new(1., 0., 8);
|
||||
let color = Color::from_rgbaf32_unchecked(0., 0., 0., 1.);
|
||||
let quantized = quantize_color(color, [quant; 4]);
|
||||
assert_eq!(quantized.0, 0xff);
|
||||
let dequantized = dequantize_color(quantized, [quant; 4]);
|
||||
assert_eq!(color, dequantized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_getters() {
|
||||
let quant = Quantization::new(1., 3., 8);
|
||||
assert_eq!(quant.a(), 1.);
|
||||
assert_eq!(quant.b(), 3.);
|
||||
assert_eq!(quant.bits(), 8);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use core::fmt::Debug;
|
||||
|
||||
use crate::Node;
|
||||
use crate::{registry::types::Percentage, transform::Footprint};
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use glam::DVec2;
|
||||
@@ -282,422 +282,50 @@ impl<'i, T: BitmapMut + Bitmap> BitmapMut for &'i mut T {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MapNode<MapFn> {
|
||||
map_fn: MapFn,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(MapNode)]
|
||||
fn map_node<_Iter: Iterator, MapFnNode>(input: _Iter, map_fn: &'input MapFnNode) -> MapFnIterator<'input, _Iter, MapFnNode>
|
||||
where
|
||||
MapFnNode: for<'any_input> Node<'any_input, _Iter::Item>,
|
||||
{
|
||||
MapFnIterator::new(input, map_fn)
|
||||
}
|
||||
|
||||
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
||||
pub struct MapFnIterator<'i, Iter, MapFn> {
|
||||
iter: Iter,
|
||||
map_fn: &'i MapFn,
|
||||
}
|
||||
|
||||
impl<'i, Iter: Debug, MapFn> Debug for MapFnIterator<'i, Iter, MapFn> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("MapFnIterator").field("iter", &self.iter).field("map_fn", &"MapFn").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, Iter: Clone, MapFn> Clone for MapFnIterator<'i, Iter, MapFn> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
iter: self.iter.clone(),
|
||||
map_fn: self.map_fn,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'i, Iter: Copy, MapFn> Copy for MapFnIterator<'i, Iter, MapFn> {}
|
||||
|
||||
impl<'i, Iter, MapFn> MapFnIterator<'i, Iter, MapFn> {
|
||||
pub fn new(iter: Iter, map_fn: &'i MapFn) -> Self {
|
||||
Self { iter, map_fn }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, I: Iterator + 'i, F> Iterator for MapFnIterator<'i, I, F>
|
||||
where
|
||||
F: Node<'i, I::Item> + 'i,
|
||||
Self: 'i,
|
||||
{
|
||||
type Item = F::Output;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<F::Output> {
|
||||
self.iter.next().map(|x| self.map_fn.eval(x))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.iter.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WeightedAvgNode {}
|
||||
|
||||
#[node_macro::node_fn(WeightedAvgNode)]
|
||||
fn weighted_avg_node<_Iter: Iterator<Item = (Color, f32)>>(input: _Iter) -> Color
|
||||
where
|
||||
_Iter: Clone,
|
||||
{
|
||||
let total_weight: f32 = input.clone().map(|(_, weight)| weight).sum();
|
||||
let total_r: f32 = input.clone().map(|(color, weight)| color.r() * weight).sum();
|
||||
let total_g: f32 = input.clone().map(|(color, weight)| color.g() * weight).sum();
|
||||
let total_b: f32 = input.clone().map(|(color, weight)| color.b() * weight).sum();
|
||||
let total_a: f32 = input.map(|(color, weight)| color.a() * weight).sum();
|
||||
Color::from_rgbaf32_unchecked(total_r / total_weight, total_g / total_weight, total_b / total_weight, total_a / total_weight)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GaussianNode<Sigma> {
|
||||
sigma: Sigma,
|
||||
}
|
||||
#[node_macro::node_fn(GaussianNode)]
|
||||
fn gaussian_node(input: f32, sigma: f64) -> f32 {
|
||||
let sigma = sigma as f32;
|
||||
(1.0 / (2.0 * core::f32::consts::PI * sigma * sigma).sqrt()) * (-input * input / (2.0 * sigma * sigma)).exp()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DistanceNode;
|
||||
|
||||
#[node_macro::node_fn(DistanceNode)]
|
||||
fn distance_node(input: (i32, i32)) -> f32 {
|
||||
let (x, y) = input;
|
||||
((x * x + y * y) as f32).sqrt()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ImageIndexIterNode<P> {
|
||||
_p: core::marker::PhantomData<P>,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(ImageIndexIterNode<_P>)]
|
||||
fn image_index_iter_node<_P>(input: ImageSlice<'input, _P>) -> core::ops::Range<u32> {
|
||||
0..(input.width * input.height)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WindowNode<P, Radius: for<'i> Node<'i, (), Output = u32>, Image: for<'i> Node<'i, (), Output = ImageSlice<'i, P>>> {
|
||||
radius: Radius,
|
||||
image: Image,
|
||||
_pixel: core::marker::PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<'input, P: 'input, S0: 'input, S1: 'input> Node<'input, u32> for WindowNode<P, S0, S1>
|
||||
where
|
||||
S0: for<'any_input> Node<'any_input, (), Output = u32>,
|
||||
S1: for<'any_input> Node<'any_input, (), Output = ImageSlice<'any_input, P>>,
|
||||
{
|
||||
type Output = ImageWindowIterator<'input, P>;
|
||||
#[inline]
|
||||
fn eval(&'input self, input: u32) -> Self::Output {
|
||||
let radius = self.radius.eval(());
|
||||
let image = self.image.eval(());
|
||||
{
|
||||
let iter = ImageWindowIterator::new(image, radius, input);
|
||||
iter
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<P, S0, S1> WindowNode<P, S0, S1>
|
||||
where
|
||||
S0: for<'any_input> Node<'any_input, (), Output = u32>,
|
||||
S1: for<'any_input> Node<'any_input, (), Output = ImageSlice<'any_input, P>>,
|
||||
{
|
||||
pub const fn new(radius: S0, image: S1) -> Self {
|
||||
Self {
|
||||
radius,
|
||||
image,
|
||||
_pixel: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
#[node_macro::node_fn(WindowNode)]
|
||||
fn window_node(input: u32, radius: u32, image: ImageSlice<'input>) -> ImageWindowIterator<'input> {
|
||||
let iter = ImageWindowIterator::new(image, radius, input);
|
||||
iter
|
||||
}*/
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ImageWindowIterator<'a, P> {
|
||||
image: ImageSlice<'a, P>,
|
||||
radius: u32,
|
||||
index: u32,
|
||||
x: u32,
|
||||
y: u32,
|
||||
}
|
||||
|
||||
impl<'a, P> ImageWindowIterator<'a, P> {
|
||||
fn new(image: ImageSlice<'a, P>, radius: u32, index: u32) -> Self {
|
||||
let start_x = index as i32 % image.width as i32;
|
||||
let start_y = index as i32 / image.width as i32;
|
||||
let min_x = (start_x - radius as i32).max(0) as u32;
|
||||
let min_y = (start_y - radius as i32).max(0) as u32;
|
||||
|
||||
Self {
|
||||
image,
|
||||
radius,
|
||||
index,
|
||||
x: min_x,
|
||||
y: min_y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
impl<'a, P: Copy> Iterator for ImageWindowIterator<'a, P> {
|
||||
type Item = (P, (i32, i32));
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let start_x = self.index as i32 % self.image.width as i32;
|
||||
let start_y = self.index as i32 / self.image.width as i32;
|
||||
let radius = self.radius as i32;
|
||||
|
||||
let min_x = (start_x - radius).max(0) as u32;
|
||||
let max_x = (start_x + radius).min(self.image.width as i32 - 1) as u32;
|
||||
let max_y = (start_y + radius).min(self.image.height as i32 - 1) as u32;
|
||||
if self.y > max_y {
|
||||
return None;
|
||||
}
|
||||
#[cfg(target_arch = "spirv")]
|
||||
let value = None;
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
let value = Some((self.image.data[(self.x + self.y * self.image.width) as usize], (self.x as i32 - start_x, self.y as i32 - start_y)));
|
||||
|
||||
self.x += 1;
|
||||
if self.x > max_x {
|
||||
self.x = min_x;
|
||||
self.y += 1;
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MapSecondNode<First, Second, MapFn> {
|
||||
map_fn: MapFn,
|
||||
_first: PhantomData<First>,
|
||||
_second: PhantomData<Second>,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(MapSecondNode< _First, _Second>)]
|
||||
fn map_snd_node<MapFn, _First, _Second>(input: (_First, _Second), map_fn: &'input MapFn) -> (_First, <MapFn as Node<'input, _Second>>::Output)
|
||||
where
|
||||
MapFn: for<'any_input> Node<'any_input, _Second>,
|
||||
{
|
||||
let (a, b) = input;
|
||||
(a, map_fn.eval(b))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BrightenColorNode<Brightness> {
|
||||
brightness: Brightness,
|
||||
}
|
||||
#[node_macro::node_fn(BrightenColorNode)]
|
||||
fn brighten_color_node(color: Color, brightness: f32) -> Color {
|
||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
||||
Color::from_rgbaf32_unchecked(per_channel(color.r()), per_channel(color.g()), per_channel(color.b()), color.a())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ForEachNode<MapNode> {
|
||||
map_node: MapNode,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(ForEachNode)]
|
||||
fn map_node<_Iter: Iterator, MapNode>(input: _Iter, map_node: &'input MapNode) -> ()
|
||||
where
|
||||
MapNode: for<'any_input> Node<'any_input, _Iter::Item, Output = ()> + 'input,
|
||||
{
|
||||
input.for_each(|x| map_node.eval(x));
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "spirv")]
|
||||
const NOTHING: () = ();
|
||||
|
||||
use dyn_any::{StaticType, StaticTypeSized};
|
||||
#[derive(Clone, Debug, PartialEq, Copy)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct ImageSlice<'a, Pixel> {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
pub data: &'a [Pixel],
|
||||
#[cfg(target_arch = "spirv")]
|
||||
pub data: &'a (),
|
||||
#[cfg(target_arch = "spirv")]
|
||||
pub _marker: PhantomData<Pixel>,
|
||||
}
|
||||
|
||||
unsafe impl<P: StaticTypeSized> StaticType for ImageSlice<'_, P> {
|
||||
type Static = ImageSlice<'static, P::Static>;
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl<'a, P> Default for ImageSlice<'a, P> {
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: Default::default(),
|
||||
height: Default::default(),
|
||||
data: Default::default(),
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "spirv")]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: Default::default(),
|
||||
height: Default::default(),
|
||||
data: &NOTHING,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
impl<P: Copy + Debug + Pixel> Bitmap for ImageSlice<'_, P> {
|
||||
type Pixel = P;
|
||||
fn get_pixel(&self, x: u32, y: u32) -> Option<P> {
|
||||
self.data.get((x + y * self.width) as usize).copied()
|
||||
}
|
||||
fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> ImageSlice<'_, P> {
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
pub const fn empty() -> Self {
|
||||
Self { width: 0, height: 0, data: &[] }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
impl<'a, P: 'a> IntoIterator for ImageSlice<'a, P> {
|
||||
type Item = &'a P;
|
||||
type IntoIter = core::slice::Iter<'a, P>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.data.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
impl<'a, P: 'a> IntoIterator for &'a ImageSlice<'a, P> {
|
||||
type Item = &'a P;
|
||||
type IntoIter = core::slice::Iter<'a, P>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.data.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImageDimensionsNode<P> {
|
||||
_p: PhantomData<P>,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(ImageDimensionsNode<_P>)]
|
||||
fn dimensions_node<_P>(input: ImageSlice<'input, _P>) -> (u32, u32) {
|
||||
(input.width, input.height)
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use self::image::{CollectNode, Image, ImageFrame, ImageRefNode, MapImageSliceNode};
|
||||
pub use self::image::{Image, ImageFrame};
|
||||
#[cfg(feature = "alloc")]
|
||||
pub(crate) mod image;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{ops::CloneNode, structural::Then, value::ValueNode, Node};
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn map_node() {
|
||||
// let array = &mut [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()];
|
||||
|
||||
// LuminanceNode.eval(Color::from_rgbf32_unchecked(1., 0., 0.));
|
||||
|
||||
/*let map = ForEachNode(MutWrapper(LuminanceNode));
|
||||
(&map).eval(array.iter_mut());
|
||||
assert_eq!(array[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());*/
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_node() {
|
||||
use alloc::vec;
|
||||
let radius = ValueNode::new(1u32).then(CloneNode::new());
|
||||
let image = ValueNode::<_>::new(Image {
|
||||
width: 5,
|
||||
height: 5,
|
||||
data: vec![Color::from_rgbf32_unchecked(1., 0., 0.); 25],
|
||||
base64_string: None,
|
||||
});
|
||||
let image = image.then(ImageRefNode::new());
|
||||
let window = WindowNode::new(radius, image);
|
||||
let vec = window.eval(0);
|
||||
assert_eq!(vec.count(), 4);
|
||||
let vec = window.eval(5);
|
||||
assert_eq!(vec.count(), 6);
|
||||
let vec = window.eval(12);
|
||||
assert_eq!(vec.count(), 9);
|
||||
}
|
||||
|
||||
// TODO: I can't be bothered to fix this test rn
|
||||
// #[test]
|
||||
// fn blur_node() {
|
||||
// use alloc::vec;
|
||||
// let radius = ValueNode::new(1u32).then(CloneNode::new());
|
||||
// let sigma = ValueNode::new(3f64).then(CloneNode::new());
|
||||
// let radius = ValueNode::new(1u32).then(CloneNode::new());
|
||||
// let image = ValueNode::<_>::new(Image {
|
||||
// width: 5,
|
||||
// height: 5,
|
||||
// data: vec![Color::from_rgbf32_unchecked(1., 0., 0.); 25],
|
||||
// });
|
||||
// let image = image.then(ImageRefNode::new());
|
||||
// let window = WindowNode::new(radius, image);
|
||||
// let window: TypeNode<_, u32, ImageWindowIterator<'_>> = TypeNode::new(window);
|
||||
// let distance = ValueNode::new(DistanceNode::new());
|
||||
// let pos_to_dist = MapSecondNode::new(distance);
|
||||
// let type_erased = &window as &dyn for<'a> Node<'a, u32, Output = ImageWindowIterator<'a>>;
|
||||
// type_erased.eval(0);
|
||||
// let map_pos_to_dist = MapNode::new(ValueNode::new(pos_to_dist));
|
||||
|
||||
// let type_erased = &map_pos_to_dist as &dyn for<'a> Node<'a, u32, Output = ImageWindowIterator<'a>>;
|
||||
// type_erased.eval(0);
|
||||
|
||||
// let distance = window.then(map_pos_to_dist);
|
||||
// let map_gaussian = MapSecondNode::new(ValueNode(GaussianNode::new(sigma)));
|
||||
// let map_gaussian: TypeNode<_, (_, f32), (_, f32)> = TypeNode::new(map_gaussian);
|
||||
// let map_gaussian = ValueNode(map_gaussian);
|
||||
// let map_gaussian: TypeNode<_, (), &_> = TypeNode::new(map_gaussian);
|
||||
// let map_distances = MapNode::new(map_gaussian);
|
||||
// let map_distances: TypeNode<_, _, MapFnIterator<'_, '_, _, _>> = TypeNode::new(map_distances);
|
||||
// let gaussian_iter = distance.then(map_distances);
|
||||
// let avg = gaussian_iter.then(WeightedAvgNode::new());
|
||||
// let avg: TypeNode<_, u32, Color> = TypeNode::new(avg);
|
||||
// let blur_iter = MapNode::new(ValueNode::new(avg));
|
||||
// let blur = image.then(ImageIndexIterNode).then(blur_iter);
|
||||
// let blur: TypeNode<_, (), MapFnIterator<_, _>> = TypeNode::new(blur);
|
||||
// let collect = CollectNode::new();
|
||||
// let vec = collect.eval(0..10);
|
||||
// assert_eq!(vec.len(), 10);
|
||||
// let _ = blur.eval(());
|
||||
// let vec = blur.then(collect);
|
||||
// let _image = vec.eval(());
|
||||
// }
|
||||
trait SetBlendMode {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode);
|
||||
}
|
||||
|
||||
impl SetBlendMode for crate::vector::VectorData {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
self.alpha_blending.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
impl SetBlendMode for crate::GraphicGroup {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
self.alpha_blending.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
impl SetBlendMode for ImageFrame<Color> {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
self.alpha_blending.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Style"))]
|
||||
async fn blend_mode<T: SetBlendMode>(
|
||||
footprint: Footprint,
|
||||
#[implementations((Footprint, crate::vector::VectorData), (Footprint, crate::GraphicGroup), (Footprint, ImageFrame<Color>))] value: impl Node<Footprint, Output = T>,
|
||||
blend_mode: BlendMode,
|
||||
) -> T {
|
||||
let mut value = value.eval(footprint).await;
|
||||
value.set_blend_mode(blend_mode);
|
||||
value
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Style"))]
|
||||
async fn opacity<T: MultiplyAlpha>(
|
||||
footprint: Footprint,
|
||||
#[implementations((Footprint, crate::vector::VectorData), (Footprint, crate::GraphicGroup), (Footprint, ImageFrame<Color>))] value: impl Node<Footprint, Output = T>,
|
||||
#[default(100.)] factor: Percentage,
|
||||
) -> T {
|
||||
let mut value = value.eval(footprint).await;
|
||||
let opacity_multiplier = factor / 100.;
|
||||
value.multiply_alpha(opacity_multiplier);
|
||||
value
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
|
||||
|
||||
@@ -26,8 +26,8 @@ pub struct GenerateBrightnessContrastLegacyMapperNode<Brightness, Contrast> {
|
||||
contrast: Contrast,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(GenerateBrightnessContrastLegacyMapperNode)]
|
||||
fn brightness_contrast_legacy_node(_primary: (), brightness: f64, contrast: f64) -> BrightnessContrastLegacyMapperNode {
|
||||
#[node_macro::old_node_fn(GenerateBrightnessContrastLegacyMapperNode)]
|
||||
fn brightness_contrast_legacy(_primary: (), brightness: f64, contrast: f64) -> BrightnessContrastLegacyMapperNode {
|
||||
let brightness = brightness as f32 / 255.;
|
||||
|
||||
let contrast = contrast as f32 / 100.;
|
||||
@@ -67,8 +67,8 @@ pub struct GenerateBrightnessContrastMapperNode<Brightness, Contrast> {
|
||||
|
||||
// TODO: Replace this node implementation with one that reuses the more generalized Curves adjustment node.
|
||||
// TODO: It will be necessary to ensure the tests below are faithfully translated in a way that ensures identical results.
|
||||
#[node_macro::node_fn(GenerateBrightnessContrastMapperNode)]
|
||||
fn brightness_contrast_node(_primary: (), brightness: f64, contrast: f64) -> BrightnessContrastMapperNode {
|
||||
#[node_macro::old_node_fn(GenerateBrightnessContrastMapperNode)]
|
||||
fn brightness_contrast(_primary: (), brightness: f64, contrast: f64) -> BrightnessContrastMapperNode {
|
||||
// Brightness LUT
|
||||
let brightness_is_negative = brightness < 0.;
|
||||
let brightness = brightness.abs() as f32 / 100.;
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
|
||||
use crate::raster::Image;
|
||||
use crate::raster::ImageFrame;
|
||||
@@ -100,12 +100,18 @@ pub struct BrushPlan {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, DynAny, Default)]
|
||||
#[derive(Debug, DynAny)]
|
||||
pub struct BrushCache {
|
||||
inner: Arc<Mutex<BrushCacheImpl>>,
|
||||
proto: bool,
|
||||
}
|
||||
|
||||
impl Default for BrushCache {
|
||||
fn default() -> Self {
|
||||
Self::new_proto()
|
||||
}
|
||||
}
|
||||
|
||||
// A bit of a cursed implementation to work around the current node system.
|
||||
// The original object is a 'prototype' that when cloned gives you a independent
|
||||
// new object. Any further clones however are all the same underlying cache object.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float};
|
||||
use super::{Alpha, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB};
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
#[cfg(feature = "serde")]
|
||||
#[cfg(target_arch = "spirv")]
|
||||
use spirv_std::num_traits::float::Float;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{Channel, Linear, LuminanceMut};
|
||||
use crate::Node;
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||
|
||||
use core::ops::{Add, Mul, Sub};
|
||||
|
||||
@@ -176,6 +176,10 @@ pub struct ValueMapperNode<C> {
|
||||
lut: Vec<C>,
|
||||
}
|
||||
|
||||
unsafe impl<C: StaticTypeSized> StaticType for ValueMapperNode<C> {
|
||||
type Static = ValueMapperNode<C::Static>;
|
||||
}
|
||||
|
||||
impl<C> ValueMapperNode<C> {
|
||||
pub const fn new(lut: Vec<C>) -> Self {
|
||||
Self { lut }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::discrete_srgb::float_to_srgb_u8;
|
||||
use super::{Color, ImageSlice};
|
||||
use crate::{AlphaBlending, Node};
|
||||
use super::Color;
|
||||
use crate::AlphaBlending;
|
||||
use alloc::vec::Vec;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use dyn_any::StaticType;
|
||||
@@ -65,7 +65,7 @@ impl<P: Pixel + Debug> Debug for Image<P> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<P: StaticTypeSized + Pixel> StaticType for Image<P>
|
||||
unsafe impl<P: dyn_any::StaticTypeSized + Pixel> StaticType for Image<P>
|
||||
where
|
||||
P::Static: Pixel,
|
||||
{
|
||||
@@ -126,14 +126,6 @@ impl<P: Pixel> Image<P> {
|
||||
base64_string: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> ImageSlice<P> {
|
||||
ImageSlice {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
data: self.data.as_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Image<Color> {
|
||||
@@ -224,42 +216,6 @@ impl<P: Pixel> IntoIterator for Image<P> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct ImageRefNode<P> {
|
||||
_p: PhantomData<P>,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(ImageRefNode<_P>)]
|
||||
fn image_ref_node<_P: Pixel>(image: &'input Image<_P>) -> ImageSlice<'input, _P> {
|
||||
image.as_slice()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CollectNode {}
|
||||
|
||||
#[node_macro::node_fn(CollectNode)]
|
||||
fn collect_node<_Iter>(input: _Iter) -> Vec<_Iter::Item>
|
||||
where
|
||||
_Iter: Iterator,
|
||||
{
|
||||
input.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MapImageSliceNode<Data> {
|
||||
data: Data,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(MapImageSliceNode)]
|
||||
fn map_node<P: Pixel>(input: (u32, u32), data: Vec<P>) -> Image<P> {
|
||||
Image {
|
||||
width: input.0,
|
||||
height: input.1,
|
||||
data,
|
||||
base64_string: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ImageFrame<P: Pixel> {
|
||||
@@ -314,7 +270,7 @@ impl<P: Copy + Pixel> BitmapMut for ImageFrame<P> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<P: StaticTypeSized + Pixel> StaticType for ImageFrame<P>
|
||||
unsafe impl<P: dyn_any::StaticTypeSized + Pixel> StaticType for ImageFrame<P>
|
||||
where
|
||||
P::Static: Pixel,
|
||||
{
|
||||
|
||||
298
node-graph/gcore/src/registry.rs
Normal file
298
node-graph/gcore/src/registry.rs
Normal file
@@ -0,0 +1,298 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use dyn_any::DynAny;
|
||||
|
||||
use crate::transform::Footprint;
|
||||
use crate::NodeIO;
|
||||
use crate::NodeIOTypes;
|
||||
|
||||
pub mod types {
|
||||
/// 0% - 100%
|
||||
pub type Percentage = f64;
|
||||
/// -180° - 180°
|
||||
pub type Angle = f64;
|
||||
/// -100% - 100%
|
||||
pub type SignedPercentage = f64;
|
||||
/// Non negative integer, px unit
|
||||
pub type PixelLength = f64;
|
||||
/// Non negative
|
||||
pub type Length = f64;
|
||||
/// 0.- 1.
|
||||
pub type Fraction = f64;
|
||||
pub type IntegerCount = u32;
|
||||
/// Int input with randomization button
|
||||
pub type SeedValue = u32;
|
||||
/// Non Negative integer vec with px unit
|
||||
pub type Resolution = glam::UVec2;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NodeMetadata {
|
||||
pub display_name: &'static str,
|
||||
pub category: Option<&'static str>,
|
||||
pub fields: Vec<FieldMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FieldMetadata {
|
||||
pub name: &'static str,
|
||||
pub exposed: bool,
|
||||
pub value_source: ValueSource,
|
||||
pub number_min: Option<f64>,
|
||||
pub number_max: Option<f64>,
|
||||
pub number_mode_range: Option<(f64, f64)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ValueSource {
|
||||
None,
|
||||
Default(&'static str),
|
||||
Scope(&'static str),
|
||||
}
|
||||
|
||||
type NodeRegistry = LazyLock<Mutex<HashMap<String, Vec<(NodeConstructor, NodeIOTypes)>>>>;
|
||||
|
||||
pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
pub static NODE_METADATA: LazyLock<Mutex<HashMap<String, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n + Send>>;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub type DynFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;
|
||||
pub type LocalFuture<'n, T> = Pin<Box<dyn core::future::Future<Output = T> + 'n>>;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub type Any<'n> = Box<dyn DynAny<'n> + 'n + Send>;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub type Any<'n> = Box<dyn DynAny<'n> + 'n>;
|
||||
pub type FutureAny<'n> = DynFuture<'n, Any<'n>>;
|
||||
// TODO: is this safe? This is assumed to be send+sync.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n + Send + Sync;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n;
|
||||
pub type TypeErasedPinnedRef<'n> = Pin<&'n TypeErasedNode<'n>>;
|
||||
pub type TypeErasedRef<'n> = &'n TypeErasedNode<'n>;
|
||||
pub type TypeErasedBox<'n> = Box<TypeErasedNode<'n>>;
|
||||
pub type TypeErasedPinned<'n> = Pin<Box<TypeErasedNode<'n>>>;
|
||||
|
||||
pub type SharedNodeContainer = std::sync::Arc<NodeContainer>;
|
||||
|
||||
pub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NodeContainer {
|
||||
#[cfg(feature = "dealloc_nodes")]
|
||||
pub node: *const TypeErasedNode<'static>,
|
||||
#[cfg(not(feature = "dealloc_nodes"))]
|
||||
pub node: TypeErasedRef<'static>,
|
||||
}
|
||||
|
||||
impl Deref for NodeContainer {
|
||||
type Target = TypeErasedNode<'static>;
|
||||
|
||||
#[cfg(feature = "dealloc_nodes")]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*(self.node) }
|
||||
#[cfg(not(feature = "dealloc_nodes"))]
|
||||
self.node
|
||||
}
|
||||
#[cfg(not(feature = "dealloc_nodes"))]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.node
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Marks NodeContainer as Sync. This dissallows the use of threadlocal storage for nodes as this would invalidate references to them.
|
||||
// TODO: implement this on a higher level wrapper to avoid missuse
|
||||
#[cfg(feature = "dealloc_nodes")]
|
||||
unsafe impl Send for NodeContainer {}
|
||||
#[cfg(feature = "dealloc_nodes")]
|
||||
unsafe impl Sync for NodeContainer {}
|
||||
|
||||
#[cfg(feature = "dealloc_nodes")]
|
||||
impl Drop for NodeContainer {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.dealloc_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for NodeContainer {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("NodeContainer").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeContainer {
|
||||
pub fn new(node: TypeErasedBox<'static>) -> SharedNodeContainer {
|
||||
let node = Box::leak(node);
|
||||
Self { node }.into()
|
||||
}
|
||||
|
||||
#[cfg(feature = "dealloc_nodes")]
|
||||
unsafe fn dealloc_unchecked(&mut self) {
|
||||
std::mem::drop(Box::from_raw(self.node as *mut TypeErasedNode));
|
||||
}
|
||||
}
|
||||
|
||||
use crate::Node;
|
||||
use crate::WasmNotSend;
|
||||
use dyn_any::StaticType;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Boxes the input and downcasts the output.
|
||||
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
|
||||
#[derive(Clone)]
|
||||
pub struct DowncastBothNode<I, O> {
|
||||
node: SharedNodeContainer,
|
||||
_i: PhantomData<I>,
|
||||
_o: PhantomData<O>,
|
||||
}
|
||||
impl<'input, O: 'input + StaticType + WasmNotSend, I: 'input + StaticType + WasmNotSend> Node<'input, I> for DowncastBothNode<I, O> {
|
||||
type Output = DynFuture<'input, O>;
|
||||
#[inline]
|
||||
fn eval(&'input self, input: I) -> Self::Output {
|
||||
{
|
||||
let node_name = self.node.node_name();
|
||||
let input = Box::new(input);
|
||||
let future = self.node.eval(input);
|
||||
Box::pin(async move {
|
||||
let out = dyn_any::downcast(future.await).unwrap_or_else(|e| panic!("DowncastBothNode Input {e} in: \n{node_name}"));
|
||||
*out
|
||||
})
|
||||
}
|
||||
}
|
||||
fn reset(&self) {
|
||||
self.node.reset();
|
||||
}
|
||||
|
||||
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
|
||||
self.node.serialize()
|
||||
}
|
||||
}
|
||||
impl<I, O> DowncastBothNode<I, O> {
|
||||
pub const fn new(node: SharedNodeContainer) -> Self {
|
||||
Self {
|
||||
node,
|
||||
_i: core::marker::PhantomData,
|
||||
_o: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct FutureWrapperNode<Node> {
|
||||
node: Node,
|
||||
}
|
||||
|
||||
impl<'i, T: 'i + WasmNotSend, N> Node<'i, T> for FutureWrapperNode<N>
|
||||
where
|
||||
N: Node<'i, T, Output: WasmNotSend> + WasmNotSend,
|
||||
{
|
||||
type Output = DynFuture<'i, N::Output>;
|
||||
#[inline(always)]
|
||||
fn eval(&'i self, input: T) -> Self::Output {
|
||||
let result = self.node.eval(input);
|
||||
Box::pin(async move { result })
|
||||
}
|
||||
#[inline(always)]
|
||||
fn reset(&self) {
|
||||
self.node.reset();
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
|
||||
self.node.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<N> FutureWrapperNode<N> {
|
||||
pub const fn new(node: N) -> Self {
|
||||
Self { node }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DynAnyNode<I, O, Node> {
|
||||
node: Node,
|
||||
_i: PhantomData<I>,
|
||||
_o: PhantomData<O>,
|
||||
}
|
||||
|
||||
impl<'input, _I: 'input + StaticType + WasmNotSend, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N>
|
||||
where
|
||||
N: Node<'input, _I, Output = DynFuture<'input, _O>>,
|
||||
{
|
||||
type Output = FutureAny<'input>;
|
||||
#[inline]
|
||||
fn eval(&'input self, input: Any<'input>) -> Self::Output {
|
||||
let node_name = core::any::type_name::<N>();
|
||||
let output = |input| {
|
||||
let result = self.node.eval(input);
|
||||
async move { Box::new(result.await) as Any<'input> }
|
||||
};
|
||||
match dyn_any::downcast(input) {
|
||||
Ok(input) => Box::pin(output(*input)),
|
||||
// If the input type of the node is `()` and we supply an invalid type, we can still call the
|
||||
// node and just ignore the input and call it with the unit type instead.
|
||||
Err(_) if core::any::TypeId::of::<_I::Static>() == core::any::TypeId::of::<()>() => {
|
||||
assert_eq!(std::mem::size_of::<_I>(), 0);
|
||||
// Rust can't know, that `_I` and `()` are the same size, so we have to use a `transmute_copy()` here
|
||||
Box::pin(output(unsafe { std::mem::transmute_copy(&()) }))
|
||||
}
|
||||
// If the Node expects a footprint but we provide (). In this case construct the default Footprint and pass that
|
||||
// This is pretty hacky pls fix
|
||||
Err(_) if core::any::TypeId::of::<_I::Static>() == core::any::TypeId::of::<Footprint>() => {
|
||||
assert_eq!(std::mem::size_of::<_I>(), std::mem::size_of::<Footprint>());
|
||||
assert_eq!(std::mem::align_of::<_I>(), std::mem::align_of::<Footprint>());
|
||||
// Rust can't know, that `_I` and `Footprint` are the same size, so we have to use a `transmute_copy()` here
|
||||
Box::pin(output(unsafe { std::mem::transmute_copy(&Footprint::default()) }))
|
||||
}
|
||||
Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, node_name),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&self) {
|
||||
self.node.reset();
|
||||
}
|
||||
|
||||
fn serialize(&self) -> Option<std::sync::Arc<dyn core::any::Any>> {
|
||||
self.node.serialize()
|
||||
}
|
||||
}
|
||||
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> DynAnyNode<_I, _O, N>
|
||||
where
|
||||
N: Node<'input, _I, Output = DynFuture<'input, _O>>,
|
||||
{
|
||||
pub const fn new(node: N) -> Self {
|
||||
Self {
|
||||
node,
|
||||
_i: core::marker::PhantomData,
|
||||
_o: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct PanicNode<I: WasmNotSend, O: WasmNotSend>(PhantomData<I>, PhantomData<O>);
|
||||
|
||||
impl<'i, I: 'i + WasmNotSend, O: 'i + WasmNotSend> Node<'i, I> for PanicNode<I, O> {
|
||||
type Output = O;
|
||||
fn eval(&'i self, _: I) -> Self::Output {
|
||||
unimplemented!("This node should never be evaluated")
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: WasmNotSend, O: WasmNotSend> PanicNode<I, O> {
|
||||
pub const fn new() -> Self {
|
||||
Self(PhantomData, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: WasmNotSend, O: WasmNotSend> Default for PanicNode<I, O> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Evaluate safety
|
||||
unsafe impl<I: WasmNotSend, O: WasmNotSend> Sync for PanicNode<I, O> {}
|
||||
@@ -1,146 +0,0 @@
|
||||
use crate::Node;
|
||||
|
||||
use core::ops::{Deref, DerefMut, Index, IndexMut};
|
||||
|
||||
pub struct SetNode<Storage> {
|
||||
storage: Storage,
|
||||
}
|
||||
impl<'input, T: 'input, I: 'input, A: 'input + 'input, S0: 'input> Node<'input, (T, I)> for SetNode<S0>
|
||||
where
|
||||
A: DerefMut,
|
||||
A::Target: IndexMut<I, Output = T>,
|
||||
S0: for<'any_input> Node<'input, (), Output = A>,
|
||||
{
|
||||
type Output = ();
|
||||
#[inline]
|
||||
fn eval(&'input self, input: (T, I)) -> Self::Output {
|
||||
let mut storage = self.storage.eval(());
|
||||
let (value, index) = input;
|
||||
*storage.deref_mut().index_mut(index).deref_mut() = value;
|
||||
}
|
||||
}
|
||||
impl<'input, S0: 'input> SetNode<S0> {
|
||||
pub const fn new(storage: S0) -> Self {
|
||||
Self { storage }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtractXNode {}
|
||||
|
||||
#[node_macro::node_fn(ExtractXNode)]
|
||||
fn extract_x_node(input: glam::UVec3) -> usize {
|
||||
input.x as usize
|
||||
}
|
||||
|
||||
pub struct SetOwnedNode<Storage> {
|
||||
storage: core::cell::RefCell<Storage>,
|
||||
}
|
||||
|
||||
impl<Storage> SetOwnedNode<Storage> {
|
||||
pub fn new(storage: Storage) -> Self {
|
||||
Self {
|
||||
storage: core::cell::RefCell::new(storage),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'input, I: 'input, T: 'input, Storage, A: ?Sized> Node<'input, (T, I)> for SetOwnedNode<Storage>
|
||||
where
|
||||
Storage: DerefMut<Target = A> + 'input,
|
||||
A: IndexMut<I, Output = T> + 'input,
|
||||
{
|
||||
type Output = ();
|
||||
fn eval(&'input self, input: (T, I)) -> Self::Output {
|
||||
let (value, index) = input;
|
||||
*self.storage.borrow_mut().index_mut(index) = value;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GetNode<Storage> {
|
||||
storage: Storage,
|
||||
}
|
||||
|
||||
impl<Storage> GetNode<Storage> {
|
||||
pub fn new(storage: Storage) -> Self {
|
||||
Self { storage }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'input, I: 'input, T: 'input, Storage, SNode, A: ?Sized> Node<'input, I> for GetNode<SNode>
|
||||
where
|
||||
SNode: Node<'input, (), Output = Storage>,
|
||||
Storage: Deref<Target = A> + 'input,
|
||||
A: Index<I, Output = T> + 'input,
|
||||
T: Clone,
|
||||
{
|
||||
type Output = T;
|
||||
fn eval(&'input self, index: I) -> Self::Output {
|
||||
let storage = self.storage.eval(());
|
||||
storage.deref().index(index).clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::value::{CopiedNode, OnceCellNode};
|
||||
use crate::Node;
|
||||
|
||||
#[test]
|
||||
fn get_node_array() {
|
||||
let storage = [1, 2, 3];
|
||||
let node = GetNode::new(CopiedNode::new(&storage));
|
||||
assert_eq!((&node as &dyn Node<'_, usize, Output = i32>).eval(1), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_node_vec() {
|
||||
let storage = vec![1, 2, 3];
|
||||
let node = GetNode::new(CopiedNode::new(&storage));
|
||||
assert_eq!(node.eval(1), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_node_slice() {
|
||||
let storage: &[i32] = &[1, 2, 3];
|
||||
let node = GetNode::new(CopiedNode::new(storage));
|
||||
let _ = &node as &dyn Node<'_, usize, Output = i32>;
|
||||
assert_eq!(node.eval(1), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_node_slice() {
|
||||
let mut backing_storage = [1, 2, 3];
|
||||
let storage: &mut [i32] = &mut backing_storage;
|
||||
let storage_node = OnceCellNode::new(storage);
|
||||
let node = SetNode::new(storage_node);
|
||||
node.eval((4, 1));
|
||||
assert_eq!(backing_storage, [1, 4, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_owned_node_array() {
|
||||
let mut storage = [1, 2, 3];
|
||||
let node = SetOwnedNode::new(&mut storage);
|
||||
node.eval((4, 1));
|
||||
assert_eq!(storage, [1, 4, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_owned_node_vec() {
|
||||
let mut storage = vec![1, 2, 3];
|
||||
let node = SetOwnedNode::new(&mut storage);
|
||||
node.eval((4, 1));
|
||||
assert_eq!(storage, [1, 4, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_owned_node_slice() {
|
||||
let mut backing_storage = [1, 2, 3];
|
||||
let storage: &mut [i32] = &mut backing_storage;
|
||||
let node = SetOwnedNode::new(storage);
|
||||
let node = &node as &dyn Node<'_, (i32, usize), Output = ()>;
|
||||
node.eval((4, 1));
|
||||
assert_eq!(backing_storage, [1, 4, 3]);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::{Node, NodeMut};
|
||||
use crate::Node;
|
||||
|
||||
/// This is how we can generically define composition of two nodes.
|
||||
/// This is done generically as shown: <https://files.keavon.com/-/SurprisedGaseousAnhinga/capture.png>
|
||||
@@ -42,18 +42,6 @@ where
|
||||
second.eval(arg)
|
||||
}
|
||||
}
|
||||
impl<'i, 'f: 'i, 's: 'i, Input: 'i, First, Second> NodeMut<'i, Input> for ComposeNode<First, Second, Input>
|
||||
where
|
||||
First: Node<'i, Input>,
|
||||
Second: NodeMut<'i, <First as Node<'i, Input>>::Output> + 'i,
|
||||
{
|
||||
type MutOutput = <Second as NodeMut<'i, <First as Node<'i, Input>>::Output>>::MutOutput;
|
||||
fn eval_mut(&'i mut self, input: Input) -> Self::MutOutput {
|
||||
let arg = self.first.eval(input);
|
||||
let second = &mut self.second;
|
||||
second.eval_mut(arg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i, First, Second, Input: 'i> ComposeNode<First, Second, Input> {
|
||||
pub const fn new(first: First, second: Second) -> Self {
|
||||
@@ -141,38 +129,6 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode<I, Root> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ApplyNode<O, N> {
|
||||
pub node: N,
|
||||
_o: PhantomData<O>,
|
||||
}
|
||||
/*
|
||||
#[node_macro::node_fn(ApplyNode)]
|
||||
fn apply<In, N>(input: In, node: &'any_input N) -> ()
|
||||
where
|
||||
// TODO: try to allows this to return output other than ()
|
||||
N: for<'any_input> Node<'any_input, In, Output = ()>,
|
||||
{
|
||||
node.eval(input)
|
||||
}
|
||||
*/
|
||||
impl<'input, In: 'input, N: 'input, S0: 'input, O: 'input> Node<'input, In> for ApplyNode<O, S0>
|
||||
where
|
||||
N: Node<'input, In, Output = O>,
|
||||
S0: Node<'input, (), Output = &'input N>,
|
||||
{
|
||||
type Output = <N as Node<'input, In>>::Output;
|
||||
#[inline]
|
||||
fn eval(&'input self, input: In) -> Self::Output {
|
||||
let node = self.node.eval(());
|
||||
node.eval(input)
|
||||
}
|
||||
}
|
||||
impl<'input, S0: 'input, O: 'static> ApplyNode<O, S0> {
|
||||
pub const fn new(node: S0) -> Self {
|
||||
Self { node, _o: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@@ -198,16 +154,4 @@ mod test {
|
||||
|
||||
assert_eq!(compose.eval(()), &5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::unit_cmp)]
|
||||
fn test_apply() {
|
||||
let mut array = [1, 2, 3];
|
||||
let slice = &mut array;
|
||||
let set_node = crate::storage::SetOwnedNode::new(slice);
|
||||
|
||||
let apply = ApplyNode::new(ValueNode::new(set_node));
|
||||
|
||||
assert_eq!(apply.eval((1, 2)), ());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
mod font_cache;
|
||||
mod to_path;
|
||||
|
||||
use crate::application_io::EditorApi;
|
||||
pub use font_cache::*;
|
||||
use node_macro::node_fn;
|
||||
pub use to_path::*;
|
||||
|
||||
use crate::Node;
|
||||
|
||||
pub struct TextGeneratorNode<Text, FontName, Size> {
|
||||
text: Text,
|
||||
font_name: FontName,
|
||||
font_size: Size,
|
||||
}
|
||||
|
||||
#[node_fn(TextGeneratorNode)]
|
||||
fn generate_text<'a: 'input, T: 'a>(editor: &'a EditorApi<T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
|
||||
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
|
||||
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None), false)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use dyn_any::StaticType;
|
||||
use glam::DAffine2;
|
||||
|
||||
use glam::DVec2;
|
||||
@@ -8,9 +7,9 @@ use crate::raster::ImageFrame;
|
||||
use crate::raster::Pixel;
|
||||
use crate::vector::VectorData;
|
||||
use crate::Artboard;
|
||||
use crate::ArtboardGroup;
|
||||
use crate::GraphicElement;
|
||||
use crate::GraphicGroup;
|
||||
use crate::Node;
|
||||
|
||||
pub trait Transform {
|
||||
fn transform(&self) -> DAffine2;
|
||||
@@ -176,15 +175,15 @@ impl Footprint {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CullNode<VectorData> {
|
||||
pub(crate) vector_data: VectorData,
|
||||
impl From<()> for Footprint {
|
||||
fn from(_: ()) -> Self {
|
||||
Footprint::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(CullNode)]
|
||||
fn cull_vector_data<T>(footprint: Footprint, vector_data: T) -> T {
|
||||
// TODO: Implement culling
|
||||
vector_data
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn cull<T>(_footprint: Footprint, #[implementations(VectorData, GraphicGroup, Artboard, ImageFrame<crate::Color>, ArtboardGroup)] data: T) -> T {
|
||||
data
|
||||
}
|
||||
|
||||
impl core::hash::Hash for Footprint {
|
||||
@@ -205,20 +204,30 @@ impl TransformMut for Footprint {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TransformNode<TransformTarget, Translation, Rotation, Scale, Shear, Pivot> {
|
||||
pub(crate) transform_target: TransformTarget,
|
||||
pub(crate) translate: Translation,
|
||||
pub(crate) rotate: Rotation,
|
||||
pub(crate) scale: Scale,
|
||||
pub(crate) shear: Shear,
|
||||
pub(crate) _pivot: Pivot,
|
||||
pub trait ApplyTransform {
|
||||
fn apply_transform(&mut self, modification: &DAffine2);
|
||||
}
|
||||
impl<T: TransformMut> ApplyTransform for T {
|
||||
fn apply_transform(&mut self, &modification: &DAffine2) {
|
||||
*self.transform_mut() = self.transform() * modification
|
||||
}
|
||||
}
|
||||
impl ApplyTransform for () {
|
||||
fn apply_transform(&mut self, &_modification: &DAffine2) {}
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(TransformNode)]
|
||||
pub(crate) async fn transform_vector_data<T: TransformMut>(
|
||||
mut footprint: Footprint,
|
||||
transform_target: impl Node<Footprint, Output = T>,
|
||||
#[node_macro::node(category(""))]
|
||||
async fn transform<I: Into<Footprint> + ApplyTransform + 'n + Clone + Send + Sync, T: TransformMut + 'n>(
|
||||
#[implementations(Footprint, Footprint, Footprint, (), (), ())] mut input: I,
|
||||
#[implementations(
|
||||
(Footprint, VectorData),
|
||||
(Footprint, GraphicGroup),
|
||||
(Footprint, ImageFrame<crate::Color>),
|
||||
((), VectorData),
|
||||
((), GraphicGroup),
|
||||
((), ImageFrame<crate::Color>),
|
||||
)]
|
||||
transform_target: impl Node<I, Output = T>,
|
||||
translate: DVec2,
|
||||
rotate: f64,
|
||||
scale: DVec2,
|
||||
@@ -226,24 +235,25 @@ pub(crate) async fn transform_vector_data<T: TransformMut>(
|
||||
_pivot: DVec2,
|
||||
) -> T {
|
||||
let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
||||
let footprint = input.clone().into();
|
||||
if !footprint.ignore_modifications {
|
||||
*footprint.transform_mut() = footprint.transform() * modification;
|
||||
input.apply_transform(&modification);
|
||||
}
|
||||
|
||||
let mut data = self.transform_target.eval(footprint).await;
|
||||
let mut data = transform_target.eval(input).await;
|
||||
|
||||
let data_transform = data.transform_mut();
|
||||
*data_transform = modification * (*data_transform);
|
||||
|
||||
data
|
||||
}
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SetTransformNode<TransformInput> {
|
||||
pub(crate) transform: TransformInput,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(SetTransformNode)]
|
||||
pub(crate) fn set_transform<Data: TransformMut, TransformInput: Transform>(mut data: Data, transform: TransformInput) -> Data {
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn replace_transform<Data: TransformMut, TransformInput: Transform>(
|
||||
_: (),
|
||||
#[implementations(VectorData, ImageFrame<crate::Color>, GraphicGroup)] mut data: Data,
|
||||
#[implementations(DAffine2)] transform: TransformInput,
|
||||
) -> Data {
|
||||
let data_transform = data.transform_mut();
|
||||
*data_transform = transform.transform();
|
||||
data
|
||||
|
||||
@@ -6,7 +6,69 @@ use dyn_any::StaticType;
|
||||
#[cfg(feature = "std")]
|
||||
pub use std::borrow::Cow;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
#[macro_export]
|
||||
macro_rules! concrete {
|
||||
($type:ty) => {
|
||||
$crate::Type::Concrete($crate::TypeDescriptor {
|
||||
id: Some(core::any::TypeId::of::<$type>()),
|
||||
name: $crate::Cow::Borrowed(core::any::type_name::<$type>()),
|
||||
alias: None,
|
||||
size: core::mem::size_of::<$type>(),
|
||||
align: core::mem::align_of::<$type>(),
|
||||
})
|
||||
};
|
||||
($type:ty, $name:ty) => {
|
||||
$crate::Type::Concrete($crate::TypeDescriptor {
|
||||
id: Some(core::any::TypeId::of::<$type>()),
|
||||
name: $crate::Cow::Borrowed(core::any::type_name::<$type>()),
|
||||
alias: Some($crate::Cow::Borrowed(stringify!($name))),
|
||||
size: core::mem::size_of::<$type>(),
|
||||
align: core::mem::align_of::<$type>(),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! concrete_with_name {
|
||||
($type:ty, $name:expr) => {
|
||||
$crate::Type::Concrete($crate::TypeDescriptor {
|
||||
id: Some(core::any::TypeId::of::<$type>()),
|
||||
name: $crate::Cow::Borrowed($name),
|
||||
alias: None,
|
||||
size: core::mem::size_of::<$type>(),
|
||||
align: core::mem::align_of::<$type>(),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! generic {
|
||||
($type:ty) => {{
|
||||
$crate::Type::Generic($crate::Cow::Borrowed(stringify!($type)))
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! future {
|
||||
($type:ty) => {{
|
||||
$crate::Type::Future(Box::new(concrete!($type)))
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fn_type {
|
||||
($type:ty) => {
|
||||
$crate::Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type)))
|
||||
};
|
||||
($in_type:ty, $type:ty, alias: $outname:ty) => {
|
||||
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type, $outname)))
|
||||
};
|
||||
($in_type:ty, $type:ty) => {
|
||||
$crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type)))
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct NodeIOTypes {
|
||||
pub input: Type,
|
||||
pub output: Type,
|
||||
@@ -14,10 +76,32 @@ pub struct NodeIOTypes {
|
||||
}
|
||||
|
||||
impl NodeIOTypes {
|
||||
pub fn new(input: Type, output: Type, parameters: Vec<Type>) -> Self {
|
||||
pub const fn new(input: Type, output: Type, parameters: Vec<Type>) -> Self {
|
||||
Self { input, output, parameters }
|
||||
}
|
||||
|
||||
pub const fn empty() -> Self {
|
||||
let tds1 = TypeDescriptor {
|
||||
id: None,
|
||||
name: Cow::Borrowed("()"),
|
||||
alias: None,
|
||||
size: 0,
|
||||
align: 0,
|
||||
};
|
||||
let tds2 = TypeDescriptor {
|
||||
id: None,
|
||||
name: Cow::Borrowed("()"),
|
||||
alias: None,
|
||||
size: 0,
|
||||
align: 0,
|
||||
};
|
||||
Self {
|
||||
input: Type::Concrete(tds1),
|
||||
output: Type::Concrete(tds2),
|
||||
parameters: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> Type {
|
||||
Type::Fn(Box::new(self.input.clone()), Box::new(self.output.clone()))
|
||||
}
|
||||
@@ -33,52 +117,16 @@ impl core::fmt::Debug for NodeIOTypes {
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! concrete {
|
||||
($type:ty) => {
|
||||
$crate::Type::Concrete($crate::TypeDescriptor {
|
||||
id: Some(core::any::TypeId::of::<$type>()),
|
||||
name: $crate::Cow::Borrowed(core::any::type_name::<$type>()),
|
||||
size: core::mem::size_of::<$type>(),
|
||||
align: core::mem::align_of::<$type>(),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! concrete_with_name {
|
||||
($type:ty, $name:expr) => {
|
||||
$crate::Type::Concrete($crate::TypeDescriptor {
|
||||
id: Some(core::any::TypeId::of::<$type>()),
|
||||
name: $crate::Cow::Borrowed($name),
|
||||
size: core::mem::size_of::<$type>(),
|
||||
align: core::mem::align_of::<$type>(),
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! generic {
|
||||
($type:ty) => {{
|
||||
$crate::Type::Generic($crate::Cow::Borrowed(stringify!($type)))
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fn_type {
|
||||
($type:ty) => {
|
||||
$crate::Type::Fn(Box::new(concrete!(())), Box::new(concrete!($type)))
|
||||
};
|
||||
($in_type:ty, $type:ty) => {
|
||||
$crate::Type::Fn(Box::new(concrete!(($in_type))), Box::new(concrete!($type)))
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ProtoNodeIdentifier {
|
||||
pub name: Cow<'static, str>,
|
||||
}
|
||||
impl From<String> for ProtoNodeIdentifier {
|
||||
fn from(value: String) -> Self {
|
||||
Self { name: Cow::Owned(value) }
|
||||
}
|
||||
}
|
||||
|
||||
fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
@@ -101,6 +149,8 @@ pub struct TypeDescriptor {
|
||||
#[serde(deserialize_with = "migrate_type_descriptor_names")]
|
||||
pub name: Cow<'static, str>,
|
||||
#[serde(default)]
|
||||
pub alias: Option<Cow<'static, str>>,
|
||||
#[serde(default)]
|
||||
pub size: usize,
|
||||
#[serde(default)]
|
||||
pub align: usize,
|
||||
@@ -199,6 +249,7 @@ impl Type {
|
||||
Self::Concrete(TypeDescriptor {
|
||||
id: Some(TypeId::of::<T::Static>()),
|
||||
name: Cow::Borrowed(core::any::type_name::<T::Static>()),
|
||||
alias: None,
|
||||
size: core::mem::size_of::<T>(),
|
||||
align: core::mem::align_of::<T>(),
|
||||
})
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
pub use uuid_generation::*;
|
||||
|
||||
use dyn_any::DynAny;
|
||||
use dyn_any::StaticType;
|
||||
pub use uuid_generation::*;
|
||||
|
||||
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct Uuid(
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::raster::bbox::AxisAlignedBbox;
|
||||
use crate::raster::BlendMode;
|
||||
use crate::Color;
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
|
||||
@@ -1,52 +1,9 @@
|
||||
use super::HandleId;
|
||||
use crate::vector::{PointId, VectorData};
|
||||
use crate::Node;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
|
||||
use glam::DVec2;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CircleGenerator<Radius> {
|
||||
radius: Radius,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(CircleGenerator)]
|
||||
fn circle_generator(_input: (), radius: f64) -> VectorData {
|
||||
let radius: f64 = radius;
|
||||
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EllipseGenerator<RadiusX, RadiusY> {
|
||||
radius_x: RadiusX,
|
||||
radius_y: RadiusY,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(EllipseGenerator)]
|
||||
fn ellipse_generator(_input: (), radius_x: f64, radius_y: f64) -> VectorData {
|
||||
let radius = DVec2::new(radius_x, radius_y);
|
||||
let corner1 = -radius;
|
||||
let corner2 = radius;
|
||||
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
|
||||
let len = ellipse.segment_domain.ids().len();
|
||||
for i in 0..len {
|
||||
ellipse
|
||||
.colinear_manipulators
|
||||
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
|
||||
}
|
||||
ellipse
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RectangleGenerator<SizeX, SizeY, IsIndividual, CornerRadius, Clamped> {
|
||||
size_x: SizeX,
|
||||
size_y: SizeY,
|
||||
is_individual: IsIndividual,
|
||||
corner_radius: CornerRadius,
|
||||
clamped: Clamped,
|
||||
}
|
||||
|
||||
trait CornerRadius {
|
||||
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData;
|
||||
}
|
||||
@@ -77,59 +34,79 @@ impl CornerRadius for [f64; 4] {
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(RectangleGenerator)]
|
||||
fn square_generator<T: CornerRadius>(_input: (), size_x: f64, size_y: f64, is_individual: bool, corner_radius: T, clamped: bool) -> VectorData {
|
||||
corner_radius.generate(DVec2::new(size_x, size_y), clamped)
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn circle(_: (), _primary: (), #[default(50.)] radius: f64) -> VectorData {
|
||||
let radius: f64 = radius;
|
||||
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RegularPolygonGenerator<Points, Radius> {
|
||||
points: Points,
|
||||
radius: Radius,
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn ellipse(_: (), _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorData {
|
||||
let radius = DVec2::new(radius_x, radius_y);
|
||||
let corner1 = -radius;
|
||||
let corner2 = radius;
|
||||
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
|
||||
let len = ellipse.segment_domain.ids().len();
|
||||
for i in 0..len {
|
||||
ellipse
|
||||
.colinear_manipulators
|
||||
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
|
||||
}
|
||||
ellipse
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(RegularPolygonGenerator)]
|
||||
fn regular_polygon_generator(_input: (), points: u32, radius: f64) -> VectorData {
|
||||
let points = points.into();
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn rectangle<T: CornerRadius>(
|
||||
_: (),
|
||||
_primary: (),
|
||||
#[default(100)] width: f64,
|
||||
#[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,
|
||||
) -> VectorData {
|
||||
corner_radius.generate(DVec2::new(width, height), clamped)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn regular_polygon(
|
||||
_: (),
|
||||
_primary: (),
|
||||
#[default(6)]
|
||||
#[min(3.)]
|
||||
sides: u32,
|
||||
#[default(50)] radius: f64,
|
||||
) -> VectorData {
|
||||
let points = sides.into();
|
||||
let radius: f64 = radius * 2.;
|
||||
super::VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct StarGenerator<Points, Radius, InnerRadius> {
|
||||
points: Points,
|
||||
radius: Radius,
|
||||
inner_radius: InnerRadius,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(StarGenerator)]
|
||||
fn star_generator(_input: (), points: u32, radius: f64, inner_radius: f64) -> VectorData {
|
||||
let points = points.into();
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn star(
|
||||
_: (),
|
||||
_primary: (),
|
||||
#[default(5)]
|
||||
#[min(2.)]
|
||||
sides: u32,
|
||||
#[default(50)] radius: f64,
|
||||
#[default(25)] inner_radius: f64,
|
||||
) -> VectorData {
|
||||
let points = sides.into();
|
||||
let diameter: f64 = radius * 2.;
|
||||
let inner_diameter = inner_radius * 2.;
|
||||
|
||||
super::VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LineGenerator<Pos1, Pos2> {
|
||||
pos_1: Pos1,
|
||||
pos_2: Pos2,
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn line(_: (), _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorData {
|
||||
super::VectorData::from_subpath(Subpath::new_line(start, end))
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(LineGenerator)]
|
||||
fn line_generator(_input: (), pos_1: DVec2, pos_2: DVec2) -> VectorData {
|
||||
super::VectorData::from_subpath(Subpath::new_line(pos_1, pos_2))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SplineGenerator<Positions> {
|
||||
positions: Positions,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(SplineGenerator)]
|
||||
fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
|
||||
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(positions));
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn spline(_: (), _primary: (), points: Vec<DVec2>) -> VectorData {
|
||||
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(points));
|
||||
for pair in spline.segment_domain.ids().windows(2) {
|
||||
spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]);
|
||||
}
|
||||
@@ -137,13 +114,9 @@ fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
|
||||
}
|
||||
|
||||
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PathGenerator<ColinearManipulators> {
|
||||
colinear_manipulators: ColinearManipulators,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(PathGenerator)]
|
||||
fn generate_path(path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
|
||||
#[node_macro::node(category(""))]
|
||||
fn path(_: (), path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
|
||||
let mut vector_data = super::VectorData::from_subpaths(path_data, false);
|
||||
vector_data.colinear_manipulators = colinear_manipulators
|
||||
.iter()
|
||||
@@ -151,27 +124,3 @@ fn generate_path(path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<Po
|
||||
.collect();
|
||||
vector_data
|
||||
}
|
||||
|
||||
// #[derive(Debug, Clone, Copy)]
|
||||
// pub struct BlitSubpath<P> {
|
||||
// path_data: P,
|
||||
// }
|
||||
|
||||
// #[node_macro::node_fn(BlitSubpath)]
|
||||
// fn blit_subpath(base_image: Image, path_data: VectorData) -> Image {
|
||||
// // TODO: Get forma to compile
|
||||
// use forma::prelude::*;
|
||||
// let composition = Composition::new();
|
||||
// let mut renderer = cpu::Renderer::new();
|
||||
// let mut path_builder = PathBuilder::new();
|
||||
// for path_segment in path_data.bezier_iter() {
|
||||
// let points = path_segment.internal.get_points().collect::<Vec<_>>();
|
||||
// match points.len() {
|
||||
// 2 => path_builder.line_to(points[1].into()),
|
||||
// 3 => path_builder.quad_to(points[1].into(), points[2].into()),
|
||||
// 4 => path_builder.cubic_to(points[1].into(), points[2].into(), points[3].into()),
|
||||
// }
|
||||
// }
|
||||
|
||||
// base_image
|
||||
// }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
|
||||
/// Represents different ways of calculating the centroid.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT};
|
||||
use crate::renderer::format_transform_matrix;
|
||||
use crate::Color;
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::fmt::{self, Display, Write};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use super::style::{PathStyle, Stroke};
|
||||
use crate::{AlphaBlending, Color};
|
||||
|
||||
use bezier_rs::ManipulatorGroup;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
|
||||
use core::borrow::Borrow;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::HandleId;
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use super::*;
|
||||
use crate::uuid::generate_uuid;
|
||||
use crate::Node;
|
||||
|
||||
use bezier_rs::BezierHandles;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use dyn_any::DynAny;
|
||||
|
||||
use core::hash::BuildHasher;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -422,14 +421,15 @@ impl core::hash::Hash for VectorModification {
|
||||
}
|
||||
}
|
||||
|
||||
use crate::transform::Footprint;
|
||||
/// A node that applies a procedural modification to some [`VectorData`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PathModify<VectorModificationNode> {
|
||||
modification: VectorModificationNode,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(PathModify)]
|
||||
fn path_modify(mut vector_data: VectorData, modification: VectorModification) -> VectorData {
|
||||
#[node_macro::node(category(""))]
|
||||
async fn path_modify<F: 'n + Send + Sync + Clone>(
|
||||
#[implementations((), Footprint)] input: F,
|
||||
#[implementations(((), VectorData), (Footprint, VectorData))] vector_data: impl Node<F, Output = VectorData>,
|
||||
modification: VectorModification,
|
||||
) -> VectorData {
|
||||
let mut vector_data = vector_data.eval(input).await;
|
||||
modification.apply(&mut vector_data);
|
||||
vector_data
|
||||
}
|
||||
|
||||
@@ -1,153 +1,101 @@
|
||||
use super::misc::CentroidType;
|
||||
use super::style::{Fill, GradientStops, Stroke};
|
||||
use super::style::{Fill, Gradient, GradientStops, Stroke};
|
||||
use super::{PointId, SegmentId, StrokeId, VectorData};
|
||||
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
|
||||
use crate::renderer::GraphicElementRendered;
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
use crate::{Color, GraphicGroup, Node};
|
||||
use crate::{Color, GraphicGroup};
|
||||
|
||||
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AssignColorsNode<Fill, Stroke, Gradient, Reverse, Randomize, Seed, RepeatEvery> {
|
||||
fill: Fill,
|
||||
stroke: Stroke,
|
||||
gradient: Gradient,
|
||||
reverse: Reverse,
|
||||
randomize: Randomize,
|
||||
seed: Seed,
|
||||
repeat_every: RepeatEvery,
|
||||
trait VectorIterMut {
|
||||
fn vector_iter_mut(&mut self) -> impl ExactSizeIterator<Item = &mut VectorData>;
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(AssignColorsNode)]
|
||||
fn assign_colors_node(group: GraphicGroup, fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, seed: u32, repeat_every: u32) -> GraphicGroup {
|
||||
let mut group = group;
|
||||
let vector_data_list: Vec<_> = group.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).collect();
|
||||
let list = (vector_data_list.len(), vector_data_list.into_iter());
|
||||
|
||||
assign_colors(
|
||||
list,
|
||||
AlignColorsOptions {
|
||||
fill,
|
||||
stroke,
|
||||
gradient,
|
||||
reverse,
|
||||
randomize,
|
||||
seed,
|
||||
repeat_every,
|
||||
},
|
||||
);
|
||||
|
||||
group
|
||||
impl VectorIterMut for GraphicGroup {
|
||||
fn vector_iter_mut(&mut self) -> impl ExactSizeIterator<Item = &mut VectorData> {
|
||||
self.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).collect::<Vec<_>>().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node_impl(AssignColorsNode)]
|
||||
fn assign_colors_node(vector_data: VectorData, fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, seed: u32, repeat_every: u32) -> GraphicGroup {
|
||||
let mut vector_data_list: Vec<_> = vector_data
|
||||
.region_bezier_paths()
|
||||
.map(|(_, subpath)| {
|
||||
let mut vector = VectorData::from_subpath(subpath);
|
||||
|
||||
vector.style = vector_data.style.clone();
|
||||
|
||||
crate::GraphicElement::VectorData(Box::new(vector))
|
||||
})
|
||||
.collect();
|
||||
let list = (vector_data_list.len(), vector_data_list.iter_mut().map(|element| element.as_vector_data_mut().unwrap()));
|
||||
|
||||
assign_colors(
|
||||
list,
|
||||
AlignColorsOptions {
|
||||
fill,
|
||||
stroke,
|
||||
gradient,
|
||||
reverse,
|
||||
randomize,
|
||||
seed,
|
||||
repeat_every,
|
||||
},
|
||||
);
|
||||
|
||||
let mut group = GraphicGroup::new(vector_data_list);
|
||||
group.transform = vector_data.transform;
|
||||
group.alpha_blending = vector_data.alpha_blending;
|
||||
|
||||
group
|
||||
impl VectorIterMut for VectorData {
|
||||
fn vector_iter_mut(&mut self) -> impl ExactSizeIterator<Item = &mut VectorData> {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
|
||||
struct AlignColorsOptions {
|
||||
fill: bool,
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||
async fn assign_colors<T: VectorIterMut>(
|
||||
footprint: Footprint,
|
||||
#[implementations((Footprint, GraphicGroup), (Footprint, VectorData))] vector_group: impl Node<Footprint, Output = T>,
|
||||
#[default(true)] fill: bool,
|
||||
stroke: bool,
|
||||
gradient: GradientStops,
|
||||
reverse: bool,
|
||||
randomize: bool,
|
||||
seed: u32,
|
||||
seed: SeedValue,
|
||||
repeat_every: u32,
|
||||
}
|
||||
) -> T {
|
||||
let mut input = vector_group.eval(footprint).await;
|
||||
let vector_data = input.vector_iter_mut();
|
||||
let length = vector_data.len();
|
||||
let gradient = if reverse { gradient.reversed() } else { gradient };
|
||||
|
||||
fn assign_colors<'a>((length, vector_data): (usize, impl Iterator<Item = &'a mut VectorData>), options: AlignColorsOptions) {
|
||||
let gradient = if options.reverse { options.gradient.reversed() } else { options.gradient };
|
||||
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(options.seed as u64);
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||
|
||||
for (i, vector_data) in vector_data.enumerate() {
|
||||
let factor = match options.randomize {
|
||||
let factor = match randomize {
|
||||
true => rng.gen::<f64>(),
|
||||
false => match options.repeat_every {
|
||||
false => match repeat_every {
|
||||
0 => i as f64 / (length - 1) as f64,
|
||||
1 => 0.,
|
||||
_ => i as f64 % options.repeat_every as f64 / (options.repeat_every - 1) as f64,
|
||||
_ => i as f64 % repeat_every as f64 / (repeat_every - 1) as f64,
|
||||
},
|
||||
};
|
||||
|
||||
let color = gradient.evalute(factor);
|
||||
|
||||
if options.fill {
|
||||
if fill {
|
||||
vector_data.style.set_fill(Fill::Solid(color));
|
||||
}
|
||||
if options.stroke {
|
||||
if stroke {
|
||||
if let Some(stroke) = vector_data.style.stroke().and_then(|stroke| stroke.with_color(&Some(color))) {
|
||||
vector_data.style.set_stroke(stroke);
|
||||
}
|
||||
}
|
||||
}
|
||||
input
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SetFillNode<Fill> {
|
||||
fill: Fill,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(SetFillNode)]
|
||||
fn set_vector_data_fill<T: Into<Fill>>(mut vector_data: VectorData, fill: T) -> VectorData {
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||
async fn fill<T: Into<Fill> + 'n + Send>(
|
||||
footprint: Footprint,
|
||||
vector_data: impl Node<Footprint, Output = VectorData>,
|
||||
#[implementations(Fill, Color, Option<Color>, crate::vector::style::Gradient)] fill: T, // TODO: Set the default to black
|
||||
_backup_color: Option<Color>,
|
||||
_backup_gradient: Gradient,
|
||||
) -> VectorData {
|
||||
let mut vector_data = vector_data.eval(footprint).await;
|
||||
vector_data.style.set_fill(fill.into());
|
||||
|
||||
vector_data
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SetStrokeNode<Color, Weight, DashLengths, DashOffset, LineCap, LineJoin, MiterLimit> {
|
||||
color: Color,
|
||||
weight: Weight,
|
||||
dash_lengths: DashLengths,
|
||||
dash_offset: DashOffset,
|
||||
line_cap: LineCap,
|
||||
line_join: LineJoin,
|
||||
miter_limit: MiterLimit,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(SetStrokeNode)]
|
||||
fn set_vector_data_stroke(
|
||||
mut vector_data: VectorData,
|
||||
color: Option<Color>,
|
||||
weight: f64,
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||
async fn stroke(
|
||||
footprint: Footprint,
|
||||
vector_data: impl Node<Footprint, Output = VectorData>,
|
||||
color: Option<Color>, // TODO: Set the default to black
|
||||
#[default(5.)] weight: f64,
|
||||
dash_lengths: Vec<f64>,
|
||||
dash_offset: f64,
|
||||
line_cap: super::style::LineCap,
|
||||
line_join: super::style::LineJoin,
|
||||
miter_limit: f64,
|
||||
line_cap: crate::vector::style::LineCap,
|
||||
line_join: crate::vector::style::LineJoin,
|
||||
#[default(4.)] miter_limit: f64,
|
||||
) -> VectorData {
|
||||
let mut vector_data = vector_data.eval(footprint).await;
|
||||
vector_data.style.set_stroke(Stroke {
|
||||
color,
|
||||
weight,
|
||||
@@ -161,28 +109,22 @@ fn set_vector_data_stroke(
|
||||
vector_data
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RepeatNode<Direction, Angle, Instances> {
|
||||
direction: Direction,
|
||||
angle: Angle,
|
||||
instances: Instances,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(RepeatNode)]
|
||||
fn repeat_vector_data(vector_data: VectorData, direction: DVec2, angle: f64, instances: u32) -> VectorData {
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn repeat(footprint: Footprint, instance: impl Node<Footprint, Output = VectorData>, #[default(100., 100.)] direction: DVec2, angle: Angle, #[default(4)] instances: IntegerCount) -> VectorData {
|
||||
let instance = instance.eval(footprint).await;
|
||||
let angle = angle.to_radians();
|
||||
let instances = instances.max(1);
|
||||
let total = (instances - 1) as f64;
|
||||
|
||||
if instances == 1 {
|
||||
return vector_data;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Repeat the vector data
|
||||
let mut result = VectorData::empty();
|
||||
|
||||
let Some(bounding_box) = vector_data.bounding_box_with_transform(vector_data.transform) else {
|
||||
return vector_data;
|
||||
let Some(bounding_box) = instance.bounding_box_with_transform(instance.transform) else {
|
||||
return instance;
|
||||
};
|
||||
let center = (bounding_box[0] + bounding_box[1]) / 2.;
|
||||
|
||||
@@ -192,31 +134,31 @@ fn repeat_vector_data(vector_data: VectorData, direction: DVec2, angle: f64, ins
|
||||
|
||||
let transform = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
|
||||
|
||||
result.concat(&vector_data, transform);
|
||||
result.concat(&instance, transform);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CircularRepeatNode<AngleOffset, Radius, Instances> {
|
||||
angle_offset: AngleOffset,
|
||||
radius: Radius,
|
||||
instances: Instances,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(CircularRepeatNode)]
|
||||
fn circular_repeat_vector_data(vector_data: VectorData, angle_offset: f64, radius: f64, instances: u32) -> VectorData {
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn circular_repeat(
|
||||
footprint: Footprint,
|
||||
instance: impl Node<Footprint, Output = VectorData>,
|
||||
angle_offset: Angle,
|
||||
#[default(5)] radius: Length,
|
||||
#[default(5)] instances: IntegerCount,
|
||||
) -> VectorData {
|
||||
let instance = instance.eval(footprint).await;
|
||||
let instances = instances.max(1);
|
||||
|
||||
if instances == 1 {
|
||||
return vector_data;
|
||||
return instance;
|
||||
}
|
||||
|
||||
let mut result = VectorData::empty();
|
||||
|
||||
let Some(bounding_box) = vector_data.bounding_box_with_transform(vector_data.transform) else {
|
||||
return vector_data;
|
||||
let Some(bounding_box) = instance.bounding_box_with_transform(instance.transform) else {
|
||||
return instance;
|
||||
};
|
||||
let center = (bounding_box[0] + bounding_box[1]) / 2.;
|
||||
|
||||
@@ -226,27 +168,27 @@ fn circular_repeat_vector_data(vector_data: VectorData, angle_offset: f64, radiu
|
||||
let angle = (std::f64::consts::TAU / instances as f64) * i as f64 + angle_offset.to_radians();
|
||||
let rotation = DAffine2::from_angle(angle);
|
||||
let transform = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
|
||||
result.concat(&vector_data, transform);
|
||||
result.concat(&instance, transform);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BoundingBoxNode;
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn bounding_box<F: 'n + Copy + Send>(
|
||||
#[implementations((), Footprint)] footprint: F,
|
||||
#[implementations(((), VectorData), (Footprint, VectorData))] vector_data: impl Node<F, Output = VectorData>,
|
||||
) -> VectorData {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
|
||||
#[node_macro::node_fn(BoundingBoxNode)]
|
||||
fn generate_bounding_box(vector_data: VectorData) -> VectorData {
|
||||
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap();
|
||||
VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1]))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SolidifyStrokeNode;
|
||||
|
||||
#[node_macro::node_fn(SolidifyStrokeNode)]
|
||||
fn solidify_stroke(vector_data: VectorData) -> VectorData {
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn solidify_stroke(footprint: Footprint, vector_data: impl Node<Footprint, Output = VectorData>) -> VectorData {
|
||||
// Grab what we need from original data.
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
let VectorData { transform, style, .. } = &vector_data;
|
||||
let subpaths = vector_data.stroke_bezier_paths();
|
||||
let mut result = VectorData::empty();
|
||||
@@ -306,33 +248,22 @@ impl ConcatElement for GraphicGroup {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CopyToPoints<Points, Instance, RandomScaleMin, RandomScaleMax, RandomScaleBias, RandomScaleSeed, RandomRotation, RandomRotationSeed> {
|
||||
points: Points,
|
||||
instance: Instance,
|
||||
random_scale_min: RandomScaleMin,
|
||||
random_scale_max: RandomScaleMax,
|
||||
random_scale_bias: RandomScaleBias,
|
||||
random_scale_seed: RandomScaleSeed,
|
||||
random_rotation: RandomRotation,
|
||||
random_rotation_seed: RandomRotationSeed,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[node_macro::node_fn(CopyToPoints)]
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + TransformMut + Send>(
|
||||
footprint: Footprint,
|
||||
points: impl Node<Footprint, Output = VectorData>,
|
||||
#[expose]
|
||||
#[implementations((Footprint, VectorData), (Footprint, GraphicGroup))]
|
||||
instance: impl Node<Footprint, Output = I>,
|
||||
random_scale_min: f64,
|
||||
random_scale_max: f64,
|
||||
#[default(1)] random_scale_min: f64,
|
||||
#[default(1)] random_scale_max: f64,
|
||||
random_scale_bias: f64,
|
||||
random_scale_seed: u32,
|
||||
random_rotation: f64,
|
||||
random_rotation_seed: u32,
|
||||
random_scale_seed: SeedValue,
|
||||
random_rotation: Angle,
|
||||
random_rotation_seed: SeedValue,
|
||||
) -> I {
|
||||
let points = self.points.eval(footprint).await;
|
||||
let instance = self.instance.eval(footprint).await;
|
||||
let points = points.eval(footprint).await;
|
||||
let instance = instance.eval(footprint).await;
|
||||
let random_scale_difference = random_scale_max - random_scale_min;
|
||||
|
||||
let points_list = points.point_domain.positions();
|
||||
@@ -340,8 +271,8 @@ async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + Tr
|
||||
let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY).unwrap_or_default();
|
||||
let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]);
|
||||
|
||||
let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed as u64);
|
||||
let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed as u64);
|
||||
let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into());
|
||||
let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into());
|
||||
|
||||
let do_scale = random_scale_difference.abs() > 1e-6;
|
||||
let do_rotation = random_rotation.abs() > 1e-6;
|
||||
@@ -379,28 +310,18 @@ async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + Tr
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SamplePoints<VectorData, Spacing, StartOffset, StopOffset, AdaptiveSpacing, LengthsOfSegmentsOfSubpaths> {
|
||||
vector_data: VectorData,
|
||||
spacing: Spacing,
|
||||
start_offset: StartOffset,
|
||||
stop_offset: StopOffset,
|
||||
adaptive_spacing: AdaptiveSpacing,
|
||||
lengths_of_segments_of_subpaths: LengthsOfSegmentsOfSubpaths,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(SamplePoints)]
|
||||
#[node_macro::node(category(""))]
|
||||
async fn sample_points(
|
||||
footprint: Footprint,
|
||||
mut vector_data: impl Node<Footprint, Output = VectorData>,
|
||||
vector_data: impl Node<Footprint, Output = VectorData>,
|
||||
spacing: f64,
|
||||
start_offset: f64,
|
||||
stop_offset: f64,
|
||||
adaptive_spacing: bool,
|
||||
lengths_of_segments_of_subpaths: impl Node<Footprint, Output = Vec<f64>>,
|
||||
) -> VectorData {
|
||||
let vector_data = self.vector_data.eval(footprint).await;
|
||||
let lengths_of_segments_of_subpaths = self.lengths_of_segments_of_subpaths.eval(footprint).await;
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
let lengths_of_segments_of_subpaths = lengths_of_segments_of_subpaths.eval(footprint).await;
|
||||
|
||||
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
|
||||
|
||||
@@ -463,16 +384,24 @@ async fn sample_points(
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PoissonDiskPoints<SeparationDiskDiameter, Seed> {
|
||||
separation_disk_diameter: SeparationDiskDiameter,
|
||||
seed: Seed,
|
||||
}
|
||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||
async fn poisson_disk_points<F: 'n + Copy + Send>(
|
||||
#[implementations((), Footprint)] footprint: F,
|
||||
#[implementations(((), VectorData), (Footprint, VectorData))] vector_data: impl Node<F, Output = VectorData>,
|
||||
#[default(10.)]
|
||||
#[min(0.01)]
|
||||
separation_disk_diameter: f64,
|
||||
seed: SeedValue,
|
||||
) -> VectorData {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
|
||||
#[node_macro::node_fn(PoissonDiskPoints)]
|
||||
fn poisson_disk_points(vector_data: VectorData, separation_disk_diameter: f64, seed: u32) -> VectorData {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed as u64);
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||
let mut result = VectorData::empty();
|
||||
|
||||
if separation_disk_diameter <= 0.01 {
|
||||
return result;
|
||||
}
|
||||
|
||||
for mut subpath in vector_data.stroke_bezier_paths() {
|
||||
if subpath.manipulator_groups().len() < 3 {
|
||||
continue;
|
||||
@@ -488,22 +417,18 @@ fn poisson_disk_points(vector_data: VectorData, separation_disk_diameter: f64, s
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LengthsOfSegmentsOfSubpaths;
|
||||
#[node_macro::node(name("Lengths of Segments of Subpaths"), category(""))]
|
||||
async fn lengths_of_segments_of_subpaths(footprint: Footprint, vector_data: impl Node<Footprint, Output = VectorData>) -> Vec<f64> {
|
||||
let vector_data = vector_data.eval(footprint).await;
|
||||
|
||||
#[node_macro::node_fn(LengthsOfSegmentsOfSubpaths)]
|
||||
fn lengths_of_segments_of_subpaths(vector_data: VectorData) -> Vec<f64> {
|
||||
vector_data
|
||||
.segment_bezier_iter()
|
||||
.map(|(_id, bezier, _, _)| bezier.apply_transformation(|point| vector_data.transform.transform_point2(point)).length(None))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SplinesFromPointsNode;
|
||||
|
||||
#[node_macro::node_fn(SplinesFromPointsNode)]
|
||||
fn splines_from_points(mut vector_data: VectorData) -> VectorData {
|
||||
#[node_macro::node(name("Splines from Points"), category(""), path(graphene_core::vector))]
|
||||
fn splines_from_points(_: (), mut vector_data: VectorData) -> VectorData {
|
||||
let points = &vector_data.point_domain;
|
||||
|
||||
vector_data.segment_domain.clear();
|
||||
@@ -527,17 +452,18 @@ fn splines_from_points(mut vector_data: VectorData) -> VectorData {
|
||||
vector_data
|
||||
}
|
||||
|
||||
pub struct MorphNode<Source, Target, StartIndex, Time> {
|
||||
source: Source,
|
||||
target: Target,
|
||||
start_index: StartIndex,
|
||||
time: Time,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(MorphNode)]
|
||||
async fn morph(footprint: Footprint, source: impl Node<Footprint, Output = VectorData>, target: impl Node<Footprint, Output = VectorData>, start_index: u32, time: f64) -> VectorData {
|
||||
let source = self.source.eval(footprint).await;
|
||||
let target = self.target.eval(footprint).await;
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn morph(
|
||||
footprint: Footprint,
|
||||
source: impl Node<Footprint, Output = VectorData>,
|
||||
#[expose] target: impl Node<Footprint, Output = VectorData>,
|
||||
#[range((0., 1.))]
|
||||
#[default(0.5)]
|
||||
time: Fraction,
|
||||
#[min(0.)] start_index: IntegerCount,
|
||||
) -> VectorData {
|
||||
let source = source.eval(footprint).await;
|
||||
let target = target.eval(footprint).await;
|
||||
let mut result = VectorData::empty();
|
||||
|
||||
// Lerp styles
|
||||
@@ -617,14 +543,9 @@ async fn morph(footprint: Footprint, source: impl Node<Footprint, Output = Vecto
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AreaNode<VectorData> {
|
||||
vector_data: VectorData,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(AreaNode)]
|
||||
async fn area_node(empty: (), vector_data: impl Node<Footprint, Output = VectorData>) -> f64 {
|
||||
let vector_data = self.vector_data.eval(Footprint::default()).await;
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn area(_: (), vector_data: impl Node<Footprint, Output = VectorData>) -> f64 {
|
||||
let vector_data = vector_data.eval(Footprint::default()).await;
|
||||
|
||||
let mut area = 0.;
|
||||
let scale = vector_data.transform.decompose_scale();
|
||||
@@ -634,15 +555,9 @@ async fn area_node(empty: (), vector_data: impl Node<Footprint, Output = VectorD
|
||||
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(empty: (), vector_data: impl Node<Footprint, Output = VectorData>, centroid_type: CentroidType) -> DVec2 {
|
||||
let vector_data = self.vector_data.eval(Footprint::default()).await;
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn centroid(_: (), vector_data: impl Node<Footprint, Output = VectorData>, centroid_type: CentroidType) -> DVec2 {
|
||||
let vector_data = vector_data.eval(Footprint::default()).await;
|
||||
|
||||
if centroid_type == CentroidType::Area {
|
||||
let mut area = 0.;
|
||||
@@ -689,65 +604,50 @@ async fn centroid_node(empty: (), vector_data: impl Node<Footprint, Output = Vec
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::transform::CullNode;
|
||||
use crate::value::ClonedNode;
|
||||
use crate::Node;
|
||||
|
||||
use bezier_rs::Bezier;
|
||||
|
||||
use std::pin::Pin;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FutureWrapperNode<Node: Clone>(Node);
|
||||
pub struct FutureWrapperNode<T: Clone>(T);
|
||||
|
||||
impl<'i, T: 'i, N: Node<'i, T> + Clone> Node<'i, T> for FutureWrapperNode<N>
|
||||
where
|
||||
N: Node<'i, T, Output: Send>,
|
||||
{
|
||||
type Output = Pin<Box<dyn core::future::Future<Output = N::Output> + 'i + Send>>;
|
||||
fn eval(&'i self, input: T) -> Self::Output {
|
||||
let result = self.0.eval(input);
|
||||
Box::pin(async move { result })
|
||||
impl<'i, T: 'i + Clone + Send> Node<'i, Footprint> for FutureWrapperNode<T> {
|
||||
type Output = Pin<Box<dyn core::future::Future<Output = T> + 'i + Send>>;
|
||||
fn eval(&'i self, _input: Footprint) -> Self::Output {
|
||||
let value = self.0.clone();
|
||||
Box::pin(async move { value })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeat() {
|
||||
fn vector_node(data: Subpath<PointId>) -> FutureWrapperNode<VectorData> {
|
||||
FutureWrapperNode(VectorData::from_subpath(data))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repeat() {
|
||||
let direction = DVec2::X * 1.5;
|
||||
let instances = 3;
|
||||
let repeated = RepeatNode {
|
||||
direction: ClonedNode::new(direction),
|
||||
angle: ClonedNode::new(0.),
|
||||
instances: ClonedNode::new(instances),
|
||||
}
|
||||
.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)));
|
||||
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||
assert_eq!(repeated.region_bezier_paths().count(), 3);
|
||||
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
|
||||
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn repeat_transform_position() {
|
||||
#[tokio::test]
|
||||
async fn repeat_transform_position() {
|
||||
let direction = DVec2::new(12., 10.);
|
||||
let instances = 8;
|
||||
let repeated = RepeatNode {
|
||||
direction: ClonedNode::new(direction),
|
||||
angle: ClonedNode::new(0.),
|
||||
instances: ClonedNode::new(instances),
|
||||
}
|
||||
.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)));
|
||||
let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||
assert_eq!(repeated.region_bezier_paths().count(), 8);
|
||||
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
|
||||
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn circle_repeat() {
|
||||
let repeated = CircularRepeatNode {
|
||||
angle_offset: ClonedNode::new(45.),
|
||||
radius: ClonedNode::new(4.),
|
||||
instances: ClonedNode::new(8),
|
||||
}
|
||||
.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)));
|
||||
#[tokio::test]
|
||||
async fn circle_repeat() {
|
||||
let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
|
||||
assert_eq!(repeated.region_bezier_paths().count(), 8);
|
||||
for (index, (_, subpath)) in repeated.region_bezier_paths().enumerate() {
|
||||
let expected_angle = (index as f64 + 1.) * 45.;
|
||||
@@ -756,9 +656,12 @@ mod test {
|
||||
assert!((actual_angle - expected_angle).abs() % 360. < 1e-5);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn bounding_box() {
|
||||
let bounding_box = BoundingBoxNode.eval(VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)));
|
||||
#[tokio::test]
|
||||
async fn bounding_box() {
|
||||
let bounding_box = BoundingBoxNode {
|
||||
vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)),
|
||||
};
|
||||
let bounding_box = bounding_box.eval(Footprint::default()).await;
|
||||
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
|
||||
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
|
||||
assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);
|
||||
@@ -766,7 +669,11 @@ mod test {
|
||||
// test a VectorData with non-zero rotation
|
||||
let mut square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE));
|
||||
square.transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4);
|
||||
let bounding_box = BoundingBoxNode.eval(square);
|
||||
let bounding_box = BoundingBoxNode {
|
||||
vector_data: FutureWrapperNode(square),
|
||||
}
|
||||
.eval(Footprint::default())
|
||||
.await;
|
||||
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
|
||||
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
|
||||
let sqrt2 = core::f64::consts::SQRT_2;
|
||||
@@ -775,20 +682,10 @@ mod test {
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn copy_to_points() {
|
||||
let points = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.));
|
||||
let expected_points = points.point_domain.positions().to_vec();
|
||||
let bounding_box = CopyToPoints {
|
||||
points: CullNode::new(FutureWrapperNode(ClonedNode(points))),
|
||||
instance: CullNode::new(FutureWrapperNode(ClonedNode(VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))))),
|
||||
random_scale_min: FutureWrapperNode(ClonedNode(1.)),
|
||||
random_scale_max: FutureWrapperNode(ClonedNode(1.)),
|
||||
random_scale_bias: FutureWrapperNode(ClonedNode(0.)),
|
||||
random_scale_seed: FutureWrapperNode(ClonedNode(0)),
|
||||
random_rotation: FutureWrapperNode(ClonedNode(0.)),
|
||||
random_rotation_seed: FutureWrapperNode(ClonedNode(0)),
|
||||
}
|
||||
.eval(Footprint::default())
|
||||
.await;
|
||||
let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.);
|
||||
let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE);
|
||||
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
|
||||
let bounding_box = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await;
|
||||
assert_eq!(bounding_box.region_bezier_paths().count(), expected_points.len());
|
||||
for (index, (_, subpath)) in bounding_box.region_bezier_paths().enumerate() {
|
||||
let offset = expected_points[index];
|
||||
@@ -800,17 +697,8 @@ mod test {
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn sample_points() {
|
||||
let path = VectorData::from_subpath(Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)));
|
||||
let sample_points = SamplePoints {
|
||||
vector_data: CullNode::new(FutureWrapperNode(ClonedNode(path))),
|
||||
spacing: FutureWrapperNode(ClonedNode(30.)),
|
||||
start_offset: FutureWrapperNode(ClonedNode(0.)),
|
||||
stop_offset: FutureWrapperNode(ClonedNode(0.)),
|
||||
adaptive_spacing: FutureWrapperNode(ClonedNode(false)),
|
||||
lengths_of_segments_of_subpaths: CullNode::new(FutureWrapperNode(ClonedNode(vec![100.]))),
|
||||
}
|
||||
.eval(Footprint::default())
|
||||
.await;
|
||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 30., 0., 0., false, &FutureWrapperNode(vec![100.])).await;
|
||||
assert_eq!(sample_points.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
||||
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
|
||||
@@ -818,29 +706,22 @@ mod test {
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn adaptive_spacing() {
|
||||
let path = VectorData::from_subpath(Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)));
|
||||
let sample_points = SamplePoints {
|
||||
vector_data: CullNode::new(FutureWrapperNode(ClonedNode(path))),
|
||||
spacing: FutureWrapperNode(ClonedNode(18.)),
|
||||
start_offset: FutureWrapperNode(ClonedNode(45.)),
|
||||
stop_offset: FutureWrapperNode(ClonedNode(10.)),
|
||||
adaptive_spacing: FutureWrapperNode(ClonedNode(true)),
|
||||
lengths_of_segments_of_subpaths: CullNode::new(FutureWrapperNode(ClonedNode(vec![100.]))),
|
||||
}
|
||||
.eval(Footprint::default())
|
||||
.await;
|
||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 18., 45., 10., true, &FutureWrapperNode(vec![100.])).await;
|
||||
assert_eq!(sample_points.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
||||
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn poisson() {
|
||||
let sample_points = PoissonDiskPoints {
|
||||
separation_disk_diameter: ClonedNode(10. * std::f64::consts::SQRT_2),
|
||||
seed: ClonedNode(0),
|
||||
}
|
||||
.eval(VectorData::from_subpath(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)));
|
||||
#[tokio::test]
|
||||
async fn poisson() {
|
||||
let sample_points = super::poisson_disk_points(
|
||||
Footprint::default(),
|
||||
&vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)),
|
||||
10. * std::f64::consts::SQRT_2,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
(20..=40).contains(&sample_points.point_domain.positions().len()),
|
||||
"actual len {}",
|
||||
@@ -850,31 +731,24 @@ mod test {
|
||||
assert!(point.length() < 50. + 1., "Expected point in circle {point}")
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn lengths() {
|
||||
let subpath = VectorData::from_subpath(Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)));
|
||||
let lengths = LengthsOfSegmentsOfSubpaths.eval(subpath);
|
||||
#[tokio::test]
|
||||
async fn lengths() {
|
||||
let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let lengths = lengths_of_segments_of_subpaths(Footprint::default(), &vector_node(subpath)).await;
|
||||
assert_eq!(lengths, vec![100.]);
|
||||
}
|
||||
#[test]
|
||||
fn spline() {
|
||||
let subpath = VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.));
|
||||
let spline = SplinesFromPointsNode.eval(subpath);
|
||||
let spline = splines_from_points((), subpath);
|
||||
assert_eq!(spline.stroke_bezier_paths().count(), 1);
|
||||
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn morph() {
|
||||
let source = VectorData::from_subpath(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.));
|
||||
let target = VectorData::from_subpath(Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO));
|
||||
let sample_points = MorphNode {
|
||||
source: CullNode::new(FutureWrapperNode(ClonedNode(source))),
|
||||
target: CullNode::new(FutureWrapperNode(ClonedNode(target))),
|
||||
time: FutureWrapperNode(ClonedNode(0.5)),
|
||||
start_index: FutureWrapperNode(ClonedNode(0)),
|
||||
}
|
||||
.eval(Footprint::default())
|
||||
.await;
|
||||
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
||||
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
|
||||
let sample_points = super::morph(Footprint::default(), &vector_node(source), &vector_node(target), 0.5, 0).await;
|
||||
assert_eq!(
|
||||
&sample_points.point_domain.positions()[..4],
|
||||
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]
|
||||
|
||||
Reference in New Issue
Block a user