Fluid simulation scaffolding

This commit is contained in:
austin 2023-07-01 09:34:10 -04:00
parent 7d4b3b1a97
commit e225cac5cf
14 changed files with 2206 additions and 1 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,82 @@
package electrosphere.client.fluid.cache;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Acts as a cache in front of fluid model to streamline receiving chunks
*/
public class ClientFluidCache {
//cache capacity
int cacheSize;
//the map of chunk key -> chunk data
Map<String,FluidChunkData> cacheMap = new ConcurrentHashMap<String,FluidChunkData>();
//the list of keys in the cache
List<String> cacheList = new CopyOnWriteArrayList<String>();
/**
* Constructor
* @param cacheSize The capacity of the cache
*/
public ClientFluidCache(int cacheSize){
this.cacheSize = cacheSize;
}
/**
* Adds a chunk data to the fluid cache
* @param worldX The x world position
* @param worldY The y world position
* @param worldZ The z world position
* @param chunkData The chunk data to add at the specified positions
*/
public void addChunkDataToCache(int worldX, int worldY, int worldZ, FluidChunkData chunkData){
cacheMap.put(getKey(worldX,worldY,worldZ),chunkData);
while(cacheList.size() > cacheSize){
String currentChunk = cacheList.remove(0);
cacheMap.remove(currentChunk);
}
}
/**
* Generates a key for the cache based on the position provided
* @param worldX The x world position
* @param worldY The y world position
* @param worldZ The z world position
* @return The cache key
*/
public String getKey(int worldX, int worldY, int worldZ){
return worldX + "_" + worldY + "_" + worldZ;
}
/**
* Checks whether the cache contains chunk data at a given world point
* @param worldX The x world position
* @param worldY The y world position
* @param worldZ The z world position
* @return True if the cache contains chunk data at the specified point, false otherwise
*/
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
return cacheMap.containsKey(getKey(worldX,worldY,worldZ));
}
/**
* Gets chunk data at the given world point
* @param worldX The x world position
* @param worldY The y world position
* @param worldZ The z world position
* @return The chunk data if it exists, null otherwise
*/
public FluidChunkData getSubChunkDataAtPoint(int worldX, int worldY, int worldZ){
return cacheMap.get(getKey(worldX,worldY,worldZ));
}
}

View File

@ -0,0 +1,160 @@
package electrosphere.client.fluid.cache;
import java.util.HashSet;
import java.util.Set;
import org.joml.Vector3i;
import electrosphere.server.terrain.manager.ServerTerrainChunk;
/**
* A container of data about a chunk of fluid
*/
public class FluidChunkData {
//The size of a chunk in virtual data
public static final int CHUNK_SIZE = ServerTerrainChunk.CHUNK_DIMENSION;
//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 list of positions modified since the last call to resetModifiedPositions
//Used in DrawCell to keep track of which positions to invalidate
Set<String> modifiedSinceLastGeneration = new HashSet<String>();
/**
* 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
*/
public float[][][] getVoxelWeight(){
return voxelWeight;
}
/**
* Sets the voxel weight array in this container
* @param voxelWeight The voxel weight array
*/
public void setVoxelWeight(float[][][] voxelWeight){
//mark changed cells
if(this.voxelWeight != 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(voxelWeight[x][y][z] != this.voxelWeight[x][y][z]){
String key = getVoxelPositionKey(new Vector3i(x,y,z));
if(!modifiedSinceLastGeneration.contains(key)){
modifiedSinceLastGeneration.add(key);
}
}
}
}
}
}
//update data
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);
}
}
/**
* 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 voxelWeight[localPosition.x][localPosition.y][localPosition.z];
}
/**
* Gets the type of a voxel at a position
* @param localPosition The local position
* @return The type of the specified voxel
*/
public int getType(Vector3i localPosition){
return voxelType[localPosition.x][localPosition.y][localPosition.z];
}
/**
* Resets the cache of modified positions
*/
public void resetModifiedPositions(){
this.modifiedSinceLastGeneration.clear();
}
/**
* Gets the set of all modified positions since the last call to resetModifiedPositions
* @return The set of all modified positions
*/
public Set<Vector3i> getModifiedPositions(){
Set<Vector3i> rVal = new HashSet<Vector3i>();
for(String key : modifiedSinceLastGeneration){
String[] split = key.split("_");
rVal.add(new Vector3i(Integer.parseInt(split[0]),Integer.parseInt(split[1]),Integer.parseInt(split[2])));
}
return rVal;
}
/**
* Gets a key for the modifiedSinceLastGeneration set based on a voxel position
* @param position The voxel position
* @return The key
*/
private String getVoxelPositionKey(Vector3i position){
return position.x + "_" + position.y + "_" + position.z;
}
}

View File

