package electrosphere.renderer.meshgen; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.joml.Vector2f; import org.joml.Vector3f; import org.joml.Vector3i; import org.lwjgl.BufferUtils; import electrosphere.client.block.BlockChunkData; import electrosphere.engine.Globals; import electrosphere.entity.state.collidable.MultiShapeTriGeomData; import electrosphere.entity.state.collidable.TriGeomData; import electrosphere.renderer.OpenGLState; import electrosphere.renderer.model.Material; import electrosphere.renderer.model.Mesh; import electrosphere.renderer.model.Model; /** * Generates a model for a block */ public class BlockMeshgen { /** * The indices to draw faces on cubes */ static final int[] CUBE_INDICES = new int[]{ //Top 2, 6, 7, 2, 3, 7, //Bottom 0, 4, 5, 0, 1, 5, //Left 0, 2, 6, 0, 4, 6, //Right 1, 3, 7, 1, 5, 7, //Front 0, 2, 3, 0, 1, 3, //Back 4, 6, 7, 4, 5, 7 }; /** * Position of the sampler data in the buffer */ static final int SAMPLER_SHADER_POSITION = 5; /** * The size of the sampler data per-vertex */ static final int SAMPLER_DATA_SIZE = 1; /** * Checks whether this block should be rasterized or not * @param data The data * @param x The x coordinate * @param y The y coordinate * @param z The z coordinate * @param solids true to rasterize solids, false to rasterize transparents * @param solidsMap The map of block type to solid status * @return true if it should be rasterized, false otherwise */ protected static boolean shouldRasterize(BlockMeshgenData data, int x, int y, int z, boolean solids, Map solidsMap){ if(data.isEmpty(x, y, z)){ return false; } if(solidsMap == null){ return true; } return solids == solidsMap.get((int)data.getType(x, y, z)); } /** * Calculates the quad meshes for the provided data * @param quadMeshes The quad mesh list to fill * @param data The block data */ protected static void fillQuadMeshes(List quadMeshes, BlockMeshgenData data){ BlockMeshgen.fillQuadMeshes(quadMeshes, data, true, null); } /** * Calculates the quad meshes for the provided data * @param quadMeshes The quad mesh list to fill * @param data The block data */ protected static void fillQuadMeshes(List quadMeshes, BlockMeshgenData data, boolean solids, Map solidsMap){ Vector3i dimensions = data.getDimensions(); for(int z = 0; z < dimensions.z; z++){ for(int x = 0; x < dimensions.x; x++){ QuadMesh currentQuad = null; for(int y = 0; y < dimensions.y; y++){ if(!BlockMeshgen.shouldRasterize(data,x,y,z,solids,solidsMap)){ if(currentQuad == null){ continue; } else { currentQuad.h = y - currentQuad.y; //check if should merge with previous quad for(QuadMesh prevMesh : quadMeshes){ if(prevMesh.x + prevMesh.w == currentQuad.x && prevMesh.y == currentQuad.y && prevMesh.h == currentQuad.h && prevMesh.z == currentQuad.z){ prevMesh.w = prevMesh.w + 1; currentQuad = null; break; } } if(currentQuad != null){ quadMeshes.add(currentQuad); } currentQuad = null; } } else { if(currentQuad == null){ currentQuad = new QuadMesh(); currentQuad.x = x; currentQuad.y = y; currentQuad.z = z; currentQuad.w = 1; currentQuad.h = 1; currentQuad.type = data.getType(x, y, z); } else if(currentQuad.type == data.getType(x, y, z)) { continue; } else { currentQuad.h = y - currentQuad.y; //check if should merge with previous quad for(QuadMesh prevMesh : quadMeshes){ if(prevMesh.x + prevMesh.w == currentQuad.x && prevMesh.y == currentQuad.y && prevMesh.h == currentQuad.h && prevMesh.z == currentQuad.z){ prevMesh.w = prevMesh.w + 1; currentQuad = null; break; } } if(currentQuad != null){ quadMeshes.add(currentQuad); } currentQuad = new QuadMesh(); currentQuad.x = x; currentQuad.y = y; currentQuad.z = z; currentQuad.w = 1; currentQuad.h = 1; currentQuad.type = data.getType(x, y, z); } } } if(currentQuad != null){ currentQuad.h = dimensions.y - currentQuad.y; //check if should merge with previous quad for(QuadMesh prevMesh : quadMeshes){ if(prevMesh.x + prevMesh.w == currentQuad.x && prevMesh.y == currentQuad.y && prevMesh.h == currentQuad.h && prevMesh.z == currentQuad.z){ prevMesh.w = prevMesh.w + 1; currentQuad = null; break; } } if(currentQuad != null){ quadMeshes.add(currentQuad); } } } } } /** * Meshes a box * @param verts The list of verts to store into * @param normals The list of normals to store into * @param uvs The list of uvs to store into * @param indices The list of indices to store into * @param samplers The sampler for a given vertex * @param quad The quad * @param depth The depth of the box * @param blockType The type of block * @param indexOffset The offset for the indices that will be added (ie, if there are already vertices in the verts list, pass in the size of the vert list) */ protected static void meshifyBox(List verts, List normals, List uvs, List indices, List samplers, QuadMesh quad, int depth, int blockType, int indexOffset){ // //face 1 // int samplerIndex = Globals.blockTextureAtlas.getVoxelTypeOffset(blockType); //verts verts.add(new Vector3f(quad.x, quad.y, quad.z).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y, quad.z).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x, quad.y + quad.h, quad.z).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y + quad.h, quad.z).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); //indices indices.add(indexOffset + 0); indices.add(indexOffset + 2); indices.add(indexOffset + 3); indices.add(indexOffset + 0); indices.add(indexOffset + 3); indices.add(indexOffset + 1); //normals normals.add(new Vector3f(0,0,-1)); normals.add(new Vector3f(0,0,-1)); normals.add(new Vector3f(0,0,-1)); normals.add(new Vector3f(0,0,-1)); //uvs uvs.add(new Vector2f( 0, 0)); uvs.add(new Vector2f(quad.w, 0)); uvs.add(new Vector2f( 0, quad.h)); uvs.add(new Vector2f(quad.w, quad.h)); //samplers samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); // //face 2 // //verts verts.add(new Vector3f(quad.x, quad.y, quad.z ).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x, quad.y, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x, quad.y + quad.h, quad.z ).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x, quad.y + quad.h, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); //indices indices.add(indexOffset + 4); indices.add(indexOffset + 7); indices.add(indexOffset + 6); indices.add(indexOffset + 4); indices.add(indexOffset + 5); indices.add(indexOffset + 7); //normals normals.add(new Vector3f(-1,0,0)); normals.add(new Vector3f(-1,0,0)); normals.add(new Vector3f(-1,0,0)); normals.add(new Vector3f(-1,0,0)); //uvs uvs.add(new Vector2f( 0, 0)); uvs.add(new Vector2f(depth, 0)); uvs.add(new Vector2f( 0, quad.h)); uvs.add(new Vector2f(depth, quad.h)); //samplers samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); // //face 3 // //verts verts.add(new Vector3f(quad.x, quad.y, quad.z ).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x, quad.y, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y, quad.z ).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); //indices indices.add(indexOffset + 8); indices.add(indexOffset + 10); indices.add(indexOffset + 11); indices.add(indexOffset + 9); indices.add(indexOffset + 8); indices.add(indexOffset + 11); //normals normals.add(new Vector3f(0,-1,0)); normals.add(new Vector3f(0,-1,0)); normals.add(new Vector3f(0,-1,0)); normals.add(new Vector3f(0,-1,0)); //uvs uvs.add(new Vector2f( 0, 0)); uvs.add(new Vector2f(depth, 0)); uvs.add(new Vector2f( 0, quad.w)); uvs.add(new Vector2f(depth, quad.w)); //samplers samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); // //face 4 // //verts verts.add(new Vector3f(quad.x, quad.y, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x, quad.y + quad.h, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y + quad.h, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); //indices indices.add(indexOffset + 12); indices.add(indexOffset + 15); indices.add(indexOffset + 14); indices.add(indexOffset + 12); indices.add(indexOffset + 13); indices.add(indexOffset + 15); //normals normals.add(new Vector3f(0,0,1)); normals.add(new Vector3f(0,0,1)); normals.add(new Vector3f(0,0,1)); normals.add(new Vector3f(0,0,1)); //uvs uvs.add(new Vector2f( 0, 0)); uvs.add(new Vector2f(quad.w, 0)); uvs.add(new Vector2f( 0, quad.h)); uvs.add(new Vector2f(quad.w, quad.h)); //samplers samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); // //face 5 // //verts verts.add(new Vector3f(quad.x + quad.w, quad.y, quad.z ).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y + quad.h, quad.z ).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y + quad.h, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); //indices indices.add(indexOffset + 16); indices.add(indexOffset + 18); indices.add(indexOffset + 19); indices.add(indexOffset + 16); indices.add(indexOffset + 19); indices.add(indexOffset + 17); //normals normals.add(new Vector3f(1,0,0)); normals.add(new Vector3f(1,0,0)); normals.add(new Vector3f(1,0,0)); normals.add(new Vector3f(1,0,0)); //uvs uvs.add(new Vector2f( 0, 0)); uvs.add(new Vector2f(depth, 0)); uvs.add(new Vector2f( 0, quad.h)); uvs.add(new Vector2f(depth, quad.h)); //samplers samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); // //face 6 // //verts verts.add(new Vector3f(quad.x, quad.y + quad.h, quad.z ).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x, quad.y + quad.h, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y + quad.h, quad.z ).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); verts.add(new Vector3f(quad.x + quad.w, quad.y + quad.h, quad.z + depth).mul(BlockChunkData.BLOCK_SIZE_MULTIPLIER)); //indicesindexOffset + indices.add(indexOffset + 20); indices.add(indexOffset + 23); indices.add(indexOffset + 22); indices.add(indexOffset + 20); indices.add(indexOffset + 21); indices.add(indexOffset + 23); //normals normals.add(new Vector3f(0,1,0)); normals.add(new Vector3f(0,1,0)); normals.add(new Vector3f(0,1,0)); normals.add(new Vector3f(0,1,0)); //uvs uvs.add(new Vector2f( 0, 0)); uvs.add(new Vector2f(depth, 0)); uvs.add(new Vector2f( 0, quad.w)); uvs.add(new Vector2f(depth, quad.w)); //samplers samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); samplers.add(samplerIndex); } /** * Rasterizes a block chunk data into mesh data * @param chunkData The block chunk data * @param solids true to rasterize solid blocks, false to rasterize transparent blocks * @param solidsMap The solids map * @return The mesh data */ public static BlockMeshData rasterize(BlockMeshgenData chunkData, boolean solids, Map solidsMap){ BlockMeshData rVal = new BlockMeshData(); //calculate quad meshes List quadMeshes = new LinkedList(); BlockMeshgen.fillQuadMeshes(quadMeshes, chunkData, solids, solidsMap); //allocate lists to store mesh data in List verts = new LinkedList(); List normals = new LinkedList(); List uvs = new LinkedList(); List indices = new LinkedList(); List samplers = new LinkedList(); //the texture to sample for this quad //sort Collections.sort(quadMeshes); int lastVertexCount = 0; int lastFaceCount = 0; //generate volumes QuadMesh quad1 = null; QuadMesh quad2 = null; int zEnd = 0; for(int i = 0; i < quadMeshes.size();){ quad1 = quadMeshes.get(i); zEnd = 1; for(int j = i + 1; j < quadMeshes.size(); j++){ quad2 = quadMeshes.get(j); if(quad1.x == quad2.x && quad1.y == quad2.y && quad1.w == quad2.w && quad1.h == quad2.h && quad1.z + zEnd == quad2.z){ zEnd++; } else { BlockMeshgen.meshifyBox(verts,normals,uvs,indices,samplers,quad1,zEnd,quad1.type,verts.size()); quad1 = quad2; BlockSingleShape blockSingleShape = BlockMeshgen.copyDataToShape(verts,indices,lastVertexCount,lastFaceCount); lastVertexCount = verts.size(); lastFaceCount = indices.size(); rVal.shapeData.add(blockSingleShape); break; } } i = i + zEnd; } if(quad1 != null){ BlockMeshgen.meshifyBox(verts,normals,uvs,indices,samplers,quad1,zEnd,quad1.type,verts.size()); BlockSingleShape blockSingleShape = BlockMeshgen.copyDataToShape(verts,indices,lastVertexCount,lastFaceCount); lastVertexCount = verts.size(); lastFaceCount = indices.size(); rVal.shapeData.add(blockSingleShape); } // //store in flat arrays // //verts rVal.vertices = new float[verts.size() * 3]; for(int i = 0; i < verts.size(); i++){ Vector3f currentVert = verts.get(i); rVal.vertices[3 * i + 0] = currentVert.x; rVal.vertices[3 * i + 1] = currentVert.y; rVal.vertices[3 * i + 2] = currentVert.z; } rVal.vertBuffer = BufferUtils.createFloatBuffer(rVal.vertices.length); rVal.vertBuffer.put(rVal.vertices); //faces rVal.faceElements = new int[indices.size()]; for(int i = 0; i < indices.size(); i++){ rVal.faceElements[i] = indices.get(i); } rVal.faceBuffer = BufferUtils.createIntBuffer(rVal.faceElements.length); rVal.faceBuffer.put(rVal.faceElements); //normals rVal.normals = new float[normals.size() * 3]; for(int i = 0; i < normals.size(); i++){ Vector3f currentNormal = normals.get(i); rVal.normals[3 * i + 0] = currentNormal.x; rVal.normals[3 * i + 1] = currentNormal.y; rVal.normals[3 * i + 2] = currentNormal.z; } rVal.normalBuffer = BufferUtils.createFloatBuffer(rVal.normals.length); rVal.normalBuffer.put(rVal.normals); //uvs rVal.uvs = new float[uvs.size() * 2]; for(int i = 0; i < uvs.size(); i++){ Vector2f currentUV = uvs.get(i); rVal.uvs[2 * i + 0] = currentUV.x; rVal.uvs[2 * i + 1] = currentUV.y; } rVal.uvBuffer = BufferUtils.createFloatBuffer(rVal.uvs.length); rVal.uvBuffer.put(rVal.uvs); //samplers rVal.samplers = new int[samplers.size()]; for(int i = 0; i < samplers.size(); i++){ rVal.samplers[i] = samplers.get(i); } rVal.samplerBuffer = BufferUtils.createIntBuffer(rVal.samplers.length); rVal.samplerBuffer.put(rVal.samplers); return rVal; } /** * Rasterizes a block chunk data into mesh data * @param chunkData The block chunk data * @return The mesh data */ public static BlockMeshData rasterize(BlockMeshgenData chunkData){ return BlockMeshgen.rasterize(chunkData, true, null); } /** * Copies vertex and index data from the combined array into a single shape * @param verts The list of vertices * @param indices The list of indices * @param lastVertCount The last vertex position that was copied * @param lastFaceCount The last index position that was copied * @return The data containing a single shape's worth of geometry data */ private static BlockSingleShape copyDataToShape(List verts, List indices, int lastVertCount, int lastFaceCount){ BlockSingleShape blockSingleShape = new BlockSingleShape((verts.size() - lastVertCount),(indices.size() - lastFaceCount)); for(int i = lastVertCount; i < verts.size(); i++){ Vector3f vert = verts.get(i); blockSingleShape.vertices[(i - lastVertCount) * 3 + 0] = vert.x; blockSingleShape.vertices[(i - lastVertCount) * 3 + 1] = vert.y; blockSingleShape.vertices[(i - lastVertCount) * 3 + 2] = vert.z; } for(int i = lastFaceCount; i < indices.size(); i++){ blockSingleShape.faceElements[i - lastFaceCount] = indices.get(i) - lastVertCount; } return blockSingleShape; } /** * Generates a mesh based on a block mesh data object * @param data The block mesh data object * @return The mesh */ protected static Mesh generateBlockMesh(OpenGLState openGLState, BlockMeshData meshData){ Mesh mesh = new Mesh("blockChunk"); // // VAO // mesh.generateVAO(openGLState); FloatBuffer vertexArrayBufferData = meshData.vertBuffer; FloatBuffer normalArrayBufferData = meshData.normalBuffer; FloatBuffer textureArrayBufferData = meshData.uvBuffer; IntBuffer elementArrayBufferData = meshData.faceBuffer; IntBuffer samplerArrayBufferData = meshData.samplerBuffer; // // Buffer data to GPU // int elementCount = meshData.faceElements.length; if(elementCount < 1){ throw new Error("Invalid mesh data!"); } try { //actually buffer vertices if(vertexArrayBufferData.position() > 0){ vertexArrayBufferData.flip(); mesh.bufferVertices(vertexArrayBufferData, 3); } //actually buffer normals if(normalArrayBufferData != null && normalArrayBufferData.position() > 0){ normalArrayBufferData.flip(); mesh.bufferNormals(normalArrayBufferData, 3); } //actually buffer UVs if(textureArrayBufferData != null && textureArrayBufferData.position() > 0){ textureArrayBufferData.flip(); mesh.bufferTextureCoords(textureArrayBufferData, 2); } //buffer element indices if(elementArrayBufferData.position() > 0){ elementArrayBufferData.flip(); mesh.bufferFaces(elementArrayBufferData, elementCount); } //buffer sampler indices if(samplerArrayBufferData != null && samplerArrayBufferData.position() > 0){ samplerArrayBufferData.flip(); mesh.bufferCustomIntAttribArray(samplerArrayBufferData, SAMPLER_DATA_SIZE, SAMPLER_SHADER_POSITION); } } catch (NullPointerException ex){ ex.printStackTrace(); } //bounding sphere logic int distance = BlockChunkData.CHUNK_DATA_WIDTH / 2; mesh.updateBoundingSphere( distance, distance, distance, (float)Math.sqrt( distance * distance + distance * distance + distance * distance )); openGLState.glBindVertexArray(0); return mesh; } /** * Generates the model for the block mesh * @param chunkData The mesh data * @return The model object */ public static Model generateBlockModel(BlockMeshData meshData){ Model rVal = new Model(); Mesh m = BlockMeshgen.generateBlockMesh(Globals.renderingEngine.getOpenGLState(), meshData); //construct the material for the chunk Material groundMat = Material.create(Globals.blockTextureAtlas.getSpecular(), Globals.blockTextureAtlas.getNormal()); m.setMaterial(groundMat); //shader logic m.setShader(Globals.blockShader); m.setParent(rVal); rVal.getMeshes().add(m); rVal.setBoundingSphere(m.getBoundingSphere()); return rVal; } /** * Contains the geom data for a single shape in the block mesh */ public static class BlockSingleShape implements TriGeomData { /** * The verts */ float[] vertices; /** * The faces */ int[] faceElements; /** * Constructor * @param vertCount The number of verts * @param faceCount The number of faces */ public BlockSingleShape(int vertCount, int faceCount){ vertices = new float[vertCount * 3]; faceElements = new int[faceCount]; } @Override public float[] getVertices() { return vertices; } @Override public int[] getFaceElements() { return faceElements; } } /** * The final rasterization data that is emitted */ public static class BlockMeshData implements TriGeomData, MultiShapeTriGeomData { /** * Vertex data in array form */ float[] vertices; /** * Normal data in array form */ float[] normals; /** * Face data in array form */ int[] faceElements; /** * UV data in array form */ float[] uvs; /** * Sampler data in array form */ int[] samplers; /** * Data broken out by each shape */ List shapeData = new LinkedList(); /** * Buffer of vertex data */ FloatBuffer vertBuffer; /** * Buffer of normal data */ FloatBuffer normalBuffer; /** * Buffer of face data */ IntBuffer faceBuffer; /** * Buffer of UV data */ FloatBuffer uvBuffer; /** * Buffer of sampler data */ IntBuffer samplerBuffer; @Override public float[] getVertices() { return vertices; } @Override public int[] getFaceElements() { return faceElements; } @Override public Collection getData() { return shapeData; } } /** * Intermediary structure used during rasterization */ public static class QuadMesh implements Comparable { int x; int y; int z; int w; int h; int type; public QuadMesh(){} public QuadMesh(int x, int y, int z, int w, int h, int type){ this.x = x; this.y = y; this.z = z; this.w = w; this.h = h; this.type = type; } @Override public int compareTo(QuadMesh other) { if(this.y != other.y){ return this.y - other.y; } if(this.x != other.x){ return this.x - other.x; } if(this.w != other.w){ return other.w - this.w; } return other.h - this.h; } } }