Voxel lag due to attached geometry

Also wanted to mention, GeometryBatchFactory only batches objects with the same material. So, if you are using texture atlasing… all is good. Lots of quads with tons of different textures… not so good.

I assume your voxel world will be “editable”, ya? You probably want to look into managing a custom Mesh(es) yourself instead of using GeometryBatchFactory.

I have no physics or controls. I think the reason it lags is because I am making the quads in a terrible way. Here is the code for making a quad…

[java]
package mygame.assets.voxelengine;

import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils;

public class EasyVoxelSide {

int tiles_per_row;

public EasyVoxelSide(int facing,Texture tex,int tiles_per_row, Material mat,Vector3f loc,int tex_id,ColorRGBA color){
	this.tex_id = tex_id;
	this.tex = tex;
	this.tiles_per_row=tiles_per_row;
	this.mat = mat;
	this.loc = loc;
	this.facing = facing;
	
	init();
	
}

Mesh m;
Vector3f [] vertices = new Vector3f[4];
Vector2f [] texCoord;
int [] indexes = {2,0,1,1,3,2};// Indexes. We define the order in which mesh should be constructed

public int tex_id = 240;	 


Texture tex;
Material mat;
void init(){
	
	   m = new Mesh();

	   geom = new Geometry("OurMesh", m);
     
       geom.setMaterial(mat);
                       
        
        updateVertices();
        UpdateTexCoords();
		refresh();
	    
}

public Geometry geom;



float CUBE_SIZE = 3;
//float PAD_HEIGHT = 0.25f;

Vector3f loc = new Vector3f(); 


public int facing = 0;




public void updateVertices() {
	
	for(int i=0;i<4;i++){
		vertices[i] = loc;
	}
	
	
	if(facing == Sides.RIGHT.id){//THIS IS PERFECT
	vertices[0] = vertices[0].add(0,0,CUBE_SIZE);
	vertices[1]= vertices[1].add(CUBE_SIZE,0,CUBE_SIZE);
	vertices[2] = vertices[2].add(0,CUBE_SIZE,CUBE_SIZE);
	vertices[3]= vertices[3].add(CUBE_SIZE,CUBE_SIZE,CUBE_SIZE);
	}
	
	//2,0,1   1,3,2
	if(facing == Sides.LEFT.id){//fix
	
    vertices[0]= vertices[0].add(CUBE_SIZE,0,0);
    vertices[1] = vertices[1].add(0,0,0);
	vertices[2]= vertices[2].add(CUBE_SIZE,CUBE_SIZE,0);
	vertices[3] = vertices[3].add(0,CUBE_SIZE,0);
	}
	
	
	
	if(facing == Sides.TOP.id){
    	vertices[0] = vertices[0].add(0,CUBE_SIZE,CUBE_SIZE);
    	vertices[1]= vertices[1].add(CUBE_SIZE,CUBE_SIZE,CUBE_SIZE);
    	vertices[2] = vertices[2].add(0,CUBE_SIZE,0);
    	vertices[3]= vertices[3].add(CUBE_SIZE,CUBE_SIZE,0);
    }
	
	
	if(facing == Sides.BOTTOM.id){//fix
		vertices[1]= vertices[1].add(0,0,CUBE_SIZE);
    	vertices[0] = vertices[0].add(CUBE_SIZE,0,CUBE_SIZE);
    	vertices[3]= vertices[3].add(0,0,0);
    	vertices[2] = vertices[2].add(CUBE_SIZE,0,0);
    	}
	
	if(facing == Sides.FRONT.id){
		vertices[0]= vertices[0].add(CUBE_SIZE,0,CUBE_SIZE); 
		vertices[1]= vertices[1].add(CUBE_SIZE,0,0);
		vertices[2] = vertices[2].add(CUBE_SIZE,CUBE_SIZE,CUBE_SIZE);
		vertices[3] = vertices[3].add(CUBE_SIZE,CUBE_SIZE,0);
	}
	
	if(facing == Sides.BACK.id){
    	vertices[0] = vertices[0].add(0,0,0);
    	vertices[1] = vertices[1].add(0,0,CUBE_SIZE);
    	vertices[2]= vertices[2].add(0,CUBE_SIZE,0);
    	vertices[3]= vertices[3].add(0,CUBE_SIZE,CUBE_SIZE);
    	}
	
	if(facing == Sides.X1.id){
		vertices[0] = vertices[0].add(0,0,0);
    	vertices[1]= vertices[1].add(CUBE_SIZE,0,CUBE_SIZE);
    	vertices[2] = vertices[2].add(0,CUBE_SIZE,0);
    	vertices[3]= vertices[3].add(CUBE_SIZE,CUBE_SIZE,CUBE_SIZE);
    	
    	 
    }
	if(facing == Sides.X2.id){
		vertices[0] = vertices[0].add(0,0,CUBE_SIZE);
    	vertices[1]= vertices[1].add(CUBE_SIZE,0,0);
    	vertices[2] = vertices[2].add(0,CUBE_SIZE,CUBE_SIZE);
    	vertices[3]= vertices[3].add(CUBE_SIZE,CUBE_SIZE,0);
    	
    }
	if(facing == Sides.PAD_TOP.id){
		    		
		vertices[0] = vertices[0].add(0,0.25f,CUBE_SIZE);
    	vertices[1]= vertices[1].add(CUBE_SIZE,0.25f,CUBE_SIZE);
    	vertices[2] = vertices[2].add(0,0.25f,0);
    	vertices[3]= vertices[3].add(CUBE_SIZE,0.25f,0);        	
    }

	
}


private void UpdateTexCoords(){

// System.out.println(“ttw”+tex_tile_width);

	// int number_of_tiles_per_row = 16;
   	 float tile_size = (1f/(float)tiles_per_row);
    	 
   	
        
        float x = ((tex_id)%tiles_per_row) * tile_size;
        float y = 1f - (((tex_id)/tiles_per_row) * tile_size) - tile_size;
        
       
        
        // Texture coordinates
        texCoord = new Vector2f[4];
        texCoord[0] = new Vector2f(x,y); //System.out.println(tex_id+":"+x+","+y);
        texCoord[1] = new Vector2f(x+tile_size,y);
        texCoord[2] = new Vector2f(x,y+tile_size);
        texCoord[3] = new Vector2f(x+tile_size,y+tile_size);

}

void refresh(){
	
	 m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
     m.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord));
      m.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(indexes));
        m.updateBound();
        geom.setMesh(m);
        
        
       
}

}
[/java]

