Browse Source

Engine :
- WIP : Added new batching system : GeometryBatch, that allow batched geometries to be transformed


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

rem..om 14 years ago
parent
commit
cd474b2be9

+ 135 - 0
engine/src/core/com/jme3/scene/BatchedGeometry.java

@@ -0,0 +1,135 @@
+/*
+ * To change this template, choose Tools | Templates and open the template in
+ * the editor.
+ */
+package com.jme3.scene;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.UnsupportedCollisionException;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.util.TempVars;
+import java.util.Queue;
+
+/**
+ *
+ * @author Nehon
+ */
+public class BatchedGeometry extends Spatial {
+
+    private GeometryBatch batch;
+    protected int startIndex;
+    protected int vertexCount;
+    protected int triangleCount;
+    protected Transform prevLocalTransform = new Transform();
+    protected Matrix4f cachedOffsetMat = new Matrix4f();
+    protected Matrix4f tmpMat = new Matrix4f();
+    
+
+    protected BatchedGeometry(GeometryBatch batch, Geometry geom) {
+        this.batch = batch;
+        vertexCount = geom.getVertexCount();
+        triangleCount = geom.getTriangleCount();
+        name = geom.getName();
+    }
+
+    /**
+     * Should only be called from updateGeometricState().
+     * In most cases should not be subclassed.
+     */
+    @Override
+    protected void updateWorldTransforms() {
+        if (batch == null) {
+            worldTransform.set(localTransform);
+            refreshFlags &= ~RF_TRANSFORM;
+        } else {
+            // check if transform for parent is updated
+            assert ((batch.refreshFlags & RF_TRANSFORM) == 0);
+            worldTransform.set(localTransform);
+            worldTransform.combineWithParent(batch.worldTransform);          
+            computeOffsetTransform();           
+            batch.updateSubBatch(this);
+            prevLocalTransform.set(localTransform);
+            refreshFlags &= ~RF_TRANSFORM;
+        }
+    }
+
+    @Override
+    public Node getParent() {
+        return batch.getParent();
+    }
+    
+       
+      /**
+     * Recomputes the matrix returned by {@link Geometry#getWorldMatrix() }.
+     * This will require a localized transform update for this geometry.
+     */
+    public void computeOffsetTransform() {     
+     
+        
+        // Compute the cached world matrix
+        cachedOffsetMat.loadIdentity();
+        cachedOffsetMat.setRotationQuaternion(prevLocalTransform.getRotation());
+        cachedOffsetMat.setTranslation(prevLocalTransform.getTranslation());
+
+        TempVars vars = TempVars.get();
+        Matrix4f scaleMat = vars.tempMat4;
+        scaleMat.loadIdentity();
+        scaleMat.scale(prevLocalTransform.getScale());
+        cachedOffsetMat.multLocal(scaleMat);
+        cachedOffsetMat.invertLocal();
+       
+        tmpMat.loadIdentity();
+        tmpMat.setRotationQuaternion(localTransform.getRotation());
+        tmpMat.setTranslation(localTransform.getTranslation());      
+        scaleMat.loadIdentity();
+        scaleMat.scale(localTransform.getScale());
+        tmpMat.multLocal(scaleMat);
+        
+        tmpMat.mult(cachedOffsetMat,cachedOffsetMat);
+        
+        vars.release();
+    }   
+     
+
+    @Override
+    public void updateModelBound() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void setModelBound(BoundingVolume modelBound) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public int getVertexCount() {
+        return vertexCount;
+    }
+
+    @Override
+    public int getTriangleCount() {
+        return triangleCount;
+    }
+
+    @Override
+    public Spatial deepClone() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    public void depthFirstTraversal(SceneGraphVisitor visitor) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    @Override
+    protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+}

+ 398 - 0
engine/src/core/com/jme3/scene/GeometryBatch.java

