Browse Source

Merge remote-tracking branch 'origin/master' into in-pass-shadows

Kirill Vainer 8 years ago
parent
commit
5108f52ebf

+ 167 - 89
jme3-core/src/main/java/com/jme3/input/FlyByCamera.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2017 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -32,7 +32,11 @@
 package com.jme3.input;
 
 import com.jme3.collision.MotionAllowedListener;
-import com.jme3.input.controls.*;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.MouseAxisTrigger;
+import com.jme3.input.controls.MouseButtonTrigger;
 import com.jme3.math.FastMath;
 import com.jme3.math.Matrix3f;
 import com.jme3.math.Quaternion;
@@ -40,12 +44,13 @@ import com.jme3.math.Vector3f;
 import com.jme3.renderer.Camera;
 
 /**
- * A first person view camera controller.
- * After creation, you must register the camera controller with the
- * dispatcher using #registerWithDispatcher().
+ * A first-person camera controller.
+ *
+ * After creation, you (or FlyCamAppState) must register the controller using
+ * {@link #registerWithInput(com.jme3.input.InputManager)}.
  *
  * Controls:
- *  - Move the mouse to rotate the camera
+ *  - Move (or, in drag-to-rotate mode, drag) the mouse to rotate the camera
  *  - Mouse wheel for zooming in or out
  *  - WASD keys for moving forward/backward and strafing
  *  - QZ keys raise or lower the camera
@@ -53,41 +58,62 @@ import com.jme3.renderer.Camera;
 public class FlyByCamera implements AnalogListener, ActionListener {
 
     private static String[] mappings = new String[]{
-            CameraInput.FLYCAM_LEFT,
-            CameraInput.FLYCAM_RIGHT,
-            CameraInput.FLYCAM_UP,
-            CameraInput.FLYCAM_DOWN,
-
-            CameraInput.FLYCAM_STRAFELEFT,
-            CameraInput.FLYCAM_STRAFERIGHT,
-            CameraInput.FLYCAM_FORWARD,
-            CameraInput.FLYCAM_BACKWARD,
-
-            CameraInput.FLYCAM_ZOOMIN,
-            CameraInput.FLYCAM_ZOOMOUT,
-            CameraInput.FLYCAM_ROTATEDRAG,
-
-            CameraInput.FLYCAM_RISE,
-            CameraInput.FLYCAM_LOWER,
-            
-            CameraInput.FLYCAM_INVERTY
-        };
+        CameraInput.FLYCAM_LEFT,
+        CameraInput.FLYCAM_RIGHT,
+        CameraInput.FLYCAM_UP,
+        CameraInput.FLYCAM_DOWN,
+
+        CameraInput.FLYCAM_STRAFELEFT,
+        CameraInput.FLYCAM_STRAFERIGHT,
+        CameraInput.FLYCAM_FORWARD,
+        CameraInput.FLYCAM_BACKWARD,
+
+        CameraInput.FLYCAM_ZOOMIN,
+        CameraInput.FLYCAM_ZOOMOUT,
+        CameraInput.FLYCAM_ROTATEDRAG,
 
+        CameraInput.FLYCAM_RISE,
+        CameraInput.FLYCAM_LOWER,
+
+        CameraInput.FLYCAM_INVERTY
+    };
+    /**
+     * camera controlled by this controller (not null)
+     */
     protected Camera cam;
+    /**
+     * normalized "up" direction (a unit vector)
+     */
     protected Vector3f initialUpVec;
+    /**
+     * rotation-rate multiplier (1=default)
+     */
     protected float rotationSpeed = 1f;
+    /**
+     * translation speed (in world units per second)
+     */
     protected float moveSpeed = 3f;
+    /**
+     * zoom-rate multiplier (1=default)
+     */
     protected float zoomSpeed = 1f;
     protected MotionAllowedListener motionAllowed = null;
+    /**
+     * enable flag for controller (false→ignoring input)
+     */
     protected boolean enabled = true;
+    /**
+     * drag-to-rotate mode flag
+     */
     protected boolean dragToRotate = false;
     protected boolean canRotate = false;
     protected boolean invertY = false;
     protected InputManager inputManager;
