387 lines
14 KiB
Java
387 lines
14 KiB
Java
package net.minecraft.world.level.levelgen.blending;
|
|
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.ImmutableMap.Builder;
|
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.Direction8;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.QuartPos;
|
|
import net.minecraft.data.worldgen.NoiseData;
|
|
import net.minecraft.server.level.WorldGenRegion;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
import net.minecraft.world.level.WorldGenLevel;
|
|
import net.minecraft.world.level.biome.Biome;
|
|
import net.minecraft.world.level.biome.BiomeResolver;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
import net.minecraft.world.level.chunk.ProtoChunk;
|
|
import net.minecraft.world.level.chunk.CarvingMask.Mask;
|
|
import net.minecraft.world.level.levelgen.Heightmap;
|
|
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
|
|
import net.minecraft.world.level.levelgen.DensityFunction.FunctionContext;
|
|
import net.minecraft.world.level.levelgen.synth.NormalNoise;
|
|
import net.minecraft.world.level.material.FluidState;
|
|
import org.apache.commons.lang3.mutable.MutableDouble;
|
|
import org.apache.commons.lang3.mutable.MutableObject;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
public class Blender {
|
|
private static final Blender EMPTY = new Blender(new Long2ObjectOpenHashMap(), new Long2ObjectOpenHashMap()) {
|
|
@Override
|
|
public Blender.BlendingOutput blendOffsetAndFactor(int x, int z) {
|
|
return new Blender.BlendingOutput(1.0, 0.0);
|
|
}
|
|
|
|
@Override
|
|
public double blendDensity(FunctionContext context, double density) {
|
|
return density;
|
|
}
|
|
|
|
@Override
|
|
public BiomeResolver getBiomeResolver(BiomeResolver resolver) {
|
|
return resolver;
|
|
}
|
|
};
|
|
private static final NormalNoise SHIFT_NOISE = NormalNoise.create(new XoroshiroRandomSource(42L), NoiseData.DEFAULT_SHIFT);
|
|
private static final int HEIGHT_BLENDING_RANGE_CELLS = QuartPos.fromSection(7) - 1;
|
|
private static final int HEIGHT_BLENDING_RANGE_CHUNKS = QuartPos.toSection(HEIGHT_BLENDING_RANGE_CELLS + 3);
|
|
private static final int DENSITY_BLENDING_RANGE_CELLS = 2;
|
|
private static final int DENSITY_BLENDING_RANGE_CHUNKS = QuartPos.toSection(5);
|
|
private static final double OLD_CHUNK_XZ_RADIUS = 8.0;
|
|
private final Long2ObjectOpenHashMap<BlendingData> heightAndBiomeBlendingData;
|
|
private final Long2ObjectOpenHashMap<BlendingData> densityBlendingData;
|
|
|
|
public static Blender empty() {
|
|
return EMPTY;
|
|
}
|
|
|
|
public static Blender of(@Nullable WorldGenRegion region) {
|
|
if (region == null) {
|
|
return EMPTY;
|
|
} else {
|
|
ChunkPos chunkPos = region.getCenter();
|
|
if (!region.isOldChunkAround(chunkPos, HEIGHT_BLENDING_RANGE_CHUNKS)) {
|
|
return EMPTY;
|
|
} else {
|
|
Long2ObjectOpenHashMap<BlendingData> long2ObjectOpenHashMap = new Long2ObjectOpenHashMap<>();
|
|
Long2ObjectOpenHashMap<BlendingData> long2ObjectOpenHashMap2 = new Long2ObjectOpenHashMap<>();
|
|
int i = Mth.square(HEIGHT_BLENDING_RANGE_CHUNKS + 1);
|
|
|
|
for (int j = -HEIGHT_BLENDING_RANGE_CHUNKS; j <= HEIGHT_BLENDING_RANGE_CHUNKS; j++) {
|
|
for (int k = -HEIGHT_BLENDING_RANGE_CHUNKS; k <= HEIGHT_BLENDING_RANGE_CHUNKS; k++) {
|
|
if (j * j + k * k <= i) {
|
|
int l = chunkPos.x + j;
|
|
int m = chunkPos.z + k;
|
|
BlendingData blendingData = BlendingData.getOrUpdateBlendingData(region, l, m);
|
|
if (blendingData != null) {
|
|
long2ObjectOpenHashMap.put(ChunkPos.asLong(l, m), blendingData);
|
|
if (j >= -DENSITY_BLENDING_RANGE_CHUNKS
|
|
&& j <= DENSITY_BLENDING_RANGE_CHUNKS
|
|
&& k >= -DENSITY_BLENDING_RANGE_CHUNKS
|
|
&& k <= DENSITY_BLENDING_RANGE_CHUNKS) {
|
|
long2ObjectOpenHashMap2.put(ChunkPos.asLong(l, m), blendingData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return long2ObjectOpenHashMap.isEmpty() && long2ObjectOpenHashMap2.isEmpty() ? EMPTY : new Blender(long2ObjectOpenHashMap, long2ObjectOpenHashMap2);
|
|
}
|
|
}
|
|
}
|
|
|
|
Blender(Long2ObjectOpenHashMap<BlendingData> heightAndBiomeBlendingData, Long2ObjectOpenHashMap<BlendingData> densityBlendingData) {
|
|
this.heightAndBiomeBlendingData = heightAndBiomeBlendingData;
|
|
this.densityBlendingData = densityBlendingData;
|
|
}
|
|
|
|
public Blender.BlendingOutput blendOffsetAndFactor(int x, int z) {
|
|
int i = QuartPos.fromBlock(x);
|
|
int j = QuartPos.fromBlock(z);
|
|
double d = this.getBlendingDataValue(i, 0, j, BlendingData::getHeight);
|
|
if (d != Double.MAX_VALUE) {
|
|
return new Blender.BlendingOutput(0.0, heightToOffset(d));
|
|
} else {
|
|
MutableDouble mutableDouble = new MutableDouble(0.0);
|
|
MutableDouble mutableDouble2 = new MutableDouble(0.0);
|
|
MutableDouble mutableDouble3 = new MutableDouble(Double.POSITIVE_INFINITY);
|
|
this.heightAndBiomeBlendingData
|
|
.forEach(
|
|
(long_, blendingData) -> blendingData.iterateHeights(
|
|
QuartPos.fromSection(ChunkPos.getX(long_)), QuartPos.fromSection(ChunkPos.getZ(long_)), (k, l, dx) -> {
|
|
double ex = Mth.length((float)(i - k), (float)(j - l));
|
|
if (!(ex > HEIGHT_BLENDING_RANGE_CELLS)) {
|
|
if (ex < mutableDouble3.doubleValue()) {
|
|
mutableDouble3.setValue(ex);
|
|
}
|
|
|
|
double fx = 1.0 / (ex * ex * ex * ex);
|
|
mutableDouble2.add(dx * fx);
|
|
mutableDouble.add(fx);
|
|
}
|
|
}
|
|
)
|
|
);
|
|
if (mutableDouble3.doubleValue() == Double.POSITIVE_INFINITY) {
|
|
return new Blender.BlendingOutput(1.0, 0.0);
|
|
} else {
|
|
double e = mutableDouble2.doubleValue() / mutableDouble.doubleValue();
|
|
double f = Mth.clamp(mutableDouble3.doubleValue() / (HEIGHT_BLENDING_RANGE_CELLS + 1), 0.0, 1.0);
|
|
f = 3.0 * f * f - 2.0 * f * f * f;
|
|
return new Blender.BlendingOutput(f, heightToOffset(e));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static double heightToOffset(double height) {
|
|
double d = 1.0;
|
|
double e = height + 0.5;
|
|
double f = Mth.positiveModulo(e, 8.0);
|
|
return 1.0 * (32.0 * (e - 128.0) - 3.0 * (e - 120.0) * f + 3.0 * f * f) / (128.0 * (32.0 - 3.0 * f));
|
|
}
|
|
|
|
public double blendDensity(FunctionContext context, double density) {
|
|
int i = QuartPos.fromBlock(context.blockX());
|
|
int j = context.blockY() / 8;
|
|
int k = QuartPos.fromBlock(context.blockZ());
|
|
double d = this.getBlendingDataValue(i, j, k, BlendingData::getDensity);
|
|
if (d != Double.MAX_VALUE) {
|
|
return d;
|
|
} else {
|
|
MutableDouble mutableDouble = new MutableDouble(0.0);
|
|
MutableDouble mutableDouble2 = new MutableDouble(0.0);
|
|
MutableDouble mutableDouble3 = new MutableDouble(Double.POSITIVE_INFINITY);
|
|
this.densityBlendingData
|
|
.forEach(
|
|
(long_, blendingData) -> blendingData.iterateDensities(
|
|
QuartPos.fromSection(ChunkPos.getX(long_)), QuartPos.fromSection(ChunkPos.getZ(long_)), j - 1, j + 1, (l, m, n, dx) -> {
|
|
double ex = Mth.length(i - l, (j - m) * 2, k - n);
|
|
if (!(ex > 2.0)) {
|
|
if (ex < mutableDouble3.doubleValue()) {
|
|
mutableDouble3.setValue(ex);
|
|
}
|
|
|
|
double fx = 1.0 / (ex * ex * ex * ex);
|
|
mutableDouble2.add(dx * fx);
|
|
mutableDouble.add(fx);
|
|
}
|
|
}
|
|
)
|
|
);
|
|
if (mutableDouble3.doubleValue() == Double.POSITIVE_INFINITY) {
|
|
return density;
|
|
} else {
|
|
double e = mutableDouble2.doubleValue() / mutableDouble.doubleValue();
|
|
double f = Mth.clamp(mutableDouble3.doubleValue() / 3.0, 0.0, 1.0);
|
|
return Mth.lerp(f, e, density);
|
|
}
|
|
}
|
|
}
|
|
|
|
private double getBlendingDataValue(int x, int y, int z, Blender.CellValueGetter getter) {
|
|
int i = QuartPos.toSection(x);
|
|
int j = QuartPos.toSection(z);
|
|
boolean bl = (x & 3) == 0;
|
|
boolean bl2 = (z & 3) == 0;
|
|
double d = this.getBlendingDataValue(getter, i, j, x, y, z);
|
|
if (d == Double.MAX_VALUE) {
|
|
if (bl && bl2) {
|
|
d = this.getBlendingDataValue(getter, i - 1, j - 1, x, y, z);
|
|
}
|
|
|
|
if (d == Double.MAX_VALUE) {
|
|
if (bl) {
|
|
d = this.getBlendingDataValue(getter, i - 1, j, x, y, z);
|
|
}
|
|
|
|
if (d == Double.MAX_VALUE && bl2) {
|
|
d = this.getBlendingDataValue(getter, i, j - 1, x, y, z);
|
|
}
|
|
}
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
private double getBlendingDataValue(Blender.CellValueGetter getter, int sectionX, int sectionZ, int x, int y, int z) {
|
|
BlendingData blendingData = this.heightAndBiomeBlendingData.get(ChunkPos.asLong(sectionX, sectionZ));
|
|
return blendingData != null ? getter.get(blendingData, x - QuartPos.fromSection(sectionX), y, z - QuartPos.fromSection(sectionZ)) : Double.MAX_VALUE;
|
|
}
|
|
|
|
public BiomeResolver getBiomeResolver(BiomeResolver resolver) {
|
|
return (i, j, k, sampler) -> {
|
|
Holder<Biome> holder = this.blendBiome(i, j, k);
|
|
return holder == null ? resolver.getNoiseBiome(i, j, k, sampler) : holder;
|
|
};
|
|
}
|
|
|
|
@Nullable
|
|
private Holder<Biome> blendBiome(int x, int y, int z) {
|
|
MutableDouble mutableDouble = new MutableDouble(Double.POSITIVE_INFINITY);
|
|
MutableObject<Holder<Biome>> mutableObject = new MutableObject<>();
|
|
this.heightAndBiomeBlendingData
|
|
.forEach(
|
|
(long_, blendingData) -> blendingData.iterateBiomes(
|
|
QuartPos.fromSection(ChunkPos.getX(long_)), y, QuartPos.fromSection(ChunkPos.getZ(long_)), (kx, l, holder) -> {
|
|
double dx = Mth.length((float)(x - kx), (float)(z - l));
|
|
if (!(dx > HEIGHT_BLENDING_RANGE_CELLS)) {
|
|
if (dx < mutableDouble.doubleValue()) {
|
|
mutableObject.setValue(holder);
|
|
mutableDouble.setValue(dx);
|
|
}
|
|
}
|
|
}
|
|
)
|
|
);
|
|
if (mutableDouble.doubleValue() == Double.POSITIVE_INFINITY) {
|
|
return null;
|
|
} else {
|
|
double d = SHIFT_NOISE.getValue(x, 0.0, z) * 12.0;
|
|
double e = Mth.clamp((mutableDouble.doubleValue() + d) / (HEIGHT_BLENDING_RANGE_CELLS + 1), 0.0, 1.0);
|
|
return e > 0.5 ? null : mutableObject.getValue();
|
|
}
|
|
}
|
|
|
|
public static void generateBorderTicks(WorldGenRegion region, ChunkAccess chunk) {
|
|
ChunkPos chunkPos = chunk.getPos();
|
|
boolean bl = chunk.isOldNoiseGeneration();
|
|
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
|
|
BlockPos blockPos = new BlockPos(chunkPos.getMinBlockX(), 0, chunkPos.getMinBlockZ());
|
|
BlendingData blendingData = chunk.getBlendingData();
|
|
if (blendingData != null) {
|
|
int i = blendingData.getAreaWithOldGeneration().getMinY();
|
|
int j = blendingData.getAreaWithOldGeneration().getMaxY();
|
|
if (bl) {
|
|
for (int k = 0; k < 16; k++) {
|
|
for (int l = 0; l < 16; l++) {
|
|
generateBorderTick(chunk, mutableBlockPos.setWithOffset(blockPos, k, i - 1, l));
|
|
generateBorderTick(chunk, mutableBlockPos.setWithOffset(blockPos, k, i, l));
|
|
generateBorderTick(chunk, mutableBlockPos.setWithOffset(blockPos, k, j, l));
|
|
generateBorderTick(chunk, mutableBlockPos.setWithOffset(blockPos, k, j + 1, l));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Direction direction : Direction.Plane.HORIZONTAL) {
|
|
if (region.getChunk(chunkPos.x + direction.getStepX(), chunkPos.z + direction.getStepZ()).isOldNoiseGeneration() != bl) {
|
|
int m = direction == Direction.EAST ? 15 : 0;
|
|
int n = direction == Direction.WEST ? 0 : 15;
|
|
int o = direction == Direction.SOUTH ? 15 : 0;
|
|
int p = direction == Direction.NORTH ? 0 : 15;
|
|
|
|
for (int q = m; q <= n; q++) {
|
|
for (int r = o; r <= p; r++) {
|
|
int s = Math.min(j, chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, q, r)) + 1;
|
|
|
|
for (int t = i; t < s; t++) {
|
|
generateBorderTick(chunk, mutableBlockPos.setWithOffset(blockPos, q, t, r));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void generateBorderTick(ChunkAccess chunk, BlockPos pos) {
|
|
BlockState blockState = chunk.getBlockState(pos);
|
|
if (blockState.is(BlockTags.LEAVES)) {
|
|
chunk.markPosForPostprocessing(pos);
|
|
}
|
|
|
|
FluidState fluidState = chunk.getFluidState(pos);
|
|
if (!fluidState.isEmpty()) {
|
|
chunk.markPosForPostprocessing(pos);
|
|
}
|
|
}
|
|
|
|
public static void addAroundOldChunksCarvingMaskFilter(WorldGenLevel level, ProtoChunk chunk) {
|
|
ChunkPos chunkPos = chunk.getPos();
|
|
Builder<Direction8, BlendingData> builder = ImmutableMap.builder();
|
|
|
|
for (Direction8 direction8 : Direction8.values()) {
|
|
int i = chunkPos.x + direction8.getStepX();
|
|
int j = chunkPos.z + direction8.getStepZ();
|
|
BlendingData blendingData = level.getChunk(i, j).getBlendingData();
|
|
if (blendingData != null) {
|
|
builder.put(direction8, blendingData);
|
|
}
|
|
}
|
|
|
|
ImmutableMap<Direction8, BlendingData> immutableMap = builder.build();
|
|
if (chunk.isOldNoiseGeneration() || !immutableMap.isEmpty()) {
|
|
Blender.DistanceGetter distanceGetter = makeOldChunkDistanceGetter(chunk.getBlendingData(), immutableMap);
|
|
Mask mask = (ix, jx, k) -> {
|
|
double d = ix + 0.5 + SHIFT_NOISE.getValue(ix, jx, k) * 4.0;
|
|
double e = jx + 0.5 + SHIFT_NOISE.getValue(jx, k, ix) * 4.0;
|
|
double f = k + 0.5 + SHIFT_NOISE.getValue(k, ix, jx) * 4.0;
|
|
return distanceGetter.getDistance(d, e, f) < 4.0;
|
|
};
|
|
chunk.getOrCreateCarvingMask().setAdditionalMask(mask);
|
|
}
|
|
}
|
|
|
|
public static Blender.DistanceGetter makeOldChunkDistanceGetter(@Nullable BlendingData blendingData, Map<Direction8, BlendingData> surroundingBlendingData) {
|
|
List<Blender.DistanceGetter> list = Lists.<Blender.DistanceGetter>newArrayList();
|
|
if (blendingData != null) {
|
|
list.add(makeOffsetOldChunkDistanceGetter(null, blendingData));
|
|
}
|
|
|
|
surroundingBlendingData.forEach((direction8, blendingDatax) -> list.add(makeOffsetOldChunkDistanceGetter(direction8, blendingDatax)));
|
|
return (d, e, f) -> {
|
|
double g = Double.POSITIVE_INFINITY;
|
|
|
|
for (Blender.DistanceGetter distanceGetter : list) {
|
|
double h = distanceGetter.getDistance(d, e, f);
|
|
if (h < g) {
|
|
g = h;
|
|
}
|
|
}
|
|
|
|
return g;
|
|
};
|
|
}
|
|
|
|
private static Blender.DistanceGetter makeOffsetOldChunkDistanceGetter(@Nullable Direction8 direction, BlendingData blendingData) {
|
|
double d = 0.0;
|
|
double e = 0.0;
|
|
if (direction != null) {
|
|
for (Direction direction2 : direction.getDirections()) {
|
|
d += direction2.getStepX() * 16;
|
|
e += direction2.getStepZ() * 16;
|
|
}
|
|
}
|
|
|
|
double f = d;
|
|
double g = e;
|
|
double h = blendingData.getAreaWithOldGeneration().getHeight() / 2.0;
|
|
double i = blendingData.getAreaWithOldGeneration().getMinY() + h;
|
|
return (hx, ix, j) -> distanceToCube(hx - 8.0 - f, ix - i, j - 8.0 - g, 8.0, h, 8.0);
|
|
}
|
|
|
|
private static double distanceToCube(double x1, double y1, double z1, double x2, double y2, double z2) {
|
|
double d = Math.abs(x1) - x2;
|
|
double e = Math.abs(y1) - y2;
|
|
double f = Math.abs(z1) - z2;
|
|
return Mth.length(Math.max(0.0, d), Math.max(0.0, e), Math.max(0.0, f));
|
|
}
|
|
|
|
public record BlendingOutput(double alpha, double blendingOffset) {
|
|
}
|
|
|
|
interface CellValueGetter {
|
|
double get(BlendingData blendingData, int i, int j, int k);
|
|
}
|
|
|
|
public interface DistanceGetter {
|
|
double getDistance(double d, double e, double f);
|
|
}
|
|
}
|