jwagner / simplex-noise.js

A fast simplex noise implementation in Javascript / Typescript.
MIT License
1.61k stars 130 forks source link

`noise()` generic function #42

Open abernier opened 2 years ago

abernier commented 2 years ago

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

jwagner commented 2 years ago

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?

abernier commented 2 years ago

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 ;)

jwagner commented 2 years ago

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. :)

SReject commented 2 years ago

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);