New node: Mandelbrot (#1421)

* Implement Mandelbrot node

* Make mandelbrot monochrome

* Remove log statements
This commit is contained in:
Dennis Kobert 2023-09-14 11:08:57 +02:00 committed by Keavon Chambers
parent 833f41bccb
commit 34f2d61257
3 changed files with 74 additions and 3 deletions

View file

@ -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<Color>) -> ImageFrame<Co
};
// we need to adjust the offset if we truncate the offset calculation
let new_transform = image_frame.transform * DAffine2::from_translation(offset) * DAffine2::from_scale(DVec2::new(size.x, size.y));
let new_transform = image_frame.transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
ImageFrame { image, transform: new_transform }
}
@ -567,6 +567,67 @@ fn pixel_noise(width: u32, height: u32, seed: u32, noise_type: NoiseType) -> gra
}
}
#[derive(Debug, Clone, Copy)]
pub struct MandelbrotNode;
#[node_macro::node_fn(MandelbrotNode)]
fn mandelbrot_node(footprint: Footprint) -> ImageFrame<Color> {
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 {

View file

@ -733,6 +733,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::transform::CullNode<_>, 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<Color>]),
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]),