So what I do basically is make about a metric ton of those quads and then combine them together with GeometryBatchFactory. I would imagine there is a FAR better way of doing it since mythrunia works soooo efficiently, but I don’t know how. I am not well versed in GPU pipeline etc. Yeah my scene is really really puny compared to that scene and I get 20 fps on a nice rig :frowning: I would imagine if I used something like a texture atlas or otherwise improved the pipeline I would see fps skyrocket 100x or more.

Thanks!

Andy

Your code for generating cubes is fine. The problem is you have a mesh per cube.

Here is my code that generates batched chunks 16x16 in size and gets 500fps+ for me:

Uses a sprite map for textures and a heightmap for block location. Feel free to modify.

Maps.java
[java]
package mygame.map;

import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.terrain.heightmap.RawHeightMap;
import com.jme3.texture.Texture;
import java.io.File;
import java.io.IOException;
import java.util.List;

import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import mygame.entity.Tree;
import mygame.main.SharedParts;
import mygame.utils.XMLLoader;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class Map extends Node {

RawHeightMap heightMap;
static Map instance;
int chunkCountX = 16;
int chunkCountZ = 16;
int chunkCountY = 8;
int CHUNK_SIZE = 16;
Chunk[][][] chunks = new Chunk[chunkCountX][chunkCountZ][chunkCountY];
byte[][][] blockHealth = new byte[chunkCountX * CHUNK_SIZE][chunkCountZ * CHUNK_SIZE][chunkCountY * CHUNK_SIZE];
byte[][][] light = new byte[chunkCountX * CHUNK_SIZE][chunkCountZ * CHUNK_SIZE][chunkCountY * CHUNK_SIZE];
float darkness=0.66f;
Texture sheet;
Material material;
SharedParts sharedParts;

public Map(SharedParts sharedParts, String heightFile) {
    this.sharedParts = sharedParts;
    this.setName("Map");

    try {
        heightMap = new RawHeightMap(heightFile, 256);
        //heightMap.load();
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }

    if (sheet == null) {
        sheet = sharedParts.assetManager.loadTexture("Textures/tiles.png");
        sheet.setWrap(Texture.WrapMode.Clamp);
        sheet.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
        sheet.setMagFilter(Texture.MagFilter.Nearest);
    }

    material = new Material(sharedParts.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    //material.setBoolean("VertexLighting", true);
    material.setBoolean("VertexColor", true);
    material.setTexture("ColorMap", getSheet());
    //material.getAdditionalRenderState().setWireframe(true);

}

public void update() {
    for (int x = 0; x < chunkCountX; x++) {
        for (int z = 0; z < chunkCountZ; z++) {
            for (int y = 0; y < chunkCountY; y++) {
                if(chunks[x][z][y] != null)
                    chunks[x][z][y].update();
            }
        }
    }     
}

public void loadXML(String xmlUrl) {
    XMLLoader loader = new XMLLoader();
    org.jdom2.Element root = null;
    try {
        root = (org.jdom2.Element) loader.load(xmlUrl);
    } catch (IOException ex) {
        Logger.getLogger(Map.class.getName()).log(Level.SEVERE, null, ex);
    }

    List<org.jdom2.Element> trees = root.getChild("entities").getChildren("tree");

    for (int i = 0; i < trees.size(); i++) {
        org.jdom2.Element tree1 = trees.get(i);
        int x = Integer.parseInt(tree1.getAttribute("x").getValue());
        int y = Integer.parseInt(tree1.getAttribute("y").getValue());
        int z = Integer.parseInt(tree1.getAttribute("z").getValue());

        Tree treeEntity = new Tree(sharedParts);
        treeEntity.setPosition(new Vector3f(x * 2, y, z * 2));
        this.attachChild(treeEntity);
        System.out.println("Tree added at: " + x + " " + y + " " + z);
    }
}

public void fillTileStrips(Element tiles, Document doc1) {
    byte oldBlockType = -1;
    int startY = 0;

    for (int x = 0; x < chunkCountX * CHUNK_SIZE; x++) {
        for (int z = 0; z < chunkCountZ * CHUNK_SIZE; z++) {
            startY = 0;
            for (int y = 0; y < chunkCountY * CHUNK_SIZE; y++) {
                byte block1 = getBlock(x, z, y);

                if ((y != 0) && (block1 != oldBlockType)) {

                    //Dont write empty blocks
                    if (oldBlockType != Block.EMPTY_BLOCK) {
                        Element strip = doc1.createElement("strip");
                        strip.setAttribute("x", x + "");
                        strip.setAttribute("z", z + "");
                        strip.setAttribute("y1", startY + "");
                        strip.setAttribute("y2", y + "");
                        strip.setAttribute("type", oldBlockType + "");

                        tiles.appendChild(strip);
                    }
                    startY = y + 1;
                }

                oldBlockType = block1;
            }
        }
    }
}

public void saveXML(String xmlURL) {
    DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder docBuilder = null;
    try {
        docBuilder = docFactory.newDocumentBuilder();
    } catch (Exception e) {
        System.out.println(e);
        return;
    }

    Document doc = docBuilder.newDocument();
    Element rootElement = doc.createElement("map");



    Element tiles = doc.createElement("tiles");
    rootElement.appendChild(tiles);

    fillTileStrips(tiles, doc);

    doc.appendChild(rootElement);

    TransformerFactory transformerFactory = TransformerFactory.newInstance();
    Transformer transformer = null;;
    try {
        transformer = transformerFactory.newTransformer();
    } catch (Exception e) {
        System.out.println(e);
        return;
    }
    DOMSource source = new DOMSource(doc);
    StreamResult result = new StreamResult(new File(xmlURL + ".xml"));

    // Output to console for testing
    // StreamResult result = new StreamResult(System.out);
    try {
        transformer.transform(source, result);
    } catch (Exception e) {
        System.out.println(e);
    }

}

public Material getMaterial() {
    return material;
}

public void loadHealth() {
    for (int x = 0; x < chunkCountX * CHUNK_SIZE; x++) {
        for (int z = 0; z < chunkCountZ * CHUNK_SIZE; z++) {
            for (int y = 0; y < chunkCountY * CHUNK_SIZE; y++) {
                blockHealth[x][z][y] = 20;
            }
        }
    }
}

/*
public void rebuild() {
    //castLight();
    
    for (int x = 0; x < chunkCountX; x++) {
        for (int z = 0; z < chunkCountZ; z++) {
            for (int y = 0; y < chunkCountY; y++) {
                Chunk c1 = chunks[x][z][y];
                if (c1.needsRebuild) {
                    c1.build();
                }
            }
        }
    }
}*/

public float getDarkness(){
    return darkness;
}

public float getLightAverage(byte b1, byte b2, byte b3, byte b4) {

    //10 * 4 is max brightness for 4 blocks
    float average = (b1 + b2 + b3 + b4) / 40;

    return average;
}

public byte[][][] getVertexLightZone(int x, int y, int z) {
    //x2,y2,z2 = offset
    byte[][][] lights = new byte[3][3][3];

    for (int x2 = 0; x2 < 3; x2++) {
        for (int y2 = 0; y2 < 3; y2++) {
            for (int z2 = 0; z2 < 3; z2++) {
                lights[x2][y2][z2] = getLightLevel((x - 1) + x2, (z - 1) + z2, (y - 1) + y2);
            }
        }
    }

    return lights;
}

public byte getLightLevel(int x, int z, int y) {
    if ((x < 0) || (z < 0) || (y < 0)) {
        return 0;
    }

    if (y >= (CHUNK_SIZE * chunkCountY)) {
        return 0;
    }

    if (x >= (CHUNK_SIZE * chunkCountX)) {
        return 0;
    }

    if (z >= (CHUNK_SIZE * chunkCountZ)) {
        return 0;
    }
    
    if(this.getBlock(x,z,y) == Block.EMPTY_BLOCK)
        return 10;
    else 
        return 0;
}

/*
public void castLight() {
    for (int x = 0; x < chunkCountX * CHUNK_SIZE; x++) {
        for (int z = 0; z < chunkCountZ * CHUNK_SIZE; z++) {
            for (int y = 0; y < chunkCountY * CHUNK_SIZE; y++) {
                light[x][z][y] = 0;
            }
        }
    }

    for (int x = 0; x < chunkCountX * CHUNK_SIZE; x++) {
        for (int z = 0; z < chunkCountZ * CHUNK_SIZE; z++) {
            for (int y = (chunkCountY * CHUNK_SIZE) - 1; y >= 0; y--) {
                if (getBlock(x, z, y) == Block.EMPTY_BLOCK) {
                    light[x][z][y] = 10;

                } else {
                    //light[x][z][y] = 10;
                    y = 0;
                }
            }
        }
    }
}
*/
public void loadMap() {
    for (int x = 0; x < chunkCountX; x++) {
        for (int z = 0; z < chunkCountZ; z++) {
            for (int y = 0; y < chunkCountY; y++) {
                chunks[x][z][y] = new Chunk(x, z, y, sharedParts, this);
                chunks[x][z][y].needsRebuild = true;
            }
        }
    }

    loadHeightMap();
    //castLight();

    loadHealth();
    //loadXML("assets/map1.xml");
    
    System.out.println("Map loaded.");
}

public Texture getSheet() {
    return sheet;
}

private void loadHeightMap() {

    for (int x = 0; x < 256; x++) {
        for (int z = 0; z < 256; z++) {

            float point = heightMap.getTrueHeightAtPoint(x, z);
            int p2 = (int) Math.floor((point * (CHUNK_SIZE * chunkCountY) / 255));
            //float rand = (float) (Math.random() * 4);
            //int bottomPoint = (int) (rand);
            int bottomPoint = 0;

            for (int y = bottomPoint; y < p2; y++) {

                if (point == 0) {
                    this.setBlock(x, z, y, Block.EMPTY_BLOCK, false);
                } else {
                    this.setBlock(x, z, y, Block.DIRT_BLOCK, false);

                    if (y == (p2 - 1)) {
                        this.setBlock(x, z, y, Block.GRASS_BLOCK, false);
                    }
                }

            }
        }
    }
}

public int damageBlock(int x, int y, int z, int dmg) {
    blockHealth[x][z][y] -= dmg;

    return blockHealth[x][z][y];
}

public void makeDust(int x, int y, int z) {
    /** Explosion effect. Uses Texture from jme3-test-data library! */
    ParticleEmitter debris = new ParticleEmitter("Debris", ParticleMesh.Type.Triangle, 25);
    Material debris_mat = new Material(sharedParts.assetManager, "Common/MatDefs/Misc/Particle.j3md");
    debris_mat.setTexture("Texture", sharedParts.assetManager.loadTexture("Effects/dust.png"));
    debris_mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
    debris_mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
    debris.setStartSize(1.5f);
    debris.setEndSize(1.6f);
    debris.setLowLife(0.3f);
    debris.setHighLife(1.8f);

    debris.setMaterial(debris_mat);
    debris.setLocalTranslation((x * 2) + 1, y + 0.5f, (z * 2) - 1);
    debris.setRotateSpeed(1.2f);
    debris.getParticleInfluencer().setInitialVelocity(new Vector3f(1.4f, 1.4f, 1.4f));
    debris.setStartColor(new ColorRGBA(0.8f, 0.7f, 0.45f, 0.96f));
    debris.setGravity(0f, 0f, 0f);
    debris.getParticleInfluencer().setVelocityVariation(1.70f);
    this.attachChild(debris);
    debris.emitAllParticles();
    debris.setParticlesPerSec(0);
}

public void destroyBlock(int x, int z, int y, byte blockType, boolean rebuild) {
    if (blockType == Block.EMPTY_BLOCK && sharedParts.isServer() == false) {
        makeDust(x, y, z);
    }

    setBlock(x, z, y, blockType, rebuild);
}

public void setBlock(int x, int z, int y, byte blockType, boolean rebuild) {
    if ((x < 0) || (z < 0) || (y < 0)) {
        return;
    }

    if (y >= (CHUNK_SIZE * chunkCountY)) {
        return;
    }

    if (x >= (CHUNK_SIZE * chunkCountX)) {
        return;
    }

    if (z >= (CHUNK_SIZE * chunkCountZ)) {
        return;
    }

    int chunkX = (int) Math.floor(x / CHUNK_SIZE);
    int chunkZ = (int) Math.floor(z / CHUNK_SIZE);
    int chunkY = (int) Math.floor(y / CHUNK_SIZE);

    final int tileX = (int) x - (chunkX * CHUNK_SIZE);
    final int tileZ = (int) z - (chunkZ * CHUNK_SIZE);
    final int tileY = (int) y - (chunkY * CHUNK_SIZE);

    final Chunk chunk1 = chunks[chunkX][chunkZ][chunkY];

    chunk1.setBlock(tileX, tileY, tileZ, blockType);

    if (rebuild) {
        
        Chunk chunk2 = null;
        Chunk chunk3 = null;
        Chunk chunk4 = null;
        Chunk chunk5 = null;
        Chunk chunk6 = null;
        Chunk chunk7 = null;

        
        //define neighboring chunks
        if (tileX == 0) {
            chunk2 = getChunk(chunk1.x - 1, chunk1.z, chunk1.y);
            if(chunk2!=null)
                chunk2.needsRebuild = true;
        }
        if (tileX == (CHUNK_SIZE - 1)) {
            chunk3 = getChunk(chunk1.x + 1, chunk1.z, chunk1.y);
            if(chunk3!=null)
                chunk3.needsRebuild = true;
        }

        if (tileZ == 0) {
            chunk4 = getChunk(chunk1.x, chunk1.z - 1, chunk1.y);
            if(chunk4!=null)
                chunk4.needsRebuild = true;
        }
        if (tileZ == (CHUNK_SIZE - 1)) {
            chunk5 = getChunk(chunk1.x, chunk1.z + 1, chunk1.y);
            if(chunk5!=null)
                chunk5.needsRebuild = true;
        }

        if (tileY == 0) {
            chunk6 = getChunk(chunk1.x, chunk1.z, chunk1.y - 1);
            if(chunk6!=null)
                chunk6.needsRebuild = true;
        }
        if (tileY == (CHUNK_SIZE - 1)) {
            chunk7 = getChunk(chunk1.x, chunk1.z, chunk1.y + 1);
            if(chunk7!=null)
                chunk7.needsRebuild = true;
        }

        //rebuild all chunks
        /*
        if (chunk1 != null) {
            chunk1.buildThreaded();
        }
        if (chunk2 != null) {
            chunk2.buildThreaded();
        }
        if (chunk3 != null) {
            chunk3.buildThreaded();
        }
        if (chunk4 != null) {
            chunk4.buildThreaded();
        }
        if (chunk5 != null) {
            chunk5.buildThreaded();
        }
        if (chunk6 != null) {
            chunk6.buildThreaded();
        }
        if (chunk7 != null) {
            chunk7.buildThreaded();
        }*/
    }
}

public Chunk getChunk(int x, int z, int y) {
    if ((x < 0) || (z < 0) || (y < 0)) {
        return null;
    }

    if ((x > chunkCountX) || (y > chunkCountY) || (z > chunkCountZ)) {
        return null;
    }

    return chunks[x][z][y];
}

public byte getBlock(int x, int z, int y) {
    if ((x < 0) || (z < 0) || (y < 0)) {
        return 0;
    }

    if ((y >= (CHUNK_SIZE * chunkCountY))) {
        return 0;
    }

    if (x >= (CHUNK_SIZE * chunkCountX)) {
        return 0;
    }

    if (z >= (CHUNK_SIZE * chunkCountZ)) {
        return 0;
    }

    int chunkX = (int) Math.floor(x / CHUNK_SIZE);
    int chunkZ = (int) Math.floor(z / CHUNK_SIZE);
    int chunkY = (int) Math.floor(y / CHUNK_SIZE);

    int tileX = (int) x - (chunkX * CHUNK_SIZE);
    int tileZ = (int) z - (chunkZ * CHUNK_SIZE);
    int tileY = (int) y - (chunkY * CHUNK_SIZE);

    Chunk chunk1 = chunks[chunkX][chunkZ][chunkY];

    byte ireturn = chunk1.getBlock(tileX, tileZ, tileY);

    return ireturn;
}

}

[/java]

Chunk.java
[java]
package mygame.map;

import java.util.ArrayList;

import com.jme3.bullet.collision.shapes.MeshCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import mygame.main.SharedParts;

public class Chunk extends Geometry {

int x = 0;
int y = 0;
int z = 0;
public static int CHUNK_SIZE = 16;
public static int BLOCK_WIDTH = 2;
public static int BLOCK_HEIGHT = 2;
public static float TEXTURE_SIZE = 256;
public static float TILE_SIZE = 32;
public static float SUB_TILE_SIZE = 16;
final static ArrayList<Vector3f> vertices = new ArrayList<Vector3f>(); // points
final static ArrayList<Vector3f> normals = new ArrayList<Vector3f>(); // normals
final static ArrayList<Vector2f> texCoord = new ArrayList<Vector2f>(); // tex cords
final static ArrayList<Integer> indexes = new ArrayList<Integer>(); // indexes
final static ArrayList<Vector4f> colors = new ArrayList<Vector4f>();
public final static Vector3f normalUp = new Vector3f(0, 1, 0);
public final static Vector3f normalDown = new Vector3f(0, -1, 0);
public final static Vector3f normalRight = new Vector3f(1, 0, 0);
public final static Vector3f normalLeft = new Vector3f(-1, 0, 0);
public final static Vector3f normalFront = new Vector3f(0, 0, 1);
public final static Vector3f normalBack = new Vector3f(0, 0, -1);
public final static Vector4f darkColor = new Vector4f(0.6f,0.6f,0.6f, 0.6f);
public final static Vector4f brightColor = new Vector4f(1f, 1f, 1f, 1f);
public final static Vector4f green = new Vector4f(0,1,0,1);
//Block[][][] blocks = new Block[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE];
byte[][][] blocks = new byte[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE];
    
Mesh mesh = new Mesh();
RigidBodyControl chunkPhysicsNode;
SharedParts sharedParts;
Map map;
public final static int RIGHT_FACE = 0;
public final static int LEFT_FACE = 1;
public final static int TOP_FACE = 2;
public final static int BOTTOM_FACE = 3;
public final static int FRONT_FACE = 4;
public final static int BACK_FACE = 5;

boolean needsRebuild = false;
Future future;
ChunkAndPhysics futureChunk;

//private static Texture sheet;
public Chunk(int x, int z, int y, SharedParts sharedParts, Map map) {
    this.x = x;
    this.y = y;
    this.z = z;

    this.sharedParts = sharedParts;
    this.map = map;
    this.setName("Chunk");

    initEmpty();
}

public void update(){
    if(needsRebuild)
        build();
}

private void initEmpty() {
    for (int x = 0; x < CHUNK_SIZE; x++) {
        for (int z = 0; z < CHUNK_SIZE; z++) {
            for (int y = 0; y < CHUNK_SIZE; y++) {
                blocks[x][z][y] = Block.EMPTY_BLOCK;
            }
        }
    }
}

public void setBlock(int x, int y, int z, byte blockType) {

    blocks[x][z][y] = blockType;
    needsRebuild = true;
}

/*
public void buildThreaded(){
    // A self-contained time-intensive task:
    if(needsRebuild){
        if(futureChunk==null && future==null){
            GenerateChunk genChunk = new GenerateChunk(blocks, x, y, z, this, map,  sharedParts);
            future = sharedParts.executor.submit(genChunk);
        } else if (future!=null) {

            if(future.isDone()){

                needsRebuild = false;
                future = null;
            }
        }
    }
}*/


public void build() {
    int[] faces = new int[7];
    colors.clear();
    vertices.clear();
    texCoord.clear();
    indexes.clear();
    normals.clear();
    
    float middle = (SUB_TILE_SIZE / TEXTURE_SIZE);
    float max = (TILE_SIZE / TEXTURE_SIZE);
    //float fourth = (SUB_TILE_SIZE / 2) / TEXTURE_SIZE;
    float fourth = (SUB_TILE_SIZE / TEXTURE_SIZE);

    for (int x2 = 0; x2 < CHUNK_SIZE; x2++) {
        for (int z2 = 0; z2 < CHUNK_SIZE; z2++) {
            for (int y2 = 0; y2 < CHUNK_SIZE; y2++) {
                byte myBlock = blocks[x2][z2][y2];

                if (myBlock == Block.EMPTY_BLOCK) {
                    continue;
                }

                //0 = x, 1 = y
                int[] spritePos = Block.getSpritePos(myBlock);

                faces = checkSix(x2, y2, z2);

                //None of the faces were visible
                if (faces[6] == 0) {
                    continue;
                }
                
                //draw positions
                float xPos = (float) ((this.x * CHUNK_SIZE * 2) + x2 * 2);
                float zPos = (float) ((this.z * CHUNK_SIZE * 2) + z2 * 2);
                float yPos = (float) ((this.y * CHUNK_SIZE * 2) + y2 * 2);
                
                int absX = ((this.x) * CHUNK_SIZE) + x2;
                int absZ = ((this.z) * CHUNK_SIZE) + z2;
                int absY = ((this.y) * CHUNK_SIZE) + y2;
                
                //float light = (float)map.getLightLevel(absX, absZ, absY)/10;
                
                //Vector4f brightness = new Vector4f(light,light,light,light);

                float startX = (TILE_SIZE / TEXTURE_SIZE) * (float) spritePos[0];
                float startY = (TILE_SIZE / TEXTURE_SIZE) * (float) spritePos[1];

                int verticesSize = vertices.size();

                Vector3f pa = new Vector3f(xPos + 0, yPos + 0, zPos + 0);
                Vector3f pb = new Vector3f(xPos + 2, yPos + 0, zPos + 0);
                Vector3f pc = new Vector3f(xPos + 0, yPos + 2, zPos + 0);
                Vector3f pd = new Vector3f(xPos + 2, yPos + 2, zPos + 0);
                Vector3f pe = new Vector3f(xPos + 0, yPos + 0, zPos - 2);
                Vector3f pf = new Vector3f(xPos + 0, yPos + 2, zPos - 2);
                Vector3f pg = new Vector3f(xPos + 2, yPos + 0, zPos - 2);
                Vector3f ph = new Vector3f(xPos + 2, yPos + 2, zPos - 2);
                
                byte[][][] lights = map.getVertexLightZone(absX, absY, absZ);

                //FRONT
                if (faces[FRONT_FACE] == 1) {
                    float botLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[1][1][2], lights[0][1][2], lights[1][0][2], lights[0][0][2]));
                    float botRight = Math.max(map.getDarkness(), map.getLightAverage(lights[1][1][2], lights[2][1][2], lights[1][0][2], lights[2][0][2]));
                    float topLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[1][2][2], lights[0][2][2], lights[1][1][2], lights[0][1][2]));
                    float topRight = Math.max(map.getDarkness(), map.getLightAverage(lights[1][2][2], lights[2][2][2], lights[1][1][2], lights[2][1][2]));
                    
                    vertices.add(pa);
                    vertices.add(pb);
                    vertices.add(pc);
                    vertices.add(pd);
                    indexes.add(verticesSize + 2);
                    indexes.add(verticesSize + 0);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 3);
                    indexes.add(verticesSize + 2);
                    texCoord.add(new Vector2f(startX, startY));
                    texCoord.add(new Vector2f(startX + middle, startY));
                    texCoord.add(new Vector2f(startX, startY + fourth));
                    texCoord.add(new Vector2f(startX + middle, startY + fourth));
                    normals.add(normalFront);
                    normals.add(normalFront);
                    normals.add(normalFront);
                    normals.add(normalFront);
                    colors.add(new Vector4f(botLeft,botLeft,botLeft, botLeft));
                    colors.add(new Vector4f(botRight,botRight,botRight, botRight));
                    colors.add(new Vector4f(topLeft,topLeft,topLeft, topLeft));
                    colors.add(new Vector4f(topRight,topRight,topRight, topRight));
                }


                //LEFT
                if (faces[LEFT_FACE] == 1) {
                    verticesSize = vertices.size();
                    
                    float botRight = Math.max(map.getDarkness(), map.getLightAverage(lights[0][1][2], lights[0][1][1], lights[0][0][2], lights[0][0][1]));
                    float botLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[0][1][0], lights[0][1][1], lights[0][0][0], lights[0][0][1]));
                    float topRight = Math.max(map.getDarkness(), map.getLightAverage(lights[0][2][2], lights[0][1][1], lights[0][2][1], lights[0][1][2]));
                    float topLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[0][2][0], lights[0][1][1], lights[0][1][0], lights[0][2][1]));
                    
                    vertices.add(pe);//BOTTOM LEFT
                    vertices.add(pa);//BOTTOM RIGHT
                    vertices.add(pf);//TOP LEFT
                    vertices.add(pc);//TOP RIGHT
                    indexes.add(verticesSize + 2);
                    indexes.add(verticesSize + 0);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 3);
                    indexes.add(verticesSize + 2);
                    texCoord.add(new Vector2f(startX, startY));
                    texCoord.add(new Vector2f(startX + middle, startY));
                    texCoord.add(new Vector2f(startX, startY + fourth));
                    texCoord.add(new Vector2f(startX + middle, startY + fourth));
                    normals.add(normalLeft);
                    normals.add(normalLeft);
                    normals.add(normalLeft);
                    normals.add(normalLeft);
                    colors.add(new Vector4f(botLeft,botLeft,botLeft, botLeft));
                    colors.add(new Vector4f(botRight,botRight,botRight, botRight));
                    colors.add(new Vector4f(topLeft,topLeft,topLeft, topLeft));
                    colors.add(new Vector4f(topRight,topRight,topRight, topRight));

                }

                //RIGHT
                if (faces[RIGHT_FACE] == 1) {
                    verticesSize = vertices.size();

                    float botRight = Math.max(map.getDarkness(), map.getLightAverage(lights[2][1][2], lights[2][1][1], lights[2][0][2], lights[2][0][1]));
                    float botLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[2][1][0], lights[2][1][1], lights[2][0][0], lights[2][0][1]));
                    float topRight = Math.max(map.getDarkness(), map.getLightAverage(lights[2][2][2], lights[2][1][1], lights[2][2][1], lights[2][1][2]));
                    float topLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[2][2][0], lights[2][1][1], lights[2][1][0], lights[2][2][1]));
                    
                    
                    vertices.add(pb);//BOTTOM LEFT
                    vertices.add(pg);//BOTTOM RIGHT
                    vertices.add(pd);//TOP LEFT
                    vertices.add(ph);//TOP RIGHT
                    indexes.add(verticesSize + 2);
                    indexes.add(verticesSize + 0);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 3);
                    indexes.add(verticesSize + 2);
                    texCoord.add(new Vector2f(startX, startY));
                    texCoord.add(new Vector2f(startX + middle, startY));
                    texCoord.add(new Vector2f(startX, startY + fourth));
                    texCoord.add(new Vector2f(startX + middle, startY + fourth));
                    normals.add(normalRight);
                    normals.add(normalRight);
                    normals.add(normalRight);
                    normals.add(normalRight);
                    colors.add(new Vector4f(botRight,botRight,botRight, botRight));
                    colors.add(new Vector4f(botLeft,botLeft,botLeft, botLeft));
                    colors.add(new Vector4f(topRight,topRight,topRight, topRight));
                    colors.add(new Vector4f(topLeft,topLeft,topLeft, topLeft));
                }

                //BACK
                if (faces[BACK_FACE] == 1) {
                    verticesSize = vertices.size();

                    float botRight = Math.max(map.getDarkness(), map.getLightAverage(lights[1][1][0], lights[0][1][0], lights[1][0][0], lights[0][0][0]));
                    float botLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[1][1][0], lights[2][1][0], lights[1][0][0], lights[2][0][0]));
                    float topRight = Math.max(map.getDarkness(), map.getLightAverage(lights[1][2][0], lights[0][2][0], lights[1][1][0], lights[0][1][0]));
                    float topLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[1][2][0], lights[2][2][0], lights[1][1][0], lights[2][1][0]));
                    
                    vertices.add(pg);//BOTTOM LEFT
                    vertices.add(pe);//BOTTOM RIGHT
                    vertices.add(ph);//TOP LEFT
                    vertices.add(pf);//TOP RIGHT
                    indexes.add(verticesSize + 2);
                    indexes.add(verticesSize + 0);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 3);
                    indexes.add(verticesSize + 2);
                    texCoord.add(new Vector2f(startX, startY));
                    texCoord.add(new Vector2f(startX + middle, startY));
                    texCoord.add(new Vector2f(startX, startY + fourth));
                    texCoord.add(new Vector2f(startX + middle, startY + fourth));
                    normals.add(normalBack);
                    normals.add(normalBack);
                    normals.add(normalBack);
                    normals.add(normalBack);
                    
                    colors.add(new Vector4f(botLeft,botLeft,botLeft, botLeft));
                    colors.add(new Vector4f(botRight,botRight,botRight, botRight));
                    colors.add(new Vector4f(topLeft,topLeft,topLeft, topLeft));
                    colors.add(new Vector4f(topRight,topRight,topRight, topRight));
                }

                //TOP
                if (faces[TOP_FACE] == 1) {
                    verticesSize = vertices.size();

                    float botRight = Math.max(map.getDarkness(), map.getLightAverage(lights[2][2][2], lights[2][2][1], lights[1][2][1], lights[1][2][2]));
                    float botLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[0][2][1], lights[0][2][2], lights[1][2][1], lights[1][2][2]));
                    
                    float topRight = Math.max(map.getDarkness(), map.getLightAverage(lights[2][2][1], lights[2][2][0], lights[1][2][0], lights[1][2][1]));
                    float topLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[0][2][0], lights[0][2][1], lights[1][2][1], lights[1][2][0]));
                    
                    vertices.add(pc);//BOTTOM LEFT
                    vertices.add(pd);//BOTTOM RIGHT
                    vertices.add(pf);//TOP LEFT
                    vertices.add(ph);//TOP RIGHT
                    indexes.add(verticesSize + 2);
                    indexes.add(verticesSize + 0);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 3);
                    indexes.add(verticesSize + 2);
                    texCoord.add(new Vector2f(startX, startY + middle));
                    texCoord.add(new Vector2f(startX + middle, startY + middle));
                    texCoord.add(new Vector2f(startX, startY + max));
                    texCoord.add(new Vector2f(startX + middle, startY + max));
                    normals.add(normalUp);
                    normals.add(normalUp);
                    normals.add(normalUp);
                    normals.add(normalUp);
                    colors.add(new Vector4f(botLeft,botLeft,botLeft, botLeft));
                    colors.add(new Vector4f(botRight,botRight,botRight, botRight));
                    colors.add(new Vector4f(topLeft,topLeft,topLeft, topLeft));
                    colors.add(new Vector4f(topRight,topRight,topRight, topRight));
                }

                //BOTTOM
                if (faces[BOTTOM_FACE] == 1) {
                    verticesSize = vertices.size();

                    float botRight = Math.max(map.getDarkness(), map.getLightAverage(lights[2][0][2], lights[2][0][1], lights[1][0][1], lights[1][0][2]));
                    float botLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[0][0][1], lights[0][0][2], lights[1][0][1], lights[1][0][2]));
                    float topRight = Math.max(map.getDarkness(), map.getLightAverage(lights[2][0][1], lights[2][0][0], lights[1][0][0], lights[1][0][1]));
                    float topLeft = Math.max(map.getDarkness(), map.getLightAverage(lights[0][0][0], lights[0][0][1], lights[1][0][1], lights[1][0][0]));
                    
                    vertices.add(pa);//BOTTOM LEFT
                    vertices.add(pb);//BOTTOM RIGHT
                    vertices.add(pe);//TOP LEFT
                    vertices.add(pg);//TOP RIGHT
                    indexes.add(verticesSize + 2);
                    indexes.add(verticesSize + 3);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 1);
                    indexes.add(verticesSize + 0);
                    indexes.add(verticesSize + 2);

                    texCoord.add(new Vector2f(startX + middle, startY + middle));
                    texCoord.add(new Vector2f(startX + max, startY + middle));
                    texCoord.add(new Vector2f(startX + middle, startY + max));
                    texCoord.add(new Vector2f(startX + max, startY + max));
                    normals.add(normalDown);
                    normals.add(normalDown);
                    normals.add(normalDown);
                    normals.add(normalDown);
                    colors.add(new Vector4f(botLeft,botLeft,botLeft, botLeft));
                    colors.add(new Vector4f(botRight,botRight,botRight, botRight));
                    colors.add(new Vector4f(topLeft,topLeft,topLeft, topLeft));
                    colors.add(new Vector4f(topRight,topRight,topRight, topRight));
                }

            }
        }
    }

    float[] c4 = convertFloats(colors);
    Vector3f[] v3 = vertices.toArray(new Vector3f[vertices.size()]);
    Vector3f[] n3 = normals.toArray(new Vector3f[normals.size()]);
    Vector2f[] v2 = texCoord.toArray(new Vector2f[texCoord.size()]);
    int indx[] = convertIntegers(indexes);


    if (this.getMesh() == null) {
        mesh.setBuffer(Type.Color,4, BufferUtils.createFloatBuffer(c4));
        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(v3));
        mesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(v2));
        mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(n3));
        mesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indx));
        mesh.updateBound();

        if (sharedParts.isServer() == false) {
            mesh.createCollisionData();
            this.setMesh(mesh);
            this.mesh.updateBound();
            map.attachChild(this);
        }
    } else {

        mesh = new Mesh();

        mesh.setBuffer(Type.Color,4, BufferUtils.createFloatBuffer(c4));
        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(v3));
        mesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(v2));
        mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(n3));
        mesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indx));

        if (sharedParts.isServer() == false) {
            mesh.createCollisionData();
            this.setMesh(mesh);
            this.mesh.updateBound();
        }

    }
    this.setShadowMode(ShadowMode.Receive);

    setMaterial(map.getMaterial());

    if (mesh.getTriangleCount() > 0) {

        if (chunkPhysicsNode != null) {
            sharedParts.bulletPhysics.getPhysicsSpace().remove(chunkPhysicsNode);
        }

        chunkPhysicsNode = new RigidBodyControl(new MeshCollisionShape(mesh), 0);
        this.controls.clear();
        this.addControl(chunkPhysicsNode);
        sharedParts.bulletPhysics.getPhysicsSpace().add(chunkPhysicsNode);

    }

    colors.clear();
    vertices.clear();
    texCoord.clear();
    indexes.clear();
    normals.clear();

    needsRebuild = false;
}

