0

Typically when generating seamless Simplex noise the strategy is to go to 4-dimensions (a strategy which has worked well for me in the past when using 2-D Simplex), however I am trying to generate a seamless GIF loop using 1-D simplex noise (only interested in the noise specifying the Y-value in a line graph).

I'm curious if I'm either misunderstanding how to make things seamless in 1-D or if I possibly have an error in logic here. Basically, I'm generating a 2-D array, where the first dimension is the Z axis and the second dimension is a list of the points (x-y values) for that Z. I iterate over each z and simply plot each vertex in turn.

What I notice is that, when I hit my maximum Z-value, there is a clear jump indicating that I'm doing something wrong (not seamless).

I'm using the fast-simplex-noise library (I like it better than P5's built-in noise function) and specify it as so:

function setup() {
  let freq = 0.005;
  let octaves = 14;
  _noise = new FastSimplexNoise({ frequency: freq, octaves: octaves });

  // specify the points:
  points = [];
  
  step = 0;
  maxSteps = 150;
  let r = 1.0;
  
  for (let z = 0; z < maxSteps; z++) {
    let t = (1.0 * z) / maxSteps;
    
    points[z] = [];

    for (let x = o + 10; x < width - o - 10; x++) {
      let _n = _noise.get4DNoise(x, z, r*cos(TWO_PI*t), r*sin(TWO_PI*t));
      let _y = height/2 + 250*_n;
      points[i].push({ x: x, y: _y });
    }
  }
}

In the draw function I simply iterate over each vertex in the points list and keep track of the current z value per-draw iteration.

erik
  • 3,810
  • 6
  • 32
  • 63

1 Answers1

2

It sounds like you are expecting the change in noise values when jumping from z = maxSteps - 1 to z = 0 to be small. However this is not going to be the case when you specify z as the second parameter to get4DNoise because while the third and fourth dimensions will be quite close to each other for these two values of z (thanks to the use of sine and cosine), the second will differ by maxSteps - 1. This begs the question: why are you using 4D noise at all? You are trying to vary y randomly such that it is smoothly changing for changes in either z or x and also looping back around. In order to achieve this you simply need to sample noise values along a straight line in 3d space moving around a cylinder.

Here's a visualization of your noise algorithm, and a very similar one that only uses 3d noise. Notice how the cylinder on the left has a seam, but the one on the right has no seam:

const maxSteps = 100;
const scaleFactor = 20;

let texture1;
let texture2;

let _noise;
let cam;

function setup() {
  let size = Math.min(windowWidth, windowHeight);
  createCanvas(size, size, WEBGL);
  noStroke();
  cam = createCamera();
  cam.setPosition(0, 0, 500);
  cam.lookAt(0, 0, 0);

  _noise = new FastSimplexNoise({
    frequency: 0.005,
    octaves: 14
  });

  texture1 = makeTexture(true);
  texture2 = makeTexture(false);
}

function doubleClicked() {
  console.log(cam);
}

function makeTexture(use4d) {
  let points = [];
  // Using an r of 1.0 covers a very small and unchanging region of noise space
  let r = use4d ? 1.0 : maxSteps / 2;

  for (let z = 0; z < maxSteps; z++) {
    let t = z / maxSteps;

    points[z] = [];

    for (let x = 0; x < maxSteps; x++) {
      let _n =
        use4d ?
        _noise.get4DNoise(x, z, r * cos(TWO_PI * t), r * sin(TWO_PI * t)) :
        _noise.get3DNoise(x, r * cos(TWO_PI * t), r * sin(TWO_PI * t));
      let _y = 250 * _n;
      points[z].push({
        x: x,
        y: _y
      });
    }
  }

  let g = createGraphics(maxSteps, maxSteps);

  for (let z = 0; z < maxSteps; z++) {
    for (let x = 0; x < maxSteps; x++) {
      // x == points[z][x].x
      // Using z as x and x as y because of the texture coordinate layout for cylinders
      g.set(
        z, x,
        // Shifting y values upward because they tend to be small resulting in a dark texture
        map(points[z][x].y, 0, 250, 100, 300)
      );
    }
  }

  // required after set()?
  g.updatePixels();

  return g;
}

function draw() {
  background(255);
  orbitControl(2, 1, 0.1);

  push();
  translate(-150, 0, 0);
  texture(texture1);
  cylinder(100, 200);
  pop();

  push();
  translate(150, 0, 0);
  texture(texture2);
  cylinder(100, 200);
  pop();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fast-simplex-noise@1.0.0/fast-simplex-noise.js"></script>
Paul Wheeler
  • 18,988
  • 3
  • 28
  • 41
  • That makes a lot of sense (and works nicely). Not sure why I didn't think that if I'm dropping a dimension that my cylinder should also drop a dimension. Thanks! – erik Nov 30 '21 at 18:43