-    
+
     /**
-     * Creates a new FlyByCamera to control the given Camera object.
-     * @param cam
+     * Creates a new FlyByCamera to control the specified camera.
+     *
+     * @param cam camera to be controlled (not null)
      */
     public FlyByCamera(Camera cam){
         this.cam = cam;
@@ -96,10 +122,11 @@ public class FlyByCamera implements AnalogListener, ActionListener {
 
     /**
      * Sets the up vector that should be used for the camera.
+     *
      * @param upVec
      */
     public void setUpVector(Vector3f upVec) {
-       initialUpVec.set(upVec);
+        initialUpVec.set(upVec);
     }
 
     public void setMotionAllowedListener(MotionAllowedListener listener){
@@ -107,56 +134,68 @@ public class FlyByCamera implements AnalogListener, ActionListener {
     }
 
     /**
-     * Sets the move speed. The speed is given in world units per second.
-     * @param moveSpeed
+     * Set the translation speed.
+     *
+     * @param moveSpeed new speed (in world units per second)
      */
     public void setMoveSpeed(float moveSpeed){
         this.moveSpeed = moveSpeed;
     }
-    
+
     /**
-     * Gets the move speed. The speed is given in world units per second.
-     * @return moveSpeed
+     * Read the translation speed.
+     *
+     * @return current speed (in world units per second)
      */
     public float getMoveSpeed(){
         return moveSpeed;
     }
 
     /**
-     * Sets the rotation speed.
-     * @param rotationSpeed
+     * Set the rotation-rate multiplier. The bigger the multiplier, the more
+     * rotation for a given movement of the mouse.
+     *
+     * @param rotationSpeed new rate multiplier (1=default)
      */
     public void setRotationSpeed(float rotationSpeed){
         this.rotationSpeed = rotationSpeed;
     }
-    
+
     /**
-     * Gets the move speed. The speed is given in world units per second.
-     * @return rotationSpeed
+     * Read the rotation-rate multiplier. The bigger the multiplier, the more
+     * rotation for a given movement of the mouse.
+     *
+     * @return current rate multiplier (1=default)
      */
     public float getRotationSpeed(){
         return rotationSpeed;
     }
-    
+
     /**
-     * Sets the zoom speed.
-     * @param zoomSpeed 
+     * Set the zoom-rate multiplier. The bigger the multiplier, the more zoom
+     * for a given movement of the mouse wheel.
+     *
+     * @param zoomSpeed new rate multiplier (1=default)
      */
     public void setZoomSpeed(float zoomSpeed) {
         this.zoomSpeed = zoomSpeed;
     }
-    
+
     /**
-     * Gets the zoom speed.  The speed is a multiplier to increase/decrease
-     * the zoom rate.
-     * @return zoomSpeed
+     * Read the zoom-rate multiplier. The bigger the multiplier, the more zoom
+     * for a given movement of the mouse wheel.
+     *
+     * @return current rate multiplier (1=default)
      */
     public float getZoomSpeed() {
         return zoomSpeed;
     }
 
     /**
-     * @param enable If false, the camera will ignore input.
+     * Enable or disable this controller. When disabled, the controller ignored
+     * input.
+     *
+     * @param enable true to enable, false to disable
      */
     public void setEnabled(boolean enable){
         if (enabled && !enable){
@@ -168,32 +207,36 @@ public class FlyByCamera implements AnalogListener, ActionListener {
     }
 
     /**
-     * @return If enabled
-     * @see FlyByCamera#setEnabled(boolean)
+     * Test whether this controller is enabled.
+     *
+     * @return true if enabled, otherwise false
+     * @see #setEnabled(boolean)
      */
     public boolean isEnabled(){
         return enabled;
     }
 
     /**
+     * Test whether drag-to-rotate mode is enabled.
+     *
      * @return If drag to rotate feature is enabled.
      *
-     * @see FlyByCamera#setDragToRotate(boolean) 
+     * @see #setDragToRotate(boolean)
      */
     public boolean isDragToRotate() {
         return dragToRotate;
     }
 
     /**
-     * Set if drag to rotate mode is enabled.
-     * 
-     * When true, the user must hold the mouse button
-     * and drag over the screen to rotate the camera, and the cursor is
-     * visible until dragged. Otherwise, the cursor is invisible at all times
-     * and holding the mouse button is not needed to rotate the camera.
-     * This feature is disabled by default.
-     * 
-     * @param dragToRotate True if drag to rotate mode is enabled.
+     * Enable or disable drag-to-rotate mode.
+     *
+     * When drag-to-rotate mode is enabled, the user must hold the mouse button
+     * and drag over the screen to rotate the camera, and the cursor is visible
+     * until dragged. When drag-to-rotate mode is disabled, the cursor is
+     * invisible at all times and holding the mouse button is not needed to
+     * rotate the camera. This mode is disabled by default.
+     *
+     * @param dragToRotate true to enable, false to disable
      */
     public void setDragToRotate(boolean dragToRotate) {
         this.dragToRotate = dragToRotate;
@@ -203,25 +246,26 @@ public class FlyByCamera implements AnalogListener, ActionListener {
     }
 
     /**
-     * Registers the FlyByCamera to receive input events from the provided
-     * Dispatcher.
+     * Register this controller to receive input events from the specified input
+     * manager.
+     *
      * @param inputManager
      */
     public void registerWithInput(InputManager inputManager){
         this.inputManager = inputManager;
-        
+
         // both mouse and button - rotation of cam
         inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true),
-                                               new KeyTrigger(KeyInput.KEY_LEFT));
+                new KeyTrigger(KeyInput.KEY_LEFT));
 
         inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false),
-                                                new KeyTrigger(KeyInput.KEY_RIGHT));
+                new KeyTrigger(KeyInput.KEY_RIGHT));
 
         inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false),