private int[] checkSix(int x, int y, int z) {
    boolean anyVisible = true;

    int faces[] = new int[7];

    for (int i = 0; i < faces.length; i++) {
        faces[i] = 0;
    }

    int absX = ((this.x) * CHUNK_SIZE) + x;
    int absZ = ((this.z) * CHUNK_SIZE) + z;
    int absY = ((this.y) * CHUNK_SIZE) + y;


    if (map.getBlock(absX, absZ, absY + 1) == 0) {
        faces[TOP_FACE] = 1;
        anyVisible = true;
    }

    if (map.getBlock(absX - 1, absZ, absY) == 0) {
        faces[LEFT_FACE] = 1;
        anyVisible = true;
    }

    if (map.getBlock(absX + 1, absZ, absY) == 0) {
        faces[RIGHT_FACE] = 1;
        anyVisible = true;
    }

    if (map.getBlock(absX, absZ - 1, absY) == 0) {
        faces[BACK_FACE] = 1;
        anyVisible = true;
    }

    if (map.getBlock(absX, absZ + 1, absY) == 0) {
        faces[FRONT_FACE] = 1;
        anyVisible = true;
    }

    if (map.getBlock(absX, absZ, absY - 1) == 0) {
        faces[BOTTOM_FACE] = 1;
        anyVisible = true;
    }

    if (anyVisible == true) {
        faces[6] = 1;
    } else {
        faces[6] = 0;
    }

    return faces;
}

