Unexpected spikes in noise values?

I’m trying to create a circle/ellipse/loop whose radius varies with the built-in noise function. I’m using Quil, a set of Clojure bindings to the Processing framework, so I’ll paste my code below and explain it since I suspect few if any here are Clojure people.

Problem

First, here is the output. I’ve limited it to a single loop and illustrated each vertex with a little dot.


The spike happens between the first, last, and second to last points. The first point is at a radius of 186.96024, the last point is at a radius of 202.97652, and the second to last point is at a radius of 180.59953.

The fact that the second to last point is inwards of the mean, the last point is outside of the mean leads me to believe there’s some kind of instability near that point in the noise function.

Code

Here is the code for how I generate this (explanation below):

(defn noise-path
  []
  (let [divs 1000
        nscale 0.008
        mean-rad (* 0.75 win-rad)
        ]
    (reduce
      (fn
        [acc div]
        (let [{:keys [last-x last-y]} acc
              noise (q/noise
                      (* nscale last-x)
                      (* nscale last-y))
              max-rad (* 1.25 mean-rad)
              min-rad (* 0.75 mean-rad)
              new-rad (q/map-range noise 0 1 min-rad max-rad)
              angle (* q/TWO-PI (/ div divs))
              new-x (* new-rad (q/cos angle))
              new-y (* new-rad (q/sin angle))]
          (-> acc
              (assoc :last-x new-x)
              (assoc :last-y new-y)
              (update :vertices
                      (fn [vs] (cons [new-x new-y] vs))))
          ))
      {:last-x   (* nscale (q/random 0 d))
       :last-y   (* nscale (q/random 0 d))
       :vertices nil}
      (range divs))))

I divide the ellipse into some number of angle increments (divs, 1000 in this case), then iterate over them to generate a random radius at each angle.

To generate a random radius I first set a mean radius (mean-radius, randomly selected in the real code, set to 0.75x the window radius for debugging), then from that mean radius I set an upper bound (max-rad) and lower bound (min-rad) within which the randomly selected radius will be selected. I use the (scaled) x- and y-coordinates of the previous point as the inputs to the noise function (q/noise, which is just Processing’s noise function).

When I’m “rendering” the path I just connect the vertices with lines and let end-shape close the shape.

Thoughts

I haven’t been able to figure out why the last point freaks out. The last point isn’t always on the inside or outside of the mean, and it’s not pegged at the minimum or maximum radius.

i think you want looping noise or 2d noise. you can see it in this daniel shiffman codingtrain video the whole video is worth a watch but i’ve set it at the point it relates to your issue

1 Like

That’s actually a different issue. I do use 2D noise, and I use point n-1 to determine the noise value for point n, which keeps the noise values close to one another.

In the video you posted when Daniel displays the curve without close-ing the shape, you can see that the radius slowly wanders away from the initial radius. If you look at the image I’ve posted below (displayed the same way i.e. without closing the curve), you can see that it’s just the last couple of points that begin to oscillate. To me that points at some kind of numerical instability in the noise function.

However the spikes are not happening in the Shiffman tutorial…
There must be some other variable at play?

In the Shiffman tutorial there is a single point of origin from where the circle begins. Since I cannot read the code provided and am basing this on your description, I wonder if you have a single point of origin in how you are drawing your circle? Also the noise appears to be mirrored in the 4 quadrants of the circle…
which may be your intention, however could that be a factor that is affecting the desired smooth closure of the shape?
:nerd_face:

I threw together a Java (Processing) version of the program so that everyone else can see exactly what’s happening:

void setup() {
  size(500, 500);
  colorMode(HSB, 360, 100, 100, 1);
  pixelDensity(2);
  randomSeed(0);
  noiseSeed(0);
  smooth();
}

void draw() {
  noLoop();
  background(263, 11, 46);
  fill(9, 26, 100);
  noStroke();
  int d = 500;
  float win_rad = (0.99 * d) / 2;
  ellipse(d/2, d/2, d/25, d/25);
  strokeWeight(d/500);
  stroke(360, 0, 100, 1);
  noFill();
  float mean_radius = 0.75 * win_rad;
  float min_rad = 0.75 * mean_radius;
  float max_rad = 1.25 * mean_radius;
  float nscale = 0.008;
  float last_x = random(0, d);
  float last_y = random(0, d);
  int divs = 1000;
  translate(d/2, d/2);
  beginShape();
  for (int div = 0; div < 1000; div++) {
    float noise_value = noise(nscale*last_x, nscale*last_y);
    //float new_rad = noise_value * (min_rad + max_rad) - min_rad;
    float new_rad = map(noise_value, 0.0, 1.0, min_rad, max_rad);
    float angle = 2 * 3.14159 * ((float)div / (float)divs);
    float x = new_rad * cos(angle);
    float y = new_rad * sin(angle);
    vertex(x, y);
    last_x = x;
    last_y = y;
  }
  endShape();
  save("sketch.png");
}

