diff --git a/assets/Shaders/fluid2/fluid2.fs b/assets/Shaders/fluid2/fluid2.fs new file mode 100644 index 00000000..1e59f1a4 --- /dev/null +++ b/assets/Shaders/fluid2/fluid2.fs @@ -0,0 +1,208 @@ +#version 330 core + +#define NR_POINT_LIGHTS 10 + +out vec4 FragColor; + + +layout (std140) uniform Lights { + // this is how many because we have to align + // bytes it SHOULD in multiples of 16, this + // take it where it ACTUALLY is + // + //refer: https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL + // + // base alignment aligned offset + //direct light + vec3 dLDirection; // 16 0 + vec3 dLAmbient; // 16 16 + vec3 dLDiffuse; // 16 32 + vec3 dLSpecular; // 16 48 + + //point light + vec3 pLposition[NR_POINT_LIGHTS]; // 16*10 64 + float pLconstant[NR_POINT_LIGHTS]; // 16*10 224 + float pLlinear[NR_POINT_LIGHTS]; // 16*10 384 + float pLquadratic[NR_POINT_LIGHTS]; // 16*10 544 + vec3 pLambient[NR_POINT_LIGHTS]; // 16*10 704 + vec3 pLdiffuse[NR_POINT_LIGHTS]; // 16*10 864 + vec3 pLspecular[NR_POINT_LIGHTS]; // 16*10 1024 + + //for a total size of 1184 + +}; + +struct Material { + sampler2D diffuse; + sampler2D specular; + float shininess; +}; + +in vec3 FragPos; +in vec3 Normal; +in vec2 texPlane1; +in vec2 texPlane2; +in vec2 texPlane3; +in vec4 FragPosLightSpace; + + +uniform vec3 viewPos; +// uniform DirLight dirLight; +// uniform PointLight pointLights[NR_POINT_LIGHTS]; +// uniform SpotLight spotLight; +uniform Material material; + +//texture stuff +// uniform sampler2D ourTexture; +uniform int hasTransparency; +// uniform sampler2D specularTexture; + +//light depth map +uniform sampler2D shadowMap; + + +// function prototypes +// vec3 CalcDirLight(vec3 normal, vec3 viewDir); +// vec3 CalcPointLight(int i, vec3 normal, vec3 fragPos, vec3 viewDir); +// vec3 CalcSpotLight(vec3 normal, vec3 fragPos, vec3 viewDir); +float calcLightIntensityTotal(vec3 normal); +float ShadowCalculation(vec4 fragPosLightSpace, vec3 lightDir, vec3 normal); +vec3 getColor(vec2 texPlane1, vec2 texPlane2, vec2 texPlane3, vec3 normal, Material material); + +void main(){ + vec3 norm = normalize(Normal); + vec3 viewDir = normalize(viewPos - FragPos); + + //grab light intensity + float lightIntensity = calcLightIntensityTotal(norm); + + //get color of base texture + vec3 textureColor = getColor(texPlane1, texPlane2, texPlane3, norm, material); + + //shadow + float shadow = ShadowCalculation(FragPosLightSpace, normalize(-dLDirection), norm); + + //calculate final color + vec3 finalColor = textureColor * lightIntensity * max(shadow,0.4); + // vec3 lightAmount = CalcDirLight(norm, viewDir); + // for(int i = 0; i < NR_POINT_LIGHTS; i++){ + // lightAmount += CalcPointLight(i, norm, FragPos, viewDir); + // } + + //this final calculation is for transparency + FragColor = vec4(finalColor, 1); +} + + +vec3 getColor(vec2 texPlane1, vec2 texPlane2, vec2 texPlane3, vec3 normal, Material material){ + + vec3 weights = abs(normal); + + vec3 albedoX = texture(material.diffuse, texPlane1).rgb; + vec3 albedoY = texture(material.diffuse, texPlane2).rgb; + vec3 albedoZ = texture(material.diffuse, texPlane3).rgb; + + + return (albedoX * weights.x + albedoY * weights.y + albedoZ * weights.z); +} + +// +float calcLightIntensityAmbient(){ + //calculate average of ambient light + float avg = (dLAmbient.x + dLAmbient.y + dLAmbient.z)/3.0; + return avg; +} + +// +float calcLightIntensityDir(vec3 normal){ + vec3 lightDir = normalize(-dLDirection); + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + + return diff; +} + +// +float calcLightIntensityTotal(vec3 normal){ + //ambient intensity + float ambientLightIntensity = calcLightIntensityAmbient(); + + //get direct intensity + float directLightIntensity = calcLightIntensityDir(normal); + + //sum + float total = ambientLightIntensity + directLightIntensity; + return total; +} + +// +vec3 getTotalLightColor(vec3 normal){ + //get the direct light color adjusted for intensity + vec3 diffuseLightColor = dLDiffuse * calcLightIntensityDir(normal); + + //sum light colors + vec3 totalLightColor = diffuseLightColor; + return totalLightColor; +} + +vec3 CalcPointLight(int i, vec3 normal, vec3 fragPos, vec3 viewDir){ + vec3 lightDir = normalize(pLposition[i] - fragPos); + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + // specular shading + // vec3 reflectDir = reflect(-lightDir, normal); + // float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); + // attenuation + float distance = length(pLposition[i] - fragPos); + float attenuation = 1.0 / (pLconstant[i] + pLlinear[i] * distance + pLquadratic[i] * (distance * distance)); + // combine results + vec3 ambient = pLambient[i]; + vec3 diffuse = pLdiffuse[i] * diff; + ambient *= attenuation; + diffuse *= attenuation; + // specular *= attenuation; + vec3 specular = vec3(0,0,0); + + vec3 finalValue = (ambient + diffuse + specular); + finalValue = vec3(max(finalValue.x,0),max(finalValue.y,0),max(finalValue.z,0)); + + return finalValue; +} + + +float ShadowCalculation(vec4 fragPosLightSpace, vec3 lightDir, vec3 normal){ + + // perform perspective divide + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + + //transform to NDC + projCoords = projCoords * 0.5 + 0.5; + + //get closest depth from light's POV + float closestDepth = texture(shadowMap, projCoords.xy).r; + + //get depth of current fragment + float currentDepth = projCoords.z; + + //calculate bias + float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); + + //calculate shadow value + float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; + + if(projCoords.z > 1.0){ + shadow = 0.0; + } + + //calculate dot product, if it is >0 we know they're parallel-ish therefore should disregard the shadow mapping + //ie the fragment is already facing away from the light source + float dotprod = dot(normalize(lightDir),normalize(normal)); + + if(dotprod > 0.0){ + shadow = 0.0; + } + + // shadow = currentDepth; + + return shadow; +} \ No newline at end of file diff --git a/assets/Shaders/fluid2/fluid2.vs b/assets/Shaders/fluid2/fluid2.vs new file mode 100644 index 00000000..25f6219c --- /dev/null +++ b/assets/Shaders/fluid2/fluid2.vs @@ -0,0 +1,62 @@ +//Vertex Shader +#version 330 core + +//defines +#define TEXTURE_MAP_SCALE 3.0 + + +//input buffers +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; +layout (location = 4) in vec2 aTex; + + +//coordinate space transformation matrices +uniform mat4 transform; +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; +uniform mat4 lightSpaceMatrix; + + + +//output buffers +out vec3 Normal; +out vec3 FragPos; +out vec2 texPlane1; +out vec2 texPlane2; +out vec2 texPlane3; +out vec4 FragPosLightSpace; + + + + +void main() { + //normalize posiiton and normal + vec4 FinalVertex = vec4(aPos, 1.0); + vec4 FinalNormal = vec4(aNormal, 1.0); + + + //push frag, normal, and texture positions to fragment shader + FragPos = vec3(model * FinalVertex); + Normal = mat3(transpose(inverse(model))) * aNormal; + + //reference https://catlikecoding.com/unity/tutorials/advanced-rendering/triplanar-mapping/ + texPlane1 = aPos.zy * TEXTURE_MAP_SCALE; + texPlane2 = aPos.xz * TEXTURE_MAP_SCALE; + texPlane3 = aPos.xy * TEXTURE_MAP_SCALE; + + //flip first coordinate if the normal is negative + //this minimizes texture flipping + texPlane1.x = texPlane1.x * sign(Normal.x); + texPlane2.x = texPlane2.x * sign(Normal.y); + texPlane3.x = texPlane3.x * sign(Normal.z); + + + //shadow map stuff + FragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0); + + + //set final position with opengl space + gl_Position = projection * view * model * FinalVertex; +} diff --git a/docs/src/highlevel-design/terraingeneration/terraingenerationideas.md b/docs/src/highlevel-design/terraingeneration/terraingenerationideas.md index 5531cfe2..1059c6f4 100644 --- a/docs/src/highlevel-design/terraingeneration/terraingenerationideas.md +++ b/docs/src/highlevel-design/terraingeneration/terraingenerationideas.md @@ -3,7 +3,7 @@ ``` generate 200 x 200 interpolate x 20 in each direction -this map will be 0.25 km resolution +this map will be 0.25 km resolution and ~62mb in size Useful for macro sim ``` diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 55cb5487..03cad141 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -196,6 +196,10 @@ Transvoxel Algorithm - Prebake all textures into atlas - Rewrite marching cubes shader to leverage this atlas +Another pass at grass + - Fix shader being camera position independent (if you move the wind moves with you lol) + - Multiple foliage models in same cell + Build a lod system - Could potentially be held at actor level - Link different models based on LOD level diff --git a/net/terrain.json b/net/terrain.json index 6cd55ebd..0c34fab0 100644 --- a/net/terrain.json +++ b/net/terrain.json @@ -184,6 +184,23 @@ "worldZ", "chunkData" ] + }, + { + "messageName" : "RequestFluidData", + "data" : [ + "worldX", + "worldY", + "worldZ" + ] + }, + { + "messageName" : "sendFluidData", + "data" : [ + "worldX", + "worldY", + "worldZ", + "chunkData" + ] } ] } diff --git a/src/main/java/electrosphere/client/culling/ClientEntityCullingManager.java b/src/main/java/electrosphere/client/culling/ClientEntityCullingManager.java index e685b7f7..f45cd0ea 100644 --- a/src/main/java/electrosphere/client/culling/ClientEntityCullingManager.java +++ b/src/main/java/electrosphere/client/culling/ClientEntityCullingManager.java @@ -11,6 +11,7 @@ import electrosphere.entity.EntityUtils; import electrosphere.entity.Scene; import electrosphere.entity.types.attach.AttachUtils; +@Deprecated public class ClientEntityCullingManager { Scene scene; @@ -40,38 +41,4 @@ public class ClientEntityCullingManager { target.putData(EntityDataStrings.DATA_STRING_DRAW, true); } - public void clearOutOfBoundsEntities(){ - if(Globals.commonWorldData != null && Globals.playerEntity != null && Globals.clientPlayerData != null){ - Vector3d playerCharacterPos = EntityUtils.getPosition(Globals.playerEntity); - int playerCharacterWorldX = Globals.clientWorldData.convertRealToWorld(playerCharacterPos.x); - int playerCharacterWorldY = Globals.clientWorldData.convertRealToWorld(playerCharacterPos.y); - int playerCharacterWorldZ = Globals.clientWorldData.convertRealToWorld(playerCharacterPos.z); - if( - playerCharacterWorldX != Globals.clientPlayerData.getWorldPos().x|| - playerCharacterWorldY != Globals.clientPlayerData.getWorldPos().y || - playerCharacterWorldZ != Globals.clientPlayerData.getWorldPos().z - ){ - for(Entity entity : scene.getEntityList()){ - if(entity.containsKey(EntityDataStrings.TERRAIN_IS_TERRAIN) || entity.containsKey(EntityDataStrings.ATTACH_PARENT) || entity.containsKey(EntityDataStrings.COLLISION_ENTITY_PARENT)){ - - } else { - Vector3d position = EntityUtils.getPosition(entity); - //common world data is initialized with the collision data - //if this is null then the engine hasn't fully started up yet - if(position != null){ - int worldX = Globals.clientWorldData.convertRealToWorld(position.x); - int worldZ = Globals.clientWorldData.convertRealToWorld(position.z); - if(!Globals.drawCellManager.coordsInPhysicsSpace(worldX, worldZ)){ - //we need to just hide the entity - recursiveHide(entity); - } else { - //if the entity is within range and it's not set to visible, make it visible - recursiveShow(entity); - } - } - } - } - } - } - } } diff --git a/src/main/java/electrosphere/client/fluid/cache/FluidChunkData.java b/src/main/java/electrosphere/client/fluid/cache/FluidChunkData.java index 3378228c..195016be 100644 --- a/src/main/java/electrosphere/client/fluid/cache/FluidChunkData.java +++ b/src/main/java/electrosphere/client/fluid/cache/FluidChunkData.java @@ -17,48 +17,19 @@ public class FluidChunkData { //The size of the data passed into marching cubes/transvoxel algorithm to get a fully connected and seamless chunk public static final int CHUNK_DATA_GENERATOR_SIZE = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE; - //What type of fluid is in this voxel, eg stone vs dirt vs grass, etc - int[][][] voxelType; //How much of that fluid type is in this voxel float[][][] voxelWeight; + //The velocities + float[][][] velocityX; + float[][][] velocityY; + float[][][] velocityZ; + //the list of positions modified since the last call to resetModifiedPositions //Used in DrawCell to keep track of which positions to invalidate Set modifiedSinceLastGeneration = new HashSet(); - /** - * Gets the voxel type array in this container - * @return The voxel type array - */ - public int[][][] getVoxelType(){ - return voxelType; - } - - /** - * Sets the voxel type array in this container - * @param voxelType The voxel type array - */ - public void setVoxelType(int[][][] voxelType){ - //mark changed cells - if(this.voxelType != null){ - for(int x = 0; x < CHUNK_SIZE; x++){ - for(int y = 0; y < CHUNK_SIZE; y++){ - for(int z = 0; z < CHUNK_SIZE; z++){ - if(voxelType[x][y][z] != this.voxelType[x][y][z]){ - String key = getVoxelPositionKey(new Vector3i(x,y,z)); - if(!modifiedSinceLastGeneration.contains(key)){ - modifiedSinceLastGeneration.add(key); - } - } - } - } - } - } - //update data - this.voxelType = voxelType; - } - /** * Gets the voxel weight array in this container * @return The voxel weight array @@ -91,23 +62,23 @@ public class FluidChunkData { this.voxelWeight = voxelWeight; } - /** - * Updates the value of a single voxel in the chunk - * @param localX The local position X - * @param localY The local position Y - * @param localZ The local position Z - * @param weight The weight to set it to - * @param type The type to set the voxel to - */ - public void updatePosition(int localX, int localY, int localZ, float weight, int type){ - voxelWeight[localX][localY][localZ] = weight; - voxelType[localX][localY][localZ] = type; - //store as modified in cache - String key = getVoxelPositionKey(new Vector3i(localX,localY,localZ)); - if(!modifiedSinceLastGeneration.contains(key)){ - modifiedSinceLastGeneration.add(key); - } - } + // /** + // * Updates the value of a single voxel in the chunk + // * @param localX The local position X + // * @param localY The local position Y + // * @param localZ The local position Z + // * @param weight The weight to set it to + // * @param type The type to set the voxel to + // */ + // public void updatePosition(int localX, int localY, int localZ, float weight, int type){ + // voxelWeight[localX][localY][localZ] = weight; + // voxelType[localX][localY][localZ] = type; + // //store as modified in cache + // String key = getVoxelPositionKey(new Vector3i(localX,localY,localZ)); + // if(!modifiedSinceLastGeneration.contains(key)){ + // modifiedSinceLastGeneration.add(key); + // } + // } /** * Gets the weight of a voxel at a poisiton @@ -119,12 +90,11 @@ public class FluidChunkData { } /** - * Gets the type of a voxel at a position - * @param localPosition The local position - * @return The type of the specified voxel + * Gets the weight of a voxel at a poisiton + * @return The weight of the specified voxel */ - public int getType(Vector3i localPosition){ - return voxelType[localPosition.x][localPosition.y][localPosition.z]; + public float getWeight(int x, int y, int z){ + return voxelWeight[z][y][z]; } /** diff --git a/src/main/java/electrosphere/client/fluid/cells/FluidCell.java b/src/main/java/electrosphere/client/fluid/cells/FluidCell.java new file mode 100644 index 00000000..049fe552 --- /dev/null +++ b/src/main/java/electrosphere/client/fluid/cells/FluidCell.java @@ -0,0 +1,240 @@ +package electrosphere.client.fluid.cells; + +import org.joml.Vector3d; +import org.joml.Vector3i; +import org.ode4j.ode.DBody; + +import electrosphere.client.fluid.cache.FluidChunkData; +import electrosphere.collision.CollisionEngine; +import electrosphere.engine.Globals; +import electrosphere.entity.ClientEntityUtils; +import electrosphere.entity.Entity; +import electrosphere.entity.EntityUtils; +import electrosphere.entity.types.fluid.FluidChunk; +import electrosphere.renderer.shader.ShaderProgram; +import electrosphere.renderer.texture.Texture; +import electrosphere.server.terrain.manager.ServerTerrainChunk; + +/** + * + * @author satellite + */ +public class FluidCell { + //the position of the draw cell in world coordinates + Vector3i worldPos; + + FluidChunkData data; + + Entity modelEntity; + + ShaderProgram program; + + DBody physicsObject; + + float[][][] weights = new float[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.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"); + } + + + FluidCell(){ + + } + + + /** + * Constructs a drawcell object + */ + public static FluidCell generateFluidCell( + Vector3i worldPos, + FluidChunkData data, + ShaderProgram program + ){ + FluidCell rVal = new FluidCell(); + rVal.worldPos = worldPos; + rVal.program = program; + rVal.data = data; + return rVal; + } + + /** + * Generates a drawable entity based on this chunk + */ + public void generateDrawableEntity(){ + if(modelEntity != null){ + Globals.clientScene.deregisterEntity(modelEntity); + } + + fillInData(); + + modelEntity = FluidChunk.clientCreateFluidChunkEntity(data.getVoxelWeight()); + + ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos()); + } + + protected Vector3d getRealPos(){ + return new Vector3d( + worldPos.x * FluidChunkData.CHUNK_SIZE, + worldPos.y * FluidChunkData.CHUNK_SIZE, + worldPos.z * FluidChunkData.CHUNK_SIZE + ); + } + + /** + * Destroys a drawcell including its physics + */ + public void destroy(){ + CollisionEngine collisionEngine = Globals.clientSceneWrapper.getCollisionEngine(); + collisionEngine.destroyEntityThatHasPhysics(modelEntity); + EntityUtils.cleanUpEntity(modelEntity); + } + + /** + * Gets the current chunk data for this draw cell + * @return The chunk data + */ + public FluidChunkData getData(){ + return data; + } + + /** + * Fills in the internal arrays of data for generate terrain models + */ + private void fillInData(){ + // + //fill in data + // + //main chunk + FluidChunkData currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos); + for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){ + for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){ + for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){ + weights[x][y][z] = currentChunk.getWeight(x,y,z); + } + } + } + //face X + if(worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize()){ + currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z); + if(currentChunk != null){ + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){ + weights[ServerTerrainChunk.CHUNK_DIMENSION][i][j] = currentChunk.getWeight(0, i, j); + } + } + } + } else { + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){ + weights[ServerTerrainChunk.CHUNK_DIMENSION][i][j] = -1; + } + } + } + //face Y + if(worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize()){ + currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y + 1, worldPos.z); + if(currentChunk != null){ + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){ + weights[i][ServerTerrainChunk.CHUNK_DIMENSION][j] = currentChunk.getWeight(i, 0, j); + } + } + } + } else { + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){ + weights[i][ServerTerrainChunk.CHUNK_DIMENSION][j] = -1; + } + } + } + //face Z + if(worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize()){ + currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z + 1); + if(currentChunk != null){ + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){ + weights[i][j][ServerTerrainChunk.CHUNK_DIMENSION] = currentChunk.getWeight(i, j, 0); + } + } + } + } else { + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + for(int j = 0; j < ServerTerrainChunk.CHUNK_DIMENSION; j++){ + weights[i][j][ServerTerrainChunk.CHUNK_DIMENSION] = -1; + } + } + } + //edge X-Y + if( + worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() && + worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() + ){ + currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y + 1, worldPos.z); + if(currentChunk != null){ + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + weights[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][i] = currentChunk.getWeight(0, 0, i); + } + } + } else { + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + weights[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][i] = -1; + } + } + //edge X-Z + if( + worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize() && + worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize() + ){ + currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z + 1); + if(currentChunk != null){ + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + weights[ServerTerrainChunk.CHUNK_DIMENSION][i][ServerTerrainChunk.CHUNK_DIMENSION] = currentChunk.getWeight(0, i, 0); + } + } + } else { + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + weights[ServerTerrainChunk.CHUNK_DIMENSION][i][ServerTerrainChunk.CHUNK_DIMENSION] = -1; + } + } + //edge Y-Z + if( + worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() && + worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize() + ){ + currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y + 1, worldPos.z + 1); + if(currentChunk != null){ + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + weights[i][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION] = currentChunk.getWeight(i, 0, 0); + } + } + } else { + for(int i = 0; i < ServerTerrainChunk.CHUNK_DIMENSION; i++){ + weights[i][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION] = -1; + } + } + if( + worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize() && + worldPos.y + 1 < Globals.clientWorldData.getWorldDiscreteSize() && + worldPos.z + 1 < Globals.clientWorldData.getWorldDiscreteSize() + ){ + currentChunk = Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y + 1, worldPos.z + 1); + if(currentChunk != null){ + if(currentChunk != null){ + weights[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION] = currentChunk.getWeight(0, 0, 0); + } + } + } else { + weights[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION] = -1; + } + } + +} diff --git a/src/main/java/electrosphere/client/fluid/cells/FluidDrawCellManager.java b/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java similarity index 94% rename from src/main/java/electrosphere/client/fluid/cells/FluidDrawCellManager.java rename to src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java index cd66a1cd..c7608c67 100644 --- a/src/main/java/electrosphere/client/fluid/cells/FluidDrawCellManager.java +++ b/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java @@ -19,7 +19,7 @@ import electrosphere.renderer.shader.ShaderProgram; * * @author satellite */ -public class FluidDrawCellManager { +public class FluidCellManager { //the center of this cell manager's array in cell space @@ -35,8 +35,8 @@ public class FluidDrawCellManager { int miniCellWidth; //all currently displaying mini cells - Set cells; - Map keyCellMap = new HashMap(); + Set cells; + Map keyCellMap = new HashMap(); Set hasNotRequested; Set hasRequested; Set drawable; @@ -59,8 +59,6 @@ public class FluidDrawCellManager { int worldBoundDiscreteMin = 0; int worldBoundDiscreteMax = 0; - //client terrain manager - // ClientTerrainManager clientTerrainManager; //ready to start updating? @@ -78,9 +76,9 @@ public class FluidDrawCellManager { * @param discreteX The initial discrete position X coordinate * @param discreteY The initial discrete position Y coordinate */ - public FluidDrawCellManager(ClientTerrainManager clientTerrainManager, int discreteX, int discreteY, int discreteZ){ + public FluidCellManager(ClientTerrainManager clientTerrainManager, int discreteX, int discreteY, int discreteZ){ worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / Globals.clientWorldData.getDynamicInterpolationRatio() * 1.0f); - cells = new HashSet(); + cells = new HashSet(); hasNotRequested = new HashSet(); drawable = new HashSet(); undrawable = new HashSet(); @@ -102,7 +100,7 @@ public class FluidDrawCellManager { update = true; } - FluidDrawCellManager(){ + FluidCellManager(){ } @@ -132,7 +130,7 @@ public class FluidDrawCellManager { ){ // if(!hasRequested.contains(targetKey)){ //client should request chunk data from server - Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage( + Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestFluidDataMessage( worldPos.x, worldPos.y, worldPos.z @@ -161,7 +159,7 @@ public class FluidDrawCellManager { worldPos.z < Globals.clientWorldData.getWorldDiscreteSize() ){ if(containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z)){ - FluidDrawCell cell = FluidDrawCell.generateFluidCell( + FluidCell cell = FluidCell.generateFluidCell( worldPos, Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z), program @@ -256,14 +254,14 @@ public class FluidDrawCellManager { * Clears all cells outside of draw radius */ private void clearOutOfBoundsCells(){ - Set cellsToRemove = new HashSet(); - for(FluidDrawCell cell : cells){ + Set cellsToRemove = new HashSet(); + for(FluidCell cell : cells){ Vector3d realPos = cell.getRealPos(); if(Globals.playerEntity != null && EntityUtils.getPosition(Globals.playerEntity).distance(realPos) > drawRadius){ cellsToRemove.add(cell); } } - for(FluidDrawCell cell : cellsToRemove){ + for(FluidCell cell : cellsToRemove){ cells.remove(cell); String key = getCellKey(cell.worldPos.x, cell.worldPos.y, cell.worldPos.z); hasNotRequested.remove(key); @@ -358,8 +356,8 @@ public class FluidDrawCellManager { } boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ - if(Globals.clientTerrainManager != null){ - return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ); + if(Globals.clientFluidManager != null){ + return Globals.clientFluidManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ); } return true; } diff --git a/src/main/java/electrosphere/client/fluid/cells/FluidDrawCell.java b/src/main/java/electrosphere/client/fluid/cells/FluidDrawCell.java deleted file mode 100644 index 0b2fd4bd..00000000 --- a/src/main/java/electrosphere/client/fluid/cells/FluidDrawCell.java +++ /dev/null @@ -1,103 +0,0 @@ -package electrosphere.client.fluid.cells; - -import org.joml.Vector3d; -import org.joml.Vector3i; -import org.ode4j.ode.DBody; - -import electrosphere.client.fluid.cache.FluidChunkData; -import electrosphere.collision.CollisionEngine; -import electrosphere.engine.Globals; -import electrosphere.entity.ClientEntityUtils; -import electrosphere.entity.Entity; -import electrosphere.entity.EntityUtils; -import electrosphere.entity.types.fluid.FluidChunk; -import electrosphere.renderer.shader.ShaderProgram; -import electrosphere.renderer.texture.Texture; - -/** - * - * @author satellite - */ -public class FluidDrawCell { - //the position of the draw cell in world coordinates - Vector3i worldPos; - - FluidChunkData data; - - Entity modelEntity; - - ShaderProgram program; - - DBody physicsObject; - - 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"); - } - - - FluidDrawCell(){ - - } - - - /** - * Constructs a drawcell object - */ - public static FluidDrawCell generateFluidCell( - Vector3i worldPos, - FluidChunkData data, - ShaderProgram program - ){ - FluidDrawCell rVal = new FluidDrawCell(); - rVal.worldPos = worldPos; - rVal.program = program; - rVal.data = data; - return rVal; - } - - /** - * Generates a drawable entity based on this chunk - */ - public void generateDrawableEntity(){ - if(modelEntity != null){ - Globals.clientScene.deregisterEntity(modelEntity); - } - modelEntity = FluidChunk.clientCreateFluidChunkEntity(data.getVoxelWeight(), data.getVoxelType()); - - ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos()); - } - - protected Vector3d getRealPos(){ - return new Vector3d( - worldPos.x * FluidChunkData.CHUNK_SIZE, - worldPos.y * FluidChunkData.CHUNK_SIZE, - worldPos.z * FluidChunkData.CHUNK_SIZE - ); - } - - /** - * Destroys a drawcell including its physics - */ - public void destroy(){ - CollisionEngine collisionEngine = Globals.clientSceneWrapper.getCollisionEngine(); - collisionEngine.destroyEntityThatHasPhysics(modelEntity); - EntityUtils.cleanUpEntity(modelEntity); - } - - /** - * Gets the current chunk data for this draw cell - * @return The chunk data - */ - public FluidChunkData getData(){ - return data; - } - -} diff --git a/src/main/java/electrosphere/client/fluid/manager/ClientFluidManager.java b/src/main/java/electrosphere/client/fluid/manager/ClientFluidManager.java index 4dc591a3..b8952cfd 100644 --- a/src/main/java/electrosphere/client/fluid/manager/ClientFluidManager.java +++ b/src/main/java/electrosphere/client/fluid/manager/ClientFluidManager.java @@ -57,29 +57,42 @@ public class ClientFluidManager { for(TerrainMessage message : messageQueue){ messageQueue.remove(message); switch(message.getMessageSubtype()){ - case SENDCHUNKDATA: { - int[][][] values = new int[FluidChunkData.CHUNK_DATA_GENERATOR_SIZE][FluidChunkData.CHUNK_DATA_GENERATOR_SIZE][FluidChunkData.CHUNK_DATA_GENERATOR_SIZE]; - float[][][] weights = new float[FluidChunkData.CHUNK_DATA_GENERATOR_SIZE][FluidChunkData.CHUNK_DATA_GENERATOR_SIZE][FluidChunkData.CHUNK_DATA_GENERATOR_SIZE]; + case SENDFLUIDDATA: { + float[][][] weights = new float[FluidChunkData.CHUNK_SIZE][FluidChunkData.CHUNK_SIZE][FluidChunkData.CHUNK_SIZE]; + float[][][] velocityX = new float[FluidChunkData.CHUNK_SIZE][FluidChunkData.CHUNK_SIZE][FluidChunkData.CHUNK_SIZE]; + float[][][] velocityY = new float[FluidChunkData.CHUNK_SIZE][FluidChunkData.CHUNK_SIZE][FluidChunkData.CHUNK_SIZE]; + float[][][] velocityZ = new float[FluidChunkData.CHUNK_SIZE][FluidChunkData.CHUNK_SIZE][FluidChunkData.CHUNK_SIZE]; ByteBuffer buffer = ByteBuffer.wrap(message.getchunkData()); FloatBuffer floatBuffer = buffer.asFloatBuffer(); - for(int x = 0; x < FluidChunkData.CHUNK_DATA_GENERATOR_SIZE; x++){ - for(int y = 0; y < FluidChunkData.CHUNK_DATA_GENERATOR_SIZE; y++){ - for(int z = 0; z < FluidChunkData.CHUNK_DATA_GENERATOR_SIZE; z++){ + for(int x = 0; x < FluidChunkData.CHUNK_SIZE; x++){ + for(int y = 0; y < FluidChunkData.CHUNK_SIZE; y++){ + for(int z = 0; z < FluidChunkData.CHUNK_SIZE; z++){ weights[x][y][z] = floatBuffer.get(); } } } - IntBuffer intView = buffer.asIntBuffer(); - intView.position(floatBuffer.position()); - for(int x = 0; x < FluidChunkData.CHUNK_DATA_GENERATOR_SIZE; x++){ - for(int y = 0; y < FluidChunkData.CHUNK_DATA_GENERATOR_SIZE; y++){ - for(int z = 0; z < FluidChunkData.CHUNK_DATA_GENERATOR_SIZE; z++){ - values[x][y][z] = intView.get(); + for(int x = 0; x < FluidChunkData.CHUNK_SIZE; x++){ + for(int y = 0; y < FluidChunkData.CHUNK_SIZE; y++){ + for(int z = 0; z < FluidChunkData.CHUNK_SIZE; z++){ + velocityX[x][y][z] = floatBuffer.get(); + } + } + } + for(int x = 0; x < FluidChunkData.CHUNK_SIZE; x++){ + for(int y = 0; y < FluidChunkData.CHUNK_SIZE; y++){ + for(int z = 0; z < FluidChunkData.CHUNK_SIZE; z++){ + velocityY[x][y][z] = floatBuffer.get(); + } + } + } + for(int x = 0; x < FluidChunkData.CHUNK_SIZE; x++){ + for(int y = 0; y < FluidChunkData.CHUNK_SIZE; y++){ + for(int z = 0; z < FluidChunkData.CHUNK_SIZE; z++){ + velocityZ[x][y][z] = floatBuffer.get(); } } } FluidChunkData data = new FluidChunkData(); - data.setVoxelType(values); data.setVoxelWeight(weights); fluidCache.addChunkDataToCache( message.getworldX(), message.getworldY(), message.getworldZ(), @@ -96,7 +109,7 @@ public class ClientFluidManager { } } - public void attachTerrainMessage(TerrainMessage message){ + public void attachFluidMessage(TerrainMessage message){ messageQueue.add(message); } diff --git a/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java b/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java index e7702935..f2f1bf8d 100644 --- a/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java +++ b/src/main/java/electrosphere/client/foliagemanager/ClientFoliageManager.java @@ -311,7 +311,6 @@ public class ClientFoliageManager { * @param voxelPos The voxel position */ private void createFoliageCell(Vector3i worldPos, Vector3i voxelPos, float initialGrowthLevel){ - System.out.println("Create foliage cell"); //get foliage types supported ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos); List foliageTypesSupported = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(data.getType(voxelPos)).getAmbientFoliage(); @@ -350,11 +349,6 @@ public class ClientFoliageManager { float height_20 = (float)(sample_20 != null ? sample_20.y : height_11); float height_21 = (float)(sample_21 != null ? sample_21.y : height_11); float height_22 = (float)(sample_22 != null ? sample_22.y : height_11); - if(worldPos.x == 0 && worldPos.z == 0 && voxelPos.x < 5 && voxelPos.z < 5){ - System.out.println(voxelPos); - System.out.println(height_11); - System.out.println(realPos); - } //each height is in real world coordinates that are absolute //when rendering, there's already a y offset for the center of the field of grass (based on the model matrix) //so when offseting the position of the blade of grass RELATIVE to the overall instance being drawn, need to subtract the real world coordinates of the overall instance @@ -375,15 +369,6 @@ public class ClientFoliageManager { double offsetZ = relativePositionOnGridZ; //determine quadrant we're placing in double offsetY = 0; - if(worldPos.x == 0 && worldPos.z == 0 && voxelPos.x < 5 && voxelPos.z < 5 && x == 26 && z == 26){ - offsetY = - height_11 * (1-relativePositionOnGridX) * (1-relativePositionOnGridZ) + - height_12 * (1-relativePositionOnGridX) * ( relativePositionOnGridZ) + - height_21 * ( relativePositionOnGridX) * (1-relativePositionOnGridZ) + - height_22 * ( relativePositionOnGridX) * ( relativePositionOnGridZ); - System.out.println(offsetY); - System.out.println((float)offsetY - (float)realPos.y); - } boolean addBlade = false; if(relativePositionOnGridX >=0){ if(relativePositionOnGridZ >= 0){ diff --git a/src/main/java/electrosphere/client/sim/ClientFunctions.java b/src/main/java/electrosphere/client/sim/ClientFunctions.java index f3cf4097..d6470bfd 100644 --- a/src/main/java/electrosphere/client/sim/ClientFunctions.java +++ b/src/main/java/electrosphere/client/sim/ClientFunctions.java @@ -1,5 +1,6 @@ package electrosphere.client.sim; +import electrosphere.client.fluid.manager.ClientFluidManager; import electrosphere.client.instancing.InstanceUpdater; import electrosphere.client.terrain.manager.ClientTerrainManager; import electrosphere.engine.Globals; @@ -27,6 +28,7 @@ public class ClientFunctions { public static void runClientFunctions(){ ClientTerrainManager.generateTerrainChunkGeometry(); + ClientFluidManager.generateFluidChunkGeometry(); updateSkyboxPos(); Globals.clientSceneWrapper.destroyEntitiesOutsideSimRange(); InstanceUpdater.updateInstancedActorPriority(); @@ -48,11 +50,15 @@ public class ClientFunctions { public static void loadTerrain(){ if(Globals.clientTerrainManager != null){ Globals.clientTerrainManager.handleMessages(); - updateCellManager(); + updateTerrainCellManager(); + } + if(Globals.clientFluidManager != null){ + Globals.clientFluidManager.handleMessages(); + updateFluidCellManager(); } } - static void updateCellManager(){ + static void updateTerrainCellManager(){ /// /// C L I E N T C E L L M A N A G E R /// @@ -65,4 +71,15 @@ public class ClientFunctions { Globals.drawCellManager.update(); } } + + static void updateFluidCellManager(){ + //fluid work + if(Globals.fluidCellManager != null && Globals.clientWorldData != null){ + if(Globals.playerEntity != null){ + newPlayerCharacterPosition = EntityUtils.getPosition(Globals.playerEntity); + } + Globals.fluidCellManager.calculateDeltas(newPlayerCharacterPosition); + Globals.fluidCellManager.update(); + } + } } diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 20d4213e..c8871600 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -12,6 +12,7 @@ import electrosphere.audio.AudioEngine; import electrosphere.audio.VirtualAudioSourceManager; import electrosphere.auth.AuthenticationManager; import electrosphere.client.culling.ClientEntityCullingManager; +import electrosphere.client.fluid.cells.FluidCellManager; import electrosphere.client.fluid.manager.ClientFluidManager; import electrosphere.client.foliagemanager.ClientFoliageManager; import electrosphere.client.player.ClientPlayerData; @@ -52,6 +53,7 @@ import electrosphere.renderer.actor.instance.InstanceManager; import electrosphere.renderer.light.PointLight; import electrosphere.renderer.light.SpotLight; import electrosphere.renderer.loading.ModelPretransforms; +import electrosphere.renderer.meshgen.FluidChunkModelGeneration; import electrosphere.renderer.meshgen.TerrainChunkModelGeneration; import electrosphere.renderer.model.Material; import electrosphere.renderer.shader.ShaderOptionMap; @@ -66,6 +68,7 @@ import electrosphere.server.content.ServerContentManager; import electrosphere.server.datacell.EntityDataCellMapper; import electrosphere.server.datacell.RealmManager; import electrosphere.server.db.DatabaseController; +import electrosphere.server.fluid.manager.ServerFluidManager; import electrosphere.server.pathfinding.NavMeshManager; import electrosphere.server.simulation.MacroSimulation; import electrosphere.server.simulation.MicroSimulation; @@ -254,6 +257,12 @@ public class Globals { //terrain manager // public static boolean LOAD_TERRAIN = true; public static ServerTerrainManager serverTerrainManager; + + + //fluid manager + public static ServerFluidManager serverFluidManager; + + //spawn point public static Vector3d spawnPoint = new Vector3d(0,0,0); //content manager @@ -304,8 +313,11 @@ public class Globals { public static ClientPlayerData clientPlayerData = new ClientPlayerData(); //chunk stuff - //constant for how far in game units you have to move to load chunks + //draw cell manager public static DrawCellManager drawCellManager; + + //fluid cell manager + public static FluidCellManager fluidCellManager; //navmesh manager public static NavMeshManager navMeshManager; @@ -413,6 +425,8 @@ public class Globals { navMeshManager = new NavMeshManager(); //terrain Globals.clientTerrainManager = new ClientTerrainManager(); + //fluid + Globals.clientFluidManager = new ClientFluidManager(); //game config gameConfigDefault = electrosphere.game.data.Config.loadDefaultConfig(); gameConfigCurrent = gameConfigDefault; @@ -471,8 +485,7 @@ public class Globals { terrainShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/terrain2/terrain2.vs", "/Shaders/terrain2/terrain2.fs"); TerrainChunkModelGeneration.terrainChunkShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/terrain2/terrain2.vs", "/Shaders/terrain2/terrain2.fs"); //init fluid shader program - terrainShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/fluid1/fluid1.vs", "/Shaders/fluid1/fluid1.fs"); - TerrainChunkModelGeneration.terrainChunkShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/fluid1/fluid1.vs", "/Shaders/fluid1/fluid1.fs"); + FluidChunkModelGeneration.fluidChunkShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/fluid2/fluid2.vs", "/Shaders/fluid2/fluid2.fs"); //init models assetManager.addModelPathToQueue("Models/unitsphere.fbx"); assetManager.addModelPathToQueue("Models/unitsphere_1.fbx"); diff --git a/src/main/java/electrosphere/engine/loadingthreads/ArenaLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ArenaLoading.java index fcf58d1b..6a5c80c6 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/ArenaLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/ArenaLoading.java @@ -15,6 +15,7 @@ import electrosphere.entity.ServerEntityUtils; import electrosphere.game.server.world.ServerWorldData; import electrosphere.logger.LoggerInterface; import electrosphere.server.content.ServerContentManager; +import electrosphere.server.fluid.manager.ServerFluidManager; import electrosphere.server.saves.SaveUtils; import electrosphere.server.terrain.manager.ServerTerrainManager; import electrosphere.util.FileUtils; @@ -56,6 +57,7 @@ public class ArenaLoading { private static void initServerArenaTerrainManager(){ Globals.serverTerrainManager = ServerTerrainManager.constructArenaTerrainManager(); + Globals.serverFluidManager = ServerFluidManager.constructArenaFluidManager(); } private static void initServerArenaWorldData(){ diff --git a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java index da575035..a83dbdc1 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java @@ -11,6 +11,7 @@ import electrosphere.audio.AudioUtils; import electrosphere.audio.VirtualAudioSource; import electrosphere.audio.VirtualAudioSourceManager.VirtualAudioSourceType; import electrosphere.client.culling.ClientEntityCullingManager; +import electrosphere.client.fluid.cells.FluidCellManager; import electrosphere.client.foliagemanager.ClientFoliageManager; import electrosphere.client.sim.ClientSimulation; import electrosphere.client.targeting.crosshair.Crosshair; @@ -93,6 +94,8 @@ public class ClientLoading { initFoliageManager(); //initialize the cell manager (client) initDrawCellManager(); + //init the fluid cell manager + initFluidCellManager(); //initialize the basic graphical entities of the world (skybox, camera) initWorldBaseGraphicalEntities(); //init arena specific stuff (ie different skybox colors) @@ -290,6 +293,41 @@ public class ClientLoading { // System.out.println("Draw Cell Manager ready"); } + static void initFluidCellManager(){ + while(Globals.clientWorldData == null){ + try { + TimeUnit.MILLISECONDS.sleep(10); + } catch (InterruptedException ex) { + } + } + //initialize draw cell manager + Globals.fluidCellManager = new FluidCellManager(Globals.clientTerrainManager, 0, 0, 0); + //set our draw cell manager to actually generate drawable chunks + Globals.fluidCellManager.setGenerateDrawables(true); + //Alerts the client simulation that it should start loading terrain + Globals.clientSimulation.setLoadingTerrain(true); + //wait for all the terrain data to arrive + while(Globals.fluidCellManager.containsUnrequestedCell()){ +// Globals.drawCellManager.updateInvalidCell(); + try { + TimeUnit.MILLISECONDS.sleep(10); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } +// System.out.println("invalid cell"); + } + + while(Globals.fluidCellManager.containsUndrawableCell()){ +// Globals.drawCellManager.makeCellDrawable(); + try { + TimeUnit.MILLISECONDS.sleep(10); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } +// System.out.println("undrawable"); + } + } + /** * Starts up the entity culling manager */ diff --git a/src/main/java/electrosphere/entity/types/fluid/FluidChunk.java b/src/main/java/electrosphere/entity/types/fluid/FluidChunk.java index 389ea07b..714bd6c3 100644 --- a/src/main/java/electrosphere/entity/types/fluid/FluidChunk.java +++ b/src/main/java/electrosphere/entity/types/fluid/FluidChunk.java @@ -17,9 +17,9 @@ public class FluidChunk { * @param values The values (block types) * @return The fluid chunk entity */ - public static Entity clientCreateFluidChunkEntity(float[][][] weights, int[][][] values){ + public static Entity clientCreateFluidChunkEntity(float[][][] weights){ - FluidChunkModelData data = FluidChunkModelGeneration.generateFluidChunkData(weights, values); + FluidChunkModelData data = FluidChunkModelGeneration.generateFluidChunkData(weights); String modelPath = ClientFluidManager.queueFluidGridGeneration(data); Entity rVal = EntityCreationUtils.createClientSpatialEntity(); diff --git a/src/main/java/electrosphere/logger/LoggerInterface.java b/src/main/java/electrosphere/logger/LoggerInterface.java index ffe3f69d..bd343cda 100644 --- a/src/main/java/electrosphere/logger/LoggerInterface.java +++ b/src/main/java/electrosphere/logger/LoggerInterface.java @@ -22,7 +22,7 @@ public class LoggerInterface { public static void initLoggers(){ loggerStartup = new Logger(LogLevel.WARNING); - loggerNetworking = new Logger(LogLevel.WARNING); + loggerNetworking = new Logger(LogLevel.DEBUG); loggerFileIO = new Logger(LogLevel.WARNING); loggerGameLogic = new Logger(LogLevel.WARNING); loggerRenderer = new Logger(LogLevel.WARNING); diff --git a/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java b/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java index 8cfdaf78..bf973373 100644 --- a/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java +++ b/src/main/java/electrosphere/net/client/protocol/TerrainProtocol.java @@ -3,6 +3,7 @@ package electrosphere.net.client.protocol; import org.joml.Vector3f; +import electrosphere.client.fluid.cache.FluidChunkData; import electrosphere.client.scene.ClientWorldData; import electrosphere.client.terrain.cache.ChunkData; import electrosphere.collision.CollisionWorldData; @@ -49,6 +50,9 @@ public class TerrainProtocol { } } } break; + case SENDFLUIDDATA: { + Globals.clientFluidManager.attachFluidMessage(message); + } break; default: LoggerInterface.loggerNetworking.WARNING("Client networking: Unhandled message of type: " + message.getMessageSubtype()); break; 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 dd383b46..d4c06a5d 100644 --- a/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java @@ -236,6 +236,16 @@ SYNCHRONIZATION_MESSAGE, rVal = TerrainMessage.parsesendChunkDataMessage(byteBuffer); } break; + case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA: + if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){ + rVal = TerrainMessage.parseRequestFluidDataMessage(byteBuffer); + } + break; + case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA: + if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){ + rVal = TerrainMessage.parsesendFluidDataMessage(byteBuffer); + } + break; } break; case TypeBytes.MESSAGE_TYPE_SERVER: 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 854dffb9..5f2a0e4e 100644 --- a/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java @@ -17,6 +17,8 @@ public class TerrainMessage extends NetworkMessage { SPAWNPOSITION, REQUESTCHUNKDATA, SENDCHUNKDATA, + REQUESTFLUIDDATA, + SENDFLUIDDATA, } TerrainMessageType messageType; @@ -266,6 +268,14 @@ public class TerrainMessage extends NetworkMessage { } case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDCHUNKDATA: return TerrainMessage.canParsesendChunkDataMessage(byteBuffer); + case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA: + if(byteBuffer.getRemaining() >= TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE){ + return true; + } else { + return false; + } + case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA: + return TerrainMessage.canParsesendFluidDataMessage(byteBuffer); } return false; } @@ -488,6 +498,72 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + public static TerrainMessage parseRequestFluidDataMessage(CircularByteBuffer byteBuffer){ + TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTFLUIDDATA); + stripPacketHeader(byteBuffer); + rVal.setworldX(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setworldY(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setworldZ(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + return rVal; + } + + public static TerrainMessage constructRequestFluidDataMessage(int worldX,int worldY,int worldZ){ + TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTFLUIDDATA); + rVal.setworldX(worldX); + rVal.setworldY(worldY); + rVal.setworldZ(worldZ); + rVal.serialize(); + return rVal; + } + + public static boolean canParsesendFluidDataMessage(CircularByteBuffer byteBuffer){ + int currentStreamLength = byteBuffer.getRemaining(); + List temporaryByteQueue = new LinkedList(); + if(currentStreamLength < 6){ + return false; + } + if(currentStreamLength < 10){ + return false; + } + if(currentStreamLength < 14){ + return false; + } + int chunkDataSize = 0; + if(currentStreamLength < 18){ + return false; + } else { + temporaryByteQueue.add(byteBuffer.peek(14 + 0)); + temporaryByteQueue.add(byteBuffer.peek(14 + 1)); + temporaryByteQueue.add(byteBuffer.peek(14 + 2)); + temporaryByteQueue.add(byteBuffer.peek(14 + 3)); + chunkDataSize = ByteStreamUtils.popIntFromByteQueue(temporaryByteQueue); + } + if(currentStreamLength < 18 + chunkDataSize){ + return false; + } + return true; + } + + public static TerrainMessage parsesendFluidDataMessage(CircularByteBuffer byteBuffer){ + TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDFLUIDDATA); + stripPacketHeader(byteBuffer); + rVal.setworldX(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setworldY(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setworldZ(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setchunkData(ByteStreamUtils.popByteArrayFromByteQueue(byteBuffer)); + return rVal; + } + + public static TerrainMessage constructsendFluidDataMessage(int worldX,int worldY,int worldZ,byte[] chunkData){ + TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDFLUIDDATA); + rVal.setworldX(worldX); + rVal.setworldY(worldY); + rVal.setworldZ(worldZ); + rVal.setchunkData(chunkData); + rVal.serialize(); + return rVal; + } + @Override void serialize(){ byte[] intValues = new byte[8]; @@ -718,6 +794,51 @@ public class TerrainMessage extends NetworkMessage { rawBytes[18+i] = chunkData[i]; } break; + case REQUESTFLUIDDATA: + rawBytes = new byte[2+4+4+4]; + //message header + rawBytes[0] = TypeBytes.MESSAGE_TYPE_TERRAIN; + //entity messaage header + rawBytes[1] = TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA; + 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]; + } + break; + case SENDFLUIDDATA: + rawBytes = new byte[2+4+4+4+4+chunkData.length]; + //message header + rawBytes[0] = TypeBytes.MESSAGE_TYPE_TERRAIN; + //entity messaage header + rawBytes[1] = TypeBytes.TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA; + 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(chunkData.length); + for(int i = 0; i < 4; i++){ + rawBytes[14+i] = intValues[i]; + } + for(int i = 0; i < chunkData.length; i++){ + rawBytes[18+i] = chunkData[i]; + } + break; } serialized = true; } 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 cbedff68..9dfacff9 100644 --- a/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java +++ b/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java @@ -88,6 +88,8 @@ Message categories public static final byte TERRAIN_MESSAGE_TYPE_SPAWNPOSITION = 6; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTCHUNKDATA = 7; public static final byte TERRAIN_MESSAGE_TYPE_SENDCHUNKDATA = 8; + public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA = 9; + public static final byte TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA = 10; /* Terrain packet sizes */ @@ -99,6 +101,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_REQUESTFLUIDDATA_SIZE = 14; /* Server subcategories */ 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 146e1a81..325a7302 100644 --- a/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java +++ b/src/main/java/electrosphere/net/parser/net/raw/NetworkParser.java @@ -1,7 +1,6 @@ -package electrosphere.net.parser.net.raw; - -import electrosphere.logger.LoggerInterface; -import electrosphere.net.parser.net.message.NetworkMessage; +package electrosphere.net.parser.net.raw; + +import electrosphere.net.parser.net.message.NetworkMessage; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -52,7 +51,7 @@ public class NetworkParser { } } catch (IOException ex) { ex.printStackTrace(); - LoggerInterface.loggerNetworking.ERROR("", ex); + System.exit(0); } } diff --git a/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java b/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java index e0393c80..e3ac1433 100644 --- a/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java @@ -13,6 +13,7 @@ import electrosphere.net.server.Server; import electrosphere.net.server.ServerConnectionHandler; import electrosphere.net.server.player.Player; import electrosphere.server.datacell.Realm; +import electrosphere.server.fluid.manager.ServerFluidChunk; import electrosphere.server.terrain.editing.TerrainEditing; import electrosphere.server.terrain.manager.ServerTerrainChunk; import electrosphere.server.terrain.models.TerrainModification; @@ -38,11 +39,17 @@ public class TerrainProtocol { case REQUESTUSETERRAINPALETTE: { attemptUseTerrainEditPalette(connectionHandler, message); } break; + case REQUESTFLUIDDATA: { + sendWorldFluidSubChunk(connectionHandler, + message.getworldX(), message.getworldY(), message.getworldZ() + ); + } break; //all ignored message types case RESPONSEMETADATA: case SPAWNPOSITION: case UPDATEVOXEL: case SENDCHUNKDATA: + case SENDFLUIDDATA: //silently ignore break; } @@ -194,6 +201,64 @@ public class TerrainProtocol { // } } + + static void sendWorldFluidSubChunk(ServerConnectionHandler connectionHandler, int worldX, int worldY, int worldZ){ + + // System.out.println("Received request for chunk " + message.getworldX() + " " + message.getworldY()); + + ServerFluidChunk chunk = Globals.serverFluidManager.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 + //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; + + ByteBuffer buffer = ByteBuffer.allocate(xWidth*yWidth*zWidth*(4+4+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]); + } + } + } + + for(int x = 0; x < xWidth; x++){ + for(int y = 0; y < yWidth; y++){ + for(int z = 0; z < zWidth; z++){ + floatView.put(chunk.getVelocityX()[x][y][z]); + } + } + } + + for(int x = 0; x < xWidth; x++){ + for(int y = 0; y < yWidth; y++){ + for(int z = 0; z < zWidth; z++){ + floatView.put(chunk.getVelocityY()[x][y][z]); + } + } + } + + for(int x = 0; x < xWidth; x++){ + for(int y = 0; y < yWidth; y++){ + for(int z = 0; z < zWidth; z++){ + floatView.put(chunk.getVelocityZ()[x][y][z]); + } + } + } + + + connectionHandler.addMessagetoOutgoingQueue(TerrainMessage.constructsendFluidDataMessage(worldX, worldY, worldZ, buffer.array())); + + } + static void sendWorldMetadata(ServerConnectionHandler connectionHandler){ //world metadata connectionHandler.addMessagetoOutgoingQueue( diff --git a/src/main/java/electrosphere/renderer/meshgen/FluidChunkModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/FluidChunkModelGeneration.java index a4fe24f8..dc952ce4 100644 --- a/src/main/java/electrosphere/renderer/meshgen/FluidChunkModelGeneration.java +++ b/src/main/java/electrosphere/renderer/meshgen/FluidChunkModelGeneration.java @@ -17,6 +17,7 @@ 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; import static org.lwjgl.opengl.GL30.glBindVertexArray; import static org.lwjgl.opengl.GL30.glGenVertexArrays; @@ -559,7 +560,7 @@ public class FluidChunkModelGeneration { return new Vector3f(x,y,z); } - public static FluidChunkModelData generateFluidChunkData(float[][][] weightGrid, int[][][] typeGrid){ + public static FluidChunkModelData generateFluidChunkData(float[][][] weightGrid){ // 5 6 // +-------------+ +-----5-------+ ^ Y @@ -593,6 +594,9 @@ public class FluidChunkModelGeneration { for(int x = 0; x < weightGrid.length - 1; x++){ for(int y = 0; y < weightGrid[0].length - 1; y++){ for(int z = 0; z < weightGrid[0][0].length - 1; z++){ + if(x == 3 && y == 3 && z == 3){ + System.out.println("erihjy at 3,3,3 " + weightGrid[x][y][z]); + } //push the current cell's values into the gridcell currentCell.setValues( new Vector3f(x+0,y+0,z+0), new Vector3f(x+0,y+0,z+1), new Vector3f(x+1,y+0,z+1), new Vector3f(x+1,y+0,z+0), @@ -665,6 +669,10 @@ public class FluidChunkModelGeneration { UVs.add(temp[1]); } + System.out.println("weight at 3 3 3 " + weightGrid[3][3][3]); + + System.out.println("Fluid verts: " + vertsFlat.size()); + //List vertices, List normals, List faceElements, List uvs FluidChunkModelData rVal = new FluidChunkModelData(vertsFlat, normalsFlat, elementsFlat, UVs); return rVal; @@ -768,7 +776,13 @@ public class FluidChunkModelGeneration { ex.printStackTrace(); } - + float halfChunk = ServerTerrainChunk.CHUNK_DIMENSION / 2.0f; + mesh.updateBoundingSphere( + halfChunk, + halfChunk, + halfChunk, + (float)Math.sqrt(halfChunk * halfChunk + halfChunk * halfChunk + halfChunk * halfChunk) + ); glBindVertexArray(0); @@ -796,6 +810,7 @@ public class FluidChunkModelGeneration { m.setParent(rVal); rVal.getMeshes().add(m); + rVal.setBoundingSphere(m.getBoundingSphere()); return rVal; } diff --git a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java index f3d87a4c..e2a2e3ca 100644 --- a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java +++ b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java @@ -599,6 +599,9 @@ public class TerrainChunkModelGeneration { for(int x = 0; x < terrainGrid.length - 1; x++){ for(int y = 0; y < terrainGrid[0].length - 1; y++){ for(int z = 0; z < terrainGrid[0][0].length - 1; z++){ + if(x == 0 && y == 0 && z == 0){ + System.out.println("asdf"); + } //push the current cell's values into the gridcell currentCell.setValues( new Vector3f(x+0,y+0,z+0), new Vector3f(x+0,y+0,z+1), new Vector3f(x+1,y+0,z+1), new Vector3f(x+1,y+0,z+0), diff --git a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java index d188b5f9..6b713686 100644 --- a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java @@ -21,6 +21,8 @@ import electrosphere.server.content.ServerContentManager; import electrosphere.server.datacell.interfaces.DataCellManager; import electrosphere.server.datacell.interfaces.VoxelCellManager; import electrosphere.server.datacell.physics.PhysicsDataCell; +import electrosphere.server.fluid.manager.ServerFluidChunk; +import electrosphere.server.fluid.manager.ServerFluidManager; import electrosphere.server.terrain.manager.ServerTerrainManager; import electrosphere.server.terrain.manager.ServerTerrainChunk; @@ -43,6 +45,8 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager Realm parent; //Manager for terrain for this particular cell manager ServerTerrainManager serverTerrainManager; + //manager for fluids for this particular cell manager + ServerFluidManager serverFluidManager; //lock for terrain editing Semaphore terrainEditLock = new Semaphore(1); //manager for getting entities to fill in a cell @@ -52,9 +56,15 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager * Constructor * @param parent The gridded data cell manager's parent realm */ - public GriddedDataCellManager(Realm parent, ServerTerrainManager serverTerrainManager, ServerContentManager serverContentManager) { + public GriddedDataCellManager( + Realm parent, + ServerTerrainManager serverTerrainManager, + ServerFluidManager serverFluidManager, + ServerContentManager serverContentManager + ) { this.parent = parent; this.serverTerrainManager = serverTerrainManager; + this.serverFluidManager = serverFluidManager; this.serverContentManager = serverContentManager; } @@ -344,6 +354,8 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager for(ServerDataCell cell : loadedCells){ Globals.microSimulation.simulate(cell, parent.getHitboxManager()); } + //simulate fluid + this.serverFluidManager.simulate(); loadedCellsLock.release(); updatePlayerPositions(); } @@ -357,6 +369,14 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager return serverTerrainManager; } + /** + * Gets the server fluid manager for this realm if it exists + * @return The server fluid manager if it exists, null otherwise + */ + public ServerFluidManager getServerFluidManager(){ + return serverFluidManager; + } + /** * Runs code to generate physics entities and register cell in a dedicated thread. * Because cell hasn't been registered yet, no simulation is performed until the physics is created. @@ -461,4 +481,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager return cellPositionMap.get(cell); } + @Override + public ServerFluidChunk getFluidChunkAtPosition(Vector3i worldPosition) { + return serverFluidManager.getChunk(worldPosition.x, worldPosition.y, worldPosition.z); + } + } diff --git a/src/main/java/electrosphere/server/datacell/RealmManager.java b/src/main/java/electrosphere/server/datacell/RealmManager.java index 06509805..13567d65 100644 --- a/src/main/java/electrosphere/server/datacell/RealmManager.java +++ b/src/main/java/electrosphere/server/datacell/RealmManager.java @@ -53,7 +53,7 @@ public class RealmManager { //create realm Realm realm = new Realm(collisionEngine, new HitboxManager()); //create function classes - GriddedDataCellManager griddedDataCellManager = new GriddedDataCellManager(realm,Globals.serverTerrainManager,Globals.serverContentManager); + GriddedDataCellManager griddedDataCellManager = new GriddedDataCellManager(realm,Globals.serverTerrainManager,Globals.serverFluidManager,Globals.serverContentManager); EntityDataCellMapper entityDataCellMapper = new EntityDataCellMapper(); //init gridded manager griddedDataCellManager.init(serverWorldData); diff --git a/src/main/java/electrosphere/server/datacell/interfaces/VoxelCellManager.java b/src/main/java/electrosphere/server/datacell/interfaces/VoxelCellManager.java index 81d3d004..8c9b06c8 100644 --- a/src/main/java/electrosphere/server/datacell/interfaces/VoxelCellManager.java +++ b/src/main/java/electrosphere/server/datacell/interfaces/VoxelCellManager.java @@ -2,6 +2,7 @@ package electrosphere.server.datacell.interfaces; import org.joml.Vector3i; +import electrosphere.server.fluid.manager.ServerFluidChunk; import electrosphere.server.terrain.manager.ServerTerrainChunk; /** @@ -40,5 +41,13 @@ public interface VoxelCellManager { * @param type The type to set the voxel to */ public void editChunk(Vector3i worldPosition, Vector3i voxelPosition, float weight, int type); + + + /** + * Gets the fluid chunk at a given world position + * @param worldPosition The world position + * @return the fluid chunk + */ + public ServerFluidChunk getFluidChunkAtPosition(Vector3i worldPosition); } diff --git a/src/main/java/electrosphere/server/fluid/diskmap/FluidDiskMap.java b/src/main/java/electrosphere/server/fluid/diskmap/FluidDiskMap.java new file mode 100644 index 00000000..f1d47dd5 --- /dev/null +++ b/src/main/java/electrosphere/server/fluid/diskmap/FluidDiskMap.java @@ -0,0 +1,217 @@ +package electrosphere.server.fluid.diskmap; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.DeflaterInputStream; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterOutputStream; + +import electrosphere.engine.Globals; +import electrosphere.logger.LoggerInterface; +import electrosphere.net.server.Server; +import electrosphere.server.fluid.manager.ServerFluidChunk; +import electrosphere.server.terrain.manager.ServerTerrainChunk; +import electrosphere.util.FileUtils; + +/** + * An interface for accessing the disk map of chunk information + */ +public class FluidDiskMap { + + //The map of world position+chunk type to the file that actually houses that information + Map worldPosFileMap = new HashMap(); + + /** + * Constructor + */ + public FluidDiskMap(){ + + } + + /** + * Gets a key for a given chunk file based on a world coordinate + * @param worldX The x component + * @param worldY The y component + * @param worldZ The z component + * @return The key + */ + private static String getFluidChunkKey(int worldX, int worldY, int worldZ){ + return worldX + "_" + worldY + "_" + worldZ + "f"; + } + + /** + * Initializes a diskmap based on a given save name + * @param saveName The save name + */ + public void init(String saveName){ + LoggerInterface.loggerEngine.DEBUG("INIT CHUNK MAP " + saveName); + if(FileUtils.getSaveFile(saveName, "chunk.map").exists()){ + worldPosFileMap = FileUtils.loadObjectFromSavePath(saveName, "fluid.map", Map.class); + LoggerInterface.loggerEngine.DEBUG("POS FILE MAP: " + worldPosFileMap.keySet()); + } else { + worldPosFileMap = new HashMap(); + } + } + + /** + * Saves the disk map to disk + */ + public void save(){ + FileUtils.serializeObjectToSavePath(Globals.currentSaveName, "fluid.map", worldPosFileMap); + } + + /** + * Checks if the map contains a given chunk position + * @param worldX The x component + * @param worldY The y component + * @param worldZ The z component + * @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)); + } + + /** + * Gets the server fluid chunk from disk if it exists, otherwise returns null + * @param worldX The x coordinate + * @param worldY The y coordinate + * @param worldZ The z coordinate + * @return The server fluid chunk if it exists, null otherwise + */ + public ServerFluidChunk getFluidChunk(int worldX, int worldY, int worldZ){ + LoggerInterface.loggerEngine.INFO("Load chunk " + worldX + " " + worldY + " " + worldZ); + ServerFluidChunk rVal = null; + if(containsFluidAtPosition(worldX, worldY, worldZ)){ + //read file + String fileName = worldPosFileMap.get(getFluidChunkKey(worldX, worldY, worldZ)); + byte[] rawDataCompressed = FileUtils.loadBinaryFromSavePath(Globals.currentSaveName, fileName); + //decompress + byte[] rawData = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + InflaterOutputStream inflaterInputStream = new InflaterOutputStream(out); + try { + inflaterInputStream.write(rawDataCompressed); + inflaterInputStream.flush(); + inflaterInputStream.close(); + rawData = out.toByteArray(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + //parse + if(rawData != null){ + ByteBuffer buffer = ByteBuffer.wrap(rawData); + FloatBuffer floatView = buffer.asFloatBuffer(); + int DIM = ServerTerrainChunk.CHUNK_DIMENSION; + float[][][] weights = new float[DIM][DIM][DIM]; + float[][][] velocityX = new float[DIM][DIM][DIM]; + float[][][] velocityY = new float[DIM][DIM][DIM]; + float[][][] velocityZ = new float[DIM][DIM][DIM]; + for(int x = 0; x < DIM; x++){ + for(int y = 0; y < DIM; y++){ + for(int z = 0; z < DIM; z++){ + weights[x][y][z] = floatView.get(); + } + } + } + for(int x = 0; x < DIM; x++){ + for(int y = 0; y < DIM; y++){ + for(int z = 0; z < DIM; z++){ + velocityX[x][y][z] = floatView.get(); + } + } + } + for(int x = 0; x < DIM; x++){ + for(int y = 0; y < DIM; y++){ + for(int z = 0; z < DIM; z++){ + velocityY[x][y][z] = floatView.get(); + } + } + } + for(int x = 0; x < DIM; x++){ + for(int y = 0; y < DIM; y++){ + for(int z = 0; z < DIM; z++){ + velocityZ[x][y][z] = floatView.get(); + } + } + } + rVal = new ServerFluidChunk(worldX, worldY, worldZ, weights, velocityX, velocityY, velocityZ); + } + } + return rVal; + } + + /** + * Saves a fluid chunk to disk + * @param fluidChunk The fluid chunk + */ + public void saveToDisk(ServerFluidChunk fluidChunk){ + LoggerInterface.loggerEngine.DEBUG("Save to disk: " + fluidChunk.getWorldX() + " " + fluidChunk.getWorldY() + " " + fluidChunk.getWorldZ()); + //get the file name for this chunk + String fileName = null; + String chunkKey = getFluidChunkKey(fluidChunk.getWorldX(),fluidChunk.getWorldY(),fluidChunk.getWorldZ()); + if(worldPosFileMap.containsKey(chunkKey)){ + fileName = worldPosFileMap.get(chunkKey); + } else { + fileName = chunkKey + ".dat"; + } + //generate binary for the file + float[][][] weights = fluidChunk.getWeights(); + float[][][] velocityX = fluidChunk.getVelocityX(); + float[][][] velocityY = fluidChunk.getVelocityY(); + float[][][] velocityZ = fluidChunk.getVelocityZ(); + int DIM = ServerTerrainChunk.CHUNK_DIMENSION; + ByteBuffer buffer = ByteBuffer.allocate(DIM * DIM * DIM * 4 + DIM * DIM * DIM * 4 + DIM * DIM * DIM * 4 + DIM * DIM * DIM * 4); + FloatBuffer floatView = buffer.asFloatBuffer(); + for(int x = 0; x < DIM; x++){ + for(int y = 0; y < DIM; y++){ + for(int z = 0; z < DIM; z++){ + floatView.put(weights[x][y][z]); + } + } + } + for(int x = 0; x < DIM; x++){ + for(int y = 0; y < DIM; y++){ + for(int z = 0; z < DIM; z++){ + floatView.put(velocityX[x][y][z]); + } + } + } + for(int x = 0; x < DIM; x++){ + for(int y = 0; y < DIM; y++){ + for(int z = 0; z < DIM; z++){ + floatView.put(velocityY[x][y][z]); + } + } + } + for(int x = 0; x < DIM; x++){ + for(int y = 0; y < DIM; y++){ + for(int z = 0; z < DIM; z++){ + floatView.put(velocityZ[x][y][z]); + } + } + } + //compress + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DeflaterOutputStream deflaterInputStream = new DeflaterOutputStream(out); + try { + deflaterInputStream.write(buffer.array()); + deflaterInputStream.flush(); + deflaterInputStream.close(); + //write to disk + FileUtils.saveBinaryToSavePath(Globals.currentSaveName, fileName, out.toByteArray()); + //save to the map of filenames + worldPosFileMap.put(chunkKey,fileName); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/electrosphere/server/fluid/generation/ArenaFluidGenerator.java b/src/main/java/electrosphere/server/fluid/generation/ArenaFluidGenerator.java new file mode 100644 index 00000000..384c25ef --- /dev/null +++ b/src/main/java/electrosphere/server/fluid/generation/ArenaFluidGenerator.java @@ -0,0 +1,35 @@ +package electrosphere.server.fluid.generation; + +import electrosphere.server.fluid.manager.ServerFluidChunk; +import electrosphere.server.fluid.models.FluidModel; +import electrosphere.server.terrain.manager.ServerTerrainChunk; + +public class ArenaFluidGenerator implements FluidGenerator { + + @Override + public ServerFluidChunk generateChunk(int worldX, int worldY, int worldZ) { + float[][][] weights = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION]; + float[][][] velocityX = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION]; + float[][][] velocityY = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION]; + float[][][] velocityZ = new float[ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DIMENSION]; + ServerFluidChunk chunk = new ServerFluidChunk(worldX, worldY, worldZ, weights, velocityX, velocityY, velocityZ); + + for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){ + for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){ + for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){ + weights[x][y][z] = -1.0f; + } + } + } + + weights[3][3][3] = 0.8f; + return chunk; + } + + @Override + public void setModel(FluidModel model) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setModel'"); + } + +} diff --git a/src/main/java/electrosphere/server/fluid/generation/FluidGenerator.java b/src/main/java/electrosphere/server/fluid/generation/FluidGenerator.java new file mode 100644 index 00000000..355258e2 --- /dev/null +++ b/src/main/java/electrosphere/server/fluid/generation/FluidGenerator.java @@ -0,0 +1,26 @@ +package electrosphere.server.fluid.generation; + +import electrosphere.server.fluid.manager.ServerFluidChunk; +import electrosphere.server.fluid.models.FluidModel; + +/** + * Generates fluid + */ +public interface FluidGenerator { + + /** + * Generates a chunk given an x, y, and z + * @param worldX The x component + * @param worldY The y component + * @param worldZ The z component + * @return The chunk + */ + public ServerFluidChunk generateChunk(int worldX, int worldY, int worldZ); + + /** + * Sets the fluid model for the generation algorithm + * @param model The fluid model + */ + public void setModel(FluidModel model); + +} diff --git a/src/main/java/electrosphere/server/fluid/manager/ServerFluidChunk.java b/src/main/java/electrosphere/server/fluid/manager/ServerFluidChunk.java new file mode 100644 index 00000000..76da9193 --- /dev/null +++ b/src/main/java/electrosphere/server/fluid/manager/ServerFluidChunk.java @@ -0,0 +1,129 @@ +package electrosphere.server.fluid.manager; + + +import org.joml.Vector3f; +import org.joml.Vector3i; + + +/** + * Is a single chunk of terrain on the server + */ +public class ServerFluidChunk { + + + int worldX, worldY, worldZ; + float[][][] weights; + float[][][] velocityX; + float[][][] velocityY; + float[][][] velocityZ; + + public ServerFluidChunk( + int worldX, + int worldY, + int worldZ, + float[][][] weights, + float[][][] velocityX, + float[][][] velocityY, + float[][][] velocityZ + ) { + this.worldX = worldX; + this.worldY = worldY; + this.worldZ = worldZ; + this.weights = weights; + this.velocityX = velocityX; + this.velocityY = velocityY; + this.velocityZ = velocityZ; + } + + public int getWorldX() { + return worldX; + } + + public int getWorldY() { + return worldY; + } + + public int getWorldZ() { + return worldZ; + } + + /** + * Gets the world position of this terrain chunk as a joml Vector + * @return The vector + */ + public Vector3i getWorldPosition(){ + return new Vector3i(worldX,worldY,worldZ); + } + + public float[][][] getWeights() { + return weights; + } + + /** + * Gets the weight of a voxel at a poisiton + * @param localPosition The local position + * @return The weight of the specified voxel + */ + public float getWeight(Vector3i localPosition){ + return getWeight(localPosition.x,localPosition.y,localPosition.z); + } + + /** + * Gets the weight of a voxel at a poisiton + * @param x The x coordinate + * @param y The y coordinate + * @param z The z coordinate + * @return The weight of the specified voxel + */ + public float getWeight(int x, int y, int z){ + return weights[x][y][z]; + } + + //get velocity x + public float[][][] getVelocityX() { + return velocityX; + } + + + //set velocity x + public void setVelocityX(float[][][] velocityX) { + this.velocityX = velocityX; + } + + //get velocity y + public float[][][] getVelocityY() { + return velocityY; + } + + //set velocity y + public void setVelocityY(float[][][] velocityY) { + this.velocityY = velocityY; + } + + + //get velocity z + public float[][][] getVelocityZ() { + return velocityZ; + } + + //set velocity z + public void setVelocityZ(float[][][] velocityZ) { + this.velocityZ = velocityZ; + } + + //get a velocity at a given x, y and z as a Vector3f + public Vector3f getVelocity(int x, int y, int z){ + return new Vector3f(velocityX[x][y][z],velocityY[x][y][z],velocityZ[x][y][z]); + } + + //set a velocity at a given x, y, and z given three ints + public void setVelocity(int x, int y, int z, float velX, float velY, float velZ){ + velocityX[x][y][z] = velX; + velocityY[x][y][z] = velY; + velocityZ[x][y][z] = velZ; + } + + + + +} diff --git a/src/main/java/electrosphere/server/fluid/manager/ServerFluidManager.java b/src/main/java/electrosphere/server/fluid/manager/ServerFluidManager.java new file mode 100644 index 00000000..9fc60742 --- /dev/null +++ b/src/main/java/electrosphere/server/fluid/manager/ServerFluidManager.java @@ -0,0 +1,284 @@ +package electrosphere.server.fluid.manager; + + +import electrosphere.engine.Globals; +import electrosphere.server.fluid.diskmap.FluidDiskMap; +import electrosphere.server.fluid.generation.ArenaFluidGenerator; +import electrosphere.server.fluid.generation.FluidGenerator; +import electrosphere.server.fluid.models.FluidModel; +import electrosphere.util.FileUtils; +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 org.joml.Vector3i; + +/** + * Provides an interface for the server to query information about fluid + */ +public class ServerFluidManager { + + //The size of the world in discrete units * must be multiple of 200 + int worldSizeDiscrete; + + //The vertical multiplier applied to the statically generated fluid + int verticalInterpolationRatio; + + float interpolationRandomDampener; + + long seed; + + //The model of the fluid this manager is managing + FluidModel 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 map of chunk position <-> file on disk containing chunk data + FluidDiskMap chunkDiskMap = null; + + //The generation algorithm for this fluid manager + FluidGenerator chunkGenerator; + + + /** + * Constructor + */ + public ServerFluidManager( + int worldSizeDiscrete, + int verticalInterpolationRatio, + float interpolationRandomDampener, + long seed, + FluidGenerator chunkGenerator + ){ + this.worldSizeDiscrete = worldSizeDiscrete; + this.verticalInterpolationRatio = verticalInterpolationRatio; + this.chunkCache = new ConcurrentHashMap(); + this.chunkCacheContents = new CopyOnWriteArrayList(); + this.interpolationRandomDampener = interpolationRandomDampener; + this.seed = seed; + this.chunkGenerator = chunkGenerator; + } + + ServerFluidManager(){ + + } + + /** + * Constructs an arena fluid manager + * @return The arena fluid manager + */ + public static ServerFluidManager constructArenaFluidManager(){ + ServerFluidManager rVal = new ServerFluidManager(); + rVal.worldSizeDiscrete = 2; + rVal.verticalInterpolationRatio = 0; + rVal.chunkCache = new ConcurrentHashMap(); + rVal.chunkCacheContents = new CopyOnWriteArrayList(); + rVal.interpolationRandomDampener = 0.0f; + rVal.chunkGenerator = new ArenaFluidGenerator(); + return rVal; + } + + /** + * Generates a fluid model for the manager + */ + public void generate(){ + // FluidGenerator terrainGen = new FluidGenerator(); + // terrainGen.setInterpolationRatio(worldSizeDiscrete/200); + // terrainGen.setVerticalInterpolationRatio(verticalInterpolationRatio); + // terrainGen.setRandomSeed(seed); + // model = terrainGen.generateModel(); + // this.chunkGenerator.setModel(model); + // model.setInterpolationRandomDampener(interpolationRandomDampener); + // this.chunkDiskMap = new ChunkDiskMap(); + } + + /** + * Saves the fluid model backing this manager to a save file + * @param saveName The name of the save + */ + public void save(String saveName){ + ByteBuffer buffer = ByteBuffer.allocate(model.getElevation().length * model.getElevation()[0].length * 4); + FloatBuffer floatView = buffer.asFloatBuffer(); + for(int x = 0; x < model.getElevation().length; x++){ + floatView.put(model.getElevation()[x]); + } + floatView.flip(); + FileUtils.saveBinaryToSavePath(saveName, "./fluid.dat", buffer.array()); + FileUtils.serializeObjectToSavePath(saveName, "./fluid.json", model); + //for each chunk, save via disk map + for(String chunkKey : chunkCacheContents){ + ServerFluidChunk chunk = chunkCache.get(chunkKey); + chunkDiskMap.saveToDisk(chunk); + } + //save disk map itself + if(chunkDiskMap != null){ + chunkDiskMap.save(); + } + } + + /** + * Loads a fluid manager from a save file + * @param saveName The name of the save + */ + public void load(String saveName){ + //load fluid model + model = FileUtils.loadObjectFromSavePath(saveName, "./fluid.json", FluidModel.class); + chunkGenerator.setModel(model); + byte[] data = FileUtils.loadBinaryFromSavePath(saveName, "./fluid.dat"); + ByteBuffer buffer = ByteBuffer.wrap(data); + FloatBuffer floatView = buffer.asFloatBuffer(); + float[][] elevation = new float[Globals.serverWorldData.getWorldSizeDiscrete()][Globals.serverWorldData.getWorldSizeDiscrete()]; + for(int x = 0; x < Globals.serverWorldData.getWorldSizeDiscrete(); x++){ + for(int y = 0; y < Globals.serverWorldData.getWorldSizeDiscrete(); y++){ + elevation[x][y] = floatView.get(); + } + } + model.setElevationArray(elevation); + //load chunk disk map + chunkDiskMap = new FluidDiskMap(); + chunkDiskMap.init(saveName); + } + + public float[][] getFluidAtChunk(int x, int y){ + return model.getElevationForChunk(x, y); + } + + public double getHeightAtPosition(double x, double y, double z){ + return y; + } + + public int getWorldDiscreteSize(){ + return worldSizeDiscrete; + } + + public float getDiscreteValue(int x, int y){ + if(model != null){ + return model.getElevation()[x][y]; + } else { + return 0; + } + } + + public int getDynamicInterpolationRatio(){ + //THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING + if(model != null){ + return model.getDynamicInterpolationRatio(); + } else { + //THIS FIRES IF THERE IS AN ARENA WORLD RUNNING + return 0; + } + } + + public float getRandomDampener(){ + //THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING + if(model != null){ + return model.getRandomDampener(); + } else { + //THIS FIRES IF THERE IS AN ARENA WORLD RUNNING + return 0.0f; + } + } + + /** + * Gets the fluid model backing this fluid manager + * @return The fluid model + */ + public FluidModel getModel() { + return model; + } + + /** + * 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 fluid chunk + * @param worldX The world x position + * @param worldY The world y position + * @param worldZ The world z position + * @return The ServerFluidChunk + */ + public ServerFluidChunk getChunk(int worldX, int worldY, int worldZ){ + //THIS FIRES IF THERE IS A MAIN GAME WORLD RUNNING + String key = getKey(worldX,worldY,worldZ); + ServerFluidChunk returnedChunk = null; + if(chunkCache.containsKey(key)){ + chunkCacheContents.remove(key); + chunkCacheContents.add(0, key); + returnedChunk = chunkCache.get(key); + return returnedChunk; + } 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.containsFluidAtPosition(worldX, worldY, worldZ)){ + returnedChunk = chunkDiskMap.getFluidChunk(worldX, worldY, worldZ); + } + } + //generate if it does not exist + if(returnedChunk == null){ + returnedChunk = chunkGenerator.generateChunk(worldX, worldY, worldZ); + } + chunkCache.put(key, returnedChunk); + chunkCacheContents.add(key); + return returnedChunk; + } + } + + /** + * Saves a given position's chunk to disk. + * Uses the current global save name + * @param position The position to save + */ + public void savePositionToDisk(Vector3i position){ + chunkDiskMap.saveToDisk(getChunk(position.x, position.y, position.z)); + } + + /** + * Applies a deform to fluid at a given location + * @param worldPos The world coordinates of the chunk to modify + * @param voxelPos The voxel coordinates of the voxel to modify + * @param weight The weight to set it to + * @param value The value to set it to + */ + public void deformFluidAtLocationToValue(Vector3i worldPos, Vector3i voxelPos, float weight, int value){ + // TerrainModification modification = new TerrainModification(worldPos,voxelPos,weight,value); + // //could be null if, for instance, arena mode + // if(model != null){ + // model.addModification(modification); + // } + // String key = getKey(worldPos.x,worldPos.y,worldPos.z); + // if(chunkCache.containsKey(key)){ + // ServerFluidChunk chunk = chunkCache.get(key); + // chunk.addModification(modification); + // } + } + + /** + * Simulates all active fluid chunks + */ + public void simulate(){ + //TODO: implement + } + +} diff --git a/src/main/java/electrosphere/server/fluid/models/FluidModel.java b/src/main/java/electrosphere/server/fluid/models/FluidModel.java new file mode 100644 index 00000000..c15f5665 --- /dev/null +++ b/src/main/java/electrosphere/server/fluid/models/FluidModel.java @@ -0,0 +1,343 @@ +package electrosphere.server.fluid.models; + +import electrosphere.util.annotation.Exclude; + +public class FluidModel { + + int dynamicInterpolationRatio; + float interpolationRandomDampener = 0.4f; + + int discreteArrayDimension; + @Exclude + private float[][] elevation; + + float realMountainThreshold; + float realOceanThreshold; + + FluidModel() { + } + + public FluidModel( + int dimension, + int dynamicInterpolationRatio + ){ + + this.dynamicInterpolationRatio = dynamicInterpolationRatio; + this.discreteArrayDimension = dimension; + } + + public static FluidModel constructFluidModel(int dimension, int dynamicInterpolationRatio){ + FluidModel rVal = new FluidModel(); + rVal.discreteArrayDimension = dimension; + rVal.dynamicInterpolationRatio = dynamicInterpolationRatio; + return rVal; + } + + public float[][] getElevation(){ + return elevation; + } + + public void setInterpolationRandomDampener(float f){ + interpolationRandomDampener = f; + } + + /** + * Dynamically interpolates a chunk of a specific size from the pre-existing elevation map + * @param x The x position on the elevation map to get a chunk from + * @param y The y position on the elevation map to get a chunk from + * @return Dynamically interpolated float array of elevations of chunk + */ + public float[][] getElevationForChunk(int x, int y){ + + //this is what we intend to return from the function + float[][] rVal = new float[dynamicInterpolationRatio][dynamicInterpolationRatio]; + + /* + So we're looking at chunk x,y + + if this is our grid: + + 4 0.1 0.2 0.3 0.4 + ^ + | 3 0.1 0.2 0.3 0.4 + | + | 2 0.1 0.2 0.3 0.4 + + x 1 0.1 0.2 0.3 0.4 + + 0 1 2 3 + + y ---- > + + say we're looking at x=2,y=1 + + "macroValues" should contain the values for bounds x = [1,3] and y = [0,2] + + the goal is to have the "center" of the output chunk have the value the + elevation grid at x=2,y=1 + + */ + + + //set macroValues + float[][] macroValues = getMacroValuesAtPosition(x,y); + + + + + int halfLength = dynamicInterpolationRatio/2; + + /* + + Four quadrants we're generating + + _____________________ + |1 |2 | + | | | + | | | + | | | + |__________|__________| + |3 |4 | + | | | + | | | + |__________|__________| + + First set of loops is quadrant 1 + then quadrant 2 + then quadrant 3 + then quadrant 4 + + */ + + int outXOffset = 0; + int outYOffset = 0; + + for(int i = 0; i < halfLength; i++){ + for(int j = 0; j < halfLength; j++){ + rVal[i+outXOffset][j+outYOffset] = + (1.0f * (halfLength - i) * (halfLength - j))/(halfLength * halfLength) * macroValues[0][0] + + (1.0f * (0 - i) * (halfLength - j))/(halfLength * halfLength) * macroValues[1][0] + + (1.0f * (halfLength - i) * (0 - j))/(halfLength * halfLength) * macroValues[0][1] + + (1.0f * (0 - i) * (0 - j))/(halfLength * halfLength) * macroValues[1][1] + ; + } + } + + outXOffset = halfLength; + for(int i = 0; i < halfLength; i++){ + for(int j = 0; j < halfLength; j++){ + rVal[i+outXOffset][j+outYOffset] = + (1.0f * (halfLength - i) * (halfLength - j))/(halfLength * halfLength) * macroValues[1][0] + + (1.0f * (0 - i) * (halfLength - j))/(halfLength * halfLength) * macroValues[2][0] + + (1.0f * (halfLength - i) * (0 - j))/(halfLength * halfLength) * macroValues[1][1] + + (1.0f * (0 - i) * (0 - j))/(halfLength * halfLength) * macroValues[2][1] + ; + } + } + + outXOffset = 0; + outYOffset = halfLength; + for(int i = 0; i < halfLength; i++){ + for(int j = 0; j < halfLength; j++){ + rVal[i+outXOffset][j+outYOffset] = + (1.0f * (halfLength - i) * (halfLength - j))/(halfLength * halfLength) * macroValues[0][1] + + (1.0f * (0 - i) * (halfLength - j))/(halfLength * halfLength) * macroValues[1][1] + + (1.0f * (halfLength - i) * (0 - j))/(halfLength * halfLength) * macroValues[0][2] + + (1.0f * (0 - i) * (0 - j))/(halfLength * halfLength) * macroValues[1][2] + ; + } + } + + outXOffset = halfLength; + for(int i = 0; i < halfLength; i++){ + for(int j = 0; j < halfLength; j++){ + rVal[i+outXOffset][j+outYOffset] = + (1.0f * (halfLength - i) * (halfLength - j))/(halfLength * halfLength) * macroValues[1][1] + + (1.0f * (0 - i) * (halfLength - j))/(halfLength * halfLength) * macroValues[2][1] + + (1.0f * (halfLength - i) * (0 - j))/(halfLength * halfLength) * macroValues[1][2] + + (1.0f * (0 - i) * (0 - j))/(halfLength * halfLength) * macroValues[2][2] + ; + } + } + + + return rVal; + } + + + /* + So we're looking at chunk x,y + + if this is our grid: + + 4 0.1 0.2 0.3 0.4 + ^ + | 3 0.1 0.2 0.3 0.4 + | + | 2 0.1 0.2 0.3 0.4 + + x 1 0.1 0.2 0.3 0.4 + + 0 1 2 3 + + y ---- > + + say we're looking at x=2,y=1 + + "macroValues" should contain the values for x = [1,3] and y = [0,2] + + the goal is to have the "center" of the output chunk have the value the + elevation grid at x=2,y=1 + + */ + + public float[][] getMacroValuesAtPosition(int x, int y){ + + float[][] rVal = new float[3][3]; + rVal[1][1] = elevation[x][y]; + if(x - 1 >= 0){ + rVal[0][1] = elevation[x-1][y]; + if(y - 1 >= 0){ + rVal[0][0] = elevation[x-1][y-1]; + } else { + rVal[0][0] = 0; + } + if(y + 1 < discreteArrayDimension){ + rVal[0][2] = elevation[x-1][y+1]; + } else { + rVal[0][2] = 0; + } + } else { + rVal[0][0] = 0; + rVal[0][1] = 0; + rVal[0][2] = 0; + } + if(x + 1 < discreteArrayDimension){ + rVal[2][1] = elevation[x+1][y]; + if(y - 1 >= 0){ + rVal[2][0] = elevation[x+1][y-1]; + } else { + rVal[2][0] = 0; + } + if(y + 1 < discreteArrayDimension){ + rVal[2][2] = elevation[x+1][y+1]; + } else { + rVal[2][2] = 0; + } + } else { + rVal[2][0] = 0; + rVal[2][1] = 0; + rVal[2][2] = 0; + } + if(y - 1 >= 0){ + rVal[1][0] = elevation[x][y-1]; + } else { + rVal[1][0] = 0; + } + if(y + 1 < discreteArrayDimension){ + rVal[1][2] = elevation[x][y+1]; + } else { + rVal[1][2] = 0; + } + + return rVal; + } + + + + + + public float[][] getRad5MacroValuesAtPosition(int x, int y){ + + float[][] rVal = new float[5][5]; + for(int i = -2; i < 3; i++){ + for(int j = -2; j < 3; j++){ + if(x + i >= 0 && x + i < discreteArrayDimension && y + j >= 0 && y + j < discreteArrayDimension){ + rVal[i+2][j+2] = elevation[x+i][y+j]; + } else { + rVal[i+2][j+2] = 0; + } + } + } + + return rVal; + } + + + /* + So we're looking at chunk x,y + + if this is our grid: + + 4 0.1 0.2 0.3 0.4 + ^ + | 3 0.1 0.2 0.3 0.4 + | + | 2 0.1 0.2 0.3 0.4 + + x 1 0.1 0.2 0.3 0.4 + + 0 1 2 3 + + y ---- > + + say we're looking at x=2,y=1 + + "macroValues" should contain the values for x = [1,3] and y = [0,2] + + the goal is to have the "center" of the output chunk have the value the + elevation grid at x=2,y=1 + + */ + + + public float getRandomDampener(){ + return interpolationRandomDampener; + } + + public int getDynamicInterpolationRatio(){ + return dynamicInterpolationRatio; + } + + public float getRealMountainThreshold() { + return realMountainThreshold; + } + + public float getRealOceanThreshold() { + return realOceanThreshold; + } + + public String getModificationKey(int x, int y, int z){ + return x + "_" + y + "_" + z; + } + +// public void addModification(TerrainModification modification){ +// String key = getModificationKey(modification.getWorldPos().x,modification.getWorldPos().y,modification.getWorldPos().z); +// ModificationList list; +// if(!modifications.containsKey(key)){ +// list = new ModificationList(); +// modifications.put(key, list); +// } else { +// list = modifications.get(key); +// } +// list.addModification(modification); +// } + +// public boolean containsModificationsAtCoord(int worldX, int worldY, int worldZ){ +// return modifications.containsKey(getModificationKey(worldX, worldY, worldZ)); +// } + +// public ModificationList getModifications(int worldX, int worldY, int worldZ){ +// // System.out.println("Got modifications at " + worldX + " " + worldY); +// return modifications.get(getModificationKey(worldX, worldY, worldZ)); +// } + + /** + * Sets the elevation array (For instance when read from save file on loading a save) + * @param elevation The elevation array to set to + */ + public void setElevationArray(float[][] elevation){ + this.elevation = elevation; + } + +}