Pārlūkot izejas kodu

Merge branch 'master' of https://github.com/jMonkeyEngine/jmonkeyengine

abies 11 gadi atpakaļ
vecāks
revīzija
bd69385571
32 mainītis faili ar 1279 papildinājumiem un 588 dzēšanām
  1. 0 1
      jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java
  2. 3 1
      jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java
  3. 2 1
      jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java
  4. 225 19
      jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java
  5. 12 1
      jme3-core/src/main/java/com/jme3/animation/Bone.java
  6. 23 0
      jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java
  7. 5 4
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  8. 5 1
      jme3-core/src/main/java/com/jme3/material/Material.java
  9. 4 4
      jme3-core/src/main/java/com/jme3/math/ColorRGBA.java
  10. 1 1
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  11. 1 1
      jme3-core/src/main/java/com/jme3/renderer/Statistics.java
  12. 2 6
      jme3-core/src/main/java/com/jme3/scene/BatchNode.java
  13. 9 7
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  14. 14 0
      jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java
  15. 25 1
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  16. 57 19
      jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
  17. 174 341
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
  18. 335 0
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
  19. 24 1
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  20. 13 9
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert
  21. 2 0
      jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md
  22. 17 1
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
  23. 1 0
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md
  24. 2 4
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert
  25. 3 4
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert
  26. 2 4
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert
  27. 18 5
      jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib
  28. 4 2
      jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert
  29. 93 0
      jme3-examples/src/main/java/jme3test/bullet/TestIK.java
  30. 192 0
      jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java
  31. 0 146
      jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java
  32. 11 4
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java

+ 0 - 1
jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java

@@ -6,7 +6,6 @@ import android.opengl.ETC1Util.ETC1Texture;
 import android.opengl.GLES20;
 import android.opengl.GLUtils;
 import com.jme3.asset.AndroidImageInfo;
-import com.jme3.math.FastMath;
 import com.jme3.renderer.RendererException;
 import com.jme3.texture.Image;
 import com.jme3.texture.Image.Format;

+ 3 - 1
jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java

@@ -5,6 +5,7 @@ import com.jme3.asset.AndroidImageInfo;
 import com.jme3.asset.AssetInfo;
 import com.jme3.asset.AssetLoader;
 import com.jme3.texture.Image;
+import com.jme3.texture.image.ColorSpace;
 import java.io.IOException;
 
 public class AndroidImageLoader implements AssetLoader {
@@ -13,7 +14,8 @@ public class AndroidImageLoader implements AssetLoader {
         AndroidImageInfo imageInfo = new AndroidImageInfo(info);
         Bitmap bitmap = imageInfo.getBitmap();
         
-        Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null);
+        Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null, ColorSpace.sRGB);
+        
         image.setEfficentData(imageInfo);
         return image;
     }

+ 2 - 1
jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java

@@ -5,6 +5,7 @@ import com.jme3.asset.AssetLoadException;
 import com.jme3.asset.AssetLoader;
 import com.jme3.asset.TextureKey;
 import com.jme3.texture.Image;
+import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.BufferUtils;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -90,7 +91,7 @@ public class AndroidNativeImageLoader  implements AssetLoader {
         BufferUtils.destroyDirectBuffer(origDataBuffer);
         BufferUtils.destroyDirectBuffer(headerDataBuffer);
 
-        Image img = new Image(getImageFormat(numComponents), width, height, imageDataBuffer);
+        Image img = new Image(getImageFormat(numComponents), width, height, imageDataBuffer, ColorSpace.sRGB);
 
         return img;
     }

+ 225 - 19
jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java

@@ -52,6 +52,7 @@ import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.Savable;
+import com.jme3.math.FastMath;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 import com.jme3.renderer.RenderManager;
@@ -113,11 +114,17 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
     protected float eventDispatchImpulseThreshold = 10;
     protected float rootMass = 15;
     protected float totalMass = 0;
+    private Map<String, Vector3f> ikTargets = new HashMap<String, Vector3f>();
+    private Map<String, Integer> ikChainDepth = new HashMap<String, Integer>();
+    private float ikRotSpeed = 7f;
+    private float limbDampening = 0.6f;
 
+    private float IKThreshold = 0.1f;
     public static enum Mode {
 
         Kinematic,
-        Ragdoll
+        Ragdoll,
+        IK
     }
 
     public class PhysicsBoneLink implements Savable {
@@ -189,9 +196,10 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
         if (!enabled) {
             return;
         }
-
-        //if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space.
-        if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) {
+        if(mode == Mode.IK){
+            ikUpdate(tpf);
+        } else if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) {
+            //if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space.
             ragDollUpdate(tpf);
         } else {
             kinematicUpdate(tpf);
@@ -260,6 +268,9 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
         Quaternion tmpRot2 = vars.quat2;
         Vector3f position = vars.vect1;
         for (PhysicsBoneLink link : boneLinks.values()) {
+//            if(link.usedbyIK){
+//                continue;
+//            }
             //if blended control this means, keyframed animation is updating the skeleton, 
             //but to allow smooth transition, we blend this transformation with the saved position of the ragdoll
             if (blendedControl) {
@@ -300,6 +311,94 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
         }
         vars.release();
     }
+    private void ikUpdate(float tpf){
+        TempVars vars = TempVars.get();
+
+        Quaternion tmpRot1 = vars.quat1;
+        Quaternion[] tmpRot2 = new Quaternion[]{vars.quat2, new Quaternion()};
+
+        Iterator<String> it = ikTargets.keySet().iterator();
+        float distance;
+        Bone bone;
+        String boneName;
+        while (it.hasNext()) {
+            
+            boneName = it.next();
+            bone = (Bone) boneLinks.get(boneName).bone;
+            if (!bone.hasUserControl()) {
+                Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.FINE, "{0} doesn't have user control", boneName);
+                continue;
+            }
+            distance = bone.getModelSpacePosition().distance(ikTargets.get(boneName));
+            if (distance < IKThreshold) {
+                Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.FINE, "Distance is close enough");
+                continue;
+            }
+            int depth = 0;
+            int maxDepth = ikChainDepth.get(bone.getName());
+            updateBone(boneLinks.get(bone.getName()), tpf * (float) FastMath.sqrt(distance), vars, tmpRot1, tmpRot2, bone, ikTargets.get(boneName), depth, maxDepth);
+
+            Vector3f position = vars.vect1;
+            
+            for (PhysicsBoneLink link : boneLinks.values()) {
+                matchPhysicObjectToBone(link, position, tmpRot1);
+            }
+        }
+        vars.release();
+    }
+    
+    public void updateBone(PhysicsBoneLink link, float tpf, TempVars vars, Quaternion tmpRot1, Quaternion[] tmpRot2, Bone tipBone, Vector3f target, int depth, int maxDepth) {
+        if (link == null || link.bone.getParent() == null) {
+            return;
+        }
+        Quaternion preQuat = link.bone.getLocalRotation();
+        Vector3f vectorAxis;
+        
+        float[] measureDist = new float[]{Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY};
+        for (int dirIndex = 0; dirIndex < 3; dirIndex++) {
+            if (dirIndex == 0) {
+                vectorAxis = Vector3f.UNIT_Z;
+            } else if (dirIndex == 1) {
+                vectorAxis = Vector3f.UNIT_X;
+            } else {
+                vectorAxis = Vector3f.UNIT_Y;
+            }
+
+            for (int posOrNeg = 0; posOrNeg < 2; posOrNeg++) {
+                float rot = ikRotSpeed * tpf / (link.rigidBody.getMass() * 2);
+
+                rot = FastMath.clamp(rot, link.joint.getRotationalLimitMotor(dirIndex).getLoLimit(), link.joint.getRotationalLimitMotor(dirIndex).getHiLimit());
+                tmpRot1.fromAngleAxis(rot, vectorAxis);
+//                tmpRot1.fromAngleAxis(rotSpeed * tpf / (link.rigidBody.getMass() * 2), vectorAxis);
+                
+                
+                tmpRot2[posOrNeg] = link.bone.getLocalRotation().mult(tmpRot1);
+                tmpRot2[posOrNeg].normalizeLocal();
+
+                ikRotSpeed = -ikRotSpeed;
+               
+                link.bone.setLocalRotation(tmpRot2[posOrNeg]);
+                link.bone.update();
+                measureDist[posOrNeg] = tipBone.getModelSpacePosition().distance(target);
+                link.bone.setLocalRotation(preQuat);
+            }
+
+            if (measureDist[0] < measureDist[1]) {
+                link.bone.setLocalRotation(tmpRot2[0]);
+            } else if (measureDist[0] > measureDist[1]) {
+                link.bone.setLocalRotation(tmpRot2[1]);
+            }
+
+        }
+        link.bone.getLocalRotation().normalizeLocal();
+
+        link.bone.update();
+//        link.usedbyIK = true;
+        if (link.bone.getParent() != null && depth < maxDepth) {
+            
+            updateBone(boneLinks.get(link.bone.getParent().getName()), tpf * limbDampening, vars, tmpRot1, tmpRot2, tipBone, target, depth + 1, maxDepth);
+        }
+    }
 
     /**
      * Set the transforms of a rigidBody to match the transforms of a bone. this
@@ -618,23 +717,28 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
         animControl.setEnabled(mode == Mode.Kinematic);
 
         baseRigidBody.setKinematic(mode == Mode.Kinematic);
-        TempVars vars = TempVars.get();
-
-        for (PhysicsBoneLink link : boneLinks.values()) {
-            link.rigidBody.setKinematic(mode == Mode.Kinematic);
-            if (mode == Mode.Ragdoll) {
-                Quaternion tmpRot1 = vars.quat1;
-                Vector3f position = vars.vect1;
-                //making sure that the ragdoll is at the correct place.
-                matchPhysicObjectToBone(link, position, tmpRot1);
+		if (mode != Mode.IK) {
+			TempVars vars = TempVars.get();
+
+			for (PhysicsBoneLink link : boneLinks.values()) {
+				link.rigidBody.setKinematic(mode == Mode.Kinematic);
+				if (mode == Mode.Ragdoll) {
+					Quaternion tmpRot1 = vars.quat1;
+					Vector3f position = vars.vect1;
+					//making sure that the ragdoll is at the correct place.
+					matchPhysicObjectToBone(link, position, tmpRot1);
+				}
+
+			}
+			vars.release();
+		}
+
+        if(mode != Mode.IK){
+            for (Bone bone : skeleton.getRoots()) {
+                RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll);
             }
-
-        }
-        vars.release();
-
-        for (Bone bone : skeleton.getRoots()) {
-            RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll);
         }
+        
     }
 
     /**
@@ -703,6 +807,16 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
         }
     }
 
+    /**
+     * Sets the control into Inverse Kinematics mode. The affected bones are affected by IK.
+     * physics.
+     */
+    public void setIKMode() {
+        if (mode != Mode.IK) {
+            setMode(Mode.IK);
+        }
+    }
+    
     /**
      * retruns the mode of this control
      *
@@ -804,7 +918,97 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
         control.setApplyPhysicsLocal(applyLocal);
         return control;
     }
+   
+    public Vector3f setIKTarget(Bone bone, Vector3f worldPos, int chainLength) {
+        Vector3f target = worldPos.subtract(targetModel.getWorldTranslation());
+        ikTargets.put(bone.getName(), target);
+        ikChainDepth.put(bone.getName(), chainLength);
+        int i = 0;
+        while (i < chainLength+2 && bone.getParent() != null) {
+            if (!bone.hasUserControl()) {
+                bone.setUserControl(true);
+            }
+            bone = bone.getParent();
+            i++;
+        }
+
+
+//        setIKMode();
+        return target;
+    }
+
+    public void removeIKTarget(Bone bone) {
+        int depth = ikChainDepth.remove(bone.getName());
+        int i = 0;
+        while (i < depth+2 && bone.getParent() != null) {
+            if (bone.hasUserControl()) {
+//                matchPhysicObjectToBone(boneLinks.get(bone.getName()), position, tmpRot1);
+                bone.setUserControl(false);
+            }
+            bone = bone.getParent();
+            i++;
+        }
+    }
+    
+    public void removeAllIKTargets(){
+        ikTargets.clear();
+        ikChainDepth.clear();
+        applyUserControl();
+    }
+    public void applyUserControl() {
+        for (Bone bone : skeleton.getRoots()) {
+            RagdollUtils.setUserControl(bone, false);
+        }
+
+        if (ikTargets.isEmpty()) {
+            setKinematicMode();
+        } else {
+            Iterator iterator = ikTargets.keySet().iterator();
+
+            TempVars vars = TempVars.get();
+
+            while (iterator.hasNext()) {
+                Bone bone = (Bone) iterator.next();
+                while (bone.getParent() != null) {
+
+                    Quaternion tmpRot1 = vars.quat1;
+                    Vector3f position = vars.vect1;
+                    matchPhysicObjectToBone(boneLinks.get(bone.getName()), position, tmpRot1);
+                    bone.setUserControl(true);
+                    bone = bone.getParent();
+                }
+            }
+            vars.release();
+        }
+    }
+    public float getIkRotSpeed() {
+        return ikRotSpeed;
+    }
+
+    public void setIkRotSpeed(float ikRotSpeed) {
+        this.ikRotSpeed = ikRotSpeed;
+    }
+
+    public float getIKThreshold() {
+        return IKThreshold;
+    }
+
+    public void setIKThreshold(float IKThreshold) {
+        this.IKThreshold = IKThreshold;
+    }
 
+    
+    public float getLimbDampening() {
+        return limbDampening;
+    }
+
+    public void setLimbDampening(float limbDampening) {
+        this.limbDampening = limbDampening;
+    }
+    
+    public Bone getBone(String name){
+        return skeleton.getBone(name);
+    }
     /**
      * serialize this control
      *
@@ -831,6 +1035,8 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
         oc.write(eventDispatchImpulseThreshold, "eventDispatchImpulseThreshold", 10);
         oc.write(rootMass, "rootMass", 15);
         oc.write(totalMass, "totalMass", 0);
+        oc.write(ikRotSpeed, "rotSpeed", 7f);
+        oc.write(limbDampening, "limbDampening", 0.6f);
     }
 
     /**

+ 12 - 1
jme3-core/src/main/java/com/jme3/animation/Bone.java

@@ -459,7 +459,7 @@ public final class Bone implements Savable {
     /**
      * Updates world transforms for this bone and it's children.
      */
