diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 89bf77a7..86fc35f5 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -2089,7 +2089,16 @@ fn static_nodes() -> Vec { identifier: NodeImplementation::proto("graphene_std::raster::SampleNode<_>"), manual_composition: Some(concrete!(Footprint)), inputs: vec![DocumentInputType::value("Raseter Data", TaggedValue::ImageFrame(ImageFrame::empty()), true)], - outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Raster)], + outputs: vec![DocumentOutputType::new("Raster", FrontendGraphDataType::Raster)], + ..Default::default() + }, + DocumentNodeType { + name: "Mandelbrot", + category: "Generators", + identifier: NodeImplementation::proto("graphene_std::raster::MandelbrotNode"), + manual_composition: Some(concrete!(Footprint)), + inputs: vec![], + outputs: vec![DocumentOutputType::new("Raster", FrontendGraphDataType::Raster)], ..Default::default() }, DocumentNodeType { diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index c0ad26ed..2f1b3d52 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -1,5 +1,5 @@ use dyn_any::{DynAny, StaticType}; -use glam::{DAffine2, DVec2}; +use glam::{DAffine2, DVec2, Vec2}; use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; use graph_craft::proto::DynFuture; use graphene_core::raster::{Alpha, BlendMode, BlendNode, Image, ImageFrame, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, Raster, RasterMut, RedGreenBlue, Sample}; @@ -111,7 +111,7 @@ fn sample(footprint: Footprint, image_frame: ImageFrame) -> ImageFrame gra } } +#[derive(Debug, Clone, Copy)] +pub struct MandelbrotNode; + +#[node_macro::node_fn(MandelbrotNode)] +fn mandelbrot_node(footprint: Footprint) -> ImageFrame { + let viewport_bounds = footprint.viewport_bounds_in_local_space(); + + let width = footprint.resolution.x; + let height = footprint.resolution.y; + + let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox(); + let intersection = viewport_bounds.intersect(&image_bounds); + let size = intersection.size(); + + // If the image would not be visible, return an empty image + if size.x <= 0. || size.y <= 0. { + return ImageFrame::empty(); + } + + let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO); + + let width = footprint.transform.transform_vector2(DVec2::X * size.x).length() as u32; + let height = footprint.transform.transform_vector2(DVec2::Y * size.y).length() as u32; + + let mut data = Vec::with_capacity(width as usize * height as usize); + let max_iter = 255; + + let scale = 3. * size.as_vec2() / Vec2::new(width as f32, height as f32); + let coordinate_offset = offset.as_vec2() * 3. - Vec2::new(2., 1.5); + for y in 0..height { + for x in 0..width { + let pos = Vec2::new(x as f32, y as f32); + let c = pos * scale + coordinate_offset; + + let iter = mandelbrot(c, max_iter); + data.push(map_color(iter, max_iter)); + } + } + ImageFrame { + image: Image { width, height, data }, + transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), + } +} + +#[inline(always)] +fn mandelbrot(c: Vec2, max_iter: usize) -> usize { + let mut z = Vec2::new(0.0, 0.0); + for i in 0..max_iter { + z = Vec2::new(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; + if z.length_squared() > 4.0 { + return i; + } + } + max_iter +} + +fn map_color(iter: usize, max_iter: usize) -> Color { + let v = iter as f32 / max_iter as f32; + Color::from_rgbaf32_unchecked(v, v, v, 1.) +} + #[cfg(test)] mod test { diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 23def063..481316ad 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -733,6 +733,7 @@ fn node_registry() -> HashMap, input: Footprint, params: [graphene_core::Artboard]), register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [graphene_core::GraphicGroup]), register_node!(graphene_std::raster::SampleNode<_>, input: Footprint, params: [ImageFrame]), + register_node!(graphene_std::raster::MandelbrotNode, input: Footprint, params: []), register_node!(graphene_core::vector::ResamplePoints<_>, input: VectorData, params: [f64]), register_node!(graphene_core::vector::SplineFromPointsNode, input: VectorData, params: []), register_node!(graphene_core::vector::generator_nodes::CircleGenerator<_>, input: (), params: [f32]),