/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.Lists * it.unimi.dsi.fastutil.longs.Long2IntMap * it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap * org.jspecify.annotations.Nullable */ package net.minecraft.world.level.levelgen; import com.google.common.collect.Lists; import it.unimi.dsi.fastutil.longs.Long2IntMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import net.minecraft.core.QuartPos; import net.minecraft.core.SectionPos; import net.minecraft.server.level.ColumnPos; import net.minecraft.util.KeyDispatchDataCodec; import net.minecraft.util.Mth; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.biome.Climate; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.levelgen.Aquifer; import net.minecraft.world.level.levelgen.DensityFunction; import net.minecraft.world.level.levelgen.DensityFunctions; import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; import net.minecraft.world.level.levelgen.NoiseRouter; import net.minecraft.world.level.levelgen.NoiseSettings; import net.minecraft.world.level.levelgen.OreVeinifier; import net.minecraft.world.level.levelgen.RandomState; import net.minecraft.world.level.levelgen.blending.Blender; import net.minecraft.world.level.levelgen.material.MaterialRuleList; import org.jspecify.annotations.Nullable; public class NoiseChunk implements DensityFunction.FunctionContext, DensityFunction.ContextProvider { private final NoiseSettings noiseSettings; private final int cellCountXZ; private final int cellCountY; private final int cellNoiseMinY; private final int firstCellX; private final int firstCellZ; private final int firstNoiseX; private final int firstNoiseZ; private final List interpolators; private final List cellCaches; private final Map wrapped = new HashMap(); private final Long2IntMap preliminarySurfaceLevelCache = new Long2IntOpenHashMap(); private final Aquifer aquifer; private final DensityFunction preliminarySurfaceLevel; private final BlockStateFiller blockStateRule; private final Blender blender; private final FlatCache blendAlpha; private final FlatCache blendOffset; private final DensityFunctions.BeardifierOrMarker beardifier; private long lastBlendingDataPos = ChunkPos.INVALID_CHUNK_POS; private Blender.BlendingOutput lastBlendingOutput = new Blender.BlendingOutput(1.0, 0.0); private final int noiseSizeXZ; private final int cellWidth; private final int cellHeight; private boolean interpolating; private boolean fillingCell; private int cellStartBlockX; private int cellStartBlockY; private int cellStartBlockZ; private int inCellX; private int inCellY; private int inCellZ; private long interpolationCounter; private long arrayInterpolationCounter; private int arrayIndex; private final DensityFunction.ContextProvider sliceFillingContextProvider = new DensityFunction.ContextProvider(){ @Override public DensityFunction.FunctionContext forIndex(int cellYIndex) { NoiseChunk.this.cellStartBlockY = (cellYIndex + NoiseChunk.this.cellNoiseMinY) * NoiseChunk.this.cellHeight; ++NoiseChunk.this.interpolationCounter; NoiseChunk.this.inCellY = 0; NoiseChunk.this.arrayIndex = cellYIndex; return NoiseChunk.this; } @Override public void fillAllDirectly(double[] output, DensityFunction function) { for (int cellYIndex = 0; cellYIndex < NoiseChunk.this.cellCountY + 1; ++cellYIndex) { NoiseChunk.this.cellStartBlockY = (cellYIndex + NoiseChunk.this.cellNoiseMinY) * NoiseChunk.this.cellHeight; ++NoiseChunk.this.interpolationCounter; NoiseChunk.this.inCellY = 0; NoiseChunk.this.arrayIndex = cellYIndex; output[cellYIndex] = function.compute(NoiseChunk.this); } } }; public static NoiseChunk forChunk(ChunkAccess chunk, RandomState randomState, DensityFunctions.BeardifierOrMarker beardifier, NoiseGeneratorSettings settings, Aquifer.FluidPicker globalFluidPicker, Blender blender) { NoiseSettings noiseSettings = settings.noiseSettings().clampToHeightAccessor(chunk); ChunkPos pos = chunk.getPos(); int cellCountXZ = 16 / noiseSettings.getCellWidth(); return new NoiseChunk(cellCountXZ, randomState, pos.getMinBlockX(), pos.getMinBlockZ(), noiseSettings, beardifier, settings, globalFluidPicker, blender); } public NoiseChunk(int cellCountXZ, RandomState randomState, int chunkMinBlockX, int chunkMinBlockZ, NoiseSettings noiseSettings, DensityFunctions.BeardifierOrMarker beardifier, NoiseGeneratorSettings settings, Aquifer.FluidPicker globalFluidPicker, Blender blender) { this.noiseSettings = noiseSettings; this.cellWidth = noiseSettings.getCellWidth(); this.cellHeight = noiseSettings.getCellHeight(); this.cellCountXZ = cellCountXZ; this.cellCountY = Mth.floorDiv(noiseSettings.height(), this.cellHeight); this.cellNoiseMinY = Mth.floorDiv(noiseSettings.minY(), this.cellHeight); this.firstCellX = Math.floorDiv(chunkMinBlockX, this.cellWidth); this.firstCellZ = Math.floorDiv(chunkMinBlockZ, this.cellWidth); this.interpolators = Lists.newArrayList(); this.cellCaches = Lists.newArrayList(); this.firstNoiseX = QuartPos.fromBlock(chunkMinBlockX); this.firstNoiseZ = QuartPos.fromBlock(chunkMinBlockZ); this.noiseSizeXZ = QuartPos.fromBlock(cellCountXZ * this.cellWidth); this.blender = blender; this.beardifier = beardifier; this.blendAlpha = new FlatCache(new BlendAlpha(), false); this.blendOffset = new FlatCache(new BlendOffset(), false); if (!blender.isEmpty()) { for (int x = 0; x <= this.noiseSizeXZ; ++x) { int quartX = this.firstNoiseX + x; int blockX = QuartPos.toBlock(quartX); for (int z = 0; z <= this.noiseSizeXZ; ++z) { int quartZ = this.firstNoiseZ + z; int blockZ = QuartPos.toBlock(quartZ); Blender.BlendingOutput blendingOutput = blender.blendOffsetAndFactor(blockX, blockZ); this.blendAlpha.values[x + z * this.blendAlpha.sizeXZ] = blendingOutput.alpha(); this.blendOffset.values[x + z * this.blendOffset.sizeXZ] = blendingOutput.blendingOffset(); } } } else { Arrays.fill(this.blendAlpha.values, 1.0); Arrays.fill(this.blendOffset.values, 0.0); } NoiseRouter router = randomState.router(); NoiseRouter wrappedRouter = router.mapAll(this::wrap); this.preliminarySurfaceLevel = wrappedRouter.preliminarySurfaceLevel(); if (!settings.isAquifersEnabled()) { this.aquifer = Aquifer.createDisabled(globalFluidPicker); } else { int chunkX = SectionPos.blockToSectionCoord(chunkMinBlockX); int chunkZ = SectionPos.blockToSectionCoord(chunkMinBlockZ); this.aquifer = Aquifer.create(this, new ChunkPos(chunkX, chunkZ), wrappedRouter, randomState.aquiferRandom(), noiseSettings.minY(), noiseSettings.height(), globalFluidPicker); } ArrayList builder = new ArrayList(); DensityFunction fullNoiseValue = DensityFunctions.cacheAllInCell(DensityFunctions.add(wrappedRouter.finalDensity(), DensityFunctions.BeardifierMarker.INSTANCE)).mapAll(this::wrap); builder.add(context -> this.aquifer.computeSubstance(context, fullNoiseValue.compute(context))); if (settings.oreVeinsEnabled()) { builder.add(OreVeinifier.create(wrappedRouter.veinToggle(), wrappedRouter.veinRidged(), wrappedRouter.veinGap(), randomState.oreRandom())); } this.blockStateRule = new MaterialRuleList(builder.toArray(new BlockStateFiller[0])); } protected Climate.Sampler cachedClimateSampler(NoiseRouter noises, List spawnTarget) { return new Climate.Sampler(noises.temperature().mapAll(this::wrap), noises.vegetation().mapAll(this::wrap), noises.continents().mapAll(this::wrap), noises.erosion().mapAll(this::wrap), noises.depth().mapAll(this::wrap), noises.ridges().mapAll(this::wrap), spawnTarget); } protected @Nullable BlockState getInterpolatedState() { return this.blockStateRule.calculate(this); } @Override public int blockX() { return this.cellStartBlockX + this.inCellX; } @Override public int blockY() { return this.cellStartBlockY + this.inCellY; } @Override public int blockZ() { return this.cellStartBlockZ + this.inCellZ; } public int maxPreliminarySurfaceLevel(int minBlockX, int minBlockZ, int maxBlockX, int maxBlockZ) { int maxY = Integer.MIN_VALUE; for (int blockZ = minBlockZ; blockZ <= maxBlockZ; blockZ += 4) { for (int blockX = minBlockX; blockX <= maxBlockX; blockX += 4) { int surfaceLevel = this.preliminarySurfaceLevel(blockX, blockZ); if (surfaceLevel <= maxY) continue; maxY = surfaceLevel; } } return maxY; } public int preliminarySurfaceLevel(int sampleX, int sampleZ) { int quantizedX = QuartPos.toBlock(QuartPos.fromBlock(sampleX)); int quantizedZ = QuartPos.toBlock(QuartPos.fromBlock(sampleZ)); return this.preliminarySurfaceLevelCache.computeIfAbsent(ColumnPos.asLong(quantizedX, quantizedZ), this::computePreliminarySurfaceLevel); } private int computePreliminarySurfaceLevel(long key) { int blockX = ColumnPos.getX(key); int blockZ = ColumnPos.getZ(key); return Mth.floor(this.preliminarySurfaceLevel.compute(new DensityFunction.SinglePointContext(blockX, 0, blockZ))); } @Override public Blender getBlender() { return this.blender; } private void fillSlice(boolean slice0, int cellX) { this.cellStartBlockX = cellX * this.cellWidth; this.inCellX = 0; for (int cellZIndex = 0; cellZIndex < this.cellCountXZ + 1; ++cellZIndex) { int cellZ = this.firstCellZ + cellZIndex; this.cellStartBlockZ = cellZ * this.cellWidth; this.inCellZ = 0; ++this.arrayInterpolationCounter; for (NoiseInterpolator noiseInterpolator : this.interpolators) { double[] slice = (slice0 ? noiseInterpolator.slice0 : noiseInterpolator.slice1)[cellZIndex]; noiseInterpolator.fillArray(slice, this.sliceFillingContextProvider); } } ++this.arrayInterpolationCounter; } public void initializeForFirstCellX() { if (this.interpolating) { throw new IllegalStateException("Staring interpolation twice"); } this.interpolating = true; this.interpolationCounter = 0L; this.fillSlice(true, this.firstCellX); } public void advanceCellX(int cellXIndex) { this.fillSlice(false, this.firstCellX + cellXIndex + 1); this.cellStartBlockX = (this.firstCellX + cellXIndex) * this.cellWidth; } @Override public NoiseChunk forIndex(int cellIndex) { int zInCell = Math.floorMod(cellIndex, this.cellWidth); int xyIndex = Math.floorDiv(cellIndex, this.cellWidth); int xInCell = Math.floorMod(xyIndex, this.cellWidth); int yInCell = this.cellHeight - 1 - Math.floorDiv(xyIndex, this.cellWidth); this.inCellX = xInCell; this.inCellY = yInCell; this.inCellZ = zInCell; this.arrayIndex = cellIndex; return this; } @Override public void fillAllDirectly(double[] output, DensityFunction function) { this.arrayIndex = 0; for (int yInCell = this.cellHeight - 1; yInCell >= 0; --yInCell) { this.inCellY = yInCell; for (int xInCell = 0; xInCell < this.cellWidth; ++xInCell) { this.inCellX = xInCell; int zInCell = 0; while (zInCell < this.cellWidth) { this.inCellZ = zInCell++; output[this.arrayIndex++] = function.compute(this); } } } } public void selectCellYZ(int cellYIndex, int cellZIndex) { for (NoiseInterpolator i : this.interpolators) { i.selectCellYZ(cellYIndex, cellZIndex); } this.fillingCell = true; this.cellStartBlockY = (cellYIndex + this.cellNoiseMinY) * this.cellHeight; this.cellStartBlockZ = (this.firstCellZ + cellZIndex) * this.cellWidth; ++this.arrayInterpolationCounter; for (CacheAllInCell cellCache : this.cellCaches) { cellCache.noiseFiller.fillArray(cellCache.values, this); } ++this.arrayInterpolationCounter; this.fillingCell = false; } public void updateForY(int posY, double factorY) { this.inCellY = posY - this.cellStartBlockY; for (NoiseInterpolator i : this.interpolators) { i.updateForY(factorY); } } public void updateForX(int posX, double factorX) { this.inCellX = posX - this.cellStartBlockX; for (NoiseInterpolator i : this.interpolators) { i.updateForX(factorX); } } public void updateForZ(int posZ, double factorZ) { this.inCellZ = posZ - this.cellStartBlockZ; ++this.interpolationCounter; for (NoiseInterpolator i : this.interpolators) { i.updateForZ(factorZ); } } public void stopInterpolation() { if (!this.interpolating) { throw new IllegalStateException("Staring interpolation twice"); } this.interpolating = false; } public void swapSlices() { this.interpolators.forEach(NoiseInterpolator::swapSlices); } public Aquifer aquifer() { return this.aquifer; } protected int cellWidth() { return this.cellWidth; } protected int cellHeight() { return this.cellHeight; } private Blender.BlendingOutput getOrComputeBlendingOutput(int blockX, int blockZ) { Blender.BlendingOutput output; long pos2D = ChunkPos.asLong(blockX, blockZ); if (this.lastBlendingDataPos == pos2D) { return this.lastBlendingOutput; } this.lastBlendingDataPos = pos2D; this.lastBlendingOutput = output = this.blender.blendOffsetAndFactor(blockX, blockZ); return output; } protected DensityFunction wrap(DensityFunction function) { return this.wrapped.computeIfAbsent(function, this::wrapNew); } private DensityFunction wrapNew(DensityFunction function) { if (function instanceof DensityFunctions.Marker) { DensityFunctions.Marker marker = (DensityFunctions.Marker)function; return switch (marker.type()) { default -> throw new MatchException(null, null); case DensityFunctions.Marker.Type.Interpolated -> new NoiseInterpolator(marker.wrapped()); case DensityFunctions.Marker.Type.FlatCache -> new FlatCache(marker.wrapped(), true); case DensityFunctions.Marker.Type.Cache2D -> new Cache2D(marker.wrapped()); case DensityFunctions.Marker.Type.CacheOnce -> new CacheOnce(marker.wrapped()); case DensityFunctions.Marker.Type.CacheAllInCell -> new CacheAllInCell(marker.wrapped()); }; } if (this.blender != Blender.empty()) { if (function == DensityFunctions.BlendAlpha.INSTANCE) { return this.blendAlpha; } if (function == DensityFunctions.BlendOffset.INSTANCE) { return this.blendOffset; } } if (function == DensityFunctions.BeardifierMarker.INSTANCE) { return this.beardifier; } if (function instanceof DensityFunctions.HolderHolder) { DensityFunctions.HolderHolder holder = (DensityFunctions.HolderHolder)function; return holder.function().value(); } return function; } private class FlatCache implements NoiseChunkDensityFunction, DensityFunctions.MarkerOrMarked { private final DensityFunction noiseFiller; private final double[] values; private final int sizeXZ; private FlatCache(DensityFunction noiseFiller, boolean fill) { this.noiseFiller = noiseFiller; this.sizeXZ = NoiseChunk.this.noiseSizeXZ + 1; this.values = new double[this.sizeXZ * this.sizeXZ]; if (fill) { for (int x = 0; x <= NoiseChunk.this.noiseSizeXZ; ++x) { int quartX = NoiseChunk.this.firstNoiseX + x; int blockX = QuartPos.toBlock(quartX); for (int z = 0; z <= NoiseChunk.this.noiseSizeXZ; ++z) { int quartZ = NoiseChunk.this.firstNoiseZ + z; int blockZ = QuartPos.toBlock(quartZ); this.values[x + z * this.sizeXZ] = noiseFiller.compute(new DensityFunction.SinglePointContext(blockX, 0, blockZ)); } } } } @Override public double compute(DensityFunction.FunctionContext context) { int quartX = QuartPos.fromBlock(context.blockX()); int quartZ = QuartPos.fromBlock(context.blockZ()); int x = quartX - NoiseChunk.this.firstNoiseX; int z = quartZ - NoiseChunk.this.firstNoiseZ; if (x >= 0 && z >= 0 && x < this.sizeXZ && z < this.sizeXZ) { return this.values[x + z * this.sizeXZ]; } return this.noiseFiller.compute(context); } @Override public void fillArray(double[] output, DensityFunction.ContextProvider contextProvider) { contextProvider.fillAllDirectly(output, this); } @Override public DensityFunction wrapped() { return this.noiseFiller; } @Override public DensityFunctions.Marker.Type type() { return DensityFunctions.Marker.Type.FlatCache; } } private class BlendAlpha implements NoiseChunkDensityFunction { private BlendAlpha() { } @Override public DensityFunction wrapped() { return DensityFunctions.BlendAlpha.INSTANCE; } @Override public DensityFunction mapAll(DensityFunction.Visitor visitor) { return this.wrapped().mapAll(visitor); } @Override public double compute(DensityFunction.FunctionContext context) { return NoiseChunk.this.getOrComputeBlendingOutput(context.blockX(), context.blockZ()).alpha(); } @Override public void fillArray(double[] output, DensityFunction.ContextProvider contextProvider) { contextProvider.fillAllDirectly(output, this); } @Override public double minValue() { return 0.0; } @Override public double maxValue() { return 1.0; } @Override public KeyDispatchDataCodec codec() { return DensityFunctions.BlendAlpha.CODEC; } } private class BlendOffset implements NoiseChunkDensityFunction { private BlendOffset() { } @Override public DensityFunction wrapped() { return DensityFunctions.BlendOffset.INSTANCE; } @Override public DensityFunction mapAll(DensityFunction.Visitor visitor) { return this.wrapped().mapAll(visitor); } @Override public double compute(DensityFunction.FunctionContext context) { return NoiseChunk.this.getOrComputeBlendingOutput(context.blockX(), context.blockZ()).blendingOffset(); } @Override public void fillArray(double[] output, DensityFunction.ContextProvider contextProvider) { contextProvider.fillAllDirectly(output, this); } @Override public double minValue() { return Double.NEGATIVE_INFINITY; } @Override public double maxValue() { return Double.POSITIVE_INFINITY; } @Override public KeyDispatchDataCodec codec() { return DensityFunctions.BlendOffset.CODEC; } } @FunctionalInterface public static interface BlockStateFiller { public @Nullable BlockState calculate(DensityFunction.FunctionContext var1); } public class NoiseInterpolator implements NoiseChunkDensityFunction, DensityFunctions.MarkerOrMarked { private double[][] slice0; private double[][] slice1; private final DensityFunction noiseFiller; private double noise000; private double noise001; private double noise100; private double noise101; private double noise010; private double noise011; private double noise110; private double noise111; private double valueXZ00; private double valueXZ10; private double valueXZ01; private double valueXZ11; private double valueZ0; private double valueZ1; private double value; private NoiseInterpolator(DensityFunction noiseFiller) { this.noiseFiller = noiseFiller; this.slice0 = this.allocateSlice(NoiseChunk.this.cellCountY, NoiseChunk.this.cellCountXZ); this.slice1 = this.allocateSlice(NoiseChunk.this.cellCountY, NoiseChunk.this.cellCountXZ); NoiseChunk.this.interpolators.add(this); } private double[][] allocateSlice(int cellCountY, int cellCountZ) { int sizeZ = cellCountZ + 1; int sizeY = cellCountY + 1; double[][] result = new double[sizeZ][sizeY]; for (int cellZIndex = 0; cellZIndex < sizeZ; ++cellZIndex) { result[cellZIndex] = new double[sizeY]; } return result; } private void selectCellYZ(int cellYIndex, int cellZIndex) { this.noise000 = this.slice0[cellZIndex][cellYIndex]; this.noise001 = this.slice0[cellZIndex + 1][cellYIndex]; this.noise100 = this.slice1[cellZIndex][cellYIndex]; this.noise101 = this.slice1[cellZIndex + 1][cellYIndex]; this.noise010 = this.slice0[cellZIndex][cellYIndex + 1]; this.noise011 = this.slice0[cellZIndex + 1][cellYIndex + 1]; this.noise110 = this.slice1[cellZIndex][cellYIndex + 1]; this.noise111 = this.slice1[cellZIndex + 1][cellYIndex + 1]; } private void updateForY(double factorY) { this.valueXZ00 = Mth.lerp(factorY, this.noise000, this.noise010); this.valueXZ10 = Mth.lerp(factorY, this.noise100, this.noise110); this.valueXZ01 = Mth.lerp(factorY, this.noise001, this.noise011); this.valueXZ11 = Mth.lerp(factorY, this.noise101, this.noise111); } private void updateForX(double factorX) { this.valueZ0 = Mth.lerp(factorX, this.valueXZ00, this.valueXZ10); this.valueZ1 = Mth.lerp(factorX, this.valueXZ01, this.valueXZ11); } private void updateForZ(double factorZ) { this.value = Mth.lerp(factorZ, this.valueZ0, this.valueZ1); } @Override public double compute(DensityFunction.FunctionContext context) { if (context != NoiseChunk.this) { return this.noiseFiller.compute(context); } if (!NoiseChunk.this.interpolating) { throw new IllegalStateException("Trying to sample interpolator outside the interpolation loop"); } if (NoiseChunk.this.fillingCell) { return Mth.lerp3((double)NoiseChunk.this.inCellX / (double)NoiseChunk.this.cellWidth, (double)NoiseChunk.this.inCellY / (double)NoiseChunk.this.cellHeight, (double)NoiseChunk.this.inCellZ / (double)NoiseChunk.this.cellWidth, this.noise000, this.noise100, this.noise010, this.noise110, this.noise001, this.noise101, this.noise011, this.noise111); } return this.value; } @Override public void fillArray(double[] output, DensityFunction.ContextProvider contextProvider) { if (NoiseChunk.this.fillingCell) { contextProvider.fillAllDirectly(output, this); return; } this.wrapped().fillArray(output, contextProvider); } @Override public DensityFunction wrapped() { return this.noiseFiller; } private void swapSlices() { double[][] tmp = this.slice0; this.slice0 = this.slice1; this.slice1 = tmp; } @Override public DensityFunctions.Marker.Type type() { return DensityFunctions.Marker.Type.Interpolated; } } private class CacheAllInCell implements NoiseChunkDensityFunction, DensityFunctions.MarkerOrMarked { private final DensityFunction noiseFiller; private final double[] values; private CacheAllInCell(DensityFunction noiseFiller) { this.noiseFiller = noiseFiller; this.values = new double[NoiseChunk.this.cellWidth * NoiseChunk.this.cellWidth * NoiseChunk.this.cellHeight]; NoiseChunk.this.cellCaches.add(this); } @Override public double compute(DensityFunction.FunctionContext context) { if (context != NoiseChunk.this) { return this.noiseFiller.compute(context); } if (!NoiseChunk.this.interpolating) { throw new IllegalStateException("Trying to sample interpolator outside the interpolation loop"); } int x = NoiseChunk.this.inCellX; int y = NoiseChunk.this.inCellY; int z = NoiseChunk.this.inCellZ; if (x >= 0 && y >= 0 && z >= 0 && x < NoiseChunk.this.cellWidth && y < NoiseChunk.this.cellHeight && z < NoiseChunk.this.cellWidth) { return this.values[((NoiseChunk.this.cellHeight - 1 - y) * NoiseChunk.this.cellWidth + x) * NoiseChunk.this.cellWidth + z]; } return this.noiseFiller.compute(context); } @Override public void fillArray(double[] output, DensityFunction.ContextProvider contextProvider) { contextProvider.fillAllDirectly(output, this); } @Override public DensityFunction wrapped() { return this.noiseFiller; } @Override public DensityFunctions.Marker.Type type() { return DensityFunctions.Marker.Type.CacheAllInCell; } } private static class Cache2D implements NoiseChunkDensityFunction, DensityFunctions.MarkerOrMarked { private final DensityFunction function; private long lastPos2D = ChunkPos.INVALID_CHUNK_POS; private double lastValue; private Cache2D(DensityFunction function) { this.function = function; } @Override public double compute(DensityFunction.FunctionContext context) { double value; int blockZ; int blockX = context.blockX(); long pos2D = ChunkPos.asLong(blockX, blockZ = context.blockZ()); if (this.lastPos2D == pos2D) { return this.lastValue; } this.lastPos2D = pos2D; this.lastValue = value = this.function.compute(context); return value; } @Override public void fillArray(double[] output, DensityFunction.ContextProvider contextProvider) { this.function.fillArray(output, contextProvider); } @Override public DensityFunction wrapped() { return this.function; } @Override public DensityFunctions.Marker.Type type() { return DensityFunctions.Marker.Type.Cache2D; } } private class CacheOnce implements NoiseChunkDensityFunction, DensityFunctions.MarkerOrMarked { private final DensityFunction function; private long lastCounter; private long lastArrayCounter; private double lastValue; private double @Nullable [] lastArray; private CacheOnce(DensityFunction function) { this.function = function; } @Override public double compute(DensityFunction.FunctionContext context) { double value; if (context != NoiseChunk.this) { return this.function.compute(context); } if (this.lastArray != null && this.lastArrayCounter == NoiseChunk.this.arrayInterpolationCounter) { return this.lastArray[NoiseChunk.this.arrayIndex]; } if (this.lastCounter == NoiseChunk.this.interpolationCounter) { return this.lastValue; } this.lastCounter = NoiseChunk.this.interpolationCounter; this.lastValue = value = this.function.compute(context); return value; } @Override public void fillArray(double[] output, DensityFunction.ContextProvider contextProvider) { if (this.lastArray != null && this.lastArrayCounter == NoiseChunk.this.arrayInterpolationCounter) { System.arraycopy(this.lastArray, 0, output, 0, output.length); return; } this.wrapped().fillArray(output, contextProvider); if (this.lastArray != null && this.lastArray.length == output.length) { System.arraycopy(output, 0, this.lastArray, 0, output.length); } else { this.lastArray = (double[])output.clone(); } this.lastArrayCounter = NoiseChunk.this.arrayInterpolationCounter; } @Override public DensityFunction wrapped() { return this.function; } @Override public DensityFunctions.Marker.Type type() { return DensityFunctions.Marker.Type.CacheOnce; } } private static interface NoiseChunkDensityFunction extends DensityFunction { public DensityFunction wrapped(); @Override default public double minValue() { return this.wrapped().minValue(); } @Override default public double maxValue() { return this.wrapped().maxValue(); } } }