-    final void update() {
+    public final void update() {
         this.updateModelTransforms();
 
         for (int i = children.size() - 1; i >= 0; i--) {
@@ -796,4 +796,15 @@ public final class Bone implements Savable {
         output.write(bindScale, "bindScale", new Vector3f(1.0f, 1.0f, 1.0f));
         output.writeSavableArrayList(children, "children", null);
     }
+
+    public void setLocalRotation(Quaternion rot){
+        if (!userControl) {
+            throw new IllegalStateException("User control must be on bone to allow user transforms");
+        }
+        this.localRot = rot;
+    }
+    
+    public boolean hasUserControl(){
+        return userControl;
+    }
 }

+ 23 - 0
jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java

@@ -221,6 +221,24 @@ public class AnimationEvent extends AbstractCinematicEvent {
         initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
         this.channelIndex = channelIndex;
     }
+    
+    /**
+     * creates an animation event
+     *
+     * @param model the model on which the animation will be played
+     * @param animationName the name of the animation to play
+     * @param channelIndex the index of the channel default is 0. Events on the
+     * @param blendTime the time during the animation are gonna be blended
+     * same channelIndex will use the same channel.
+     */
+    public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, int channelIndex, float blendTime) {
+        this.model = model;
+        this.animationName = animationName;
+        this.loopMode = loopMode;
+        initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
+        this.channelIndex = channelIndex;
+        this.blendTime = blendTime;
+    }
 
     /**
      * creates an animation event
@@ -264,6 +282,10 @@ public class AnimationEvent extends AbstractCinematicEvent {
             Object s = cinematic.getEventData(MODEL_CHANNELS, model);
             if (s == null) {
                 s = new HashMap<Integer, AnimChannel>();
+                int numChannels = model.getControl(AnimControl.class).getNumChannels();
+                for(int i = 0; i < numChannels; i++){
+                    ((HashMap<Integer, AnimChannel>)s).put(i, model.getControl(AnimControl.class).getChannel(i));
+                }
                 cinematic.putEventData(MODEL_CHANNELS, model, s);
             }
 
@@ -319,6 +341,7 @@ public class AnimationEvent extends AbstractCinematicEvent {
             channel.setTime(t);
             channel.getControl().update(0);
         }
+
     }
 
     @Override

+ 5 - 4
jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java

@@ -167,11 +167,12 @@ public class ParticleEmitter extends Geometry {
         clone.endColor = endColor.clone();
         clone.particleInfluencer = particleInfluencer.clone();
 
-        // remove wrong control
-        clone.controls.remove(control);
+        // remove original control from the clone
+        clone.controls.remove(this.control);
 
-        // put correct control
-        clone.controls.add(new ParticleEmitterControl(clone));
+        // put clone's control in
+        clone.control = new ParticleEmitterControl(clone);
+        clone.controls.add(clone.control);
 
         // Reinitialize particle mesh
         switch (meshType) {

+ 5 - 1
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -702,7 +702,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         int lodLevel = geom.getLodLevel();
         if (geom instanceof InstancedGeometry) {
             InstancedGeometry instGeom = (InstancedGeometry) geom;
-            renderer.renderMesh(mesh, lodLevel, instGeom.getCurrentNumInstances(), instGeom.getAllInstanceData());
+            int numInstances = instGeom.getActualNumInstances();
+            if (numInstances == 0) {
+                return;
+            }
+            renderer.renderMesh(mesh, lodLevel, numInstances, instGeom.getAllInstanceData());
         } else {
             renderer.renderMesh(mesh, lodLevel, 1, null);
         }

+ 4 - 4
jme3-core/src/main/java/com/jme3/math/ColorRGBA.java

@@ -206,10 +206,10 @@ public final class ColorRGBA implements Savable, Cloneable, java.io.Serializable
      * Saturate that color ensuring all channels have a value between 0 and 1
      */
     public void clamp() {
-        FastMath.clamp(r, 0f, 1f);
-        FastMath.clamp(g, 0f, 1f);
-        FastMath.clamp(b, 0f, 1f);
-        FastMath.clamp(a, 0f, 1f);        
+        r = FastMath.clamp(r, 0f, 1f);
+        g = FastMath.clamp(g, 0f, 1f);
+        b = FastMath.clamp(b, 0f, 1f);
+        a = FastMath.clamp(a, 0f, 1f);
     }
 
     /**

+ 1 - 1
jme3-core/src/main/java/com/jme3/renderer/RenderManager.java

@@ -575,7 +575,7 @@ public class RenderManager {
             Geometry gm = (Geometry) s;
 
             RenderQueue.ShadowMode shadowMode = s.getShadowMode();
-            if (shadowMode != RenderQueue.ShadowMode.Off && shadowMode != RenderQueue.ShadowMode.Receive) {
+            if (shadowMode != RenderQueue.ShadowMode.Off && shadowMode != RenderQueue.ShadowMode.Receive && !gm.isGrouped()) {
                 //forcing adding to shadow cast mode, culled objects doesn't have to be in the receiver queue
                 rq.addToShadowQueue(gm, RenderQueue.ShadowMode.Cast);
             }

+ 1 - 1
jme3-core/src/main/java/com/jme3/renderer/Statistics.java

@@ -125,7 +125,7 @@ public class Statistics {
         if( !enabled )
             return;
             
-        numObjects += count;
+        numObjects += 1;
         numTriangles += mesh.getTriangleCount(lod) * count;
         numVertices += mesh.getVertexCount() * count;
     }

+ 2 - 6
jme3-core/src/main/java/com/jme3/scene/BatchNode.java

@@ -773,10 +773,6 @@ public class BatchNode extends GeometryGroupNode implements Savable {
         this.needsFullRebatch = needsFullRebatch;
     }
 
-    public int getOffsetIndex(Geometry batchedGeometry) {
-        return batchedGeometry.startIndex;
-    }
-        
     @Override
     public Node clone(boolean cloneMaterials) {
         BatchNode clone = (BatchNode)super.clone(cloneMaterials);
@@ -790,8 +786,8 @@ public class BatchNode extends GeometryGroupNode implements Savable {
                 }
             }
             clone.needsFullRebatch = true;
-            clone.batches.clear();
-            clone.batchesByGeom.clear();
+            clone.batches = new SafeArrayList<Batch>(Batch.class);
+            clone.batchesByGeom = new HashMap<Geometry, Batch>();
             clone.batch();
         }
         return clone;

+ 9 - 7
jme3-core/src/main/java/com/jme3/scene/Geometry.java

@@ -81,7 +81,7 @@ public class Geometry extends Spatial {
      * The start index of this <code>Geometry's</code> inside 
      * the {@link GeometryGroupNode}.
      */
-    protected int startIndex;    
+    protected int startIndex = -1;    
     /**
      * Serialization only. Do not use.
      */
@@ -316,7 +316,7 @@ public class Geometry extends Spatial {
      * @param node Which {@link GeometryGroupNode} to associate with.
      * @param startIndex The starting index of this geometry in the group.
      */
-    protected void associateWithGroupNode(GeometryGroupNode node, int startIndex) {
+    public void associateWithGroupNode(GeometryGroupNode node, int startIndex) {
         if (isGrouped()) {
             unassociateFromGroupNode();
         }
@@ -331,13 +331,15 @@ public class Geometry extends Spatial {
      * 
      * Should only be called by the parent {@link GeometryGroupNode}.
      */
-    protected void unassociateFromGroupNode() {
+    public void unassociateFromGroupNode() {
         if (groupNode != null) {
             // Once the geometry is removed 
             // from the parent, the group node needs to be updated.
             groupNode.onGeoemtryUnassociated(this);
             groupNode = null;
-            startIndex = 0;
+            
+            // change the default to -1 to make error detection easier
+            startIndex = -1; 
         }
     }
 
@@ -484,9 +486,9 @@ public class Geometry extends Spatial {
         
         // This geometry is managed,
         // but the cloned one is not attached to anything, hence not managed.
-        if (isGrouped()) {
-            groupNode = null;
-            startIndex = 0;
+        if (geomClone.isGrouped()) {
+            geomClone.groupNode = null;
+            geomClone.startIndex = -1;
         }
         
         geomClone.cachedWorldMat = cachedWorldMat.clone();

+ 14 - 0
jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java

@@ -8,6 +8,20 @@ package com.jme3.scene;
  */
 public abstract class GeometryGroupNode extends Node {
     
+    protected static int getGeometryStartIndex(Geometry geom) {
+        if (geom.startIndex == -1) {
+            throw new AssertionError();
+        }
+        return geom.startIndex;
+    }
+    
+    protected static void setGeometryStartIndex(Geometry geom, int startIndex) {
+        if (startIndex < -1) {
+            throw new AssertionError();
+        }
+        geom.startIndex = startIndex;
+    }
+    
     /**
      * Construct a <code>GeometryGroupNode</code>
      */

+ 25 - 1
jme3-core/src/main/java/com/jme3/scene/Mesh.java

@@ -174,6 +174,7 @@ public class Mesh implements Savable, Cloneable {
 
     private int vertCount = -1;
     private int elementCount = -1;
+    private int instanceCount = -1;
     private int maxNumWeights = -1; // only if using skeletal animation
 
     private int[] elementLengths;
@@ -242,6 +243,7 @@ public class Mesh implements Savable, Cloneable {
             clone.vertexArrayID = -1;
             clone.vertCount = vertCount;
             clone.elementCount = elementCount;
+            clone.instanceCount = instanceCount;
             
             // although this could change
             // if the bone weight/index buffers are modified
@@ -718,6 +720,17 @@ public class Mesh implements Savable, Cloneable {
         }
     }
 
+    private int computeInstanceCount() {
+        // Whatever the max of the base instance counts
+        int max = 0;
+        for( VertexBuffer vb : buffersList ) {
+            if( vb.getBaseInstanceCount() > max ) {
+                max = vb.getBaseInstanceCount(); 
+            } 
+        }        
+        return max;
+    }
+
     /**
      * Update the {@link #getVertexCount() vertex} and 
      * {@link #getTriangleCount() triangle} counts for this mesh
@@ -741,7 +754,8 @@ public class Mesh implements Savable, Cloneable {
             elementCount = computeNumElements(ib.getData().limit());
         }else{
             elementCount = computeNumElements(vertCount);
-        }
+        }        
+        instanceCount = computeInstanceCount();
     }
 
     /**
@@ -789,6 +803,14 @@ public class Mesh implements Savable, Cloneable {
     public int getVertexCount(){
         return vertCount;
     }
+    
+    /**
+     * Returns the number of instances this mesh contains.  The instance
+     * count is based on any VertexBuffers with instancing set.
+     */
+    public int getInstanceCount() {
+        return instanceCount;
+    }     
 
     /**
      * Gets the triangle vertex positions at the given triangle index 
@@ -1333,6 +1355,7 @@ public class Mesh implements Savable, Cloneable {
         out.write(meshBound, "modelBound", null);
         out.write(vertCount, "vertCount", -1);
         out.write(elementCount, "elementCount", -1);
+        out.write(instanceCount, "instanceCount", -1);
         out.write(maxNumWeights, "max_num_weights", -1);
         out.write(mode, "mode", Mode.Triangles);
         out.write(collisionTree, "collisionTree", null);
@@ -1370,6 +1393,7 @@ public class Mesh implements Savable, Cloneable {
         meshBound = (BoundingVolume) in.readSavable("modelBound", null);
         vertCount = in.readInt("vertCount", -1);
         elementCount = in.readInt("elementCount", -1);
+        instanceCount = in.readInt("instanceCount", -1);
         maxNumWeights = in.readInt("max_num_weights", -1);
         mode = in.readEnum("mode", Mode.class, Mode.Triangles);
         elementLengths = in.readIntArray("elementLengths", null);

+ 57 - 19
jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java

@@ -84,12 +84,12 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
          */
         Color,
 
-	/**
-	 * Tangent vector, normalized (4 floats) (x,y,z,w). The w component is
-	 * called the binormal parity, is not normalized, and is either 1f or
-	 * -1f. It's used to compute the direction on the binormal vector on the
-	 * GPU at render time.
-	 */
+        /**
+         * Tangent vector, normalized (4 floats) (x,y,z,w). The w component is
+         * called the binormal parity, is not normalized, and is either 1f or
+         * -1f. It's used to compute the direction on the binormal vector on the
+         * GPU at render time.
+         */
         Tangent,
 
         /**
@@ -208,12 +208,12 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         HWBoneIndex,
         
         /**
-	 * Information about this instance.
+         * Information about this instance.
          * 
-	 * Format should be {@link Format#Float} and number of components
-	 * should be 16.
-	 */
-	InstanceData
+         * Format should be {@link Format#Float} and number of components
+         * should be 16.
+         */
+        InstanceData
     }
 
     /**
@@ -333,7 +333,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
     protected Type bufType;
     protected Format format;
     protected boolean normalized = false;
-    protected transient boolean instanced = false;
+    protected int instanceSpan = 0;
     protected transient boolean dataSizeChanged = false;
 
     /**
@@ -545,14 +545,39 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
     }
 
     /**
-     * TODO:
+     * Sets the instanceSpan to 1 or 0 depending on
+     * the value of instanced and the existing value of
+     * instanceSpan.
      */
     public void setInstanced(boolean instanced) {
-        this.instanced = instanced;
+        if( instanced && instanceSpan == 0 ) {
+            instanceSpan = 1;
+        } else if( !instanced ) {
+            instanceSpan = 0;
+        }
     }
 
+    /**
+     * Returns true if instanceSpan is more than 0 indicating
+     * that this vertex buffer contains per-instance data.
+     */
     public boolean isInstanced() {
-        return instanced;
+        return instanceSpan > 0;
+    }
+ 
+    /**
+     * Sets how this vertex buffer matches with rendered instances
+     * where 0 means no instancing at all, ie: all elements are
+     * per vertex.  If set to 1 then each element goes with one
+     * instance.  If set to 2 then each element goes with two
+     * instances and so on.
+     */
+    public void setInstanceSpan(int i) {
+        this.instanceSpan = i;
+    }
+    
+    public int getInstanceSpan() {
+        return instanceSpan;
     }
     
     /**
@@ -587,6 +612,20 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         return elements;
     }
 
+    /**
+     *  Returns the number of 'instances' in this VertexBuffer.  This
+     *  is dependent on the current instanceSpan.  When instanceSpan
+     *  is 0 then 'instances' is 1.  Otherwise, instances is elements *
+     *  instanceSpan.  It is possible to render a mesh with more instances
+     *  but the instance data begins to repeat.
+     */
+    public int getBaseInstanceCount() {
+        if( instanceSpan == 0 ) {
+            return 1;
+        }
+        return getNumElements() * instanceSpan;
+    }
+
     /**
      * Called to initialize the data in the <code>VertexBuffer</code>. Must only
      * be called once.
@@ -1009,7 +1048,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         vb.handleRef = new Object();
         vb.id = -1;
         vb.normalized = normalized;
-        vb.instanced = instanced;
+        vb.instanceSpan = instanceSpan;
         vb.offset = offset;
         vb.stride = stride;
         vb.updateNeeded = true;
@@ -1060,9 +1099,6 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
     
     @Override
     public void write(JmeExporter ex) throws IOException {
-        if (instanced) {
-            throw new IOException("Serialization of instanced data not allowed");
-        }
         
         OutputCapsule oc = ex.getCapsule(this);
         oc.write(components, "components", 0);
@@ -1072,6 +1108,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         oc.write(normalized, "normalized", false);
         oc.write(offset, "offset", 0);
         oc.write(stride, "stride", 0);
+        oc.write(instanceSpan, "instanceSpan", 0);
 
         String dataName = "data" + format.name();
         Buffer roData = getDataReadOnly();
@@ -1107,6 +1144,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
         normalized = ic.readBoolean("normalized", false);
         offset = ic.readInt("offset", 0);
         stride = ic.readInt("stride", 0);
+        instanceSpan = ic.readInt("instanceSpan", 0);
         componentsLength = components * format.getComponentSize();
 
         String dataName = "data" + format.name();

+ 174 - 341
jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java

@@ -39,172 +39,51 @@ import com.jme3.export.Savable;
 import com.jme3.math.Matrix3f;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Quaternion;
-import com.jme3.math.Transform;
-import com.jme3.renderer.Camera;
-import com.jme3.renderer.RenderManager;
-import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Geometry;
-import com.jme3.scene.Mesh;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.VertexBuffer;
 import com.jme3.scene.VertexBuffer.Format;
 import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.VertexBuffer.Usage;
-import com.jme3.scene.control.AbstractControl;
 import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.nio.FloatBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
 
-/**
- * <code>InstancedGeometry</code> allows rendering many similar 
- * geometries efficiently through a feature called geometry 
- * instancing. 
- * 
- * <p>
- * All rendered geometries share material, mesh, and lod level 
- * but have different world transforms or possibly other parameters. 
- * The settings for all instances are inherited from this geometry's 
- * {@link #setMesh(com.jme3.scene.Mesh) mesh},
- * {@link #setMaterial(com.jme3.material.Material) material} and 
- * {@link #setLodLevel(int) lod level} and cannot be changed per-instance.
- * </p>
- * 
- * <p>
- * In order to receive any per-instance parameters, the material's shader
- * must be changed to retrieve per-instance data via 
- * {@link VertexBuffer#setInstanced(boolean) instanced vertex attributes} 
- * or uniform arrays indexed with the GLSL built-in uniform 
- * <code>gl_InstanceID</code>. At the very least, they should use the 
- * functions specified in <code>Instancing.glsllib</code> shader library
- * to transform vertex positions and normals instead of multiplying by the 
- * built-in matrix uniforms.
- * </p>
- * 
- * <p>
- * This class can operate in two modes, {@link InstancedGeometry.Mode#Auto}
- * and {@link InstancedGeometry.Mode#Manual}. See the respective enums
- * for more information</p>
- * 
- * <p>
- * Prior to usage, the maximum number of instances must be set via 
- * {@link #setMaxNumInstances(int) } and the current number of instances set
- * via {@link #setCurrentNumInstances(int) }. The user is then
- * expected to provide transforms for all instances up to the number
- * of current instances.
- * </p>
- * 
- * @author Kirill Vainer
- */
 public class InstancedGeometry extends Geometry {
     
-    /**
-     * Indicates how the per-instance data is to be specified.
-     */
-    public static enum Mode {
-        
-        /**
-         * The user must specify all per-instance transforms and 
-         * parameters manually via
-         * {@link InstancedGeometry#setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }
-         * or 
-         * {@link InstancedGeometry#setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }.
-         */
-        Manual,
-        
-        /**
-         * The user 
-         * {@link InstancedGeometry#setInstanceTransform(int, com.jme3.math.Transform) provides world transforms}
-         * and then uses the <code>Instancing.glsllib</code> transform functions in the 
-         * shader to transform vertex attributes to the respective spaces.
-         * Additional per-instance data can be specified via
-         * {@link InstancedGeometry#setManualGlobalInstanceData(com.jme3.scene.VertexBuffer[]) }.
-         * {@link #setManualCameraInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }
-         * cannot be used at this mode since it is computed automatically.
-         */
-        Auto
-    }
-    
-    private static class InstancedGeometryControl extends AbstractControl {
-
-        private InstancedGeometry geom;
-        
-        public InstancedGeometryControl() {
-        }
-        
-        public InstancedGeometryControl(InstancedGeometry geom) {
-            this.geom = geom;
-        }
-        
-        @Override
-        protected void controlUpdate(float tpf) {
-        }
-
-        @Override
-        protected void controlRender(RenderManager rm, ViewPort vp) {
-            geom.renderFromControl(vp.getCamera());
-        }
-    }
-    
     private static final int INSTANCE_SIZE = 16;
     
-    private InstancedGeometry.Mode mode;
-    private InstancedGeometryControl control;
-    private int currentNumInstances = 1;
-    private Camera lastCamera = null;
-    private Matrix4f[] worldMatrices = new Matrix4f[1];
     private VertexBuffer[] globalInstanceData;
+    private VertexBuffer transformInstanceData;
+    private Geometry[] geometries = new Geometry[1];
     
-    private final HashMap<Camera, VertexBuffer> instanceDataPerCam 
-            = new HashMap<Camera, VertexBuffer>();
-    
-    // TODO: determine if perhaps its better to use TempVars here.
-    
-    private final Matrix4f tempMat4 = new Matrix4f();
-    private final Matrix4f tempMat4_2 = new Matrix4f();
-    private final Matrix3f tempMat3 = new Matrix3f();
-    private final Quaternion tempQuat = new Quaternion();
-    private final float[] tempFloatArray = new float[16];
-    
+    private int firstUnusedIndex = 0;
+
     /**
      * Serialization only. Do not use.
      */
     public InstancedGeometry() {
         super();
         setIgnoreTransform(true);
+        setBatchHint(BatchHint.Never);
+        setMaxNumInstances(1);
     }
     
     /**
      * Creates instanced geometry with the specified mode and name.
      * 
-     * @param mode The {@link Mode} at which the instanced geometry operates at.
      * @param name The name of the spatial. 
      * 
-     * @see Mode
      * @see Spatial#Spatial(java.lang.String)
      */
-    public InstancedGeometry(InstancedGeometry.Mode mode, String name) {
+    public InstancedGeometry(String name) {
         super(name);
-        this.mode = mode;
         setIgnoreTransform(true);
-        if (mode == InstancedGeometry.Mode.Auto) {
-            control = new InstancedGeometryControl(this);
-            addControl(control);
-        }
-    }
-    
-    /**
-     * The mode with which this instanced geometry was initialized
-     * with. Cannot be changed after initialization.
-     * 
-     * @return instanced geometry mode.
-     */
-    public InstancedGeometry.Mode getMode() {
-        return mode;
+        setBatchHint(BatchHint.Never);
+        setMaxNumInstances(1);
     }
     
     /**
@@ -238,170 +117,54 @@ public class InstancedGeometry extends Geometry {
     /**
      * Specify camera specific user per-instance data.
      * 
-     * Only applies when operating in {@link Mode#Manual}. 
-     * When operating in {@link Mode#Auto}, this data is computed automatically,
-     * and using this method is not allowed.
-     * 
-     * @param camera The camera for which per-instance data is to be set.
-     * @param cameraInstanceData The camera's per-instance data.
-     * 
-     * @throws IllegalArgumentException If camera is null.
-     * @throws IllegalStateException If {@link #getMode() mode} is set to 
-     * {@link Mode#Auto}.
-     * 
-     * @see Mode
-     * @see #getCameraUserInstanceData(com.jme3.renderer.Camera)
-     */
-    public void setCameraUserInstanceData(Camera camera, VertexBuffer cameraInstanceData) {
-        if (mode == Mode.Auto) {
-            throw new IllegalStateException("Not allowed in auto mode");
-        }
-        if (camera == null) {
-            throw new IllegalArgumentException("camera cannot be null");
-        }
-        instanceDataPerCam.put(camera, cameraInstanceData);
-    }
-    
-    /**
-     * Return camera specific user per-instance data.
-     * 
-     * Only applies when operating in {@link Mode#Manual}. 
-     * When operating in {@link Mode#Auto}, this data is computed automatically,
-     * and using this method is not allowed.
-     * 
-     * @param camera The camera to look up the per-instance data for.
-     * @return The per-instance data, or <code>null</code> if none was specified
-     * for the given camera.
-     * 
-     * @throws IllegalArgumentException If camera is null.
-     * @throws IllegalStateException If {@link #getMode() mode} is set to 
-     * {@link Mode#Auto}.
-     * 
-     * @see Mode
-     * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) 
+     * @param transformInstanceData The transforms for each instance.
      */
-    public VertexBuffer getCameraUserInstanceData(Camera camera) {
-        if (mode == Mode.Auto) {
-            throw new IllegalStateException("Not allowed in auto mode");
-        }
-        if (camera == null) {
-            throw new IllegalArgumentException("camera cannot be null");
-        }
-        return instanceDataPerCam.get(camera);
+    public void setTransformUserInstanceData(VertexBuffer transformInstanceData) {
+        this.transformInstanceData = transformInstanceData;
     }
     
     /**
-     * Return a read only map with the mappings between cameras and camera 
-     * specific per-instance data. 
-     * 
-     * Only applies when operating in {@link Mode#Manual}. 
-     * When operating in {@link Mode#Auto}, this data is computed automatically,
-     * and using this method is not allowed.
+     * Return user per-instance transform data.
      * 
-     * @return read only map with the mappings between cameras and camera 
-     * specific per-instance data. 
-     * 
-     * @throws IllegalStateException If {@link #getMode() mode} is set to 
-     * {@link Mode#Auto}.
-     * 
-     * @see Mode
-     * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) 
+     * @return The per-instance transform data.
+     *
+     * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer) 
      */
-    public Map<Camera, VertexBuffer> getAllCameraUserInstanceData() {
-        if (mode == Mode.Auto) {
-            throw new IllegalStateException("Not allowed in auto mode");
-        }
-        return Collections.unmodifiableMap(instanceDataPerCam);
+    public VertexBuffer getTransformUserInstanceData() {
+        return transformInstanceData;
     }
     
-    private void updateInstance(Matrix4f viewMatrix, Matrix4f worldMatrix, float[] store, int offset) {
-        viewMatrix.mult(worldMatrix, tempMat4);
-        tempMat4.toRotationMatrix(tempMat3);
+    private void updateInstance(Matrix4f worldMatrix, float[] store, 
+                                int offset, Matrix3f tempMat3, 
+                                Quaternion tempQuat) {
+        worldMatrix.toRotationMatrix(tempMat3);
         tempMat3.invertLocal();
-        
+
         // NOTE: No need to take the transpose in order to encode
         // into quaternion, the multiplication in the shader is vec * quat
         // apparently...
         tempQuat.fromRotationMatrix(tempMat3);
-        
+
         // Column-major encoding. The "W" field in each of the encoded
         // vectors represents the quaternion.
-        store[offset + 0] = tempMat4.m00;
-        store[offset + 1] = tempMat4.m10;
-        store[offset + 2] = tempMat4.m20;
+        store[offset + 0] = worldMatrix.m00;
+        store[offset + 1] = worldMatrix.m10;
+        store[offset + 2] = worldMatrix.m20;
         store[offset + 3] = tempQuat.getX();
-        store[offset + 4] = tempMat4.m01;
-        store[offset + 5] = tempMat4.m11;
-        store[offset + 6] = tempMat4.m21;
+        store[offset + 4] = worldMatrix.m01;
+        store[offset + 5] = worldMatrix.m11;
+        store[offset + 6] = worldMatrix.m21;
         store[offset + 7] = tempQuat.getY();
-        store[offset + 8] = tempMat4.m02;
-        store[offset + 9] = tempMat4.m12;
-        store[offset + 10] = tempMat4.m22;
+        store[offset + 8] = worldMatrix.m02;
+        store[offset + 9] = worldMatrix.m12;
+        store[offset + 10] = worldMatrix.m22;
         store[offset + 11] = tempQuat.getZ();
-        store[offset + 12] = tempMat4.m03;
-        store[offset + 13] = tempMat4.m13;
-        store[offset + 14] = tempMat4.m23;
+        store[offset + 12] = worldMatrix.m03;
+        store[offset + 13] = worldMatrix.m13;
+        store[offset + 14] = worldMatrix.m23;
         store[offset + 15] = tempQuat.getW();
     }
     
-    private void renderFromControl(Camera cam) {
-        if (mode != Mode.Auto) {
-            return;
-        }
-        
-        // Get the instance data VBO for this camera.
-        VertexBuffer instanceDataVB = instanceDataPerCam.get(cam);
-        FloatBuffer instanceData;
-        
-        if (instanceDataVB == null) {
-            // This is a new camera, create instance data VBO for it.
-            instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE);
-            instanceDataVB = new VertexBuffer(Type.InstanceData);
-            instanceDataVB.setInstanced(true);
-            instanceDataVB.setupData(Usage.Stream, INSTANCE_SIZE, Format.Float, instanceData);
-            instanceDataPerCam.put(cam, instanceDataVB);
-        } else {
-            // Retrieve the current instance data buffer.
-            instanceData = (FloatBuffer) instanceDataVB.getData();
-        }
-        
-        Matrix4f viewMatrix = cam.getViewMatrix();
-        
-        instanceData.limit(instanceData.capacity());
-        instanceData.position(0);
-        
-        assert currentNumInstances <= worldMatrices.length;
-        
-        for (int i = 0; i < currentNumInstances; i++) {
-            Matrix4f worldMatrix = worldMatrices[i];
-            if (worldMatrix == null) {
-                worldMatrix = Matrix4f.IDENTITY;
-            }
-            updateInstance(viewMatrix, worldMatrix, tempFloatArray, 0);
-            instanceData.put(tempFloatArray);
-        }
-        
-        instanceData.flip();
-        
-        this.lastCamera = cam;
-        instanceDataVB.updateData(instanceDataVB.getData());
-    }
-    
-    /**
-     * Set the current number of instances to be rendered.
-     * 
-     * @param currentNumInstances the current number of instances to be rendered.
-     * 
-     * @throws IllegalArgumentException If current number of instances is 
-     * greater than the maximum number of instances.
-     */
-    public void setCurrentNumInstances(int currentNumInstances) {
-        if (currentNumInstances > worldMatrices.length) {
-            throw new IllegalArgumentException("currentNumInstances cannot be larger than maxNumInstances");
-        }
-        this.currentNumInstances = currentNumInstances;
-    }
-    
     /**
      * Set the maximum amount of instances that can be rendered by this
      * instanced geometry when mode is set to auto.
@@ -415,88 +178,168 @@ public class InstancedGeometry extends Geometry {
      * @throws IllegalStateException If mode is set to manual.
      * @throws IllegalArgumentException If maxNumInstances is zero or negative
      */
-    public void setMaxNumInstances(int maxNumInstances) {
-        if (mode == Mode.Manual) {
-            throw new IllegalStateException("Not allowed in manual mode");
-        }
+    public final void setMaxNumInstances(int maxNumInstances) {
         if (maxNumInstances < 1) {
             throw new IllegalArgumentException("maxNumInstances must be 1 or higher");
         }
         
-        this.worldMatrices = new Matrix4f[maxNumInstances];
+        Geometry[] originalGeometries = geometries;
+        this.geometries = new Geometry[maxNumInstances];
         
-        if (currentNumInstances > maxNumInstances) {
-            currentNumInstances = maxNumInstances;
+        if (originalGeometries != null) {
+            System.arraycopy(originalGeometries, 0, geometries, 0, originalGeometries.length);
         }
         
-        // Resize instance data for each of the cameras.
-        for (VertexBuffer instanceDataVB : instanceDataPerCam.values()) {
-            FloatBuffer instanceData = (FloatBuffer) instanceDataVB.getData();
-            if (instanceData.capacity() / INSTANCE_SIZE != worldMatrices.length) {
-                // Delete old data.
-                BufferUtils.destroyDirectBuffer(instanceData);
-
-                // Resize instance data for this camera.
-                // Create new data with new length.
-                instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE);
-                instanceDataVB.updateData(instanceData);
-            }
+        // Resize instance data.
+        if (transformInstanceData != null) {
+            BufferUtils.destroyDirectBuffer(transformInstanceData.getData());
+            transformInstanceData.updateData(BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE));
+        } else if (transformInstanceData == null) {
+            transformInstanceData = new VertexBuffer(Type.InstanceData);
+            transformInstanceData.setInstanced(true);
+            transformInstanceData.setupData(Usage.Stream,
+                    INSTANCE_SIZE,
+                    Format.Float,
+                    BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE));
         }
     }
     
     public int getMaxNumInstances() {
-        return worldMatrices.length;
+        return geometries.length;
     }
 
-    public int getCurrentNumInstances() {
-        return currentNumInstances;
+    public int getActualNumInstances() {
+        return firstUnusedIndex;
     }
     
-    public void setInstanceTransform(int instanceIndex, Matrix4f worldTransform) {
-        if (mode == Mode.Manual) {
-            throw new IllegalStateException("Not allowed in manual mode");
+    private void swap(int idx1, int idx2) {
+        Geometry g = geometries[idx1];
+        geometries[idx1] = geometries[idx2];
+        geometries[idx2] = g;
+        
+        if (geometries[idx1] != null) {
+            InstancedNode.setGeometryStartIndex2(geometries[idx1], idx1);
         }
-        if (worldTransform == null) {
-            throw new IllegalArgumentException("worldTransform cannot be null");
+        if (geometries[idx2] != null) {
+            InstancedNode.setGeometryStartIndex2(geometries[idx2], idx2);
         }
-        if (instanceIndex < 0) {
-            throw new IllegalArgumentException("instanceIndex cannot be smaller than zero");
+    }
+    
+    private void sanitize(boolean insideEntriesNonNull) {
+        if (firstUnusedIndex >= geometries.length) {
+            throw new AssertionError();
         }
-        if (instanceIndex >= currentNumInstances) {
-            throw new IllegalArgumentException("instanceIndex cannot be larger than currentNumInstances");
+        for (int i = 0; i < geometries.length; i++) {
+            if (i < firstUnusedIndex) {
+                if (geometries[i] == null) {
+                    if (insideEntriesNonNull) {
+                        throw new AssertionError();
+                    }  
+                } else if (InstancedNode.getGeometryStartIndex2(geometries[i]) != i) {
+                    throw new AssertionError();
+                }
+            } else {
+                if (geometries[i] != null) {
+                    throw new AssertionError();
+                }
+            }
         }
-        // TODO: Determine if need to make a copy of matrix or just doing this
-        // is fine.
-        worldMatrices[instanceIndex] = worldTransform;
     }
     
-    public void setInstanceTransform(int instanceIndex, Transform worldTransform) {
-        if (worldTransform == null) {
-            throw new IllegalArgumentException("worldTransform cannot be null");
+    public void updateInstances() {
+        FloatBuffer fb = (FloatBuffer) transformInstanceData.getData();
+        fb.limit(fb.capacity());
+        fb.position(0);
+        
+        TempVars vars = TempVars.get();
+        {
+            float[] temp = vars.matrixWrite;
+            
+            for (int i = 0; i < firstUnusedIndex; i++) {
+                Geometry geom = geometries[i];
+
+                if (geom == null) {
+                    geom = geometries[firstUnusedIndex - 1];
+                    
+                    if (geom == null) {
+                        throw new AssertionError();
+                    }
+                    
+                    swap(i, firstUnusedIndex - 1);
+                    
+                    while (geometries[firstUnusedIndex -1] == null) {
+                        firstUnusedIndex--;
+                    }
+                }
+                
+                Matrix4f worldMatrix = geom.getWorldMatrix();
+                updateInstance(worldMatrix, temp, 0, vars.tempMat3, vars.quat1);
+                fb.put(temp);
+            }
         }
+        vars.release();
         
-        // Compute the world transform matrix.
-        tempMat4.loadIdentity();
-        tempMat4.setRotationQuaternion(worldTransform.getRotation());
-        tempMat4.setTranslation(worldTransform.getTranslation());
-        tempMat4_2.loadIdentity();
-        tempMat4_2.scale(worldTransform.getScale());
-        tempMat4.multLocal(tempMat4_2);
+        fb.flip();
+        
+        if (fb.limit() / INSTANCE_SIZE != firstUnusedIndex) {
+            throw new AssertionError();
+        }
+
+        transformInstanceData.updateData(fb);
+    }
+    
+    public void deleteInstance(Geometry geom) {
+        int idx = InstancedNode.getGeometryStartIndex2(geom);
+        InstancedNode.setGeometryStartIndex2(geom, -1);
+        
+        geometries[idx] = null;
+        
+        if (idx == firstUnusedIndex - 1) {
+            // Deleting the last element.
+            // Move index back.
+            firstUnusedIndex--;
+            while (geometries[firstUnusedIndex] == null) {
+                firstUnusedIndex--;
+                if (firstUnusedIndex < 0) {
+                    break;
+                }
+            }
+            firstUnusedIndex++;
+        } else {
+            // Deleting element in the middle
+        }
+    }
+    
+    public void addInstance(Geometry geometry) {
+        if (geometry == null) {
+            throw new IllegalArgumentException("geometry cannot be null");
+        }
+       
+        // Take an index from the end.
+        if (firstUnusedIndex + 1 >= geometries.length) {
+            // No more room.
+            setMaxNumInstances(getMaxNumInstances() * 2);
+        }
+
+        int freeIndex = firstUnusedIndex;
+        firstUnusedIndex++;
         
-        setInstanceTransform(instanceIndex, tempMat4.clone());
+        geometries[freeIndex] = geometry;
+        InstancedNode.setGeometryStartIndex2(geometry, freeIndex);
+    }
+    
+    public Geometry[] getGeometries() {
+        return geometries;
     }
     
     public VertexBuffer[] getAllInstanceData() {
-        VertexBuffer instanceDataForCam = instanceDataPerCam.get(lastCamera);
         ArrayList<VertexBuffer> allData = new ArrayList();
-
-        if (instanceDataForCam != null) {
-            allData.add(instanceDataForCam);
+        if (transformInstanceData != null) {
+            allData.add(transformInstanceData);
         }
         if (globalInstanceData != null) {
             allData.addAll(Arrays.asList(globalInstanceData));
         }
-
         return allData.toArray(new VertexBuffer[allData.size()]);
     }
 
@@ -504,30 +347,20 @@ public class InstancedGeometry extends Geometry {
     public void write(JmeExporter exporter) throws IOException {
         super.write(exporter);
         OutputCapsule capsule = exporter.getCapsule(this);
-        capsule.write(currentNumInstances, "cur_num_instances", 1);
-        capsule.write(mode, "instancing_mode", InstancedGeometry.Mode.Auto);
-        if (mode == Mode.Auto) {
-            capsule.write(worldMatrices, "world_matrices", null);
-        }
+        //capsule.write(currentNumInstances, "cur_num_instances", 1);
+        capsule.write(geometries, "geometries", null);
     }
     
     @Override
     public void read(JmeImporter importer) throws IOException {
         super.read(importer);
         InputCapsule capsule = importer.getCapsule(this);
-        currentNumInstances = capsule.readInt("cur_num_instances", 1);
-        mode = capsule.readEnum("instancing_mode", InstancedGeometry.Mode.class, 
-                                InstancedGeometry.Mode.Auto);
-        
-        if (mode == Mode.Auto) {
-            Savable[] matrixSavables = capsule.readSavableArray("world_matrices", null);
-            worldMatrices = new Matrix4f[matrixSavables.length];
-            for (int i = 0; i < worldMatrices.length; i++) {
-                worldMatrices[i] = (Matrix4f) matrixSavables[i];
-            }
+        //currentNumInstances = capsule.readInt("cur_num_instances", 1);
 
-            control = getControl(InstancedGeometryControl.class);
-            control.geom = this;
+        Savable[] geometrySavables = capsule.readSavableArray("geometries", null);
+        geometries = new Geometry[geometrySavables.length];
+        for (int i = 0; i < geometrySavables.length; i++) {
+            geometries[i] = (Geometry) geometrySavables[i];
         }
     }
 }

+ 335 - 0
jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java

@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2014 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.
+ */
+package com.jme3.scene.instancing;
+
+import com.jme3.material.Material;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.GeometryGroupNode;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.UserData;
+import com.jme3.scene.control.Control;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.material.MatParam;
+import java.io.IOException;
+import java.util.HashMap;
+
+public class InstancedNode extends GeometryGroupNode {
+    
+    static int getGeometryStartIndex2(Geometry geom) {
+        return getGeometryStartIndex(geom);
+    }
+    
+    static void setGeometryStartIndex2(Geometry geom, int startIndex) {
+        setGeometryStartIndex(geom, startIndex);
+    }
+    
+    private static class InstanceTypeKey implements Cloneable {
+
+        Mesh mesh;
+        Material material;
+        int lodLevel;
+
+        public InstanceTypeKey(Mesh mesh, Material material, int lodLevel) {
+            this.mesh = mesh;
+            this.material = material;
+            this.lodLevel = lodLevel;
+        }
+        
+        public InstanceTypeKey(){
+        }
+
+        @Override
+        public int hashCode() {
+            int hash = 3;
+            hash = 41 * hash + this.mesh.hashCode();
+            hash = 41 * hash + this.material.hashCode();
+            hash = 41 * hash + this.lodLevel;
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            final InstanceTypeKey other = (InstanceTypeKey) obj;
+            if (this.mesh != other.mesh) {
+                return false;
+            }
+            if (this.material != other.material) {
+                return false;
+            }
+            if (this.lodLevel != other.lodLevel) {
+                return false;
+            }
+            return true;
+        }
+        
+        @Override
+        public InstanceTypeKey clone() {
+            try {
+                return (InstanceTypeKey) super.clone();
+            } catch (CloneNotSupportedException ex) {
+                throw new AssertionError();
+            }
+        }
+    }
+    
+    private static class InstancedNodeControl implements Control {
+
+        private InstancedNode node;
+        
+        public InstancedNodeControl() {
+        }
+        
+        public InstancedNodeControl(InstancedNode node) {
+            this.node = node;
+        }
+        
+        @Override
+        public Control cloneForSpatial(Spatial spatial) {
+            return this; 
+            // WARNING: Sets wrong control on spatial. Will be
+            // fixed automatically by InstancedNode.clone() method.
+        }
+        
+        public void setSpatial(Spatial spatial){
+        }
+        
+        public void update(float tpf){
+        }
+        
+        public void render(RenderManager rm, ViewPort vp) {
+            node.renderFromControl();
+        }
+        
+        public void write(JmeExporter ex) throws IOException {
+        }
+
+        public void read(JmeImporter im) throws IOException {
+        }
+    }
+    
+    protected InstancedNodeControl control;
+    
+    protected HashMap<Geometry, InstancedGeometry> igByGeom 
+            = new HashMap<Geometry, InstancedGeometry>();
+    
+    private InstanceTypeKey lookUp = new InstanceTypeKey();
+    
+    private HashMap<InstanceTypeKey, InstancedGeometry> instancesMap = 
+            new HashMap<InstanceTypeKey, InstancedGeometry>();
+    
+    public InstancedNode() {
+        super();
+        // NOTE: since we are deserializing,
+        // the control is going to be added automatically here.
+    }
+    
+    public InstancedNode(String name) {
+        super(name);
+        control = new InstancedNodeControl(this);
+        addControl(control);
+    }
+    
+    private void renderFromControl() {
+        for (InstancedGeometry ig : instancesMap.values()) {
+            ig.updateInstances();
+        }
+    }
+
+    private InstancedGeometry lookUpByGeometry(Geometry geom) {
+        lookUp.mesh = geom.getMesh();
+        lookUp.material = geom.getMaterial();
+        lookUp.lodLevel = geom.getLodLevel();
+
+        InstancedGeometry ig = instancesMap.get(lookUp);
+
+        if (ig == null) {
+            ig = new InstancedGeometry(
+                    "mesh-" + System.identityHashCode(lookUp.mesh) + "," +
+                    "material-" + lookUp.material.getMaterialDef().getName() + ","
+                    + "lod-" + lookUp.lodLevel);
+            ig.setMaterial(lookUp.material);
+            ig.setMesh(lookUp.mesh);
+            ig.setUserData(UserData.JME_PHYSICSIGNORE, true);
+            ig.setCullHint(CullHint.Never);
+            instancesMap.put(lookUp.clone(), ig);
+            attachChild(ig);
+        }
+
+        return ig;
+    }
+    
+    private void addToInstancedGeometry(Geometry geom) {
+        Material material = geom.getMaterial();
+        MatParam param = material.getParam("UseInstancing");
+        if (param == null || !((Boolean)param.getValue()).booleanValue()) {
+            throw new IllegalStateException("You must set the 'UseInstancing' "
+                    + "parameter to true on the material prior "
+                    + "to adding it to InstancedNode");
+        }
+        
+        InstancedGeometry ig = lookUpByGeometry(geom);
+        igByGeom.put(geom, ig);
+        geom.associateWithGroupNode(this, 0);
+        ig.addInstance(geom);
+    }
+    
+    private void removeFromInstancedGeometry(Geometry geom) {
+        InstancedGeometry ig = igByGeom.remove(geom);
+        if (ig != null) {
+            ig.deleteInstance(geom);
+        }
+    }
+    
+    private void relocateInInstancedGeometry(Geometry geom) {
+        InstancedGeometry oldIG = igByGeom.get(geom);
+        InstancedGeometry newIG = lookUpByGeometry(geom);
+        if (oldIG != newIG) {
+            if (oldIG == null) {
+                throw new AssertionError();
+            }
+            oldIG.deleteInstance(geom);
+            newIG.addInstance(geom);
+            igByGeom.put(geom, newIG);
+        }
+    }
+    
+    private void ungroupSceneGraph(Spatial s) {
+        if (s instanceof Node) {
+            for (Spatial sp : ((Node) s).getChildren()) {
+                ungroupSceneGraph(sp);
+            }
+        } else if (s instanceof Geometry) {
+            Geometry g = (Geometry) s;
+            if (g.isGrouped()) {
+                // Will invoke onGeometryUnassociated automatically.
+                g.unassociateFromGroupNode();
+                
+                if (InstancedNode.getGeometryStartIndex(g) != -1) {
+                    throw new AssertionError();
+                }
+            }
+        }
+    }
+    
+    @Override
+    public Spatial detachChildAt(int index) {
+        Spatial s = super.detachChildAt(index);
+        if (s instanceof Node) {
+            ungroupSceneGraph(s);
+        }
+        return s;
+    }
+    
+    private void instance(Spatial n) {
+        if (n instanceof Geometry) {
+            Geometry g = (Geometry) n;
+            if (!g.isGrouped() && g.getBatchHint() != BatchHint.Never) {
+                addToInstancedGeometry(g);
+            }
+        } else if (n instanceof Node) {
+            for (Spatial child : ((Node) n).getChildren()) {
+                if (child instanceof GeometryGroupNode) {
+                    continue;
+                }
+                instance(child);
+            }
+        }
+    }
+    
+    public void instance() {
+        instance(this);
+    }
+    
+    @Override
+    public Node clone() {
+        return clone(true);
+    }
+    
+    @Override
+    public Node clone(boolean cloneMaterials) {
+        InstancedNode clone = (InstancedNode)super.clone(cloneMaterials);
+        
+        if (instancesMap.size() > 0) {
+            // Remove all instanced geometries from the clone
+            for (int i = 0; i < clone.children.size(); i++) {
+                if (clone.children.get(i) instanceof InstancedGeometry) {
+                    clone.children.remove(i);
+                } else if (clone.children.get(i) instanceof Geometry) {
+                    Geometry geom = (Geometry) clone.children.get(i);
+                    if (geom.isGrouped()) {
+                        throw new AssertionError();
+                    }
+                }
+            }
+        }
+        
+        // remove original control from the clone
+        clone.controls.remove(this.control);
+
+        // put clone's control in
+        clone.control = new InstancedNodeControl(clone);
+        clone.controls.add(clone.control);
+
+        clone.lookUp = new InstanceTypeKey();
+        clone.igByGeom = new HashMap<Geometry, InstancedGeometry>();
+        clone.instancesMap = new HashMap<InstanceTypeKey, InstancedGeometry>();
+        
+        clone.instance();
+        
+        return clone;
+    }
+    
+    @Override
+    public void onTransformChange(Geometry geom) {
+        // Handled automatically
+    }
+
+    @Override
+    public void onMaterialChange(Geometry geom) {
+        relocateInInstancedGeometry(geom);
+    }
+
+    @Override
+    public void onMeshChange(Geometry geom) {
+        relocateInInstancedGeometry(geom);
+    }
+
+    @Override
+    public void onGeoemtryUnassociated(Geometry geom) {
+        removeFromInstancedGeometry(geom);
+    }
+}

+ 24 - 1
jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md

@@ -132,6 +132,8 @@ MaterialDef Phong Lighting {
         // For hardware skinning
         Int NumberOfBones
         Matrix4Array BoneMatrices
+                
+        Boolean UseInstancing
     }
 
     Technique {
@@ -148,6 +150,7 @@ MaterialDef Phong Lighting {
             ViewMatrix
             CameraPosition
             WorldMatrix
+            ViewProjectionMatrix            
         }
 
         Defines {
@@ -177,6 +180,8 @@ MaterialDef Phong Lighting {
             SPHERE_MAP : SphereMap  
 
             NUM_BONES : NumberOfBones
+                        
+            INSTANCING : UseInstancing
         }
     }
 
@@ -188,12 +193,15 @@ MaterialDef Phong Lighting {
         WorldParameters {
             WorldViewProjectionMatrix
             WorldViewMatrix
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
             COLOR_MAP : ColorMap
             DISCARD_ALPHA : AlphaDiscardThreshold
             NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
         }
 
         ForcedRenderState {
@@ -214,6 +222,8 @@ MaterialDef Phong Lighting {
         WorldParameters {
             WorldViewProjectionMatrix
             WorldMatrix
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
@@ -227,6 +237,7 @@ MaterialDef Phong Lighting {
             PSSM : Splits
             POINTLIGHT : LightViewProjectionMatrix5
             NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
         }
 
         ForcedRenderState {
@@ -243,6 +254,8 @@ MaterialDef Phong Lighting {
         WorldParameters {
             WorldViewProjectionMatrix
             WorldMatrix
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
@@ -256,6 +269,7 @@ MaterialDef Phong Lighting {
             PSSM : Splits
             POINTLIGHT : LightViewProjectionMatrix5
             NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
         }
 
         ForcedRenderState {
@@ -274,11 +288,14 @@ MaterialDef Phong Lighting {
             WorldViewProjectionMatrix
             WorldViewMatrix
             NormalMatrix
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
             DIFFUSEMAP_ALPHA : DiffuseMap
             NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
         }
 
     }
@@ -292,12 +309,15 @@ MaterialDef Phong Lighting {
         WorldParameters {
             WorldViewProjectionMatrix
             WorldViewMatrix
-            NormalMatrix
+            NormalMatrix                        
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
             DIFFUSEMAP_ALPHA : DiffuseMap
             NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
         }
 
     }
@@ -339,6 +359,8 @@ MaterialDef Phong Lighting {
 
         WorldParameters {
             WorldViewProjectionMatrix
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
@@ -347,6 +369,7 @@ MaterialDef Phong Lighting {
             HAS_GLOWCOLOR : GlowColor
 
             NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
         }
     }
 

+ 13 - 9
jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert

@@ -1,12 +1,16 @@
+#import "Common/ShaderLib/Instancing.glsllib"
 #define ATTENUATION
 //#define HQ_ATTENUATION
 
 #import "Common/ShaderLib/Skinning.glsllib"
 
-uniform mat4 g_WorldViewProjectionMatrix;
-uniform mat4 g_WorldViewMatrix;
-uniform mat3 g_NormalMatrix;
-uniform mat4 g_ViewMatrix;
+/*
+    uniform mat4 g_WorldViewProjectionMatrix;
+    uniform mat4 g_WorldViewMatrix;
+    uniform mat3 g_NormalMatrix;
+    uniform mat4 g_ViewMatrix;
+*/
+
 
 uniform vec4 m_Ambient;
 uniform vec4 m_Diffuse;
@@ -148,14 +152,14 @@ void main(){
         #endif
    #endif
 
-   gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
+   gl_Position = TransformWorldViewProjection(modelSpacePos);// g_WorldViewProjectionMatrix * modelSpacePos;
    texCoord = inTexCoord;
    #ifdef SEPARATE_TEXCOORD
       texCoord2 = inTexCoord2;
    #endif
 
-   vec3 wvPosition = (g_WorldViewMatrix * modelSpacePos).xyz;
-   vec3 wvNormal  = normalize(g_NormalMatrix * modelSpaceNorm);
+   vec3 wvPosition = TransformWorldView(modelSpacePos).xyz;// (g_WorldViewMatrix * modelSpacePos).xyz;
+   vec3 wvNormal  = normalize(TransformNormal(modelSpaceNorm));//normalize(g_NormalMatrix * modelSpaceNorm);
    vec3 viewDir = normalize(-wvPosition);
   
        //vec4 lightColor = g_LightColor[gl_InstanceID];
@@ -168,7 +172,7 @@ void main(){
    vec4 lightColor = g_LightColor;
 
    #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
-     vec3 wvTangent = normalize(g_NormalMatrix * modelSpaceTan);
+     vec3 wvTangent = normalize(TransformNormal(modelSpaceTan));
      vec3 wvBinormal = cross(wvNormal, wvTangent);
 
      mat3 tbnMat = mat3(wvTangent, wvBinormal * inTangent.w,wvNormal);
@@ -187,7 +191,7 @@ void main(){
      lightComputeDir(wvPosition, lightColor, wvLightPos, vLightDir);
 
      #ifdef V_TANGENT
-        vNormal = normalize(g_NormalMatrix * inTangent.xyz);
+        vNormal = normalize(TransformNormal(inTangent.xyz));
         vNormal = -cross(cross(vLightDir.xyz, vNormal), vNormal);
      #endif
    #endif

+ 2 - 0
jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md

@@ -10,6 +10,8 @@ MaterialDef Debug Normals {
 
         WorldParameters {
             WorldViewProjectionMatrix
+            ViewProjectionMatrix
+            ViewMatrix
             ProjectionMatrix
         }
 

+ 17 - 1
jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md

@@ -59,7 +59,8 @@ MaterialDef Unshaded {
 
         WorldParameters {
             WorldViewProjectionMatrix
-            ProjectionMatrix
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
@@ -86,10 +87,13 @@ MaterialDef Unshaded {
               WorldViewProjectionMatrix
               WorldViewMatrix
               NormalMatrix
+              ViewProjectionMatrix
+              ViewMatrix
           }
 
           Defines {
               NUM_BONES : NumberOfBones
+              INSTANCING : UseInstancing
           }
    }
 
@@ -101,12 +105,15 @@ MaterialDef Unshaded {
         WorldParameters {
             WorldViewProjectionMatrix
             WorldViewMatrix
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
             COLOR_MAP : ColorMap
             DISCARD_ALPHA : AlphaDiscardThreshold
             NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
         }
 
         ForcedRenderState {
@@ -127,6 +134,8 @@ MaterialDef Unshaded {
         WorldParameters {
             WorldViewProjectionMatrix
             WorldMatrix
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
@@ -140,6 +149,7 @@ MaterialDef Unshaded {
             PSSM : Splits
             POINTLIGHT : LightViewProjectionMatrix5
             NUM_BONES : NumberOfBones
+	    INSTANCING : UseInstancing
         }
 
         ForcedRenderState {
@@ -156,6 +166,8 @@ MaterialDef Unshaded {
         WorldParameters {
             WorldViewProjectionMatrix
             WorldMatrix
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
@@ -169,6 +181,7 @@ MaterialDef Unshaded {
             PSSM : Splits
             POINTLIGHT : LightViewProjectionMatrix5
             NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
         }
 
         ForcedRenderState {
@@ -185,6 +198,8 @@ MaterialDef Unshaded {
 
         WorldParameters {
             WorldViewProjectionMatrix
+            ViewProjectionMatrix
+            ViewMatrix
         }
 
         Defines {
@@ -192,6 +207,7 @@ MaterialDef Unshaded {
             HAS_GLOWMAP : GlowMap
             HAS_GLOWCOLOR : GlowColor
             NUM_BONES : NumberOfBones
+	    INSTANCING : UseInstancing
         }
     }
 }

+ 1 - 0
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md

@@ -29,6 +29,7 @@ MaterialDef Post Shadow {
         Float PCFEdge
 
         Float ShadowMapSize
+		
     }
 
     Technique {

+ 2 - 4
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert

@@ -1,12 +1,10 @@
+#import "Common/ShaderLib/Instancing.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
 uniform mat4 m_LightViewProjectionMatrix0;
 uniform mat4 m_LightViewProjectionMatrix1;
 uniform mat4 m_LightViewProjectionMatrix2;
 uniform mat4 m_LightViewProjectionMatrix3;
 
-uniform mat4 g_WorldViewProjectionMatrix;
-uniform mat4 g_WorldMatrix;
-uniform mat4 g_ViewMatrix;
 uniform vec3 m_LightPos; 
 
 varying vec4 projCoord0;
@@ -52,7 +50,7 @@ void main(){
    #ifdef NUM_BONES
        Skinning_Compute(modelSpacePos);
    #endif
-    gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
+    gl_Position = TransformWorldViewProjection(modelSpacePos);
 
     #ifndef POINTLIGHT
         #ifdef PSSM

+ 3 - 4
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert

@@ -1,11 +1,10 @@
+#import "Common/ShaderLib/Instancing.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
 uniform mat4 m_LightViewProjectionMatrix0;
 uniform mat4 m_LightViewProjectionMatrix1;
 uniform mat4 m_LightViewProjectionMatrix2;
 uniform mat4 m_LightViewProjectionMatrix3;
 
-uniform mat4 g_WorldViewProjectionMatrix;
-uniform mat4 g_WorldMatrix;
 
 out vec4 projCoord0;
 out vec4 projCoord1;
@@ -51,7 +50,7 @@ void main(){
    #ifdef NUM_BONES
        Skinning_Compute(modelSpacePos);
    #endif
-    gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
+    gl_Position =  TransformWorldViewProjection(modelSpacePos);
 
     #ifndef POINTLIGHT
         #ifdef PSSM
@@ -60,7 +59,7 @@ void main(){
         vec4 worldPos=vec4(0.0);
     #endif
     // get the vertex in world space
-    worldPos = g_WorldMatrix * modelSpacePos;
+    worldPos = TransformWorld(modelSpacePos);
 
     #ifdef DISCARD_ALPHA
        texCoord = inTexCoord;

+ 2 - 4
jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert

@@ -1,10 +1,8 @@
+#import "Common/ShaderLib/Instancing.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
 attribute vec3 inPosition;
 attribute vec2 inTexCoord;
 
-uniform mat4 g_WorldViewProjectionMatrix;
-uniform mat4 g_WorldViewMatrix;
-
 varying vec2 texCoord;
 
 void main(){
@@ -13,6 +11,6 @@ void main(){
    #ifdef NUM_BONES
        Skinning_Compute(modelSpacePos);
    #endif
-    gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
+    gl_Position = TransformWorldViewProjection(modelSpacePos);
     texCoord = inTexCoord;
 }

+ 18 - 5
jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib

@@ -23,6 +23,7 @@ uniform mat4 g_ViewMatrix;
 uniform mat4 g_ProjectionMatrix;
 uniform mat4 g_WorldViewMatrix;
 uniform mat4 g_WorldViewProjectionMatrix;
+uniform mat4 g_ViewProjectionMatrix;
 uniform mat3 g_NormalMatrix;
 
 #if defined INSTANCING
@@ -37,29 +38,36 @@ uniform mat3 g_NormalMatrix;
 // 2 vertex attributes which now can be used for additional per-vertex data.
 attribute mat4 inInstanceData;
 
-// Extract the world view matrix out of the instance data, leaving out the 
+// Extract the world matrix out of the instance data, leaving out the 
 // quaternion at the end.
-mat4 worldViewMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0), 
+mat4 worldMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0), 
                             vec4(inInstanceData[1].xyz, 0.0), 
                             vec4(inInstanceData[2].xyz, 0.0), 
                             vec4(inInstanceData[3].xyz, 1.0));
 
+vec4 TransformWorld(vec4 position)
+{
+    return (worldMatrix * position);
+}
 
 vec4 TransformWorldView(vec4 position)
 {
-    return worldViewMatrix * position;
+    return g_ViewMatrix * TransformWorld(position);
 }
 
 vec4 TransformWorldViewProjection(vec4 position)
 {
-    return g_ProjectionMatrix * TransformWorldView(position);
+    return g_ViewProjectionMatrix * TransformWorld(position);
 }
 
 vec3 TransformNormal(vec3 vec)
 {
     vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w,
                      inInstanceData[2].w, inInstanceData[3].w);
-    return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
+
+    vec3 worldNormal = vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
+    
+    return (g_ViewMatrix * vec4(worldNormal, 0.0)).xyz;
 }
 
 // Prevent user from using g_** matrices which will have invalid data in this case.
@@ -70,6 +78,11 @@ vec3 TransformNormal(vec3 vec)
 
 #else
 
+vec4 TransformWorld(vec4 position)
+{
+    return g_WorldMatrix * position;
+}
+
 vec4 TransformWorldView(vec4 position)
 {
     return g_WorldViewMatrix * position;

+ 4 - 2
jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert

@@ -1,6 +1,8 @@
+#import "Common/ShaderLib/Instancing.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
-uniform mat4 g_WorldViewProjectionMatrix;
-uniform mat3 g_NormalMatrix;
+// These are included in the above now
+//uniform mat4 g_WorldViewProjectionMatrix;
+//uniform mat3 g_NormalMatrix;
 
 attribute vec3 inPosition;
 attribute vec3 inNormal;

+ 93 - 0
jme3-examples/src/main/java/jme3test/bullet/TestIK.java

@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+package jme3test.bullet;
+
+import com.jme3.animation.AnimEventListener;
+import com.jme3.animation.Bone;
+import com.jme3.bullet.collision.RagdollCollisionListener;
+import com.jme3.bullet.control.KinematicRagdollControl;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+
+/**
+ * @author reden
+ */
+public class TestIK extends TestBoneRagdoll implements RagdollCollisionListener, AnimEventListener {
+
+    Node targetNode = new Node("");
+    Vector3f targetPoint;
+    Bone mouseBone;
+    Vector3f oldMousePos;
+ 
+    public static void main(String[] args) {
+        TestIK app = new TestIK();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        super.simpleInitApp();
+        final KinematicRagdollControl ikControl = model.getControl(KinematicRagdollControl.class);
+        inputManager.addListener(new ActionListener() {
+
+            public void onAction(String name, boolean isPressed, float tpf) {
+
+                if (name.equals("stop") && isPressed) {
+                    ikControl.setEnabled(!ikControl.isEnabled());
+                    ikControl.setIKMode();
+                }
+
+                if (name.equals("one") && isPressed) {
+                    //ragdoll.setKinematicMode();
+                    targetPoint = model.getWorldTranslation().add(new Vector3f(0,2,4));
+                    targetNode.setLocalTranslation(targetPoint);
+                    ikControl.setIKTarget(ikControl.getBone("Hand.L"), targetPoint, 2);
+                    ikControl.setIKMode();
+                }
+                if (name.equals("two") && isPressed) {
+                    //ragdoll.setKinematicMode();
+                    targetPoint = model.getWorldTranslation().add(new Vector3f(-3,3,0));
+                    targetNode.setLocalTranslation(targetPoint);
+                    ikControl.setIKTarget(ikControl.getBone("Hand.R"), targetPoint, 3);
+                    ikControl.setIKMode();
+                }
+            }
+        }, "one", "two");
+        inputManager.addMapping("one", new KeyTrigger(KeyInput.KEY_1));
+        inputManager.addMapping("two", new KeyTrigger(KeyInput.KEY_2));
+        inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_H));
+    }
+    
+}

+ 192 - 0
jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java

@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2009-2012 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.
+ */
+
+package jme3test.scene.instancing;
+
+import com.jme3.app.SimpleApplication;
+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.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.Node;
+import com.jme3.scene.instancing.InstancedGeometry;
+import com.jme3.scene.instancing.InstancedNode;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.system.AppSettings;
+
+public class TestInstanceNode extends SimpleApplication  {
+
+    private Mesh mesh1;
+    private Mesh mesh2;
+    private final Material[] materials = new Material[6];
+    private Node instancedNode;
+    private float time = 0;
+    private boolean INSTANCING = false;
+    
+    public static void main(String[] args){
+        TestInstanceNode app = new TestInstanceNode();
+        AppSettings settings = new AppSettings(true);
+        settings.setVSync(false);
+        app.setSettings(settings);
+        app.start();
+    }
+
+    private Geometry createInstance(float x, float z) {
+        Mesh mesh; 
+        if (FastMath.nextRandomInt(0, 1) == 1) mesh = mesh2;
+        else mesh = mesh1;
+        Geometry geometry = new Geometry("randomGeom", mesh);
+        geometry.setMaterial(materials[FastMath.nextRandomInt(0, materials.length - 1)]);
+        geometry.setLocalTranslation(x, 0, z);
+        return geometry;
+    }
+    
+    @Override
+    public void simpleInitApp() {
+        mesh1 = new Sphere(13, 13, 0.4f, true, false);
+        mesh2 = new Box(0.4f, 0.4f, 0.4f);
+        
+        materials[0] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        materials[0].setBoolean("UseInstancing", INSTANCING);
+        materials[0].setColor("Color", ColorRGBA.Red);
+        
+        materials[1] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        materials[1].setBoolean("UseInstancing", INSTANCING);
+        materials[1].setColor("Color", ColorRGBA.Green);
+        
+        materials[2] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        materials[2].setBoolean("UseInstancing", INSTANCING);
+        materials[2].setColor("Color", ColorRGBA.Blue);
+        
+        materials[3] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        materials[3].setBoolean("UseInstancing", INSTANCING);
+        materials[3].setColor("Color", ColorRGBA.Cyan);
+        
+        materials[4] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        materials[4].setBoolean("UseInstancing", INSTANCING);
+        materials[4].setColor("Color", ColorRGBA.Magenta);
+        
+        materials[5] = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        materials[5].setBoolean("UseInstancing", INSTANCING);
+        materials[5].setColor("Color", ColorRGBA.Yellow);
+       
+        instancedNode = new InstancedNode("instanced_node");
+        
+        rootNode.attachChild(instancedNode);
+        
+        int extent = 30;
+        
+        for (int y = -extent; y < extent; y++) {
+            for (int x = -extent; x < extent; x++) {
+                Geometry instance = createInstance(x, y);
+                
+                float height = (smoothstep(0, 1, FastMath.nextRandomFloat()) * 2.5f) - 1.25f;
+                instance.setUserData("height", height);
+                instance.setUserData("dir", 1f);
+                
+                instancedNode.attachChild(instance);
+            }
+        }
+        
+        if (INSTANCING) {
+            ((InstancedNode)instancedNode).instance();
+        }
+        
+        instancedNode = (InstancedNode) instancedNode.clone();
+        instancedNode.move(0, 5, 0);
+        rootNode.attachChild(instancedNode);
+        
+        cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f));
+        cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f));
+        flyCam.setMoveSpeed(15);
+        flyCam.setEnabled(false);
+    }
+    
+    private float smoothstep(float edge0, float edge1, float x) {
+        // Scale, bias and saturate x to 0..1 range
+        x = FastMath.clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
+        // Evaluate polynomial
+        return x * x * (3 - 2 * x);
+    }
+    
+    
+    @Override
+    public void simpleUpdate(float tpf) {
+        time += tpf;
+
+        if (time > 1f) {
+            time = 0f;
+            
+            for (Spatial instance : instancedNode.getChildren()) {
+                if (!(instance instanceof InstancedGeometry)) {
+                    Geometry geom = (Geometry) instance;
+                    geom.setMaterial(materials[FastMath.nextRandomInt(0, materials.length - 1)]);
+
+                    Mesh mesh; 
+                    if (FastMath.nextRandomInt(0, 1) == 1) mesh = mesh2;
+                    else mesh = mesh1;
+                    geom.setMesh(mesh);
+                }
+            }
+        }
+        
+        for (Spatial child : instancedNode.getChildren()) {
+            if (!(child instanceof InstancedGeometry)) {
+                float val = child.getUserData("height");
+                float dir = child.getUserData("dir");
+
+                val += (dir + ((FastMath.nextRandomFloat() * 0.5f) - 0.25f)) * tpf;
+
+                if (val > 1f) {
+                    val = 1f;
+                    dir = -dir;
+                } else if (val < 0f) {
+                    val = 0f;
+                    dir = -dir;
+                }
+
+                Vector3f translation = child.getLocalTranslation();
+                translation.y = (smoothstep(0, 1, val) * 2.5f) - 1.25f;
+
+                child.setUserData("height", val);
+                child.setUserData("dir", dir);
+
+                child.setLocalTranslation(translation);
+            }
+        }
+    }
+}

+ 0 - 146
jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java

@@ -1,146 +0,0 @@
-/*
- * Copyright (c) 2009-2012 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.
- */
-
-package jme3test.scene.instancing;
-
-import com.jme3.app.SimpleApplication;
-import com.jme3.input.KeyInput;
-import com.jme3.input.controls.ActionListener;
-import com.jme3.input.controls.AnalogListener;
-import com.jme3.input.controls.KeyTrigger;
-import com.jme3.material.Material;
-import com.jme3.math.Quaternion;
-import com.jme3.math.Vector3f;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.Node;
-import com.jme3.scene.Spatial;
-import com.jme3.scene.Spatial.CullHint;
-import com.jme3.scene.instancing.InstancedGeometry;
-import com.jme3.scene.shape.Sphere;
-
-public class TestInstancing extends SimpleApplication  {
-
-    private InstancedGeometry instancedGeometry;
-    private Node instancedGeoms;
-    private Material material;
-    private boolean enabled = true;
-    
-    public static void main(String[] args){
-        TestInstancing app = new TestInstancing();
-        //app.setShowSettings(false);
-        //app.setDisplayFps(false);
-        //app.setDisplayStatView(false);
-        app.start();
-    }
-
-    private Geometry createInstance(float x, float z) {
-        // Note: it doesn't matter what mesh or material we set here.
-        Geometry geometry = new Geometry("randomGeom", instancedGeometry.getMesh());
-        geometry.setMaterial(instancedGeometry.getMaterial());
-        geometry.setLocalTranslation(x, 0, z);
-        return geometry;
-    }
-    
-    @Override
-    public void simpleInitApp() {
-        initInputs();
-        
-        Sphere sphere = new Sphere(10, 10, 0.5f, true, false);
-        material = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
-        material.setBoolean("UseInstancing", true);
-        
-        instancedGeometry = new InstancedGeometry(InstancedGeometry.Mode.Auto, "instanced_geom");
-        instancedGeometry.setMaxNumInstances(60 * 60);
-        instancedGeometry.setCurrentNumInstances(60 * 60);
-        instancedGeometry.setCullHint(CullHint.Never);
-        instancedGeometry.setMesh(sphere);
-        instancedGeometry.setMaterial(material);
-        rootNode.attachChild(instancedGeometry);
-        
-        instancedGeoms = new Node("instances_node");
-        
-        // Important: Do not render these geometries, only
-        // use their world transforms to instance them via
-        // InstancedGeometry.
-        instancedGeoms.setCullHint(CullHint.Always);
-        
-        for (int y = -30; y < 30; y++) {
-            for (int x = -30; x < 30; x++) {
-                Geometry instance = createInstance(x, y);
-                instancedGeoms.attachChild(instance);
-            }
-        }
-        
-        rootNode.attachChild(instancedGeoms);
-        rootNode.setCullHint(CullHint.Never);
-
-        int instanceIndex = 0;
-        for (Spatial child : instancedGeoms.getChildren()) {
-            if (instanceIndex < instancedGeometry.getMaxNumInstances()) {
-                instancedGeometry.setInstanceTransform(instanceIndex++, child.getWorldTransform());
-            }
-        }
-        
-        instancedGeometry.setCurrentNumInstances(instanceIndex);
-
-        cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f));
-        cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f));
-        flyCam.setMoveSpeed(15);
-    }
-
-    private void initInputs() {
-        inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
-
-        ActionListener acl = new ActionListener() {
-
-            public void onAction(String name, boolean keyPressed, float tpf) {
-                if (name.equals("toggle") && keyPressed) {
-                    if (enabled) {
-                        enabled = false;
-                        instancedGeoms.setCullHint(CullHint.Dynamic);
-                        instancedGeometry.setCullHint(CullHint.Always);
-                        material.setBoolean("UseInstancing", false);
-                        System.out.println("Instancing OFF");
-                    } else {
-                        enabled = true;
-                        instancedGeoms.setCullHint(CullHint.Always);
-                        instancedGeometry.setCullHint(CullHint.Never);
-                        material.setBoolean("UseInstancing", true);
-                        System.out.println("Instancing ON");
-                    }
-                }
-            }
-        };
-
-        inputManager.addListener(acl, "toggle");
-    }
-}

+ 11 - 4
jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java

@@ -2226,7 +2226,6 @@ public class LwjglRenderer implements Renderer {
                 }
             }
             
-            int slotsRequired = 1;
             if (vb.isInstanced()) {
                 if (!ctxCaps.GL_ARB_instanced_arrays
                  || !ctxCaps.GL_ARB_draw_instanced) {
@@ -2234,7 +2233,10 @@ public class LwjglRenderer implements Renderer {
                             + "but not supported by the "
                             + "graphics hardware");
                 }
-                if (vb.getNumComponents() > 4 && vb.getNumComponents() % 4 != 0) {
+            }
+            int slotsRequired = 1;
+            if (vb.getNumComponents() > 4) {
+                if (vb.getNumComponents() % 4 != 0) {
                     throw new RendererException("Number of components in multi-slot "
                             + "buffers must be divisible by 4");
                 }
@@ -2273,7 +2275,7 @@ public class LwjglRenderer implements Renderer {
                             vb.getOffset());
                 } else {
                     for (int i = 0; i < slotsRequired; i++) {
-	 	 	// The pointer maps the next 4 floats in the slot.
+                        // The pointer maps the next 4 floats in the slot.
                         // E.g.
                         // P1: XXXX____________XXXX____________
                         // P2: ____XXXX____________XXXX________
@@ -2294,7 +2296,7 @@ public class LwjglRenderer implements Renderer {
                     int slot = loc + i;
                     if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) {
                         // non-instanced -> instanced
-                        glVertexAttribDivisorARB(slot, 1);
+                        glVertexAttribDivisorARB(slot, vb.getInstanceSpan());
                     } else if (!vb.isInstanced() && attribs[slot] != null && attribs[slot].isInstanced()) {
                         // instanced -> non-instanced
                         glVertexAttribDivisorARB(slot, 0);
@@ -2491,6 +2493,11 @@ public class LwjglRenderer implements Renderer {
     }
 
     private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
+ 
+        // Here while count is still passed in.  Can be removed when/if
+        // the method is collapsed again.  -pspeed        
+        count = Math.max(mesh.getInstanceCount(), count);
+    
         VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
         if (interleavedData != null && interleavedData.isUpdateNeeded()) {
             updateBufferData(interleavedData);