@@ -0,0 +1,398 @@
+/*
+ * To change this template, choose Tools | Templates and open the template in
+ * the editor.
+ */
+package com.jme3.scene;
+
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.SafeArrayList;
+import com.jme3.util.TempVars;
+import java.nio.Buffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Nehon
+ */
+public class GeometryBatch extends Geometry {
+
+    private SafeArrayList<BatchedGeometry> children = new SafeArrayList<BatchedGeometry>(BatchedGeometry.class);
+    private List<Geometry> tmpList = new ArrayList<Geometry>();
+    private boolean needMeshUpdate = false;
+
+    public GeometryBatch() {
+        this("GeometryBatch");
+    }
+
+    public GeometryBatch(String name) {
+        this.name = name;
+        addControl(new ControlUpdate());
+    }
+
+    public BatchedGeometry batch(Geometry geom) {
+
+        tmpList.clear();
+        Mesh m = new Mesh();
+        if (mesh != null) {
+
+            tmpList.add(this);
+        }
+        tmpList.add(geom);
+        List<BatchedGeometry> l = mergeGeometries(m, tmpList);
+        mesh = m;
+        mesh.updateCounts();
+        mesh.updateBound();
+        return l.get(0);
+
+    }
+
+    public List<BatchedGeometry> batch(List<Geometry> geom) {
+        if (mesh != null) {
+            geom.add(0, this);
+        }
+
+        Mesh m = new Mesh();
+        List<BatchedGeometry> l = mergeGeometries(m, geom);
+        mesh = m;
+        mesh.updateCounts();
+        mesh.updateBound();
+        return l;
+    }
+
+    public List<BatchedGeometry> batch(Geometry... geoms) {
+        tmpList.clear();
+        Mesh m = new Mesh();
+        if (mesh != null) {
+            tmpList.add(this);
+
+        }
+        for (Geometry geometry : geoms) {
+            tmpList.add(geometry);
+        }
+
+        List<BatchedGeometry> l = mergeGeometries(m, tmpList);
+        mesh = m;
+        mesh.updateCounts();
+        mesh.updateBound();
+        return l;
+    }
+
+    @Override
+    public void updateGeometricState() {
+        if ((refreshFlags & RF_LIGHTLIST) != 0) {
+            updateWorldLightList();
+        }
+
+        if ((refreshFlags & RF_TRANSFORM) != 0) {
+            // combine with parent transforms- same for all spatial
+            // subclasses.
+            updateWorldTransforms();
+        }
+
+        if (!children.isEmpty()) {
+            // the important part- make sure child geometric state is refreshed
+            // first before updating own world bound. This saves
+            // a round-trip later on.
+            // NOTE 9/19/09
+            // Although it does save a round trip,
+            for (Spatial child : children.getArray()) {
+                child.updateGeometricState();
+            }
+            if (needMeshUpdate) {
+                updateModelBound();
+                needMeshUpdate = false;
+            }
+        }
+
+        if ((refreshFlags & RF_BOUND) != 0) {
+            updateWorldBound();
+        }
+
+        assert refreshFlags == 0;
+    }
+
+    protected void updateSubBatch(BatchedGeometry bg) {
+        FloatBuffer buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Position).getData();
+        doTransformVerts(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat);
+        mesh.getBuffer(VertexBuffer.Type.Position).updateData(buf);
+
+        buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Normal).getData();
+        doTransformNorm(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat);
+        mesh.getBuffer(VertexBuffer.Type.Normal).updateData(buf);
+
+
+        if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
+            buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Tangent).getData();
+            doTransformNorm(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat);
+            mesh.getBuffer(VertexBuffer.Type.Tangent).updateData(buf);
+        }
+
+        needMeshUpdate = true;
+    }
+
+    /**
+     * <code>getChild</code> returns the first child found with exactly the
+     * given name (case sensitive.)
+     * 
+     * @param name
+     *            the name of the child to retrieve. If null, we'll return null.
+     * @return the child if found, or null.
+     */
+    public Spatial getChild(String name) {
+        if (name == null) {
+            return null;
+        }
+
+        for (Spatial child : children.getArray()) {
+            if (name.equals(child.getName())) {
+                return child;
+            } else if (child instanceof Node) {
+                Spatial out = ((Node) child).getChild(name);
+                if (out != null) {
+                    return out;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Merges all geometries in the collection into
+     * the output mesh. Does not take into account materials.
+     * 
+     * @param geometries
+     * @param outMesh
+     */
+    private List<BatchedGeometry> mergeGeometries(Mesh outMesh, List<Geometry> geometries) {
+        int[] compsForBuf = new int[VertexBuffer.Type.values().length];
+        VertexBuffer.Format[] formatForBuf = new VertexBuffer.Format[compsForBuf.length];
+        List<BatchedGeometry> batchedGeoms = new ArrayList<BatchedGeometry>();
+
+        int totalVerts = 0;
+        int totalTris = 0;
+        int totalLodLevels = 0;
+
+        Mesh.Mode mode = null;
+        for (Geometry geom : geometries) {
+            totalVerts += geom.getVertexCount();
+            totalTris += geom.getTriangleCount();
+            totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());
+
+            Mesh.Mode listMode;
+            int components;
+            switch (geom.getMesh().getMode()) {
+                case Points:
+                    listMode = Mesh.Mode.Points;
+                    components = 1;
+                    break;
+                case LineLoop:
+                case LineStrip:
+                case Lines:
+                    listMode = Mesh.Mode.Lines;
+                    components = 2;
+                    break;
+                case TriangleFan:
+                case TriangleStrip:
+                case Triangles:
+                    listMode = Mesh.Mode.Triangles;
+                    components = 3;
+                    break;
+                default:
+                    throw new UnsupportedOperationException();
+            }
+
+            for (Entry<VertexBuffer> entry : geom.getMesh().getBuffers()) {
+                compsForBuf[entry.getKey()] = entry.getValue().getNumComponents();
+                formatForBuf[entry.getKey()] = entry.getValue().getFormat();
+            }
+
+            if (mode != null && mode != listMode) {
+                throw new UnsupportedOperationException("Cannot combine different"
+                        + " primitive types: " + mode + " != " + listMode);
+            }
+            mode = listMode;
+            compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
+        }
+
+        outMesh.setMode(mode);
+        if (totalVerts >= 65536) {
+            // make sure we create an UnsignedInt buffer so
+            // we can fit all of the meshes
+            formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
+        } else {
+            formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort;
+        }
+
+        // generate output buffers based on retrieved info
+        for (int i = 0; i < compsForBuf.length; i++) {
+            if (compsForBuf[i] == 0) {
+                continue;
+            }
+
+            Buffer data;
+            if (i == VertexBuffer.Type.Index.ordinal()) {
+                data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
+            } else {
+                data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
+            }
+
+            VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.values()[i]);
+            vb.setupData(VertexBuffer.Usage.Static, compsForBuf[i], formatForBuf[i], data);
+            outMesh.setBuffer(vb);
+        }
+
+        int globalVertIndex = 0;
+        int globalTriIndex = 0;
+
+        for (Geometry geom : geometries) {
+            if (geom != this) {
+                BatchedGeometry bg = new BatchedGeometry(this, geom);
+                bg.startIndex = globalVertIndex;
+                bg.setLocalTransform(geom.getLocalTransform());
+                children.add(bg);
+                batchedGeoms.add(bg);
+            }
+            Mesh inMesh = geom.getMesh();            
+
+            int geomVertCount = inMesh.getVertexCount();
+            int geomTriCount = inMesh.getTriangleCount();
+
+            for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
+                VertexBuffer inBuf = inMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
+                VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
+
+                if (outBuf == null) {
+                    continue;
+                }
+
+                if (VertexBuffer.Type.Index.ordinal() == bufType) {
+                    int components = compsForBuf[bufType];
+
+                    IndexBuffer inIdx = inMesh.getIndicesAsList();
+                    IndexBuffer outIdx = outMesh.getIndexBuffer();
+
+                    for (int tri = 0; tri < geomTriCount; tri++) {
+                        for (int comp = 0; comp < components; comp++) {
+                            int idx = inIdx.get(tri * components + comp) + globalVertIndex;
+                            outIdx.put((globalTriIndex + tri) * components + comp, idx);
+                        }
+                    }
+                } else if (VertexBuffer.Type.Position.ordinal() == bufType) {
+                    FloatBuffer inPos = (FloatBuffer) inBuf.getData();
+                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+                    doCopyBuffer(inPos, globalVertIndex, outPos);                   
+                } else if (VertexBuffer.Type.Normal.ordinal() == bufType || VertexBuffer.Type.Tangent.ordinal() == bufType) {
+                    FloatBuffer inPos = (FloatBuffer) inBuf.getData();
+                    FloatBuffer outPos = (FloatBuffer) outBuf.getData();                   
+                    doCopyBuffer(inPos, globalVertIndex, outPos);
+                } else {
+                    for (int vert = 0; vert < geomVertCount; vert++) {
+                        int curGlobalVertIndex = globalVertIndex + vert;
+                        inBuf.copyElement(vert, outBuf, curGlobalVertIndex);
+                    }
+                }
+            }
+
+            globalVertIndex += geomVertCount;
+            globalTriIndex += geomTriCount;
+        }
+        return batchedGeoms;
+    }
+
+    private void doTransformVerts(FloatBuffer inBuf, int offset, int start, int end, FloatBuffer outBuf, Matrix4f transform) {
+        TempVars vars = TempVars.get();
+        Vector3f pos = vars.vect1;
+
+        // offset is given in element units
+        // convert to be in component units
+        offset *= 3;
+
+        for (int i = start; i < end; i++) {
+            pos.x = inBuf.get(i * 3 + 0);
+            pos.y = inBuf.get(i * 3 + 1);
+            pos.z = inBuf.get(i * 3 + 2);
+
+            transform.mult(pos, pos);
+
+            outBuf.put(offset + i * 3 + 0, pos.x);
+            outBuf.put(offset + i * 3 + 1, pos.y);
+            outBuf.put(offset + i * 3 + 2, pos.z);
+        }
+        vars.release();
+    }
+
+    private void doTransformNorm(FloatBuffer inBuf, int offset, int start, int end, FloatBuffer outBuf, Matrix4f transform) {
+        TempVars vars = TempVars.get();
+        Vector3f pos = vars.vect1;
+
+        // offset is given in element units
+        // convert to be in component units
+        offset *= 3;
+
+        for (int i = start; i < end; i++) {
+            pos.x = inBuf.get(i * 3 + 0);
+            pos.y = inBuf.get(i * 3 + 1);
+            pos.z = inBuf.get(i * 3 + 2);
+
+            transform.multNormal(pos, pos);
+
+            outBuf.put(offset + i * 3 + 0, pos.x);
+            outBuf.put(offset + i * 3 + 1, pos.y);
+            outBuf.put(offset + i * 3 + 2, pos.z);
+        }
+        vars.release();
+    }
+
+    private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf) {
+        TempVars vars = TempVars.get();
+        Vector3f pos = vars.vect1;
+
+        // offset is given in element units
+        // convert to be in component units
+        offset *= 3;
+
+        for (int i = 0; i < inBuf.capacity() / 3; i++) {
+            pos.x = inBuf.get(i * 3 + 0);
+            pos.y = inBuf.get(i * 3 + 1);
+            pos.z = inBuf.get(i * 3 + 2);
+
+            outBuf.put(offset + i * 3 + 0, pos.x);
+            outBuf.put(offset + i * 3 + 1, pos.y);
+            outBuf.put(offset + i * 3 + 2, pos.z);
+        }
+        vars.release();
+    }
+
+    public class ControlUpdate extends AbstractControl {
+
+        @Override
+        protected void controlUpdate(float tpf) {
+            for (BatchedGeometry batchedGeometry : children) {
+                for (int i = 0; i < batchedGeometry.getNumControls(); i++) {
+                    batchedGeometry.getControl(i).update(tpf);
+                }
+            }
+        }
+
+        @Override
+        protected void controlRender(RenderManager rm, ViewPort vp) {
+            for (BatchedGeometry batchedGeometry : children) {
+                for (int i = 0; i < batchedGeometry.getNumControls(); i++) {
+                    batchedGeometry.getControl(i).render(rm, vp);
+                }
+            }
+        }
+
+        public com.jme3.scene.control.Control cloneForSpatial(Spatial spatial) {
+            return null;
+        }
+    }
+}

