Some advice for handling alot of Boxs

Greetings Monkeys.



I’m new to the forum and to jME so hi to you all and thanks to all involved for the awesome libraries and tutorials etc.



I’ve been coding in Flash with Actionscript for the last few years and more recently I adopted Java. Now, of course, I’m playing with jME and I have a few things I’d like to try but being a noob… I have issues.



The main project I want to make involves cubes. Something akin to Minecraft. Here’s a video so you can get a quick look, if you don’t know what Minecraft is. http://www.youtube.com/watch?v=OFms0vS_7Cc&feature=fvst



The way the engine works means that blocks are always aligned and this is just what I want in my engine. I thought it would be relatively simple. Mistake number one.



The natural thing to do with jME was throw up a load of textured Box instances, but I can only manage 500 or so before I get serious slowdown. I’m not a great computer at the moment but still, it’s not enough.



I had a few ideas for how to improve performance and had a go at a couple but without the real knowledge of how jME works and limited Java experience, learning through examining the examples isn’t proving terribly fruitful.



I tried creating my own blocks using six quads and only attaching the quads that were exposed. I.E Attach one block to another and both facing quads would be detached. While this worked and did provide a performance boost, it’s not really a big enough boost and caused me problems when using mouse picking that worked fine on the Box instances.



So… rant over… Does anyone have any insight, ideas or techniques that I could employ to create the best ‘cube world’ possible in jME… Or is using jME at all a bad idea if I want something even remotely the size of the maps Minecraft is capable of?



Thanks for reading, any comments will be appreciated.


  • Stretch.

are you creating each box separately or are you cloning them? Also which engine are you using jME2 or jME3. Also doesn't Minecraft use voxels??

I'm new to JME too, so beware.



In current project i try to build map from squares with materials and to mitigate slowdown (in my case its premature optimalization :slight_smile: ), I go through them and categorize them by material they use and make one big mesh from each group.

if you are using the same geometry all the time then as bonechilla hinted it would be best to clone/"share" the geometry and just update it's translation…

have a look at shared mesh in jme2 for an implementation…

try to avoid having a large number of elements in your scene graph as updates can cause serious lag once you start using large number of scene objects…

if possible attempt to merge your geometries into one mesh…

this will render a hell of a lot faster…

e.g. in my scene i have 1 scene object comprised of 65k polygons rendering with 300 fps…

Here is some code I wrote, if you want to take a look. It handles a 128x128x5~ map of boxes and gets about 100-150fps



package com.fortwars.map;

import java.net.URL;
import java.util.ArrayList;

import javax.swing.ImageIcon;

import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.TexCoords;
import com.jme.scene.TriMesh;
import com.jme.scene.geometryinstancing.GeometryBatchInstance;
import com.jme.scene.geometryinstancing.GeometryBatchInstanceAttributes;
import com.jme.scene.geometryinstancing.instance.GeometryBatchCreator;
import com.jme.scene.shape.Box;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.util.geom.BufferUtils;
import com.jmex.terrain.util.AbstractHeightMap;
import com.jmex.terrain.util.ImageBasedHeightMap;

public class Map extends Node {
   
   private ArrayList<TriMesh> meshes = new ArrayList<TriMesh>();
   private ArrayList<GeometryBatchCreator> geometryBatchCreators = new ArrayList<GeometryBatchCreator>();
   AbstractHeightMap heightMap=null;
   
   public Map(String heightMapLoc){
      URL heightmapImg = Map.class.getClassLoader().getResource(heightMapLoc);
      heightMap = new ImageBasedHeightMap(new ImageIcon(heightmapImg).getImage());
           
        createBoxes();
   }
    private void createBoxes() {
        // A box that will be instantiated
       Box box = new Box("Box", new Vector3f(0, 0, 0), new Vector3f(1, 1, 1));

        // The batch geometry creator
             
       
        // Loop that creates NxN instances
        for (int y = 0; y < 128; y++) {
            for (int x = 0; x < 128; x++) {
               for(int z = 0;z<(heightMap.getTrueHeightAtPoint(x, y)*0.3f)-10; z++) {
                  if(geometryBatchCreators.size()-1<z)
                     geometryBatchCreators.add(z,new GeometryBatchCreator());
                   // Box instance attributes
                   GeometryBatchInstanceAttributes attributes =
                           new GeometryBatchInstanceAttributes(
                                   new Vector3f(x,y,z),
                                   // Translation
                                   new Vector3f(1f,1f,1f),
                                   // Scale
                                   box.getLocalRotation(),
                                   // Rotation
                                   new ColorRGBA(1,1,1,0));   
                               // Color
   
                   // Box instance (batch and attributes)
                   GeometryBatchInstance instance =
                           new GeometryBatchInstance(box, attributes);
   
                   // Add the instance
                   geometryBatchCreators.get(z).addInstance(instance);
               }
            }
        }
       
        // Create a TriMesh
        for(int n = 0;n<geometryBatchCreators.size();n++){
           TriMesh mesh = new TriMesh();
           mesh.setModelBound(new BoundingBox());
   
           // Create the batch's buffers
           mesh.setIndexBuffer(BufferUtils.createIntBuffer(
                   geometryBatchCreators.get(n).getNumIndices()));
           mesh.setVertexBuffer(BufferUtils.createVector3Buffer(
                 geometryBatchCreators.get(n).getNumVertices()));
           mesh.setNormalBuffer(BufferUtils.createVector3Buffer(
                 geometryBatchCreators.get(n).getNumVertices()));
           mesh.setTextureCoords(new TexCoords(BufferUtils.createVector2Buffer(
                 geometryBatchCreators.get(n).getNumVertices())), 0);
           mesh.setColorBuffer(BufferUtils.createFloatBuffer(
                 geometryBatchCreators.get(n).getNumVertices() * 4));
   
   
           // Commit the instances to the mesh batch
           geometryBatchCreators.get(n).commit(mesh);
          
          
          
        // Add a texture
           TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
           Texture t0 = TextureManager.loadTexture(
                   Map.class.getClassLoader().getResource(
                           "grass.png"),
                   Texture.MinificationFilter.Trilinear,
                   Texture.MagnificationFilter.Bilinear, 0.0f, false);
           t0.setWrap(Texture.WrapMode.Repeat);
           ts.setTexture(t0, 0);

           // Material
           MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
           ms.setColorMaterial(MaterialState.ColorMaterial.AmbientAndDiffuse);

           // Render states
           mesh.setRenderState(ms);
           mesh.setRenderState(ts);
           mesh.updateRenderState();
          
           // Return the mesh
           mesh.updateModelBound();
           mesh.lock();
          
           meshes.add(mesh);
           this.attachChild(mesh);
        }
    }
}