Terrain generator updates + Foliage work
This commit is contained in:
parent
c69caeeab9
commit
3948f1f921
@ -17,6 +17,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modelPath" : "Models/falloak1.fbx"
|
"modelPath" : "Models/falloak1.fbx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "Green Grass",
|
||||||
|
"tokens" : [
|
||||||
|
"AMBIENT"
|
||||||
|
],
|
||||||
|
"modelPath" : "Models/falloak1.fbx"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
19
assets/Data/voxelTypes.json
Normal file
19
assets/Data/voxelTypes.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"types" : [
|
||||||
|
{
|
||||||
|
"id" : 0,
|
||||||
|
"name" : "air"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id" : 1,
|
||||||
|
"name" : "dirt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id" : 2,
|
||||||
|
"name" : "grass",
|
||||||
|
"ambientFoliage" : [
|
||||||
|
"Grass"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
docs/TerrainEditing.txt
Normal file
20
docs/TerrainEditing.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
electrosphere.client.terrain.editing.TerrainEditing
|
||||||
|
- Client static interface for editing terrain
|
||||||
|
- The idea is that this provides functions you can call anywhere from client side to trigger a request to perform a terrain edit
|
||||||
|
|
||||||
|
Which leads to
|
||||||
|
|
||||||
|
electrosphere.server.terrain.editing.TerrainEditing
|
||||||
|
- Server utility functions for actually editing terrain
|
||||||
|
- Does the calculations of a real coordinate + radius to determine which cells to edit and how much
|
||||||
|
- This then updates the server terrain manager with edits via the VoxelCellManager interface
|
||||||
|
|
||||||
|
VoxelCellManager interface
|
||||||
|
- Provides an interface on top of DataCellManager to update terrain functions
|
||||||
|
- Makes functions that must be implemented on data cell manager so implementation specific to cell manager
|
||||||
|
- For GriddedDataCellManager, this uses a lock and updates values
|
||||||
|
- As values are updated, they should be send 1-by-1 over the network via individual update packets to the client
|
||||||
|
|
||||||
|
When client receives voxel update packet in ClientTerrainManager, it triggers the cell to update that specific drawcell
|
||||||
|
This should also update all ambient foliage
|
||||||
|
|
||||||
@ -13,7 +13,9 @@ import org.joml.Matrix4f;
|
|||||||
import org.joml.Quaterniond;
|
import org.joml.Quaterniond;
|
||||||
import org.joml.Vector3d;
|
import org.joml.Vector3d;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
import org.joml.Vector3i;
|
||||||
|
|
||||||
|
import electrosphere.client.terrain.cache.ChunkData;
|
||||||
import electrosphere.engine.Globals;
|
import electrosphere.engine.Globals;
|
||||||
import electrosphere.entity.Entity;
|
import electrosphere.entity.Entity;
|
||||||
import electrosphere.entity.EntityCreationUtils;
|
import electrosphere.entity.EntityCreationUtils;
|
||||||
@ -26,7 +28,7 @@ import electrosphere.renderer.buffer.ShaderAttribute;
|
|||||||
import electrosphere.renderer.buffer.HomogenousUniformBuffer.HomogenousBufferTypes;
|
import electrosphere.renderer.buffer.HomogenousUniformBuffer.HomogenousBufferTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages foliage (grass, small plants, etc) that should be shown, typically instanced
|
* Manages ambient foliage (grass, small plants, etc) that should be shown, typically instanced
|
||||||
*/
|
*/
|
||||||
public class ClientFoliageManager {
|
public class ClientFoliageManager {
|
||||||
|
|
||||||
@ -50,6 +52,8 @@ public class ClientFoliageManager {
|
|||||||
|
|
||||||
//FoliageCells that are active and have foliage that is being drawn
|
//FoliageCells that are active and have foliage that is being drawn
|
||||||
Set<FoliageCell> activeCells = new HashSet<FoliageCell>();
|
Set<FoliageCell> activeCells = new HashSet<FoliageCell>();
|
||||||
|
//map of position-based key to foliage cell at the position
|
||||||
|
Map<String,FoliageCell> locationCellMap = new HashMap<String,FoliageCell>();
|
||||||
//The maximum distance a cell can be away from the player before being destroyed
|
//The maximum distance a cell can be away from the player before being destroyed
|
||||||
static final float CELL_DISTANCE_MAX = 25f;
|
static final float CELL_DISTANCE_MAX = 25f;
|
||||||
//The maximum number of foliage cells
|
//The maximum number of foliage cells
|
||||||
@ -172,6 +176,16 @@ public class ClientFoliageManager {
|
|||||||
return new Quaterniond().rotationX(-Math.PI / 2.0f).rotateLocalY(Math.PI * placementRandomizer.nextFloat());
|
return new Quaterniond().rotationX(-Math.PI / 2.0f).rotateLocalY(Math.PI * placementRandomizer.nextFloat());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a key for a foliage cell in the localCellMap
|
||||||
|
* @param worldPosition The world position of the cell
|
||||||
|
* @param voxelPosition The voxel position of the cell
|
||||||
|
* @return The key for the cell
|
||||||
|
*/
|
||||||
|
private String getFoliageCellKey(Vector3i worldPosition, Vector3i voxelPosition){
|
||||||
|
return worldPosition.x + "_" + worldPosition.y + "_" + worldPosition.z + "_" + voxelPosition.x + "_" + voxelPosition.y + "_" + voxelPosition.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes an already created entity a drawable, instanced entity (client only) by backing it with an InstancedActor
|
* Makes an already created entity a drawable, instanced entity (client only) by backing it with an InstancedActor
|
||||||
@ -188,6 +202,62 @@ public class ClientFoliageManager {
|
|||||||
Globals.clientScene.registerEntityToTag(entity, EntityTags.DRAW_INSTANCED);
|
Globals.clientScene.registerEntityToTag(entity, EntityTags.DRAW_INSTANCED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates a chunk to see where foliage cells should be created or updated
|
||||||
|
* @param worldPos The world position of the chunk
|
||||||
|
*/
|
||||||
|
public void evaluateChunk(Vector3i worldPos){
|
||||||
|
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos);
|
||||||
|
for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){
|
||||||
|
//can't go to very top 'cause otherwise there would be no room to put grass
|
||||||
|
for(int y = 0; y < ChunkData.CHUNK_SIZE - 1; y++){
|
||||||
|
for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){
|
||||||
|
String key = getFoliageCellKey(worldPos, new Vector3i(x,y,z));
|
||||||
|
if(locationCellMap.get(key) != null){
|
||||||
|
//destroy if there's no longer ground or
|
||||||
|
//if the cell above is now occupied or
|
||||||
|
//if the lower cell is no longer supporting foliage
|
||||||
|
if(data.getWeight(new Vector3i(x,y,z)) <= 0 ||
|
||||||
|
data.getWeight(new Vector3i(x,y + 1,z)) > 0 ||
|
||||||
|
!typeSupportsFoliage(data.getType(new Vector3i(x,y,z)))){
|
||||||
|
//TODO: destroy
|
||||||
|
} else {
|
||||||
|
//TODO: evaluate if foliage is placed well
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//create if current is ground and above is air
|
||||||
|
if(
|
||||||
|
data.getWeight(new Vector3i(x,y,z)) > 0 &&
|
||||||
|
data.getWeight(new Vector3i(x,y + 1,z)) < 0 &&
|
||||||
|
typeSupportsFoliage(data.getType(new Vector3i(x,y,z)))
|
||||||
|
){
|
||||||
|
//create foliage cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//evaluate top cells if chunk above this one exists
|
||||||
|
ChunkData aboveData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0));
|
||||||
|
if(aboveData != null){
|
||||||
|
for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){
|
||||||
|
for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){
|
||||||
|
if(data.getWeight(new Vector3i(x,ChunkData.CHUNK_SIZE - 1,z)) > 0 && aboveData.getWeight(new Vector3i(x,0,z)) < 0){
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether the voxel type supports foliage or not
|
||||||
|
* @param type
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean typeSupportsFoliage(int type){
|
||||||
|
return Globals.gameConfigCurrent.getVoxelData().getTypeFromId(type).getAmbientFoliage() != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate all foliage cells to see if any should be deconstructed and any new ones should be created
|
* Evaluate all foliage cells to see if any should be deconstructed and any new ones should be created
|
||||||
@ -196,7 +266,7 @@ public class ClientFoliageManager {
|
|||||||
Vector3d playerPosition = EntityUtils.getPosition(Globals.playerEntity);
|
Vector3d playerPosition = EntityUtils.getPosition(Globals.playerEntity);
|
||||||
for(FoliageCell activeCell : activeCells){
|
for(FoliageCell activeCell : activeCells){
|
||||||
//if cell is outside of range of player, disable cell
|
//if cell is outside of range of player, disable cell
|
||||||
if(activeCell.position.distance(playerPosition) > CELL_DISTANCE_MAX){
|
if(Globals.clientWorldData.convertWorldToRealSpace(activeCell.worldPosition).distance(playerPosition) > CELL_DISTANCE_MAX){
|
||||||
//TODO: destroy cell
|
//TODO: destroy cell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package electrosphere.client.foliagemanager;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.joml.Vector3d;
|
import org.joml.Vector3d;
|
||||||
|
import org.joml.Vector3i;
|
||||||
|
|
||||||
import electrosphere.entity.Entity;
|
import electrosphere.entity.Entity;
|
||||||
|
|
||||||
@ -10,9 +11,12 @@ import electrosphere.entity.Entity;
|
|||||||
* Contains a set of foliage entities and groups them together.
|
* Contains a set of foliage entities and groups them together.
|
||||||
*/
|
*/
|
||||||
public class FoliageCell {
|
public class FoliageCell {
|
||||||
//position of the foliage cell
|
//position of the foliage cell in world coordinates
|
||||||
Vector3d position;
|
protected Vector3i worldPosition;
|
||||||
|
//position of the foliage cell in local coordinates
|
||||||
|
protected Vector3i localPosition;
|
||||||
//constituent entities
|
//constituent entities
|
||||||
Set<Entity> containedEntities;
|
protected Set<Entity> containedEntities;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -102,6 +102,19 @@ public class ClientWorldData {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a world space vector to a real space vector
|
||||||
|
* @param position The world space vector
|
||||||
|
* @return The real space vector
|
||||||
|
*/
|
||||||
|
public Vector3d convertWorldToRealSpace(Vector3i position){
|
||||||
|
return new Vector3d(
|
||||||
|
convertWorldToReal(position.x),
|
||||||
|
convertWorldToReal(position.y),
|
||||||
|
convertWorldToReal(position.z)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public Vector3i convertRealToVoxelSpace(Vector3d position){
|
public Vector3i convertRealToVoxelSpace(Vector3d position){
|
||||||
return new Vector3i(
|
return new Vector3i(
|
||||||
(int)Math.floor(position.x - convertChunkToRealSpace(convertRealToChunkSpace(position.x))),
|
(int)Math.floor(position.x - convertChunkToRealSpace(convertRealToChunkSpace(position.x))),
|
||||||
|
|||||||
@ -57,9 +57,7 @@ public class ClientSimulation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update foliage
|
//update foliage
|
||||||
if(Globals.clientFoliageManager != null){
|
Globals.clientFoliageManager.update();
|
||||||
Globals.clientFoliageManager.update();
|
|
||||||
}
|
|
||||||
//tally collidables and offset position accordingly
|
//tally collidables and offset position accordingly
|
||||||
// for(Entity currentCollidable : Globals.entityManager.getEntitiesWithTag(EntityTags.COLLIDABLE)){
|
// for(Entity currentCollidable : Globals.entityManager.getEntitiesWithTag(EntityTags.COLLIDABLE)){
|
||||||
// CollidableTree tree = CollidableTree.getCollidableTree(currentCollidable);
|
// CollidableTree tree = CollidableTree.getCollidableTree(currentCollidable);
|
||||||
|
|||||||
@ -173,6 +173,8 @@ public class DrawCellManager {
|
|||||||
drawable.add(targetKey);
|
drawable.add(targetKey);
|
||||||
//make drawable entity
|
//make drawable entity
|
||||||
keyCellMap.get(targetKey).generateDrawableEntity();
|
keyCellMap.get(targetKey).generateDrawableEntity();
|
||||||
|
//evaluate for foliage
|
||||||
|
Globals.clientFoliageManager.evaluateChunk(worldPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,6 +206,8 @@ public class DrawCellManager {
|
|||||||
// }
|
// }
|
||||||
keyCellMap.get(targetKey).destroy();
|
keyCellMap.get(targetKey).destroy();
|
||||||
keyCellMap.get(targetKey).generateDrawableEntity();
|
keyCellMap.get(targetKey).generateDrawableEntity();
|
||||||
|
//evaluate for foliage
|
||||||
|
Globals.clientFoliageManager.evaluateChunk(worldPos);
|
||||||
}
|
}
|
||||||
drawable.add(targetKey);
|
drawable.add(targetKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
import org.joml.Vector3i;
|
||||||
|
|
||||||
import electrosphere.client.scene.ClientWorldData;
|
import electrosphere.client.scene.ClientWorldData;
|
||||||
import electrosphere.client.terrain.cache.ChunkData;
|
import electrosphere.client.terrain.cache.ChunkData;
|
||||||
import electrosphere.client.terrain.cache.ClientTerrainCache;
|
import electrosphere.client.terrain.cache.ClientTerrainCache;
|
||||||
@ -127,9 +129,25 @@ public class ClientTerrainManager {
|
|||||||
Globals.drawCellManager.markUpdateable(worldX, worldY, worldZ);
|
Globals.drawCellManager.markUpdateable(worldX, worldY, worldZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the chunk data at a given world position
|
||||||
|
* @param worldX The x component of the world coordinate
|
||||||
|
* @param worldY The y component of the world coordinate
|
||||||
|
* @param worldZ The z component of the world coordinate
|
||||||
|
* @return The chunk data if it exists, otherwise null
|
||||||
|
*/
|
||||||
public ChunkData getChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
|
public ChunkData getChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
|
||||||
return terrainCache.getSubChunkDataAtPoint(worldX, worldY, worldZ);
|
return terrainCache.getSubChunkDataAtPoint(worldX, worldY, worldZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the chunk data at a given world position
|
||||||
|
* @param worldPos The world position as a joml vector
|
||||||
|
* @return The chunk data if it exists, otherwise null
|
||||||
|
*/
|
||||||
|
public ChunkData getChunkDataAtWorldPoint(Vector3i worldPos){
|
||||||
|
return terrainCache.getSubChunkDataAtPoint(worldPos.x, worldPos.y, worldPos.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -378,12 +378,9 @@ public class Globals {
|
|||||||
clientHitboxManager = new HitboxManager();
|
clientHitboxManager = new HitboxManager();
|
||||||
//ai manager
|
//ai manager
|
||||||
aiManager = new AIManager();
|
aiManager = new AIManager();
|
||||||
//data cell manager
|
//realm & data cell manager
|
||||||
realmManager = new RealmManager();
|
realmManager = new RealmManager();
|
||||||
// dataCellManager = new DataCellManager();
|
|
||||||
// griddedDataCellManager = new GriddedDataCellManager();
|
|
||||||
entityDataCellMapper = new EntityDataCellMapper();
|
entityDataCellMapper = new EntityDataCellMapper();
|
||||||
// dataCellLocationResolver = new DataCellLocationResolver();
|
|
||||||
//nav mesh manager
|
//nav mesh manager
|
||||||
navMeshManager = new NavMeshManager();
|
navMeshManager = new NavMeshManager();
|
||||||
//terrain
|
//terrain
|
||||||
|
|||||||
@ -136,7 +136,7 @@ public class Main {
|
|||||||
|
|
||||||
|
|
||||||
//debug: create terrain/world viewer
|
//debug: create terrain/world viewer
|
||||||
// TerrainViewer.runViewer();
|
TerrainViewer.runViewer();
|
||||||
|
|
||||||
//create the drawing context
|
//create the drawing context
|
||||||
if(Globals.RUN_CLIENT && !Globals.HEADLESS){
|
if(Globals.RUN_CLIENT && !Globals.HEADLESS){
|
||||||
|
|||||||
@ -80,6 +80,8 @@ public class ClientLoading {
|
|||||||
Globals.controlHandler.setHandlerState(ControlHandler.ControlsState.NO_INPUT);
|
Globals.controlHandler.setHandlerState(ControlHandler.ControlsState.NO_INPUT);
|
||||||
//initialize the "real" objects simulation
|
//initialize the "real" objects simulation
|
||||||
initClientSimulation();
|
initClientSimulation();
|
||||||
|
//init foliage manager
|
||||||
|
initFoliageManager();
|
||||||
//initialize the cell manager (client)
|
//initialize the cell manager (client)
|
||||||
initDrawCellManager();
|
initDrawCellManager();
|
||||||
//initialize the basic graphical entities of the world (skybox, camera)
|
//initialize the basic graphical entities of the world (skybox, camera)
|
||||||
@ -90,8 +92,6 @@ public class ClientLoading {
|
|||||||
setSimulationsToReady();
|
setSimulationsToReady();
|
||||||
//init culling manager and other graphics-focused non-simulation items
|
//init culling manager and other graphics-focused non-simulation items
|
||||||
initEntityCullingManager();
|
initEntityCullingManager();
|
||||||
//init foliage manager
|
|
||||||
initFoliageManager();
|
|
||||||
//hide cursor
|
//hide cursor
|
||||||
Globals.controlHandler.hideMouse();
|
Globals.controlHandler.hideMouse();
|
||||||
//make loading window disappear
|
//make loading window disappear
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import electrosphere.game.data.object.type.model.ObjectTypeLoader;
|
|||||||
import electrosphere.game.data.object.type.model.ObjectTypeMap;
|
import electrosphere.game.data.object.type.model.ObjectTypeMap;
|
||||||
import electrosphere.game.data.projectile.ProjectileTypeHolder;
|
import electrosphere.game.data.projectile.ProjectileTypeHolder;
|
||||||
import electrosphere.game.data.structure.type.model.StructureTypeMap;
|
import electrosphere.game.data.structure.type.model.StructureTypeMap;
|
||||||
|
import electrosphere.game.data.voxel.VoxelData;
|
||||||
import electrosphere.game.server.race.model.RaceMap;
|
import electrosphere.game.server.race.model.RaceMap;
|
||||||
import electrosphere.game.server.symbolism.model.SymbolMap;
|
import electrosphere.game.server.symbolism.model.SymbolMap;
|
||||||
import electrosphere.util.FileUtils;
|
import electrosphere.util.FileUtils;
|
||||||
@ -32,6 +33,8 @@ public class Config {
|
|||||||
SymbolMap symbolMap;
|
SymbolMap symbolMap;
|
||||||
RaceMap raceMap;
|
RaceMap raceMap;
|
||||||
ProjectileTypeHolder projectileTypeHolder;
|
ProjectileTypeHolder projectileTypeHolder;
|
||||||
|
//data about every voxel type
|
||||||
|
VoxelData voxelData;
|
||||||
|
|
||||||
public static Config loadDefaultConfig(){
|
public static Config loadDefaultConfig(){
|
||||||
Config config = new Config();
|
Config config = new Config();
|
||||||
@ -42,6 +45,7 @@ public class Config {
|
|||||||
config.objectTypeLoader = loadObjectTypes("Data/objects.json");
|
config.objectTypeLoader = loadObjectTypes("Data/objects.json");
|
||||||
config.symbolMap = FileUtils.loadObjectFromAssetPath("Data/symbolism.json", SymbolMap.class);
|
config.symbolMap = FileUtils.loadObjectFromAssetPath("Data/symbolism.json", SymbolMap.class);
|
||||||
config.raceMap = FileUtils.loadObjectFromAssetPath("Data/races.json", RaceMap.class);
|
config.raceMap = FileUtils.loadObjectFromAssetPath("Data/races.json", RaceMap.class);
|
||||||
|
config.voxelData = FileUtils.loadObjectFromAssetPath("Data/voxelTypes.json", VoxelData.class);
|
||||||
config.projectileTypeHolder = FileUtils.loadObjectFromAssetPath("Data/projectile.json", ProjectileTypeHolder.class);
|
config.projectileTypeHolder = FileUtils.loadObjectFromAssetPath("Data/projectile.json", ProjectileTypeHolder.class);
|
||||||
config.projectileTypeHolder.init();
|
config.projectileTypeHolder.init();
|
||||||
return config;
|
return config;
|
||||||
@ -139,5 +143,9 @@ public class Config {
|
|||||||
public ProjectileTypeHolder getProjectileMap(){
|
public ProjectileTypeHolder getProjectileMap(){
|
||||||
return projectileTypeHolder;
|
return projectileTypeHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VoxelData getVoxelData(){
|
||||||
|
return voxelData;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,17 +4,26 @@ import electrosphere.game.data.foliage.type.FoliageType;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* The map of all types of foliage in the game
|
||||||
* @author amaterasu
|
|
||||||
*/
|
*/
|
||||||
public class FoliageTypeMap {
|
public class FoliageTypeMap {
|
||||||
|
|
||||||
|
//The list of all foliage types
|
||||||
List<FoliageType> foliageList;
|
List<FoliageType> foliageList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of all foliage types
|
||||||
|
* @return The list of all foliage types
|
||||||
|
*/
|
||||||
public List<FoliageType> getFoliageList() {
|
public List<FoliageType> getFoliageList() {
|
||||||
return foliageList;
|
return foliageList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a foliage type by its name
|
||||||
|
* @param name The name of the foliage type
|
||||||
|
* @return The type object
|
||||||
|
*/
|
||||||
public FoliageType getFoliage(String name){
|
public FoliageType getFoliage(String name){
|
||||||
for(FoliageType foliage : foliageList){
|
for(FoliageType foliage : foliageList){
|
||||||
if(foliage.getName().matches(name)){
|
if(foliage.getName().matches(name)){
|
||||||
|
|||||||
47
src/main/java/electrosphere/game/data/voxel/VoxelData.java
Normal file
47
src/main/java/electrosphere/game/data/voxel/VoxelData.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package electrosphere.game.data.voxel;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all voxel types in game
|
||||||
|
*/
|
||||||
|
public class VoxelData {
|
||||||
|
//The set of all voxel types
|
||||||
|
Set<VoxelType> types;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all voxel types
|
||||||
|
* @return The set of all voxel types
|
||||||
|
*/
|
||||||
|
public Set<VoxelType> getTypes(){
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the voxel type by its name, or null if that type does not exist
|
||||||
|
* @param name The name of the voxel type
|
||||||
|
* @return The voxel type or null
|
||||||
|
*/
|
||||||
|
public VoxelType getTypeFromName(String name){
|
||||||
|
for(VoxelType type : types){
|
||||||
|
if(type.name.contains(name)){
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the voxel type by its id, or null if that type does not exist
|
||||||
|
* @param id The id of the voxel type
|
||||||
|
* @return The voxel type or null
|
||||||
|
*/
|
||||||
|
public VoxelType getTypeFromId(int id){
|
||||||
|
for(VoxelType type : types){
|
||||||
|
if(type.id == id){
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/main/java/electrosphere/game/data/voxel/VoxelType.java
Normal file
39
src/main/java/electrosphere/game/data/voxel/VoxelType.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package electrosphere.game.data.voxel;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data about a particular type of voxel
|
||||||
|
*/
|
||||||
|
public class VoxelType {
|
||||||
|
//the id of this voxel type
|
||||||
|
int id;
|
||||||
|
//the name of the type
|
||||||
|
String name;
|
||||||
|
//any ambient foliage that can be placed on this voxel type
|
||||||
|
Set<String> ambientFoliage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the id of the voxel type
|
||||||
|
* @return The id
|
||||||
|
*/
|
||||||
|
public int getId(){
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the name of the voxel type
|
||||||
|
* @return The name
|
||||||
|
*/
|
||||||
|
public String getName(){
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the names of all ambient foliage that can be placed on this voxel type
|
||||||
|
* @return The set of names
|
||||||
|
*/
|
||||||
|
public Set<String> getAmbientFoliage(){
|
||||||
|
return ambientFoliage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -874,8 +874,13 @@ public class TerrainInterpolator {
|
|||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
public static float[][] getBicubicInterpolatedChunk(float[][] macroValues, long[][] randomizerValues, int dynamicInterpolationRatio, float randomDampener){
|
* Gets the bicubic interpolation of an array of 5 x 5 values to an array of (dynamicInterpolationRatio + 1) x (dynamicInterpolationRatio + 1)
|
||||||
|
* @param macroValues The array of values to sample from
|
||||||
|
* @param dynamicInterpolationRatio The interpolation ratio
|
||||||
|
* @return The interpolated array
|
||||||
|
*/
|
||||||
|
public static float[][] getBicubicInterpolatedChunk(float[][] macroValues, int dynamicInterpolationRatio){
|
||||||
float[][] rVal = new float[dynamicInterpolationRatio + 1][dynamicInterpolationRatio + 1];
|
float[][] rVal = new float[dynamicInterpolationRatio + 1][dynamicInterpolationRatio + 1];
|
||||||
|
|
||||||
float[][] subValues = new float[4][4];
|
float[][] subValues = new float[4][4];
|
||||||
@ -894,14 +899,6 @@ public class TerrainInterpolator {
|
|||||||
//
|
//
|
||||||
//Inbetween phase 1
|
//Inbetween phase 1
|
||||||
//
|
//
|
||||||
long phase1Randomizer =
|
|
||||||
randomizerValues[0][0] +
|
|
||||||
randomizerValues[0][1] +
|
|
||||||
randomizerValues[0][2] +
|
|
||||||
randomizerValues[0][3];
|
|
||||||
|
|
||||||
Random randomizer = new Random(phase1Randomizer);
|
|
||||||
|
|
||||||
float a0 = subValues[0][3] - subValues[0][2] - subValues[0][0] + subValues[0][1];
|
float a0 = subValues[0][3] - subValues[0][2] - subValues[0][0] + subValues[0][1];
|
||||||
float a1 = subValues[0][0] - subValues[0][1] - a0;
|
float a1 = subValues[0][0] - subValues[0][1] - a0;
|
||||||
float a2 = subValues[0][2] - subValues[0][0];
|
float a2 = subValues[0][2] - subValues[0][0];
|
||||||
@ -909,7 +906,7 @@ public class TerrainInterpolator {
|
|||||||
for(int i = 0; i < dynamicInterpolationRatio + 1; i++){
|
for(int i = 0; i < dynamicInterpolationRatio + 1; i++){
|
||||||
float x = (float)i/(float)dynamicInterpolationRatio;
|
float x = (float)i/(float)dynamicInterpolationRatio;
|
||||||
float x2 = x * x;
|
float x2 = x * x;
|
||||||
inbetweenStage[0][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3 + randomizer.nextFloat() * randomDampener;
|
inbetweenStage[0][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -917,14 +914,6 @@ public class TerrainInterpolator {
|
|||||||
//Inbetween phase 2
|
//Inbetween phase 2
|
||||||
//
|
//
|
||||||
|
|
||||||
phase1Randomizer =
|
|
||||||
randomizerValues[1][0] +
|
|
||||||
randomizerValues[1][1] +
|
|
||||||
randomizerValues[1][2] +
|
|
||||||
randomizerValues[1][3];
|
|
||||||
|
|
||||||
randomizer = new Random(phase1Randomizer);
|
|
||||||
|
|
||||||
a0 = subValues[1][3] - subValues[1][2] - subValues[1][0] + subValues[1][1];
|
a0 = subValues[1][3] - subValues[1][2] - subValues[1][0] + subValues[1][1];
|
||||||
a1 = subValues[1][0] - subValues[1][1] - a0;
|
a1 = subValues[1][0] - subValues[1][1] - a0;
|
||||||
a2 = subValues[1][2] - subValues[1][0];
|
a2 = subValues[1][2] - subValues[1][0];
|
||||||
@ -932,7 +921,7 @@ public class TerrainInterpolator {
|
|||||||
for(int i = 0; i < dynamicInterpolationRatio + 1; i++){
|
for(int i = 0; i < dynamicInterpolationRatio + 1; i++){
|
||||||
float x = (float)i/(float)dynamicInterpolationRatio;
|
float x = (float)i/(float)dynamicInterpolationRatio;
|
||||||
float x2 = x * x;
|
float x2 = x * x;
|
||||||
inbetweenStage[1][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3 + randomizer.nextFloat() * randomDampener;
|
inbetweenStage[1][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -940,14 +929,6 @@ public class TerrainInterpolator {
|
|||||||
//Inbetween phase 3
|
//Inbetween phase 3
|
||||||
//
|
//
|
||||||
|
|
||||||
phase1Randomizer =
|
|
||||||
randomizerValues[2][0] +
|
|
||||||
randomizerValues[2][1] +
|
|
||||||
randomizerValues[2][2] +
|
|
||||||
randomizerValues[2][3];
|
|
||||||
|
|
||||||
randomizer = new Random(phase1Randomizer);
|
|
||||||
|
|
||||||
a0 = subValues[2][3] - subValues[2][2] - subValues[2][0] + subValues[2][1];
|
a0 = subValues[2][3] - subValues[2][2] - subValues[2][0] + subValues[2][1];
|
||||||
a1 = subValues[2][0] - subValues[2][1] - a0;
|
a1 = subValues[2][0] - subValues[2][1] - a0;
|
||||||
a2 = subValues[2][2] - subValues[2][0];
|
a2 = subValues[2][2] - subValues[2][0];
|
||||||
@ -955,7 +936,7 @@ public class TerrainInterpolator {
|
|||||||
for(int i = 0; i < dynamicInterpolationRatio + 1; i++){
|
for(int i = 0; i < dynamicInterpolationRatio + 1; i++){
|
||||||
float x = (float)i/(float)dynamicInterpolationRatio;
|
float x = (float)i/(float)dynamicInterpolationRatio;
|
||||||
float x2 = x * x;
|
float x2 = x * x;
|
||||||
inbetweenStage[2][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3 + randomizer.nextFloat() * randomDampener;
|
inbetweenStage[2][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -964,14 +945,6 @@ public class TerrainInterpolator {
|
|||||||
//Inbetween phase 4
|
//Inbetween phase 4
|
||||||
//
|
//
|
||||||
|
|
||||||
phase1Randomizer =
|
|
||||||
randomizerValues[3][0] +
|
|
||||||
randomizerValues[3][1] +
|
|
||||||
randomizerValues[3][2] +
|
|
||||||
randomizerValues[3][3];
|
|
||||||
|
|
||||||
randomizer = new Random(phase1Randomizer);
|
|
||||||
|
|
||||||
a0 = subValues[3][3] - subValues[3][2] - subValues[3][0] + subValues[3][1];
|
a0 = subValues[3][3] - subValues[3][2] - subValues[3][0] + subValues[3][1];
|
||||||
a1 = subValues[3][0] - subValues[3][1] - a0;
|
a1 = subValues[3][0] - subValues[3][1] - a0;
|
||||||
a2 = subValues[3][2] - subValues[3][0];
|
a2 = subValues[3][2] - subValues[3][0];
|
||||||
@ -979,7 +952,7 @@ public class TerrainInterpolator {
|
|||||||
for(int i = 0; i < dynamicInterpolationRatio + 1; i++){
|
for(int i = 0; i < dynamicInterpolationRatio + 1; i++){
|
||||||
float x = (float)i/(float)dynamicInterpolationRatio;
|
float x = (float)i/(float)dynamicInterpolationRatio;
|
||||||
float x2 = x * x;
|
float x2 = x * x;
|
||||||
inbetweenStage[3][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3 + randomizer.nextFloat() * randomDampener;
|
inbetweenStage[3][i] = a0 * x * x2 + a1 * x2 + a2 * x + a3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -989,14 +962,6 @@ public class TerrainInterpolator {
|
|||||||
|
|
||||||
|
|
||||||
for(int x = 0; x < dynamicInterpolationRatio + 1; x++){
|
for(int x = 0; x < dynamicInterpolationRatio + 1; x++){
|
||||||
// long phase2Randomizer =
|
|
||||||
// randomizerValues[3][0] +
|
|
||||||
// randomizerValues[3][1] +
|
|
||||||
// randomizerValues[3][2] +
|
|
||||||
// randomizerValues[3][3];
|
|
||||||
|
|
||||||
// randomizer = new Random(phase2Randomizer);
|
|
||||||
|
|
||||||
a0 = inbetweenStage[3][x] - inbetweenStage[2][x] - inbetweenStage[0][x] + inbetweenStage[1][x];
|
a0 = inbetweenStage[3][x] - inbetweenStage[2][x] - inbetweenStage[0][x] + inbetweenStage[1][x];
|
||||||
a1 = inbetweenStage[0][x] - inbetweenStage[1][x] - a0;
|
a1 = inbetweenStage[0][x] - inbetweenStage[1][x] - a0;
|
||||||
a2 = inbetweenStage[2][x] - inbetweenStage[0][x];
|
a2 = inbetweenStage[2][x] - inbetweenStage[0][x];
|
||||||
@ -1004,8 +969,7 @@ public class TerrainInterpolator {
|
|||||||
for(int y = 0; y < dynamicInterpolationRatio + 1; y++){
|
for(int y = 0; y < dynamicInterpolationRatio + 1; y++){
|
||||||
float i = (float)y/(float)dynamicInterpolationRatio;
|
float i = (float)y/(float)dynamicInterpolationRatio;
|
||||||
float i2 = i * i;
|
float i2 = i * i;
|
||||||
rVal[y][x] = a0 * i * i2 + a1 * i2 + a2 * i + a3 + randomizer.nextFloat() * randomDampener;
|
rVal[y][x] = a0 * i * i2 + a1 * i2 + a2 * i + a3;
|
||||||
// rVal[x][y] = i * inbetweenStage[1][x] + (1.0f - i) * inbetweenStage[2][x];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ public class ServerContentManager {
|
|||||||
if(!Globals.serverWorldData.isArena()){ //in other words, if not arena mode
|
if(!Globals.serverWorldData.isArena()){ //in other words, if not arena mode
|
||||||
//if on disk (has already been generated)
|
//if on disk (has already been generated)
|
||||||
//else create from scratch
|
//else create from scratch
|
||||||
EnvironmentGenerator.generatePlains(cell, worldPos, Globals.serverTerrainManager.getRandomizerAtPoint(worldPos.x, worldPos.z));
|
EnvironmentGenerator.generatePlains(cell, worldPos, 0);
|
||||||
}
|
}
|
||||||
cell.setNavMesh(
|
cell.setNavMesh(
|
||||||
NavMeshUtils.createMeshFromChunk(Globals.serverTerrainManager.getChunk(
|
NavMeshUtils.createMeshFromChunk(Globals.serverTerrainManager.getChunk(
|
||||||
|
|||||||
@ -0,0 +1,425 @@
|
|||||||
|
package electrosphere.server.terrain.generation;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
import electrosphere.game.terrain.processing.TerrainInterpolator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an erosion simulation that expands the heightmap and simulates drainage across the world
|
||||||
|
*/
|
||||||
|
public class ErosionSimulation {
|
||||||
|
|
||||||
|
//The number of threads to farm simulation chunks out to
|
||||||
|
private static final int NUMBER_THREADS = 16;
|
||||||
|
|
||||||
|
|
||||||
|
//The initial heightmap passed into the simulation
|
||||||
|
private float[][] startHeightmap;
|
||||||
|
|
||||||
|
//The actually valid data is flipped between primaryHeightmap and alternateHeightmap as the simulation runs
|
||||||
|
//so that no more than one allocation has to happen. isStartHeightmap tracks which one contains the
|
||||||
|
//valid data.
|
||||||
|
|
||||||
|
//The primary data heightmap
|
||||||
|
private float[][] primaryHeightmap;
|
||||||
|
//The second array to hold values
|
||||||
|
private float[][] alternateHeightmap;
|
||||||
|
//Controls which heightmap contains the hot data
|
||||||
|
private boolean isPrimaryHeightmap = true;
|
||||||
|
|
||||||
|
//Keeps track of how much water is in a given location
|
||||||
|
private float[][] primaryHydrationMap;
|
||||||
|
private float[][] alternateHydrationMap;
|
||||||
|
|
||||||
|
//The height at which the ocean begins. No erosion simulation will happen below this point.
|
||||||
|
private float oceanLevel;
|
||||||
|
|
||||||
|
//The size of the chunks of simulation that are created
|
||||||
|
private int interpolationRatio;
|
||||||
|
|
||||||
|
//Random for seeding worker threads
|
||||||
|
Random rand;
|
||||||
|
|
||||||
|
//threadpool for the step phase
|
||||||
|
ThreadPoolExecutor threadPool;
|
||||||
|
|
||||||
|
protected ErosionSimulation(float[][] heightmap, float oceanLevel, int interpolationRatio, long randomSeed){
|
||||||
|
this.interpolationRatio = interpolationRatio;
|
||||||
|
this.startHeightmap = heightmap;
|
||||||
|
this.primaryHeightmap = new float[heightmap.length * interpolationRatio][heightmap[0].length * interpolationRatio];
|
||||||
|
this.alternateHeightmap = new float[heightmap.length * interpolationRatio][heightmap[0].length * interpolationRatio];
|
||||||
|
this.primaryHydrationMap = new float[heightmap.length * interpolationRatio][heightmap[0].length * interpolationRatio];
|
||||||
|
this.alternateHydrationMap = new float[heightmap.length * interpolationRatio][heightmap[0].length * interpolationRatio];
|
||||||
|
this.oceanLevel = oceanLevel;
|
||||||
|
this.rand = new Random(randomSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the erosion simulation
|
||||||
|
*/
|
||||||
|
protected void simulate(){
|
||||||
|
setup();
|
||||||
|
float totalHydration = getTotalHydration();
|
||||||
|
float waterLevelRatio = totalHydration / (this.primaryHydrationMap.length * this.primaryHydrationMap[0].length);
|
||||||
|
while(totalHydration > 0){
|
||||||
|
CountDownLatch latch = new CountDownLatch(this.startHeightmap.length * this.startHeightmap[0].length);
|
||||||
|
for(int x = 0; x < this.startHeightmap.length; x++){
|
||||||
|
for(int y = 0; y < this.startHeightmap[0].length; y++){
|
||||||
|
//queue location
|
||||||
|
ErosionJob job = new ErosionJob(
|
||||||
|
primaryHeightmap,
|
||||||
|
alternateHeightmap,
|
||||||
|
primaryHydrationMap,
|
||||||
|
alternateHydrationMap,
|
||||||
|
isPrimaryHeightmap,
|
||||||
|
new Vector(x,y),
|
||||||
|
interpolationRatio,
|
||||||
|
oceanLevel,
|
||||||
|
waterLevelRatio,
|
||||||
|
rand.nextLong(),
|
||||||
|
latch
|
||||||
|
);
|
||||||
|
threadPool.submit(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//await all jobs
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
totalHydration = getTotalHydration();
|
||||||
|
waterLevelRatio = totalHydration / (this.primaryHydrationMap.length * this.primaryHydrationMap[0].length);
|
||||||
|
//flip primary map
|
||||||
|
isPrimaryHeightmap = !isPrimaryHeightmap;
|
||||||
|
System.out.println(totalHydration + " - " + waterLevelRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the simulation
|
||||||
|
*/
|
||||||
|
private void setup(){
|
||||||
|
threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(NUMBER_THREADS);
|
||||||
|
//interpolate start heightmap into full heightmap
|
||||||
|
int sampleCount = 25;
|
||||||
|
int[] xSampleOffset = new int[]{
|
||||||
|
-2,-1,0,1,2,
|
||||||
|
-2,-1,0,1,2,
|
||||||
|
-2,-1,0,1,2,
|
||||||
|
-2,-1,0,1,2,
|
||||||
|
-2,-1,0,1,2,
|
||||||
|
};
|
||||||
|
int[] ySampleOffset = new int[]{
|
||||||
|
-2,-2,-2,-2,-2,
|
||||||
|
-1,-1,-1,-1,-1,
|
||||||
|
0,0,0,0,0,
|
||||||
|
1,1,1,1,1,
|
||||||
|
2,2,2,2,2,
|
||||||
|
};
|
||||||
|
for(int x = 0; x < startHeightmap.length; x++){
|
||||||
|
for(int y = 0; y < startHeightmap[0].length; y++){
|
||||||
|
//get the array of samples
|
||||||
|
float[][] sample = new float[5][5];
|
||||||
|
for(int i = 0; i < sampleCount; i++){
|
||||||
|
if(x + xSampleOffset[i] >= 0 && x + xSampleOffset[i] < startHeightmap.length &&
|
||||||
|
y + ySampleOffset[i] >= 0 && y + ySampleOffset[i] < startHeightmap[0].length){
|
||||||
|
//have to add 2 to xSampleOffset and ySampleOffset to get accurate position in sample array
|
||||||
|
sample[xSampleOffset[i] + 2][ySampleOffset[i] + 2] = startHeightmap[x + xSampleOffset[i]][y + ySampleOffset[i]];
|
||||||
|
} else {
|
||||||
|
sample[xSampleOffset[i] + 2][ySampleOffset[i] + 2] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float[][] interpolatedValues = TerrainInterpolator.getBicubicInterpolatedChunk(sample, interpolationRatio);
|
||||||
|
for(int m = 0; m < interpolationRatio; m++){
|
||||||
|
for(int n = 0; n < interpolationRatio; n++){
|
||||||
|
primaryHeightmap[x * interpolationRatio + m][y * interpolationRatio + n] = interpolatedValues[m][n];
|
||||||
|
alternateHeightmap[x * interpolationRatio + m][y * interpolationRatio + n] = interpolatedValues[m][n];
|
||||||
|
//seed initial hydration map
|
||||||
|
primaryHydrationMap[x * interpolationRatio + m][y * interpolationRatio + n] = 1;
|
||||||
|
alternateHydrationMap[x * interpolationRatio + m][y * interpolationRatio + n] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the data resulting from the erosion simulation
|
||||||
|
* @return The data
|
||||||
|
*/
|
||||||
|
protected float[][] getData(){
|
||||||
|
if(isPrimaryHeightmap){
|
||||||
|
return primaryHeightmap;
|
||||||
|
} else {
|
||||||
|
return alternateHeightmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total hydration of the currently active map
|
||||||
|
* @return The total hydration
|
||||||
|
*/
|
||||||
|
private float getTotalHydration(){
|
||||||
|
float sum = 0;
|
||||||
|
float highestElevation = 0;
|
||||||
|
float highestHydration = 0;
|
||||||
|
if(isPrimaryHeightmap){
|
||||||
|
for(int x = 0; x < primaryHydrationMap.length; x++){
|
||||||
|
for(int y = 0; y < primaryHydrationMap[0].length; y++){
|
||||||
|
sum = sum + primaryHydrationMap[x][y];
|
||||||
|
if(primaryHeightmap[x][y] > highestElevation){
|
||||||
|
highestElevation = primaryHeightmap[x][y];
|
||||||
|
}
|
||||||
|
if(primaryHydrationMap[x][y] > highestHydration){
|
||||||
|
highestHydration = primaryHydrationMap[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(int x = 0; x < alternateHydrationMap.length; x++){
|
||||||
|
for(int y = 0; y < alternateHydrationMap[0].length; y++){
|
||||||
|
sum = sum + alternateHydrationMap[x][y];
|
||||||
|
if(alternateHeightmap[x][y] > highestElevation){
|
||||||
|
highestElevation = alternateHeightmap[x][y];
|
||||||
|
}
|
||||||
|
if(alternateHydrationMap[x][y] > highestHydration){
|
||||||
|
highestHydration = alternateHydrationMap[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("Highest elev: " + highestElevation);
|
||||||
|
System.out.println("Highest hydra: " + highestHydration);
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A runnable job of simulation erosion on a single chunk
|
||||||
|
*/
|
||||||
|
static class ErosionJob implements Runnable {
|
||||||
|
|
||||||
|
static final int MAX_HYDRATION = 25;
|
||||||
|
|
||||||
|
float primaryHeightmap[][];
|
||||||
|
float alternateHeightmap[][];
|
||||||
|
float[][] primaryHydrationMap;
|
||||||
|
float[][] alternateHydrationMap;
|
||||||
|
boolean usePrimaryMaps;
|
||||||
|
Vector targetLocation;
|
||||||
|
int interpolationRatio;
|
||||||
|
float oceanLevel;
|
||||||
|
float waterLevelRatio;
|
||||||
|
Random rand;
|
||||||
|
CountDownLatch latch;
|
||||||
|
|
||||||
|
protected ErosionJob(
|
||||||
|
float[][] primaryHeightmap,
|
||||||
|
float[][] alternateHeightmap,
|
||||||
|
float[][] primaryHydrationMap,
|
||||||
|
float[][] alternateHydrationMap,
|
||||||
|
boolean usePrimaryMaps,
|
||||||
|
Vector targetLocation,
|
||||||
|
int interpolationRatio,
|
||||||
|
float oceanLevel,
|
||||||
|
float waterLevelRatio,
|
||||||
|
long randomSeed,
|
||||||
|
CountDownLatch latch
|
||||||
|
){
|
||||||
|
this.primaryHeightmap = primaryHeightmap;
|
||||||
|
this.alternateHeightmap = alternateHeightmap;
|
||||||
|
this.primaryHydrationMap = primaryHydrationMap;
|
||||||
|
this.alternateHydrationMap = alternateHydrationMap;
|
||||||
|
this.usePrimaryMaps = usePrimaryMaps;
|
||||||
|
this.targetLocation = targetLocation;
|
||||||
|
this.interpolationRatio = interpolationRatio;
|
||||||
|
this.oceanLevel = oceanLevel;
|
||||||
|
this.waterLevelRatio = waterLevelRatio;
|
||||||
|
this.rand = new Random(randomSeed);
|
||||||
|
this.latch = latch;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final int[] offsetX = new int[]{
|
||||||
|
-1,1,0,0
|
||||||
|
};
|
||||||
|
static final int[] offsetY = new int[]{
|
||||||
|
0,0,-1,1
|
||||||
|
};
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for(int x = 0; x < interpolationRatio; x++){
|
||||||
|
for(int y = 0; y < interpolationRatio; y++){
|
||||||
|
float currentHeight = 0;
|
||||||
|
int targetX = targetLocation.x * interpolationRatio + x;
|
||||||
|
int targetY = targetLocation.y * interpolationRatio + y;
|
||||||
|
if(usePrimaryMaps){
|
||||||
|
currentHeight = primaryHeightmap[targetX][targetY];
|
||||||
|
float oldHydration = primaryHydrationMap[targetX][targetY];
|
||||||
|
float newHydration = 0;
|
||||||
|
float highestEncounteredElevation = 0;
|
||||||
|
int numberHydrationHits = 0;
|
||||||
|
// if(targetX == 728 && targetY == 732){
|
||||||
|
// System.out.println("asdf");
|
||||||
|
// }
|
||||||
|
//calculate total hydration
|
||||||
|
for(int i = 0; i < 4; i++){
|
||||||
|
if(targetX + offsetX[i] >= 0 && targetX + offsetX[i] < primaryHeightmap.length &&
|
||||||
|
targetY + offsetY[i] >= 0 && targetY + offsetY[i] < primaryHeightmap[0].length
|
||||||
|
){
|
||||||
|
if(currentHeight < primaryHeightmap[targetX + offsetX[i]][targetY + offsetY[i]]){
|
||||||
|
numberHydrationHits++;
|
||||||
|
float sourceHydration = primaryHydrationMap[targetX + offsetX[i]][targetY + offsetY[i]];
|
||||||
|
float percentageFromSource = calculatePercentageRunoff(
|
||||||
|
targetX + offsetX[i],
|
||||||
|
targetY + offsetY[i],
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
primaryHeightmap
|
||||||
|
);
|
||||||
|
newHydration = newHydration + sourceHydration * percentageFromSource;
|
||||||
|
} else if(currentHeight == primaryHeightmap[targetX + offsetX[i]][targetY + offsetY[i]] && rand.nextInt() % alternateHydrationMap[targetX][targetY] > 5) {
|
||||||
|
// numberHydrationHits++;
|
||||||
|
// alternateHydrationMap[targetX][targetY] = alternateHydrationMap[targetX][targetY] + primaryHydrationMap[targetX + offsetX[i]][targetY + offsetY[i]];
|
||||||
|
} else {
|
||||||
|
if(primaryHeightmap[targetX + offsetX[i]][targetY + offsetY[i]] > highestEncounteredElevation){
|
||||||
|
highestEncounteredElevation = primaryHeightmap[targetX + offsetX[i]][targetY + offsetY[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//calculate shear due to hydration
|
||||||
|
float shear = Math.abs(newHydration - oldHydration);
|
||||||
|
if(waterLevelRatio > 1.0f){
|
||||||
|
shear = alternateHydrationMap[targetX][targetY] / waterLevelRatio;
|
||||||
|
}
|
||||||
|
//clamp hydration value
|
||||||
|
alternateHydrationMap[targetX][targetY] = Math.min(newHydration,MAX_HYDRATION);
|
||||||
|
if(numberHydrationHits == 4){
|
||||||
|
alternateHydrationMap[targetX][targetY] = 0;
|
||||||
|
}
|
||||||
|
if(currentHeight > oceanLevel && numberHydrationHits < 4){
|
||||||
|
alternateHeightmap[targetX][targetY] = Math.max(highestEncounteredElevation,currentHeight - 0.1f / shear);
|
||||||
|
} else {
|
||||||
|
//if below sea level, delete hydration
|
||||||
|
alternateHeightmap[targetX][targetY] = currentHeight;
|
||||||
|
alternateHydrationMap[targetX][targetY] = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if(targetX == 728 && targetY == 732){
|
||||||
|
// System.out.println("asdf");
|
||||||
|
// }
|
||||||
|
currentHeight = alternateHeightmap[targetX][targetY];
|
||||||
|
float oldHydration = primaryHydrationMap[targetX][targetY];
|
||||||
|
float newHydration = 0;
|
||||||
|
float highestEncounteredElevation = 0;
|
||||||
|
int numberHydrationHits = 0;
|
||||||
|
//check each neighbor to see who we can interact with
|
||||||
|
for(int i = 0; i < 4; i++){
|
||||||
|
if(targetX + offsetX[i] >= 0 && targetX + offsetX[i] < primaryHeightmap.length &&
|
||||||
|
targetY + offsetY[i] >= 0 && targetY + offsetY[i] < primaryHeightmap[0].length
|
||||||
|
){
|
||||||
|
//if the neighbor is taller, pull hydration from it
|
||||||
|
if(currentHeight < alternateHeightmap[targetX + offsetX[i]][targetY + offsetY[i]]){
|
||||||
|
numberHydrationHits++;
|
||||||
|
float sourceHydration = primaryHydrationMap[targetX + offsetX[i]][targetY + offsetY[i]];
|
||||||
|
float percentageFromSource = calculatePercentageRunoff(
|
||||||
|
targetX + offsetX[i],
|
||||||
|
targetY + offsetY[i],
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
alternateHeightmap
|
||||||
|
);
|
||||||
|
newHydration = newHydration + sourceHydration * percentageFromSource;
|
||||||
|
} else if(currentHeight == alternateHeightmap[targetX + offsetX[i]][targetY + offsetY[i]] && rand.nextInt() % primaryHydrationMap[targetX][targetY] > 5) {
|
||||||
|
//if the neighbor is the same height, have a chance to pull hydration from it
|
||||||
|
// numberHydrationHits++;
|
||||||
|
// primaryHydrationMap[targetX][targetY] = primaryHydrationMap[targetX][targetY] + alternateHydrationMap[targetX + offsetX[i]][targetY + offsetY[i]];
|
||||||
|
} else {
|
||||||
|
//if the neighbor is smaller, but taller than the tallest neighbor currently encountered, record its height
|
||||||
|
if(alternateHeightmap[targetX + offsetX[i]][targetY + offsetY[i]] > highestEncounteredElevation){
|
||||||
|
highestEncounteredElevation = alternateHeightmap[targetX + offsetX[i]][targetY + offsetY[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//calculate shear due to hydration
|
||||||
|
float shear = Math.abs(newHydration - oldHydration);
|
||||||
|
//bound the hydration by the total hydration of the map
|
||||||
|
//This keeps the hydration from explosively increasing unbounded
|
||||||
|
if(waterLevelRatio > 1.0f){
|
||||||
|
shear = alternateHydrationMap[targetX][targetY] / waterLevelRatio;
|
||||||
|
}
|
||||||
|
//clamp hydration value
|
||||||
|
primaryHydrationMap[targetX][targetY] = Math.min(newHydration,MAX_HYDRATION);
|
||||||
|
//If every neighbor is taller, this is a local minimum and should be treated as a lake (removes all hydration)
|
||||||
|
if(numberHydrationHits == 4){
|
||||||
|
primaryHydrationMap[targetX][targetY] = 0;
|
||||||
|
}
|
||||||
|
if(currentHeight > oceanLevel && numberHydrationHits < 4){
|
||||||
|
primaryHeightmap[targetX][targetY] = Math.max(highestEncounteredElevation,currentHeight - 0.1f / shear);
|
||||||
|
} else {
|
||||||
|
//if below sea level, delete hydration
|
||||||
|
primaryHeightmap[targetX][targetY] = currentHeight;
|
||||||
|
primaryHydrationMap[targetX][targetY] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basically calculates how much runoff should go from source to destination. The percentage is based on how much of the total drop to all neighbors destination would be.
|
||||||
|
* @param sourceX The x coordinate of the source of the water
|
||||||
|
* @param sourceY The y coordinate of the source of the water
|
||||||
|
* @param destinationX The x coordinate of the destination of the water
|
||||||
|
* @param destinationY The y coordinate of the destination of the water
|
||||||
|
* @param elevationMapToCheck The elevation map to use as reference
|
||||||
|
* @return The percentage of water to pull from source to destination
|
||||||
|
*/
|
||||||
|
private float calculatePercentageRunoff(int sourceX, int sourceY, int destinationX, int destinationY, float[][] elevationMapToCheck){
|
||||||
|
//the difference between the source and destination points in elevation
|
||||||
|
float heightDifferencToDestination = elevationMapToCheck[sourceX][sourceY] - elevationMapToCheck[destinationX][destinationY];
|
||||||
|
//the sum difference between source and all its smaller neighbors
|
||||||
|
float totalHeightDifference = 0;
|
||||||
|
for(int i = 0; i < 4; i++){
|
||||||
|
if(sourceX + offsetX[i] >= 0 && sourceX + offsetX[i] < primaryHeightmap.length &&
|
||||||
|
sourceY + offsetY[i] >= 0 && sourceY + offsetY[i] < primaryHeightmap[0].length
|
||||||
|
){
|
||||||
|
if(elevationMapToCheck[sourceX][sourceY] > elevationMapToCheck[sourceX + offsetX[i]][sourceY + offsetY[i]]){
|
||||||
|
totalHeightDifference = totalHeightDifference + elevationMapToCheck[sourceX][sourceY] - elevationMapToCheck[sourceX + offsetX[i]][sourceY + offsetY[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return heightDifferencToDestination / totalHeightDifference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given location has all neighbors that are the same height
|
||||||
|
* @param sourceX The location to check x coordinate
|
||||||
|
* @param sourceY The location to check y coordinate
|
||||||
|
* @param elevationMapToCheck The elevation map to reference
|
||||||
|
* @return True if all neighbors are flat, false otherwise
|
||||||
|
*/
|
||||||
|
private boolean neighborIsFlat(int sourceX, int sourceY, float[][] elevationMapToCheck){
|
||||||
|
for(int i = 0; i < 4; i++){
|
||||||
|
if(sourceX + offsetX[i] >= 0 && sourceX + offsetX[i] < primaryHeightmap.length &&
|
||||||
|
sourceY + offsetY[i] >= 0 && sourceY + offsetY[i] < primaryHeightmap[0].length
|
||||||
|
){
|
||||||
|
if(elevationMapToCheck[sourceX][sourceY] != elevationMapToCheck[sourceX + offsetX[i]][sourceY + offsetY[i]]){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void createVisualization(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -12,10 +12,10 @@ class Hotspot {
|
|||||||
int life_max;
|
int life_max;
|
||||||
int magnitude_current;
|
int magnitude_current;
|
||||||
int magnitude_max;
|
int magnitude_max;
|
||||||
TerrainGenerator parent;
|
TectonicSimulation parent;
|
||||||
|
|
||||||
|
|
||||||
protected Hotspot(int x, int y, int life_max, int magnitude, TerrainGenerator parent) {
|
protected Hotspot(int x, int y, int life_max, int magnitude, TectonicSimulation parent) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.life_current = 0;
|
this.life_current = 0;
|
||||||
|
|||||||
@ -10,9 +10,9 @@ import javax.swing.JPanel;
|
|||||||
*/
|
*/
|
||||||
class InterpolationDisplay extends JPanel{
|
class InterpolationDisplay extends JPanel{
|
||||||
|
|
||||||
TerrainGen parent;
|
TerrainGenerator parent;
|
||||||
|
|
||||||
protected InterpolationDisplay(TerrainGen parent){
|
protected InterpolationDisplay(TerrainGenerator parent){
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,9 +22,9 @@ class InterpolationDisplay extends JPanel{
|
|||||||
if(parent.displayToggle == 0) {
|
if(parent.displayToggle == 0) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
for (int y = 0; y < width; y++) {
|
for (int y = 0; y < width; y++) {
|
||||||
if (parent.mountainParsed[x][y] > TerrainGen.MOUNTAIN_THRESHOLD - 1) {
|
if (parent.mountainParsed[x][y] > TerrainGenerator.MOUNTAIN_THRESHOLD - 1) {
|
||||||
g.setColor(new Color((int) (parent.elevation[x][y] / 100.0 * 254 * (parent.brightness / 100.0)), 1, 1));
|
g.setColor(new Color((int) (parent.elevation[x][y] / 100.0 * 254 * (parent.brightness / 100.0)), 1, 1));
|
||||||
} else if (parent.oceanParsed[x][y] > TerrainGen.OCEAN_THRESHOLD - 1) {
|
} else if (parent.oceanParsed[x][y] > TerrainGenerator.OCEAN_THRESHOLD - 1) {
|
||||||
g.setColor(
|
g.setColor(
|
||||||
new Color(
|
new Color(
|
||||||
1,
|
1,
|
||||||
@ -38,7 +38,29 @@ class InterpolationDisplay extends JPanel{
|
|||||||
g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2);
|
g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(parent.displayToggle == 1){
|
} else if(parent.displayToggle == 1) {
|
||||||
|
for (int x = 0; x < parent.continentPhaseDimension * parent.EROSION_INTERPOLATION_RATIO; x++) {
|
||||||
|
for (int y = 0; y < parent.continentPhaseDimension * parent.EROSION_INTERPOLATION_RATIO; y++) {
|
||||||
|
if (parent.erosionHeightmap[x][y] > TerrainGenerator.MOUNTAIN_THRESHOLD - 1) {
|
||||||
|
float color = Math.max(0,Math.min(parent.erosionHeightmap[x][y],100));
|
||||||
|
g.setColor(new Color((int) (color / 100.0 * 254 * (parent.brightness / 100.0)), 1, 1));
|
||||||
|
} else if (parent.erosionHeightmap[x][y] < TerrainGenerator.OCEAN_THRESHOLD - 1) {
|
||||||
|
float color = Math.max(0,Math.min(parent.erosionHeightmap[x][y],100));
|
||||||
|
g.setColor(
|
||||||
|
new Color(
|
||||||
|
1,
|
||||||
|
(int) (color / 100.0 * 254 * (parent.brightness / 100.0)),
|
||||||
|
(int) (color / 100.0 * 254 * (parent.brightness / 100.0))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
float color = Math.max(0,Math.min(parent.erosionHeightmap[x][y],100));
|
||||||
|
g.setColor(new Color(1, (int) (color / 100.0 * 254 * (parent.brightness / 100.0)), 1));
|
||||||
|
}
|
||||||
|
g.fillRect(x + 25, y + 25, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(parent.displayToggle == 2){
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
for (int y = 0; y < width; y++) {
|
for (int y = 0; y < width; y++) {
|
||||||
if (parent.precipitationChart[x][y] > 0) {
|
if (parent.precipitationChart[x][y] > 0) {
|
||||||
@ -55,7 +77,7 @@ class InterpolationDisplay extends JPanel{
|
|||||||
g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2);
|
g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(parent.displayToggle == 2){
|
} else if(parent.displayToggle == 3){
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
for (int y = 0; y < width; y++) {
|
for (int y = 0; y < width; y++) {
|
||||||
// if (TerrainInterpolator.precipitation_Chart[x][y] > 0) {
|
// if (TerrainInterpolator.precipitation_Chart[x][y] > 0) {
|
||||||
@ -72,7 +94,7 @@ class InterpolationDisplay extends JPanel{
|
|||||||
g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2);
|
g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(parent.displayToggle == 3){
|
} else if(parent.displayToggle == 4){
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
for (int y = 0; y < width; y++) {
|
for (int y = 0; y < width; y++) {
|
||||||
if (parent.climateCategory[x][y] == 0) {
|
if (parent.climateCategory[x][y] == 0) {
|
||||||
@ -143,7 +165,7 @@ class InterpolationDisplay extends JPanel{
|
|||||||
g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2);
|
g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(parent.displayToggle == 4){
|
} else if(parent.displayToggle == 5){
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
for (int y = 0; y < width; y++) {
|
for (int y = 0; y < width; y++) {
|
||||||
if (parent.continentIdField[x][y] > 8) {
|
if (parent.continentIdField[x][y] > 8) {
|
||||||
@ -170,7 +192,7 @@ class InterpolationDisplay extends JPanel{
|
|||||||
g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2);
|
g.fillRect(x * 2 + 25, y * 2 + 25, 2, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (parent.displayToggle == 5) {
|
} else if (parent.displayToggle == 6) {
|
||||||
Continent current = parent.continents.get(parent.current_Continent);
|
Continent current = parent.continents.get(parent.current_Continent);
|
||||||
g.drawString("dim_x: " + current.dim_x, 20, 20);
|
g.drawString("dim_x: " + current.dim_x, 20, 20);
|
||||||
g.drawString("dim_y: " + current.dim_y, 20, 30);
|
g.drawString("dim_y: " + current.dim_y, 20, 30);
|
||||||
@ -182,7 +204,7 @@ class InterpolationDisplay extends JPanel{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (parent.displayToggle == 6){
|
} else if (parent.displayToggle == 7){
|
||||||
// Continent current = parent.continents.get(parent.current_Continent);
|
// Continent current = parent.continents.get(parent.current_Continent);
|
||||||
// for(int x = 0; x < Region.REGION_DIMENSION; x++){
|
// for(int x = 0; x < Region.REGION_DIMENSION; x++){
|
||||||
// for(int y = 0; y < Region.REGION_DIMENSION; y++){
|
// for(int y = 0; y < Region.REGION_DIMENSION; y++){
|
||||||
|
|||||||
@ -0,0 +1,645 @@
|
|||||||
|
package electrosphere.server.terrain.generation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core continent phase terrain generator
|
||||||
|
*/
|
||||||
|
class TectonicSimulation {
|
||||||
|
|
||||||
|
//size of a parallelized chunk
|
||||||
|
static final int PARALLEL_CHUNK_SIZE = 32;
|
||||||
|
//number of threads for threadpool
|
||||||
|
static final int THREAD_POOL_COUNT = 16;
|
||||||
|
|
||||||
|
//the dimensions of the map
|
||||||
|
int DIMENSION = 200;
|
||||||
|
int[][] asthenosphereHeat;
|
||||||
|
int[][] rockHardness;
|
||||||
|
int[][] elevation;
|
||||||
|
int[][] smoothedElevation;
|
||||||
|
int currentElev[][];
|
||||||
|
int newElevation[][];
|
||||||
|
//currents used for pushing terrain elevation around the map
|
||||||
|
Vector[][] currents;
|
||||||
|
//hotspots that thrust rock up from the ocean floor
|
||||||
|
List<Hotspot> spots = new ArrayList<Hotspot>();
|
||||||
|
int time = 0;
|
||||||
|
int lifespan = 75000;
|
||||||
|
|
||||||
|
Random rand;
|
||||||
|
|
||||||
|
//thread pool for parallelized force calculation
|
||||||
|
ThreadPoolExecutor threadPool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param seed Seed for random
|
||||||
|
*/
|
||||||
|
protected TectonicSimulation(long seed){
|
||||||
|
this.rand = new Random(seed);
|
||||||
|
threadPool = (ThreadPoolExecutor)Executors.newFixedThreadPool(THREAD_POOL_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data width
|
||||||
|
* @param newDim The dimension of the data
|
||||||
|
*/
|
||||||
|
protected void setDimension(int newDim){
|
||||||
|
DIMENSION = newDim;
|
||||||
|
if(DIMENSION % PARALLEL_CHUNK_SIZE != 0){
|
||||||
|
//this requirement is for parallelization purposes
|
||||||
|
throw new Error("DIMENSION MUST BE A MULTIPLE OF 16!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the simulation lifespan for the Continent Phase
|
||||||
|
* @param newLifespan The lifespan in units of simulation frames
|
||||||
|
*/
|
||||||
|
protected void setLifespan(int newLifespan){
|
||||||
|
lifespan = newLifespan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the continent phase simulation. Blocks until completed
|
||||||
|
*/
|
||||||
|
protected void run(){
|
||||||
|
allocateData();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
long lastTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
//construct convection cells prior to simulation
|
||||||
|
constructConvectionCells();
|
||||||
|
|
||||||
|
|
||||||
|
//main simulation
|
||||||
|
while(true){
|
||||||
|
time++;
|
||||||
|
simulateHotspots();
|
||||||
|
heatToElevation();
|
||||||
|
applyVectorsToElevationParallel();
|
||||||
|
calculateSmoothedElevations();
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// TimeUnit.MILLISECONDS.sleep(1);
|
||||||
|
// } catch (InterruptedException ex) {
|
||||||
|
// }
|
||||||
|
if(time % 500 == 0) {
|
||||||
|
long new_Time = System.currentTimeMillis();
|
||||||
|
long time_Delta = new_Time - lastTime;
|
||||||
|
lastTime = new_Time;
|
||||||
|
System.out.println("Progress: " + time + "/" + lifespan + " ETA: " + (time_Delta * (lifespan - time) / 1000 / 500) + "S");
|
||||||
|
}
|
||||||
|
if(time > lifespan){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:
|
||||||
|
//next subphase is to find large areas without continents and place ones there
|
||||||
|
//the terrain added in this next phase will be made with a more quick and dirty implementation
|
||||||
|
|
||||||
|
|
||||||
|
//shutdown threadpool
|
||||||
|
threadPool.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the raw terrain
|
||||||
|
* @return The raw terrain
|
||||||
|
*/
|
||||||
|
protected int[][] getTerrain(){
|
||||||
|
return elevation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the terrain smoothed
|
||||||
|
* @return The terrain smoothed
|
||||||
|
*/
|
||||||
|
protected int[][] getTerrainSmoothed(){
|
||||||
|
return smoothedElevation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates all arrays generated based on the dimension provided
|
||||||
|
*/
|
||||||
|
private void allocateData(){
|
||||||
|
asthenosphereHeat = new int[DIMENSION][DIMENSION];
|
||||||
|
elevation = new int[DIMENSION][DIMENSION];
|
||||||
|
smoothedElevation = new int[DIMENSION][DIMENSION];
|
||||||
|
newElevation = new int[DIMENSION][DIMENSION];
|
||||||
|
currents = new Vector[DIMENSION][DIMENSION];
|
||||||
|
currentElev = new int[DIMENSION][DIMENSION];
|
||||||
|
for(int x = 0; x < DIMENSION; x++){
|
||||||
|
for(int y = 0; y < DIMENSION; y++){
|
||||||
|
currents[x][y] = new Vector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the asthenosphere is sufficiently hot, increases elevation of position
|
||||||
|
*/
|
||||||
|
private void heatToElevation(){
|
||||||
|
for(int x = 0; x < DIMENSION; x++){
|
||||||
|
for(int y = 0; y < DIMENSION; y++){
|
||||||
|
if(asthenosphereHeat[x][y] > 25){
|
||||||
|
if(electrosphere.server.terrain.generation.Utilities.random_Integer(1, 10, rand) == 10){
|
||||||
|
elevation[x][y] = elevation[x][y] + 1;
|
||||||
|
if(elevation[x][y] > 100){
|
||||||
|
elevation[x][y] = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs convection cells in the force vector field
|
||||||
|
*/
|
||||||
|
private void constructConvectionCells(){
|
||||||
|
//controls whether the cell rotates clockwise or couterclockwise
|
||||||
|
boolean isCellType1 = false;
|
||||||
|
//one fourth of the width of the data set
|
||||||
|
int fourth = DIMENSION / 4;
|
||||||
|
for(int x = 0; x < DIMENSION; x++){
|
||||||
|
for(int y = 0; y < DIMENSION; y++){
|
||||||
|
//the current position RELATIVE to the center point of the current convection cell center
|
||||||
|
int normalizedX = x;
|
||||||
|
int normalizedY = y;
|
||||||
|
|
||||||
|
//determine relative position and whether convection cell type one or two
|
||||||
|
if(y < fourth || (y < fourth * 3 && y > (fourth * 2) - 1)){
|
||||||
|
isCellType1 = true;
|
||||||
|
if(normalizedY > fourth){
|
||||||
|
normalizedY = normalizedY - fourth * 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isCellType1 = false;
|
||||||
|
if(normalizedY > fourth * 2 + 1){
|
||||||
|
normalizedY = normalizedY - fourth * 3;
|
||||||
|
} else {
|
||||||
|
normalizedY = normalizedY - fourth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(normalizedX > fourth){
|
||||||
|
normalizedX = normalizedX - fourth;
|
||||||
|
}
|
||||||
|
if(normalizedX < 0){
|
||||||
|
normalizedX = 0;
|
||||||
|
}
|
||||||
|
if(normalizedY < 0){
|
||||||
|
normalizedY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//one eighth of the width of the data set
|
||||||
|
int eigth = fourth / 2;
|
||||||
|
//Moves the relative position to be in its correct eighth
|
||||||
|
normalizedY = normalizedY - eigth;
|
||||||
|
normalizedX = normalizedX - eigth;
|
||||||
|
|
||||||
|
//calculates the distance from convection cell center to the relative position
|
||||||
|
float magnitude = (float)Math.sqrt(Math.pow(normalizedY, 2) + Math.pow(normalizedX, 2));
|
||||||
|
|
||||||
|
//If the distance is small enough we stretch it along the X axis ... ?
|
||||||
|
if(magnitude < fourth / 10){
|
||||||
|
normalizedX = normalizedX + fourth / 10;
|
||||||
|
magnitude = (float)Math.sqrt(Math.pow(normalizedY, 2) + Math.pow(normalizedX, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
//calculates the angle of the point relative to convection cell center
|
||||||
|
double offsetAngle = Math.atan2(normalizedY / magnitude, normalizedX / magnitude);
|
||||||
|
if(offsetAngle < 0){
|
||||||
|
offsetAngle = offsetAngle + Math.PI * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//rotate based on cell type
|
||||||
|
if(isCellType1){
|
||||||
|
offsetAngle = offsetAngle + Math.PI / 2;
|
||||||
|
} else {
|
||||||
|
offsetAngle = offsetAngle - Math.PI / 2;
|
||||||
|
}
|
||||||
|
//normalize
|
||||||
|
while(offsetAngle > Math.PI * 2){
|
||||||
|
offsetAngle = offsetAngle - Math.PI * 2;
|
||||||
|
}
|
||||||
|
while(offsetAngle < 0){
|
||||||
|
offsetAngle = offsetAngle + Math.PI * 2;
|
||||||
|
}
|
||||||
|
//Lastly, actually set the force vector
|
||||||
|
currents[x][y].x = (int)(99 * Math.cos(offsetAngle));
|
||||||
|
currents[x][y].y = (int)(99 * Math.sin(offsetAngle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the terrain around based on the vector field
|
||||||
|
*/
|
||||||
|
private void applyVectorsToElevation(){
|
||||||
|
//allocate new elevation array
|
||||||
|
for(int x = 0; x < DIMENSION; x++){
|
||||||
|
for(int y = 0; y < DIMENSION; y++){
|
||||||
|
newElevation[x][y] = 0;
|
||||||
|
currentElev[x][y] = elevation[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//transfer terrain
|
||||||
|
for(int x = 0; x < DIMENSION; x++){
|
||||||
|
for(int y = 0; y < DIMENSION; y++){
|
||||||
|
boolean transfer = false;
|
||||||
|
if (Utilities.random_Integer(1, 50, rand) == 1) {
|
||||||
|
transfer = true;
|
||||||
|
}
|
||||||
|
int transfer_goal;
|
||||||
|
if(Utilities.random_Integer(1, 2, rand)==1){
|
||||||
|
transfer_goal = Utilities.random_Integer(20, 60, rand);
|
||||||
|
} else {
|
||||||
|
transfer_goal = Utilities.random_Integer(0, 60, rand);
|
||||||
|
}
|
||||||
|
if(Utilities.random_Integer(1, 2, rand)==1){
|
||||||
|
if (currents[x][y].x >= 0) {
|
||||||
|
if (transfer) {
|
||||||
|
if (x + 1 < DIMENSION) {
|
||||||
|
while(newElevation[x + 1][y] + currentElev[x + 1][y] < 99 && currentElev[x][y] > transfer_goal){
|
||||||
|
newElevation[x + 1][y]++;
|
||||||
|
currentElev[x][y]--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (transfer) {
|
||||||
|
if (x - 1 >= 0) {
|
||||||
|
while(newElevation[x - 1][y] + currentElev[x - 1][y] < 99 && currentElev[x][y] > transfer_goal){
|
||||||
|
newElevation[x - 1][y]++;
|
||||||
|
currentElev[x][y]--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currents[x][y].y >= 0) {
|
||||||
|
if (transfer) {
|
||||||
|
if (y + 1 < DIMENSION) { // V REPLACE THIS WITH GOAL
|
||||||
|
while(newElevation[x][y + 1] + currentElev[x][y + 1] < 99 && currentElev[x][y] > transfer_goal){
|
||||||
|
newElevation[x][y + 1]++;
|
||||||
|
currentElev[x][y]--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (transfer) {
|
||||||
|
if (y - 1 >= 0) {
|
||||||
|
while(newElevation[x][y - 1] + currentElev[x][y - 1] < 99 && currentElev[x][y] > transfer_goal){
|
||||||
|
newElevation[x][y - 1]++;
|
||||||
|
currentElev[x][y]--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//move data from temporary array to main array
|
||||||
|
for(int x = 0; x < DIMENSION; x++){
|
||||||
|
for(int y = 0; y < DIMENSION; y++){
|
||||||
|
newElevation[x][y] = newElevation[x][y] + currentElev[x][y];
|
||||||
|
while(newElevation[x][y] > 99){
|
||||||
|
newElevation[x][y] = 99;
|
||||||
|
}
|
||||||
|
elevation[x][y] = newElevation[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a smooth kernel to the terrain data
|
||||||
|
*/
|
||||||
|
private void calculateSmoothedElevations(){
|
||||||
|
int[][] buffer = new int[DIMENSION][DIMENSION];
|
||||||
|
for(int x = 1; x < DIMENSION - 2; x++){
|
||||||
|
for(int y = 1; y < DIMENSION - 2; y++){
|
||||||
|
buffer[x][y] = elevation[x][y] * 4 * elevation[x+1][y] * 2 + elevation[x-1][y] * 2 + elevation[x][y+1] * 2 +
|
||||||
|
elevation[x][y-1] * 2 + elevation[x+1][y+1] + elevation[x+1][y-1] + elevation[x-1][y+1] + elevation[x-1][y-1];
|
||||||
|
buffer[x][y] = (int)(buffer[x][y] / 16.0);
|
||||||
|
while(buffer[x][y] > 100){
|
||||||
|
buffer[x][y] = buffer[x][y]/2;
|
||||||
|
}
|
||||||
|
smoothedElevation[x][y] = buffer[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(int x = 1; x < DIMENSION - 2; x++){
|
||||||
|
for(int y = 1; y < DIMENSION - 2; y++){
|
||||||
|
buffer[x][y] = smoothedElevation[x][y] * 4 * smoothedElevation[x+1][y] * 2 + smoothedElevation[x-1][y] * 2 + smoothedElevation[x][y+1] * 2 +
|
||||||
|
smoothedElevation[x][y-1] * 2 + smoothedElevation[x+1][y+1] + smoothedElevation[x+1][y-1] + smoothedElevation[x-1][y+1] + smoothedElevation[x-1][y-1];
|
||||||
|
buffer[x][y] = (int)(buffer[x][y] / 16.0);
|
||||||
|
while(buffer[x][y] > 100){
|
||||||
|
buffer[x][y] = buffer[x][y]/2;
|
||||||
|
}
|
||||||
|
smoothedElevation[x][y] = buffer[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* simulates the hotspot logic
|
||||||
|
*/
|
||||||
|
private void simulateHotspots(){
|
||||||
|
if(spots.size() >= 1){
|
||||||
|
List<Hotspot> to_Remove = new ArrayList<Hotspot>();
|
||||||
|
Iterator<Hotspot> spot_Iterator = spots.iterator();
|
||||||
|
while(spot_Iterator.hasNext()){
|
||||||
|
Hotspot current_Spot = spot_Iterator.next();
|
||||||
|
if(current_Spot.life_current >= current_Spot.life_max){
|
||||||
|
to_Remove.add(current_Spot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spot_Iterator = to_Remove.iterator();
|
||||||
|
while(spot_Iterator.hasNext()){
|
||||||
|
Hotspot current_Spot = spot_Iterator.next();
|
||||||
|
spots.remove(current_Spot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(spots.size() < 5){
|
||||||
|
spots.add(new Hotspot(
|
||||||
|
Utilities.random_Integer(0, DIMENSION - 1, rand),
|
||||||
|
Utilities.random_Integer(0, DIMENSION - 1, rand),
|
||||||
|
Utilities.random_Integer(6000, 10000, rand),
|
||||||
|
Utilities.random_Integer(3, 5, rand),
|
||||||
|
this));
|
||||||
|
}
|
||||||
|
for(int x = 0; x < DIMENSION; x++){
|
||||||
|
for(int y = 0; y < DIMENSION; y++){
|
||||||
|
asthenosphereHeat[x][y] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(spots.size() >= 1){
|
||||||
|
Iterator<Hotspot> spot_Iterator = spots.iterator();
|
||||||
|
while(spot_Iterator.hasNext()){
|
||||||
|
Hotspot current_Spot = spot_Iterator.next();
|
||||||
|
current_Spot.simulate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills in the gaps not covered by the main chunks
|
||||||
|
*/
|
||||||
|
private void applyVectorToElevationGaps(){
|
||||||
|
for(int x = 0; x < DIMENSION; x++){
|
||||||
|
for(int y = 0; y < DIMENSION; y++){
|
||||||
|
if(x % 16 == 0 || x % 16 == 1 || y % 16 == 0 || y % 16 == 1){
|
||||||
|
boolean transfer = false;
|
||||||
|
if (Utilities.random_Integer(1, 50, rand) == 1) {
|
||||||
|
transfer = true;
|
||||||
|
}
|
||||||
|
int transfer_goal;
|
||||||
|
if(Utilities.random_Integer(1, 2, rand)==1){
|
||||||
|
transfer_goal = Utilities.random_Integer(20, 60, rand);
|
||||||
|
} else {
|
||||||
|
transfer_goal = Utilities.random_Integer(0, 60, rand);
|
||||||
|
}
|
||||||
|
if(Utilities.random_Integer(1, 2, rand)==1){
|
||||||
|
if (currents[x][y].x >= 0) {
|
||||||
|
if (transfer) {
|
||||||
|
if (x + 1 < DIMENSION) {
|
||||||
|
while(newElevation[x + 1][y] + currentElev[x + 1][y] < 99 && currentElev[x][y] > transfer_goal){
|
||||||
|
newElevation[x + 1][y]++;
|
||||||
|
currentElev[x][y]--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (transfer) {
|
||||||
|
if (x - 1 >= 0) {
|
||||||
|
while(newElevation[x - 1][y] + currentElev[x - 1][y] < 99 && currentElev[x][y] > transfer_goal){
|
||||||
|
newElevation[x - 1][y]++;
|
||||||
|
currentElev[x][y]--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currents[x][y].y >= 0) {
|
||||||
|
if (transfer) {
|
||||||
|
if (y + 1 < DIMENSION) { // V REPLACE THIS WITH GOAL
|
||||||
|
while(newElevation[x][y + 1] + currentElev[x][y + 1] < 99 && currentElev[x][y] > transfer_goal){
|
||||||
|
newElevation[x][y + 1]++;
|
||||||
|
currentElev[x][y]--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (transfer) {
|
||||||
|
if (y - 1 >= 0) {
|
||||||
|
while(newElevation[x][y - 1] + currentElev[x][y - 1] < 99 && currentElev[x][y] > transfer_goal){
|
||||||
|
newElevation[x][y - 1]++;
|
||||||
|
currentElev[x][y]--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//latch for synchronizing parallel force vector computation
|
||||||
|
CountDownLatch latch;
|
||||||
|
/**
|
||||||
|
* Moves the terrain around based on the vector field, parallelized
|
||||||
|
*/
|
||||||
|
private void applyVectorsToElevationParallel(){
|
||||||
|
//allocate new elevation array
|
||||||
|
for(int x = 0; x < DIMENSION; x++){
|
||||||
|
for(int y = 0; y < DIMENSION; y++){
|
||||||
|
newElevation[x][y] = 0;
|
||||||
|
currentElev[x][y] = elevation[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
latch = new CountDownLatch(DIMENSION / PARALLEL_CHUNK_SIZE * DIMENSION / PARALLEL_CHUNK_SIZE);
|
||||||
|
//transfer terrain in main chunks
|
||||||
|
for(int x = 0; x < DIMENSION / PARALLEL_CHUNK_SIZE; x++){
|
||||||
|
for(int y = 0; y < DIMENSION / PARALLEL_CHUNK_SIZE; y++){
|
||||||
|
threadPool.execute(new TerrainMovementWorker(
|
||||||
|
DIMENSION,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
new Random(rand.nextLong()),
|
||||||
|
currents,
|
||||||
|
newElevation,
|
||||||
|
currentElev,
|
||||||
|
latch
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//await main chunks
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
//fill in gaps
|
||||||
|
applyVectorToElevationGaps();
|
||||||
|
//move data from temporary array to main array
|
||||||
|
for(int x = 0; x < DIMENSION; x++){
|
||||||
|
for(int y = 0; y < DIMENSION; y++){
|
||||||
|
newElevation[x][y] = newElevation[x][y] + currentElev[x][y];
|
||||||
|
while(newElevation[x][y] > 99){
|
||||||
|
newElevation[x][y] = 99;
|
||||||
|
}
|
||||||
|
elevation[x][y] = newElevation[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A worker thread for simulating terrain moving due to force vector field
|
||||||
|
*/
|
||||||
|
static class TerrainMovementWorker implements Runnable {
|
||||||
|
|
||||||
|
//size of data map
|
||||||
|
int continentPhaseDimension;
|
||||||
|
//The offsets into the data array
|
||||||
|
int offsetX;
|
||||||
|
int offsetY;
|
||||||
|
//random
|
||||||
|
Random rand;
|
||||||
|
//force vector field
|
||||||
|
Vector[][] currents;
|
||||||
|
//new elevation map to fill in
|
||||||
|
int[][] newElevation;
|
||||||
|
//reference elevation map to pull from
|
||||||
|
int[][] referenceElevation;
|
||||||
|
//latch to resynchronize threads
|
||||||
|
CountDownLatch latch;
|
||||||
|
|
||||||
|
protected TerrainMovementWorker(
|
||||||
|
int continentPhaseDimension,
|
||||||
|
int offsetX,
|
||||||
|
int offsetY,
|
||||||
|
Random rand,
|
||||||
|
Vector[][] currents,
|
||||||
|
int[][] newElevation,
|
||||||
|
int[][] referenceElevation,
|
||||||
|
CountDownLatch latch
|
||||||
|
){
|
||||||
|
this.continentPhaseDimension = continentPhaseDimension;
|
||||||
|
this.offsetX = offsetX;
|
||||||
|
this.offsetY = offsetY;
|
||||||
|
this.rand = rand;
|
||||||
|
this.currents = currents;
|
||||||
|
this.newElevation = newElevation;
|
||||||
|
this.referenceElevation = referenceElevation;
|
||||||
|
this.latch = latch;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the terrain movement simulation for this worker
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for(int x = 0; x < PARALLEL_CHUNK_SIZE; x++){
|
||||||
|
for(int y = 0; y < PARALLEL_CHUNK_SIZE; y++){
|
||||||
|
if(x % PARALLEL_CHUNK_SIZE != 0 && x % PARALLEL_CHUNK_SIZE != 1 && y % PARALLEL_CHUNK_SIZE != 0 && y % PARALLEL_CHUNK_SIZE != 1){
|
||||||
|
//current absolute position in data arrays
|
||||||
|
int currentX = x + offsetX * PARALLEL_CHUNK_SIZE;
|
||||||
|
int currentY = y + offsetY * PARALLEL_CHUNK_SIZE;
|
||||||
|
|
||||||
|
//roll whether should transfer terrain or not
|
||||||
|
boolean transfer = false;
|
||||||
|
if (Utilities.random_Integer(1, 50, rand) == 1) {
|
||||||
|
transfer = true;
|
||||||
|
}
|
||||||
|
//sets the goal of how much elevation to transfer to neighbors
|
||||||
|
int transferGoal;
|
||||||
|
if(Utilities.random_Integer(1, 2, rand)==1){
|
||||||
|
transferGoal = Utilities.random_Integer(20, 60, rand);
|
||||||
|
} else {
|
||||||
|
transferGoal = Utilities.random_Integer(0, 60, rand);
|
||||||
|
}
|
||||||
|
//roll whether to transfer horizontally or vertically
|
||||||
|
if(Utilities.random_Integer(1, 2, rand)==1){
|
||||||
|
//transfers horizontally
|
||||||
|
if (currents[currentX][currentY].x >= 0) {
|
||||||
|
if (transfer) {
|
||||||
|
if (currentX + 1 < continentPhaseDimension) {
|
||||||
|
while(
|
||||||
|
newElevation[currentX + 1][currentY] + referenceElevation[currentX + 1][currentY] < 99 &&
|
||||||
|
referenceElevation[currentX][currentY] > transferGoal){
|
||||||
|
newElevation[currentX + 1][currentY]++;
|
||||||
|
referenceElevation[currentX][currentY]--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (transfer) {
|
||||||
|
if (currentX - 1 >= 0) {
|
||||||
|
while(
|
||||||
|
newElevation[currentX - 1][currentY] + referenceElevation[currentX - 1][currentY] < 99 &&
|
||||||
|
referenceElevation[currentX][currentY] > transferGoal){
|
||||||
|
newElevation[currentX - 1][currentY]++;
|
||||||
|
referenceElevation[currentX][currentY]--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//transfer vertically
|
||||||
|
if (currents[currentX][currentY].y >= 0) {
|
||||||
|
if (transfer) {
|
||||||
|
if (currentY + 1 < continentPhaseDimension) { // V REPLACE THIS WITH GOAL
|
||||||
|
while(
|
||||||
|
newElevation[currentX][currentY + 1] + referenceElevation[currentX][currentY + 1] < 99 &&
|
||||||
|
referenceElevation[currentX][currentY] > transferGoal){
|
||||||
|
newElevation[currentX][currentY + 1]++;
|
||||||
|
referenceElevation[currentX][currentY]--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (transfer) {
|
||||||
|
if (currentY - 1 >= 0) {
|
||||||
|
while(
|
||||||
|
newElevation[currentX][currentY - 1] + referenceElevation[currentX][currentY - 1] < 99 &&
|
||||||
|
referenceElevation[currentX][currentY] > transferGoal){
|
||||||
|
newElevation[currentX][currentY - 1]++;
|
||||||
|
referenceElevation[currentX][currentY]--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -6,4 +6,11 @@ package electrosphere.server.terrain.generation;
|
|||||||
class Vector {
|
class Vector {
|
||||||
public int x;
|
public int x;
|
||||||
public int y;
|
public int y;
|
||||||
|
protected Vector(){
|
||||||
|
|
||||||
|
}
|
||||||
|
protected Vector(int x, int y){
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package electrosphere.server.terrain.manager;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import electrosphere.game.terrain.processing.TerrainInterpolator;
|
import electrosphere.game.terrain.processing.TerrainInterpolator;
|
||||||
import electrosphere.server.terrain.generation.TerrainGen;
|
import electrosphere.server.terrain.generation.TerrainGenerator;
|
||||||
import electrosphere.server.terrain.models.ModificationList;
|
import electrosphere.server.terrain.models.ModificationList;
|
||||||
import electrosphere.server.terrain.models.TerrainModel;
|
import electrosphere.server.terrain.models.TerrainModel;
|
||||||
import electrosphere.server.terrain.models.TerrainModification;
|
import electrosphere.server.terrain.models.TerrainModification;
|
||||||
@ -85,7 +85,7 @@ public class ServerTerrainManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void generate(){
|
public void generate(){
|
||||||
TerrainGen terrainGen = new TerrainGen();
|
TerrainGenerator terrainGen = new TerrainGenerator();
|
||||||
terrainGen.setInterpolationRatio(worldSizeDiscrete/200);
|
terrainGen.setInterpolationRatio(worldSizeDiscrete/200);
|
||||||
terrainGen.setVerticalInterpolationRatio(verticalInterpolationRatio);
|
terrainGen.setVerticalInterpolationRatio(verticalInterpolationRatio);
|
||||||
terrainGen.setDynamicInterpolationRatio(dynamicInterpolationRatio);
|
terrainGen.setDynamicInterpolationRatio(dynamicInterpolationRatio);
|
||||||
@ -343,12 +343,9 @@ public class ServerTerrainManager {
|
|||||||
return heightmapCache.get(key);
|
return heightmapCache.get(key);
|
||||||
} else {
|
} else {
|
||||||
float[][] macroValues = model.getRad5MacroValuesAtPosition(worldX, worldZ);
|
float[][] macroValues = model.getRad5MacroValuesAtPosition(worldX, worldZ);
|
||||||
long[][] randomizer = model.getRad5RandomizerValuesAtPosition(worldX, worldZ);
|
|
||||||
float[][] heightmap = TerrainInterpolator.getBicubicInterpolatedChunk(
|
float[][] heightmap = TerrainInterpolator.getBicubicInterpolatedChunk(
|
||||||
macroValues,
|
macroValues,
|
||||||
randomizer,
|
model.getDynamicInterpolationRatio()
|
||||||
model.getDynamicInterpolationRatio(),
|
|
||||||
model.getRandomDampener()
|
|
||||||
);
|
);
|
||||||
heightmapCache.put(key,heightmap);
|
heightmapCache.put(key,heightmap);
|
||||||
return heightmap;
|
return heightmap;
|
||||||
@ -375,8 +372,4 @@ public class ServerTerrainManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getRandomizerAtPoint(int worldX, int worldY){
|
|
||||||
return model.getRad5RandomizerValuesAtPosition(worldX, worldY)[2][2];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ public class TerrainModel {
|
|||||||
|
|
||||||
int discreteArrayDimension;
|
int discreteArrayDimension;
|
||||||
float[][] elevation;
|
float[][] elevation;
|
||||||
long[][] chunkRandomizer;
|
|
||||||
|
|
||||||
float realMountainThreshold;
|
float realMountainThreshold;
|
||||||
float realOceanThreshold;
|
float realOceanThreshold;
|
||||||
@ -28,7 +27,6 @@ public class TerrainModel {
|
|||||||
public TerrainModel(
|
public TerrainModel(
|
||||||
int dimension,
|
int dimension,
|
||||||
float[][] elevation,
|
float[][] elevation,
|
||||||
long[][] chunkRandomizer,
|
|
||||||
float realOceanThreshold,
|
float realOceanThreshold,
|
||||||
float realMountainThreshold,
|
float realMountainThreshold,
|
||||||
int dynamicInterpolationRatio
|
int dynamicInterpolationRatio
|
||||||
@ -37,7 +35,6 @@ public class TerrainModel {
|
|||||||
this.dynamicInterpolationRatio = dynamicInterpolationRatio;
|
this.dynamicInterpolationRatio = dynamicInterpolationRatio;
|
||||||
this.discreteArrayDimension = dimension;
|
this.discreteArrayDimension = dimension;
|
||||||
this.elevation = elevation;
|
this.elevation = elevation;
|
||||||
this.chunkRandomizer = chunkRandomizer;
|
|
||||||
this.realMountainThreshold = realMountainThreshold;
|
this.realMountainThreshold = realMountainThreshold;
|
||||||
this.realOceanThreshold = realOceanThreshold;
|
this.realOceanThreshold = realOceanThreshold;
|
||||||
this.modifications = new HashMap<String,ModificationList>();
|
this.modifications = new HashMap<String,ModificationList>();
|
||||||
@ -66,7 +63,6 @@ public class TerrainModel {
|
|||||||
*/
|
*/
|
||||||
public float[][] getElevationForChunk(int x, int y){
|
public float[][] getElevationForChunk(int x, int y){
|
||||||
|
|
||||||
Random rand = new Random(chunkRandomizer[x][y]);
|
|
||||||
//this is what we intend to return from the function
|
//this is what we intend to return from the function
|
||||||
float[][] rVal = new float[dynamicInterpolationRatio][dynamicInterpolationRatio];
|
float[][] rVal = new float[dynamicInterpolationRatio][dynamicInterpolationRatio];
|
||||||
|
|
||||||
@ -308,53 +304,6 @@ public class TerrainModel {
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public long[][] getRandomizerValuesAtPosition(int x, int y){
|
|
||||||
long[][] rVal = new long[3][3];
|
|
||||||
rVal[1][1] = chunkRandomizer[x][y];
|
|
||||||
if(x - 1 >= 0){
|
|
||||||
rVal[0][1] = chunkRandomizer[x-1][y];
|
|
||||||
if(y - 1 >= 0){
|
|
||||||
rVal[0][0] = chunkRandomizer[x-1][y-1];
|
|
||||||
}
|
|
||||||
if(y + 1 < discreteArrayDimension){
|
|
||||||
rVal[0][2] = chunkRandomizer[x-1][y+1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(x + 1 < discreteArrayDimension){
|
|
||||||
rVal[2][1] = chunkRandomizer[x+1][y];
|
|
||||||
if(y - 1 >= 0){
|
|
||||||
rVal[2][0] = chunkRandomizer[x+1][y-1];
|
|
||||||
}
|
|
||||||
if(y + 1 < discreteArrayDimension){
|
|
||||||
rVal[2][2] = chunkRandomizer[x+1][y+1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(y - 1 >= 0){
|
|
||||||
rVal[1][0] = chunkRandomizer[x][y-1];
|
|
||||||
}
|
|
||||||
if(y + 1 < discreteArrayDimension){
|
|
||||||
rVal[1][2] = chunkRandomizer[x][y+1];
|
|
||||||
}
|
|
||||||
return rVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public long[][] getRad5RandomizerValuesAtPosition(int x, int y){
|
|
||||||
|
|
||||||
long[][] rVal = new long[5][5];
|
|
||||||
for(int i = -2; i < 3; i++){
|
|
||||||
for(int j = -2; j < 3; j++){
|
|
||||||
if(x + i >= 0 && x + i < discreteArrayDimension && y + j >= 0 && y + j < discreteArrayDimension){
|
|
||||||
rVal[i+2][j+2] = chunkRandomizer[x+i][y+j];
|
|
||||||
} else {
|
|
||||||
rVal[i+2][j+2] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public float getRandomDampener(){
|
public float getRandomDampener(){
|
||||||
return interpolationRandomDampener;
|
return interpolationRandomDampener;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user