2
0
Эх сурвалжийг харах

Batching :
- BatchNode can now batch a scene graph with several materials. It creates a batch by material
- Added a SimpleBatchNode that batch only geometries (no sub node graph) for better performances
- removed RF_REFRESHBATCH from Spatial as it's no longer needed
- changed test cases a bit

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8527 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

rem..om 14 жил өмнө
parent
commit
7193c0b2d3

+ 158 - 151
engine/src/core/com/jme3/scene/BatchNode.java

@@ -31,8 +31,6 @@
  */
 package com.jme3.scene;
 
-import com.jme3.asset.AssetNotFoundException;
-import com.jme3.bounding.BoundingVolume;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
@@ -40,31 +38,33 @@ import com.jme3.export.OutputCapsule;
 import com.jme3.export.Savable;
 import com.jme3.material.Material;
 import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.mesh.IndexBuffer;
 import com.jme3.util.IntMap.Entry;
-import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.nio.Buffer;
 import java.nio.FloatBuffer;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
- * BatchNode holds a geometry that is a batched version of all geometries that are in its sub scenegraph.
- * this geometry is directly attached to the node in the scene graph.
- * usage is like any other node except you have to call the {@link #batch()} method once all geoms have been attached to the sub scene graph (see todo more automagic for further enhancements)
+ * BatchNode holds geometrie that are batched version of all geometries that are in its sub scenegraph.
+ * There is one geometry per different material in the sub tree.
+ * this geometries are directly attached to the node in the scene graph.
+ * usage is like any other node except you have to call the {@link #batch()} method once all geoms have been attached to the sub scene graph and theire material set
+ * (see todo more automagic for further enhancements)
  * all the geometry that have been batched are set to {@link CullHint#Always} to not render them.
  * the sub geometries can be transformed as usual their transforms are used to update the mesh of the geometryBatch.
  * sub geoms can be removed but it may be slower than the normal spatial removing
  * Sub geoms can be added after the batch() method has been called but won't be batched and will be rendered as normal geometries.
- * To integrated them in the batch you have to call the batch() method again on the batchNode.
+ * To integrate them in the batch you have to call the batch() method again on the batchNode.
  * 
- * TODO account for sub-BatchNodes
- * TODO account for geometries that have different materials
  * TODO normal or tangents or both looks a bit weird
  * TODO more automagic (batch when needed in the updateLigicalState)
  * @author Nehon
@@ -73,12 +73,13 @@ public class BatchNode extends Node implements Savable {
 
     private static final Logger logger = Logger.getLogger(BatchNode.class.getName());
     /**
-     * the geometry holding the batched mesh
+     * the map of geometry holding the batched meshes
      */
-    protected Geometry batch;
-    protected Material material;
-    private boolean needMeshUpdate = false;
+    protected Map<Material, Batch> batches = new HashMap<Material, Batch>();
 
+    /**
+     * Construct a batchNode
+     */
     public BatchNode() {
         super();
     }
@@ -109,12 +110,17 @@ public class BatchNode extends Node implements Savable {
             for (Spatial child : children.getArray()) {
                 child.updateGeometricState();
             }
-            if (needMeshUpdate) {
-                batch.getMesh().updateBound();
-                batch.updateWorldBound();
-                needMeshUpdate = false;
+
+            for (Batch batch : batches.values()) {
+                if (batch.needMeshUpdate) {
+                    batch.geometry.getMesh().updateBound();
+                    batch.geometry.updateWorldBound();
+                    batch.needMeshUpdate = false;
+
+                }
             }
 
+
         }
 
         if ((refreshFlags & RF_BOUND) != 0) {
@@ -123,10 +129,15 @@ public class BatchNode extends Node implements Savable {
 
         assert refreshFlags == 0;
     }
+    
+    protected Transform getTransforms(Geometry geom){
+        return geom.getWorldTransform();
+    }
 
     protected void updateSubBatch(Geometry bg) {
+        Batch batch = batches.get(bg.getMaterial());
         if (batch != null) {
-            Mesh mesh = batch.getMesh();
+            Mesh mesh = batch.geometry.getMesh();
 
             FloatBuffer buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Position).getData();
             doTransformVerts(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat);
@@ -144,162 +155,155 @@ public class BatchNode extends Node implements Savable {
                 mesh.getBuffer(VertexBuffer.Type.Tangent).updateData(buf);
             }
 
-            needMeshUpdate = true;
+            batch.needMeshUpdate = true;
         }
     }
 
-    @Override
-    protected void setTransformRefresh() {
-        refreshFlags |= RF_TRANSFORM;
-        setBoundRefresh();
-
-        for (Spatial child : children.getArray()) {
-            if ((child.refreshFlags & RF_TRANSFORM) != 0) {
-                continue;
-            }
-
-           innerTransformRefresh(child);
-            //child.setTransformRefresh();
-
-        }
-    }
-//
-    private void innerTransformRefresh(Spatial s) {
-        s.refreshFlags |= RF_TRANSFORM;
-        s.setBoundRefresh();
-        if (s instanceof Node) {            
-            Node n = (Node) s;
-           
-            for (Spatial child :((SafeArrayList<Spatial>) n.getChildren()).getArray()) {
-                if ((child.refreshFlags & RF_TRANSFORM) != 0) {
-                    continue;
-                }
-                innerTransformRefresh(child);
-            }
-        }
-
-    }
-
     /**
      * Batch this batchNode
      * every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call
      */
     public void batch() {
+        doBatch();
+        //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice        
+        for (Batch batch : batches.values()) {
+            batch.geometry.setIgnoreTransform(true);
+        }
+    }
 
-        List<Geometry> tmpList = new ArrayList<Geometry>();
-        Mesh m = new Mesh();
-        populateList(tmpList, this);
-        mergeGeometries(m, tmpList);
-
-        if (batch == null) {
-            batch = new Geometry(name + "-batch");
-            batch.setMaterial(material);
-            this.attachChild(batch);
+    protected void doBatch() {
+        ///List<Geometry> tmpList = new ArrayList<Geometry>();
+        Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();
+
+        gatherGeomerties(matMap, this);
+        batches.clear();
+        int nbGeoms = 0;
+        for (Material material : matMap.keySet()) {
+            Mesh m = new Mesh();
+            List<Geometry> list = matMap.get(material);
+            nbGeoms += list.size();
+            mergeGeometries(m, list);
+            Batch batch = new Batch();
+
+            batch.geometry = new Geometry(name + "-batch" + batches.size());
+            batch.geometry.setMaterial(material);
+            this.attachChild(batch.geometry);
+
+
+            batch.geometry.setMesh(m);
+            batch.geometry.getMesh().updateCounts();
+            batch.geometry.getMesh().updateBound();
+            batches.put(material, batch);
         }
-        batch.setMesh(m);
-        batch.getMesh().updateCounts();
-        batch.getMesh().updateBound();
+        logger.log(Level.INFO, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()});
     }
 
-    private void populateList(List<Geometry> list, Spatial n) {
+    private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n) {
 
-        if (n instanceof Geometry) {
-            if (n != batch) {
-                list.add((Geometry) n);
+        if (n.getClass() == Geometry.class) {
+            if (!isBatch(n)) {
+                Geometry g = (Geometry) n;
+                if (g.getMaterial() == null) {
+                    throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching");
+                }
+                List<Geometry> list = map.get(g.getMaterial());
+                if (list == null) {
+                    list = new ArrayList<Geometry>();
+                    map.put(g.getMaterial(), list);
+                }
+                list.add(g);
             }
 
         } else if (n instanceof Node) {
             for (Spatial child : ((Node) n).getChildren()) {
-                populateList(list, child);
+                if (child instanceof BatchNode) {
+                    continue;
+                }
+                gatherGeomerties(map, child);
             }
         }
 
     }
 
+    private boolean isBatch(Spatial s) {
+        for (Batch batch : batches.values()) {
+            if (batch.geometry == s) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
-     * Sets the material to use for this geometry.
+     * Sets the material to the all the batches of this BatchNode
+     * use setMaterial(Material material,int batchIndex) to set a material to a specific batch
      * 
      * @param material the material to use for this geometry
      */
     @Override
     public void setMaterial(Material material) {
-        super.setMaterial(material);
-        if (batch != null) {
-            batch.setMaterial(material);
-        }
-        this.material = material;
-
+//        for (Batch batch : batches.values()) {
+//            batch.geometry.setMaterial(material);
+//        }
+        throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching");
     }
 
     /**
-     * Returns the material that is used for this geometry.
+     * Returns the material that is used for the first batch of this BatchNode
+     * 
+     * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
      * 
-     * @return the material that is used for this geometry
+     * @return the material that is used for the first batch of this BatchNode
      * 
      * @see #setMaterial(com.jme3.material.Material) 
      */
     public Material getMaterial() {
-        return material;
-    }
-
-    /**
-     * @return The bounding volume of the mesh, in model space.
-     */
-    public BoundingVolume getModelBound() {
-        if (batch != null) {
-            return batch.getMesh().getBound();
+        if (!batches.isEmpty()) {
+            Batch b = batches.get(batches.keySet().iterator().next());
+            return b.geometry.getMaterial();
         }
-        return super.getWorldBound();
-
-    }
-
-    /**
-     * This version of clone is a shallow clone, in other words, the
-     * same mesh is referenced as the original geometry.
-     * Exception: if the mesh is marked as being a software
-     * animated mesh, (bind pose is set) then the positions
-     * and normals are deep copied.
-     */
-    @Override
-    public BatchNode clone(boolean cloneMaterial) {
-        BatchNode clone = (BatchNode) super.clone(cloneMaterial);
-        clone.batch = batch.clone(cloneMaterial);
-        return clone;
-    }
-
-    /**
-     * This version of clone is a shallow clone, in other words, the
-     * same mesh is referenced as the original geometry.
-     * Exception: if the mesh is marked as being a software
-     * animated mesh, (bind pose is set) then the positions
-     * and normals are deep copied.
-     */
-    @Override
-    public BatchNode clone() {
-        return clone(true);
-    }
-
-    /**
-     * Creates a deep clone of the geometry,
-     * this creates an identical copy of the mesh
-     * with the vertexbuffer data duplicated.
-     */
-    @Override
-    public Spatial deepClone() {
-        BatchNode clone = clone(true);
-        clone.batch = (Geometry) batch.deepClone();
-        return clone;
+        return null;//material;
     }
+    
+//    /**
+//     * Sets the material to the a specific batch of this BatchNode
+//     * 
+//     * 
+//     * @param material the material to use for this geometry
+//     */   
+//    public void setMaterial(Material material,int batchIndex) {
+//        if (!batches.isEmpty()) {
+//            
+//        }
+//        
+//    }
+//
+//    /**
+//     * Returns the material that is used for the first batch of this BatchNode
+//     * 
+//     * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
+//     * 
+//     * @return the material that is used for the first batch of this BatchNode
+//     * 
+//     * @see #setMaterial(com.jme3.material.Material) 
+//     */
+//    public Material getMaterial(int batchIndex) {
+//        if (!batches.isEmpty()) {
+//            Batch b = batches.get(batches.keySet().iterator().next());
+//            return b.geometry.getMaterial();
+//        }
+//        return null;//material;
+//    }
 
     @Override
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
-
-        if (material != null) {
-            oc.write(material.getAssetName(), "materialName", null);
-        }
-        oc.write(material, "material", null);
+//
+//        if (material != null) {
+//            oc.write(material.getAssetName(), "materialName", null);
+//        }
+//        oc.write(material, "material", null);
 
     }
 
@@ -309,23 +313,23 @@ public class BatchNode extends Node implements Savable {
         InputCapsule ic = im.getCapsule(this);
 
 
-        material = null;
-        String matName = ic.readString("materialName", null);
-        if (matName != null) {
-            // Material name is set,
-            // Attempt to load material via J3M
-            try {
-                material = im.getAssetManager().loadMaterial(matName);
-            } catch (AssetNotFoundException ex) {
-                // Cannot find J3M file.
-                logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.",
-                        matName);
-            }
-        }
-        // If material is NULL, try to load it from the geometry
-        if (material == null) {
-            material = (Material) ic.readSavable("material", null);
-        }
+//        material = null;
+//        String matName = ic.readString("materialName", null);
+//        if (matName != null) {
+//            // Material name is set,
+//            // Attempt to load material via J3M
+//            try {
+//                material = im.getAssetManager().loadMaterial(matName);
+//            } catch (AssetNotFoundException ex) {
+//                // Cannot find J3M file.
+//                logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.",
+//                        matName);
+//            }
+//        }
+//        // If material is NULL, try to load it from the geometry
+//        if (material == null) {
+//            material = (Material) ic.readSavable("material", null);
+//        }
 
     }
 
@@ -418,9 +422,6 @@ public class BatchNode extends Node implements Savable {
 
         for (Geometry geom : geometries) {
             Mesh inMesh = geom.getMesh();
-            if (geom.getMaterial() != null && material == null) {
-                material = geom.getMaterial();
-            }
             geom.batch(this, globalVertIndex);
 
             int geomVertCount = inMesh.getVertexCount();
@@ -532,4 +533,10 @@ public class BatchNode extends Node implements Savable {
         }
         vars.release();
     }
+
+    protected class Batch {
+
+        Geometry geometry;
+        boolean needMeshUpdate = false;
+    }
 }

+ 14 - 17
engine/src/core/com/jme3/scene/Geometry.java

@@ -79,11 +79,11 @@ public class Geometry extends Spatial {
     /**
      * the previous transforms of the geometry used to compute world transforms
      */
-    protected Transform prevLocalTransform = null;
+    protected Transform prevBatchTransforms = null;
     /**
      * the cached offset matrix used when the geometry is batched
      */
-    protected Matrix4f cachedOffsetMat = null;   
+    protected Matrix4f cachedOffsetMat = null;
 
     /**
      * Serialization only. Do not use.
@@ -287,11 +287,12 @@ public class Geometry extends Spatial {
 
         super.updateWorldTransforms();
         computeWorldMatrix();
-        if ((refreshFlags & RF_REFRESHBATCH) != 0) {
+
+        if (isBatched()) {
             computeOffsetTransform();
             batchNode.updateSubBatch(this);
-            prevLocalTransform.set(localTransform);
-            refreshFlags &= ~RF_REFRESHBATCH;
+            prevBatchTransforms.set(batchNode.getTransforms(this));
+
         }
         // geometry requires lights to be sorted
         worldLights.sort(true);
@@ -305,10 +306,9 @@ public class Geometry extends Spatial {
     protected void batch(BatchNode node, int startIndex) {
         this.batchNode = node;
         this.startIndex = startIndex;
-        prevLocalTransform = new Transform();
+        prevBatchTransforms = new Transform();
         cachedOffsetMat = new Matrix4f();
         setCullHint(CullHint.Always);
-        refreshFlags |= RF_REFRESHBATCH;
     }
 
     /**
@@ -316,7 +316,7 @@ public class Geometry extends Spatial {
      */
     protected void unBatch() {
         this.startIndex = 0;
-        prevLocalTransform = null;
+        prevBatchTransforms = null;
         cachedOffsetMat = null;
         //once the geometry is removed from the screnegraph we call batch on the batchNode before unreferencing it.
         this.batchNode.batch();
@@ -343,21 +343,21 @@ public class Geometry extends Spatial {
 
         // Compute the cached world matrix
         cachedOffsetMat.loadIdentity();
-        cachedOffsetMat.setRotationQuaternion(prevLocalTransform.getRotation());
-        cachedOffsetMat.setTranslation(prevLocalTransform.getTranslation());
+        cachedOffsetMat.setRotationQuaternion(prevBatchTransforms.getRotation());
+        cachedOffsetMat.setTranslation(prevBatchTransforms.getTranslation());
 
 
         Matrix4f scaleMat = vars.tempMat4;
         scaleMat.loadIdentity();
-        scaleMat.scale(prevLocalTransform.getScale());
+        scaleMat.scale(prevBatchTransforms.getScale());
         cachedOffsetMat.multLocal(scaleMat);
         cachedOffsetMat.invertLocal();
 
         tmpMat.loadIdentity();
-        tmpMat.setRotationQuaternion(localTransform.getRotation());
-        tmpMat.setTranslation(localTransform.getTranslation());
+        tmpMat.setRotationQuaternion(batchNode.getTransforms(this).getRotation());
+        tmpMat.setTranslation(batchNode.getTransforms(this).getTranslation());
         scaleMat.loadIdentity();
-        scaleMat.scale(localTransform.getScale());
+        scaleMat.scale(batchNode.getTransforms(this).getScale());
         tmpMat.multLocal(scaleMat);
 
         tmpMat.mult(cachedOffsetMat, cachedOffsetMat);
@@ -373,9 +373,6 @@ public class Geometry extends Spatial {
     protected void setTransformRefresh() {
         refreshFlags |= RF_TRANSFORM;
         setBoundRefresh();
-        if (isBatched()) {
-             refreshFlags |= RF_REFRESHBATCH;
-        }
     }
 
     /**

+ 56 - 0
engine/src/core/com/jme3/scene/SimpleBatchNode.java

@@ -0,0 +1,56 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.scene;
+
+import com.jme3.math.Transform;
+
+/**
+ * 
+ * SimpleBatchNode  comes with some restrictions, but can yield better performances.
+ * Geometries to be batched has to be attached directly to the BatchNode
+ * You can't attach a Node to a SimpleBatchNode
+ * SimpleBatchNode is recommended when you have a large number of geometries using the same material that does not require a complex scene graph structure.
+ * @see BatchNode
+ * @author Nehon
+ */
+public class SimpleBatchNode extends BatchNode {
+
+    public SimpleBatchNode() {
+        super();
+    }
+
+    public SimpleBatchNode(String name) {
+        super(name);
+    }
+
+    @Override
+    public int attachChild(Spatial child) {
+
+        if (!(child instanceof Geometry)) {
+            throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child of type Geometry, use BatchMode.Complex to use a complex structure");
+        }
+
+        return super.attachChild(child);
+    }
+
+    @Override
+    protected void setTransformRefresh() {
+
+        refreshFlags |= RF_TRANSFORM;
+        setBoundRefresh();
+        for (Batch batch : batches.values()) {
+            batch.geometry.setTransformRefresh();
+        }
+    }
+    
+     protected Transform getTransforms(Geometry geom){
+        return geom.getLocalTransform();
+    }
+
+    @Override
+    public void batch() {
+        doBatch();
+    }
+}

+ 1 - 2
engine/src/core/com/jme3/scene/Spatial.java

@@ -112,8 +112,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset {
      */
     protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
             RF_BOUND = 0x02,
-            RF_LIGHTLIST = 0x04, // changes in light lists
-            RF_REFRESHBATCH = 0x08; //  chamge in geometry transforms that require refreshing the batched mesh
+            RF_LIGHTLIST = 0x04; // changes in light lists          
     
     protected CullHint cullHint = CullHint.Inherit;
     

+ 6 - 3
engine/src/test/jme3test/batching/TestBatchNode.java

@@ -16,6 +16,7 @@ import com.jme3.scene.BatchNode;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Node;
 import com.jme3.scene.shape.Box;
+import com.jme3.system.NanoTimer;
 import com.jme3.util.TangentBinormalGenerator;
 
 /**
@@ -33,6 +34,7 @@ public class TestBatchNode extends SimpleApplication {
 
     @Override
     public void simpleInitApp() {
+        timer = new NanoTimer();
         batch = new BatchNode("theBatchNode");
 
         /**
@@ -41,7 +43,8 @@ public class TestBatchNode extends SimpleApplication {
          */
         Box boxshape4 = new Box(Vector3f.ZERO, 1f, 1f, 1f );
         cube = new Geometry("cube1", boxshape4);
-        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");        
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");     
+        cube.setMaterial(mat);
 //        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");        
 //        mat.setColor("Diffuse", ColorRGBA.Blue);
 //        mat.setBoolean("UseMaterialColors", true);
@@ -51,7 +54,7 @@ public class TestBatchNode extends SimpleApplication {
          */
         Box box = new Box(Vector3f.ZERO, 1f, 1f, 1f);
         cube2 = new Geometry("cube2", box);
-        
+        cube2.setMaterial(mat);
         
         TangentBinormalGenerator.generate(cube);
         TangentBinormalGenerator.generate(cube2);
@@ -61,7 +64,7 @@ public class TestBatchNode extends SimpleApplication {
        // n.attachChild(cube2);
         batch.attachChild(cube);
         batch.attachChild(cube2);
-        batch.setMaterial(mat);
+      //  batch.setMaterial(mat);
         batch.batch();
         rootNode.attachChild(batch);
         cube.setLocalTranslation(3, 0, 0);

+ 44 - 59
engine/src/test/jme3test/batching/TestBatchNodeCluster.java

@@ -22,6 +22,7 @@ import com.jme3.post.FilterPostProcessor;
 import com.jme3.post.filters.BloomFilter;
 import com.jme3.scene.BatchNode;
 import com.jme3.scene.Node;
+import com.jme3.scene.SimpleBatchNode;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.debug.Arrow;
 import com.jme3.system.NanoTimer;
@@ -60,13 +61,8 @@ public class TestBatchNodeCluster extends SimpleApplication {
     protected int dynamic = 4;
     protected static AppSettings settingst;
     protected boolean isTrue = true;
-
     private int lineLength = 50;
-    protected BatchNode blue;
-    protected BatchNode brown;
-    protected BatchNode pink;
-    protected BatchNode orange;
-  
+    protected BatchNode batchNode;
     Material mat1;
     Material mat2;
     Material mat3;
@@ -77,34 +73,33 @@ public class TestBatchNodeCluster extends SimpleApplication {
 
     @Override
     public void simpleInitApp() {
-       
+        timer = new NanoTimer();
+
+        batchNode = new SimpleBatchNode("BatchNode");
+
 
-        blue = new BatchNode("blue");
-        brown = new BatchNode("brown");
-        pink = new BatchNode("pink");
-        orange = new BatchNode("orange");
-        
         xPosition.add(0);
         yPosition.add(0);
         zPosition.add(0);
-        randomGenerator();
-        
+
         mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         mat1.setColor("Color", ColorRGBA.White);
         mat1.setColor("GlowColor", ColorRGBA.Blue.mult(10));
-        blue.setMaterial(mat1);
+
         mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         mat2.setColor("Color", ColorRGBA.White);
         mat2.setColor("GlowColor", ColorRGBA.Red.mult(10));
-        brown.setMaterial(mat2);
+
         mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         mat3.setColor("Color", ColorRGBA.White);
         mat3.setColor("GlowColor", ColorRGBA.Yellow.mult(10));
-        pink.setMaterial(mat3);
+
         mat4 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         mat4.setColor("Color", ColorRGBA.White);
         mat4.setColor("GlowColor", ColorRGBA.Orange.mult(10));
-        orange.setMaterial(mat4);
+
+        randomGenerator();
+
         //rootNode.attachChild(SkyFactory.createSky(
         //  assetManager, "Textures/SKY02.zip", false));
         inputManager.addMapping("Start Game", new KeyTrigger(KeyInput.KEY_J));
@@ -115,24 +110,17 @@ public class TestBatchNodeCluster extends SimpleApplication {
         cam.setRotation(new Quaternion(0.022630932f, 0.9749435f, -0.18736298f, 0.11776358f));
 
 
-        blue.batch();
-        brown.batch();
-        pink.batch();
-        orange.batch();
+        batchNode.batch();
+
 
         terrain = new Node("terrain");
         terrain.setLocalTranslation(50, 0, 50);
-        terrain.attachChild(blue);
-        terrain.attachChild(brown);
-        terrain.attachChild(pink);
-        terrain.attachChild(orange);
+        terrain.attachChild(batchNode);
+
         flyCam.setMoveSpeed(100);
         rootNode.attachChild(terrain);
         Vector3f pos = new Vector3f(-40, 0, -40);
-        blue.setLocalTranslation(pos);
-        brown.setLocalTranslation(pos);
-        pink.setLocalTranslation(pos);
-        orange.setLocalTranslation(pos);
+        batchNode.setLocalTranslation(pos);
 
 
         Arrow a = new Arrow(new Vector3f(0, 50, 0));
@@ -141,7 +129,7 @@ public class TestBatchNodeCluster extends SimpleApplication {
         Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         m.setColor("Color", ColorRGBA.Blue);
         g.setMaterial(m);
-      
+
 
 
         FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
@@ -159,35 +147,37 @@ public class TestBatchNodeCluster extends SimpleApplication {
             box.setLocalTranslation(new Vector3f(xPosition.get(xPosition.size() - 1),
                     yPosition.get(yPosition.size() - 1),
                     zPosition.get(zPosition.size() - 1)));
-
+            batchNode.attachChild(box);
             if (i < 500) {
-                blue.attachChild(box);
+                box.setMaterial(mat1);
             } else if (i < 1000) {
-                brown.attachChild(box);
+
+                box.setMaterial(mat2);
             } else if (i < 1500) {
-                pink.attachChild(box);
-            } else {
-                orange.attachChild(box);
-            }
 
-        }
-    }
+                box.setMaterial(mat3);
+            } else {
 
-    public BatchNode randomBatch() {
+                box.setMaterial(mat4);
+            }
 
-        int randomn = rand.nextInt(4);
-        if (randomn == 0) {
-            return blue;
-        } else if (randomn == 1) {
-            return brown;
-        } else if (randomn == 2) {
-            return pink;
-        } else if (randomn == 3) {
-            return orange;
         }
-        return null;
     }
 
+//    public BatchNode randomBatch() {
+//
+//        int randomn = rand.nextInt(4);
+//        if (randomn == 0) {
+//            return blue;
+//        } else if (randomn == 1) {
+//            return brown;
+//        } else if (randomn == 2) {
+//            return pink;
+//        } else if (randomn == 3) {
+//            return orange;
+//        }
+//        return null;
+//    }
     public ColorRGBA randomColor() {
         ColorRGBA color = ColorRGBA.Black;
         int randomn = rand.nextInt(4);
@@ -330,32 +320,27 @@ public class TestBatchNodeCluster extends SimpleApplication {
 
     @Override
     public void simpleUpdate(float tpf) {
-             time += tpf;
+        time += tpf;
         int random = rand.nextInt(2000);
         float mult1 = 1.0f;
         float mult2 = 1.0f;
-        BatchNode b = null;
         if (random < 500) {
-            b = blue;
             mult1 = 1.0f;
             mult2 = 1.0f;
         } else if (random < 1000) {
-            b = brown;
             mult1 = -1.0f;
             mult2 = 1.0f;
         } else if (random < 1500) {
-            b = pink;
             mult1 = 1.0f;
             mult2 = -1.0f;
         } else if (random <= 2000) {
-            b = orange;
             mult1 = -1.0f;
             mult2 = -1.0f;
         }
-        box = b.getChild("Box" + random);
+        box = batchNode.getChild("Box" + random);
         if (box != null) {
             Vector3f v = box.getLocalTranslation();
-            box.setLocalTranslation(v.x + FastMath.sin(time * mult1) * 20, v.y +( FastMath.sin(time * mult1)* FastMath.cos(time * mult1)* 20), v.z + FastMath.cos(time * mult2) * 20);
+            box.setLocalTranslation(v.x + FastMath.sin(time * mult1) * 20, v.y + (FastMath.sin(time * mult1) * FastMath.cos(time * mult1) * 20), v.z + FastMath.cos(time * mult2) * 20);
         }
         terrain.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Y));
 

+ 9 - 2
engine/src/test/jme3test/batching/TestBatchNodeTower.java

@@ -54,12 +54,15 @@ import com.jme3.math.Vector3f;
 import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 import com.jme3.scene.BatchNode;
 import com.jme3.scene.Geometry;
+import com.jme3.scene.SimpleBatchNode;
 import com.jme3.scene.shape.Box;
 import com.jme3.scene.shape.Sphere;
 import com.jme3.scene.shape.Sphere.TextureMode;
 import com.jme3.shadow.PssmShadowRenderer;
 import com.jme3.shadow.PssmShadowRenderer.CompareMode;
 import com.jme3.shadow.PssmShadowRenderer.FilterMode;
+import com.jme3.system.AppSettings;
+import com.jme3.system.NanoTimer;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.WrapMode;
 
@@ -90,11 +93,14 @@ public class TestBatchNodeTower extends SimpleApplication {
     
     public static void main(String args[]) {
         TestBatchNodeTower f = new TestBatchNodeTower();
+        AppSettings s = new AppSettings(true);
+        f.setSettings(s);
         f.start();
     }
 
     @Override
     public void simpleInitApp() {
+        timer = new NanoTimer();
         bulletAppState = new BulletAppState();
         bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
      //   bulletAppState.setEnabled(false);
@@ -128,7 +134,7 @@ public class TestBatchNodeTower extends SimpleApplication {
         bsr.setShadowIntensity(0.6f);
         bsr.setCompareMode(CompareMode.Hardware);
         bsr.setFilterMode(FilterMode.PCF4);
-        viewPort.addProcessor(bsr);
+        viewPort.addProcessor(bsr);   
     }
 
     private PhysicsSpace getPhysicsSpace() {
@@ -219,7 +225,7 @@ public class TestBatchNodeTower extends SimpleApplication {
         tex3.setWrap(WrapMode.Repeat);
         mat3.setTexture("ColorMap", tex3);
     }
-
+int nbBrick =0;
     public void addBrick(Vector3f ori) {
         Geometry reBoxg = new Geometry("brick", brick);
         reBoxg.setMaterial(mat);
@@ -230,6 +236,7 @@ public class TestBatchNodeTower extends SimpleApplication {
         reBoxg.getControl(RigidBodyControl.class).setFriction(1.6f);
         this.batchNode.attachChild(reBoxg);
         this.getPhysicsSpace().add(reBoxg);
+        nbBrick++;
     }
 
     protected void initCrossHairs() {