From 7a2bdf774588a0e05749dfede6cc5818d3f3fbe8 Mon Sep 17 00:00:00 2001 From: austin Date: Sat, 22 Jun 2024 16:10:30 -0400 Subject: [PATCH] transvoxel algorithm meshgen --- buildNumber.properties | 4 +- docs/src/architecture/architectureindex.md | 1 + docs/src/architecture/drawcell/drawcell.md | 6 + .../drawcell/transvoxelalgorithm.md | 49 + .../narrativemanager/narrativearcdesign.md | 3 + .../narrativemanager/narrativemanager.md | 1 + .../architecture/drawcell/ClientMacroArch.png | Bin 0 -> 28924 bytes .../architecture/drawcell/adaptedvoxel.png | Bin 0 -> 9921 bytes .../architecture/drawcell/bisectedvoxel.png | Bin 0 -> 10079 bytes .../architecture/drawcell/completevoxel.png | Bin 0 -> 6540 bytes docs/src/progress/bigthings.md | 11 + docs/src/progress/renderertodo.md | 53 +- docs/src/testing/testing.md | 17 + net/terrain.json | 26 + .../client/terrain/cells/DrawCell.java | 103 +- .../client/terrain/cells/DrawCellManager.java | 233 +- .../terrain/cells/VoxelTextureAtlas.java | 2 +- .../terrain/editing/TerrainEditing.java | 2 - .../terrain/manager/ClientTerrainManager.java | 24 +- .../engine/loadingthreads/ClientLoading.java | 44 +- .../entity/types/terrain/TerrainChunk.java | 19 +- .../parser/net/message/NetworkMessage.java | 10 + .../parser/net/message/TerrainMessage.java | 145 + .../net/parser/net/message/TypeBytes.java | 9 +- .../meshgen/TerrainChunkModelGeneration.java | 12 +- .../meshgen/TransvoxelModelGeneration.java | 2755 +++++++++++++++-- .../server/content/ServerContentManager.java | 5 +- 27 files changed, 3051 insertions(+), 483 deletions(-) create mode 100644 docs/src/architecture/drawcell/drawcell.md create mode 100644 docs/src/architecture/drawcell/transvoxelalgorithm.md create mode 100644 docs/src/highlevel-design/narrativemanager/narrativearcdesign.md create mode 100644 docs/src/images/architecture/drawcell/ClientMacroArch.png create mode 100644 docs/src/images/architecture/drawcell/adaptedvoxel.png create mode 100644 docs/src/images/architecture/drawcell/bisectedvoxel.png create mode 100644 docs/src/images/architecture/drawcell/completevoxel.png create mode 100644 docs/src/testing/testing.md diff --git a/buildNumber.properties b/buildNumber.properties index 14c7a866..147fbfe3 100644 --- a/buildNumber.properties +++ b/buildNumber.properties @@ -1,3 +1,3 @@ #maven.buildNumber.plugin properties file -#Fri Jun 14 13:45:11 EDT 2024 -buildNumber=137 +#Wed Jun 19 19:19:09 EDT 2024 +buildNumber=138 diff --git a/docs/src/architecture/architectureindex.md b/docs/src/architecture/architectureindex.md index dc28fc1a..f5717514 100644 --- a/docs/src/architecture/architectureindex.md +++ b/docs/src/architecture/architectureindex.md @@ -10,6 +10,7 @@ - @subpage archimprovementtargets - @subpage savesindex - @subpage hitboxesindex +- @subpage drawcell # What is this section diff --git a/docs/src/architecture/drawcell/drawcell.md b/docs/src/architecture/drawcell/drawcell.md new file mode 100644 index 00000000..05d42f61 --- /dev/null +++ b/docs/src/architecture/drawcell/drawcell.md @@ -0,0 +1,6 @@ +@page drawcell Draw Cells + +[TOC] + - @subpage transvoxelalgorithm + +![](/docs/src/images/architecture/drawcell/ClientMacroArch.png) \ No newline at end of file diff --git a/docs/src/architecture/drawcell/transvoxelalgorithm.md b/docs/src/architecture/drawcell/transvoxelalgorithm.md new file mode 100644 index 00000000..e0a8002e --- /dev/null +++ b/docs/src/architecture/drawcell/transvoxelalgorithm.md @@ -0,0 +1,49 @@ +@page transvoxelalgorithm Transvoxel Chunk Generation + + + +# High Level + + +The goal of the transvoxel algorithm is to bridge the divide between a chunk of a higher resolution and a chunk of a lower resolution + +![](/docs/src/images/architecture/drawcell/completevoxel.png) + +For the voxels on the border between a low resolution chunk and a high resolution chunk, we split the voxels in half + +![](/docs/src/images/architecture/drawcell/bisectedvoxel.png) + +On the low-resolution half, we perform marching cubes + +On the high resolution half, we generate a mesh that adapts the high resolution chunk to the low resolution voxel + +![](/docs/src/images/architecture/drawcell/adaptedvoxel.png) + +# Major files +[TransvoxelModelGeneration.java](@ref #electrosphere.renderer.meshgen.TransvoxelModelGeneration) - The main class that turns voxel data into mesh data + +[TerrainChunk.java](@ref #electrosphere.entity.types.terrain.TerrainChunk) - The class that the DrawCellManager calls to generate chunk meshes + +[ClientTerrainManager.java](@ref #electrosphere.client.terrain.manager.ClientTerrainManager) - Handles queueing the mesh data to the gpu + +[TerrainChunkGenQueueItem.java](@ref #electrosphere.client.terrain.manager.TerrainChunkGenQueueItem) - A terrain mesh that has been queued to be added to the gpu + + + + +# Implementation Notes + +The description of the algorithm always refers to the transition cells in terms of y pointing upwards and x pointing to the right. +This can be confusing to think about how it would apply to other orientations (ie what if we're working along the y axis or something and x is "up"). +All these transforms are done implicitly in the `generateTerrainChunkData` function. When iterating across each axis, I've manually calculated what point should be where. +The inner polygonize functions always treat it as y-up, but that works because this transform has already been done before the data is passed in. + + + + +# Notes about table format + - `transitionCellClass` is indexed into by the case index value generated from the high resolution face data + - `transitionCellData` is indexed into by the class value from the `transitionCellClass` + - `transitionVertexData` is ALSO indexed into by the CASE INDEX VALUE, NOT THE CLASS VALUE. You can figure this out because the `transitionVertexData` has 512 entries + + diff --git a/docs/src/highlevel-design/narrativemanager/narrativearcdesign.md b/docs/src/highlevel-design/narrativemanager/narrativearcdesign.md new file mode 100644 index 00000000..92c38791 --- /dev/null +++ b/docs/src/highlevel-design/narrativemanager/narrativearcdesign.md @@ -0,0 +1,3 @@ +@page narrativearcdesign Narrative Arc Design + +Use the idea of conspiracy to create mystery eventually leading to a big series of confrontations and narrative payoff \ No newline at end of file diff --git a/docs/src/highlevel-design/narrativemanager/narrativemanager.md b/docs/src/highlevel-design/narrativemanager/narrativemanager.md index bc206cff..6aa9fcea 100644 --- a/docs/src/highlevel-design/narrativemanager/narrativemanager.md +++ b/docs/src/highlevel-design/narrativemanager/narrativemanager.md @@ -4,6 +4,7 @@ [TOC] - @subpage whatmakesaquestgood + - @subpage narrativearcdesign TODO: describe diff --git a/docs/src/images/architecture/drawcell/ClientMacroArch.png b/docs/src/images/architecture/drawcell/ClientMacroArch.png new file mode 100644 index 0000000000000000000000000000000000000000..9c5691d2f201f2d849496af724f120283fb61ec0 GIT binary patch literal 28924 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%0A~?u_mnF*aJQHFUQY3acibPs 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 literal 0 HcmV?d00001 diff --git a/docs/src/images/architecture/drawcell/adaptedvoxel.png b/docs/src/images/architecture/drawcell/adaptedvoxel.png new file mode 100644 index 0000000000000000000000000000000000000000..4d8c28b46b7970a7753462e2fc10e6056f458cdc GIT binary patch literal 9921 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 literal 0 HcmV?d00001 diff --git a/docs/src/progress/bigthings.md b/docs/src/progress/bigthings.md index 3680cf07..bd559a1a 100644 --- a/docs/src/progress/bigthings.md +++ b/docs/src/progress/bigthings.md @@ -1,6 +1,11 @@ @page bigthings Big Things I want to build # and may or may not have the sanity to build +DONE: + + +TODO(?): + - CFD - Internal Boundaries - Multigrid optimization @@ -8,6 +13,8 @@ - Transvoxel Algorithm + - Building cube voxels w/ LOD + - Deferred Shading Pipeline - Audio Ray Tracing @@ -17,3 +24,7 @@ - Massive scale creature groups (ie 10k armies) + - Use HTML to define ui windows + - Some css support + - Language file for translation + diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 9e8b4d2b..f758955e 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -371,9 +371,23 @@ Highlevel netcode gen updates - Furthermore, keep tracking of the existing ids for trees and fields and only generate ids for new trees and fields Fix client gravity tree name +(06/19/2024) +Transvoxel implementation + - Begin work on transvoxel algo + +(06/21/2024) +Transvoxel implementation + - First working implementation of mesh generation for transvoxel chunks (architecture of adding it to drawcellmanager still todo) + +(06/22/2024) +Transvoxel implementation + - Scaling LODed chunks by lod level + # TODO + + Demo requirements: = Assets = Block animation in first person @@ -400,6 +414,26 @@ Ability for private realms to have time start/stop based on the player's feedbac + + + + +Goof off today requirements: +Transvoxel implementation + - Fix draw cell manager requesting far-out chunks + - Properly update to higher LOD meshes as you get closer + Client Terrain Entity Management (specifically creation/teardown for client) + - Also queries for far out chunks to load far away terrain + Server Terrain Management (specifically for collision) + - Handles communicating far out LOD chunks to client as well + Terrain Interface Positional Access Interface + - Ability to get terrain at point for interactions with game world eg placing grass/water collision + + + + + + BIG BIG BIG BIG IMMEDIATE TO DO: always enforce opengl interface across all opengl calls jesus christ the bone uniform bug was impossible @@ -456,22 +490,6 @@ Shader library system - Abiltiy to include the shader library in individual files (ie implement #include) Break control handlers into separate files with new logic to transition between control handler states - - -Transvoxel Algorithm - Client Terrain Entity Management (specifically creation/teardown for client) - - Also queries for far out chunks to load far away terrain - Server Terrain Management (specifically for collision) - - Handles communicating far out LOD chunks to client as well - Terrain Interface Positional Access Interface - - Ability to get terrain at point for interactions with game world eg placing grass/water collision - Actually implement transvoxel algo - Marching Cubes Texture Overhaul - - Detect opengl max image size - - Construct texture atlas of max size - - (target 256x256 resolution initially, should give ~1000 types for 8192x8192) - - Prebake all textures into atlas - - Rewrite marching cubes shader to leverage this atlas Another pass at grass - Multiple foliage models in same cell @@ -580,9 +598,6 @@ Generic collision engine to support different instances of engine (eg hitboxes v - Major refactoring to happen here Procedural Cliff Texture - Uses noise or fractals or something to generate infinite textures in shader -Terrain Chunks: - - Scale textures to be 1 texture per unit of terrain - - Texture atlasing (512x512) Loot Generator - System that can generate items that would be appropriate reward given some variables - ie you tell it 'this is this character's stats, this is the relative level of loot I want to provide' diff --git a/docs/src/testing/testing.md b/docs/src/testing/testing.md new file mode 100644 index 00000000..f313197e --- /dev/null +++ b/docs/src/testing/testing.md @@ -0,0 +1,17 @@ +@page testing Testing + +Eventual goal is to have unit tests for parts of the engine that it makes sense for. Some ideas: + - Loading assets + - UI functionality + - Basic behavior trees + - Script Engine + - AI + - Networking + + +Current CI is Jenkins, which has a plugin +https://plugins.jenkins.io/xvfb/ +that allows for graphical sessions while building + +I need to figure out hooking this up to my container in order to do most of the above testing + diff --git a/net/terrain.json b/net/terrain.json index 25a05fff..bd70d2c8 100644 --- a/net/terrain.json +++ b/net/terrain.json @@ -90,6 +90,11 @@ "type" : "BYTE_ARRAY" }, + { + "name" : "chunkResolution", + "type" : "FIXED_INT" + }, + { "name" : "terrainWeight", "type" : "FIXED_FLOAT" @@ -186,6 +191,27 @@ "chunkData" ] }, + { + "messageName" : "RequestReducedChunkData", + "description" : "Requests reduced resolution chunk data from the server", + "data" : [ + "worldX", + "worldY", + "worldZ", + "chunkResolution" + ] + }, + { + "messageName" : "SendReducedChunkData", + "description" : "Sends chunk data to the client", + "data" : [ + "worldX", + "worldY", + "worldZ", + "chunkResolution", + "chunkData" + ] + }, { "messageName" : "RequestFluidData", "description" : "Requests a fluid data from the server", diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java index 7e9ca219..2bd9a1a6 100644 --- a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java +++ b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java @@ -11,39 +11,47 @@ import electrosphere.entity.ClientEntityUtils; import electrosphere.entity.Entity; import electrosphere.entity.EntityUtils; import electrosphere.entity.types.terrain.TerrainChunk; -import electrosphere.renderer.shader.ShaderProgram; -import electrosphere.renderer.texture.Texture; -import electrosphere.server.datacell.Realm; +import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData; +import electrosphere.server.terrain.manager.ServerTerrainChunk; /** - * - * @author satellite + * A single drawcell - contains an entity that has a physics mesh and potentially graphics */ public class DrawCell { + + /** + * Enum for the different faces of a draw cell -- used when filling in data for higher LOD faces + */ + public enum DrawCellFace { + X_POSITIVE, + X_NEGATIVE, + Y_POSITIVE, + Y_NEGATIVE, + Z_POSITIVE, + Z_NEGATIVE, + } + //the position of the draw cell in world coordinates Vector3i worldPos; + //the main entity for the cell Entity modelEntity; + //the physics mesh DBody physicsObject; //Allocated once instead of continuously, used to generate the visual/physics models float[][][] weights = new float[ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE]; int[][][] types = new int[ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE][ChunkData.CHUNK_DATA_GENERATOR_SIZE]; + + + //the maximum detail LOD level + public static final int FULL_DETAIL_LOD = 0; + - 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"); - } - - + /** + * Private constructor + */ DrawCell(){ } @@ -63,18 +71,23 @@ public class DrawCell { /** * Generates a drawable entity based on this chunk */ - public void generateDrawableEntity(VoxelTextureAtlas atlas){ + public void generateDrawableEntity(VoxelTextureAtlas atlas, int lod, DrawCellFace higherLODFace){ if(modelEntity != null){ Globals.clientScene.deregisterEntity(modelEntity); } + this.fillInData(); + TransvoxelChunkData chunkData = new TransvoxelChunkData(weights, types, lod); + if(lod > FULL_DETAIL_LOD){ - fillInData(); - - modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(weights, types, 0, atlas); - + } + modelEntity = TerrainChunk.clientCreateTerrainChunkEntity(chunkData, lod, atlas); ClientEntityUtils.initiallyPositionEntity(modelEntity, getRealPos()); } + /** + * Gets the real-space position of the draw cell + * @return the real-space position + */ protected Vector3d getRealPos(){ return new Vector3d( worldPos.x * ChunkData.CHUNK_SIZE, @@ -224,5 +237,49 @@ public class DrawCell { types[ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE][ChunkData.CHUNK_SIZE] = 0; } } + + + /** + * Fills in the internal arrays of data for generate terrain models + */ + private void fillInData(TransvoxelChunkData chunkData, int lod, DrawCellFace face){ + float[][] faceData = new float[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE]; + int[][] atlasData = new int[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE]; + switch(face){ + case X_POSITIVE: { + } break; + case X_NEGATIVE: { + } break; + case Y_POSITIVE: { + } break; + case Y_NEGATIVE: { + } break; + case Z_POSITIVE: { + } break; + case Z_NEGATIVE: { + } break; + } + // + //fill in data + // + //main chunk + //face X + // if(worldPos.x + 1 < Globals.clientWorldData.getWorldDiscreteSize()){ + // currentChunk = Globals.clientTerrainManager.getChunkDataAtWorldPoint(worldPos.x + 1, worldPos.y, worldPos.z); + // for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ + // for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){ + // weights[ChunkData.CHUNK_SIZE][i][j] = currentChunk.getWeight(0, i, j); + // types[ChunkData.CHUNK_SIZE][i][j] = currentChunk.getType(0, i, j); + // } + // } + // } else { + // for(int i = 0; i < ChunkData.CHUNK_SIZE; i++){ + // for(int j = 0; j < ChunkData.CHUNK_SIZE; j++){ + // weights[ChunkData.CHUNK_SIZE][i][j] = -1; + // types[ChunkData.CHUNK_SIZE][i][j] = 0; + // } + // } + // } + } } diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java index b5e332d9..4f7a3508 100644 --- a/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java +++ b/src/main/java/electrosphere/client/terrain/cells/DrawCellManager.java @@ -9,15 +9,40 @@ import org.joml.Vector3d; import org.joml.Vector3i; import electrosphere.client.terrain.cache.ChunkData; +import electrosphere.client.terrain.cells.DrawCell.DrawCellFace; import electrosphere.client.terrain.manager.ClientTerrainManager; import electrosphere.engine.Globals; import electrosphere.entity.EntityUtils; import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.renderer.shader.ShaderProgram; +import electrosphere.server.terrain.manager.ServerTerrainChunk; /** - * - * @author satellite + * Manages the graphical entities for the terrain chunks + * + * + * +Notes for integrating with transvoxel algo: +Different problems to tackle + +For all chunks within minimum radius, check if they can be updated and update accordingly <--- we do this currently + + +For all chunks between minimum radius and first LOD radius, check if we can make a LOD chunk +If we can, make a lod chunk or see if it can be updated +This check will be: +For every position, check if all four positions required for LOD chunk are within lod radius + if yes, make lod chunk + +If we cannot, create a fullres chunk +This check will be: +For every position, check if all four positions required for LOD chunk are within lod radius + if yes, make lod chunk + if they are outside the far bound, create lod chunk + if they are within the near bound, create a fullres chunk + + + */ public class DrawCellManager { @@ -27,45 +52,46 @@ public class DrawCellManager { int cellY; int cellZ; - - //the dimensions of the world that this cell manager can handles - int cellWidth; - - //the width of a minicell in this manager - int miniCellWidth; - //all currently displaying mini cells - Set cells; + Set cells = new HashSet(); Map keyCellMap = new HashMap(); - Set hasNotRequested; - Set hasRequested; - Set drawable; - Set undrawable; - Set updateable; + + //status of all position keys + Set hasNotRequested = new HashSet(); + Set requested = new HashSet(); + Set drawable = new HashSet(); + Set undrawable = new HashSet(); + Set updateable = new HashSet(); + + //LOD level of all position keys + Map positionLODLevel = new HashMap(); //voxel atlas VoxelTextureAtlas atlas; - + //shader program for drawable cells ShaderProgram program; - // int drawRadius = 5; - int drawStepdownInterval = 3; - int drawStepdownValue = 25; + //the real-space radius for which we will construct draw cells inside of + //ie, we check if the draw cell's entity would be inside this radius. If it would, create the draw cell, otherwise don't + double drawFullModelRadius = 50; - double drawRadius = 50; + //the radius we'll draw LODed chunks for + double drawLODRadius = drawFullModelRadius + ServerTerrainChunk.CHUNK_DIMENSION * (2*2 + 4*4 + 8*8 + 16*16); + + + //the number of possible LOD levels + //1,2,4,8,16 + static final int NUMBER_OF_LOD_LEVELS = 5; + + //the table of lod leve -> radius at which we will look for chunks within this log + double[] lodLevelRadiusTable = new double[5]; + //the radius for which physics meshes are created when draw cells are created int physicsRadius = 3; - int worldBoundDiscreteMin = 0; - int worldBoundDiscreteMax = 0; - - //client terrain manager - // ClientTerrainManager clientTerrainManager; - - //ready to start updating? boolean update = false; @@ -82,22 +108,22 @@ public class DrawCellManager { * @param discreteY The initial discrete position Y coordinate */ public DrawCellManager(ClientTerrainManager clientTerrainManager, int discreteX, int discreteY, int discreteZ){ - worldBoundDiscreteMax = (int)(Globals.clientWorldData.getWorldBoundMin().x / Globals.clientWorldData.getDynamicInterpolationRatio() * 1.0f); - cells = new HashSet(); - hasNotRequested = new HashSet(); - drawable = new HashSet(); - undrawable = new HashSet(); - updateable = new HashSet(); - hasRequested = new HashSet(); - cellX = discreteX; cellY = discreteY; cellZ = discreteZ; program = Globals.terrainShaderProgram; + + //the first lod level is set by user + lodLevelRadiusTable[0] = drawFullModelRadius; + //generate LOD radius table + for(int i = 1; i < NUMBER_OF_LOD_LEVELS; i++){ + double sizeOfSingleModel = Math.pow(2,i) * ServerTerrainChunk.CHUNK_DIMENSION; + //size of the radius for this lod level should be three times the size of a model + the previous radius + //this guarantees we get at least one adapter chunk, one proper chunk, and also that the radius accounts for the previous lod level chunks + lodLevelRadiusTable[i] = lodLevelRadiusTable[i-1] + sizeOfSingleModel * 3; + } - // drawRadius = Globals.userSettings.getGraphicsPerformanceLODChunkRadius(); - drawStepdownInterval = Globals.userSettings.getGameplayPhysicsCellRadius(); physicsRadius = Globals.userSettings.getGameplayPhysicsCellRadius(); invalidateAllCells(); @@ -105,29 +131,41 @@ public class DrawCellManager { update = true; } + /** + * Private constructor + */ DrawCellManager(){ } - public void setCell(Vector3i cellPos){ + /** + * Sets the player's current position in cell-space + * @param cellPos The cell's position + */ + public void setPlayerCell(Vector3i cellPos){ cellX = cellPos.x; cellY = cellPos.y; cellZ = cellPos.z; } + /** + * Update function that is called if a cell has not been requested + */ void updateUnrequestedCell(){ if(hasNotRequested.size() > 0){ String targetKey = hasNotRequested.iterator().next(); hasNotRequested.remove(targetKey); Vector3i worldPos = getVectorFromKey(targetKey); - // Vector3i vector = getVectorFromKey(targetKey); - // int currentCellX = cellX - drawRadius + vector.x; - // int currentCellY = cellY - drawRadius + vector.y; - // int currentCellZ = cellZ - drawRadius + vector.z; + + // + //Because of the way marching cubes works, we need to request the adjacent chunks so we know how to properly blend between one chunk and the next + //The following loop-hell does this + // for(int i = 0; i < 2; i++){ for(int j = 0; j < 2; j++){ for(int k = 0; k < 2; k++){ Vector3i posToCheck = new Vector3i(worldPos).add(i,j,k); + String requestKey = getCellKey(posToCheck.x,posToCheck.y,posToCheck.z); if( posToCheck.x >= 0 && posToCheck.x < Globals.clientWorldData.getWorldDiscreteSize() && @@ -137,20 +175,20 @@ public class DrawCellManager { posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() && !Globals.clientTerrainManager.containsChunkDataAtWorldPoint(posToCheck.x, posToCheck.y, posToCheck.z) ){ - // if(!hasRequested.contains(targetKey)){ + if(!requested.contains(requestKey)){ //client should request chunk data from server for each chunk necessary to create the model Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestChunkDataMessage( posToCheck.x, posToCheck.y, posToCheck.z )); - // } + } } - undrawable.add(targetKey); - hasRequested.add(targetKey); } } } + undrawable.add(targetKey); + requested.add(targetKey); } } @@ -158,10 +196,12 @@ public class DrawCellManager { * Makes one of the undrawable cells drawable */ void makeCellDrawable(){ - if(undrawable.size() > 0){ String targetKey = undrawable.iterator().next(); Vector3i worldPos = getVectorFromKey(targetKey); + + // + //Checks if all chunk data necessary to generate a mesh is present boolean containsNecessaryChunks = true; for(int i = 0; i < 2; i++){ for(int j = 0; j < 2; j++){ @@ -175,25 +215,28 @@ public class DrawCellManager { posToCheck.z < Globals.clientWorldData.getWorldDiscreteSize() && !containsChunkDataAtWorldPoint(posToCheck.x,posToCheck.y,posToCheck.z) ){ - containsChunkDataAtWorldPoint(posToCheck.x,posToCheck.y,posToCheck.z); containsNecessaryChunks = false; } } } } - //if contains all chunks necessary to generate visuals + // + //if contains data for all chunks necessary to generate visuals if(containsNecessaryChunks){ - //build float array + + //update the status of the terrain key + undrawable.remove(targetKey); + drawable.add(targetKey); + + //build the cell DrawCell cell = DrawCell.generateTerrainCell( worldPos ); cells.add(cell); keyCellMap.put(targetKey,cell); - // undrawable.add(targetKey); - undrawable.remove(targetKey); - drawable.add(targetKey); - //make drawable entity - keyCellMap.get(targetKey).generateDrawableEntity(atlas); + DrawCellFace higherLODFace = null; + keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace); + //evaluate for foliage Globals.clientFoliageManager.evaluateChunk(worldPos); } @@ -216,37 +259,49 @@ public class DrawCellManager { worldPos.z >= 0 && worldPos.z < Globals.clientWorldData.getWorldDiscreteSize() ){ -// if(Math.abs(drawRadius + 1 - targetX) < physicsRadius && Math.abs(drawRadius + 1 - targetY) < physicsRadius){ -// needsPhysics[targetX][targetY] = true; -// } - // int dist = (int)Math.sqrt((targetX - drawRadius)*(targetX - drawRadius) + (targetY - drawRadius) * (targetY - drawRadius)); //Math.abs(targetX - drawRadius) * Math.abs(targetY - drawRadius); - // int stride = Math.min(commonWorldData.getDynamicInterpolationRatio()/2, Math.max(1, dist / drawStepdownInterval * drawStepdownValue)); - // while(commonWorldData.getDynamicInterpolationRatio() % stride != 0){ - // stride = stride + 1; - // } keyCellMap.get(targetKey).destroy(); - keyCellMap.get(targetKey).generateDrawableEntity(atlas); + DrawCellFace higherLODFace = null; + keyCellMap.get(targetKey).generateDrawableEntity(atlas,0,higherLODFace); } drawable.add(targetKey); } } + + + + /** + * Checks if the manager contains a cell position that hasn't had its chunk data requested from the server yet + * @return true if there is an unrequested cell, false otherwise + */ public boolean containsUnrequestedCell(){ return hasNotRequested.size() > 0; } + /** + * Checks if the manager contains a cell who hasn't been made drawable yet + * @return true if there is an undrawable cell, false otherwise + */ public boolean containsUndrawableCell(){ return undrawable.size() > 0; } + /** + * Checks if the manager contains a cell who needs to be updated + * @return true if there is an updateable cell, false otherwise + */ public boolean containsUpdateableCell(){ return updateable.size() > 0; } - + /** + * Transforms a real coordinate into a cell-space coordinate + * @param input the real coordinate + * @return the cell coordinate + */ public int transformRealSpaceToCellSpace(double input){ return (int)(input / Globals.clientWorldData.getDynamicInterpolationRatio()); } @@ -278,7 +333,7 @@ public class DrawCellManager { Set cellsToRemove = new HashSet(); for(DrawCell cell : cells){ Vector3d realPos = cell.getRealPos(); - if(Globals.playerEntity != null && EntityUtils.getPosition(Globals.playerEntity).distance(realPos) > drawRadius){ + if(Globals.playerEntity != null && EntityUtils.getPosition(Globals.playerEntity).distance(realPos) > drawFullModelRadius){ cellsToRemove.add(cell); } } @@ -290,7 +345,7 @@ public class DrawCellManager { undrawable.remove(key); updateable.remove(key); keyCellMap.remove(key); - hasRequested.remove(key); + requested.remove(key); cell.destroy(); } } @@ -301,9 +356,9 @@ public class DrawCellManager { private void queueNewCells(){ if(Globals.playerEntity != null && Globals.clientWorldData != null){ Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity); - for(int x = -(int)drawRadius; x < drawRadius; x = x + ChunkData.CHUNK_SIZE){ - for(int y = -(int)drawRadius; y < drawRadius; y = y + ChunkData.CHUNK_SIZE){ - for(int z = -(int)drawRadius; z < drawRadius; z = z + ChunkData.CHUNK_SIZE){ + for(int x = -(int)drawFullModelRadius; x < drawFullModelRadius; x = x + ChunkData.CHUNK_SIZE){ + for(int y = -(int)drawFullModelRadius; y < drawFullModelRadius; y = y + ChunkData.CHUNK_SIZE){ + for(int z = -(int)drawFullModelRadius; z < drawFullModelRadius; z = z + ChunkData.CHUNK_SIZE){ Vector3d newPos = new Vector3d(playerPos.x + x, playerPos.y + y, playerPos.z + z); Vector3i worldPos = new Vector3i( Globals.clientWorldData.convertRealToChunkSpace(newPos.x), @@ -316,7 +371,7 @@ public class DrawCellManager { Globals.clientWorldData.convertChunkToRealSpace(worldPos.z) ); if( - playerPos.distance(chunkRealSpace) < drawRadius && + playerPos.distance(chunkRealSpace) < drawFullModelRadius && worldPos.x >= 0 && worldPos.x < Globals.clientWorldData.getWorldDiscreteSize() && worldPos.y >= 0 && @@ -330,7 +385,7 @@ public class DrawCellManager { Globals.clientWorldData.convertRealToChunkSpace(chunkRealSpace.z) ); if(!keyCellMap.containsKey(key) && !hasNotRequested.contains(key) && !undrawable.contains(key) && !drawable.contains(key) && - !hasRequested.contains(key)){ + !requested.contains(key)){ hasNotRequested.add(key); } } @@ -355,27 +410,20 @@ public class DrawCellManager { } /** - * Splits a cell key into its constituent coordinates in array format. - * @param cellKey The cell key to split - * @return The coordinates in array format + * Controls whether the client generates drawable chunks or just physics chunks (ie if running a headless client) + * @param generate true to generate graphics, false otherwise */ - // private int[] splitKeyToCoordinates(String cellKey){ - // int[] rVal = new int[3]; - // String[] components = cellKey.split("_"); - // for(int i = 0; i < 3; i++){ - // rVal[i] = Integer.parseInt(components[i]); - // } - // return rVal; - // } - - public boolean coordsInPhysicsSpace(int worldX, int worldY){ - return worldX <= cellX + physicsRadius && worldX >= cellX - physicsRadius && worldY <= cellY + physicsRadius && worldY >= cellY - physicsRadius; - } - public void setGenerateDrawables(boolean generate){ this.generateDrawables = generate; } + /** + * Checks if the terrain cache has a chunk at a given world point + * @param worldX the x coordinate + * @param worldY the y coordinate + * @param worldZ the z coordinate + * @return true if the chunk data exists, false otherwise + */ boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ if(Globals.clientTerrainManager != null){ return Globals.clientTerrainManager.containsChunkDataAtWorldPoint(worldX,worldY,worldZ); @@ -428,11 +476,11 @@ public class DrawCellManager { /** - * Gets the draw radius - * @return the draw radius + * Gets the radius within which full-detail models are drawn + * @return the radius */ - public double getDrawRadius(){ - return drawRadius; + public double getDrawFullModelRadius(){ + return drawFullModelRadius; } /** @@ -444,6 +492,5 @@ public class DrawCellManager { -// public } diff --git a/src/main/java/electrosphere/client/terrain/cells/VoxelTextureAtlas.java b/src/main/java/electrosphere/client/terrain/cells/VoxelTextureAtlas.java index 490d31ce..fe2f0e42 100644 --- a/src/main/java/electrosphere/client/terrain/cells/VoxelTextureAtlas.java +++ b/src/main/java/electrosphere/client/terrain/cells/VoxelTextureAtlas.java @@ -74,7 +74,7 @@ public class VoxelTextureAtlas { * @return the index in the atlas of the texture of the provided voxel type */ public int getVoxelTypeOffset(int voxelTypeId){ - return typeCoordMap.get(voxelTypeId); + return typeCoordMap.containsKey(voxelTypeId) ? typeCoordMap.get(voxelTypeId) : -1; } } diff --git a/src/main/java/electrosphere/client/terrain/editing/TerrainEditing.java b/src/main/java/electrosphere/client/terrain/editing/TerrainEditing.java index ccd375ef..06b7fce1 100644 --- a/src/main/java/electrosphere/client/terrain/editing/TerrainEditing.java +++ b/src/main/java/electrosphere/client/terrain/editing/TerrainEditing.java @@ -1,9 +1,7 @@ package electrosphere.client.terrain.editing; import org.joml.Vector3d; -import org.joml.Vector3i; -import electrosphere.client.terrain.cache.ChunkData; import electrosphere.engine.Globals; import electrosphere.net.parser.net.message.TerrainMessage; diff --git a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java index d6194196..9b30da9b 100644 --- a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java +++ b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java @@ -6,7 +6,6 @@ import java.nio.IntBuffer; import java.util.Collection; import java.util.LinkedList; import java.util.List; -import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; @@ -36,7 +35,7 @@ public class ClientTerrainManager { public static final int INTERPOLATION_RATIO = ServerTerrainManager.SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO; //caches chunks from server - static final int CACHE_SIZE = 50; + static final int CACHE_SIZE = 500; //used for caching the macro values ClientTerrainCache terrainCache; @@ -55,6 +54,9 @@ public class ClientTerrainManager { } + /** + * Handles messages that have been received from the server + */ public void handleMessages(){ List bouncedMessages = new LinkedList(); for(TerrainMessage message : messageQueue){ @@ -99,14 +101,32 @@ public class ClientTerrainManager { } } + /** + * Attaches a terrain message to the queue of messages that this manager needs to process + * @param message The message + */ public void attachTerrainMessage(TerrainMessage message){ messageQueue.add(message); } + /** + * Checks if the terrain cache contains chunk data at a given world position + * @param worldX the x position + * @param worldY the y position + * @param worldZ the z position + * @return true if the data exists, false otherwise + */ public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ return terrainCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ); } + /** + * Checks that the cache contains chunk data at a real-space coordinate + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return true if the cache contains the chunk data at the coordinate, false otherwise + */ public boolean containsChunkDataAtRealPoint(double x, double y, double z){ assert clientWorldData != null; return terrainCache.containsChunkDataAtWorldPoint( diff --git a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java index 90a5e094..95563480 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java @@ -6,6 +6,7 @@ import java.util.concurrent.TimeUnit; import org.joml.Quaterniond; import org.joml.Vector3d; import org.joml.Vector3f; +import org.joml.Vector3i; import electrosphere.audio.AudioUtils; import electrosphere.audio.VirtualAudioSource; @@ -15,7 +16,9 @@ import electrosphere.client.fluid.cells.FluidCellManager; import electrosphere.client.foliagemanager.ClientFoliageManager; import electrosphere.client.sim.ClientSimulation; import electrosphere.client.targeting.crosshair.Crosshair; +import electrosphere.client.terrain.cells.DrawCell; import electrosphere.client.terrain.cells.DrawCellManager; +import electrosphere.client.terrain.cells.DrawCell.DrawCellFace; import electrosphere.collision.CollisionEngine; import electrosphere.controls.ControlHandler; import electrosphere.engine.Globals; @@ -28,6 +31,7 @@ import electrosphere.entity.EntityUtils; import electrosphere.entity.btree.BehaviorTree; import electrosphere.entity.state.movement.ApplyRotationTree; import electrosphere.entity.types.camera.CameraEntityUtils; +import electrosphere.entity.types.terrain.TerrainChunk; import electrosphere.entity.types.tree.ProceduralTree; import electrosphere.logger.LoggerInterface; import electrosphere.menu.MenuGenerators; @@ -36,8 +40,10 @@ import electrosphere.menu.WindowUtils; import electrosphere.menu.mainmenu.MenuGeneratorsMultiplayer; import electrosphere.net.NetUtils; import electrosphere.net.client.ClientNetworking; +import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData; import electrosphere.renderer.ui.elements.Window; import electrosphere.server.datacell.EntityDataCellMapper; +import electrosphere.server.terrain.manager.ServerTerrainChunk; import electrosphere.util.MathUtils; public class ClientLoading { @@ -180,7 +186,43 @@ public class ClientLoading { } else { Globals.playerCamera = CameraEntityUtils.spawnPlayerEntityTrackingCameraFirstPersonEntity(new Vector3f(1,0,1), MathUtils.getOriginVectorf()); } - + + + /** + * Test LODed chunk + */ + float[][][] isoValues = new float[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE]; + int[][][] atlasValues = new int[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE]; + float[][] edgeIsoValues = new float[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE + ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE + ServerTerrainChunk.CHUNK_DIMENSION]; + int[][] edgeAtlasValues = new int[ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE + ServerTerrainChunk.CHUNK_DIMENSION][ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE + ServerTerrainChunk.CHUNK_DIMENSION]; + for(int x = 0; x < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE; x++){ + for(int y = 0; y < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE; y++){ + for(int z = 0; z < ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE; z++){ + if(x < 5 && y < 5){ + isoValues[x][y][z] = 1.0f; + } + atlasValues[x][y][z] = 1; + } + if(y < 10 && x < 10){ + edgeIsoValues[x][y] = 1.0f; + } + if(y == 3 && x == 3){ + edgeIsoValues[x][y] = -1.0f; + } + edgeAtlasValues[x][y] = 1; + } + } + TransvoxelChunkData chunkData = new TransvoxelChunkData( + isoValues, + atlasValues, + 1 + ); + chunkData.addZNegativeEdge( + edgeIsoValues, + edgeAtlasValues + ); + Entity transvoxelEntityTest = TerrainChunk.clientCreateTerrainChunkEntity(chunkData, 1, Globals.voxelTextureAtlas); + ClientEntityUtils.initiallyPositionEntity(transvoxelEntityTest, new Vector3d(3,3,3)); /* diff --git a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java index e139481c..36941edb 100644 --- a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java +++ b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java @@ -2,6 +2,7 @@ package electrosphere.entity.types.terrain; import org.joml.Vector3d; +import electrosphere.client.terrain.cells.DrawCell; import electrosphere.client.terrain.cells.VoxelTextureAtlas; import electrosphere.client.terrain.manager.ClientTerrainManager; import electrosphere.collision.PhysicsEntityUtils; @@ -11,6 +12,8 @@ import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.ServerEntityUtils; import electrosphere.renderer.meshgen.TerrainChunkModelGeneration; +import electrosphere.renderer.meshgen.TransvoxelModelGeneration; +import electrosphere.renderer.meshgen.TransvoxelModelGeneration.TransvoxelChunkData; import electrosphere.server.datacell.Realm; /** @@ -21,19 +24,23 @@ public class TerrainChunk { /** * Creates a client terrain chunk based on weights and values provided - * @param weights The terrain weights - * @param values The values (block types) + * @param chunkData the chunk data to generate with * @param levelOfDetail Increasing value that increments level of detail. 0 would be full resolution, 1 would be half resolution and so on. Only generates physics if levelOfDetail is 0 * @return The terrain chunk entity */ - public static Entity clientCreateTerrainChunkEntity(float[][][] weights, int[][][] values, int levelOfDetail, VoxelTextureAtlas atlas){ + public static Entity clientCreateTerrainChunkEntity(TransvoxelChunkData chunkData, int levelOfDetail, VoxelTextureAtlas atlas){ - TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values); + TerrainChunkData data; + if(levelOfDetail == DrawCell.FULL_DETAIL_LOD){ + data = TerrainChunkModelGeneration.generateTerrainChunkData(chunkData.terrainGrid, chunkData.textureGrid, levelOfDetail); + } else { + data = TransvoxelModelGeneration.generateTerrainChunkData(chunkData); + } String modelPath = ClientTerrainManager.queueTerrainGridGeneration(data, atlas); Entity rVal = EntityCreationUtils.createClientSpatialEntity(); EntityCreationUtils.makeEntityDrawablePreexistingModel(rVal, modelPath); - if(data.vertices.size() > 0 && levelOfDetail < 1){ + if(data.vertices.size() > 0 && levelOfDetail == DrawCell.FULL_DETAIL_LOD){ PhysicsEntityUtils.clientAttachTerrainChunkRigidBody(rVal, data); } @@ -53,7 +60,7 @@ public class TerrainChunk { */ public static Entity serverCreateTerrainChunkEntity(Realm realm, Vector3d position, float[][][] weights, int[][][] values){ - TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values); + TerrainChunkData data = TerrainChunkModelGeneration.generateTerrainChunkData(weights, values, DrawCell.FULL_DETAIL_LOD); Entity rVal = EntityCreationUtils.createServerEntity(realm, position); if(data.vertices.size() > 0){ 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 49e4cfb6..86795b3f 100644 --- a/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java @@ -176,6 +176,16 @@ SYNCHRONIZATION_MESSAGE, rVal = TerrainMessage.parsesendChunkDataMessage(byteBuffer); } break; + case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA: + if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){ + rVal = TerrainMessage.parseRequestReducedChunkDataMessage(byteBuffer); + } + break; + case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA: + if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){ + rVal = TerrainMessage.parseSendReducedChunkDataMessage(byteBuffer); + } + break; case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA: if(TerrainMessage.canParseMessage(byteBuffer,secondByte)){ rVal = TerrainMessage.parseRequestFluidDataMessage(byteBuffer); 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 bc47ab9c..adcf912c 100644 --- a/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java @@ -16,6 +16,8 @@ public class TerrainMessage extends NetworkMessage { SPAWNPOSITION, REQUESTCHUNKDATA, SENDCHUNKDATA, + REQUESTREDUCEDCHUNKDATA, + SENDREDUCEDCHUNKDATA, REQUESTFLUIDDATA, SENDFLUIDDATA, UPDATEFLUIDDATA, @@ -40,6 +42,7 @@ public class TerrainMessage extends NetworkMessage { double realLocationY; double realLocationZ; byte[] chunkData; + int chunkResolution; float terrainWeight; int terrainValue; @@ -196,6 +199,14 @@ public class TerrainMessage extends NetworkMessage { this.chunkData = chunkData; } + public int getchunkResolution() { + return chunkResolution; + } + + public void setchunkResolution(int chunkResolution) { + this.chunkResolution = chunkResolution; + } + public float getterrainWeight() { return terrainWeight; } @@ -262,6 +273,14 @@ public class TerrainMessage extends NetworkMessage { } case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDCHUNKDATA: return TerrainMessage.canParsesendChunkDataMessage(byteBuffer); + case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA: + if(byteBuffer.getRemaining() >= TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA_SIZE){ + return true; + } else { + return false; + } + case TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA: + return TerrainMessage.canParseSendReducedChunkDataMessage(byteBuffer); case TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA: if(byteBuffer.getRemaining() >= TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE){ return true; @@ -478,6 +497,79 @@ public class TerrainMessage extends NetworkMessage { return rVal; } + public static TerrainMessage parseRequestReducedChunkDataMessage(CircularByteBuffer byteBuffer){ + TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTREDUCEDCHUNKDATA); + stripPacketHeader(byteBuffer); + rVal.setworldX(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setworldY(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setworldZ(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setchunkResolution(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + return rVal; + } + + public static TerrainMessage constructRequestReducedChunkDataMessage(int worldX,int worldY,int worldZ,int chunkResolution){ + TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTREDUCEDCHUNKDATA); + rVal.setworldX(worldX); + rVal.setworldY(worldY); + rVal.setworldZ(worldZ); + rVal.setchunkResolution(chunkResolution); + rVal.serialize(); + return rVal; + } + + public static boolean canParseSendReducedChunkDataMessage(CircularByteBuffer byteBuffer){ + int currentStreamLength = byteBuffer.getRemaining(); + List temporaryByteQueue = new LinkedList(); + if(currentStreamLength < 6){ + return false; + } + if(currentStreamLength < 10){ + return false; + } + if(currentStreamLength < 14){ + return false; + } + if(currentStreamLength < 18){ + return false; + } + int chunkDataSize = 0; + if(currentStreamLength < 22){ + return false; + } else { + temporaryByteQueue.add(byteBuffer.peek(18 + 0)); + temporaryByteQueue.add(byteBuffer.peek(18 + 1)); + temporaryByteQueue.add(byteBuffer.peek(18 + 2)); + temporaryByteQueue.add(byteBuffer.peek(18 + 3)); + chunkDataSize = ByteStreamUtils.popIntFromByteQueue(temporaryByteQueue); + } + if(currentStreamLength < 22 + chunkDataSize){ + return false; + } + return true; + } + + public static TerrainMessage parseSendReducedChunkDataMessage(CircularByteBuffer byteBuffer){ + TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDCHUNKDATA); + stripPacketHeader(byteBuffer); + rVal.setworldX(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setworldY(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setworldZ(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setchunkResolution(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setchunkData(ByteStreamUtils.popByteArrayFromByteQueue(byteBuffer)); + return rVal; + } + + public static TerrainMessage constructSendReducedChunkDataMessage(int worldX,int worldY,int worldZ,int chunkResolution,byte[] chunkData){ + TerrainMessage rVal = new TerrainMessage(TerrainMessageType.SENDREDUCEDCHUNKDATA); + rVal.setworldX(worldX); + rVal.setworldY(worldY); + rVal.setworldZ(worldZ); + rVal.setchunkResolution(chunkResolution); + rVal.setchunkData(chunkData); + rVal.serialize(); + return rVal; + } + public static TerrainMessage parseRequestFluidDataMessage(CircularByteBuffer byteBuffer){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTFLUIDDATA); stripPacketHeader(byteBuffer); @@ -807,6 +899,59 @@ public class TerrainMessage extends NetworkMessage { rawBytes[18+i] = chunkData[i]; } break; + case REQUESTREDUCEDCHUNKDATA: + rawBytes = new byte[2+4+4+4+4]; + //message header + rawBytes[0] = TypeBytes.MESSAGE_TYPE_TERRAIN; + //entity messaage header + rawBytes[1] = TypeBytes.TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA; + intValues = ByteStreamUtils.serializeIntToBytes(worldX); + for(int i = 0; i < 4; i++){ + rawBytes[2+i] = intValues[i]; + } + intValues = ByteStreamUtils.serializeIntToBytes(worldY); + for(int i = 0; i < 4; i++){ + rawBytes[6+i] = intValues[i]; + } + intValues = ByteStreamUtils.serializeIntToBytes(worldZ); + for(int i = 0; i < 4; i++){ + rawBytes[10+i] = intValues[i]; + } + intValues = ByteStreamUtils.serializeIntToBytes(chunkResolution); + for(int i = 0; i < 4; i++){ + rawBytes[14+i] = intValues[i]; + } + break; + case SENDREDUCEDCHUNKDATA: + rawBytes = new byte[2+4+4+4+4+4+chunkData.length]; + //message header + rawBytes[0] = TypeBytes.MESSAGE_TYPE_TERRAIN; + //entity messaage header + rawBytes[1] = TypeBytes.TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA; + intValues = ByteStreamUtils.serializeIntToBytes(worldX); + for(int i = 0; i < 4; i++){ + rawBytes[2+i] = intValues[i]; + } + intValues = ByteStreamUtils.serializeIntToBytes(worldY); + for(int i = 0; i < 4; i++){ + rawBytes[6+i] = intValues[i]; + } + intValues = ByteStreamUtils.serializeIntToBytes(worldZ); + for(int i = 0; i < 4; i++){ + rawBytes[10+i] = intValues[i]; + } + intValues = ByteStreamUtils.serializeIntToBytes(chunkResolution); + for(int i = 0; i < 4; i++){ + rawBytes[14+i] = intValues[i]; + } + intValues = ByteStreamUtils.serializeIntToBytes(chunkData.length); + for(int i = 0; i < 4; i++){ + rawBytes[18+i] = intValues[i]; + } + for(int i = 0; i < chunkData.length; i++){ + rawBytes[22+i] = chunkData[i]; + } + break; case REQUESTFLUIDDATA: rawBytes = new byte[2+4+4+4]; //message header 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 c6be057e..5a808ed5 100644 --- a/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java +++ b/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java @@ -69,9 +69,11 @@ Message categories public static final byte TERRAIN_MESSAGE_TYPE_SPAWNPOSITION = 5; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTCHUNKDATA = 6; public static final byte TERRAIN_MESSAGE_TYPE_SENDCHUNKDATA = 7; - public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA = 8; - public static final byte TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA = 9; - public static final byte TERRAIN_MESSAGE_TYPE_UPDATEFLUIDDATA = 10; + public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA = 8; + public static final byte TERRAIN_MESSAGE_TYPE_SENDREDUCEDCHUNKDATA = 9; + public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA = 10; + public static final byte TERRAIN_MESSAGE_TYPE_SENDFLUIDDATA = 11; + public static final byte TERRAIN_MESSAGE_TYPE_UPDATEFLUIDDATA = 12; /* Terrain packet sizes */ @@ -82,6 +84,7 @@ Message categories public static final byte TERRAIN_MESSAGE_TYPE_REQUESTUSETERRAINPALETTE_SIZE = 38; public static final byte TERRAIN_MESSAGE_TYPE_SPAWNPOSITION_SIZE = 26; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTCHUNKDATA_SIZE = 14; + public static final byte TERRAIN_MESSAGE_TYPE_REQUESTREDUCEDCHUNKDATA_SIZE = 18; public static final byte TERRAIN_MESSAGE_TYPE_REQUESTFLUIDDATA_SIZE = 14; /* Server subcategories diff --git a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java index 0ae198a1..27eae355 100644 --- a/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java +++ b/src/main/java/electrosphere/renderer/meshgen/TerrainChunkModelGeneration.java @@ -1,23 +1,18 @@ package electrosphere.renderer.meshgen; import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import org.joml.Matrix4f; -import org.joml.Vector2f; import org.joml.Vector3f; import org.lwjgl.BufferUtils; import static org.lwjgl.opengl.GL30.glBindVertexArray; -import static org.lwjgl.opengl.GL30.glGenVertexArrays; import electrosphere.client.terrain.cells.VoxelTextureAtlas; import electrosphere.engine.Globals; @@ -25,14 +20,13 @@ import electrosphere.entity.types.terrain.TerrainChunkData; import electrosphere.renderer.model.Material; import electrosphere.renderer.model.Mesh; import electrosphere.renderer.model.Model; -import electrosphere.renderer.shader.ShaderProgram; import electrosphere.server.terrain.manager.ServerTerrainChunk; public class TerrainChunkModelGeneration { //http://paulbourke.net/geometry/polygonise/ - static int edgeTable[]={ + public static int edgeTable[]={ 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, @@ -68,7 +62,7 @@ public class TerrainChunkModelGeneration { }; //256 by 16 - static int triTable[][] = { + public static int triTable[][] = { {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, @@ -625,7 +619,7 @@ public class TerrainChunkModelGeneration { return new Vector3f(x,y,z); } - public static TerrainChunkData generateTerrainChunkData(float[][][] terrainGrid, int[][][] textureGrid){ + public static TerrainChunkData generateTerrainChunkData(float[][][] terrainGrid, int[][][] textureGrid, int lod){ // 5 6 // +-------------+ +-----5-------+ ^ Y diff --git a/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java b/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java index 1450278b..606b8d46 100644 --- a/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java +++ b/src/main/java/electrosphere/renderer/meshgen/TransvoxelModelGeneration.java @@ -1,190 +1,29 @@ 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.terrain.TerrainChunkData; -import electrosphere.renderer.meshgen.TerrainChunkModelGeneration.GridCell; -import electrosphere.renderer.meshgen.TerrainChunkModelGeneration.Triangle; -import electrosphere.renderer.model.Material; -import electrosphere.renderer.model.Mesh; -import electrosphere.renderer.model.Model; +import electrosphere.logger.LoggerInterface; +import electrosphere.server.terrain.manager.ServerTerrainChunk; /** * Utility functions for generating transvoxel based meshes */ public class TransvoxelModelGeneration { - // 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; - // } - // } - - // protected static int polygonize( - // GridCell grid, - // double isolevel, - // List triangles, - // Map vertMap, - // List verts, - // List normals, - // List 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); + //this is the width of the transition cell + //it should be between 0 and 1, exclusive + //the higher the value, the more of the high resolution chunk we will see + //the lower the value, the more of the low resolution chunk we will see + static final float TRANSITION_CELL_WIDTH = 0.5f; @@ -193,179 +32,2457 @@ public class TransvoxelModelGeneration { - // 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); + /** + * A single generated triangle + */ + static class Triangle { + //the indices + int[] indices = new int[3]; //array of size 3 - // currentNormal = normals.get(index2); - // currentNormal = averageNormals(currentNormal,oldProportion,n,newProportion); - // normals.get(index2).set(currentNormal); + /** + * Creates a triangle object + * @param index0 + * @param index1 + * @param index2 + */ + public Triangle(int index0, int index1, int index2){ + indices[0] = index0; + indices[1] = index1; + indices[2] = index2; + } + } - // } + /** + * The grid cell currently being looked at + */ + static class GridCell { + Vector3f[] points = new Vector3f[8]; //array of size 8 + double[] val = new double[8]; //array of size 8 + int[] atlasValues = new int[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, + int atl1, int atl2, int atl3, int atl4, + int atl5, int atl6, int atl7, int atl8 + ){ + //triangle points + points[0] = p1; points[1] = p2; points[2] = p3; points[3] = p4; + points[4] = p5; points[5] = p6; points[6] = p7; points[7] = p8; + //iso values + val[0] = val1; val[1] = val2; val[2] = val3; val[3] = val4; + val[4] = val5; val[5] = val6; val[6] = val7; val[7] = val8; + //atlas values + atlasValues[0] = atl1; atlasValues[1] = atl2; atlasValues[2] = atl3; atlasValues[3] = atl4; + atlasValues[4] = atl5; atlasValues[5] = atl6; atlasValues[6] = atl7; atlasValues[7] = atl8; + } + } - // return(ntriang); - // } + /** + * The transition grid cell currently being looked at + */ + static class TransitionGridCell { + Vector3f[][] complexFacePoints = new Vector3f[3][3]; + Vector3f[][] simpleFacePoints = new Vector3f[2][2]; + float[][] complexFaceValues = new float[3][3]; + float[][] simpleFaceValues = new float[2][2]; + int[][] complexFaceAtlasValues = new int[3][3]; + int[][] simpleFaceAtlasValues = new int[2][2]; + public void setValues( + Vector3f c1, Vector3f c2, Vector3f c3, + Vector3f c4, Vector3f c5, Vector3f c6, + Vector3f c7, Vector3f c8, Vector3f c9, - // //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; + Vector3f s1, Vector3f s2, + Vector3f s3, Vector3f s4, - // 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)); + float cVal1, float cVal2, float cVal3, + float cVal4, float cVal5, float cVal6, + float cVal7, float cVal8, float cVal9, - // return new Vector3f(x,y,z); - // } + float sVal1, float sVal2, + float sVal3, float sVal4, - // /** - // * Creates terrain chunk data based on data provided - // * @param terrainGrid the grid of weights to generate off of - // * @param textureGrid the grid of texture indices to generate off of - // * @param neighborLODLevels The lod levels of all neighbors and this cell. Should be 3x3x3 array of lod level values. - // */ - // public static TerrainChunkData generateTerrainChunkData(float[][][] terrainGrid, int[][][] textureGrid, int[][][] neighborLODLevels){ + int cA1, int cA2, int cA3, int cA4, + int cA5, int cA6, int cA7, int cA8, + int cA9, - // // 5 6 - // // +-------------+ +-----5-------+ ^ Y - // // / | / | / | /| | _ - // // / | / | 4 9 6 10 | /\ Z - // // 4 +-----+-------+ 7 | +-----+7------+ | | / - // // | 1 +-------+-----+ 2 | +-----1-+-----+ | / - // // | / | / 8 0 11 2 | / - // // | / | / | / | / |/ - // // 0 +-------------+ 3 +------3------+ +---------------> X + int sA1, int sA2, int sA3, int sA4 + ){ + //triangle points + complexFacePoints[0][0] = c1; complexFacePoints[0][1] = c2; complexFacePoints[0][2] = c3; + complexFacePoints[1][0] = c4; complexFacePoints[1][1] = c5; complexFacePoints[1][2] = c6; + complexFacePoints[2][0] = c7; complexFacePoints[2][1] = c8; complexFacePoints[2][2] = c9; + simpleFacePoints[0][0] = s1; simpleFacePoints[0][1] = s2; + simpleFacePoints[1][0] = s3; simpleFacePoints[1][1] = s4; + //iso values + complexFaceValues[0][0] = cVal1; complexFaceValues[0][1] = cVal2; complexFaceValues[0][2] = cVal3; + complexFaceValues[1][0] = cVal4; complexFaceValues[1][1] = cVal5; complexFaceValues[1][2] = cVal6; + complexFaceValues[2][0] = cVal7; complexFaceValues[2][1] = cVal8; complexFaceValues[2][2] = cVal9; + simpleFaceValues[0][0] = sVal1; simpleFaceValues[0][1] = sVal2; + simpleFaceValues[1][0] = sVal3; simpleFaceValues[1][1] = sVal4; + //atlas values + complexFaceAtlasValues[0][0] = cA1; complexFaceAtlasValues[0][1] = cA2; complexFaceAtlasValues[0][2] = cA3; + complexFaceAtlasValues[1][0] = cA4; complexFaceAtlasValues[1][1] = cA5; complexFaceAtlasValues[1][2] = cA6; + complexFaceAtlasValues[2][0] = cA7; complexFaceAtlasValues[2][1] = cA8; complexFaceAtlasValues[2][2] = cA9; + simpleFaceAtlasValues[0][0] = sA1; simpleFaceAtlasValues[0][1] = sA2; + simpleFaceAtlasValues[1][0] = sA3; simpleFaceAtlasValues[1][1] = sA4; + } + } - // //the current grid cell - // GridCell currentCell = new GridCell(); - // //the list of all triangles - // List triangles = new LinkedList(); - // //the map of vertex to index - // Map vertMap = new HashMap(); - // //the list of all verts - // List verts = new LinkedList(); - // //the list of all normals - // List normals = new LinkedList(); - // //the list of number of triangles that share a vert - // List trianglesSharingVert = new LinkedList(); - // //List of elements in order - // List faceElements = new LinkedList(); - // //List of UVs - // List UVs = new LinkedList(); + + + + + + + + + //location of the sampler index data in the shader + public static final int SAMPLER_INDEX_ATTRIB_LOC = 5; + //the ratio vectors of how much to pull from each texture + public static final int SAMPLER_RATIO_ATTRIB_LOC = 6; + + + + + + + + + + + /** + * Creates polygons for a voxel that is in the interior of this mesh + * @param grid + * @param isolevel + * @param triangles + * @param samplerIndices + * @param vertMap + * @param verts + * @param normals + * @param trianglesSharingVert + * @return + */ + protected static int polygonize( + GridCell grid, + double isolevel, + List triangles, + List samplerIndices, + Map vertMap, + List verts, + List normals, + List trianglesSharingVert + ){ + int i; + int ntriang; + int cubeIndex = 0; + Vector3f[] vertList = new Vector3f[12]; + int[] samplerIndex = new int[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 (TerrainChunkModelGeneration.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((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 1) > 0){ + vertList[0] = VertexInterp(isolevel,grid.points[0],grid.points[1],grid.val[0],grid.val[1]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 2) > 0){ + vertList[1] = VertexInterp(isolevel,grid.points[1],grid.points[2],grid.val[1],grid.val[2]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 4) > 0){ + vertList[2] = VertexInterp(isolevel,grid.points[2],grid.points[3],grid.val[2],grid.val[3]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 8) > 0){ + vertList[3] = VertexInterp(isolevel,grid.points[3],grid.points[0],grid.val[3],grid.val[0]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 16) > 0){ + vertList[4] = VertexInterp(isolevel,grid.points[4],grid.points[5],grid.val[4],grid.val[5]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 32) > 0){ + vertList[5] = VertexInterp(isolevel,grid.points[5],grid.points[6],grid.val[5],grid.val[6]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 64) > 0){ + vertList[6] = VertexInterp(isolevel,grid.points[6],grid.points[7],grid.val[6],grid.val[7]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 128) > 0){ + vertList[7] = VertexInterp(isolevel,grid.points[7],grid.points[4],grid.val[7],grid.val[4]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 256) > 0){ + vertList[8] = VertexInterp(isolevel,grid.points[0],grid.points[4],grid.val[0],grid.val[4]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 512) > 0){ + vertList[9] = VertexInterp(isolevel,grid.points[1],grid.points[5],grid.val[1],grid.val[5]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 1024) > 0){ + vertList[10] = VertexInterp(isolevel,grid.points[2],grid.points[6],grid.val[2],grid.val[6]); + } + if((TerrainChunkModelGeneration.edgeTable[cubeIndex] & 2048) > 0){ + vertList[11] = VertexInterp(isolevel,grid.points[3],grid.points[7],grid.val[3],grid.val[7]); + } + + if(grid.val[0] > isolevel){ samplerIndex[0] = 0; } else { samplerIndex[0] = 1; } + if(grid.val[1] > isolevel){ samplerIndex[1] = 1; } else { samplerIndex[1] = 2; } + if(grid.val[2] > isolevel){ samplerIndex[2] = 2; } else { samplerIndex[2] = 3; } + if(grid.val[3] > isolevel){ samplerIndex[3] = 3; } else { samplerIndex[3] = 0; } + if(grid.val[4] > isolevel){ samplerIndex[4] = 4; } else { samplerIndex[4] = 5; } + if(grid.val[5] > isolevel){ samplerIndex[5] = 5; } else { samplerIndex[5] = 6; } + if(grid.val[6] > isolevel){ samplerIndex[6] = 6; } else { samplerIndex[6] = 7; } + if(grid.val[7] > isolevel){ samplerIndex[7] = 7; } else { samplerIndex[7] = 4; } + if(grid.val[0] > isolevel){ samplerIndex[8] = 0; } else { samplerIndex[8] = 4; } + if(grid.val[1] > isolevel){ samplerIndex[9] = 1; } else { samplerIndex[9] = 5; } + if(grid.val[2] > isolevel){ samplerIndex[10] = 2; } else { samplerIndex[10] = 6; } + if(grid.val[3] > isolevel){ samplerIndex[11] = 3; } else { samplerIndex[11] = 7; } + + //Create the triangle + ntriang = 0; + for (i=0; TerrainChunkModelGeneration.triTable[cubeIndex][i]!=-1; i+=3) { + // + // Triangles calculation + // + //get indices + Vector3f vert0 = vertList[TerrainChunkModelGeneration.triTable[cubeIndex][i+0]]; + Vector3f vert1 = vertList[TerrainChunkModelGeneration.triTable[cubeIndex][i+1]]; + Vector3f vert2 = vertList[TerrainChunkModelGeneration.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++; + + + + + // + //Sampler triangles + // + for(int j = 0; j < 3; j++){ + //we add the triangle three times so all three vertices have the same values + //that way they don't interpolate when you're in a middle point of the fragment + //this could eventually potentially be optimized to send 1/3rd the data, but + //the current approach is easier to reason about + Vector3f samplerTriangle = new Vector3f( + grid.atlasValues[samplerIndex[TerrainChunkModelGeneration.triTable[cubeIndex][i+0]]], + grid.atlasValues[samplerIndex[TerrainChunkModelGeneration.triTable[cubeIndex][i+1]]], + grid.atlasValues[samplerIndex[TerrainChunkModelGeneration.triTable[cubeIndex][i+2]]] + ); + samplerIndices.add(samplerTriangle); + } + + + + + // + // 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); + } + + + /** + * Generates a transition cell + * @param grid The main grid cell + * @param highresFaceValues The values along the face that are double-resolution + * @param isolevel The iso level + * @param triangles The triangles buffer + * @param samplerIndices The Sampler indices buffer + * @param vertMap The vert map + * @param verts The list of verts + * @param normals The normal map + * @param trianglesSharingVert The indices of triangles sharing verts + * @return The number of triangles created + */ + protected static int polygonizeTransition( + TransitionGridCell transitionCell, + double isolevel, + List triangles, + List samplerIndices, + Map vertMap, + List verts, + List normals, + List trianglesSharingVert + ){ + + + /** + + + For a transition face, the face vertices are numbered differently + + complex face numbering: + + 6 7 8 + +-------+-------+ + | | | + | | | + | |4 | + 3+-------+-------+ 5 + | | | + | | | + | | | + +-------+-------+ + 0 1 2 + + + + + non complex face numbering: + + B C + +---------------+ + | | + | | + | | + | | + | | + | | + | | + +---------------+ + 9 A + + + + Coordinage system: + + Y + ^ + | + | + | + +---------> X + + + + + */ + + + + + + int i; + int ntriang; + int caseIndex = 0; + Vector3f[] vertList = new Vector3f[12]; + int[] samplerIndex = new int[12]; + + + + /** + The case index is constructed by using the values of the high-resolution face. + We assign each of the vertices the following values: + + 40 20 10 + +-------+-------+ + | | | + | | | + | |100 | + 80+-------+-------+ 08 + | | | + | | | + | | | + +-------+-------+ + 01 02 04 + + */ + + //get lookup key (index) for edge table + //edge table tells us which vertices are inside of the surface + if (transitionCell.complexFaceValues[0][0] < isolevel) caseIndex |= 1; + if (transitionCell.complexFaceValues[1][0] < isolevel) caseIndex |= 2; + if (transitionCell.complexFaceValues[2][0] < isolevel) caseIndex |= 4; + if (transitionCell.complexFaceValues[2][1] < isolevel) caseIndex |= 8; + if (transitionCell.complexFaceValues[2][2] < isolevel) caseIndex |= 16; + if (transitionCell.complexFaceValues[1][2] < isolevel) caseIndex |= 32; + if (transitionCell.complexFaceValues[0][2] < isolevel) caseIndex |= 64; + if (transitionCell.complexFaceValues[0][1] < isolevel) caseIndex |= 128; + if (transitionCell.complexFaceValues[1][1] < isolevel) caseIndex |= 256; + + //Cube is entirely in/out of the surface + if(caseIndex == 0 || caseIndex == 511){ + return(0); + } + + + //the class of transition cell + short cellClass = (short)(transitionCellClass[caseIndex]); + + LoggerInterface.loggerRenderer.DEBUG("Cell class: " + cellClass + " " + String.format("0x%02X", cellClass)); + + + /** + + The complex edges that a vertex can lie on are numbered as follows: + + 83 84 + +-------+-------+ + | | | + 16| |46 |86 + | 43 | 44 | + +-------+-------+ + | | | + 15| |45 |85 + | | | + +-------+-------+ + 23 24 + + + + The simple edges that a vertex can lie on are numbered as follows: + + 88 + +---------------+ + | | + | | + | | + 19| |89 + | | + | | + | | + +---------------+ + 28 + + + + */ + + + /* + + + The sample locations are assigned an 8 bit code + + + Complex face: + + + 10 81 80 + +-------+-------+ + | | | + | | | + | |40 | + 12+-------+-------+ 82 + | | | + | | | + | | | + +-------+-------+ + 30 21 20 + + + Simple face: + + 17 87 + +---------------+ + | | + | | + | | + | | + | | + | | + | | + +---------------+ + 37 27 + + + + + + + */ + + + // + // + // Calculate the locations of all vertices for this cube + // + // + + int[] vertexData = transitionVertexData[caseIndex]; + + //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(vertexData.length > 0){ + for(i = 0; i < vertexData.length; i++){ + //get into from transvoxel tables + + //contains the corner indexes of the edge's endpoints in one nibble each + short lowByte = (short)(vertexData[i] & 0xFF); + + LoggerInterface.loggerRenderer.DEBUG("Low byte: "); + LoggerInterface.loggerRenderer.DEBUG(String.format("0x%02X", lowByte)); + + //contains the vertex reuse data + //the bit values 1 and 2 indicate we should subtract one from the x or y coordinate respectively + //they're never simultaneously set + //bit 4 indicates a new vertex is to be created on an interior edge were we cannot reuse + //bit 8 indicates that a new vertex is created on the maximal edge where it can be reused + short highByte = (short)(vertexData[i] >> 8 & 0xFF); + // int subX = highByte & 0x01; + // int subY = highByte & 0x02; + int newInteriorVertex = highByte & 0x04; + // int vertexCanBeReused = highByte & 0x08; + + LoggerInterface.loggerRenderer.DEBUG("High byte: "); + LoggerInterface.loggerRenderer.DEBUG(String.format("0x%02X", highByte)); + + if(newInteriorVertex > 0){ + LoggerInterface.loggerRenderer.DEBUG("New interior Vertex"); + } + + //the corner indices to sample + int firstCornerSampleIndex = (int)(lowByte >> 4 & 0xF); + int secondCornerSampleIndex = (int)(lowByte & 0xF); + + LoggerInterface.loggerRenderer.DEBUG("Corner indices: " + firstCornerSampleIndex + " " + secondCornerSampleIndex); + + //get the iso sample values + float firstSample = getTransvoxelSampleValue(transitionCell.simpleFaceValues,transitionCell.complexFaceValues,firstCornerSampleIndex); + float secondSample = getTransvoxelSampleValue(transitionCell.simpleFaceValues,transitionCell.complexFaceValues,secondCornerSampleIndex); + + // + //Sample check -- we should never be interpolating between two samples of 0 value + if(firstSample <= 0 && secondSample <= 0){ + String message = "" + + transitionCell.complexFaceValues[0][2] + " " + transitionCell.complexFaceValues[1][2] + " " + transitionCell.complexFaceValues[2][2] + "\n" + + transitionCell.complexFaceValues[0][1] + " " + transitionCell.complexFaceValues[1][1] + " " + transitionCell.complexFaceValues[2][1] + "\n" + + transitionCell.complexFaceValues[0][0] + " " + transitionCell.complexFaceValues[1][0] + " " + transitionCell.complexFaceValues[2][0] + "\n" + + "\n" + + transitionCell.simpleFaceValues[0][1] + " " + transitionCell.simpleFaceValues[1][1] + "\n" + + transitionCell.simpleFaceValues[0][0] + " " + transitionCell.simpleFaceValues[1][0] + ; + LoggerInterface.loggerRenderer.ERROR(message, new IllegalStateException("Two samples in transvoxel algorithm are both zero -- can't interpolate between them!")); + } + + //get the vertices we're interpolating + //we need to map 0x0 through 0xC to the coordinates we're actually modifying + Vector3f firstVertex = getTransvoxelVectorByIndex(transitionCell,firstCornerSampleIndex); + Vector3f secondVertex = getTransvoxelVectorByIndex(transitionCell,secondCornerSampleIndex); + + //calculate interpolated vertex between the two samples such that it lies on the edge of the isosurface + vertList[i] = VertexInterp(isolevel,firstVertex,secondVertex,firstSample,secondSample); + + //figure out what sample we're pulling texture from + if(firstSample > isolevel){ + samplerIndex[i] = getTransvoxelTextureValue(transitionCell.simpleFaceAtlasValues,transitionCell.complexFaceAtlasValues,firstCornerSampleIndex); + } else { + samplerIndex[i] = sampleIndexTable[1][i]; + } + } + } + + + + + // + // + // Create the triangles using the vertices we just created + // + // + + //the triangle data + //basically, a list of indices into the vertex array where every three entries + //in triangleData correspondes to three vertices which we want to turn into a triangle + int lookupValue = cellClass & 0x7F; //per instruction, must be ANDed before lookup + LoggerInterface.loggerRenderer.DEBUG("Triangulation lookup value: " + lookupValue); + TransitionCellData triangleData = transitionCellData[lookupValue]; + + short vertexCount = (short)(triangleData.geometryCounts >> 4 & 0xF); + short triangleCount = (short)(triangleData.geometryCounts & 0xF); + LoggerInterface.loggerRenderer.DEBUG("Vertex Count: " + vertexCount + " Triangle Count: " + triangleCount); + + //Create the triangle + ntriang = 0; + for (i=0; i < triangleData.vertexIndex.length; i+=3) { + // + // Triangles calculation + // + //get indices -- these values are the same as the indices we sample from (refer to figure 4.19 for the listing) + char cornerReuseData0 = transitionCornerData[triangleData.vertexIndex[i+0]]; + char cornerReuseData1 = transitionCornerData[triangleData.vertexIndex[i+1]]; + char cornerReuseData2 = transitionCornerData[triangleData.vertexIndex[i+2]]; + LoggerInterface.loggerRenderer.DEBUG((int)triangleData.vertexIndex[i+0] + " " + (int)triangleData.vertexIndex[i+1] + " " + (int)triangleData.vertexIndex[i+2]); + LoggerInterface.loggerRenderer.DEBUG((int)cornerReuseData0 + " " + (int)cornerReuseData1 + " " + (int)cornerReuseData2); + Vector3f vert0 = vertList[triangleData.vertexIndex[i+0]]; + Vector3f vert1 = vertList[triangleData.vertexIndex[i+1]]; + Vector3f vert2 = vertList[triangleData.vertexIndex[i+2]]; + + LoggerInterface.loggerRenderer.DEBUG(vert0 + ""); + LoggerInterface.loggerRenderer.DEBUG(vert1 + ""); + LoggerInterface.loggerRenderer.DEBUG(vert2 + ""); + 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++; + + + + + // + //Sampler triangles + // + for(int j = 0; j < 3; j++){ + //we add the triangle three times so all three vertices have the same values + //that way they don't interpolate when you're in a middle point of the fragment + //this could eventually potentially be optimized to send 1/3rd the data, but + //the current approach is easier to reason about + Vector3f samplerTriangle = new Vector3f( + samplerIndex[triangleData.vertexIndex[i+0]], + samplerIndex[triangleData.vertexIndex[i+1]], + samplerIndex[triangleData.vertexIndex[i+2]] + ); + samplerIndices.add(samplerTriangle); + } + + + + + // + // 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); + } + + /** + * Sample grid values based on the index + * @param simpleEdge simple edge values + * @param complexEdgeValues complex edge values + * @param index index to sample at + * @return the iso value + */ + static float getTransvoxelSampleValue(float[][] simpleEdge, float[][] complexEdgeValues, int index){ + if(index < 9){ + //sample from complex edge + switch(index){ + case 0: + return complexEdgeValues[0][0]; + case 1: + return complexEdgeValues[1][0]; + case 2: + return complexEdgeValues[2][0]; + case 3: + return complexEdgeValues[0][1]; + case 4: + return complexEdgeValues[1][1]; + case 5: + return complexEdgeValues[2][1]; + case 6: + return complexEdgeValues[0][2]; + case 7: + return complexEdgeValues[1][2]; + case 8: + return complexEdgeValues[2][2]; + } + } else { + //sample from non-complex edge + switch(index){ + case 9: + return simpleEdge[0][0]; + case 10: + return simpleEdge[1][0]; + case 11: + return simpleEdge[0][1]; + case 12: + return simpleEdge[1][1]; + } + } + return 0.0f; + } + + /** + * Sample grid values based on the index + * @param simpleEdge simple edge values + * @param complexEdgeValues complex edge values + * @param index index to sample at + * @return the iso value + */ + static int getTransvoxelTextureValue(int[][] simpleEdge, int[][] complexEdgeValues, int index){ + if(index < 9){ + //sample from complex edge + switch(index){ + case 0: + return complexEdgeValues[0][0]; + case 1: + return complexEdgeValues[1][0]; + case 2: + return complexEdgeValues[2][0]; + case 3: + return complexEdgeValues[0][1]; + case 4: + return complexEdgeValues[1][1]; + case 5: + return complexEdgeValues[2][1]; + case 6: + return complexEdgeValues[0][2]; + case 7: + return complexEdgeValues[1][2]; + case 8: + return complexEdgeValues[2][2]; + } + } else { + //sample from non-complex edge + switch(index){ + case 9: + return simpleEdge[0][0]; + case 10: + return simpleEdge[1][0]; + case 11: + return simpleEdge[0][1]; + case 12: + return simpleEdge[1][1]; + } + } + return 0; + } + + /** + * Gets the vector from the transition grid cell based on its index + * @param transitionCell The transition cell + * @param index The index + * @return The vector + */ + static Vector3f getTransvoxelVectorByIndex(TransitionGridCell transitionCell, int index){ + if(index < 9){ + //sample from complex edge + switch(index){ + case 0: + return transitionCell.complexFacePoints[0][0]; + case 1: + return transitionCell.complexFacePoints[1][0]; + case 2: + return transitionCell.complexFacePoints[2][0]; + case 3: + return transitionCell.complexFacePoints[0][1]; + case 4: + return transitionCell.complexFacePoints[1][1]; + case 5: + return transitionCell.complexFacePoints[2][1]; + case 6: + return transitionCell.complexFacePoints[0][2]; + case 7: + return transitionCell.complexFacePoints[1][2]; + case 8: + return transitionCell.complexFacePoints[2][2]; + } + } else { + //sample from non-complex edge + switch(index){ + case 9: + return transitionCell.simpleFacePoints[0][0]; + case 10: + return transitionCell.simpleFacePoints[1][0]; + case 11: + return transitionCell.simpleFacePoints[0][1]; + case 12: + return transitionCell.simpleFacePoints[1][1]; + } + } + //index should never be greater than 12 as there are only 13 points we're concerned with per transition voxel + throw new UnknownError("Managed to index outside of expected range"); + } + + //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); + } + + //TODO: more optimal key creation + private static String getVertKeyFromPoints(float x, float y, float z){ + return x + "_" + y + "_" + z; + } + + /** + * Gets the already existing index of this point + * @param vert the vertex's raw position + * @param vertMap the map of key ->Vert index + * @param verts + * @return + */ + private static int getVertIndex(Vector3f vert, Map vertMap, List 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; + } + + + /** + * Generates mesh data given chunk data + * @param terrainGrid The chunk data + * @param textureGrid The chunk texture data + * @return The mesh data + */ + public static TerrainChunkData generateTerrainChunkData(TransvoxelChunkData chunkData){ + + // 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(); + //Transition grid cell + TransitionGridCell currentTransitionCell = new TransitionGridCell(); + //the list of all triangles + List triangles = new LinkedList(); + //the map of vertex to index + Map vertMap = new HashMap(); + //the list of all verts + List verts = new LinkedList(); + //the list of all normals + List normals = new LinkedList(); + //the list of number of triangles that share a vert + List trianglesSharingVert = new LinkedList(); + //List of texture sampler values + List samplerTriangles = new LinkedList(); + //List of UVs + List UVs = new LinkedList(); - // 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++){ - // //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), - // terrainGrid[x+0][y+0][z+0], terrainGrid[x+0][y+0][z+1], terrainGrid[x+1][y+0][z+1], terrainGrid[x+1][y+0][z+0], - // terrainGrid[x+0][y+1][z+0], terrainGrid[x+0][y+1][z+1], terrainGrid[x+1][y+1][z+1], terrainGrid[x+1][y+1][z+0] - // ); - // //polygonize the current gridcell - // polygonize(currentCell, 0, triangles, vertMap, verts, normals, trianglesSharingVert); - // } - // } - // } + // + //Generate the interior of the mesh + for(int x = 1; x < chunkData.terrainGrid.length - 2; x++){ + for(int y = 1; y < chunkData.terrainGrid[0].length - 2; y++){ + for(int z = 1; z < chunkData.terrainGrid[0][0].length - 2; 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), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.terrainGrid[x+0][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } - // //all verts in order, flattened as an array of floats instead of vecs - // List vertsFlat = new LinkedList(); - // //all normals in order, flattened as an array of floats instead of vecs - // List normalsFlat = new LinkedList(); - // //all elements of faces in order - // List elementsFlat = new LinkedList(); - - // //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); - // } + int chunkWidth = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE; - // for(Triangle triangle : triangles){ - // elementsFlat.add(triangle.indices[0]); - // elementsFlat.add(triangle.indices[1]); - // elementsFlat.add(triangle.indices[2]); - // } + LoggerInterface.loggerRenderer.DEBUG("Triangles prior to transition cells: " + triangles.size()); - // 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); + int xStartIndex = chunkData.xNegativeEdgeIso != null ? 1 : 0; + int xEndIndex = chunkData.xPositiveEdgeIso != null ? chunkWidth - 2 : chunkWidth - 1; + int yStartIndex = chunkData.yNegativeEdgeIso != null ? 1 : 0; + int yEndIndex = chunkData.yPositiveEdgeIso != null ? chunkWidth - 2 : chunkWidth - 1; + int zStartIndex = chunkData.zNegativeEdgeIso != null ? 1 : 0; + int zEndIndex = chunkData.zPositiveEdgeIso != null ? chunkWidth - 2 : chunkWidth - 1; - // 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; + // + //generate the x-positive face + if(chunkData.xPositiveEdgeIso != null){ + int x = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2; + for(int y = yStartIndex; y < yEndIndex - 1; y++){ + for(int z = zStartIndex; z < zEndIndex - 1; z++){ + // + //Generate the transition cell + // + currentTransitionCell.setValues( + //complex face vertex coordinates + new Vector3f(x+1,y,z), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+1,y+1,z), + new Vector3f(x+1,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), + new Vector3f(x+1,y,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+1,z+1), + //simple face vertex coordinates + new Vector3f(x+TRANSITION_CELL_WIDTH,y,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z), + new Vector3f(x+TRANSITION_CELL_WIDTH,y,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+1), + //complex face iso values + chunkData.xPositiveEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xPositiveEdgeIso[(y+0)*2+1][(z+0)*2+0], chunkData.xPositiveEdgeIso[(y+1)*2+0][(z+0)*2+0], + chunkData.xPositiveEdgeIso[(y+0)*2+0][(z+0)*2+1], chunkData.xPositiveEdgeIso[(y+0)*2+1][(z+0)*2+1], chunkData.xPositiveEdgeIso[(y+1)*2+0][(z+0)*2+1], + chunkData.xPositiveEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeIso[(y+0)*2+1][(z+1)*2+0], chunkData.xPositiveEdgeIso[(y+1)*2+0][(z+1)*2+0], + //simple face iso values + chunkData.xPositiveEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xPositiveEdgeIso[(y+1)*2+0][(z+0)*2+0], + chunkData.xPositiveEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeIso[(y+1)*2+0][(z+1)*2+0], + //complex face texture atlas values + chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xPositiveEdgeAtlas[(y+0)*2+1][(z+0)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+0)*2+0], + chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+0)*2+1], chunkData.xPositiveEdgeAtlas[(y+0)*2+1][(z+0)*2+1], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+0)*2+1], + chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeAtlas[(y+0)*2+1][(z+1)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+1)*2+0], + //simple face texture atlas values + chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+0)*2+0], + chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+1)*2+0] + ); + polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); - // // 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]); - // } + // + //Generate the normal cell with half width + // + currentCell.setValues( + new Vector3f(x+0,y+0,z+0), new Vector3f(x+0,y+0,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z+0), + new Vector3f(x+0,y+1,z+0), new Vector3f(x+0,y+1,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+0), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.xPositiveEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeIso[(y+0)*2+0][(z+0)*2+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.terrainGrid[x+0][y+1][z+1], chunkData.xPositiveEdgeIso[(y+1)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeIso[(y+1)*2+0][(z+0)*2+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeAtlas[(y+0)*2+0][(z+0)*2+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+1)*2+0], chunkData.xPositiveEdgeAtlas[(y+1)*2+0][(z+0)*2+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } else { + int x = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2; + for(int y = yStartIndex; y < yEndIndex; y++){ + for(int z = zStartIndex; z < zEndIndex; 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), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.terrainGrid[x+0][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } - // //List vertices, List normals, List faceElements, List uvs - // TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs); - // return rVal; - // } + LoggerInterface.loggerRenderer.DEBUG("Triangles after transition cells: " + triangles.size()); - // private static String getVertKeyFromPoints(float x, float y, float z){ - // return x + "_" + y + "_" + z; - // } - // private static int getVertIndex(Vector3f vert, Map vertMap, List 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; - // } + + + // + //generate the x-negative face + if(chunkData.xNegativeEdgeIso != null){ + int x = 0; + for(int y = yStartIndex; y < yEndIndex - 1; y++){ + for(int z = zStartIndex; z < zEndIndex - 1; z++){ + // + //Generate the transition cell + // + currentTransitionCell.setValues( + //complex face vertex coordinates + new Vector3f(x,y,z), new Vector3f(x,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x,y+1,z), + new Vector3f(x,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x,y+TRANSITION_CELL_WIDTH,z+TRANSITION_CELL_WIDTH), new Vector3f(x,y+1,z+TRANSITION_CELL_WIDTH), + new Vector3f(x,y,z+1), new Vector3f(x,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x,y+1,z+1), + //simple face vertex coordinates + new Vector3f(x+TRANSITION_CELL_WIDTH,y,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z), + new Vector3f(x+TRANSITION_CELL_WIDTH,y,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+1), + //complex face iso values + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+0)*2+1][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+0], + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+1], chunkData.xNegativeEdgeIso[(y+0)*2+1][(z+0)*2+1], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+1], + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeIso[(y+0)*2+1][(z+1)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+1)*2+0], + //simple face iso values + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+0], + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+1)*2+0], + //complex face texture atlas values + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+0)*2+1][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0], + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+1], chunkData.xNegativeEdgeAtlas[(y+0)*2+1][(z+0)*2+1], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+1], + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+0)*2+1][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0], + //simple face texture atlas values + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0], + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0] + ); + polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + + // + //Generate the normal cell with half width + // + currentCell.setValues( + new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z+0), new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z+1), new Vector3f(x+1,y+0,z+1), new Vector3f(x+1,y+0,z+0), + new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+0), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+1), new Vector3f(x+1,y+1,z+1), new Vector3f(x+1,y+1,z+0), + chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+0)*2+0][(z+1)*2+0], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeIso[(y+1)*2+0][(z+1)*2+0], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+0)*2+0][(z+1)*2+0], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+0)*2+0], chunkData.xNegativeEdgeAtlas[(y+1)*2+0][(z+1)*2+0], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } else { + int x = 0; + for(int y = yStartIndex; y < yEndIndex; y++){ + for(int z = zStartIndex; z < zEndIndex; 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), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.terrainGrid[x+0][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } + + + + + + + // + //generate the y-positive face + if(chunkData.yPositiveEdgeIso != null){ + int y = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2; + for(int x = xStartIndex; x < xEndIndex - 1; x++){ + for(int z = zStartIndex; z < zEndIndex - 1; z++){ + // + //Generate the transition cell + // + currentTransitionCell.setValues( + //complex face vertex coordinates + new Vector3f(x,y+1,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z), new Vector3f(x+1,y+1,z), + new Vector3f(x,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), + new Vector3f(x,y+1,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+1), new Vector3f(x+1,y+1,z+1), + //simple face vertex coordinates + new Vector3f(x,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z), + new Vector3f(x,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+1), + //complex face iso values + chunkData.yPositiveEdgeIso[(x+0)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeIso[(x+0)*2+1][(z+0)*2+0], chunkData.yPositiveEdgeIso[(x+1)*2+0][(z+0)*2+0], + chunkData.yPositiveEdgeIso[(x+0)*2+0][(z+0)*2+1], chunkData.yPositiveEdgeIso[(x+0)*2+1][(z+0)*2+1], chunkData.yPositiveEdgeIso[(x+1)*2+0][(z+0)*2+1], + chunkData.yPositiveEdgeIso[(x+0)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeIso[(x+0)*2+1][(z+1)*2+0], chunkData.yPositiveEdgeIso[(x+1)*2+0][(z+1)*2+0], + //simple face iso values + chunkData.yPositiveEdgeIso[(x+0)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeIso[(x+1)*2+0][(z+0)*2+0], + chunkData.yPositiveEdgeIso[(x+0)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeIso[(x+1)*2+0][(z+1)*2+0], + //complex face texture atlas values + chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeAtlas[(x+0)*2+1][(z+0)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+0)*2+0], + chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+0)*2+1], chunkData.yPositiveEdgeAtlas[(x+0)*2+1][(z+0)*2+1], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+0)*2+1], + chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+0)*2+1][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+1)*2+0], + //simple face texture atlas values + chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+0)*2+0], + chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+1)*2+0] + ); + polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + + // + //Generate the normal cell with half width + // + currentCell.setValues( + new Vector3f(x+0,y+0,z+0), new Vector3f(x+0,y+0,z+1), new Vector3f(x+1,y,z+1), new Vector3f(x+1,y,z+0), + new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z+0), new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+0), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.yPositiveEdgeIso[(x+0)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeIso[(x+0)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeIso[(x+1)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeIso[(x+1)*2+0][(z+0)*2+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yPositiveEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+1)*2+0], chunkData.yPositiveEdgeAtlas[(x+1)*2+0][(z+0)*2+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } else { + int y = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2; + for(int x = xStartIndex; x < xEndIndex; x++){ + for(int z = zStartIndex; z < zEndIndex; 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), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.terrainGrid[x+0][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } + + + + + + + // + //generate the y-negative face + if(chunkData.yNegativeEdgeIso != null){ + int y = 0; + for(int x = xStartIndex; x < xEndIndex - 1; x++){ + for(int z = zStartIndex; z < zEndIndex - 1; z++){ + // + //Generate the transition cell + // + currentTransitionCell.setValues( + //complex face vertex coordinates + new Vector3f(x,y+0,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z), new Vector3f(x+1,y+0,z), + new Vector3f(x,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+0,z+TRANSITION_CELL_WIDTH), + new Vector3f(x,y+0,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+0,z+1), new Vector3f(x+1,y+0,z+1), + //simple face vertex coordinates + new Vector3f(x,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z), + new Vector3f(x,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+1), + //complex face iso values + chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeIso[(x+0)*2+1][(z+0)*2+0], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+0)*2+0], + chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+0)*2+1], chunkData.yNegativeEdgeIso[(x+0)*2+1][(z+0)*2+1], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+0)*2+1], + chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeIso[(x+0)*2+1][(z+1)*2+0], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+1)*2+0], + //simple face iso values + chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+0)*2+0], + chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+1)*2+0], + //complex face texture atlas values + chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+0)*2+1][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0], + chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+1], chunkData.yNegativeEdgeAtlas[(x+0)*2+1][(z+0)*2+1], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+1], + chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+0)*2+1][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0], + //simple face texture atlas values + chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0], + chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0] + ); + polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + + // + //Generate the normal cell with half width + // + currentCell.setValues( + new Vector3f(x+0,y+1,z+0), new Vector3f(x+0,y+1,z+1), new Vector3f(x+1,y+1,z+1), new Vector3f(x+1,y+1,z+0), + new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z+0), new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+0), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeIso[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeIso[(x+1)*2+0][(z+0)*2+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+0)*2+0], chunkData.yNegativeEdgeAtlas[(x+0)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+1)*2+0], chunkData.yNegativeEdgeAtlas[(x+1)*2+0][(z+0)*2+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } else { + int y = 0; + for(int x = xStartIndex; x < xEndIndex; x++){ + for(int z = zStartIndex; z < zEndIndex; 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), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.terrainGrid[x+0][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } + + + + + + + + // + //generate the z-positive face + if(chunkData.zPositiveEdgeIso != null){ + int z = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2; + for(int x = xStartIndex; x < xEndIndex - 1; x++){ + for(int y = yStartIndex; y < yEndIndex - 1; y++){ + // + //Generate the transition cell + // + currentTransitionCell.setValues( + //complex face vertex coordinates + new Vector3f(x+0,y,z+1), new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+0,y+1,z+1), + new Vector3f(x+TRANSITION_CELL_WIDTH,y,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z+1), + new Vector3f(x+1,y,z+1), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z+1), new Vector3f(x+1,y+1,z+1), + //simple face vertex coordinates + new Vector3f(x+0,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+0,y+1,z+TRANSITION_CELL_WIDTH), + new Vector3f(x+1,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), + //complex face iso values + chunkData.zPositiveEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeIso[(x+0)*2+0][(y+0)*2+1], chunkData.zPositiveEdgeIso[(x+0)*2+0][(y+1)*2+0], + chunkData.zPositiveEdgeIso[(x+0)*2+1][(y+0)*2+0], chunkData.zPositiveEdgeIso[(x+0)*2+1][(y+0)*2+1], chunkData.zPositiveEdgeIso[(x+0)*2+1][(y+1)*2+0], + chunkData.zPositiveEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeIso[(x+1)*2+0][(y+0)*2+1], chunkData.zPositiveEdgeIso[(x+1)*2+0][(y+1)*2+0], + //simple face iso values + chunkData.zPositiveEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeIso[(x+0)*2+0][(y+1)*2+0], + chunkData.zPositiveEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeIso[(x+1)*2+0][(y+1)*2+0], + //complex face texture atlas values + chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+0)*2+1], chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+1)*2+0], + chunkData.zPositiveEdgeAtlas[(x+0)*2+1][(y+0)*2+0], chunkData.zPositiveEdgeAtlas[(x+0)*2+1][(y+0)*2+1], chunkData.zPositiveEdgeAtlas[(x+0)*2+1][(y+1)*2+0], + chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+0)*2+1], chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+1)*2+0], + //simple face texture atlas values + chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+1)*2+0], + chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+1)*2+0] + ); + polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + + // + //Generate the normal cell with half width + // + currentCell.setValues( + new Vector3f(x+0,y+0,z+0), new Vector3f(x+0,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+0,z+0), + new Vector3f(x+0,y+1,z+0), new Vector3f(x+0,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+0), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.zPositiveEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.zPositiveEdgeIso[(x+0)*2+0][(y+1)*2+0], chunkData.zPositiveEdgeIso[(x+1)*2+0][(y+1)*2+0], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.zPositiveEdgeAtlas[(x+0)*2+0][(y+1)*2+0], chunkData.zPositiveEdgeAtlas[(x+1)*2+0][(y+1)*2+0], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } else { + int z = ServerTerrainChunk.CHUNK_DATA_GENERATOR_SIZE - 2; + for(int x = xStartIndex; x < xEndIndex; x++){ + for(int y = yStartIndex; y < yEndIndex; y++){ + //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), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.terrainGrid[x+0][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } + + + + + + + + // + //generate the z-negative face + if(chunkData.zNegativeEdgeIso != null){ + int z = 0; + for(int x = xStartIndex; x < xEndIndex - 1; x++){ + for(int y = yStartIndex; y < yEndIndex - 1; y++){ + // + //Generate the transition cell + // + currentTransitionCell.setValues( + //complex face vertex coordinates + new Vector3f(x+0,y,z), new Vector3f(x+0,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+0,y+1,z), + new Vector3f(x+TRANSITION_CELL_WIDTH,y,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+TRANSITION_CELL_WIDTH,y+1,z), + new Vector3f(x+1,y,z), new Vector3f(x+1,y+TRANSITION_CELL_WIDTH,z), new Vector3f(x+1,y+1,z), + //simple face vertex coordinates + new Vector3f(x+0,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+0,y+1,z+TRANSITION_CELL_WIDTH), + new Vector3f(x+1,y,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), + //complex face iso values + chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0], + chunkData.zNegativeEdgeIso[(x+0)*2+1][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+0)*2+1][(y+0)*2+1], chunkData.zNegativeEdgeIso[(x+0)*2+1][(y+1)*2+0], + chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0], + //simple face iso values + chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0], + chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0], + //complex face texture atlas values + chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0], + chunkData.zNegativeEdgeAtlas[(x+0)*2+1][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+1][(y+0)*2+1], chunkData.zNegativeEdgeAtlas[(x+0)*2+1][(y+1)*2+0], + chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+1], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0], + //simple face texture atlas values + chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0], + chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0] + ); + polygonizeTransition(currentTransitionCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + + // + //Generate the normal cell with half width + // + currentCell.setValues( + new Vector3f(x+0,y+0,z+1), new Vector3f(x+0,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+0,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+0,z+1), + new Vector3f(x+0,y+1,z+1), new Vector3f(x+0,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+TRANSITION_CELL_WIDTH), new Vector3f(x+1,y+1,z+1), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+0)*2+0], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.zNegativeEdgeIso[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeIso[(x+1)*2+0][(y+1)*2+0], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+0)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+0)*2+0], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.zNegativeEdgeAtlas[(x+0)*2+0][(y+1)*2+0], chunkData.zNegativeEdgeAtlas[(x+1)*2+0][(y+1)*2+0], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } else { + int z = 0; + for(int x = xStartIndex; x < xEndIndex; x++){ + for(int y = yStartIndex; y < yEndIndex; y++){ + //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), + chunkData.terrainGrid[x+0][y+0][z+0], chunkData.terrainGrid[x+0][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+1], chunkData.terrainGrid[x+1][y+0][z+0], + chunkData.terrainGrid[x+0][y+1][z+0], chunkData.terrainGrid[x+0][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+1], chunkData.terrainGrid[x+1][y+1][z+0], + chunkData.textureGrid[x+0][y+0][z+0], chunkData.textureGrid[x+0][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+1], chunkData.textureGrid[x+1][y+0][z+0], + chunkData.textureGrid[x+0][y+1][z+0], chunkData.textureGrid[x+0][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+1], chunkData.textureGrid[x+1][y+1][z+0] + ); + //polygonize the current gridcell + polygonize(currentCell, 0.01f, triangles, samplerTriangles, vertMap, verts, normals, trianglesSharingVert); + } + } + } + + + + + //all verts in order, flattened as an array of floats instead of vecs + List vertsFlat = new LinkedList(); + //all normals in order, flattened as an array of floats instead of vecs + List normalsFlat = new LinkedList(); + //all elements of faces in order + List elementsFlat = new LinkedList(); + //List of texture sampler values + List textureSamplers = new LinkedList(); + //List of texture ratio values + List textureRatioData = new LinkedList(); + + float scalingFactor = (float)Math.pow(2,chunkData.levelOfDetail); + + //flatten verts + normals + for(Vector3f vert : verts){ + vertsFlat.add(vert.x * scalingFactor); + vertsFlat.add(vert.y * scalingFactor); + vertsFlat.add(vert.z * scalingFactor); + } + + 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]); + } + + //flatten sampler indices + for(Vector3f samplerVec : samplerTriangles){ + textureSamplers.add((float)Globals.voxelTextureAtlas.getVoxelTypeOffset((int)samplerVec.x)); + textureSamplers.add((float)Globals.voxelTextureAtlas.getVoxelTypeOffset((int)samplerVec.y)); + textureSamplers.add((float)Globals.voxelTextureAtlas.getVoxelTypeOffset((int)samplerVec.z)); + } + + //set ratio dat + for(int j = 0; j < triangles.size(); j++){ + //first vertex + textureRatioData.add(1.0f); + textureRatioData.add(0.0f); + textureRatioData.add(0.0f); + + //second vertex + textureRatioData.add(0.0f); + textureRatioData.add(1.0f); + textureRatioData.add(0.0f); + + //third vertex + textureRatioData.add(0.0f); + textureRatioData.add(0.0f); + textureRatioData.add(1.0f); + } + + //List vertices, List normals, List faceElements, List uvs + TerrainChunkData rVal = new TerrainChunkData(vertsFlat, normalsFlat, elementsFlat, UVs, textureSamplers, textureRatioData); + return rVal; + } + + + + + + + + + /** + * Contains data required to generate a transvoxel mesh + */ + public static class TransvoxelChunkData { + + /** + + 5 6 + +-------------+ +-----5-------+ ^ Y + / | / | / | /| | _ + / | / | 4 9 6 10 | /\ Z + 4 +-----+-------+ 7 | +-----+7------+ | | / + | 1 +-------+-----+ 2 | +-----1-+-----+ | / + | / | / 8 0 11 2 | / + | / | / | / | / |/ + 0 +-------------+ 3 +------3------+ +---------------> X + + + */ + + /** + The assumption is that the edge arrays are laid out perfectly adjacent + ie + + + +---------------+---------------+ + | | | + | | | + | higher res | higher res | + | chunk 1 | chunk 2 | + | | | + | | | + | | | + +---------------+---------------+ + | | | + | | | + | higher res | higher res | + | chunk 3 | chunk 4 | + | | | + | | | + | | | + +---------------+---------------+ + + + */ + + float[][] xPositiveEdgeIso = null; //full-resolution values for the x-positive edge if it is twice the resolution of the core mesh + int[][] xPositiveEdgeAtlas = null; + float[][] xNegativeEdgeIso = null; //full-resolution values for the x-negative edge if it is twice the resolution of the core mesh + int[][] xNegativeEdgeAtlas = null; + float[][] yPositiveEdgeIso = null; //full-resolution values for the y-positive edge if it is twice the resolution of the core mesh + int[][] yPositiveEdgeAtlas = null; + float[][] yNegativeEdgeIso = null; //full-resolution values for the y-negative edge if it is twice the resolution of the core mesh + int[][] yNegativeEdgeAtlas = null; + float[][] zPositiveEdgeIso = null; //full-resolution values for the z-positive edge if it is twice the resolution of the core mesh + int[][] zPositiveEdgeAtlas = null; + float[][] zNegativeEdgeIso = null; //full-resolution values for the z-negative edge if it is twice the resolution of the core mesh + int[][] zNegativeEdgeAtlas = null; + + //the core voxel data for the main part of the mesh + public float[][][] terrainGrid; + + //the core texture data for the main part of the mesh + public int[][][] textureGrid; + + int levelOfDetail; + + /** + * Constructor -- please note that the edge arrays do not need to be defined + * They are left as null if there is not a higher resolution edge to this chunk + */ + public TransvoxelChunkData( + float[][][] terrainGrid, + int[][][] textureGrid, + int levelOfDetail + ){ + this.terrainGrid = terrainGrid; + this.textureGrid = textureGrid; + this.levelOfDetail = levelOfDetail; + } + + + /** + * Adds values for the face along positive x + * @param isoValues the iso values + * @param atlasValues the atlas values + */ + public void addXPositiveEdge(float[][] isoValues, int[][] atlasValues){ + this.xPositiveEdgeIso = isoValues; + this.xPositiveEdgeAtlas = atlasValues; + } + + /** + * Adds values for the face along negative x + * @param isoValues the iso values + * @param atlasValues the atlas values + */ + public void addXNegativeEdge(float[][] isoValues, int[][] atlasValues){ + this.xNegativeEdgeIso = isoValues; + this.xNegativeEdgeAtlas = atlasValues; + } + + /** + * Adds values for the face along positive y + * @param isoValues the iso values + * @param atlasValues the atlas values + */ + public void addYPositiveEdge(float[][] isoValues, int[][] atlasValues){ + this.yPositiveEdgeIso = isoValues; + this.yPositiveEdgeAtlas = atlasValues; + } + + /** + * Adds values for the face along negative y + * @param isoValues the iso values + * @param atlasValues the atlas values + */ + public void addYNegativeEdge(float[][] isoValues, int[][] atlasValues){ + this.yNegativeEdgeIso = isoValues; + this.yNegativeEdgeAtlas = atlasValues; + } + + /** + * Adds values for the face along positive z + * @param isoValues the iso values + * @param atlasValues the atlas values + */ + public void addZPositiveEdge(float[][] isoValues, int[][] atlasValues){ + this.zPositiveEdgeIso = isoValues; + this.zPositiveEdgeAtlas = atlasValues; + } + + /** + * Adds values for the face along negative z + * @param isoValues the iso values + * @param atlasValues the atlas values + */ + public void addZNegativeEdge(float[][] isoValues, int[][] atlasValues){ + this.zNegativeEdgeIso = isoValues; + this.zNegativeEdgeAtlas = atlasValues; + } + + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //The lookup table that maps iso value to the index to lookup for texture + static final int[][] sampleIndexTable = new int[][]{ + {0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3}, + {1, 2, 3, 0, 5, 6, 7, 4, 4, 5, 6, 7}, + }; + + + + + + + + + + + + // The transitionCellClass table maps a 9-bit transition cell case index to an equivalence + // class index. Even though there are 73 equivalence classes in the Transvoxel Algorithm, + // several of them use the same exact triangulations, just with different vertex locations. + // We combined those classes for this table so that the class index ranges from 0 to 55. + // The high bit is set in the cases for which the inverse state of the voxel data maps to + // the equivalence class, meaning that the winding order of each triangle should be reversed. + static final short transitionCellClass[] = new short[]{ + 0x00, 0x01, 0x02, 0x84, 0x01, 0x05, 0x04, 0x04, 0x02, 0x87, 0x09, 0x8C, 0x84, 0x0B, 0x05, 0x05, + 0x01, 0x08, 0x07, 0x8D, 0x05, 0x0F, 0x8B, 0x0B, 0x04, 0x0D, 0x0C, 0x1C, 0x04, 0x8B, 0x85, 0x85, + 0x02, 0x07, 0x09, 0x8C, 0x87, 0x10, 0x0C, 0x0C, 0x09, 0x12, 0x15, 0x9A, 0x8C, 0x19, 0x90, 0x10, + 0x84, 0x8D, 0x8C, 0x9C, 0x0B, 0x9D, 0x0F, 0x0F, 0x05, 0x1B, 0x10, 0xAC, 0x05, 0x0F, 0x8B, 0x0B, + 0x01, 0x05, 0x87, 0x0B, 0x08, 0x0F, 0x0D, 0x8B, 0x07, 0x10, 0x12, 0x19, 0x8D, 0x9D, 0x1B, 0x0F, + 0x05, 0x0F, 0x10, 0x9D, 0x0F, 0x1E, 0x1D, 0xA1, 0x8B, 0x1D, 0x99, 0x32, 0x0B, 0xA1, 0x8F, 0x94, + 0x04, 0x8B, 0x0C, 0x0F, 0x0D, 0x1D, 0x1C, 0x8F, 0x0C, 0x99, 0x1A, 0x31, 0x1C, 0x32, 0x2C, 0xA7, + 0x04, 0x0B, 0x0C, 0x0F, 0x8B, 0xA1, 0x8F, 0x96, 0x85, 0x8F, 0x90, 0x27, 0x85, 0x94, 0x8B, 0x8A, + 0x02, 0x04, 0x09, 0x05, 0x07, 0x8B, 0x0C, 0x85, 0x09, 0x0C, 0x15, 0x90, 0x8C, 0x0F, 0x10, 0x8B, + 0x87, 0x0D, 0x12, 0x1B, 0x10, 0x1D, 0x99, 0x8F, 0x0C, 0x1C, 0x1A, 0x2C, 0x0C, 0x8F, 0x90, 0x8B, + 0x09, 0x0C, 0x15, 0x10, 0x12, 0x99, 0x1A, 0x90, 0x15, 0x1A, 0x23, 0x30, 0x9A, 0x31, 0x30, 0x19, + 0x8C, 0x1C, 0x9A, 0xAC, 0x19, 0x32, 0x31, 0x27, 0x90, 0x2C, 0x30, 0x29, 0x10, 0xA7, 0x19, 0x24, + 0x84, 0x04, 0x8C, 0x05, 0x8D, 0x0B, 0x1C, 0x85, 0x8C, 0x0C, 0x9A, 0x10, 0x9C, 0x0F, 0xAC, 0x0B, + 0x0B, 0x8B, 0x19, 0x0F, 0x9D, 0xA1, 0x32, 0x94, 0x0F, 0x8F, 0x31, 0xA7, 0x0F, 0x96, 0x27, 0x8A, + 0x05, 0x85, 0x90, 0x8B, 0x1B, 0x8F, 0x2C, 0x8B, 0x10, 0x90, 0x30, 0x19, 0xAC, 0x27, 0x29, 0x24, + 0x05, 0x85, 0x10, 0x0B, 0x0F, 0x94, 0xA7, 0x8A, 0x8B, 0x8B, 0x19, 0x24, 0x0B, 0x8A, 0x24, 0x83, + 0x03, 0x06, 0x0A, 0x8B, 0x06, 0x0E, 0x0B, 0x0B, 0x0A, 0x91, 0x14, 0x8F, 0x8B, 0x17, 0x05, 0x85, + 0x06, 0x13, 0x11, 0x98, 0x0E, 0x1F, 0x97, 0x2B, 0x0B, 0x18, 0x0F, 0x36, 0x0B, 0xAB, 0x05, 0x85, + 0x0A, 0x11, 0x16, 0x8F, 0x91, 0x20, 0x0F, 0x8F, 0x14, 0x22, 0x21, 0x1D, 0x8F, 0x2D, 0x0B, 0x8B, + 0x8B, 0x98, 0x8F, 0xB7, 0x17, 0xAE, 0x8C, 0x0C, 0x05, 0x2F, 0x8B, 0xB5, 0x85, 0xA6, 0x84, 0x04, + 0x06, 0x0E, 0x91, 0x17, 0x13, 0x1F, 0x18, 0xAB, 0x11, 0x20, 0x22, 0x2D, 0x98, 0xAE, 0x2F, 0xA6, + 0x0E, 0x1F, 0x20, 0xAE, 0x1F, 0x33, 0x2E, 0x2A, 0x97, 0x2E, 0xAD, 0x28, 0x2B, 0x2A, 0x26, 0x25, + 0x0B, 0x97, 0x0F, 0x8C, 0x18, 0x2E, 0x37, 0x8C, 0x0F, 0xAD, 0x9D, 0x90, 0x36, 0x28, 0x35, 0x07, + 0x0B, 0x2B, 0x8F, 0x0C, 0xAB, 0x2A, 0x8C, 0x89, 0x05, 0x26, 0x0B, 0x87, 0x85, 0x25, 0x84, 0x82, + 0x0A, 0x0B, 0x14, 0x05, 0x11, 0x97, 0x0F, 0x05, 0x16, 0x0F, 0x21, 0x0B, 0x8F, 0x8C, 0x8B, 0x84, + 0x91, 0x18, 0x22, 0x2F, 0x20, 0x2E, 0xAD, 0x26, 0x0F, 0x37, 0x9D, 0x35, 0x8F, 0x8C, 0x0B, 0x84, + 0x14, 0x0F, 0x21, 0x8B, 0x22, 0xAD, 0x9D, 0x0B, 0x21, 0x9D, 0x9E, 0x8F, 0x1D, 0x90, 0x8F, 0x85, + 0x8F, 0x36, 0x1D, 0xB5, 0x2D, 0x28, 0x90, 0x87, 0x0B, 0x35, 0x8F, 0x34, 0x8B, 0x07, 0x85, 0x81, + 0x8B, 0x0B, 0x8F, 0x85, 0x98, 0x2B, 0x36, 0x85, 0x8F, 0x8F, 0x1D, 0x8B, 0xB7, 0x0C, 0xB5, 0x04, + 0x17, 0xAB, 0x2D, 0xA6, 0xAE, 0x2A, 0x28, 0x25, 0x8C, 0x8C, 0x90, 0x07, 0x0C, 0x89, 0x87, 0x82, + 0x05, 0x05, 0x0B, 0x84, 0x2F, 0x26, 0x35, 0x84, 0x8B, 0x0B, 0x8F, 0x85, 0xB5, 0x87, 0x34, 0x81, + 0x85, 0x85, 0x8B, 0x04, 0xA6, 0x25, 0x07, 0x82, 0x84, 0x84, 0x85, 0x81, 0x04, 0x82, 0x81, 0x80 + }; + + + // The transitionCellData table holds the triangulation data for all 56 distinct classes to + // which a case can be mapped by the transitionCellClass table. The class index should be ANDed + // with 0x7F before using it to look up triangulation data in this table. + static final TransitionCellData[] transitionCellData = new TransitionCellData[]{ + new TransitionCellData((char)0x00, new char[]{}), + new TransitionCellData((char)0x42, new char[]{0, 1, 3, 1, 2, 3}), + new TransitionCellData((char)0x31, new char[]{0, 1, 2}), + new TransitionCellData((char)0x42, new char[]{0, 1, 2, 0, 2, 3}), + new TransitionCellData((char)0x53, new char[]{0, 1, 4, 1, 3, 4, 1, 2, 3}), + new TransitionCellData((char)0x64, new char[]{0, 1, 5, 1, 2, 5, 2, 4, 5, 2, 3, 4}), + new TransitionCellData((char)0x84, new char[]{0, 1, 3, 1, 2, 3, 4, 5, 6, 4, 6, 7}), + new TransitionCellData((char)0x73, new char[]{0, 1, 3, 1, 2, 3, 4, 5, 6}), + new TransitionCellData((char)0x84, new char[]{0, 1, 3, 1, 2, 3, 4, 5, 7, 5, 6, 7}), + new TransitionCellData((char)0x62, new char[]{0, 1, 2, 3, 4, 5}), + new TransitionCellData((char)0x53, new char[]{0, 1, 3, 0, 3, 4, 1, 2, 3}), + new TransitionCellData((char)0x75, new char[]{0, 1, 6, 1, 2, 6, 2, 5, 6, 2, 3, 5, 3, 4, 5}), + new TransitionCellData((char)0x84, new char[]{0, 1, 4, 1, 3, 4, 1, 2, 3, 5, 6, 7}), + new TransitionCellData((char)0x95, new char[]{0, 1, 4, 1, 3, 4, 1, 2, 3, 5, 6, 8, 6, 7, 8}), + new TransitionCellData((char)0xA6, new char[]{0, 1, 5, 1, 2, 5, 2, 4, 5, 2, 3, 4, 6, 7, 8, 6, 8, 9}), + new TransitionCellData((char)0x86, new char[]{0, 1, 7, 1, 2, 7, 2, 3, 7, 3, 6, 7, 3, 4, 6, 4, 5, 6}), + new TransitionCellData((char)0x95, new char[]{0, 1, 5, 1, 2, 5, 2, 4, 5, 2, 3, 4, 6, 7, 8}), + new TransitionCellData((char)0x95, new char[]{0, 1, 3, 1, 2, 3, 4, 5, 7, 4, 7, 8, 5, 6, 7}), + new TransitionCellData((char)0xA4, new char[]{0, 1, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9}), + new TransitionCellData((char)0xC6, new char[]{0, 1, 3, 1, 2, 3, 4, 5, 7, 5, 6, 7, 8, 9, 10, 8, 10, 11}), + new TransitionCellData((char)0x64, new char[]{0, 1, 3, 1, 2, 3, 0, 3, 4, 0, 4, 5}), + new TransitionCellData((char)0x93, new char[]{0, 1, 2, 3, 4, 5, 6, 7, 8}), + new TransitionCellData((char)0x64, new char[]{0, 1, 4, 0, 4, 5, 1, 3, 4, 1, 2, 3}), + new TransitionCellData((char)0x97, new char[]{0, 1, 8, 1, 7, 8, 1, 2, 7, 2, 3, 7, 3, 4, 7, 4, 5, 7, 5, 6, 7}), + new TransitionCellData((char)0xB7, new char[]{0, 1, 6, 1, 2, 6, 2, 5, 6, 2, 3, 5, 3, 4, 5, 7, 8, 10, 8, 9, 10}), + new TransitionCellData((char)0xA6, new char[]{0, 1, 6, 1, 2, 6, 2, 5, 6, 2, 3, 5, 3, 4, 5, 7, 8, 9}), + new TransitionCellData((char)0xB5, new char[]{0, 1, 4, 1, 3, 4, 1, 2, 3, 5, 6, 7, 8, 9, 10}), + new TransitionCellData((char)0xA6, new char[]{0, 1, 5, 1, 2, 5, 2, 4, 5, 2, 3, 4, 6, 7, 9, 7, 8, 9}), + new TransitionCellData((char)0xA6, new char[]{0, 1, 4, 1, 3, 4, 1, 2, 3, 5, 6, 9, 6, 8, 9, 6, 7, 8}), + new TransitionCellData((char)0x97, new char[]{0, 1, 8, 1, 2, 8, 2, 3, 8, 3, 7, 8, 3, 4, 7, 4, 5, 7, 5, 6, 7}), + new TransitionCellData((char)0x86, new char[]{0, 1, 7, 1, 6, 7, 1, 2, 6, 2, 5, 6, 2, 4, 5, 2, 3, 4}), + new TransitionCellData((char)0xC8, new char[]{0, 1, 7, 1, 2, 7, 2, 3, 7, 3, 6, 7, 3, 4, 6, 4, 5, 6, 8, 9, 10, 8, 10, 11}), + new TransitionCellData((char)0xB7, new char[]{0, 1, 5, 1, 2, 5, 2, 4, 5, 2, 3, 4, 6, 9, 10, 6, 7, 9, 7, 8, 9}), + new TransitionCellData((char)0x75, new char[]{0, 1, 6, 1, 3, 6, 1, 2, 3, 3, 4, 6, 4, 5, 6}), + new TransitionCellData((char)0xA6, new char[]{0, 1, 3, 1, 2, 3, 4, 5, 9, 5, 8, 9, 5, 6, 8, 6, 7, 8}), + new TransitionCellData((char)0xC4, new char[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), + new TransitionCellData((char)0x86, new char[]{1, 2, 4, 2, 3, 4, 0, 1, 7, 1, 4, 7, 4, 6, 7, 4, 5, 6}), + new TransitionCellData((char)0x64, new char[]{0, 4, 5, 0, 1, 4, 1, 3, 4, 1, 2, 3}), + new TransitionCellData((char)0x86, new char[]{0, 1, 4, 1, 3, 4, 1, 2, 3, 0, 4, 7, 4, 6, 7, 4, 5, 6}), + new TransitionCellData((char)0x97, new char[]{1, 2, 3, 1, 3, 4, 1, 4, 5, 0, 1, 8, 1, 5, 8, 5, 7, 8, 5, 6, 7}), + new TransitionCellData((char)0xA6, new char[]{0, 1, 3, 1, 2, 3, 4, 5, 9, 5, 8, 9, 5, 6, 8, 6, 7, 8}), + new TransitionCellData((char)0xC8, new char[]{0, 1, 5, 1, 2, 5, 2, 4, 5, 2, 3, 4, 6, 7, 11, 7, 10, 11, 7, 8, 10, 8, 9, 10}), + new TransitionCellData((char)0x97, new char[]{0, 1, 8, 1, 2, 8, 2, 7, 8, 2, 3, 7, 3, 6, 7, 3, 4, 6, 4, 5, 6}), + new TransitionCellData((char)0x97, new char[]{0, 1, 4, 1, 3, 4, 1, 2, 3, 0, 4, 8, 4, 7, 8, 4, 5, 7, 5, 6, 7}), + new TransitionCellData((char)0xB7, new char[]{0, 1, 5, 1, 2, 5, 2, 4, 5, 2, 3, 4, 6, 7, 10, 7, 9, 10, 7, 8, 9}), + new TransitionCellData((char)0xA8, new char[]{0, 1, 9, 1, 2, 9, 2, 8, 9, 2, 3, 8, 3, 7, 8, 3, 4, 7, 4, 6, 7, 4, 5, 6}), + new TransitionCellData((char)0xB9, new char[]{0, 1, 7, 1, 6, 7, 1, 2, 6, 2, 5, 6, 2, 3, 5, 3, 4, 5, 0, 7, 10, 7, 9, 10, 7, 8, 9}), + new TransitionCellData((char)0xA6, new char[]{0, 1, 5, 1, 4, 5, 1, 2, 4, 2, 3, 4, 6, 7, 9, 7, 8, 9}), + new TransitionCellData((char)0xC6, new char[]{0, 1, 5, 1, 2, 5, 2, 4, 5, 2, 3, 4, 6, 7, 8, 9, 10, 11}), + new TransitionCellData((char)0xB7, new char[]{0, 1, 7, 1, 2, 7, 2, 3, 7, 3, 6, 7, 3, 4, 6, 4, 5, 6, 8, 9, 10}), + new TransitionCellData((char)0xA8, new char[]{1, 2, 3, 1, 3, 4, 1, 4, 6, 4, 5, 6, 0, 1, 9, 1, 6, 9, 6, 8, 9, 6, 7, 8}), + new TransitionCellData((char)0xCC, new char[]{0, 1, 9, 1, 8, 9, 1, 2, 8, 2, 11, 8, 2, 3, 11, 3, 4, 11, 4, 5, 11, 5, 10, 11, 5, 6, 10, 6, 9, 10, 6, 7, 9, 7, 0, 9}), + new TransitionCellData((char)0x86, new char[]{0, 1, 2, 0, 2, 3, 0, 6, 7, 0, 3, 6, 1, 4, 5, 1, 5, 2}), + new TransitionCellData((char)0x97, new char[]{0, 1, 4, 1, 3, 4, 1, 2, 3, 2, 5, 6, 2, 6, 3, 0, 7, 8, 0, 4, 7}), + new TransitionCellData((char)0xA8, new char[]{0, 1, 5, 1, 4, 5, 1, 2, 4, 2, 3, 4, 3, 6, 7, 3, 7, 4, 0, 8, 9, 0, 5, 8}), + new TransitionCellData((char)0xA8, new char[]{0, 1, 5, 1, 4, 5, 1, 2, 4, 2, 3, 4, 2, 6, 3, 3, 6, 7, 0, 8, 9, 0, 5, 8}), + }; + + // The transitionCornerData table contains the transition cell corner reuse data + // shown in Figure 4.19 + static final char[] transitionCornerData = new char[]{ + 0x30, 0x21, 0x20, 0x12, 0x40, 0x82, 0x10, 0x81, 0x80, 0x37, 0x27, 0x17, 0x87 + }; + + + // The transitionVertexData table gives the vertex locations for every one of the 512 possible + // cases in the Tranvoxel Algorithm. Each 16-bit value also provides information about whether + // a vertex can be reused from a neighboring cell. See Section 4.5 for details. The low byte + // contains the indexes for the two endpoints of the edge on which the vertex lies, as numbered + // in Figure 4.16. The high byte contains the vertex reuse data shown in Figure 4.17. + static final int transitionVertexData[][] = new int[][]{ + {}, + {0x2301, 0x1503, 0x199B, 0x289A}, + {0x2301, 0x2412, 0x4514}, + {0x1503, 0x4514, 0x2412, 0x289A, 0x199B}, + {0x8525, 0x2412, 0x289A, 0x89AC}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC}, + {0x8525, 0x4514, 0x2301, 0x289A, 0x89AC}, + {0x8525, 0x4514, 0x1503, 0x199B, 0x89AC}, + {0x8525, 0x8658, 0x4445}, + {0x1503, 0x2301, 0x289A, 0x199B, 0x8658, 0x8525, 0x4445}, + {0x8525, 0x8658, 0x4445, 0x2301, 0x2412, 0x4514}, + {0x1503, 0x4514, 0x2412, 0x289A, 0x199B, 0x8658, 0x8525, 0x4445}, + {0x2412, 0x4445, 0x8658, 0x89AC, 0x289A}, + {0x8658, 0x4445, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC}, + {0x8658, 0x4445, 0x4514, 0x2301, 0x289A, 0x89AC}, + {0x8658, 0x4445, 0x4514, 0x1503, 0x199B, 0x89AC}, + {0x8478, 0x8658, 0x89AC, 0x88BC}, + {0x2301, 0x1503, 0x199B, 0x289A, 0x8478, 0x8658, 0x89AC, 0x88BC}, + {0x8478, 0x8658, 0x89AC, 0x88BC, 0x2301, 0x2412, 0x4514}, + {0x1503, 0x4514, 0x2412, 0x289A, 0x199B, 0x8658, 0x8478, 0x88BC, 0x89AC}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x2301, 0x1503, 0x199B, 0x88BC}, + {0x2301, 0x4514, 0x8525, 0x8658, 0x8478, 0x88BC, 0x289A}, + {0x8478, 0x8658, 0x8525, 0x4514, 0x1503, 0x199B, 0x88BC}, + {0x8478, 0x4445, 0x8525, 0x89AC, 0x88BC}, + {0x8478, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x2301, 0x1503, 0x199B, 0x289A}, + {0x8478, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x2301, 0x2412, 0x4514}, + {0x8478, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x2412, 0x4514, 0x1503, 0x199B, 0x289A}, + {0x8478, 0x4445, 0x2412, 0x289A, 0x88BC}, + {0x1503, 0x2301, 0x2412, 0x4445, 0x8478, 0x88BC, 0x199B}, + {0x2301, 0x4514, 0x4445, 0x8478, 0x88BC, 0x289A}, + {0x1503, 0x4514, 0x4445, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x8367, 0x4647}, + {0x2301, 0x1503, 0x199B, 0x289A, 0x8478, 0x8367, 0x4647}, + {0x2301, 0x2412, 0x4514, 0x8478, 0x8367, 0x4647}, + {0x1503, 0x4514, 0x2412, 0x289A, 0x199B, 0x8367, 0x8478, 0x4647}, + {0x2412, 0x8525, 0x89AC, 0x289A, 0x8367, 0x8478, 0x4647}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC, 0x8478, 0x8367, 0x4647}, + {0x8525, 0x4514, 0x2301, 0x289A, 0x89AC, 0x8478, 0x8367, 0x4647}, + {0x8525, 0x4514, 0x1503, 0x199B, 0x89AC, 0x8478, 0x8367, 0x4647}, + {0x8478, 0x8367, 0x4647, 0x8525, 0x8658, 0x4445}, + {0x2301, 0x1503, 0x199B, 0x289A, 0x8478, 0x8367, 0x4647, 0x8525, 0x8658, 0x4445}, + {0x8478, 0x8367, 0x4647, 0x8525, 0x8658, 0x4445, 0x2301, 0x2412, 0x4514}, + {0x1503, 0x4514, 0x2412, 0x289A, 0x199B, 0x8658, 0x8525, 0x4445, 0x8367, 0x8478, 0x4647}, + {0x2412, 0x4445, 0x8658, 0x89AC, 0x289A, 0x8367, 0x8478, 0x4647}, + {0x8658, 0x4445, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC, 0x8478, 0x8367, 0x4647}, + {0x2301, 0x4514, 0x4445, 0x8658, 0x89AC, 0x289A, 0x8367, 0x8478, 0x4647}, + {0x8658, 0x4445, 0x4514, 0x1503, 0x199B, 0x89AC, 0x8478, 0x8367, 0x4647}, + {0x8658, 0x4647, 0x8367, 0x88BC, 0x89AC}, + {0x8658, 0x4647, 0x8367, 0x88BC, 0x89AC, 0x1503, 0x2301, 0x289A, 0x199B}, + {0x8658, 0x4647, 0x8367, 0x88BC, 0x89AC, 0x2412, 0x2301, 0x4514}, + {0x1503, 0x4514, 0x2412, 0x289A, 0x199B, 0x8658, 0x4647, 0x8367, 0x88BC, 0x89AC}, + {0x8367, 0x4647, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC}, + {0x1503, 0x2301, 0x2412, 0x8525, 0x8658, 0x4647, 0x8367, 0x88BC, 0x199B}, + {0x8367, 0x4647, 0x8658, 0x8525, 0x4514, 0x2301, 0x289A, 0x88BC}, + {0x8367, 0x4647, 0x8658, 0x8525, 0x4514, 0x1503, 0x199B, 0x88BC}, + {0x8367, 0x4647, 0x4445, 0x8525, 0x89AC, 0x88BC}, + {0x8367, 0x4647, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x2301, 0x1503, 0x199B, 0x289A}, + {0x8367, 0x4647, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x2301, 0x2412, 0x4514}, + {0x8525, 0x4445, 0x4647, 0x8367, 0x88BC, 0x89AC, 0x1503, 0x4514, 0x2412, 0x289A, 0x199B}, + {0x8367, 0x4647, 0x4445, 0x2412, 0x289A, 0x88BC}, + {0x8367, 0x4647, 0x4445, 0x2412, 0x2301, 0x1503, 0x199B, 0x88BC}, + {0x2301, 0x4514, 0x4445, 0x4647, 0x8367, 0x88BC, 0x289A}, + {0x8367, 0x4647, 0x4445, 0x4514, 0x1503, 0x199B, 0x88BC}, + {0x1636, 0x8367, 0x88BC, 0x199B}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A}, + {0x8367, 0x1636, 0x199B, 0x88BC, 0x2412, 0x2301, 0x4514}, + {0x2412, 0x4514, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A}, + {0x8525, 0x2412, 0x289A, 0x89AC, 0x1636, 0x8367, 0x88BC, 0x199B}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x1636, 0x8367, 0x88BC, 0x89AC}, + {0x8525, 0x4514, 0x2301, 0x289A, 0x89AC, 0x1636, 0x8367, 0x88BC, 0x199B}, + {0x8367, 0x1636, 0x1503, 0x4514, 0x8525, 0x89AC, 0x88BC}, + {0x1636, 0x8367, 0x88BC, 0x199B, 0x8525, 0x8658, 0x4445}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A, 0x8525, 0x8658, 0x4445}, + {0x1636, 0x8367, 0x88BC, 0x199B, 0x8525, 0x8658, 0x4445, 0x2301, 0x2412, 0x4514}, + {0x2412, 0x4514, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A, 0x8525, 0x8658, 0x4445}, + {0x2412, 0x4445, 0x8658, 0x89AC, 0x289A, 0x8367, 0x1636, 0x199B, 0x88BC}, + {0x8367, 0x1636, 0x1503, 0x2301, 0x2412, 0x4445, 0x8658, 0x89AC, 0x88BC}, + {0x8658, 0x4445, 0x4514, 0x2301, 0x289A, 0x89AC, 0x1636, 0x8367, 0x88BC, 0x199B}, + {0x8658, 0x4445, 0x4514, 0x1503, 0x1636, 0x8367, 0x88BC, 0x89AC}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x8478, 0x8658, 0x89AC, 0x289A}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B, 0x2301, 0x2412, 0x4514}, + {0x8658, 0x8478, 0x8367, 0x1636, 0x1503, 0x4514, 0x2412, 0x289A, 0x89AC}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x8525, 0x2412, 0x289A, 0x199B}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x8525, 0x2412, 0x2301, 0x1503}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x8525, 0x4514, 0x2301, 0x289A, 0x199B}, + {0x1503, 0x4514, 0x8525, 0x8658, 0x8478, 0x8367, 0x1636}, + {0x8525, 0x4445, 0x8478, 0x8367, 0x1636, 0x199B, 0x89AC}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x8478, 0x4445, 0x8525, 0x89AC, 0x289A}, + {0x8525, 0x4445, 0x8478, 0x8367, 0x1636, 0x199B, 0x89AC, 0x2412, 0x2301, 0x4514}, + {0x2412, 0x4514, 0x1503, 0x1636, 0x8367, 0x8478, 0x4445, 0x8525, 0x89AC, 0x289A}, + {0x1636, 0x8367, 0x8478, 0x4445, 0x2412, 0x289A, 0x199B}, + {0x2412, 0x4445, 0x8478, 0x8367, 0x1636, 0x1503, 0x2301}, + {0x2301, 0x4514, 0x4445, 0x8478, 0x8367, 0x1636, 0x199B, 0x289A}, + {0x8367, 0x1636, 0x1503, 0x4514, 0x4445, 0x8478}, + {0x1636, 0x4647, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x4647, 0x1636, 0x1503, 0x2301, 0x289A, 0x88BC}, + {0x1636, 0x4647, 0x8478, 0x88BC, 0x199B, 0x2301, 0x2412, 0x4514}, + {0x2412, 0x4514, 0x1503, 0x1636, 0x4647, 0x8478, 0x88BC, 0x289A}, + {0x1636, 0x4647, 0x8478, 0x88BC, 0x199B, 0x8525, 0x2412, 0x289A, 0x89AC}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x1636, 0x4647, 0x8478, 0x88BC, 0x89AC}, + {0x8525, 0x4514, 0x2301, 0x289A, 0x89AC, 0x1636, 0x4647, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x4647, 0x1636, 0x1503, 0x4514, 0x8525, 0x89AC, 0x88BC}, + {0x1636, 0x4647, 0x8478, 0x88BC, 0x199B, 0x8525, 0x8658, 0x4445}, + {0x8478, 0x4647, 0x1636, 0x1503, 0x2301, 0x289A, 0x88BC, 0x8658, 0x8525, 0x4445}, + {0x1636, 0x4647, 0x8478, 0x88BC, 0x199B, 0x8525, 0x8658, 0x4445, 0x2301, 0x2412, 0x4514}, + {0x2412, 0x4514, 0x1503, 0x1636, 0x4647, 0x8478, 0x88BC, 0x289A, 0x8525, 0x8658, 0x4445}, + {0x1636, 0x4647, 0x8478, 0x88BC, 0x199B, 0x8658, 0x4445, 0x2412, 0x289A, 0x89AC}, + {0x8658, 0x4445, 0x2412, 0x2301, 0x1503, 0x1636, 0x4647, 0x8478, 0x88BC, 0x89AC}, + {0x8658, 0x4445, 0x4514, 0x2301, 0x289A, 0x89AC, 0x1636, 0x4647, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x4647, 0x1636, 0x1503, 0x4514, 0x4445, 0x8658, 0x89AC, 0x88BC}, + {0x1636, 0x4647, 0x8658, 0x89AC, 0x199B}, + {0x2301, 0x1503, 0x1636, 0x4647, 0x8658, 0x89AC, 0x289A}, + {0x1636, 0x4647, 0x8658, 0x89AC, 0x199B, 0x2301, 0x2412, 0x4514}, + {0x2412, 0x4514, 0x1503, 0x1636, 0x4647, 0x8658, 0x89AC, 0x289A}, + {0x2412, 0x8525, 0x8658, 0x4647, 0x1636, 0x199B, 0x289A}, + {0x8658, 0x4647, 0x1636, 0x1503, 0x2301, 0x2412, 0x8525}, + {0x2301, 0x4514, 0x8525, 0x8658, 0x4647, 0x1636, 0x199B, 0x289A}, + {0x1503, 0x4514, 0x8525, 0x8658, 0x4647, 0x1636}, + {0x8525, 0x4445, 0x4647, 0x1636, 0x199B, 0x89AC}, + {0x8525, 0x4445, 0x4647, 0x1636, 0x1503, 0x2301, 0x289A, 0x89AC}, + {0x8525, 0x4445, 0x4647, 0x1636, 0x199B, 0x89AC, 0x2412, 0x2301, 0x4514}, + {0x2412, 0x4514, 0x1503, 0x1636, 0x4647, 0x4445, 0x8525, 0x89AC, 0x289A}, + {0x2412, 0x4445, 0x4647, 0x1636, 0x199B, 0x289A}, + {0x1503, 0x2301, 0x2412, 0x4445, 0x4647, 0x1636}, + {0x2301, 0x4514, 0x4445, 0x4647, 0x1636, 0x199B, 0x289A}, + {0x1503, 0x4514, 0x4445, 0x4647, 0x1636}, + {0x1636, 0x1503, 0x4334}, + {0x2301, 0x4334, 0x1636, 0x199B, 0x289A}, + {0x2301, 0x2412, 0x4514, 0x1636, 0x1503, 0x4334}, + {0x2412, 0x4514, 0x4334, 0x1636, 0x199B, 0x289A}, + {0x8525, 0x2412, 0x289A, 0x89AC, 0x1636, 0x1503, 0x4334}, + {0x1636, 0x4334, 0x2301, 0x2412, 0x8525, 0x89AC, 0x199B}, + {0x8525, 0x4514, 0x2301, 0x289A, 0x89AC, 0x1636, 0x1503, 0x4334}, + {0x1636, 0x4334, 0x4514, 0x8525, 0x89AC, 0x199B}, + {0x1636, 0x1503, 0x4334, 0x8525, 0x8658, 0x4445}, + {0x2301, 0x4334, 0x1636, 0x199B, 0x289A, 0x8525, 0x8658, 0x4445}, + {0x8525, 0x8658, 0x4445, 0x2301, 0x2412, 0x4514, 0x1636, 0x1503, 0x4334}, + {0x1636, 0x4334, 0x4514, 0x2412, 0x289A, 0x199B, 0x8658, 0x8525, 0x4445}, + {0x2412, 0x4445, 0x8658, 0x89AC, 0x289A, 0x1503, 0x1636, 0x4334}, + {0x8658, 0x4445, 0x2412, 0x2301, 0x4334, 0x1636, 0x199B, 0x89AC}, + {0x8658, 0x4445, 0x4514, 0x2301, 0x289A, 0x89AC, 0x1636, 0x1503, 0x4334}, + {0x1636, 0x4334, 0x4514, 0x4445, 0x8658, 0x89AC, 0x199B}, + {0x8658, 0x8478, 0x88BC, 0x89AC, 0x1503, 0x1636, 0x4334}, + {0x2301, 0x4334, 0x1636, 0x199B, 0x289A, 0x8478, 0x8658, 0x89AC, 0x88BC}, + {0x8478, 0x8658, 0x89AC, 0x88BC, 0x2301, 0x2412, 0x4514, 0x1636, 0x1503, 0x4334}, + {0x2412, 0x4514, 0x4334, 0x1636, 0x199B, 0x289A, 0x8478, 0x8658, 0x89AC, 0x88BC}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC, 0x1636, 0x1503, 0x4334}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x2301, 0x4334, 0x1636, 0x199B, 0x88BC}, + {0x2301, 0x4514, 0x8525, 0x8658, 0x8478, 0x88BC, 0x289A, 0x1503, 0x1636, 0x4334}, + {0x1636, 0x4334, 0x4514, 0x8525, 0x8658, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x1636, 0x1503, 0x4334}, + {0x2301, 0x4334, 0x1636, 0x199B, 0x289A, 0x8478, 0x4445, 0x8525, 0x89AC, 0x88BC}, + {0x8478, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x2301, 0x2412, 0x4514, 0x1636, 0x1503, 0x4334}, + {0x2412, 0x4514, 0x4334, 0x1636, 0x199B, 0x289A, 0x8478, 0x4445, 0x8525, 0x89AC, 0x88BC}, + {0x8478, 0x4445, 0x2412, 0x289A, 0x88BC, 0x1636, 0x1503, 0x4334}, + {0x1636, 0x4334, 0x2301, 0x2412, 0x4445, 0x8478, 0x88BC, 0x199B}, + {0x2301, 0x4514, 0x4445, 0x8478, 0x88BC, 0x289A, 0x1503, 0x1636, 0x4334}, + {0x1636, 0x4334, 0x4514, 0x4445, 0x8478, 0x88BC, 0x199B}, + {0x1636, 0x1503, 0x4334, 0x8478, 0x8367, 0x4647}, + {0x2301, 0x4334, 0x1636, 0x199B, 0x289A, 0x8478, 0x8367, 0x4647}, + {0x2301, 0x2412, 0x4514, 0x1636, 0x1503, 0x4334, 0x8478, 0x8367, 0x4647}, + {0x2412, 0x4514, 0x4334, 0x1636, 0x199B, 0x289A, 0x8478, 0x8367, 0x4647}, + {0x8525, 0x2412, 0x289A, 0x89AC, 0x1636, 0x1503, 0x4334, 0x8478, 0x8367, 0x4647}, + {0x1636, 0x4334, 0x2301, 0x2412, 0x8525, 0x89AC, 0x199B, 0x8367, 0x8478, 0x4647}, + {0x8525, 0x4514, 0x2301, 0x289A, 0x89AC, 0x1636, 0x1503, 0x4334, 0x8478, 0x8367, 0x4647}, + {0x1636, 0x4334, 0x4514, 0x8525, 0x89AC, 0x199B, 0x8367, 0x8478, 0x4647}, + {0x1636, 0x1503, 0x4334, 0x8478, 0x8367, 0x4647, 0x8525, 0x8658, 0x4445}, + {0x2301, 0x4334, 0x1636, 0x199B, 0x289A, 0x8478, 0x8367, 0x4647, 0x8525, 0x8658, 0x4445}, + {0x2301, 0x2412, 0x4514, 0x1636, 0x1503, 0x4334, 0x8478, 0x8367, 0x4647, 0x8525, 0x8658, 0x4445}, + {0x2412, 0x4514, 0x4334, 0x1636, 0x199B, 0x289A, 0x8478, 0x8367, 0x4647, 0x8525, 0x8658, 0x4445}, + {0x2412, 0x4445, 0x8658, 0x89AC, 0x289A, 0x8367, 0x8478, 0x4647, 0x1503, 0x1636, 0x4334}, + {0x8658, 0x4445, 0x2412, 0x2301, 0x4334, 0x1636, 0x199B, 0x89AC, 0x8478, 0x8367, 0x4647}, + {0x8658, 0x4445, 0x4514, 0x2301, 0x289A, 0x89AC, 0x1636, 0x1503, 0x4334, 0x8478, 0x8367, 0x4647}, + {0x8658, 0x4445, 0x4514, 0x4334, 0x1636, 0x199B, 0x89AC, 0x8478, 0x8367, 0x4647}, + {0x8658, 0x4647, 0x8367, 0x88BC, 0x89AC, 0x1503, 0x1636, 0x4334}, + {0x2301, 0x4334, 0x1636, 0x199B, 0x289A, 0x8367, 0x4647, 0x8658, 0x89AC, 0x88BC}, + {0x8658, 0x4647, 0x8367, 0x88BC, 0x89AC, 0x1503, 0x1636, 0x4334, 0x2412, 0x2301, 0x4514}, + {0x1636, 0x4334, 0x4514, 0x2412, 0x289A, 0x199B, 0x8658, 0x4647, 0x8367, 0x88BC, 0x89AC}, + {0x8367, 0x4647, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC, 0x1636, 0x1503, 0x4334}, + {0x8367, 0x4647, 0x8658, 0x8525, 0x2412, 0x2301, 0x4334, 0x1636, 0x199B, 0x88BC}, + {0x8367, 0x4647, 0x8658, 0x8525, 0x4514, 0x2301, 0x289A, 0x88BC, 0x1636, 0x1503, 0x4334}, + {0x8367, 0x4647, 0x8658, 0x8525, 0x4514, 0x4334, 0x1636, 0x199B, 0x88BC}, + {0x8525, 0x4445, 0x4647, 0x8367, 0x88BC, 0x89AC, 0x1503, 0x1636, 0x4334}, + {0x8367, 0x4647, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x2301, 0x4334, 0x1636, 0x199B, 0x289A}, + {0x8367, 0x4647, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x2301, 0x2412, 0x4514, 0x1636, 0x1503, 0x4334}, + {0x8367, 0x4647, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x2412, 0x4514, 0x4334, 0x1636, 0x199B, 0x289A}, + {0x8367, 0x4647, 0x4445, 0x2412, 0x289A, 0x88BC, 0x1636, 0x1503, 0x4334}, + {0x1636, 0x4334, 0x2301, 0x2412, 0x4445, 0x4647, 0x8367, 0x88BC, 0x199B}, + {0x8367, 0x4647, 0x4445, 0x4514, 0x2301, 0x289A, 0x88BC, 0x1636, 0x1503, 0x4334}, + {0x8367, 0x4647, 0x4445, 0x4514, 0x4334, 0x1636, 0x199B, 0x88BC}, + {0x8367, 0x4334, 0x1503, 0x199B, 0x88BC}, + {0x2301, 0x4334, 0x8367, 0x88BC, 0x289A}, + {0x8367, 0x4334, 0x1503, 0x199B, 0x88BC, 0x2412, 0x2301, 0x4514}, + {0x2412, 0x4514, 0x4334, 0x8367, 0x88BC, 0x289A}, + {0x8367, 0x4334, 0x1503, 0x199B, 0x88BC, 0x2412, 0x8525, 0x89AC, 0x289A}, + {0x8525, 0x2412, 0x2301, 0x4334, 0x8367, 0x88BC, 0x89AC}, + {0x8525, 0x4514, 0x2301, 0x289A, 0x89AC, 0x1503, 0x4334, 0x8367, 0x88BC, 0x199B}, + {0x8367, 0x4334, 0x4514, 0x8525, 0x89AC, 0x88BC}, + {0x8367, 0x4334, 0x1503, 0x199B, 0x88BC, 0x8658, 0x8525, 0x4445}, + {0x2301, 0x4334, 0x8367, 0x88BC, 0x289A, 0x8525, 0x8658, 0x4445}, + {0x8367, 0x4334, 0x1503, 0x199B, 0x88BC, 0x2412, 0x2301, 0x4514, 0x8658, 0x8525, 0x4445}, + {0x2412, 0x4514, 0x4334, 0x8367, 0x88BC, 0x289A, 0x8525, 0x8658, 0x4445}, + {0x2412, 0x4445, 0x8658, 0x89AC, 0x289A, 0x8367, 0x4334, 0x1503, 0x199B, 0x88BC}, + {0x8658, 0x4445, 0x2412, 0x2301, 0x4334, 0x8367, 0x88BC, 0x89AC}, + {0x2301, 0x4514, 0x4445, 0x8658, 0x89AC, 0x289A, 0x8367, 0x4334, 0x1503, 0x199B, 0x88BC}, + {0x8658, 0x4445, 0x4514, 0x4334, 0x8367, 0x88BC, 0x89AC}, + {0x1503, 0x4334, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B}, + {0x8658, 0x8478, 0x8367, 0x4334, 0x2301, 0x289A, 0x89AC}, + {0x1503, 0x4334, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B, 0x2301, 0x2412, 0x4514}, + {0x2412, 0x4514, 0x4334, 0x8367, 0x8478, 0x8658, 0x89AC, 0x289A}, + {0x2412, 0x8525, 0x8658, 0x8478, 0x8367, 0x4334, 0x1503, 0x199B, 0x289A}, + {0x8367, 0x4334, 0x2301, 0x2412, 0x8525, 0x8658, 0x8478}, + {0x1503, 0x4334, 0x8367, 0x8478, 0x8658, 0x8525, 0x4514, 0x2301, 0x289A, 0x199B}, + {0x8658, 0x8478, 0x8367, 0x4334, 0x4514, 0x8525}, + {0x1503, 0x4334, 0x8367, 0x8478, 0x4445, 0x8525, 0x89AC, 0x199B}, + {0x8525, 0x4445, 0x8478, 0x8367, 0x4334, 0x2301, 0x289A, 0x89AC}, + {0x1503, 0x4334, 0x8367, 0x8478, 0x4445, 0x8525, 0x89AC, 0x199B, 0x2301, 0x2412, 0x4514}, + {0x8525, 0x4445, 0x8478, 0x8367, 0x4334, 0x4514, 0x2412, 0x289A, 0x89AC}, + {0x1503, 0x4334, 0x8367, 0x8478, 0x4445, 0x2412, 0x289A, 0x199B}, + {0x8367, 0x4334, 0x2301, 0x2412, 0x4445, 0x8478}, + {0x1503, 0x4334, 0x8367, 0x8478, 0x4445, 0x4514, 0x2301, 0x289A, 0x199B}, + {0x8367, 0x4334, 0x4514, 0x4445, 0x8478}, + {0x1503, 0x4334, 0x4647, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x4647, 0x4334, 0x2301, 0x289A, 0x88BC}, + {0x8478, 0x4647, 0x4334, 0x1503, 0x199B, 0x88BC, 0x2412, 0x2301, 0x4514}, + {0x8478, 0x4647, 0x4334, 0x4514, 0x2412, 0x289A, 0x88BC}, + {0x1503, 0x4334, 0x4647, 0x8478, 0x88BC, 0x199B, 0x8525, 0x2412, 0x289A, 0x89AC}, + {0x8478, 0x4647, 0x4334, 0x2301, 0x2412, 0x8525, 0x89AC, 0x88BC}, + {0x1503, 0x4334, 0x4647, 0x8478, 0x88BC, 0x199B, 0x8525, 0x4514, 0x2301, 0x289A, 0x89AC}, + {0x8478, 0x4647, 0x4334, 0x4514, 0x8525, 0x89AC, 0x88BC}, + {0x1503, 0x4334, 0x4647, 0x8478, 0x88BC, 0x199B, 0x8525, 0x8658, 0x4445}, + {0x8478, 0x4647, 0x4334, 0x2301, 0x289A, 0x88BC, 0x8658, 0x8525, 0x4445}, + {0x1503, 0x4334, 0x4647, 0x8478, 0x88BC, 0x199B, 0x8525, 0x8658, 0x4445, 0x2301, 0x2412, 0x4514}, + {0x2412, 0x4514, 0x4334, 0x4647, 0x8478, 0x88BC, 0x289A, 0x8525, 0x8658, 0x4445}, + {0x8478, 0x4647, 0x4334, 0x1503, 0x199B, 0x88BC, 0x2412, 0x4445, 0x8658, 0x89AC, 0x289A}, + {0x8658, 0x4445, 0x2412, 0x2301, 0x4334, 0x4647, 0x8478, 0x88BC, 0x89AC}, + {0x8658, 0x4445, 0x4514, 0x2301, 0x289A, 0x89AC, 0x1503, 0x4334, 0x4647, 0x8478, 0x88BC, 0x199B}, + {0x8658, 0x4445, 0x4514, 0x4334, 0x4647, 0x8478, 0x88BC, 0x89AC}, + {0x1503, 0x4334, 0x4647, 0x8658, 0x89AC, 0x199B}, + {0x8658, 0x4647, 0x4334, 0x2301, 0x289A, 0x89AC}, + {0x1503, 0x4334, 0x4647, 0x8658, 0x89AC, 0x199B, 0x2301, 0x2412, 0x4514}, + {0x2412, 0x4514, 0x4334, 0x4647, 0x8658, 0x89AC, 0x289A}, + {0x1503, 0x4334, 0x4647, 0x8658, 0x8525, 0x2412, 0x289A, 0x199B}, + {0x2412, 0x8525, 0x8658, 0x4647, 0x4334, 0x2301}, + {0x2301, 0x4514, 0x8525, 0x8658, 0x4647, 0x4334, 0x1503, 0x199B, 0x289A}, + {0x8658, 0x4647, 0x4334, 0x4514, 0x8525}, + {0x8525, 0x4445, 0x4647, 0x4334, 0x1503, 0x199B, 0x89AC}, + {0x8525, 0x4445, 0x4647, 0x4334, 0x2301, 0x289A, 0x89AC}, + {0x1503, 0x4334, 0x4647, 0x4445, 0x8525, 0x89AC, 0x199B, 0x2301, 0x2412, 0x4514}, + {0x2412, 0x4514, 0x4334, 0x4647, 0x4445, 0x8525, 0x89AC, 0x289A}, + {0x1503, 0x4334, 0x4647, 0x4445, 0x2412, 0x289A, 0x199B}, + {0x2412, 0x4445, 0x4647, 0x4334, 0x2301}, + {0x1503, 0x4334, 0x4647, 0x4445, 0x4514, 0x2301, 0x289A, 0x199B}, + {0x4514, 0x4445, 0x4647, 0x4334}, + {0x4514, 0x4445, 0x4647, 0x4334}, + {0x2301, 0x1503, 0x199B, 0x289A, 0x4334, 0x4514, 0x4445, 0x4647}, + {0x2412, 0x4445, 0x4647, 0x4334, 0x2301}, + {0x1503, 0x4334, 0x4647, 0x4445, 0x2412, 0x289A, 0x199B}, + {0x8525, 0x2412, 0x289A, 0x89AC, 0x4514, 0x4445, 0x4647, 0x4334}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC, 0x4514, 0x4445, 0x4647, 0x4334}, + {0x8525, 0x4445, 0x4647, 0x4334, 0x2301, 0x289A, 0x89AC}, + {0x8525, 0x4445, 0x4647, 0x4334, 0x1503, 0x199B, 0x89AC}, + {0x8658, 0x4647, 0x4334, 0x4514, 0x8525}, + {0x1503, 0x2301, 0x289A, 0x199B, 0x8525, 0x4514, 0x4334, 0x4647, 0x8658}, + {0x2412, 0x8525, 0x8658, 0x4647, 0x4334, 0x2301}, + {0x1503, 0x4334, 0x4647, 0x8658, 0x8525, 0x2412, 0x289A, 0x199B}, + {0x2412, 0x4514, 0x4334, 0x4647, 0x8658, 0x89AC, 0x289A}, + {0x8658, 0x4647, 0x4334, 0x4514, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC}, + {0x8658, 0x4647, 0x4334, 0x2301, 0x289A, 0x89AC}, + {0x1503, 0x4334, 0x4647, 0x8658, 0x89AC, 0x199B}, + {0x8478, 0x8658, 0x89AC, 0x88BC, 0x4445, 0x4647, 0x4334, 0x4514}, + {0x2301, 0x1503, 0x199B, 0x289A, 0x8478, 0x8658, 0x89AC, 0x88BC, 0x4334, 0x4514, 0x4445, 0x4647}, + {0x8478, 0x8658, 0x89AC, 0x88BC, 0x2412, 0x4445, 0x4647, 0x4334, 0x2301}, + {0x1503, 0x4334, 0x4647, 0x4445, 0x2412, 0x289A, 0x199B, 0x8658, 0x8478, 0x88BC, 0x89AC}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC, 0x4445, 0x4647, 0x4334, 0x4514}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x2301, 0x1503, 0x199B, 0x88BC, 0x4514, 0x4445, 0x4647, 0x4334}, + {0x2301, 0x4334, 0x4647, 0x4445, 0x8525, 0x8658, 0x8478, 0x88BC, 0x289A}, + {0x8478, 0x8658, 0x8525, 0x4445, 0x4647, 0x4334, 0x1503, 0x199B, 0x88BC}, + {0x8478, 0x4647, 0x4334, 0x4514, 0x8525, 0x89AC, 0x88BC}, + {0x8478, 0x4647, 0x4334, 0x4514, 0x8525, 0x89AC, 0x88BC, 0x2301, 0x1503, 0x199B, 0x289A}, + {0x8478, 0x4647, 0x4334, 0x2301, 0x2412, 0x8525, 0x89AC, 0x88BC}, + {0x8478, 0x4647, 0x4334, 0x1503, 0x2412, 0x8525, 0x199B, 0x289A, 0x89AC, 0x88BC}, + {0x8478, 0x4647, 0x4334, 0x4514, 0x2412, 0x289A, 0x88BC}, + {0x1503, 0x2301, 0x2412, 0x4514, 0x4334, 0x4647, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x4647, 0x4334, 0x2301, 0x289A, 0x88BC}, + {0x1503, 0x4334, 0x4647, 0x8478, 0x88BC, 0x199B}, + {0x8367, 0x4334, 0x4514, 0x4445, 0x8478}, + {0x2301, 0x1503, 0x199B, 0x289A, 0x8367, 0x4334, 0x4514, 0x4445, 0x8478}, + {0x8367, 0x4334, 0x2301, 0x2412, 0x4445, 0x8478}, + {0x1503, 0x4334, 0x8367, 0x8478, 0x4445, 0x2412, 0x289A, 0x199B}, + {0x2412, 0x8525, 0x89AC, 0x289A, 0x8478, 0x4445, 0x4514, 0x4334, 0x8367}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC, 0x8367, 0x4334, 0x4514, 0x4445, 0x8478}, + {0x8525, 0x4445, 0x8478, 0x8367, 0x4334, 0x2301, 0x289A, 0x89AC}, + {0x1503, 0x4334, 0x8367, 0x8478, 0x4445, 0x8525, 0x89AC, 0x199B}, + {0x8658, 0x8478, 0x8367, 0x4334, 0x4514, 0x8525}, + {0x2301, 0x1503, 0x199B, 0x289A, 0x8367, 0x4334, 0x4514, 0x8525, 0x8658, 0x8478}, + {0x8367, 0x4334, 0x2301, 0x2412, 0x8525, 0x8658, 0x8478}, + {0x2412, 0x8525, 0x8658, 0x8478, 0x8367, 0x4334, 0x1503, 0x199B, 0x289A}, + {0x2412, 0x4514, 0x4334, 0x8367, 0x8478, 0x8658, 0x89AC, 0x289A}, + {0x8658, 0x8478, 0x8367, 0x4334, 0x4514, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC}, + {0x8658, 0x8478, 0x8367, 0x4334, 0x2301, 0x289A, 0x89AC}, + {0x1503, 0x4334, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B}, + {0x8658, 0x4445, 0x4514, 0x4334, 0x8367, 0x88BC, 0x89AC}, + {0x8658, 0x4445, 0x4514, 0x4334, 0x8367, 0x88BC, 0x89AC, 0x1503, 0x2301, 0x289A, 0x199B}, + {0x8658, 0x4445, 0x2412, 0x2301, 0x4334, 0x8367, 0x88BC, 0x89AC}, + {0x8658, 0x4445, 0x2412, 0x1503, 0x4334, 0x8367, 0x289A, 0x199B, 0x88BC, 0x89AC}, + {0x8367, 0x4334, 0x4514, 0x4445, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC}, + {0x1503, 0x2301, 0x2412, 0x8525, 0x8658, 0x4445, 0x4514, 0x4334, 0x8367, 0x88BC, 0x199B}, + {0x2301, 0x4334, 0x8367, 0x88BC, 0x289A, 0x8525, 0x8658, 0x4445}, + {0x8367, 0x4334, 0x1503, 0x199B, 0x88BC, 0x8658, 0x8525, 0x4445}, + {0x8367, 0x4334, 0x4514, 0x8525, 0x89AC, 0x88BC}, + {0x8367, 0x4334, 0x4514, 0x8525, 0x89AC, 0x88BC, 0x2301, 0x1503, 0x199B, 0x289A}, + {0x8525, 0x2412, 0x2301, 0x4334, 0x8367, 0x88BC, 0x89AC}, + {0x1503, 0x4334, 0x8367, 0x8525, 0x2412, 0x88BC, 0x89AC, 0x289A, 0x199B}, + {0x2412, 0x4514, 0x4334, 0x8367, 0x88BC, 0x289A}, + {0x1503, 0x2301, 0x2412, 0x4514, 0x4334, 0x8367, 0x88BC, 0x199B}, + {0x2301, 0x4334, 0x8367, 0x88BC, 0x289A}, + {0x8367, 0x4334, 0x1503, 0x199B, 0x88BC}, + {0x1636, 0x8367, 0x88BC, 0x199B, 0x4647, 0x4334, 0x4514, 0x4445}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A, 0x4334, 0x4514, 0x4445, 0x4647}, + {0x8367, 0x1636, 0x199B, 0x88BC, 0x2301, 0x4334, 0x4647, 0x4445, 0x2412}, + {0x2412, 0x4445, 0x4647, 0x4334, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A}, + {0x8525, 0x2412, 0x289A, 0x89AC, 0x1636, 0x8367, 0x88BC, 0x199B, 0x4514, 0x4445, 0x4647, 0x4334}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x1636, 0x8367, 0x88BC, 0x89AC, 0x4334, 0x4514, 0x4445, 0x4647}, + {0x8525, 0x4445, 0x4647, 0x4334, 0x2301, 0x289A, 0x89AC, 0x1636, 0x8367, 0x88BC, 0x199B}, + {0x8367, 0x1636, 0x1503, 0x4334, 0x4647, 0x4445, 0x8525, 0x89AC, 0x88BC}, + {0x1636, 0x8367, 0x88BC, 0x199B, 0x8658, 0x4647, 0x4334, 0x4514, 0x8525}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A, 0x8658, 0x4647, 0x4334, 0x4514, 0x8525}, + {0x1636, 0x8367, 0x88BC, 0x199B, 0x8658, 0x4647, 0x4334, 0x2301, 0x2412, 0x8525}, + {0x2412, 0x8525, 0x8658, 0x4647, 0x4334, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A}, + {0x2412, 0x4514, 0x4334, 0x4647, 0x8658, 0x89AC, 0x289A, 0x8367, 0x1636, 0x199B, 0x88BC}, + {0x8367, 0x1636, 0x1503, 0x2301, 0x2412, 0x4514, 0x4334, 0x4647, 0x8658, 0x89AC, 0x88BC}, + {0x8658, 0x4647, 0x4334, 0x2301, 0x289A, 0x89AC, 0x1636, 0x8367, 0x88BC, 0x199B}, + {0x8367, 0x1636, 0x1503, 0x4334, 0x4647, 0x8658, 0x89AC, 0x88BC}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B, 0x4647, 0x4334, 0x4514, 0x4445}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x8478, 0x8658, 0x89AC, 0x289A, 0x4647, 0x4334, 0x4514, 0x4445}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B, 0x2412, 0x4445, 0x4647, 0x4334, 0x2301}, + {0x8658, 0x8478, 0x8367, 0x1636, 0x1503, 0x4334, 0x4647, 0x4445, 0x2412, 0x289A, 0x89AC}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x8525, 0x2412, 0x289A, 0x199B, 0x4445, 0x4647, 0x4334, 0x4514}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x8478, 0x8658, 0x8525, 0x2412, 0x4334, 0x4514, 0x4445, 0x4647}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x8525, 0x4445, 0x4647, 0x4334, 0x2301, 0x289A, 0x199B}, + {0x1503, 0x1636, 0x8367, 0x8478, 0x8658, 0x8525, 0x4445, 0x4647, 0x4334}, + {0x8525, 0x4514, 0x4334, 0x4647, 0x8478, 0x8367, 0x1636, 0x199B, 0x89AC}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x8478, 0x4647, 0x4334, 0x4514, 0x8525, 0x89AC, 0x289A}, + {0x8525, 0x2412, 0x2301, 0x4334, 0x4647, 0x8478, 0x8367, 0x1636, 0x199B, 0x89AC}, + {0x2412, 0x8525, 0x89AC, 0x289A, 0x1503, 0x1636, 0x8367, 0x8478, 0x4647, 0x4334}, + {0x1636, 0x8367, 0x8478, 0x4647, 0x4334, 0x4514, 0x2412, 0x289A, 0x199B}, + {0x2412, 0x2301, 0x1503, 0x1636, 0x8367, 0x8478, 0x4647, 0x4334, 0x4514}, + {0x1636, 0x8367, 0x8478, 0x4647, 0x4334, 0x2301, 0x289A, 0x199B}, + {0x1636, 0x8367, 0x8478, 0x4647, 0x4334, 0x1503}, + {0x1636, 0x4334, 0x4514, 0x4445, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x4445, 0x4514, 0x4334, 0x1636, 0x1503, 0x2301, 0x289A, 0x88BC}, + {0x1636, 0x4334, 0x2301, 0x2412, 0x4445, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x4445, 0x2412, 0x289A, 0x88BC, 0x1636, 0x1503, 0x4334}, + {0x1636, 0x4334, 0x4514, 0x4445, 0x8478, 0x88BC, 0x199B, 0x8525, 0x2412, 0x289A, 0x89AC}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x1636, 0x4334, 0x4514, 0x4445, 0x8478, 0x88BC, 0x89AC}, + {0x1636, 0x4334, 0x2301, 0x8525, 0x4445, 0x8478, 0x289A, 0x89AC, 0x88BC, 0x199B}, + {0x8478, 0x4445, 0x8525, 0x89AC, 0x88BC, 0x1636, 0x1503, 0x4334}, + {0x1636, 0x4334, 0x4514, 0x8525, 0x8658, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x8658, 0x8525, 0x4514, 0x4334, 0x1636, 0x1503, 0x2301, 0x289A, 0x88BC}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x2301, 0x4334, 0x1636, 0x199B, 0x88BC}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC, 0x1636, 0x1503, 0x4334}, + {0x1636, 0x4334, 0x4514, 0x2412, 0x8658, 0x8478, 0x289A, 0x89AC, 0x88BC, 0x199B}, + {0x8658, 0x8478, 0x88BC, 0x89AC, 0x2412, 0x2301, 0x1503, 0x1636, 0x4334, 0x4514}, + {0x1636, 0x4334, 0x2301, 0x8658, 0x8478, 0x289A, 0x89AC, 0x88BC, 0x199B}, + {0x8658, 0x8478, 0x88BC, 0x89AC, 0x1503, 0x1636, 0x4334}, + {0x1636, 0x4334, 0x4514, 0x4445, 0x8658, 0x89AC, 0x199B}, + {0x2301, 0x1503, 0x1636, 0x4334, 0x4514, 0x4445, 0x8658, 0x89AC, 0x289A}, + {0x8658, 0x4445, 0x2412, 0x2301, 0x4334, 0x1636, 0x199B, 0x89AC}, + {0x2412, 0x4445, 0x8658, 0x89AC, 0x289A, 0x1503, 0x1636, 0x4334}, + {0x2412, 0x8525, 0x8658, 0x4445, 0x4514, 0x4334, 0x1636, 0x199B, 0x289A}, + {0x8658, 0x8525, 0x2412, 0x2301, 0x1503, 0x1636, 0x4334, 0x4514, 0x4445}, + {0x2301, 0x4334, 0x1636, 0x199B, 0x289A, 0x8525, 0x8658, 0x4445}, + {0x1636, 0x1503, 0x4334, 0x8525, 0x8658, 0x4445}, + {0x1636, 0x4334, 0x4514, 0x8525, 0x89AC, 0x199B}, + {0x2301, 0x1503, 0x1636, 0x4334, 0x4514, 0x8525, 0x89AC, 0x289A}, + {0x1636, 0x4334, 0x2301, 0x2412, 0x8525, 0x89AC, 0x199B}, + {0x8525, 0x2412, 0x289A, 0x89AC, 0x1636, 0x1503, 0x4334}, + {0x2412, 0x4514, 0x4334, 0x1636, 0x199B, 0x289A}, + {0x2301, 0x1503, 0x1636, 0x4334, 0x4514, 0x2412}, + {0x2301, 0x4334, 0x1636, 0x199B, 0x289A}, + {0x1636, 0x1503, 0x4334}, + {0x1503, 0x4514, 0x4445, 0x4647, 0x1636}, + {0x2301, 0x4514, 0x4445, 0x4647, 0x1636, 0x199B, 0x289A}, + {0x1503, 0x2301, 0x2412, 0x4445, 0x4647, 0x1636}, + {0x2412, 0x4445, 0x4647, 0x1636, 0x199B, 0x289A}, + {0x8525, 0x2412, 0x289A, 0x89AC, 0x1503, 0x4514, 0x4445, 0x4647, 0x1636}, + {0x1636, 0x4647, 0x4445, 0x4514, 0x2301, 0x2412, 0x8525, 0x89AC, 0x199B}, + {0x8525, 0x4445, 0x4647, 0x1636, 0x1503, 0x2301, 0x289A, 0x89AC}, + {0x8525, 0x4445, 0x4647, 0x1636, 0x199B, 0x89AC}, + {0x1503, 0x4514, 0x8525, 0x8658, 0x4647, 0x1636}, + {0x2301, 0x4514, 0x8525, 0x8658, 0x4647, 0x1636, 0x199B, 0x289A}, + {0x8658, 0x4647, 0x1636, 0x1503, 0x2301, 0x2412, 0x8525}, + {0x2412, 0x8525, 0x8658, 0x4647, 0x1636, 0x199B, 0x289A}, + {0x2412, 0x4514, 0x1503, 0x1636, 0x4647, 0x8658, 0x89AC, 0x289A}, + {0x1636, 0x4647, 0x8658, 0x89AC, 0x199B, 0x2301, 0x2412, 0x4514}, + {0x2301, 0x1503, 0x1636, 0x4647, 0x8658, 0x89AC, 0x289A}, + {0x1636, 0x4647, 0x8658, 0x89AC, 0x199B}, + {0x8658, 0x8478, 0x88BC, 0x89AC, 0x1636, 0x4647, 0x4445, 0x4514, 0x1503}, + {0x2301, 0x4514, 0x4445, 0x4647, 0x1636, 0x199B, 0x289A, 0x8478, 0x8658, 0x89AC, 0x88BC}, + {0x8478, 0x8658, 0x89AC, 0x88BC, 0x2412, 0x4445, 0x4647, 0x1636, 0x1503, 0x2301}, + {0x2412, 0x4445, 0x4647, 0x1636, 0x199B, 0x289A, 0x8478, 0x8658, 0x89AC, 0x88BC}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC, 0x1503, 0x4514, 0x4445, 0x4647, 0x1636}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x2301, 0x4514, 0x4445, 0x4647, 0x1636, 0x199B, 0x88BC}, + {0x2301, 0x1503, 0x1636, 0x4647, 0x4445, 0x8525, 0x8658, 0x8478, 0x88BC, 0x289A}, + {0x8478, 0x8658, 0x8525, 0x4445, 0x4647, 0x1636, 0x199B, 0x88BC}, + {0x8478, 0x4647, 0x1636, 0x1503, 0x4514, 0x8525, 0x89AC, 0x88BC}, + {0x2301, 0x4514, 0x8525, 0x8478, 0x4647, 0x1636, 0x89AC, 0x88BC, 0x199B, 0x289A}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x1636, 0x4647, 0x8478, 0x88BC, 0x89AC}, + {0x8478, 0x4647, 0x1636, 0x2412, 0x8525, 0x199B, 0x289A, 0x89AC, 0x88BC}, + {0x2412, 0x4514, 0x1503, 0x1636, 0x4647, 0x8478, 0x88BC, 0x289A}, + {0x1636, 0x4647, 0x8478, 0x88BC, 0x199B, 0x2301, 0x2412, 0x4514}, + {0x8478, 0x4647, 0x1636, 0x1503, 0x2301, 0x289A, 0x88BC}, + {0x1636, 0x4647, 0x8478, 0x88BC, 0x199B}, + {0x8367, 0x1636, 0x1503, 0x4514, 0x4445, 0x8478}, + {0x2301, 0x4514, 0x4445, 0x8478, 0x8367, 0x1636, 0x199B, 0x289A}, + {0x2412, 0x4445, 0x8478, 0x8367, 0x1636, 0x1503, 0x2301}, + {0x1636, 0x8367, 0x8478, 0x4445, 0x2412, 0x289A, 0x199B}, + {0x8525, 0x2412, 0x289A, 0x89AC, 0x1503, 0x4514, 0x4445, 0x8478, 0x8367, 0x1636}, + {0x1636, 0x8367, 0x8478, 0x4445, 0x4514, 0x2301, 0x2412, 0x8525, 0x89AC, 0x199B}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x8478, 0x4445, 0x8525, 0x89AC, 0x289A}, + {0x8525, 0x4445, 0x8478, 0x8367, 0x1636, 0x199B, 0x89AC}, + {0x1503, 0x4514, 0x8525, 0x8658, 0x8478, 0x8367, 0x1636}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x8525, 0x4514, 0x2301, 0x289A, 0x199B}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x8525, 0x2412, 0x2301, 0x1503}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x8525, 0x2412, 0x289A, 0x199B}, + {0x8658, 0x8478, 0x8367, 0x1636, 0x1503, 0x4514, 0x2412, 0x289A, 0x89AC}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B, 0x2301, 0x2412, 0x4514}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x8478, 0x8658, 0x89AC, 0x289A}, + {0x1636, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B}, + {0x8658, 0x4445, 0x4514, 0x1503, 0x1636, 0x8367, 0x88BC, 0x89AC}, + {0x2301, 0x4514, 0x4445, 0x8658, 0x8367, 0x1636, 0x89AC, 0x88BC, 0x199B, 0x289A}, + {0x8367, 0x1636, 0x1503, 0x2301, 0x2412, 0x4445, 0x8658, 0x89AC, 0x88BC}, + {0x8658, 0x4445, 0x2412, 0x1636, 0x8367, 0x289A, 0x199B, 0x88BC, 0x89AC}, + {0x8367, 0x1636, 0x1503, 0x4514, 0x4445, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC}, + {0x8367, 0x1636, 0x199B, 0x88BC, 0x8658, 0x8525, 0x2412, 0x2301, 0x4514, 0x4445}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A, 0x8525, 0x8658, 0x4445}, + {0x1636, 0x8367, 0x88BC, 0x199B, 0x8525, 0x8658, 0x4445}, + {0x8367, 0x1636, 0x1503, 0x4514, 0x8525, 0x89AC, 0x88BC}, + {0x2301, 0x4514, 0x8525, 0x8367, 0x1636, 0x89AC, 0x88BC, 0x199B, 0x289A}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x1636, 0x8367, 0x88BC, 0x89AC}, + {0x2412, 0x8525, 0x8367, 0x1636, 0x89AC, 0x88BC, 0x199B, 0x289A}, + {0x2412, 0x4514, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A}, + {0x8367, 0x1636, 0x199B, 0x88BC, 0x2412, 0x2301, 0x4514}, + {0x2301, 0x1503, 0x1636, 0x8367, 0x88BC, 0x289A}, + {0x1636, 0x8367, 0x88BC, 0x199B}, + {0x8367, 0x4647, 0x4445, 0x4514, 0x1503, 0x199B, 0x88BC}, + {0x2301, 0x4514, 0x4445, 0x4647, 0x8367, 0x88BC, 0x289A}, + {0x8367, 0x4647, 0x4445, 0x2412, 0x2301, 0x1503, 0x199B, 0x88BC}, + {0x8367, 0x4647, 0x4445, 0x2412, 0x289A, 0x88BC}, + {0x8367, 0x4647, 0x4445, 0x4514, 0x1503, 0x199B, 0x88BC, 0x2412, 0x8525, 0x89AC, 0x289A}, + {0x8525, 0x2412, 0x2301, 0x4514, 0x4445, 0x4647, 0x8367, 0x88BC, 0x89AC}, + {0x8525, 0x4445, 0x4647, 0x8367, 0x1503, 0x2301, 0x88BC, 0x199B, 0x289A, 0x89AC}, + {0x8367, 0x4647, 0x4445, 0x8525, 0x89AC, 0x88BC}, + {0x8367, 0x4647, 0x8658, 0x8525, 0x4514, 0x1503, 0x199B, 0x88BC}, + {0x8367, 0x4647, 0x8658, 0x8525, 0x4514, 0x2301, 0x289A, 0x88BC}, + {0x1503, 0x2301, 0x2412, 0x8525, 0x8658, 0x4647, 0x8367, 0x88BC, 0x199B}, + {0x8367, 0x4647, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC}, + {0x2412, 0x4514, 0x1503, 0x8367, 0x4647, 0x8658, 0x199B, 0x88BC, 0x89AC, 0x289A}, + {0x8658, 0x4647, 0x8367, 0x88BC, 0x89AC, 0x2412, 0x2301, 0x4514}, + {0x8367, 0x4647, 0x8658, 0x2301, 0x1503, 0x89AC, 0x289A, 0x199B, 0x88BC}, + {0x8658, 0x4647, 0x8367, 0x88BC, 0x89AC}, + {0x1503, 0x4514, 0x4445, 0x4647, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B}, + {0x8658, 0x8478, 0x8367, 0x4647, 0x4445, 0x4514, 0x2301, 0x289A, 0x89AC}, + {0x1503, 0x2301, 0x2412, 0x4445, 0x4647, 0x8367, 0x8478, 0x8658, 0x89AC, 0x199B}, + {0x8658, 0x8478, 0x8367, 0x4647, 0x4445, 0x2412, 0x289A, 0x89AC}, + {0x2412, 0x8525, 0x8658, 0x8478, 0x8367, 0x4647, 0x4445, 0x4514, 0x1503, 0x199B, 0x289A}, + {0x8367, 0x8478, 0x8658, 0x8525, 0x2412, 0x2301, 0x4514, 0x4445, 0x4647}, + {0x1503, 0x2301, 0x289A, 0x199B, 0x8367, 0x8478, 0x8658, 0x8525, 0x4445, 0x4647}, + {0x8478, 0x8658, 0x8525, 0x4445, 0x4647, 0x8367}, + {0x8525, 0x4514, 0x1503, 0x199B, 0x89AC, 0x8478, 0x8367, 0x4647}, + {0x8525, 0x4514, 0x2301, 0x289A, 0x89AC, 0x8478, 0x8367, 0x4647}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC, 0x8478, 0x8367, 0x4647}, + {0x2412, 0x8525, 0x89AC, 0x289A, 0x8367, 0x8478, 0x4647}, + {0x1503, 0x4514, 0x2412, 0x289A, 0x199B, 0x8367, 0x8478, 0x4647}, + {0x2301, 0x2412, 0x4514, 0x8478, 0x8367, 0x4647}, + {0x2301, 0x1503, 0x199B, 0x289A, 0x8478, 0x8367, 0x4647}, + {0x8478, 0x8367, 0x4647}, + {0x1503, 0x4514, 0x4445, 0x8478, 0x88BC, 0x199B}, + {0x2301, 0x4514, 0x4445, 0x8478, 0x88BC, 0x289A}, + {0x1503, 0x2301, 0x2412, 0x4445, 0x8478, 0x88BC, 0x199B}, + {0x8478, 0x4445, 0x2412, 0x289A, 0x88BC}, + {0x1503, 0x4514, 0x4445, 0x8478, 0x88BC, 0x199B, 0x8525, 0x2412, 0x289A, 0x89AC}, + {0x8525, 0x2412, 0x2301, 0x4514, 0x4445, 0x8478, 0x88BC, 0x89AC}, + {0x8525, 0x4445, 0x8478, 0x1503, 0x2301, 0x88BC, 0x199B, 0x289A, 0x89AC}, + {0x8478, 0x4445, 0x8525, 0x89AC, 0x88BC}, + {0x8478, 0x8658, 0x8525, 0x4514, 0x1503, 0x199B, 0x88BC}, + {0x2301, 0x4514, 0x8525, 0x8658, 0x8478, 0x88BC, 0x289A}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x2301, 0x1503, 0x199B, 0x88BC}, + {0x8478, 0x8658, 0x8525, 0x2412, 0x289A, 0x88BC}, + {0x2412, 0x4514, 0x1503, 0x8478, 0x8658, 0x199B, 0x88BC, 0x89AC, 0x289A}, + {0x8478, 0x8658, 0x89AC, 0x88BC, 0x2301, 0x2412, 0x4514}, + {0x1503, 0x2301, 0x8658, 0x8478, 0x289A, 0x89AC, 0x88BC, 0x199B}, + {0x8478, 0x8658, 0x89AC, 0x88BC}, + {0x8658, 0x4445, 0x4514, 0x1503, 0x199B, 0x89AC}, + {0x8658, 0x4445, 0x4514, 0x2301, 0x289A, 0x89AC}, + {0x8658, 0x4445, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC}, + {0x2412, 0x4445, 0x8658, 0x89AC, 0x289A}, + {0x2412, 0x8525, 0x8658, 0x4445, 0x4514, 0x1503, 0x199B, 0x289A}, + {0x8525, 0x2412, 0x2301, 0x4514, 0x4445, 0x8658}, + {0x1503, 0x2301, 0x289A, 0x199B, 0x8658, 0x8525, 0x4445}, + {0x8525, 0x8658, 0x4445}, + {0x8525, 0x4514, 0x1503, 0x199B, 0x89AC}, + {0x8525, 0x4514, 0x2301, 0x289A, 0x89AC}, + {0x8525, 0x2412, 0x2301, 0x1503, 0x199B, 0x89AC}, + {0x8525, 0x2412, 0x289A, 0x89AC}, + {0x1503, 0x4514, 0x2412, 0x289A, 0x199B}, + {0x2301, 0x2412, 0x4514}, + {0x2301, 0x1503, 0x199B, 0x289A}, + {} + }; + + + /** + * The RegularCellData structure holds information about the triangulation + * used for a single equivalence class in the modified Marching Cubes algorithm, + * described in Section 3.2. + */ + static class RegularCellData { + char geometryCounts; // High nibble is vertex count, low nibble is triangle count. + char[] vertexIndex; // Groups of 3 indexes giving the triangulation. + + long getVertexCount(){ + return (geometryCounts >> 4); + } + + long getTriangleCount(){ + return (geometryCounts & 0x0F); + } + + public RegularCellData(char geometryCounts, char[] vertexIndex){ + this.geometryCounts = geometryCounts; + this.vertexIndex = vertexIndex; + } + + }; + + /** + * The TransitionCellData structure holds information about the triangulation + * used for a single equivalence class in the Transvoxel Algorithm transition cell, + * described in Section 4.3. + */ + static class TransitionCellData { + long geometryCounts; // High nibble is vertex count, low nibble is triangle count. + char[] vertexIndex; // Groups of 3 indexes giving the triangulation. + + long getVertexCount(){ + return (geometryCounts >> 4); + } + + long getTriangleCount(){ + return (geometryCounts & 0x0F); + } + + public TransitionCellData(char geometryCounts, char[] vertexIndex){ + this.geometryCounts = geometryCounts; + this.vertexIndex = vertexIndex; + } + }; } diff --git a/src/main/java/electrosphere/server/content/ServerContentManager.java b/src/main/java/electrosphere/server/content/ServerContentManager.java index 1fc2e05d..6a1f3847 100644 --- a/src/main/java/electrosphere/server/content/ServerContentManager.java +++ b/src/main/java/electrosphere/server/content/ServerContentManager.java @@ -7,14 +7,12 @@ import org.joml.Vector3i; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityUtils; -import electrosphere.entity.ServerEntityUtils; import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.entity.types.item.ItemUtils; import electrosphere.server.content.serialization.ContentSerialization; import electrosphere.server.content.serialization.EntitySerialization; import electrosphere.server.datacell.Realm; import electrosphere.server.datacell.ServerDataCell; -import electrosphere.server.pathfinding.NavMeshUtils; import electrosphere.server.saves.SaveUtils; import electrosphere.util.FileUtils; @@ -54,7 +52,8 @@ public class ServerContentManager { hydrateRawContent(realm,cell,contentRaw); } else { //else create from scratch - EnvironmentGenerator.generateForest(realm, cell, worldPos, 0); + //UNCOMMENT THIS WHEN YOU WANT CONTENT GENERATED FOR WORLDS AGAIN + // EnvironmentGenerator.generateForest(realm, cell, worldPos, 0); } } else { //just because content wasn't generated doesn't mean there isn't data saved under that key