@ -0,0 +1,414 @@
package electrosphere.client.fluid.cells;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.joml.Vector3d;
import org.joml.Vector3i;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.engine.Globals;
import electrosphere.entity.EntityUtils;
import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.renderer.ShaderProgram;
/**
*
* @author satellite
*/
public class DrawCellManager {
//the center of this cell manager's array in cell space
int cellX;
int cellY;
int cellZ;
//the dimensions of the world that this cell manager can handles
int cellWidth;
//the width of a minicell in this manager
int miniCellWidth;
//all currently displaying mini cells
Set<FluidDrawCell> cells;
Map<String,FluidDrawCell> keyCellMap = new HashMap<String,FluidDrawCell>();
Set<String> hasNotRequested;
Set<String> hasRequested;
Set<String> drawable;
Set<String> undrawable;
Set<String> updateable;
ShaderProgram program;
// int drawRadius = 5;
int drawStepdownInterval = 3;
int drawStepdownValue = 25;
double drawRadius = 200;
int physicsRadius = 3;
int worldBoundDiscreteMin = 0;
int worldBoundDiscreteMax = 0;
//client terrain manager
// ClientTerrainManager clientTerrainManager;
//ready to start updating?
boolean update = false;
//controls whether we try to generate the drawable entities
//we want this to be false when in server-only mode
boolean generateDrawables = false;
/**
* DrawCellManager constructor
* @param commonWorldData The common world data
* @param clientTerrainManager The client terrain manager
* @param discreteX The initial discrete position X coordinate
* @param discreteY The initial discrete position Y coordinate
*/
public DrawCellManager(ClientTerrainManager clientTerrainManager, int discreteX, int discreteY, int discreteZ){
worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / Globals.clientWorldData.getDynamicInterpolationRatio() * 1.0f);
cells = new HashSet<FluidDrawCell>();
hasNotRequested = new HashSet<String>();
drawable = new HashSet<String>();
undrawable = new HashSet<String>();
updateable = new HashSet<String>();
hasRequested = new HashSet<String>();
cellX = discreteX;
cellY = discreteY;
cellZ = discreteZ;
program = Globals.terrainShaderProgram;
// drawRadius = Globals.userSettings.getGraphicsPerformanceLODChunkRadius();
drawStepdownInterval = Globals.userSettings.getGameplayPhysicsCellRadius();
physicsRadius = Globals.userSettings.getGameplayPhysicsCellRadius();
invalidateAllCells();
update = true;
}
DrawCellManager(){
}
public void setCell(Vector3i cellPos){
cellX = cellPos.x;
cellY = cellPos.y;
cellZ = cellPos.z;
}
void updateUnrequestedCell(){
if(hasNotRequested.size() > 0){
String targetKey = hasNotRequested.iterator().next();
hasNotRequested.remove(targetKey);
Vector3i worldPos = getVectorFromKey(targetKey);
// Vector3i vector = getVectorFromKey(targetKey);
// int currentCellX = cellX - drawRadius + vector.x;
// int currentCellY = cellY - drawRadius + vector.y;
// int currentCellZ = cellZ - drawRadius + vector.z;
if(
worldPos.x >= 0 &&
worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.y >= 0 &&
worldPos.y < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.z >= 0 &&
worldPos.z < Globals.clientWorldData.getWorldDiscreteSize()
){
// if(!hasRequested.contains(targetKey)){
//client should request chunk data from server
Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage(
worldPos.x,
worldPos.y,
worldPos.z
));
undrawable.add(targetKey);
hasRequested.add(targetKey);
// }
}
}
}
/**
* Makes one of the undrawable cells drawable
*/
void makeCellDrawable(){
if(undrawable.size() > 0){
String targetKey = undrawable.iterator().next();
Vector3i worldPos = getVectorFromKey(targetKey);
if(
worldPos.x >= 0 &&
worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.y >= 0 &&
worldPos.y < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.z >= 0 &&
worldPos.z < Globals.clientWorldData.getWorldDiscreteSize()
){
if(containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z)){
FluidDrawCell cell = FluidDrawCell.generateFluidCell(
worldPos,
Globals.clientFluidManager.getChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z),
program
);
cells.add(cell);
keyCellMap.put(targetKey,cell);
// undrawable.add(targetKey);
undrawable.remove(targetKey);
drawable.add(targetKey);
//make drawable entity
keyCellMap.get(targetKey).generateDrawableEntity();
//evaluate for foliage
Globals.clientFoliageManager.evaluateChunk(worldPos);
}
}
}
}
/**
* Updates a cell that can be updated
*/
void updateCellModel(){
if(updateable.size() > 0){
String targetKey = updateable.iterator().next();
updateable.remove(targetKey);
Vector3i worldPos = getVectorFromKey(targetKey);
if(
worldPos.x >= 0 &&
worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.y >= 0 &&
worldPos.y < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.z >= 0 &&
worldPos.z < Globals.clientWorldData.getWorldDiscreteSize()
){
// if(Math.abs(drawRadius + 1 - targetX) < physicsRadius && Math.abs(drawRadius + 1 - targetY) < physicsRadius){
// needsPhysics[targetX][targetY] = true;
// }
// int dist = (int)Math.sqrt((targetX - drawRadius)*(targetX - drawRadius) + (targetY - drawRadius) * (targetY - drawRadius)); //Math.abs(targetX - drawRadius) * Math.abs(targetY - drawRadius);
// int stride = Math.min(commonWorldData.getDynamicInterpolationRatio()/2, Math.max(1, dist / drawStepdownInterval * drawStepdownValue));
// while(commonWorldData.getDynamicInterpolationRatio() % stride != 0){
// stride = stride + 1;
// }
keyCellMap.get(targetKey).destroy();
keyCellMap.get(targetKey).generateDrawableEntity();
}
drawable.add(targetKey);
}
}
public boolean containsUnrequestedCell(){
return hasNotRequested.size() > 0;
}
public boolean containsUndrawableCell(){
return undrawable.size() > 0;
}
public boolean containsUpdateableCell(){
return updateable.size() > 0;
}
public int transformRealSpaceToCellSpace(double input){
return (int)(input / Globals.clientWorldData.getDynamicInterpolationRatio());
}
/**
* Clears the valid set and adds all keys to invalid set
*/
public void invalidateAllCells(){
drawable.clear();
hasNotRequested.clear();
clearOutOfBoundsCells();
queueNewCells();
}
/**
* Calculates whether the position of the player has changed and if so, invalidates and cleans up cells accordingly
* @param position The position of the player entity on current frame
*/
public void calculateDeltas(Vector3d position){
//check if any not requested cells no longer need to be requested
clearOutOfBoundsCells();
//check if any cells should be added
queueNewCells();
}
/**
* Clears all cells outside of draw radius
*/
private void clearOutOfBoundsCells(){
Set<FluidDrawCell> cellsToRemove = new HashSet<FluidDrawCell>();
for(FluidDrawCell cell : cells){
Vector3d realPos = cell.getRealPos();
if(Globals.playerEntity != null && EntityUtils.getPosition(Globals.playerEntity).distance(realPos) > drawRadius){
cellsToRemove.add(cell);
}
}
for(FluidDrawCell cell : cellsToRemove){
cells.remove(cell);
String key = getCellKey(cell.worldPos.x, cell.worldPos.y, cell.worldPos.z);
hasNotRequested.remove(key);
drawable.remove(key);
undrawable.remove(key);
updateable.remove(key);
keyCellMap.remove(key);
hasRequested.remove(key);
}
}
/**
* Queues new cells that are in bounds but not currently accounted for
*/
private void queueNewCells(){
if(Globals.playerEntity != null && Globals.clientWorldData != null){
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
for(int x = -(int)drawRadius; x < drawRadius; x = x + ChunkData.CHUNK_SIZE){
for(int y = -(int)drawRadius; y < drawRadius; y = y + ChunkData.CHUNK_SIZE){
for(int z = -(int)drawRadius; z < drawRadius; z = z + ChunkData.CHUNK_SIZE){
Vector3d newPos = new Vector3d(playerPos.x + x, playerPos.y + y, playerPos.z + z);
Vector3i worldPos = new Vector3i(
Globals.clientWorldData.convertRealToChunkSpace(newPos.x),
Globals.clientWorldData.convertRealToChunkSpace(newPos.y),
Globals.clientWorldData.convertRealToChunkSpace(newPos.z)
);
Vector3d chunkRealSpace = new Vector3d(
Globals.clientWorldData.convertChunkToRealSpace(worldPos.x),
Globals.clientWorldData.convertChunkToRealSpace(worldPos.y),
Globals.clientWorldData.convertChunkToRealSpace(worldPos.z)
);
if(
playerPos.distance(chunkRealSpace) < drawRadius &&
worldPos.x >= 0 &&
worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.y >= 0 &&
worldPos.y < Globals.clientWorldData.getWorldDiscreteSize() &&
worldPos.z >= 0 &&
worldPos.z < Globals.clientWorldData.getWorldDiscreteSize()
){
String key = getCellKey(
Globals.clientWorldData.convertRealToChunkSpace(chunkRealSpace.x),
Globals.clientWorldData.convertRealToChunkSpace(chunkRealSpace.y),
Globals.clientWorldData.convertRealToChunkSpace(chunkRealSpace.z)
);
if(!keyCellMap.containsKey(key) && !hasNotRequested.contains(key) && !undrawable.contains(key) && !drawable.contains(key) &&
!hasRequested.contains(key)){
hasNotRequested.add(key);
}
}
}
}
}
}
}
/**
* Updates cells that need updating in this manager
*/
public void update(){
if(update){
if(containsUnrequestedCell() && !containsUndrawableCell()){
updateUnrequestedCell();
} else if(containsUndrawableCell()){
makeCellDrawable();
} else if(containsUpdateableCell()){
updateCellModel();
}
}
}
/**
* Splits a cell key into its constituent coordinates in array format.
* @param cellKey The cell key to split
* @return The coordinates in array format
*/
// private int[] splitKeyToCoordinates(String cellKey){
// int[] rVal = new int[3];
// String[] components = cellKey.split("_");
// for(int i = 0; i < 3; i++){
// rVal[i] = Integer.parseInt(components[i]);
// }
// return rVal;
// }
public boolean coordsInPhysicsSpace(int worldX, int worldY){
return worldX <= cellX + physicsRadius && worldX >= cellX - physicsRadius && worldY <= cellY + physicsRadius && worldY >= cellY - physicsRadius;
}
public void setGenerateDrawables(boolean generate){
this.generateDrawables = generate;
}
boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
if(Globals.clientTerrainManager != null){
return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ);
}
return true;
}
/**
* Gets the chunk data at a given point
* @param worldX The world position x component of the cell
* @param worldY The world position y component of the cell
* @param worldZ The world position z component of the cell
* @return The chunk data at the specified points
*/
ChunkData getChunkDataAtPoint(int worldX, int worldY, int worldZ){
return Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldX,worldY,worldZ);
}
/**
* Gets a unique key for the cell
* @param worldX The world position x component of the cell
* @param worldY The world position y component of the cell
* @param worldZ The world position z component of the cell
* @return The key
*/
private String getCellKey(int worldX, int worldY, int worldZ){
return worldX + "_" + worldY + "_" + worldZ;
}
/**
* Parses a vector3i from the cell key
* @param key The cell key
* @return The vector3i containing the components of the cell key
*/
private Vector3i getVectorFromKey(String key){
String[] keyComponents = key.split("_");
return new Vector3i(Integer.parseInt(keyComponents[0]),Integer.parseInt(keyComponents[1]),Integer.parseInt(keyComponents[2]));
}
/**
* Marks a data cell as updateable (can be regenerated with a new model because the underlying data has changed)
* @param chunkX The chunk x coordinate
* @param chunkY The chunk y coordinate
* @param chunkZ The chunk z coordinate
*/
public void markUpdateable(int chunkX, int chunkY, int chunkZ){
updateable.add(getCellKey(chunkX, chunkY, chunkZ));
}
// public
}