-                                             new KeyTrigger(KeyInput.KEY_UP));
+                new KeyTrigger(KeyInput.KEY_UP));
 
         inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true),
-                                               new KeyTrigger(KeyInput.KEY_DOWN));
+                new KeyTrigger(KeyInput.KEY_DOWN));
 
         // mouse only - zoom in/out with wheel, and rotate drag
         inputManager.addMapping(CameraInput.FLYCAM_ZOOMIN, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
@@ -248,43 +292,42 @@ public class FlyByCamera implements AnalogListener, ActionListener {
     }
 
     protected void mapJoystick( Joystick joystick ) {
-        
+
         // Map it differently if there are Z axis
         if( joystick.getAxis( JoystickAxis.Z_ROTATION ) != null && joystick.getAxis( JoystickAxis.Z_AXIS ) != null ) {
- 
+
             // Make the left stick move
             joystick.getXAxis().assignAxis( CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT );
             joystick.getYAxis().assignAxis( CameraInput.FLYCAM_BACKWARD, CameraInput.FLYCAM_FORWARD );
-            
-            // And the right stick control the camera                       
+
+            // And the right stick control the camera
             joystick.getAxis( JoystickAxis.Z_ROTATION ).assignAxis( CameraInput.FLYCAM_DOWN, CameraInput.FLYCAM_UP );
             joystick.getAxis( JoystickAxis.Z_AXIS ).assignAxis(  CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_LEFT );
- 
-            // And let the dpad be up and down           
+
+            // And let the dpad be up and down
             joystick.getPovYAxis().assignAxis(CameraInput.FLYCAM_RISE, CameraInput.FLYCAM_LOWER);
- 
-            if( joystick.getButton( "Button 8" ) != null ) { 
+
+            if( joystick.getButton( "Button 8" ) != null ) {
                 // Let the stanard select button be the y invert toggle
                 joystick.getButton( "Button 8" ).assignButton( CameraInput.FLYCAM_INVERTY );
-            }                
-            
-        } else {             
+            }
+
+        } else {
             joystick.getPovXAxis().assignAxis(CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT);
             joystick.getPovYAxis().assignAxis(CameraInput.FLYCAM_FORWARD, CameraInput.FLYCAM_BACKWARD);
             joystick.getXAxis().assignAxis(CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_LEFT);
             joystick.getYAxis().assignAxis(CameraInput.FLYCAM_DOWN, CameraInput.FLYCAM_UP);
-        }                
+        }
     }
 
     /**
-     * Unregisters the FlyByCamera from the event Dispatcher.
+     * Unregister this controller from its input manager.
      */
     public void unregisterInput(){
-    
         if (inputManager == null) {
             return;
         }
-    
+
         for (String s : mappings) {
             if (inputManager.hasMapping(s)) {
                 inputManager.deleteMapping( s );
@@ -296,12 +339,16 @@ public class FlyByCamera implements AnalogListener, ActionListener {
 
         Joystick[] joysticks = inputManager.getJoysticks();
         if (joysticks != null && joysticks.length > 0){
-            Joystick joystick = joysticks[0];
-            
-            // No way to unassing axis
+            // No way to unassign axis
         }
     }
 
+    /**
+     * Rotate the camera by the specified amount around the specified axis.
+     *
+     * @param value rotation amount
+     * @param axis direction of rotation (a unit vector)
+     */
     protected void rotateCamera(float value, Vector3f axis){
         if (dragToRotate){
             if (canRotate){
@@ -329,6 +376,11 @@ public class FlyByCamera implements AnalogListener, ActionListener {
         cam.setAxes(q);
     }
 
+    /**
+     * Zoom the camera by the specified amount.
+     *
+     * @param value zoom amount
+     */
     protected void zoomCamera(float value){
         // derive fovY value
         float h = cam.getFrustumTop();
@@ -338,7 +390,7 @@ public class FlyByCamera implements AnalogListener, ActionListener {
         float near = cam.getFrustumNear();
 
         float fovY = FastMath.atan(h / near)
-                  / (FastMath.DEG_TO_RAD * .5f);
+                / (FastMath.DEG_TO_RAD * .5f);
         float newFovY = fovY + value * 0.1f * zoomSpeed;
         if (newFovY > 0f) {
             // Don't let the FOV go zero or negative.
@@ -354,6 +406,11 @@ public class FlyByCamera implements AnalogListener, ActionListener {
         cam.setFrustumRight(w);
     }
 
+    /**
+     * Translate the camera upward by the specified amount.
+     *
+     * @param value translation amount
+     */
     protected void riseCamera(float value){
         Vector3f vel = new Vector3f(0, value * moveSpeed, 0);
         Vector3f pos = cam.getLocation().clone();
@@ -366,6 +423,12 @@ public class FlyByCamera implements AnalogListener, ActionListener {
         cam.setLocation(pos);
     }
 
+    /**
+     * Translate the camera left or forward by the specified amount.
+     *
+     * @param value translation amount
+     * @param sideways true→left, false→forward
+     */
     protected void moveCamera(float value, boolean sideways){
         Vector3f vel = new Vector3f();
         Vector3f pos = cam.getLocation().clone();
@@ -385,6 +448,14 @@ public class FlyByCamera implements AnalogListener, ActionListener {
         cam.setLocation(pos);
     }
 
+    /**
+     * Callback to notify this controller of an analog input event.
+     *
+     * @param name name of the input event
+     * @param value value of the axis (from 0 to 1)
+     * @param tpf time per frame (in seconds)
+     */
+    @Override
     public void onAnalog(String name, float value, float tpf) {
         if (!enabled)
             return;
@@ -416,6 +487,14 @@ public class FlyByCamera implements AnalogListener, ActionListener {
         }
     }
 
+    /**
+     * Callback to notify this controller of an action input event.
+     *
+     * @param name name of the input event
+     * @param value true if the action is "pressed", false otherwise
+     * @param tpf time per frame (in seconds)
+     */
+    @Override
     public void onAction(String name, boolean value, float tpf) {
         if (!enabled)
             return;
@@ -424,11 +503,10 @@ public class FlyByCamera implements AnalogListener, ActionListener {
             canRotate = value;
             inputManager.setCursorVisible(!value);
         } else if (name.equals(CameraInput.FLYCAM_INVERTY)) {
-            // Toggle on the up.
-            if( !value ) {  
+            // Invert the "up" direction.
+            if( !value ) {
                 invertY = !invertY;
             }
-        }        
+        }
     }
-
 }

+ 14 - 16
jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java

@@ -31,16 +31,15 @@
  */
 package com.jme3.scene;
 
-import com.jme3.asset.AssetInfo;
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.ModelKey;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
-import com.jme3.export.binary.BinaryImporter;
-import com.jme3.util.clone.Cloner;
 import com.jme3.util.SafeArrayList;
+import com.jme3.util.clone.Cloner;
+
 import java.io.IOException;
 import java.util.*;
 import java.util.Map.Entry;
@@ -164,25 +163,24 @@ public class AssetLinkNode extends Node {
     @Override
     public void read(JmeImporter e) throws IOException {
         super.read(e);
-        InputCapsule capsule = e.getCapsule(this);
-        BinaryImporter importer = BinaryImporter.getInstance();
-        AssetManager loaderManager = e.getAssetManager();
+
+        final InputCapsule capsule = e.getCapsule(this);
+        final AssetManager assetManager = e.getAssetManager();
 
         assetLoaderKeys = (ArrayList<ModelKey>) capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList<ModelKey>());
-        for (Iterator<ModelKey> it = assetLoaderKeys.iterator(); it.hasNext();) {
-            ModelKey modelKey = it.next();
-            AssetInfo info = loaderManager.locateAsset(modelKey);
-            Spatial child = null;
-            if (info != null) {
-                child = (Spatial) importer.load(info);
-            }
+
+        for (final Iterator<ModelKey> iterator = assetLoaderKeys.iterator(); iterator.hasNext(); ) {
+
+            final ModelKey modelKey = iterator.next();
+            final Spatial child = assetManager.loadAsset(modelKey);
+
             if (child != null) {
                 child.parent = this;
                 children.add(child);
                 assetChildren.put(modelKey, child);
             } else {
-                Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}",
-                                                                    new Object[]{ modelKey, key });
+                Logger.getLogger(this.getClass().getName()).log(Level.WARNING,
+                        "Cannot locate {0} for asset link node {1}", new Object[]{modelKey, key});
             }
         }
     }
@@ -190,7 +188,7 @@ public class AssetLinkNode extends Node {
     @Override
     public void write(JmeExporter e) throws IOException {
         SafeArrayList<Spatial> childs = children;
-        children = new SafeArrayList<Spatial>(Spatial.class);
+        children = new SafeArrayList<>(Spatial.class);
         super.write(e);
         OutputCapsule capsule = e.getCapsule(this);
         capsule.writeSavableArrayList(assetLoaderKeys, "assetLoaderKeyList", null);

+ 15 - 3
jme3-core/src/main/java/com/jme3/scene/Geometry.java

@@ -137,6 +137,20 @@ public class Geometry extends Spatial {
         return super.checkCulling(cam);
     }
 
+    /**
+     * Update the world transform of this Geometry and clear the
+     * TRANSFORM refresh flag.
+     */
+    @Override
+    void checkDoTransformUpdate() {
+        if (ignoreTransform) {
+            worldTransform.loadIdentity();
+            refreshFlags &= ~RF_TRANSFORM;
+        } else {
+            super.checkDoTransformUpdate();
+        }    
+    }
+    
     /**
      * @return If ignoreTransform mode is set.
      *
@@ -151,6 +165,7 @@ public class Geometry extends Spatial {
      */
     public void setIgnoreTransform(boolean ignoreTransform) {
         this.ignoreTransform = ignoreTransform;
+        setTransformRefresh();
     }
 
     /**
@@ -398,9 +413,6 @@ public class Geometry extends Spatial {
 
         // Compute the cached world matrix
         cachedWorldMat.loadIdentity();
-        if (ignoreTransform) {
-            return;
-        }
         cachedWorldMat.setRotationQuaternion(worldTransform.getRotation());
         cachedWorldMat.setTranslation(worldTransform.getTranslation());
 

+ 2 - 2
jme3-core/src/main/resources/com/jme3/asset/General.cfg

@@ -12,8 +12,8 @@ LOADER com.jme3.texture.plugins.DDSLoader : dds
 LOADER com.jme3.texture.plugins.PFMLoader : pfm
 LOADER com.jme3.texture.plugins.HDRLoader : hdr
 LOADER com.jme3.texture.plugins.TGALoader : tga
-LOADER com.jme3.export.binary.BinaryImporter : j3o
-LOADER com.jme3.export.binary.BinaryImporter : j3f
+LOADER com.jme3.export.binary.BinaryLoader : j3o
+LOADER com.jme3.export.binary.BinaryLoader : j3f
 LOADER com.jme3.scene.plugins.OBJLoader : obj
 LOADER com.jme3.scene.plugins.MTLLoader : mtl
 LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml

+ 41 - 0
jme3-core/src/plugins/java/com/jme3/export/binary/BinaryLoader.java

@@ -0,0 +1,41 @@
+package com.jme3.export.binary;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * The default loader to load binaries files.
+ *
+ * @author JavaSaBr
+ */
+public class BinaryLoader implements AssetLoader {
+
+    /**
+     * The importers queue.
+     */
+    private final Deque<BinaryImporter> importers;
+
+    public BinaryLoader() {
+        importers = new ArrayDeque<>();
+    }
+
+    @Override
+    public Object load(final AssetInfo assetInfo) throws IOException {
+
+        BinaryImporter importer = importers.pollLast();
+
+        if (importer == null) {
+            importer = new BinaryImporter();
+        }
+
+        try {
+            return importer.load(assetInfo);
+        } finally {
+            importers.addLast(importer);
+        }
+    }
+}

+ 3 - 0
jme3-core/src/test/java/com/jme3/collision/CollideIgnoreTransformTest.java

@@ -42,6 +42,8 @@ import com.jme3.scene.Geometry;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.Node;
 import com.jme3.scene.shape.Quad;
+import com.jme3.system.JmeSystem;
+import com.jme3.system.MockJmeSystemDelegate;
 import org.junit.Test;
 
 /**
@@ -87,6 +89,7 @@ public class CollideIgnoreTransformTest {
 
     @Test
     public void testPhantomTriangles() {
+        JmeSystem.setSystemDelegate(new MockJmeSystemDelegate());
         assetManager = new DesktopAssetManager();
         assetManager.registerLocator(null, ClasspathLocator.class);
         assetManager.registerLoader(J3MLoader.class, "j3m", "j3md");

+ 296 - 0
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/AnimData.java

@@ -0,0 +1,296 @@
+package com.jme3.scene.plugins.gltf;
+
+import com.jme3.math.*;
+
+import java.util.*;
+
+public class AnimData {
+
+    public enum Type {
+        Translation,
+        Rotation,
+        Scale
+    }
+
+    Float length;
+    float[] times;
+    List<TimeData> timeArrays = new ArrayList<>();
+
+
+    Vector3f[] translations;
+    Quaternion[] rotations;
+    Vector3f[] scales;
+    //not used for now
+    float[] weights;
+
+    public void update() {
+
+        if (equalTimes(timeArrays)) {
+            times = timeArrays.get(0).times;
+            ensureArraysInit();
+        } else {
+            //Times array are different and contains different sampling times.
+            //We have to merge them because JME needs the 3 types of transforms for each keyFrame.
+
+            //extracting keyframes information
+            List<KeyFrame> keyFrames = new ArrayList<>();
+            TimeData timeData = timeArrays.get(0);
+            Type type = timeData.type;
+            for (int i = 0; i < timeData.times.length; i++) {
+                float time = timeData.times[i];
+                KeyFrame keyFrame = new KeyFrame();
+                keyFrame.time = time;
+                setKeyFrameTransforms(type, keyFrame, timeData.times);
+                keyFrames.add(keyFrame);
+            }
+
+            for (int i = 1; i < timeArrays.size(); i++) {
+                timeData = timeArrays.get(i);
+                type = timeData.type;
+                for (float time : timeData.times) {
+                    for (int j = 0; j < keyFrames.size(); j++) {
+                        KeyFrame kf = keyFrames.get(j);
+                        if (Float.floatToIntBits(kf.time) != Float.floatToIntBits(time)) {
+                            if (time > kf.time) {
+                                continue;
+                            } else {
+                                kf = new KeyFrame();
+                                kf.time = time;
+                                keyFrames.add(j, kf);
+                                //we inserted a keyframe let's shift the counter.
+                                j++;
+                            }
+                        }
+                        setKeyFrameTransforms(type, kf, timeData.times);
+                        break;
+                    }
+                }
+            }
+            // populating transforms array from the keyframes, interpolating
+            times = new float[keyFrames.size()];
+
+            ensureArraysInit();
+
+            TransformIndices translationIndices = new TransformIndices();
+            TransformIndices rotationIndices = new TransformIndices();
+            TransformIndices scaleIndices = new TransformIndices();
+
+            for (int i = 0; i < keyFrames.size(); i++) {
+                KeyFrame kf = keyFrames.get(i);
+                //we need Interpolate between keyframes when transforms are sparse.
+                times[i] = kf.time;
+                populateTransform(Type.Translation, i, keyFrames, kf, translationIndices);
+                populateTransform(Type.Rotation, i, keyFrames, kf, rotationIndices);
+                populateTransform(Type.Scale, i, keyFrames, kf, scaleIndices);
+            }
+        }
+
+        ensureArraysInit();
+
+        if (times[0] > 0) {
+            //Anim doesn't start at 0, JME can't handle that and will interpolate transforms linearly from 0 to the first frame of the anim.
+            //we need to add a frame at 0 that copies the first real frame
+
+            float[] newTimes = new float[times.length + 1];
+            newTimes[0] = 0f;
+            System.arraycopy(times, 0, newTimes, 1, times.length);
+            times = newTimes;
+
+            if (translations != null) {
+                Vector3f[] newTranslations = new Vector3f[translations.length + 1];
+                newTranslations[0] = translations[0];
+                System.arraycopy(translations, 0, newTranslations, 1, translations.length);
+                translations = newTranslations;
+            }
+            if (rotations != null) {
+                Quaternion[] newRotations = new Quaternion[rotations.length + 1];
+                newRotations[0] = rotations[0];
+                System.arraycopy(rotations, 0, newRotations, 1, rotations.length);
+                rotations = newRotations;
+            }
+            if (scales != null) {
+                Vector3f[] newScales = new Vector3f[scales.length + 1];
+                newScales[0] = scales[0];
+                System.arraycopy(scales, 0, newScales, 1, scales.length);
+                scales = newScales;
+            }
+        }
+
+        length = times[times.length - 1];
+    }
+
+    private void populateTransform(Type type, int index, List<KeyFrame> keyFrames, KeyFrame currentKeyFrame, TransformIndices transformIndices) {
+        Object transform = getTransform(type, currentKeyFrame);
+        if (transform != null) {
+            getArray(type)[index] = transform;
+            transformIndices.last = index;
+        } else {
+            transformIndices.next = findNext(keyFrames, type, index);
+            if (transformIndices.next == -1) {
+                //no next let's use prev value.
+                if (transformIndices.last == -1) {
+                    //last Transform Index = -1 it means there are no transforms. nothing more to do
+                    return;
+                }
+                KeyFrame lastKeyFrame = keyFrames.get(transformIndices.last);
+                getArray(type)[index] = getTransform(type, lastKeyFrame);
+                return;
+            }
+            KeyFrame nextKeyFrame = keyFrames.get(transformIndices.next);
+            if (transformIndices.last == -1) {
+                //no previous transforms let's use the new one.
+                translations[index] = nextKeyFrame.translation;
+            } else {
+                //interpolation between the previous transform and the next one.
+                KeyFrame lastKeyFrame = keyFrames.get(transformIndices.last);
+                float ratio = currentKeyFrame.time / (nextKeyFrame.time - lastKeyFrame.time);
+                interpolate(type, ratio, lastKeyFrame, nextKeyFrame, index);
+            }
+
+        }
+    }
+
+    private int findNext(List<KeyFrame> keyFrames, Type type, int fromIndex) {
+        for (int i = fromIndex + 1; i < keyFrames.size(); i++) {
+            KeyFrame kf = keyFrames.get(i);
+            switch (type) {
+                case Translation:
+                    if (kf.translation != null) {
+                        return i;
+                    }
+                    break;
+                case Rotation:
+                    if (kf.rotation != null) {
+                        return i;
+                    }
+                    break;
+                case Scale:
+                    if (kf.scale != null) {
+                        return i;
+                    }
+                    break;
+            }
+        }
+        return -1;
+    }
+
+    private void interpolate(Type type, float ratio, KeyFrame lastKeyFrame, KeyFrame nextKeyFrame, int currentIndex) {
+        //TODO here we should interpolate differently according to the interpolation given in the gltf file.
+        switch (type) {
+            case Translation:
+                translations[currentIndex] = FastMath.interpolateLinear(ratio, lastKeyFrame.translation, nextKeyFrame.translation);
+                break;
+            case Rotation:
+                Quaternion rot = new Quaternion().set(lastKeyFrame.rotation);
+                rot.nlerp(nextKeyFrame.rotation, ratio);
+                rotations[currentIndex] = rot;
+                break;
+            case Scale:
+                scales[currentIndex] = FastMath.interpolateLinear(ratio, lastKeyFrame.scale, nextKeyFrame.scale);
+                break;
+        }
+    }
+
+    private Object[] getArray(Type type) {
+        switch (type) {
+            case Translation:
+                return translations;
+            case Rotation:
+                return rotations;
+            case Scale:
+                return scales;
+            default:
+                return translations;
+        }
+    }
+
+    private Object getTransform(Type type, KeyFrame kf) {
+        switch (type) {
+            case Translation:
+                return kf.translation;
+            case Rotation:
+                return kf.rotation;
+            case Scale:
+                return kf.scale;
+            default:
+                return kf.translation;
+        }
+    }
+
+    private void ensureArraysInit() {
+        if (translations == null || translations.length < times.length) {
+            translations = new Vector3f[times.length];
+            for (int i = 0; i < translations.length; i++) {
+                translations[i] = new Vector3f();
+            }
+        }
+        if (rotations == null || rotations.length < times.length) {
+            rotations = new Quaternion[times.length];
+            for (int i = 0; i < rotations.length; i++) {
+                rotations[i] = new Quaternion();
+            }
+        }
+        if (scales == null || scales.length < times.length) {
+            scales = new Vector3f[times.length];
+            for (int i = 0; i < scales.length; i++) {
+                scales[i] = new Vector3f().set(Vector3f.UNIT_XYZ);
+            }
+        }
+    }
+
+    private void setKeyFrameTransforms(Type type, KeyFrame keyFrame, float[] transformTimes) {
+        int index = 0;
+        while (Float.floatToIntBits(transformTimes[index]) != Float.floatToIntBits(keyFrame.time)) {
+            index++;
+        }
+        switch (type) {
+            case Translation:
+                keyFrame.translation = translations[index];
+                break;
+            case Rotation:
+                keyFrame.rotation = rotations[index];
+                break;
+            case Scale:
+                keyFrame.scale = scales[index];
+                break;
+        }
+    }
+
+    private boolean equalTimes(List<TimeData> timeData) {
+        if (timeData.size() == 1) {
+            return true;
+        }
+        float[] times0 = timeData.get(0).times;
+        for (int i = 1; i < timeData.size(); i++) {
+            float[] timesI = timeData.get(i).times;
+            if (!Arrays.equals(times0, timesI)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static class TimeData {
+
+        float[] times;
+        Type type;
+
+        public TimeData(float[] times, Type type) {
+            this.times = times;
+            this.type = type;
+        }
+    }
+
+    private class TransformIndices {
+        int last = -1;
+        int next = -1;
+    }
+
+    private class KeyFrame {
+        float time;
+        Vector3f translation;
+        Quaternion rotation;
+        Vector3f scale;
+    }
+
+}

+ 6 - 0
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinDataKey.java

@@ -1,6 +1,7 @@
 package com.jme3.scene.plugins.gltf;
 
 import com.jme3.asset.AssetKey;
+import com.jme3.asset.cache.AssetCache;
 
 /**
  * Created by Nehon on 09/09/2017.
@@ -9,4 +10,9 @@ class BinDataKey extends AssetKey<Object> {
     public BinDataKey(String name) {
         super(name);
     }
+
+    @Override
+    public Class<? extends AssetCache> getCacheType() {
+        return null;
+    }
 }

+ 21 - 56
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java

@@ -19,6 +19,7 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
 import javax.xml.bind.DatatypeConverter;
 import java.io.*;
 import java.nio.Buffer;
+import java.sql.Time;
 import java.util.*;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -140,6 +141,8 @@ public class GltfLoader implements AssetLoader {
             return rootNode;
         } catch (Exception e) {
             throw new AssetLoadException("An error occurred loading " + assetInfo.getKey().getName(), e);
+        } finally {
+            stream.close();
         }
     }
 
@@ -527,7 +530,9 @@ public class GltfLoader implements AssetLoader {
                 BinDataKey key = new BinDataKey(info.getKey().getFolder() + uri);
                 InputStream input = (InputStream) info.getManager().loadAsset(key);
                 data = new byte[bufferLength];
-                input.read(data);
+                DataInputStream dataStream = new DataInputStream(input);
+                dataStream.readFully(data);
+                dataStream.close();
             }
         } else {
             //no URI this should not happen in a gltf file, only in glb files.
@@ -756,27 +761,27 @@ public class GltfLoader implements AssetLoader {
                 times = readAccessorData(timeIndex, floatArrayPopulator);
                 addToCache("accessors", timeIndex, times, accessors.size());
             }
-            if (animData.times == null) {
-                animData.times = times;
-            } else {
-                //check if we are loading the same time array
-                if (animData.times != times) {
-                    //TODO there might be work to do here... if the inputs are different we might want to merge the different times array...
-                    //easier said than done.
-                    logger.log(Level.WARNING, "Channel has different input accessors for samplers");
-                }
-            }
-            if (animData.length == null) {
-                //animation length is the last timestamp
-                animData.length = times[times.length - 1];
-            }
+//            if (animData.times == null) {
+//                animData.times = times;
+//            } else {
+//                //check if we are loading the same time array
+//                if (animData.times != times) {
+//                    //TODO there might be work to do here... if the inputs are different we might want to merge the different times array...
+//                    //easier said than done.
+//                    logger.log(Level.WARNING, "Channel has different input accessors for samplers");
+//                }
+//            }
+
             if (targetPath.equals("translation")) {
+                animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Translation));
                 Vector3f[] translations = readAccessorData(dataIndex, vector3fArrayPopulator);
                 animData.translations = translations;
             } else if (targetPath.equals("scale")) {
+                animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Scale));
                 Vector3f[] scales = readAccessorData(dataIndex, vector3fArrayPopulator);
                 animData.scales = scales;
             } else if (targetPath.equals("rotation")) {
+                animData.timeArrays.add(new AnimData.TimeData(times, AnimData.Type.Rotation));
                 Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator);
                 animData.rotations = rotations;
             }
@@ -797,10 +802,10 @@ public class GltfLoader implements AssetLoader {
             if (animData == null) {
                 continue;
             }
+            animData.update();
             if (animData.length > anim.getLength()) {
                 anim.setLength(animData.length);
             }
-            animData.update();
             Object node = fetchFromCache("nodes", i, Object.class);
             if (node instanceof Spatial) {
                 Spatial s = (Spatial) node;
@@ -1136,46 +1141,6 @@ public class GltfLoader implements AssetLoader {
         }
     }
 
-    private class AnimData {
-        Float length;
-        float[] times;
-        Vector3f[] translations;
-        Quaternion[] rotations;
-        Vector3f[] scales;
-        //not used for now
-        float[] weights;
-
-        public void update() {
-            if (times[0] > 0) {
-                //Anim doesn't start at 0, JME can't handle that and will interpolate transforms linearly from 0 to the first frame of the anim.
-                //we need to add a frame at 0 that copies the first real frame
-
-                float[] newTimes = new float[times.length + 1];
-                newTimes[0] = 0f;
-                System.arraycopy(times, 0, newTimes, 1, times.length);
-                times = newTimes;
-
-                if (translations != null) {
-                    Vector3f[] newTranslations = new Vector3f[translations.length + 1];
-                    newTranslations[0] = translations[0];
-                    System.arraycopy(translations, 0, newTranslations, 1, translations.length);
-                    translations = newTranslations;
-                }
-                if (rotations != null) {
-                    Quaternion[] newRotations = new Quaternion[rotations.length + 1];
-                    newRotations[0] = rotations[0];
-                    System.arraycopy(rotations, 0, newRotations, 1, rotations.length);
-                    rotations = newRotations;
-                }
-                if (scales != null) {
-                    Vector3f[] newScales = new Vector3f[scales.length + 1];
-                    newScales[0] = scales[0];
-                    System.arraycopy(scales, 0, newScales, 1, scales.length);
-                    scales = newScales;
-                }
-            }
-        }
-    }
 
     private class BoneWrapper {
         Bone bone;