diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/document_node_types.rs index a3dd34af..2ef6c16d 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_types.rs @@ -776,48 +776,10 @@ fn static_nodes() -> Vec { identifier: "Noise Pattern", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0)], - nodes: vec![ - DocumentNode { - inputs: vec![ - NodeInput::network(concrete!(()), 0), - NodeInput::network(concrete!(UVec2), 1), - NodeInput::network(concrete!(u32), 2), - NodeInput::network(concrete!(f64), 3), - NodeInput::network(concrete!(graphene_core::raster::NoiseType), 4), - NodeInput::network(concrete!(graphene_core::raster::FractalType), 5), - NodeInput::network(concrete!(f64), 6), - NodeInput::network(concrete!(graphene_core::raster::FractalType), 7), - NodeInput::network(concrete!(u32), 8), - NodeInput::network(concrete!(f64), 9), - NodeInput::network(concrete!(f64), 10), - NodeInput::network(concrete!(f64), 11), - NodeInput::network(concrete!(f64), 12), - NodeInput::network(concrete!(graphene_core::raster::CellularDistanceFunction), 13), - NodeInput::network(concrete!(graphene_core::raster::CellularReturnType), 14), - NodeInput::network(concrete!(f64), 15), - ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::raster::NoisePatternNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _>")), - ..Default::default() - }, - // TODO: Make noise pattern node resolution aware and remove the cull node - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode<_>")), - manual_composition: Some(concrete!(Footprint)), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), + manual_composition: Some(concrete!(Footprint)), + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::raster::NoisePatternNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _>")), inputs: vec![ - NodeInput::value(TaggedValue::None, false), - NodeInput::value(TaggedValue::UVec2((512, 512).into()), false), + NodeInput::value(TaggedValue::Bool(true), false), NodeInput::value(TaggedValue::U32(0), false), NodeInput::value(TaggedValue::F64(10.), false), NodeInput::value(TaggedValue::NoiseType(NoiseType::default()), false), @@ -837,8 +799,7 @@ fn static_nodes() -> Vec { }, persistent_node_metadata: DocumentNodePersistentMetadata { input_names: vec![ - "None".to_string(), - "Dimensions".to_string(), + "Clip".to_string(), "Seed".to_string(), "Scale".to_string(), "Noise Type".to_string(), diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 95ff38bc..41504b89 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -945,15 +945,15 @@ pub fn extract_channel_properties(document_node: &DocumentNode, node_id: NodeId, // As soon as there are more types of noise, this should be uncommented. pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { // Get the current values of the inputs of interest so they can set whether certain inputs are disabled based on various conditions. - let current_noise_type = match &document_node.inputs[4].as_value() { + let current_noise_type = match &document_node.inputs[3].as_value() { Some(&TaggedValue::NoiseType(noise_type)) => Some(noise_type), _ => None, }; - let current_domain_warp_type = match &document_node.inputs[5].as_value() { + let current_domain_warp_type = match &document_node.inputs[4].as_value() { Some(&TaggedValue::DomainWarpType(domain_warp_type)) => Some(domain_warp_type), _ => None, }; - let current_fractal_type = match &document_node.inputs[7].as_value() { + let current_fractal_type = match &document_node.inputs[6].as_value() { Some(&TaggedValue::FractalType(fractal_type)) => Some(fractal_type), _ => None, }; @@ -966,28 +966,30 @@ pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _ !domain_warp_active && (current_fractal_type == Some(FractalType::DomainWarpIndependent) || current_fractal_type == Some(FractalType::DomainWarpProgressive)); // All - let dimensions = vec2_widget(document_node, node_id, 1, "Dimensions", "W", "H", "px", Some(1.), add_blank_assist); - let seed = number_widget(document_node, node_id, 2, "Seed", NumberInput::default().min(0.).is_integer(true), true); - let scale = number_widget(document_node, node_id, 3, "Scale", NumberInput::default().min(0.).disabled(!coherent_noise_active), true); - let noise_type_row = noise_type(document_node, node_id, 4, "Noise Type", true); + let clip = LayoutGroup::Row { + widgets: bool_widget(document_node, node_id, 0, "Clip", true), + }; + let seed = number_widget(document_node, node_id, 1, "Seed", NumberInput::default().min(0.).is_integer(true), true); + let scale = number_widget(document_node, node_id, 2, "Scale", NumberInput::default().min(0.).disabled(!coherent_noise_active), true); + let noise_type_row = noise_type(document_node, node_id, 3, "Noise Type", true); // Domain Warp - let domain_warp_type_row = domain_warp_type(document_node, node_id, 5, "Domain Warp Type", true, !coherent_noise_active); + let domain_warp_type_row = domain_warp_type(document_node, node_id, 4, "Domain Warp Type", true, !coherent_noise_active); let domain_warp_amplitude = number_widget( document_node, node_id, - 6, + 5, "Domain Warp Amplitude", NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active), true, ); // Fractal - let fractal_type_row = fractal_type(document_node, node_id, 7, "Fractal Type", true, !coherent_noise_active); + let fractal_type_row = fractal_type(document_node, node_id, 6, "Fractal Type", true, !coherent_noise_active); let fractal_octaves = number_widget( document_node, node_id, - 8, + 7, "Fractal Octaves", NumberInput::default() .mode_range() @@ -1001,7 +1003,7 @@ pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _ let fractal_lacunarity = number_widget( document_node, node_id, - 9, + 8, "Fractal Lacunarity", NumberInput::default() .mode_range() @@ -1013,7 +1015,7 @@ pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _ let fractal_gain = number_widget( document_node, node_id, - 10, + 9, "Fractal Gain", NumberInput::default() .mode_range() @@ -1025,7 +1027,7 @@ pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _ let fractal_weighted_strength = number_widget( document_node, node_id, - 11, + 10, "Fractal Weighted Strength", NumberInput::default() .mode_range() @@ -1037,7 +1039,7 @@ pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _ let fractal_ping_pong_strength = number_widget( document_node, node_id, - 12, + 11, "Fractal Ping Pong Strength", NumberInput::default() .mode_range() @@ -1048,12 +1050,12 @@ pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _ ); // Cellular - let cellular_distance_function_row = cellular_distance_function(document_node, node_id, 13, "Cellular Distance Function", true, !coherent_noise_active || !cellular_noise_active); - let cellular_return_type = cellular_return_type(document_node, node_id, 14, "Cellular Return Type", true, !coherent_noise_active || !cellular_noise_active); + let cellular_distance_function_row = cellular_distance_function(document_node, node_id, 12, "Cellular Distance Function", true, !coherent_noise_active || !cellular_noise_active); + let cellular_return_type = cellular_return_type(document_node, node_id, 13, "Cellular Return Type", true, !coherent_noise_active || !cellular_noise_active); let cellular_jitter = number_widget( document_node, node_id, - 15, + 14, "Cellular Jitter", NumberInput::default() .mode_range() @@ -1065,7 +1067,7 @@ pub fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _ vec![ // All - dimensions, + clip, LayoutGroup::Row { widgets: seed }, LayoutGroup::Row { widgets: scale }, noise_type_row, diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 05284d49..81cf83bc 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -168,9 +168,7 @@ impl Footprint { } pub fn scale(&self) -> DVec2 { - let x = self.transform.transform_vector2((1., 0.).into()).length(); - let y = self.transform.transform_vector2((0., 1.).into()).length(); - DVec2::new(x, y) + self.transform.decompose_scale() } pub fn offset(&self) -> DVec2 { diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 711c42b1..d8ae1693 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -13,7 +13,7 @@ use graphene_core::value::CopiedNode; use graphene_core::{AlphaBlending, Color, Node, WasmNotSend}; use fastnoise_lite; -use glam::{DAffine2, DVec2, UVec2, Vec2}; +use glam::{DAffine2, DVec2, Vec2}; use rand::prelude::*; use rand_chacha::ChaCha8Rng; use std::collections::HashMap; @@ -586,7 +586,7 @@ fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> graphene_cor #[derive(Debug, Clone, Copy)] pub struct NoisePatternNode< - Dimensions, + Clip, Seed, Scale, NoiseType, @@ -602,7 +602,7 @@ pub struct NoisePatternNode< CellularReturnType, CellularJitter, > { - dimensions: Dimensions, + clip: Clip, seed: Seed, scale: Scale, noise_type: NoiseType, @@ -622,8 +622,8 @@ pub struct NoisePatternNode< #[node_macro::node_fn(NoisePatternNode)] #[allow(clippy::too_many_arguments)] fn noise_pattern( - _no_primary_input: (), - dimensions: UVec2, + footprint: Footprint, + clip: bool, seed: u32, scale: f64, noise_type: NoiseType, @@ -639,11 +639,33 @@ fn noise_pattern( cellular_return_type: CellularReturnType, cellular_jitter: f64, ) -> graphene_core::raster::ImageFrame { + let viewport_bounds = footprint.viewport_bounds_in_local_space(); + + let mut size = viewport_bounds.size(); + let mut offset = viewport_bounds.start; + if clip { + // TODO: Remove "clip" entirely (and its arbitrary 100x100 clipping square) once we have proper resolution-aware layer clipping + const CLIPPING_SQUARE_SIZE: f64 = 100.; + let image_bounds = Bbox::from_transform(DAffine2::from_scale(DVec2::splat(CLIPPING_SQUARE_SIZE))).to_axis_aligned_bbox(); + let intersection = viewport_bounds.intersect(&image_bounds); + + offset = (intersection.start - image_bounds.start).max(DVec2::ZERO); + 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 footprint_scale = footprint.scale(); + let width = (size.x * footprint_scale.x) as u32; + let height = (size.y * footprint_scale.y) as u32; + // All - let [width, height] = dimensions.to_array(); let mut image = Image::new(width, height, Color::from_luminance(0.5)); let mut noise = fastnoise_lite::FastNoiseLite::with_seed(seed as i32); - noise.set_frequency(Some(scale as f32 / 1000.)); + noise.set_frequency(Some(1. / (scale as f32).max(f32::EPSILON))); // Domain Warp let domain_warp_type = match domain_warp_type { @@ -665,6 +687,8 @@ fn noise_pattern( NoiseType::ValueCubic => fastnoise_lite::NoiseType::ValueCubic, NoiseType::Value => fastnoise_lite::NoiseType::Value, NoiseType::WhiteNoise => { + // TODO: Generate in layer space, not viewport space + let mut rng = ChaCha8Rng::seed_from_u64(seed as u64); for y in 0..height { @@ -677,7 +701,7 @@ fn noise_pattern( return ImageFrame:: { image, - transform: DAffine2::from_scale(DVec2::new(width as f64, height as f64)), + transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), alpha_blending: AlphaBlending::default(), }; } @@ -718,12 +742,16 @@ fn noise_pattern( noise.set_cellular_return_type(Some(cellular_return_type)); noise.set_cellular_jitter(Some(cellular_jitter as f32)); + let coordinate_offset = offset.as_vec2(); + let scale = size.as_vec2() / Vec2::new(width as f32, height as f32); // Calculate the noise for every pixel for y in 0..height { for x in 0..width { let pixel = image.get_pixel_mut(x, y).unwrap(); + let pos = Vec2::new(x as f32, y as f32); + let vec = pos * scale + coordinate_offset; - let (mut x, mut y) = (x as f32, y as f32); + let (mut x, mut y) = (vec.x, vec.y); if domain_warp_active && domain_warp_amplitude > 0. { (x, y) = noise.domain_warp_2d(x, y); } @@ -736,7 +764,7 @@ fn noise_pattern( // Return the coherent noise image ImageFrame:: { image, - transform: DAffine2::from_scale(DVec2::new(width as f64, height as f64)), + transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size), alpha_blending: AlphaBlending::default(), } } @@ -748,20 +776,17 @@ pub struct 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(); + let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO); + // 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 scale = footprint.scale(); let width = (size.x * scale.x) as u32; let height = (size.y * scale.y) as u32; diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 32abf3f9..8989ca72 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -644,7 +644,7 @@ fn node_registry() -> HashMap, input: Footprint, output: TextureFrame, fn_params: [Footprint => TextureFrame]), register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image, params: [DAffine2]), - register_node!(graphene_std::raster::NoisePatternNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _>, input: (), params: [UVec2, u32, f64, NoiseType, DomainWarpType, f64, FractalType, u32, f64, f64, f64, f64, CellularDistanceFunction, CellularReturnType, f64]), + register_node!(graphene_std::raster::NoisePatternNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _>, input: Footprint, params: [bool, u32, f64, NoiseType, DomainWarpType, f64, FractalType, u32, f64, f64, f64, f64, CellularDistanceFunction, CellularReturnType, f64]), #[cfg(feature = "quantization")] register_node!(graphene_std::quantization::GenerateQuantizationNode<_, _>, input: ImageFrame, params: [u32, u32]), register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),