+ 246 - 0
engine/src/test/jme3test/batching/TestBatchedTower.java

@@ -0,0 +1,246 @@
+/*
+ * To change this template, choose Tools | Templates and open the template in
+ * the editor.
+ */
+package jme3test.batching;
+
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+import com.jme3.bullet.BulletAppState;
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.BoxCollisionShape;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.font.BitmapText;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.BatchedGeometry;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.GeometryBatch;
+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.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.util.ArrayList;
+import java.util.List;
+import jme3test.bullet.BombControl;
+
+/**
+ *
+ * @author double1984 (tower mod by atom)
+ */
+public class TestBatchedTower extends SimpleApplication {
+
+    int bricksPerLayer = 8;
+    int brickLayers = 30;
+    static float brickWidth = .75f, brickHeight = .25f, brickDepth = .25f;
+    float radius = 3f;
+    float angle = 0;
+    List<Geometry> geoms = new ArrayList<Geometry>();
+    Material mat;
+    Material mat2;
+    Material mat3;
+    PssmShadowRenderer bsr;
+    private Sphere bullet;
+    private Box brick;
+    private SphereCollisionShape bulletCollisionShape;
+    private GeometryBatch batch = new GeometryBatch();
+    private BulletAppState bulletAppState;
+
+    public static void main(String args[]) {
+        TestBatchedTower f = new TestBatchedTower();
+        f.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        bulletAppState = new BulletAppState();
+        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
+       // bulletAppState.setEnabled(false);
+        stateManager.attach(bulletAppState);
+        bullet = new Sphere(32, 32, 0.4f, true, false);
+        bullet.setTextureMode(TextureMode.Projected);
+        bulletCollisionShape = new SphereCollisionShape(0.4f);
+
+        brick = new Box(Vector3f.ZERO, brickWidth, brickHeight, brickDepth);
+        brick.scaleTextureCoordinates(new Vector2f(1f, .5f));
+      //  bulletAppState.getPhysicsSpace().enableDebug(assetManager);
+        initMaterial();
+        initTower();
+        initFloor();
+        initCrossHairs();
+
+        List<BatchedGeometry> bgs = batch.batch(geoms);
+        for (BatchedGeometry bg : bgs) {
+            RigidBodyControl rb = new RigidBodyControl(1.5f);
+            rb.setCollisionShape(new BoxCollisionShape(new Vector3f(brick.getXExtent(), brick.getYExtent(), brick.getZExtent())));
+            rb.setFriction(1.6f);
+            bg.addControl(rb);
+            this.getPhysicsSpace().add(bg);
+        }
+
+        batch.setMaterial(mat);
+        batch.setShadowMode(ShadowMode.CastAndReceive);
+
+        this.cam.setLocation(new Vector3f(0, 25f, 8f));
+        cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));
+        cam.setFrustumFar(80);
+        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+        inputManager.addListener(actionListener, "shoot");
+        rootNode.setShadowMode(ShadowMode.Off);
+        bsr = new PssmShadowRenderer(assetManager, 1024, 2);
+        bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        bsr.setLambda(0.50f);
+        bsr.setShadowIntensity(0.6f);
+        bsr.setCompareMode(CompareMode.Hardware);
+        bsr.setFilterMode(FilterMode.PCF4);
+        viewPort.addProcessor(bsr);
+        flyCam.setMoveSpeed(50);
+        rootNode.attachChild(batch);
+    }
+
+    private PhysicsSpace getPhysicsSpace() {
+        return bulletAppState.getPhysicsSpace();
+    }
+    private ActionListener actionListener = new ActionListener() {
+
+        public void onAction(String name, boolean keyPressed, float tpf) {
+            if (name.equals("shoot") && !keyPressed) {
+                Geometry bulletg = new Geometry("bullet", bullet);
+                bulletg.setMaterial(mat2);
+                bulletg.setShadowMode(ShadowMode.CastAndReceive);
+                bulletg.setLocalTranslation(cam.getLocation());
+                RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1);
+//                RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1);
+                bulletNode.setLinearVelocity(cam.getDirection().mult(25));
+                bulletg.addControl(bulletNode);
+                rootNode.attachChild(bulletg);
+                getPhysicsSpace().add(bulletNode);
+            }
+        }
+    };
+
+    public void initTower() {
+        double tempX = 0;
+        double tempY = 0;
+        double tempZ = 0;
+        angle = 0f;
+        for (int i = 0; i < brickLayers; i++) {
+            // Increment rows
+            if (i != 0) {
+                tempY += brickHeight * 2;
+            } else {
+                tempY = brickHeight;
+            }
+            // Alternate brick seams
+            angle = 360.0f / bricksPerLayer * i / 2f;
+            for (int j = 0; j < bricksPerLayer; j++) {
+                tempZ = Math.cos(Math.toRadians(angle)) * radius;
+                tempX = Math.sin(Math.toRadians(angle)) * radius;
+                System.out.println("x=" + ((float) (tempX)) + " y=" + ((float) (tempY)) + " z=" + (float) (tempZ));
+                Vector3f vt = new Vector3f((float) (tempX), (float) (tempY), (float) (tempZ));
+                // Add crenelation
+                if (i == brickLayers - 1) {
+                    if (j % 2 == 0) {
+                        addBrick(vt);
+                    }
+                } // Create main tower
+                else {
+                    addBrick(vt);
+                }
+                angle += 360.0 / bricksPerLayer;
+            }
+        }
+
+    }
+
+    public void initFloor() {
+        Box floorBox = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);
+        floorBox.scaleTextureCoordinates(new Vector2f(3, 6));
+
+        Geometry floor = new Geometry("floor", floorBox);
+        floor.setMaterial(mat3);
+        floor.setShadowMode(ShadowMode.Receive);
+        floor.setLocalTranslation(0, 0, 0);
+        floor.addControl(new RigidBodyControl(0));
+        this.rootNode.attachChild(floor);
+        this.getPhysicsSpace().add(floor);
+    }
+
+    public void initMaterial() {
+        mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
+        key.setGenerateMips(true);
+        Texture tex = assetManager.loadTexture(key);
+        mat.setTexture("ColorMap", tex);
+
+        mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
+        key2.setGenerateMips(true);
+        Texture tex2 = assetManager.loadTexture(key2);
+        mat2.setTexture("ColorMap", tex2);
+
+        mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
+        key3.setGenerateMips(true);
+        Texture tex3 = assetManager.loadTexture(key3);
+        tex3.setWrap(WrapMode.Repeat);
+        mat3.setTexture("ColorMap", tex3);
+    }
+
+    public void addBrick(Vector3f ori) {
+        Geometry reBoxg = new Geometry("brick", brick);
+        reBoxg.setLocalTranslation(ori);
+        reBoxg.rotate(0f, (float) Math.toRadians(angle), 0f);
+        geoms.add(reBoxg);
+
+    }
+
+    protected void initCrossHairs() {
+        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+        BitmapText ch = new BitmapText(guiFont, false);
+        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
+        ch.setText("+"); // crosshairs
+        ch.setLocalTranslation( // center
+                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
+                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
+        guiNode.attachChild(ch);
+    }
+}

