diff --git a/.gitignore b/.gitignore
index 9f2c3a17..b1e0ea27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,7 @@
/nb-configuration.xml
/Telephone-*.jar
-/NetArranger-*.jar
+/NetArranger*.jar
/lwjglx-debug-*.jar
/hs_err_pid*
/replay_pid*
diff --git a/buildNumber.properties b/buildNumber.properties
index eaad655c..d231cc53 100644
--- a/buildNumber.properties
+++ b/buildNumber.properties
@@ -1,3 +1,3 @@
#maven.buildNumber.plugin properties file
-#Mon Oct 28 16:07:42 EDT 2024
-buildNumber=364
+#Mon Nov 04 12:41:10 EST 2024
+buildNumber=375
diff --git a/docs/src/architecture/generation/chunkgenooptimizations.md b/docs/src/architecture/generation/chunkgenooptimizations.md
new file mode 100644
index 00000000..5294dee0
--- /dev/null
+++ b/docs/src/architecture/generation/chunkgenooptimizations.md
@@ -0,0 +1,4 @@
+@page chunkgenoptimizations Chunk Generation Optimizations
+
+Strategies to try:
+ - Cache "is surface" "is sky" "is cave" at generator level, then looking x,z against cached values to find whether the newly requested chunk is surface or not
\ No newline at end of file
diff --git a/docs/src/architecture/generation/worldgenerationindex.md b/docs/src/architecture/generation/worldgenerationindex.md
index b816c16a..60cb4fe9 100644
--- a/docs/src/architecture/generation/worldgenerationindex.md
+++ b/docs/src/architecture/generation/worldgenerationindex.md
@@ -4,4 +4,5 @@
- @subpage biomeselection
- @subpage biomegenerationproblems
- @subpage terraingenerationprocess
-- @subpage voxelgenideas
\ No newline at end of file
+- @subpage voxelgenideas
+- @subpage chunkgenoptimizations
\ No newline at end of file
diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md
index a0a81df3..0af83474 100644
--- a/docs/src/progress/renderertodo.md
+++ b/docs/src/progress/renderertodo.md
@@ -914,6 +914,22 @@ Update default resolution in config
Fix main menu ui test
Refactor math utils to spatial math utils to make room for more fundamental utils
+(10/29/2024)
+Begin drawcellmanager rewrite
+
+(10/30/2024)
+Integrate transvoxel algorithm
+Document NetArranger
+Break out datastructures library
+
+(10/31/2024)
+Fix some transvoxel bugs
+Optimizations
+Refactoring generator code
+
+(11/01/2024)
+Optimizations
+Fix transvoxel xnzn edge generation
# TODO
diff --git a/pom.xml b/pom.xml
index c262b5ea..e0081515 100644
--- a/pom.xml
+++ b/pom.xml
@@ -305,7 +305,15 @@
io.github.studiorailgun
MathUtils
- 1.0.1
+ 1.1.0
+
+
+
+
+
+ io.github.studiorailgun
+ DataStructures
+ 1.1.0
@@ -440,6 +448,24 @@
+
+ Run Net Arranger
+ generate-sources
+
+ exec
+
+
+ java.exe
+
+ -jar
+ NetArranger.jar
+
+
+ 0
+ 1
+
+
+
@@ -452,6 +478,20 @@
integration
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+
+ -javaagent:./lwjglx-debug-1.0.0.jar=t;o=trace.log
+ 0
+
+
+
+
fast,unit,integration
@@ -471,6 +511,7 @@
-javaagent:./lwjglx-debug-1.0.0.jar=t;o=trace.log
+ 0
diff --git a/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java b/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java
index 2224d964..ef4556ba 100644
--- a/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java
+++ b/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java
@@ -14,6 +14,7 @@ import electrosphere.engine.Globals;
import electrosphere.entity.EntityUtils;
import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.renderer.shader.ShaderProgram;
+import electrosphere.server.terrain.manager.ServerTerrainChunk;
/**
*
@@ -78,7 +79,7 @@ public class FluidCellManager {
*/
public FluidCellManager(ClientTerrainManager clientTerrainManager, int discreteX, int discreteY, int discreteZ){
if(Globals.clientWorldData != null){
- worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / Globals.clientWorldData.getDynamicInterpolationRatio() * 1.0f);
+ worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / ServerTerrainChunk.CHUNK_DIMENSION * 1.0f);
}
cells = new HashSet();
hasNotRequested = new HashSet();
@@ -209,24 +210,55 @@ public class FluidCellManager {
}
}
-
+ /**
+ * Checks if the manager has not requested a cell yet
+ * @return true if a cell has not been requested yet, false otherwise
+ */
public boolean containsUnrequestedCell(){
return hasNotRequested.size() > 0;
}
+
+ /**
+ * Gets the number of unrequested cells
+ * @return The number of unrequested cells
+ */
+ public int getUnrequestedSize(){
+ return hasNotRequested.size();
+ }
+ /**
+ * Checks if the manager has a cell that is not drawable
+ * @return true if there is an undrawable cell, false otherwise
+ */
public boolean containsUndrawableCell(){
return undrawable.size() > 0;
}
+
+ /**
+ * Gets the number of undrawable cells
+ * @return The number of undrawable cells
+ */
+ public int getUndrawableSize(){
+ return undrawable.size();
+ }
+ /**
+ * Checks if the manager has a cell that is updateable
+ * @return true if there is an updateable cell, false otherwise
+ */
public boolean containsUpdateableCell(){
return updateable.size() > 0;
}
-
+ /**
+ * Transforms real space into cell space
+ * @param input The real coordinate
+ * @return The cell coordinate
+ */
public int transformRealSpaceToCellSpace(double input){
- return (int)(input / Globals.clientWorldData.getDynamicInterpolationRatio());
+ return (int)(input / ServerTerrainChunk.CHUNK_DIMENSION);
}
/**
@@ -321,9 +353,10 @@ public class FluidCellManager {
* Updates cells that need updating in this manager
*/
public void update(){
+ Globals.profiler.beginCpuSample("FluidCellManager.update");
calculateDeltas();
if(update){
- if(containsUnrequestedCell() && !containsUndrawableCell()){
+ if(containsUnrequestedCell()){
updateUnrequestedCell();
} else if(containsUndrawableCell()){
makeCellDrawable();
@@ -331,6 +364,7 @@ public class FluidCellManager {
updateCellModel();
}
}
+ Globals.profiler.endCpuSample();
}
/**
diff --git a/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java b/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java
index e1824ed7..50b16afc 100644
--- a/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java
+++ b/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java
@@ -83,6 +83,7 @@ public class ClientFoliageManager {
* @param worldPos The world position
*/
private void updatePosition(Vector3i worldPos){
+ Globals.profiler.beginCpuSample("ClientFoliageManager.updatePosition");
FoliageChunk foundChunk = null;
for(FoliageChunk chunk : chunkUpdateCache){
if(chunk.getWorldPos().equals(worldPos)){
@@ -98,7 +99,8 @@ public class ClientFoliageManager {
} else {
chunkUpdateCache.remove(foundChunk);
}
- foundChunk.updateCells();
+ foundChunk.updateCells(false);
+ Globals.profiler.endCpuSample();
}
/**
@@ -130,7 +132,7 @@ public class ClientFoliageManager {
public boolean dependenciesAreReady(){
return
Globals.clientWorldData != null &&
- Globals.drawCellManager != null &&
+ Globals.clientDrawCellManager != null &&
Globals.playerEntity != null
;
}
@@ -142,7 +144,7 @@ public class ClientFoliageManager {
public void evaluateChunk(Vector3i worldPos){
for(FoliageChunk chunk : chunkUpdateCache){
if(chunk.getWorldPos().equals(worldPos)){
- chunk.updateCells();
+ chunk.updateCells(true);
break;
}
}
diff --git a/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java b/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java
index ff065454..2205ad8d 100644
--- a/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java
+++ b/src/main/java/electrosphere/client/foliagemanager/FoliageCell.java
@@ -212,7 +212,7 @@ public class FoliageCell {
if(voxelPosition.y + 1 >= ServerTerrainChunk.CHUNK_DIMENSION){
return;
}
- if(!Globals.drawCellManager.generatedPhysics(worldPosition)){
+ if(!Globals.clientDrawCellManager.isFullLOD(worldPosition)){
return;
}
List foliageTypesSupported = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(voxelPosition)).getAmbientFoliage();
diff --git a/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java b/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java
index 16c829b0..67979564 100644
--- a/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java
+++ b/src/main/java/electrosphere/client/foliagemanager/FoliageChunk.java
@@ -34,6 +34,11 @@ public class FoliageChunk {
*/
static final double HALF_RES_DIST = 30;
+ /**
+ * Tracks whether this chunk contains a foliage voxel or not
+ */
+ boolean containsFoliageVoxel = false;
+
/**
* The octree holding all the chunks to evaluate
*/
@@ -75,27 +80,56 @@ public class FoliageChunk {
this.currentChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos);
// //evaluate top cells if chunk above this one exists
this.aboveChunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(worldPos).add(0,1,0));
- this.updateCells();
+ this.updateCells(true);
Globals.profiler.endCpuSample();
}
/**
* Updates all cells in the chunk
+ * @param force true if should ignore cache, false otherwise
*/
- public void updateCells(){
+ public void updateCells(boolean force){
Globals.profiler.beginCpuSample("FoliageChunk.updateCells");
- Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
- //the sets to iterate through
- boolean updated = true;
- int attempts = 0;
- while(updated && attempts < 3){
- ChunkTreeNode rootNode = this.chunkTree.getRoot();
- updated = this.recursivelyUpdateCells(rootNode, playerPos);
- attempts++;
+ //re-evaluate whether contains foliage voxel or not
+ if(force){
+ this.containsFoliageVoxel = checkContainsFoliageVoxel();
+ }
+ if(force || containsFoliageVoxel){
+ Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
+ //the sets to iterate through
+ boolean updated = true;
+ int attempts = 0;
+ while(updated && attempts < 3){
+ ChunkTreeNode rootNode = this.chunkTree.getRoot();
+ updated = this.recursivelyUpdateCells(rootNode, playerPos);
+ attempts++;
+ }
}
Globals.profiler.endCpuSample();
}
+ /**
+ * Checks if the chunk contains a foliage voxel or not
+ * @return true if contains foliage voxel, false otherwise
+ */
+ private boolean checkContainsFoliageVoxel(){
+ ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(this.getWorldPos());
+ if(data == null){
+ return false;
+ }
+ for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){
+ for(int y = 0; y < ChunkData.CHUNK_SIZE; y++){
+ for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){
+ List foliageTypesSupported = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(new Vector3i(x,y,z))).getAmbientFoliage();
+ if(foliageTypesSupported != null && foliageTypesSupported.size() > 0){
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Recursively update child nodes
* @param node The root node
@@ -104,6 +138,7 @@ public class FoliageChunk {
private boolean recursivelyUpdateCells(ChunkTreeNode node, Vector3d playerPos){
boolean updated = false;
if(this.shouldSplit(playerPos, node)){
+ Globals.profiler.beginCpuSample("FoliageChunk.split");
//perform op
ChunkTreeNode container = chunkTree.split(node);
@@ -119,8 +154,10 @@ public class FoliageChunk {
);
child.convertToLeaf(new FoliageCell(worldPos, child.getMinBound(), realPos, 5 - child.getLevel()));
});
+ Globals.profiler.endCpuSample();
updated = true;
} else if(this.shouldJoin(playerPos, node)) {
+ // Globals.profiler.beginCpuSample("FoliageChunk.join");
//perform op
ChunkTreeNode newLeaf = chunkTree.join(node);
@@ -129,9 +166,12 @@ public class FoliageChunk {
//do creations
newLeaf.convertToLeaf(new FoliageCell(worldPos, newLeaf.getMinBound(), realPos, 5 - newLeaf.getLevel()));
+ // Globals.profiler.endCpuSample();
updated = true;
} else if(shouldGenerate(playerPos, node)){
+ // Globals.profiler.beginCpuSample("FoliageChunk.generate");
node.getData().generate();
+ // Globals.profiler.endCpuSample();
updated = true;
} else if(!node.isLeaf()){
List> children = new LinkedList>(node.getChildren());
diff --git a/src/main/java/electrosphere/client/scene/ClientWorldData.java b/src/main/java/electrosphere/client/scene/ClientWorldData.java
index 3b579b78..3312ea83 100644
--- a/src/main/java/electrosphere/client/scene/ClientWorldData.java
+++ b/src/main/java/electrosphere/client/scene/ClientWorldData.java
@@ -4,6 +4,8 @@ import org.joml.Vector3d;
import org.joml.Vector3f;
import org.joml.Vector3i;
+import electrosphere.server.terrain.manager.ServerTerrainChunk;
+
/**
* Client's data on the world
*/
@@ -31,8 +33,6 @@ public class ClientWorldData {
Vector3f worldMinPoint;
Vector3f worldMaxPoint;
- int dynamicInterpolationRatio;
-
float randomDampener;
@@ -42,13 +42,11 @@ public class ClientWorldData {
public ClientWorldData(
Vector3f worldMinPoint,
Vector3f worldMaxPoint,
- int dynamicInterpolationRatio,
float randomDampener,
int worldDiscreteSize
) {
this.worldMinPoint = worldMinPoint;
this.worldMaxPoint = worldMaxPoint;
- this.dynamicInterpolationRatio = dynamicInterpolationRatio;
this.randomDampener = randomDampener;
this.worldDiscreteSize = worldDiscreteSize;
}
@@ -64,10 +62,6 @@ public class ClientWorldData {
return worldMaxPoint;
}
- public int getDynamicInterpolationRatio() {
- return dynamicInterpolationRatio;
- }
-
public float getRandomDampener() {
return randomDampener;
}
@@ -78,11 +72,11 @@ public class ClientWorldData {
public int convertRealToChunkSpace(double real){
- return (int)Math.floor(real / dynamicInterpolationRatio);
+ return (int)Math.floor(real / ServerTerrainChunk.CHUNK_DIMENSION);
}
public float convertChunkToRealSpace(int chunk){
- return chunk * dynamicInterpolationRatio;
+ return chunk * ServerTerrainChunk.CHUNK_DIMENSION;
}
public int convertRealToWorld(double real){
diff --git a/src/main/java/electrosphere/client/sim/ClientSimulation.java b/src/main/java/electrosphere/client/sim/ClientSimulation.java
index b7d18bd7..88d1ee49 100644
--- a/src/main/java/electrosphere/client/sim/ClientSimulation.java
+++ b/src/main/java/electrosphere/client/sim/ClientSimulation.java
@@ -166,7 +166,7 @@ public class ClientSimulation {
* Loads terrain that is in queue
*/
public void loadTerrain(){
- Globals.profiler.beginCpuSample("load terrain");
+ Globals.profiler.beginCpuSample("ClientSimulation.loadTerrain");
if(Globals.clientTerrainManager != null){
Globals.clientTerrainManager.handleMessages();
updateTerrainCellManager();
@@ -185,9 +185,9 @@ public class ClientSimulation {
///
/// C L I E N T C E L L M A N A G E R
///
- if(Globals.drawCellManager != null && Globals.clientWorldData != null){
+ if(Globals.clientDrawCellManager != null && Globals.clientWorldData != null){
//Cell manager do your things
- Globals.drawCellManager.update();
+ Globals.clientDrawCellManager.update();
}
}
diff --git a/src/main/java/electrosphere/client/terrain/cache/ChunkData.java b/src/main/java/electrosphere/client/terrain/cache/ChunkData.java
index 59bd0447..4036d6a0 100644
--- a/src/main/java/electrosphere/client/terrain/cache/ChunkData.java
+++ b/src/main/java/electrosphere/client/terrain/cache/ChunkData.java
@@ -26,6 +26,31 @@ public class ChunkData {
//Used in DrawCell to keep track of which positions to invalidate
Set modifiedSinceLastGeneration = new HashSet();
+ /**
+ * The word x coordinate
+ */
+ int worldX;
+ /**
+ * The word y coordinate
+ */
+ int worldY;
+ /**
+ * The word z coordinate
+ */
+ int worldZ;
+
+ /**
+ * Creates a chunk data
+ * @param worldX The word x coordinate
+ * @param worldY The word y coordinate
+ * @param worldZ The word z coordinate
+ */
+ public ChunkData(int worldX, int worldY, int worldZ){
+ this.worldX = worldX;
+ this.worldY = worldY;
+ this.worldZ = worldZ;
+ }
+
/**
* Gets the voxel type array in this container
@@ -178,5 +203,13 @@ public class ChunkData {
return position.x + "_" + position.y + "_" + position.z;
}
+ /**
+ * Gets the world position of the chunk data
+ * @return The world position
+ */
+ public Vector3i getWorldPos(){
+ return new Vector3i(worldX,worldY,worldZ);
+ }
+
}
diff --git a/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java b/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java
index 4bb90b52..90f1dcf4 100644
--- a/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java
+++ b/src/main/java/electrosphere/client/terrain/cache/ClientTerrainCache.java
@@ -8,6 +8,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
import org.joml.Vector3i;
+import io.github.studiorailgun.HashUtils;
+
/**
* Acts as a cache in front of terrain model to streamline receiving chunks
@@ -17,16 +19,21 @@ public class ClientTerrainCache {
//cache capacity
int cacheSize;
//the map of chunk key -> chunk data
- Map cacheMap = new ConcurrentHashMap();
+ Map cacheMap = new ConcurrentHashMap();
//the list of keys in the cache
- List cacheList = new CopyOnWriteArrayList();
+ List cacheList = new CopyOnWriteArrayList();
//A map of chunk to its world position
Map chunkPositionMap = new ConcurrentHashMap();
+
+ /**
+ * The map tracking chunks that have been requested
+ */
+ Map requestedChunks = new ConcurrentHashMap();
/**
* Constructor
* @param cacheSize The capacity of the cache
- */
+ */
public ClientTerrainCache(int cacheSize){
this.cacheSize = cacheSize;
}
@@ -42,8 +49,10 @@ public class ClientTerrainCache {
cacheMap.put(getKey(worldX,worldY,worldZ),chunkData);
chunkPositionMap.put(chunkData,new Vector3i(worldX,worldY,worldZ));
while(cacheList.size() > cacheSize){
- String currentChunk = cacheList.remove(0);
- cacheMap.remove(currentChunk);
+ Long currentChunk = cacheList.remove(0);
+ ChunkData data = cacheMap.remove(currentChunk);
+ Vector3i worldPos = data.getWorldPos();
+ requestedChunks.remove(getKey(worldPos.x,worldPos.y,worldPos.z));
}
}
@@ -64,8 +73,8 @@ public class ClientTerrainCache {
* @param worldZ The z world position
* @return The cache key
*/
- public String getKey(int worldX, int worldY, int worldZ){
- return worldX + "_" + worldY + "_" + worldZ;
+ public long getKey(int worldX, int worldY, int worldZ){
+ return HashUtils.cantorHash(worldX, worldY, worldZ);
}
/**
@@ -110,5 +119,34 @@ public class ClientTerrainCache {
public Vector3i getChunkPosition(ChunkData chunk){
return chunkPositionMap.get(chunk);
}
+
+ /**
+ * Gets the number of cells that have been requested
+ * @return The number of cells that have been requested
+ */
+ public int getRequestedCellCount(){
+ return this.requestedChunks.size();
+ }
+
+ /**
+ * Checks if a chunk has been requested or not
+ * @param worldX The x coordinate in the world
+ * @param worldY The y coordinate in the world
+ * @param worldZ The z coordinate in the world
+ * @return true if it has been requested, false otherwise
+ */
+ public boolean hasRequested(int worldX, int worldY, int worldZ){
+ return this.requestedChunks.containsKey(getKey(worldX, worldY, worldZ));
+ }
+
+ /**
+ * Marks a chunk as requested
+ * @param worldX The x coordinate in the world
+ * @param worldY The y coordinate in the world
+ * @param worldZ The z coordinate in the world
+ */
+ public void markAsRequested(int worldX, int worldY, int worldZ){
+ this.requestedChunks.put(getKey(worldX, worldY, worldZ),true);
+ }
}
diff --git a/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java
new file mode 100644
index 00000000..f41d2212
--- /dev/null
+++ b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java
@@ -0,0 +1,690 @@
+package electrosphere.client.terrain.cells;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.joml.Vector3d;
+import org.joml.Vector3i;
+
+import electrosphere.client.terrain.cells.DrawCell.DrawCellFace;
+import electrosphere.engine.Globals;
+import electrosphere.entity.EntityUtils;
+import electrosphere.logger.LoggerInterface;
+import electrosphere.server.terrain.manager.ServerTerrainChunk;
+import electrosphere.util.ds.octree.WorldOctTree;
+import electrosphere.util.ds.octree.WorldOctTree.FloatingChunkTreeNode;
+import electrosphere.util.math.GeomUtils;
+
+/**
+ * Manages draw cells on the client
+ */
+public class ClientDrawCellManager {
+
+ /**
+ * Number of times to try updating per frame. Lower this to reduce lag but slow down terrain mesh generation.
+ */
+ static final int UPDATE_ATTEMPTS_PER_FRAME = 3;
+
+ /**
+ * The distance to draw at full resolution
+ */
+ public static final double FULL_RES_DIST = 5 * ServerTerrainChunk.CHUNK_DIMENSION;
+
+ /**
+ * The distance for half resolution
+ */
+ public static final double HALF_RES_DIST = 15 * ServerTerrainChunk.CHUNK_DIMENSION;
+
+ /**
+ * The octree holding all the chunks to evaluate
+ */
+ WorldOctTree chunkTree;
+
+ /**
+ * Tracks whether the cell manager updated last frame or not
+ */
+ boolean updatedLastFrame = true;
+
+ /**
+ * Controls whether the client draw cell manager should update or not
+ */
+ boolean shouldUpdate = true;
+
+ /**
+ * The voxel texture atlas
+ */
+ VoxelTextureAtlas textureAtlas;
+
+ /**
+ * The dimensions of the world
+ */
+ int worldDim = 0;
+
+ /**
+ * Tracks the number of currently valid cells (ie didn't require an update this frame)
+ */
+ int validCellCount = 0;
+
+ /**
+ * The number of maximum resolution chunks
+ */
+ int maxResCount = 0;
+
+ /**
+ * The number of half resolution chunks
+ */
+ int halfResCount = 0;
+
+ /**
+ * The number of generated chunks
+ */
+ int generated = 0;
+
+ /**
+ * Constructor
+ * @param voxelTextureAtlas The voxel texture atlas
+ * @param worldDim The size of the world in chunks
+ */
+ public ClientDrawCellManager(VoxelTextureAtlas voxelTextureAtlas, int worldDim){
+ this.chunkTree = new WorldOctTree(new Vector3i(0,0,0), new Vector3i(worldDim, worldDim, worldDim));
+ this.worldDim = worldDim;
+ this.textureAtlas = voxelTextureAtlas;
+ }
+
+ /**
+ * Updates all cells in the chunk
+ */
+ public void update(){
+ Globals.profiler.beginCpuSample("ClientDrawCellManager.updateCells");
+ if(shouldUpdate && Globals.playerEntity != null){
+ Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
+ //the sets to iterate through
+ updatedLastFrame = true;
+ int attempts = 0;
+ validCellCount = 0;
+ while(updatedLastFrame && attempts < UPDATE_ATTEMPTS_PER_FRAME){
+ FloatingChunkTreeNode rootNode = this.chunkTree.getRoot();
+ updatedLastFrame = this.recursivelyUpdateCells(rootNode, playerPos);
+ attempts++;
+ }
+ }
+ Globals.profiler.endCpuSample();
+ }
+
+ /**
+ * Recursively update child nodes
+ * @param node The root node
+ * @param playerPos The player's position
+ * @return true if there is work remaining to be done, false otherwise
+ */
+ private boolean recursivelyUpdateCells(FloatingChunkTreeNode node, Vector3d playerPos){
+ Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity);
+ boolean updated = false;
+ if(this.shouldSplit(playerPos, node)){
+ Globals.profiler.beginCpuSample("ClientDrawCellManager.split");
+ //perform op
+ FloatingChunkTreeNode container = chunkTree.split(node);
+
+ //do deletions
+ this.recursivelyDestroy(node);
+
+ //do creations
+ container.getChildren().forEach(child -> {
+ Vector3i cellWorldPos = new Vector3i(
+ child.getMinBound().x,
+ child.getMinBound().y,
+ child.getMinBound().z
+ );
+ DrawCell drawCell = DrawCell.generateTerrainCell(cellWorldPos);
+ child.convertToLeaf(drawCell);
+ });
+
+ //update neighbors
+ this.conditionalUpdateAdjacentNodes(container, container.getChildren().get(0).getLevel());
+
+ Globals.profiler.endCpuSample();
+ updated = true;
+ } else if(this.shouldJoin(playerPos, node)) {
+ Globals.profiler.beginCpuSample("ClientDrawCellManager.join");
+ //perform op
+ FloatingChunkTreeNode newLeaf = chunkTree.join(node);
+
+ //do deletions
+ this.recursivelyDestroy(node);
+
+ //do creations
+ DrawCell drawCell = DrawCell.generateTerrainCell(node.getMinBound());
+ newLeaf.convertToLeaf(drawCell);
+
+ //update neighbors
+ this.conditionalUpdateAdjacentNodes(newLeaf, newLeaf.getLevel());
+
+ Globals.profiler.endCpuSample();
+ updated = true;
+ } else if(shouldRequest(playerPos, node)){
+ Globals.profiler.beginCpuSample("ClientDrawCellManager.request");
+ DrawCell cell = node.getData();
+ this.requestChunks(node);
+ cell.setHasRequested(true);
+ Globals.profiler.endCpuSample();
+ updated = true;
+ } else if(shouldGenerate(playerPos, node)){
+ Globals.profiler.beginCpuSample("ClientDrawCellManager.generate");
+ int lodLevel = this.getLODLevel(playerRealPos, node);
+ List highResFaces = this.solveHighResFace(node);
+ if(containsDataToGenerate(node,highResFaces)){
+ node.getData().generateDrawableEntity(textureAtlas, lodLevel, highResFaces);
+ }
+ Globals.profiler.endCpuSample();
+ updated = true;
+ } else if(!node.isLeaf()){
+ this.validCellCount++;
+ List> children = new LinkedList>(node.getChildren());
+ for(FloatingChunkTreeNode child : children){
+ boolean childUpdate = recursivelyUpdateCells(child, playerPos);
+ if(childUpdate == true){
+ updated = true;
+ }
+ }
+ }
+ return updated;
+ }
+
+ /**
+ * Gets the minimum distance from a node to a point
+ * @param pos the position to check against
+ * @param node the node
+ * @return the distance
+ */
+ public double getMinDistance(Vector3d pos, FloatingChunkTreeNode node){
+ Vector3i min = node.getMinBound();
+ Vector3i max = node.getMaxBound();
+ return GeomUtils.getMinDistanceAABB(pos, Globals.clientWorldData.convertWorldToRealSpace(min), Globals.clientWorldData.convertWorldToRealSpace(max));
+ }
+
+ /**
+ * Gets whether this should be split or not
+ * @param pos the player position
+ * @param node The node
+ * @return true if should split, false otherwise
+ */
+ public boolean shouldSplit(Vector3d pos, FloatingChunkTreeNode node){
+ //breaking out into dedicated function so can add case handling ie if we want
+ //to combine fullres nodes into larger nodes to conserve on draw calls
+ return
+ node.isLeaf() &&
+ node.canSplit() &&
+ (
+ (
+ node.getLevel() < this.chunkTree.getMaxLevel() - 1 &&
+ this.getMinDistance(pos, node) <= HALF_RES_DIST
+ )
+ ||
+ (
+ node.getLevel() < this.chunkTree.getMaxLevel() &&
+ this.getMinDistance(pos, node) <= FULL_RES_DIST
+ )
+ )
+ ;
+ }
+
+ /**
+ * Gets the LOD level of the draw cell
+ * @param pos The position of the cell
+ * @param node The node to consider
+ * @return -1 if outside of render range, -1 if the node is not a valid draw cell leaf, otherwise returns the LOD level
+ */
+ private int getLODLevel(Vector3d pos, FloatingChunkTreeNode node){
+ return this.chunkTree.getMaxLevel() - node.getLevel();
+ }
+
+ /**
+ * Solves which face (if any) is the high res face for a LOD chunk
+ * @param node The node for the chunk
+ * @return The face if there is a higher resolution face, null otherwise
+ */
+ private List solveHighResFace(FloatingChunkTreeNode node){
+ //don't bother to check if it's a full res chunk
+ if(node.getLevel() == this.chunkTree.getMaxLevel()){
+ return null;
+ }
+ int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1;
+ int spacing = (int)Math.pow(2,lodMultiplitier);
+ List faces = new LinkedList();
+ if(node.getMinBound().x - 1 >= 0){
+ FloatingChunkTreeNode xNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(-1,1,1), false);
+ if(xNegNode != null && xNegNode.getLevel() > node.getLevel()){
+ faces.add(DrawCellFace.X_NEGATIVE);
+ }
+ }
+ if(node.getMinBound().y - 1 >= 0){
+ FloatingChunkTreeNode yNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(1,-1,1), false);
+ if(yNegNode != null && yNegNode.getLevel() > node.getLevel()){
+ faces.add(DrawCellFace.Y_NEGATIVE);
+ }
+ }
+ if(node.getMinBound().z - 1 >= 0){
+ FloatingChunkTreeNode zNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(1,1,-1), false);
+ if(zNegNode != null && zNegNode.getLevel() > node.getLevel()){
+ faces.add(DrawCellFace.Z_NEGATIVE);
+ }
+ }
+ if(node.getMaxBound().x + spacing + 1 < this.worldDim){
+ FloatingChunkTreeNode xPosNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(spacing + 1,1,1), false);
+ if(xPosNode != null && xPosNode.getLevel() > node.getLevel()){
+ faces.add(DrawCellFace.X_POSITIVE);
+ }
+ }
+ if(node.getMaxBound().y + spacing + 1 < this.worldDim){
+ FloatingChunkTreeNode yPosNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(1,spacing + 1,1), false);
+ if(yPosNode != null && yPosNode.getLevel() > node.getLevel()){
+ faces.add(DrawCellFace.Y_POSITIVE);
+ }
+ }
+ if(node.getMaxBound().z + spacing + 1 < this.worldDim){
+ FloatingChunkTreeNode zPosNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(1,1,spacing + 1), false);
+ if(zPosNode != null && zPosNode.getLevel() > node.getLevel()){
+ faces.add(DrawCellFace.Z_POSITIVE);
+ }
+ }
+ if(faces.size() > 0){
+ return faces;
+ }
+ return null;
+ }
+
+ /**
+ * Conditionally updates all adjacent nodes if their level would require transition cells in the voxel rasterization
+ * @param node The node to search from adjacencies from
+ * @param level The level to check against
+ */
+ private void conditionalUpdateAdjacentNodes(FloatingChunkTreeNode node, int level){
+ //don't bother to check if it's a lowest-res chunk
+ if(this.chunkTree.getMaxLevel() - level > DrawCell.LOWEST_LOD){
+ return;
+ }
+ if(node.getMinBound().x - 1 >= 0){
+ FloatingChunkTreeNode xNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(-1,0,0), false);
+ if(xNegNode != null && xNegNode.getLevel() < level){
+ xNegNode.getData().setHasGenerated(false);
+ }
+ }
+ if(node.getMinBound().y - 1 >= 0){
+ FloatingChunkTreeNode yNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(0,-1,0), false);
+ if(yNegNode != null && yNegNode.getLevel() < level){
+ yNegNode.getData().setHasGenerated(false);
+ }
+ }
+ if(node.getMinBound().z - 1 >= 0){
+ FloatingChunkTreeNode zNegNode = this.chunkTree.search(new Vector3i(node.getMinBound()).add(0,0,-1), false);
+ if(zNegNode != null && zNegNode.getLevel() < level){
+ zNegNode.getData().setHasGenerated(false);
+ }
+ }
+ if(node.getMaxBound().x + 1 < this.worldDim){
+ FloatingChunkTreeNode xPosNode = this.chunkTree.search(new Vector3i(node.getMaxBound()).add(1,-1,-1), false);
+ if(xPosNode != null && xPosNode.getLevel() < level){
+ xPosNode.getData().setHasGenerated(false);
+ }
+ }
+ if(node.getMaxBound().y + 1 < this.worldDim){
+ FloatingChunkTreeNode yPosNode = this.chunkTree.search(new Vector3i(node.getMaxBound()).add(-1,1,-1), false);
+ if(yPosNode != null && yPosNode.getLevel() < level){
+ yPosNode.getData().setHasGenerated(false);
+ }
+ }
+ if(node.getMaxBound().z + 1 < this.worldDim){
+ FloatingChunkTreeNode zPosNode = this.chunkTree.search(new Vector3i(node.getMaxBound()).add(-1,-1,1), false);
+ if(zPosNode != null && zPosNode.getLevel() < level){
+ zPosNode.getData().setHasGenerated(false);
+ }
+ }
+ }
+
+ /**
+ * Gets whether this should be joined or not
+ * @param pos the player position
+ * @param node The node
+ * @return true if should be joined, false otherwise
+ */
+ public boolean shouldJoin(Vector3d pos, FloatingChunkTreeNode node){
+ //breaking out into dedicated function so can add case handling ie if we want
+ //to combine fullres nodes into larger nodes to conserve on draw calls
+ return
+ node.getLevel() > 0 &&
+ !node.isLeaf() &&
+ (
+ (
+ node.getLevel() == this.chunkTree.getMaxLevel() &&
+ this.getMinDistance(pos, node) > FULL_RES_DIST
+ )
+ ||
+ (
+ this.getMinDistance(pos, node) > HALF_RES_DIST
+ )
+ )
+ ;
+ }
+
+ /**
+ * Checks if this cell should request chunk data
+ * @param pos the player's position
+ * @param node the node
+ * @return true if should request chunk data, false otherwise
+ */
+ public boolean shouldRequest(Vector3d pos, FloatingChunkTreeNode node){
+ return
+ node.isLeaf() &&
+ node.getData() != null &&
+ !node.getData().hasRequested() &&
+ (
+ (
+ node.getLevel() == this.chunkTree.getMaxLevel()
+ // &&
+ // this.getMinDistance(pos, node) <= FULL_RES_DIST
+ )
+ ||
+ (
+ node.getLevel() == this.chunkTree.getMaxLevel() - 1
+ &&
+ this.getMinDistance(pos, node) <= HALF_RES_DIST
+ )
+ )
+ ;
+ }
+
+ /**
+ * Checks if this cell should generate
+ * @param pos the player's position
+ * @param node the node
+ * @return true if should generate, false otherwise
+ */
+ public boolean shouldGenerate(Vector3d pos, FloatingChunkTreeNode node){
+ return
+ node.isLeaf() &&
+ node.getData() != null &&
+ !node.getData().hasGenerated() &&
+ (
+ (
+ node.getLevel() == this.chunkTree.getMaxLevel()
+ // &&
+ // this.getMinDistance(pos, node) <= FULL_RES_DIST
+ )
+ ||
+ (
+ node.getLevel() == this.chunkTree.getMaxLevel() - 1
+ &&
+ this.getMinDistance(pos, node) <= HALF_RES_DIST
+ )
+ )
+ ;
+ }
+
+ /**
+ * Checks if the node should have destroy called on it
+ * @param node The node
+ * @return true if should destroy, false otherwise
+ */
+ public boolean shouldDestroy(FloatingChunkTreeNode node){
+ return
+ node.getData() != null &&
+ node.getData().getEntity() != null
+ ;
+ }
+
+ /**
+ * Destroys the foliage chunk
+ */
+ protected void destroy(){
+ this.recursivelyDestroy(this.chunkTree.getRoot());
+ }
+
+ /**
+ * Recursively destroy a tree
+ * @param node The root of the tree
+ */
+ private void recursivelyDestroy(FloatingChunkTreeNode node){
+ if(node.getChildren().size() > 0){
+ node.getChildren().forEach(child -> recursivelyDestroy(child));
+ }
+ if(node.getData() != null){
+ node.getData().destroy();
+ }
+ }
+
+ /**
+ * Checks if the cell manager made an update last frame or not
+ * @return true if an update occurred, false otherwise
+ */
+ public boolean updatedLastFrame(){
+ return this.updatedLastFrame;
+ }
+
+ /**
+ * Checks if the position is within the full LOD range
+ * @param worldPos The world position
+ * @return true if within full LOD range, false otherwise
+ */
+ public boolean isFullLOD(Vector3i worldPos){
+ Vector3d playerRealPos = EntityUtils.getPosition(Globals.playerEntity);
+ Vector3d chunkMin = Globals.clientWorldData.convertWorldToRealSpace(worldPos);
+ Vector3d chunkMax = Globals.clientWorldData.convertWorldToRealSpace(new Vector3i(worldPos).add(1,1,1));
+ return GeomUtils.getMinDistanceAABB(playerRealPos, chunkMin, chunkMax) <= FULL_RES_DIST;
+ }
+
+ /**
+ * Evicts all cells
+ */
+ public void evictAll(){
+ this.chunkTree.clear();
+ }
+
+
+ /**
+ * Marks a draw cell as updateable
+ * @param worldX The world x position
+ * @param worldY The world y position
+ * @param worldZ The world z position
+ */
+ public void markUpdateable(float worldX, float worldY, float worldZ){
+ throw new Error("Unimplemented");
+ }
+
+ /**
+ * Checks if the terrain cache has a chunk at a given world point
+ * @param worldX the x coordinate
+ * @param worldY the y coordinate
+ * @param worldZ the z coordinate
+ * @return true if the chunk data exists, false otherwise
+ */
+ boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
+ if(Globals.clientTerrainManager != null){
+ return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ);
+ }
+ return true;
+ }
+
+ /**
+ * Requests all chunks for a given draw cell
+ * @param cell The cell
+ */
+ private void requestChunks(WorldOctTree.FloatingChunkTreeNode node){
+ DrawCell cell = node.getData();
+ int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1;
+ for(int i = 0; i < 2 * lodMultiplitier; i++){
+ for(int j = 0; j < 2 * lodMultiplitier; j++){
+ for(int k = 0; k < 2 * lodMultiplitier; k++){
+ Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i,j,k);
+ if(
+ posToCheck.x >= 0 &&
+ posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
+ posToCheck.y >= 0 &&
+ posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
+ posToCheck.z >= 0 &&
+ posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
+ !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z)
+ ){
+ //client should request chunk data from server for each chunk necessary to create the model
+ LoggerInterface.loggerNetworking.DEBUG("(Client) Send Request for terrain at " + posToCheck);
+ Globals.clientTerrainManager.requestChunk(posToCheck.x, posToCheck.y, posToCheck.z);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if all chunk data required to generate this draw cell is present
+ * @param node The node
+ * @param highResFace The higher resolution face of a not-full-resolution chunk. Null if the chunk is max resolution or there is no higher resolution face for the current chunk
+ * @return true if all data is available, false otherwise
+ */
+ private boolean containsDataToGenerate(WorldOctTree.FloatingChunkTreeNode node, List highResFaces){
+ DrawCell cell = node.getData();
+ int lodMultiplitier = this.chunkTree.getMaxLevel() - node.getLevel() + 1;
+ for(int i = 0; i < 2 * lodMultiplitier; i++){
+ for(int j = 0; j < 2 * lodMultiplitier; j++){
+ for(int k = 0; k < 2 * lodMultiplitier; k++){
+ Vector3i posToCheck = new Vector3i(cell.getWorldPos()).add(i,j,k);
+ if(
+ posToCheck.x >= 0 &&
+ posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
+ posToCheck.y >= 0 &&
+ posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
+ posToCheck.z >= 0 &&
+ posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
+ !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z)
+ ){
+ return false;
+ }
+ }
+ }
+ }
+ if(highResFaces != null){
+ for(DrawCellFace highResFace : highResFaces){
+ //x & y are in face-space
+ for(int x = 0; x < 2 * lodMultiplitier; x++){
+ for(int y = 0; y < 2 * lodMultiplitier; y++){
+ Vector3i posToCheck = null;
+ //implicitly performing transforms to adapt from face-space to world space
+ switch(highResFace){
+ case X_POSITIVE: {
+ posToCheck = new Vector3i(cell.getWorldPos()).add(lodMultiplitier,x,y);
+ } break;
+ case X_NEGATIVE: {
+ posToCheck = new Vector3i(cell.getWorldPos()).add(-1,x,y);
+ } break;
+ case Y_POSITIVE: {
+ posToCheck = new Vector3i(cell.getWorldPos()).add(x,lodMultiplitier,y);
+ } break;
+ case Y_NEGATIVE: {
+ posToCheck = new Vector3i(cell.getWorldPos()).add(x,-1,y);
+ } break;
+ case Z_POSITIVE: {
+ posToCheck = new Vector3i(cell.getWorldPos()).add(x,y,lodMultiplitier);
+ } break;
+ case Z_NEGATIVE: {
+ posToCheck = new Vector3i(cell.getWorldPos()).add(x,y,-1);
+ } break;
+ }
+ if(
+ posToCheck.x >= 0 &&
+ posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
+ posToCheck.y >= 0 &&
+ posToCheck.y < Globals.clientWorldData.getWorldDiscreteSize() &&
+ posToCheck.z >= 0 &&
+ posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
+ !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z)
+ ){
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Sets whether the draw cell manager should update or not
+ * @param shouldUpdate true if should update, false otherwise
+ */
+ public void setShouldUpdate(boolean shouldUpdate){
+ this.shouldUpdate = shouldUpdate;
+ }
+
+ /**
+ * Gets whether the client draw cell manager should update or not
+ * @return true if should update, false otherwise
+ */
+ public boolean getShouldUpdate(){
+ return this.shouldUpdate;
+ }
+
+ /**
+ * Gets the number of currently valid cells
+ * @return The number of currently valid cells
+ */
+ public int getValidCellCount(){
+ return validCellCount;
+ }
+
+ /**
+ * Calculates the status of the draw cell manager
+ */
+ public void updateStatus(){
+ maxResCount = 0;
+ halfResCount = 0;
+ generated = 0;
+ this.recursivelyCalculateStatus(this.chunkTree.getRoot());
+ }
+
+ /**
+ * Recursively calculates the status of the manager
+ * @param node The root node
+ */
+ private void recursivelyCalculateStatus(FloatingChunkTreeNode node){
+ if(node.getLevel() == this.chunkTree.getMaxLevel() - 1){
+ halfResCount++;
+ }
+ if(node.getLevel() == this.chunkTree.getMaxLevel()){
+ maxResCount++;
+ }
+ if(node.getData() != null && node.getData().hasGenerated()){
+ generated++;
+ }
+ if(node.getChildren() != null && node.getChildren().size() > 0){
+ List> children = new LinkedList>(node.getChildren());
+ for(FloatingChunkTreeNode child : children){
+ recursivelyCalculateStatus(child);
+ }
+ }
+ }
+
+ /**
+ * Gets The number of maximum resolution chunks
+ * @return The number of maximum resolution chunks
+ */
+ public int getMaxResCount() {
+ return maxResCount;
+ }
+
+ /**
+ * Gets The number of half resolution chunks
+ * @return The number of half resolution chunks
+ */
+ public int getHalfResCount() {
+ return halfResCount;
+ }
+
+ /**
+ * Gets The number of generated chunks
+ * @return
+ */
+ public int getGenerated() {
+ return generated;
+ }
+
+
+
+}
diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java
index d9221982..96eed5b9 100644
--- a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java
+++ b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java
@@ -1,5 +1,7 @@
package electrosphere.client.terrain.cells;
+import java.util.List;
+
import org.joml.Quaterniond;
import org.joml.Vector3d;
import org.joml.Vector3i;
@@ -10,6 +12,7 @@ import electrosphere.engine.Globals;
import electrosphere.entity.ClientEntityUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.types.terrain.TerrainChunk;
+import electrosphere.renderer.meshgen.TransvoxelModelGeneration;
import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData;
/**
@@ -39,15 +42,57 @@ public class DrawCell {
float[][][] weights = new float[ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE];
int[][][] types = new int[ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE];
+ /**
+ * An invalid LOD level
+ */
+ public static final int INVALID_LOD_LEVEL = -1;
- //the maximum detail LOD level
+ /**
+ * The maximum detail LOD level
+ */
public static final int FULL_DETAIL_LOD = 0;
+
+ /**
+ * The half detail lod level
+ */
+ public static final int HALF_DETAIL_LOD = 1;
+
+ /**
+ * The lowest available LOD
+ */
+ public static final int LOWEST_LOD = HALF_DETAIL_LOD;
+
+ /**
+ * The lod level of this draw cell
+ */
+ int lodLevel = FULL_DETAIL_LOD;
+
+ /**
+ * Tracks whether the draw cell has requested its chunk data or not
+ */
+ boolean hasRequested = false;
+
+ /**
+ * Tracks whether the draw cell has generated its entity or not
+ */
+ boolean hasGenerated = false;
+
+
+ /**
+ * The initial voxel type encountered
+ */
+ int initialVoxelType = -1;
+
+ /**
+ * True if there are multiple types of voxels in this chunk
+ */
+ boolean multiVoxelType = false;
/**
* Private constructor
*/
- DrawCell(){
+ private DrawCell(){
}
@@ -66,17 +111,35 @@ public class DrawCell {
/**
* Generates a drawable entity based on this chunk
*/
- public void generateDrawableEntity(VoxelTextureAtlas atlas, int lod, DrawCellFace higherLODFace){
+ public void generateDrawableEntity(VoxelTextureAtlas atlas, int lod, List higherLODFaces){
+ Globals.profiler.beginCpuSample("DrawCell.fillInData");
+ boolean success = this.fillInData(lod);
+ Globals.profiler.endCpuSample();
+ if(!success){
+ this.setHasRequested(false);
+ return;
+ }
if(modelEntity != null){
Globals.clientScene.deregisterEntity(modelEntity);
}
- this.fillInData();
TransvoxelChunkData chunkData = new TransvoxelChunkData(weights, types, lod);
- if(lod > FULL_DETAIL_LOD){
-
+ if(higherLODFaces != null){
+ for(DrawCellFace face : higherLODFaces){
+ Globals.profiler.beginCpuSample("DrawCell.fillInFaceData");
+ success = this.fillInFaceData(chunkData,face,lod);
+ Globals.profiler.endCpuSample();
+ if(!success){
+ this.setHasRequested(false);
+ return;
+ }
+ }
}
- modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(chunkData, lod, atlas);
+ if(lod == INVALID_LOD_LEVEL){
+ throw new Error("Trying to generate invalid LOD");
+ }
+ modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(chunkData, lod, atlas, this.hasPolygons());
ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos(), new Quaterniond());
+ this.setHasGenerated(true);
}
/**
@@ -90,147 +153,322 @@ public class DrawCell {
worldPos.z * ChunkData.CHUNK_SIZE
);
}
+
+ /**
+ * Gets the world-space position of the draw cell
+ * @return the world-space position
+ */
+ protected Vector3i getWorldPos(){
+ return new Vector3i(worldPos);
+ }
/**
* Destroys a drawcell including its physics
*/
public void destroy(){
CollisionEngine collisionEngine = Globals.clientSceneWrapper.getCollisionEngine();
- collisionEngine.destroyPhysics(modelEntity);
- ClientEntityUtils.destroyEntity(modelEntity);
+ if(modelEntity != null){
+ collisionEngine.destroyPhysics(modelEntity);
+ ClientEntityUtils.destroyEntity(modelEntity);
+ }
+ }
+
+ /**
+ * Gets the entity for the cell
+ * @return The entity if it exists, null otherwise
+ */
+ public Entity getEntity(){
+ return modelEntity;
}
/**
* Fills in the internal arrays of data for generate terrain models
+ * @return true if the data was successfully filled, false otherwise
*/
- private void fillInData(){
- //
- //fill in data
- //
- //main chunk
- ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos);
- for(int x = 0; x < ChunkData.CHUNK_SIZE; x++){
- for(int y = 0; y < ChunkData.CHUNK_SIZE; y++){
- for(int z = 0; z < ChunkData.CHUNK_SIZE; z++){
- weights[x][y][z] = currentChunk.getWeight(x,y,z);
- types[x][y][z] = currentChunk.getType(x,y,z);
+ private boolean fillInData(int lod){
+ int spacingFactor = (int)Math.pow(2,lod);
+ for(int x = 0; x < ChunkData.CHUNK_DATA_GENERATOR_SIZE; x++){
+ for(int y = 0; y < ChunkData.CHUNK_DATA_GENERATOR_SIZE; y++){
+ for(int z = 0; z < ChunkData.CHUNK_DATA_GENERATOR_SIZE; z++){
+ ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(
+ worldPos.x + (x * spacingFactor) / ChunkData.CHUNK_SIZE,
+ worldPos.y + (y * spacingFactor) / ChunkData.CHUNK_SIZE,
+ worldPos.z + (z * spacingFactor) / ChunkData.CHUNK_SIZE
+ );
+ if(currentChunk == null){
+ return false;
+ }
+ weights[x][y][z] = currentChunk.getWeight(
+ (x * spacingFactor) % ChunkData.CHUNK_SIZE,
+ (y * spacingFactor) % ChunkData.CHUNK_SIZE,
+ (z * spacingFactor) % ChunkData.CHUNK_SIZE
+ );
+ types[x][y][z] = currentChunk.getType(
+ (x * spacingFactor) % ChunkData.CHUNK_SIZE,
+ (y * spacingFactor) % ChunkData.CHUNK_SIZE,
+ (z * spacingFactor) % ChunkData.CHUNK_SIZE
+ );
+
+ //checks to see if there is only one type of voxel in this chunk
+ if(!this.multiVoxelType){
+ if(this.initialVoxelType == -1){
+ this.initialVoxelType = types[x][y][z];
+ } else if(this.initialVoxelType != types[x][y][z]){
+ this.multiVoxelType = true;
+ }
+ }
}
}
}
- //face X
- if(worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize()){
- currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z);
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
- weights[ChunkData.CHUNK_SIZE][i][j] = currentChunk.getWeight(0, i, j);
- types[ChunkData.CHUNK_SIZE][i][j] = currentChunk.getType(0, i, j);
- }
- }
- } else {
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
- weights[ChunkData.CHUNK_SIZE][i][j] = -1;
- types[ChunkData.CHUNK_SIZE][i][j] = 0;
- }
- }
- }
- //face Y
- if(worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize()){
- currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y + 1, worldPos.z);
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
- weights[i][ChunkData.CHUNK_SIZE][j] = currentChunk.getWeight(i, 0, j);
- types[i][ChunkData.CHUNK_SIZE][j] = currentChunk.getType(i, 0, j);
- }
- }
- } else {
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
- weights[i][ChunkData.CHUNK_SIZE][j] = -1;
- types[i][ChunkData.CHUNK_SIZE][j] = 0;
- }
- }
- }
- //face Z
- if(worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()){
- currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z + 1);
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
- weights[i][j][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(i, j, 0);
- types[i][j][ChunkData.CHUNK_SIZE] = currentChunk.getType(i, j, 0);
- }
- }
- } else {
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){
- weights[i][j][ChunkData.CHUNK_SIZE] = -1;
- types[i][j][ChunkData.CHUNK_SIZE] = 0;
- }
- }
- }
- //edge X-Y
- if(
- worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
- worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize()
- ){
- currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y + 1, worldPos.z);
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = currentChunk.getWeight(0, 0, i);
- types [ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = currentChunk.getType(0, 0, i);
- }
- } else {
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = -1;
- types [ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][i] = 0;
- }
- }
- //edge X-Z
- if(
- worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
- worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()
- ){
- currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z + 1);
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- weights[ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(0, i, 0);
- types [ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = currentChunk.getType(0, i, 0);
- }
- } else {
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- weights[ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = -1;
- types [ChunkData.CHUNK_SIZE][i][ChunkData.CHUNK_SIZE] = 0;
- }
- }
- //edge Y-Z
- if(
- worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
- worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()
- ){
- currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y + 1, worldPos.z + 1);
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- weights[i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(i, 0, 0);
- types [i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getType(i, 0, 0);
- }
- } else {
- for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){
- weights[i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = -1;
- types [i][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = 0;
- }
- }
- if(
- worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
- worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() &&
- worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()
- ){
- currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y + 1, worldPos.z + 1);
- if(currentChunk != null){
- weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getWeight(0, 0, 0);
- types[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = currentChunk.getType(0, 0, 0);
- }
- } else {
- weights[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = -1;
- types[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = 0;
- }
+ return true;
}
+
+ /**
+ * Fills in the data for the higher resolution face
+ * @param chunkData The data for the chunk to generate
+ * @param higherLODFace The face that is higher LOD
+ * @param lod The Level of Detail for this chunk
+ * @return true if successfully filled in data, false otherwise
+ */
+ private boolean fillInFaceData(TransvoxelChunkData chunkData, DrawCellFace higherLODFace, int lod){
+ int mainSpacing = (int)Math.pow(2,lod);
+ int higherResSpacing = (int)Math.pow(2,lod - 1);
+ float[][] faceWeights = new float[TransvoxelModelGeneration.FACE_DATA_DIMENSIONS][TransvoxelModelGeneration.FACE_DATA_DIMENSIONS];
+ int[][] faceTypes = new int[TransvoxelModelGeneration.FACE_DATA_DIMENSIONS][TransvoxelModelGeneration.FACE_DATA_DIMENSIONS];
+ //allocate face array
+ for(int x = 0; x < TransvoxelModelGeneration.FACE_DATA_DIMENSIONS; x++){
+ for(int y = 0; y < TransvoxelModelGeneration.FACE_DATA_DIMENSIONS; y++){
+ int worldCoordOffset1 = (x * higherResSpacing) / ChunkData.CHUNK_SIZE;
+ int worldCoordOffset2 = (y * higherResSpacing) / ChunkData.CHUNK_SIZE;
+ //solve coordinates relative to the face
+ int localCoord1 = (x * higherResSpacing) % ChunkData.CHUNK_SIZE;
+ int localCoord2 = (y * higherResSpacing) % ChunkData.CHUNK_SIZE;
+
+ //implicitly performing transforms to adapt from face-space to world & local space
+ switch(higherLODFace){
+ case X_POSITIVE: {
+ ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
+ worldPos.x + (17 * mainSpacing) / ChunkData.CHUNK_SIZE,
+ worldPos.y + worldCoordOffset1,
+ worldPos.z + worldCoordOffset2
+ ));
+ if(currentChunk == null){
+ return false;
+ }
+ faceWeights[x][y] = currentChunk.getWeight(
+ 0,
+ localCoord1,
+ localCoord2
+ );
+ faceTypes[x][y] = currentChunk.getType(
+ 0,
+ localCoord1,
+ localCoord2
+ );
+ } break;
+ case X_NEGATIVE: {
+ ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
+ worldPos.x,
+ worldPos.y + worldCoordOffset1,
+ worldPos.z + worldCoordOffset2
+ ));
+ if(currentChunk == null){
+ return false;
+ }
+ faceWeights[x][y] = currentChunk.getWeight(
+ 0,
+ localCoord1,
+ localCoord2
+ );
+ faceTypes[x][y] = currentChunk.getType(
+ 0,
+ localCoord1,
+ localCoord2
+ );
+ } break;
+ case Y_POSITIVE: {
+ ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
+ worldPos.x + worldCoordOffset1,
+ worldPos.y + (17 * mainSpacing) / ChunkData.CHUNK_SIZE,
+ worldPos.z + worldCoordOffset2
+ ));
+ if(currentChunk == null){
+ return false;
+ }
+ faceWeights[x][y] = currentChunk.getWeight(
+ localCoord1,
+ 0,
+ localCoord2
+ );
+ faceTypes[x][y] = currentChunk.getType(
+ localCoord1,
+ 0,
+ localCoord2
+ );
+ } break;
+ case Y_NEGATIVE: {
+ ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
+ worldPos.x + worldCoordOffset1,
+ worldPos.y,
+ worldPos.z + worldCoordOffset2
+ ));
+ if(currentChunk == null){
+ return false;
+ }
+ faceWeights[x][y] = currentChunk.getWeight(
+ localCoord1,
+ 0,
+ localCoord2
+ );
+ faceTypes[x][y] = currentChunk.getType(
+ localCoord1,
+ 0,
+ localCoord2
+ );
+ } break;
+ case Z_POSITIVE: {
+ ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
+ worldPos.x + worldCoordOffset1,
+ worldPos.y + worldCoordOffset2,
+ worldPos.z + (17 * mainSpacing) / ChunkData.CHUNK_SIZE
+ ));
+ if(currentChunk == null){
+ return false;
+ }
+ faceWeights[x][y] = currentChunk.getWeight(
+ localCoord1,
+ localCoord2,
+ 0
+ );
+ faceTypes[x][y] = currentChunk.getType(
+ localCoord1,
+ localCoord2,
+ 0
+ );
+ } break;
+ case Z_NEGATIVE: {
+ ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(new Vector3i(
+ worldPos.x + worldCoordOffset1,
+ worldPos.y + worldCoordOffset2,
+ worldPos.z
+ ));
+ if(currentChunk == null){
+ return false;
+ }
+ faceWeights[x][y] = currentChunk.getWeight(
+ localCoord1,
+ localCoord2,
+ 0
+ );
+ faceTypes[x][y] = currentChunk.getType(
+ localCoord1,
+ localCoord2,
+ 0
+ );
+ } break;
+ }
+ // Vector3i sampleChunkWorldPos = new Vector3i(
+ // worldPos.x + (x * higherResSpacing) / ChunkData.CHUNK_SIZE,
+ // worldPos.y + (y * higherResSpacing) / ChunkData.CHUNK_SIZE,
+ // worldPos.z + (z * spacingFactor) / ChunkData.CHUNK_SIZE
+ // );
+ // ChunkData currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(sampleChunkWorldPos);
+ // if(currentChunk == null){
+ // throw new Error("Chunk is null! " + worldPos);
+ // }
+ // weights[x][y][z] = currentChunk.getWeight(
+ // (x * higherResSpacing) % ChunkData.CHUNK_SIZE,
+ // (y * higherResSpacing) % ChunkData.CHUNK_SIZE,
+ // (z * spacingFactor) % ChunkData.CHUNK_SIZE
+ // );
+ // types[x][y][z] = currentChunk.getType(
+ // (x * higherResSpacing) % ChunkData.CHUNK_SIZE,
+ // (y * higherResSpacing) % ChunkData.CHUNK_SIZE,
+ // (z * spacingFactor) % ChunkData.CHUNK_SIZE
+ // );
+ }
+ }
+ switch(higherLODFace){
+ case X_POSITIVE: {
+ chunkData.addXPositiveEdge(faceWeights, faceTypes);
+ } break;
+ case X_NEGATIVE: {
+ chunkData.addXNegativeEdge(faceWeights, faceTypes);
+ } break;
+ case Y_POSITIVE: {
+ chunkData.addYPositiveEdge(faceWeights, faceTypes);
+ } break;
+ case Y_NEGATIVE: {
+ chunkData.addYNegativeEdge(faceWeights, faceTypes);
+ } break;
+ case Z_POSITIVE: {
+ chunkData.addZPositiveEdge(faceWeights, faceTypes);
+ } break;
+ case Z_NEGATIVE: {
+ chunkData.addZNegativeEdge(faceWeights, faceTypes);
+ } break;
+ }
+ return true;
+ }
+
+
+ /**
+ * Gets the LOD level
+ * @return The LOD level
+ */
+ public int getLodLevel() {
+ return lodLevel;
+ }
+
+ /**
+ * Sets the LOD level
+ * @param lodLevel The LOD level
+ */
+ public void setLodLevel(int lodLevel) {
+ this.lodLevel = lodLevel;
+ }
+
+ /**
+ * Gets whether this draw cell has requested its chunk data or not
+ * @return true if has requested, false otherwise
+ */
+ public boolean hasRequested() {
+ return hasRequested;
+ }
+
+ /**
+ * Sets whether this draw cell has requested its chunk data or not
+ * @param hasRequested true if has requested, false otherwise
+ */
+ public void setHasRequested(boolean hasRequested) {
+ this.hasRequested = hasRequested;
+ }
+
+ /**
+ * Gets whether this draw cell has generated its entity or not
+ * @return true if has generated, false otherwise
+ */
+ public boolean hasGenerated() {
+ return hasGenerated;
+ }
+
+ /**
+ * Sets whether this draw cell has generated its entity or not
+ * @param hasGenerated true if has generated, false otherwise
+ */
+ public void setHasGenerated(boolean hasGenerated) {
+ this.hasGenerated = hasGenerated;
+ }
+
+ /**
+ * Gets whether this draw cell will generate polygons or not
+ * @return true if it has polygons, false otherwise
+ */
+ private boolean hasPolygons(){
+ return this.multiVoxelType;
+ }
+
+
}
diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java
index 8b76ea40..de4c287e 100644
--- a/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java
+++ b/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java
@@ -2,6 +2,7 @@ package electrosphere.client.terrain.cells;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -18,6 +19,7 @@ import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.renderer.shader.ShaderProgram;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
+@Deprecated
/**
* Manages the graphical entities for the terrain chunks
*
@@ -235,7 +237,7 @@ public class DrawCellManager {
);
cells.add(cell);
keyCellMap.put(targetKey,cell);
- DrawCellFace higherLODFace = null;
+ List higherLODFace = null;
keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace);
//evaluate for foliage
@@ -261,7 +263,7 @@ public class DrawCellManager {
worldPos.z < Globals.clientWorldData.getWorldDiscreteSize()
){
keyCellMap.get(targetKey).destroy();
- DrawCellFace higherLODFace = null;
+ List higherLODFace = null;
keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace);
}
drawable.add(targetKey);
@@ -304,7 +306,7 @@ public class DrawCellManager {
* @return the cell coordinate
*/
public int transformRealSpaceToCellSpace(double input){
- return (int)(input / Globals.clientWorldData.getDynamicInterpolationRatio());
+ return (int)(input / ServerTerrainChunk.CHUNK_DIMENSION);
}
/**
diff --git a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java
index baf4d774..5fdba3c1 100644
--- a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java
+++ b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java
@@ -8,12 +8,14 @@ import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Semaphore;
import org.joml.Vector3i;
import electrosphere.client.scene.ClientWorldData;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.client.terrain.cache.ClientTerrainCache;
+import electrosphere.client.terrain.cells.ClientDrawCellManager;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.engine.Globals;
import electrosphere.entity.types.terrain.TerrainChunkData;
@@ -21,6 +23,7 @@ import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.renderer.meshgen.TerrainChunkModelGeneration;
import electrosphere.renderer.model.Model;
+import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.server.terrain.manager.ServerTerrainManager;
/**
@@ -30,12 +33,22 @@ public class ClientTerrainManager {
//queues messages from server
List messageQueue = new CopyOnWriteArrayList();
+
+ /**
+ * Locks the terrain manager (eg if adding message from network)
+ */
+ static Semaphore lock = new Semaphore(1);
//The interpolation ratio of terrain
public static final int INTERPOLATION_RATIO = ServerTerrainManager.SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO;
//caches chunks from server
- static final int CACHE_SIZE = 500;
+ static final int CACHE_SIZE = 1500 + (int)(ClientDrawCellManager.FULL_RES_DIST * 10) + (int)(ClientDrawCellManager.HALF_RES_DIST * 10);
+
+ /**
+ * Size of the cache in bytes
+ */
+ static final int CACHE_SIZE_IN_MB = (CACHE_SIZE * ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION * ServerTerrainChunk.CHUNK_DIMENSION * 2 * 4) / 1024 / 1024;
//used for caching the macro values
ClientTerrainCache terrainCache;
@@ -58,6 +71,8 @@ public class ClientTerrainManager {
* Handles messages that have been received from the server
*/
public void handleMessages(){
+ Globals.profiler.beginCpuSample("ClientTerrainManager.handleMessages");
+ lock.acquireUninterruptibly();
List bouncedMessages = new LinkedList();
for(TerrainMessage message : messageQueue){
messageQueue.remove(message);
@@ -83,7 +98,7 @@ public class ClientTerrainManager {
}
}
}
- ChunkData data = new ChunkData();
+ ChunkData data = new ChunkData(message.getworldX(), message.getworldY(), message.getworldZ());
data.setVoxelType(values);
data.setVoxelWeight(weights);
terrainCache.addChunkDataToCache(
@@ -99,6 +114,8 @@ public class ClientTerrainManager {
for(TerrainMessage message : bouncedMessages){
messageQueue.add(message);
}
+ lock.release();
+ Globals.profiler.endCpuSample();
}
/**
@@ -113,7 +130,9 @@ public class ClientTerrainManager {
* @param message The message
*/
public void attachTerrainMessage(TerrainMessage message){
+ lock.acquireUninterruptibly();
messageQueue.add(message);
+ lock.release();
}
/**
@@ -135,6 +154,22 @@ public class ClientTerrainManager {
public boolean containsChunkDataAtWorldPoint(Vector3i worldPos){
return terrainCache.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z);
}
+
+ /**
+ * Requests a chunk from the server
+ * @param worldX the world x coordinate of the chunk
+ * @param worldY the world y coordinate of the chunk
+ * @param worldZ the world z coordinate of the chunk
+ */
+ public void requestChunk(int worldX, int worldY, int worldZ){
+ if(!this.terrainCache.hasRequested(worldX, worldY, worldZ)){
+ Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage(
+ worldX,
+ worldY,
+ worldZ
+ ));
+ }
+ }
/**
* Checks that the cache contains chunk data at a real-space coordinate
@@ -184,7 +219,9 @@ public class ClientTerrainManager {
UUID newUUID = UUID.randomUUID();
promisedHash = newUUID.toString();
TerrainChunkGenQueueItem queueItem = new TerrainChunkGenQueueItem(data, promisedHash, atlas);
+ lock.acquireUninterruptibly();
terrainChunkGenerationQueue.add(queueItem);
+ lock.release();
return promisedHash;
}
@@ -192,12 +229,14 @@ public class ClientTerrainManager {
* Pushes all terrain data in queue to the gpu and registers the resulting models
*/
public static void generateTerrainChunkGeometry(){
- Globals.profiler.beginCpuSample("generateTerrainChunkGeometry");
+ Globals.profiler.beginCpuSample("ClientTerrainManager.generateTerrainChunkGeometry");
+ lock.acquireUninterruptibly();
for(TerrainChunkGenQueueItem queueItem : terrainChunkGenerationQueue){
Model terrainModel = TerrainChunkModelGeneration.generateTerrainModel(queueItem.getData(), queueItem.getAtlas());
Globals.assetManager.registerModelToSpecificString(terrainModel, queueItem.getPromisedHash());
}
terrainChunkGenerationQueue.clear();
+ lock.release();
Globals.profiler.endCpuSample();
}
@@ -217,5 +256,13 @@ public class ClientTerrainManager {
public Vector3i getPositionOfChunk(ChunkData chunk){
return terrainCache.getChunkPosition(chunk);
}
+
+ /**
+ * Gets the number of chunks that have been requested
+ * @return The number of chunks
+ */
+ public int getRequestedCellCount(){
+ return this.terrainCache.getRequestedCellCount();
+ }
}
diff --git a/src/main/java/electrosphere/client/ui/menu/WindowUtils.java b/src/main/java/electrosphere/client/ui/menu/WindowUtils.java
index f4235921..59aff70c 100644
--- a/src/main/java/electrosphere/client/ui/menu/WindowUtils.java
+++ b/src/main/java/electrosphere/client/ui/menu/WindowUtils.java
@@ -184,6 +184,9 @@ public class WindowUtils {
initTooltipWindow();
}
+ /**
+ * Inits the loading window
+ */
static void initLoadingWindow(){
Window loadingWindow = Window.create(Globals.renderingEngine.getOpenGLState(), 0, 0, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT, false);
Label loadingLabel = Label.createLabel("LOADING");
@@ -208,6 +211,18 @@ public class WindowUtils {
Globals.elementService.registerWindow(WindowStrings.WINDOW_ITEM_DRAG_CONTAINER, itemDragContainerWindow);
}
+ /**
+ * Updates the loading window
+ */
+ public static void updateLoadingWindow(String message){
+ Globals.signalSystem.post(SignalType.UI_MODIFICATION,() -> {
+ Window loadingWindow = (Window)Globals.elementService.getWindow(WindowStrings.WINDOW_LOADING);
+ loadingWindow.clear();
+ loadingWindow.addChild(Label.createLabel(message));
+ Globals.signalSystem.post(SignalType.YOGA_APPLY,loadingWindow);
+ });
+ }
+
/**
* Creates the tooltip window
*/
diff --git a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java
new file mode 100644
index 00000000..3067161f
--- /dev/null
+++ b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiChunkMonitor.java
@@ -0,0 +1,87 @@
+package electrosphere.client.ui.menu.debug;
+
+import electrosphere.engine.Globals;
+import electrosphere.renderer.ui.imgui.ImGuiLinePlot;
+import electrosphere.renderer.ui.imgui.ImGuiLinePlot.ImGuiLinePlotDataset;
+import electrosphere.renderer.ui.imgui.ImGuiWindow;
+import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback;
+import imgui.ImGui;
+
+public class ImGuiChunkMonitor {
+
+ /**
+ * Num datapoints
+ */
+ public static final int PRESSURE_GRAPH_POINT_COUNT = 100;
+
+ /**
+ * Window for viewing chunk status on server and client
+ */
+ protected static ImGuiWindow chunkMonitorWindow;
+
+ /**
+ * Creates the windows in this file
+ */
+ protected static void createChunkMonitorWindows(){
+ createChunkMonitorWindow();
+ }
+
+ /**
+ * Client scene entity view
+ */
+ protected static void createChunkMonitorWindow(){
+ chunkMonitorWindow = new ImGuiWindow("Chunk Monitor");
+
+ //client network pressure graph
+ ImGuiLinePlot clientNetworkBandwith = new ImGuiLinePlot("Client Network Pressure",400,400);
+ ImGuiLinePlotDataset clientPressureDataset = new ImGuiLinePlotDataset("Client bytes per frame", PRESSURE_GRAPH_POINT_COUNT);
+ clientPressureDataset.zeroOut();
+ clientNetworkBandwith.addDataset(clientPressureDataset);
+
+ //server network pressure graph
+ ImGuiLinePlot serverNetworkPressureGraph = new ImGuiLinePlot("Server Network Pressure",400,400);
+ ImGuiLinePlotDataset serverPressureDataset = new ImGuiLinePlotDataset("Server bytes per frame", PRESSURE_GRAPH_POINT_COUNT);
+ serverPressureDataset.zeroOut();
+ serverNetworkPressureGraph.addDataset(serverPressureDataset);
+
+ chunkMonitorWindow.setCallback(new ImGuiWindowCallback() {
+ long clientPressureLastValue = 0;
+ long serverPressureLastValue = 0;
+
+ @Override
+ public void exec() {
+
+
+ //ui framework text
+ ImGui.text("Chunk Monitor");
+
+ Globals.clientDrawCellManager.updateStatus();
+ ImGui.text("Renderable chunks: " + Globals.clientDrawCellManager.getGenerated());
+ ImGui.text("Full res chunks: " + Globals.clientDrawCellManager.getMaxResCount());
+ ImGui.text("Half res chunks: " + Globals.clientDrawCellManager.getHalfResCount());
+
+
+ //client network pressure
+ if(Globals.clientConnection != null){
+ long clientPressureNewTotal = Globals.clientConnection.getNumBytesRead();
+ long clientPressureDelta = clientPressureNewTotal - clientPressureLastValue;
+ clientPressureDataset.addPoint(clientPressureDelta);
+ clientPressureLastValue = clientPressureNewTotal;
+ }
+ clientNetworkBandwith.draw();
+
+ //server network pressure
+ if(Globals.server != null && Globals.server.getFirstConnection() != null){
+ long serverPressureNewTotal = Globals.server.getFirstConnection().getNumBytesRead();
+ long serverPressureDelta = serverPressureNewTotal - serverPressureLastValue;
+ serverPressureDataset.addPoint(serverPressureDelta);
+ serverPressureLastValue = serverPressureNewTotal;
+ }
+ serverNetworkPressureGraph.draw();
+
+ }
+ });
+ chunkMonitorWindow.setOpen(false);
+ Globals.renderingEngine.getImGuiPipeline().addImGuiWindow(chunkMonitorWindow);
+ }
+}
diff --git a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiTestGen.java b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiTestGen.java
index a15e476e..52400574 100644
--- a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiTestGen.java
+++ b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiTestGen.java
@@ -47,7 +47,7 @@ public class ImGuiTestGen {
if(ImGui.button("Regenerate")){
GriddedDataCellManager gridManager = (GriddedDataCellManager)Globals.realmManager.first().getDataCellManager();
gridManager.evictAll();
- Globals.drawCellManager.evictAll();
+ Globals.clientDrawCellManager.evictAll();
Globals.clientTerrainManager.evictAll();
}
@@ -80,6 +80,11 @@ public class ImGuiTestGen {
if(ImGui.inputInt("biome[1][1]", biome11)){
terrainModel.getBiome()[1][1] = biome11.shortValue();
}
+
+ //Toggles whether the client draws cell manager should update or not
+ if(ImGui.button("Toggle ClientDrawCellManager updates")){
+ Globals.clientDrawCellManager.setShouldUpdate(!Globals.clientDrawCellManager.getShouldUpdate());
+ }
}
});
diff --git a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiWindowMacros.java b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiWindowMacros.java
index f0962f94..1a019e8d 100644
--- a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiWindowMacros.java
+++ b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiWindowMacros.java
@@ -51,6 +51,7 @@ public class ImGuiWindowMacros {
ImGuiLogger.createLoggersWindows();
ImGuiRenderer.createRendererWindows();
ImGuiTestGen.createTestGenWindows();
+ ImGuiChunkMonitor.createChunkMonitorWindows();
}
/**
@@ -183,6 +184,10 @@ public class ImGuiWindowMacros {
){
ImGuiTestGen.testGenWindow.setOpen(true);
}
+ //chunk monitor
+ if(ImGui.button("Chunk Monitor")){
+ ImGuiChunkMonitor.chunkMonitorWindow.setOpen(true);
+ }
//close button
if(ImGui.button("Close")){
mainDebugWindow.setOpen(false);
diff --git a/src/main/java/electrosphere/collision/CollisionEngine.java b/src/main/java/electrosphere/collision/CollisionEngine.java
index 6fd71c84..8c31840b 100644
--- a/src/main/java/electrosphere/collision/CollisionEngine.java
+++ b/src/main/java/electrosphere/collision/CollisionEngine.java
@@ -724,7 +724,9 @@ public class CollisionEngine {
DBody rigidBody = PhysicsEntityUtils.getDBody(e);
deregisterCollisionObject(rigidBody,PhysicsEntityUtils.getCollidable(e));
e.removeData(EntityDataStrings.PHYSICS_COLLISION_BODY);
- deregisterPhysicsObject(rigidBody);
+ if(rigidBody != null){
+ deregisterPhysicsObject(rigidBody);
+ }
}
if(ServerPhysicsSyncTree.hasTree(e)){
ServerPhysicsSyncTree.detachTree(e, ServerPhysicsSyncTree.getTree(e));
diff --git a/src/main/java/electrosphere/collision/CollisionWorldData.java b/src/main/java/electrosphere/collision/CollisionWorldData.java
index 33c3d00d..ade9da9d 100644
--- a/src/main/java/electrosphere/collision/CollisionWorldData.java
+++ b/src/main/java/electrosphere/collision/CollisionWorldData.java
@@ -62,7 +62,7 @@ public class CollisionWorldData {
public int getDynamicInterpolationRatio(){
if(clientWorldData != null){
- return clientWorldData.getDynamicInterpolationRatio();
+ return clientWorldData.getWorldDiscreteSize();
} else {
return serverWorldData.getDynamicInterpolationRatio();
}
diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java
index 7e7a8d05..8f6a99d0 100644
--- a/src/main/java/electrosphere/engine/Globals.java
+++ b/src/main/java/electrosphere/engine/Globals.java
@@ -21,7 +21,7 @@ import electrosphere.client.player.ClientPlayerData;
import electrosphere.client.scene.ClientSceneWrapper;
import electrosphere.client.scene.ClientWorldData;
import electrosphere.client.sim.ClientSimulation;
-import electrosphere.client.terrain.cells.DrawCellManager;
+import electrosphere.client.terrain.cells.ClientDrawCellManager;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.client.ui.menu.WindowUtils;
@@ -235,7 +235,7 @@ public class Globals {
//
//Generic OpenGL Statements
//
- public static long window;
+ public static long window = -1;
@@ -351,7 +351,7 @@ public class Globals {
//chunk stuff
//draw cell manager
- public static DrawCellManager drawCellManager;
+ public static ClientDrawCellManager clientDrawCellManager;
public static VoxelTextureAtlas voxelTextureAtlas = new VoxelTextureAtlas();
//fluid cell manager
@@ -696,6 +696,7 @@ public class Globals {
Globals.realmManager = null;
Globals.clientSceneWrapper = null;
Globals.clientScene = null;
+ Globals.clientWorldData = null;
Globals.audioEngine = null;
Globals.renderingEngine = null;
Globals.threadManager = null;
diff --git a/src/main/java/electrosphere/engine/Main.java b/src/main/java/electrosphere/engine/Main.java
index eb0c9f12..8cca6c93 100644
--- a/src/main/java/electrosphere/engine/Main.java
+++ b/src/main/java/electrosphere/engine/Main.java
@@ -420,14 +420,7 @@ public class Main {
Globals.profiler.endCpuSample();
} catch (NullPointerException ex){
- LoggerInterface.loggerEngine.ERROR("Main frame uncaught NPE", ex);
- //after a while, jvm will stop reporting stack traces with errors
- //need to explicitly kill the vm if you want to see the stack trace
- if(Globals.ENGINE_DEBUG){
- System.exit(1);
- } else {
- throw new Error("NPE!");
- }
+ LoggerInterface.loggerEngine.ERROR(ex);
}
}
diff --git a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java
index 9da36f63..9d6523d7 100644
--- a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java
+++ b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java
@@ -10,7 +10,7 @@ import electrosphere.client.entity.crosshair.Crosshair;
import electrosphere.client.fluid.cells.FluidCellManager;
import electrosphere.client.foliagemanager.ClientFoliageManager;
import electrosphere.client.sim.ClientSimulation;
-import electrosphere.client.terrain.cells.DrawCellManager;
+import electrosphere.client.terrain.cells.ClientDrawCellManager;
import electrosphere.client.ui.menu.MenuGenerators;
import electrosphere.client.ui.menu.WindowStrings;
import electrosphere.client.ui.menu.WindowUtils;
@@ -29,16 +29,27 @@ import electrosphere.net.NetUtils;
import electrosphere.net.client.ClientNetworking;
import electrosphere.renderer.actor.Actor;
import electrosphere.renderer.actor.ActorTextureMask;
-import electrosphere.renderer.ui.elements.Window;
public class ClientLoading {
+
+
+ /**
+ * Number of frames to wait before updating status of draw cell manager loading
+ */
+ static final int DRAW_CELL_UPDATE_RATE = 60;
+
+
+ /**
+ * The number of frames the draw cell is expected to take (minimum) to init
+ */
+ static final int DRAW_CELL_EXPECTED_MINIMUM_FRAMES_TO_INIT = 100;
protected static void loadCharacterServer(Object[] params){
- Window loadingWindow = (Window)Globals.elementService.getWindow(WindowStrings.WINDOW_LOADING);
WindowUtils.recursiveSetVisible(Globals.elementService.getWindow(WindowStrings.WINDOW_MENU_MAIN), false);
WindowUtils.replaceMainMenuContents(MenuGenerators.createEmptyMainMenu());
- loadingWindow.setVisible(true);
+ WindowUtils.recursiveSetVisible(Globals.elementService.getWindow(WindowStrings.WINDOW_LOADING), true);
+ WindowUtils.updateLoadingWindow("Waiting on server");
//disable menu input
Globals.controlHandler.hintUpdateControlState(ControlHandler.ControlsState.NO_INPUT);
//initialize the client thread (client)
@@ -54,7 +65,7 @@ public class ClientLoading {
//eventually should replace with at ui to select an already created character or create a new one
WindowUtils.replaceMainMenuContents(MenuGeneratorsMultiplayer.createMultiplayerCharacterCreationWindow());
//make loading dialog disappear
- loadingWindow.setVisible(false);
+ WindowUtils.recursiveSetVisible(Globals.elementService.getWindow(WindowStrings.WINDOW_LOADING), false);
//make character creation window visible
WindowUtils.recursiveSetVisible(Globals.elementService.getWindow(WindowStrings.WINDOW_MENU_MAIN), true);
//recapture window
@@ -70,6 +81,7 @@ public class ClientLoading {
Globals.signalSystem.post(SignalType.UI_MODIFICATION, () -> {
WindowUtils.closeWindow(WindowStrings.WINDOW_MENU_MAIN);
WindowUtils.recursiveSetVisible(WindowStrings.WINDOW_LOADING, true);
+ WindowUtils.updateLoadingWindow("LOADING");
});
//disable menu input
Globals.controlHandler.hintUpdateControlState(ControlHandler.ControlsState.NO_INPUT);
@@ -263,7 +275,8 @@ public class ClientLoading {
*/
static void initDrawCellManager(boolean blockForInit){
int iterations = 0;
- while(blockForInit && (Globals.clientWorldData == null || InitialAssetLoading.atlasQueuedTexture == null || !InitialAssetLoading.atlasQueuedTexture.hasLoaded())){
+ WindowUtils.updateLoadingWindow("WAITING ON WORLD DATA");
+ while(blockForInit && (Globals.clientWorldData == null || InitialAssetLoading.atlasQueuedTexture == null || !InitialAssetLoading.atlasQueuedTexture.hasLoaded()) && Globals.threadManager.shouldKeepRunning()){
try {
TimeUnit.MILLISECONDS.sleep(10);
iterations++;
@@ -278,28 +291,25 @@ public class ClientLoading {
}
}
//initialize draw cell manager
- Globals.drawCellManager = new DrawCellManager(Globals.clientTerrainManager, 0, 0, 0);
- //construct texture atlas
- Globals.drawCellManager.attachTextureAtlas(Globals.voxelTextureAtlas);
- //set our draw cell manager to actually generate drawable chunks
- Globals.drawCellManager.setGenerateDrawables(true);
+ // Globals.drawCellManager = new DrawCellManager(Globals.clientTerrainManager, 0, 0, 0);
+ Globals.clientDrawCellManager = new ClientDrawCellManager(Globals.voxelTextureAtlas, Globals.clientWorldData.getWorldDiscreteSize());
//Alerts the client simulation that it should start loading terrain
Globals.clientSimulation.setLoadingTerrain(true);
//wait for all the terrain data to arrive
- while(blockForInit && Globals.drawCellManager.containsUnrequestedCell()){
+ int i = 0;
+ while(blockForInit && Globals.clientDrawCellManager.updatedLastFrame() && Globals.threadManager.shouldKeepRunning()){
+ i++;
+ if(i % DRAW_CELL_UPDATE_RATE == 0){
+ WindowUtils.updateLoadingWindow("WAITING ON SERVER TO SEND TERRAIN (" + Globals.clientTerrainManager.getAllChunks().size() + "/" + Globals.clientTerrainManager.getRequestedCellCount() + ")");
+ }
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
-
- while(blockForInit && Globals.drawCellManager.containsUndrawableCell()){
- try {
- TimeUnit.MILLISECONDS.sleep(10);
- } catch (InterruptedException ex) {
- ex.printStackTrace();
- }
+ if(i < DRAW_CELL_EXPECTED_MINIMUM_FRAMES_TO_INIT){
+ LoggerInterface.loggerEngine.WARNING("Probably didn't block for draw cell manager initialization!");
}
}
@@ -310,7 +320,8 @@ public class ClientLoading {
static void initFluidCellManager(boolean blockForInit){
//wait for world data
- while(blockForInit && Globals.clientWorldData == null){
+ WindowUtils.updateLoadingWindow("WAITING ON WORLD DATA");
+ while(blockForInit && Globals.clientWorldData == null && Globals.threadManager.shouldKeepRunning()){
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException ex) {
@@ -323,7 +334,8 @@ public class ClientLoading {
Globals.clientSimulation.setLoadingTerrain(true);
//wait for all the terrain data to arrive
- while(blockForInit && Globals.fluidCellManager.containsUnrequestedCell()){
+ WindowUtils.updateLoadingWindow("REQUESTING FLUID CHUNKS FROM SERVER (" + Globals.fluidCellManager.getUnrequestedSize() + ")");
+ while(blockForInit && Globals.fluidCellManager.containsUnrequestedCell() && Globals.threadManager.shouldKeepRunning()){
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException ex) {
@@ -332,7 +344,8 @@ public class ClientLoading {
}
//wait for undrawable cells
- while(blockForInit && Globals.fluidCellManager.containsUndrawableCell()){
+ WindowUtils.updateLoadingWindow("WAITING ON SERVER TO SEND FLUID CHUNKS (" + Globals.fluidCellManager.getUndrawableSize() + ")");
+ while(blockForInit && Globals.fluidCellManager.containsUndrawableCell() && Globals.threadManager.shouldKeepRunning()){
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException ex) {
diff --git a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java
index 526f344c..aee1994d 100644
--- a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java
+++ b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java
@@ -17,7 +17,6 @@ import electrosphere.entity.types.creature.CreatureTemplate;
import electrosphere.entity.types.creature.CreatureToolbarData.ToolbarItem;
import electrosphere.game.data.creature.type.CreatureData;
import electrosphere.game.data.creature.type.visualattribute.VisualAttribute;
-import electrosphere.game.server.world.MacroData;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.NetUtils;
import electrosphere.net.client.ClientNetworking;
@@ -26,41 +25,17 @@ import electrosphere.net.server.Server;
import electrosphere.net.server.ServerConnectionHandler;
import electrosphere.net.server.player.Player;
import electrosphere.server.datacell.Realm;
-import electrosphere.server.simulation.MacroSimulation;
import electrosphere.server.simulation.MicroSimulation;
/**
* Utilities for all loading thread types
*/
public class LoadingUtils {
-
-
- // static void initCommonWorldData(boolean FLAG_INIT_SERVER){
- // if(Globals.commonWorldData == null){
- // if(FLAG_INIT_SERVER){
- // Globals.commonWorldData = new CommonWorldData(Globals.serverWorldData, Globals.serverTerrainManager);
- // if(Globals.macroSimulation != null){
- // Town startTown = Globals.macroData.getTowns().get(0);
- // Vector2i firstPos = startTown.getPositions().get(0);
- // // double startX = firstPos.x * Globals.serverTerrainManager.getChunkWidth();
- // // double startZ = firstPos.y * Globals.serverTerrainManager.getChunkWidth();
- // double startX = Globals.commonWorldData.convertWorldToReal(firstPos.x);
- // double startZ = Globals.commonWorldData.convertWorldToReal(firstPos.y);
- // Globals.spawnPoint.set((float)startX,(float)Globals.commonWorldData.getElevationAtPoint(new Vector3d(startX,0,startZ)),(float)startZ);
- // }
- // } else {
- // //basically wait for the client to receive the world metadata
- // while(!Globals.clientConnection.getClientProtocol().hasReceivedWorld()){
- // try {
- // TimeUnit.MILLISECONDS.sleep(5);
- // } catch (InterruptedException ex) {
- // }
- // }
- // //then create common world data
- // Globals.commonWorldData = new CommonWorldData(Globals.clientWorldData, Globals.clientTerrainManager);
- // }
- // }
- // }
+
+ /**
+ * The size of the buffer
+ */
+ static final int STREAM_BUFFER_SIZE = 16 * 1024 * 1024;
@@ -93,7 +68,12 @@ public class LoadingUtils {
}
}
- static final int STREAM_BUFFER_SIZE = 32 * 1024 * 1024;
+
+ /**
+ * Initializes a local connection
+ * @param runServerThread true if should run the server in a separate thread, false otherwise
+ * @return The server connection
+ */
static ServerConnectionHandler initLocalConnection(boolean runServerThread){
ServerConnectionHandler rVal = null;
try {
@@ -106,7 +86,7 @@ public class LoadingUtils {
PipedInputStream clientInput = new PipedInputStream(STREAM_BUFFER_SIZE);
PipedOutputStream serverOutput = new PipedOutputStream(clientInput);
//server -> client pipe
- PipedInputStream serverInput = new PipedInputStream();
+ PipedInputStream serverInput = new PipedInputStream(STREAM_BUFFER_SIZE);
PipedOutputStream clientOutput;
clientOutput = new PipedOutputStream(serverInput);
//start server communication thread
diff --git a/src/main/java/electrosphere/engine/signal/Signal.java b/src/main/java/electrosphere/engine/signal/Signal.java
index 49f4935b..fcabe215 100644
--- a/src/main/java/electrosphere/engine/signal/Signal.java
+++ b/src/main/java/electrosphere/engine/signal/Signal.java
@@ -29,6 +29,14 @@ public class Signal {
YOGA_DESTROY,
UI_MODIFICATION,
+ //
+ //Terrain
+ //
+ REQUEST_CHUNK,
+ CHUNK_CREATED,
+ REQUEST_CHUNK_EDIT,
+ CHUNK_EDITED,
+
}
/**
diff --git a/src/main/java/electrosphere/engine/threads/ThreadManager.java b/src/main/java/electrosphere/engine/threads/ThreadManager.java
index 23a189b3..2353c289 100644
--- a/src/main/java/electrosphere/engine/threads/ThreadManager.java
+++ b/src/main/java/electrosphere/engine/threads/ThreadManager.java
@@ -9,6 +9,8 @@ import java.util.concurrent.TimeUnit;
import electrosphere.engine.Globals;
import electrosphere.engine.loadingthreads.LoadingThread;
import electrosphere.engine.threads.LabeledThread.ThreadLabel;
+import electrosphere.entity.types.terrain.TerrainChunk;
+import electrosphere.server.datacell.Realm;
import electrosphere.util.CodeUtils;
/**
@@ -110,6 +112,19 @@ public class ThreadManager {
Globals.server.close();
}
+ if(Globals.realmManager.getRealms() != null){
+ for(Realm realm : Globals.realmManager.getRealms()){
+ if(realm.getServerWorldData() != null && realm.getServerWorldData().getServerTerrainManager() != null){
+ realm.getServerWorldData().getServerTerrainManager().closeThreads();
+ }
+ }
+ }
+
+ /**
+ * Halds all terrain chunk threads
+ */
+ TerrainChunk.haltThreads();
+
//
//interrupt all threads
for(int i = 0; i < 3; i++){
diff --git a/src/main/java/electrosphere/entity/EntityCreationUtils.java b/src/main/java/electrosphere/entity/EntityCreationUtils.java
index a4701de6..06d19d65 100644
--- a/src/main/java/electrosphere/entity/EntityCreationUtils.java
+++ b/src/main/java/electrosphere/entity/EntityCreationUtils.java
@@ -129,9 +129,6 @@ public class EntityCreationUtils {
*/
public static void makeEntityDrawablePreexistingModel(Entity entity, String modelPath){
entity.putData(EntityDataStrings.DATA_STRING_ACTOR, ActorUtils.createActorOfLoadingModel(modelPath));
- entity.putData(EntityDataStrings.DATA_STRING_POSITION, new Vector3d(0,0,0));
- entity.putData(EntityDataStrings.DATA_STRING_ROTATION, new Quaterniond().identity());
- entity.putData(EntityDataStrings.DATA_STRING_SCALE, new Vector3f(1,1,1));
entity.putData(EntityDataStrings.DATA_STRING_DRAW, true);
entity.putData(EntityDataStrings.DRAW_SOLID_PASS, true);
Globals.clientScene.registerEntityToTag(entity, EntityTags.DRAWABLE);
diff --git a/src/main/java/electrosphere/entity/scene/Scene.java b/src/main/java/electrosphere/entity/scene/Scene.java
index 21af95e5..77f40a00 100644
--- a/src/main/java/electrosphere/entity/scene/Scene.java
+++ b/src/main/java/electrosphere/entity/scene/Scene.java
@@ -127,6 +127,15 @@ public class Scene {
return (Entity)entityIdMap.get(id);
}
+ /**
+ * Checks if a scene contains a given entity
+ * @param e The entity
+ * @return true if the scene contains the entity, false otherwise
+ */
+ public boolean containsEntity(Entity e){
+ return entityIdMap.containsKey(e.getId());
+ }
+
/**
* Registers a behavior tree to simulate each scene simulation frame
* @param tree The behavior tree to register
diff --git a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java
index b265825d..2e237e61 100644
--- a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java
+++ b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java
@@ -1,11 +1,15 @@
package electrosphere.entity.types.terrain;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
import org.joml.Vector3d;
import electrosphere.client.terrain.cells.DrawCell;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.collision.PhysicsEntityUtils;
+import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings;
@@ -20,32 +24,44 @@ import electrosphere.server.datacell.Realm;
*/
public class TerrainChunk {
+ /**
+ * Used for generating terrain chunks
+ */
+ static ExecutorService generationService = Executors.newFixedThreadPool(2);
/**
* Creates a client terrain chunk based on weights and values provided
* @param chunkData the chunk data to generate with
* @param levelOfDetail Increasing value that increments level of detail. 0 would be full resolution, 1 would be half resolution and so on. Only generates physics if levelOfDetail is 0
+ * @param hasPolygons true if the chunk has polygons to generate a model with, false otherwise
* @return The terrain chunk entity
*/
- public static Entity clientCreateTerrainChunkEntity(TransvoxelChunkData chunkData, int levelOfDetail, VoxelTextureAtlas atlas){
-
- TerrainChunkData data;
- if(levelOfDetail == DrawCell.FULL_DETAIL_LOD){
- data = TerrainChunkModelGeneration.generateTerrainChunkData(chunkData.terrainGrid, chunkData.textureGrid, levelOfDetail);
- } else {
- data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData);
- }
- String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas);
+ public static Entity clientCreateTerrainChunkEntity(TransvoxelChunkData chunkData, int levelOfDetail, VoxelTextureAtlas atlas, boolean hasPolygons){
+ Globals.profiler.beginCpuSample("TerrainChunk.clientCreateTerrainChunkEntity");
Entity rVal = EntityCreationUtils.createClientSpatialEntity();
- EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);
- if(data.vertices.size() > 0 && levelOfDetail == DrawCell.FULL_DETAIL_LOD){
- PhysicsEntityUtils.clientAttachTerrainChunkRigidBody(rVal, data);
+
+ if(hasPolygons){
+ generationService.submit(() -> {
+ TerrainChunkData data;
+ if(levelOfDetail == DrawCell.FULL_DETAIL_LOD){
+ data = TerrainChunkModelGeneration.generateTerrainChunkData(chunkData.terrainGrid, chunkData.textureGrid);
+ } else {
+ data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData);
+ }
+ if(Globals.clientScene.containsEntity(rVal)){
+ String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas);
+ EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);
+ if(levelOfDetail == DrawCell.FULL_DETAIL_LOD){
+ PhysicsEntityUtils.clientAttachTerrainChunkRigidBody(rVal, data);
+ }
+ }
+ });
}
rVal.putData(EntityDataStrings.TERRAIN_IS_TERRAIN, true);
rVal.putData(EntityDataStrings.DRAW_CAST_SHADOW, true);
-
+ Globals.profiler.endCpuSample();
return rVal;
}
@@ -59,7 +75,7 @@ public class TerrainChunk {
*/
public static Entity serverCreateTerrainChunkEntity(Realm realm, Vector3d position, float[][][] weights, int[][][] values){
- TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values, DrawCell.FULL_DETAIL_LOD);
+ TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values);
Entity rVal = EntityCreationUtils.createServerEntity(realm, position);
if(data.vertices.size() > 0){
@@ -84,4 +100,11 @@ public class TerrainChunk {
return rVal;
}
+ /**
+ * Halts all running generation threads
+ */
+ public static void haltThreads(){
+ generationService.shutdownNow();
+ }
+
}
diff --git a/src/main/java/electrosphere/net/client/ClientNetworking.java b/src/main/java/electrosphere/net/client/ClientNetworking.java
index 71f92f32..2cd68929 100644
--- a/src/main/java/electrosphere/net/client/ClientNetworking.java
+++ b/src/main/java/electrosphere/net/client/ClientNetworking.java
@@ -18,6 +18,11 @@ import java.util.concurrent.TimeUnit;
* Client networking thread
*/
public class ClientNetworking implements Runnable {
+
+ /**
+ * Milliseconds after which reading is considered slow enough to be warning-worthy
+ */
+ static final int SOCKET_READ_WARNING_THRESHOLD = 1000;
/**
* The server's address
@@ -62,7 +67,7 @@ public class ClientNetworking implements Runnable {
//thresholds for when to send pings and to determine when we've disconnected
static final long SEND_PING_THRESHOLD = 3000;
- static final long PING_DISCONNECT_THRESHOLD = 20000;
+ static final long PING_DISCONNECT_THRESHOLD = 60 * 1000;
//times for calculating ping-pong
long lastPingTime = 0;
long lastPongTime = 0;
@@ -174,18 +179,43 @@ public class ClientNetworking implements Runnable {
//start parsing messages
initialized = true;
while(Globals.threadManager.shouldKeepRunning() && !this.shouldDisconnect){
+
+ //
//attempt poll incoming messages
- parser.readMessagesIn();
+ long readStart = System.currentTimeMillis();
+ try {
+ parser.readMessagesIn();
+ } catch (IOException e) {
+ LoggerInterface.loggerNetworking.ERROR(e);
+ }
+ if(System.currentTimeMillis() - readStart > SOCKET_READ_WARNING_THRESHOLD){
+ LoggerInterface.loggerNetworking.WARNING("Client is slow to read from network! Delay: " + (System.currentTimeMillis() - readStart) + " Number of total bytes read(mb): " + (parser.getNumberOfBytesRead() / 1024 / 1024));
+ }
+
+
+
+ //
//outgoing messages
try {
parser.pushMessagesOut();
} catch(IOException e){
LoggerInterface.loggerNetworking.ERROR(e);
}
- //parses messages asynchronously
- this.parseMessagesAsynchronously();
- //ping logic
+
+
+
+ //
+ //parses messages asynchronously
+ boolean foundMessages = this.parseMessagesAsynchronously();
+
+
+
+ //timeout logic
+ //if received message from server, can't have timed out
+ if(foundMessages){
+ this.markReceivedPongMessage();
+ }
long currentTime = System.currentTimeMillis();
//basically if we haven't sent a ping in a while, send one
if(currentTime - lastPingTime > SEND_PING_THRESHOLD){
@@ -228,7 +258,8 @@ public class ClientNetworking implements Runnable {
/**
* Parses messages asynchronously
*/
- public void parseMessagesAsynchronously(){
+ public boolean parseMessagesAsynchronously(){
+ boolean foundMessages = false;
if(initialized){
while(parser.hasIncomingMessaage()){
NetworkMessage message = parser.popIncomingMessage();
@@ -241,9 +272,11 @@ public class ClientNetworking implements Runnable {
//do something
Globals.profiler.beginCpuSample("ClientProtocol.handleMessage");
this.messageProtocol.handleAsyncMessage(message);
+ foundMessages = true;
Globals.profiler.endCpuSample();
}
}
+ return foundMessages;
}
@@ -314,6 +347,17 @@ public class ClientNetworking implements Runnable {
public boolean isInitialized(){
return initialized;
}
+
+ /**
+ * Gets the total number of bytes read by this connection
+ * @return The total number of bytes
+ */
+ public long getNumBytesRead(){
+ if(this.parser == null){
+ return 0;
+ }
+ return this.parser.getNumberOfBytesRead();
+ }
}
diff --git a/src/main/java/electrosphere/net/client/MessageProtocol.java b/src/main/java/electrosphere/net/client/MessageProtocol.java
index 9612bcf4..83b06c3f 100644
--- a/src/main/java/electrosphere/net/client/MessageProtocol.java
+++ b/src/main/java/electrosphere/net/client/MessageProtocol.java
@@ -97,9 +97,11 @@ public class MessageProtocol {
}
//queue bounced messages for synchronous resolution
if(result != null){
+ Globals.profiler.beginAggregateCpuSample("MessageProtocol(client) Await lock to synchronize message");
this.synchronousMessageLock.acquireUninterruptibly();
this.synchronousMessageQueue.add(result);
this.synchronousMessageLock.release();
+ Globals.profiler.endCpuSample();
}
Globals.profiler.endCpuSample();
}
diff --git a/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java b/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java
index bab65358..cdd606f2 100644
--- a/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java
+++ b/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java
@@ -34,7 +34,6 @@ public class TerrainProtocol implements ClientProtocolTemplate {
//Vector3f worldMinPoint, Vector3f worldMaxPoint, int dynamicInterpolationRatio, float randomDampener, int worldDiscreteSize
new Vector3f(message.getworldMinX(),0,message.getworldMinY()),
new Vector3f(message.getworldMaxX(),32,message.getworldMaxY()),
- ChunkData.CHUNK_SIZE,
message.getrandomDampener(),
message.getworldSizeDiscrete()
);
@@ -44,9 +43,10 @@ public class TerrainProtocol implements ClientProtocolTemplate {
case SPAWNPOSITION:
LoggerInterface.loggerNetworking.WARNING("Received spawnPosition packet on client. This is deprecated!");
break;
- case SENDCHUNKDATA:
+ case SENDCHUNKDATA: {
+ LoggerInterface.loggerNetworking.DEBUG("(Client) Received terrain at " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ());
Globals.clientTerrainManager.attachTerrainMessage(message);
- break;
+ } break;
case UPDATEVOXEL: {
//
//find what all drawcells might be updated by this voxel update
@@ -97,9 +97,10 @@ public class TerrainProtocol implements ClientProtocolTemplate {
if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z)){
ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z);
if(data != null){
- Globals.drawCellManager.markUpdateable(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z);
+ Globals.clientDrawCellManager.markUpdateable(worldPosToUpdate.x, worldPosToUpdate.y, worldPosToUpdate.z);
}
}
+ Globals.clientFoliageManager.evaluateChunk(worldPos);
}
} break;
case SENDFLUIDDATA: {
diff --git a/src/main/java/electrosphere/net/parser/net/message/AuthMessage.java b/src/main/java/electrosphere/net/parser/net/message/AuthMessage.java
index 411bdc32..9c41ad60 100644
--- a/src/main/java/electrosphere/net/parser/net/message/AuthMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/AuthMessage.java
@@ -1,12 +1,15 @@
package electrosphere.net.parser.net.message;
+import io.github.studiorailgun.CircularByteBuffer;
import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
import java.util.LinkedList;
import java.util.List;
public class AuthMessage extends NetworkMessage {
+ /**
+ * The types of messages available in this category.
+ */
public enum AuthMessageType {
AUTHREQUEST,
AUTHDETAILS,
@@ -14,10 +17,17 @@ public class AuthMessage extends NetworkMessage {
AUTHFAILURE,
}
+ /**
+ * The type of this message in particular.
+ */
AuthMessageType messageType;
String user;
String pass;
+ /**
+ * Constructor
+ * @param messageType The type of this message
+ */
AuthMessage(AuthMessageType messageType){
this.type = MessageType.AUTH_MESSAGE;
this.messageType = messageType;
@@ -27,26 +37,48 @@ public class AuthMessage extends NetworkMessage {
return this.messageType;
}
+ /**
+ * Gets user
+ */
public String getuser() {
return user;
}
+ /**
+ * Sets user
+ */
public void setuser(String user) {
this.user = user;
}
+ /**
+ * Gets pass
+ */
public String getpass() {
return pass;
}
+ /**
+ * Sets pass
+ */
public void setpass(String pass) {
this.pass = pass;
}
+ /**
+ * Removes the packet header from the buffer
+ * @param byteBuffer The buffer
+ */
static void stripPacketHeader(CircularByteBuffer byteBuffer){
byteBuffer.read(2);
}
+ /**
+ * Checks if this message can be parsed (ie are all bytes present)
+ * @param byteBuffer The buffer
+ * @param secondByte The second byte, signifying the subtype of the message
+ * @return true if the message can be parsed, false otherwise
+ */
public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){
switch(secondByte){
case TypeBytes.AUTH_MESSAGE_TYPE_AUTHREQUEST:
@@ -73,18 +105,27 @@ public class AuthMessage extends NetworkMessage {
return false;
}
+ /**
+ * Parses a message of type AuthRequest
+ */
public static AuthMessage parseAuthRequestMessage(CircularByteBuffer byteBuffer){
AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHREQUEST);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type AuthRequest
+ */
public static AuthMessage constructAuthRequestMessage(){
AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHREQUEST);
rVal.serialize();
return rVal;
}
+ /**
+ * Checks if a message of type AuthDetails can be parsed from the byte stream
+ */
public static boolean canParseAuthDetailsMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -117,6 +158,9 @@ public class AuthMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type AuthDetails
+ */
public static AuthMessage parseAuthDetailsMessage(CircularByteBuffer byteBuffer){
AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHDETAILS);
stripPacketHeader(byteBuffer);
@@ -125,6 +169,9 @@ public class AuthMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type AuthDetails
+ */
public static AuthMessage constructAuthDetailsMessage(String user,String pass){
AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHDETAILS);
rVal.setuser(user);
@@ -133,24 +180,36 @@ public class AuthMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type AuthSuccess
+ */
public static AuthMessage parseAuthSuccessMessage(CircularByteBuffer byteBuffer){
AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHSUCCESS);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type AuthSuccess
+ */
public static AuthMessage constructAuthSuccessMessage(){
AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHSUCCESS);
rVal.serialize();
return rVal;
}
+ /**
+ * Parses a message of type AuthFailure
+ */
public static AuthMessage parseAuthFailureMessage(CircularByteBuffer byteBuffer){
AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHFAILURE);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type AuthFailure
+ */
public static AuthMessage constructAuthFailureMessage(){
AuthMessage rVal = new AuthMessage(AuthMessageType.AUTHFAILURE);
rVal.serialize();
diff --git a/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java b/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java
index 0dc24127..2c889f91 100644
--- a/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/CharacterMessage.java
@@ -1,12 +1,15 @@
package electrosphere.net.parser.net.message;
+import io.github.studiorailgun.CircularByteBuffer;
import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
import java.util.LinkedList;
import java.util.List;
public class CharacterMessage extends NetworkMessage {
+ /**
+ * The types of messages available in this category.
+ */
public enum CharacterMessageType {
REQUESTCHARACTERLIST,
RESPONSECHARACTERLIST,
@@ -17,9 +20,16 @@ public class CharacterMessage extends NetworkMessage {
RESPONSESPAWNCHARACTER,
}
+ /**
+ * The type of this message in particular.
+ */
CharacterMessageType messageType;
String data;
+ /**
+ * Constructor
+ * @param messageType The type of this message
+ */
CharacterMessage(CharacterMessageType messageType){
this.type = MessageType.CHARACTER_MESSAGE;
this.messageType = messageType;
@@ -29,18 +39,34 @@ public class CharacterMessage extends NetworkMessage {
return this.messageType;
}
+ /**
+ * Gets data
+ */
public String getdata() {
return data;
}
+ /**
+ * Sets data
+ */
public void setdata(String data) {
this.data = data;
}
+ /**
+ * Removes the packet header from the buffer
+ * @param byteBuffer The buffer
+ */
static void stripPacketHeader(CircularByteBuffer byteBuffer){
byteBuffer.read(2);
}
+ /**
+ * Checks if this message can be parsed (ie are all bytes present)
+ * @param byteBuffer The buffer
+ * @param secondByte The second byte, signifying the subtype of the message
+ * @return true if the message can be parsed, false otherwise
+ */
public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){
switch(secondByte){
case TypeBytes.CHARACTER_MESSAGE_TYPE_REQUESTCHARACTERLIST:
@@ -77,18 +103,27 @@ public class CharacterMessage extends NetworkMessage {
return false;
}
+ /**
+ * Parses a message of type RequestCharacterList
+ */
public static CharacterMessage parseRequestCharacterListMessage(CircularByteBuffer byteBuffer){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTCHARACTERLIST);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type RequestCharacterList
+ */
public static CharacterMessage constructRequestCharacterListMessage(){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTCHARACTERLIST);
rVal.serialize();
return rVal;
}
+ /**
+ * Checks if a message of type ResponseCharacterList can be parsed from the byte stream
+ */
public static boolean canParseResponseCharacterListMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -108,6 +143,9 @@ public class CharacterMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type ResponseCharacterList
+ */
public static CharacterMessage parseResponseCharacterListMessage(CircularByteBuffer byteBuffer){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECHARACTERLIST);
stripPacketHeader(byteBuffer);
@@ -115,6 +153,9 @@ public class CharacterMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type ResponseCharacterList
+ */
public static CharacterMessage constructResponseCharacterListMessage(String data){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECHARACTERLIST);
rVal.setdata(data);
@@ -122,6 +163,9 @@ public class CharacterMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type RequestCreateCharacter can be parsed from the byte stream
+ */
public static boolean canParseRequestCreateCharacterMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -141,6 +185,9 @@ public class CharacterMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type RequestCreateCharacter
+ */
public static CharacterMessage parseRequestCreateCharacterMessage(CircularByteBuffer byteBuffer){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTCREATECHARACTER);
stripPacketHeader(byteBuffer);
@@ -148,6 +195,9 @@ public class CharacterMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type RequestCreateCharacter
+ */
public static CharacterMessage constructRequestCreateCharacterMessage(String data){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTCREATECHARACTER);
rVal.setdata(data);
@@ -155,42 +205,63 @@ public class CharacterMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type ResponseCreateCharacterSuccess
+ */
public static CharacterMessage parseResponseCreateCharacterSuccessMessage(CircularByteBuffer byteBuffer){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECREATECHARACTERSUCCESS);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type ResponseCreateCharacterSuccess
+ */
public static CharacterMessage constructResponseCreateCharacterSuccessMessage(){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECREATECHARACTERSUCCESS);
rVal.serialize();
return rVal;
}
+ /**
+ * Parses a message of type ResponseCreateCharacterFailure
+ */
public static CharacterMessage parseResponseCreateCharacterFailureMessage(CircularByteBuffer byteBuffer){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECREATECHARACTERFAILURE);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type ResponseCreateCharacterFailure
+ */
public static CharacterMessage constructResponseCreateCharacterFailureMessage(){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSECREATECHARACTERFAILURE);
rVal.serialize();
return rVal;
}
+ /**
+ * Parses a message of type RequestSpawnCharacter
+ */
public static CharacterMessage parseRequestSpawnCharacterMessage(CircularByteBuffer byteBuffer){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTSPAWNCHARACTER);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type RequestSpawnCharacter
+ */
public static CharacterMessage constructRequestSpawnCharacterMessage(){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.REQUESTSPAWNCHARACTER);
rVal.serialize();
return rVal;
}
+ /**
+ * Checks if a message of type ResponseSpawnCharacter can be parsed from the byte stream
+ */
public static boolean canParseResponseSpawnCharacterMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -210,6 +281,9 @@ public class CharacterMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type ResponseSpawnCharacter
+ */
public static CharacterMessage parseResponseSpawnCharacterMessage(CircularByteBuffer byteBuffer){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSESPAWNCHARACTER);
stripPacketHeader(byteBuffer);
@@ -217,6 +291,9 @@ public class CharacterMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type ResponseSpawnCharacter
+ */
public static CharacterMessage constructResponseSpawnCharacterMessage(String data){
CharacterMessage rVal = new CharacterMessage(CharacterMessageType.RESPONSESPAWNCHARACTER);
rVal.setdata(data);
diff --git a/src/main/java/electrosphere/net/parser/net/message/CombatMessage.java b/src/main/java/electrosphere/net/parser/net/message/CombatMessage.java
index 106fb361..8c546b02 100644
--- a/src/main/java/electrosphere/net/parser/net/message/CombatMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/CombatMessage.java
@@ -1,16 +1,22 @@
package electrosphere.net.parser.net.message;
+import io.github.studiorailgun.CircularByteBuffer;
import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
import java.util.LinkedList;
import java.util.List;
public class CombatMessage extends NetworkMessage {
+ /**
+ * The types of messages available in this category.
+ */
public enum CombatMessageType {
SERVERREPORTHITBOXCOLLISION,
}
+ /**
+ * The type of this message in particular.
+ */
CombatMessageType messageType;
int entityID;
int receiverEntityID;
@@ -25,6 +31,10 @@ public class CombatMessage extends NetworkMessage {
String hitboxType;
String hurtboxType;
+ /**
+ * Constructor
+ * @param messageType The type of this message
+ */
CombatMessage(CombatMessageType messageType){
this.type = MessageType.COMBAT_MESSAGE;
this.messageType = messageType;
@@ -34,106 +44,188 @@ public class CombatMessage extends NetworkMessage {
return this.messageType;
}
+ /**
+ * Gets entityID
+ */
public int getentityID() {
return entityID;
}
+ /**
+ * Sets entityID
+ */
public void setentityID(int entityID) {
this.entityID = entityID;
}
+ /**
+ * Gets receiverEntityID
+ */
public int getreceiverEntityID() {
return receiverEntityID;
}
+ /**
+ * Sets receiverEntityID
+ */
public void setreceiverEntityID(int receiverEntityID) {
this.receiverEntityID = receiverEntityID;
}
+ /**
+ * Gets positionX
+ */
public double getpositionX() {
return positionX;
}
+ /**
+ * Sets positionX
+ */
public void setpositionX(double positionX) {
this.positionX = positionX;
}
+ /**
+ * Gets positionY
+ */
public double getpositionY() {
return positionY;
}
+ /**
+ * Sets positionY
+ */
public void setpositionY(double positionY) {
this.positionY = positionY;
}
+ /**
+ * Gets positionZ
+ */
public double getpositionZ() {
return positionZ;
}
+ /**
+ * Sets positionZ
+ */
public void setpositionZ(double positionZ) {
this.positionZ = positionZ;
}
+ /**
+ * Gets rotationX
+ */
public double getrotationX() {
return rotationX;
}
+ /**
+ * Sets rotationX
+ */
public void setrotationX(double rotationX) {
this.rotationX = rotationX;
}
+ /**
+ * Gets rotationY
+ */
public double getrotationY() {
return rotationY;
}
+ /**
+ * Sets rotationY
+ */
public void setrotationY(double rotationY) {
this.rotationY = rotationY;
}
+ /**
+ * Gets rotationZ
+ */
public double getrotationZ() {
return rotationZ;
}
+ /**
+ * Sets rotationZ
+ */
public void setrotationZ(double rotationZ) {
this.rotationZ = rotationZ;
}
+ /**
+ * Gets rotationW
+ */
public double getrotationW() {
return rotationW;
}
+ /**
+ * Sets rotationW
+ */
public void setrotationW(double rotationW) {
this.rotationW = rotationW;
}
+ /**
+ * Gets time
+ */
public long gettime() {
return time;
}
+ /**
+ * Sets time
+ */
public void settime(long time) {
this.time = time;
}
+ /**
+ * Gets hitboxType
+ */
public String gethitboxType() {
return hitboxType;
}
+ /**
+ * Sets hitboxType
+ */
public void sethitboxType(String hitboxType) {
this.hitboxType = hitboxType;
}
+ /**
+ * Gets hurtboxType
+ */
public String gethurtboxType() {
return hurtboxType;
}
+ /**
+ * Sets hurtboxType
+ */
public void sethurtboxType(String hurtboxType) {
this.hurtboxType = hurtboxType;
}
+ /**
+ * Removes the packet header from the buffer
+ * @param byteBuffer The buffer
+ */
static void stripPacketHeader(CircularByteBuffer byteBuffer){
byteBuffer.read(2);
}
+ /**
+ * Checks if this message can be parsed (ie are all bytes present)
+ * @param byteBuffer The buffer
+ * @param secondByte The second byte, signifying the subtype of the message
+ * @return true if the message can be parsed, false otherwise
+ */
public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){
switch(secondByte){
case TypeBytes.COMBAT_MESSAGE_TYPE_SERVERREPORTHITBOXCOLLISION:
@@ -142,6 +234,9 @@ public class CombatMessage extends NetworkMessage {
return false;
}
+ /**
+ * Checks if a message of type serverReportHitboxCollision can be parsed from the byte stream
+ */
public static boolean canParseserverReportHitboxCollisionMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -192,6 +287,9 @@ public class CombatMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type serverReportHitboxCollision
+ */
public static CombatMessage parseserverReportHitboxCollisionMessage(CircularByteBuffer byteBuffer){
CombatMessage rVal = new CombatMessage(CombatMessageType.SERVERREPORTHITBOXCOLLISION);
stripPacketHeader(byteBuffer);
@@ -206,6 +304,9 @@ public class CombatMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type serverReportHitboxCollision
+ */
public static CombatMessage constructserverReportHitboxCollisionMessage(int entityID,int receiverEntityID,long time,String hitboxType,String hurtboxType,double positionX,double positionY,double positionZ){
CombatMessage rVal = new CombatMessage(CombatMessageType.SERVERREPORTHITBOXCOLLISION);
rVal.setentityID(entityID);
diff --git a/src/main/java/electrosphere/net/parser/net/message/EntityMessage.java b/src/main/java/electrosphere/net/parser/net/message/EntityMessage.java
index 0fda9679..11c01cfe 100644
--- a/src/main/java/electrosphere/net/parser/net/message/EntityMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/EntityMessage.java
@@ -1,12 +1,15 @@
package electrosphere.net.parser.net.message;
+import io.github.studiorailgun.CircularByteBuffer;
import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
import java.util.LinkedList;
import java.util.List;
public class EntityMessage extends NetworkMessage {
+ /**
+ * The types of messages available in this category.
+ */
public enum EntityMessageType {
CREATE,
MOVEUPDATE,
@@ -20,6 +23,9 @@ public class EntityMessage extends NetworkMessage {
SYNCPHYSICS,
}
+ /**
+ * The type of this message in particular.
+ */
EntityMessageType messageType;
int entityCategory;
String entitySubtype;
@@ -56,6 +62,10 @@ public class EntityMessage extends NetworkMessage {
int bTreeID;
int propertyValueInt;
+ /**
+ * Constructor
+ * @param messageType The type of this message
+ */
EntityMessage(EntityMessageType messageType){
this.type = MessageType.ENTITY_MESSAGE;
this.messageType = messageType;
@@ -65,282 +75,496 @@ public class EntityMessage extends NetworkMessage {
return this.messageType;
}
+ /**
+ * Gets entityCategory
+ */
public int getentityCategory() {
return entityCategory;
}
+ /**
+ * Sets entityCategory
+ */
public void setentityCategory(int entityCategory) {
this.entityCategory = entityCategory;
}
+ /**
+ * Gets entitySubtype
+ */
public String getentitySubtype() {
return entitySubtype;
}
+ /**
+ * Sets entitySubtype
+ */
public void setentitySubtype(String entitySubtype) {
this.entitySubtype = entitySubtype;
}
+ /**
+ * Gets entityID
+ */
public int getentityID() {
return entityID;
}
+ /**
+ * Sets entityID
+ */
public void setentityID(int entityID) {
this.entityID = entityID;
}
+ /**
+ * Gets creatureTemplate
+ */
public String getcreatureTemplate() {
return creatureTemplate;
}
+ /**
+ * Sets creatureTemplate
+ */
public void setcreatureTemplate(String creatureTemplate) {
this.creatureTemplate = creatureTemplate;
}
+ /**
+ * Gets positionX
+ */
public double getpositionX() {
return positionX;
}
+ /**
+ * Sets positionX
+ */
public void setpositionX(double positionX) {
this.positionX = positionX;
}
+ /**
+ * Gets positionY
+ */
public double getpositionY() {
return positionY;
}
+ /**
+ * Sets positionY
+ */
public void setpositionY(double positionY) {
this.positionY = positionY;
}
+ /**
+ * Gets positionZ
+ */
public double getpositionZ() {
return positionZ;
}
+ /**
+ * Sets positionZ
+ */
public void setpositionZ(double positionZ) {
this.positionZ = positionZ;
}
+ /**
+ * Gets rotationX
+ */
public double getrotationX() {
return rotationX;
}
+ /**
+ * Sets rotationX
+ */
public void setrotationX(double rotationX) {
this.rotationX = rotationX;
}
+ /**
+ * Gets rotationY
+ */
public double getrotationY() {
return rotationY;
}
+ /**
+ * Sets rotationY
+ */
public void setrotationY(double rotationY) {
this.rotationY = rotationY;
}
+ /**
+ * Gets rotationZ
+ */
public double getrotationZ() {
return rotationZ;
}
+ /**
+ * Sets rotationZ
+ */
public void setrotationZ(double rotationZ) {
this.rotationZ = rotationZ;
}
+ /**
+ * Gets rotationW
+ */
public double getrotationW() {
return rotationW;
}
+ /**
+ * Sets rotationW
+ */
public void setrotationW(double rotationW) {
this.rotationW = rotationW;
}
+ /**
+ * Gets linVelX
+ */
public double getlinVelX() {
return linVelX;
}
+ /**
+ * Sets linVelX
+ */
public void setlinVelX(double linVelX) {
this.linVelX = linVelX;
}
+ /**
+ * Gets linVelY
+ */
public double getlinVelY() {
return linVelY;
}
+ /**
+ * Sets linVelY
+ */
public void setlinVelY(double linVelY) {
this.linVelY = linVelY;
}
+ /**
+ * Gets linVelZ
+ */
public double getlinVelZ() {
return linVelZ;
}
+ /**
+ * Sets linVelZ
+ */
public void setlinVelZ(double linVelZ) {
this.linVelZ = linVelZ;
}
+ /**
+ * Gets angVelX
+ */
public double getangVelX() {
return angVelX;
}
+ /**
+ * Sets angVelX
+ */
public void setangVelX(double angVelX) {
this.angVelX = angVelX;
}
+ /**
+ * Gets angVelY
+ */
public double getangVelY() {
return angVelY;
}
+ /**
+ * Sets angVelY
+ */
public void setangVelY(double angVelY) {
this.angVelY = angVelY;
}
+ /**
+ * Gets angVelZ
+ */
public double getangVelZ() {
return angVelZ;
}
+ /**
+ * Sets angVelZ
+ */
public void setangVelZ(double angVelZ) {
this.angVelZ = angVelZ;
}
+ /**
+ * Gets linForceX
+ */
public double getlinForceX() {
return linForceX;
}
+ /**
+ * Sets linForceX
+ */
public void setlinForceX(double linForceX) {
this.linForceX = linForceX;
}
+ /**
+ * Gets linForceY
+ */
public double getlinForceY() {
return linForceY;
}
+ /**
+ * Sets linForceY
+ */
public void setlinForceY(double linForceY) {
this.linForceY = linForceY;
}
+ /**
+ * Gets linForceZ
+ */
public double getlinForceZ() {
return linForceZ;
}
+ /**
+ * Sets linForceZ
+ */
public void setlinForceZ(double linForceZ) {
this.linForceZ = linForceZ;
}
+ /**
+ * Gets angForceX
+ */
public double getangForceX() {
return angForceX;
}
+ /**
+ * Sets angForceX
+ */
public void setangForceX(double angForceX) {
this.angForceX = angForceX;
}
+ /**
+ * Gets angForceY
+ */
public double getangForceY() {
return angForceY;
}
+ /**
+ * Sets angForceY
+ */
public void setangForceY(double angForceY) {
this.angForceY = angForceY;
}
+ /**
+ * Gets angForceZ
+ */
public double getangForceZ() {
return angForceZ;
}
+ /**
+ * Sets angForceZ
+ */
public void setangForceZ(double angForceZ) {
this.angForceZ = angForceZ;
}
+ /**
+ * Gets yaw
+ */
public double getyaw() {
return yaw;
}
+ /**
+ * Sets yaw
+ */
public void setyaw(double yaw) {
this.yaw = yaw;
}
+ /**
+ * Gets pitch
+ */
public double getpitch() {
return pitch;
}
+ /**
+ * Sets pitch
+ */
public void setpitch(double pitch) {
this.pitch = pitch;
}
+ /**
+ * Gets velocity
+ */
public double getvelocity() {
return velocity;
}
+ /**
+ * Sets velocity
+ */
public void setvelocity(double velocity) {
this.velocity = velocity;
}
+ /**
+ * Gets treeState
+ */
public int gettreeState() {
return treeState;
}
+ /**
+ * Sets treeState
+ */
public void settreeState(int treeState) {
this.treeState = treeState;
}
+ /**
+ * Gets propertyType
+ */
public int getpropertyType() {
return propertyType;
}
+ /**
+ * Sets propertyType
+ */
public void setpropertyType(int propertyType) {
this.propertyType = propertyType;
}
+ /**
+ * Gets propertyValue
+ */
public int getpropertyValue() {
return propertyValue;
}
+ /**
+ * Sets propertyValue
+ */
public void setpropertyValue(int propertyValue) {
this.propertyValue = propertyValue;
}
+ /**
+ * Gets time
+ */
public long gettime() {
return time;
}
+ /**
+ * Sets time
+ */
public void settime(long time) {
this.time = time;
}
+ /**
+ * Gets bone
+ */
public String getbone() {
return bone;
}
+ /**
+ * Sets bone
+ */
public void setbone(String bone) {
this.bone = bone;
}
+ /**
+ * Gets targetID
+ */
public int gettargetID() {
return targetID;
}
+ /**
+ * Sets targetID
+ */
public void settargetID(int targetID) {
this.targetID = targetID;
}
+ /**
+ * Gets bTreeID
+ */
public int getbTreeID() {
return bTreeID;
}
+ /**
+ * Sets bTreeID
+ */
public void setbTreeID(int bTreeID) {
this.bTreeID = bTreeID;
}
+ /**
+ * Gets propertyValueInt
+ */
public int getpropertyValueInt() {
return propertyValueInt;
}
+ /**
+ * Sets propertyValueInt
+ */
public void setpropertyValueInt(int propertyValueInt) {
this.propertyValueInt = propertyValueInt;
}
+ /**
+ * Removes the packet header from the buffer
+ * @param byteBuffer The buffer
+ */
static void stripPacketHeader(CircularByteBuffer byteBuffer){
byteBuffer.read(2);
}
+ /**
+ * Checks if this message can be parsed (ie are all bytes present)
+ * @param byteBuffer The buffer
+ * @param secondByte The second byte, signifying the subtype of the message
+ * @return true if the message can be parsed, false otherwise
+ */
public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){
switch(secondByte){
case TypeBytes.ENTITY_MESSAGE_TYPE_CREATE:
@@ -399,6 +623,9 @@ public class EntityMessage extends NetworkMessage {
return false;
}
+ /**
+ * Checks if a message of type Create can be parsed from the byte stream
+ */
public static boolean canParseCreateMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -458,6 +685,9 @@ public class EntityMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type Create
+ */
public static EntityMessage parseCreateMessage(CircularByteBuffer byteBuffer){
EntityMessage rVal = new EntityMessage(EntityMessageType.CREATE);
stripPacketHeader(byteBuffer);
@@ -475,6 +705,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type Create
+ */
public static EntityMessage constructCreateMessage(int entityID,int entityCategory,String entitySubtype,String creatureTemplate,double positionX,double positionY,double positionZ,double rotationX,double rotationY,double rotationZ,double rotationW){
EntityMessage rVal = new EntityMessage(EntityMessageType.CREATE);
rVal.setentityID(entityID);
@@ -492,6 +725,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type moveUpdate
+ */
public static EntityMessage parsemoveUpdateMessage(CircularByteBuffer byteBuffer){
EntityMessage rVal = new EntityMessage(EntityMessageType.MOVEUPDATE);
stripPacketHeader(byteBuffer);
@@ -510,6 +746,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type moveUpdate
+ */
public static EntityMessage constructmoveUpdateMessage(int entityID,long time,double positionX,double positionY,double positionZ,double rotationX,double rotationY,double rotationZ,double rotationW,double velocity,int propertyValueInt,int treeState){
EntityMessage rVal = new EntityMessage(EntityMessageType.MOVEUPDATE);
rVal.setentityID(entityID);
@@ -528,6 +767,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type attackUpdate
+ */
public static EntityMessage parseattackUpdateMessage(CircularByteBuffer byteBuffer){
EntityMessage rVal = new EntityMessage(EntityMessageType.ATTACKUPDATE);
stripPacketHeader(byteBuffer);
@@ -544,6 +786,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type attackUpdate
+ */
public static EntityMessage constructattackUpdateMessage(int entityID,long time,double positionX,double positionY,double positionZ,double rotationX,double rotationY,double rotationZ,double velocity,int treeState){
EntityMessage rVal = new EntityMessage(EntityMessageType.ATTACKUPDATE);
rVal.setentityID(entityID);
@@ -560,18 +805,27 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type startAttack
+ */
public static EntityMessage parsestartAttackMessage(CircularByteBuffer byteBuffer){
EntityMessage rVal = new EntityMessage(EntityMessageType.STARTATTACK);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type startAttack
+ */
public static EntityMessage constructstartAttackMessage(){
EntityMessage rVal = new EntityMessage(EntityMessageType.STARTATTACK);
rVal.serialize();
return rVal;
}
+ /**
+ * Parses a message of type Kill
+ */
public static EntityMessage parseKillMessage(CircularByteBuffer byteBuffer){
EntityMessage rVal = new EntityMessage(EntityMessageType.KILL);
stripPacketHeader(byteBuffer);
@@ -580,6 +834,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type Kill
+ */
public static EntityMessage constructKillMessage(long time,int entityID){
EntityMessage rVal = new EntityMessage(EntityMessageType.KILL);
rVal.settime(time);
@@ -588,6 +845,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type Destroy
+ */
public static EntityMessage parseDestroyMessage(CircularByteBuffer byteBuffer){
EntityMessage rVal = new EntityMessage(EntityMessageType.DESTROY);
stripPacketHeader(byteBuffer);
@@ -595,6 +855,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type Destroy
+ */
public static EntityMessage constructDestroyMessage(int entityID){
EntityMessage rVal = new EntityMessage(EntityMessageType.DESTROY);
rVal.setentityID(entityID);
@@ -602,6 +865,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type setProperty
+ */
public static EntityMessage parsesetPropertyMessage(CircularByteBuffer byteBuffer){
EntityMessage rVal = new EntityMessage(EntityMessageType.SETPROPERTY);
stripPacketHeader(byteBuffer);
@@ -612,6 +878,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type setProperty
+ */
public static EntityMessage constructsetPropertyMessage(int entityID,long time,int propertyType,int propertyValue){
EntityMessage rVal = new EntityMessage(EntityMessageType.SETPROPERTY);
rVal.setentityID(entityID);
@@ -622,6 +891,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type attachEntityToEntity can be parsed from the byte stream
+ */
public static boolean canParseattachEntityToEntityMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -647,6 +919,9 @@ public class EntityMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type attachEntityToEntity
+ */
public static EntityMessage parseattachEntityToEntityMessage(CircularByteBuffer byteBuffer){
EntityMessage rVal = new EntityMessage(EntityMessageType.ATTACHENTITYTOENTITY);
stripPacketHeader(byteBuffer);
@@ -656,6 +931,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type attachEntityToEntity
+ */
public static EntityMessage constructattachEntityToEntityMessage(int entityID,String bone,int targetID){
EntityMessage rVal = new EntityMessage(EntityMessageType.ATTACHENTITYTOENTITY);
rVal.setentityID(entityID);
@@ -665,6 +943,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type updateEntityViewDir
+ */
public static EntityMessage parseupdateEntityViewDirMessage(CircularByteBuffer byteBuffer){
EntityMessage rVal = new EntityMessage(EntityMessageType.UPDATEENTITYVIEWDIR);
stripPacketHeader(byteBuffer);
@@ -676,6 +957,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type updateEntityViewDir
+ */
public static EntityMessage constructupdateEntityViewDirMessage(int entityID,long time,int propertyType,double yaw,double pitch){
EntityMessage rVal = new EntityMessage(EntityMessageType.UPDATEENTITYVIEWDIR);
rVal.setentityID(entityID);
@@ -687,6 +971,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type syncPhysics
+ */
public static EntityMessage parsesyncPhysicsMessage(CircularByteBuffer byteBuffer){
EntityMessage rVal = new EntityMessage(EntityMessageType.SYNCPHYSICS);
stripPacketHeader(byteBuffer);
@@ -714,6 +1001,9 @@ public class EntityMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type syncPhysics
+ */
public static EntityMessage constructsyncPhysicsMessage(int entityID,long time,double positionX,double positionY,double positionZ,double rotationX,double rotationY,double rotationZ,double rotationW,double linVelX,double linVelY,double linVelZ,double angVelX,double angVelY,double angVelZ,double linForceX,double linForceY,double linForceZ,double angForceX,double angForceY,double angForceZ){
EntityMessage rVal = new EntityMessage(EntityMessageType.SYNCPHYSICS);
rVal.setentityID(entityID);
diff --git a/src/main/java/electrosphere/net/parser/net/message/InventoryMessage.java b/src/main/java/electrosphere/net/parser/net/message/InventoryMessage.java
index fdd62d78..18ab2cdb 100644
--- a/src/main/java/electrosphere/net/parser/net/message/InventoryMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/InventoryMessage.java
@@ -1,12 +1,15 @@
package electrosphere.net.parser.net.message;
+import io.github.studiorailgun.CircularByteBuffer;
import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
import java.util.LinkedList;
import java.util.List;
public class InventoryMessage extends NetworkMessage {
+ /**
+ * The types of messages available in this category.
+ */
public enum InventoryMessageType {
ADDITEMTOINVENTORY,
REMOVEITEMFROMINVENTORY,
@@ -21,6 +24,9 @@ public class InventoryMessage extends NetworkMessage {
CLIENTREQUESTPERFORMITEMACTION,
}
+ /**
+ * The type of this message in particular.
+ */
InventoryMessageType messageType;
String itemTemplate;
String equipPointId;
@@ -31,6 +37,10 @@ public class InventoryMessage extends NetworkMessage {
int itemActionCode;
int itemActionCodeState;
+ /**
+ * Constructor
+ * @param messageType The type of this message
+ */
InventoryMessage(InventoryMessageType messageType){
this.type = MessageType.INVENTORY_MESSAGE;
this.messageType = messageType;
@@ -40,74 +50,132 @@ public class InventoryMessage extends NetworkMessage {
return this.messageType;
}
+ /**
+ * Gets itemTemplate
+ */
public String getitemTemplate() {
return itemTemplate;
}
+ /**
+ * Sets itemTemplate
+ */
public void setitemTemplate(String itemTemplate) {
this.itemTemplate = itemTemplate;
}
+ /**
+ * Gets equipPointId
+ */
public String getequipPointId() {
return equipPointId;
}
+ /**
+ * Sets equipPointId
+ */
public void setequipPointId(String equipPointId) {
this.equipPointId = equipPointId;
}
+ /**
+ * Gets entityId
+ */
public int getentityId() {
return entityId;
}
+ /**
+ * Sets entityId
+ */
public void setentityId(int entityId) {
this.entityId = entityId;
}
+ /**
+ * Gets equipperId
+ */
public int getequipperId() {
return equipperId;
}
+ /**
+ * Sets equipperId
+ */
public void setequipperId(int equipperId) {
this.equipperId = equipperId;
}
+ /**
+ * Gets containerType
+ */
public int getcontainerType() {
return containerType;
}
+ /**
+ * Sets containerType
+ */
public void setcontainerType(int containerType) {
this.containerType = containerType;
}
+ /**
+ * Gets toolbarId
+ */
public int gettoolbarId() {
return toolbarId;
}
+ /**
+ * Sets toolbarId
+ */
public void settoolbarId(int toolbarId) {
this.toolbarId = toolbarId;
}
+ /**
+ * Gets itemActionCode
+ */
public int getitemActionCode() {
return itemActionCode;
}
+ /**
+ * Sets itemActionCode
+ */
public void setitemActionCode(int itemActionCode) {
this.itemActionCode = itemActionCode;
}
+ /**
+ * Gets itemActionCodeState
+ */
public int getitemActionCodeState() {
return itemActionCodeState;
}
+ /**
+ * Sets itemActionCodeState
+ */
public void setitemActionCodeState(int itemActionCodeState) {
this.itemActionCodeState = itemActionCodeState;
}
+ /**
+ * Removes the packet header from the buffer
+ * @param byteBuffer The buffer
+ */
static void stripPacketHeader(CircularByteBuffer byteBuffer){
byteBuffer.read(2);
}
+ /**
+ * Checks if this message can be parsed (ie are all bytes present)
+ * @param byteBuffer The buffer
+ * @param secondByte The second byte, signifying the subtype of the message
+ * @return true if the message can be parsed, false otherwise
+ */
public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){
switch(secondByte){
case TypeBytes.INVENTORY_MESSAGE_TYPE_ADDITEMTOINVENTORY:
@@ -152,6 +220,9 @@ public class InventoryMessage extends NetworkMessage {
return false;
}
+ /**
+ * Checks if a message of type addItemToInventory can be parsed from the byte stream
+ */
public static boolean canParseaddItemToInventoryMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -174,6 +245,9 @@ public class InventoryMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type addItemToInventory
+ */
public static InventoryMessage parseaddItemToInventoryMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.ADDITEMTOINVENTORY);
stripPacketHeader(byteBuffer);
@@ -182,6 +256,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type addItemToInventory
+ */
public static InventoryMessage constructaddItemToInventoryMessage(int entityId,String itemTemplate){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.ADDITEMTOINVENTORY);
rVal.setentityId(entityId);
@@ -190,6 +267,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type removeItemFromInventory
+ */
public static InventoryMessage parseremoveItemFromInventoryMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.REMOVEITEMFROMINVENTORY);
stripPacketHeader(byteBuffer);
@@ -197,6 +277,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type removeItemFromInventory
+ */
public static InventoryMessage constructremoveItemFromInventoryMessage(int entityId){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.REMOVEITEMFROMINVENTORY);
rVal.setentityId(entityId);
@@ -204,6 +287,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type clientRequestEquipItem can be parsed from the byte stream
+ */
public static boolean canParseclientRequestEquipItemMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -226,6 +312,9 @@ public class InventoryMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type clientRequestEquipItem
+ */
public static InventoryMessage parseclientRequestEquipItemMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTEQUIPITEM);
stripPacketHeader(byteBuffer);
@@ -234,6 +323,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type clientRequestEquipItem
+ */
public static InventoryMessage constructclientRequestEquipItemMessage(String equipPointId,int entityId){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTEQUIPITEM);
rVal.setequipPointId(equipPointId);
@@ -242,6 +334,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type serverCommandMoveItemContainer can be parsed from the byte stream
+ */
public static boolean canParseserverCommandMoveItemContainerMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -267,6 +362,9 @@ public class InventoryMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type serverCommandMoveItemContainer
+ */
public static InventoryMessage parseserverCommandMoveItemContainerMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDMOVEITEMCONTAINER);
stripPacketHeader(byteBuffer);
@@ -276,6 +374,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type serverCommandMoveItemContainer
+ */
public static InventoryMessage constructserverCommandMoveItemContainerMessage(int entityId,int containerType,String equipPointId){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDMOVEITEMCONTAINER);
rVal.setentityId(entityId);
@@ -285,6 +386,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type serverCommandEquipItem can be parsed from the byte stream
+ */
public static boolean canParseserverCommandEquipItemMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -326,6 +430,9 @@ public class InventoryMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type serverCommandEquipItem
+ */
public static InventoryMessage parseserverCommandEquipItemMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDEQUIPITEM);
stripPacketHeader(byteBuffer);
@@ -337,6 +444,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type serverCommandEquipItem
+ */
public static InventoryMessage constructserverCommandEquipItemMessage(int equipperId,int containerType,String equipPointId,int entityId,String itemTemplate){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDEQUIPITEM);
rVal.setequipperId(equipperId);
@@ -348,6 +458,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type serverCommandUnequipItem can be parsed from the byte stream
+ */
public static boolean canParseserverCommandUnequipItemMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -373,6 +486,9 @@ public class InventoryMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type serverCommandUnequipItem
+ */
public static InventoryMessage parseserverCommandUnequipItemMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDUNEQUIPITEM);
stripPacketHeader(byteBuffer);
@@ -382,6 +498,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type serverCommandUnequipItem
+ */
public static InventoryMessage constructserverCommandUnequipItemMessage(int equipperId,int containerType,String equipPointId){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERCOMMANDUNEQUIPITEM);
rVal.setequipperId(equipperId);
@@ -391,6 +510,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type clientRequestUnequipItem can be parsed from the byte stream
+ */
public static boolean canParseclientRequestUnequipItemMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -410,6 +532,9 @@ public class InventoryMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type clientRequestUnequipItem
+ */
public static InventoryMessage parseclientRequestUnequipItemMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTUNEQUIPITEM);
stripPacketHeader(byteBuffer);
@@ -417,6 +542,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type clientRequestUnequipItem
+ */
public static InventoryMessage constructclientRequestUnequipItemMessage(String equipPointId){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTUNEQUIPITEM);
rVal.setequipPointId(equipPointId);
@@ -424,6 +552,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type clientRequestAddToolbar
+ */
public static InventoryMessage parseclientRequestAddToolbarMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTADDTOOLBAR);
stripPacketHeader(byteBuffer);
@@ -432,6 +563,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type clientRequestAddToolbar
+ */
public static InventoryMessage constructclientRequestAddToolbarMessage(int entityId,int toolbarId){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTADDTOOLBAR);
rVal.setentityId(entityId);
@@ -440,6 +574,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type clientRequestAddNatural
+ */
public static InventoryMessage parseclientRequestAddNaturalMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTADDNATURAL);
stripPacketHeader(byteBuffer);
@@ -447,6 +584,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type clientRequestAddNatural
+ */
public static InventoryMessage constructclientRequestAddNaturalMessage(int entityId){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTADDNATURAL);
rVal.setentityId(entityId);
@@ -454,6 +594,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type clientUpdateToolbar
+ */
public static InventoryMessage parseclientUpdateToolbarMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTUPDATETOOLBAR);
stripPacketHeader(byteBuffer);
@@ -461,6 +604,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type clientUpdateToolbar
+ */
public static InventoryMessage constructclientUpdateToolbarMessage(int toolbarId){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTUPDATETOOLBAR);
rVal.settoolbarId(toolbarId);
@@ -468,6 +614,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type clientRequestPerformItemAction can be parsed from the byte stream
+ */
public static boolean canParseclientRequestPerformItemActionMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -493,6 +642,9 @@ public class InventoryMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type clientRequestPerformItemAction
+ */
public static InventoryMessage parseclientRequestPerformItemActionMessage(CircularByteBuffer byteBuffer){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTPERFORMITEMACTION);
stripPacketHeader(byteBuffer);
@@ -502,6 +654,9 @@ public class InventoryMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type clientRequestPerformItemAction
+ */
public static InventoryMessage constructclientRequestPerformItemActionMessage(String equipPointId,int itemActionCode,int itemActionCodeState){
InventoryMessage rVal = new InventoryMessage(InventoryMessageType.CLIENTREQUESTPERFORMITEMACTION);
rVal.setequipPointId(equipPointId);
diff --git a/src/main/java/electrosphere/net/parser/net/message/LoreMessage.java b/src/main/java/electrosphere/net/parser/net/message/LoreMessage.java
index 0232c523..d2f86345 100644
--- a/src/main/java/electrosphere/net/parser/net/message/LoreMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/LoreMessage.java
@@ -1,20 +1,30 @@
package electrosphere.net.parser.net.message;
+import io.github.studiorailgun.CircularByteBuffer;
import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
import java.util.LinkedList;
import java.util.List;
public class LoreMessage extends NetworkMessage {
+ /**
+ * The types of messages available in this category.
+ */
public enum LoreMessageType {
REQUESTRACES,
RESPONSERACES,
}
+ /**
+ * The type of this message in particular.
+ */
LoreMessageType messageType;
String data;
+ /**
+ * Constructor
+ * @param messageType The type of this message
+ */
LoreMessage(LoreMessageType messageType){
this.type = MessageType.LORE_MESSAGE;
this.messageType = messageType;
@@ -24,18 +34,34 @@ public class LoreMessage extends NetworkMessage {
return this.messageType;
}
+ /**
+ * Gets data
+ */
public String getdata() {
return data;
}
+ /**
+ * Sets data
+ */
public void setdata(String data) {
this.data = data;
}
+ /**
+ * Removes the packet header from the buffer
+ * @param byteBuffer The buffer
+ */
static void stripPacketHeader(CircularByteBuffer byteBuffer){
byteBuffer.read(2);
}
+ /**
+ * Checks if this message can be parsed (ie are all bytes present)
+ * @param byteBuffer The buffer
+ * @param secondByte The second byte, signifying the subtype of the message
+ * @return true if the message can be parsed, false otherwise
+ */
public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){
switch(secondByte){
case TypeBytes.LORE_MESSAGE_TYPE_REQUESTRACES:
@@ -50,18 +76,27 @@ public class LoreMessage extends NetworkMessage {
return false;
}
+ /**
+ * Parses a message of type RequestRaces
+ */
public static LoreMessage parseRequestRacesMessage(CircularByteBuffer byteBuffer){
LoreMessage rVal = new LoreMessage(LoreMessageType.REQUESTRACES);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type RequestRaces
+ */
public static LoreMessage constructRequestRacesMessage(){
LoreMessage rVal = new LoreMessage(LoreMessageType.REQUESTRACES);
rVal.serialize();
return rVal;
}
+ /**
+ * Checks if a message of type ResponseRaces can be parsed from the byte stream
+ */
public static boolean canParseResponseRacesMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -81,6 +116,9 @@ public class LoreMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type ResponseRaces
+ */
public static LoreMessage parseResponseRacesMessage(CircularByteBuffer byteBuffer){
LoreMessage rVal = new LoreMessage(LoreMessageType.RESPONSERACES);
stripPacketHeader(byteBuffer);
@@ -88,6 +126,9 @@ public class LoreMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type ResponseRaces
+ */
public static LoreMessage constructResponseRacesMessage(String data){
LoreMessage rVal = new LoreMessage(LoreMessageType.RESPONSERACES);
rVal.setdata(data);
diff --git a/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java b/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java
index 9101878d..6e44b898 100644
--- a/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java
@@ -1,37 +1,64 @@
package electrosphere.net.parser.net.message;
-import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
-
-import java.util.List;
+import io.github.studiorailgun.CircularByteBuffer;
+/**
+ * A network message
+ */
public abstract class NetworkMessage {
+ /**
+ * The different categories of network messages
+ */
public enum MessageType {
-ENTITY_MESSAGE,
-LORE_MESSAGE,
-PLAYER_MESSAGE,
-TERRAIN_MESSAGE,
-SERVER_MESSAGE,
-AUTH_MESSAGE,
-CHARACTER_MESSAGE,
-INVENTORY_MESSAGE,
-SYNCHRONIZATION_MESSAGE,
-COMBAT_MESSAGE,
+ ENTITY_MESSAGE,
+ LORE_MESSAGE,
+ PLAYER_MESSAGE,
+ TERRAIN_MESSAGE,
+ SERVER_MESSAGE,
+ AUTH_MESSAGE,
+ CHARACTER_MESSAGE,
+ INVENTORY_MESSAGE,
+ SYNCHRONIZATION_MESSAGE,
+ COMBAT_MESSAGE,
}
+ /**
+ * The type of this message
+ */
MessageType type;
- boolean serialized; // has this message been converted to bytes?
+
+ /**
+ * Tracks whether the message has been serialized to bytes or not
+ */
+ boolean serialized;
+
+ /**
+ * The raw bytes contained in the message
+ */
byte[] rawBytes;
+ /**
+ * Gets the type of the message
+ * @return The type of the message
+ */
public MessageType getType() {
return type;
}
+ /**
+ * Gets the raw bytes of the message
+ * @return The raw bytes
+ */
public byte[] getRawBytes() {
return rawBytes;
}
+ /**
+ * Parses the byte stream for the next message
+ * @param byteBuffer The byte buffer
+ * @return The message if one is at the front of the byte stream, null otherwise
+ */
public static NetworkMessage parseBytestreamForMessage(CircularByteBuffer byteBuffer){
NetworkMessage rVal = null;
byte firstByte;
@@ -409,10 +436,17 @@ COMBAT_MESSAGE,
return rVal;
}
+ /**
+ * Checks if this message is serialized or not
+ * @return true if it is serialized, false otherwise
+ */
public boolean isSerialized(){
return serialized;
}
+ /**
+ * Serializes the message
+ */
abstract void serialize();
}
diff --git a/src/main/java/electrosphere/net/parser/net/message/PlayerMessage.java b/src/main/java/electrosphere/net/parser/net/message/PlayerMessage.java
index f2b9f931..5f6def56 100644
--- a/src/main/java/electrosphere/net/parser/net/message/PlayerMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/PlayerMessage.java
@@ -1,23 +1,30 @@
package electrosphere.net.parser.net.message;
+import io.github.studiorailgun.CircularByteBuffer;
import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
-import java.util.LinkedList;
-import java.util.List;
-
public class PlayerMessage extends NetworkMessage {
+ /**
+ * The types of messages available in this category.
+ */
public enum PlayerMessageType {
SET_ID,
SETINITIALDISCRETEPOSITION,
}
+ /**
+ * The type of this message in particular.
+ */
PlayerMessageType messageType;
int playerID;
int initialDiscretePositionX;
int initialDiscretePositionY;
int initialDiscretePositionZ;
+ /**
+ * Constructor
+ * @param messageType The type of this message
+ */
PlayerMessage(PlayerMessageType messageType){
this.type = MessageType.PLAYER_MESSAGE;
this.messageType = messageType;
@@ -27,42 +34,76 @@ public class PlayerMessage extends NetworkMessage {
return this.messageType;
}
+ /**
+ * Gets playerID
+ */
public int getplayerID() {
return playerID;
}
+ /**
+ * Sets playerID
+ */
public void setplayerID(int playerID) {
this.playerID = playerID;
}
+ /**
+ * Gets initialDiscretePositionX
+ */
public int getinitialDiscretePositionX() {
return initialDiscretePositionX;
}
+ /**
+ * Sets initialDiscretePositionX
+ */
public void setinitialDiscretePositionX(int initialDiscretePositionX) {
this.initialDiscretePositionX = initialDiscretePositionX;
}
+ /**
+ * Gets initialDiscretePositionY
+ */
public int getinitialDiscretePositionY() {
return initialDiscretePositionY;
}
+ /**
+ * Sets initialDiscretePositionY
+ */
public void setinitialDiscretePositionY(int initialDiscretePositionY) {
this.initialDiscretePositionY = initialDiscretePositionY;
}
+ /**
+ * Gets initialDiscretePositionZ
+ */
public int getinitialDiscretePositionZ() {
return initialDiscretePositionZ;
}
+ /**
+ * Sets initialDiscretePositionZ
+ */
public void setinitialDiscretePositionZ(int initialDiscretePositionZ) {
this.initialDiscretePositionZ = initialDiscretePositionZ;
}
+ /**
+ * Removes the packet header from the buffer
+ * @param byteBuffer The buffer
+ */
static void stripPacketHeader(CircularByteBuffer byteBuffer){
byteBuffer.read(2);
}
+ /**
+ * Checks if this message can be parsed (ie are all bytes present)
+ * @param byteBuffer The buffer
+ * @param secondByte The second byte, signifying the subtype of the message
+ * @return true if the message can be parsed, false otherwise
+ */
public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){
switch(secondByte){
case TypeBytes.PLAYER_MESSAGE_TYPE_SET_ID:
@@ -81,6 +122,9 @@ public class PlayerMessage extends NetworkMessage {
return false;
}
+ /**
+ * Parses a message of type Set_ID
+ */
public static PlayerMessage parseSet_IDMessage(CircularByteBuffer byteBuffer){
PlayerMessage rVal = new PlayerMessage(PlayerMessageType.SET_ID);
stripPacketHeader(byteBuffer);
@@ -88,6 +132,9 @@ public class PlayerMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type Set_ID
+ */
public static PlayerMessage constructSet_IDMessage(int playerID){
PlayerMessage rVal = new PlayerMessage(PlayerMessageType.SET_ID);
rVal.setplayerID(playerID);
@@ -95,6 +142,9 @@ public class PlayerMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type SetInitialDiscretePosition
+ */
public static PlayerMessage parseSetInitialDiscretePositionMessage(CircularByteBuffer byteBuffer){
PlayerMessage rVal = new PlayerMessage(PlayerMessageType.SETINITIALDISCRETEPOSITION);
stripPacketHeader(byteBuffer);
@@ -104,6 +154,9 @@ public class PlayerMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type SetInitialDiscretePosition
+ */
public static PlayerMessage constructSetInitialDiscretePositionMessage(int initialDiscretePositionX,int initialDiscretePositionY,int initialDiscretePositionZ){
PlayerMessage rVal = new PlayerMessage(PlayerMessageType.SETINITIALDISCRETEPOSITION);
rVal.setinitialDiscretePositionX(initialDiscretePositionX);
@@ -116,7 +169,6 @@ public class PlayerMessage extends NetworkMessage {
@Override
void serialize(){
byte[] intValues = new byte[8];
- byte[] stringBytes;
switch(this.messageType){
case SET_ID:
rawBytes = new byte[2+4];
diff --git a/src/main/java/electrosphere/net/parser/net/message/ServerMessage.java b/src/main/java/electrosphere/net/parser/net/message/ServerMessage.java
index e5684902..baf96284 100644
--- a/src/main/java/electrosphere/net/parser/net/message/ServerMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/ServerMessage.java
@@ -1,19 +1,25 @@
package electrosphere.net.parser.net.message;
-import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
-import java.util.LinkedList;
-import java.util.List;
-
+import io.github.studiorailgun.CircularByteBuffer;
public class ServerMessage extends NetworkMessage {
+ /**
+ * The types of messages available in this category.
+ */
public enum ServerMessageType {
PING,
PONG,
}
+ /**
+ * The type of this message in particular.
+ */
ServerMessageType messageType;
+ /**
+ * Constructor
+ * @param messageType The type of this message
+ */
ServerMessage(ServerMessageType messageType){
this.type = MessageType.SERVER_MESSAGE;
this.messageType = messageType;
@@ -23,10 +29,20 @@ public class ServerMessage extends NetworkMessage {
return this.messageType;
}
+ /**
+ * Removes the packet header from the buffer
+ * @param byteBuffer The buffer
+ */
static void stripPacketHeader(CircularByteBuffer byteBuffer){
byteBuffer.read(2);
}
+ /**
+ * Checks if this message can be parsed (ie are all bytes present)
+ * @param byteBuffer The buffer
+ * @param secondByte The second byte, signifying the subtype of the message
+ * @return true if the message can be parsed, false otherwise
+ */
public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){
switch(secondByte){
case TypeBytes.SERVER_MESSAGE_TYPE_PING:
@@ -45,24 +61,36 @@ public class ServerMessage extends NetworkMessage {
return false;
}
+ /**
+ * Parses a message of type Ping
+ */
public static ServerMessage parsePingMessage(CircularByteBuffer byteBuffer){
ServerMessage rVal = new ServerMessage(ServerMessageType.PING);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type Ping
+ */
public static ServerMessage constructPingMessage(){
ServerMessage rVal = new ServerMessage(ServerMessageType.PING);
rVal.serialize();
return rVal;
}
+ /**
+ * Parses a message of type Pong
+ */
public static ServerMessage parsePongMessage(CircularByteBuffer byteBuffer){
ServerMessage rVal = new ServerMessage(ServerMessageType.PONG);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type Pong
+ */
public static ServerMessage constructPongMessage(){
ServerMessage rVal = new ServerMessage(ServerMessageType.PONG);
rVal.serialize();
@@ -71,8 +99,6 @@ public class ServerMessage extends NetworkMessage {
@Override
void serialize(){
- byte[] intValues = new byte[8];
- byte[] stringBytes;
switch(this.messageType){
case PING:
rawBytes = new byte[2];
diff --git a/src/main/java/electrosphere/net/parser/net/message/SynchronizationMessage.java b/src/main/java/electrosphere/net/parser/net/message/SynchronizationMessage.java
index 5a6783bc..2d557d3e 100644
--- a/src/main/java/electrosphere/net/parser/net/message/SynchronizationMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/SynchronizationMessage.java
@@ -1,12 +1,15 @@
package electrosphere.net.parser.net.message;
+import io.github.studiorailgun.CircularByteBuffer;
import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
import java.util.LinkedList;
import java.util.List;
public class SynchronizationMessage extends NetworkMessage {
+ /**
+ * The types of messages available in this category.
+ */
public enum SynchronizationMessageType {
UPDATECLIENTSTATE,
UPDATECLIENTSTRINGSTATE,
@@ -21,6 +24,9 @@ public class SynchronizationMessage extends NetworkMessage {
LOADSCENE,
}
+ /**
+ * The type of this message in particular.
+ */
SynchronizationMessageType messageType;
int entityId;
int bTreeId;
@@ -32,6 +38,10 @@ public class SynchronizationMessage extends NetworkMessage {
float floatValue;
double doubleValue;
+ /**
+ * Constructor
+ * @param messageType The type of this message
+ */
SynchronizationMessage(SynchronizationMessageType messageType){
this.type = MessageType.SYNCHRONIZATION_MESSAGE;
this.messageType = messageType;
@@ -41,82 +51,146 @@ public class SynchronizationMessage extends NetworkMessage {
return this.messageType;
}
+ /**
+ * Gets entityId
+ */
public int getentityId() {
return entityId;
}
+ /**
+ * Sets entityId
+ */
public void setentityId(int entityId) {
this.entityId = entityId;
}
+ /**
+ * Gets bTreeId
+ */
public int getbTreeId() {
return bTreeId;
}
+ /**
+ * Sets bTreeId
+ */
public void setbTreeId(int bTreeId) {
this.bTreeId = bTreeId;
}
+ /**
+ * Gets fieldId
+ */
public int getfieldId() {
return fieldId;
}
+ /**
+ * Sets fieldId
+ */
public void setfieldId(int fieldId) {
this.fieldId = fieldId;
}
+ /**
+ * Gets bTreeValue
+ */
public int getbTreeValue() {
return bTreeValue;
}
+ /**
+ * Sets bTreeValue
+ */
public void setbTreeValue(int bTreeValue) {
this.bTreeValue = bTreeValue;
}
+ /**
+ * Gets stringValue
+ */
public String getstringValue() {
return stringValue;
}
+ /**
+ * Sets stringValue
+ */
public void setstringValue(String stringValue) {
this.stringValue = stringValue;
}
+ /**
+ * Gets intValue
+ */
public int getintValue() {
return intValue;
}
+ /**
+ * Sets intValue
+ */
public void setintValue(int intValue) {
this.intValue = intValue;
}
+ /**
+ * Gets longValue
+ */
public long getlongValue() {
return longValue;
}
+ /**
+ * Sets longValue
+ */
public void setlongValue(long longValue) {
this.longValue = longValue;
}
+ /**
+ * Gets floatValue
+ */
public float getfloatValue() {
return floatValue;
}
+ /**
+ * Sets floatValue
+ */
public void setfloatValue(float floatValue) {
this.floatValue = floatValue;
}
+ /**
+ * Gets doubleValue
+ */
public double getdoubleValue() {
return doubleValue;
}
+ /**
+ * Sets doubleValue
+ */
public void setdoubleValue(double doubleValue) {
this.doubleValue = doubleValue;
}
+ /**
+ * Removes the packet header from the buffer
+ * @param byteBuffer The buffer
+ */
static void stripPacketHeader(CircularByteBuffer byteBuffer){
byteBuffer.read(2);
}
+ /**
+ * Checks if this message can be parsed (ie are all bytes present)
+ * @param byteBuffer The buffer
+ * @param secondByte The second byte, signifying the subtype of the message
+ * @return true if the message can be parsed, false otherwise
+ */
public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){
switch(secondByte){
case TypeBytes.SYNCHRONIZATION_MESSAGE_TYPE_UPDATECLIENTSTATE:
@@ -181,6 +255,9 @@ public class SynchronizationMessage extends NetworkMessage {
return false;
}
+ /**
+ * Parses a message of type UpdateClientState
+ */
public static SynchronizationMessage parseUpdateClientStateMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTSTATE);
stripPacketHeader(byteBuffer);
@@ -191,6 +268,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type UpdateClientState
+ */
public static SynchronizationMessage constructUpdateClientStateMessage(int entityId,int bTreeId,int fieldId,int bTreeValue){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTSTATE);
rVal.setentityId(entityId);
@@ -201,6 +281,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type UpdateClientStringState can be parsed from the byte stream
+ */
public static boolean canParseUpdateClientStringStateMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -229,6 +312,9 @@ public class SynchronizationMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type UpdateClientStringState
+ */
public static SynchronizationMessage parseUpdateClientStringStateMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTSTRINGSTATE);
stripPacketHeader(byteBuffer);
@@ -239,6 +325,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type UpdateClientStringState
+ */
public static SynchronizationMessage constructUpdateClientStringStateMessage(int entityId,int bTreeId,int fieldId,String stringValue){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTSTRINGSTATE);
rVal.setentityId(entityId);
@@ -249,6 +338,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type UpdateClientIntState
+ */
public static SynchronizationMessage parseUpdateClientIntStateMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTINTSTATE);
stripPacketHeader(byteBuffer);
@@ -259,6 +351,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type UpdateClientIntState
+ */
public static SynchronizationMessage constructUpdateClientIntStateMessage(int entityId,int bTreeId,int fieldId,int intValue){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTINTSTATE);
rVal.setentityId(entityId);
@@ -269,6 +364,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type UpdateClientLongState
+ */
public static SynchronizationMessage parseUpdateClientLongStateMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTLONGSTATE);
stripPacketHeader(byteBuffer);
@@ -279,6 +377,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type UpdateClientLongState
+ */
public static SynchronizationMessage constructUpdateClientLongStateMessage(int entityId,int bTreeId,int fieldId,long longValue){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTLONGSTATE);
rVal.setentityId(entityId);
@@ -289,6 +390,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type UpdateClientFloatState
+ */
public static SynchronizationMessage parseUpdateClientFloatStateMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTFLOATSTATE);
stripPacketHeader(byteBuffer);
@@ -299,6 +403,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type UpdateClientFloatState
+ */
public static SynchronizationMessage constructUpdateClientFloatStateMessage(int entityId,int bTreeId,int fieldId,float floatValue){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTFLOATSTATE);
rVal.setentityId(entityId);
@@ -309,6 +416,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type UpdateClientDoubleState
+ */
public static SynchronizationMessage parseUpdateClientDoubleStateMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTDOUBLESTATE);
stripPacketHeader(byteBuffer);
@@ -319,6 +429,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type UpdateClientDoubleState
+ */
public static SynchronizationMessage constructUpdateClientDoubleStateMessage(int entityId,int bTreeId,int fieldId,double doubleValue){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.UPDATECLIENTDOUBLESTATE);
rVal.setentityId(entityId);
@@ -329,6 +442,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type ClientRequestBTreeAction
+ */
public static SynchronizationMessage parseClientRequestBTreeActionMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.CLIENTREQUESTBTREEACTION);
stripPacketHeader(byteBuffer);
@@ -338,6 +454,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type ClientRequestBTreeAction
+ */
public static SynchronizationMessage constructClientRequestBTreeActionMessage(int entityId,int bTreeId,int bTreeValue){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.CLIENTREQUESTBTREEACTION);
rVal.setentityId(entityId);
@@ -347,6 +466,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type ServerNotifyBTreeTransition
+ */
public static SynchronizationMessage parseServerNotifyBTreeTransitionMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.SERVERNOTIFYBTREETRANSITION);
stripPacketHeader(byteBuffer);
@@ -357,6 +479,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type ServerNotifyBTreeTransition
+ */
public static SynchronizationMessage constructServerNotifyBTreeTransitionMessage(int entityId,int bTreeId,int fieldId,int bTreeValue){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.SERVERNOTIFYBTREETRANSITION);
rVal.setentityId(entityId);
@@ -367,6 +492,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type AttachTree
+ */
public static SynchronizationMessage parseAttachTreeMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.ATTACHTREE);
stripPacketHeader(byteBuffer);
@@ -375,6 +503,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type AttachTree
+ */
public static SynchronizationMessage constructAttachTreeMessage(int entityId,int bTreeId){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.ATTACHTREE);
rVal.setentityId(entityId);
@@ -383,6 +514,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type DetatchTree
+ */
public static SynchronizationMessage parseDetatchTreeMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.DETATCHTREE);
stripPacketHeader(byteBuffer);
@@ -391,6 +525,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type DetatchTree
+ */
public static SynchronizationMessage constructDetatchTreeMessage(int entityId,int bTreeId){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.DETATCHTREE);
rVal.setentityId(entityId);
@@ -399,6 +536,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type LoadScene can be parsed from the byte stream
+ */
public static boolean canParseLoadSceneMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -418,6 +558,9 @@ public class SynchronizationMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type LoadScene
+ */
public static SynchronizationMessage parseLoadSceneMessage(CircularByteBuffer byteBuffer){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.LOADSCENE);
stripPacketHeader(byteBuffer);
@@ -425,6 +568,9 @@ public class SynchronizationMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type LoadScene
+ */
public static SynchronizationMessage constructLoadSceneMessage(String stringValue){
SynchronizationMessage rVal = new SynchronizationMessage(SynchronizationMessageType.LOADSCENE);
rVal.setstringValue(stringValue);
diff --git a/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java b/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java
index 596297dd..8a21fc6f 100644
--- a/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java
+++ b/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java
@@ -1,12 +1,15 @@
package electrosphere.net.parser.net.message;
+import io.github.studiorailgun.CircularByteBuffer;
import electrosphere.net.parser.util.ByteStreamUtils;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
import java.util.LinkedList;
import java.util.List;
public class TerrainMessage extends NetworkMessage {
+ /**
+ * The types of messages available in this category.
+ */
public enum TerrainMessageType {
REQUESTMETADATA,
RESPONSEMETADATA,
@@ -23,6 +26,9 @@ public class TerrainMessage extends NetworkMessage {
UPDATEFLUIDDATA,
}
+ /**
+ * The type of this message in particular.
+ */
TerrainMessageType messageType;
int worldSizeDiscrete;
int dynamicInterpolationRatio;
@@ -46,6 +52,10 @@ public class TerrainMessage extends NetworkMessage {
float terrainWeight;
int terrainValue;
+ /**
+ * Constructor
+ * @param messageType The type of this message
+ */
TerrainMessage(TerrainMessageType messageType){
this.type = MessageType.TERRAIN_MESSAGE;
this.messageType = messageType;
@@ -55,178 +65,314 @@ public class TerrainMessage extends NetworkMessage {
return this.messageType;
}
+ /**
+ * Gets worldSizeDiscrete
+ */
public int getworldSizeDiscrete() {
return worldSizeDiscrete;
}
+ /**
+ * Sets worldSizeDiscrete
+ */
public void setworldSizeDiscrete(int worldSizeDiscrete) {
this.worldSizeDiscrete = worldSizeDiscrete;
}
+ /**
+ * Gets dynamicInterpolationRatio
+ */
public int getdynamicInterpolationRatio() {
return dynamicInterpolationRatio;
}
+ /**
+ * Sets dynamicInterpolationRatio
+ */
public void setdynamicInterpolationRatio(int dynamicInterpolationRatio) {
this.dynamicInterpolationRatio = dynamicInterpolationRatio;
}
+ /**
+ * Gets randomDampener
+ */
public float getrandomDampener() {
return randomDampener;
}
+ /**
+ * Sets randomDampener
+ */
public void setrandomDampener(float randomDampener) {
this.randomDampener = randomDampener;
}
+ /**
+ * Gets worldMinX
+ */
public int getworldMinX() {
return worldMinX;
}
+ /**
+ * Sets worldMinX
+ */
public void setworldMinX(int worldMinX) {
this.worldMinX = worldMinX;
}
+ /**
+ * Gets worldMinY
+ */
public int getworldMinY() {
return worldMinY;
}
+ /**
+ * Sets worldMinY
+ */
public void setworldMinY(int worldMinY) {
this.worldMinY = worldMinY;
}
+ /**
+ * Gets worldMaxX
+ */
public int getworldMaxX() {
return worldMaxX;
}
+ /**
+ * Sets worldMaxX
+ */
public void setworldMaxX(int worldMaxX) {
this.worldMaxX = worldMaxX;
}
+ /**
+ * Gets worldMaxY
+ */
public int getworldMaxY() {
return worldMaxY;
}
+ /**
+ * Sets worldMaxY
+ */
public void setworldMaxY(int worldMaxY) {
this.worldMaxY = worldMaxY;
}
+ /**
+ * Gets value
+ */
public float getvalue() {
return value;
}
+ /**
+ * Sets value
+ */
public void setvalue(float value) {
this.value = value;
}
+ /**
+ * Gets worldX
+ */
public int getworldX() {
return worldX;
}
+ /**
+ * Sets worldX
+ */
public void setworldX(int worldX) {
this.worldX = worldX;
}
+ /**
+ * Gets worldY
+ */
public int getworldY() {
return worldY;
}
+ /**
+ * Sets worldY
+ */
public void setworldY(int worldY) {
this.worldY = worldY;
}
+ /**
+ * Gets worldZ
+ */
public int getworldZ() {
return worldZ;
}
+ /**
+ * Sets worldZ
+ */
public void setworldZ(int worldZ) {
this.worldZ = worldZ;
}
+ /**
+ * Gets voxelX
+ */
public int getvoxelX() {
return voxelX;
}
+ /**
+ * Sets voxelX
+ */
public void setvoxelX(int voxelX) {
this.voxelX = voxelX;
}
+ /**
+ * Gets voxelY
+ */
public int getvoxelY() {
return voxelY;
}
+ /**
+ * Sets voxelY
+ */
public void setvoxelY(int voxelY) {
this.voxelY = voxelY;
}
+ /**
+ * Gets voxelZ
+ */
public int getvoxelZ() {
return voxelZ;
}
+ /**
+ * Sets voxelZ
+ */
public void setvoxelZ(int voxelZ) {
this.voxelZ = voxelZ;
}
+ /**
+ * Gets realLocationX
+ */
public double getrealLocationX() {
return realLocationX;
}
+ /**
+ * Sets realLocationX
+ */
public void setrealLocationX(double realLocationX) {
this.realLocationX = realLocationX;
}
+ /**
+ * Gets realLocationY
+ */
public double getrealLocationY() {
return realLocationY;
}
+ /**
+ * Sets realLocationY
+ */
public void setrealLocationY(double realLocationY) {
this.realLocationY = realLocationY;
}
+ /**
+ * Gets realLocationZ
+ */
public double getrealLocationZ() {
return realLocationZ;
}
+ /**
+ * Sets realLocationZ
+ */
public void setrealLocationZ(double realLocationZ) {
this.realLocationZ = realLocationZ;
}
+ /**
+ * Gets chunkData
+ */
public byte[] getchunkData() {
return chunkData;
}
+ /**
+ * Sets chunkData
+ */
public void setchunkData(byte[] chunkData) {
this.chunkData = chunkData;
}
+ /**
+ * Gets chunkResolution
+ */
public int getchunkResolution() {
return chunkResolution;
}
+ /**
+ * Sets chunkResolution
+ */
public void setchunkResolution(int chunkResolution) {
this.chunkResolution = chunkResolution;
}
+ /**
+ * Gets terrainWeight
+ */
public float getterrainWeight() {
return terrainWeight;
}
+ /**
+ * Sets terrainWeight
+ */
public void setterrainWeight(float terrainWeight) {
this.terrainWeight = terrainWeight;
}
+ /**
+ * Gets terrainValue
+ */
public int getterrainValue() {
return terrainValue;
}
+ /**
+ * Sets terrainValue
+ */
public void setterrainValue(int terrainValue) {
this.terrainValue = terrainValue;
}
+ /**
+ * Removes the packet header from the buffer
+ * @param byteBuffer The buffer
+ */
static void stripPacketHeader(CircularByteBuffer byteBuffer){
byteBuffer.read(2);
}
+ /**
+ * Checks if this message can be parsed (ie are all bytes present)
+ * @param byteBuffer The buffer
+ * @param secondByte The second byte, signifying the subtype of the message
+ * @return true if the message can be parsed, false otherwise
+ */
public static boolean canParseMessage(CircularByteBuffer byteBuffer, byte secondByte){
switch(secondByte){
case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTMETADATA:
@@ -295,18 +441,27 @@ public class TerrainMessage extends NetworkMessage {
return false;
}
+ /**
+ * Parses a message of type RequestMetadata
+ */
public static TerrainMessage parseRequestMetadataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTMETADATA);
stripPacketHeader(byteBuffer);
return rVal;
}
+ /**
+ * Constructs a message of type RequestMetadata
+ */
public static TerrainMessage constructRequestMetadataMessage(){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTMETADATA);
rVal.serialize();
return rVal;
}
+ /**
+ * Parses a message of type ResponseMetadata
+ */
public static TerrainMessage parseResponseMetadataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.RESPONSEMETADATA);
stripPacketHeader(byteBuffer);
@@ -320,6 +475,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type ResponseMetadata
+ */
public static TerrainMessage constructResponseMetadataMessage(int worldSizeDiscrete,int dynamicInterpolationRatio,float randomDampener,int worldMinX,int worldMinY,int worldMaxX,int worldMaxY){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.RESPONSEMETADATA);
rVal.setworldSizeDiscrete(worldSizeDiscrete);
@@ -333,6 +491,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type RequestEditVoxel
+ */
public static TerrainMessage parseRequestEditVoxelMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTEDITVOXEL);
stripPacketHeader(byteBuffer);
@@ -347,6 +508,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type RequestEditVoxel
+ */
public static TerrainMessage constructRequestEditVoxelMessage(int worldX,int worldY,int worldZ,int voxelX,int voxelY,int voxelZ,float terrainWeight,int terrainValue){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTEDITVOXEL);
rVal.setworldX(worldX);
@@ -361,6 +525,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type UpdateVoxel
+ */
public static TerrainMessage parseUpdateVoxelMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.UPDATEVOXEL);
stripPacketHeader(byteBuffer);
@@ -375,6 +542,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type UpdateVoxel
+ */
public static TerrainMessage constructUpdateVoxelMessage(int worldX,int worldY,int worldZ,int voxelX,int voxelY,int voxelZ,float terrainWeight,int terrainValue){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.UPDATEVOXEL);
rVal.setworldX(worldX);
@@ -389,6 +559,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type RequestUseTerrainPalette
+ */
public static TerrainMessage parseRequestUseTerrainPaletteMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTUSETERRAINPALETTE);
stripPacketHeader(byteBuffer);
@@ -401,6 +574,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type RequestUseTerrainPalette
+ */
public static TerrainMessage constructRequestUseTerrainPaletteMessage(double realLocationX,double realLocationY,double realLocationZ,float value,float terrainWeight,int terrainValue){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTUSETERRAINPALETTE);
rVal.setrealLocationX(realLocationX);
@@ -413,6 +589,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type SpawnPosition
+ */
public static TerrainMessage parseSpawnPositionMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SPAWNPOSITION);
stripPacketHeader(byteBuffer);
@@ -422,6 +601,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type SpawnPosition
+ */
public static TerrainMessage constructSpawnPositionMessage(double realLocationX,double realLocationY,double realLocationZ){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SPAWNPOSITION);
rVal.setrealLocationX(realLocationX);
@@ -431,6 +613,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type RequestChunkData
+ */
public static TerrainMessage parseRequestChunkDataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTCHUNKDATA);
stripPacketHeader(byteBuffer);
@@ -440,6 +625,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type RequestChunkData
+ */
public static TerrainMessage constructRequestChunkDataMessage(int worldX,int worldY,int worldZ){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTCHUNKDATA);
rVal.setworldX(worldX);
@@ -449,6 +637,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type sendChunkData can be parsed from the byte stream
+ */
public static boolean canParsesendChunkDataMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -477,6 +668,9 @@ public class TerrainMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type sendChunkData
+ */
public static TerrainMessage parsesendChunkDataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDCHUNKDATA);
stripPacketHeader(byteBuffer);
@@ -487,6 +681,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type sendChunkData
+ */
public static TerrainMessage constructsendChunkDataMessage(int worldX,int worldY,int worldZ,byte[] chunkData){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDCHUNKDATA);
rVal.setworldX(worldX);
@@ -497,6 +694,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type RequestReducedChunkData
+ */
public static TerrainMessage parseRequestReducedChunkDataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTREDUCEDCHUNKDATA);
stripPacketHeader(byteBuffer);
@@ -507,6 +707,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type RequestReducedChunkData
+ */
public static TerrainMessage constructRequestReducedChunkDataMessage(int worldX,int worldY,int worldZ,int chunkResolution){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTREDUCEDCHUNKDATA);
rVal.setworldX(worldX);
@@ -517,6 +720,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type SendReducedChunkData can be parsed from the byte stream
+ */
public static boolean canParseSendReducedChunkDataMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -548,6 +754,9 @@ public class TerrainMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type SendReducedChunkData
+ */
public static TerrainMessage parseSendReducedChunkDataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDCHUNKDATA);
stripPacketHeader(byteBuffer);
@@ -559,6 +768,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type SendReducedChunkData
+ */
public static TerrainMessage constructSendReducedChunkDataMessage(int worldX,int worldY,int worldZ,int chunkResolution,byte[] chunkData){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDCHUNKDATA);
rVal.setworldX(worldX);
@@ -570,6 +782,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Parses a message of type RequestFluidData
+ */
public static TerrainMessage parseRequestFluidDataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTFLUIDDATA);
stripPacketHeader(byteBuffer);
@@ -579,6 +794,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type RequestFluidData
+ */
public static TerrainMessage constructRequestFluidDataMessage(int worldX,int worldY,int worldZ){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTFLUIDDATA);
rVal.setworldX(worldX);
@@ -588,6 +806,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type sendFluidData can be parsed from the byte stream
+ */
public static boolean canParsesendFluidDataMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -616,6 +837,9 @@ public class TerrainMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type sendFluidData
+ */
public static TerrainMessage parsesendFluidDataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDFLUIDDATA);
stripPacketHeader(byteBuffer);
@@ -626,6 +850,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type sendFluidData
+ */
public static TerrainMessage constructsendFluidDataMessage(int worldX,int worldY,int worldZ,byte[] chunkData){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDFLUIDDATA);
rVal.setworldX(worldX);
@@ -636,6 +863,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Checks if a message of type updateFluidData can be parsed from the byte stream
+ */
public static boolean canParseupdateFluidDataMessage(CircularByteBuffer byteBuffer){
int currentStreamLength = byteBuffer.getRemaining();
List temporaryByteQueue = new LinkedList();
@@ -664,6 +894,9 @@ public class TerrainMessage extends NetworkMessage {
return true;
}
+ /**
+ * Parses a message of type updateFluidData
+ */
public static TerrainMessage parseupdateFluidDataMessage(CircularByteBuffer byteBuffer){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.UPDATEFLUIDDATA);
stripPacketHeader(byteBuffer);
@@ -674,6 +907,9 @@ public class TerrainMessage extends NetworkMessage {
return rVal;
}
+ /**
+ * Constructs a message of type updateFluidData
+ */
public static TerrainMessage constructupdateFluidDataMessage(int worldX,int worldY,int worldZ,byte[] chunkData){
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.UPDATEFLUIDDATA);
rVal.setworldX(worldX);
@@ -687,7 +923,6 @@ public class TerrainMessage extends NetworkMessage {
@Override
void serialize(){
byte[] intValues = new byte[8];
- byte[] stringBytes;
switch(this.messageType){
case REQUESTMETADATA:
rawBytes = new byte[2];
diff --git a/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java b/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java
index caf8e007..4d7256b2 100644
--- a/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java
+++ b/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java
@@ -1,10 +1,12 @@
package electrosphere.net.parser.net.message;
-
+/**
+ * The constants used in serializing/deserializing messages
+ */
public class TypeBytes {
-/*
-Message categories
-*/
+ /**
+ * Message categories
+ */
public static final byte MESSAGE_TYPE_ENTITY = 0;
public static final byte MESSAGE_TYPE_LORE = 1;
public static final byte MESSAGE_TYPE_PLAYER = 2;
@@ -16,7 +18,7 @@ Message categories
public static final byte MESSAGE_TYPE_SYNCHRONIZATION = 8;
public static final byte MESSAGE_TYPE_COMBAT = 9;
/*
- Entity subcategories
+ Entity subcategories
*/
public static final byte ENTITY_MESSAGE_TYPE_CREATE = 0;
public static final byte ENTITY_MESSAGE_TYPE_MOVEUPDATE = 1;
@@ -29,7 +31,7 @@ Message categories
public static final byte ENTITY_MESSAGE_TYPE_UPDATEENTITYVIEWDIR = 8;
public static final byte ENTITY_MESSAGE_TYPE_SYNCPHYSICS = 9;
/*
- Entity packet sizes
+ Entity packet sizes
*/
public static final byte ENTITY_MESSAGE_TYPE_MOVEUPDATE_SIZE = 86;
public static final byte ENTITY_MESSAGE_TYPE_ATTACKUPDATE_SIZE = 74;
@@ -39,27 +41,30 @@ Message categories
public static final byte ENTITY_MESSAGE_TYPE_SETPROPERTY_SIZE = 22;
public static final byte ENTITY_MESSAGE_TYPE_UPDATEENTITYVIEWDIR_SIZE = 34;
public static final short ENTITY_MESSAGE_TYPE_SYNCPHYSICS_SIZE = 166;
+
/*
- Lore subcategories
+ Lore subcategories
*/
public static final byte LORE_MESSAGE_TYPE_REQUESTRACES = 0;
public static final byte LORE_MESSAGE_TYPE_RESPONSERACES = 1;
/*
- Lore packet sizes
+ Lore packet sizes
*/
public static final byte LORE_MESSAGE_TYPE_REQUESTRACES_SIZE = 2;
+
/*
- Player subcategories
+ Player subcategories
*/
public static final byte PLAYER_MESSAGE_TYPE_SET_ID = 0;
public static final byte PLAYER_MESSAGE_TYPE_SETINITIALDISCRETEPOSITION = 1;
/*
- Player packet sizes
+ Player packet sizes
*/
public static final byte PLAYER_MESSAGE_TYPE_SET_ID_SIZE = 6;
public static final byte PLAYER_MESSAGE_TYPE_SETINITIALDISCRETEPOSITION_SIZE = 14;
+
/*
- Terrain subcategories
+ Terrain subcategories
*/
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTMETADATA = 0;
public static final byte TERRAIN_MESSAGE_TYPE_RESPONSEMETADATA = 1;
@@ -75,7 +80,7 @@ Message categories
public static final byte TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA = 11;
public static final byte TERRAIN_MESSAGE_TYPE_UPDATEFLUIDDATA = 12;
/*
- Terrain packet sizes
+ Terrain packet sizes
*/
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTMETADATA_SIZE = 2;
public static final byte TERRAIN_MESSAGE_TYPE_RESPONSEMETADATA_SIZE = 30;
@@ -86,31 +91,34 @@ Message categories
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTCHUNKDATA_SIZE = 14;
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA_SIZE = 18;
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE = 14;
+
/*
- Server subcategories
+ Server subcategories
*/
public static final byte SERVER_MESSAGE_TYPE_PING = 0;
public static final byte SERVER_MESSAGE_TYPE_PONG = 1;
/*
- Server packet sizes
+ Server packet sizes
*/
public static final byte SERVER_MESSAGE_TYPE_PING_SIZE = 2;
public static final byte SERVER_MESSAGE_TYPE_PONG_SIZE = 2;
+
/*
- Auth subcategories
+ Auth subcategories
*/
public static final byte AUTH_MESSAGE_TYPE_AUTHREQUEST = 0;
public static final byte AUTH_MESSAGE_TYPE_AUTHDETAILS = 1;
public static final byte AUTH_MESSAGE_TYPE_AUTHSUCCESS = 2;
public static final byte AUTH_MESSAGE_TYPE_AUTHFAILURE = 3;
/*
- Auth packet sizes
+ Auth packet sizes
*/
public static final byte AUTH_MESSAGE_TYPE_AUTHREQUEST_SIZE = 2;
public static final byte AUTH_MESSAGE_TYPE_AUTHSUCCESS_SIZE = 2;
public static final byte AUTH_MESSAGE_TYPE_AUTHFAILURE_SIZE = 2;
+
/*
- Character subcategories
+ Character subcategories
*/
public static final byte CHARACTER_MESSAGE_TYPE_REQUESTCHARACTERLIST = 0;
public static final byte CHARACTER_MESSAGE_TYPE_RESPONSECHARACTERLIST = 1;
@@ -120,14 +128,15 @@ Message categories
public static final byte CHARACTER_MESSAGE_TYPE_REQUESTSPAWNCHARACTER = 5;
public static final byte CHARACTER_MESSAGE_TYPE_RESPONSESPAWNCHARACTER = 6;
/*
- Character packet sizes
+ Character packet sizes
*/
public static final byte CHARACTER_MESSAGE_TYPE_REQUESTCHARACTERLIST_SIZE = 2;
public static final byte CHARACTER_MESSAGE_TYPE_RESPONSECREATECHARACTERSUCCESS_SIZE = 2;
public static final byte CHARACTER_MESSAGE_TYPE_RESPONSECREATECHARACTERFAILURE_SIZE = 2;
public static final byte CHARACTER_MESSAGE_TYPE_REQUESTSPAWNCHARACTER_SIZE = 2;
+
/*
- Inventory subcategories
+ Inventory subcategories
*/
public static final byte INVENTORY_MESSAGE_TYPE_ADDITEMTOINVENTORY = 0;
public static final byte INVENTORY_MESSAGE_TYPE_REMOVEITEMFROMINVENTORY = 1;
@@ -141,14 +150,15 @@ Message categories
public static final byte INVENTORY_MESSAGE_TYPE_CLIENTUPDATETOOLBAR = 9;
public static final byte INVENTORY_MESSAGE_TYPE_CLIENTREQUESTPERFORMITEMACTION = 10;
/*
- Inventory packet sizes
+ Inventory packet sizes
*/
public static final byte INVENTORY_MESSAGE_TYPE_REMOVEITEMFROMINVENTORY_SIZE = 6;
public static final byte INVENTORY_MESSAGE_TYPE_CLIENTREQUESTADDTOOLBAR_SIZE = 10;
public static final byte INVENTORY_MESSAGE_TYPE_CLIENTREQUESTADDNATURAL_SIZE = 6;
public static final byte INVENTORY_MESSAGE_TYPE_CLIENTUPDATETOOLBAR_SIZE = 6;
+
/*
- Synchronization subcategories
+ Synchronization subcategories
*/
public static final byte SYNCHRONIZATION_MESSAGE_TYPE_UPDATECLIENTSTATE = 0;
public static final byte SYNCHRONIZATION_MESSAGE_TYPE_UPDATECLIENTSTRINGSTATE = 1;
@@ -162,7 +172,7 @@ Message categories
public static final byte SYNCHRONIZATION_MESSAGE_TYPE_DETATCHTREE = 9;
public static final byte SYNCHRONIZATION_MESSAGE_TYPE_LOADSCENE = 10;
/*
- Synchronization packet sizes
+ Synchronization packet sizes
*/
public static final byte SYNCHRONIZATION_MESSAGE_TYPE_UPDATECLIENTSTATE_SIZE = 18;
public static final byte SYNCHRONIZATION_MESSAGE_TYPE_UPDATECLIENTINTSTATE_SIZE = 18;
@@ -173,12 +183,14 @@ Message categories
public static final byte SYNCHRONIZATION_MESSAGE_TYPE_SERVERNOTIFYBTREETRANSITION_SIZE = 18;
public static final byte SYNCHRONIZATION_MESSAGE_TYPE_ATTACHTREE_SIZE = 10;
public static final byte SYNCHRONIZATION_MESSAGE_TYPE_DETATCHTREE_SIZE = 10;
+
/*
- Combat subcategories
+ Combat subcategories
*/
public static final byte COMBAT_MESSAGE_TYPE_SERVERREPORTHITBOXCOLLISION = 0;
/*
- Combat packet sizes
+ Combat packet sizes
*/
+
}
diff --git a/src/main/java/electrosphere/net/parser/net/raw/CircularByteBuffer.java b/src/main/java/electrosphere/net/parser/net/raw/CircularByteBuffer.java
deleted file mode 100644
index 2aadb3f8..00000000
--- a/src/main/java/electrosphere/net/parser/net/raw/CircularByteBuffer.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package electrosphere.net.parser.net.raw;
-
-
-import java.util.concurrent.Semaphore;
-
-/**
- * A circular byte buffer optimized for high throughput (relative to a list) and peaking at early elements of the current position.
- */
-public class CircularByteBuffer {
-
- //The array backing this circular byte buffer
- byte[] backingArray;
- //the current read position of the buffer in the backing array
- int position;
- //the remaining bytes to read before the read position equals the write position
- int remaining;
- //the capacity of the backing array
- int capacity;
- //Lock to make the structure threadsafe
- Semaphore lock = new Semaphore(1);
-
- /**
- * Constructs a CircularByteBuffer
- * @param capacity The capacity of the backing array in bytes
- */
- public CircularByteBuffer(int capacity){
- backingArray = new byte[capacity];
- position = 0;
- remaining = 0;
- this.capacity = capacity;
- }
-
- /**
- * Adds an array of bytes to the circular buffer
- * @param bytes The bytes
- * @param len The number of bytes to pull from the array bytes
- */
- public void add(byte[] bytes, int len){
- lock.acquireUninterruptibly();
- // System.out.println("Add start");
- int writePosition = (position + remaining) % capacity;
- //amount possible to write before wrapping
- int writeBeforeWrap = capacity - writePosition;
- //only run wrapping logic if necessary
- if(len > writeBeforeWrap){
- System.arraycopy(bytes, 0, backingArray, writePosition, writeBeforeWrap);
- System.arraycopy(bytes, writeBeforeWrap, backingArray, 0, len - writeBeforeWrap);
- } else {
- System.arraycopy(bytes, 0, backingArray, writePosition, len);
- }
- remaining = remaining + len;
- lock.release();
- }
-
- /**
- * Peeks at the next element in the buffer
- * @return The value of the byte next in the buffer
- */
- public byte peek(){
- byte rVal = peek(0);
- return rVal;
- }
-
- /**
- * Peeks at an element @param offset elements further along the buffer from the current position
- * @param offset The offset, in bytes, to look forward in the buffer
- * @return The value of the byte at the current position + @param offset
- */
- public byte peek(int offset){
- lock.acquireUninterruptibly();
- byte rVal = backingArray[(position + offset) % capacity];
- lock.release();
- return rVal;
- }
-
- /**
- * Gets the remaining number of bytes in the buffer
- * @return The remaining number of bytes
- */
- public int getRemaining(){
- lock.acquireUninterruptibly();
- int rVal = remaining;
- lock.release();
- return rVal;
- }
-
- /**
- * Gets the capacity of the buffer
- * @return The capacity
- */
- public int getCapacity(){
- lock.acquireUninterruptibly();
- int rVal = capacity;
- lock.release();
- return rVal;
- }
-
- /**
- * Reads a given number of bytes from the buffer
- * @param len The number of bytes to read
- * @return The bytes in an array
- */
- public byte[] read(int len){
- lock.acquireUninterruptibly();
- byte[] rVal = new byte[len];
- //amount possible to read before loop
- int toReadBeforeLoop = capacity - position;
- if(len > capacity - position){
- System.arraycopy(backingArray, position, rVal, 0, toReadBeforeLoop);
- System.arraycopy(backingArray, 0, rVal, toReadBeforeLoop, len - toReadBeforeLoop);
- } else {
- System.arraycopy(backingArray, position, rVal, 0, len);
- }
- position = (position + len) % capacity;
- remaining = remaining - len;
- lock.release();
- return rVal;
- }
-
-}
diff --git a/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java b/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java
index f91afbb3..1422ab13 100644
--- a/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java
+++ b/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java
@@ -1,76 +1,132 @@
package electrosphere.net.parser.net.raw;
import electrosphere.net.parser.net.message.NetworkMessage;
+import io.github.studiorailgun.CircularByteBuffer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+/**
+ * The main message parser. This is used to serialize/deserialize messages to/from the provided streams.
+ */
public class NetworkParser {
+
+ /**
+ * The size of the read buffer
+ */
+ static final int READ_BLOCK_SIZE = 16 * 1024 * 1024;
+
+ /**
+ * The size of the circular buffer
+ */
+ static final int CIRCULAR_BUFFER_SIZE = 64 * 1024 * 1024;
+ /**
+ * The input stream for the parser
+ */
InputStream incomingStream;
+
+ /**
+ * The output stream for the parser
+ */
OutputStream outgoingStream;
+ /**
+ * The queue of incoming messages that have been parsed
+ */
CopyOnWriteArrayList incomingMessageQueue = new CopyOnWriteArrayList();
+
+ /**
+ * The queue of outgoing messages that have yet to be sent
+ */
CopyOnWriteArrayList outgoingMessageQueue = new CopyOnWriteArrayList();
- CircularByteBuffer incomingByteBuffer = new CircularByteBuffer(64 * 1024 * 124);
+ /**
+ * The byte buffer for storing incoming bytes
+ */
+ CircularByteBuffer incomingByteBuffer = new CircularByteBuffer(CIRCULAR_BUFFER_SIZE);
+
+ /**
+ * The block array used to read blocks of bytes in
+ */
+ byte[] readBuffer = new byte[READ_BLOCK_SIZE];
+
+ /**
+ * The outgoing byte buffer
+ */
CopyOnWriteArrayList outgoingByteQueue = new CopyOnWriteArrayList();
+
+ /**
+ * The number of bytes read
+ */
+ long totalBytesRead = 0;
-
+ /**
+ * Constructor
+ * @param incomingStream The stream of incoming bytes
+ * @param outgoingStream The stream of outgoing bytes
+ */
public NetworkParser(InputStream incomingStream, OutputStream outgoingStream){
this.incomingStream = incomingStream;
this.outgoingStream = outgoingStream;
}
-
- public void start(){
-
- }
-
- static final int READ_BUFFER_SIZE = 64 * 1024 * 1024;
- byte[] readBuffer = new byte[READ_BUFFER_SIZE];
- public void readMessagesIn(){
- try {
- //read in bytes
- int bytesRead = 0;
- byte currentByte = -1;
- while(incomingStream.available() > 0){
- // nextValue = incomingStream.read();
- bytesRead = incomingStream.read(readBuffer, 0, READ_BUFFER_SIZE);
- if(bytesRead > 0){
- incomingByteBuffer.add(readBuffer, bytesRead);
- }
+
+ /**
+ * Reads messages from the input stream
+ */
+ public void readMessagesIn() throws IOException {
+ //read in bytes
+ int bytesRead = 0;
+ while(incomingStream.available() > 0){
+ // nextValue = incomingStream.read();
+ bytesRead = incomingStream.read(readBuffer, 0, READ_BLOCK_SIZE);
+ if(bytesRead > 0){
+ incomingByteBuffer.add(readBuffer, bytesRead);
}
- //parse byte queue for messages
- //for each message, append to clientIncomingMessageQueue
- NetworkMessage newMessage;
- while((newMessage = NetworkMessage.parseBytestreamForMessage(incomingByteBuffer))!=null){
- incomingMessageQueue.add(newMessage);
- }
- } catch (IOException ex) {
- ex.printStackTrace();
- System.exit(0);
+ totalBytesRead = totalBytesRead + bytesRead;
+ }
+ //parse byte queue for messages
+ //for each message, append to clientIncomingMessageQueue
+ NetworkMessage newMessage;
+ while((newMessage = NetworkMessage.parseBytestreamForMessage(incomingByteBuffer))!=null){
+ incomingMessageQueue.add(newMessage);
}
}
+ /**
+ * Pushes messages out across the output stream
+ * @throws IOException Thrown if a message fails to serialize or the output stream fails to write
+ */
public void pushMessagesOut() throws IOException {
for(NetworkMessage message : outgoingMessageQueue){
outgoingMessageQueue.remove(message);
-// System.out.println("Write message of type " + message.getType());
outgoingStream.write(message.getRawBytes());
}
}
+ /**
+ * Checks if there is a fully parsed incoming message in the queue
+ * @return true if there is message in the queue, false otherwise
+ */
public boolean hasIncomingMessaage(){
return incomingMessageQueue.size() > 0;
}
+ /**
+ * Pops a fully parsed incoming message from the queue
+ * @return The message
+ */
public NetworkMessage popIncomingMessage(){
return incomingMessageQueue.remove(0);
}
+ /**
+ * Adds a message to the outgoing queue
+ * @param message The message
+ */
public void addOutgoingMessage(NetworkMessage message){
outgoingMessageQueue.add(message);
}
@@ -90,5 +146,13 @@ public class NetworkParser {
public void copyOutgoingMessages(List messages){
messages.addAll(outgoingMessageQueue);
}
+
+ /**
+ * Gets the total number of bytes read by this connection
+ * @return The total number of bytes
+ */
+ public long getNumberOfBytesRead(){
+ return totalBytesRead;
+ }
}
diff --git a/src/main/java/electrosphere/net/parser/util/ByteStreamUtils.java b/src/main/java/electrosphere/net/parser/util/ByteStreamUtils.java
index f1c036cc..edfb9912 100644
--- a/src/main/java/electrosphere/net/parser/util/ByteStreamUtils.java
+++ b/src/main/java/electrosphere/net/parser/util/ByteStreamUtils.java
@@ -1,6 +1,6 @@
package electrosphere.net.parser.util;
-import electrosphere.net.parser.net.raw.CircularByteBuffer;
+import io.github.studiorailgun.CircularByteBuffer;
import java.nio.ByteBuffer;
import java.util.List;
diff --git a/src/main/java/electrosphere/net/server/MessageProtocol.java b/src/main/java/electrosphere/net/server/MessageProtocol.java
index 7f506bab..2cef56d5 100644
--- a/src/main/java/electrosphere/net/server/MessageProtocol.java
+++ b/src/main/java/electrosphere/net/server/MessageProtocol.java
@@ -67,7 +67,7 @@ public class MessageProtocol {
* @param message The message
*/
public void handleAsyncMessage(NetworkMessage message){
- Globals.profiler.beginAggregateCpuSample("MessageProtocol(client).handleAsyncMessage");
+ Globals.profiler.beginAggregateCpuSample("MessageProtocol(server).handleAsyncMessage");
printMessage(message);
NetworkMessage result = null;
switch(message.getType()){
@@ -113,7 +113,7 @@ public class MessageProtocol {
}
public void handleSyncMessages(){
- Globals.profiler.beginAggregateCpuSample("MessageProtocol(client).handleSyncMessages");
+ Globals.profiler.beginAggregateCpuSample("MessageProtocol(server).handleSyncMessages");
this.synchronousMessageLock.acquireUninterruptibly();
LoggerInterface.loggerNetworking.DEBUG_LOOP("[SERVER] HANDLE SYNC MESSAGE [Sync queue size: " + this.synchronousMessageQueue.size() + "]");
for(NetworkMessage message : synchronousMessageQueue){
diff --git a/src/main/java/electrosphere/net/server/Server.java b/src/main/java/electrosphere/net/server/Server.java
index 0458673e..fb4228b6 100644
--- a/src/main/java/electrosphere/net/server/Server.java
+++ b/src/main/java/electrosphere/net/server/Server.java
@@ -170,8 +170,11 @@ public class Server implements Runnable {
* @return The first connection
*/
public ServerConnectionHandler getFirstConnection(){
+ ServerConnectionHandler firstCon = null;
connectListLock.acquireUninterruptibly();
- ServerConnectionHandler firstCon = this.activeConnections.get(0);
+ if(this.activeConnections.size() > 0){
+ firstCon = this.activeConnections.get(0);
+ }
connectListLock.release();
return firstCon;
}
diff --git a/src/main/java/electrosphere/net/server/ServerConnectionHandler.java b/src/main/java/electrosphere/net/server/ServerConnectionHandler.java
index f4d6ea82..dfea176c 100644
--- a/src/main/java/electrosphere/net/server/ServerConnectionHandler.java
+++ b/src/main/java/electrosphere/net/server/ServerConnectionHandler.java
@@ -8,6 +8,7 @@ import electrosphere.net.parser.net.message.NetworkMessage;
import electrosphere.net.parser.net.message.ServerMessage;
import electrosphere.net.parser.net.raw.NetworkParser;
import electrosphere.net.server.player.Player;
+import electrosphere.util.CodeUtils;
import java.io.IOException;
import java.io.InputStream;
@@ -69,7 +70,7 @@ public class ServerConnectionHandler implements Runnable {
//thresholds for determining when to send pings and when a client has disconnected
static final long SEND_PING_THRESHOLD = 3000;
- static final long PING_DISCONNECT_THRESHOLD = 20000;
+ static final long PING_DISCONNECT_THRESHOLD = 60 * 1000;
//used to keep track of ping/pong messages with client
long lastPingTime = 0;
long lastPongTime = 0;
@@ -191,18 +192,19 @@ public class ServerConnectionHandler implements Runnable {
initialized = true;
while(Globals.threadManager.shouldKeepRunning() && this.isConnected == true && Globals.server != null && Globals.server.isOpen()){
+ boolean receivedMessageThisLoop = false;
//
// Main Loop
//
//parse messages both incoming and outgoing
try {
- parseMessages();
+ receivedMessageThisLoop = parseMessages();
} catch (SocketException e) {
//if we get a SocketException broken pipe (basically the client dc'd without telling us)
//set flag to disconnect client
//TODO: fix, this doesn't actually catch the socket exception which is exceedingly obnoxious
socketException = true;
- LoggerInterface.loggerNetworking.DEBUG(e.getLocalizedMessage());
+ LoggerInterface.loggerNetworking.ERROR("Client disconnected", e);
this.disconnect();
break;
} catch (IOException e){
@@ -210,14 +212,18 @@ public class ServerConnectionHandler implements Runnable {
//set flag to disconnect client
//TODO: fix, this doesn't actually catch the socket exception which is exceedingly obnoxious
socketException = true;
- LoggerInterface.loggerNetworking.DEBUG(e.getLocalizedMessage());
+ LoggerInterface.loggerNetworking.ERROR("Client disconnected", e);
this.disconnect();
break;
}
//
- // Pings
+ // Timeout logic
//
+ //mark as alive if a message was received from client
+ if(receivedMessageThisLoop){
+ this.markReceivedPongMessage();
+ }
//ping logic
long currentTime = System.currentTimeMillis();
//basically if we haven't sent a ping in a while, send one
@@ -235,7 +241,14 @@ public class ServerConnectionHandler implements Runnable {
//check if we meet disconnection criteria
//has it been too long since the last ping?
//have we had a socket exception?
- if(lastPingTime - lastPongTime > PING_DISCONNECT_THRESHOLD || this.socketException == true){
+ if(lastPingTime - lastPongTime > PING_DISCONNECT_THRESHOLD){
+ //disconnected from the server
+ LoggerInterface.loggerNetworking.WARNING("Client timeout");
+ //run disconnect routine
+ disconnect();
+ break;
+ }
+ if(this.socketException == true){
//disconnected from the server
LoggerInterface.loggerNetworking.WARNING("Client disconnected");
//run disconnect routine
@@ -251,13 +264,16 @@ public class ServerConnectionHandler implements Runnable {
* Had to wrap the message parsing block in a function to throw a SocketException
* without my linter freaking out
* @throws SocketException
+ * @return true if connection is alive, false otherwise
*/
- void parseMessages() throws SocketException, IOException {
+ boolean parseMessages() throws SocketException, IOException {
+ boolean rVal = false;
//
//Read in messages
//
//attempt poll incoming messages
networkParser.readMessagesIn();
+ rVal = networkParser.hasIncomingMessaage();
//
@@ -305,8 +321,9 @@ public class ServerConnectionHandler implements Runnable {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException ex) {
//silently ignore
- // CodeUtils.todo(ex, "Handle sleep interrupt on server connection");
+ CodeUtils.todo(ex, "Handle sleep interrupt on server connection");
}
+ return rVal;
}
/**
@@ -404,4 +421,15 @@ public class ServerConnectionHandler implements Runnable {
}
}
+ /**
+ * Gets the total number of bytes read by this connection
+ * @return The total number of bytes
+ */
+ public long getNumBytesRead(){
+ if(this.networkParser == null){
+ return 0;
+ }
+ return this.networkParser.getNumberOfBytesRead();
+ }
+
}
diff --git a/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java b/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java
index 61fd39dc..99b1d4f8 100644
--- a/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java
+++ b/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java
@@ -3,10 +3,12 @@ package electrosphere.net.server.protocol;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
+import java.util.function.Consumer;
import org.joml.Vector3d;
import electrosphere.engine.Globals;
+import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.net.server.ServerConnectionHandler;
import electrosphere.net.server.player.Player;
@@ -23,20 +25,32 @@ public class TerrainProtocol implements ServerProtocolTemplate {
@Override
public TerrainMessage handleAsyncMessage(ServerConnectionHandler connectionHandler, TerrainMessage message) {
+ switch(message.getMessageSubtype()){
+ case REQUESTCHUNKDATA: {
+ sendWorldSubChunkAsync(connectionHandler,
+ message.getworldX(), message.getworldY(), message.getworldZ()
+ );
+ return null;
+ }
+ default: {
+ } break;
+ }
return message;
}
@Override
public void handleSyncMessage(ServerConnectionHandler connectionHandler, TerrainMessage message) {
switch(message.getMessageSubtype()){
- case REQUESTMETADATA:
+ case REQUESTMETADATA: {
sendWorldMetadata(connectionHandler);
- break;
- case REQUESTCHUNKDATA:
+ } break;
+ case REQUESTCHUNKDATA: {
+ LoggerInterface.loggerNetworking.DEBUG("(Server) Received request for terrain " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ());
+ // System.out.println("Received request for terrain " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ());
sendWorldSubChunk(connectionHandler,
message.getworldX(), message.getworldY(), message.getworldZ()
);
- break;
+ } break;
case REQUESTEDITVOXEL: {
attemptTerrainEdit(connectionHandler, message);
} break;
@@ -44,6 +58,8 @@ public class TerrainProtocol implements ServerProtocolTemplate {
attemptUseTerrainEditPalette(connectionHandler, message);
} break;
case REQUESTFLUIDDATA: {
+ LoggerInterface.loggerNetworking.DEBUG("(Server) Received request for fluid " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ());
+ // System.out.println("Received request for fluid " + message.getworldX() + " " + message.getworldY() + " " + message.getworldZ());
sendWorldFluidSubChunk(connectionHandler,
message.getworldX(), message.getworldY(), message.getworldZ()
);
@@ -70,28 +86,7 @@ public class TerrainProtocol implements ServerProtocolTemplate {
* @param worldZ the world z
*/
static void sendWorldSubChunk(ServerConnectionHandler connectionHandler, int worldX, int worldY, int worldZ){
- /*
- int locationX,
- int locationY,
- float macroValue00,
- float macroValue01,
- float macroValue02,
- float macroValue10,
- float macroValue11,
- float macroValue12,
- float macroValue20,
- float macroValue21,
- float macroValue22,
- long randomizerValue00,
- long randomizerValue01,
- long randomizerValue02,
- long randomizerValue10,
- long randomizerValue11,
- long randomizerValue12,
- long randomizerValue20,
- long randomizerValue21,
- long randomizerValue22
- */
+ Globals.profiler.beginAggregateCpuSample("TerrainProtocol(server).sendWorldSubChunk");
// System.out.println("Received request for chunk " + message.getworldX() + " " + message.getworldY());
Realm realm = Globals.playerManager.getPlayerRealm(connectionHandler.getPlayer());
@@ -99,12 +94,8 @@ public class TerrainProtocol implements ServerProtocolTemplate {
return;
}
- //get the chunk
+ //request chunk
ServerTerrainChunk chunk = realm.getServerWorldData().getServerTerrainManager().getChunk(worldX, worldY, worldZ);
-
- // float[][] macroValues = chunk.getMacroValues();//Globals.serverTerrainManager.getRad5MacroValues(message.getworldX(), message.getworldY());
-
- // long[][] randomizer = chunk.getRandomizer();//Globals.serverTerrainManager.getRandomizer(message.getworldX(), message.getworldY());
//The length along each access of the chunk data. Typically, should be at least 17.
//Because CHUNK_SIZE is 16, 17 adds the necessary extra value. Each chunk needs the value of the immediately following position to generate
@@ -135,89 +126,68 @@ public class TerrainProtocol implements ServerProtocolTemplate {
}
}
-
+ System.out.println("(Server) Send terrain at " + worldX + " " + worldY + " " + worldZ);
+ LoggerInterface.loggerNetworking.DEBUG("(Server) Send terrain at " + worldX + " " + worldY + " " + worldZ);
connectionHandler.addMessagetoOutgoingQueue(TerrainMessage.constructsendChunkDataMessage(worldX, worldY, worldZ, buffer.array()));
- // int numMessages = 2 + chunk.getModifications().size();
+ Globals.profiler.endCpuSample();
+ }
- // connectionHandler.addMessagetoOutgoingQueue(
- // TerrainMessage.constructchunkLoadStartMessage(worldX, worldY, numMessages)
- // );
+ /**
+ * Sends a subchunk to the client
+ * @param connectionHandler The connection handler
+ * @param worldX the world x
+ * @param worldY the world y
+ * @param worldZ the world z
+ */
+ static void sendWorldSubChunkAsync(ServerConnectionHandler connectionHandler, int worldX, int worldY, int worldZ){
+ Globals.profiler.beginAggregateCpuSample("TerrainProtocol(server).sendWorldSubChunk");
+
+ // System.out.println("Received request for chunk " + message.getworldX() + " " + message.getworldY());
+ Realm realm = Globals.playerManager.getPlayerRealm(connectionHandler.getPlayer());
+ if(realm.getServerWorldData().getServerTerrainManager() == null){
+ return;
+ }
- // connectionHandler.addMessagetoOutgoingQueue(
- // TerrainMessage.constructMacroValueMessage(
- // worldX,
- // worldY,
-
-
- // macroValues[0][0],
- // macroValues[0][1],
- // macroValues[0][2],
- // macroValues[0][3],
- // macroValues[0][4],
- // macroValues[1][0],
- // macroValues[1][1],
- // macroValues[1][2],
- // macroValues[1][3],
- // macroValues[1][4],
- // macroValues[2][0],
- // macroValues[2][1],
- // macroValues[2][2],
- // macroValues[2][3],
- // macroValues[2][4],
- // macroValues[3][0],
- // macroValues[3][1],
- // macroValues[3][2],
- // macroValues[3][3],
- // macroValues[3][4],
- // macroValues[4][0],
- // macroValues[4][1],
- // macroValues[4][2],
- // macroValues[4][3],
- // macroValues[4][4],
-
-
- // randomizer[0][0],
- // randomizer[0][1],
- // randomizer[0][2],
- // randomizer[0][3],
- // randomizer[0][4],
- // randomizer[1][0],
- // randomizer[1][1],
- // randomizer[1][2],
- // randomizer[1][3],
- // randomizer[1][4],
- // randomizer[2][0],
- // randomizer[2][1],
- // randomizer[2][2],
- // randomizer[2][3],
- // randomizer[2][4],
- // randomizer[3][0],
- // randomizer[3][1],
- // randomizer[3][2],
- // randomizer[3][3],
- // randomizer[3][4],
- // randomizer[4][0],
- // randomizer[4][1],
- // randomizer[4][2],
- // randomizer[4][3],
- // randomizer[4][4]
- // )
- // );
+ Consumer onLoad = (ServerTerrainChunk chunk) -> {
+ //The length along each access of the chunk data. Typically, should be at least 17.
+ //Because CHUNK_SIZE is 16, 17 adds the necessary extra value. Each chunk needs the value of the immediately following position to generate
+ //chunk data that connects seamlessly to the next chunk.
+ int xWidth = chunk.getWeights().length;
+ int yWidth = chunk.getWeights()[0].length;
+ int zWidth = chunk.getWeights()[0][0].length;
- // for(TerrainModification modification : chunk.getModifications()){
- // connectionHandler.addMessagetoOutgoingQueue(
- // TerrainMessage.constructheightMapModificationMessage(
- // modification.getValue(),
- // modification.getWorldX(),
- // 0,
- // modification.getWorldY(),
- // modification.getLocationX(),
- // 0,
- // modification.getLocationY()
- // )
- // );
- // }
+ ByteBuffer buffer = ByteBuffer.allocate(xWidth*yWidth*zWidth*(4+4));
+ FloatBuffer floatView = buffer.asFloatBuffer();
+
+ for(int x = 0; x < xWidth; x++){
+ for(int y = 0; y < yWidth; y++){
+ for(int z = 0; z < zWidth; z++){
+ floatView.put(chunk.getWeights()[x][y][z]);
+ }
+ }
+ }
+
+ IntBuffer intView = buffer.asIntBuffer();
+ intView.position(floatView.position());
+
+ for(int x = 0; x < xWidth; x++){
+ for(int y = 0; y < yWidth; y++){
+ for(int z = 0; z < zWidth; z++){
+ intView.put(chunk.getValues()[x][y][z]);
+ }
+ }
+ }
+
+ // System.out.println("(Server) Send terrain at " + worldX + " " + worldY + " " + worldZ);
+ LoggerInterface.loggerNetworking.DEBUG("(Server) Send terrain at " + worldX + " " + worldY + " " + worldZ);
+ connectionHandler.addMessagetoOutgoingQueue(TerrainMessage.constructsendChunkDataMessage(worldX, worldY, worldZ, buffer.array()));
+ };
+
+ //request chunk
+ realm.getServerWorldData().getServerTerrainManager().getChunkAsync(worldX, worldY, worldZ, onLoad);
+
+ Globals.profiler.endCpuSample();
}
diff --git a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java
index 763008a5..880065ec 100644
--- a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java
+++ b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java
@@ -25,6 +25,11 @@ import electrosphere.server.terrain.manager.ServerTerrainChunk;
public class TerrainChunkModelGeneration {
+ /**
+ * The minimum iso value
+ */
+ public static final float MIN_ISO_VALUE = 0.01f;
+
//http://paulbourke.net/geometry/polygonise/
public static int edgeTable[]={
@@ -620,7 +625,7 @@ public class TerrainChunkModelGeneration {
return new Vector3f(x,y,z);
}
- public static TerrainChunkData generateTerrainChunkData(float[][][] terrainGrid, int[][][] textureGrid, int lod){
+ public static TerrainChunkData generateTerrainChunkData(float[][][] terrainGrid, int[][][] textureGrid){
// 5 6
// +-------------+ +-----5-------+ ^ Y
@@ -664,7 +669,7 @@ public class TerrainChunkModelGeneration {
textureGrid[x+0][y+1][z+0], textureGrid[x+0][y+1][z+1], textureGrid[x+1][y+1][z+1], textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
}
diff --git a/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java
index 606b8d46..9608f7f3 100644
--- a/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java
+++ b/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java
@@ -25,6 +25,11 @@ public class TransvoxelModelGeneration {
//the lower the value, the more of the low resolution chunk we will see
static final float TRANSITION_CELL_WIDTH = 0.5f;
+ /**
+ * The dimension of the array for the face generator. It must be 2 * + 1. The extra 1 is for the neighbor value
+ */
+ public static final int FACE_DATA_DIMENSIONS = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE + ServerTerrainChunk.CHUNK_DIMENSION;
+
@@ -647,7 +652,7 @@ public class TransvoxelModelGeneration {
if(firstSample > isolevel){
samplerIndex[i] = getTransvoxelTextureValue(transitionCell.simpleFaceAtlasValues,transitionCell.complexFaceAtlasValues,firstCornerSampleIndex);
} else {
- samplerIndex[i] = sampleIndexTable[1][i];
+ samplerIndex[i] = getTransvoxelTextureValue(transitionCell.simpleFaceAtlasValues,transitionCell.complexFaceAtlasValues,secondCornerSampleIndex);
}
}
}
@@ -982,8 +987,7 @@ public class TransvoxelModelGeneration {
/**
* Generates mesh data given chunk data
- * @param terrainGrid The chunk data
- * @param textureGrid The chunk texture data
+ * @param chunkData The chunk data
* @return The mesh data
*/
public static TerrainChunkData generateTerrainChunkData(TransvoxelChunkData chunkData){
@@ -1019,9 +1023,9 @@ public class TransvoxelModelGeneration {
List samplerTriangles = new LinkedList();
//List of UVs
List UVs = new LinkedList();
-
-
-
+
+
+
//
//Generate the interior of the mesh
for(int x = 1; x < chunkData.terrainGrid.length - 2; x++){
@@ -1037,7 +1041,7 @@ public class TransvoxelModelGeneration {
chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
}
@@ -1059,8 +1063,8 @@ public class TransvoxelModelGeneration {
//generate the x-positive face
if(chunkData.xPositiveEdgeIso != null){
int x = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2;
- for(int y = yStartIndex; y < yEndIndex - 1; y++){
- for(int z = zStartIndex; z < zEndIndex - 1; z++){
+ for(int y = yStartIndex; y < yEndIndex; y++){
+ for(int z = zStartIndex; z < zEndIndex; z++){
//
//Generate the transition cell
//
@@ -1087,7 +1091,7 @@ public class TransvoxelModelGeneration {
chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+0)*2+0],
chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+1)*2+0]
);
- polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
//
//Generate the normal cell with half width
@@ -1101,7 +1105,7 @@ public class TransvoxelModelGeneration {
chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+0)*2+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
} else {
@@ -1118,7 +1122,7 @@ public class TransvoxelModelGeneration {
chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
}
@@ -1133,8 +1137,8 @@ public class TransvoxelModelGeneration {
//generate the x-negative face
if(chunkData.xNegativeEdgeIso != null){
int x = 0;
- for(int y = yStartIndex; y < yEndIndex - 1; y++){
- for(int z = zStartIndex; z < zEndIndex - 1; z++){
+ for(int y = yStartIndex; y < yEndIndex; y++){
+ for(int z = zStartIndex; z < zEndIndex; z++){
//
//Generate the transition cell
//
@@ -1161,7 +1165,7 @@ public class TransvoxelModelGeneration {
chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0],
chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0]
);
- polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
//
//Generate the normal cell with half width
@@ -1175,7 +1179,7 @@ public class TransvoxelModelGeneration {
chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
} else {
@@ -1192,7 +1196,7 @@ public class TransvoxelModelGeneration {
chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
}
@@ -1206,8 +1210,8 @@ public class TransvoxelModelGeneration {
//generate the y-positive face
if(chunkData.yPositiveEdgeIso != null){
int y = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2;
- for(int x = xStartIndex; x < xEndIndex - 1; x++){
- for(int z = zStartIndex; z < zEndIndex - 1; z++){
+ for(int x = xStartIndex; x < xEndIndex; x++){
+ for(int z = zStartIndex; z < zEndIndex; z++){
//
//Generate the transition cell
//
@@ -1234,7 +1238,7 @@ public class TransvoxelModelGeneration {
chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+0)*2+0],
chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+1)*2+0]
);
- polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
//
//Generate the normal cell with half width
@@ -1248,7 +1252,7 @@ public class TransvoxelModelGeneration {
chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+0)*2+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
} else {
@@ -1265,7 +1269,7 @@ public class TransvoxelModelGeneration {
chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
}
@@ -1279,8 +1283,8 @@ public class TransvoxelModelGeneration {
//generate the y-negative face
if(chunkData.yNegativeEdgeIso != null){
int y = 0;
- for(int x = xStartIndex; x < xEndIndex - 1; x++){
- for(int z = zStartIndex; z < zEndIndex - 1; z++){
+ for(int x = xStartIndex; x < xEndIndex; x++){
+ for(int z = zStartIndex; z < zEndIndex; z++){
//
//Generate the transition cell
//
@@ -1307,7 +1311,7 @@ public class TransvoxelModelGeneration {
chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0],
chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0]
);
- polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
//
//Generate the normal cell with half width
@@ -1315,13 +1319,13 @@ public class TransvoxelModelGeneration {
currentCell.setValues(
new Vector3f(x+0,y+1,z+0), new Vector3f(x+0,y+1,z+1), new Vector3f(x+1,y+1,z+1), new Vector3f(x+1,y+1,z+0),
new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z+0), new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+0),
- chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0],
+ chunkData.terrainGrid[x+0][y+1][z+0], chunkData.terrainGrid[x+0][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0],
chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+0)*2+0],
- chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0],
+ chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0],
chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
} else {
@@ -1338,7 +1342,7 @@ public class TransvoxelModelGeneration {
chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
}
@@ -1353,8 +1357,8 @@ public class TransvoxelModelGeneration {
//generate the z-positive face
if(chunkData.zPositiveEdgeIso != null){
int z = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2;
- for(int x = xStartIndex; x < xEndIndex - 1; x++){
- for(int y = yStartIndex; y < yEndIndex - 1; y++){
+ for(int x = xStartIndex; x < xEndIndex; x++){
+ for(int y = yStartIndex; y < yEndIndex; y++){
//
//Generate the transition cell
//
@@ -1381,7 +1385,7 @@ public class TransvoxelModelGeneration {
chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+1)*2+0],
chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+1)*2+0]
);
- polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
//
//Generate the normal cell with half width
@@ -1395,7 +1399,7 @@ public class TransvoxelModelGeneration {
chunkData.textureGrid[x+0][y+1][z+0], chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+1)*2+0], chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+1)*2+0], chunkData.textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
} else {
@@ -1412,7 +1416,7 @@ public class TransvoxelModelGeneration {
chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
}
@@ -1427,16 +1431,16 @@ public class TransvoxelModelGeneration {
//generate the z-negative face
if(chunkData.zNegativeEdgeIso != null){
int z = 0;
- for(int x = xStartIndex; x < xEndIndex - 1; x++){
- for(int y = yStartIndex; y < yEndIndex - 1; y++){
+ for(int x = xStartIndex; x < xEndIndex; x++){
+ for(int y = yStartIndex; y < yEndIndex; y++){
//
//Generate the transition cell
//
currentTransitionCell.setValues(
//complex face vertex coordinates
- new Vector3f(x+0,y,z), new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+0,y+1,z),
+ new Vector3f(x+0, y,z), new Vector3f(x+0, y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+0, y+1,z),
new Vector3f(x+TRANSITION_CELL_WIDTH,y,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z),
- new Vector3f(x+1,y,z), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+1,y+1,z),
+ new Vector3f(x+1, y,z), new Vector3f(x+1, y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+1, y+1,z),
//simple face vertex coordinates
new Vector3f(x+0,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+0,y+1,z+TRANSITION_CELL_WIDTH),
new Vector3f(x+1,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH),
@@ -1455,7 +1459,7 @@ public class TransvoxelModelGeneration {
chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0],
chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0]
);
- polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
//
//Generate the normal cell with half width
@@ -1463,13 +1467,13 @@ public class TransvoxelModelGeneration {
currentCell.setValues(
new Vector3f(x+0,y+0,z+1), new Vector3f(x+0,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+0,z+1),
new Vector3f(x+0,y+1,z+1), new Vector3f(x+0,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+1),
- chunkData.terrainGrid[x+0][y+0][z+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.terrainGrid[x+1][y+0][z+0],
- chunkData.terrainGrid[x+0][y+1][z+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0], chunkData.terrainGrid[x+1][y+1][z+0],
- chunkData.textureGrid[x+0][y+0][z+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.textureGrid[x+1][y+0][z+0],
- chunkData.textureGrid[x+0][y+1][z+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0], chunkData.textureGrid[x+1][y+1][z+0]
+ chunkData.terrainGrid[x+0][y+0][z+1], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.terrainGrid[x+1][y+0][z+1],
+ chunkData.terrainGrid[x+0][y+1][z+1], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0], chunkData.terrainGrid[x+1][y+1][z+1],
+ chunkData.textureGrid[x+0][y+0][z+1], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.textureGrid[x+1][y+0][z+1],
+ chunkData.textureGrid[x+0][y+1][z+1], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0], chunkData.textureGrid[x+1][y+1][z+1]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
} else {
@@ -1486,11 +1490,92 @@ public class TransvoxelModelGeneration {
chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
- polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
}
}
}
+
+ //xn-zn edge
+ if(chunkData.xNegativeEdgeIso != null && chunkData.zNegativeEdgeIso != null){
+ int x = 0;
+ int z = 0;
+ int edgeLength = chunkWidth - (chunkData.yNegativeEdgeIso != null ? 1 : 0) - (chunkData.yPositiveEdgeIso != null ? 1 : 0);
+ int startIndex = 0 + (chunkData.yNegativeEdgeIso != null ? 1 : 0);
+ for(int i = startIndex; i < edgeLength - 1; i++){
+ int y = i;
+ //
+ //Generate the x-side transition cell
+ //
+ currentTransitionCell.setValues(
+ //complex face vertex coordinates
+ new Vector3f(x,y,z), new Vector3f(x,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x,y+1,z),
+ new Vector3f(x,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x,y+TRANSITION_CELL_WIDTH,z+TRANSITION_CELL_WIDTH), new Vector3f(x,y+1,z+TRANSITION_CELL_WIDTH),
+ new Vector3f(x,y,z+1), new Vector3f(x,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x,y+1,z+1),
+ //simple face vertex coordinates
+ new Vector3f(x+TRANSITION_CELL_WIDTH,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+TRANSITION_CELL_WIDTH),
+ new Vector3f(x+TRANSITION_CELL_WIDTH,y,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+1),
+ //complex face iso values
+ chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+0)*2+1][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+0],
+ chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+1], chunkData.xNegativeEdgeIso[(y+0)*2+1][(z+0)*2+1], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+1],
+ chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeIso[(y+0)*2+1][(z+1)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+1)*2+0],
+ //simple face iso values
+ chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+0],
+ chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+1)*2+0],
+ //complex face texture atlas values
+ chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+0)*2+1][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0],
+ chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+1], chunkData.xNegativeEdgeAtlas[(y+0)*2+1][(z+0)*2+1], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+1],
+ chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+0)*2+1][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0],
+ //simple face texture atlas values
+ chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0],
+ chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0]
+ );
+ polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+
+ //
+ //Generate the z-side transition cell
+ //
+ currentTransitionCell.setValues(
+ //complex face vertex coordinates
+ new Vector3f(x+0, y,z), new Vector3f(x+0, y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+0, y+1,z),
+ new Vector3f(x+TRANSITION_CELL_WIDTH,y,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z),
+ new Vector3f(x+1, y,z), new Vector3f(x+1, y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+1, y+1,z),
+ //simple face vertex coordinates
+ new Vector3f(x+TRANSITION_CELL_WIDTH,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+TRANSITION_CELL_WIDTH),
+ new Vector3f(x+1,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH),
+ //complex face iso values
+ chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0],
+ chunkData.zNegativeEdgeIso[(x+0)*2+1][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+0)*2+1][(y+0)*2+1], chunkData.zNegativeEdgeIso[(x+0)*2+1][(y+1)*2+0],
+ chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0],
+ //simple face iso values
+ chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0],
+ chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0],
+ //complex face texture atlas values
+ chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0],
+ chunkData.zNegativeEdgeAtlas[(x+0)*2+1][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+1][(y+0)*2+1], chunkData.zNegativeEdgeAtlas[(x+0)*2+1][(y+1)*2+0],
+ chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0],
+ //simple face texture atlas values
+ chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0],
+ chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0]
+ );
+ polygonizeTransition(currentTransitionCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+
+ //
+ //Generate the normal cell with half width
+ //
+ currentCell.setValues(
+ new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z+1), new Vector3f(x+1,y+0,z+1), new Vector3f(x+1,y+0,z+TRANSITION_CELL_WIDTH),
+ new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+1), new Vector3f(x+1,y+1,z+1), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH),
+ chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0],
+ chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+1)*2+0], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0],
+ chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0],
+ chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0]
+ );
+ //polygonize the current gridcell
+ polygonize(currentCell, TerrainChunkModelGeneration.MIN_ISO_VALUE, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert);
+ }
+ }
+
diff --git a/src/main/java/electrosphere/renderer/ui/imgui/ImGuiLinePlot.java b/src/main/java/electrosphere/renderer/ui/imgui/ImGuiLinePlot.java
index 74adff72..3a9cc95b 100644
--- a/src/main/java/electrosphere/renderer/ui/imgui/ImGuiLinePlot.java
+++ b/src/main/java/electrosphere/renderer/ui/imgui/ImGuiLinePlot.java
@@ -18,6 +18,11 @@ public class ImGuiLinePlot implements ImGuiElement {
//the data sets to draw
List dataSets = new LinkedList();
+ /**
+ * Size of the plot
+ */
+ ImVec2 size = new ImVec2(-1,-1);
+
/**
* Creates an im gui line plot
*/
@@ -25,9 +30,18 @@ public class ImGuiLinePlot implements ImGuiElement {
this.plotTitle = plotTitle;
}
+ /**
+ * Creates an im gui line plot
+ */
+ public ImGuiLinePlot(String plotTitle, int sizeX, int sizeY){
+ this.plotTitle = plotTitle;
+ this.size.x = sizeX;
+ this.size.y = sizeY;
+ }
+
@Override
public void draw() {
- if(ImPlot.beginPlot(plotTitle,"","",new ImVec2(-1,-1),0,ImPlotAxisFlags.AutoFit,ImPlotAxisFlags.AutoFit)){
+ if(ImPlot.beginPlot(plotTitle,"","",size,0,ImPlotAxisFlags.AutoFit,ImPlotAxisFlags.AutoFit)){
for(ImGuiLinePlotDataset dataSet : dataSets){
double[] xs = dataSet.xData.stream().mapToDouble(Double::doubleValue).toArray();//(Double[])dataSet.xData.toArray(new Double[dataSet.xData.size()]);
double[] ys = dataSet.yData.stream().mapToDouble(Double::doubleValue).toArray();//(Double[])dataSet.yData.toArray(new Double[dataSet.yData.size()]);
@@ -102,6 +116,15 @@ public class ImGuiLinePlot implements ImGuiElement {
}
}
+ /**
+ * Zeroes out the dataset
+ */
+ public void zeroOut(){
+ for(int i = 0; i < limit; i++){
+ this.addPoint(i, 0);
+ }
+ }
+
}
diff --git a/src/main/java/electrosphere/server/MainServerFunctions.java b/src/main/java/electrosphere/server/MainServerFunctions.java
index ba931dea..3c4f665e 100644
--- a/src/main/java/electrosphere/server/MainServerFunctions.java
+++ b/src/main/java/electrosphere/server/MainServerFunctions.java
@@ -25,6 +25,8 @@ public class MainServerFunctions {
if(Globals.server != null){
Globals.server.synchronousPacketHandling();
}
+ Globals.profiler.endCpuSample();
+ Globals.profiler.beginCpuSample("Server process synchronization messages");
if(Globals.serverSynchronizationManager != null){
Globals.serverSynchronizationManager.processMessages();
}
diff --git a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java
index 93138b0f..81bfdb28 100644
--- a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java
+++ b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java
@@ -44,7 +44,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
/**
* The max grid size allowed
*/
- public static final int MAX_GRID_SIZE = 10;
+ public static final int MAX_GRID_SIZE = 2000 * 1024;
/**
* Tracks whether this manager has been flagged to unload cells or not
diff --git a/src/main/java/electrosphere/server/datacell/physics/DataCellPhysicsManager.java b/src/main/java/electrosphere/server/datacell/physics/DataCellPhysicsManager.java
index c05c984c..752433f2 100644
--- a/src/main/java/electrosphere/server/datacell/physics/DataCellPhysicsManager.java
+++ b/src/main/java/electrosphere/server/datacell/physics/DataCellPhysicsManager.java
@@ -3,6 +3,7 @@ package electrosphere.server.datacell.physics;
import electrosphere.engine.Globals;
import electrosphere.renderer.shader.ShaderProgram;
import electrosphere.server.datacell.Realm;
+import electrosphere.server.terrain.manager.ServerTerrainChunk;
import java.util.HashMap;
import java.util.HashSet;
@@ -65,7 +66,7 @@ public class DataCellPhysicsManager {
* @param discreteY The initial discrete position Y coordinate
*/
public DataCellPhysicsManager(Realm realm, int discreteX, int discreteY, int discreteZ){
- worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / Globals.clientWorldData.getDynamicInterpolationRatio() * 1.0f);
+ worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / ServerTerrainChunk.CHUNK_DIMENSION * 1.0f);
cells = new HashSet();
invalid = new HashSet();
updateable = new HashSet();
diff --git a/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java b/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java
index 1c83190e..037b8469 100644
--- a/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java
+++ b/src/main/java/electrosphere/server/terrain/diskmap/ChunkDiskMap.java
@@ -7,6 +7,7 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.Semaphore;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterOutputStream;
@@ -14,6 +15,7 @@ import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.util.FileUtils;
+import electrosphere.util.annotation.Exclude;
/**
* An interface for accessing the disk map of chunk information
@@ -23,6 +25,12 @@ public class ChunkDiskMap {
//The map of world position+chunk type to the file that actually houses that information
Map worldPosFileMap = new HashMap();
+ /**
+ * Locks the chunk disk map for thread safety
+ */
+ @Exclude
+ Semaphore lock = new Semaphore(1);
+
/**
* Constructor
*/
@@ -81,7 +89,10 @@ public class ChunkDiskMap {
* @return True if the map contains the chunk, false otherwise
*/
public boolean containsTerrainAtPosition(int worldX, int worldY, int worldZ){
- return worldPosFileMap.containsKey(getTerrainChunkKey(worldX, worldY, worldZ));
+ lock.acquireUninterruptibly();
+ boolean rVal = worldPosFileMap.containsKey(getTerrainChunkKey(worldX, worldY, worldZ));
+ lock.release();
+ return rVal;
}
/**
@@ -92,7 +103,10 @@ public class ChunkDiskMap {
* @return True if the map contains the chunk, false otherwise
*/
public boolean containsFluidAtPosition(int worldX, int worldY, int worldZ){
- return worldPosFileMap.containsKey(getFluidChunkKey(worldX, worldY, worldZ));
+ lock.acquireUninterruptibly();
+ boolean rVal = worldPosFileMap.containsKey(getFluidChunkKey(worldX, worldY, worldZ));
+ lock.release();
+ return rVal;
}
/**
@@ -103,6 +117,7 @@ public class ChunkDiskMap {
* @return The server terrain chunk if it exists, null otherwise
*/
public ServerTerrainChunk getTerrainChunk(int worldX, int worldY, int worldZ){
+ lock.acquireUninterruptibly();
LoggerInterface.loggerEngine.INFO("Load chunk " + worldX + " " + worldY + " " + worldZ);
ServerTerrainChunk rVal = null;
if(containsTerrainAtPosition(worldX, worldY, worldZ)){
@@ -148,6 +163,7 @@ public class ChunkDiskMap {
rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values);
}
}
+ lock.release();
return rVal;
}
@@ -156,6 +172,7 @@ public class ChunkDiskMap {
* @param terrainChunk The terrain chunk
*/
public void saveToDisk(ServerTerrainChunk terrainChunk){
+ lock.acquireUninterruptibly();
LoggerInterface.loggerEngine.DEBUG("Save to disk: " + terrainChunk.getWorldX() + " " + terrainChunk.getWorldY() + " " + terrainChunk.getWorldZ());
//get the file name for this chunk
String fileName = null;
@@ -202,6 +219,7 @@ public class ChunkDiskMap {
// TODO Auto-generated catch block
e.printStackTrace();
}
+ lock.release();
}
}
diff --git a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java
index 00773fff..669c6fb1 100644
--- a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java
+++ b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java
@@ -4,6 +4,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import electrosphere.engine.Globals;
import electrosphere.game.data.biome.BiomeData;
import electrosphere.game.data.biome.BiomeSurfaceGenerationParams;
import electrosphere.game.server.world.ServerWorldData;
@@ -65,6 +66,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
@Override
public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ) {
+ Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator.generateChunk");
ServerTerrainChunk rVal = null;
float[][][] weights;
int[][][] values;
@@ -95,19 +97,37 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
values = new int[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
//biome of the current chunk
- BiomeData biome = this.terrainModel.getSurfaceBiome(worldX, worldY, worldZ);
+ BiomeData surfaceBiome = this.terrainModel.getSurfaceBiome(worldX, worldY, worldZ);
+
+ BiomeSurfaceGenerationParams surfaceParams = surfaceBiome.getSurfaceGenerationParams();
+ HeightmapGenerator heightmapGen = this.tagGeneratorMap.get(surfaceParams.getSurfaceGenTag());
+ if(heightmapGen == null){
+ throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag());
+ }
+
+ //presolve heightfield
+ float[][] heightfield = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION];
+ for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
+ for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
+ heightfield[x][z] = heightmapGen.getHeight(this.terrainModel.getSeed(), this.serverWorldData.convertVoxelToRealSpace(x, worldX), this.serverWorldData.convertVoxelToRealSpace(z, worldZ));
+ }
+ }
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
+ Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator - Generate slice");
for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
- weights[x][y][z] = this.getChunkWeight(worldX, worldY, worldZ, x, y, z, this.terrainModel, biome);
- values[x][y][z] = this.getChunkValue(worldX, worldY, worldZ, x, y, z, this.terrainModel, biome);
+ GeneratedVoxel voxel = this.getVoxel(worldX, worldY, worldZ, x, y, z, heightfield, this.terrainModel, surfaceBiome);
+ weights[x][y][z] = voxel.weight;
+ values[x][y][z] = voxel.type;
}
}
+ Globals.profiler.endCpuSample();
}
}
rVal = new ServerTerrainChunk(worldX, worldY, worldZ, weights, values);
+ Globals.profiler.endCpuSample();
return rVal;
}
@@ -116,7 +136,6 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
this.terrainModel = model;
}
-
/**
* Gets the value for a chunk
* @param worldX The world x pos
@@ -125,71 +144,133 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
* @param chunkX The chunk x pos
* @param chunkY The chunk y pos
* @param chunkZ The chunk z pos
+ * @param heightfield The precomputed heightfield
* @param terrainModel The terrain model
* @param surfaceBiome The surface biome of the chunk
* @return The value of the chunk
*/
- private int getChunkValue(
+ private GeneratedVoxel getVoxel(
int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ,
+ float[][] heightfield,
TerrainModel terrainModel,
BiomeData surfaceBiome
){
+ Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator.getChunkValue");
BiomeSurfaceGenerationParams surfaceParams = surfaceBiome.getSurfaceGenerationParams();
HeightmapGenerator heightmapGen = this.tagGeneratorMap.get(surfaceParams.getSurfaceGenTag());
if(heightmapGen == null){
throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag());
}
- double realX = this.serverWorldData.convertVoxelToRealSpace(chunkX,worldX);
+ double realX = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldX);
double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY);
- double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ);
+ double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldZ);
- float surfaceHeight = heightmapGen.getHeight(terrainModel.getSeed(), realX, realZ);
- if(realY <= surfaceHeight){
- return 1;
+ float surfaceHeight = heightfield[chunkX][chunkZ];
+ double flooredSurfaceHeight = Math.floor(surfaceHeight);
+ Globals.profiler.endCpuSample();
+ if(realY < surfaceHeight - 1){
+ return getSubsurfaceVoxel(
+ worldX,worldY, worldZ,
+ chunkX, chunkY, chunkZ,
+ realX, realY, realZ,
+ surfaceHeight, flooredSurfaceHeight,
+ terrainModel,
+ surfaceBiome
+ );
+ } else if(realY > flooredSurfaceHeight) {
+ return getOverSurfaceVoxel(
+ worldX,worldY, worldZ,
+ chunkX, chunkY, chunkZ,
+ realX, realY, realZ,
+ surfaceHeight, flooredSurfaceHeight,
+ terrainModel,
+ surfaceBiome
+ );
} else {
- return 0;
+ return getSurfaceVoxel(
+ worldX,worldY, worldZ,
+ chunkX, chunkY, chunkZ,
+ realX, realY, realZ,
+ surfaceHeight, flooredSurfaceHeight,
+ terrainModel,
+ surfaceBiome
+ );
}
}
/**
- * Gets the weight for a chunk
- * @param worldX The world x pos
- * @param worldY The world y pos
- * @param worldZ The world z pos
- * @param chunkX The chunk x pos
- * @param chunkY The chunk y pos
- * @param chunkZ The chunk z pos
- * @param terrainModel The terrain model
- * @param surfaceBiome The surface biome of the chunk
- * @return The weight of the chunk
+ * Gets the voxel on the surface
+ * @return The voxel
*/
- private float getChunkWeight(
+ private GeneratedVoxel getSurfaceVoxel(
int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ,
+ double realX, double realY, double realZ,
+ float surfaceHeight, double flooredSurfaceHeight,
TerrainModel terrainModel,
BiomeData surfaceBiome
){
- BiomeSurfaceGenerationParams surfaceParams = surfaceBiome.getSurfaceGenerationParams();
- HeightmapGenerator heightmapGen = this.tagGeneratorMap.get(surfaceParams.getSurfaceGenTag());
- if(heightmapGen == null){
- throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag());
- }
+ GeneratedVoxel voxel = new GeneratedVoxel();
+ voxel.weight = (float)(surfaceHeight - flooredSurfaceHeight) * 2 - 1;
+ voxel.type = 1;
+ return voxel;
+ }
- double realX = this.serverWorldData.convertVoxelToRealSpace(chunkX,worldX);
- double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY);
- double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ);
-
- float surfaceHeight = heightmapGen.getHeight(terrainModel.getSeed(), realX, realZ);
- double flooredSurfaceHeight = Math.floor(surfaceHeight);
- if(realY < flooredSurfaceHeight){
- return 1;
- } else if(realY > flooredSurfaceHeight) {
- return -1;
+ /**
+ * Gets the voxel below the surface
+ * @return The voxel
+ */
+ private GeneratedVoxel getSubsurfaceVoxel(
+ int worldX, int worldY, int worldZ,
+ int chunkX, int chunkY, int chunkZ,
+ double realX, double realY, double realZ,
+ float surfaceHeight, double flooredSurfaceHeight,
+ TerrainModel terrainModel,
+ BiomeData surfaceBiome
+ ){
+ GeneratedVoxel voxel = new GeneratedVoxel();
+ if(realY < surfaceHeight - 5){
+ voxel.weight = 1;
+ voxel.type = 6;
} else {
- return (float)(surfaceHeight - flooredSurfaceHeight) * 2 - 1;
+ voxel.weight = 1;
+ voxel.type = 1;
}
+ return voxel;
+ }
+
+ /**
+ * Gets the voxel above the service
+ * @return The voxel
+ */
+ private GeneratedVoxel getOverSurfaceVoxel(
+ int worldX, int worldY, int worldZ,
+ int chunkX, int chunkY, int chunkZ,
+ double realX, double realY, double realZ,
+ float surfaceHeight, double flooredSurfaceHeight,
+ TerrainModel terrainModel,
+ BiomeData surfaceBiome
+ ){
+ GeneratedVoxel voxel = new GeneratedVoxel();
+ voxel.weight = -1;
+ voxel.type = 0;
+ return voxel;
+ }
+
+ /**
+ * A voxel that was generated
+ */
+ static class GeneratedVoxel {
+ /**
+ * The type of the voxel
+ */
+ int type;
+ /**
+ * The weight of the voxel
+ */
+ float weight;
}
}
diff --git a/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java b/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java
index 5522085f..f1cce43a 100644
--- a/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java
+++ b/src/main/java/electrosphere/server/terrain/generation/heightmap/HillsGen.java
@@ -1,5 +1,6 @@
package electrosphere.server.terrain.generation.heightmap;
+import electrosphere.engine.Globals;
import electrosphere.util.noise.OpenSimplex2S;
/**
@@ -48,9 +49,12 @@ public class HillsGen implements HeightmapGenerator {
* @return The height
*/
public float getHeight(long SEED, double x, double y){
+ Globals.profiler.beginAggregateCpuSample("HillsGen.getHeight");
double scaledX = x * POSITION_SCALE;
double scaledY = y * POSITION_SCALE;
- return gradientHeight(SEED, scaledX, scaledY) * VERTICAL_SCALE + HEIGHT_OFFSET;
+ float rVal = gradientHeight(SEED, scaledX, scaledY) * VERTICAL_SCALE + HEIGHT_OFFSET;
+ Globals.profiler.endCpuSample();
+ return rVal;
}
diff --git a/src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java b/src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java
new file mode 100644
index 00000000..b66556d3
--- /dev/null
+++ b/src/main/java/electrosphere/server/terrain/manager/ChunkGenerationThread.java
@@ -0,0 +1,123 @@
+package electrosphere.server.terrain.manager;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import electrosphere.engine.Globals;
+import electrosphere.server.terrain.diskmap.ChunkDiskMap;
+import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
+
+/**
+ * A job that fetches a chunk, either by generating it or by reading it from disk
+ */
+public class ChunkGenerationThread implements Runnable {
+
+ /**
+ * The number of milliseconds to wait per iteration
+ */
+ static final int WAIT_TIME_MS = 2;
+
+ /**
+ * The maximum number of iterations to wait before failing
+ */
+ static final int MAX_TIME_TO_WAIT = 10;
+
+ /**
+ * The chunk disk map
+ */
+ ChunkDiskMap chunkDiskMap;
+
+ /**
+ * The chunk cache on the server
+ */
+ ServerChunkCache chunkCache;
+
+ /**
+ * The chunk generator
+ */
+ ChunkGenerator chunkGenerator;
+
+ /**
+ * The world x coordinate
+ */
+ int worldX;
+
+ /**
+ * The world y coordinate
+ */
+ int worldY;
+
+ /**
+ * The world z coordinate
+ */
+ int worldZ;
+
+ /**
+ * The work to do once the chunk is available
+ */
+ Consumer onLoad;
+
+ /**
+ * Creates the chunk generation job
+ * @param chunkDiskMap The chunk disk map
+ * @param chunkCache The chunk cache on the server
+ * @param chunkGenerator The chunk generator
+ * @param worldX The world x coordinate
+ * @param worldY The world y coordinate
+ * @param worldZ The world z coordinate
+ * @param onLoad The work to do once the chunk is available
+ */
+ public ChunkGenerationThread(
+ ChunkDiskMap chunkDiskMap,
+ ServerChunkCache chunkCache,
+ ChunkGenerator chunkGenerator,
+ int worldX, int worldY, int worldZ,
+ Consumer onLoad
+ ){
+ this.chunkDiskMap = chunkDiskMap;
+ this.chunkCache = chunkCache;
+ this.chunkGenerator = chunkGenerator;
+ this.worldX = worldX;
+ this.worldY = worldY;
+ this.worldZ = worldZ;
+ this.onLoad = onLoad;
+ }
+
+ @Override
+ public void run() {
+ ServerTerrainChunk chunk = null;
+ int i = 0;
+ while(chunk == null && i < MAX_TIME_TO_WAIT && Globals.threadManager.shouldKeepRunning()){
+ if(chunkCache.containsChunk(worldX,worldY,worldZ)){
+ chunk = chunkCache.get(worldX,worldY,worldZ);
+ } else {
+ //pull from disk if it exists
+ if(chunkDiskMap != null){
+ if(chunkDiskMap.containsTerrainAtPosition(worldX, worldY, worldZ)){
+ chunk = chunkDiskMap.getTerrainChunk(worldX, worldY, worldZ);
+ }
+ }
+ //generate if it does not exist
+ if(chunk == null){
+ chunk = chunkGenerator.generateChunk(worldX, worldY, worldZ);
+ }
+ if(chunk != null){
+ chunkCache.add(worldX, worldY, worldZ, chunk);
+ }
+ }
+ if(chunk == null){
+ try {
+ TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ i++;
+ }
+ if(i >= MAX_TIME_TO_WAIT){
+ throw new Error("Failed to resolve chunk!");
+ }
+ this.onLoad.accept(chunk);
+ }
+
+}
diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java b/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java
new file mode 100644
index 00000000..3d8fbc9b
--- /dev/null
+++ b/src/main/java/electrosphere/server/terrain/manager/ServerChunkCache.java
@@ -0,0 +1,166 @@
+package electrosphere.server.terrain.manager;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+
+/**
+ * Caches chunk data on the server
+ */
+public class ServerChunkCache {
+
+ /**
+ * Number of chunks to cache
+ */
+ static final int CACHE_SIZE = 5000;
+
+ /**
+ * The size of the cache
+ */
+ int cacheSize = CACHE_SIZE;
+
+ /**
+ * The cached data
+ */
+ Map chunkCache = new HashMap();
+
+ /**
+ * Tracks how recently a chunk has been queries for (used for evicting old chunks from cache)
+ */
+ List queryRecencyQueue = new LinkedList();
+
+ /**
+ * Tracks what chunks are already queued to be asynchronously loaded. Used so we don't have two threads generating/fetching the same chunk
+ */
+ Map queuedChunkMap = new HashMap();
+
+ /**
+ * The lock for thread safety
+ */
+ Semaphore lock = new Semaphore(1);
+
+ /**
+ * Gets the collection of server terrain chunks that are cached
+ * @return The collection of chunks
+ */
+ public Collection getContents(){
+ lock.acquireUninterruptibly();
+ Collection rVal = Collections.unmodifiableCollection(chunkCache.values());
+ lock.release();
+ return rVal;
+ }
+
+ /**
+ * Evicts all chunks in the cache
+ */
+ public void clear(){
+ lock.acquireUninterruptibly();
+ chunkCache.clear();
+ lock.release();
+ }
+
+ /**
+ * Gets the chunk at a given world position
+ * @param worldX The world x coordinate
+ * @param worldY The world y coordinate
+ * @param worldZ The world z coordinate
+ * @return The chunk
+ */
+ public ServerTerrainChunk get(int worldX, int worldY, int worldZ){
+ ServerTerrainChunk rVal = null;
+ String key = this.getKey(worldX, worldY, worldZ);
+ lock.acquireUninterruptibly();
+ queryRecencyQueue.remove(key);
+ queryRecencyQueue.add(0, key);
+ rVal = this.chunkCache.get(key);
+ lock.release();
+ return rVal;
+ }
+
+ /**
+ * Adds a chunk to the cache
+ * @param worldX The world x coordinate of the chunk
+ * @param worldY The world y coordinate of the chunk
+ * @param worldZ The world z coordinate of the chunk
+ * @param chunk The chunk itself
+ */
+ public void add(int worldX, int worldY, int worldZ, ServerTerrainChunk chunk){
+ String key = this.getKey(worldX, worldY, worldZ);
+ lock.acquireUninterruptibly();
+ queryRecencyQueue.add(0, key);
+ this.chunkCache.put(key, chunk);
+ lock.release();
+ }
+
+ /**
+ * Checks if the cache contains the chunk at a given world position
+ * @param worldX The world x coordinate
+ * @param worldY The world y coordinate
+ * @param worldZ The world z coordinate
+ * @return true if the cache contains this chunk, false otherwise
+ */
+ public boolean containsChunk(int worldX, int worldY, int worldZ){
+ String key = this.getKey(worldX,worldY,worldZ);
+ lock.acquireUninterruptibly();
+ boolean rVal = this.chunkCache.containsKey(key);
+ lock.release();
+ return rVal;
+ }
+
+ /**
+ * Gets the key for a given world position
+ * @param worldX The x component
+ * @param worldY The y component
+ * @param worldZ The z component
+ * @return The key
+ */
+ public String getKey(int worldX, int worldY, int worldZ){
+ return worldX + "_" + worldY + "_" + worldZ;
+ }
+
+ /**
+ * Checks if the chunk is already queued or not
+ * @param worldX The world x position of the chunk
+ * @param worldY The world y position of the chunk
+ * @param worldZ The world z position of the chunk
+ * @return true if the chunk is already queued, false otherwise
+ */
+ public boolean chunkIsQueued(int worldX, int worldY, int worldZ){
+ String key = this.getKey(worldX,worldY,worldZ);
+ lock.acquireUninterruptibly();
+ boolean rVal = this.queuedChunkMap.containsKey(key);
+ lock.release();
+ return rVal;
+ }
+
+ /**
+ * Flags a chunk as queued
+ * @param worldX The world x position of the chunk
+ * @param worldY The world y position of the chunk
+ * @param worldZ The world z position of the chunk
+ */
+ public void queueChunk(int worldX, int worldY, int worldZ){
+ String key = this.getKey(worldX,worldY,worldZ);
+ lock.acquireUninterruptibly();
+ this.queuedChunkMap.put(key,true);
+ lock.release();
+ }
+
+ /**
+ * Unflags a chunk as queued
+ * @param worldX The world x position of the chunk
+ * @param worldY The world y position of the chunk
+ * @param worldZ The world z position of the chunk
+ */
+ public void unqueueChunk(int worldX, int worldY, int worldZ){
+ String key = this.getKey(worldX,worldY,worldZ);
+ lock.acquireUninterruptibly();
+ this.queuedChunkMap.remove(key);
+ lock.release();
+ }
+
+}
diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java
index 66bb1277..2c9adec1 100644
--- a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java
+++ b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java
@@ -1,5 +1,6 @@
package electrosphere.server.terrain.manager;
+import electrosphere.engine.Globals;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.server.terrain.diskmap.ChunkDiskMap;
import electrosphere.server.terrain.generation.TestGenerationChunkGenerator;
@@ -8,12 +9,13 @@ import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
import electrosphere.server.terrain.models.TerrainModel;
import electrosphere.server.terrain.models.TerrainModification;
import electrosphere.util.FileUtils;
+import electrosphere.util.annotation.Exclude;
+
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
import org.joml.Vector3i;
@@ -22,6 +24,11 @@ import org.joml.Vector3i;
*/
public class ServerTerrainManager {
+ /**
+ * The number of threads for chunk generation
+ */
+ public static final int GENERATION_THREAD_POOL_SIZE = 1;
+
/**
* Full world discrete size
*/
@@ -51,21 +58,24 @@ public class ServerTerrainManager {
//The model of the terrain this manager is managing
TerrainModel model;
-
- //In memory cache of chunk data
- //Basic idea is we associate string that contains chunk x&y&z with elevation
- //While we incur a penalty with converting ints -> string, think this will
- //offset regenerating the array every time we want a new one
- int cacheSize = 500;
- Map chunkCache;
- List chunkCacheContents;
+ /**
+ * The cache of chunks
+ */
+ @Exclude
+ ServerChunkCache chunkCache = new ServerChunkCache();
//The map of chunk position <-> file on disk containing chunk data
ChunkDiskMap chunkDiskMap = null;
//The generation algorithm for this terrain manager
+ @Exclude
ChunkGenerator chunkGenerator;
-
+
+ /**
+ * The threadpool for chunk generation
+ */
+ @Exclude
+ ExecutorService chunkExecutorService = Executors.newFixedThreadPool(GENERATION_THREAD_POOL_SIZE);
/**
* Constructor
@@ -76,8 +86,6 @@ public class ServerTerrainManager {
ChunkGenerator chunkGenerator
){
this.parent = parent;
- this.chunkCache = new ConcurrentHashMap();
- this.chunkCacheContents = new CopyOnWriteArrayList();
this.seed = seed;
this.chunkGenerator = chunkGenerator;
}
@@ -118,8 +126,7 @@ public class ServerTerrainManager {
FileUtils.saveBinaryToSavePath(saveName, "./terrain.dat", buffer.array());
}
//for each chunk, save via disk map
- for(String chunkKey : chunkCacheContents){
- ServerTerrainChunk chunk = chunkCache.get(chunkKey);
+ for(ServerTerrainChunk chunk : this.chunkCache.getContents()){
chunkDiskMap.saveToDisk(chunk);
}
//save disk map itself
@@ -167,7 +174,6 @@ public class ServerTerrainManager {
*/
public void evictAll(){
this.chunkCache.clear();
- this.chunkCacheContents.clear();
}
public float[][] getTerrainAtChunk(int x, int y){
@@ -215,37 +221,19 @@ public class ServerTerrainManager {
}
/**
- * Gets the key for a given world position
- * @param worldX The x component
- * @param worldY The y component
- * @param worldZ The z component
- * @return The key
- */
- public String getKey(int worldX, int worldY, int worldZ){
- return worldX + "_" + worldY + "_" + worldZ;
- }
-
- /**
- * Gets a server terrain chunk
+ * Performs logic once a server chunk is available
* @param worldX The world x position
* @param worldY The world y position
* @param worldZ The world z position
* @return The ServerTerrainChunk
*/
public ServerTerrainChunk getChunk(int worldX, int worldY, int worldZ){
+ Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk");
//THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING
- String key = getKey(worldX,worldY,worldZ);
ServerTerrainChunk returnedChunk = null;
- if(chunkCache.containsKey(key)){
- chunkCacheContents.remove(key);
- chunkCacheContents.add(0, key);
- returnedChunk = chunkCache.get(key);
- return returnedChunk;
+ if(chunkCache.containsChunk(worldX,worldY,worldZ)){
+ returnedChunk = chunkCache.get(worldX,worldY,worldZ);
} else {
- if(chunkCacheContents.size() >= cacheSize){
- String oldChunk = chunkCacheContents.remove(chunkCacheContents.size() - 1);
- chunkCache.remove(oldChunk);
- }
//pull from disk if it exists
if(chunkDiskMap != null){
if(chunkDiskMap.containsTerrainAtPosition(worldX, worldY, worldZ)){
@@ -256,10 +244,23 @@ public class ServerTerrainManager {
if(returnedChunk == null){
returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ);
}
- chunkCache.put(key, returnedChunk);
- chunkCacheContents.add(key);
- return returnedChunk;
+ this.chunkCache.add(worldX, worldY, worldZ, returnedChunk);
}
+ Globals.profiler.endCpuSample();
+ return returnedChunk;
+ }
+
+ /**
+ * Performs logic once a server chunk is available
+ * @param worldX The world x position
+ * @param worldY The world y position
+ * @param worldZ The world z position
+ * @param onLoad The logic to run once the chunk is available
+ */
+ public void getChunkAsync(int worldX, int worldY, int worldZ, Consumer onLoad){
+ Globals.profiler.beginCpuSample("ServerTerrainManager.getChunkAsync");
+ this.chunkExecutorService.submit(new ChunkGenerationThread(chunkDiskMap, chunkCache, chunkGenerator, worldX, worldY, worldZ, onLoad));
+ Globals.profiler.endCpuSample();
}
/**
@@ -286,9 +287,8 @@ public class ServerTerrainManager {
if(model != null){
model.addModification(modification);
}
- String key = getKey(worldPos.x,worldPos.y,worldPos.z);
- if(chunkCache.containsKey(key)){
- ServerTerrainChunk chunk = chunkCache.get(key);
+ if(chunkCache.containsChunk(worldPos.x,worldPos.y,worldPos.z)){
+ ServerTerrainChunk chunk = chunkCache.get(worldPos.x,worldPos.y,worldPos.z);
chunk.addModification(modification);
}
}
@@ -309,4 +309,11 @@ public class ServerTerrainManager {
return chunkGenerator;
}
+ /**
+ * Closes the generation threadpool
+ */
+ public void closeThreads(){
+ this.chunkExecutorService.shutdownNow();
+ }
+
}
diff --git a/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java b/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java
new file mode 100644
index 00000000..d0a25519
--- /dev/null
+++ b/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java
@@ -0,0 +1,435 @@
+package electrosphere.util.ds.octree;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.joml.Vector3i;
+
+import io.github.studiorailgun.MathUtils;
+
+/**
+ * A power of two oct tree that supports arbitrary world size (not fixed)
+ */
+public class WorldOctTree {
+ /**
+ * The maximum level of the chunk tree
+ */
+ int maxLevel;
+
+ /**
+ * The root node
+ */
+ private FloatingChunkTreeNode root = null;
+
+ /**
+ * The list of all nodes in the tree
+ */
+ List> nodes = null;
+
+ /**
+ * The minimum position
+ */
+ Vector3i min;
+
+ /**
+ * The maximum position
+ */
+ Vector3i max;
+
+ /**
+ * Constructor
+ * @param min The minimum position of the world
+ * @param max The maximum position of the world
+ */
+ public WorldOctTree(Vector3i min, Vector3i max){
+ //check that dimensions are a multiple of 2
+ if(
+ ((max.x - min.x) & (max.x - min.x - 1)) != 0 ||
+ ((max.y - min.y) & (max.y - min.y - 1)) != 0 ||
+ ((max.z - min.z) & (max.z - min.z - 1)) != 0
+ ){
+ throw new Error("Invalid dimensions! Must be a power of two! " + min + " " + max);
+ }
+ if(max.x - min.x != max.y - min.y || max.x - min.x != max.z - min.z){
+ throw new Error("Invalid dimensions! Must be the same size along all three axis! " + min + " " + max);
+ }
+ this.min = new Vector3i(min);
+ this.max = new Vector3i(max);
+ //calculate max level
+ int dimRaw = max.x - min.x;
+ this.maxLevel = (int)MathUtils.log2(dimRaw);
+ this.nodes = new ArrayList>();
+ this.root = new FloatingChunkTreeNode(this, 0, new Vector3i(min), new Vector3i(max));
+ this.root.isLeaf = true;
+ this.nodes.add(this.root);
+ }
+
+ /**
+ * Splits a parent into child nodes
+ * @param parent The parent
+ * @return The new non-leaf node
+ */
+ public FloatingChunkTreeNode split(FloatingChunkTreeNode existing){
+ if(!existing.isLeaf()){
+ throw new IllegalArgumentException("Tried to split non-leaf!");
+ }
+ Vector3i min = existing.getMinBound();
+ Vector3i max = existing.getMaxBound();
+ int midX = (max.x - min.x) / 2 + min.x;
+ int midY = (max.y - min.y) / 2 + min.y;
+ int midZ = (max.z - min.z) / 2 + min.z;
+ int currentLevel = existing.getLevel();
+ FloatingChunkTreeNode newContainer = new FloatingChunkTreeNode<>(this, currentLevel, min, max);
+ //add children
+ newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(min.x,min.y,min.z), new Vector3i(midX,midY,midZ)));
+ newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(midX,min.y,min.z), new Vector3i(max.x,midY,midZ)));
+ newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(min.x,midY,min.z), new Vector3i(midX,max.y,midZ)));
+ newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(midX,midY,min.z), new Vector3i(max.x,max.y,midZ)));
+ //
+ newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(min.x,min.y,midZ), new Vector3i(midX,midY,max.z)));
+ newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(midX,min.y,midZ), new Vector3i(max.x,midY,max.z)));
+ newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(min.x,midY,midZ), new Vector3i(midX,max.y,max.z)));
+ newContainer.addChild(new FloatingChunkTreeNode(this, currentLevel + 1, new Vector3i(midX,midY,midZ), new Vector3i(max.x,max.y,max.z)));
+
+ boolean foundMin = false;
+ for(FloatingChunkTreeNode child : newContainer.getChildren()){
+ if(child.getMinBound().distance(newContainer.getMinBound()) == 0){
+ foundMin = true;
+ }
+ }
+ if(!foundMin){
+ String message = "Failed to sanity check!\n";
+ message = message + min + " " + max + "\n";
+ message = message + midX + " " + midY + " " + midZ + "\n";
+ message = message + "container mid: " + newContainer.getMinBound();
+ for(FloatingChunkTreeNode child : newContainer.getChildren()){
+ message = message + "child min: " + child.getMinBound() + "\n";
+ }
+ throw new Error(message);
+ }
+
+ //replace existing node
+ replaceNode(existing,newContainer);
+
+ //update tracking
+ this.nodes.remove(existing);
+ this.nodes.add(newContainer);
+ this.nodes.addAll(newContainer.getChildren());
+
+ return newContainer;
+ }
+
+ /**
+ * Joins a non-leaf node's children into a single node
+ * @param parent The non-leaf
+ * @return The new leaf node
+ */
+ public FloatingChunkTreeNode join(FloatingChunkTreeNode existing){
+ if(existing.isLeaf()){
+ throw new IllegalArgumentException("Tried to split non-leaf!");
+ }
+ Vector3i min = existing.getMinBound();
+ Vector3i max = existing.getMaxBound();
+ int currentLevel = existing.getLevel();
+ FloatingChunkTreeNode newContainer = new FloatingChunkTreeNode<>(this, currentLevel, min, max);
+
+ //replace existing node
+ replaceNode(existing,newContainer);
+
+ //update tracking
+ this.nodes.remove(existing);
+ this.nodes.removeAll(existing.getChildren());
+ this.nodes.add(newContainer);
+
+ return newContainer;
+ }
+
+ /**
+ * Replaces an existing node with a new node
+ * @param existing the existing node
+ * @param newNode the new node
+ */
+ private void replaceNode(FloatingChunkTreeNode existing, FloatingChunkTreeNode newNode){
+ if(existing == this.root){
+ this.root = newNode;
+ } else {
+ FloatingChunkTreeNode parent = existing.getParent();
+ parent.removeChild(existing);
+ parent.addChild(newNode);
+ }
+ }
+
+ /**
+ * Gets the root node of the tree
+ */
+ public FloatingChunkTreeNode getRoot() {
+ return this.root;
+ }
+
+ /**
+ * Sets the max level of the tree
+ * @param maxLevel The max level
+ */
+ public void setMaxLevel(int maxLevel){
+ this.maxLevel = maxLevel;
+ }
+
+ /**
+ * Gets the max level allowed for the tree
+ * @return The max level
+ */
+ public int getMaxLevel(){
+ return maxLevel;
+ }
+
+ /**
+ * Clears the tree
+ */
+ public void clear(){
+ this.nodes.clear();
+ this.root = new FloatingChunkTreeNode(this, 0, new Vector3i(min), new Vector3i(max));
+ this.root.isLeaf = true;
+ this.nodes.add(this.root);
+ }
+
+ /**
+ * Searches for the node at a given position
+ * @param position The position
+ * @param returnNonLeaf If true, the function can return non-leaf nodes, otherwise will only return leaf nodes
+ * @return The leaf if it exists, null otherwise
+ */
+ public FloatingChunkTreeNode search(Vector3i position, boolean returnNonLeaf){
+ return this.search(position,returnNonLeaf,this.maxLevel);
+ }
+
+ /**
+ * Searches for the node at a given position
+ * @param position The position
+ * @param returnNonLeaf If true, the function can return non-leaf nodes, otherwise will only return leaf nodes
+ * @param maxLevel The maximum level to search for
+ * @return The leaf if it exists, null otherwise
+ */
+ public FloatingChunkTreeNode search(Vector3i position, boolean returnNonLeaf, int maxLevel){
+ //out of bounds check
+ if(
+ position.x < min.x || position.x > max.x ||
+ position.y < min.y || position.y > max.y ||
+ position.z < min.z || position.z > max.z
+ ){
+ throw new Error("Trying to search for node outside tree range!");
+ }
+ FloatingChunkTreeNode searchResult = recursiveSearchUnsafe(root,position,maxLevel);
+ if(!returnNonLeaf && !searchResult.isLeaf()){
+ return null;
+ }
+ return searchResult;
+ }
+
+ /**
+ * Recursively searches for the node at the position. Unsafe because it does not bounds check.
+ * @param currentNode The current node searching from
+ * @param position The position to search at
+ * @param maxLevel The maximum level to search for
+ * @return The found node
+ */
+ private FloatingChunkTreeNode recursiveSearchUnsafe(FloatingChunkTreeNode currentNode, Vector3i position, int maxLevel){
+ if(maxLevel < 0){
+ throw new Error("Provided invalid max level! Must be created than 0! " + maxLevel);
+ }
+ if(currentNode.level > maxLevel){
+ throw new Error("Failed to stop before max level!");
+ }
+ if(currentNode.level == maxLevel){
+ return currentNode;
+ }
+ if(currentNode.getChildren().size() > 0){
+ for(FloatingChunkTreeNode child : currentNode.getChildren()){
+ if(
+ position.x < child.getMaxBound().x && position.x >= child.getMinBound().x &&
+ position.y < child.getMaxBound().y && position.y >= child.getMinBound().y &&
+ position.z < child.getMaxBound().z && position.z >= child.getMinBound().z
+ ){
+ return recursiveSearchUnsafe(child, position, maxLevel);
+ }
+ }
+ String message = "Current node is within range, but no children are! This does not make any sense.\n";
+
+ message = message + " current pos: " + currentNode.getMinBound() + " " + currentNode.getMaxBound() + "\n";
+ for(FloatingChunkTreeNode child : currentNode.getChildren()){
+ message = message + " child " + child + " pos: " + child.getMinBound() + " " + child.getMaxBound() + "\n";
+ }
+ message = message + "position to search: " + position + "\n";
+ throw new Error(message);
+ } else {
+ return currentNode;
+ }
+ }
+
+
+ /**
+ * A node in a chunk tree
+ */
+ public static class FloatingChunkTreeNode {
+
+ //True if this is a leaf node, false otherwise
+ private boolean isLeaf;
+
+ //the parent node
+ private FloatingChunkTreeNode parent;
+
+ /**
+ * The tree containing this node
+ */
+ private WorldOctTree containingTree;
+
+ //the children of this node
+ private List> children = new LinkedList>();
+
+ //The data at the node
+ private T data;
+
+ /**
+ * The min bound
+ */
+ private Vector3i min;
+
+ /**
+ * The max bound.
+ * !!NOTE!! max is exclusive, not inclusive
+ */
+ private Vector3i max;
+
+ /**
+ * The level of the chunk tree node
+ */
+ int level;
+
+ /**
+ * Constructor for non-leaf node
+ * @param tree The parent tree
+ * @param level The level of the node
+ * @param min The minimum position of the node
+ * @param max The maximum position of then ode
+ */
+ private FloatingChunkTreeNode(WorldOctTree tree, int level, Vector3i min, Vector3i max){
+ if(tree == null){
+ throw new Error("Invalid tree provided " + tree);
+ }
+ int maxPos = (int)Math.pow(2,tree.getMaxLevel());
+ if(min.x == maxPos || min.y == maxPos || min.z == maxPos){
+ throw new IllegalArgumentException("Invalid minimum! " + min);
+ }
+ if(level < 0 || level > tree.getMaxLevel()){
+ throw new IllegalArgumentException("Invalid level! " + level);
+ }
+ this.containingTree = tree;
+ this.isLeaf = false;
+ this.level = level;
+ this.min = min;
+ this.max = max;
+ }
+
+ /**
+ * Constructor for use in tests
+ * @param tree The Tree
+ * @param level The level
+ * @param min The min point
+ * @param max The max point
+ * @return The node
+ */
+ public static FloatingChunkTreeNode constructorForTests(WorldOctTree tree, int level, Vector3i min, Vector3i max){
+ return new FloatingChunkTreeNode(tree, level, min, max);
+ }
+
+ /**
+ * Converts this node to a leaf
+ * @param data The data to put in the leaf
+ */
+ public void convertToLeaf(T data){
+ this.isLeaf = true;
+ this.data = data;
+ }
+
+ /**
+ * Gets the data associated with this node
+ */
+ public T getData() {
+ return data;
+ }
+
+ /**
+ * Gets the parent of this node
+ */
+ public FloatingChunkTreeNode getParent() {
+ return parent;
+ }
+
+ /**
+ * Gets the children of this node
+ */
+ public List> getChildren() {
+ return Collections.unmodifiableList(this.children);
+ }
+
+ /**
+ * Checks if this node is a leaf
+ * @return true if it is a leaf, false otherwise
+ */
+ public boolean isLeaf() {
+ return isLeaf;
+ }
+
+ /**
+ * Checks if the node can split
+ * @return true if can split, false otherwise
+ */
+ public boolean canSplit(){
+ return isLeaf && level < containingTree.getMaxLevel();
+ }
+
+ /**
+ * Gets the level of the node
+ * @return The level of the node
+ */
+ public int getLevel(){
+ return level;
+ }
+
+ /**
+ * Gets the min bound of this node
+ * @return The min bound
+ */
+ public Vector3i getMinBound(){
+ return new Vector3i(min);
+ }
+
+ /**
+ * Gets the max bound of this node
+ * @return The max bound
+ */
+ public Vector3i getMaxBound(){
+ return new Vector3i(max);
+ }
+
+ /**
+ * Adds a child to this node
+ * @param child The child
+ */
+ private void addChild(FloatingChunkTreeNode child){
+ this.children.add(child);
+ child.parent = this;
+ }
+
+ /**
+ * Removes a child node
+ * @param child the child
+ */
+ private void removeChild(FloatingChunkTreeNode child){
+ this.children.remove(child);
+ child.parent = null;
+ }
+
+ }
+}
diff --git a/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java b/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java
new file mode 100644
index 00000000..e98db024
--- /dev/null
+++ b/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java
@@ -0,0 +1,51 @@
+package electrosphere.client.terrain.cells;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.joml.Vector3d;
+import org.joml.Vector3f;
+import org.joml.Vector3i;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import electrosphere.client.scene.ClientWorldData;
+import electrosphere.engine.Globals;
+import electrosphere.engine.Main;
+import electrosphere.server.terrain.manager.ServerTerrainChunk;
+import electrosphere.test.annotations.UnitTest;
+import electrosphere.test.template.extensions.StateCleanupCheckerExtension;
+import electrosphere.util.ds.octree.WorldOctTree.FloatingChunkTreeNode;
+
+/**
+ * Tests for the client draw cell manager
+ */
+@ExtendWith(StateCleanupCheckerExtension.class)
+public class ClientDrawCellManagerTests {
+
+
+ /**
+ * Test creating a manager
+ */
+ @UnitTest
+ public void testCreation(){
+ assertDoesNotThrow(() -> {
+ new ClientDrawCellManager(null, 64);
+ });
+ }
+
+ @UnitTest
+ public void testJoinCase(){
+
+ int worldDiscreteSize = 64;
+ Globals.clientWorldData = new ClientWorldData(new Vector3f(0), new Vector3f(worldDiscreteSize * ServerTerrainChunk.CHUNK_DIMENSION), 0, worldDiscreteSize);
+ ClientDrawCellManager manager = new ClientDrawCellManager(null, 64);
+ Vector3d playerPos = new Vector3d(0,0,0);
+ FloatingChunkTreeNode node = FloatingChunkTreeNode.constructorForTests(manager.chunkTree, 1, new Vector3i(16,0,0), new Vector3i(32,16,16));
+ node.convertToLeaf(DrawCell.generateTerrainCell(new Vector3i(0,0,0)));
+
+ assertFalse(manager.shouldSplit(playerPos, node));
+
+ //cleanup
+ Main.shutdown();
+ }
+
+}
diff --git a/src/test/java/electrosphere/test/template/extensions/StateCleanupCheckerExtension.java b/src/test/java/electrosphere/test/template/extensions/StateCleanupCheckerExtension.java
index 44777fde..a3e86652 100644
--- a/src/test/java/electrosphere/test/template/extensions/StateCleanupCheckerExtension.java
+++ b/src/test/java/electrosphere/test/template/extensions/StateCleanupCheckerExtension.java
@@ -30,6 +30,7 @@ public class StateCleanupCheckerExtension implements AfterEachCallback {
Globals.playerManager,
LoggerInterface.loggerEngine,
RenderingEngine.screenFramebuffer,
+ Globals.clientWorldData,
};
for(Object object : objectsToCheck){
if(object != null){
diff --git a/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java b/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java
index d592588a..dbc2369e 100644
--- a/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java
+++ b/src/test/java/electrosphere/util/ds/octree/ChunkTreeTests.java
@@ -2,9 +2,20 @@ package electrosphere.util.ds.octree;
import static org.junit.jupiter.api.Assertions.*;
+import electrosphere.test.annotations.UnitTest;
+
/**
* Unit testing for the chunk octree implementation
*/
public class ChunkTreeTests {
+ /**
+ * Creates a chunk tree
+ */
+ @UnitTest
+ public void testCreateChunkTree(){
+ ChunkTree tree = new ChunkTree();
+ assertNotNull(tree);
+ }
+
}
diff --git a/src/test/java/electrosphere/util/ds/octree/WorldOctTreeTests.java b/src/test/java/electrosphere/util/ds/octree/WorldOctTreeTests.java
new file mode 100644
index 00000000..a1008e3b
--- /dev/null
+++ b/src/test/java/electrosphere/util/ds/octree/WorldOctTreeTests.java
@@ -0,0 +1,54 @@
+package electrosphere.util.ds.octree;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.joml.Vector3i;
+
+import electrosphere.test.annotations.UnitTest;
+
+/**
+ * Tests the floating position chunk tree implementation
+ */
+public class WorldOctTreeTests {
+
+ /**
+ * Creates a chunk tree
+ */
+ @UnitTest
+ public void testCreateFloatingChunkTree(){
+ WorldOctTree tree = new WorldOctTree(new Vector3i(0,0,0), new Vector3i(64,64,64));
+ assertNotNull(tree);
+ }
+
+ /**
+ * Test changing the centered point of the floating tree
+ */
+ @UnitTest
+ public void testMaxLevelSetting(){
+ WorldOctTree tree = new WorldOctTree(new Vector3i(0,0,0), new Vector3i(64,64,64));
+ assertEquals(6,tree.getMaxLevel());
+ }
+
+ /**
+ * Assert non-power-of-two dims fail
+ */
+ @UnitTest
+ public void testFailOnNonPowTwoDim(){
+ assertThrows(Error.class, () -> {
+ new WorldOctTree(new Vector3i(0,0,0), new Vector3i(63,63,63));
+ });
+ }
+
+ /**
+ * Assert unequal dims fail
+ */
+ @UnitTest
+ public void testFailOnUnequalDim(){
+ assertThrows(Error.class, () -> {
+ new WorldOctTree(new Vector3i(0,0,0), new Vector3i(64,1,64));
+ });
+ }
+
+
+
+}
diff --git a/template.json b/template.json
index 00a3414c..c1463157 100644
--- a/template.json
+++ b/template.json
@@ -10,7 +10,6 @@
"./net/lore.json",
"./net/player.json",
"./net/terrain.json",
- "./net/world.json",
"./net/server.json",
"./net/character.json",
"./net/inventory.json",