simplex-noise.js icon indicating copy to clipboard operation
simplex-noise.js copied to clipboard

`noise()` generic function

Open abernier opened this issue 3 years ago • 4 comments
trafficstars

what about a "generic" noise function, with variable-length arguments, ie:

generic implied comment
noise() noise2D(0, 0) 0 by default
noise(1) noise2D(1,1) same value for 2nd arg
noise(1,2) noise2D(1,2)
noise(1,2,3) noise3D(1,2,3)
noise(1,2,3,4) noise4D(1,2,3,4)
noise(1,2,3,4,5) noise4D(1,2,3,4,5) NB: when more than 4 args => 5th, 6th... will just be ignored by noise4D

This is inspired from https://processing.org/reference/noise_.html

function noise(...args) {
  let dim // 2 or 3 or 4
  let args2 = [...args] // copy of args
  
  if (args.length < 2) {
    // 0 or 1 arg => noise2d
    const arg = args[0] || 0
    args2 = [arg, arg]
    dim = 2
  } else {
    // 2 or 3 or 4 or more args => noise2D or noise3D or noise4D
    dim = Math.min(args.length, 4)
  }
  
  return simplex[`noise${dim}D`](...args2) /2 + 0.5 // normalize [0;1]
}

NB: I've chosen to normalise the value to ]0;1[ but maybe you prefer staying ]-1;1[

working demo: https://codepen.io/abernier/pen/ExvqNyj

abernier avatar Nov 26 '21 11:11 abernier

Hi @abernier ,

Thanks for your suggestion. I'm not quite sure I see the benefits outweighing the drawbacks of this extension. Here are the drawbacks I can see:

As suggested noise() is very slow. It leads to a ~4x reduction in speed for the 2d case. Even just calling noise2D would be quicker:

noise2D: 50,350,911 ops/sec ±0%
noise4D: 21,945,180 ops/sec ±0%

That could be addressed to some extent by turning it into a simple switch case. It also makes optimizations for bundlers more difficult because it's harder to determine which code is unused. At least for now this is a minor issue since afaik no bundler/minimizer performs dead code elimination on a method level.

The signature suggests that the function can handle more than 4 dimensions which isn't true.

Finally it would add multiple ways to achieve the same goal.

What are the benefits and drawbacks you see to this change?

jwagner avatar Nov 27 '21 15:11 jwagner

Yeah, I didn't even considered it on the performance point of vue...

It was more like a kind of optional "smart"/"magic" call, obviously slower then.

I understand your concerns, please just ignore then ;)

abernier avatar Nov 27 '21 16:11 abernier

Just to be clear, performance really isn't the be all end all of this discussion. Performance can be improved by writing stupid enough code for jit to understand:

function fasterNoise(x,y,z,w) {
  if(x === undefined) return 0;
  if(y === undefined) return simplex.noise2D(x,x);
  if(z === undefined) return simplex.noise2D(x,y);
  if(w === undefined) return simplex.noise3D(x,y,z);
  return simplex.noise4D(x,y,z,w);
}
noise: 13,020,651 ops/sec ±0%
fasterNoise: 50,688,165 ops/sec ±0%
noise2D: 50,697,667 ops/sec ±0%

I'll leave this issue open for a while for others to weigh in as well. Just because I'm not a fan doesn't mean this isn't something other users would like to see. :)

jwagner avatar Nov 28 '21 00:11 jwagner

I think this is better left up to the end-user. Not on the basis of performance, but code clarity. Resulting code becomes ambiguous as to its purpose when you abstract away the interface.

With that said, though not tested, a slight improvement to jwagner's implementation may be to encapsulate the various noise functions:

const MagicNoise = simplex => {
  const { noise2D, noise3D, noise4D } = simplex;
  return (x, y, z, w) => {
    if (x === undefined) return 0;
    if (y === undefined) return noise2D(x, x);
    if (z === undefined) return noise2D(x, y);
    if (w === undefined) return noise3D(x, y, z);
    return noise4D(x, y, z, w);
  };
};

// usage
const noise = MagicNoise(new SimplexNoise('seed'));
noise(1,1);

SReject avatar May 26 '22 21:05 SReject