View File

@ -0,0 +1,108 @@
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.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;
System.out.println("Create cell");
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());
for(Vector3i position : data.getModifiedPositions()){
Globals.clientFoliageManager.invalidateCell(worldPos, position);
}
data.resetModifiedPositions();
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;
}
}

View File

@ -0,0 +1,71 @@
package electrosphere.client.fluid.editing;
import org.joml.Vector3d;
import org.joml.Vector3i;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.engine.Globals;
import electrosphere.net.parser.net.message.TerrainMessage;
/**
* Utilities for editing fluid from client side of things
*/
public class FluidEditing {
/**
* Performs a fluid chunk edit. Basically has a sphere around the provided position that it attempts to add value to.
* @param position The position to perform the edit
* @param editMagnitude The magnitude of the edit to perform
* @param type The type of block to make all edited blocks
* @param weight The weight of the sphere to apply the edit to
*/
public static void editFluid(Vector3d position, float editMagnitude, int type, float weight){
if(position != null){
// Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestUseTerrainPaletteMessage(position.x, position.y, position.z, editMagnitude, weight, type));
//calculate kernel size
// int numPlacesToCheck = (int)((editMagnitude * 2 + 1) * (editMagnitude * 2 + 1) * (editMagnitude * 2 + 1));
// //create and fill in kernel of positions to check
// int[] xOffsetSet = new int[numPlacesToCheck];
// int[] yOffsetSet = new int[numPlacesToCheck];
// int[] zOffsetSet = new int[numPlacesToCheck];
// int i = 0;
// for(int x = -(int)editMagnitude; x <= (int)editMagnitude; x++){
// for(int y = -(int)editMagnitude; y <= (int)editMagnitude; y++){
// for(int z = -(int)editMagnitude; z <= (int)editMagnitude; z++){
// xOffsetSet[i] = x;
// yOffsetSet[i] = y;
// zOffsetSet[i] = z;
// i++;
// }
// }
// }
// for(i = 0; i < numPlacesToCheck; i++){
// //calculate position of edit
// Vector3d offsetPos = new Vector3d(position).add(xOffsetSet[i],yOffsetSet[i],zOffsetSet[i]);
// Vector3i chunkPos = Globals.clientWorldData.convertRealToChunkSpace(offsetPos);
// Vector3i voxelPos = Globals.clientWorldData.convertRealToVoxelSpace(offsetPos);
// //get distance from true center point of sphere to current voxel position in world space
// float distance = (float)new Vector3d(Math.floor(offsetPos.x),Math.floor(offsetPos.y),Math.floor(offsetPos.z)).distance(position);
// float currentPositionMagnitude = editMagnitude - distance;
// ChunkData data = Globals.clientTerrainManager.getChunkDataAtWorldPoint(chunkPos.x, chunkPos.y, chunkPos.z);
// if(
// voxelPos.x < ChunkData.CHUNK_SIZE &&
// voxelPos.y < ChunkData.CHUNK_SIZE &&
// voxelPos.z < ChunkData.CHUNK_SIZE &&
// currentPositionMagnitude > 0 &&
// data != null
// ){
// float current = data.getVoxelWeight()[voxelPos.x][voxelPos.y][voxelPos.z];
// //hard clamp so it doesn't go over 1
// float finalValue = Math.max(Math.min(current + weight,1),-1);
// Globals.clientTerrainManager.updateChunk(
// chunkPos.x, chunkPos.y, chunkPos.z,
// voxelPos.x, voxelPos.y, voxelPos.z,
// finalValue, 1
// );
// }
// }
}
}
}