public byte getBlock(int x, int z, int y) {
    return blocks[x][z][y];
}

public static int[] convertIntegers(ArrayList<Integer> integers) {
    int[] ret = new int[integers.size()];
    for (int i = 0; i < ret.length; i++) {
        ret[i] = integers.get(i).intValue();
    }
    return ret;
}

public static float[] convertFloats(ArrayList<Vector4f> colors) {
    float[] ret = new float[colors.size()*4];
    for (int i = 0; i < colors.size(); i++) {
        int i2 = i*4;
        
        ret[i2] = colors.get(i).getX();
        ret[i2+1] = colors.get(i).getY();
        ret[i2+2] = colors.get(i).getZ();
        ret[i2+3] = colors.get(i).getW();
    }
    return ret;
}

}

[/java]

Block.java
[java]
package mygame.map;

public class Block {

public final static byte EMPTY_BLOCK = 0;
public final static byte GRASS_BLOCK = 1;
public final static byte STONE_BLOCK = 2;
public final static byte DIRT_BLOCK = 3;
public final static byte ROCK_BLOCK = 4;
public final static byte WOOD_BLOCK = 5;

public static int[] getSpritePos(byte blockType) {
    int[] sp = new int[2];

    if (blockType == EMPTY_BLOCK) {
        sp[0] = -1;
        sp[1] = -1;
    }

    if (blockType == GRASS_BLOCK) {
        sp[0] = 1;
        sp[1] = 0;
    }

    if (blockType == DIRT_BLOCK) {
        sp[0] = 4;
        sp[1] = 0;
    }

    if (blockType == STONE_BLOCK) {
        sp[0] = 0;
        sp[1] = 0;
    }

    if (blockType == ROCK_BLOCK) {
        sp[0] = 5;
        sp[1] = 0;
    }

    if (blockType == WOOD_BLOCK) {
        sp[0] = 6;
        sp[1] = 0;
    }

    return sp;
}

}
[/java]

