This is a library for efficiently and simply composing noise samplers by generating inlined bytecode at runtime. By itself, it does not provide any actual noise sampler implementations such as simplex or perlin noise, but rather provides various operators that can be used to manipulate that noise. It is trivial, however, to plug in a custom sampler.
This library is published to Maven and can be simply added as a dependency with Gradle.
VERSION
should be replaced with the latest version from the Releases page.
repositories {
// ...
maven { url = 'https://maven.gegy.dev/' }
}
dependencies {
// ...
implementation 'dev.gegy:noise-composer:VERSION'
}
This library intends to support a wide-range of use-cases, with support for up to 5-dimensional noise as well as supporting both float and double-based samplers.
Noise sampling works through the NoiseSampler
interface, which is implemented by a number of interfaces specialized for specific dimensions and data types.
All double-operating samplers provide casting float samplers as default implementations, and all n-dimensional samplers provide default implementation for (n-1)-dimensional samplers.
For example, NoiseSampler3d
implements NoiseSampler3f
as well as NoiseSampler2d
.
Noise samplers can be implemented manually, or alternatively, an implementation is generated by compiling a composition of various noise operators.
Noise composition works through the Noise
type. It exposes various default operators which can be used to manipulate and combine from various sources.
For example, this composition of noise:
Noise noise = AxisNoise.x();
noise = Noise.constant(2.0).pow(noise);
noise = Noise.add(AxisNoise.y());
will compile as something similar to:
result = Math.pow(2.0, x) + y;
Once you have constructed and composed noise, it needs to be compiled before it is usable.
This works through the NoiseCompiler
type, which takes a ClassLoader
as a parameter.
This is used to instantiate new class types at runtime, so you should use the classloader from one of your own classes on the desired classloader.
NoiseCompiler compiler = NoiseCompiler.create(YourClass.class.getClassLoader());
The compiler can then be used to convert Noise
to a NoiseFactory
which finally instantiates a NoiseSampler
.
NoiseFactory factory = compiler.compile(noise, NoiseSampler3d.TYPE);
NoiseSampler3d sampler = factory.create(seed);
To compile a Noise
, the noise type must be given. That is, the dimension and data type for that noise.
If the constructed Noise
is not compatible with the given type, an exception will be thrown.
From the created NoiseFactory
, the actual NoiseSampler
must be instantiated by passing in a seed.
Samplers that require a seed will make use of this value. It is also worth noting that seeds passed through various operators will mix the seed as to ensure every sample gets a unique seed.
Given this library does not provide actual noise samplers, implementing a custom sampler will be needed.
This can be done, simply, by implementing one of the NoiseSampler
interfaces. For example:
public final class ExampleSampler implements NoiseSampler3d {
private final double factor;
public ExampleSampler(long seed) {
this.factor = new Random(seed).nextDouble() * 100.0;
}
@Override
public double get(double x, double y, double z) {
return (x + y + z) * this.factor;
}
}
This can then be constructed into a Noise
object with the use of the CustomNoise
type:
Noise noise = CustomNoise.of(ExampleSampler::new, NoiseSampler3d.TYPE, NoiseRange.INFINITE);
The NoiseRange
given here represents the range of values that this noise sampler may output.
It is important that this value is correct, as it will be used for optimization as well as forming the basis of the noise normalization operator.
NoiseRange.INFINITE
represents every value, and will always be technically correct, but it is encouraged to match the actual range properly.
A common use-case for combining noise is through octaves. This involves repeating a specific noise sampler at different frequency and amplitude to create a mix of broad and finer details.
This can be done through the OctaveNoise
class.
OctaveNoise octaves = OctaveNoise.builder()
.setAmplitude(2.0) // initial amplitude
.setScaleX(0.1).setScaleY(0.1).setScaleZ(0.1) // initial frequency
.setPersistence(0.5) // each octave, amplitude multiplies by 0.5
.setLacunarity(2.0); // each octave, frequency multiples by 2.0
// add 4 octaves of the given noise
octaves.add(MyNoiseSampler.create(), 4);
// build these octaves into a Noise object
Noise noise = octaves.build();
Often adding multiple layers of noise together will result in a non-intuitive range of noise values.
These ranges can be simply normalized to [-1; 1] through the use of the .normalize()
operator.
For example:
Noise noise = OctaveNoise.builder().add(...).build();
noise = noise.normalize();
To view the output that Noise Composer is producing, you can add -Dnoisecomposer.debug=true
to your program launch arguments.
This will put the compiled class files that the noise compiler creates in a folder called noisecomposer
in your root directory.
This can be useful if you're wanting to see how the compiled bytecode looks under the hood.