View File

@ -0,0 +1,163 @@
package electrosphere.client.fluid.manager;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import org.joml.Vector3i;
import electrosphere.client.fluid.cache.ClientFluidCache;
import electrosphere.client.fluid.cache.FluidChunkData;
import electrosphere.client.scene.ClientWorldData;
import electrosphere.engine.Globals;
import electrosphere.entity.types.fluid.FluidChunkModelData;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.renderer.Model;
import electrosphere.renderer.meshgen.FluidChunkModelGeneration;
import electrosphere.server.terrain.manager.ServerTerrainManager;
/**
* Manages fluid storage and access on the client
*/
public class ClientFluidManager {
//queues messages from server
List<TerrainMessage> messageQueue = new CopyOnWriteArrayList<TerrainMessage>();
//The interpolation ratio of fluid
public static final int INTERPOLATION_RATIO = ServerTerrainManager.SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO;
//caches chunks from server
static final int CACHE_SIZE = 50;
//used for caching the macro values
ClientFluidCache fluidCache;
//The world data for the client
ClientWorldData clientWorldData;
//The queue of fluid chunk data to be buffered to gpu
static List<FluidChunkGenQueueItem> fluidChunkGenerationQueue = new CopyOnWriteArrayList<FluidChunkGenQueueItem>();
/**
* Constructor
*/
public ClientFluidManager(){
fluidCache = new ClientFluidCache(CACHE_SIZE);
}
public void handleMessages(){
List<TerrainMessage> bouncedMessages = new LinkedList<TerrainMessage>();
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];
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++){
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();
}
}
}
FluidChunkData data = new FluidChunkData();
data.setVoxelType(values);
data.setVoxelWeight(weights);
fluidCache.addChunkDataToCache(
message.getworldX(), message.getworldY(), message.getworldZ(),
data
);
} break;
default:
LoggerInterface.loggerEngine.WARNING("ClientFluidManager: unhandled network message of type" + message.getMessageSubtype());
break;
}
}
for(TerrainMessage message : bouncedMessages){
messageQueue.add(message);
}
}
public void attachTerrainMessage(TerrainMessage message){
messageQueue.add(message);
}
public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
return fluidCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ);
}
public boolean containsChunkDataAtRealPoint(double x, double y, double z){
assert clientWorldData != null;
return fluidCache.containsChunkDataAtWorldPoint(
clientWorldData.convertRealToChunkSpace(x),
clientWorldData.convertRealToChunkSpace(y),
clientWorldData.convertRealToChunkSpace(z)
);
}
/**
* Gets the chunk data at a given world position
* @param worldX The x component of the world coordinate
* @param worldY The y component of the world coordinate
* @param worldZ The z component of the world coordinate
* @return The chunk data if it exists, otherwise null
*/
public FluidChunkData getChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){
return fluidCache.getSubChunkDataAtPoint(worldX, worldY, worldZ);
}
/**
* Gets the chunk data at a given world position
* @param worldPos The world position as a joml vector
* @return The chunk data if it exists, otherwise null
*/
public FluidChunkData getChunkDataAtWorldPoint(Vector3i worldPos){
return fluidCache.getSubChunkDataAtPoint(worldPos.x, worldPos.y, worldPos.z);
}
/**
* Queues a fluid chunk to be pushed to GPU based on chunk data
* @param data The chunk data (triangles, normals, etc)
* @return The model path that is promised to eventually reflect the fluid model when it makes it to gpu
*/
public static String queueFluidGridGeneration(FluidChunkModelData data){
String promisedHash = "";
UUID newUUID = UUID.randomUUID();
promisedHash = newUUID.toString();
FluidChunkGenQueueItem queueItem = new FluidChunkGenQueueItem(data, promisedHash);
fluidChunkGenerationQueue.add(queueItem);
return promisedHash;
}
/**
* Pushes all fluid data in queue to the gpu and registers the resulting models
*/
public static void generateFluidChunkGeometry(){
for(FluidChunkGenQueueItem queueItem : fluidChunkGenerationQueue){
Model fluidModel = FluidChunkModelGeneration.generateFluidModel(queueItem.getData());
Globals.assetManager.registerModelToSpecificString(fluidModel, queueItem.getPromisedHash());
}
fluidChunkGenerationQueue.clear();
}
}

View File

@ -0,0 +1,23 @@
package electrosphere.client.fluid.manager;
import electrosphere.entity.types.fluid.FluidChunkModelData;
public class FluidChunkGenQueueItem {
FluidChunkModelData data;
String promisedHash;
public FluidChunkGenQueueItem(FluidChunkModelData data, String promisedHash){
this.data = data;
this.promisedHash = promisedHash;
}
public FluidChunkModelData getData(){
return data;
}
public String getPromisedHash(){
return this.promisedHash;
}
}

View File

