Razaekel / noise-rs

Procedural noise generation library for Rust.
Apache License 2.0
850 stars 119 forks source link

N-dimensional noise? #179

Closed amaranth closed 3 years ago

amaranth commented 7 years ago

As mentioned in #134 one way to get the 5D and 6D noise functions would be to, as much as possible, make the library generic over dimensions. I've implemented this as a proof of concept using generic-array for Perlin noise (TODO: publish this and link to it) and it's at worst 200% the run time of the specialized implementations but I believe I can improve on that. If not we can always keep the existing implementations too, we'll just have four implementations of NoiseModule for each generator then (Point, Point2, Point3, Point4). On nightly Rust we could even use impl specialization to get the specialized implementations when using Point.

We can easily do N-dimensional versions of Perlin, Simplex, and Value noise as well as all of the modifiers, fractals, and checkboard and such. The complicated/impossible ones are OpenSimplex, SuperSimplex, and Worley. Those might be possible with more research or with much slower generic implementations. Worst case we can have Point\<U2>, Point\<U3>, and Point\<U4> versions that call to the specialized implementations and the compiler will give a type error if someone tries to get 5D SuperSimplex noise.

As an aside, while working on the N-dimensional Perlin version I discovered using an N-dimensional PermTable via a get(&[T]) -> usize method results in pretty dramatic speedups for the 64x64 benchmarks. Even if we don't do full N-dimensional I'm going to try to make as much of the helper machinery (PermTable, math, per-noise helpers) N-dimensional and see if that has a similar effect on performance.

Razaekel commented 7 years ago

This would be excellent to have! However, as far as I understand it (and in my testing) the current type signature of NoiseModule<T, U> would prevent being able to do this because of the fn get(&self, point: T) -> U signature, and the way some of the NoiseModules use that signature as is, and others use fn get(&self, point: PointN<T>) -> U'. If the trait definition wasfn get(&self, &[T]) -> U, I'm pretty sure it would do bad things to the composability of the modules. Not sure what would happen if theimpls used&[T]` instead.

A number of the modules use fn(&self, point: T) -> U directly, since they're agnostic about the dimensionality of the point, ex. Abs or Const.

If we had const generics, the signature could be simplified to NoiseModule<T> with a fn get(&self, [T; N]) -> T and const N.

I know generators/checkerboard.rs uses a &[T] internally, but it still uses the PointN<T> interface for compatibility. This may be an acceptable intermediate stage.

amaranth commented 7 years ago

I wasn't talking about using &[T] but rather typenum and generic-array so we can use Point\<T, U2> to refer to a 2D point at compile time. Internally it would make sense for things like PermTable to take a &[T] since they don't need to care about the dimensionality.

tayloraswift commented 7 years ago

Super-simplex is impossible to generalize into higher dimensions, there is no known 4D or higher variant. Open-simplex has a 4D variant, but no 5D or higher variant is known. Cell/Worley noise is fairly easy to extend; in fact I have a script that can be repurposed to calculate the minimum kernel to be sampled. Occluded cells can also be identified by taking the corners of the central 2n cells.

cheako commented 5 years ago

It's likely that low D variants will have SIMD accelerations, but ppl shouldn't exclude ND just because low D is faster. It would be nice if noise were to be an all inclusive API wrapper around more specialized implementations, not a collection of popular specialized implementations.

Cazadorro commented 5 years ago

maybe I'm out of the loop, but what is the point of higher dimensional noise than 4D? I've seen people mention tile-ability, but you can reliably do that with the same dimensionality you need by repeating coherent hashes. If the purpose is to simply create repeatable versions of 3D and 2D, then just use those. Perlin noise is trivially tileable, and if you can't figure out how to do it with simplex noise, using 6D to tile 3D isn't going to get you any advantages over perlin, as you've just lost your speed, and now you're just left with simplex's worse noise artifacts.