mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
Refined existing noise
- Hexagon styles produce coherent solid cells with contained subdivisions - Sierpinski triangles use equilateral recursive geometry - Noise styles share a 64-block base scale - Zoom scales the complete pattern with its distortion - Octaves preserve the base seed and feature scale - Coordinate dispatch uses the native dimensional sampler - Classic kernels retain seed precision and bounded output - Interpolation preserves fractional coordinates and symmetric curves - Noise caches respect sampling bounds and generation identity - Clover sampling reuses vertices and cached trigonometry - Noise and terrain tests cover corrected sampling behavior
This commit is contained in:
1 parent
f2887632fe
commit
50894b90ce
47 files changed
+2603
-1542
No files matched your search
+12
-10
@@ -51,6 +51,7 @@ import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisCaveCarver3DNearParityTest {
|
||||
private static final long CARVE_SEED = -54_777_863_784_492_918L;
|
||||
private static final int SURFACE_CEILING_FADE_DEPTH = 12;
|
||||
private static final double SURFACE_CEILING_SOLID_EPSILON = 0.000001D;
|
||||
|
||||
@@ -297,11 +298,11 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
public void warpCacheDoesNotCrossContaminateCarverInstancesOnSameThread() throws Exception {
|
||||
Engine engine = createEngine(128, 92);
|
||||
IrisCaveProfile firstProfile = createProfile(true, false);
|
||||
firstProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.12D));
|
||||
firstProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.1875D));
|
||||
IrisCaveProfile secondProfile = createProfile(true, false);
|
||||
secondProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.CELLULAR).zoomed(0.12D));
|
||||
secondProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.CELLULAR).zoomed(0.1875D));
|
||||
IrisCaveProfile controlProfile = createProfile(true, false);
|
||||
controlProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.CELLULAR).zoomed(0.12D));
|
||||
controlProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.CELLULAR).zoomed(0.1875D));
|
||||
IrisCaveCarver3D firstCarver = new IrisCaveCarver3D(engine, firstProfile);
|
||||
IrisCaveCarver3D secondCarver = new IrisCaveCarver3D(engine, secondProfile);
|
||||
IrisCaveCarver3D controlCarver = new IrisCaveCarver3D(engine, controlProfile);
|
||||
@@ -1101,13 +1102,14 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisData data = mock(IrisData.class);
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
SeedManager seedManager = new SeedManager(942_337_445L);
|
||||
SeedManager seedManager = mock(SeedManager.class);
|
||||
EngineMetrics metrics = new EngineMetrics(16);
|
||||
IrisWorld world = IrisWorld.builder().minHeight(0).maxHeight(worldHeight).build();
|
||||
|
||||
doReturn(data).when(engine).getData();
|
||||
doReturn(dimension).when(engine).getDimension();
|
||||
doReturn(seedManager).when(engine).getSeedManager();
|
||||
doReturn(CARVE_SEED).when(seedManager).getCarve();
|
||||
doReturn(metrics).when(engine).getMetrics();
|
||||
doReturn(world).when(engine).getWorld();
|
||||
doReturn(sampledHeight).when(engine).getHeight(anyInt(), anyInt());
|
||||
@@ -1124,10 +1126,10 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
profile.setVerticalRange(new IrisRange(0D, 120D));
|
||||
profile.setVerticalEdgeFade(14);
|
||||
profile.setVerticalEdgeFadeStrength(0.21D);
|
||||
profile.setBaseDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.07D));
|
||||
profile.setDetailDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.17D));
|
||||
profile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.12D));
|
||||
profile.setSurfaceBreakStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.09D));
|
||||
profile.setBaseDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.109375D));
|
||||
profile.setDetailDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.265625D));
|
||||
profile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.1875D));
|
||||
profile.setSurfaceBreakStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.140625D));
|
||||
profile.setBaseWeight(1D);
|
||||
profile.setDetailWeight(0.48D);
|
||||
profile.setWarpStrength(warp ? 0.37D : 0D);
|
||||
@@ -1146,14 +1148,14 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
if (modules) {
|
||||
KList<IrisCaveFieldModule> caveModules = new KList<>();
|
||||
caveModules.add(new IrisCaveFieldModule(
|
||||
new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.11D),
|
||||
new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.171875D),
|
||||
0.23D,
|
||||
0.04D,
|
||||
new IrisRange(0D, 72D),
|
||||
false
|
||||
));
|
||||
caveModules.add(new IrisCaveFieldModule(
|
||||
new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.19D),
|
||||
new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.296875D),
|
||||
0.17D,
|
||||
-0.06D,
|
||||
new IrisRange(24D, 120D),
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisStructureCarveShape;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.iris.util.project.noise.NoiseType;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -99,7 +100,7 @@ public class StructureCarveEnvelopeTest {
|
||||
|
||||
@Test
|
||||
public void lobedSideReachWandersInsideTheConfiguredPaddingBand() {
|
||||
CNG lobe = new CNG(new RNG(1337L), 1D, 1);
|
||||
CNG lobe = new CNG(new RNG(1337L), NoiseType.CLOVER, 1D, 1);
|
||||
double lowest = 14D;
|
||||
double highest = 0D;
|
||||
|
||||
|
||||
+7
-7
@@ -17,10 +17,10 @@ public class IrisMathNoiseHotPathParityTest {
|
||||
HexRandomSizeNoise randomSize = new HexRandomSizeNoise(12345L);
|
||||
HexJamesNoise james = new HexJamesNoise(54321L);
|
||||
|
||||
assertEquals(0.4670864519829246D, randomSize.noise(12.5D, -8.25D), 0D);
|
||||
assertEquals(0.4935590260372404D, randomSize.noise(-31.75D, 44.5D, 9.125D), 0D);
|
||||
assertEquals(0.5185375921658786D, james.noise(12.5D, -8.25D), 0D);
|
||||
assertEquals(0.4557738681128938D, james.noise(-31.75D, 44.5D, 9.125D), 0D);
|
||||
assertEquals(0.4924242426867459D, randomSize.noise(12.5D, -8.25D), 0D);
|
||||
assertEquals(0.4856492100555178D, randomSize.noise(-31.75D, 44.5D, 9.125D), 0D);
|
||||
assertEquals(0.3769400526313378D, james.noise(12.5D, -8.25D), 0D);
|
||||
assertEquals(0.5487488096537423D, james.noise(-31.75D, 44.5D, 9.125D), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -33,17 +33,17 @@ public class IrisMathNoiseHotPathParityTest {
|
||||
.setStyle(NoiseStyle.PERLIN_IRIS.style())
|
||||
.setExponent(1.15D);
|
||||
|
||||
assertEquals(0.3886291844359823D, noiseGenerator.getNoise(9988L, 31.25D, -17.5D, null), 0D);
|
||||
assertEquals(0.5084984235498856D, noiseGenerator.getNoise(9988L, 31.25D, -17.5D, null), 0D);
|
||||
|
||||
IrisGeneratorStyle style = NoiseStyle.IRIS_DOUBLE.style();
|
||||
assertEquals(0.8640377573263068D, style.createNoCache(new RNG(112233L), null).noiseFast2D(42.5D, -19.75D), 0D);
|
||||
assertEquals(0.6224926166486215D, style.createNoCache(new RNG(112233L), null).noiseFast2D(42.5D, -19.75D), 0D);
|
||||
|
||||
IrisGenerator generator = new IrisGenerator()
|
||||
.setZoom(9.5D)
|
||||
.setOpacity(0.91D)
|
||||
.setComposite(new KList<IrisNoiseGenerator>().qadd(noiseGenerator));
|
||||
|
||||
assertEquals(0.5671980255964687D, generator.getHeight(63.0D, -27.0D, 445566L), 0D);
|
||||
assertEquals(0.43997118860461754D, generator.getHeight(63.0D, -27.0D, 445566L), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -78,13 +79,11 @@ public final class IrisNoiseGeneratorCacheTest {
|
||||
|
||||
private GeneratorFixture() {
|
||||
when(data.getEngine()).thenReturn(engine);
|
||||
when(firstGenerator.oct(OCTAVES)).thenReturn(firstGenerator);
|
||||
when(secondGenerator.oct(OCTAVES)).thenReturn(secondGenerator);
|
||||
Map<Long, CNG> generators = Map.of(
|
||||
11L + GENERATOR_SALT - CONFIGURED_SEED, firstGenerator,
|
||||
29L + GENERATOR_SALT - CONFIGURED_SEED, secondGenerator
|
||||
);
|
||||
when(style.createNoCache(any(RNG.class), same(data))).thenAnswer(invocation -> {
|
||||
when(style.createForLayer(any(RNG.class), same(data), eq(OCTAVES))).thenAnswer(invocation -> {
|
||||
RNG rng = invocation.getArgument(0);
|
||||
return generators.get(rng.getSeed());
|
||||
});
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IrisNoiseGeneratorRemapTest {
|
||||
@Test
|
||||
public void parametricCurvePreservesEndpointsAndShapesBothHalves() {
|
||||
double[][] samples = {{0D, 0D}, {0.25D, 0.1D}, {0.5D, 0.5D}, {0.75D, 0.9D}, {1D, 1D}};
|
||||
for (double[] sample : samples) {
|
||||
IrisNoiseGenerator layer = new IrisNoiseGenerator()
|
||||
.setStyle(NoiseStyle.FLAT.style())
|
||||
.setOpacity(sample[0])
|
||||
.setParametric(true);
|
||||
assertEquals(sample[1], layer.getNoise(75L, 13D, -21D, null), 0.000000000001D);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.iris.util.project.noise.NoiseGenerator;
|
||||
import art.arcane.iris.util.project.noise.NoiseType;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public final class NoiseStyleContractTest {
|
||||
@Rule
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void builtInStylesAreFiniteAndRepeatableAcrossWorldCoordinates() {
|
||||
double[] coordinates = {-29_999_983.25D, -513D, -64D, -1D, 0D, 0.125D, 17D, 64D, 8192D, 29_999_983.25D};
|
||||
for (NoiseStyle style : NoiseStyle.values()) {
|
||||
CNG first = style.create(new RNG(813753L));
|
||||
CNG second = style.create(new RNG(813753L));
|
||||
for (double coordinate : coordinates) {
|
||||
double x = coordinate;
|
||||
double z = coordinate * -0.713D;
|
||||
double[] samples = {first.noise(x), first.noise(x, z), first.noise(x, 81.25D, z)};
|
||||
for (double sample : samples) {
|
||||
assertTrue(style + " is non-finite at " + coordinate, Double.isFinite(sample));
|
||||
}
|
||||
assertEquals(style.name(), samples[0], second.noise(x), 0D);
|
||||
assertEquals(style.name(), samples[1], second.noise(x, z), 0D);
|
||||
assertEquals(style.name(), samples[2], second.noise(x, 81.25D, z), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void classicPresetsShareOneBaseScale() {
|
||||
NoiseStyle[] styles = {NoiseStyle.SIMPLEX, NoiseStyle.PERLIN, NoiseStyle.CUBIC,
|
||||
NoiseStyle.FRACTAL_CUBIC, NoiseStyle.BIOCTAVE_SIMPLEX, NoiseStyle.VIGOCTAVE_SIMPLEX,
|
||||
NoiseStyle.BIOCTAVE_FRACTAL_CUBIC, NoiseStyle.QUADOCTAVE_FRACTAL_CUBIC};
|
||||
for (NoiseStyle style : styles) {
|
||||
CNG generator = style.create(new RNG(7751L));
|
||||
NoiseGenerator source = generator.getGenerator();
|
||||
double scale = 100D / 64D;
|
||||
assertEquals(style.name(), source.noise(27.5D * scale, -83.25D * scale),
|
||||
generator.noise(27.5D, -83.25D), 0D);
|
||||
}
|
||||
assertEquals(2D / 64D, NoiseType.HEXAGON.getCoordinateScale(), 0D);
|
||||
assertEquals(8D / 64D, NoiseType.SIERPINSKI_TRIANGLE.getCoordinateScale(), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitNativeGeneratorFrequenciesKeepTheirUnits() {
|
||||
CNG generator = new CNG(new RNG(7231L), NoiseType.SIMPLEX, 1D, 1).scale(0.375D);
|
||||
assertEquals(generator.getGenerator().noise(28D * 0.375D, -74D * 0.375D),
|
||||
generator.noise(28D, -74D), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitZoomDoublesFeatureSize() {
|
||||
for (NoiseStyle style : NoiseStyle.values()) {
|
||||
if (style == NoiseStyle.STATIC) {
|
||||
continue;
|
||||
}
|
||||
CNG normal = style.style().createNoCache(new RNG(45231L), null);
|
||||
CNG enlarged = style.style().zoomed(2D).createNoCache(new RNG(45231L), null);
|
||||
assertEquals(style.name(), normal.noise(13.25D, -17.75D),
|
||||
enlarged.noise(26.5D, -35.5D), 0.000000001D);
|
||||
assertEquals(style.name(), normal.noise(13.25D), enlarged.noise(26.5D), 0.000000001D);
|
||||
assertEquals(style.name(), normal.noise(13.25D, 41.125D, -17.75D),
|
||||
enlarged.noise(26.5D, 82.25D, -35.5D), 0.000000001D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultSettingsPreserveEveryPreset() {
|
||||
for (NoiseStyle style : NoiseStyle.values()) {
|
||||
CNG direct = style.create(new RNG(7253L));
|
||||
CNG configured = style.style().createNoCache(new RNG(7253L), null);
|
||||
for (int index = 0; index < 32; index++) {
|
||||
double x = index * 19.713D - 400D;
|
||||
double z = index * -31.579D + 100D;
|
||||
assertEquals(style.name(), direct.noise(x, z), configured.noise(x, z), 0D);
|
||||
assertEquals(style.name(), direct.noise(x, 32.75D, z), configured.noise(x, 32.75D, z), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builtInStylesStayInsideTheirUnitRange() {
|
||||
for (NoiseStyle style : NoiseStyle.values()) {
|
||||
for (long seed : new long[]{0L, 813753L, Long.MAX_VALUE}) {
|
||||
CNG generator = style.create(new RNG(seed));
|
||||
for (int index = 0; index < 256; index++) {
|
||||
double x = index * 17.12345D - 2000D;
|
||||
double z = index * -27.98765D + 3000D;
|
||||
double[] values = {generator.noise(x), generator.noise(x, z), generator.noise(x, 53.75D, z)};
|
||||
for (double value : values) {
|
||||
assertTrue(style + " produced " + value, value >= 0D && value <= 1D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitFractureAndCellularizationZoomWithTheirSource() {
|
||||
IrisGeneratorStyle[] styles = {
|
||||
NoiseStyle.SIMPLEX.style().setFracture(NoiseStyle.CLOVER.style().setMultiplier(50D).setZoom(1.5D)),
|
||||
NoiseStyle.IRIS.style().setCellularFrequency(0.7D).setCellularZoom(1.25D)
|
||||
};
|
||||
for (IrisGeneratorStyle style : styles) {
|
||||
CNG normal = style.createNoCache(new RNG(217L), null);
|
||||
CNG enlarged = style.setZoom(2D).createNoCache(new RNG(217L), null);
|
||||
assertEquals(normal.noise(19.25D, -71.75D), enlarged.noise(38.5D, -143.5D), 0.000000001D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configuredExponentCompoundsThePresetCurve() {
|
||||
CNG base = NoiseStyle.VASCULAR_THIN.style().createNoCache(new RNG(8831L), null);
|
||||
CNG squared = NoiseStyle.VASCULAR_THIN.style().setExponent(2D).createNoCache(new RNG(8831L), null);
|
||||
double value = base.noise(29.5D, -33.25D);
|
||||
assertEquals(value * value, squared.noise(29.5D, -33.25D), 0.000000000001D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void octavePresetsKeepTheirBaseSeed() {
|
||||
NoiseStyle[][] families = {
|
||||
{NoiseStyle.SIMPLEX, NoiseStyle.BIOCTAVE_SIMPLEX},
|
||||
{NoiseStyle.FRACTAL_FBM_SIMPLEX, NoiseStyle.BIOCTAVE_FRACTAL_FBM_SIMPLEX},
|
||||
{NoiseStyle.FRACTAL_BILLOW_SIMPLEX, NoiseStyle.BIOCTAVE_FRACTAL_BILLOW_SIMPLEX},
|
||||
{NoiseStyle.FRACTAL_RM_SIMPLEX, NoiseStyle.BIOCTAVE_FRACTAL_RM_SIMPLEX},
|
||||
{NoiseStyle.FRACTAL_BILLOW_PERLIN, NoiseStyle.BIOCTAVE_FRACTAL_BILLOW_PERLIN},
|
||||
{NoiseStyle.FRACTAL_CUBIC, NoiseStyle.BIOCTAVE_FRACTAL_CUBIC}
|
||||
};
|
||||
for (NoiseStyle[] family : families) {
|
||||
CNG configured = family[0].create(new RNG(82731L)).oct(2);
|
||||
CNG preset = family[1].create(new RNG(82731L));
|
||||
for (int index = 0; index < 32; index++) {
|
||||
double x = index * 7.125D;
|
||||
double z = index * -11.875D;
|
||||
assertEquals(family[1].name(), configured.noise(x, z), preset.noise(x, z), 0D);
|
||||
assertEquals(family[1].name(), configured.noise(x, 31.25D, z), preset.noise(x, 31.25D, z), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void layerOctavesPreserveAndMultiplyPresetDetail() {
|
||||
IrisGeneratorStyle style = NoiseStyle.QUADOCTAVE_SIMPLEX.style();
|
||||
CNG unchanged = style.createForLayer(new RNG(71L), null, 1);
|
||||
CNG detailed = style.createForLayer(new RNG(71L), null, 2);
|
||||
assertEquals(4, unchanged.getOct());
|
||||
assertEquals(8, detailed.getOct());
|
||||
assertEquals(16, style.createForLayer(new RNG(71L), null, Integer.MAX_VALUE).getOct());
|
||||
assertNotEquals(unchanged.noise(21D, -31D), detailed.noise(21D, -31D), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cachedLayersRetainTheirOctavesAcrossReload() throws Exception {
|
||||
File dataFolder = folder.newFolder("pack");
|
||||
IrisData data = mock(IrisData.class);
|
||||
when(data.getDataFolder()).thenReturn(dataFolder);
|
||||
IrisGeneratorStyle cached = NoiseStyle.BIOCTAVE_SIMPLEX.style().setCacheSize(32);
|
||||
CNG first = cached.createForLayer(new RNG(921L), data, 3);
|
||||
CNG reloaded = cached.createForLayer(new RNG(921L), data, 3);
|
||||
CNG uncached = NoiseStyle.BIOCTAVE_SIMPLEX.style().createForLayer(new RNG(921L), data, 3);
|
||||
CNG different = cached.createForLayer(new RNG(921L), data, 1);
|
||||
assertEquals(6, first.getOct());
|
||||
assertEquals(6, reloaded.getOct());
|
||||
assertEquals(uncached.noise(4D, 7D), first.noise(4D, 7D), 0.000001D);
|
||||
assertEquals(first.noise(4D, 7D), reloaded.noise(4D, 7D), 0D);
|
||||
assertNotEquals(first.noise(4D, 7D), different.noise(4D, 7D), 0D);
|
||||
File[] caches = new File(dataFolder, ".cache").listFiles((parent, name) -> name.endsWith(".cnm"));
|
||||
assertEquals(2, caches.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidTransformsFailBeforeSampling() {
|
||||
double[] invalid = {0D, -1D, Double.NaN, Double.POSITIVE_INFINITY, Double.MIN_VALUE};
|
||||
for (double value : invalid) {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> NoiseStyle.SIMPLEX.style().setZoom(value).createNoCache(new RNG(1L), null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> NoiseStyle.SIMPLEX.style().setExponent(value).createNoCache(new RNG(1L), null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> NoiseStyle.SIMPLEX.style().setCellularFrequency(1D).setCellularZoom(value)
|
||||
.createNoCache(new RNG(1L), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -37,6 +37,26 @@ public class IrisInterpolationBoundsTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractionalBoundsMatchSeparatePassesAndSampleEachCoordinateOnce() {
|
||||
for (InterpolationMethod method : InterpolationMethod.values()) {
|
||||
Map<Coordinate, NoiseBounds> expectedSamples = new HashMap<>();
|
||||
NoiseBounds expected = separatePasses(method, -31.75D, 29.25D, 32D,
|
||||
IrisInterpolationBoundsTest::asymmetricBounds, expectedSamples);
|
||||
Map<Coordinate, Integer> actualSamples = new HashMap<>();
|
||||
NoiseBounds actual = IrisInterpolation.getNoiseBounds(method, -31.75D, 29.25D, 32D, (x, z) -> {
|
||||
actualSamples.merge(new Coordinate(x, z), 1, Integer::sum);
|
||||
return asymmetricBounds(x, z);
|
||||
});
|
||||
|
||||
assertBounds(expected, actual);
|
||||
assertEquals(expectedSamples.keySet(), actualSamples.keySet());
|
||||
for (int calls : actualSamples.values()) {
|
||||
assertEquals(1, calls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedProvidersKeepTheirOwnSamplingSequences() {
|
||||
for (InterpolationMethod method : InterpolationMethod.values()) {
|
||||
@@ -95,7 +115,7 @@ public class IrisInterpolationBoundsTest {
|
||||
assertBounds(expected, actual);
|
||||
}
|
||||
|
||||
private static NoiseBounds separatePasses(InterpolationMethod method, int x, int z, double scale,
|
||||
private static NoiseBounds separatePasses(InterpolationMethod method, double x, double z, double scale,
|
||||
NoiseBoundsProvider provider, Map<Coordinate, NoiseBounds> samples) {
|
||||
double minimum = IrisInterpolation.getNoise(method, x, z, scale,
|
||||
(sx, sz) -> samples.computeIfAbsent(new Coordinate(sx, sz),
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package art.arcane.iris.util.project.interpolation;
|
||||
|
||||
import art.arcane.volmlib.util.function.NoiseProvider;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisInterpolationSamplingTest {
|
||||
@Test
|
||||
public void bicubicDispatchUsesSixteenSamplesAndTheCubicKernel() {
|
||||
NoiseProvider source = (x, z) -> x * x + z * z * z;
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
NoiseProvider countingSource = (x, z) -> {
|
||||
calls.incrementAndGet();
|
||||
return source.noise(x, z);
|
||||
};
|
||||
double expected = IrisInterpolation.getBicubicNoise(3, 5, 8D, source);
|
||||
double bilinear = IrisInterpolation.getBilinearNoise(3, 5, 8D, source);
|
||||
double actual = IrisInterpolation.getNoise(InterpolationMethod.BICUBIC, 3, 5, 8D, countingSource);
|
||||
|
||||
assertEquals(expected, actual, 0D);
|
||||
assertNotEquals(bilinear, actual, 1.0E-12D);
|
||||
assertEquals(16, calls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bilinearSamplingPreservesFractionalAndNegativeCoordinates() {
|
||||
NoiseProvider source = (x, z) -> x * 0.001D + z * 0.002D;
|
||||
|
||||
for (double x : new double[]{-31.75D, -0.25D, 0D, 0.75D, 31.25D, 29_999_999.25D}) {
|
||||
for (double z : new double[]{-33.5D, -0.5D, 0.5D, 23.75D}) {
|
||||
assertEquals(source.noise(x, z),
|
||||
IrisInterpolation.getNoise(InterpolationMethod.BILINEAR, x, z, 32D, source), 1.0E-11D);
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(-1, IrisInterpolation.getRadiusFactor(-0.25D, 2.5D));
|
||||
assertEquals(0, IrisInterpolation.getRadiusFactor(2.25D, 2.5D));
|
||||
assertEquals(1, IrisInterpolation.getRadiusFactor(2.75D, 2.5D));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void starcastPreservesFractionalCentersNearTheWorldBorder() {
|
||||
double x = 30_000_000.25D;
|
||||
double z = -29_999_999.75D;
|
||||
List<Coordinate> samples = new ArrayList<>();
|
||||
IrisInterpolation.getNoise(InterpolationMethod.STARCAST_3, x, z, 8D, (sampleX, sampleZ) -> {
|
||||
samples.add(new Coordinate(sampleX, sampleZ));
|
||||
return 0.5D;
|
||||
});
|
||||
|
||||
assertEquals(3, samples.size());
|
||||
for (int index = 0; index < samples.size(); index++) {
|
||||
double angle = index * Math.PI * 2D / 3D;
|
||||
double dx = 8D * (Math.cos(angle) - Math.sin(angle));
|
||||
double dz = 8D * (Math.sin(angle) + Math.cos(angle));
|
||||
assertEquals(x + dx, samples.get(index).x, 1.0E-12D);
|
||||
assertEquals(z + dz, samples.get(index).z, 1.0E-12D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compositeSampleCachesKeepFractionalCentersSeparate() {
|
||||
NoiseProvider source = (x, z) -> Math.sin(x * 0.013D) + Math.cos(z * 0.021D);
|
||||
|
||||
for (double x : new double[]{-31.75D, -0.25D, 0.25D, 31.75D}) {
|
||||
double z = x + 7.5D;
|
||||
Map<Coordinate, Integer> samples = new HashMap<>();
|
||||
double expected = IrisInterpolation.getNoise(InterpolationMethod.STARCAST_12, x, z, 32D,
|
||||
(sampleX, sampleZ) -> IrisInterpolation.getBilinearNoise(sampleX, sampleZ, 32D, source));
|
||||
double actual = IrisInterpolation.getNoise(InterpolationMethod.BILINEAR_STARCAST_12,
|
||||
x, z, 32D, (sampleX, sampleZ) -> {
|
||||
samples.merge(new Coordinate(sampleX, sampleZ), 1, Integer::sum);
|
||||
return source.noise(sampleX, sampleZ);
|
||||
});
|
||||
|
||||
assertEquals(expected, actual, 0D);
|
||||
for (int calls : samples.values()) {
|
||||
assertEquals(1, calls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractionalGridSpacingDoesNotCollapseOrJumpAtCellBoundaries() {
|
||||
NoiseProvider source = (x, z) -> Math.sin(x * 0.73D) * 0.4D + Math.cos(z * 0.59D) * 0.2D;
|
||||
|
||||
for (InterpolationMethod method : InterpolationMethod.values()) {
|
||||
for (double radius : new double[]{0.125D, 0.375D, 2.5D, 7.75D}) {
|
||||
for (int cell : new int[]{-7, -1, 0, 1, 7}) {
|
||||
double boundary = cell * radius;
|
||||
double center = IrisInterpolation.getNoise(method, boundary, 0.37D, radius, source);
|
||||
double before = IrisInterpolation.getNoise(method, boundary - 1.0E-8D, 0.37D, radius, source);
|
||||
double after = IrisInterpolation.getNoise(method, boundary + 1.0E-8D, 0.37D, radius, source);
|
||||
|
||||
assertTrue(method.name(), Double.isFinite(center));
|
||||
assertEquals(method.name(), center, before, 1.0E-6D);
|
||||
assertEquals(method.name(), center, after, 1.0E-6D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gridSamplesKeepTheConfiguredFractionalSpacing() {
|
||||
List<Coordinate> samples = new ArrayList<>();
|
||||
IrisInterpolation.getBilinearNoise(-0.25D, 0.125D, 0.375D, (x, z) -> {
|
||||
samples.add(new Coordinate(x, z));
|
||||
return x + z;
|
||||
});
|
||||
|
||||
assertEquals(List.of(new Coordinate(-0.375D, 0D), new Coordinate(0D, 0D),
|
||||
new Coordinate(-0.375D, 0.375D), new Coordinate(0D, 0.375D)), samples);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void starcastPreservesDoubleSamplePrecisionAndLinearFields() {
|
||||
NoiseProvider source = (x, z) -> 0.125D + x * 1.0E-11D - z * 2.0E-11D;
|
||||
|
||||
for (InterpolationMethod method : new InterpolationMethod[]{InterpolationMethod.STARCAST_3,
|
||||
InterpolationMethod.STARCAST_6, InterpolationMethod.STARCAST_9, InterpolationMethod.STARCAST_12}) {
|
||||
assertEquals(method.name(), source.noise(0.25D, -0.75D),
|
||||
IrisInterpolation.getNoise(method, 0.25D, -0.75D, 32.00000001D, source), 1.0E-16D);
|
||||
assertNotEquals(method.name(),
|
||||
IrisInterpolation.getNoise(method, 0D, 0D, 32D, source),
|
||||
IrisInterpolation.getNoise(method, 0.25D, -0.75D, 32D, source), 1.0E-13D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parametricCurvesHaveSymmetricMidpointsAndMonotonicEndpoints() {
|
||||
for (double alpha : new double[]{1D, 1.5D, 2D, 4D}) {
|
||||
assertEquals(0D, IrisInterpolation.parametric(0D, alpha), 0D);
|
||||
assertEquals(0.5D, IrisInterpolation.parametric(0.5D, alpha), 0D);
|
||||
assertEquals(1D, IrisInterpolation.parametric(1D, alpha), 0D);
|
||||
double previous = 0D;
|
||||
for (int sample = 1; sample <= 100; sample++) {
|
||||
double t = sample / 100D;
|
||||
double value = IrisInterpolation.parametric(t, alpha);
|
||||
assertTrue(value >= previous);
|
||||
assertTrue(value <= 1D);
|
||||
assertEquals(value, 1D - IrisInterpolation.parametric(1D - t, alpha), 1.0E-15D);
|
||||
previous = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void catmullRomKeepsLinearSlopesAcrossGridBoundaries() {
|
||||
NoiseProvider source = (x, z) -> x * 0.7D - z * 0.3D;
|
||||
for (double x : new double[]{-7.75D, -0.25D, 0.25D, 7.75D}) {
|
||||
assertEquals(source.noise(x, 2.75D),
|
||||
IrisInterpolation.getNoise(InterpolationMethod.CATMULL_ROM_SPLINE, x, 2.75D, 8D, source),
|
||||
1.0E-12D);
|
||||
}
|
||||
}
|
||||
|
||||
private record Coordinate(double x, double z) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class CNGDimensionDispatchTest {
|
||||
private static final double EPSILON = 1.0E-12D;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void eachEntrypointUsesItsNativeDimensionWithScalingAndOpacity() {
|
||||
CoordinateNoise source = new CoordinateNoise();
|
||||
CNG noise = new CNG(new RNG(41L), source, 0.6D, 1).scale(0.375D).bake().scale(1.75D);
|
||||
double scale = 0.375D * 1.75D;
|
||||
|
||||
for (double x : new double[]{-13.25D, 0D, 17.5D}) {
|
||||
double z = x + 2.75D;
|
||||
double y = x - 4.125D;
|
||||
double expected1D = source.noise(x * scale) * 0.6D;
|
||||
double expected2D = source.noise(x * scale, z * scale) * 0.6D;
|
||||
double expected3D = source.noise(x * scale, y * scale, z * scale) * 0.6D;
|
||||
|
||||
assertEquals(expected1D, noise.noise(x), EPSILON);
|
||||
assertEquals(expected1D, noise.noiseFast1D(x), EPSILON);
|
||||
assertEquals(expected1D, noise.noise(new double[]{x}), EPSILON);
|
||||
assertEquals(expected2D, noise.noise(x, z), EPSILON);
|
||||
assertEquals(expected2D, noise.noiseFast2D(x, z), EPSILON);
|
||||
assertEquals(expected2D, noise.noise(new double[]{x, z}), EPSILON);
|
||||
assertEquals(expected2D * 2D - 1D, noise.noiseFastSigned2D(x, z), EPSILON);
|
||||
assertEquals(expected3D, noise.noise(x, y, z), EPSILON);
|
||||
assertEquals(expected3D, noise.noiseFast3D(x, y, z), EPSILON);
|
||||
assertEquals(expected3D, noise.noise(new double[]{x, y, z}), EPSILON);
|
||||
assertEquals(expected3D * 2D - 1D, noise.noiseFastSigned3D(x, y, z), EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatorsWithoutCoordinateScalingKeepTheirNativeInputs() {
|
||||
CoordinateNoise source = new CoordinateNoise() {
|
||||
@Override
|
||||
public boolean isNoScale() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
CNG fracture = new CNG(new RNG(2L), new CoordinateNoise(), 1D, 1);
|
||||
CNG noise = new CNG(new RNG(1L), source, 1D, 1).scale(7D).bake().fractureWith(fracture, 12D);
|
||||
|
||||
assertEquals(source.noise(3D), noise.noiseFast1D(3D), EPSILON);
|
||||
assertEquals(source.noise(3D, 7D), noise.noiseFast2D(3D, 7D), EPSILON);
|
||||
assertEquals(source.noiseSigned(3D, 7D), noise.noiseFastSigned2D(3D, 7D), EPSILON);
|
||||
assertEquals(source.noise(3D, 5D, 7D), noise.noiseFast3D(3D, 5D, 7D), EPSILON);
|
||||
assertEquals(source.noiseSigned(3D, 5D, 7D), noise.noiseFastSigned3D(3D, 5D, 7D), EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractureSamplesAndDisplacesTheRequestedDimensions() {
|
||||
CoordinateNoise source = new CoordinateNoise();
|
||||
CoordinateNoise fractureSource = new CoordinateNoise();
|
||||
CNG fracture = new CNG(new RNG(2L), fractureSource, 1D, 1);
|
||||
CNG noise = new CNG(new RNG(1L), source, 0.7D, 1).scale(0.35D).fractureWith(fracture, 12D);
|
||||
double x = -13.25D;
|
||||
double y = 5.5D;
|
||||
double z = 37.75D;
|
||||
double displaced1D = x + (fractureSource.noise(x) - 0.5D) * 12D;
|
||||
double displacedX2D = x + (fractureSource.noise(x, z) - 0.5D) * 12D;
|
||||
double displacedZ2D = z + (fractureSource.noise(z, x) - 0.5D) * 12D;
|
||||
double displacedX3D = x + (fractureSource.noise(x, y, z) - 0.5D) * 12D;
|
||||
double displacedY3D = y + (fractureSource.noise(y, x) - 0.5D) * 12D;
|
||||
double displacedZ3D = z + (fractureSource.noise(z, x, y) - 0.5D) * 12D;
|
||||
double expected1D = source.noise(displaced1D * 0.35D) * 0.7D;
|
||||
double expected2D = source.noise(displacedX2D * 0.35D, displacedZ2D * 0.35D) * 0.7D;
|
||||
double expected3D = source.noise(displacedX3D * 0.35D, displacedY3D * 0.35D, displacedZ3D * 0.35D) * 0.7D;
|
||||
|
||||
assertEquals(expected1D, noise.noiseFast1D(x), EPSILON);
|
||||
assertEquals(expected1D, noise.noise(new double[]{x}), EPSILON);
|
||||
assertEquals(expected2D, noise.noiseFast2D(x, z), EPSILON);
|
||||
assertEquals(expected2D, noise.noise(new double[]{x, z}), EPSILON);
|
||||
assertEquals(expected2D * 2D - 1D, noise.noiseFastSigned2D(x, z), EPSILON);
|
||||
assertEquals(expected3D, noise.noiseFast3D(x, y, z), EPSILON);
|
||||
assertEquals(expected3D, noise.noise(new double[]{x, y, z}), EPSILON);
|
||||
assertEquals(expected3D * 2D - 1D, noise.noiseFastSigned3D(x, y, z), EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void offsetsPreserveTheNativeCoordinatePlane() {
|
||||
long seed = 75L;
|
||||
RNG random = new RNG(seed);
|
||||
double offsetX = random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
|
||||
double offsetZ = random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
|
||||
CoordinateNoise source = new CoordinateNoise();
|
||||
CNG noise = new CNG(new RNG(9L), source.offset(seed), 1D, 1);
|
||||
|
||||
assertEquals(source.noise(3D + offsetX), noise.noise(3D), EPSILON);
|
||||
assertEquals(source.noise(3D + offsetX, 7D + offsetZ), noise.noise(3D, 7D), EPSILON);
|
||||
assertEquals(source.noiseSigned(3D + offsetX, 7D + offsetZ), noise.noiseFastSigned2D(3D, 7D), EPSILON);
|
||||
assertEquals(source.noise(3D + offsetX, 5D, 7D + offsetZ), noise.noise(3D, 5D, 7D), EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void zoomResizesSharedFracturesAndChildrenOnce() {
|
||||
CNG shared = new CNG(new RNG(71L), new CoordinateNoise(), 1D, 1).scale(0.7D);
|
||||
CNG noise = new CNG(new RNG(73L), new CoordinateNoise(), 1D, 1)
|
||||
.scale(0.8D).fractureWith(shared, 3D).child(shared);
|
||||
double one = noise.noise(7D);
|
||||
double two = noise.noise(7D, -11D);
|
||||
double three = noise.noise(7D, 17D, -11D);
|
||||
noise.zoom(2D);
|
||||
assertEquals(one, noise.noise(14D), EPSILON);
|
||||
assertEquals(two, noise.noise(14D, -22D), EPSILON);
|
||||
assertEquals(three, noise.noise(14D, 34D, -22D), EPSILON);
|
||||
assertEquals(two * 2D - 1D, noise.noiseFastSigned2D(14D, -22D), EPSILON);
|
||||
assertEquals(three * 2D - 1D, noise.noiseFastSigned3D(14D, 34D, -22D), EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void zoomInvalidatesBakedAndSignedSamples() {
|
||||
CoordinateNoise source = new CoordinateNoise();
|
||||
CNG noise = new CNG(new RNG(31L), source, 1D, 1);
|
||||
noise.cached(4, "zoom", temporaryFolder.getRoot(), true);
|
||||
noise.noiseFastSigned2D(2D, 2D);
|
||||
noise.zoom(2D);
|
||||
assertEquals(source.noise(1D, 1D), noise.noise(2D, 2D), EPSILON);
|
||||
assertEquals(source.noiseSigned(1D, 1D), noise.noiseFastSigned2D(2D, 2D), EPSILON);
|
||||
for (double factor : new double[]{0D, -1D, Double.NaN, Double.POSITIVE_INFINITY, Double.MIN_VALUE}) {
|
||||
assertThrows(IllegalArgumentException.class, () -> noise.zoom(factor));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cachedIntegerAndUncachedFractionalSamplesUseTheSamePlane() {
|
||||
CoordinateNoise source = new CoordinateNoise();
|
||||
CNG noise = new CNG(new RNG(15L), source, 1D, 1);
|
||||
noise.noiseFastSigned2D(1D, 2D);
|
||||
noise.cached(4, "dimensions", temporaryFolder.getRoot(), true);
|
||||
double cached = (float) source.noise(1D, 2D);
|
||||
|
||||
assertEquals(cached, noise.noise(1D, 2D), 0D);
|
||||
assertEquals(cached, noise.noiseFast2D(1D, 2D), 0D);
|
||||
assertEquals(cached, noise.noise(new double[]{1D, 2D}), 0D);
|
||||
assertEquals(cached * 2D - 1D, noise.noiseFastSigned2D(1D, 2D), 0D);
|
||||
assertEquals(source.noise(1.25D, 2.5D), noise.noiseFast2D(1.25D, 2.5D), EPSILON);
|
||||
assertEquals(source.noise(1D), noise.noise(1D), EPSILON);
|
||||
assertEquals(source.noise(1D, 3D, 2D), noise.noise(1D, 3D, 2D), EPSILON);
|
||||
|
||||
noise.scale(0.5D);
|
||||
assertEquals(source.noise(0.5D, 1D), noise.noiseFast2D(1D, 2D), EPSILON);
|
||||
assertEquals(source.noiseSigned(0.5D, 1D), noise.noiseFastSigned2D(1D, 2D), EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cachedNoiseDoesNotMirrorOrRepeatOutsideItsBakedArea() {
|
||||
CNG uncached = new CNG(new RNG(15L), new CoordinateNoise(), 1D, 1);
|
||||
CNG cached = new CNG(new RNG(15L), new CoordinateNoise(), 1D, 1);
|
||||
cached.cached(4, "bounded-dimensions", temporaryFolder.getRoot(), true);
|
||||
double[] coordinates = {-8D, -4D, -1D, -0.25D, 0D, 0.25D, 3D, 3.75D, 4D, 4.25D, 8D, 29_999_999D};
|
||||
|
||||
for (double x : coordinates) {
|
||||
assertEquals(uncached.noise(x), cached.noise(x), 0D);
|
||||
assertEquals(uncached.noiseFast1D(x), cached.noiseFast1D(x), 0D);
|
||||
assertEquals(uncached.noise(new double[]{x}), cached.noise(new double[]{x}), 0D);
|
||||
|
||||
for (double z : coordinates) {
|
||||
double expected = uncached.noise(x, z);
|
||||
if (x >= 0D && x < 4D && z >= 0D && z < 4D && x == (int) x && z == (int) z) {
|
||||
expected = (float) expected;
|
||||
}
|
||||
|
||||
assertEquals(expected, cached.noise(x, z), 0D);
|
||||
assertEquals(expected, cached.noiseFast2D(x, z), 0D);
|
||||
assertEquals(expected, cached.noise(new double[]{x, z}), 0D);
|
||||
assertEquals(expected * 2D - 1D, cached.noiseFastSigned2D(x, z), EPSILON);
|
||||
assertEquals(uncached.noise(x, 5D, z), cached.noise(x, 5D, z), 0D);
|
||||
assertEquals(uncached.noiseFast3D(x, 5D, z), cached.noiseFast3D(x, 5D, z), 0D);
|
||||
assertEquals(uncached.noiseFastSigned3D(x, 5D, z), cached.noiseFastSigned3D(x, 5D, z), 0D);
|
||||
assertEquals(uncached.noise(new double[]{x, 5D, z}), cached.noise(new double[]{x, 5D, z}), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void octaveChangesReachNestedOffsetsAndInvalidateSignedSamples() {
|
||||
OctaveCoordinateNoise source = new OctaveCoordinateNoise();
|
||||
OffsetNoiseGenerator offset = source.offset(6L).offset(9L);
|
||||
CNG noise = new CNG(new RNG(77L), offset, 1D, 3);
|
||||
|
||||
assertEquals(3, source.octaves);
|
||||
double previous2D = noise.noiseFastSigned2D(3D, 7D);
|
||||
double previous3D = noise.noiseFastSigned3D(3D, 5D, 7D);
|
||||
noise.oct(5);
|
||||
|
||||
assertEquals(5, source.octaves);
|
||||
assertEquals(offset.noiseSigned(3D, 7D), noise.noiseFastSigned2D(3D, 7D), EPSILON);
|
||||
assertEquals(offset.noiseSigned(3D, 5D, 7D), noise.noiseFastSigned3D(3D, 5D, 7D), EPSILON);
|
||||
assertNotEquals(previous2D, noise.noiseFastSigned2D(3D, 7D), EPSILON);
|
||||
assertNotEquals(previous3D, noise.noiseFastSigned3D(3D, 5D, 7D), EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformChangesInvalidateSignedSamples() {
|
||||
CNG noise = new CNG(new RNG(34L), new CoordinateNoise(), 1D, 1);
|
||||
double previous2D = noise.noiseFastSigned2D(3D, 7D);
|
||||
double previous3D = noise.noiseFastSigned3D(3D, 5D, 7D);
|
||||
noise.up(0.125D);
|
||||
|
||||
assertEquals(previous2D + 0.25D, noise.noiseFastSigned2D(3D, 7D), EPSILON);
|
||||
assertEquals(previous3D + 0.25D, noise.noiseFastSigned3D(3D, 5D, 7D), EPSILON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidDimensionCountsFailExplicitly() {
|
||||
CNG noise = new CNG(new RNG(9L), new CoordinateNoise(), 1D, 1);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> noise.noise(new double[0]));
|
||||
assertThrows(IllegalArgumentException.class, () -> noise.noise(new double[]{1D, 2D, 3D, 4D}));
|
||||
}
|
||||
|
||||
private static class CoordinateNoise implements NoiseGenerator {
|
||||
@Override
|
||||
public double noise(double x) {
|
||||
return 0.125D + x * 0.001D;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
return 0.25D + x * 0.001D + z * 0.002D;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
return 0.5D + x * 0.001D + y * 0.002D + z * 0.003D;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class OctaveCoordinateNoise extends CoordinateNoise implements OctaveNoise {
|
||||
private int octaves;
|
||||
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
return super.noise(x, z) / octaves;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
return super.noise(x, y, z) / octaves;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOctaves(int octaves) {
|
||||
this.octaves = octaves;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,7 @@ public class CNGInjectorParityTest {
|
||||
}
|
||||
|
||||
private double legacyCombined1D(CompositeFixture fixture, double x) {
|
||||
double n = fixture.rootGenerator.noise(x, 0D, 0D);
|
||||
double n = fixture.rootGenerator.noise(x);
|
||||
double m = 1D;
|
||||
|
||||
double valueA = fixture.childA.noise(x);
|
||||
@@ -143,7 +143,7 @@ public class CNGInjectorParityTest {
|
||||
}
|
||||
|
||||
private double legacyCombined2D(CompositeFixture fixture, double x, double z) {
|
||||
double n = fixture.rootGenerator.noise(x, z, 0D);
|
||||
double n = fixture.rootGenerator.noise(x, z);
|
||||
double m = 1D;
|
||||
|
||||
double valueA = fixture.childA.noise(x, z);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class CellularNoisePrecisionTest {
|
||||
@Test
|
||||
public void distantCellBoundariesRetainSubFloatCoordinatePrecision() {
|
||||
for (double origin : new double[]{-29_999_000D, 29_998_000D}) {
|
||||
CellularNoise noise = new CellularNoise(1337L);
|
||||
for (int dimensions = 1; dimensions <= 3; dimensions++) {
|
||||
double previousCoordinate = origin;
|
||||
double previousValue = sample(noise, dimensions, origin);
|
||||
boolean preciseBoundary = false;
|
||||
for (int step = 1; step <= 8192; step++) {
|
||||
double coordinate = origin + step * 0.125D;
|
||||
double value = sample(noise, dimensions, coordinate);
|
||||
if (Float.floatToRawIntBits((float) coordinate)
|
||||
== Float.floatToRawIntBits((float) previousCoordinate)
|
||||
&& value != previousValue) {
|
||||
preciseBoundary = true;
|
||||
break;
|
||||
}
|
||||
previousCoordinate = coordinate;
|
||||
previousValue = value;
|
||||
}
|
||||
assertTrue("Missing precise cell boundary at " + origin + " in " + dimensions + "D", preciseBoundary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void distantSamplesAreBoundedAndRepeatableAcrossSeeds() {
|
||||
for (long seed : new long[]{0L, -1L, Long.MIN_VALUE, Long.MAX_VALUE}) {
|
||||
CellularNoise first = new CellularNoise(seed);
|
||||
CellularNoise second = new CellularNoise(seed);
|
||||
for (int dimensions = 1; dimensions <= 3; dimensions++) {
|
||||
for (int step = -256; step <= 256; step++) {
|
||||
double coordinate = Math.copySign(29_999_000D, step) + step * 0.125D;
|
||||
double value = sample(first, dimensions, coordinate);
|
||||
assertTrue(Double.isFinite(value) && value >= 0D && value <= 1D);
|
||||
assertEquals(value, sample(second, dimensions, coordinate), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static double sample(CellularNoise noise, int dimensions, double coordinate) {
|
||||
return switch (dimensions) {
|
||||
case 1 -> noise.noise(coordinate);
|
||||
case 2 -> noise.noise(coordinate, -29_998_713.375D);
|
||||
default -> noise.noise(coordinate, 73.375D, 29_998_713.375D);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ClassicNoiseQualityTest {
|
||||
private static final long SEED = 972453L;
|
||||
|
||||
@Test
|
||||
public void directCubicUsesConfiguredSeedInBothPrecisions() {
|
||||
for (long seed : new long[]{1L, -23L, Long.MAX_VALUE}) {
|
||||
FastNoiseDouble doubles = new FastNoiseDouble(seed);
|
||||
doubles.setNoiseType(FastNoiseDouble.NoiseType.Cubic);
|
||||
FastNoise floats = new FastNoise((int) seed);
|
||||
floats.SetNoiseType(FastNoise.NoiseType.Cubic);
|
||||
for (int x = -300; x <= 300; x += 37) {
|
||||
double sampleX = x + 0.193D;
|
||||
double sampleZ = x * 0.71D + 43.517D;
|
||||
assertEquals(doubles.GetNoise(sampleX, sampleZ), doubles.GetCubic(sampleX, sampleZ), 0D);
|
||||
assertEquals(floats.GetNoise((float) sampleX, (float) sampleZ),
|
||||
floats.GetCubic((float) sampleX, (float) sampleZ), 0F);
|
||||
}
|
||||
}
|
||||
CubicNoise first = new CubicNoise(SEED);
|
||||
CubicNoise second = new CubicNoise(SEED + 1);
|
||||
assertNotEquals(first.noise(37.219D), second.noise(37.219D), 1E-8D);
|
||||
assertNotEquals(first.noise(37.219D, -91.713D), second.noise(37.219D, -91.713D), 1E-8D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void perlinDefaultsToQuinticSmoothing() {
|
||||
FastNoiseDouble source = new FastNoiseDouble(new RNG(SEED).lmax());
|
||||
source.setLongerp(FastNoiseDouble.Longerp.Qulongic);
|
||||
source.setFractalType(FastNoiseDouble.FractalType.Billow);
|
||||
source.setFractalOctaves(1);
|
||||
PerlinNoise perlin = new PerlinNoise(SEED);
|
||||
FractalBillowPerlinNoise billow = new FractalBillowPerlinNoise(SEED);
|
||||
for (int x = -240; x <= 240; x += 17) {
|
||||
double sampleX = x + 0.37D;
|
||||
double sampleZ = x * 0.91D - 8.143D;
|
||||
assertEquals(source.GetPerlin(sampleX, sampleZ), perlin.noiseSigned(sampleX, sampleZ), 0D);
|
||||
assertEquals(source.GetPerlinFractal(sampleX, sampleZ) / 2D + 0.5D,
|
||||
billow.noise(sampleX, sampleZ), 0D);
|
||||
assertEquals(source.GetPerlin(sampleX, 63.21D, sampleZ),
|
||||
perlin.noiseSigned(sampleX, 63.21D, sampleZ), 0D);
|
||||
assertEquals(source.GetPerlinFractal(sampleX, 63.21D, sampleZ) / 2D + 0.5D,
|
||||
billow.noise(sampleX, 63.21D, sampleZ), 0D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void perlinSlopesRemainContinuousAcrossLatticeBoundaries() {
|
||||
PerlinNoise noise = new PerlinNoise(SEED);
|
||||
double step = 0.001D;
|
||||
for (int x = -400; x <= 400; x += 100) {
|
||||
double center = noise.noiseSigned(x, 37.219D, -91.713D);
|
||||
double leftSlope = (center - noise.noiseSigned(x - step, 37.219D, -91.713D)) / step;
|
||||
double rightSlope = (noise.noiseSigned(x + step, 37.219D, -91.713D) - center) / step;
|
||||
assertEquals(leftSlope, rightSlope, 1E-8D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ridgedSimplexNormalizesTheActualOctaveRange() {
|
||||
FractalRigidMultiSimplexNoise ridge = new FractalRigidMultiSimplexNoise(SEED);
|
||||
FastNoiseDouble source = new FastNoiseDouble(new RNG(SEED).lmax());
|
||||
source.setFractalType(FastNoiseDouble.FractalType.RigidMulti);
|
||||
for (int octaves : new int[]{1, 2, 4, 8, 16}) {
|
||||
ridge.setOctaves(octaves);
|
||||
source.setFractalOctaves(octaves);
|
||||
double amplitudeSum = 2D - Math.scalb(1D, 1 - octaves);
|
||||
assertEquals(0D, ridge.f(1D - amplitudeSum), 1E-15D);
|
||||
assertEquals(1D, ridge.f(1D), 0D);
|
||||
for (int x = -400; x <= 400; x += 11) {
|
||||
double sampleX = x + 0.37D;
|
||||
double sampleZ = x * 0.91D - 8.143D;
|
||||
double value = ridge.noise(sampleX, sampleZ);
|
||||
double raw = source.GetSimplexFractal(sampleX, sampleZ);
|
||||
assertEquals((raw + amplitudeSum - 1D) / amplitudeSum, value, 1E-15D);
|
||||
assertTrue(value >= 0D && value <= 1D);
|
||||
assertEquals(value * 2D - 1D, ridge.noiseSigned(sampleX, sampleZ), 0D);
|
||||
}
|
||||
}
|
||||
ridge.setOctaves(1);
|
||||
assertEquals(1D - Math.abs(source.GetSimplex(37.219D, -91.713D)),
|
||||
ridge.noise(37.219D, -91.713D), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void classicAndCellularFamiliesRemainBoundedAtWorldEdges() {
|
||||
NoiseGenerator[] generators = {
|
||||
new CubicNoise(SEED), new FractalCubicNoise(SEED), new FractalBillowPerlinNoise(SEED),
|
||||
new FractalBillowSimplexNoise(SEED), new FractalFBMSimplexNoise(SEED),
|
||||
new FractalRigidMultiSimplexNoise(SEED), new GlobNoise(SEED), new CellHeightNoise(SEED),
|
||||
new VascularNoise(SEED), new CellularNoise(SEED)
|
||||
};
|
||||
for (NoiseGenerator generator : generators) {
|
||||
for (int octave : new int[]{1, 4, 16}) {
|
||||
if (generator instanceof OctaveNoise fractal) {
|
||||
fractal.setOctaves(octave);
|
||||
}
|
||||
for (double origin : new double[]{-30_000_000D, 0D, 30_000_000D}) {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
double x = origin + i * 0.317D;
|
||||
double z = -origin - i * 0.913D;
|
||||
assertBounded(generator.noise(x));
|
||||
assertBounded(generator.noise(x, z));
|
||||
assertBounded(generator.noise(x, 71.917D, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cellularHeightAndVascularKeepComplementaryProfiles() {
|
||||
CellHeightNoise height = new CellHeightNoise(SEED);
|
||||
VascularNoise vascular = new VascularNoise(SEED);
|
||||
for (int x = -300; x <= 300; x += 13) {
|
||||
double sampleX = x + 0.91D;
|
||||
double sampleZ = x * 0.31D - 7.193D;
|
||||
assertEquals(1D, height.noise(sampleX, sampleZ) + vascular.noise(sampleX, sampleZ), 1E-15D);
|
||||
assertEquals(1D, height.noise(sampleX, 87.137D, sampleZ)
|
||||
+ vascular.noise(sampleX, 87.137D, sampleZ), 1E-15D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vascularBrightensWhereCellDistancesConverge() {
|
||||
FastNoiseDouble distance = new FastNoiseDouble(new RNG(SEED).lmax());
|
||||
distance.setCellularDistanceFunction(FastNoiseDouble.CellularDistanceFunction.Natural);
|
||||
distance.setCellularReturnType(FastNoiseDouble.CellularReturnType.Distance2Sub);
|
||||
CellHeightNoise height = new CellHeightNoise(SEED);
|
||||
VascularNoise vascular = new VascularNoise(SEED);
|
||||
int borderSamples = 0;
|
||||
int interiorSamples = 0;
|
||||
for (int x = -500; x <= 500; x++) {
|
||||
double sampleX = x + 0.137D;
|
||||
double sampleZ = x * 3.17D - 71.913D;
|
||||
double separation = distance.GetCellular(sampleX, sampleZ) + 1D;
|
||||
if (separation < 0.02D) {
|
||||
assertTrue(vascular.noise(sampleX, sampleZ) > 0.99D);
|
||||
assertTrue(height.noise(sampleX, sampleZ) < 0.01D);
|
||||
borderSamples++;
|
||||
} else if (separation > 1D) {
|
||||
assertTrue(vascular.noise(sampleX, sampleZ) < 0.5D);
|
||||
assertTrue(height.noise(sampleX, sampleZ) > 0.5D);
|
||||
interiorSamples++;
|
||||
}
|
||||
assertEquals(vascular.noise(sampleX, sampleZ) * 2D - 1D,
|
||||
vascular.noiseSigned(sampleX, sampleZ), 2E-16D);
|
||||
}
|
||||
assertTrue(borderSamples > 0);
|
||||
assertTrue(interiorSamples > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractalConstructorsStartAtOneOctave() {
|
||||
NoiseGenerator[] generators = {
|
||||
new FractalCubicNoise(SEED), new FractalFBMSimplexNoise(SEED),
|
||||
new FractalBillowPerlinNoise(SEED), new FractalBillowSimplexNoise(SEED),
|
||||
new FractalRigidMultiSimplexNoise(SEED)
|
||||
};
|
||||
for (NoiseGenerator generator : generators) {
|
||||
double initial = generator.noise(37.219D, -91.713D, 17.731D);
|
||||
((OctaveNoise) generator).setOctaves(1);
|
||||
assertEquals(initial, generator.noise(37.219D, -91.713D, 17.731D), 0D);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertBounded(double value) {
|
||||
assertTrue(Double.isFinite(value) && value >= 0D && value <= 1D);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class CloverNoiseBoundaryTest {
|
||||
@Test
|
||||
public void threeDimensionalNoiseRemainsContinuousAcrossZeroZ() {
|
||||
for (long seed : new long[]{0L, 1337L, Long.MIN_VALUE}) {
|
||||
CloverNoise noise = new CloverNoise(seed);
|
||||
for (double x = -3.125D; x < 4D; x += 0.375D) {
|
||||
double atZero = noise.noise(x, 0.417D, 0D);
|
||||
assertEquals(atZero, noise.noise(x, 0.417D, -1E-7D), 1E-5D);
|
||||
assertEquals(atZero, noise.noise(x, 0.417D, 1E-7D), 1E-5D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 5000)
|
||||
public void fractalIterationsCannotDivideByZeroOrRunUnbounded() {
|
||||
CloverNoise.Noise2D twoDimensional = new CloverNoise.Noise2D(1337L);
|
||||
CloverNoise.Noise3D threeDimensional = new CloverNoise.Noise3D(1337L);
|
||||
assertEquals(twoDimensional.noise(0.25D, -0.75D),
|
||||
twoDimensional.fractalNoise(0.25D, -0.75D, 0), 0D);
|
||||
assertEquals(threeDimensional.noise(0.25D, 0.5D, -0.75D),
|
||||
threeDimensional.fractalNoise(0.25D, 0.5D, -0.75D, Integer.MIN_VALUE), 0D);
|
||||
double bounded2D = twoDimensional.fractalNoise(0.25D, -0.75D, 16);
|
||||
double bounded3D = threeDimensional.fractalNoise(0.25D, 0.5D, -0.75D, 16);
|
||||
assertEquals(bounded2D, twoDimensional.fractalNoise(0.25D, -0.75D, Integer.MAX_VALUE), 0D);
|
||||
assertEquals(bounded3D, threeDimensional.fractalNoise(0.25D, 0.5D, -0.75D, Integer.MAX_VALUE), 0D);
|
||||
assertTrue(Double.isFinite(bounded2D));
|
||||
assertTrue(Double.isFinite(bounded3D));
|
||||
}
|
||||
@Test
|
||||
public void stationaryCurlVectorsNormalizeToZero() {
|
||||
CloverNoise.Vector2 twoDimensional = new CloverNoise.Vector2().normalize();
|
||||
CloverNoise.Vector3 threeDimensional = new CloverNoise.Vector3().normalize();
|
||||
assertEquals(0D, twoDimensional.getX(), 0D);
|
||||
assertEquals(0D, twoDimensional.getY(), 0D);
|
||||
assertEquals(0D, threeDimensional.getX(), 0D);
|
||||
assertEquals(0D, threeDimensional.getY(), 0D);
|
||||
assertEquals(0D, threeDimensional.getZ(), 0D);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,13 @@ package art.arcane.iris.util.project.noise;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class CloverNoiseParityTest {
|
||||
@@ -40,4 +47,59 @@ public class CloverNoiseParityTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void threeDimensionalCoordinateGridPreservesExactNoiseBits() {
|
||||
long[] expected = {-4534805072329300766L, 938556028071277557L, -6813256383186365522L,
|
||||
-4452859942568220233L, 5365194784097108235L};
|
||||
|
||||
for (int index = 0; index < SEEDS.length; index++) {
|
||||
assertEquals("seed=" + SEEDS[index], expected[index], coordinateDigest(new CloverNoise(SEEDS[index]), 0));
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 15000)
|
||||
public void concurrentSamplingKeepsThreadScratchIndependent() throws Exception {
|
||||
CloverNoise shared = new CloverNoise(1337L);
|
||||
long[] expected = new long[8];
|
||||
for (int index = 0; index < expected.length; index++) {
|
||||
expected[index] = coordinateDigest(shared, index * 991);
|
||||
}
|
||||
|
||||
try (ExecutorService executor = Executors.newFixedThreadPool(4)) {
|
||||
List<Future<Long>> results = new ArrayList<>(expected.length);
|
||||
for (int index = 0; index < expected.length; index++) {
|
||||
int start = index * 991;
|
||||
results.add(executor.submit(() -> coordinateDigest(shared, start)));
|
||||
}
|
||||
|
||||
for (int index = 0; index < expected.length; index++) {
|
||||
assertEquals(expected[index], results.get(index).get(5, TimeUnit.SECONDS).longValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void samplingKeepsCallerVectorsUnchanged() {
|
||||
CloverNoise.Noise3D noise = new CloverNoise.Noise3D(1337L);
|
||||
CloverNoise.Vector3 point = new CloverNoise.Vector3(0.25D, 0.5D, -0.75D);
|
||||
double expected = noise.noise(point);
|
||||
noise.noise(71D, -31D, 113D);
|
||||
|
||||
assertEquals(0.25D, point.getX(), 0D);
|
||||
assertEquals(0.5D, point.getY(), 0D);
|
||||
assertEquals(-0.75D, point.getZ(), 0D);
|
||||
assertEquals(expected, noise.noise(point), 0D);
|
||||
}
|
||||
|
||||
private static long coordinateDigest(CloverNoise noise, int start) {
|
||||
long digest = 0L;
|
||||
for (int index = start; index < start + 8192; index++) {
|
||||
double x = ((index * 1580030173L) % 60000000L - 30000000L) / 64D + 0.371D;
|
||||
double y = ((index * 91437L) % 384L - 64L) / 64D + 0.231D;
|
||||
double z = ((index * 59260789L) % 60000000L - 30000000L) / 64D - 0.219D;
|
||||
digest = Long.rotateLeft(digest, 7) ^ Double.doubleToRawLongBits(noise.noise(x, y, z));
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
}
|
||||
+50
-11
@@ -6,6 +6,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class FastNoiseDoubleCellularParityTest {
|
||||
private static final FastNoiseDouble.CellularDistanceFunction[] DISTANCE_FUNCTIONS = new FastNoiseDouble.CellularDistanceFunction[]{
|
||||
@@ -29,7 +30,7 @@ public class FastNoiseDoubleCellularParityTest {
|
||||
private static final double[] CELL_3D_Z = getDeclaredDoubleArray("CELL_3D_Z");
|
||||
|
||||
@Test
|
||||
public void cellular2DTwoEdgeModesMatchLegacyUpdateLogic() throws Exception {
|
||||
public void cellular2DTwoEdgeModesMatchExpandedReference() throws Exception {
|
||||
for (long seed = 11L; seed <= 17L; seed++) {
|
||||
double frequency = 0.037D + (seed * 0.001D);
|
||||
for (FastNoiseDouble.CellularDistanceFunction distanceFunction : DISTANCE_FUNCTIONS) {
|
||||
@@ -42,7 +43,7 @@ public class FastNoiseDoubleCellularParityTest {
|
||||
for (int y = -96; y <= 96; y += 13) {
|
||||
double sampleX = x + 0.37D;
|
||||
double sampleY = y - 0.41D;
|
||||
double expected = legacyGetCellular2D(seed, frequency, distanceFunction, returnType, sampleX, sampleY);
|
||||
double expected = referenceGetCellular2D(seed, frequency, distanceFunction, returnType, sampleX, sampleY);
|
||||
double actual = noise.GetCellular(sampleX, sampleY);
|
||||
assertEquals("2D seed=" + seed + " distance=" + distanceFunction + " return=" + returnType + " x=" + sampleX + " y=" + sampleY, expected, actual, 0D);
|
||||
}
|
||||
@@ -53,7 +54,7 @@ public class FastNoiseDoubleCellularParityTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cellular3DTwoEdgeModesMatchLegacyUpdateLogic() throws Exception {
|
||||
public void cellular3DTwoEdgeModesMatchExpandedReference() throws Exception {
|
||||
for (long seed = 31L; seed <= 35L; seed++) {
|
||||
double frequency = 0.029D + (seed * 0.001D);
|
||||
for (FastNoiseDouble.CellularDistanceFunction distanceFunction : DISTANCE_FUNCTIONS) {
|
||||
@@ -68,7 +69,7 @@ public class FastNoiseDoubleCellularParityTest {
|
||||
double sampleX = x + 0.19D;
|
||||
double sampleY = y - 0.27D;
|
||||
double sampleZ = z + 0.43D;
|
||||
double expected = legacyGetCellular3D(seed, frequency, distanceFunction, returnType, sampleX, sampleY, sampleZ);
|
||||
double expected = referenceGetCellular3D(seed, frequency, distanceFunction, returnType, sampleX, sampleY, sampleZ);
|
||||
double actual = noise.GetCellular(sampleX, sampleY, sampleZ);
|
||||
assertEquals("3D seed=" + seed + " distance=" + distanceFunction + " return=" + returnType + " x=" + sampleX + " y=" + sampleY + " z=" + sampleZ, expected, actual, 0D);
|
||||
}
|
||||
@@ -79,7 +80,44 @@ public class FastNoiseDoubleCellularParityTest {
|
||||
}
|
||||
}
|
||||
|
||||
private double legacyGetCellular2D(long seed, double frequency, FastNoiseDouble.CellularDistanceFunction distanceFunction, FastNoiseDouble.CellularReturnType returnType, double x, double y) throws Exception {
|
||||
@Test
|
||||
public void secondNearestCellMayLieOutsideImmediateNeighbors() throws Exception {
|
||||
long seed2D = 2320148477032134941L;
|
||||
long seed3D = 8394744594210714849L;
|
||||
for (FastNoiseDouble.CellularDistanceFunction distanceFunction : DISTANCE_FUNCTIONS) {
|
||||
for (FastNoiseDouble.CellularReturnType returnType : TWO_EDGE_RETURN_TYPES) {
|
||||
FastNoiseDouble noise = new FastNoiseDouble(seed2D);
|
||||
noise.setFrequency(1D);
|
||||
noise.setCellularDistanceFunction(distanceFunction);
|
||||
noise.setCellularReturnType(returnType);
|
||||
double x = 783.44443982D;
|
||||
double y = 1896.50036984D;
|
||||
assertEquals(referenceGetCellular2D(seed2D, 1D, distanceFunction, returnType, x, y),
|
||||
noise.GetCellular(x, y), 0D);
|
||||
noise.setSeed(seed3D);
|
||||
x = -2741.75935805D;
|
||||
y = -1049.4161945D;
|
||||
double z = -1528.00711843D;
|
||||
assertEquals(referenceGetCellular3D(seed3D, 1D, distanceFunction, returnType, x, y, z),
|
||||
noise.GetCellular(x, y, z), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void distanceProfilesStayContinuousWhenSearchCenterChanges() {
|
||||
FastNoiseDouble noise = new FastNoiseDouble(2320148477032134941L);
|
||||
noise.setFrequency(1D);
|
||||
noise.setCellularDistanceFunction(FastNoiseDouble.CellularDistanceFunction.Natural);
|
||||
for (FastNoiseDouble.CellularReturnType returnType : TWO_EDGE_RETURN_TYPES) {
|
||||
noise.setCellularReturnType(returnType);
|
||||
double below = noise.GetCellular(783.44443982D, 1896.5D - 1E-8D);
|
||||
double above = noise.GetCellular(783.44443982D, 1896.5D + 1E-8D);
|
||||
assertTrue(Math.abs(above - below) < 1E-6D);
|
||||
}
|
||||
}
|
||||
|
||||
private double referenceGetCellular2D(long seed, double frequency, FastNoiseDouble.CellularDistanceFunction distanceFunction, FastNoiseDouble.CellularReturnType returnType, double x, double y) throws Exception {
|
||||
double scaledX = x * frequency;
|
||||
double scaledY = y * frequency;
|
||||
long xr = fastRound(scaledX);
|
||||
@@ -87,8 +125,8 @@ public class FastNoiseDoubleCellularParityTest {
|
||||
double distance = 999999D;
|
||||
double distance2 = 999999D;
|
||||
|
||||
for (long xi = xr - 1; xi <= xr + 1; xi++) {
|
||||
for (long yi = yr - 1; yi <= yr + 1; yi++) {
|
||||
for (long xi = xr - 2; xi <= xr + 2; xi++) {
|
||||
for (long yi = yr - 2; yi <= yr + 2; yi++) {
|
||||
int cellIndex = (int) hash2D(seed, xi, yi) & 255;
|
||||
double vecX = xi - scaledX + CELL_2D_X[cellIndex];
|
||||
double vecY = yi - scaledY + CELL_2D_Y[cellIndex];
|
||||
@@ -105,7 +143,7 @@ public class FastNoiseDoubleCellularParityTest {
|
||||
return applyTwoEdgeReturn(distance, distance2, returnType);
|
||||
}
|
||||
|
||||
private double legacyGetCellular3D(long seed, double frequency, FastNoiseDouble.CellularDistanceFunction distanceFunction, FastNoiseDouble.CellularReturnType returnType, double x, double y, double z) throws Exception {
|
||||
private double referenceGetCellular3D(long seed, double frequency, FastNoiseDouble.CellularDistanceFunction distanceFunction, FastNoiseDouble.CellularReturnType returnType, double x, double y, double z) throws Exception {
|
||||
double scaledX = x * frequency;
|
||||
double scaledY = y * frequency;
|
||||
double scaledZ = z * frequency;
|
||||
@@ -114,10 +152,11 @@ public class FastNoiseDoubleCellularParityTest {
|
||||
long zr = fastRound(scaledZ);
|
||||
double distance = 999999D;
|
||||
double distance2 = 999999D;
|
||||
int radius = distanceFunction == FastNoiseDouble.CellularDistanceFunction.Manhattan ? 3 : 2;
|
||||
|
||||
for (long xi = xr - 1; xi <= xr + 1; xi++) {
|
||||
for (long yi = yr - 1; yi <= yr + 1; yi++) {
|
||||
for (long zi = zr - 1; zi <= zr + 1; zi++) {
|
||||
for (long xi = xr - radius; xi <= xr + radius; xi++) {
|
||||
for (long yi = yr - radius; yi <= yr + radius; yi++) {
|
||||
for (long zi = zr - radius; zi <= zr + radius; zi++) {
|
||||
int cellIndex = (int) hash3D(seed, xi, yi, zi) & 255;
|
||||
double vecX = xi - scaledX + CELL_3D_X[cellIndex];
|
||||
double vecY = yi - scaledY + CELL_3D_Y[cellIndex];
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class FastNoiseFloorTest {
|
||||
@Test
|
||||
public void doubleLatticeFloorHandlesNegativeIntegersAndNeighbors() throws Exception {
|
||||
Method floor = FastNoiseDouble.class.getDeclaredMethod("fastFloor", double.class);
|
||||
floor.setAccessible(true);
|
||||
assertEquals(-1L, ((Long) floor.invoke(null, -1D)).longValue());
|
||||
assertEquals(-2L, ((Long) floor.invoke(null, -2D)).longValue());
|
||||
assertEquals(-2L, ((Long) floor.invoke(null, Math.nextDown(-1D))).longValue());
|
||||
assertEquals(-1L, ((Long) floor.invoke(null, Math.nextUp(-1D))).longValue());
|
||||
assertEquals(0L, ((Long) floor.invoke(null, -0D)).longValue());
|
||||
assertEquals(Long.MIN_VALUE, ((Long) floor.invoke(null, (double) Long.MIN_VALUE)).longValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void floatLatticeFloorHandlesNegativeIntegersAndNeighbors() throws Exception {
|
||||
Method floor = FastNoise.class.getDeclaredMethod("FastFloor", float.class);
|
||||
floor.setAccessible(true);
|
||||
assertEquals(-1, ((Integer) floor.invoke(null, -1F)).intValue());
|
||||
assertEquals(-2, ((Integer) floor.invoke(null, -2F)).intValue());
|
||||
assertEquals(-2, ((Integer) floor.invoke(null, Math.nextDown(-1F))).intValue());
|
||||
assertEquals(-1, ((Integer) floor.invoke(null, Math.nextUp(-1F))).intValue());
|
||||
assertEquals(0, ((Integer) floor.invoke(null, -0F)).intValue());
|
||||
assertEquals(Integer.MIN_VALUE, ((Integer) floor.invoke(null, (float) Integer.MIN_VALUE)).intValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GeometricNoiseTest {
|
||||
private static final double SQRT_3 = Math.sqrt(3D);
|
||||
private static final long[] SEEDS = {0L, -1L, 1337L, Long.MIN_VALUE, Long.MAX_VALUE};
|
||||
|
||||
@Test
|
||||
public void regularHexagonsMatchNearestLatticeCenters() {
|
||||
NoiseGenerator[] noises = {new HexagonNoise(177L), new HexSimplexNoise(311L)};
|
||||
for (NoiseGenerator noise : noises) {
|
||||
for (double x = -5.93D; x < 6D; x += 0.137D) {
|
||||
for (double z = -5.97D; z < 6D; z += 0.173D) {
|
||||
double bestDistance = Double.POSITIVE_INFINITY;
|
||||
double centerX = 0D;
|
||||
double centerZ = 0D;
|
||||
for (int q = -8; q <= 8; q++) {
|
||||
for (int r = -8; r <= 8; r++) {
|
||||
double candidateX = SQRT_3 * (q + (r * 0.5D));
|
||||
double candidateZ = 1.5D * r;
|
||||
double dx = x - candidateX;
|
||||
double dz = z - candidateZ;
|
||||
double distance = (dx * dx) + (dz * dz);
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
centerX = candidateX;
|
||||
centerZ = candidateZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(noise.noise(centerX, centerZ), noise.noise(x, z), 0D);
|
||||
assertEquals(noise.noise(centerX, 2.75D, centerZ), noise.noise(x, 2.75D, z), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hexagonNeighborsFollowCoherentHeat() {
|
||||
HexagonNoise noise = new HexagonNoise(7331L);
|
||||
double adjacentDifference = 0D;
|
||||
double distantDifference = 0D;
|
||||
for (int q = -32; q <= 32; q++) {
|
||||
for (int r = -32; r <= 32; r++) {
|
||||
double x = SQRT_3 * (q + (r * 0.5D));
|
||||
double z = 1.5D * r;
|
||||
double sample = noise.noise(x, z);
|
||||
adjacentDifference += Math.abs(sample - noise.noise(x + SQRT_3, z));
|
||||
distantDifference += Math.abs(sample - noise.noise(x + (SQRT_3 * 19D), z));
|
||||
}
|
||||
}
|
||||
assertTrue(adjacentDifference < distantDifference * 0.85D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedHexagonsHaveRegularInscribedFootprints() {
|
||||
for (int child = 0; child < 7; child++) {
|
||||
double angle = Math.toRadians((child * 60D) - 90D);
|
||||
double centerX = child == 0 ? 0D : Math.cos(angle) * (2D / 3D);
|
||||
double centerZ = child == 0 ? 0D : Math.sin(angle) * (2D / 3D);
|
||||
for (int direction = 0; direction < 60; direction++) {
|
||||
double sampleAngle = Math.toRadians(direction * 6D);
|
||||
double x = centerX + (Math.cos(sampleAngle) * 0.27D);
|
||||
double z = centerZ + (Math.sin(sampleAngle) * 0.27D);
|
||||
double q = (x / SQRT_3) - (z / 3D);
|
||||
double r = 2D * z / 3D;
|
||||
assertEquals(child, HexagonalNoise.pickChildIndex(q, r));
|
||||
assertEquals(0L, HexagonalNoise.roundQ(q, r));
|
||||
assertEquals(0L, HexagonalNoise.roundR(q, r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sierpinskiCutsTriangleCentroidsAtEveryDepth() {
|
||||
SierpinskiTriangleNoise noise = new SierpinskiTriangleNoise(91L);
|
||||
for (int depth = 0; depth < 4; depth++) {
|
||||
double size = Math.scalb(1D, -depth);
|
||||
double holeU = size / 3D;
|
||||
double holeV = size / 3D;
|
||||
assertTrue(sampleTriangle(noise, holeU, holeV) <= 0.12D);
|
||||
assertTrue(sampleTriangle(noise, 1D - size + holeU, holeV) <= 0.12D);
|
||||
assertTrue(sampleTriangle(noise, holeU, 1D - size + holeV) <= 0.12D);
|
||||
}
|
||||
assertTrue(sampleTriangle(noise, 1D / 48D, 1D / 48D) >= 0.35D);
|
||||
assertTrue(sampleTriangle(noise, 46D / 48D, 1D / 48D) >= 0.35D);
|
||||
assertTrue(sampleTriangle(noise, 1D / 48D, 46D / 48D) >= 0.35D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sierpinskiTilesAcrossNegativeCoordinatesWithExpectedArea() {
|
||||
SierpinskiTriangleNoise noise = new SierpinskiTriangleNoise(9123L);
|
||||
Random random = new Random(8211L);
|
||||
int filled = 0;
|
||||
int samples = 65536;
|
||||
for (int index = 0; index < samples; index++) {
|
||||
double u = random.nextDouble();
|
||||
double v = random.nextDouble();
|
||||
double x = 8D * (u + (v * 0.5D));
|
||||
double z = 4D * SQRT_3 * v;
|
||||
boolean inside = noise.noise(x, z) >= 0.35D;
|
||||
if (inside) {
|
||||
filled++;
|
||||
}
|
||||
assertEquals(inside, noise.noise(x - 8D, z) >= 0.35D);
|
||||
assertEquals(inside, noise.noise(x - 4D, z - (4D * SQRT_3)) >= 0.35D);
|
||||
}
|
||||
assertEquals(Math.pow(0.75D, 4), filled / (double) samples, 0.006D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dimensionalSlicesAreConsistentAndContinuous() {
|
||||
for (NoiseGenerator noise : generators(1337L)) {
|
||||
assertEquals(noise.noise(3.125D), noise.noise(3.125D, 0D), 0D);
|
||||
assertEquals(noise.noise(3.125D, -1.75D), noise.noise(3.125D, 0D, -1.75D), 0D);
|
||||
assertEquals(noise.noise(3.125D, 1.75D, 0D), noise.noise(3.125D, 1.75D, 1E-9D), 1E-7D);
|
||||
assertEquals(noise.noise(3.125D, 0D, -1.75D), noise.noise(3.125D, 1E-9D, -1.75D), 1E-7D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void seedsAndWorldBorderCoordinatesRemainDeterministicAndBounded() {
|
||||
double[][] points = {{0D, 0D, 0D}, {-29_999_984D, -64D, 29_999_984D},
|
||||
{3.125D, 17.375D, -9.25D}, {29_999_984D, 319D, -29_999_984D}};
|
||||
for (long seed : SEEDS) {
|
||||
NoiseGenerator[] first = generators(seed);
|
||||
NoiseGenerator[] second = generators(seed);
|
||||
for (int index = 0; index < first.length; index++) {
|
||||
if (first[index] instanceof OctaveNoise octaves) {
|
||||
octaves.setOctaves(16);
|
||||
((OctaveNoise) second[index]).setOctaves(16);
|
||||
}
|
||||
for (double[] point : points) {
|
||||
double value = first[index].noise(point[0], point[1], point[2]);
|
||||
assertTrue(Double.isFinite(value) && value >= 0D && value <= 1D);
|
||||
assertEquals(value, second[index].noise(point[0], point[1], point[2]), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double sampleTriangle(SierpinskiTriangleNoise noise, double u, double v) {
|
||||
return noise.noise(8D * (u + (v * 0.5D)), 4D * SQRT_3 * v);
|
||||
}
|
||||
|
||||
private NoiseGenerator[] generators(long seed) {
|
||||
return new NoiseGenerator[]{new HexagonNoise(seed), new HexSimplexNoise(seed),
|
||||
new HexRandomSizeNoise(seed), new HexJamesNoise(seed), new SierpinskiTriangleNoise(seed)};
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import art.arcane.iris.util.project.interpolation.InterpolationMethod;
|
||||
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class InterpolatedNoiseDimensionTest {
|
||||
@Test
|
||||
public void threeDimensionalSamplesAlwaysUseTheHorizontalPlane() {
|
||||
InterpolatedNoise noise = new InterpolatedNoise(37L, NoiseType.WHITE, InterpolationMethod.BILINEAR);
|
||||
|
||||
for (double x : new double[]{-13D, 0D, 41D}) {
|
||||
for (double y : new double[]{-89D, 0D, 173D}) {
|
||||
for (double z : new double[]{-37D, 0D, 29D}) {
|
||||
assertEquals(noise.noise(x, z), noise.noise(x, y, z), 0D);
|
||||
}
|
||||
}
|
||||
assertEquals(noise.noise(x, 0D), noise.noise(x), 0D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interpolationSamplesTheNormalizedSourceAndForwardsOctaves() {
|
||||
long seed = 93L;
|
||||
NoiseType type = NoiseType.SIMPLEX;
|
||||
NoiseGenerator source = type.create(seed);
|
||||
((OctaveNoise) source).setOctaves(4);
|
||||
InterpolatedNoise noise = new InterpolatedNoise(seed, type, InterpolationMethod.BILINEAR);
|
||||
noise.setOctaves(4);
|
||||
double scale = type.getCoordinateScale();
|
||||
|
||||
for (int x = -49; x <= 49; x += 7) {
|
||||
for (int z = -51; z <= 51; z += 11) {
|
||||
double expected = IrisInterpolation.getNoise(InterpolationMethod.BILINEAR, x, z, 32D,
|
||||
(sampleX, sampleZ) -> source.noise(sampleX * scale, sampleZ * scale));
|
||||
assertEquals(expected, noise.noise(x, z), 1.0E-12D);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractionalCoordinatesReachEveryInterpolationKernel() {
|
||||
long seed = 109L;
|
||||
NoiseType type = NoiseType.SIMPLEX;
|
||||
NoiseGenerator source = type.create(seed);
|
||||
double scale = type.getCoordinateScale();
|
||||
|
||||
for (InterpolationMethod method : InterpolationMethod.values()) {
|
||||
InterpolatedNoise noise = new InterpolatedNoise(seed, type, method);
|
||||
double expected = IrisInterpolation.getNoise(method, -7.75D, 3.25D, 32D,
|
||||
(x, z) -> source.noise(x * scale, z * scale));
|
||||
double actual = noise.noise(-7.75D, 3.25D);
|
||||
|
||||
assertEquals(method.name(), expected, actual, 0D);
|
||||
assertNotEquals(method.name(), noise.noise(-7D, 3D), actual, 1.0E-9D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noiseWrappersConstrainCubicOvershootWithoutConstrainingTheKernel() {
|
||||
double overshoot = IrisInterpolation.getNoise(InterpolationMethod.BICUBIC, 16D, 16D, 32D,
|
||||
(x, z) -> x >= 0D && x <= 32D ? 1D : 0D);
|
||||
assertEquals(1.25D, overshoot, 0D);
|
||||
|
||||
for (NoiseType type : new NoiseType[]{NoiseType.WHITE, NoiseType.CELLULAR, NoiseType.CLOVER}) {
|
||||
for (InterpolationMethod method : new InterpolationMethod[]{InterpolationMethod.BICUBIC,
|
||||
InterpolationMethod.HERMITE}) {
|
||||
InterpolatedNoise noise = new InterpolatedNoise(173L, type, method);
|
||||
boolean reachesEndpoint = false;
|
||||
for (double x = -256D; x <= 256D; x += 7.375D) {
|
||||
for (double z = -256D; z <= 256D; z += 5.5D) {
|
||||
double value = noise.noise(x, z);
|
||||
assertTrue(type.name() + "/" + method.name(), value >= 0D && value <= 1D);
|
||||
reachesEndpoint |= value == 0D || value == 1D;
|
||||
}
|
||||
}
|
||||
if (type == NoiseType.WHITE) {
|
||||
assertTrue(method.name(), reachesEndpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NoiseOctaveTest {
|
||||
private static final long SEED = 972453L;
|
||||
private static final double X = 37.219D;
|
||||
private static final double Y = -91.713D;
|
||||
private static final double Z = 152.875D;
|
||||
|
||||
@Test
|
||||
public void simplexAndPerlinKeepFundamentalAndReduceDetailAmplitude() {
|
||||
for (boolean simplex : new boolean[]{false, true}) {
|
||||
NoiseGenerator noise = simplex ? new SimplexNoise(SEED) : new PerlinNoise(SEED);
|
||||
((OctaveNoise) noise).setOctaves(3);
|
||||
FastNoiseDouble source = new FastNoiseDouble(new RNG(SEED).lmax());
|
||||
source.setLongerp(FastNoiseDouble.Longerp.Qulongic);
|
||||
for (int dimensions = 1; dimensions <= 3; dimensions++) {
|
||||
double expected = 0D;
|
||||
for (int octave = 0; octave < 3; octave++) {
|
||||
double frequency = 1 << octave;
|
||||
double sample;
|
||||
if (dimensions == 3) {
|
||||
sample = simplex ? source.GetSimplex(X * frequency, Y * frequency, Z * frequency)
|
||||
: source.GetPerlin(X * frequency, Y * frequency, Z * frequency);
|
||||
} else {
|
||||
double second = dimensions == 1 ? 0D : Z * frequency;
|
||||
sample = simplex ? source.GetSimplex(X * frequency, second)
|
||||
: source.GetPerlin(X * frequency, second);
|
||||
}
|
||||
expected += sample / frequency;
|
||||
}
|
||||
expected /= 1.75D;
|
||||
double signed = switch (dimensions) {
|
||||
case 1 -> noise.noiseSigned(X);
|
||||
case 2 -> noise.noiseSigned(X, Z);
|
||||
default -> noise.noiseSigned(X, Y, Z);
|
||||
};
|
||||
double unsigned = switch (dimensions) {
|
||||
case 1 -> noise.noise(X);
|
||||
case 2 -> noise.noise(X, Z);
|
||||
default -> noise.noise(X, Y, Z);
|
||||
};
|
||||
assertEquals(expected, signed, 1E-15D);
|
||||
assertEquals(signed / 2D + 0.5D, unsigned, 0D);
|
||||
}
|
||||
assertEquals(noise.noise(X, 0D), noise.noise(X), 0D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 5000)
|
||||
public void octaveWrappersClampInvalidAndExcessiveCounts() {
|
||||
NoiseGenerator[] generators = {
|
||||
new SimplexNoise(SEED), new PerlinNoise(SEED), new FractalFBMSimplexNoise(SEED),
|
||||
new FractalBillowSimplexNoise(SEED), new FractalBillowPerlinNoise(SEED),
|
||||
new FractalRigidMultiSimplexNoise(SEED), new FractalCubicNoise(SEED)
|
||||
};
|
||||
for (NoiseGenerator noise : generators) {
|
||||
OctaveNoise octaveNoise = (OctaveNoise) noise;
|
||||
octaveNoise.setOctaves(1);
|
||||
double minimum = noise.noise(X, Y, Z);
|
||||
octaveNoise.setOctaves(Integer.MIN_VALUE);
|
||||
assertEquals(minimum, noise.noise(X, Y, Z), 0D);
|
||||
octaveNoise.setOctaves(16);
|
||||
double maximum = noise.noise(X, Y, Z);
|
||||
octaveNoise.setOctaves(Integer.MAX_VALUE);
|
||||
assertEquals(maximum, noise.noise(X, Y, Z), 0D);
|
||||
assertTrue(Double.isFinite(maximum));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cubicFractalUsesRequestedOctaves() {
|
||||
FractalCubicNoise noise = new FractalCubicNoise(SEED);
|
||||
noise.setOctaves(1);
|
||||
double fundamental = noise.noise(X, Y, Z);
|
||||
noise.setOctaves(4);
|
||||
double detailed = noise.noise(X, Y, Z);
|
||||
assertNotEquals(fundamental, detailed, 1E-8D);
|
||||
FastNoiseDouble source = new FastNoiseDouble(new RNG(SEED).lmax());
|
||||
source.setFractalType(FastNoiseDouble.FractalType.Billow);
|
||||
source.setFractalOctaves(4);
|
||||
assertEquals(source.GetCubicFractal(X, Y, Z) / 2D + 0.5D, detailed, 0D);
|
||||
}
|
||||
|
||||
@Test(timeout = 5000)
|
||||
public void rawFractalSamplersBoundOctaveWork() {
|
||||
FastNoiseDouble doubles = new FastNoiseDouble(SEED);
|
||||
FastNoise floats = new FastNoise((int) SEED);
|
||||
doubles.setFractalOctaves(16);
|
||||
floats.SetFractalOctaves(16);
|
||||
double expectedDouble = doubles.GetSimplexFractal(X, Y, Z);
|
||||
float expectedFloat = floats.GetSimplexFractal((float) X, (float) Y, (float) Z);
|
||||
doubles.setFractalOctaves(Long.MAX_VALUE);
|
||||
floats.SetFractalOctaves(Integer.MAX_VALUE);
|
||||
assertEquals(expectedDouble, doubles.GetSimplexFractal(X, Y, Z), 0D);
|
||||
assertEquals(expectedFloat, floats.GetSimplexFractal((float) X, (float) Y, (float) Z), 0D);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class WhiteNoiseTest {
|
||||
@Test
|
||||
public void scatterDoesNotRepeatEvery8192Blocks() {
|
||||
WhiteNoise noise = new WhiteNoise(7153L);
|
||||
for (double x : new double[]{-29999999.25D, -8192D, 0D, 37.5D, 8192D}) {
|
||||
assertNotEquals(noise.noise(x, 73D), noise.noise(x + 8192D, 73D), 0D);
|
||||
assertNotEquals(noise.noise(x, 53D, 73D), noise.noise(x, 53D, 73D + 8192D), 0D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractionalCoordinatesRemainDistinctNearWorldBorder() {
|
||||
WhiteNoise noise = new WhiteNoise(15273L);
|
||||
double x = 29999111.25D;
|
||||
assertNotEquals(noise.noise(x, -x), noise.noise(x + 0.0001D, -x), 0D);
|
||||
assertNotEquals(noise.noise(x, 71.5D, -x), noise.noise(x, 71.5001D, -x), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scatterIsRepeatableAndUsesFullSeed() {
|
||||
WhiteNoise first = new WhiteNoise(725L);
|
||||
WhiteNoise same = new WhiteNoise(725L);
|
||||
WhiteNoise upperBits = new WhiteNoise(725L + (1L << 48));
|
||||
for (int index = 0; index < 512; index++) {
|
||||
double x = index * 1.25D;
|
||||
double value = first.noise(x, 53D, -x);
|
||||
assertEquals(value, same.noise(x, 53D, -x), 0D);
|
||||
assertTrue(value >= 0D && value <= 1D);
|
||||
assertNotEquals(value, upperBits.noise(x, 53D, -x), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user