Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 79 additions & 5 deletions node-graph/nodes/vector/src/vector_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,13 +286,14 @@ async fn copy_to_points<I: 'n + Send + Clone>(

let random_scale_difference = random_scale_max - random_scale_min;

for row in points.into_iter() {
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());
// Initialize RNGs once before the loop to ensure unique random values across all paths
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;
let do_scale = random_scale_difference.abs() > 1e-6;
let do_rotation = random_rotation.abs() > 1e-6;

for row in points.into_iter() {
let points_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
for &point in row.element().point_domain.positions() {
let translation = points_transform.transform_point2(point);
Expand Down Expand Up @@ -3097,6 +3098,7 @@ mod test {
assert_eq!(manipulator_groups_anchors[i], expected_bounding_box[i]);
}
}

#[tokio::test]
async fn copy_to_points() {
let points = Rect::new(-10., -10., 10., 10.).to_path(DEFAULT_ACCURACY);
Expand Down Expand Up @@ -3140,6 +3142,78 @@ mod test {
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
}
}

#[tokio::test]
async fn copy_to_points_unique_randomization() {
// Regression test for RNG reset bug
//
// BUG: RNG was reinitialized inside the outer loop (for each path/row), causing the same
// random sequence to be generated for each separate path.
//
// This test uses TWO SEPARATE PATHS. If the bug exists, the first point of path1
// and the first point of path2 will get IDENTICAL random values because RNG resets.

// Create two separate rectangular paths (each produces 4 corner points)
let path1 = Rect::new(0., 0., 10., 10.).to_path(DEFAULT_ACCURACY);
let path2 = Rect::new(100., 100., 110., 110.).to_path(DEFAULT_ACCURACY);

// Create a List with two separate rows (each row is a separate path iteration)
let mut points = List::new();
points.push(create_vector_item(path1, DAffine2::IDENTITY));
points.push(create_vector_item(path2, DAffine2::IDENTITY));

let element = Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY);

// Call with strong randomization to make differences obvious
let result = super::copy_to_points(
Footprint::default(),
points,
vector_node_from_bezpath(element),
0.5, // random_scale_min
1.5, // random_scale_max (wide range)
0.0, // random_scale_bias (uniform distribution)
42, // random_scale_seed
180.0, // random_rotation (wide range in degrees)
123, // random_rotation_seed
)
.await;

// We have 8 total copies (4 points per rectangle × 2 rectangles)
assert_eq!(result.len(), 8, "Should have 8 copies total");

// Extract transforms from all copies
let transforms: Vec<DAffine2> = (0..result.len()).map(|i| result.attribute_cloned_or_default(ATTR_TRANSFORM, i)).collect();

// Extract scales (length of transform's x-axis vector)
let scales: Vec<f64> = transforms.iter().map(|t| DVec2::new(t.matrix2.x_axis.x, t.matrix2.x_axis.y).length()).collect();

// Extract rotations (angle of transform's x-axis vector)
let rotations: Vec<f64> = transforms.iter().map(|t| DVec2::new(t.matrix2.x_axis.x, t.matrix2.x_axis.y).to_angle()).collect();

// CRITICAL TEST: Compare first point of path1 (index 0) with first point of path2 (index 4)
// If RNG is reset between paths, these will be IDENTICAL
let scale_diff = (scales[0] - scales[4]).abs();
let rotation_diff = (rotations[0] - rotations[4]).abs();

assert!(
scale_diff > 0.01 || rotation_diff > 0.01,
"RNG RESET BUG DETECTED: First points of different paths have identical random values!\n\
Path1 Point1: scale={:.4}, rotation={:.4}\n\
Path2 Point1: scale={:.4}, rotation={:.4}\n\
Scale diff: {:.6}, Rotation diff: {:.6}\n\
This indicates RNG is being reinitialized for each path.",
scales[0],
rotations[0],
scales[4],
rotations[4],
scale_diff,
rotation_diff
);

// Additional verification: values should vary within each path too
assert!((scales[0] - scales[1]).abs() > 0.001, "Sequential points within same path should have different scales");
}

#[tokio::test]
async fn poisson() {
let poisson_points = super::scatter_points(
Expand Down