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 { public IcebergFeature(Codec codec) { super(codec); } @Override public boolean place(FeaturePlaceContext 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()); } } } } } } } }