aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.World/BasicTree.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Minecraft.World/BasicTree.cpp')
-rw-r--r--Minecraft.World/BasicTree.cpp565
1 files changed, 565 insertions, 0 deletions
diff --git a/Minecraft.World/BasicTree.cpp b/Minecraft.World/BasicTree.cpp
new file mode 100644
index 00000000..01f10cb3
--- /dev/null
+++ b/Minecraft.World/BasicTree.cpp
@@ -0,0 +1,565 @@
+#include "stdafx.h"
+#include "net.minecraft.world.level.h"
+#include "net.minecraft.world.level.tile.h"
+#include "BasicTree.h"
+
+byte BasicTree::axisConversionArray[] = { 2, 0, 0, 1, 2, 1 };
+
+BasicTree::~BasicTree()
+{
+ delete rnd;
+
+ for( int i = 0; i < foliageCoordsLength; i++ )
+ {
+ delete [] foliageCoords[i];
+ }
+ delete [] foliageCoords;
+}
+
+BasicTree::BasicTree(bool doUpdate) : Feature(doUpdate)
+{
+ rnd = new Random();
+ origin[0] = 0;
+ origin[1] = 0;
+ origin[2] = 0;
+ // Field to hold the tree height.
+ height = 0;
+ // Other important tree information.
+ trunkHeight = 0;
+ trunkHeightScale = 0.618;
+ branchDensity = 1.0;
+ branchSlope = 0.381;
+ widthScale = 1.0;
+ foliageDensity = 1.0;
+ trunkWidth = 1;
+ heightVariance = 12;
+ foliageHeight = 4;
+ foliageCoords = NULL;
+ foliageCoordsLength = 0;
+}
+
+void BasicTree::prepare()
+{
+ // Initialize the instance variables.
+ // Populate the list of foliage cluster locations.
+ // Designed to be overridden in child classes to change basic
+ // tree properties (trunk width, branch angle, foliage density, etc..).
+ trunkHeight = (int) (height * trunkHeightScale);
+ if (trunkHeight >= height) trunkHeight = height - 1;
+ int clustersPerY = (int) (1.382 + pow(foliageDensity * height / 13.0, 2));
+ if (clustersPerY < 1) clustersPerY = 1;
+ // The foliage coordinates are a list of [x,y,z,y of branch base] values for each cluster
+ int **tempFoliageCoords = new int *[clustersPerY * height];
+ for( int i = 0; i < clustersPerY * height; i++ )
+ {
+ tempFoliageCoords[i] = new int[4];
+ }
+ int y = origin[1] + height - foliageHeight;
+ int clusterCount = 1;
+ int trunkTop = origin[1] + trunkHeight;
+ int relativeY = y - origin[1];
+
+ tempFoliageCoords[0][0] = origin[0];
+ tempFoliageCoords[0][1] = y;
+ tempFoliageCoords[0][2] = origin[2];
+ tempFoliageCoords[0][3] = trunkTop;
+ y--;
+
+ while (relativeY >= 0)
+ {
+ int num = 0;
+
+ float shapefac = treeShape(relativeY);
+ if (shapefac < 0)
+ {
+ y--;
+ relativeY--;
+ continue;
+ }
+
+ // The originOffset is to put the value in the middle of the block.
+ double originOffset = 0.5;
+ while (num < clustersPerY)
+ {
+ double radius = widthScale * (shapefac * (rnd->nextFloat() + 0.328));
+ double angle = rnd->nextFloat() * 2.0 * 3.14159;
+ int x = Mth::floor(radius * sin(angle) + origin[0] + originOffset);
+ int z = Mth::floor(radius * cos(angle) + origin[2] + originOffset);
+ int checkStart[] = { x, y, z };
+ int checkEnd[] = { x, y + foliageHeight, z };
+ // check the center column of the cluster for obstructions.
+ if (checkLine(checkStart, checkEnd) == -1) {
+ // If the cluster can be created, check the branch path
+ // for obstructions.
+ int checkBranchBase[] = { origin[0], origin[1], origin[2] };
+ double distance = sqrt(pow(abs(origin[0] - checkStart[0]), 2.0) + pow(abs(origin[2] - checkStart[2]), 2.0));
+ double branchHeight = distance * branchSlope;
+ if ((checkStart[1] - branchHeight) > trunkTop)
+ {
+ checkBranchBase[1] = trunkTop;
+ }
+ else
+ {
+ checkBranchBase[1] = (int) (checkStart[1] - branchHeight);
+ }
+ // Now check the branch path
+ if (checkLine(checkBranchBase, checkStart) == -1)
+ {
+ // If the branch path is clear, add the position to the list
+ // of foliage positions
+ tempFoliageCoords[clusterCount][0] = x;
+ tempFoliageCoords[clusterCount][1] = y;
+ tempFoliageCoords[clusterCount][2] = z;
+ tempFoliageCoords[clusterCount][3] = checkBranchBase[1];
+ clusterCount++;
+ }
+ }
+ num++;
+ }
+ y--;
+ relativeY--;
+ }
+ // 4J Stu - Rather than copying the array, we are storing the number of valid elements in the array
+ foliageCoordsLength = clusterCount;
+ foliageCoords = tempFoliageCoords;
+ // Delete the rest of the array whilst we still know how big it was
+ for( int i = clusterCount; i < clustersPerY * height; i++ )
+ {
+ delete [] tempFoliageCoords[i];
+ tempFoliageCoords[i] = NULL;
+ }
+ // 4J - original code for above is the following, it isn't obvious to me why it is doing a copy of the array, so let's not for now
+// foliageCoords = new int[clusterCount][4];
+// System.arraycopy(tempFoliageCoords, 0, foliageCoords, 0, clusterCount);
+
+}
+
+void BasicTree::crossection(int x, int y, int z, float radius, byte direction, int material)
+{
+ PIXBeginNamedEvent(0, "BasicTree crossection");
+ // Create a circular cross section.
+ //
+ // Used to nearly everything in the foliage, branches, and trunk.
+ // This is a good target for performance optimization.
+
+ // Passed values:
+ // x,y,z is the center location of the cross section
+ // radius is the radius of the section from the center
+ // direction is the direction the cross section is pointed, 0 for x, 1 for y, 2 for z
+ // material is the index number for the material to use
+ int rad = (int) (radius + 0.618);
+ byte secidx1 = axisConversionArray[direction];
+ byte secidx2 = axisConversionArray[direction + 3];
+ int center[] = { x, y, z };
+ int position[] = { 0, 0, 0 };
+ int offset1 = -rad;
+ int offset2 = -rad;
+ int thismat;
+ position[direction] = center[direction];
+ while (offset1 <= rad)
+ {
+ position[secidx1] = center[secidx1] + offset1;
+ offset2 = -rad;
+ while (offset2 <= rad)
+ {
+ double thisdistance = pow(abs(offset1) + 0.5, 2) + pow(abs(offset2) + 0.5, 2);
+ if (thisdistance > radius * radius)
+ {
+ offset2++;
+ continue;
+ }
+ position[secidx2] = center[secidx2] + offset2;
+ PIXBeginNamedEvent(0,"BasicTree getting tile");
+ thismat = thisLevel->getTile(position[0], position[1], position[2]);
+ PIXEndNamedEvent();
+ if (!((thismat == 0) || (thismat == Tile::leaves_Id)))
+ {
+ // If the material of the checked block is anything other than
+ // air or foliage, skip this tile.
+ offset2++;
+ continue;
+ }
+ PIXBeginNamedEvent(0,"BasicTree placing block");
+ placeBlock(thisLevel, position[0], position[1], position[2], material, 0);
+ PIXEndNamedEvent();
+ offset2++;
+ }
+ offset1++;
+ }
+ PIXEndNamedEvent();
+}
+
+float BasicTree::treeShape(int y)
+{
+ // Take the y position relative to the base of the tree.
+ // Return the distance the foliage should be from the trunk axis.
+ // Return a negative number if foliage should not be created at this height.
+ // This method is intended for overriding in child classes, allowing
+ // different shaped trees.
+ // This method should return a consistent value for each y (don't randomize).
+ if (y < (((float) height) * 0.3)) return (float) -1.618;
+ float radius = ((float) height) / ((float) 2.0);
+ float adjacent = (((float) height) / ((float) 2.0)) - y;
+ float distance;
+ if (adjacent == 0) distance = radius;
+ else if (abs(adjacent) >= radius) distance = (float) 0.0;
+ else distance = (float) sqrt(pow(abs(radius), 2) - pow(abs(adjacent), 2));
+ // Alter this factor to change the overall width of the tree.
+ distance *= (float) 0.5;
+ return distance;
+}
+
+float BasicTree::foliageShape(int y)
+{
+ // Take the y position relative to the base of the foliage cluster.
+ // Return the radius of the cluster at this y
+ // Return a negative number if no foliage should be created at this level
+ // this method is intended for overriding in child classes, allowing
+ // foliage of different sizes and shapes.
+ if ((y < 0) || (y >= foliageHeight)) return (float) -1;
+ else if ((y == 0) || (y == (foliageHeight - 1))) return (float) 2;
+ else return (float) 3;
+}
+
+void BasicTree::foliageCluster(int x, int y, int z)
+{
+ PIXBeginNamedEvent(0,"BasicTree foliageCluster");
+ // Generate a cluster of foliage, with the base at x, y, z.
+ // The shape of the cluster is derived from foliageShape
+ // crossection is called to make each level.
+ int topy = y + foliageHeight;
+ int cury = topy - 1;
+ float radius;
+ // 4J Stu - Generate foliage from the top down so that we don't keep recalculating heightmaps
+ while (cury >= y)
+ {
+ radius = foliageShape(cury - y);
+ crossection(x, cury, z, radius, (byte) 1, Tile::leaves_Id);
+ cury--;
+ }
+ PIXEndNamedEvent();
+}
+
+void BasicTree::limb(int *start, int *end, int material)
+{
+ // Create a limb from the start position to the end position.
+ // Used for creating the branches and trunk.
+
+ // Populate delta, the difference between start and end for all three axies.
+ // Set primidx to the index with the largest overall distance traveled.
+ int delta[] = { 0, 0, 0 };
+ byte idx = 0;
+ byte primidx = 0;
+ while (idx < 3)
+ {
+ delta[idx] = end[idx] - start[idx];
+ if (abs(delta[idx]) > abs(delta[primidx]))
+ {
+ primidx = idx;
+ }
+ idx++;
+ }
+ // If the largest distance is zero, don't bother to do anything else.
+ if (delta[primidx] == 0) return;
+ // set up the other two axis indices.
+ byte secidx1 = axisConversionArray[primidx];
+ byte secidx2 = axisConversionArray[primidx + 3];
+ // primsign is digit 1 or -1 depending on whether the limb is headed
+ // along the positive or negative primidx axis.
+ char primsign;
+ if (delta[primidx] > 0) primsign = 1;
+ else primsign = -1;
+ // Initilize the per-step movement for the non-primary axies.
+ double secfac1 = ((double) delta[secidx1]) / ((double) delta[primidx]);
+ double secfac2 = ((double) delta[secidx2]) / ((double) delta[primidx]);
+ // Initialize the coordinates.
+ int coordinate[] = { 0, 0, 0 };
+ // Loop through each crossection along the primary axis, from start to end
+ int primoffset = 0;
+ int endoffset = delta[primidx] + primsign;
+ while (primoffset != endoffset)
+ {
+ coordinate[primidx] = Mth::floor(start[primidx] + primoffset + 0.5);
+ coordinate[secidx1] = Mth::floor(start[secidx1] + (primoffset * secfac1) + 0.5);
+ coordinate[secidx2] = Mth::floor(start[secidx2] + (primoffset * secfac2) + 0.5);
+
+ int dir = TreeTile::FACING_Y;
+ int xdiff = abs(coordinate[0] - start[0]);
+ int zdiff = abs(coordinate[2] - start[2]);
+ int maxdiff = max(xdiff, zdiff);
+
+ if (maxdiff > 0)
+ {
+ if (xdiff == maxdiff)
+ {
+ dir = TreeTile::FACING_X;
+ }
+ else if (zdiff == maxdiff)
+ {
+ dir = TreeTile::FACING_Z;
+ }
+ }
+ placeBlock(thisLevel, coordinate[0], coordinate[1], coordinate[2], material, dir);
+ primoffset += primsign;
+ }
+}
+
+void BasicTree::makeFoliage()
+{
+ // Create the tree foliage.
+ // Call foliageCluster at the correct locations
+ int idx = 0;
+ int finish = foliageCoordsLength;
+ while (idx < finish)
+ {
+ int x = foliageCoords[idx][0];
+ int y = foliageCoords[idx][1];
+ int z = foliageCoords[idx][2];
+ foliageCluster(x, y, z);
+ idx++;
+ }
+}
+
+bool BasicTree::trimBranches(int localY)
+{
+ // For larger trees, randomly "prune" the branches so there
+ // aren't too many.
+ // Return true if the branch should be created.
+ // This method is intended for overriding in child classes, allowing
+ // decent amounts of branches on very large trees.
+ // Can also be used to disable branches on some tree types, or
+ // make branches more sparse.
+ if (localY < (height * 0.2)) return false;
+ else return true;
+}
+
+void BasicTree::makeTrunk()
+{
+ // Create the trunk of the tree.
+ int x = origin[0];
+ int startY = origin[1];
+ int topY = origin[1] + trunkHeight;
+ int z = origin[2];
+ int startCoord[] = { x, startY, z };
+ int endCoord[] = { x, topY, z };
+ limb(startCoord, endCoord, Tile::treeTrunk_Id);
+ if (trunkWidth == 2)
+ {
+ startCoord[0] += 1;
+ endCoord[0] += 1;
+ limb(startCoord, endCoord, Tile::treeTrunk_Id);
+ startCoord[2] += 1;
+ endCoord[2] += 1;
+ limb(startCoord, endCoord, Tile::treeTrunk_Id);
+ startCoord[0] += -1;
+ endCoord[0] += -1;
+ limb(startCoord, endCoord, Tile::treeTrunk_Id);
+ }
+}
+
+void BasicTree::makeBranches()
+{
+ // Create the tree branches.
+ // Call trimBranches for each branch to see if you should create it.
+ // Call taperedLimb to the correct locations
+ int idx = 0;
+ int finish = foliageCoordsLength;
+ int baseCoord[] = { origin[0], origin[1], origin[2] };
+ while (idx < finish)
+ {
+ int *coordValues = foliageCoords[idx];
+ int endCoord[] = { coordValues[0], coordValues[1], coordValues[2] };
+ baseCoord[1] = coordValues[3];
+ int localY = baseCoord[1] - origin[1];
+ if (trimBranches(localY))
+ {
+ limb(baseCoord, endCoord, Tile::treeTrunk_Id);
+ }
+ idx++;
+ }
+}
+
+int BasicTree::checkLine(int *start, int *end)
+{
+ // Check from coordinates start to end (both inclusive) for blocks other than air and foliage
+ // If a block other than air and foliage is found, return the number of steps taken.
+ // If no block other than air and foliage is found, return -1.
+ // Examples:
+ // If the third block searched is stone, return 2
+ // If the first block searched is lava, return 0
+
+ int delta[] = { 0, 0, 0 };
+ byte idx = 0;
+ byte primidx = 0;
+ while (idx < 3)
+ {
+ delta[idx] = end[idx] - start[idx];
+ if (abs(delta[idx]) > abs(delta[primidx]))
+ {
+ primidx = idx;
+ }
+ idx++;
+ }
+ // If the largest distance is zero, don't bother to do anything else.
+ if (delta[primidx] == 0) return -1;
+ // set up the other two axis indices.
+ byte secidx1 = axisConversionArray[primidx];
+ byte secidx2 = axisConversionArray[primidx + 3];
+ // primsign is digit 1 or -1 depending on whether the limb is headed
+ // along the positive or negative primidx axis.
+ char primsign; // 4J Stu - Was byte, but we use in a sum below and byte=unsigned char so we were setting endoffset incorrectly
+ if (delta[primidx] > 0) primsign = 1;
+ else primsign = -1;
+ // Initilize the per-step movement for the non-primary axies.
+ double secfac1 = ((double) delta[secidx1]) / ((double) delta[primidx]);
+ double secfac2 = ((double) delta[secidx2]) / ((double) delta[primidx]);
+ // Initialize the coordinates.
+ int coordinate[] = { 0, 0, 0 };
+ // Loop through each crossection along the primary axis, from start to end
+ int primoffset = 0;
+ int endoffset = delta[primidx] + primsign;
+ int thismat;
+ while (primoffset != endoffset)
+ {
+ coordinate[primidx] = start[primidx] + primoffset;
+ coordinate[secidx1] = Mth::floor(start[secidx1] + (primoffset * secfac1));
+ coordinate[secidx2] = Mth::floor(start[secidx2] + (primoffset * secfac2));
+ thismat = thisLevel->getTile(coordinate[0], coordinate[1], coordinate[2]);
+ if (!((thismat == 0) || (thismat == Tile::leaves_Id)))
+ {
+ // If the material of the checked block is anything other than
+ // air or foliage, stop looking.
+ break;
+ }
+ primoffset += primsign;
+ }
+ // If you reached the end without finding anything, return -1.
+ if (primoffset == endoffset)
+ {
+ return -1;
+ }
+ // Otherwise, return the number of steps you took.
+ else
+ {
+ return abs(primoffset);
+ }
+}
+
+bool BasicTree::checkLocation()
+{
+ // Return true if the tree can be placed here.
+ // Return false if the tree can not be placed here.
+
+ // Examine the square under the trunk. Is it grass or dirt?
+ // If not, return false
+ // Examine center column for how tall the tree can be.
+ // If the checked height is shorter than height, but taller
+ // than 4, set the tree to the maximum height allowed.
+ // If the space is too short, return false.
+ int startPosition[] = { origin[0], origin[1], origin[2] };
+ int endPosition[] = { origin[0], origin[1] + height - 1, origin[2] };
+
+ // 4J Stu Added to stop tree features generating areas previously place by game rule generation
+ if(app.getLevelGenerationOptions() != NULL)
+ {
+ LevelGenerationOptions *levelGenOptions = app.getLevelGenerationOptions();
+ bool intersects = levelGenOptions->checkIntersects(startPosition[0], startPosition[1], startPosition[2], endPosition[0], endPosition[1], endPosition[2]);
+ if(intersects)
+ {
+ //app.DebugPrintf("Skipping reeds feature generation as it overlaps a game rule structure\n");
+ return false;
+ }
+ }
+
+ // Check the location it is resting on
+ int baseMaterial = thisLevel->getTile(origin[0], origin[1] - 1, origin[2]);
+ if (!((baseMaterial == 2) || (baseMaterial == 3)))
+ {
+ return false;
+ }
+ int allowedHeight = checkLine(startPosition, endPosition);
+ // If the set height is good, go with that
+ if (allowedHeight == -1)
+ {
+ return true;
+ }
+ // If the space is too short, tell the build to abort
+ else if (allowedHeight < 6)
+ {
+ return false;
+ }
+ // If the space is shorter than the set height, but not too short
+ // shorten the height, and tell the build to continue
+ else
+ {
+ height = allowedHeight;
+ //System.out.println("Shortened the tree");
+ return true;
+ }
+}
+
+void BasicTree::init(double heightInit, double widthInit, double foliageDensityInit)
+{
+ // all of the parameters should be from 0.0 to 1.0
+ // heightInit scales the maximum overall height of the tree (still randomizes height within the possible range)
+ // widthInit scales the maximum overall width of the tree (keep this above 0.3 or so)
+ // foliageDensityInit scales how many foliage clusters are created.
+ //
+ // Note, you can call "place" without calling "init".
+ // This is the same as calling init(1.0,1.0,1.0) and then calling place.
+ heightVariance = (int) (heightInit * 12);
+ if (heightInit > 0.5) foliageHeight = 5;
+ widthScale = widthInit;
+ foliageDensity = foliageDensityInit;
+}
+
+bool BasicTree::place(Level *level, Random *random, int x, int y, int z)
+{
+ // Note to Markus.
+ // currently the following fields are set randomly. If you like, make them
+ // parameters passed into "place".
+ //
+ // height: so the map generator can intelligently set the height of the tree,
+ // and make forests with large trees in the middle and smaller ones on the edges.
+
+ // Initialize the instance fields for the level and the seed.
+ thisLevel = level;
+ __int64 seed = random->nextLong();
+ rnd->setSeed(seed);
+ // Initialize the origin of the tree trunk
+ origin[0] = x;
+ origin[1] = y;
+ origin[2] = z;
+ // Sets the height. Take out this line if height is passed as a parameter
+ if (height == 0)
+ {
+ height = 5 + rnd->nextInt(heightVariance);
+ }
+ if (!(checkLocation()))
+ {
+ //System.out.println("Tree location failed");
+ return false;
+ }
+ PIXBeginNamedEvent(0, "Placing BasicTree");
+ //System.out.println("The height is");
+ //System.out.println(height);
+ //System.out.println("Trunk Height check done");
+ PIXBeginNamedEvent(0, "Preparing tree");
+ prepare();
+ PIXEndNamedEvent();
+ //System.out.println("Prepare done");
+ PIXBeginNamedEvent(0, "Making foliage");
+ makeFoliage();
+ PIXEndNamedEvent();
+ //System.out.println("Foliage done");
+ PIXBeginNamedEvent(0, "Making trunk");
+ makeTrunk();
+ PIXEndNamedEvent();
+ //System.out.println("Trunk done");
+ PIXBeginNamedEvent(0, "Making branches");
+ makeBranches();
+ PIXEndNamedEvent();
+ //System.out.println("Branches done");
+ PIXEndNamedEvent();
+ return true;
+} \ No newline at end of file