From a2e4ec7070b2ff38fb990502d11c3c61cd81566a Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Thu, 22 Oct 2026 02:20:35 -0400 Subject: [PATCH] Batched warped lattice noise - Cell lattice passes gather every missing corner sample of the pass and evaluate base, detail and modules through NoiseProgram.fillPoints3D - Warp B samples and single-row warp A samples are evaluated as one point batch per pass; longer warp A runs keep fillColumn3D - Module bands compact their points before the batch call and scatter back in the original accumulation order - Lattice batch buffers and their NoiseScratch live in a per-thread CaveLatticeBatch chain that nested carves acquire separately - Lattice test compares pass-wide point batches against scalar plane density bit for bit --- .../generation/mantle/CaveCarveScratch.java | 22 -- .../generation/mantle/CaveLatticeBatch.java | 81 +++++ .../generation/mantle/IrisCaveCarver3D.java | 314 ++++++++++++------ .../mantle/IrisCaveCarver3DLatticeTest.java | 60 ++-- 4 files changed, 335 insertions(+), 142 deletions(-) create mode 100644 core/src/main/java/art/arcane/iris/generation/mantle/CaveLatticeBatch.java diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java b/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java index 5fd6a127c..ba0e5fd64 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/CaveCarveScratch.java @@ -97,14 +97,6 @@ final class CaveCarveScratch { int adaptiveGeometryStep = -1; int adaptiveGeometryAxisCells = -1; boolean fullWeightsInitialized; - int[] latticeY = new int[0]; - int[] latticeSnapY = new int[0]; - double[] latticeWarpA = new double[0]; - double[] latticeWarpB = new double[0]; - double[] latticeWarpedX = new double[0]; - double[] latticeWarpedY = new double[0]; - double[] latticeWarpedZ = new double[0]; - double[] latticeDensity = new double[0]; void releaseReferences() { Arrays.fill(sectionMatter, null); @@ -112,18 +104,4 @@ final class CaveCarveScratch { Arrays.fill(densityColumns, null); cellLattice.releaseReferences(); } - - void ensureLatticeBatch(int size) { - if (latticeY.length >= size) { - return; - } - latticeY = new int[size]; - latticeSnapY = new int[size]; - latticeWarpA = new double[size]; - latticeWarpB = new double[size]; - latticeWarpedX = new double[size]; - latticeWarpedY = new double[size]; - latticeWarpedZ = new double[size]; - latticeDensity = new double[size]; - } } diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/CaveLatticeBatch.java b/core/src/main/java/art/arcane/iris/generation/mantle/CaveLatticeBatch.java new file mode 100644 index 000000000..0b7a77023 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/generation/mantle/CaveLatticeBatch.java @@ -0,0 +1,81 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.iris.generation.mantle; + +import art.arcane.volmlib.util.noise.program.NoiseScratch; + +final class CaveLatticeBatch { + private static final ThreadLocal CURRENT = ThreadLocal.withInitial(CaveLatticeBatch::new); + + final NoiseScratch noise = new NoiseScratch(); + final int[] cornerStart = new int[CaveDensityLattice.CORNERS + 1]; + final int[] snapStart = new int[CaveDensityLattice.CORNERS + 1]; + int[] y = new int[0]; + int[] snapY = new int[0]; + boolean[] snapMissing = new boolean[0]; + double[] warpA = new double[0]; + double[] warpB = new double[0]; + double[] warpedX = new double[0]; + double[] warpedY = new double[0]; + double[] warpedZ = new double[0]; + double[] density = new double[0]; + double[] values = new double[0]; + double[] pointX = new double[0]; + double[] pointY = new double[0]; + double[] pointZ = new double[0]; + int[] pointIndex = new int[0]; + private CaveLatticeBatch nested; + private boolean active; + + static CaveLatticeBatch acquire() { + CaveLatticeBatch batch = CURRENT.get(); + while (batch.active) { + if (batch.nested == null) { + batch.nested = new CaveLatticeBatch(); + } + batch = batch.nested; + } + batch.active = true; + return batch; + } + + void release() { + active = false; + } + + void ensure(int size) { + if (y.length >= size) { + return; + } + y = new int[size]; + snapY = new int[size]; + snapMissing = new boolean[size]; + warpA = new double[size]; + warpB = new double[size]; + warpedX = new double[size]; + warpedY = new double[size]; + warpedZ = new double[size]; + density = new double[size]; + values = new double[size * 2]; + pointX = new double[size * 2]; + pointY = new double[size * 2]; + pointZ = new double[size * 2]; + pointIndex = new int[size * 2]; + } +} diff --git a/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java b/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java index fa348442e..30a355965 100644 --- a/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java +++ b/core/src/main/java/art/arcane/iris/generation/mantle/IrisCaveCarver3D.java @@ -75,7 +75,6 @@ public class IrisCaveCarver3D { private static final double WARP_B_OFFSET_X = 31.37D; private static final double WARP_B_OFFSET_Y = 17.21D; private static final double WARP_B_OFFSET_Z = 23.91D; - private static final ThreadLocal NOISE_SCRATCH = ThreadLocal.withInitial(NoiseScratch::new); private final Engine engine; private final IrisData data; @@ -1009,7 +1008,7 @@ public class IrisCaveCarver3D { if (cellKernel) { if (classified == null) { - fillCellLattice(scratch, scratch, x0, z0, minY, passMaxY); + fillCellLattice(scratch, x0, z0, minY, passMaxY); } return emitCellPass(chunk, scratch, classified, x0, z0, minY, passMaxY, minSection, maxSection, activeColumnIndices, activeColumnTopY, activeColumnCount, surfaceFluidBoundaries, @@ -1237,16 +1236,24 @@ public class IrisCaveCarver3D { } } - private void fillCellLattice(CaveCarveScratch scratch, CaveCarveScratch plan, int x0, int z0, int fromY, int toY) { - CaveDensityLattice lattice = plan.cellLattice; - scratch.ensureLatticeBatch(toY - fromY + 1); - int[] latticeY = scratch.latticeY; - double[] density = scratch.latticeDensity; + private void fillCellLattice(CaveCarveScratch plan, int x0, int z0, int fromY, int toY) { + CaveLatticeBatch batch = CaveLatticeBatch.acquire(); + try { + fillCellLattice(batch, plan.cellLattice, x0, z0, fromY, toY); + } finally { + batch.release(); + } + } + + private void fillCellLattice(CaveLatticeBatch batch, CaveDensityLattice lattice, int x0, int z0, int fromY, int toY) { + batch.ensure((toY - fromY + 1) * CaveDensityLattice.CORNERS); + int[] latticeY = batch.y; + int[] cornerStart = batch.cornerStart; long reads = 0L; - long samples = 0L; + int count = 0; for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + cornerStart[corner] = count; CaveDensityGrid.Column column = lattice.densityColumns[corner]; - int count = 0; for (int y = fromY; y <= toY; y++) { if (!lattice.needs(corner, y)) { continue; @@ -1259,55 +1266,60 @@ public class IrisCaveCarver3D { lattice.store(corner, y, cached); } } - if (count == 0) { - continue; - } - samples += count; - sampleLatticeColumn(scratch, lattice.warpColumns[corner], x0 + (corner / 3) * ADAPTIVE_DEEP_SAMPLE_STEP, - z0 + (corner % 3) * ADAPTIVE_DEEP_SAMPLE_STEP, count); - for (int index = 0; index < count; index++) { - lattice.store(corner, latticeY[index], density[index]); - if (column != null) { - column.set(latticeY[index], density[index]); + } + cornerStart[CaveDensityLattice.CORNERS] = count; + if (count > 0) { + sampleLatticePoints(batch, lattice, x0, z0, count); + double[] density = batch.density; + for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + CaveDensityGrid.Column column = lattice.densityColumns[corner]; + for (int index = cornerStart[corner]; index < cornerStart[corner + 1]; index++) { + lattice.store(corner, latticeY[index], density[index]); + if (column != null) { + column.set(latticeY[index], density[index]); + } } } } if (GenerationCounter.ENABLED) { GenerationCounter.CAVE_LATTICE_READS.add(reads); - GenerationCounter.CAVE_LATTICE_SAMPLES.add(samples); + GenerationCounter.CAVE_LATTICE_SAMPLES.add(count); } } - private void sampleLatticeColumn(CaveCarveScratch scratch, CaveDensityGrid.Column warpColumn, int x, int z, int count) { - int[] latticeY = scratch.latticeY; - double[] density = scratch.latticeDensity; - if (!hasWarp) { - for (int index = 0; index < count; index++) { - density[index] = sampleDensityNoWarpNoModules(x, latticeY[index], z); + private void sampleLatticePoints(CaveLatticeBatch batch, CaveDensityLattice lattice, int x0, int z0, int count) { + double[] xs = batch.warpedX; + double[] ys = batch.warpedY; + double[] zs = batch.warpedZ; + if (hasWarp) { + resolveLatticeWarp(batch, lattice, x0, z0); + } else { + int[] latticeY = batch.y; + int[] cornerStart = batch.cornerStart; + for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + double x = x0 + (corner / 3) * ADAPTIVE_DEEP_SAMPLE_STEP; + double z = z0 + (corner % 3) * ADAPTIVE_DEEP_SAMPLE_STEP; + for (int index = cornerStart[corner]; index < cornerStart[corner + 1]; index++) { + xs[index] = x; + ys[index] = latticeY[index]; + zs[index] = z; + } } - return; } - resolveLatticeWarp(scratch, warpColumn, x, z, count); - double[] warpedX = scratch.latticeWarpedX; - double[] warpedY = scratch.latticeWarpedY; - double[] warpedZ = scratch.latticeWarpedZ; + NoiseScratch noise = batch.noise; + double[] density = batch.density; + double[] values = batch.values; + baseDensity.fillPoints3D(noise, xs, ys, zs, count, values); for (int index = 0; index < count; index++) { - density[index] = baseDensity.sampleSigned3D(warpedX[index], warpedY[index], warpedZ[index]) * baseWeight; + density[index] = values[index] * baseWeight; } + detailDensity.fillPoints3D(noise, xs, ys, zs, count, values); for (int index = 0; index < count; index++) { - density[index] += detailDensity.sampleSigned3D(warpedX[index], warpedY[index], warpedZ[index]) * detailWeight; + density[index] += values[index] * detailWeight; } - for (CaveFieldModuleState module : modules) { - for (int index = 0; index < count; index++) { - int y = latticeY[index]; - if (y < module.minY || y > module.maxY) { - continue; - } - double moduleDensity = module.density.sampleSigned3D(warpedX[index], warpedY[index], warpedZ[index]) - module.threshold; - if (module.invert) { - moduleDensity = -moduleDensity; - } - density[index] += moduleDensity * module.weight; + if (hasWarp) { + for (CaveFieldModuleState module : modules) { + addLatticeModule(batch, module, count); } } for (int index = 0; index < count; index++) { @@ -1315,78 +1327,186 @@ public class IrisCaveCarver3D { } } - private void resolveLatticeWarp(CaveCarveScratch scratch, CaveDensityGrid.Column warpColumn, int x, int z, int count) { - int[] latticeY = scratch.latticeY; - int[] snapY = scratch.latticeSnapY; - double[] warpA = scratch.latticeWarpA; - double[] warpB = scratch.latticeWarpB; - int snapCount = 0; + private void addLatticeModule(CaveLatticeBatch batch, CaveFieldModuleState module, int count) { + NoiseScratch noise = batch.noise; + int[] latticeY = batch.y; + double[] xs = batch.warpedX; + double[] ys = batch.warpedY; + double[] zs = batch.warpedZ; + double[] density = batch.density; + double[] values = batch.values; + int[] band = batch.pointIndex; + int inBand = 0; for (int index = 0; index < count; index++) { - int snap = snapWarp(latticeY[index]); - if (snapCount == 0 || snapY[snapCount - 1] != snap) { - snapY[snapCount++] = snap; + int y = latticeY[index]; + if (y >= module.minY && y <= module.maxY) { + band[inBand++] = index; } } + if (inBand == 0) { + return; + } + if (inBand == count) { + module.density.fillPoints3D(noise, xs, ys, zs, count, values); + for (int index = 0; index < count; index++) { + double moduleDensity = values[index] - module.threshold; + if (module.invert) { + moduleDensity = -moduleDensity; + } + density[index] += moduleDensity * module.weight; + } + return; + } + double[] bandX = batch.pointX; + double[] bandY = batch.pointY; + double[] bandZ = batch.pointZ; + for (int slot = 0; slot < inBand; slot++) { + int index = band[slot]; + bandX[slot] = xs[index]; + bandY[slot] = ys[index]; + bandZ[slot] = zs[index]; + } + module.density.fillPoints3D(noise, bandX, bandY, bandZ, inBand, values); + for (int slot = 0; slot < inBand; slot++) { + double moduleDensity = values[slot] - module.threshold; + if (module.invert) { + moduleDensity = -moduleDensity; + } + density[band[slot]] += moduleDensity * module.weight; + } + } + + private void resolveLatticeWarp(CaveLatticeBatch batch, CaveDensityLattice lattice, int x0, int z0) { + int[] latticeY = batch.y; + int[] cornerStart = batch.cornerStart; + int[] snapStart = batch.snapStart; + int[] snapY = batch.snapY; + boolean[] snapMissing = batch.snapMissing; + double[] warpA = batch.warpA; + double[] warpB = batch.warpB; + int snapCount = 0; + int pending = 0; long computed = 0L; - int runStart = -1; - for (int snapIndex = 0; snapIndex <= snapCount; snapIndex++) { - boolean missing = false; - if (snapIndex < snapCount) { - int snap = snapY[snapIndex]; - double cachedA = warpColumn == null ? Double.NaN : warpColumn.get(snap); - double cachedB = warpColumn == null ? Double.NaN : warpColumn.get(snap + 1); - missing = Double.isNaN(cachedA) || Double.isNaN(cachedB); - warpA[snapIndex] = cachedA; - warpB[snapIndex] = cachedB; + for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + int first = snapCount; + snapStart[corner] = first; + for (int index = cornerStart[corner]; index < cornerStart[corner + 1]; index++) { + int snap = snapWarp(latticeY[index]); + if (snapCount == first || snapY[snapCount - 1] != snap) { + snapY[snapCount++] = snap; + } } - if (runStart >= 0 && (!missing || snapY[snapIndex] != snapY[snapIndex - 1] + warpResolution)) { - computeLatticeWarpRun(scratch, warpColumn, x, z, runStart, snapIndex); - computed += snapIndex - runStart; - runStart = -1; + if (snapCount == first) { + continue; } - if (missing && runStart < 0) { - runStart = snapIndex; + int x = x0 + (corner / 3) * ADAPTIVE_DEEP_SAMPLE_STEP; + int z = z0 + (corner % 3) * ADAPTIVE_DEEP_SAMPLE_STEP; + CaveDensityGrid.Column warpColumn = lattice.warpColumns[corner]; + int runStart = -1; + for (int snapIndex = first; snapIndex <= snapCount; snapIndex++) { + boolean missing = false; + if (snapIndex < snapCount) { + int snap = snapY[snapIndex]; + double cachedA = warpColumn == null ? Double.NaN : warpColumn.get(snap); + double cachedB = warpColumn == null ? Double.NaN : warpColumn.get(snap + 1); + missing = Double.isNaN(cachedA) || Double.isNaN(cachedB); + snapMissing[snapIndex] = missing; + warpA[snapIndex] = cachedA; + warpB[snapIndex] = cachedB; + } + if (runStart >= 0 && (!missing || snapY[snapIndex] != snapY[snapIndex - 1] + warpResolution)) { + pending = queueLatticeWarpRun(batch, x, z, runStart, snapIndex, pending); + computed += snapIndex - runStart; + runStart = -1; + } + if (missing && runStart < 0) { + runStart = snapIndex; + } } } + snapStart[CaveDensityLattice.CORNERS] = snapCount; + if (pending > 0) { + evaluateLatticeWarpPoints(batch, lattice, pending); + } if (GenerationCounter.ENABLED) { GenerationCounter.CAVE_WARP_SAMPLES.add(computed); } - double[] warpedX = scratch.latticeWarpedX; - double[] warpedY = scratch.latticeWarpedY; - double[] warpedZ = scratch.latticeWarpedZ; - int snapIndex = 0; - for (int index = 0; index < count; index++) { - int y = latticeY[index]; - int snap = snapWarp(y); - while (snapY[snapIndex] != snap) { - snapIndex++; + double[] warpedX = batch.warpedX; + double[] warpedY = batch.warpedY; + double[] warpedZ = batch.warpedZ; + for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + int x = x0 + (corner / 3) * ADAPTIVE_DEEP_SAMPLE_STEP; + int z = z0 + (corner % 3) * ADAPTIVE_DEEP_SAMPLE_STEP; + int snapIndex = snapStart[corner]; + for (int index = cornerStart[corner]; index < cornerStart[corner + 1]; index++) { + int y = latticeY[index]; + int snap = snapWarp(y); + while (snapY[snapIndex] != snap) { + snapIndex++; + } + double a = warpA[snapIndex]; + double b = warpB[snapIndex]; + warpedX[index] = x + (a * warpStrength); + warpedY[index] = y + (b * warpStrength); + warpedZ[index] = z + ((a - b) * 0.5D * warpStrength); } - double a = warpA[snapIndex]; - double b = warpB[snapIndex]; - warpedX[index] = x + (a * warpStrength); - warpedY[index] = y + (b * warpStrength); - warpedZ[index] = z + ((a - b) * 0.5D * warpStrength); } } - private void computeLatticeWarpRun(CaveCarveScratch scratch, CaveDensityGrid.Column warpColumn, int x, int z, int from, int to) { - int[] snapY = scratch.latticeSnapY; - double[] warpA = scratch.latticeWarpA; - double[] warpB = scratch.latticeWarpB; + private int queueLatticeWarpRun(CaveLatticeBatch batch, int x, int z, int from, int to, int pending) { + int[] snapY = batch.snapY; + double[] pointX = batch.pointX; + double[] pointY = batch.pointY; + double[] pointZ = batch.pointZ; + int[] target = batch.pointIndex; int length = to - from; + int queued = pending; if (length == 1) { - warpA[from] = warpDensity.sampleSigned3D(x, snapY[from], z); + pointX[queued] = x; + pointY[queued] = snapY[from]; + pointZ[queued] = z; + target[queued++] = from << 1; } else { - double[] column = scratch.latticeWarpedX; - warpDensity.fillColumn3D(NOISE_SCRATCH.get(), x, z, snapY[from], warpResolution, length, column); - System.arraycopy(column, 0, warpA, from, length); + double[] column = batch.values; + warpDensity.fillColumn3D(batch.noise, x, z, snapY[from], warpResolution, length, column); + System.arraycopy(column, 0, batch.warpA, from, length); } for (int snapIndex = from; snapIndex < to; snapIndex++) { - int snap = snapY[snapIndex]; - warpB[snapIndex] = warpDensity.sampleSigned3D(x + WARP_B_OFFSET_X, snap - WARP_B_OFFSET_Y, z + WARP_B_OFFSET_Z); - if (warpColumn != null) { - warpColumn.set(snap, warpA[snapIndex]); - warpColumn.set(snap + 1, warpB[snapIndex]); + pointX[queued] = x + WARP_B_OFFSET_X; + pointY[queued] = snapY[snapIndex] - WARP_B_OFFSET_Y; + pointZ[queued] = z + WARP_B_OFFSET_Z; + target[queued++] = (snapIndex << 1) | 1; + } + return queued; + } + + private void evaluateLatticeWarpPoints(CaveLatticeBatch batch, CaveDensityLattice lattice, int pending) { + double[] values = batch.values; + warpDensity.fillPoints3D(batch.noise, batch.pointX, batch.pointY, batch.pointZ, pending, values); + int[] target = batch.pointIndex; + double[] warpA = batch.warpA; + double[] warpB = batch.warpB; + for (int slot = 0; slot < pending; slot++) { + int code = target[slot]; + if ((code & 1) == 0) { + warpA[code >> 1] = values[slot]; + } else { + warpB[code >> 1] = values[slot]; + } + } + int[] snapStart = batch.snapStart; + int[] snapY = batch.snapY; + boolean[] snapMissing = batch.snapMissing; + for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + CaveDensityGrid.Column warpColumn = lattice.warpColumns[corner]; + if (warpColumn == null) { + continue; + } + for (int snapIndex = snapStart[corner]; snapIndex < snapStart[corner + 1]; snapIndex++) { + if (snapMissing[snapIndex]) { + warpColumn.set(snapY[snapIndex], warpA[snapIndex]); + warpColumn.set(snapY[snapIndex] + 1, warpB[snapIndex]); + } } } } @@ -1470,7 +1590,7 @@ public class IrisCaveCarver3D { private void classifyCellRows(DensityPass pass, int minimumY, int maximumY, byte[] classified) { CaveCarveScratch scratch = new CaveCarveScratch(); CaveCarveScratch plan = pass.sourceScratch(); - fillCellLattice(scratch, plan, pass.x(), pass.z(), minimumY, maximumY); + fillCellLattice(plan, pass.x(), pass.z(), minimumY, maximumY); long[] rowBits = scratch.rowCarveBits; for (int y = minimumY; y <= maximumY; y++) { int fadeIndex = y - pass.minimumY(); diff --git a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DLatticeTest.java b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DLatticeTest.java index db789b87f..2f0d6aab3 100644 --- a/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DLatticeTest.java +++ b/core/src/test/java/art/arcane/iris/generation/mantle/IrisCaveCarver3DLatticeTest.java @@ -32,7 +32,7 @@ public class IrisCaveCarver3DLatticeTest { private static final int WORLD_HEIGHT = 384; @Test - public void batchedLatticeColumnsMatchScalarPlaneDensityBitForBit() throws Exception { + public void batchedLatticePointsMatchScalarPlaneDensityBitForBit() throws Exception { Engine engine = createEngine(); CaveDensityGrid grid = new CaveDensityGrid(256); IrisCaveProfile[] profiles = { @@ -43,9 +43,9 @@ public class IrisCaveCarver3DLatticeTest { module(NoiseStyle.VASCULAR_THIN, 1.28D, 8D, 220D, false)), profile(NoiseStyle.SIMPLEX, 0.5D, 0D, NoiseStyle.FRACTAL_BILLOW_SIMPLEX, 0.656D) }; - Method sampleLatticeColumn = IrisCaveCarver3D.class.getDeclaredMethod("sampleLatticeColumn", - CaveCarveScratch.class, CaveDensityGrid.Column.class, int.class, int.class, int.class); - sampleLatticeColumn.setAccessible(true); + Method sampleLatticePoints = IrisCaveCarver3D.class.getDeclaredMethod("sampleLatticePoints", + CaveLatticeBatch.class, CaveDensityLattice.class, int.class, int.class, int.class); + sampleLatticePoints.setAccessible(true); Method samplePlaneDensity = IrisCaveCarver3D.class.getDeclaredMethod("samplePlaneDensity", CaveCarveScratch.class, int.class, int.class, int.class); samplePlaneDensity.setAccessible(true); @@ -55,23 +55,30 @@ public class IrisCaveCarver3DLatticeTest { for (IrisCaveProfile profile : profiles) { IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile, grid); CaveDensityGrid.Owner warpOwner = (CaveDensityGrid.Owner) warpOwnerField.get(carver); - CaveCarveScratch batch = new CaveCarveScratch(); + CaveLatticeBatch batch = new CaveLatticeBatch(); + CaveDensityLattice lattice = new CaveDensityLattice(); CaveCarveScratch reference = new CaveCarveScratch(); - for (int trial = 0; trial < 48; trial++) { + for (int trial = 0; trial < 24; trial++) { int latticeX = random.nextInt(4001) - 2000; int latticeZ = random.nextInt(4001) - 2000; - int x = latticeX << CaveDensityGrid.LATTICE_SHIFT; - int z = latticeZ << CaveDensityGrid.LATTICE_SHIFT; + int x0 = latticeX << CaveDensityGrid.LATTICE_SHIFT; + int z0 = latticeZ << CaveDensityGrid.LATTICE_SHIFT; + for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + lattice.warpColumns[corner] = (trial + corner) % 3 == 0 ? null + : grid.warpColumn(warpOwner, WORLD_HEIGHT + 1, latticeX + corner / 3, latticeZ + corner % 3); + } int count = selectRows(random, batch); - CaveDensityGrid.Column warpColumn = trial % 3 == 0 ? null - : grid.warpColumn(warpOwner, WORLD_HEIGHT + 1, latticeX, latticeZ); for (int pass = 0; pass < 2; pass++) { - sampleLatticeColumn.invoke(carver, batch, warpColumn, x, z, count); - for (int index = 0; index < count; index++) { - int y = batch.latticeY[index]; - double expected = (double) samplePlaneDensity.invoke(carver, reference, x, y, z); - assertEquals("x=" + x + " y=" + y + " z=" + z + " pass=" + pass, - Double.doubleToRawLongBits(expected), Double.doubleToRawLongBits(batch.latticeDensity[index])); + sampleLatticePoints.invoke(carver, batch, lattice, x0, z0, count); + for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + int x = x0 + (corner / 3) * 8; + int z = z0 + (corner % 3) * 8; + for (int index = batch.cornerStart[corner]; index < batch.cornerStart[corner + 1]; index++) { + int y = batch.y[index]; + double expected = (double) samplePlaneDensity.invoke(carver, reference, x, y, z); + assertEquals("x=" + x + " y=" + y + " z=" + z + " pass=" + pass, + Double.doubleToRawLongBits(expected), Double.doubleToRawLongBits(batch.density[index])); + } } } } @@ -107,16 +114,23 @@ public class IrisCaveCarver3DLatticeTest { assertNotSame(warpOwnerField.get(first), warpOwnerField.get(otherWarp)); } - private static int selectRows(Random random, CaveCarveScratch scratch) { - int start = random.nextInt(40); - int end = start + 20 + random.nextInt(WORLD_HEIGHT - start - 20); - scratch.ensureLatticeBatch(end - start + 1); + private static int selectRows(Random random, CaveLatticeBatch batch) { + batch.ensure(WORLD_HEIGHT * CaveDensityLattice.CORNERS); int count = 0; - for (int y = start; y <= end; y++) { - if (random.nextInt(5) != 0) { - scratch.latticeY[count++] = y; + for (int corner = 0; corner < CaveDensityLattice.CORNERS; corner++) { + batch.cornerStart[corner] = count; + if (random.nextInt(4) == 0) { + continue; + } + int start = random.nextInt(40); + int end = start + random.nextInt(WORLD_HEIGHT - start); + for (int y = start; y <= end; y++) { + if (random.nextInt(5) != 0) { + batch.y[count++] = y; + } } } + batch.cornerStart[CaveDensityLattice.CORNERS] = count; return count; }