314 lines
12 KiB
Java
314 lines
12 KiB
Java
package net.minecraft.world.level.levelgen.feature;
|
|
|
|
import com.mojang.serialization.Codec;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.world.level.BlockGetter;
|
|
import net.minecraft.world.level.LevelAccessor;
|
|
import net.minecraft.world.level.WorldGenLevel;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.levelgen.feature.configurations.BlockStateConfiguration;
|
|
|
|
/**
|
|
* This feature generates part of the icebergs found in frozen oceans.
|
|
* Specifically, it generates tall, triangular prism icebergs, and "donut" or torus shaped icebergs.
|
|
* Other icebergs are generated by the frozen ocean surface builder instead.
|
|
*/
|
|
public class IcebergFeature extends Feature<BlockStateConfiguration> {
|
|
public IcebergFeature(Codec<BlockStateConfiguration> codec) {
|
|
super(codec);
|
|
}
|
|
|
|
@Override
|
|
public boolean place(FeaturePlaceContext<BlockStateConfiguration> context) {
|
|
BlockPos blockPos = context.origin();
|
|
WorldGenLevel worldGenLevel = context.level();
|
|
blockPos = new BlockPos(blockPos.getX(), context.chunkGenerator().getSeaLevel(), blockPos.getZ());
|
|
RandomSource randomSource = context.random();
|
|
boolean bl = randomSource.nextDouble() > 0.7;
|
|
BlockState blockState = context.config().state;
|
|
double d = randomSource.nextDouble() * 2.0 * Math.PI;
|
|
int i = 11 - randomSource.nextInt(5);
|
|
int j = 3 + randomSource.nextInt(3);
|
|
boolean bl2 = randomSource.nextDouble() > 0.7;
|
|
int k = 11;
|
|
int l = bl2 ? randomSource.nextInt(6) + 6 : randomSource.nextInt(15) + 3;
|
|
if (!bl2 && randomSource.nextDouble() > 0.9) {
|
|
l += randomSource.nextInt(19) + 7;
|
|
}
|
|
|
|
int m = Math.min(l + randomSource.nextInt(11), 18);
|
|
int n = Math.min(l + randomSource.nextInt(7) - randomSource.nextInt(5), 11);
|
|
int o = bl2 ? i : 11;
|
|
|
|
for (int p = -o; p < o; p++) {
|
|
for (int q = -o; q < o; q++) {
|
|
for (int r = 0; r < l; r++) {
|
|
int s = bl2 ? this.heightDependentRadiusEllipse(r, l, n) : this.heightDependentRadiusRound(randomSource, r, l, n);
|
|
if (bl2 || p < s) {
|
|
this.generateIcebergBlock(worldGenLevel, randomSource, blockPos, l, p, r, q, s, o, bl2, j, d, bl, blockState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.smooth(worldGenLevel, blockPos, n, l, bl2, i);
|
|
|
|
for (int p = -o; p < o; p++) {
|
|
for (int q = -o; q < o; q++) {
|
|
for (int rx = -1; rx > -m; rx--) {
|
|
int s = bl2 ? Mth.ceil(o * (1.0F - (float)Math.pow(rx, 2.0) / (m * 8.0F))) : o;
|
|
int t = this.heightDependentRadiusSteep(randomSource, -rx, m, n);
|
|
if (p < t) {
|
|
this.generateIcebergBlock(worldGenLevel, randomSource, blockPos, m, p, rx, q, t, s, bl2, j, d, bl, blockState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
boolean bl3 = bl2 ? randomSource.nextDouble() > 0.1 : randomSource.nextDouble() > 0.7;
|
|
if (bl3) {
|
|
this.generateCutOut(randomSource, worldGenLevel, n, l, blockPos, bl2, i, d, j);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void generateCutOut(
|
|
RandomSource random, LevelAccessor level, int majorAxis, int height, BlockPos pos, boolean elliptical, int ellipseRadius, double angle, int minorAxis
|
|
) {
|
|
int i = random.nextBoolean() ? -1 : 1;
|
|
int j = random.nextBoolean() ? -1 : 1;
|
|
int k = random.nextInt(Math.max(majorAxis / 2 - 2, 1));
|
|
if (random.nextBoolean()) {
|
|
k = majorAxis / 2 + 1 - random.nextInt(Math.max(majorAxis - majorAxis / 2 - 1, 1));
|
|
}
|
|
|
|
int l = random.nextInt(Math.max(majorAxis / 2 - 2, 1));
|
|
if (random.nextBoolean()) {
|
|
l = majorAxis / 2 + 1 - random.nextInt(Math.max(majorAxis - majorAxis / 2 - 1, 1));
|
|
}
|
|
|
|
if (elliptical) {
|
|
k = l = random.nextInt(Math.max(ellipseRadius - 5, 1));
|
|
}
|
|
|
|
BlockPos blockPos = new BlockPos(i * k, 0, j * l);
|
|
double d = elliptical ? angle + (Math.PI / 2) : random.nextDouble() * 2.0 * Math.PI;
|
|
|
|
for (int m = 0; m < height - 3; m++) {
|
|
int n = this.heightDependentRadiusRound(random, m, height, majorAxis);
|
|
this.carve(n, m, pos, level, false, d, blockPos, ellipseRadius, minorAxis);
|
|
}
|
|
|
|
for (int m = -1; m > -height + random.nextInt(5); m--) {
|
|
int n = this.heightDependentRadiusSteep(random, -m, height, majorAxis);
|
|
this.carve(n, m, pos, level, true, d, blockPos, ellipseRadius, minorAxis);
|
|
}
|
|
}
|
|
|
|
private void carve(
|
|
int radius,
|
|
int localY,
|
|
BlockPos pos,
|
|
LevelAccessor level,
|
|
boolean placeWater,
|
|
double perpendicularAngle,
|
|
BlockPos ellipseOrigin,
|
|
int majorRadius,
|
|
int minorRadius
|
|
) {
|
|
int i = radius + 1 + majorRadius / 3;
|
|
int j = Math.min(radius - 3, 3) + minorRadius / 2 - 1;
|
|
|
|
for (int k = -i; k < i; k++) {
|
|
for (int l = -i; l < i; l++) {
|
|
double d = this.signedDistanceEllipse(k, l, ellipseOrigin, i, j, perpendicularAngle);
|
|
if (d < 0.0) {
|
|
BlockPos blockPos = pos.offset(k, localY, l);
|
|
BlockState blockState = level.getBlockState(blockPos);
|
|
if (isIcebergState(blockState) || blockState.is(Blocks.SNOW_BLOCK)) {
|
|
if (placeWater) {
|
|
this.setBlock(level, blockPos, Blocks.WATER.defaultBlockState());
|
|
} else {
|
|
this.setBlock(level, blockPos, Blocks.AIR.defaultBlockState());
|
|
this.removeFloatingSnowLayer(level, blockPos);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void removeFloatingSnowLayer(LevelAccessor level, BlockPos pos) {
|
|
if (level.getBlockState(pos.above()).is(Blocks.SNOW)) {
|
|
this.setBlock(level, pos.above(), Blocks.AIR.defaultBlockState());
|
|
}
|
|
}
|
|
|
|
private void generateIcebergBlock(
|
|
LevelAccessor level,
|
|
RandomSource random,
|
|
BlockPos pos,
|
|
int height,
|
|
int localX,
|
|
int localY,
|
|
int localZ,
|
|
int radius,
|
|
int majorRadius,
|
|
boolean elliptical,
|
|
int minorRadius,
|
|
double angle,
|
|
boolean placeSnow,
|
|
BlockState state
|
|
) {
|
|
double d = elliptical
|
|
? this.signedDistanceEllipse(localX, localZ, BlockPos.ZERO, majorRadius, this.getEllipseC(localY, height, minorRadius), angle)
|
|
: this.signedDistanceCircle(localX, localZ, BlockPos.ZERO, radius, random);
|
|
if (d < 0.0) {
|
|
BlockPos blockPos = pos.offset(localX, localY, localZ);
|
|
double e = elliptical ? -0.5 : -6 - random.nextInt(3);
|
|
if (d > e && random.nextDouble() > 0.9) {
|
|
return;
|
|
}
|
|
|
|
this.setIcebergBlock(blockPos, level, random, height - localY, height, elliptical, placeSnow, state);
|
|
}
|
|
}
|
|
|
|
private void setIcebergBlock(
|
|
BlockPos pos, LevelAccessor level, RandomSource random, int heightRemaining, int height, boolean elliptical, boolean placeSnow, BlockState state
|
|
) {
|
|
BlockState blockState = level.getBlockState(pos);
|
|
if (blockState.isAir() || blockState.is(Blocks.SNOW_BLOCK) || blockState.is(Blocks.ICE) || blockState.is(Blocks.WATER)) {
|
|
boolean bl = !elliptical || random.nextDouble() > 0.05;
|
|
int i = elliptical ? 3 : 2;
|
|
if (placeSnow && !blockState.is(Blocks.WATER) && heightRemaining <= random.nextInt(Math.max(1, height / i)) + height * 0.6 && bl) {
|
|
this.setBlock(level, pos, Blocks.SNOW_BLOCK.defaultBlockState());
|
|
} else {
|
|
this.setBlock(level, pos, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
private int getEllipseC(int y, int height, int minorAxis) {
|
|
int i = minorAxis;
|
|
if (y > 0 && height - y <= 3) {
|
|
i = minorAxis - (4 - (height - y));
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
private double signedDistanceCircle(int x, int z, BlockPos center, int radius, RandomSource random) {
|
|
float f = 10.0F * Mth.clamp(random.nextFloat(), 0.2F, 0.8F) / radius;
|
|
return f + Math.pow(x - center.getX(), 2.0) + Math.pow(z - center.getZ(), 2.0) - Math.pow(radius, 2.0);
|
|
}
|
|
|
|
/**
|
|
* Given an ellipse defined by the equation {@code (x/a)^2 + (y/b)^2 = 1}, where {@code a} and {@code b} are the semi-major and semi-minor axes respectively, this computes the distance between an arbitrary point and the ellipse.
|
|
* The point (x, y) is within the ellipse if the return value is < 0, outside the ellipse if the return value is > 0 and exactly on the edge of the ellipse if the return value is 0.
|
|
*
|
|
* @param x The x position of the point to measure the distance to.
|
|
* @param z The z position of the point to measure distance to.
|
|
* @param center The center point of the ellipse.
|
|
* @param majorRadius The semi-major axis ({@code a}) of the ellipse.
|
|
* @param minorRadius The semi-minor axis ({@code b}) of the ellipse
|
|
* @param angle The rotation angle of the ellipse (the angle from the positive horizontal axis to the ellipse's major axis).
|
|
*/
|
|
private double signedDistanceEllipse(int x, int z, BlockPos center, int majorRadius, int minorRadius, double angle) {
|
|
return Math.pow(((x - center.getX()) * Math.cos(angle) - (z - center.getZ()) * Math.sin(angle)) / majorRadius, 2.0)
|
|
+ Math.pow(((x - center.getX()) * Math.sin(angle) + (z - center.getZ()) * Math.cos(angle)) / minorRadius, 2.0)
|
|
- 1.0;
|
|
}
|
|
|
|
private int heightDependentRadiusRound(RandomSource random, int y, int height, int majorAxis) {
|
|
float f = 3.5F - random.nextFloat();
|
|
float g = (1.0F - (float)Math.pow(y, 2.0) / (height * f)) * majorAxis;
|
|
if (height > 15 + random.nextInt(5)) {
|
|
int i = y < 3 + random.nextInt(6) ? y / 2 : y;
|
|
g = (1.0F - i / (height * f * 0.4F)) * majorAxis;
|
|
}
|
|
|
|
return Mth.ceil(g / 2.0F);
|
|
}
|
|
|
|
/**
|
|
* Given a horizontal projection of an iceberg, defines the three-dimensional extrusion by defining a radius at any given y value.
|
|
* The radius curve is a parabolic function, resulting in more rounded iceberg peaks.
|
|
*
|
|
* @param y The y value to calculate a radius at.
|
|
* @param height The maximum height of the iceberg.
|
|
* @param maxRadius The maximum radius of the iceberg, at the horizontal.
|
|
*/
|
|
private int heightDependentRadiusEllipse(int y, int height, int maxRadius) {
|
|
float f = 1.0F;
|
|
float g = (1.0F - (float)Math.pow(y, 2.0) / (height * 1.0F)) * maxRadius;
|
|
return Mth.ceil(g / 2.0F);
|
|
}
|
|
|
|
/**
|
|
* Given a horizontal projection of an iceberg, defines the three-dimensional extrusion by defining a radius at any given y value.
|
|
* The radius curve is a linear function, with a slope that is both dependent on the {@code majorAxis} and randomly varies, which results in steep conical icebergs.
|
|
*
|
|
* @param random A random to use to vary the slope of the falloff curve.
|
|
* @param y The y value to calculate a radius at.
|
|
* @param height The maximum height of the iceberg.
|
|
* @param maxRadius The maximum radius radius of the iceberg, at the horizontal.
|
|
*/
|
|
private int heightDependentRadiusSteep(RandomSource random, int y, int height, int maxRadius) {
|
|
float f = 1.0F + random.nextFloat() / 2.0F;
|
|
float g = (1.0F - y / (height * f)) * maxRadius;
|
|
return Mth.ceil(g / 2.0F);
|
|
}
|
|
|
|
private static boolean isIcebergState(BlockState state) {
|
|
return state.is(Blocks.PACKED_ICE) || state.is(Blocks.SNOW_BLOCK) || state.is(Blocks.BLUE_ICE);
|
|
}
|
|
|
|
private boolean belowIsAir(BlockGetter level, BlockPos pos) {
|
|
return level.getBlockState(pos.below()).isAir();
|
|
}
|
|
|
|
/**
|
|
* Smooths out an iceberg by removing blocks which either have air below, or non-iceberg blocks on three or more horizontal sides, with air.
|
|
*/
|
|
private void smooth(LevelAccessor level, BlockPos pos, int majorRadius, int height, boolean elliptical, int minorRadius) {
|
|
int i = elliptical ? minorRadius : majorRadius / 2;
|
|
|
|
for (int j = -i; j <= i; j++) {
|
|
for (int k = -i; k <= i; k++) {
|
|
for (int l = 0; l <= height; l++) {
|
|
BlockPos blockPos = pos.offset(j, l, k);
|
|
BlockState blockState = level.getBlockState(blockPos);
|
|
if (isIcebergState(blockState) || blockState.is(Blocks.SNOW)) {
|
|
if (this.belowIsAir(level, blockPos)) {
|
|
this.setBlock(level, blockPos, Blocks.AIR.defaultBlockState());
|
|
this.setBlock(level, blockPos.above(), Blocks.AIR.defaultBlockState());
|
|
} else if (isIcebergState(blockState)) {
|
|
BlockState[] blockStates = new BlockState[]{
|
|
level.getBlockState(blockPos.west()),
|
|
level.getBlockState(blockPos.east()),
|
|
level.getBlockState(blockPos.north()),
|
|
level.getBlockState(blockPos.south())
|
|
};
|
|
int m = 0;
|
|
|
|
for (BlockState blockState2 : blockStates) {
|
|
if (!isIcebergState(blockState2)) {
|
|
m++;
|
|
}
|
|
}
|
|
|
|
if (m >= 3) {
|
|
this.setBlock(level, blockPos, Blocks.AIR.defaultBlockState());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|