@ -12,6 +12,7 @@ import org.lwjgl.glfw.GLFWErrorCallback;
import electrosphere.audio.AudioEngine; import electrosphere.audio.AudioEngine;
import electrosphere.auth.AuthenticationManager; import electrosphere.auth.AuthenticationManager;
import electrosphere.client.culling.ClientEntityCullingManager; import electrosphere.client.culling.ClientEntityCullingManager;
import electrosphere.client.fluid.manager.ClientFluidManager;
import electrosphere.client.foliagemanager.ClientFoliageManager; import electrosphere.client.foliagemanager.ClientFoliageManager;
import electrosphere.client.player.ClientPlayerData; import electrosphere.client.player.ClientPlayerData;
import electrosphere.client.scene.ClientSceneWrapper; import electrosphere.client.scene.ClientSceneWrapper;
@ -273,6 +274,9 @@ public class Globals {
//client terrain manager //client terrain manager
public static ClientTerrainManager clientTerrainManager; public static ClientTerrainManager clientTerrainManager;
//client fluid manager
public static ClientFluidManager clientFluidManager;
//client player data //client player data
public static ClientPlayerData clientPlayerData = new ClientPlayerData(); public static ClientPlayerData clientPlayerData = new ClientPlayerData();
@ -439,8 +443,11 @@ public class Globals {
//init default shaderProgram //init default shaderProgram
defaultMeshShader = ShaderProgram.smart_assemble_shader(false,true); defaultMeshShader = ShaderProgram.smart_assemble_shader(false,true);
//init terrain shader program //init terrain shader program
terrainShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/terrain/terrain.vs", "/Shaders/terrain/terrain.fs"); terrainShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/terrain2/terrain.vs", "/Shaders/terrain2/terrain.fs");
TerrainChunkModelGeneration.terrainChunkShaderProgram = 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");
//init skybox //init skybox
assetManager.registerModelToSpecificString(RenderUtils.createSkyboxModel(null), AssetDataStrings.ASSET_STRING_SKYBOX_BASIC); assetManager.registerModelToSpecificString(RenderUtils.createSkyboxModel(null), AssetDataStrings.ASSET_STRING_SKYBOX_BASIC);
//init leaves //init leaves

View File

@ -29,6 +29,11 @@ public class EntityDataStrings {
Terrain Entity Terrain Entity
*/ */
public static final String TERRAIN_IS_TERRAIN = "terrainEntity"; public static final String TERRAIN_IS_TERRAIN = "terrainEntity";
/*
* Fluid Entity
*/
public static final String FLUID_IS_FLUID = "fluidEntity";
/* /*

View File

@ -0,0 +1,36 @@
package electrosphere.entity.types.fluid;
import electrosphere.client.fluid.manager.ClientFluidManager;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings;
import electrosphere.renderer.meshgen.FluidChunkModelGeneration;
/**
* Creates a fluid chunk entity
*/
public class FluidChunk {
/**
* Creates a client fluid chunk based on weights and values provided
* @param weights The fluid weights
* @param values The values (block types)
* @return The fluid chunk entity
*/
public static Entity clientCreateFluidChunkEntity(float[][][] weights, int[][][] values){
FluidChunkModelData data = FluidChunkModelGeneration.generateFluidChunkData(weights, values);
String modelPath = ClientFluidManager.queueFluidGridGeneration(data);
Entity rVal = EntityCreationUtils.createClientSpatialEntity();
EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath);
// if(data.vertices.size() > 0 && levelOfDetail < 1){
// PhysicsUtils.clientAttachTerrainChunkRigidBody(rVal, data);
// }
rVal.putData(EntityDataStrings.FLUID_IS_FLUID, true);
return rVal;
}
}

View File

@ -0,0 +1,35 @@
package electrosphere.entity.types.fluid;
import java.util.List;
public class FluidChunkModelData {
List<Float> vertices;
List<Float> normals;
List<Integer> faceElements;
List<Float> uvs;
public FluidChunkModelData(List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs){
this.vertices = vertices;
this.normals = normals;
this.faceElements = faceElements;
this.uvs = uvs;
}
public List<Float> getVertices(){
return vertices;
}
public List<Float> getNormals(){
return normals;
}
public List<Integer> getFaceElements(){
return faceElements;
}
public List<Float> getUVs(){
return uvs;
}
}

View File

@ -0,0 +1,831 @@
package electrosphere.renderer.meshgen;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import electrosphere.engine.Globals;
import electrosphere.entity.types.fluid.FluidChunkModelData;
import electrosphere.renderer.Material;
import electrosphere.renderer.Mesh;
import electrosphere.renderer.Model;
import electrosphere.renderer.ShaderProgram;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
public class FluidChunkModelGeneration {
//http://paulbourke.net/geometry/polygonise/
static int edgeTable[]={
0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0
};
//256 by 16
static int triTable[][] = {
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1},
{3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1},
{3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1},
{3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1},
{9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1},
{9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
{2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1},
{8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1},
{9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
{4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1},
{3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1},
{1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1},
{4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1},
{4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1},
{9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
{5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1},
{2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1},
{9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
{0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
{2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1},
{10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1},
{4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1},
{5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1},
{5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1},
{9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1},
{0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1},
{1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1},
{10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1},
{8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1},
{2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1},
{7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1},
{9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1},
{2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1},
{11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1},
{9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1},
{5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1},
{11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1},
{11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
{1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1},
{9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1},
{5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1},
{2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
{0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
{5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1},
{6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1},
{3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1},
{6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1},
{5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1},
{1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
{10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1},
{6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1},
{8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1},
{7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1},
{3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
{5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1},
{0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1},
{9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1},
{8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1},
{5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1},
{0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1},
{6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1},
{10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1},
{10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1},
{8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1},
{1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1},
{3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1},
{0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1},
{10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1},
{3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1},
{6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1},
{9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1},
{8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1},
{3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1},
{6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1},
{0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1},
{10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1},
{10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1},
{2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1},
{7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1},
{7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1},
{2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1},
{1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1},
{11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1},
{8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1},
{0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1},
{7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
{10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
{2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
{6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1},
{7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1},
{2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1},
{1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1},
{10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1},
{10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1},
{0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1},
{7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1},
{6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1},
{8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1},
{9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1},
{6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1},
{4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1},
{10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1},
{8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1},
{0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1},
{1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1},
{8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1},
{10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1},
{4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1},
{10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
{5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
{11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1},
{9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
{6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1},
{7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1},
{3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1},
{7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1},
{9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1},
{3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1},
{6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1},
{9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1},
{1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1},
{4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1},
{7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1},
{6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1},
{3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1},
{0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1},
{6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1},
{0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1},
{11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1},
{6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1},
{5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1},
{9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1},
{1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1},
{1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1},
{10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1},
{0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1},
{5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1},
{10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1},
{11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1},
{9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1},
{7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1},
{2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1},
{8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1},
{9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1},
{9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1},
{1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1},
{9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1},
{9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1},
{5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1},
{0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1},
{10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1},
{2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1},
{0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1},
{0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1},
{9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1},
{5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1},
{3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1},
{5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1},
{8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1},
{0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1},
{9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1},
{0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1},
{1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1},
{3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1},
{4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1},
{9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1},
{11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1},
{11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1},
{2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1},
{9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1},
{3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1},
{1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1},
{4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1},
{4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1},
{0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1},
{3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1},
{3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1},
{0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1},
{9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1},
{1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
};
private static final byte[] BLOCK_PICK_BITS_BY_VERT_INDEX = new byte[]{
(byte)0x67,
(byte)0x26,
(byte)0x23,
(byte)0x37,
(byte)0x45,
(byte)0x04,
(byte)0x01,
(byte)0x15,
(byte)0x57,
(byte)0x46,
(byte)0x02,
(byte)0x13
};
static class Triangle {
int[] indices = new int[3]; //array of size 3
public Triangle(int index0, int index1, int index2){
indices[0] = index0;
indices[1] = index1;
indices[2] = index2;
}
}
static class GridCell {
Vector3f[] points = new Vector3f[8]; //array of size 8
double[] val = new double[8]; //array of size 8
public void setValues(
Vector3f p1, Vector3f p2, Vector3f p3, Vector3f p4,
Vector3f p5, Vector3f p6, Vector3f p7, Vector3f p8,
double val1, double val2, double val3, double val4,
double val5, double val6, double val7, double val8
){
points[0] = p1; points[1] = p2; points[2] = p3; points[3] = p4;
points[4] = p5; points[5] = p6; points[6] = p7; points[7] = p8;
val[0] = val1; val[1] = val2; val[2] = val3; val[3] = val4;
val[4] = val5; val[5] = val6; val[6] = val7; val[7] = val8;
}
}
public static ShaderProgram fluidChunkShaderProgram = null;
protected static int polygonize(
GridCell grid,
double isolevel,
List<Triangle> triangles,
Map<String,Integer> vertMap,
List<Vector3f> verts,
List<Vector3f> normals,
List<Integer> trianglesSharingVert
){
int i;
int ntriang;
int cubeIndex = 0;
Vector3f[] vertList = new Vector3f[12];
//get lookup key (index) for edge table
//edge table tells us which vertices are inside of the surface
if (grid.val[0] < isolevel) cubeIndex |= 1;
if (grid.val[1] < isolevel) cubeIndex |= 2;
if (grid.val[2] < isolevel) cubeIndex |= 4;
if (grid.val[3] < isolevel) cubeIndex |= 8;
if (grid.val[4] < isolevel) cubeIndex |= 16;
if (grid.val[5] < isolevel) cubeIndex |= 32;
if (grid.val[6] < isolevel) cubeIndex |= 64;
if (grid.val[7] < isolevel) cubeIndex |= 128;
//Cube is entirely in/out of the surface
if (edgeTable[cubeIndex] == 0)
return(0);
//instead of having all intersections be perfectly at the midpoint,
//for each edge this code calculates where along the edge to place the vertex
//this should dramatically smooth the surface
if ((edgeTable[cubeIndex] & 1) > 0)
vertList[0] =
VertexInterp(isolevel,grid.points[0],grid.points[1],grid.val[0],grid.val[1]);
if ((edgeTable[cubeIndex] & 2) > 0)
vertList[1] =
VertexInterp(isolevel,grid.points[1],grid.points[2],grid.val[1],grid.val[2]);
if ((edgeTable[cubeIndex] & 4) > 0)
vertList[2] =
VertexInterp(isolevel,grid.points[2],grid.points[3],grid.val[2],grid.val[3]);
if ((edgeTable[cubeIndex] & 8) > 0)
vertList[3] =
VertexInterp(isolevel,grid.points[3],grid.points[0],grid.val[3],grid.val[0]);
if ((edgeTable[cubeIndex] & 16) > 0)
vertList[4] =
VertexInterp(isolevel,grid.points[4],grid.points[5],grid.val[4],grid.val[5]);
if ((edgeTable[cubeIndex] & 32) > 0)
vertList[5] =
VertexInterp(isolevel,grid.points[5],grid.points[6],grid.val[5],grid.val[6]);
if ((edgeTable[cubeIndex] & 64) > 0)
vertList[6] =
VertexInterp(isolevel,grid.points[6],grid.points[7],grid.val[6],grid.val[7]);
if ((edgeTable[cubeIndex] & 128) > 0)
vertList[7] =
VertexInterp(isolevel,grid.points[7],grid.points[4],grid.val[7],grid.val[4]);
if ((edgeTable[cubeIndex] & 256) > 0)
vertList[8] =
VertexInterp(isolevel,grid.points[0],grid.points[4],grid.val[0],grid.val[4]);
if ((edgeTable[cubeIndex] & 512) > 0)
vertList[9] =
VertexInterp(isolevel,grid.points[1],grid.points[5],grid.val[1],grid.val[5]);
if ((edgeTable[cubeIndex] & 1024) > 0)
vertList[10] =
VertexInterp(isolevel,grid.points[2],grid.points[6],grid.val[2],grid.val[6]);
if ((edgeTable[cubeIndex] & 2048) > 0)
vertList[11] =
VertexInterp(isolevel,grid.points[3],grid.points[7],grid.val[3],grid.val[7]);
//Create the triangle
ntriang = 0;
for (i=0; triTable[cubeIndex][i]!=-1; i+=3) {
//
// Triangles calculation
//
//get indices
Vector3f vert0 = vertList[triTable[cubeIndex][i+0]];
Vector3f vert1 = vertList[triTable[cubeIndex][i+1]];
Vector3f vert2 = vertList[triTable[cubeIndex][i+2]];
int index0 = getVertIndex(vert0,vertMap,verts);
int index1 = getVertIndex(vert1,vertMap,verts);
int index2 = getVertIndex(vert2,vertMap,verts);
//add 0's to normals until it matches vert count
while(trianglesSharingVert.size() < verts.size()){
trianglesSharingVert.add(0);
normals.add(new Vector3f());
}
//add new triangle
Triangle newTriangle = new Triangle(index0,index1,index2);
triangles.add(newTriangle);
ntriang++;
//
// Normals calculation
//
//calculate normal for new triangle
Vector3f u = verts.get(index1).sub(verts.get(index0), new Vector3f());
Vector3f v = verts.get(index2).sub(verts.get(index1), new Vector3f());
Vector3f n = new Vector3f(u.y * v.z - u.z * v.y, u.z * v.x - u.x * v.z, u.x * v.y - u.y * v.x).normalize();
//for each vertex, average the new normal with the normals that are already there
int trianglesSharingIndex0 = trianglesSharingVert.get(index0);
//calculate proportion of each normal
float oldProportion = trianglesSharingIndex0 / (float)(trianglesSharingIndex0 + 1);
float newProportion = 1.0f / (float)(trianglesSharingIndex0 + 1);
//increment number of triangles sharing vert
trianglesSharingVert.set(index0, trianglesSharingIndex0 + 1);
Vector3f currentNormal = normals.get(index0);
currentNormal = averageNormals(currentNormal,oldProportion,n,newProportion);
normals.get(index0).set(currentNormal);
int trianglesSharingIndex1 = trianglesSharingVert.get(index1);
//calculate proportion of each normal
oldProportion = trianglesSharingIndex1 / (float)(trianglesSharingIndex1 + 1);
newProportion = 1.0f / (float)(trianglesSharingIndex1 + 1);
//increment number of triangles sharing vert
trianglesSharingVert.set(index1, trianglesSharingIndex1 + 1);
currentNormal = normals.get(index1);
currentNormal = averageNormals(currentNormal,oldProportion,n,newProportion);
normals.get(index1).set(currentNormal);
int trianglesSharingIndex2 = trianglesSharingVert.get(index2);
//calculate proportion of each normal
oldProportion = trianglesSharingIndex2 / (float)(trianglesSharingIndex2 + 1);
newProportion = 1.0f / (float)(trianglesSharingIndex2 + 1);
//increment number of triangles sharing vert
trianglesSharingVert.set(index2, trianglesSharingIndex2 + 1);
currentNormal = normals.get(index2);
currentNormal = averageNormals(currentNormal,oldProportion,n,newProportion);
normals.get(index2).set(currentNormal);
}
return(ntriang);
}
//interpolates the location that the edge gets cut based on the magnitudes of the scalars of the vertices at either end of the edge
static Vector3f VertexInterp(double isolevel, Vector3f p1, Vector3f p2, double valp1, double valp2){
double mu;
float x, y, z;
if (Math.abs(isolevel-valp1) < 0.00001)
return(p1);
if (Math.abs(isolevel-valp2) < 0.00001)
return(p2);
if (Math.abs(valp1-valp2) < 0.00001)
return(p1);
mu = (isolevel - valp1) / (valp2 - valp1);
x = (float)(p1.x + mu * (p2.x - p1.x));
y = (float)(p1.y + mu * (p2.y - p1.y));
z = (float)(p1.z + mu * (p2.z - p1.z));
return new Vector3f(x,y,z);
}
public static FluidChunkModelData generateFluidChunkData(float[][][] weightGrid, int[][][] typeGrid){
// 5 6
// +-------------+ +-----5-------+ ^ Y
// / | / | / | /| | _
// / | / | 4 9 6 10 | /\ Z
// 4 +-----+-------+ 7 | +-----+7------+ | | /
// | 1 +-------+-----+ 2 | +-----1-+-----+ | /
// | / | / 8 0 11 2 | /
// | / | / | / | / |/
// 0 +-------------+ 3 +------3------+ +---------------> X
//the current grid cell
GridCell currentCell = new GridCell();
//the list of all triangles
List<Triangle> triangles = new LinkedList<Triangle>();
//the map of vertex to index
Map<String,Integer> vertMap = new HashMap<String,Integer>();
//the list of all verts
List<Vector3f> verts = new LinkedList<Vector3f>();
//the list of all normals
List<Vector3f> normals = new LinkedList<Vector3f>();
//the list of number of triangles that share a vert
List<Integer> trianglesSharingVert = new LinkedList<Integer>();
//List of elements in order
List<Integer> faceElements = new LinkedList<Integer>();
//List of UVs
List<Float> UVs = new LinkedList<Float>();
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++){
//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),
new Vector3f(x+0,y+1,z+0), new Vector3f(x+0,y+1,z+1), new Vector3f(x+1,y+1,z+1), new Vector3f(x+1,y+1,z+0),
weightGrid[x+0][y+0][z+0], weightGrid[x+0][y+0][z+1], weightGrid[x+1][y+0][z+1], weightGrid[x+1][y+0][z+0],
weightGrid[x+0][y+1][z+0], weightGrid[x+0][y+1][z+1], weightGrid[x+1][y+1][z+1], weightGrid[x+1][y+1][z+0]
);
//polygonize the current gridcell
polygonize(currentCell, 0, triangles, vertMap, verts, normals, trianglesSharingVert);
}
}
}
//all verts in order, flattened as an array of floats instead of vecs
List<Float> vertsFlat = new LinkedList<Float>();
//all normals in order, flattened as an array of floats instead of vecs
List<Float> normalsFlat = new LinkedList<Float>();
//all elements of faces in order
List<Integer> elementsFlat = new LinkedList<Integer>();
//flatten verts + normals
for(Vector3f vert : verts){
vertsFlat.add(vert.x);
vertsFlat.add(vert.y);
vertsFlat.add(vert.z);
}
for(Vector3f normal : normals){
normalsFlat.add(normal.x);
normalsFlat.add(normal.y);
normalsFlat.add(normal.z);
}
for(Triangle triangle : triangles){
elementsFlat.add(triangle.indices[0]);
elementsFlat.add(triangle.indices[1]);
elementsFlat.add(triangle.indices[2]);
}
float[] temp = new float[3];
int i = 0;
for(Vector3f normal : normals){
Vector3f vert = verts.get(i);
float absX = Math.abs(normal.x);
float absY = Math.abs(normal.y);
float absZ = Math.abs(normal.z);
float uvX = vert.z * absX + vert.x * absY + vert.x * absZ;
float uvY = vert.y * absX + vert.z * absY + vert.y * absZ;
temp[0] = uvX;
temp[1] = uvY;
// if(absX >= absZ && absX >= absY){
// temp[0] = normal.z / 2.0f + 0.5f + vert.z * (absX / (absX + absZ)) + vert.x * (absZ / (absX + absZ));
// temp[1] = normal.y / 2.0f + 0.5f + vert.x * (absY / (absX + absY)) + vert.y * (absX / (absX + absY));
// } else if(absZ >= absX && absZ >= absY){
// temp[0] = normal.x / 2.0f + 0.5f + vert.z * (absX / (absX + absZ)) + vert.x * (absZ / (absX + absZ));
// temp[1] = normal.y / 2.0f + 0.5f + vert.z * (absY / (absZ + absY)) + vert.y * (absZ / (absZ + absY));
// } else if(absY >= absX && absY >= absZ){
// temp[0] = normal.x / 2.0f + 0.5f + vert.y * (absX / (absX + absY)) + vert.x * (absY / (absX + absY));
// temp[1] = normal.z / 2.0f + 0.5f + vert.y * (absZ / (absZ + absY)) + vert.z * (absY / (absZ + absY));
// } else {
// temp[0] = vert.x / 1.5f + vert.z / 1.5f;
// temp[1] = vert.y / 1.5f + vert.z / 1.5f;
// }
i++;
UVs.add(temp[0]);
UVs.add(temp[1]);
}
//List<Float> vertices, List<Float> normals, List<Integer> faceElements, List<Float> uvs
FluidChunkModelData rVal = new FluidChunkModelData(vertsFlat, normalsFlat, elementsFlat, UVs);
return rVal;
}
/**
* Generates a mesh based on a fluidchunkdata object
* @param data The fluid chunk data object
* @return The mesh
*/
protected static Mesh generateFluidMesh(FluidChunkModelData data){
Mesh mesh = new Mesh();
mesh.mesh = null;
//
// VAO
//
mesh.vertexArrayObject = glGenVertexArrays();
glBindVertexArray(mesh.vertexArrayObject);
//
//Buffer data to GPU
//
try {
mesh.vertexCount = data.getVertices().size() / 3;
FloatBuffer VertexArrayBufferData = BufferUtils.createFloatBuffer(mesh.vertexCount * 3);
float[] temp = new float[3];
for(float vertValue : data.getVertices()){
VertexArrayBufferData.put(vertValue);
}
VertexArrayBufferData.flip();
mesh.buffer_vertices(VertexArrayBufferData, 3);
} catch (NullPointerException ex){
ex.printStackTrace();
}
//
// FACES
//
mesh.faceCount = data.getFaceElements().size() / 3;
mesh.elementCount = data.getFaceElements().size();
try {
IntBuffer elementArrayBufferData = BufferUtils.createIntBuffer(mesh.elementCount);
int[] temp = new int[3];
for(int element : data.getFaceElements()){
elementArrayBufferData.put(element);
}
elementArrayBufferData.flip();
mesh.buffer_faces(elementArrayBufferData);
} catch (NullPointerException ex){
ex.printStackTrace();
}
//
// NORMALS
//
try {
mesh.normalCount = data.getNormals().size() / 3;
FloatBuffer NormalArrayBufferData;
if(mesh.normalCount > 0){
NormalArrayBufferData = BufferUtils.createFloatBuffer(mesh.normalCount * 3);
float[] temp = new float[3];
for(float normalValue : data.getNormals()){
NormalArrayBufferData.put(normalValue);
}
NormalArrayBufferData.flip();
mesh.buffer_normals(NormalArrayBufferData, 3);
}
} catch (NullPointerException ex){
ex.printStackTrace();
}
//
// TEXTURE COORDINATES
//
try {
mesh.textureCoordCount = data.getUVs().size() / 2;
FloatBuffer TextureArrayBufferData;
if(mesh.textureCoordCount > 0){
TextureArrayBufferData = BufferUtils.createFloatBuffer(mesh.textureCoordCount * 2);
float[] temp = new float[2];
for(float uvValue : data.getUVs()){
TextureArrayBufferData.put(uvValue);
}
TextureArrayBufferData.flip();
mesh.buffer_texture_coords(TextureArrayBufferData, 2);
}
} catch (NullPointerException ex){
ex.printStackTrace();
}
glBindVertexArray(0);
mesh.nodeID = "fluidChunk";
return mesh;
}
/**
* Generates a model based on a fluidchunkdata object
* @param data The fluid chunk data object
* @return The model
*/
public static Model generateFluidModel(FluidChunkModelData data){
Model rVal = new Model();
rVal.meshes = new ArrayList<Mesh>();
Mesh m = generateFluidMesh(data);
Material groundMat = new Material();
groundMat.set_diffuse("/Textures/Ground/Dirt1.png");
groundMat.set_specular("/Textures/Ground/Dirt1.png");
Globals.assetManager.addTexturePathtoQueue("/Textures/Ground/Dirt1.png");
m.setMaterial(groundMat);
m.setShader(FluidChunkModelGeneration.fluidChunkShaderProgram);
m.parent = rVal;
rVal.meshes.add(m);
return rVal;
}
//TODO: more optimal key creation
private static String getVertKeyFromPoints(float x, float y, float z){
return x + "_" + y + "_" + z;
}
private static int getVertIndex(Vector3f vert, Map<String,Integer> vertMap, List<Vector3f> verts){
int rVal = -1;
String vertKey = getVertKeyFromPoints(vert.x,vert.y,vert.z);
if(vertMap.containsKey(vertKey)){
return vertMap.get(vertKey);
} else {
rVal = verts.size();
verts.add(vert);
vertMap.put(vertKey,rVal);
return rVal;
}
}
private static Vector3f averageNormals(Vector3f normal0, float proportion0, Vector3f normal1, float proportion1){
Vector3f rVal = new Vector3f(normal0);
rVal = rVal.mul(proportion0).add(new Vector3f(normal1).mul(proportion1));
return rVal;
}
}