+ 399 - 0
engine/src/test/jme3test/batching/TestCubeCluster.java

@@ -0,0 +1,399 @@
+/*
+ * To change this template, choose Tools | Templates and open the template in
+ * the editor.
+ */
+package jme3test.batching;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapText;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.AppSettings;
+import com.jme3.input.controls.KeyTrigger;
+import java.util.ArrayList;
+import java.util.Random;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.post.ssao.SSAOFilter;
+import com.jme3.scene.BatchedGeometry;
+import com.jme3.scene.GeometryBatch;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.control.Control;
+import com.jme3.scene.debug.Arrow;
+import java.util.List;
+import jme3test.post.SSAOUI;
+
+public class TestCubeCluster extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestCubeCluster app = new TestCubeCluster();
+        settingst = new AppSettings(true);
+        //settingst.setFrameRate(75);
+        settingst.setResolution(640, 480);
+        settingst.setVSync(false);
+        settingst.setFullscreen(false);
+        app.setSettings(settingst);
+        app.setShowSettings(false);
+        app.start();
+    }
+    private ActionListener al = new ActionListener() {
+
+        public void onAction(String name, boolean isPressed, float tpf) {
+            if (name.equals("Start Game")) {
+//              randomGenerator();
+            }
+        }
+    };
+    protected Random rand = new Random();
+    protected int maxCubes = 2000;
+    protected int startAt = 0;
+    protected static int xPositions = 0, yPositions = 0, zPositions = 0;
+    protected int returner = 0;
+    protected ArrayList<Integer> xPosition = new ArrayList<Integer>();
+    protected ArrayList<Integer> yPosition = new ArrayList<Integer>();
+    protected ArrayList<Integer> zPosition = new ArrayList<Integer>();
+    protected int xLimitf = 60, xLimits = -60, yLimitf = 60, yLimits = -20, zLimitf = 60, zLimits = -60;
+    protected int circ = 8;//increases by 8 every time.
+    protected int dynamic = 4;
+    protected static AppSettings settingst;
+    protected boolean isTrue = true;
+    private BitmapText helloText2;
+    private int lineLength = 50;
+    protected GeometryBatch blue;
+    protected GeometryBatch brown;
+    protected GeometryBatch pink;
+    protected GeometryBatch orange;
+    protected List<Geometry> blueList = new ArrayList<Geometry>();
+    protected List<Geometry> brownList = new ArrayList<Geometry>();
+    protected List<Geometry> pinkList = new ArrayList<Geometry>();
+    protected List<Geometry> orangeList = new ArrayList<Geometry>();
+    Material mat1;
+    Material mat2;
+    Material mat3;
+    Material mat4;
+    Node terrain;
+    //protected
+//    protected Geometry player;
+
+    @Override
+    public void simpleInitApp() {
+
+
+        blue = new GeometryBatch("blue");
+        brown = new GeometryBatch("brown");
+        pink = new GeometryBatch("pink");
+        orange = new GeometryBatch("orange");
+        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);
+        //rootNode.attachChild(SkyFactory.createSky(
+        //  assetManager, "Textures/SKY02.zip", false));
+        inputManager.addMapping("Start Game", new KeyTrigger(KeyInput.KEY_J));
+        inputManager.addListener(al, new String[]{"Start Game"});
+
+
+        cam.setLocation(new Vector3f(-34.403286f, 126.65158f, 434.791f));
+        cam.setRotation(new Quaternion(0.022630932f, 0.9749435f, -0.18736298f, 0.11776358f));
+
+
+        //ChaseCamera c = new ChaseCamera();
+        //flyCam.setEnabled(false);
+        xPosition.add(0);
+        yPosition.add(0);
+        zPosition.add(0);
+
+
+        randomGenerator();
+
+
+        List<BatchedGeometry> l = blue.batch(blueList);
+//        for (Iterator<BatchedGeometry> it = l.iterator(); it.hasNext();) {
+//            BatchedGeometry batchedGeometry = it.next();
+//            batchedGeometry.addControl(new MyControl());
+//        }
+        l = brown.batch(brownList);
+//        for (Iterator<BatchedGeometry> it = l.iterator(); it.hasNext();) {
+//            BatchedGeometry batchedGeometry = it.next();
+//            batchedGeometry.addControl(new MyControl());
+//        }
+        l = pink.batch(pinkList);
+//        for (Iterator<BatchedGeometry> it = l.iterator(); it.hasNext();) {
+//            BatchedGeometry batchedGeometry = it.next();
+//            batchedGeometry.addControl(new MyControl());
+//        }
+
+        l = orange.batch(orangeList);
+//        for (Iterator<BatchedGeometry> it = l.iterator(); it.hasNext();) {
+//            BatchedGeometry batchedGeometry = it.next();
+//            batchedGeometry.addControl(new MyControl());
+//            
+//        }
+        //    GeometryBatchFactory.optimize(terrain);
+        terrain = new Node("terrain");
+        terrain.setLocalTranslation(50, 0, 50);
+        terrain.attachChild(blue);
+        terrain.attachChild(brown);
+        terrain.attachChild(pink);
+        terrain.attachChild(orange);
+        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);
+
+
+        Arrow a = new Arrow(new Vector3f(0, 50, 0));
+        Geometry g = new Geometry("a", a);
+        g.setLocalTranslation(terrain.getLocalTranslation());
+        Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        m.setColor("Color", ColorRGBA.Blue);
+        g.setMaterial(m);
+        //    rootNode.attachChild(g);
+
+
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        fpp.addFilter(new BloomFilter(BloomFilter.GlowMode.Objects));
+//        SSAOFilter ssao = new SSAOFilter(8.630104f,22.970434f,2.9299977f,0.2999997f);    
+//        fpp.addFilter(ssao);
+        viewPort.addProcessor(fpp);
+        //   viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+    }
+
+    public void randomGenerator() {
+        for (int i = startAt; i < maxCubes - 1; i++) {
+            randomize();
+            Geometry box = new Geometry("Box" + i, new Box(Vector3f.ZERO, 1, 1, 1));
+            box.setLocalTranslation(new Vector3f(xPosition.get(xPosition.size() - 1),
+                    yPosition.get(yPosition.size() - 1),
+                    zPosition.get(zPosition.size() - 1)));
+
+            if (i < 500) {
+                blueList.add(box);
+            } else if (i < 1000) {
+                brownList.add(box);
+            } else if (i < 1500) {
+                pinkList.add(box);
+            } else {
+                orangeList.add(box);
+            }
+
+        }
+    }
+
+    public GeometryBatch 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);
+        if (randomn == 0) {
+            color = ColorRGBA.Orange;
+        } else if (randomn == 1) {
+            color = ColorRGBA.Blue;
+        } else if (randomn == 2) {
+            color = ColorRGBA.Brown;
+        } else if (randomn == 3) {
+            color = ColorRGBA.Magenta;
+        }
+        return color;
+    }
+
+    public void randomize() {
+        int xpos = xPosition.get(xPosition.size() - 1);
+        int ypos = yPosition.get(yPosition.size() - 1);
+        int zpos = zPosition.get(zPosition.size() - 1);
+        int x = 0;
+        int y = 0;
+        int z = 0;
+        boolean unTrue = true;
+        while (unTrue) {
+            unTrue = false;
+            boolean xChanged = false;
+            x = 0;
+            y = 0;
+            z = 0;
+            if (xpos >= lineLength * 2) {
+                x = 2;
+                xChanged = true;
+            } else {
+                x = xPosition.get(xPosition.size() - 1) + 2;
+            }
+            if (xChanged) {
+                //y = yPosition.get(yPosition.size() - lineLength) + 2;
+            } else {
+                y = rand.nextInt(3);
+                if (yPosition.size() > lineLength) {
+                    if (yPosition.size() > 51) {
+                        if (y == 0 && ypos < yLimitf && getym(lineLength) > ypos - 2) {
+                            y = ypos + 2;
+                        } else if (y == 1 && ypos > yLimits && getym(lineLength) < ypos + 2) {
+                            y = ypos - 2;
+                        } else if (y == 2 && getym(lineLength) > ypos - 2 && getym(lineLength) < ypos + 2) {
+                            y = ypos;
+                        } else {
+                            if (ypos >= yLimitf) {
+                                y = ypos - 2;
+                            } else if (ypos <= yLimits) {
+                                y = ypos + 2;
+                            } else if (y == 0 && getym(lineLength) >= ypos - 4) {
+                                y = ypos - 2;
+                            } else if (y == 0 && getym(lineLength) >= ypos - 2) {
+                                y = ypos;
+                            } else if (y == 1 && getym(lineLength) >= ypos + 4) {
+                                y = ypos + 2;
+                            } else if (y == 1 && getym(lineLength) >= ypos + 2) {
+                                y = ypos;
+                            } else if (y == 2 && getym(lineLength) <= ypos - 2) {
+                                y = ypos - 2;
+                            } else if (y == 2 && getym(lineLength) >= ypos + 2) {
+                                y = ypos + 2;
+                            } else {
+                                System.out.println("wtf");
+                            }
+                        }
+                    } else if (yPosition.size() == lineLength) {
+                        if (y == 0 && ypos < yLimitf) {
+                            y = getym(lineLength) + 2;
+                        } else if (y == 1 && ypos > yLimits) {
+                            y = getym(lineLength) - 2;
+                        }
+                    }
+                } else {
+                    if (y == 0 && ypos < yLimitf) {
+                        y = ypos + 2;
+                    } else if (y == 1 && ypos > yLimits) {
+                        y = ypos - 2;
+                    } else if (y == 2) {
+                        y = ypos;
+                    } else if (y == 0 && ypos >= yLimitf) {
+                        y = ypos - 2;
+                    } else if (y == 1 && ypos <= yLimits) {
+                        y = ypos + 2;
+                    }
+                }
+            }
+            if (xChanged) {
+                z = zpos + 2;
+            } else {
+                z = zpos;
+            }
+//          for (int i = 0; i < xPosition.size(); i++)
+//          {
+//              if (x - xPosition.get(i) <= 1 && x - xPosition.get(i) >= -1 &&
+//                      y - yPosition.get(i) <= 1 && y - yPosition.get(i) >= -1
+//                      &&z - zPosition.get(i) <= 1 && z - zPosition.get(i) >=
+//                      -1)
+//              {
+//                  unTrue = true;
+//              }
+//          }
+        }
+        xPosition.add(x);
+        yPosition.add(y);
+        zPosition.add(z);
+    }
+
+    public int getxm(int i) {
+        return xPosition.get(xPosition.size() - i);
+    }
+
+    public int getym(int i) {
+        return yPosition.get(yPosition.size() - i);
+    }
+
+    public int getzm(int i) {
+        return zPosition.get(zPosition.size() - i);
+    }
+
+    public int getx(int i) {
+        return xPosition.get(i);
+    }
+
+    public int gety(int i) {
+        return yPosition.get(i);
+    }
+
+    public int getz(int i) {
+        return zPosition.get(i);
+    }
+    long nbFrames = 0;
+    long cullTime = 0;
+    float time = 0;
+    Vector3f lookAtPos = new Vector3f(0, 0, 0);
+    float xpos = 0;
+    Spatial box;
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        time += tpf;
+        int random = rand.nextInt(2000);
+        float mult1 = 1.0f;
+        float mult2 = 1.0f;
+        GeometryBatch 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);
+        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);
+        }
+        terrain.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Y));
+
+
+    }
+}