You know… there is also a library someone has already open sourced that does this. Even if you don’t want to use it directly, you might be able to test it to compare performance and then tweak your own to match or whatever.

http://hub.jmonkeyengine.org/forum/topic/cubes-a-block-world-framework/

1 Like

The funny thing is, I was originally using Cubes and it was ~okay~ but I still didn’t have amazing performance and there was some glitching going on. I decided to roll my own voxel engine a while back and, while it works fine, it is obviously inefficient.

Thanks for helping me so much! After looking at what you have done, it seems like you have made the exposed top of each chunk a single mesh? That is exactly what I was hoping to see. That would definitely speed up the updateGeometricState loop as there would be FAR less Spatials to loop through and check every frame. I already use a sprite map, use chunks that update just when they need to, and already store voxel data in an integer array (which I should make a byte array haha) so I just need to go ahead and place cube faces as vertices on a mesh instead of placing cube faces as individual quads.

Thanks again :smiley:

Andy

(this little hobby project of mine is called Spearhead. It is supposed to mimic Island Troll Tribes, a custom game mode from Warcraft 3, but with Voxels and in the first person)

but I still didn’t have amazing performance and there was some glitching going on.
Yeah, especially transparency was a big issue - I fixed it quite some time ago, but since the nightly updates aren't working since then, I can't "deliver" the new version. :(
@admazzola said: The funny thing is, I was originally using Cubes and it was ~okay~ but I still didn't have amazing performance and there was some glitching going on. I decided to roll my own voxel engine a while back and, while it works fine, it is obviously inefficient.

Thanks for helping me so much! After looking at what you have done, it seems like you have made the exposed top of each chunk a single mesh? That is exactly what I was hoping to see. That would definitely speed up the updateGeometricState loop as there would be FAR less Spatials to loop through and check every frame. I already use a sprite map, use chunks that update just when they need to, and already store voxel data in an integer array (which I should make a byte array haha) so I just need to go ahead and place cube faces as vertices on a mesh instead of placing cube faces as individual quads.

Thanks again :smiley:

Andy

(this little hobby project of mine is called Spearhead. It is supposed to mimic Island Troll Tribes, a custom game mode from Warcraft 3, but with Voxels and in the first person)

I still wonder how many spatials you actually have in your scene graph. Someone a month from now will post another “updateGeometricState is slow” thread and will point to this one as further evidence as we have pointed to the last one. Despite the fact that for updateGeometricState to be slow (for real) you’d have to have thousands and thousands of spatials in the scene graph and they’d have to be moving every frame. And actually, I’ve had upwards of 3000 objects rendered (let alone just in the scene graph) and still not had significant hits from these methods.

The time to render a scene like this should be dwarfing these methods. So if you feel like looking into it just a little more then that would be useful. Like, more accurately timing them with System.nanoTime() and counting the objects in the scene graph. (You could time that, too, because updateGeometricState() is basically just a scene graph traversal when nothing has moved).

I’m really curious.

@pspeed I will definitely keep you posted on how this goes. I agree, my FPS is extremely bizarre! I do have thousands of spatials, but they are not moving at all. My theory is that the transveral checking of them is causing the lag. Even though it takes a minimal amount of time to check each spatial, there are WAYYYYY too many of them and that time adds up to 20ms, lagging my game to 30fps with a relatively small scene. I am convinced that this is my fault, not JME3’s fault. Everyone who says ‘updateGeometricState is lagging!!!’ ,including me, is doing something fundamentally wrong and would have the same issue on any engine.

@destroflyer Cubes worked quite well and I would LOVE to see the new version! I wish there was a bit more I could access and that is why I rolled my own eventually. In my game, voxel types (wood, grass, sand) are all defined at run-time by the user through a script so I had to do some funky register things with Cubes to get it to work properly, and it was fine except sometimes when a block was destroyed, the chunk would re-calculate its geometry and some sides of blocks just wouldn’t be there (transparency). That could have been due to something strange I was doing though. I also wanted to add foliage with alpha test and that couldn’t be done with Cubes.

Update:

I am getting great results now! Look at these stats:

And the bake time still seems instant!

What changes did you make? Did you implement more chunks per spatial? :slight_smile:

@admazzola said: @pspeed I will definitely keep you posted on how this goes. I agree, my FPS is extremely bizarre! I do have thousands of spatials, but they are not moving at all. My theory is that the transveral checking of them is causing the lag. Even though it takes a minimal amount of time to check each spatial, there are WAYYYYY too many of them and that time adds up to 20ms, lagging my game to 30fps with a relatively small scene. I am convinced that this is my fault, not JME3's fault. Everyone who says 'updateGeometricState is lagging!!!' ,including me, is doing something fundamentally wrong and would have the same issue on any engine.

Well, I’m glad you got it working. On the other stuff, I don’t trust your timings or your counts without actual answers to my questions. If you used System.currentTimeMillis() for timing, for example, then the results are meaningless. And the numbers just don’t compute. Even the number of spatials in your scene seems confusing to me since you won’t count them, have somehow batched them, but still think you have a lot.

If you had so many spatials that updateGeometricState and updateLogicalState were taking 40 ms then your rendering time would be even higher and you’d be getting at most 4-5 FPS. It just doesn’t compute.

Now, if you were only batching like two cubes together and weren’t actually removing hidden faces before… then things start to make more sense and I will make a point to ask better (more extremely detailed) questions in the future instead of assuming we all mean the same thing by batching and only drawing visible faces.

But it seems you are on the right track now, we can just chalk up the slow update thing as unconfirmed/unmeasured erroneous behavior. As I said, I have had scenes with many thousands of nodes with no issue on update performance.

Sorry, I was not sure how to count the number of spatials besides looking at the stats. Also, I was not using currentTimeMillis, I was using System.nanoTime(). I wouldn’t make a huge deal over it, I was just generating geometry in a horribly inefficient way. This geometry generation fix completely remedied the issue. Look:

:slight_smile:

1 Like

Looks good.

Nice :slight_smile: