Compare commits
2 Commits
2b715ab325
...
8dcff7efe0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dcff7efe0 | ||
|
|
7a2bdf7745 |
@ -1,3 +1,3 @@
|
||||
#maven.buildNumber.plugin properties file
|
||||
#Fri Jun 14 13:45:11 EDT 2024
|
||||
buildNumber=137
|
||||
#Wed Jun 19 19:19:09 EDT 2024
|
||||
buildNumber=138
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
- @subpage archimprovementtargets
|
||||
- @subpage savesindex
|
||||
- @subpage hitboxesindex
|
||||
- @subpage drawcell
|
||||
|
||||
|
||||
# What is this section
|
||||
|
||||
6
docs/src/architecture/drawcell/drawcell.md
Normal file
6
docs/src/architecture/drawcell/drawcell.md
Normal file
@ -0,0 +1,6 @@
|
||||
@page drawcell Draw Cells
|
||||
|
||||
[TOC]
|
||||
- @subpage transvoxelalgorithm
|
||||
|
||||

|
||||
49
docs/src/architecture/drawcell/transvoxelalgorithm.md
Normal file
49
docs/src/architecture/drawcell/transvoxelalgorithm.md
Normal file
@ -0,0 +1,49 @@
|
||||
@page transvoxelalgorithm Transvoxel Chunk Generation
|
||||
|
||||
|
||||
|
||||
# High Level
|
||||
|
||||
|
||||
The goal of the transvoxel algorithm is to bridge the divide between a chunk of a higher resolution and a chunk of a lower resolution
|
||||
|
||||

|
||||
|
||||
For the voxels on the border between a low resolution chunk and a high resolution chunk, we split the voxels in half
|
||||
|
||||

|
||||
|
||||
On the low-resolution half, we perform marching cubes
|
||||
|
||||
On the high resolution half, we generate a mesh that adapts the high resolution chunk to the low resolution voxel
|
||||
|
||||

|
||||
|
||||
# Major files
|
||||
[TransvoxelModelGeneration.java](@ref #electrosphere.renderer.meshgen.TransvoxelModelGeneration) - The main class that turns voxel data into mesh data
|
||||
|
||||
[TerrainChunk.java](@ref #electrosphere.entity.types.terrain.TerrainChunk) - The class that the DrawCellManager calls to generate chunk meshes
|
||||
|
||||
[ClientTerrainManager.java](@ref #electrosphere.client.terrain.manager.ClientTerrainManager) - Handles queueing the mesh data to the gpu
|
||||
|
||||
[TerrainChunkGenQueueItem.java](@ref #electrosphere.client.terrain.manager.TerrainChunkGenQueueItem) - A terrain mesh that has been queued to be added to the gpu
|
||||
|
||||
|
||||
|
||||
|
||||
# Implementation Notes
|
||||
|
||||
The description of the algorithm always refers to the transition cells in terms of y pointing upwards and x pointing to the right.
|
||||
This can be confusing to think about how it would apply to other orientations (ie what if we're working along the y axis or something and x is "up").
|
||||
All these transforms are done implicitly in the `generateTerrainChunkData` function. When iterating across each axis, I've manually calculated what point should be where.
|
||||
The inner polygonize functions always treat it as y-up, but that works because this transform has already been done before the data is passed in.
|
||||
|
||||
|
||||
|
||||
|
||||
# Notes about table format
|
||||
- `transitionCellClass` is indexed into by the case index value generated from the high resolution face data
|
||||
- `transitionCellData` is indexed into by the class value from the `transitionCellClass`
|
||||
- `transitionVertexData` is ALSO indexed into by the CASE INDEX VALUE, NOT THE CLASS VALUE. You can figure this out because the `transitionVertexData` has 512 entries
|
||||
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
@page narrativearcdesign Narrative Arc Design
|
||||
|
||||
Use the idea of conspiracy to create mystery eventually leading to a big series of confrontations and narrative payoff
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
[TOC]
|
||||
- @subpage whatmakesaquestgood
|
||||
- @subpage narrativearcdesign
|
||||
|
||||
|
||||
TODO: describe
|
||||
|
||||
BIN
docs/src/images/architecture/drawcell/ClientMacroArch.png
Normal file
BIN
docs/src/images/architecture/drawcell/ClientMacroArch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/src/images/architecture/drawcell/adaptedvoxel.png
Normal file
BIN
docs/src/images/architecture/drawcell/adaptedvoxel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
BIN
docs/src/images/architecture/drawcell/bisectedvoxel.png
Normal file
BIN
docs/src/images/architecture/drawcell/bisectedvoxel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
BIN
docs/src/images/architecture/drawcell/completevoxel.png
Normal file
BIN
docs/src/images/architecture/drawcell/completevoxel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
@ -1,6 +1,11 @@
|
||||
@page bigthings Big Things I want to build
|
||||
# and may or may not have the sanity to build
|
||||
|
||||
DONE:
|
||||
|
||||
|
||||
TODO(?):
|
||||
|
||||
- CFD
|
||||
- Internal Boundaries
|
||||
- Multigrid optimization
|
||||
@ -8,6 +13,8 @@
|
||||
|
||||
- Transvoxel Algorithm
|
||||
|
||||
- Building cube voxels w/ LOD
|
||||
|
||||
- Deferred Shading Pipeline
|
||||
|
||||
- Audio Ray Tracing
|
||||
@ -17,3 +24,7 @@
|
||||
- Massive scale creature groups
|
||||
(ie 10k armies)
|
||||
|
||||
- Use HTML to define ui windows
|
||||
- Some css support
|
||||
- Language file for translation
|
||||
|
||||
|
||||
@ -371,9 +371,25 @@ Highlevel netcode gen updates
|
||||
- Furthermore, keep tracking of the existing ids for trees and fields and only generate ids for new trees and fields
|
||||
Fix client gravity tree name
|
||||
|
||||
(06/19/2024)
|
||||
Transvoxel implementation
|
||||
- Begin work on transvoxel algo
|
||||
|
||||
(06/21/2024)
|
||||
Transvoxel implementation
|
||||
- First working implementation of mesh generation for transvoxel chunks (architecture of adding it to drawcellmanager still todo)
|
||||
|
||||
(06/22/2024)
|
||||
Transvoxel implementation
|
||||
- Scaling LODed chunks by lod level
|
||||
|
||||
Fix items falling below the ground
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
|
||||
|
||||
Demo requirements:
|
||||
= Assets =
|
||||
Block animation in first person
|
||||
@ -385,7 +401,6 @@ Audio FX for everything
|
||||
|
||||
= Coding =
|
||||
Sour spot, sweet spot for damage hitboxes and hurtboxes
|
||||
Fix items falling below the ground
|
||||
Sub menu on title screen that allows changing control mappings
|
||||
Redo hitboxes to have capsules and also chaining between frames (but not between swinging the camera around)
|
||||
- Introduce block hitbox (blockbox) type
|
||||
@ -400,6 +415,26 @@ Ability for private realms to have time start/stop based on the player's feedbac
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Goof off today requirements:
|
||||
Transvoxel implementation
|
||||
- Fix draw cell manager requesting far-out chunks
|
||||
- Properly update to higher LOD meshes as you get closer
|
||||
Client Terrain Entity Management (specifically creation/teardown for client)
|
||||
- Also queries for far out chunks to load far away terrain
|
||||
Server Terrain Management (specifically for collision)
|
||||
- Handles communicating far out LOD chunks to client as well
|
||||
Terrain Interface Positional Access Interface
|
||||
- Ability to get terrain at point for interactions with game world eg placing grass/water collision
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIG BIG BIG BIG IMMEDIATE TO DO:
|
||||
always enforce opengl interface across all opengl calls jesus christ the bone uniform bug was impossible
|
||||
|
||||
@ -457,22 +492,6 @@ Shader library system
|
||||
|
||||
Break control handlers into separate files with new logic to transition between control handler states
|
||||
|
||||
|
||||
Transvoxel Algorithm
|
||||
Client Terrain Entity Management (specifically creation/teardown for client)
|
||||
- Also queries for far out chunks to load far away terrain
|
||||
Server Terrain Management (specifically for collision)
|
||||
- Handles communicating far out LOD chunks to client as well
|
||||
Terrain Interface Positional Access Interface
|
||||
- Ability to get terrain at point for interactions with game world eg placing grass/water collision
|
||||
Actually implement transvoxel algo
|
||||
Marching Cubes Texture Overhaul
|
||||
- Detect opengl max image size
|
||||
- Construct texture atlas of max size
|
||||
- (target 256x256 resolution initially, should give ~1000 types for 8192x8192)
|
||||
- Prebake all textures into atlas
|
||||
- Rewrite marching cubes shader to leverage this atlas
|
||||
|
||||
Another pass at grass
|
||||
- Multiple foliage models in same cell
|
||||
|
||||
@ -580,9 +599,6 @@ Generic collision engine to support different instances of engine (eg hitboxes v
|
||||
- Major refactoring to happen here
|
||||
Procedural Cliff Texture
|
||||
- Uses noise or fractals or something to generate infinite textures in shader
|
||||
Terrain Chunks:
|
||||
- Scale textures to be 1 texture per unit of terrain
|
||||
- Texture atlasing (512x512)
|
||||
Loot Generator
|
||||
- System that can generate items that would be appropriate reward given some variables
|
||||
- ie you tell it 'this is this character's stats, this is the relative level of loot I want to provide'
|
||||
|
||||
17
docs/src/testing/testing.md
Normal file
17
docs/src/testing/testing.md
Normal file
@ -0,0 +1,17 @@
|
||||
@page testing Testing
|
||||
|
||||
Eventual goal is to have unit tests for parts of the engine that it makes sense for. Some ideas:
|
||||
- Loading assets
|
||||
- UI functionality
|
||||
- Basic behavior trees
|
||||
- Script Engine
|
||||
- AI
|
||||
- Networking
|
||||
|
||||
|
||||
Current CI is Jenkins, which has a plugin
|
||||
https://plugins.jenkins.io/xvfb/
|
||||
that allows for graphical sessions while building
|
||||
|
||||
I need to figure out hooking this up to my container in order to do most of the above testing
|
||||
|
||||
@ -90,6 +90,11 @@
|
||||
"type" : "BYTE_ARRAY"
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "chunkResolution",
|
||||
"type" : "FIXED_INT"
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "terrainWeight",
|
||||
"type" : "FIXED_FLOAT"
|
||||
@ -186,6 +191,27 @@
|
||||
"chunkData"
|
||||
]
|
||||
},
|
||||
{
|
||||
"messageName" : "RequestReducedChunkData",
|
||||
"description" : "Requests reduced resolution chunk data from the server",
|
||||
"data" : [
|
||||
"worldX",
|
||||
"worldY",
|
||||
"worldZ",
|
||||
"chunkResolution"
|
||||
]
|
||||
},
|
||||
{
|
||||
"messageName" : "SendReducedChunkData",
|
||||
"description" : "Sends chunk data to the client",
|
||||
"data" : [
|
||||
"worldX",
|
||||
"worldY",
|
||||
"worldZ",
|
||||
"chunkResolution",
|
||||
"chunkData"
|
||||
]
|
||||
},
|
||||
{
|
||||
"messageName" : "RequestFluidData",
|
||||
"description" : "Requests a fluid data from the server",
|
||||
|
||||
@ -11,39 +11,47 @@ import electrosphere.entity.ClientEntityUtils;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.types.terrain.TerrainChunk;
|
||||
import electrosphere.renderer.shader.ShaderProgram;
|
||||
import electrosphere.renderer.texture.Texture;
|
||||
import electrosphere.server.datacell.Realm;
|
||||
import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData;
|
||||
import electrosphere.server.terrain.manager.ServerTerrainChunk;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author satellite
|
||||
* A single drawcell - contains an entity that has a physics mesh and potentially graphics
|
||||
*/
|
||||
public class DrawCell {
|
||||
|
||||
/**
|
||||
* Enum for the different faces of a draw cell -- used when filling in data for higher LOD faces
|
||||
*/
|
||||
public enum DrawCellFace {
|
||||
X_POSITIVE,
|
||||
X_NEGATIVE,
|
||||
Y_POSITIVE,
|
||||
Y_NEGATIVE,
|
||||
Z_POSITIVE,
|
||||
Z_NEGATIVE,
|
||||
}
|
||||
|
||||
//the position of the draw cell in world coordinates
|
||||
Vector3i worldPos;
|
||||
|
||||
//the main entity for the cell
|
||||
Entity modelEntity;
|
||||
|
||||
//the physics mesh
|
||||
DBody physicsObject;
|
||||
|
||||
//Allocated once instead of continuously, used to generate the visual/physics models
|
||||
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];
|
||||
|
||||
static Texture groundTextureOne = new Texture("/Textures/Ground/Dirt1.png");
|
||||
static Texture groundTextureTwo = new Texture("/Textures/Ground/Dirt1.png");
|
||||
static Texture groundTextureThree = new Texture("/Textures/Ground/Dirt1.png");
|
||||
static Texture groundTextureFour = new Texture("/Textures/Ground/Dirt1.png");
|
||||
|
||||
static {
|
||||
// groundTextureOne = new Texture("/Textures/Ground/GrassTileable.png");
|
||||
// groundTextureTwo = new Texture("/Textures/Ground/Dirt1.png");
|
||||
// groundTextureThree = new Texture("/Textures/Ground/Dirt1.png");
|
||||
// groundTextureFour = new Texture("/Textures/Ground/Dirt1.png");
|
||||
}
|
||||
//the maximum detail LOD level
|
||||
public static final int FULL_DETAIL_LOD = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor
|
||||
*/
|
||||
DrawCell(){
|
||||
|
||||
}
|
||||
@ -63,18 +71,23 @@ public class DrawCell {
|
||||
/**
|
||||
* Generates a drawable entity based on this chunk
|
||||
*/
|
||||
public void generateDrawableEntity(VoxelTextureAtlas atlas){
|
||||
public void generateDrawableEntity(VoxelTextureAtlas atlas, int lod, DrawCellFace higherLODFace){
|
||||
if(modelEntity != null){
|
||||
Globals.clientScene.deregisterEntity(modelEntity);
|
||||
}
|
||||
this.fillInData();
|
||||
TransvoxelChunkData chunkData = new TransvoxelChunkData(weights, types, lod);
|
||||
if(lod > FULL_DETAIL_LOD){
|
||||
|
||||
fillInData();
|
||||
|
||||
modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(weights, types, 0, atlas);
|
||||
|
||||
}
|
||||
modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(chunkData, lod, atlas);
|
||||
ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the real-space position of the draw cell
|
||||
* @return the real-space position
|
||||
*/
|
||||
protected Vector3d getRealPos(){
|
||||
return new Vector3d(
|
||||
worldPos.x * ChunkData.CHUNK_SIZE,
|
||||
@ -225,4 +238,48 @@ public class DrawCell {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fills in the internal arrays of data for generate terrain models
|
||||
*/
|
||||
private void fillInData(TransvoxelChunkData chunkData, int lod, DrawCellFace face){
|
||||
float[][] faceData = new float[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE];
|
||||
int[][] atlasData = new int[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE];
|
||||
switch(face){
|
||||
case X_POSITIVE: {
|
||||
} break;
|
||||
case X_NEGATIVE: {
|
||||
} break;
|
||||
case Y_POSITIVE: {
|
||||
} break;
|
||||
case Y_NEGATIVE: {
|
||||
} break;
|
||||
case Z_POSITIVE: {
|
||||
} break;
|
||||
case Z_NEGATIVE: {
|
||||
} break;
|
||||
}
|
||||
//
|
||||
//fill in data
|
||||
//
|
||||
//main chunk
|
||||
//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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -9,15 +9,40 @@ import org.joml.Vector3d;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import electrosphere.client.terrain.cache.ChunkData;
|
||||
import electrosphere.client.terrain.cells.DrawCell.DrawCellFace;
|
||||
import electrosphere.client.terrain.manager.ClientTerrainManager;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Manages the graphical entities for the terrain chunks
|
||||
*
|
||||
* @author satellite
|
||||
*
|
||||
*
|
||||
Notes for integrating with transvoxel algo:
|
||||
Different problems to tackle
|
||||
|
||||
For all chunks within minimum radius, check if they can be updated and update accordingly <--- we do this currently
|
||||
|
||||
|
||||
For all chunks between minimum radius and first LOD radius, check if we can make a LOD chunk
|
||||
If we can, make a lod chunk or see if it can be updated
|
||||
This check will be:
|
||||
For every position, check if all four positions required for LOD chunk are within lod radius
|
||||
if yes, make lod chunk
|
||||
|
||||
If we cannot, create a fullres chunk
|
||||
This check will be:
|
||||
For every position, check if all four positions required for LOD chunk are within lod radius
|
||||
if yes, make lod chunk
|
||||
if they are outside the far bound, create lod chunk
|
||||
if they are within the near bound, create a fullres chunk
|
||||
|
||||
|
||||
|
||||
*/
|
||||
public class DrawCellManager {
|
||||
|
||||
@ -27,45 +52,46 @@ public class DrawCellManager {
|
||||
int cellY;
|
||||
int cellZ;
|
||||
|
||||
|
||||
//the dimensions of the world that this cell manager can handles
|
||||
int cellWidth;
|
||||
|
||||
//the width of a minicell in this manager
|
||||
int miniCellWidth;
|
||||
|
||||
//all currently displaying mini cells
|
||||
Set<DrawCell> cells;
|
||||
Set<DrawCell> cells = new HashSet<DrawCell>();
|
||||
Map<String,DrawCell> keyCellMap = new HashMap<String,DrawCell>();
|
||||
Set<String> hasNotRequested;
|
||||
Set<String> hasRequested;
|
||||
Set<String> drawable;
|
||||
Set<String> undrawable;
|
||||
Set<String> updateable;
|
||||
|
||||
//status of all position keys
|
||||
Set<String> hasNotRequested = new HashSet<String>();
|
||||
Set<String> requested = new HashSet<String>();
|
||||
Set<String> drawable = new HashSet<String>();
|
||||
Set<String> undrawable = new HashSet<String>();
|
||||
Set<String> updateable = new HashSet<String>();
|
||||
|
||||
//LOD level of all position keys
|
||||
Map<String,Integer> positionLODLevel = new HashMap<String,Integer>();
|
||||
|
||||
//voxel atlas
|
||||
VoxelTextureAtlas atlas;
|
||||
|
||||
|
||||
//shader program for drawable cells
|
||||
ShaderProgram program;
|
||||
|
||||
|
||||
|
||||
// int drawRadius = 5;
|
||||
int drawStepdownInterval = 3;
|
||||
int drawStepdownValue = 25;
|
||||
//the real-space radius for which we will construct draw cells inside of
|
||||
//ie, we check if the draw cell's entity would be inside this radius. If it would, create the draw cell, otherwise don't
|
||||
double drawFullModelRadius = 50;
|
||||
|
||||
double drawRadius = 50;
|
||||
//the radius we'll draw LODed chunks for
|
||||
double drawLODRadius = drawFullModelRadius + ServerTerrainChunk.CHUNK_DIMENSION * (2*2 + 4*4 + 8*8 + 16*16);
|
||||
|
||||
|
||||
//the number of possible LOD levels
|
||||
//1,2,4,8,16
|
||||
static final int NUMBER_OF_LOD_LEVELS = 5;
|
||||
|
||||
//the table of lod leve -> radius at which we will look for chunks within this log
|
||||
double[] lodLevelRadiusTable = new double[5];
|
||||
|
||||
//the radius for which physics meshes are created when draw cells are created
|
||||
int physicsRadius = 3;
|
||||
|
||||
int worldBoundDiscreteMin = 0;
|
||||
int worldBoundDiscreteMax = 0;
|
||||
|
||||
//client terrain manager
|
||||
// ClientTerrainManager clientTerrainManager;
|
||||
|
||||
|
||||
//ready to start updating?
|
||||
boolean update = false;
|
||||
|
||||
@ -82,22 +108,22 @@ public class DrawCellManager {
|
||||
* @param discreteY The initial discrete position Y coordinate
|
||||
*/
|
||||
public DrawCellManager(ClientTerrainManager clientTerrainManager, int discreteX, int discreteY, int discreteZ){
|
||||
worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / Globals.clientWorldData.getDynamicInterpolationRatio() * 1.0f);
|
||||
cells = new HashSet<DrawCell>();
|
||||
hasNotRequested = new HashSet<String>();
|
||||
drawable = new HashSet<String>();
|
||||
undrawable = new HashSet<String>();
|
||||
updateable = new HashSet<String>();
|
||||
hasRequested = new HashSet<String>();
|
||||
|
||||
cellX = discreteX;
|
||||
cellY = discreteY;
|
||||
cellZ = discreteZ;
|
||||
|
||||
program = Globals.terrainShaderProgram;
|
||||
|
||||
// drawRadius = Globals.userSettings.getGraphicsPerformanceLODChunkRadius();
|
||||
drawStepdownInterval = Globals.userSettings.getGameplayPhysicsCellRadius();
|
||||
//the first lod level is set by user
|
||||
lodLevelRadiusTable[0] = drawFullModelRadius;
|
||||
//generate LOD radius table
|
||||
for(int i = 1; i < NUMBER_OF_LOD_LEVELS; i++){
|
||||
double sizeOfSingleModel = Math.pow(2,i) * ServerTerrainChunk.CHUNK_DIMENSION;
|
||||
//size of the radius for this lod level should be three times the size of a model + the previous radius
|
||||
//this guarantees we get at least one adapter chunk, one proper chunk, and also that the radius accounts for the previous lod level chunks
|
||||
lodLevelRadiusTable[i] = lodLevelRadiusTable[i-1] + sizeOfSingleModel * 3;
|
||||
}
|
||||
|
||||
physicsRadius = Globals.userSettings.getGameplayPhysicsCellRadius();
|
||||
|
||||
invalidateAllCells();
|
||||
@ -105,29 +131,41 @@ public class DrawCellManager {
|
||||
update = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor
|
||||
*/
|
||||
DrawCellManager(){
|
||||
|
||||
}
|
||||
|
||||
public void setCell(Vector3i cellPos){
|
||||
/**
|
||||
* Sets the player's current position in cell-space
|
||||
* @param cellPos The cell's position
|
||||
*/
|
||||
public void setPlayerCell(Vector3i cellPos){
|
||||
cellX = cellPos.x;
|
||||
cellY = cellPos.y;
|
||||
cellZ = cellPos.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update function that is called if a cell has not been requested
|
||||
*/
|
||||
void updateUnrequestedCell(){
|
||||
if(hasNotRequested.size() > 0){
|
||||
String targetKey = hasNotRequested.iterator().next();
|
||||
hasNotRequested.remove(targetKey);
|
||||
Vector3i worldPos = getVectorFromKey(targetKey);
|
||||
// Vector3i vector = getVectorFromKey(targetKey);
|
||||
// int currentCellX = cellX - drawRadius + vector.x;
|
||||
// int currentCellY = cellY - drawRadius + vector.y;
|
||||
// int currentCellZ = cellZ - drawRadius + vector.z;
|
||||
|
||||
//
|
||||
//Because of the way marching cubes works, we need to request the adjacent chunks so we know how to properly blend between one chunk and the next
|
||||
//The following loop-hell does this
|
||||
//
|
||||
for(int i = 0; i < 2; i++){
|
||||
for(int j = 0; j < 2; j++){
|
||||
for(int k = 0; k < 2; k++){
|
||||
Vector3i posToCheck = new Vector3i(worldPos).add(i,j,k);
|
||||
String requestKey = getCellKey(posToCheck.x,posToCheck.y,posToCheck.z);
|
||||
if(
|
||||
posToCheck.x >= 0 &&
|
||||
posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() &&
|
||||
@ -137,20 +175,20 @@ public class DrawCellManager {
|
||||
posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
|
||||
!Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z)
|
||||
){
|
||||
// if(!hasRequested.contains(targetKey)){
|
||||
if(!requested.contains(requestKey)){
|
||||
//client should request chunk data from server for each chunk necessary to create the model
|
||||
Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage(
|
||||
posToCheck.x,
|
||||
posToCheck.y,
|
||||
posToCheck.z
|
||||
));
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
undrawable.add(targetKey);
|
||||
hasRequested.add(targetKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
requested.add(targetKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,10 +196,12 @@ public class DrawCellManager {
|
||||
* Makes one of the undrawable cells drawable
|
||||
*/
|
||||
void makeCellDrawable(){
|
||||
|
||||
if(undrawable.size() > 0){
|
||||
String targetKey = undrawable.iterator().next();
|
||||
Vector3i worldPos = getVectorFromKey(targetKey);
|
||||
|
||||
//
|
||||
//Checks if all chunk data necessary to generate a mesh is present
|
||||
boolean containsNecessaryChunks = true;
|
||||
for(int i = 0; i < 2; i++){
|
||||
for(int j = 0; j < 2; j++){
|
||||
@ -175,25 +215,28 @@ public class DrawCellManager {
|
||||
posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() &&
|
||||
!containsChunkDataAtWorldPoint(posToCheck.x,posToCheck.y,posToCheck.z)
|
||||
){
|
||||
containsChunkDataAtWorldPoint(posToCheck.x,posToCheck.y,posToCheck.z);
|
||||
containsNecessaryChunks = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//if contains all chunks necessary to generate visuals
|
||||
//
|
||||
//if contains data for all chunks necessary to generate visuals
|
||||
if(containsNecessaryChunks){
|
||||
//build float array
|
||||
|
||||
//update the status of the terrain key
|
||||
undrawable.remove(targetKey);
|
||||
drawable.add(targetKey);
|
||||
|
||||
//build the cell
|
||||
DrawCell cell = DrawCell.generateTerrainCell(
|
||||
worldPos
|
||||
);
|
||||
cells.add(cell);
|
||||
keyCellMap.put(targetKey,cell);
|
||||
// undrawable.add(targetKey);
|
||||
undrawable.remove(targetKey);
|
||||
drawable.add(targetKey);
|
||||
//make drawable entity
|
||||
keyCellMap.get(targetKey).generateDrawableEntity(atlas);
|
||||
DrawCellFace higherLODFace = null;
|
||||
keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace);
|
||||
|
||||
//evaluate for foliage
|
||||
Globals.clientFoliageManager.evaluateChunk(worldPos);
|
||||
}
|
||||
@ -216,37 +259,49 @@ public class DrawCellManager {
|
||||
worldPos.z >= 0 &&
|
||||
worldPos.z < Globals.clientWorldData.getWorldDiscreteSize()
|
||||
){
|
||||
// if(Math.abs(drawRadius + 1 - targetX) < physicsRadius && Math.abs(drawRadius + 1 - targetY) < physicsRadius){
|
||||
// needsPhysics[targetX][targetY] = true;
|
||||
// }
|
||||
// int dist = (int)Math.sqrt((targetX - drawRadius)*(targetX - drawRadius) + (targetY - drawRadius) * (targetY - drawRadius)); //Math.abs(targetX - drawRadius) * Math.abs(targetY - drawRadius);
|
||||
// int stride = Math.min(commonWorldData.getDynamicInterpolationRatio()/2, Math.max(1, dist / drawStepdownInterval * drawStepdownValue));
|
||||
// while(commonWorldData.getDynamicInterpolationRatio() % stride != 0){
|
||||
// stride = stride + 1;
|
||||
// }
|
||||
keyCellMap.get(targetKey).destroy();
|
||||
keyCellMap.get(targetKey).generateDrawableEntity(atlas);
|
||||
DrawCellFace higherLODFace = null;
|
||||
keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace);
|
||||
}
|
||||
drawable.add(targetKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the manager contains a cell position that hasn't had its chunk data requested from the server yet
|
||||
* @return true if there is an unrequested cell, false otherwise
|
||||
*/
|
||||
public boolean containsUnrequestedCell(){
|
||||
return hasNotRequested.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the manager contains a cell who hasn't been made drawable yet
|
||||
* @return true if there is an undrawable cell, false otherwise
|
||||
*/
|
||||
public boolean containsUndrawableCell(){
|
||||
return undrawable.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the manager contains a cell who needs to be updated
|
||||
* @return true if there is an updateable cell, false otherwise
|
||||
*/
|
||||
public boolean containsUpdateableCell(){
|
||||
return updateable.size() > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a real coordinate into a cell-space coordinate
|
||||
* @param input the real coordinate
|
||||
* @return the cell coordinate
|
||||
*/
|
||||
public int transformRealSpaceToCellSpace(double input){
|
||||
return (int)(input / Globals.clientWorldData.getDynamicInterpolationRatio());
|
||||
}
|
||||
@ -278,7 +333,7 @@ public class DrawCellManager {
|
||||
Set<DrawCell> cellsToRemove = new HashSet<DrawCell>();
|
||||
for(DrawCell cell : cells){
|
||||
Vector3d realPos = cell.getRealPos();
|
||||
if(Globals.playerEntity != null && EntityUtils.getPosition(Globals.playerEntity).distance(realPos) > drawRadius){
|
||||
if(Globals.playerEntity != null && EntityUtils.getPosition(Globals.playerEntity).distance(realPos) > drawFullModelRadius){
|
||||
cellsToRemove.add(cell);
|
||||
}
|
||||
}
|
||||
@ -290,7 +345,7 @@ public class DrawCellManager {
|
||||
undrawable.remove(key);
|
||||
updateable.remove(key);
|
||||
keyCellMap.remove(key);
|
||||
hasRequested.remove(key);
|
||||
requested.remove(key);
|
||||
cell.destroy();
|
||||
}
|
||||
}
|
||||
@ -301,9 +356,9 @@ public class DrawCellManager {
|
||||
private void queueNewCells(){
|
||||
if(Globals.playerEntity != null && Globals.clientWorldData != null){
|
||||
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
|
||||
for(int x = -(int)drawRadius; x < drawRadius; x = x + ChunkData.CHUNK_SIZE){
|
||||
for(int y = -(int)drawRadius; y < drawRadius; y = y + ChunkData.CHUNK_SIZE){
|
||||
for(int z = -(int)drawRadius; z < drawRadius; z = z + ChunkData.CHUNK_SIZE){
|
||||
for(int x = -(int)drawFullModelRadius; x < drawFullModelRadius; x = x + ChunkData.CHUNK_SIZE){
|
||||
for(int y = -(int)drawFullModelRadius; y < drawFullModelRadius; y = y + ChunkData.CHUNK_SIZE){
|
||||
for(int z = -(int)drawFullModelRadius; z < drawFullModelRadius; z = z + ChunkData.CHUNK_SIZE){
|
||||
Vector3d newPos = new Vector3d(playerPos.x + x, playerPos.y + y, playerPos.z + z);
|
||||
Vector3i worldPos = new Vector3i(
|
||||
Globals.clientWorldData.convertRealToChunkSpace(newPos.x),
|
||||
@ -316,7 +371,7 @@ public class DrawCellManager {
|
||||
Globals.clientWorldData.convertChunkToRealSpace(worldPos.z)
|
||||
);
|
||||
if(
|
||||
playerPos.distance(chunkRealSpace) < drawRadius &&
|
||||
playerPos.distance(chunkRealSpace) < drawFullModelRadius &&
|
||||
worldPos.x >= 0 &&
|
||||
worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() &&
|
||||
worldPos.y >= 0 &&
|
||||
@ -330,7 +385,7 @@ public class DrawCellManager {
|
||||
Globals.clientWorldData.convertRealToChunkSpace(chunkRealSpace.z)
|
||||
);
|
||||
if(!keyCellMap.containsKey(key) && !hasNotRequested.contains(key) && !undrawable.contains(key) && !drawable.contains(key) &&
|
||||
!hasRequested.contains(key)){
|
||||
!requested.contains(key)){
|
||||
hasNotRequested.add(key);
|
||||
}
|
||||
}
|
||||
@ -355,27 +410,20 @@ public class DrawCellManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a cell key into its constituent coordinates in array format.
|
||||
* @param cellKey The cell key to split
|
||||
* @return The coordinates in array format
|
||||
* Controls whether the client generates drawable chunks or just physics chunks (ie if running a headless client)
|
||||
* @param generate true to generate graphics, false otherwise
|
||||
*/
|
||||
// private int[] splitKeyToCoordinates(String cellKey){
|
||||
// int[] rVal = new int[3];
|
||||
// String[] components = cellKey.split("_");
|
||||
// for(int i = 0; i < 3; i++){
|
||||
// rVal[i] = Integer.parseInt(components[i]);
|
||||
// }
|
||||
// return rVal;
|
||||
// }
|
||||
|
||||
public boolean coordsInPhysicsSpace(int worldX, int worldY){
|
||||
return worldX <= cellX + physicsRadius && worldX >= cellX - physicsRadius && worldY <= cellY + physicsRadius && worldY >= cellY - physicsRadius;
|
||||
}
|
||||
|
||||
public void setGenerateDrawables(boolean generate){
|
||||
this.generateDrawables = generate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@ -428,11 +476,11 @@ public class DrawCellManager {
|
||||
|
||||
|
||||
/**
|
||||
* Gets the draw radius
|
||||
* @return the draw radius
|
||||
* Gets the radius within which full-detail models are drawn
|
||||
* @return the radius
|
||||
*/
|
||||
public double getDrawRadius(){
|
||||
return drawRadius;
|
||||
public double getDrawFullModelRadius(){
|
||||
return drawFullModelRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -444,6 +492,5 @@ public class DrawCellManager {
|
||||
|
||||
|
||||
|
||||
// public
|
||||
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ public class VoxelTextureAtlas {
|
||||
* @return the index in the atlas of the texture of the provided voxel type
|
||||
*/
|
||||
public int getVoxelTypeOffset(int voxelTypeId){
|
||||
return typeCoordMap.get(voxelTypeId);
|
||||
return typeCoordMap.containsKey(voxelTypeId) ? typeCoordMap.get(voxelTypeId) : -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
package electrosphere.client.terrain.editing;
|
||||
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import electrosphere.client.terrain.cache.ChunkData;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.net.parser.net.message.TerrainMessage;
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ import java.nio.IntBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@ -36,7 +35,7 @@ public class ClientTerrainManager {
|
||||
public static final int INTERPOLATION_RATIO = ServerTerrainManager.SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO;
|
||||
|
||||
//caches chunks from server
|
||||
static final int CACHE_SIZE = 50;
|
||||
static final int CACHE_SIZE = 500;
|
||||
|
||||
//used for caching the macro values
|
||||
ClientTerrainCache terrainCache;
|
||||
@ -55,6 +54,9 @@ public class ClientTerrainManager {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles messages that have been received from the server
|
||||
*/
|
||||
public void handleMessages(){
|
||||
List<TerrainMessage> bouncedMessages = new LinkedList<TerrainMessage>();
|
||||
for(TerrainMessage message : messageQueue){
|
||||
@ -99,14 +101,32 @@ public class ClientTerrainManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a terrain message to the queue of messages that this manager needs to process
|
||||
* @param message The message
|
||||
*/
|
||||
public void attachTerrainMessage(TerrainMessage message){
|
||||
messageQueue.add(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the terrain cache contains chunk data at a given world position
|
||||
* @param worldX the x position
|
||||
* @param worldY the y position
|
||||
* @param worldZ the z position
|
||||
* @return true if the data exists, false otherwise
|
||||
*/
|
||||
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
|
||||
return terrainCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the cache contains chunk data at a real-space coordinate
|
||||
* @param x the x coordinate
|
||||
* @param y the y coordinate
|
||||
* @param z the z coordinate
|
||||
* @return true if the cache contains the chunk data at the coordinate, false otherwise
|
||||
*/
|
||||
public boolean containsChunkDataAtRealPoint(double x, double y, double z){
|
||||
assert clientWorldData != null;
|
||||
return terrainCache.containsChunkDataAtWorldPoint(
|
||||
|
||||
@ -6,6 +6,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import org.joml.Quaterniond;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import electrosphere.audio.AudioUtils;
|
||||
import electrosphere.audio.VirtualAudioSource;
|
||||
@ -15,7 +16,9 @@ import electrosphere.client.fluid.cells.FluidCellManager;
|
||||
import electrosphere.client.foliagemanager.ClientFoliageManager;
|
||||
import electrosphere.client.sim.ClientSimulation;
|
||||
import electrosphere.client.targeting.crosshair.Crosshair;
|
||||
import electrosphere.client.terrain.cells.DrawCell;
|
||||
import electrosphere.client.terrain.cells.DrawCellManager;
|
||||
import electrosphere.client.terrain.cells.DrawCell.DrawCellFace;
|
||||
import electrosphere.collision.CollisionEngine;
|
||||
import electrosphere.controls.ControlHandler;
|
||||
import electrosphere.engine.Globals;
|
||||
@ -28,6 +31,7 @@ import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.btree.BehaviorTree;
|
||||
import electrosphere.entity.state.movement.ApplyRotationTree;
|
||||
import electrosphere.entity.types.camera.CameraEntityUtils;
|
||||
import electrosphere.entity.types.terrain.TerrainChunk;
|
||||
import electrosphere.entity.types.tree.ProceduralTree;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.menu.MenuGenerators;
|
||||
@ -36,8 +40,10 @@ import electrosphere.menu.WindowUtils;
|
||||
import electrosphere.menu.mainmenu.MenuGeneratorsMultiplayer;
|
||||
import electrosphere.net.NetUtils;
|
||||
import electrosphere.net.client.ClientNetworking;
|
||||
import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData;
|
||||
import electrosphere.renderer.ui.elements.Window;
|
||||
import electrosphere.server.datacell.EntityDataCellMapper;
|
||||
import electrosphere.server.terrain.manager.ServerTerrainChunk;
|
||||
import electrosphere.util.MathUtils;
|
||||
|
||||
public class ClientLoading {
|
||||
@ -182,7 +188,6 @@ public class ClientLoading {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Targeting crosshair
|
||||
*/
|
||||
|
||||
@ -2,6 +2,7 @@ package electrosphere.entity.types.terrain;
|
||||
|
||||
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;
|
||||
@ -11,6 +12,8 @@ import electrosphere.entity.EntityCreationUtils;
|
||||
import electrosphere.entity.EntityDataStrings;
|
||||
import electrosphere.entity.ServerEntityUtils;
|
||||
import electrosphere.renderer.meshgen.TerrainChunkModelGeneration;
|
||||
import electrosphere.renderer.meshgen.TransvoxelModelGeneration;
|
||||
import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData;
|
||||
import electrosphere.server.datacell.Realm;
|
||||
|
||||
/**
|
||||
@ -21,19 +24,23 @@ public class TerrainChunk {
|
||||
|
||||
/**
|
||||
* Creates a client terrain chunk based on weights and values provided
|
||||
* @param weights The terrain weights
|
||||
* @param values The values (block types)
|
||||
* @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
|
||||
* @return The terrain chunk entity
|
||||
*/
|
||||
public static Entity clientCreateTerrainChunkEntity(float[][][] weights, int[][][] values, int levelOfDetail, VoxelTextureAtlas atlas){
|
||||
public static Entity clientCreateTerrainChunkEntity(TransvoxelChunkData chunkData, int levelOfDetail, VoxelTextureAtlas atlas){
|
||||
|
||||
TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values);
|
||||
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);
|
||||
|
||||
Entity rVal = EntityCreationUtils.createClientSpatialEntity();
|
||||
EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);
|
||||
if(data.vertices.size() > 0 && levelOfDetail < 1){
|
||||
if(data.vertices.size() > 0 && levelOfDetail == DrawCell.FULL_DETAIL_LOD){
|
||||
PhysicsEntityUtils.clientAttachTerrainChunkRigidBody(rVal, data);
|
||||
}
|
||||
|
||||
@ -53,7 +60,7 @@ public class TerrainChunk {
|
||||
*/
|
||||
public static Entity serverCreateTerrainChunkEntity(Realm realm, Vector3d position, float[][][] weights, int[][][] values){
|
||||
|
||||
TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values);
|
||||
TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values, DrawCell.FULL_DETAIL_LOD);
|
||||
|
||||
Entity rVal = EntityCreationUtils.createServerEntity(realm, position);
|
||||
if(data.vertices.size() > 0){
|
||||
|
||||
@ -52,6 +52,9 @@ public class MenuGeneratorsLevelEditor {
|
||||
//is the voxel selection window open
|
||||
static boolean voxelWindowOpen = false;
|
||||
|
||||
//vertical offset from cursor position to spawn things at
|
||||
static final Vector3d cursorVerticalOffset = new Vector3d(0,0.05,0);
|
||||
|
||||
|
||||
/**
|
||||
* Creates the level editor side panel top view
|
||||
@ -172,7 +175,7 @@ public class MenuGeneratorsLevelEditor {
|
||||
Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(Globals.playerCamera));
|
||||
Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
|
||||
Realm realm = Globals.realmManager.getRealms().iterator().next();
|
||||
Vector3d cursorPos = realm.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0);
|
||||
Vector3d cursorPos = realm.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0).add(cursorVerticalOffset);
|
||||
CreatureUtils.serverSpawnBasicCreature(realm, cursorPos, data.getCreatureId(), null);
|
||||
return false;
|
||||
}}));
|
||||
@ -202,7 +205,7 @@ public class MenuGeneratorsLevelEditor {
|
||||
Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(Globals.playerCamera));
|
||||
Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
|
||||
Realm realm = Globals.realmManager.getRealms().iterator().next();
|
||||
Vector3d cursorPos = realm.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0);
|
||||
Vector3d cursorPos = realm.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0).add(cursorVerticalOffset);
|
||||
FoliageUtils.serverSpawnTreeFoliage(realm, cursorPos, data.getName(), new Random().nextLong());
|
||||
return false;
|
||||
}}));
|
||||
@ -232,7 +235,7 @@ public class MenuGeneratorsLevelEditor {
|
||||
Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(Globals.playerCamera));
|
||||
Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
|
||||
Realm realm = Globals.realmManager.getRealms().iterator().next();
|
||||
Vector3d cursorPos = realm.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0);
|
||||
Vector3d cursorPos = realm.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0).add(cursorVerticalOffset);
|
||||
ItemUtils.serverSpawnBasicItem(realm, cursorPos, item.getItemId());
|
||||
return false;
|
||||
}}));
|
||||
@ -263,7 +266,7 @@ public class MenuGeneratorsLevelEditor {
|
||||
Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(Globals.playerCamera));
|
||||
Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
|
||||
Realm realm = Globals.realmManager.getRealms().iterator().next();
|
||||
Vector3d cursorPos = realm.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0);
|
||||
Vector3d cursorPos = realm.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0).add(cursorVerticalOffset);
|
||||
ObjectUtils.serverSpawnBasicObject(realm, cursorPos, object.getObjectId());
|
||||
return false;
|
||||
}}));
|
||||
|
||||
@ -176,6 +176,16 @@ SYNCHRONIZATION_MESSAGE,
|
||||
rVal = TerrainMessage.parsesendChunkDataMessage(byteBuffer);
|
||||
}
|
||||
break;
|
||||
case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA:
|
||||
if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){
|
||||
rVal = TerrainMessage.parseRequestReducedChunkDataMessage(byteBuffer);
|
||||
}
|
||||
break;
|
||||
case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA:
|
||||
if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){
|
||||
rVal = TerrainMessage.parseSendReducedChunkDataMessage(byteBuffer);
|
||||
}
|
||||
break;
|
||||
case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA:
|
||||
if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){
|
||||
rVal = TerrainMessage.parseRequestFluidDataMessage(byteBuffer);
|
||||
|
||||
@ -16,6 +16,8 @@ public class TerrainMessage extends NetworkMessage {
|
||||
SPAWNPOSITION,
|
||||
REQUESTCHUNKDATA,
|
||||
SENDCHUNKDATA,
|
||||
REQUESTREDUCEDCHUNKDATA,
|
||||
SENDREDUCEDCHUNKDATA,
|
||||
REQUESTFLUIDDATA,
|
||||
SENDFLUIDDATA,
|
||||
UPDATEFLUIDDATA,
|
||||
@ -40,6 +42,7 @@ public class TerrainMessage extends NetworkMessage {
|
||||
double realLocationY;
|
||||
double realLocationZ;
|
||||
byte[] chunkData;
|
||||
int chunkResolution;
|
||||
float terrainWeight;
|
||||
int terrainValue;
|
||||
|
||||
@ -196,6 +199,14 @@ public class TerrainMessage extends NetworkMessage {
|
||||
this.chunkData = chunkData;
|
||||
}
|
||||
|
||||
public int getchunkResolution() {
|
||||
return chunkResolution;
|
||||
}
|
||||
|
||||
public void setchunkResolution(int chunkResolution) {
|
||||
this.chunkResolution = chunkResolution;
|
||||
}
|
||||
|
||||
public float getterrainWeight() {
|
||||
return terrainWeight;
|
||||
}
|
||||
@ -262,6 +273,14 @@ public class TerrainMessage extends NetworkMessage {
|
||||
}
|
||||
case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDCHUNKDATA:
|
||||
return TerrainMessage.canParsesendChunkDataMessage(byteBuffer);
|
||||
case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA:
|
||||
if(byteBuffer.getRemaining() >= TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA_SIZE){
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA:
|
||||
return TerrainMessage.canParseSendReducedChunkDataMessage(byteBuffer);
|
||||
case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA:
|
||||
if(byteBuffer.getRemaining() >= TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE){
|
||||
return true;
|
||||
@ -478,6 +497,79 @@ public class TerrainMessage extends NetworkMessage {
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public static TerrainMessage parseRequestReducedChunkDataMessage(CircularByteBuffer byteBuffer){
|
||||
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTREDUCEDCHUNKDATA);
|
||||
stripPacketHeader(byteBuffer);
|
||||
rVal.setworldX(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
|
||||
rVal.setworldY(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
|
||||
rVal.setworldZ(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
|
||||
rVal.setchunkResolution(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public static TerrainMessage constructRequestReducedChunkDataMessage(int worldX,int worldY,int worldZ,int chunkResolution){
|
||||
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTREDUCEDCHUNKDATA);
|
||||
rVal.setworldX(worldX);
|
||||
rVal.setworldY(worldY);
|
||||
rVal.setworldZ(worldZ);
|
||||
rVal.setchunkResolution(chunkResolution);
|
||||
rVal.serialize();
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public static boolean canParseSendReducedChunkDataMessage(CircularByteBuffer byteBuffer){
|
||||
int currentStreamLength = byteBuffer.getRemaining();
|
||||
List<Byte> temporaryByteQueue = new LinkedList();
|
||||
if(currentStreamLength < 6){
|
||||
return false;
|
||||
}
|
||||
if(currentStreamLength < 10){
|
||||
return false;
|
||||
}
|
||||
if(currentStreamLength < 14){
|
||||
return false;
|
||||
}
|
||||
if(currentStreamLength < 18){
|
||||
return false;
|
||||
}
|
||||
int chunkDataSize = 0;
|
||||
if(currentStreamLength < 22){
|
||||
return false;
|
||||
} else {
|
||||
temporaryByteQueue.add(byteBuffer.peek(18 + 0));
|
||||
temporaryByteQueue.add(byteBuffer.peek(18 + 1));
|
||||
temporaryByteQueue.add(byteBuffer.peek(18 + 2));
|
||||
temporaryByteQueue.add(byteBuffer.peek(18 + 3));
|
||||
chunkDataSize = ByteStreamUtils.popIntFromByteQueue(temporaryByteQueue);
|
||||
}
|
||||
if(currentStreamLength < 22 + chunkDataSize){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static TerrainMessage parseSendReducedChunkDataMessage(CircularByteBuffer byteBuffer){
|
||||
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDCHUNKDATA);
|
||||
stripPacketHeader(byteBuffer);
|
||||
rVal.setworldX(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
|
||||
rVal.setworldY(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
|
||||
rVal.setworldZ(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
|
||||
rVal.setchunkResolution(ByteStreamUtils.popIntFromByteQueue(byteBuffer));
|
||||
rVal.setchunkData(ByteStreamUtils.popByteArrayFromByteQueue(byteBuffer));
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public static TerrainMessage constructSendReducedChunkDataMessage(int worldX,int worldY,int worldZ,int chunkResolution,byte[] chunkData){
|
||||
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDCHUNKDATA);
|
||||
rVal.setworldX(worldX);
|
||||
rVal.setworldY(worldY);
|
||||
rVal.setworldZ(worldZ);
|
||||
rVal.setchunkResolution(chunkResolution);
|
||||
rVal.setchunkData(chunkData);
|
||||
rVal.serialize();
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public static TerrainMessage parseRequestFluidDataMessage(CircularByteBuffer byteBuffer){
|
||||
TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTFLUIDDATA);
|
||||
stripPacketHeader(byteBuffer);
|
||||
@ -807,6 +899,59 @@ public class TerrainMessage extends NetworkMessage {
|
||||
rawBytes[18+i] = chunkData[i];
|
||||
}
|
||||
break;
|
||||
case REQUESTREDUCEDCHUNKDATA:
|
||||
rawBytes = new byte[2+4+4+4+4];
|
||||
//message header
|
||||
rawBytes[0] = TypeBytes.MESSAGE_TYPE_TERRAIN;
|
||||
//entity messaage header
|
||||
rawBytes[1] = TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA;
|
||||
intValues = ByteStreamUtils.serializeIntToBytes(worldX);
|
||||
for(int i = 0; i < 4; i++){
|
||||
rawBytes[2+i] = intValues[i];
|
||||
}
|
||||
intValues = ByteStreamUtils.serializeIntToBytes(worldY);
|
||||
for(int i = 0; i < 4; i++){
|
||||
rawBytes[6+i] = intValues[i];
|
||||
}
|
||||
intValues = ByteStreamUtils.serializeIntToBytes(worldZ);
|
||||
for(int i = 0; i < 4; i++){
|
||||
rawBytes[10+i] = intValues[i];
|
||||
}
|
||||
intValues = ByteStreamUtils.serializeIntToBytes(chunkResolution);
|
||||
for(int i = 0; i < 4; i++){
|
||||
rawBytes[14+i] = intValues[i];
|
||||
}
|
||||
break;
|
||||
case SENDREDUCEDCHUNKDATA:
|
||||
rawBytes = new byte[2+4+4+4+4+4+chunkData.length];
|
||||
//message header
|
||||
rawBytes[0] = TypeBytes.MESSAGE_TYPE_TERRAIN;
|
||||
//entity messaage header
|
||||
rawBytes[1] = TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA;
|
||||
intValues = ByteStreamUtils.serializeIntToBytes(worldX);
|
||||
for(int i = 0; i < 4; i++){
|
||||
rawBytes[2+i] = intValues[i];
|
||||
}
|
||||
intValues = ByteStreamUtils.serializeIntToBytes(worldY);
|
||||
for(int i = 0; i < 4; i++){
|
||||
rawBytes[6+i] = intValues[i];
|
||||
}
|
||||
intValues = ByteStreamUtils.serializeIntToBytes(worldZ);
|
||||
for(int i = 0; i < 4; i++){
|
||||
rawBytes[10+i] = intValues[i];
|
||||
}
|
||||
intValues = ByteStreamUtils.serializeIntToBytes(chunkResolution);
|
||||
for(int i = 0; i < 4; i++){
|
||||
rawBytes[14+i] = intValues[i];
|
||||
}
|
||||
intValues = ByteStreamUtils.serializeIntToBytes(chunkData.length);
|
||||
for(int i = 0; i < 4; i++){
|
||||
rawBytes[18+i] = intValues[i];
|
||||
}
|
||||
for(int i = 0; i < chunkData.length; i++){
|
||||
rawBytes[22+i] = chunkData[i];
|
||||
}
|
||||
break;
|
||||
case REQUESTFLUIDDATA:
|
||||
rawBytes = new byte[2+4+4+4];
|
||||
//message header
|
||||
|
||||
@ -69,9 +69,11 @@ Message categories
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_SPAWNPOSITION = 5;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTCHUNKDATA = 6;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_SENDCHUNKDATA = 7;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA = 8;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA = 9;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_UPDATEFLUIDDATA = 10;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA = 8;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA = 9;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA = 10;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA = 11;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_UPDATEFLUIDDATA = 12;
|
||||
/*
|
||||
Terrain packet sizes
|
||||
*/
|
||||
@ -82,6 +84,7 @@ Message categories
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_REQUESTUSETERRAINPALETTE_SIZE = 38;
|
||||
public static final byte TERRAIN_MESSAGE_TYPE_SPAWNPOSITION_SIZE = 26;
|
||||
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
|
||||
|
||||
@ -1,23 +1,18 @@
|
||||
package electrosphere.renderer.meshgen;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector2f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
|
||||
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
|
||||
|
||||
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
|
||||
import electrosphere.engine.Globals;
|
||||
@ -25,14 +20,13 @@ import electrosphere.entity.types.terrain.TerrainChunkData;
|
||||
import electrosphere.renderer.model.Material;
|
||||
import electrosphere.renderer.model.Mesh;
|
||||
import electrosphere.renderer.model.Model;
|
||||
import electrosphere.renderer.shader.ShaderProgram;
|
||||
import electrosphere.server.terrain.manager.ServerTerrainChunk;
|
||||
|
||||
public class TerrainChunkModelGeneration {
|
||||
|
||||
//http://paulbourke.net/geometry/polygonise/
|
||||
|
||||
static int edgeTable[]={
|
||||
public static int edgeTable[]={
|
||||
0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
|
||||
0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
|
||||
0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
|
||||
@ -68,7 +62,7 @@ public class TerrainChunkModelGeneration {
|
||||
};
|
||||
|
||||
//256 by 16
|
||||
static int triTable[][] = {
|
||||
public static int triTable[][] = {
|
||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
@ -625,7 +619,7 @@ public class TerrainChunkModelGeneration {
|
||||
return new Vector3f(x,y,z);
|
||||
}
|
||||
|
||||
public static TerrainChunkData generateTerrainChunkData(float[][][] terrainGrid, int[][][] textureGrid){
|
||||
public static TerrainChunkData generateTerrainChunkData(float[][][] terrainGrid, int[][][] textureGrid, int lod){
|
||||
|
||||
// 5 6
|
||||
// +-------------+ +-----5-------+ ^ Y
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -24,13 +24,13 @@ public class RenderScreenPipeline implements RenderPipeline {
|
||||
//the leftover texture gets used to draw the screen framebuffer quad
|
||||
//which doesnt work
|
||||
openGLState.glActiveTexture(GL40.GL_TEXTURE0);
|
||||
GL40.glBindTexture(GL40.GL_TEXTURE_2D, 0);
|
||||
openGLState.glBindTexture(GL40.GL_TEXTURE_2D, 0);
|
||||
openGLState.glActiveTexture(GL40.GL_TEXTURE1);
|
||||
GL40.glBindTexture(GL40.GL_TEXTURE_2D, 0);
|
||||
openGLState.glBindTexture(GL40.GL_TEXTURE_2D, 0);
|
||||
openGLState.glActiveTexture(GL40.GL_TEXTURE2);
|
||||
GL40.glBindTexture(GL40.GL_TEXTURE_2D, 0);
|
||||
openGLState.glBindTexture(GL40.GL_TEXTURE_2D, 0);
|
||||
openGLState.glActiveTexture(GL40.GL_TEXTURE3);
|
||||
GL40.glBindTexture(GL40.GL_TEXTURE_2D, 0);
|
||||
openGLState.glBindTexture(GL40.GL_TEXTURE_2D, 0);
|
||||
openGLState.glActiveTexture(GL40.GL_TEXTURE0);
|
||||
|
||||
openGLState.glDepthTest(false);
|
||||
|
||||
@ -40,7 +40,7 @@ public class VolumeBufferPipeline implements RenderPipeline {
|
||||
|
||||
RenderingEngine.volumeDepthBackfaceFramebuffer.bind();
|
||||
GL40.glClear(GL40.GL_DEPTH_BUFFER_BIT);
|
||||
GL40.glActiveTexture(GL40.GL_TEXTURE0);
|
||||
openGLState.glActiveTexture(GL40.GL_TEXTURE0);
|
||||
// glBindTexture(GL_TEXTURE_2D, woodTexture);
|
||||
// renderScene(simpleDepthShader);
|
||||
|
||||
|
||||
@ -7,14 +7,12 @@ import org.joml.Vector3i;
|
||||
import electrosphere.engine.Globals;
|
||||
import electrosphere.entity.Entity;
|
||||
import electrosphere.entity.EntityUtils;
|
||||
import electrosphere.entity.ServerEntityUtils;
|
||||
import electrosphere.entity.types.creature.CreatureUtils;
|
||||
import electrosphere.entity.types.item.ItemUtils;
|
||||
import electrosphere.server.content.serialization.ContentSerialization;
|
||||
import electrosphere.server.content.serialization.EntitySerialization;
|
||||
import electrosphere.server.datacell.Realm;
|
||||
import electrosphere.server.datacell.ServerDataCell;
|
||||
import electrosphere.server.pathfinding.NavMeshUtils;
|
||||
import electrosphere.server.saves.SaveUtils;
|
||||
import electrosphere.util.FileUtils;
|
||||
|
||||
@ -54,7 +52,8 @@ public class ServerContentManager {
|
||||
hydrateRawContent(realm,cell,contentRaw);
|
||||
} else {
|
||||
//else create from scratch
|
||||
EnvironmentGenerator.generateForest(realm, cell, worldPos, 0);
|
||||
//UNCOMMENT THIS WHEN YOU WANT CONTENT GENERATED FOR WORLDS AGAIN
|
||||
// EnvironmentGenerator.generateForest(realm, cell, worldPos, 0);
|
||||
}
|
||||
} else {
|
||||
//just because content wasn't generated doesn't mean there isn't data saved under that key
|
||||
|
||||
Loading…
Reference in New Issue
Block a user