+ 92 - 0
engine/src/test/jme3test/batching/TestGeometryBatch.java

@@ -0,0 +1,92 @@
+/*
+ * To change this template, choose Tools | Templates and open the template in
+ * the editor.
+ */
+package jme3test.batching;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.BatchedGeometry;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.GeometryBatch;
+import com.jme3.scene.shape.Box;
+import com.jme3.util.TangentBinormalGenerator;
+
+/**
+ *
+ * @author Nehon
+ */
+public class TestGeometryBatch extends SimpleApplication {
+
+    public static void main(String[] args) {
+
+        TestGeometryBatch app = new TestGeometryBatch();
+        app.start();
+    }
+    GeometryBatch batch;
+
+    @Override
+    public void simpleInitApp() {
+        batch = new GeometryBatch("MyBatch");
+
+        /**
+         * A cube with a color "bleeding" through transparent texture. Uses
+         * Texture from jme3-test-data library!
+         */
+        Box boxshape4 = new Box(Vector3f.ZERO, 1f, 1f, 1f);
+        Geometry cube = new Geometry("cube1", boxshape4);
+        Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");        
+//        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");        
+//        mat.setColor("Diffuse", ColorRGBA.Blue);
+//        mat.setBoolean("UseMaterialColors", true);
+        /**
+         * A cube with a color "bleeding" through transparent texture. Uses
+         * Texture from jme3-test-data library!
+         */
+        Box box = new Box(Vector3f.ZERO, 1f, 1f, 1f);
+        cube2 = new Geometry("cube2", box);
+        cube.setLocalTranslation(3, 0, 0);
+        cube2.setLocalTranslation(0, 3, 0);
+        
+        TangentBinormalGenerator.generate(cube);
+        TangentBinormalGenerator.generate(cube2);
+
+
+        batch.batch(cube,cube2);
+
+        batchedCube1 = (BatchedGeometry) batch.getChild("cube1");
+        batchedCube2 = (BatchedGeometry) batch.getChild("cube2");
+
+        batch.setMaterial(mat);
+        rootNode.attachChild(batch);
+
+
+         dl=new DirectionalLight();
+        dl.setColor(ColorRGBA.White.mult(2));
+        dl.setDirection(new Vector3f(1, -1, -1));
+        rootNode.addLight(dl);
+        flyCam.setMoveSpeed(10);
+    }
+    BatchedGeometry batchedCube1;
+    BatchedGeometry batchedCube2;
+    Geometry cube2;
+    float time = 0;
+    DirectionalLight dl;
+    @Override
+    public void simpleUpdate(float tpf) {
+        time += tpf;
+        dl.setDirection(cam.getDirection());
+        batchedCube2.setLocalTranslation(FastMath.sin(-time)*3, FastMath.cos(time)*3, 0);        
+        batchedCube2.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z));
+        batchedCube2.setLocalScale(Math.max(FastMath.sin(time),0.5f));
+
+        batch.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z));
+        
+    }
+//    
+}