Renderer/src/main/java/electrosphere/renderer/meshgen/BlockMeshgen.java
austin 2d26a8e73a
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
fab tool work
2025-04-26 12:24:23 -04:00

716 lines
24 KiB
Java

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 org.joml.Vector2f;
import org.joml.Vector3f;
import org.joml.Vector3i;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL40;
import electrosphere.client.block.BlockChunkData;
import electrosphere.engine.Globals;
import electrosphere.entity.state.collidable.MultiShapeTriGeomData;
import electrosphere.entity.state.collidable.TriGeomData;
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;
/**
* 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<QuadMesh> quadMeshes, BlockMeshgenData data){
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(data.isEmpty(x, y, z)){
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 {
continue;
}
}
}
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<Vector3f> verts, List<Vector3f> normals, List<Vector2f> uvs, List<Integer> indices, List<Integer> 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
* @return The mesh data
*/
public static BlockMeshData rasterize(BlockMeshgenData chunkData){
BlockMeshData rVal = new BlockMeshData();
//calculate quad meshes
List<QuadMesh> quadMeshes = new LinkedList<QuadMesh>();
BlockMeshgen.fillQuadMeshes(quadMeshes, chunkData);
//allocate lists to store mesh data in
List<Vector3f> verts = new LinkedList<Vector3f>();
List<Vector3f> normals = new LinkedList<Vector3f>();
List<Vector2f> uvs = new LinkedList<Vector2f>();
List<Integer> indices = new LinkedList<Integer>();
List<Integer> samplers = new LinkedList<Integer>(); //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;
}
/**
* 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<Vector3f> verts, List<Integer> 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(BlockMeshData meshData){
Mesh mesh = new Mesh("blockChunk");
//
// VAO
//
mesh.generateVAO();
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;
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
));
GL40.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(meshData);
//construct the material for the chunk
Material groundMat = new Material();
groundMat.setTexturePointer(Globals.blockTextureAtlas.getSpecular().getTexturePointer());
groundMat.setNormalTexturePointer(Globals.blockTextureAtlas.getNormal().getTexturePointer());
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<TriGeomData> shapeData = new LinkedList<TriGeomData>();
/**
* 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<TriGeomData> getData() {
return shapeData;
}
}
/**
* Intermediary structure used during rasterization
*/
public static class QuadMesh implements Comparable<QuadMesh> {
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;
}
}
}