/* * This file is part of Cubic Chunks Mod, licensed under the MIT License (MIT). * * Copyright (c) 2015 contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package cubicchunks.worldgen.generator.custom.structures; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Blocks; import net.minecraft.world.gen.structure.StructureBoundingBox; import java.util.Random; import java.util.function.Predicate; import cubicchunks.util.CubePos; import cubicchunks.util.StructureGenUtil; import cubicchunks.world.ICubicWorld; import cubicchunks.world.cube.Cube; import cubicchunks.worldgen.generator.ICubePrimer; import static cubicchunks.util.Coords.cubeToMinBlock; import static cubicchunks.util.Coords.localToBlock; import static net.minecraft.util.math.MathHelper.cos; import static net.minecraft.util.math.MathHelper.floor; import static net.minecraft.util.math.MathHelper.sin; public class CubicRavineGenerator extends CubicStructureGenerator { private static final int RAVINE_RARITY = 50*16; private static final int MAX_CUBE_Y = 4; private static final double VERT_SIZE_FACTOR = 3.0; /** * Value added to the size of the cave (radius) */ private static final double RAVINE_SIZE_ADD = 1.5D; private static final double MIN_RAND_SIZE_FACTOR = 0.75; private static final double MAX_RAND_SIZE_FACTOR = 1.00; /** * After each step the Y direction component will be multiplied by this value */ private static final double FLATTEN_FACTOR = 0.7; /** * Each step ravine direction angles will be changed by this fraction of values that specify how direction changes */ private static final double DIRECTION_CHANGE_FACTOR = 0.05; /** * This fraction of the previous value that controls horizontal direction changes will be used in next step */ private static final double PREV_HORIZ_DIRECTION_CHANGE_WEIGHT = 0.5; /** * This fraction of the previous value that controls vertical direction changes will be used in next step */ private static final double PREV_VERT_DIRECTION_CHANGE_WEIGHT = 0.8; /** * Maximum value by which horizontal cave direction randomly changes each step, lower values are much more likely. */ private static final double MAX_ADD_DIRECTION_CHANGE_HORIZ = 4.0; /** * Maximum value by which vertical cave direction randomly changes each step, lower values are much more likely. */ private static final double MAX_ADD_DIRECTION_CHANGE_VERT = 2.0; /** * 1 in this amount of steps will actually carve any blocks, */ private static final int CARVE_STEP_RARITY = 4; /** * Controls which blocks can be replaced by cave */ private static final Predicate<IBlockState> isBlockReplaceable = (state -> state.getBlock() == Blocks.STONE || state.getBlock() == Blocks.DIRT || state.getBlock() == Blocks.GRASS); /** * Contains values of ravine widths at each height. * <p> * For cubic chunks the height value used wraps around. */ private float[] widthDecreaseFactors = new float[1024]; @Override protected void generate(ICubicWorld world, ICubePrimer cube, int structureX, int structureY, int structureZ, CubePos generatedCubePos) { if (rand.nextInt(RAVINE_RARITY) != 0 || structureY > MAX_CUBE_Y) { return; } double startX = localToBlock(structureX, rand.nextInt(Cube.SIZE)); double startY = localToBlock(structureY, rand.nextInt(Cube.SIZE)); double startZ = localToBlock(structureZ, rand.nextInt(Cube.SIZE)); float vertDirectionAngle = rand.nextFloat()*(float) Math.PI*2.0F; float horizDirectionAngle = (rand.nextFloat() - 0.5F)*2.0F/8.0F; float baseRavineSize = (rand.nextFloat()*2.0F + rand.nextFloat())*2.0F; int startWalkedDistance = 0; int maxWalkedDistance = 0;//choose value automatically this.generateNode(cube, rand.nextLong(), generatedCubePos, startX, startY, startZ, baseRavineSize, vertDirectionAngle, horizDirectionAngle, startWalkedDistance, maxWalkedDistance, VERT_SIZE_FACTOR); } protected void generateNode(ICubePrimer cube, long seed, CubePos generatedCubePos, double ravineX, double ravineY, double ravineZ, float baseRavineSize, float horizDirAngle, float vertDirAngle, int startWalkedDistance, int maxWalkedDistance, double vertRavineSizeMod) { Random rand = new Random(seed); //store by how much the horizontal and vertical(?) direction angles will change each step float horizDirChange = 0.0F; float vertDirChange = 0.0F; if (maxWalkedDistance <= 0) { int maxBlockRadius = cubeToMinBlock(this.range - 1); maxWalkedDistance = maxBlockRadius - rand.nextInt(maxBlockRadius/4); } //always false for ravine generator boolean finalStep = false; int walkedDistance; if (startWalkedDistance == -1) { //UNUSED: generate a ravine equivalent of cave room //start at half distance towards the end = max size walkedDistance = maxWalkedDistance/2; finalStep = true; } else { walkedDistance = startWalkedDistance; } this.widthDecreaseFactors = generateRavineWidthFactors(rand); for (; walkedDistance < maxWalkedDistance; ++walkedDistance) { float fractionWalked = walkedDistance/(float) maxWalkedDistance; //horizontal and vertical size of the ravine //size starts small and increases, then decreases as ravine goes further double ravineSizeHoriz = RAVINE_SIZE_ADD + sin(fractionWalked*(float) Math.PI)*baseRavineSize; double ravineSizeVert = ravineSizeHoriz*vertRavineSizeMod; ravineSizeHoriz *= rand.nextFloat()*(MAX_RAND_SIZE_FACTOR - MIN_RAND_SIZE_FACTOR) + MIN_RAND_SIZE_FACTOR; ravineSizeVert *= rand.nextFloat()*(MAX_RAND_SIZE_FACTOR - MIN_RAND_SIZE_FACTOR) + MIN_RAND_SIZE_FACTOR; //Walk forward a single step: //from sin(alpha)=y/r and cos(alpha)=x/r ==> x = r*cos(alpha) and y = r*sin(alpha) //always moves by one block in some direction //here x is xzDirectionSize, y is yDirection float xzDirectionFactor = cos(vertDirAngle); float yDirectionFactor = sin(vertDirAngle); ravineX += cos(horizDirAngle)*xzDirectionFactor; ravineY += yDirectionFactor; ravineZ += sin(horizDirAngle)*xzDirectionFactor; vertDirAngle *= FLATTEN_FACTOR; //change the direction vertDirAngle += vertDirChange*DIRECTION_CHANGE_FACTOR; horizDirAngle += horizDirChange*DIRECTION_CHANGE_FACTOR; //update direction change angles vertDirChange *= PREV_VERT_DIRECTION_CHANGE_WEIGHT; horizDirChange *= PREV_HORIZ_DIRECTION_CHANGE_WEIGHT; vertDirChange += (rand.nextFloat() - rand.nextFloat())*rand.nextFloat()*MAX_ADD_DIRECTION_CHANGE_VERT; horizDirChange += (rand.nextFloat() - rand.nextFloat())*rand.nextFloat()*MAX_ADD_DIRECTION_CHANGE_HORIZ; if (rand.nextInt(CARVE_STEP_RARITY) == 0 && !finalStep) { continue; } double xDist = ravineX - generatedCubePos.getXCenter(); double zDist = ravineZ - generatedCubePos.getZCenter(); double maxStepsDist = maxWalkedDistance - walkedDistance; double maxDistToCube = baseRavineSize + RAVINE_SIZE_ADD + Cube.SIZE; //can this cube be reached at all? //if even after going max distance allowed by remaining steps, it's still too far - stop //NOTE: don't check yDist, this is optimization and with Y scale stretched as much as with ravines //the check would be useless //TODO: does it make any performance difference? if (xDist*xDist + zDist*zDist - maxStepsDist*maxStepsDist > maxDistToCube*maxDistToCube) { return; } tryCarveBlocks(cube, generatedCubePos, ravineX, ravineY, ravineZ, ravineSizeHoriz, ravineSizeVert); if (finalStep) { return; } } } private void tryCarveBlocks(ICubePrimer cube, CubePos generatedCubePos, double ravineX, double ravineY, double ravineZ, double ravineSizeHoriz, double ravineSizeVert) { double genCubeCenterX = generatedCubePos.getXCenter(); double genCubeCenterY = generatedCubePos.getYCenter(); double genCubeCenterZ = generatedCubePos.getZCenter(); if (ravineX < genCubeCenterX - Cube.SIZE - ravineSizeHoriz*2.0D || ravineY < genCubeCenterY - Cube.SIZE - ravineSizeVert*2.0D || ravineZ < genCubeCenterZ - Cube.SIZE - ravineSizeHoriz*2.0D || ravineX > genCubeCenterX + Cube.SIZE + ravineSizeHoriz*2.0D || ravineY > genCubeCenterY + Cube.SIZE + ravineSizeVert*2.0D || ravineZ > genCubeCenterZ + Cube.SIZE + ravineSizeHoriz*2.0D) { return; } int minLocalX = floor(ravineX - ravineSizeHoriz) - generatedCubePos.getMinBlockX() - 1; int maxLocalX = floor(ravineX + ravineSizeHoriz) - generatedCubePos.getMinBlockX() + 1; int minLocalY = floor(ravineY - ravineSizeVert) - generatedCubePos.getMinBlockY() - 1; int maxLocalY = floor(ravineY + ravineSizeVert) - generatedCubePos.getMinBlockY() + 1; int minLocalZ = floor(ravineZ - ravineSizeHoriz) - generatedCubePos.getMinBlockZ() - 1; int maxLocalZ = floor(ravineZ + ravineSizeHoriz) - generatedCubePos.getMinBlockZ() + 1; //skip is if everything is outside of that cube if (maxLocalX <= 0 || minLocalX >= Cube.SIZE || maxLocalY <= 0 || minLocalY >= Cube.SIZE || maxLocalZ <= 0 || minLocalZ >= Cube.SIZE) { return; } StructureBoundingBox boundingBox = new StructureBoundingBox(minLocalX, minLocalY, minLocalZ, maxLocalX, maxLocalY, maxLocalZ); StructureGenUtil.clampBoundingBoxToLocalCube(boundingBox); boolean hitLiquid = StructureGenUtil.scanWallsForBlock(cube, boundingBox, (b) -> b.getBlock() == Blocks.WATER || b.getBlock() == Blocks.FLOWING_WATER); if (!hitLiquid) { carveBlocks(cube, generatedCubePos, ravineX, ravineY, ravineZ, ravineSizeHoriz, ravineSizeVert, boundingBox); } } private void carveBlocks(ICubePrimer cube, CubePos generatedCubePos, double ravineX, double ravineY, double ravineZ, double ravineSizeHoriz, double ravineSizeVert, StructureBoundingBox boundingBox) { int generatedCubeX = generatedCubePos.getX(); int generatedCubeY = generatedCubePos.getY(); int generatedCubeZ = generatedCubePos.getZ(); int minX = boundingBox.minX; int maxX = boundingBox.maxX; int minY = boundingBox.minY; int maxY = boundingBox.maxY; int minZ = boundingBox.minZ; int maxZ = boundingBox.maxZ; for (int localX = minX; localX < maxX; ++localX) { double distX = StructureGenUtil.normalizedDistance(generatedCubeX, localX, ravineX, ravineSizeHoriz); for (int localZ = minZ; localZ < maxZ; ++localZ) { double distZ = StructureGenUtil.normalizedDistance(generatedCubeZ, localZ, ravineZ, ravineSizeHoriz); if (distX*distX + distZ*distZ >= 1.0D) { continue; } for (int localY = minY; localY < maxY; ++localY) { double distY = StructureGenUtil.normalizedDistance(generatedCubeY, localY, ravineY, ravineSizeVert); //distY*distY/6.0D is a hack //it should make the ravine way more stretched in the Y dimension, but because of previous checks //most of these blocks beyond the not-stretched height range are never carved out //the result is that instead the ravine isn't very small at the bottom, //but ends with actual floor instead double widthDecreaseFactor = this.widthDecreaseFactors[(localY + generatedCubeY*16) & 0xFF]; if ((distX*distX + distZ*distZ)*widthDecreaseFactor + distY*distY/6.0D >= 1.0D) { continue; } if (!isBlockReplaceable.test(cube.getBlockState(localX, localY, localZ))) { continue; } //vanilla places lava at the bottom of ravines if it below some block Y coordinate //this is actually tricky to do right with cubic chunks - the absolute minimum depth is too deep //so instead of placing ave below some absolute depth - let it vary between different ravines //this will also have the side effect that lava won't be placed perfectly flat there :( if (distY < -0.8) { cube.setBlockState(localX, localY, localZ, Blocks.FLOWING_LAVA.getDefaultState()); } else { cube.setBlockState(localX, localY, localZ, Blocks.AIR.getDefaultState()); } } } } } private float[] generateRavineWidthFactors(Random rand) { float[] values = new float[1024]; float value = 1.0F; for (int i = 0; i < 256; ++i) { //~33% probability that the value will change at that height if (i == 0 || rand.nextInt(3) == 0) { //value = 1.xxx, lower = higher probability -> Wider parts are more common. value = 1.0F + rand.nextFloat()*rand.nextFloat(); } values[i] = value*value; } return values; } }