Improved island map generator.

Added documentation as well.

Untested.
This commit is contained in:
Harrison Deng 2020-04-20 16:27:15 -05:00
parent 6cf1f6dcc1
commit 44f183cca0

View File

@ -4,53 +4,81 @@ import java.util.Random;
import org.bukkit.util.noise.SimplexOctaveGenerator;
public class IslandMapGenerator {
import ca.recrown.islandsurvivalcraft.pathfinding.CoordinateValidatable;
import ca.recrown.islandsurvivalcraft.pathfinding.DepthFirstSearch;
public class IslandMapGenerator implements CoordinateValidatable {
private SimplexOctaveGenerator noiseGenerator;
private final int noiseOctaves = 8;
private final int islandGenerationPercent = 47;
private final float exaggerationFactor = 1.5f;
private final double noiseFrequency = 0.5D;
private final double noiseAmplitude = 0.5D;
private final int shoreSize = 4;
private final int maxHeightAboveWater = 8;
private final float shoreFactor = 0.065f;
private final int maxHeightAboveSea = 8;
private final int lowestSeaFloor = 16;
private final int seaLevel;
private final int shallowDepth;
private final DepthFirstSearch dfs;
public IslandMapGenerator(Random random, int seaLevel, int shallowDepth) {
this.noiseGenerator = new SimplexOctaveGenerator(random, noiseOctaves);
this.seaLevel = seaLevel;
this.shallowDepth = shallowDepth;
dfs = new DepthFirstSearch(this);
}
/**
* Considers this land if it is greater than sea level.
* @param worldX the X coordinate of location in world.
* @param worldZ the Z coordinate of location in world.
* @return
*/
public boolean isLand(int worldX, int worldZ) {
if (getBaseBlockHeight(worldX, worldZ) >= seaLevel) {
if (getIslandValue(worldX, worldZ) >= 0) {
return true;
}
return false;
}
/**
* Calculates if this is the edge of the island by checking if it's surrounding blocks are part of this island.
* @param worldX The world X coordinate.
* @param worldZ The world Z coordinate.
* @return true if it is the edge of the island and false if it isn't.
*/
public boolean isEdgeOfIsland(int worldX, int worldZ) {
return isIsland(worldX, worldZ) &&
(!isSameIsland(worldX, worldZ, worldX + 1, worldZ) ||
isSameIsland(worldX, worldZ, worldX - 1, worldZ) ||
isSameIsland(worldX, worldZ, worldX , worldZ + 1) ||
!(isSameIsland(worldX, worldZ, worldX + 1, worldZ) &&
isSameIsland(worldX, worldZ, worldX - 1, worldZ) &&
isSameIsland(worldX, worldZ, worldX , worldZ + 1) &&
isSameIsland(worldX, worldZ, worldX, worldZ - 1));
}
/**
* These coordinates are considered to be the shore if:
* It is a part of an island,
* within a square radius of shoreSize, there is an edge of the island.
* @param worldX the X coordinate of location in world.
* @param worldZ the Z coordinate of location in world.
* @return
*/
public boolean isShore(int worldX, int worldZ) {
if (!isIsland(worldX, worldZ)) return false;
for (int xPos = 0; xPos < shoreSize; xPos++) {
for (int zPos = 0; zPos < shoreSize; zPos++) {
if (isEdgeOfIsland(worldX + xPos, worldZ + zPos) || isEdgeOfIsland(worldX - xPos, worldZ - zPos)) {
if (isLand(worldX, worldZ) && getIslandValue(worldX, worldZ) <= shoreFactor) {
return true;
}
}
}
return false;
}
/**
* Considers these coordinates to be part of an island if:
* It is land, or if it is a shallow portion of land.
* @param worldX the X coordinate of location in world.
* @param worldZ the Y coordinate of location in world.
* @return true if these coordinates represent an island.
*/
public boolean isIsland(int worldX, int worldZ) {
if (isLand(worldX, worldZ) || isShallowPortion(worldX, worldZ)) {
return true;
@ -58,9 +86,16 @@ public class IslandMapGenerator {
return false;
}
/**
* This block is considered a shallow portion of the island:
* The block height is less than the sea level,
* and that the block height is greater than the sea level - the shallow depth.
* @param worldX the X coordinate of location in world.
* @param worldZ the Z coordinate of location in world.
* @return true if it is considered the shallow portion.
*/
public boolean isShallowPortion(int worldX, int worldZ) {
int blockHeight = getBaseBlockHeight(worldX, worldZ);
if (blockHeight < seaLevel && blockHeight >= (seaLevel - shallowDepth)) {
if (getIslandValue(worldX, worldZ) >= - (float) shallowDepth / (float) seaLevel) {
return true;
}
return true;
@ -68,34 +103,51 @@ public class IslandMapGenerator {
private double getTerrainValue(int worldX, int worldZ) {
double normalized = getNoiseValue(worldX, worldZ);
return Math.pow(normalized, exaggerationFactor);
return Math.pow(normalized, exaggerationFactor) * (islandGenerationPercent + seaLevel);
}
public double getNoiseValue(int worldX, int worldZ) {
return (noiseGenerator.noise(worldX, worldZ, noiseFrequency, noiseAmplitude, true) + 1D) / 2D;
}
public int getBaseBlockHeight(int worldX, int worldZ) {
return Math.min((int) getTerrainValue(worldX, worldZ) * (islandGenerationPercent + seaLevel), maxHeightAboveWater + seaLevel);
/***
* Island value will be 0 or positive if it is a part of an island.
* If less than, it is considered under the sea.
* Does not factor in a shallow depth.
* The value above zero is normalized to [0, 1].
* @param worldX the x world coordinate to obtain this value for.
* @param worldZ the z world coordinate to obtain this value for.
* @return a value representing the island at the given point.
*/
public float getIslandValue(int worldX, int worldZ) {
return (float) ((getTerrainValue(worldX, worldZ) - seaLevel) / (islandGenerationPercent));
}
public int getBaseTerrainHeight(int worldX, int worldZ) {
double terrainVal = getTerrainValue(worldX, worldZ);
return (int) Math.max(Math.min(terrainVal, maxHeightAboveSea + seaLevel), lowestSeaFloor);
}
/**
* Using DFS, determines if two given blocks are from the same island.
* Works by checking if a path can be drawn between these two.
* @param firstBlockX The x coord of the first block.
* @param firstBlockZ The z coord of the first block.
* @param secondBlockX The X coord of the first block.
* @param secondBlockZ The Z coord of the first block.
* @return true if the two blocks are on the same island, and false otherwise.
*/
public boolean isSameIsland(int firstBlockX, int firstBlockZ, int secondBlockX, int secondBlockZ) {
if (!isIsland(firstBlockX, firstBlockZ)) return false;
if (!isIsland(secondBlockX, secondBlockZ)) return false;
int xDirection = (secondBlockX - firstBlockX) / Math.abs(secondBlockX - firstBlockX);
for (int xPos = firstBlockX; xPos != secondBlockX; xPos += xDirection) {
if (!isIsland(xPos, firstBlockZ)) {
return false;
}
dfs.setStartNode(secondBlockX, secondBlockZ, firstBlockX, firstBlockZ);
boolean res = dfs.isConnected();
dfs.deleteTree();
return res;
}
int zDirection = (secondBlockZ - secondBlockZ) / Math.abs(secondBlockZ - secondBlockZ);
for (int zPos = firstBlockZ; zPos != secondBlockZ; zPos += zDirection) {
if (!isIsland(secondBlockX, zPos)) {
return false;
}
}
return true;
@Override
public boolean validate(int x, int y) {
return isIsland(x, x);
}
}