I’m not exactly sure what you mean by this. The origin of my loop is right in the center of the window.

Yeah, I don’t know why that happens.

I was referring to where along the radius of the circle the first point is placed. And I see your code provides the answer. :slight_smile:

And regarding the 4 quadrants:

I was curious and rewatched the Shiffman tutorial. He does address the reason and solution to the mirrored quadrants.
He adds +1 to the lines below like this:

However when I tested that with your code, it didn’t work…
Your code is basically modeled on Shiffman solution, but there is something extra going on and I can’t figure out what that extra thing is.

If you haven’t already you might try doing a line by line comparison between Shiffman code in the tutorial and your code to see if (or where) there is an unintended departure?

At this point I’ll have to take a step back as I’m not sure definitively the issue.
Good luck!
:nerd_face:

if you watch that video you can see the symmetry comes from where you are sampling the noise and can be fixed by offsetting the sample point (explained here in the video). the other problem is the noise not being closed correctly and that is because you are not sampling from the noise space in a perfect circle but rather by feeding the noisy values back in and breaking the circle many times. basically you need to decouple the sampling and the use of the sample. i don’t have processing installed atm but here is a p5js example that works and here is your code modified which should work (i haven’t tested it).

void setup() {
  size(500, 500);
  colorMode(HSB, 360, 100, 100, 1);
  pixelDensity(2);
  randomSeed(0);
  noiseSeed(0);
  smooth();
}

void draw() {
  noLoop();
  background(263, 11, 46);
  fill(9, 26, 100);
  noStroke();
  int d = 500;
  float win_rad = (0.99 * d) / 2;
  ellipse(d/2, d/2, d/25, d/25);
  strokeWeight(d/500);
  stroke(360, 0, 100, 1);
  noFill();
  float mean_radius = 0.75 * win_rad;
  float min_rad = 0.75 * mean_radius;
  float max_rad = 1.25 * mean_radius;
  float nscale = 0.8;
  int divs = 1000;
  translate(d/2, d/2);
  beginShape();
  for (int div = 0; div < 1000; div++) {
    float angle = 2 * 3.14159 * ((float)div / (float)divs);
    float cosValue = cos(angle);
    float sinValue = sin(angle);
    float noise_value = noise(1 + nscale * cosValue, 1 + nscale * sinValue);
    //float new_rad = noise_value * (min_rad + max_rad) - min_rad;
    float new_rad = map(noise_value, 0.0, 1.0, min_rad, max_rad);
    float x = new_rad * cosValue;
    float y = new_rad * sinValue;
    vertex(x, y);
  }
  endShape();
  save("sketch.png");
}
1 Like

I figured it out, so I’ll document what I found in case anyone else has the same issue in the future.

“Aha” moment

I plotted the points along the loop with varying brightness and discovered that it wasn’t the last few points that were “freaking out,” but instead it was the first few! I messed this up because the axes are oriented differently from how you would think of the axes in a math class.

Context

I’m generating each loop vertex sequentially based on the previous one. In principle this works because even though you aren’t sampling the noise in a perfect circle, you’re still sampling the noise from points that are very close to one another i.e. point n is close to point n-1, so the difference in noise values shouldn’t be huge. This also works in practice, which is why most of my loop is fine aside from a few points. In fact, if I add an extra vertex the loop closes no problem (aside from the bad first point).

However, if you generate point n based on point n-1, how do you generate the first point? The answer is that you just have to pick a point, but which point you choose makes a big difference.

The actual problem

How you select your first point is pretty arbitrary. After all, you’re just using these coordinates as the input to the noise function, not adding it as a vertex.

If you choose the point (mean_rad, 0) as the initial input for each loop, then loops with similar mean radii will end up looking similar because their first points are close to one another and they’re essentially sampling the same noise.

Another solution (the one I chose) was to select a random point within the window as the first input for each loop. This way each loop is more likely to look distinct from other loops. However, this is where the problem lies. Here’s how the chain of events goes:

  1. Select a random location in the window (n_x, n_y) as the initial input for the noise function.
  2. Compute the radius r_0 for the first point at angle_0 based on (n_x, n_y).
  3. Compute (x_0, y_0) from the radius and angle relative to an origin placed at the center of the window.
  4. Use (x_0, y_0) as the input for computing (x_1, y_1)
  5. Repeat

The issue is that (x_0, y_0) and (n_x, n_y) are in completely different locations since (n_x, n_y) is chosen at random, whereas (x_0, y_0) is always plotted at angle = 0 since it’s the first point.

Solution

I think the easiest solution is just to sample the noise in a perfect circle as has been suggested, that way you don’t have to do things sequentially in the first place.