Bladeren bron

Merge branch 'master' into PBRisComing

Conflicts:
	jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
Nehon 10 jaren geleden
bovenliggende
commit
e1d0e06c59
100 gewijzigde bestanden met toevoegingen van 2616 en 2064 verwijderingen
  1. 580 586
      jme3-android/src/main/java/com/jme3/app/AndroidHarness.java
  2. 0 13
      jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java
  3. 1 1
      jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java
  4. 263 265
      jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java
  5. 257 0
      jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java
  6. 46 130
      jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java
  7. 0 17
      jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java
  8. 12 8
      jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java
  9. 5 7
      jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java
  10. 8 304
      jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java
  11. 134 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java
  12. 68 23
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java
  13. 169 94
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java
  14. 1 70
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java
  15. 4 3
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java
  16. 6 6
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BlenderAction.java
  17. 7 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java
  18. 11 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java
  19. 8 7
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java
  20. 31 14
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java
  21. 46 41
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java
  22. 2 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java
  23. 1 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java
  24. 4 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java
  25. 23 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  26. 8 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java
  27. 6 7
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  28. 38 32
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java
  29. 7 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java
  30. 6 5
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java
  31. 62 34
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java
  32. 14 13
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java
  33. 10 8
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java
  34. 3 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java
  35. 4 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java
  36. 3 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java
  37. 3 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java
  38. 18 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java
  39. 2 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java
  40. 1 1
      jme3-bullet-native/build.gradle
  41. 61 0
      jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp
  42. 9 0
      jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h
  43. 90 0
      jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp
  44. 3 0
      jme3-bullet-native/src/native/cpp/jmeBulletUtil.h
  45. 79 0
      jme3-bullet-native/src/native/cpp/jmeClasses.cpp
  46. 12 0
      jme3-bullet-native/src/native/cpp/jmeClasses.h
  47. 32 29
      jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java
  48. 21 6
      jme3-core/build.gradle
  49. 0 1
      jme3-core/src/main/java/com/jme3/animation/AnimChannel.java
  50. 1 1
      jme3-core/src/main/java/com/jme3/asset/AssetConfig.java
  51. 2 3
      jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java
  52. 3 3
      jme3-core/src/main/java/com/jme3/asset/ImplHandler.java
  53. 0 2
      jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java
  54. 1 5
      jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java
  55. 4 1
      jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java
  56. 1 2
      jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java
  57. 5 0
      jme3-core/src/main/java/com/jme3/collision/CollisionResult.java
  58. 1 1
      jme3-core/src/main/java/com/jme3/light/SpotLight.java
  59. 59 64
      jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
  60. 21 56
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  61. 4 0
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java
  62. 1 1
      jme3-core/src/main/java/com/jme3/scene/BatchNode.java
  63. 1 1
      jme3-core/src/main/java/com/jme3/scene/Node.java
  64. 193 28
      jme3-core/src/main/java/com/jme3/scene/UserData.java
  65. 1 1
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
  66. 1 1
      jme3-core/src/main/java/com/jme3/scene/shape/Dome.java
  67. 1 1
      jme3-core/src/main/java/com/jme3/shader/DefineList.java
  68. 3 1
      jme3-core/src/main/java/com/jme3/system/NullContext.java
  69. 4 3
      jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java
  70. 3 6
      jme3-core/src/main/java/com/jme3/texture/Texture.java
  71. 2 1
      jme3-core/src/main/java/com/jme3/util/IntMap.java
  72. 7 11
      jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java
  73. 1 1
      jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java
  74. 2 1
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert
  75. 1 0
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
  76. 9 1
      jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib
  77. 1 1
      jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java
  78. 1 3
      jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java
  79. 1 0
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  80. 1 1
      jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java
  81. 15 7
      jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java
  82. 0 27
      jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java
  83. 3 3
      jme3-jogl/build.gradle
  84. 5 5
      jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java
  85. 11 11
      jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java
  86. 11 8
      jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java
  87. 14 14
      jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java
  88. 1 1
      jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java
  89. 5 3
      jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java
  90. 1 1
      jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java
  91. 13 13
      jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java
  92. 1 1
      jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java
  93. 2 2
      jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java
  94. 7 7
      jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java
  95. 3 1
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java
  96. 2 2
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java
  97. 2 0
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
  98. 1 1
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java
  99. 1 1
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java
  100. 3 2
      jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java

+ 580 - 586
jme3-android/src/main/java/com/jme3/app/AndroidHarness.java

@@ -1,586 +1,580 @@
-package com.jme3.app;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.pm.ActivityInfo;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.NinePatchDrawable;
-import android.opengl.GLSurfaceView;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.*;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-import com.jme3.audio.AudioRenderer;
-import com.jme3.input.JoyInput;
-import com.jme3.input.TouchInput;
-import com.jme3.input.android.AndroidSensorJoyInput;
-import com.jme3.input.controls.TouchListener;
-import com.jme3.input.controls.TouchTrigger;
-import com.jme3.input.event.TouchEvent;
-import com.jme3.system.AppSettings;
-import com.jme3.system.SystemListener;
-import com.jme3.system.android.JmeAndroidSystem;
-import com.jme3.system.android.OGLESContext;
-import com.jme3.util.AndroidLogHandler;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.LogManager;
-import java.util.logging.Logger;
-
-/**
- * <code>AndroidHarness</code> wraps a jme application object and runs it on
- * Android
- *
- * @author Kirill
- * @author larynx
- */
-public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener, SystemListener {
-
-    protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName());
-    /**
-     * The application class to start
-     */
-    protected String appClass = "jme3test.android.Test";
-    /**
-     * The jme3 application object
-     */
-    protected Application app = null;
-
-    /**
-     * Sets the desired RGB size for the surfaceview.  16 = RGB565, 24 = RGB888.
-     * (default = 24)
-     */
-    protected int eglBitsPerPixel = 24;
-
-    /**
-     * Sets the desired number of Alpha bits for the surfaceview.  This affects
-     * how the surfaceview is able to display Android views that are located
-     * under the surfaceview jME uses to render the scenegraph.
-     * 0 = Opaque surfaceview background (fastest)
-     * 1-&gt;7 = Transparent surfaceview background
-     * 8 or higher = Translucent surfaceview background
-     * (default = 0)
-     */
-    protected int eglAlphaBits = 0;
-
-    /**
-     * The number of depth bits specifies the precision of the depth buffer.
-     * (default = 16)
-     */
-    protected int eglDepthBits = 16;
-
-    /**
-     * Sets the number of samples to use for multisampling.</br>
-     * Leave 0 (default) to disable multisampling.</br>
-     * Set to 2 or 4 to enable multisampling.
-     */
-    protected int eglSamples = 0;
-
-    /**
-     * Set the number of stencil bits.
-     * (default = 0)
-     */
-    protected int eglStencilBits = 0;
-
-    /**
-     * Set the desired frame rate.  If frameRate > 0, the application
-     * will be capped at the desired frame rate.
-     * (default = -1, no frame rate cap)
-     */
-    protected int frameRate = -1;
-
-    /**
-     * Sets the type of Audio Renderer to be used.
-     * <p>
-     * Android MediaPlayer / SoundPool can be used on all
-     * supported Android platform versions (2.2+)<br>
-     * OpenAL Soft uses an OpenSL backend and is only supported on Android
-     * versions 2.3+.
-     * <p>
-     * Only use ANDROID_ static strings found in AppSettings
-     *
-     */
-    protected String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT;
-
-    /**
-     * If true Android Sensors are used as simulated Joysticks. Users can use the
-     * Android sensor feedback through the RawInputListener or by registering
-     * JoyAxisTriggers.
-     */
-    protected boolean joystickEventsEnabled = false;
-    /**
-     * If true KeyEvents are generated from TouchEvents
-     */
-    protected boolean keyEventsEnabled = true;
-    /**
-     * If true MouseEvents are generated from TouchEvents
-     */
-    protected boolean mouseEventsEnabled = true;
-    /**
-     * Flip X axis
-     */
-    protected boolean mouseEventsInvertX = false;
-    /**
-     * Flip Y axis
-     */
-    protected boolean mouseEventsInvertY = false;
-    /**
-     * if true finish this activity when the jme app is stopped
-     */
-    protected boolean finishOnAppStop = true;
-    /**
-     * set to false if you don't want the harness to handle the exit hook
-     */
-    protected boolean handleExitHook = true;
-    /**
-     * Title of the exit dialog, default is "Do you want to exit?"
-     */
-    protected String exitDialogTitle = "Do you want to exit?";
-    /**
-     * Message of the exit dialog, default is "Use your home key to bring this
-     * app into the background or exit to terminate it."
-     */
-    protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";
-    /**
-     * Set the screen window mode. If screenFullSize is true, then the
-     * notification bar and title bar are removed and the screen covers the
-     * entire display. If screenFullSize is false, then the notification bar
-     * remains visible if screenShowTitle is true while screenFullScreen is
-     * false, then the title bar is also displayed under the notification bar.
-     */
-    protected boolean screenFullScreen = true;
-    /**
-     * if screenShowTitle is true while screenFullScreen is false, then the
-     * title bar is also displayed under the notification bar
-     */
-    protected boolean screenShowTitle = true;
-    /**
-     * Splash Screen picture Resource ID. If a Splash Screen is desired, set
-     * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If
-     * splashPicID = 0, then no splash screen will be displayed.
-     */
-    protected int splashPicID = 0;
-
-    /**
-     * No longer used - Use the android:screenOrientation declaration in
-     * the AndroidManifest.xml file.
-     */
-    @Deprecated
-    protected int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR;
-
-    protected OGLESContext ctx;
-    protected GLSurfaceView view = null;
-    protected boolean isGLThreadPaused = true;
-    protected ImageView splashImageView = null;
-    protected FrameLayout frameLayout = null;
-    final private String ESCAPE_EVENT = "TouchEscape";
-    private boolean firstDrawFrame = true;
-    private boolean inConfigChange = false;
-
-    private class DataObject {
-        protected Application app = null;
-    }
-
-    @Override
-    public Object onRetainNonConfigurationInstance() {
-        logger.log(Level.FINE, "onRetainNonConfigurationInstance");
-        final DataObject data = new DataObject();
-        data.app = this.app;
-        inConfigChange = true;
-        return data;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        initializeLogHandler();
-
-        logger.fine("onCreate");
-        super.onCreate(savedInstanceState);
-
-        if (screenFullScreen) {
-            requestWindowFeature(Window.FEATURE_NO_TITLE);
-            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
-                                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
-        } else {
-            if (!screenShowTitle) {
-                requestWindowFeature(Window.FEATURE_NO_TITLE);
-            }
-        }
-
-        final DataObject data = (DataObject) getLastNonConfigurationInstance();
-        if (data != null) {
-            logger.log(Level.FINE, "Using Retained App");
-            this.app = data.app;
-        } else {
-            // Discover the screen reolution
-            //TODO try to find a better way to get a hand on the resolution
-            WindowManager wind = this.getWindowManager();
-            Display disp = wind.getDefaultDisplay();
-            Log.d("AndroidHarness", "Resolution from Window, width:" + disp.getWidth() + ", height: " + disp.getHeight());
-
-            // Create Settings
-            logger.log(Level.FINE, "Creating settings");
-            AppSettings settings = new AppSettings(true);
-            settings.setEmulateMouse(mouseEventsEnabled);
-            settings.setEmulateMouseFlipAxis(mouseEventsInvertX, mouseEventsInvertY);
-            settings.setUseJoysticks(joystickEventsEnabled);
-            settings.setEmulateKeyboard(keyEventsEnabled);
-
-            settings.setBitsPerPixel(eglBitsPerPixel);
-            settings.setAlphaBits(eglAlphaBits);
-            settings.setDepthBits(eglDepthBits);
-            settings.setSamples(eglSamples);
-            settings.setStencilBits(eglStencilBits);
-
-            settings.setResolution(disp.getWidth(), disp.getHeight());
-            settings.setAudioRenderer(audioRendererType);
-
-            settings.setFrameRate(frameRate);
-
-            // Create application instance
-            try {
-                if (app == null) {
-                    @SuppressWarnings("unchecked")
-                    Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
-                    app = clazz.newInstance();
-                }
-
-                app.setSettings(settings);
-                app.start();
-            } catch (Exception ex) {
-                handleError("Class " + appClass + " init failed", ex);
-                setContentView(new TextView(this));
-            }
-        }
-
-        ctx = (OGLESContext) app.getContext();
-        view = ctx.createView(this);
-        // store the glSurfaceView in JmeAndroidSystem for future use
-        JmeAndroidSystem.setView(view);
-        // AndroidHarness wraps the app as a SystemListener.
-        ctx.setSystemListener(this);
-        layoutDisplay();
-    }
-
-    @Override
-    protected void onRestart() {
-        logger.fine("onRestart");
-        super.onRestart();
-        if (app != null) {
-            app.restart();
-        }
-    }
-
-    @Override
-    protected void onStart() {
-        logger.fine("onStart");
-        super.onStart();
-    }
-
-    @Override
-    protected void onResume() {
-        logger.fine("onResume");
-        super.onResume();
-
-        gainFocus();
-    }
-
-    @Override
-    protected void onPause() {
-        logger.fine("onPause");
-        loseFocus();
-
-        super.onPause();
-    }
-
-    @Override
-    protected void onStop() {
-        logger.fine("onStop");
-        super.onStop();
-    }
-
-    @Override
-    protected void onDestroy() {
-        logger.fine("onDestroy");
-        final DataObject data = (DataObject) getLastNonConfigurationInstance();
-        if (data != null || inConfigChange) {
-            logger.fine("In Config Change, not stopping app.");
-        } else {
-            if (app != null) {
-                app.stop(!isGLThreadPaused);
-            }
-        }
-        setContentView(new TextView(this));
-        ctx = null;
-        app = null;
-        view = null;
-        JmeAndroidSystem.setView(null);
-
-        super.onDestroy();
-    }
-
-    public Application getJmeApplication() {
-        return app;
-    }
-
-    /**
-     * Called when an error has occurred. By default, will show an error message
-     * to the user and print the exception/error to the log.
-     */
-    @Override
-    public void handleError(final String errorMsg, final Throwable t) {
-        String stackTrace = "";
-        String title = "Error";
-
-        if (t != null) {
-            // Convert exception to string
-            StringWriter sw = new StringWriter(100);
-            t.printStackTrace(new PrintWriter(sw));
-            stackTrace = sw.toString();
-            title = t.toString();
-        }
-
-        final String finalTitle = title;
-        final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception")
-                + "\n" + stackTrace;
-
-        logger.log(Level.SEVERE, finalMsg);
-
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
-                        .setTitle(finalTitle).setPositiveButton("Kill", AndroidHarness.this).setMessage(finalMsg).create();
-                dialog.show();
-            }
-        });
-    }
-
-    /**
-     * Called by the android alert dialog, terminate the activity and OpenGL
-     * rendering
-     *
-     * @param dialog
-     * @param whichButton
-     */
-    public void onClick(DialogInterface dialog, int whichButton) {
-        if (whichButton != -2) {
-            if (app != null) {
-                app.stop(true);
-            }
-            app = null;
-            this.finish();
-        }
-    }
-
-    /**
-     * Gets called by the InputManager on all touch/drag/scale events
-     */
-    @Override
-    public void onTouch(String name, TouchEvent evt, float tpf) {
-        if (name.equals(ESCAPE_EVENT)) {
-            switch (evt.getType()) {
-                case KEY_UP:
-                    runOnUiThread(new Runnable() {
-                        @Override
-                        public void run() {
-                            AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
-                                    .setTitle(exitDialogTitle).setPositiveButton("Yes", AndroidHarness.this).setNegativeButton("No", AndroidHarness.this).setMessage(exitDialogMessage).create();
-                            dialog.show();
-                        }
-                    });
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
-
-    public void layoutDisplay() {
-        logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID);
-        if (view == null) {
-            logger.log(Level.FINE, "view is null!");
-        }
-        if (splashPicID != 0) {
-            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
-                    LayoutParams.MATCH_PARENT,
-                    LayoutParams.MATCH_PARENT,
-                    Gravity.CENTER);
-
-            frameLayout = new FrameLayout(this);
-            splashImageView = new ImageView(this);
-
-            Drawable drawable = this.getResources().getDrawable(splashPicID);
-            if (drawable instanceof NinePatchDrawable) {
-                splashImageView.setBackgroundDrawable(drawable);
-            } else {
-                splashImageView.setImageResource(splashPicID);
-            }
-
-            if (view.getParent() != null) {
-                ((ViewGroup) view.getParent()).removeView(view);
-            }
-            frameLayout.addView(view);
-
-            if (splashImageView.getParent() != null) {
-                ((ViewGroup) splashImageView.getParent()).removeView(splashImageView);
-            }
-            frameLayout.addView(splashImageView, lp);
-
-            setContentView(frameLayout);
-            logger.log(Level.FINE, "Splash Screen Created");
-        } else {
-            logger.log(Level.FINE, "Splash Screen Skipped.");
-            setContentView(view);
-        }
-    }
-
-    public void removeSplashScreen() {
-        logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID);
-        if (splashPicID != 0) {
-            if (frameLayout != null) {
-                if (splashImageView != null) {
-                    this.runOnUiThread(new Runnable() {
-                        @Override
-                        public void run() {
-                            splashImageView.setVisibility(View.INVISIBLE);
-                            frameLayout.removeView(splashImageView);
-                        }
-                    });
-                } else {
-                    logger.log(Level.FINE, "splashImageView is null");
-                }
-            } else {
-                logger.log(Level.FINE, "frameLayout is null");
-            }
-        }
-    }
-
-    /**
-     * Removes the standard Android log handler due to an issue with not logging
-     * entries lower than INFO level and adds a handler that produces
-     * JME formatted log messages.
-     */
-    protected void initializeLogHandler() {
-        Logger log = LogManager.getLogManager().getLogger("");
-        for (Handler handler : log.getHandlers()) {
-            if (log.getLevel() != null && log.getLevel().intValue() <= Level.FINE.intValue()) {
-                Log.v("AndroidHarness", "Removing Handler class: " + handler.getClass().getName());
-            }
-            log.removeHandler(handler);
-        }
-        Handler handler = new AndroidLogHandler();
-        log.addHandler(handler);
-        handler.setLevel(Level.ALL);
-    }
-
-    public void initialize() {
-        app.initialize();
-        if (handleExitHook) {
-            // remove existing mapping from SimpleApplication that stops the app
-            // when the esc key is pressed (esc key = android back key) so that
-            // AndroidHarness can produce the exit app dialog box.
-            if (app.getInputManager().hasMapping(SimpleApplication.INPUT_MAPPING_EXIT)) {
-                app.getInputManager().deleteMapping(SimpleApplication.INPUT_MAPPING_EXIT);
-            }
-
-            app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK));
-            app.getInputManager().addListener(this, new String[]{ESCAPE_EVENT});
-        }
-    }
-
-    public void reshape(int width, int height) {
-        app.reshape(width, height);
-    }
-
-    public void update() {
-        app.update();
-        // call to remove the splash screen, if present.
-        // call after app.update() to make sure no gap between
-        // splash screen going away and app display being shown.
-        if (firstDrawFrame) {
-            removeSplashScreen();
-            firstDrawFrame = false;
-        }
-    }
-
-    public void requestClose(boolean esc) {
-        app.requestClose(esc);
-    }
-
-    public void destroy() {
-        if (app != null) {
-            app.destroy();
-        }
-        if (finishOnAppStop) {
-            finish();
-        }
-    }
-
-    public void gainFocus() {
-        logger.fine("gainFocus");
-        if (view != null) {
-            view.onResume();
-        }
-
-        if (app != null) {
-            //resume the audio
-            AudioRenderer audioRenderer = app.getAudioRenderer();
-            if (audioRenderer != null) {
-                audioRenderer.resumeAll();
-            }
-            //resume the sensors (aka joysticks)
-            if (app.getContext() != null) {
-                JoyInput joyInput = app.getContext().getJoyInput();
-                if (joyInput != null) {
-                    if (joyInput instanceof AndroidSensorJoyInput) {
-                        AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
-                        androidJoyInput.resumeSensors();
-                    }
-                }
-            }
-        }
-
-        isGLThreadPaused = false;
-
-        if (app != null) {
-            app.gainFocus();
-        }
-    }
-
-    public void loseFocus() {
-        logger.fine("loseFocus");
-        if (app != null) {
-            app.loseFocus();
-        }
-
-        if (view != null) {
-            view.onPause();
-        }
-
-        if (app != null) {
-            //pause the audio
-            AudioRenderer audioRenderer = app.getAudioRenderer();
-            if (audioRenderer != null) {
-                audioRenderer.pauseAll();
-            }
-            //pause the sensors (aka joysticks)
-            if (app.getContext() != null) {
-                JoyInput joyInput = app.getContext().getJoyInput();
-                if (joyInput != null) {
-                    if (joyInput instanceof AndroidSensorJoyInput) {
-                        AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
-                        androidJoyInput.pauseSensors();
-                    }
-                }
-            }
-        }
-        isGLThreadPaused = true;
-    }
-}
+package com.jme3.app;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.pm.ActivityInfo;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.*;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.input.JoyInput;
+import com.jme3.input.TouchInput;
+import com.jme3.input.android.AndroidSensorJoyInput;
+import com.jme3.input.controls.TouchListener;
+import com.jme3.input.controls.TouchTrigger;
+import com.jme3.input.event.TouchEvent;
+import com.jme3.system.AppSettings;
+import com.jme3.system.SystemListener;
+import com.jme3.system.android.JmeAndroidSystem;
+import com.jme3.system.android.OGLESContext;
+import com.jme3.util.AndroidLogHandler;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+/**
+ * <code>AndroidHarness</code> wraps a jme application object and runs it on
+ * Android
+ *
+ * @author Kirill
+ * @author larynx
+ */
+public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener, SystemListener {
+
+    protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName());
+    /**
+     * The application class to start
+     */
+    protected String appClass = "jme3test.android.Test";
+    /**
+     * The jme3 application object
+     */
+    protected Application app = null;
+
+    /**
+     * Sets the desired RGB size for the surfaceview.  16 = RGB565, 24 = RGB888.
+     * (default = 24)
+     */
+    protected int eglBitsPerPixel = 24;
+
+    /**
+     * Sets the desired number of Alpha bits for the surfaceview.  This affects
+     * how the surfaceview is able to display Android views that are located
+     * under the surfaceview jME uses to render the scenegraph.
+     * 0 = Opaque surfaceview background (fastest)
+     * 1-&gt;7 = Transparent surfaceview background
+     * 8 or higher = Translucent surfaceview background
+     * (default = 0)
+     */
+    protected int eglAlphaBits = 0;
+
+    /**
+     * The number of depth bits specifies the precision of the depth buffer.
+     * (default = 16)
+     */
+    protected int eglDepthBits = 16;
+
+    /**
+     * Sets the number of samples to use for multisampling.</br>
+     * Leave 0 (default) to disable multisampling.</br>
+     * Set to 2 or 4 to enable multisampling.
+     */
+    protected int eglSamples = 0;
+
+    /**
+     * Set the number of stencil bits.
+     * (default = 0)
+     */
+    protected int eglStencilBits = 0;
+
+    /**
+     * Set the desired frame rate.  If frameRate > 0, the application
+     * will be capped at the desired frame rate.
+     * (default = -1, no frame rate cap)
+     */
+    protected int frameRate = -1;
+
+    /**
+     * Sets the type of Audio Renderer to be used.
+     * <p>
+     * Android MediaPlayer / SoundPool can be used on all
+     * supported Android platform versions (2.2+)<br>
+     * OpenAL Soft uses an OpenSL backend and is only supported on Android
+     * versions 2.3+.
+     * <p>
+     * Only use ANDROID_ static strings found in AppSettings
+     *
+     */
+    protected String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT;
+
+    /**
+     * If true Android Sensors are used as simulated Joysticks. Users can use the
+     * Android sensor feedback through the RawInputListener or by registering
+     * JoyAxisTriggers.
+     */
+    protected boolean joystickEventsEnabled = false;
+    /**
+     * If true KeyEvents are generated from TouchEvents
+     */
+    protected boolean keyEventsEnabled = true;
+    /**
+     * If true MouseEvents are generated from TouchEvents
+     */
+    protected boolean mouseEventsEnabled = true;
+    /**
+     * Flip X axis
+     */
+    protected boolean mouseEventsInvertX = false;
+    /**
+     * Flip Y axis
+     */
+    protected boolean mouseEventsInvertY = false;
+    /**
+     * if true finish this activity when the jme app is stopped
+     */
+    protected boolean finishOnAppStop = true;
+    /**
+     * set to false if you don't want the harness to handle the exit hook
+     */
+    protected boolean handleExitHook = true;
+    /**
+     * Title of the exit dialog, default is "Do you want to exit?"
+     */
+    protected String exitDialogTitle = "Do you want to exit?";
+    /**
+     * Message of the exit dialog, default is "Use your home key to bring this
+     * app into the background or exit to terminate it."
+     */
+    protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";
+    /**
+     * Set the screen window mode. If screenFullSize is true, then the
+     * notification bar and title bar are removed and the screen covers the
+     * entire display. If screenFullSize is false, then the notification bar
+     * remains visible if screenShowTitle is true while screenFullScreen is
+     * false, then the title bar is also displayed under the notification bar.
+     */
+    protected boolean screenFullScreen = true;
+    /**
+     * if screenShowTitle is true while screenFullScreen is false, then the
+     * title bar is also displayed under the notification bar
+     */
+    protected boolean screenShowTitle = true;
+    /**
+     * Splash Screen picture Resource ID. If a Splash Screen is desired, set
+     * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If
+     * splashPicID = 0, then no splash screen will be displayed.
+     */
+    protected int splashPicID = 0;
+
+
+    protected OGLESContext ctx;
+    protected GLSurfaceView view = null;
+    protected boolean isGLThreadPaused = true;
+    protected ImageView splashImageView = null;
+    protected FrameLayout frameLayout = null;
+    final private String ESCAPE_EVENT = "TouchEscape";
+    private boolean firstDrawFrame = true;
+    private boolean inConfigChange = false;
+
+    private class DataObject {
+        protected Application app = null;
+    }
+
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        logger.log(Level.FINE, "onRetainNonConfigurationInstance");
+        final DataObject data = new DataObject();
+        data.app = this.app;
+        inConfigChange = true;
+        return data;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        initializeLogHandler();
+
+        logger.fine("onCreate");
+        super.onCreate(savedInstanceState);
+
+        if (screenFullScreen) {
+            requestWindowFeature(Window.FEATURE_NO_TITLE);
+            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+                                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        } else {
+            if (!screenShowTitle) {
+                requestWindowFeature(Window.FEATURE_NO_TITLE);
+            }
+        }
+
+        final DataObject data = (DataObject) getLastNonConfigurationInstance();
+        if (data != null) {
+            logger.log(Level.FINE, "Using Retained App");
+            this.app = data.app;
+        } else {
+            // Discover the screen reolution
+            //TODO try to find a better way to get a hand on the resolution
+            WindowManager wind = this.getWindowManager();
+            Display disp = wind.getDefaultDisplay();
+            Log.d("AndroidHarness", "Resolution from Window, width:" + disp.getWidth() + ", height: " + disp.getHeight());
+
+            // Create Settings
+            logger.log(Level.FINE, "Creating settings");
+            AppSettings settings = new AppSettings(true);
+            settings.setEmulateMouse(mouseEventsEnabled);
+            settings.setEmulateMouseFlipAxis(mouseEventsInvertX, mouseEventsInvertY);
+            settings.setUseJoysticks(joystickEventsEnabled);
+            settings.setEmulateKeyboard(keyEventsEnabled);
+
+            settings.setBitsPerPixel(eglBitsPerPixel);
+            settings.setAlphaBits(eglAlphaBits);
+            settings.setDepthBits(eglDepthBits);
+            settings.setSamples(eglSamples);
+            settings.setStencilBits(eglStencilBits);
+
+            settings.setResolution(disp.getWidth(), disp.getHeight());
+            settings.setAudioRenderer(audioRendererType);
+
+            settings.setFrameRate(frameRate);
+
+            // Create application instance
+            try {
+                if (app == null) {
+                    @SuppressWarnings("unchecked")
+                    Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
+                    app = clazz.newInstance();
+                }
+
+                app.setSettings(settings);
+                app.start();
+            } catch (Exception ex) {
+                handleError("Class " + appClass + " init failed", ex);
+                setContentView(new TextView(this));
+            }
+        }
+
+        ctx = (OGLESContext) app.getContext();
+        view = ctx.createView(this);
+        // store the glSurfaceView in JmeAndroidSystem for future use
+        JmeAndroidSystem.setView(view);
+        // AndroidHarness wraps the app as a SystemListener.
+        ctx.setSystemListener(this);
+        layoutDisplay();
+    }
+
+    @Override
+    protected void onRestart() {
+        logger.fine("onRestart");
+        super.onRestart();
+        if (app != null) {
+            app.restart();
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        logger.fine("onStart");
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        logger.fine("onResume");
+        super.onResume();
+
+        gainFocus();
+    }
+
+    @Override
+    protected void onPause() {
+        logger.fine("onPause");
+        loseFocus();
+
+        super.onPause();
+    }
+
+    @Override
+    protected void onStop() {
+        logger.fine("onStop");
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        logger.fine("onDestroy");
+        final DataObject data = (DataObject) getLastNonConfigurationInstance();
+        if (data != null || inConfigChange) {
+            logger.fine("In Config Change, not stopping app.");
+        } else {
+            if (app != null) {
+                app.stop(!isGLThreadPaused);
+            }
+        }
+        setContentView(new TextView(this));
+        ctx = null;
+        app = null;
+        view = null;
+        JmeAndroidSystem.setView(null);
+
+        super.onDestroy();
+    }
+
+    public Application getJmeApplication() {
+        return app;
+    }
+
+    /**
+     * Called when an error has occurred. By default, will show an error message
+     * to the user and print the exception/error to the log.
+     */
+    @Override
+    public void handleError(final String errorMsg, final Throwable t) {
+        String stackTrace = "";
+        String title = "Error";
+
+        if (t != null) {
+            // Convert exception to string
+            StringWriter sw = new StringWriter(100);
+            t.printStackTrace(new PrintWriter(sw));
+            stackTrace = sw.toString();
+            title = t.toString();
+        }
+
+        final String finalTitle = title;
+        final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception")
+                + "\n" + stackTrace;
+
+        logger.log(Level.SEVERE, finalMsg);
+
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
+                        .setTitle(finalTitle).setPositiveButton("Kill", AndroidHarness.this).setMessage(finalMsg).create();
+                dialog.show();
+            }
+        });
+    }
+
+    /**
+     * Called by the android alert dialog, terminate the activity and OpenGL
+     * rendering
+     *
+     * @param dialog
+     * @param whichButton
+     */
+    public void onClick(DialogInterface dialog, int whichButton) {
+        if (whichButton != -2) {
+            if (app != null) {
+                app.stop(true);
+            }
+            app = null;
+            this.finish();
+        }
+    }
+
+    /**
+     * Gets called by the InputManager on all touch/drag/scale events
+     */
+    @Override
+    public void onTouch(String name, TouchEvent evt, float tpf) {
+        if (name.equals(ESCAPE_EVENT)) {
+            switch (evt.getType()) {
+                case KEY_UP:
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
+                                    .setTitle(exitDialogTitle).setPositiveButton("Yes", AndroidHarness.this).setNegativeButton("No", AndroidHarness.this).setMessage(exitDialogMessage).create();
+                            dialog.show();
+                        }
+                    });
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    public void layoutDisplay() {
+        logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID);
+        if (view == null) {
+            logger.log(Level.FINE, "view is null!");
+        }
+        if (splashPicID != 0) {
+            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+                    LayoutParams.MATCH_PARENT,
+                    LayoutParams.MATCH_PARENT,
+                    Gravity.CENTER);
+
+            frameLayout = new FrameLayout(this);
+            splashImageView = new ImageView(this);
+
+            Drawable drawable = this.getResources().getDrawable(splashPicID);
+            if (drawable instanceof NinePatchDrawable) {
+                splashImageView.setBackgroundDrawable(drawable);
+            } else {
+                splashImageView.setImageResource(splashPicID);
+            }
+
+            if (view.getParent() != null) {
+                ((ViewGroup) view.getParent()).removeView(view);
+            }
+            frameLayout.addView(view);
+
+            if (splashImageView.getParent() != null) {
+                ((ViewGroup) splashImageView.getParent()).removeView(splashImageView);
+            }
+            frameLayout.addView(splashImageView, lp);
+
+            setContentView(frameLayout);
+            logger.log(Level.FINE, "Splash Screen Created");
+        } else {
+            logger.log(Level.FINE, "Splash Screen Skipped.");
+            setContentView(view);
+        }
+    }
+
+    public void removeSplashScreen() {
+        logger.log(Level.FINE, "Splash Screen Picture Resource ID: {0}", splashPicID);
+        if (splashPicID != 0) {
+            if (frameLayout != null) {
+                if (splashImageView != null) {
+                    this.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            splashImageView.setVisibility(View.INVISIBLE);
+                            frameLayout.removeView(splashImageView);
+                        }
+                    });
+                } else {
+                    logger.log(Level.FINE, "splashImageView is null");
+                }
+            } else {
+                logger.log(Level.FINE, "frameLayout is null");
+            }
+        }
+    }
+
+    /**
+     * Removes the standard Android log handler due to an issue with not logging
+     * entries lower than INFO level and adds a handler that produces
+     * JME formatted log messages.
+     */
+    protected void initializeLogHandler() {
+        Logger log = LogManager.getLogManager().getLogger("");
+        for (Handler handler : log.getHandlers()) {
+            if (log.getLevel() != null && log.getLevel().intValue() <= Level.FINE.intValue()) {
+                Log.v("AndroidHarness", "Removing Handler class: " + handler.getClass().getName());
+            }
+            log.removeHandler(handler);
+        }
+        Handler handler = new AndroidLogHandler();
+        log.addHandler(handler);
+        handler.setLevel(Level.ALL);
+    }
+
+    public void initialize() {
+        app.initialize();
+        if (handleExitHook) {
+            // remove existing mapping from SimpleApplication that stops the app
+            // when the esc key is pressed (esc key = android back key) so that
+            // AndroidHarness can produce the exit app dialog box.
+            if (app.getInputManager().hasMapping(SimpleApplication.INPUT_MAPPING_EXIT)) {
+                app.getInputManager().deleteMapping(SimpleApplication.INPUT_MAPPING_EXIT);
+            }
+
+            app.getInputManager().addMapping(ESCAPE_EVENT, new TouchTrigger(TouchInput.KEYCODE_BACK));
+            app.getInputManager().addListener(this, new String[]{ESCAPE_EVENT});
+        }
+    }
+
+    public void reshape(int width, int height) {
+        app.reshape(width, height);
+    }
+
+    public void update() {
+        app.update();
+        // call to remove the splash screen, if present.
+        // call after app.update() to make sure no gap between
+        // splash screen going away and app display being shown.
+        if (firstDrawFrame) {
+            removeSplashScreen();
+            firstDrawFrame = false;
+        }
+    }
+
+    public void requestClose(boolean esc) {
+        app.requestClose(esc);
+    }
+
+    public void destroy() {
+        if (app != null) {
+            app.destroy();
+        }
+        if (finishOnAppStop) {
+            finish();
+        }
+    }
+
+    public void gainFocus() {
+        logger.fine("gainFocus");
+        if (view != null) {
+            view.onResume();
+        }
+
+        if (app != null) {
+            //resume the audio
+            AudioRenderer audioRenderer = app.getAudioRenderer();
+            if (audioRenderer != null) {
+                audioRenderer.resumeAll();
+            }
+            //resume the sensors (aka joysticks)
+            if (app.getContext() != null) {
+                JoyInput joyInput = app.getContext().getJoyInput();
+                if (joyInput != null) {
+                    if (joyInput instanceof AndroidSensorJoyInput) {
+                        AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
+                        androidJoyInput.resumeSensors();
+                    }
+                }
+            }
+        }
+
+        isGLThreadPaused = false;
+
+        if (app != null) {
+            app.gainFocus();
+        }
+    }
+
+    public void loseFocus() {
+        logger.fine("loseFocus");
+        if (app != null) {
+            app.loseFocus();
+        }
+
+        if (view != null) {
+            view.onPause();
+        }
+
+        if (app != null) {
+            //pause the audio
+            AudioRenderer audioRenderer = app.getAudioRenderer();
+            if (audioRenderer != null) {
+                audioRenderer.pauseAll();
+            }
+            //pause the sensors (aka joysticks)
+            if (app.getContext() != null) {
+                JoyInput joyInput = app.getContext().getJoyInput();
+                if (joyInput != null) {
+                    if (joyInput instanceof AndroidSensorJoyInput) {
+                        AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
+                        androidJoyInput.pauseSensors();
+                    }
+                }
+            }
+        }
+        isGLThreadPaused = true;
+    }
+}

+ 0 - 13
jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java

@@ -195,19 +195,6 @@ public class AndroidHarnessFragment extends Fragment implements
      */
     protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";
 
-    /**
-     * Set the screen window mode. If screenFullSize is true, then the
-     * notification bar and title bar are removed and the screen covers the
-     * entire display. If screenFullSize is false, then the notification bar
-     * remains visible if screenShowTitle is true while screenFullScreen is
-     * false, then the title bar is also displayed under the notification bar.
-     */
-    protected boolean screenFullScreen = true;
-    /**
-     * if screenShowTitle is true while screenFullScreen is false, then the
-     * title bar is also displayed under the notification bar
-     */
-    protected boolean screenShowTitle = true;
     /**
      * Splash Screen picture Resource ID. If a Splash Screen is desired, set
      * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If

+ 1 - 1
jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java

@@ -74,7 +74,7 @@ public class VideoRecorderAppState extends AbstractAppState {
 
         public Thread newThread(Runnable r) {
             Thread th = new Thread(r);
-            th.setName("jME Video Processing Thread");
+            th.setName("jME3 Video Processor");
             th.setDaemon(true);
             return th;
         }

+ 263 - 265
jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java

@@ -1,265 +1,263 @@
-/*
- * 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 com.jme3.input.android;
-
-import android.opengl.GLSurfaceView;
-import android.os.Build;
-import android.view.View;
-import com.jme3.input.RawInputListener;
-import com.jme3.input.TouchInput;
-import com.jme3.input.event.InputEvent;
-import com.jme3.input.event.KeyInputEvent;
-import com.jme3.input.event.MouseButtonEvent;
-import com.jme3.input.event.MouseMotionEvent;
-import com.jme3.input.event.TouchEvent;
-import com.jme3.system.AppSettings;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * <code>AndroidInput</code> is the main class that connects the Android system
- * inputs to jME. It serves as the manager that gathers inputs from the various
- * Android input methods and provides them to jME's <code>InputManager</code>.
- *
- * @author iwgeric
- */
-public class AndroidInputHandler implements TouchInput {
-    private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName());
-
-    // Custom settings
-    private boolean mouseEventsEnabled = true;
-    private boolean mouseEventsInvertX = false;
-    private boolean mouseEventsInvertY = false;
-    private boolean keyboardEventsEnabled = false;
-    private boolean joystickEventsEnabled = false;
-    private boolean dontSendHistory = false;
-
-
-    // Internal
-    private GLSurfaceView view;
-    private AndroidTouchHandler touchHandler;
-    private AndroidKeyHandler keyHandler;
-    private AndroidGestureHandler gestureHandler;
-    private boolean initialized = false;
-    private RawInputListener listener = null;
-    private ConcurrentLinkedQueue<InputEvent> inputEventQueue = new ConcurrentLinkedQueue<InputEvent>();
-    private final static int MAX_TOUCH_EVENTS = 1024;
-    private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS);
-    private float scaleX = 1f;
-    private float scaleY = 1f;
-
-
-    public AndroidInputHandler() {
-        int buildVersion = Build.VERSION.SDK_INT;
-        logger.log(Level.INFO, "Android Build Version: {0}", buildVersion);
-        if (buildVersion >= 14) {
-            // add support for onHover and GenericMotionEvent (ie. gamepads)
-            gestureHandler = new AndroidGestureHandler(this);
-            touchHandler = new AndroidTouchHandler14(this, gestureHandler);
-            keyHandler = new AndroidKeyHandler(this);
-        } else if (buildVersion >= 8){
-            gestureHandler = new AndroidGestureHandler(this);
-            touchHandler = new AndroidTouchHandler(this, gestureHandler);
-            keyHandler = new AndroidKeyHandler(this);
-        }
-    }
-
-    public AndroidInputHandler(AndroidTouchHandler touchInput,
-            AndroidKeyHandler keyInput, AndroidGestureHandler gestureHandler) {
-        this.touchHandler = touchInput;
-        this.keyHandler = keyInput;
-        this.gestureHandler = gestureHandler;
-    }
-
-    public void setView(View view) {
-        if (touchHandler != null) {
-            touchHandler.setView(view);
-        }
-        if (keyHandler != null) {
-            keyHandler.setView(view);
-        }
-        if (gestureHandler != null) {
-            gestureHandler.setView(view);
-        }
-        this.view = (GLSurfaceView)view;
-    }
-
-    public View getView() {
-        return view;
-    }
-
-    public float invertX(float origX) {
-        return getJmeX(view.getWidth()) - origX;
-    }
-
-    public float invertY(float origY) {
-        return getJmeY(view.getHeight()) - origY;
-    }
-
-    public float getJmeX(float origX) {
-        return origX * scaleX;
-    }
-
-    public float getJmeY(float origY) {
-        return origY * scaleY;
-    }
-
-    public void loadSettings(AppSettings settings) {
-        keyboardEventsEnabled = settings.isEmulateKeyboard();
-        mouseEventsEnabled = settings.isEmulateMouse();
-        mouseEventsInvertX = settings.isEmulateMouseFlipX();
-        mouseEventsInvertY = settings.isEmulateMouseFlipY();
-        joystickEventsEnabled = settings.useJoysticks();
-
-        // view width and height are 0 until the view is displayed on the screen
-        if (view.getWidth() != 0 && view.getHeight() != 0) {
-            scaleX = (float)settings.getWidth() / (float)view.getWidth();
-            scaleY = (float)settings.getHeight() / (float)view.getHeight();
-        }
-        logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}",
-                new Object[]{scaleX, scaleY});
-
-    }
-
-        // -----------------------------------------
-    // JME3 Input interface
-    @Override
-    public void initialize() {
-        touchEventPool.initialize();
-        if (touchHandler != null) {
-            touchHandler.initialize();
-        }
-        if (keyHandler != null) {
-            keyHandler.initialize();
-        }
-        if (gestureHandler != null) {
-            gestureHandler.initialize();
-        }
-
-        initialized = true;
-    }
-
-    @Override
-    public void destroy() {
-        initialized = false;
-
-        touchEventPool.destroy();
-        if (touchHandler != null) {
-            touchHandler.destroy();
-        }
-        if (keyHandler != null) {
-            keyHandler.destroy();
-        }
-        if (gestureHandler != null) {
-            gestureHandler.destroy();
-        }
-
-        setView(null);
-    }
-
-    @Override
-    public boolean isInitialized() {
-        return initialized;
-    }
-
-    @Override
-    public void setInputListener(RawInputListener listener) {
-        this.listener = listener;
-    }
-
-    @Override
-    public long getInputTimeNanos() {
-        return System.nanoTime();
-    }
-
-    public void update() {
-        if (listener != null) {
-            InputEvent inputEvent;
-
-            while ((inputEvent = inputEventQueue.poll()) != null) {
-                if (inputEvent instanceof TouchEvent) {
-                    listener.onTouchEvent((TouchEvent)inputEvent);
-                } else if (inputEvent instanceof MouseButtonEvent) {
-                    listener.onMouseButtonEvent((MouseButtonEvent)inputEvent);
-                } else if (inputEvent instanceof MouseMotionEvent) {
-                    listener.onMouseMotionEvent((MouseMotionEvent)inputEvent);
-                } else if (inputEvent instanceof KeyInputEvent) {
-                    listener.onKeyEvent((KeyInputEvent)inputEvent);
-                }
-            }
-        }
-    }
-
-    // -----------------------------------------
-
-    public TouchEvent getFreeTouchEvent() {
-            return touchEventPool.getNextFreeEvent();
-    }
-
-    public void addEvent(InputEvent event) {
-        inputEventQueue.add(event);
-        if (event instanceof TouchEvent) {
-            touchEventPool.storeEvent((TouchEvent)event);
-        }
-    }
-
-    public void setSimulateMouse(boolean simulate) {
-        this.mouseEventsEnabled = simulate;
-    }
-
-    public boolean isSimulateMouse() {
-        return mouseEventsEnabled;
-    }
-
-    public boolean isMouseEventsInvertX() {
-        return mouseEventsInvertX;
-    }
-
-    public boolean isMouseEventsInvertY() {
-        return mouseEventsInvertY;
-    }
-
-    public void setSimulateKeyboard(boolean simulate) {
-        this.keyboardEventsEnabled = simulate;
-    }
-
-    public boolean isSimulateKeyboard() {
-        return keyboardEventsEnabled;
-    }
-
-    public void setOmitHistoricEvents(boolean dontSendHistory) {
-        this.dontSendHistory = dontSendHistory;
-    }
-
-}
+/*
+ * 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 com.jme3.input.android;
+
+import android.opengl.GLSurfaceView;
+import android.os.Build;
+import android.view.View;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.TouchInput;
+import com.jme3.input.event.InputEvent;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.input.event.TouchEvent;
+import com.jme3.system.AppSettings;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AndroidInput</code> is the main class that connects the Android system
+ * inputs to jME. It serves as the manager that gathers inputs from the various
+ * Android input methods and provides them to jME's <code>InputManager</code>.
+ *
+ * @author iwgeric
+ */
+public class AndroidInputHandler implements TouchInput {
+    private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName());
+
+    // Custom settings
+    private boolean mouseEventsEnabled = true;
+    private boolean mouseEventsInvertX = false;
+    private boolean mouseEventsInvertY = false;
+    private boolean keyboardEventsEnabled = false;
+    private boolean dontSendHistory = false;
+
+
+    // Internal
+    private GLSurfaceView view;
+    private AndroidTouchHandler touchHandler;
+    private AndroidKeyHandler keyHandler;
+    private AndroidGestureHandler gestureHandler;
+    private boolean initialized = false;
+    private RawInputListener listener = null;
+    private ConcurrentLinkedQueue<InputEvent> inputEventQueue = new ConcurrentLinkedQueue<InputEvent>();
+    private final static int MAX_TOUCH_EVENTS = 1024;
+    private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS);
+    private float scaleX = 1f;
+    private float scaleY = 1f;
+
+
+    public AndroidInputHandler() {
+        int buildVersion = Build.VERSION.SDK_INT;
+        logger.log(Level.INFO, "Android Build Version: {0}", buildVersion);
+        if (buildVersion >= 14) {
+            // add support for onHover and GenericMotionEvent (ie. gamepads)
+            gestureHandler = new AndroidGestureHandler(this);
+            touchHandler = new AndroidTouchHandler14(this, gestureHandler);
+            keyHandler = new AndroidKeyHandler(this);
+        } else if (buildVersion >= 8){
+            gestureHandler = new AndroidGestureHandler(this);
+            touchHandler = new AndroidTouchHandler(this, gestureHandler);
+            keyHandler = new AndroidKeyHandler(this);
+        }
+    }
+
+    public AndroidInputHandler(AndroidTouchHandler touchInput,
+            AndroidKeyHandler keyInput, AndroidGestureHandler gestureHandler) {
+        this.touchHandler = touchInput;
+        this.keyHandler = keyInput;
+        this.gestureHandler = gestureHandler;
+    }
+
+    public void setView(View view) {
+        if (touchHandler != null) {
+            touchHandler.setView(view);
+        }
+        if (keyHandler != null) {
+            keyHandler.setView(view);
+        }
+        if (gestureHandler != null) {
+            gestureHandler.setView(view);
+        }
+        this.view = (GLSurfaceView)view;
+    }
+
+    public View getView() {
+        return view;
+    }
+
+    public float invertX(float origX) {
+        return getJmeX(view.getWidth()) - origX;
+    }
+
+    public float invertY(float origY) {
+        return getJmeY(view.getHeight()) - origY;
+    }
+
+    public float getJmeX(float origX) {
+        return origX * scaleX;
+    }
+
+    public float getJmeY(float origY) {
+        return origY * scaleY;
+    }
+
+    public void loadSettings(AppSettings settings) {
+        keyboardEventsEnabled = settings.isEmulateKeyboard();
+        mouseEventsEnabled = settings.isEmulateMouse();
+        mouseEventsInvertX = settings.isEmulateMouseFlipX();
+        mouseEventsInvertY = settings.isEmulateMouseFlipY();
+
+        // view width and height are 0 until the view is displayed on the screen
+        if (view.getWidth() != 0 && view.getHeight() != 0) {
+            scaleX = (float)settings.getWidth() / (float)view.getWidth();
+            scaleY = (float)settings.getHeight() / (float)view.getHeight();
+        }
+        logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}",
+                new Object[]{scaleX, scaleY});
+
+    }
+
+        // -----------------------------------------
+    // JME3 Input interface
+    @Override
+    public void initialize() {
+        touchEventPool.initialize();
+        if (touchHandler != null) {
+            touchHandler.initialize();
+        }
+        if (keyHandler != null) {
+            keyHandler.initialize();
+        }
+        if (gestureHandler != null) {
+            gestureHandler.initialize();
+        }
+
+        initialized = true;
+    }
+
+    @Override
+    public void destroy() {
+        initialized = false;
+
+        touchEventPool.destroy();
+        if (touchHandler != null) {
+            touchHandler.destroy();
+        }
+        if (keyHandler != null) {
+            keyHandler.destroy();
+        }
+        if (gestureHandler != null) {
+            gestureHandler.destroy();
+        }
+
+        setView(null);
+    }
+
+    @Override
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    @Override
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public long getInputTimeNanos() {
+        return System.nanoTime();
+    }
+
+    public void update() {
+        if (listener != null) {
+            InputEvent inputEvent;
+
+            while ((inputEvent = inputEventQueue.poll()) != null) {
+                if (inputEvent instanceof TouchEvent) {
+                    listener.onTouchEvent((TouchEvent)inputEvent);
+                } else if (inputEvent instanceof MouseButtonEvent) {
+                    listener.onMouseButtonEvent((MouseButtonEvent)inputEvent);
+                } else if (inputEvent instanceof MouseMotionEvent) {
+                    listener.onMouseMotionEvent((MouseMotionEvent)inputEvent);
+                } else if (inputEvent instanceof KeyInputEvent) {
+                    listener.onKeyEvent((KeyInputEvent)inputEvent);
+                }
+            }
+        }
+    }
+
+    // -----------------------------------------
+
+    public TouchEvent getFreeTouchEvent() {
+            return touchEventPool.getNextFreeEvent();
+    }
+
+    public void addEvent(InputEvent event) {
+        inputEventQueue.add(event);
+        if (event instanceof TouchEvent) {
+            touchEventPool.storeEvent((TouchEvent)event);
+        }
+    }
+
+    public void setSimulateMouse(boolean simulate) {
+        this.mouseEventsEnabled = simulate;
+    }
+
+    public boolean isSimulateMouse() {
+        return mouseEventsEnabled;
+    }
+
+    public boolean isMouseEventsInvertX() {
+        return mouseEventsInvertX;
+    }
+
+    public boolean isMouseEventsInvertY() {
+        return mouseEventsInvertY;
+    }
+
+    public void setSimulateKeyboard(boolean simulate) {
+        this.keyboardEventsEnabled = simulate;
+    }
+
+    public boolean isSimulateKeyboard() {
+        return keyboardEventsEnabled;
+    }
+
+    public void setOmitHistoricEvents(boolean dontSendHistory) {
+        this.dontSendHistory = dontSendHistory;
+    }
+
+}

+ 257 - 0
jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java

@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2009-2015 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.input.android;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.os.Build;
+import android.os.Vibrator;
+import android.view.View;
+import com.jme3.input.InputManager;
+import com.jme3.input.JoyInput;
+import com.jme3.input.Joystick;
+import com.jme3.input.RawInputListener;
+import com.jme3.input.event.InputEvent;
+import com.jme3.input.event.JoyAxisEvent;
+import com.jme3.input.event.JoyButtonEvent;
+import com.jme3.system.AppSettings;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Main class that manages various joystick devices.  Joysticks can be many forms
+ * including a simulated joystick to communicate the device orientation as well
+ * as physical gamepads. </br>
+ * This class manages all the joysticks and feeds the inputs from each back
+ * to jME's InputManager.
+ *
+ * This handler also supports the joystick.rumble(rumbleAmount) method.  In this
+ * case, when joystick.rumble(rumbleAmount) is called, the Android device will vibrate
+ * if the device has a built in vibrate motor.
+ *
+ * Because Andorid does not allow for the user to define the intensity of the
+ * vibration, the rumble amount (ie strength) is converted into vibration pulses
+ * The stronger the strength amount, the shorter the delay between pulses.  If
+ * amount is 1, then the vibration stays on the whole time.  If amount is 0.5,
+ * the vibration will a pulse of equal parts vibration and delay.
+ * To turn off vibration, set rumble amount to 0.
+ *
+ * MainActivity needs the following line to enable Joysticks on Android platforms
+ *    joystickEventsEnabled = true;
+ * This is done to allow for battery conservation when sensor data or gamepads
+ * are not required by the application.
+ *
+ * To use the joystick rumble feature, the following line needs to be
+ * added to the Android Manifest File
+ *     <uses-permission android:name="android.permission.VIBRATE"/>
+ *
+ * @author iwgeric
+ */
+public class AndroidJoyInputHandler implements JoyInput {
+    private static final Logger logger = Logger.getLogger(AndroidJoyInputHandler.class.getName());
+
+    private List<Joystick> joystickList = new ArrayList<Joystick>();
+//    private boolean dontSendHistory = false;
+
+
+    // Internal
+    private GLSurfaceView view;
+    private boolean initialized = false;
+    private RawInputListener listener = null;
+    private ConcurrentLinkedQueue<InputEvent> eventQueue = new ConcurrentLinkedQueue<InputEvent>();
+    private AndroidSensorJoyInput sensorJoyInput;
+    private Vibrator vibrator = null;
+    private boolean vibratorActive = false;
+    private long maxRumbleTime = 250;  // 250ms
+
+    public AndroidJoyInputHandler() {
+        int buildVersion = Build.VERSION.SDK_INT;
+        logger.log(Level.INFO, "Android Build Version: {0}", buildVersion);
+//        if (buildVersion >= 14) {
+//            touchHandler = new AndroidTouchHandler14(this);
+//        } else if (buildVersion >= 8){
+//            touchHandler = new AndroidTouchHandler(this);
+//        }
+        sensorJoyInput = new AndroidSensorJoyInput(this);
+    }
+
+    public void setView(GLSurfaceView view) {
+//        if (touchHandler != null) {
+//            touchHandler.setView(view);
+//        }
+        if (sensorJoyInput != null) {
+            sensorJoyInput.setView(view);
+        }
+        this.view = (GLSurfaceView)view;
+
+    }
+
+    public View getView() {
+        return view;
+    }
+
+    public void loadSettings(AppSettings settings) {
+//        sensorEventsEnabled = settings.useSensors();
+    }
+
+    public void addEvent(InputEvent event) {
+        eventQueue.add(event);
+    }
+
+    /**
+     * Pauses the joystick device listeners to save battery life if they are not needed.
+     * Used to pause when the activity pauses
+     */
+    public void pauseJoysticks() {
+        if (sensorJoyInput != null) {
+            sensorJoyInput.pauseSensors();
+        }
+        if (vibrator != null && vibratorActive) {
+            vibrator.cancel();
+        }
+
+    }
+
+    /**
+     * Resumes the joystick device listeners.
+     * Used to resume when the activity comes to the top of the stack
+     */
+    public void resumeJoysticks() {
+        if (sensorJoyInput != null) {
+            sensorJoyInput.resumeSensors();
+        }
+
+    }
+
+
+
+
+
+    @Override
+    public void initialize() {
+//        if (sensorJoyInput != null) {
+//            sensorJoyInput.initialize();
+//        }
+        // Get instance of Vibrator from current Context
+        vibrator = (Vibrator) view.getContext().getSystemService(Context.VIBRATOR_SERVICE);
+        if (vibrator == null) {
+            logger.log(Level.FINE, "Vibrator Service not found.");
+        }
+        initialized = true;
+    }
+
+    @Override
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    @Override
+    public void destroy() {
+        initialized = false;
+
+        if (sensorJoyInput != null) {
+            sensorJoyInput.destroy();
+        }
+
+        setView(null);
+    }
+
+    @Override
+    public void setInputListener(RawInputListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    public long getInputTimeNanos() {
+        return System.nanoTime();
+    }
+
+    @Override
+    public void setJoyRumble(int joyId, float amount) {
+        // convert amount to pulses since Android doesn't allow intensity
+        if (vibrator != null) {
+            final long rumbleOnDur = (long)(amount * maxRumbleTime); // ms to pulse vibration on
+            final long rumbleOffDur = maxRumbleTime - rumbleOnDur; // ms to delay between pulses
+            final long[] rumblePattern = {
+                0, // start immediately
+                rumbleOnDur, // time to leave vibration on
+                rumbleOffDur // time to delay between vibrations
+            };
+            final int rumbleRepeatFrom = 0; // index into rumble pattern to repeat from
+
+            logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}",
+                    new Object[]{amount, rumbleOnDur, rumbleOffDur});
+
+            if (rumbleOnDur > 0) {
+                vibrator.vibrate(rumblePattern, rumbleRepeatFrom);
+                vibratorActive = true;
+            } else {
+                vibrator.cancel();
+                vibratorActive = false;
+            }
+        }
+    }
+
+    @Override
+    public Joystick[] loadJoysticks(InputManager inputManager) {
+        joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager));
+
+
+        return joystickList.toArray( new Joystick[joystickList.size()] );
+    }
+
+    @Override
+    public void update() {
+        if (sensorJoyInput != null) {
+            sensorJoyInput.update();
+        }
+
+        if (listener != null) {
+            InputEvent inputEvent;
+
+            while ((inputEvent = eventQueue.poll()) != null) {
+                if (inputEvent instanceof JoyAxisEvent) {
+                    listener.onJoyAxisEvent((JoyAxisEvent)inputEvent);
+                } else if (inputEvent instanceof JoyButtonEvent) {
+                    listener.onJoyButtonEvent((JoyButtonEvent)inputEvent);
+                }
+            }
+        }
+
+    }
+
+
+
+}

+ 46 - 130
jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java

@@ -37,7 +37,7 @@ import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
-import android.os.Vibrator;
+import android.opengl.GLSurfaceView;
 import android.view.Surface;
 import android.view.WindowManager;
 import com.jme3.input.AbstractJoystick;
@@ -47,10 +47,8 @@ import com.jme3.input.JoyInput;
 import com.jme3.input.Joystick;
 import com.jme3.input.JoystickAxis;
 import com.jme3.input.SensorJoystickAxis;
-import com.jme3.input.RawInputListener;
 import com.jme3.input.event.JoyAxisEvent;
 import com.jme3.math.FastMath;
-import com.jme3.system.android.JmeAndroidSystem;
 import com.jme3.util.IntMap;
 import com.jme3.util.IntMap.Entry;
 import java.util.ArrayList;
@@ -63,7 +61,7 @@ import java.util.logging.Logger;
  * A single joystick is configured and includes data for all configured sensors
  * as seperate axes of the joystick.
  *
- * Each axis is named accounting to the static strings in SensorJoystickAxis.
+ * Each axis is named according to the static strings in SensorJoystickAxis.
  * Refer to the strings defined in SensorJoystickAxis for a list of supported
  * sensors and their axis data.  Each sensor type defined in SensorJoystickAxis
  * will be attempted to be configured.  If the device does not support a particular
@@ -72,46 +70,21 @@ import java.util.logging.Logger;
  * The joystick.getXAxis and getYAxis methods of the joystick are configured to
  * return the device orientation values in the device's X and Y directions.
  *
- * This joystick also supports the joystick.rumble(rumbleAmount) method.  In this
- * case, when joystick.rumble(rumbleAmount) is called, the Android device will vibrate
- * if the device has a built in vibrate motor.
- *
- * Because Andorid does not allow for the user to define the intensity of the
- * vibration, the rumble amount (ie strength) is converted into vibration pulses
- * The stronger the strength amount, the shorter the delay between pulses.  If
- * amount is 1, then the vibration stays on the whole time.  If amount is 0.5,
- * the vibration will a pulse of equal parts vibration and delay.
- * To turn off vibration, set rumble amount to 0.
- *
- * MainActivity needs the following line to enable Joysticks on Android platforms
- *    joystickEventsEnabled = true;
- * This is done to allow for battery conservation when sensor data is not required
- * by the application.
- *
- * To use the joystick rumble feature, the following line needs to be
- * added to the Android Manifest File
- *     <uses-permission android:name="android.permission.VIBRATE"/>
- *
  * @author iwgeric
  */
-public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
+public class AndroidSensorJoyInput implements SensorEventListener {
     private final static Logger logger = Logger.getLogger(AndroidSensorJoyInput.class.getName());
 
-    private Context context = null;
-    private InputManager inputManager = null;
+    private AndroidJoyInputHandler joyHandler;
     private SensorManager sensorManager = null;
     private WindowManager windowManager = null;
-    private Vibrator vibrator = null;
-    private boolean vibratorActive = false;
-    private long maxRumbleTime = 250;  // 250ms
-    private RawInputListener listener = null;
     private IntMap<SensorData> sensors = new IntMap<SensorData>();
-    private AndroidJoystick[] joysticks;
     private int lastRotation = 0;
-    private boolean initialized = false;
     private boolean loaded = false;
 
-    private final ArrayList<JoyAxisEvent> eventQueue = new ArrayList<JoyAxisEvent>();
+    public AndroidSensorJoyInput(AndroidJoyInputHandler joyHandler) {
+        this.joyHandler = joyHandler;
+    }
 
     /**
      * Internal class to enclose data for each sensor.
@@ -120,7 +93,7 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
         int androidSensorType = -1;
         int androidSensorSpeed = SensorManager.SENSOR_DELAY_GAME;
         Sensor sensor = null;
-        int sensorAccuracy = 0;
+        int sensorAccuracy = -1;
         float[] lastValues;
         final Object valuesLock = new Object();
         ArrayList<AndroidJoystickAxis> axes = new ArrayList<AndroidJoystickAxis>();
@@ -134,16 +107,19 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
 
     }
 
-    private void initSensorManager() {
-        this.context = JmeAndroidSystem.getView().getContext();
-        // Get instance of the WindowManager from the current Context
-        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        // Get instance of the SensorManager from the current Context
-        sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
-        // Get instance of Vibrator from current Context
-        vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
-        if (vibrator == null) {
-            logger.log(Level.FINE, "Vibrator Service not found.");
+    public void setView(GLSurfaceView view) {
+        pauseSensors();
+        if (sensorManager != null) {
+            sensorManager.unregisterListener(this);
+        }
+        if (view == null) {
+            windowManager = null;
+            sensorManager = null;
+        } else {
+            // Get instance of the WindowManager from the current Context
+            windowManager = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
+            // Get instance of the SensorManager from the current Context
+            sensorManager = (SensorManager) view.getContext().getSystemService(Context.SENSOR_SERVICE);
         }
     }
 
@@ -222,9 +198,6 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
                 unRegisterListener(entry.getKey());
             }
         }
-        if (vibrator != null && vibratorActive) {
-            vibrator.cancel();
-        }
     }
 
     /**
@@ -400,10 +373,8 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
                             if (!sensorData.haveData) {
                                 sensorData.haveData = true;
                             } else {
-                                synchronized (eventQueue){
-                                    if (axis.isChanged()) {
-                                        eventQueue.add(new JoyAxisEvent(axis, axis.getJoystickAxisValue()));
-                                    }
+                                if (axis.isChanged()) {
+                                    joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue()));
                                 }
                             }
                         }
@@ -428,47 +399,14 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
 
     // Start of JoyInput methods
 
-    public void setJoyRumble(int joyId, float amount) {
-        // convert amount to pulses since Android doesn't allow intensity
-        if (vibrator != null) {
-            final long rumbleOnDur = (long)(amount * maxRumbleTime); // ms to pulse vibration on
-            final long rumbleOffDur = maxRumbleTime - rumbleOnDur; // ms to delay between pulses
-            final long[] rumblePattern = {
-                0, // start immediately
-                rumbleOnDur, // time to leave vibration on
-                rumbleOffDur // time to delay between vibrations
-            };
-            final int rumbleRepeatFrom = 0; // index into rumble pattern to repeat from
-
-            logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}",
-                    new Object[]{amount, rumbleOnDur, rumbleOffDur});
-
-            if (rumbleOnDur > 0) {
-                vibrator.vibrate(rumblePattern, rumbleRepeatFrom);
-                vibratorActive = true;
-            } else {
-                vibrator.cancel();
-                vibratorActive = false;
-            }
-        }
-
-    }
-
-    public Joystick[] loadJoysticks(InputManager inputManager) {
-        this.inputManager = inputManager;
-
-        initSensorManager();
-
+    public Joystick loadJoystick(int joyId, InputManager inputManager) {
         SensorData sensorData;
-        List<Joystick> list = new ArrayList<Joystick>();
-        AndroidJoystick joystick;
         AndroidJoystickAxis axis;
 
-        joystick = new AndroidJoystick(inputManager,
-                                    this,
-                                    list.size(),
+        AndroidJoystick joystick = new AndroidJoystick(inputManager,
+                                    joyHandler,
+                                    joyId,
                                     "AndroidSensorsJoystick");
-        list.add(joystick);
 
         List<Sensor> availSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
         for (Sensor sensor: availSensors) {
@@ -555,14 +493,8 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
 //        }
 
 
-        joysticks = list.toArray( new AndroidJoystick[list.size()] );
         loaded = true;
-        return joysticks;
-    }
-
-    public void initialize() {
-        initialized = true;
-        loaded = false;
+        return joystick;
     }
 
     public void update() {
@@ -570,15 +502,6 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
             return;
         }
         updateOrientation();
-        synchronized (eventQueue){
-            // flush events to listener
-            if (listener != null && eventQueue.size() > 0) {
-                for (int i = 0; i < eventQueue.size(); i++){
-                    listener.onJoyAxisEvent(eventQueue.get(i));
-                }
-                eventQueue.clear();
-            }
-        }
     }
 
     public void destroy() {
@@ -588,39 +511,27 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
             sensorManager.unregisterListener(this);
         }
         sensors.clear();
-        eventQueue.clear();
-        initialized = false;
         loaded = false;
-        joysticks = null;
         sensorManager = null;
-        vibrator = null;
-        context = null;
-    }
-
-    public boolean isInitialized() {
-        return initialized;
-    }
-
-    public void setInputListener(RawInputListener listener) {
-        this.listener = listener;
-    }
-
-    public long getInputTimeNanos() {
-        return System.nanoTime();
     }
 
-    // End of JoyInput methods
-
     // Start of Android SensorEventListener methods
 
+    @Override
     public void onSensorChanged(SensorEvent se) {
-        if (!initialized || !loaded) {
+        if (!loaded) {
             return;
         }
+        logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}",
+                new Object[]{se.sensor.getName(), se.accuracy, se.values});
 
         int sensorType = se.sensor.getType();
 
         SensorData sensorData = sensors.get(sensorType);
+        if (sensorData != null) {
+            logger.log(Level.FINE, "sensorData name: {0}, enabled: {1}, unreliable: {2}",
+                    new Object[]{sensorData.sensor.getName(), sensorData.enabled, sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE});
+        }
         if (sensorData != null && sensorData.sensor.equals(se.sensor) && sensorData.enabled) {
 
             if (sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) {
@@ -641,10 +552,11 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
                         if (!sensorData.haveData) {
                             sensorData.haveData = true;
                         } else {
-                            synchronized (eventQueue){
-                                if (axis.isChanged()) {
-                                    eventQueue.add(new JoyAxisEvent(axis, axis.getJoystickAxisValue()));
-                                }
+                            if (axis.isChanged()) {
+                                JoyAxisEvent event = new JoyAxisEvent(axis, axis.getJoystickAxisValue());
+                                logger.log(Level.INFO, "adding JoyAxisEvent: {0}", event);
+                                joyHandler.addEvent(event);
+//                                joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue()));
                             }
                         }
                     }
@@ -658,6 +570,7 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
         }
     }
 
+    @Override
     public void onAccuracyChanged(Sensor sensor, int i) {
         int sensorType = sensor.getType();
         SensorData sensorData = sensors.get(sensorType);
@@ -697,7 +610,7 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
             AndroidJoystickAxis axis;
 
             axis = new AndroidJoystickAxis(
-                    inputManager,               // InputManager (InputManager)
+                    getInputManager(),          // InputManager (InputManager)
                     this,                       // parent Joystick (Joystick)
                     axisNum,                    // Axis Index (int)
                     axisName,                   // Axis Name (String)
@@ -758,10 +671,12 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
             this.maxRawValue = maxRawValue;
         }
 
+        @Override
         public float getMaxRawValue() {
             return maxRawValue;
         }
 
+        @Override
         public void setMaxRawValue(float maxRawValue) {
             this.maxRawValue = maxRawValue;
         }
@@ -787,6 +702,7 @@ public class AndroidSensorJoyInput implements JoyInput, SensorEventListener {
             return hasChanged;
         }
 
+        @Override
         public void calibrateCenter() {
             zeroRawValue = lastRawValue;
             logger.log(Level.FINE, "Calibrating axis {0} to {1}",

+ 0 - 17
jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java

@@ -1850,21 +1850,6 @@ public class OGLESShaderRenderer implements Renderer {
       TextureUtil.uploadSubTexture(pixels, convertTextureType(tex.getType()), 0, x, y);
     }
 
-    public void clearTextureUnits() {
-        IDList textureList = context.textureIndexList;
-        Image[] textures = context.boundTextures;
-        for (int i = 0; i < textureList.oldLen; i++) {
-            int idx = textureList.oldList[i];
-//            if (context.boundTextureUnit != idx){
-//                glActiveTexture(GL_TEXTURE0 + idx);
-//                context.boundTextureUnit = idx;
-//            }
-//            glDisable(convertTextureType(textures[idx].getType()));
-            textures[idx] = null;
-        }
-        context.textureIndexList.copyNewToOld();
-    }
-
     public void deleteImage(Image image) {
         int texId = image.getId();
         if (texId != -1) {
@@ -2339,7 +2324,6 @@ public class OGLESShaderRenderer implements Renderer {
             RendererUtil.checkGLError();
         }
         clearVertexAttribs();
-        clearTextureUnits();
     }
 
     private void renderMeshDefault(Mesh mesh, int lod, int count) {
@@ -2378,7 +2362,6 @@ public class OGLESShaderRenderer implements Renderer {
             RendererUtil.checkGLError();
         }
         clearVertexAttribs();
-        clearTextureUnits();
     }
 
     public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {

+ 12 - 8
jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java

@@ -46,17 +46,15 @@ import android.view.ViewGroup.LayoutParams;
 import android.widget.EditText;
 import android.widget.FrameLayout;
 import com.jme3.input.*;
-import com.jme3.input.android.AndroidSensorJoyInput;
 import com.jme3.input.android.AndroidInputHandler;
+import com.jme3.input.android.AndroidJoyInputHandler;
 import com.jme3.input.controls.SoftTextDialogInputListener;
 import com.jme3.input.dummy.DummyKeyInput;
 import com.jme3.input.dummy.DummyMouseInput;
 import com.jme3.renderer.android.AndroidGL;
 import com.jme3.renderer.opengl.GL;
-import com.jme3.renderer.opengl.GLDebugES;
 import com.jme3.renderer.opengl.GLExt;
 import com.jme3.renderer.opengl.GLRenderer;
-import com.jme3.renderer.opengl.GLTracer;
 import com.jme3.system.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
@@ -77,9 +75,9 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
     protected SystemListener listener;
     protected boolean autoFlush = true;
     protected AndroidInputHandler androidInput;
+    protected AndroidJoyInputHandler androidJoyInput = null;
     protected long minFrameDuration = 0;                   // No FPS cap
     protected long lastUpdateTime = 0;
-    protected JoyInput androidSensorJoyInput = null;
 
     public OGLESContext() {
     }
@@ -119,6 +117,12 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
         androidInput.setView(view);
         androidInput.loadSettings(settings);
 
+        if (androidJoyInput == null) {
+            androidJoyInput = new AndroidJoyInputHandler();
+        }
+        androidJoyInput.setView(view);
+        androidJoyInput.loadSettings(settings);
+
         // setEGLContextClientVersion must be set before calling setRenderer
         // this means it cannot be set in AndroidConfigChooser (too late)
         view.setEGLContextClientVersion(2);
@@ -231,6 +235,9 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
         if (androidInput != null) {
             androidInput.loadSettings(settings);
         }
+        if (androidJoyInput != null) {
+            androidJoyInput.loadSettings(settings);
+        }
 
         if (settings.getFrameRate() > 0) {
             minFrameDuration = (long)(1000d / (double)settings.getFrameRate()); // ms
@@ -267,10 +274,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
 
     @Override
     public JoyInput getJoyInput() {
-        if (androidSensorJoyInput == null) {
-            androidSensorJoyInput = new AndroidSensorJoyInput();
-        }
-        return androidSensorJoyInput;
+        return androidJoyInput;
     }
 
     @Override

+ 5 - 7
jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java

@@ -7,21 +7,19 @@ import com.jme3.app.AndroidHarness;
 public class DemoAndroidHarness extends AndroidHarness
 {
     @Override
-    public void onCreate(Bundle savedInstanceState) 
-    {        
+    public void onCreate(Bundle savedInstanceState)
+    {
         // Set the application class to run
         // First Extract the bundle from intent
         Bundle bundle = getIntent().getExtras();
 
         //Next extract the values using the key as
-        appClass = bundle.getString("APPCLASSNAME");                
-        
+        appClass = bundle.getString("APPCLASSNAME");
+
         exitDialogTitle = "Close Demo?";
         exitDialogMessage = "Press Yes";
                         
-        screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
-        
-        super.onCreate(savedInstanceState);                
+        super.onCreate(savedInstanceState);
     }
 
 }

+ 8 - 304
jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java

@@ -32,36 +32,19 @@
 package com.jme3.asset;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Queue;
 
-import com.jme3.animation.Animation;
-import com.jme3.bounding.BoundingVolume;
-import com.jme3.collision.Collidable;
-import com.jme3.collision.CollisionResults;
-import com.jme3.collision.UnsupportedCollisionException;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
 import com.jme3.material.Material;
 import com.jme3.material.RenderState.FaceCullMode;
-import com.jme3.math.ColorRGBA;
-import com.jme3.post.Filter;
-import com.jme3.scene.CameraNode;
-import com.jme3.scene.LightNode;
-import com.jme3.scene.Node;
-import com.jme3.scene.SceneGraphVisitor;
-import com.jme3.scene.Spatial;
-import com.jme3.texture.Texture;
 
 /**
  * Blender key. Contains path of the blender file and its loading properties.
  * @author Marcin Roguski (Kaelthas)
  */
 public class BlenderKey extends ModelKey {
-
     protected static final int         DEFAULT_FPS               = 25;
     /**
      * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
@@ -72,7 +55,7 @@ public class BlenderKey extends ModelKey {
      * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
      */
     protected int                      featuresToLoad            = FeaturesToLoad.ALL;
-    /** This variable determines if assets that are not linked to the objects should be loaded. */
+    /** The variable that tells if content of the file (along with data unlinked to any feature on the scene) should be stored as 'user data' in the result spatial. */
     protected boolean                  loadUnlinkedAssets;
     /** The root path for all the assets. */
     protected String                   assetRootPath;
@@ -268,6 +251,7 @@ public class BlenderKey extends ModelKey {
      * @param featuresToLoad
      *            bitwise flag of FeaturesToLoad interface values
      */
+    @Deprecated
     public void includeInLoading(int featuresToLoad) {
         this.featuresToLoad |= featuresToLoad;
     }
@@ -277,10 +261,12 @@ public class BlenderKey extends ModelKey {
      * @param featuresNotToLoad
      *            bitwise flag of FeaturesToLoad interface values
      */
+    @Deprecated
     public void excludeFromLoading(int featuresNotToLoad) {
         featuresToLoad &= ~featuresNotToLoad;
     }
 
+    @Deprecated
     public boolean shouldLoad(int featureToLoad) {
         return (featuresToLoad & featureToLoad) != 0;
     }
@@ -290,6 +276,7 @@ public class BlenderKey extends ModelKey {
      * the blender file loader.
      * @return features that will be loaded by the blender file loader
      */
+    @Deprecated
     public int getFeaturesToLoad() {
         return featuresToLoad;
     }
@@ -317,15 +304,6 @@ public class BlenderKey extends ModelKey {
         this.loadUnlinkedAssets = loadUnlinkedAssets;
     }
 
-    /**
-     * This method creates an object where loading results will be stores. Only those features will be allowed to store
-     * that were specified by features-to-load flag.
-     * @return an object to store loading results
-     */
-    public LoadingResults prepareLoadingResults() {
-        return new LoadingResults(featuresToLoad);
-    }
-
     /**
      * This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y
      * is up axis.
@@ -699,8 +677,11 @@ public class BlenderKey extends ModelKey {
 
     /**
      * This interface describes the features of the scene that are to be loaded.
+     * @deprecated this interface is deprecated and is not used anymore; to ensure the loading models consistency
+     *             everything must be loaded because in blender one feature might depend on another
      * @author Marcin Roguski (Kaelthas)
      */
+    @Deprecated
     public static interface FeaturesToLoad {
 
         int SCENES     = 0x0000FFFF;
@@ -745,281 +726,4 @@ public class BlenderKey extends ModelKey {
          */
         ALL_NAMES_MATCH;
     }
-
-    /**
-     * This class holds the loading results according to the given loading flag.
-     * @author Marcin Roguski (Kaelthas)
-     */
-    public static class LoadingResults extends Spatial {
-
-        /** Bitwise mask of features that are to be loaded. */
-        private final int        featuresToLoad;
-        /** The scenes from the file. */
-        private List<Node>       scenes;
-        /** Objects from all scenes. */
-        private List<Node>       objects;
-        /** Materials from all objects. */
-        private List<Material>   materials;
-        /** Textures from all objects. */
-        private List<Texture>    textures;
-        /** Animations of all objects. */
-        private List<Animation>  animations;
-        /** All cameras from the file. */
-        private List<CameraNode> cameras;
-        /** All lights from the file. */
-        private List<LightNode>  lights;
-        /** Loaded sky. */
-        private Spatial          sky;
-        /** Scene filters (ie. FOG). */
-        private List<Filter>     filters;
-        /**
-         * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color
-         * is set to default (as in blender editor.
-         */
-        private ColorRGBA        backgroundColor = ColorRGBA.Gray;
-
-        /**
-         * Private constructor prevents users to create an instance of this class from outside the
-         * @param featuresToLoad
-         *            bitwise mask of features that are to be loaded
-         * @see FeaturesToLoad FeaturesToLoad
-         */
-        private LoadingResults(int featuresToLoad) {
-            this.featuresToLoad = featuresToLoad;
-            if ((featuresToLoad & FeaturesToLoad.SCENES) != 0) {
-                scenes = new ArrayList<Node>();
-            }
-            if ((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) {
-                objects = new ArrayList<Node>();
-                if ((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) {
-                    materials = new ArrayList<Material>();
-                    if ((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) {
-                        textures = new ArrayList<Texture>();
-                    }
-                }
-                if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) {
-                    animations = new ArrayList<Animation>();
-                }
-            }
-            if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) {
-                cameras = new ArrayList<CameraNode>();
-            }
-            if ((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) {
-                lights = new ArrayList<LightNode>();
-            }
-        }
-
-        /**
-         * This method returns a bitwise flag describing what features of the blend file will be included in the result.
-         * @return bitwise mask of features that are to be loaded
-         * @see FeaturesToLoad FeaturesToLoad
-         */
-        public int getLoadedFeatures() {
-            return featuresToLoad;
-        }
-
-        /**
-         * This method adds a scene to the result set.
-         * @param scene
-         *            scene to be added to the result set
-         */
-        public void addScene(Node scene) {
-            if (scenes != null) {
-                scenes.add(scene);
-            }
-        }
-
-        /**
-         * This method adds an object to the result set.
-         * @param object
-         *            object to be added to the result set
-         */
-        public void addObject(Node object) {
-            if (objects != null) {
-                objects.add(object);
-            }
-        }
-
-        /**
-         * This method adds a material to the result set.
-         * @param material
-         *            material to be added to the result set
-         */
-        public void addMaterial(Material material) {
-            if (materials != null) {
-                materials.add(material);
-            }
-        }
-
-        /**
-         * This method adds a texture to the result set.
-         * @param texture
-         *            texture to be added to the result set
-         */
-        public void addTexture(Texture texture) {
-            if (textures != null) {
-                textures.add(texture);
-            }
-        }
-
-        /**
-         * This method adds a camera to the result set.
-         * @param camera
-         *            camera to be added to the result set
-         */
-        public void addCamera(CameraNode camera) {
-            if (cameras != null) {
-                cameras.add(camera);
-            }
-        }
-
-        /**
-         * This method adds a light to the result set.
-         * @param light
-         *            light to be added to the result set
-         */
-        public void addLight(LightNode light) {
-            if (lights != null) {
-                lights.add(light);
-            }
-        }
-
-        /**
-         * This method sets the sky of the scene. Only one sky can be set.
-         * @param sky
-         *            the sky to be set
-         */
-        public void setSky(Spatial sky) {
-            this.sky = sky;
-        }
-
-        /**
-         * This method adds a scene filter. Filters are used to load FOG or other
-         * scene effects that blender can define.
-         * @param filter
-         *            the filter to be added
-         */
-        public void addFilter(Filter filter) {
-            if (filter != null) {
-                if (filters == null) {
-                    filters = new ArrayList<Filter>(5);
-                }
-                filters.add(filter);
-            }
-        }
-
-        /**
-         * @param backgroundColor
-         *            the background color
-         */
-        public void setBackgroundColor(ColorRGBA backgroundColor) {
-            this.backgroundColor = backgroundColor;
-        }
-
-        /**
-         * @return all loaded scenes
-         */
-        public List<Node> getScenes() {
-            return scenes;
-        }
-
-        /**
-         * @return all loaded objects
-         */
-        public List<Node> getObjects() {
-            return objects;
-        }
-
-        /**
-         * @return all loaded materials
-         */
-        public List<Material> getMaterials() {
-            return materials;
-        }
-
-        /**
-         * @return all loaded textures
-         */
-        public List<Texture> getTextures() {
-            return textures;
-        }
-
-        /**
-         * @return all loaded animations
-         */
-        public List<Animation> getAnimations() {
-            return animations;
-        }
-
-        /**
-         * @return all loaded cameras
-         */
-        public List<CameraNode> getCameras() {
-            return cameras;
-        }
-
-        /**
-         * @return all loaded lights
-         */
-        public List<LightNode> getLights() {
-            return lights;
-        }
-
-        /**
-         * @return the scene's sky
-         */
-        public Spatial getSky() {
-            return sky;
-        }
-
-        /**
-         * @return scene filters
-         */
-        public List<Filter> getFilters() {
-            return filters;
-        }
-
-        /**
-         * @return the background color
-         */
-        public ColorRGBA getBackgroundColor() {
-            return backgroundColor;
-        }
-
-        @Override
-        public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
-            return 0;
-        }
-
-        @Override
-        public void updateModelBound() {
-        }
-
-        @Override
-        public void setModelBound(BoundingVolume modelBound) {
-        }
-
-        @Override
-        public int getVertexCount() {
-            return 0;
-        }
-
-        @Override
-        public int getTriangleCount() {
-            return 0;
-        }
-
-        @Override
-        public Spatial deepClone() {
-            return null;
-        }
-
-        @Override
-        public void depthFirstTraversal(SceneGraphVisitor visitor) {
-        }
-
-        @Override
-        protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
-        }
-    }
 }

+ 134 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java

@@ -31,17 +31,34 @@
  */
 package com.jme3.scene.plugins.blender;
 
+import java.io.File;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
+import com.jme3.animation.Animation;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.BlenderKey;
 import com.jme3.export.Savable;
+import com.jme3.light.Light;
 import com.jme3.math.FastMath;
 import com.jme3.math.Quaternion;
+import com.jme3.post.Filter;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.materials.MaterialContext;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 import com.jme3.scene.plugins.blender.objects.Properties;
+import com.jme3.texture.Texture;
 
 /**
  * A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can
@@ -49,14 +66,16 @@ import com.jme3.scene.plugins.blender.objects.Properties;
  * @author Marcin Roguski
  */
 public abstract class AbstractBlenderHelper {
+    private static final Logger LOGGER = Logger.getLogger(AbstractBlenderHelper.class.getName());
+
     /** The blender context. */
-    protected BlenderContext blenderContext;
+    protected BlenderContext    blenderContext;
     /** The version of the blend file. */
-    protected final int  blenderVersion;
+    protected final int         blenderVersion;
     /** This variable indicates if the Y asxis is the UP axis or not. */
-    protected boolean    fixUpAxis;
+    protected boolean           fixUpAxis;
     /** Quaternion used to rotate data when Y is up axis. */
-    protected Quaternion upAxisRotationQuaternion;
+    protected Quaternion        upAxisRotationQuaternion;
 
     /**
      * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
@@ -129,4 +148,115 @@ public abstract class AbstractBlenderHelper {
             }
         }
     }
+
+    /**
+     * The method loads library of a given ID from linked blender file.
+     * @param id
+     *            the ID of the linked feature (it contains its name and blender path)
+     * @return loaded feature or null if none was found
+     * @throws BlenderFileException
+     *             and exception is throw when problems with reading a blend file occur
+     */
+    @SuppressWarnings("unchecked")
+    protected Object loadLibrary(Structure id) throws BlenderFileException {
+        Pointer pLib = (Pointer) id.getFieldValue("lib");
+        if (pLib.isNotNull()) {
+            String fullName = id.getFieldValue("name").toString();// we need full name with the prefix
+            String nameOfFeatureToLoad = id.getName();
+            Structure library = pLib.fetchData().get(0);
+            String path = library.getFieldValue("filepath").toString();
+
+            if (!blenderContext.getLinkedFeatures().keySet().contains(path)) {
+                File file = new File(path);
+                List<String> pathsToCheck = new ArrayList<String>();
+                String currentPath = file.getName();
+                do {
+                    pathsToCheck.add(currentPath);
+                    file = file.getParentFile();
+                    if (file != null) {
+                        currentPath = file.getName() + '/' + currentPath;
+                    }
+                } while (file != null);
+
+                Spatial loadedAsset = null;
+                BlenderKey blenderKey = null;
+                for (String p : pathsToCheck) {
+                    blenderKey = new BlenderKey(p);
+                    blenderKey.setLoadUnlinkedAssets(true);
+                    try {
+                        loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey);
+                        break;// break if no exception was thrown
+                    } catch (AssetNotFoundException e) {
+                        LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", p);
+                    }
+                }
+
+                if (loadedAsset != null) {
+                    Map<String, Map<String, Object>> linkedData = loadedAsset.getUserData("linkedData");
+                    for (Entry<String, Map<String, Object>> entry : linkedData.entrySet()) {
+                        String linkedDataFilePath = "this".equals(entry.getKey()) ? path : entry.getKey();
+
+                        List<Node> scenes = (List<Node>) entry.getValue().get("scenes");
+                        for (Node scene : scenes) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "SC" + scene.getName(), scene);
+                        }
+                        List<Node> objects = (List<Node>) entry.getValue().get("objects");
+                        for (Node object : objects) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "OB" + object.getName(), object);
+                        }
+                        List<TemporalMesh> meshes = (List<TemporalMesh>) entry.getValue().get("meshes");
+                        for (TemporalMesh mesh : meshes) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "ME" + mesh.getName(), mesh);
+                        }
+                        List<MaterialContext> materials = (List<MaterialContext>) entry.getValue().get("materials");
+                        for (MaterialContext materialContext : materials) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "MA" + materialContext.getName(), materialContext);
+                        }
+                        List<Texture> textures = (List<Texture>) entry.getValue().get("textures");
+                        for (Texture texture : textures) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "TE" + texture.getName(), texture);
+                        }
+                        List<Texture> images = (List<Texture>) entry.getValue().get("images");
+                        for (Texture image : images) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "IM" + image.getName(), image);
+                        }
+                        List<Animation> animations = (List<Animation>) entry.getValue().get("animations");
+                        for (Animation animation : animations) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "AC" + animation.getName(), animation);
+                        }
+                        List<Camera> cameras = (List<Camera>) entry.getValue().get("cameras");
+                        for (Camera camera : cameras) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "CA" + camera.getName(), camera);
+                        }
+                        List<Light> lights = (List<Light>) entry.getValue().get("lights");
+                        for (Light light : lights) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "LA" + light.getName(), light);
+                        }
+                        Spatial sky = (Spatial) entry.getValue().get("sky");
+                        if (sky != null) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, sky.getName(), sky);
+                        }
+                        List<Filter> filters = (List<Filter>) entry.getValue().get("filters");
+                        for (Filter filter : filters) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, filter.getName(), filter);
+                        }
+                    }
+                } else {
+                    LOGGER.log(Level.WARNING, "No features loaded from path: {0}.", path);
+                }
+            }
+
+            Object result = blenderContext.getLinkedFeature(path, fullName);
+            if (result == null) {
+                LOGGER.log(Level.WARNING, "Could NOT find asset named {0} in the library of path: {1}.", new Object[] { nameOfFeatureToLoad, path });
+            } else {
+                blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.STRUCTURE, id);
+                blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
+            }
+            return result;
+        } else {
+            LOGGER.warning("Library link points to nothing!");
+        }
+        return null;
+    }
 }

+ 68 - 23
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java

@@ -53,6 +53,7 @@ import com.jme3.scene.plugins.blender.constraints.Constraint;
 import com.jme3.scene.plugins.blender.file.BlenderInputStream;
 import com.jme3.scene.plugins.blender.file.DnaBlockData;
 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
+import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
 import com.jme3.scene.plugins.blender.file.Structure;
 
 /**
@@ -64,49 +65,51 @@ import com.jme3.scene.plugins.blender.file.Structure;
  */
 public class BlenderContext {
     /** The blender file version. */
-    private int                                 blenderVersion;
+    private int                                    blenderVersion;
     /** The blender key. */
-    private BlenderKey                          blenderKey;
+    private BlenderKey                             blenderKey;
     /** The header of the file block. */
-    private DnaBlockData                        dnaBlockData;
+    private DnaBlockData                           dnaBlockData;
     /** The scene structure. */
-    private Structure                           sceneStructure;
+    private Structure                              sceneStructure;
     /** The input stream of the blend file. */
-    private BlenderInputStream                  inputStream;
+    private BlenderInputStream                     inputStream;
     /** The asset manager. */
-    private AssetManager                        assetManager;
+    private AssetManager                           assetManager;
     /** The blocks read from the file. */
-    protected List<FileBlockHeader>             blocks;
+    protected List<FileBlockHeader>                blocks;
     /**
      * A map containing the file block headers. The key is the old memory address.
      */
-    private Map<Long, FileBlockHeader>          fileBlockHeadersByOma  = new HashMap<Long, FileBlockHeader>();
+    private Map<Long, FileBlockHeader>             fileBlockHeadersByOma  = new HashMap<Long, FileBlockHeader>();
     /** A map containing the file block headers. The key is the block code. */
-    private Map<Integer, List<FileBlockHeader>> fileBlockHeadersByCode = new HashMap<Integer, List<FileBlockHeader>>();
+    private Map<BlockCode, List<FileBlockHeader>>  fileBlockHeadersByCode = new HashMap<BlockCode, List<FileBlockHeader>>();
     /**
      * This map stores the loaded features by their old memory address. The
      * first object in the value table is the loaded structure and the second -
      * the structure already converted into proper data.
      */
-    private Map<Long, Map<LoadedDataType, Object>>                 loadedFeatures         = new HashMap<Long, Map<LoadedDataType, Object>>();
+    private Map<Long, Map<LoadedDataType, Object>> loadedFeatures         = new HashMap<Long, Map<LoadedDataType, Object>>();
+    /** Features loaded from external blender files. The key is the file path and the value is a map between feature name and loaded feature. */
+    private Map<String, Map<String, Object>>       linkedFeatures         = new HashMap<String, Map<String, Object>>();
     /** A stack that hold the parent structure of currently loaded feature. */
-    private Stack<Structure>                    parentStack            = new Stack<Structure>();
+    private Stack<Structure>                       parentStack            = new Stack<Structure>();
     /** A list of constraints for the specified object. */
-    protected Map<Long, List<Constraint>>       constraints            = new HashMap<Long, List<Constraint>>();
+    protected Map<Long, List<Constraint>>          constraints            = new HashMap<Long, List<Constraint>>();
     /** Animations loaded for features. */
-    private Map<Long, List<Animation>>          animations             = new HashMap<Long, List<Animation>>();
+    private Map<Long, List<Animation>>             animations             = new HashMap<Long, List<Animation>>();
     /** Loaded skeletons. */
-    private Map<Long, Skeleton>                 skeletons              = new HashMap<Long, Skeleton>();
+    private Map<Long, Skeleton>                    skeletons              = new HashMap<Long, Skeleton>();
     /** A map between skeleton and node it modifies. */
-    private Map<Skeleton, Node>                 nodesWithSkeletons     = new HashMap<Skeleton, Node>();
+    private Map<Skeleton, Node>                    nodesWithSkeletons     = new HashMap<Skeleton, Node>();
     /** A map of bone contexts. */
-    protected Map<Long, BoneContext>            boneContexts           = new HashMap<Long, BoneContext>();
+    protected Map<Long, BoneContext>               boneContexts           = new HashMap<Long, BoneContext>();
     /** A map og helpers that perform loading. */
-    private Map<String, AbstractBlenderHelper>  helpers                = new HashMap<String, AbstractBlenderHelper>();
+    private Map<String, AbstractBlenderHelper>     helpers                = new HashMap<String, AbstractBlenderHelper>();
     /** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */
-    private Map<String, Map<Object, Object>>    markers                = new HashMap<String, Map<Object, Object>>();
+    private Map<String, Map<Object, Object>>       markers                = new HashMap<String, Map<Object, Object>>();
     /** A map of blender actions. The key is the action name and the value is the action itself. */
-    private Map<String, BlenderAction>          actions                = new HashMap<String, BlenderAction>();
+    private Map<String, BlenderAction>             actions                = new HashMap<String, BlenderAction>();
 
     /**
      * This method sets the blender file version.
@@ -231,10 +234,10 @@ public class BlenderContext {
      */
     public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) {
         fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader);
-        List<FileBlockHeader> headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode()));
+        List<FileBlockHeader> headers = fileBlockHeadersByCode.get(fileBlockHeader.getCode());
         if (headers == null) {
             headers = new ArrayList<FileBlockHeader>();
-            fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers);
+            fileBlockHeadersByCode.put(fileBlockHeader.getCode(), headers);
         }
         headers.add(fileBlockHeader);
     }
@@ -258,7 +261,7 @@ public class BlenderContext {
      *            the code of file blocks
      * @return a list of file blocks' headers of a specified code
      */
-    public List<FileBlockHeader> getFileBlocks(Integer code) {
+    public List<FileBlockHeader> getFileBlocks(BlockCode code) {
         return fileBlockHeadersByCode.get(code);
     }
 
@@ -299,7 +302,7 @@ public class BlenderContext {
             throw new IllegalArgumentException("One of the given arguments is null!");
         }
         Map<LoadedDataType, Object> map = loadedFeatures.get(oldMemoryAddress);
-        if(map == null) {
+        if (map == null) {
             map = new HashMap<BlenderContext.LoadedDataType, Object>();
             loadedFeatures.put(oldMemoryAddress, map);
         }
@@ -325,6 +328,48 @@ public class BlenderContext {
         return null;
     }
 
+    /**
+     * The method adds linked content to the blender context.
+     * @param blenderFilePath
+     *            the path of linked blender file
+     * @param featureName
+     *            the linked feature name
+     * @param feature
+     *            the linked feature
+     */
+    public void addLinkedFeature(String blenderFilePath, String featureName, Object feature) {
+        if (feature != null) {
+            Map<String, Object> linkedFeatures = this.linkedFeatures.get(blenderFilePath);
+            if (linkedFeatures == null) {
+                linkedFeatures = new HashMap<String, Object>();
+                this.linkedFeatures.put(blenderFilePath, linkedFeatures);
+            }
+            if (!linkedFeatures.containsKey(featureName)) {
+                linkedFeatures.put(featureName, feature);
+            }
+        }
+    }
+
+    /**
+     * The method returns linked feature of a given name from the specified blender path.
+     * @param blenderFilePath
+     *            the blender file path
+     * @param featureName
+     *            the feature name we want to get
+     * @return linked feature or null if none was found
+     */
+    public Object getLinkedFeature(String blenderFilePath, String featureName) {
+        Map<String, Object> linkedFeatures = this.linkedFeatures.get(blenderFilePath);
+        return linkedFeatures != null ? linkedFeatures.get(featureName) : null;
+    }
+
+    /**
+     * @return all linked features for the current blend file
+     */
+    public Map<String, Map<String, Object>> getLinkedFeatures() {
+        return linkedFeatures;
+    }
+
     /**
      * This method adds the structure to the parent stack.
      * 

+ 169 - 94
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java

@@ -33,17 +33,21 @@ package com.jme3.scene.plugins.blender;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import com.jme3.animation.Animation;
 import com.jme3.asset.AssetInfo;
 import com.jme3.asset.AssetLoader;
 import com.jme3.asset.BlenderKey;
-import com.jme3.asset.BlenderKey.FeaturesToLoad;
-import com.jme3.asset.BlenderKey.LoadingResults;
 import com.jme3.asset.ModelKey;
 import com.jme3.light.Light;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.Camera;
 import com.jme3.scene.CameraNode;
 import com.jme3.scene.LightNode;
 import com.jme3.scene.Node;
@@ -55,16 +59,20 @@ import com.jme3.scene.plugins.blender.curves.CurvesHelper;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.BlenderInputStream;
 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
+import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.landscape.LandscapeHelper;
 import com.jme3.scene.plugins.blender.lights.LightHelper;
+import com.jme3.scene.plugins.blender.materials.MaterialContext;
 import com.jme3.scene.plugins.blender.materials.MaterialHelper;
 import com.jme3.scene.plugins.blender.meshes.MeshHelper;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
 import com.jme3.scene.plugins.blender.objects.ObjectHelper;
 import com.jme3.scene.plugins.blender.particles.ParticlesHelper;
 import com.jme3.scene.plugins.blender.textures.TextureHelper;
+import com.jme3.texture.Texture;
 
 /**
  * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
@@ -83,72 +91,130 @@ public class BlenderLoader implements AssetLoader {
         try {
             this.setup(assetInfo);
 
-            List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
-            BlenderKey blenderKey = blenderContext.getBlenderKey();
-            LoadingResults loadingResults = blenderKey.prepareLoadingResults();
-            
             AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
             animationHelper.loadAnimations();
-            
+
+            BlenderKey blenderKey = blenderContext.getBlenderKey();
+            LoadedFeatures loadedFeatures = new LoadedFeatures();
             for (FileBlockHeader block : blocks) {
                 switch (block.getCode()) {
-                    case FileBlockHeader.BLOCK_OB00:// Object
+                    case BLOCK_OB00:
                         ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
-                        Object object = objectHelper.toObject(block.getStructure(blenderContext), blenderContext);
-                        if (object instanceof LightNode) {
-                            loadingResults.addLight((LightNode) object);
-                        } else if (object instanceof CameraNode) {
-                            loadingResults.addCamera((CameraNode) object);
-                        } else if (object instanceof Node) {
-                            if (LOGGER.isLoggable(Level.FINE)) {
-                                LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
-                            }
-                            if (this.isRootObject(loadingResults, (Node) object)) {
-                                loadingResults.addObject((Node) object);
-                            }
+                        Node object = (Node) objectHelper.toObject(block.getStructure(blenderContext), blenderContext);
+                        if (LOGGER.isLoggable(Level.FINE)) {
+                            LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { object.getName(), object.getLocalTranslation().toString(), object.getParent() == null ? "null" : object.getParent().getName() });
                         }
+                        if (object.getParent() == null) {
+                            loadedFeatures.objects.add(object);
+                        }
+                        if (object instanceof LightNode && ((LightNode) object).getLight() != null) {
+                            loadedFeatures.lights.add(((LightNode) object).getLight());
+                        } else if (object instanceof CameraNode && ((CameraNode) object).getCamera() != null) {
+                            loadedFeatures.cameras.add(((CameraNode) object).getCamera());
+                        }
+                        break;
+                    case BLOCK_SC00:// Scene
+                        loadedFeatures.sceneBlocks.add(block);
                         break;
-//                    case FileBlockHeader.BLOCK_MA00:// Material
-//                        MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
-//                        MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext);
-//                        if (blenderKey.isLoadUnlinkedAssets() && blenderKey.shouldLoad(FeaturesToLoad.MATERIALS)) {
-//                            loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext)));
-//                        }
-//                        break;
-                    case FileBlockHeader.BLOCK_SC00:// Scene
-                        if (blenderKey.shouldLoad(FeaturesToLoad.SCENES)) {
-                            sceneBlocks.add(block);
+                    case BLOCK_MA00:// Material
+                        MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
+                        MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext);
+                        loadedFeatures.materials.add(materialContext);
+                        break;
+                    case BLOCK_ME00:// Mesh
+                        MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
+                        TemporalMesh temporalMesh = meshHelper.toTemporalMesh(block.getStructure(blenderContext), blenderContext);
+                        loadedFeatures.meshes.add(temporalMesh);
+                        break;
+                    case BLOCK_IM00:// Image
+                        TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
+                        Texture image = textureHelper.loadImageAsTexture(block.getStructure(blenderContext), 0, blenderContext);
+                        if (image != null && image.getImage() != null) {// render results are stored as images but are not being loaded
+                            loadedFeatures.images.add(image);
                         }
                         break;
-                    case FileBlockHeader.BLOCK_WO00:// World
-                        if (blenderKey.shouldLoad(FeaturesToLoad.WORLD)) {
-                            Structure worldStructure = block.getStructure(blenderContext);
-                            String worldName = worldStructure.getName();
-                            if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
-                                LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class);
-                                Light ambientLight = landscapeHelper.toAmbientLight(worldStructure);
-                                if(ambientLight != null) {
-                                    loadingResults.addLight(new LightNode(null, ambientLight));
-                                }
-                                loadingResults.setSky(landscapeHelper.toSky(worldStructure));
-                                loadingResults.addFilter(landscapeHelper.toFog(worldStructure));
-                                loadingResults.setBackgroundColor(landscapeHelper.toBackgroundColor(worldStructure));
+                    case BLOCK_TE00:
+                        Structure textureStructure = block.getStructure(blenderContext);
+                        int type = ((Number) textureStructure.getFieldValue("type")).intValue();
+                        if (type == TextureHelper.TEX_IMAGE) {
+                            TextureHelper texHelper = blenderContext.getHelper(TextureHelper.class);
+                            Texture texture = texHelper.getTexture(textureStructure, null, blenderContext);
+                            if (texture != null) {// null is returned when texture has no image
+                                loadedFeatures.textures.add(texture);
                             }
+                        } else {
+                            LOGGER.fine("Only image textures can be loaded as unlinked assets. Generated textures will be applied to an existing object.");
                         }
                         break;
+                    case BLOCK_WO00:// World
+                        LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class);
+                        Structure worldStructure = block.getStructure(blenderContext);
+
+                        String worldName = worldStructure.getName();
+                        if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
+
+                            Light ambientLight = landscapeHelper.toAmbientLight(worldStructure);
+                            if (ambientLight != null) {
+                                loadedFeatures.objects.add(new LightNode(null, ambientLight));
+                                loadedFeatures.lights.add(ambientLight);
+                            }
+                            loadedFeatures.sky = landscapeHelper.toSky(worldStructure);
+                            loadedFeatures.backgroundColor = landscapeHelper.toBackgroundColor(worldStructure);
+
+                            Filter fogFilter = landscapeHelper.toFog(worldStructure);
+                            if (fogFilter != null) {
+                                loadedFeatures.filters.add(landscapeHelper.toFog(worldStructure));
+                            }
+                        }
+                        break;
+                    case BLOCK_AC00:
+                        LOGGER.fine("Loading unlinked animations is not yet supported!");
+                        break;
+                    default:
+                        LOGGER.log(Level.FINEST, "Ommiting the block: {0}.", block.getCode());
                 }
             }
 
-            // bake constraints after everything is loaded
+            LOGGER.fine("Baking constraints after every feature is loaded.");
             ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
             constraintHelper.bakeConstraints(blenderContext);
 
-            // load the scene at the very end so that the root nodes have no parent during loading or constraints applying
-            for (FileBlockHeader sceneBlock : sceneBlocks) {
-                loadingResults.addScene(this.toScene(sceneBlock.getStructure(blenderContext)));
+            LOGGER.fine("Loading scenes and attaching them to the root object.");
+            for (FileBlockHeader sceneBlock : loadedFeatures.sceneBlocks) {
+                loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext)));
+            }
+
+            LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene and loaded features to it.");
+            Node modelRoot = new Node(blenderKey.getName());
+            for (Node scene : loadedFeatures.scenes) {
+                modelRoot.attachChild(scene);
             }
 
-            return loadingResults;
+            if (blenderKey.isLoadUnlinkedAssets()) {
+                LOGGER.fine("Setting loaded content as user data in resulting sptaial.");
+                Map<String, Map<String, Object>> linkedData = new HashMap<String, Map<String, Object>>();
+
+                Map<String, Object> thisFileData = new HashMap<String, Object>();
+                thisFileData.put("scenes", loadedFeatures.scenes == null ? new ArrayList<Object>() : loadedFeatures.scenes);
+                thisFileData.put("objects", loadedFeatures.objects == null ? new ArrayList<Object>() : loadedFeatures.objects);
+                thisFileData.put("meshes", loadedFeatures.meshes == null ? new ArrayList<Object>() : loadedFeatures.meshes);
+                thisFileData.put("materials", loadedFeatures.materials == null ? new ArrayList<Object>() : loadedFeatures.materials);
+                thisFileData.put("textures", loadedFeatures.textures == null ? new ArrayList<Object>() : loadedFeatures.textures);
+                thisFileData.put("images", loadedFeatures.images == null ? new ArrayList<Object>() : loadedFeatures.images);
+                thisFileData.put("animations", loadedFeatures.animations == null ? new ArrayList<Object>() : loadedFeatures.animations);
+                thisFileData.put("cameras", loadedFeatures.cameras == null ? new ArrayList<Object>() : loadedFeatures.cameras);
+                thisFileData.put("lights", loadedFeatures.lights == null ? new ArrayList<Object>() : loadedFeatures.lights);
+                thisFileData.put("filters", loadedFeatures.filters == null ? new ArrayList<Object>() : loadedFeatures.filters);
+                thisFileData.put("backgroundColor", loadedFeatures.backgroundColor);
+                thisFileData.put("sky", loadedFeatures.sky);
+
+                linkedData.put("this", thisFileData);
+                linkedData.putAll(blenderContext.getLinkedFeatures());
+
+                modelRoot.setUserData("linkedData", linkedData);
+            }
+
+            return modelRoot;
         } catch (BlenderFileException e) {
             throw new IOException(e.getLocalizedMessage(), e);
         } catch (Exception e) {
@@ -158,62 +224,36 @@ public class BlenderLoader implements AssetLoader {
         }
     }
 
-    /**
-     * This method indicates if the given spatial is a root object. It means it
-     * has no parent or is directly attached to one of the already loaded scene
-     * nodes.
-     * 
-     * @param loadingResults
-     *            loading results containing the scene nodes
-     * @param spatial
-     *            spatial object
-     * @return <b>true</b> if the given spatial is a root object and
-     *         <b>false</b> otherwise
-     */
-    protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) {
-        if (spatial.getParent() == null) {
-            return true;
-        }
-        for (Node scene : loadingResults.getScenes()) {
-            if (spatial.getParent().equals(scene)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * This method converts the given structure to a scene node.
      * @param structure
      *            structure of a scene
      * @return scene's node
+     * @throws BlenderFileException
+     *             an exception throw when problems with blender file occur
      */
-    private Node toScene(Structure structure) {
+    private Node toScene(Structure structure) throws BlenderFileException {
         ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
         Node result = new Node(structure.getName());
-        try {
-            List<Structure> base = ((Structure) structure.getFieldValue("base")).evaluateListBase();
-            for (Structure b : base) {
-                Pointer pObject = (Pointer) b.getFieldValue("object");
-                if (pObject.isNotNull()) {
-                    Structure objectStructure = pObject.fetchData().get(0);
+        List<Structure> base = ((Structure) structure.getFieldValue("base")).evaluateListBase();
+        for (Structure b : base) {
+            Pointer pObject = (Pointer) b.getFieldValue("object");
+            if (pObject.isNotNull()) {
+                Structure objectStructure = pObject.fetchData().get(0);
 
-                    Object object = objectHelper.toObject(objectStructure, blenderContext);
-                    if (object instanceof LightNode) {
-                        result.addLight(((LightNode) object).getLight());
-                        result.attachChild((LightNode) object);
-                    } else if (object instanceof Node) {
-                        if (LOGGER.isLoggable(Level.FINE)) {
-                            LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
-                        }
-                        if (((Node) object).getParent() == null) {
-                            result.attachChild((Spatial) object);
-                        }
+                Object object = objectHelper.toObject(objectStructure, blenderContext);
+                if (object instanceof LightNode) {
+                    result.addLight(((LightNode) object).getLight());// FIXME: check if this is needed !!!
+                    result.attachChild((LightNode) object);
+                } else if (object instanceof Node) {
+                    if (LOGGER.isLoggable(Level.FINE)) {
+                        LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
+                    }
+                    if (((Node) object).getParent() == null) {
+                        result.attachChild((Spatial) object);
                     }
                 }
             }
-        } catch (BlenderFileException e) {
-            LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
         }
         return result;
     }
@@ -261,7 +301,7 @@ public class BlenderLoader implements AssetLoader {
         blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext));
         blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext));
         blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext));
-        
+
         // reading the blocks (dna block is automatically saved in the blender context when found)
         FileBlockHeader sceneFileBlock = null;
         do {
@@ -269,7 +309,7 @@ public class BlenderLoader implements AssetLoader {
             if (!fileBlock.isDnaBlock()) {
                 blocks.add(fileBlock);
                 // save the scene's file block
-                if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) {
+                if (fileBlock.getCode() == BlockCode.BLOCK_SC00) {
                     sceneFileBlock = fileBlock;
                 }
             }
@@ -287,4 +327,39 @@ public class BlenderLoader implements AssetLoader {
         blenderContext = null;
         blocks = null;
     }
+
+    /**
+     * This class holds the loading results according to the given loading flag.
+     * @author Marcin Roguski (Kaelthas)
+     */
+    private static class LoadedFeatures {
+        private List<FileBlockHeader> sceneBlocks     = new ArrayList<FileBlockHeader>();
+        /** The scenes from the file. */
+        private List<Node>            scenes          = new ArrayList<Node>();
+        /** Objects from all scenes. */
+        private List<Node>            objects         = new ArrayList<Node>();
+        /** All meshes. */
+        private List<TemporalMesh>    meshes          = new ArrayList<TemporalMesh>();
+        /** Materials from all objects. */
+        private List<MaterialContext> materials       = new ArrayList<MaterialContext>();
+        /** Textures from all objects. */
+        private List<Texture>         textures        = new ArrayList<Texture>();
+        /** The images stored in the blender file. */
+        private List<Texture>         images          = new ArrayList<Texture>();
+        /** Animations of all objects. */
+        private List<Animation>       animations      = new ArrayList<Animation>();
+        /** All cameras from the file. */
+        private List<Camera>          cameras         = new ArrayList<Camera>();
+        /** All lights from the file. */
+        private List<Light>           lights          = new ArrayList<Light>();
+        /** Loaded sky. */
+        private Spatial               sky;
+        /** Scene filters (ie. FOG). */
+        private List<Filter>          filters         = new ArrayList<Filter>();
+        /**
+         * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color
+         * is set to default (as in blender editor.
+         */
+        private ColorRGBA             backgroundColor = ColorRGBA.Gray;
+    }
 }

+ 1 - 70
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java

@@ -31,79 +31,10 @@
  */
 package com.jme3.scene.plugins.blender;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.jme3.asset.AssetInfo;
-import com.jme3.asset.BlenderKey;
-import com.jme3.asset.BlenderKey.FeaturesToLoad;
-import com.jme3.scene.LightNode;
-import com.jme3.scene.Node;
-import com.jme3.scene.Spatial;
-import com.jme3.scene.plugins.blender.animations.AnimationHelper;
-import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
-import com.jme3.scene.plugins.blender.file.BlenderFileException;
-import com.jme3.scene.plugins.blender.file.FileBlockHeader;
-import com.jme3.scene.plugins.blender.objects.ObjectHelper;
-
 /**
  * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
- * 
+ * @deprecated this class is deprecated; use BlenderLoader instead
  * @author Marcin Roguski (Kaelthas)
  */
 public class BlenderModelLoader extends BlenderLoader {
-
-    private static final Logger LOGGER = Logger.getLogger(BlenderModelLoader.class.getName());
-
-    @Override
-    public Spatial load(AssetInfo assetInfo) throws IOException {
-        try {
-            this.setup(assetInfo);
-
-            AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
-            animationHelper.loadAnimations();
-            
-            BlenderKey blenderKey = blenderContext.getBlenderKey();
-            List<Node> rootObjects = new ArrayList<Node>();
-            for (FileBlockHeader block : blocks) {
-                if (block.getCode() == FileBlockHeader.BLOCK_OB00) {
-                    ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
-                    Object object = objectHelper.toObject(block.getStructure(blenderContext), blenderContext);
-                    if (object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
-                        rootObjects.add((LightNode) object);
-                    } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) {
-                        LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
-                        if (((Node) object).getParent() == null) {
-                            rootObjects.add((Node) object);
-                        }
-                    }
-                }
-            }
-
-            // bake constraints after everything is loaded
-            ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
-            constraintHelper.bakeConstraints(blenderContext);
-
-            // attach the nodes to the root node at the very end so that the root objects have no parents during constraint applying
-            LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene to it.");
-            Node modelRoot = new Node(blenderKey.getName());
-            for (Node node : rootObjects) {
-                if (node instanceof LightNode) {
-                    modelRoot.addLight(((LightNode) node).getLight());
-                }
-                modelRoot.attachChild(node);
-            }
-
-            return modelRoot;
-        } catch (BlenderFileException e) {
-            throw new IOException(e.getLocalizedMessage(), e);
-        } catch (Exception e) {
-            throw new IOException("Unexpected importer exception occured: " + e.getLocalizedMessage(), e);
-        } finally {
-            this.clear();
-        }
-    }
 }

+ 4 - 3
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java

@@ -25,6 +25,7 @@ import com.jme3.scene.plugins.blender.curves.BezierCurve;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.BlenderInputStream;
 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
+import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.objects.ObjectHelper;
@@ -48,7 +49,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
      */
     public void loadAnimations() throws BlenderFileException {
         LOGGER.info("Loading animations that will be later applied to scene features.");
-        List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
+        List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(BlockCode.BLOCK_AC00);
         if (actionHeaders != null) {
             for (FileBlockHeader header : actionHeaders) {
                 Structure actionStructure = header.getStructure(blenderContext);
@@ -70,7 +71,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
         if (actions.size() > 0) {
             List<Animation> animations = new ArrayList<Animation>();
             for (BlenderAction action : actions) {
-                SpatialTrack[] tracks = action.toTracks(node);
+                SpatialTrack[] tracks = action.toTracks(node, blenderContext);
                 if (tracks != null && tracks.length > 0) {
                     Animation spatialAnimation = new Animation(action.getName(), action.getAnimationTime());
                     spatialAnimation.setTracks(tracks);
@@ -109,7 +110,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
         if (actions.size() > 0) {
             List<Animation> animations = new ArrayList<Animation>();
             for (BlenderAction action : actions) {
-                BoneTrack[] tracks = action.toTracks(skeleton);
+                BoneTrack[] tracks = action.toTracks(skeleton, blenderContext);
                 if (tracks != null && tracks.length > 0) {
                     Animation boneAnimation = new Animation(action.getName(), action.getAnimationTime());
                     boneAnimation.setTracks(tracks);

+ 6 - 6
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BlenderAction.java

@@ -10,9 +10,8 @@ import java.util.Map.Entry;
 import com.jme3.animation.BoneTrack;
 import com.jme3.animation.Skeleton;
 import com.jme3.animation.SpatialTrack;
-import com.jme3.math.Quaternion;
-import com.jme3.math.Vector3f;
 import com.jme3.scene.Node;
+import com.jme3.scene.plugins.blender.BlenderContext;
 
 /**
  * An abstract representation of animation. The data stored here is mainly a
@@ -69,10 +68,10 @@ public class BlenderAction implements Cloneable {
      *            the node that will be animated
      * @return the spatial tracks for the node
      */
-    public SpatialTrack[] toTracks(Node node) {
+    public SpatialTrack[] toTracks(Node node, BlenderContext blenderContext) {
         List<SpatialTrack> tracks = new ArrayList<SpatialTrack>(featuresTracks.size());
         for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
-            tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true));
+            tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, null, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true));
         }
         return tracks.toArray(new SpatialTrack[tracks.size()]);
     }
@@ -84,11 +83,12 @@ public class BlenderAction implements Cloneable {
      *            the skeleton that will be animated
      * @return the bone tracks for the node
      */
-    public BoneTrack[] toTracks(Skeleton skeleton) {
+    public BoneTrack[] toTracks(Skeleton skeleton, BlenderContext blenderContext) {
         List<BoneTrack> tracks = new ArrayList<BoneTrack>(featuresTracks.size());
         for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
             int boneIndex = skeleton.getBoneIndex(entry.getKey());
-            tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ, 1, stopFrame, fps, false));
+            BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(boneIndex));
+            tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, boneContext, boneContext.getBone().getBindPosition(), boneContext.getBone().getBindRotation(), boneContext.getBone().getBindScale(), 1, stopFrame, fps, false));
         }
         return tracks.toArray(new BoneTrack[tracks.size()]);
     }

+ 7 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java

@@ -25,10 +25,13 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
  */
 public class BoneContext {
     // the flags of the bone
-    public static final int      SELECTED                            = 0x0001;
-    public static final int      CONNECTED_TO_PARENT                 = 0x0010;
-    public static final int      DEFORM                              = 0x1000;
-
+    public static final int      SELECTED                            = 0x000001;
+    public static final int      CONNECTED_TO_PARENT                 = 0x000010;
+    public static final int      DEFORM                              = 0x001000;
+    public static final int      NO_LOCAL_LOCATION                   = 0x400000;
+    public static final int      NO_INHERIT_SCALE                    = 0x008000;
+    public static final int      NO_INHERIT_ROTATION                 = 0x000200;
+    
     /**
      * The bones' matrices have, unlike objects', the coordinate system identical to JME's (Y axis is UP, X to the right and Z toward us).
      * So in order to have them loaded properly we need to transform their armature matrix (which blender sees as rotated) to make sure we get identical results.

+ 11 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java

@@ -137,7 +137,7 @@ public class Ipo {
      *            as jme while other features have different one (Z is UP)
      * @return bone track for the specified bone
      */
-    public Track calculateTrack(int targetIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean spatialTrack) {
+    public Track calculateTrack(int targetIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean spatialTrack) {
         if (calculatedTrack == null) {
             // preparing data for track
             int framesAmount = stopFrame - startFrame;
@@ -236,6 +236,15 @@ public class Ipo {
                     }
                 }
                 translations[index] = localRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2]));
+                
+                if(boneContext != null) {
+                    if(boneContext.getBone().getParent() == null && boneContext.is(BoneContext.NO_LOCAL_LOCATION)) {
+                        float temp = translations[index].z;
+                        translations[index].z = -translations[index].y;
+                        translations[index].y = temp;
+                    }
+                }
+                
                 if (queternionRotationUsed) {
                     rotations[index] = new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);
                 } else {
@@ -292,7 +301,7 @@ public class Ipo {
         }
 
         @Override
-        public BoneTrack calculateTrack(int boneIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) {
+        public BoneTrack calculateTrack(int boneIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) {
             throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!");
         }
     }

+ 8 - 7
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java

@@ -5,7 +5,6 @@ import java.util.logging.Logger;
 
 import com.jme3.math.FastMath;
 import com.jme3.renderer.Camera;
-import com.jme3.scene.CameraNode;
 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
@@ -43,7 +42,7 @@ public class CameraHelper extends AbstractBlenderHelper {
      *             an exception is thrown when there are problems with the
      *             blender file
      */
-    public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
+    public Camera toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
         if (blenderVersion >= 250) {
             return this.toCamera250(structure, blenderContext.getSceneStructure());
         } else {
@@ -63,7 +62,7 @@ public class CameraHelper extends AbstractBlenderHelper {
      *             an exception is thrown when there are problems with the
      *             blender file
      */
-    private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException {
+    private Camera toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException {
         int width = DEFAULT_CAM_WIDTH;
         int height = DEFAULT_CAM_HEIGHT;
         if (sceneStructure != null) {
@@ -99,7 +98,7 @@ public class CameraHelper extends AbstractBlenderHelper {
                 sensor = ((Number) structure.getFieldValue(sensorName)).floatValue();
             }
             float focalLength = ((Number) structure.getFieldValue("lens")).floatValue();
-            float fov = 2.0f * FastMath.atan((sensor / 2.0f) / focalLength);
+            float fov = 2.0f * FastMath.atan(sensor / 2.0f / focalLength);
             if (sensorVertical) {
                 fovY = fov * FastMath.RAD_TO_DEG;
             } else {
@@ -111,7 +110,8 @@ public class CameraHelper extends AbstractBlenderHelper {
             fovY = ((Number) structure.getFieldValue("ortho_scale")).floatValue();
         }
         camera.setFrustumPerspective(fovY, aspect, clipsta, clipend);
-        return new CameraNode(null, camera);
+        camera.setName(structure.getName());
+        return camera;
     }
 
     /**
@@ -124,7 +124,7 @@ public class CameraHelper extends AbstractBlenderHelper {
      *             an exception is thrown when there are problems with the
      *             blender file
      */
-    private CameraNode toCamera249(Structure structure) throws BlenderFileException {
+    private Camera toCamera249(Structure structure) throws BlenderFileException {
         Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);
         int type = ((Number) structure.getFieldValue("type")).intValue();
         if (type != 0 && type != 1) {
@@ -142,6 +142,7 @@ public class CameraHelper extends AbstractBlenderHelper {
             aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue();
         }
         camera.setFrustumPerspective(aspect, camera.getWidth() / camera.getHeight(), clipsta, clipend);
-        return new CameraNode(null, camera);
+        camera.setName(structure.getName());
+        return camera;
     }
 }

+ 31 - 14
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java

@@ -1,10 +1,11 @@
 package com.jme3.scene.plugins.blender.curves;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import com.jme3.math.Vector3f;
 import com.jme3.scene.plugins.blender.file.DynamicArray;
 import com.jme3.scene.plugins.blender.file.Structure;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize
@@ -12,21 +13,26 @@ import java.util.List;
  * @author Marcin Roguski (Kaelthas)
  */
 public class BezierCurve {
+    private static final int IPO_CONSTANT = 0;
+    private static final int IPO_LINEAR   = 1;
+    private static final int IPO_BEZIER   = 2;
 
-    public static final int X_VALUE = 0;
-    public static final int Y_VALUE = 1;
-    public static final int Z_VALUE = 2;
+    public static final int  X_VALUE      = 0;
+    public static final int  Y_VALUE      = 1;
+    public static final int  Z_VALUE      = 2;
     /**
      * The type of the curve. Describes the data it modifies.
      * Used in ipos calculations.
      */
-    private int             type;
+    private int              type;
     /** The dimension of the curve. */
-    private int             dimension;
+    private int              dimension;
     /** A table of the bezier points. */
-    private double[][][]    bezierPoints;
+    private double[][][]     bezierPoints;
     /** Array that stores a radius for each bezier triple. */
-    private double[]        radiuses;
+    private double[]         radiuses;
+    /** Interpolation types of the bezier triples. */
+    private int[]            interpolations;
 
     public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension) {
         this(type, bezTriples, dimension, false);
@@ -44,6 +50,7 @@ public class BezierCurve {
         // the third index specifies the coordinates of the specific point in a bezier triple
         bezierPoints = new double[bezTriples.size()][3][dimension];
         radiuses = new double[bezTriples.size()];
+        interpolations = new int[bezTriples.size()];
         int i = 0, j, k;
         for (Structure bezTriple : bezTriples) {
             DynamicArray<Number> vec = (DynamicArray<Number>) bezTriple.getFieldValue("vec");
@@ -57,7 +64,8 @@ public class BezierCurve {
                     bezierPoints[i][j][1] = temp;
                 }
             }
-            radiuses[i++] = ((Number) bezTriple.getFieldValue("radius")).floatValue();
+            radiuses[i] = ((Number) bezTriple.getFieldValue("radius")).floatValue();
+            interpolations[i++] = ((Number) bezTriple.getFieldValue("ipo", IPO_BEZIER)).intValue();
         }
     }
 
@@ -75,10 +83,19 @@ public class BezierCurve {
         for (int i = 0; i < bezierPoints.length - 1; ++i) {
             if (frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) {
                 double t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]);
-                double oneMinusT = 1.0f - t;
-                double oneMinusT2 = oneMinusT * oneMinusT;
-                double t2 = t * t;
-                return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t;
+                switch (interpolations[i]) {
+                    case IPO_BEZIER:
+                        double oneMinusT = 1.0f - t;
+                        double oneMinusT2 = oneMinusT * oneMinusT;
+                        double t2 = t * t;
+                        return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t;
+                    case IPO_LINEAR:
+                        return (1f - t) * bezierPoints[i][1][valuePart] + t * bezierPoints[i + 1][1][valuePart];
+                    case IPO_CONSTANT:
+                        return bezierPoints[i][1][valuePart];
+                    default:
+                        throw new IllegalStateException("Unknown interpolation type for curve: " + interpolations[i]);
+                }
             }
         }
         if (frame < bezierPoints[0][1][0]) {

+ 46 - 41
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java

@@ -31,6 +31,8 @@
  */
 package com.jme3.scene.plugins.blender.file;
 
+import java.util.logging.Logger;
+
 import com.jme3.scene.plugins.blender.BlenderContext;
 
 /**
@@ -39,39 +41,23 @@ import com.jme3.scene.plugins.blender.BlenderContext;
  * @author Marcin Roguski
  */
 public class FileBlockHeader {
+    private static final Logger LOGGER = Logger.getLogger(FileBlockHeader.class.getName());
 
-    public static final int BLOCK_TE00 = 'T' << 24 | 'E' << 16;                 // TE00
-    public static final int BLOCK_ME00 = 'M' << 24 | 'E' << 16;                 // ME00
-    public static final int BLOCK_SR00 = 'S' << 24 | 'R' << 16;                 // SR00
-    public static final int BLOCK_CA00 = 'C' << 24 | 'A' << 16;                 // CA00
-    public static final int BLOCK_LA00 = 'L' << 24 | 'A' << 16;                 // LA00
-    public static final int BLOCK_OB00 = 'O' << 24 | 'B' << 16;                 // OB00
-    public static final int BLOCK_MA00 = 'M' << 24 | 'A' << 16;                 // MA00
-    public static final int BLOCK_SC00 = 'S' << 24 | 'C' << 16;                 // SC00
-    public static final int BLOCK_WO00 = 'W' << 24 | 'O' << 16;                 // WO00
-    public static final int BLOCK_TX00 = 'T' << 24 | 'X' << 16;                 // TX00
-    public static final int BLOCK_IP00 = 'I' << 24 | 'P' << 16;                 // IP00
-    public static final int BLOCK_AC00 = 'A' << 24 | 'C' << 16;                 // AC00
-    public static final int BLOCK_GLOB = 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B'; // GLOB
-    public static final int BLOCK_REND = 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D'; // REND
-    public static final int BLOCK_DATA = 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A'; // DATA
-    public static final int BLOCK_DNA1 = 'D' << 24 | 'N' << 16 | 'A' << 8 | '1'; // DNA1
-    public static final int BLOCK_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B'; // ENDB
     /** Identifier of the file-block [4 bytes]. */
-    private int             code;
+    private BlockCode           code;
     /** Total length of the data after the file-block-header [4 bytes]. */
-    private int             size;
+    private int                 size;
     /**
      * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer
      * size)].
      */
-    private long            oldMemoryAddress;
+    private long                oldMemoryAddress;
     /** Index of the SDNA structure [4 bytes]. */
-    private int             sdnaIndex;
+    private int                 sdnaIndex;
     /** Number of structure located in this file-block [4 bytes]. */
-    private int             count;
+    private int                 count;
     /** Start position of the block's data in the stream. */
-    private int             blockPosition;
+    private int                 blockPosition;
 
     /**
      * Constructor. Loads the block header from the given stream during instance creation.
@@ -84,13 +70,13 @@ public class FileBlockHeader {
      */
     public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {
         inputStream.alignPosition(4);
-        code = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
+        code = BlockCode.valueOf(inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte());
         size = inputStream.readInt();
         oldMemoryAddress = inputStream.readPointer();
         sdnaIndex = inputStream.readInt();
         count = inputStream.readInt();
         blockPosition = inputStream.getPosition();
-        if (FileBlockHeader.BLOCK_DNA1 == code) {
+        if (BlockCode.BLOCK_DNA1 == code) {
             blenderContext.setBlockData(new DnaBlockData(inputStream, blenderContext));
         } else {
             inputStream.setPosition(blockPosition + size);
@@ -116,7 +102,7 @@ public class FileBlockHeader {
      * This method returns the code of this data block.
      * @return the code of this data block
      */
-    public int getCode() {
+    public BlockCode getCode() {
         return code;
     }
 
@@ -157,7 +143,7 @@ public class FileBlockHeader {
      * @return true if this block is the last one in the file nad false otherwise
      */
     public boolean isLastBlock() {
-        return FileBlockHeader.BLOCK_ENDB == code;
+        return BlockCode.BLOCK_ENDB == code;
     }
 
     /**
@@ -165,25 +151,44 @@ public class FileBlockHeader {
      * @return true if this block is the SDNA block and false otherwise
      */
     public boolean isDnaBlock() {
-        return FileBlockHeader.BLOCK_DNA1 == code;
+        return BlockCode.BLOCK_DNA1 == code;
     }
 
     @Override
     public String toString() {
-        return "FILE BLOCK HEADER [" + this.codeToString(code) + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]";
+        return "FILE BLOCK HEADER [" + code.toString() + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]";
     }
 
-    /**
-     * This method transforms the coded bloch id into a string value.
-     * @param code
-     *            the id of the block
-     * @return the string value of the block id
-     */
-    protected String codeToString(int code) {
-        char c1 = (char) ((code & 0xFF000000) >> 24);
-        char c2 = (char) ((code & 0xFF0000) >> 16);
-        char c3 = (char) ((code & 0xFF00) >> 8);
-        char c4 = (char) (code & 0xFF);
-        return String.valueOf(c1) + c2 + c3 + c4;
+    public static enum BlockCode {
+        BLOCK_ME00('M' << 24 | 'E' << 16), // mesh
+        BLOCK_CA00('C' << 24 | 'A' << 16), // camera
+        BLOCK_LA00('L' << 24 | 'A' << 16), // lamp
+        BLOCK_OB00('O' << 24 | 'B' << 16), // object
+        BLOCK_MA00('M' << 24 | 'A' << 16), // material
+        BLOCK_SC00('S' << 24 | 'C' << 16), // scene
+        BLOCK_WO00('W' << 24 | 'O' << 16), // world
+        BLOCK_TX00('T' << 24 | 'X' << 16), // texture
+        BLOCK_IP00('I' << 24 | 'P' << 16), // ipo
+        BLOCK_AC00('A' << 24 | 'C' << 16), // action
+        BLOCK_IM00('I' << 24 | 'M' << 16), // image
+        BLOCK_TE00('T' << 24 | 'E' << 16), BLOCK_WM00('W' << 24 | 'M' << 16), BLOCK_SR00('S' << 24 | 'R' << 16), BLOCK_SN00('S' << 24 | 'N' << 16), BLOCK_BR00('B' << 24 | 'R' << 16), BLOCK_LS00('L' << 24 | 'S' << 16), BLOCK_GLOB('G' << 24 | 'L' << 16 | 'O' << 8 | 'B'), BLOCK_REND('R' << 24 | 'E' << 16 | 'N' << 8 | 'D'), BLOCK_DATA('D' << 24 | 'A' << 16 | 'T' << 8 | 'A'), BLOCK_DNA1('D' << 24 | 'N' << 16 | 'A' << 8 | '1'), BLOCK_ENDB('E' << 24 | 'N' << 16 | 'D' << 8 | 'B'), BLOCK_TEST('T' << 24 | 'E' << 16
+                | 'S' << 8 | 'T'), BLOCK_UNKN(0);
+
+        private int code;
+
+        private BlockCode(int code) {
+            this.code = code;
+        }
+
+        public static BlockCode valueOf(int code) {
+            for (BlockCode blockCode : BlockCode.values()) {
+                if (blockCode.code == code) {
+                    return blockCode;
+                }
+            }
+            byte[] codeBytes = new byte[] { (byte) (code >> 24 & 0xFF), (byte) (code >> 16 & 0xFF), (byte) (code >> 8 & 0xFF), (byte) (code & 0xFF) };
+            LOGGER.warning("Unknown block header: " + new String(codeBytes));
+            return BLOCK_UNKN;
+        }
     }
 }

+ 2 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java

@@ -254,7 +254,8 @@ public class Structure implements Cloneable {
             Structure id = (Structure) fieldValue;
             return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix
         }
-        return null;
+        Object name = this.getFieldValue("name", null);
+        return name == null ? null : name.toString().substring(2);
     }
 
     @Override

+ 1 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java

@@ -208,6 +208,6 @@ public class LandscapeHelper extends AbstractBlenderHelper {
         }
 
         LOGGER.fine("Sky texture created. Creating sky.");
-        return SkyFactory.createSky(blenderContext.getAssetManager(), texture, false);
+        return SkyFactory.createSky(blenderContext.getAssetManager(), texture, SkyFactory.EnvMapType.CubeMap);
     }
 }

+ 4 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java

@@ -40,7 +40,6 @@ import com.jme3.light.PointLight;
 import com.jme3.light.SpotLight;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.FastMath;
-import com.jme3.scene.LightNode;
 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
@@ -67,8 +66,8 @@ public class LightHelper extends AbstractBlenderHelper {
         super(blenderVersion, blenderContext);
     }
 
-    public LightNode toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
-        LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
+    public Light toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
+        Light result = (Light) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (result != null) {
             return result;
         }
@@ -111,6 +110,7 @@ public class LightHelper extends AbstractBlenderHelper {
         float g = ((Number) structure.getFieldValue("g")).floatValue();
         float b = ((Number) structure.getFieldValue("b")).floatValue();
         light.setColor(new ColorRGBA(r, g, b, 1.0f));
-        return new LightNode(structure.getName(), light);
+        light.setName(structure.getName());
+        return light;
     }
 }

+ 23 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java

@@ -1,11 +1,15 @@
 package com.jme3.scene.plugins.blender.materials;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
 import com.jme3.material.Material;
 import com.jme3.material.RenderState.BlendMode;
 import com.jme3.material.RenderState.FaceCullMode;
@@ -30,7 +34,7 @@ import com.jme3.util.BufferUtils;
  * This class holds the data about the material.
  * @author Marcin Roguski (Kaelthas)
  */
-public final class MaterialContext {
+public final class MaterialContext implements Savable {
     private static final Logger              LOGGER     = Logger.getLogger(MaterialContext.class.getName());
 
     // texture mapping types
@@ -67,7 +71,7 @@ public final class MaterialContext {
         int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue();
         diffuseShader = DiffuseShader.values()[diff_shader];
         ambientFactor = ((Number) structure.getFieldValue("amb")).floatValue();
-        
+
         if (shadeless) {
             float r = ((Number) structure.getFieldValue("r")).floatValue();
             float g = ((Number) structure.getFieldValue("g")).floatValue();
@@ -107,6 +111,13 @@ public final class MaterialContext {
         this.transparent = transparent;
     }
 
+    /**
+     * @return the name of the material
+     */
+    public String getName() {
+        return name;
+    }
+
     /**
      * Applies material to a given geometry.
      * 
@@ -314,4 +325,14 @@ public final class MaterialContext {
         float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
         return new ColorRGBA(r, g, b, alpha);
     }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        throw new IOException("Material context is not for saving! It implements savable only to be passed to another blend file as a Savable in user data!");
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        throw new IOException("Material context is not for loading! It implements savable only to be passed to another blend file as a Savable in user data!");
+    }
 }

+ 8 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java

@@ -51,6 +51,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Image;
 import com.jme3.texture.Image.Format;
+import com.jme3.texture.image.ColorSpace;
 import com.jme3.texture.Texture;
 import com.jme3.util.BufferUtils;
 
@@ -161,12 +162,17 @@ public class MaterialHelper extends AbstractBlenderHelper {
      *             an exception is throw when problems with blend file occur
      */
     public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
-        LOGGER.log(Level.FINE, "Loading material.");
         MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (result != null) {
             return result;
         }
 
+        if ("ID".equals(structure.getType())) {
+            LOGGER.fine("Loading material from external blend file.");
+            return (MaterialContext) this.loadLibrary(structure);
+        }
+
+        LOGGER.fine("Loading material.");
         result = new MaterialContext(structure, blenderContext);
         LOGGER.log(Level.FINE, "Material''s name: {0}", result.name);
         Long oma = structure.getOldMemoryAddress();
@@ -212,7 +218,7 @@ public class MaterialHelper extends AbstractBlenderHelper {
                 }
             }
 
-            image = new Image(Format.RGBA8, w, h, bb);
+            image = new Image(Format.RGBA8, w, h, bb, ColorSpace.Linear);
             texture.setImage(image);
 
             result.setTextureParam("Texture", VarType.Texture2D, texture);

+ 6 - 7
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java

@@ -40,7 +40,6 @@ import java.util.Map.Entry;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import com.jme3.asset.BlenderKey.FeaturesToLoad;
 import com.jme3.material.Material;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector2f;
@@ -52,7 +51,6 @@ import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.DynamicArray;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.scene.plugins.blender.materials.MaterialContext;
 import com.jme3.scene.plugins.blender.materials.MaterialHelper;
 import com.jme3.scene.plugins.blender.objects.Properties;
 
@@ -106,17 +104,18 @@ public class MeshHelper extends AbstractBlenderHelper {
             return temporalMesh.clone();
         }
 
+        if ("ID".equals(meshStructure.getType())) {
+            LOGGER.fine("Loading mesh from external blend file.");
+            return (TemporalMesh) this.loadLibrary(meshStructure);
+        }
+
         String name = meshStructure.getName();
         LOGGER.log(Level.FINE, "Reading mesh: {0}.", name);
         temporalMesh = new TemporalMesh(meshStructure, blenderContext);
 
         LOGGER.fine("Loading materials.");
         MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
-        MaterialContext[] materials = null;
-        if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
-            materials = materialHelper.getMaterials(meshStructure, blenderContext);
-        }
-        temporalMesh.setMaterials(materials);
+        temporalMesh.setMaterials(materialHelper.getMaterials(meshStructure, blenderContext));
 
         LOGGER.fine("Reading custom properties.");
         Properties properties = this.loadProperties(meshStructure, blenderContext);

+ 38 - 32
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

@@ -40,12 +40,15 @@ import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import com.jme3.asset.BlenderKey.FeaturesToLoad;
+import com.jme3.light.Light;
 import com.jme3.math.FastMath;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Transform;
 import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.CameraNode;
 import com.jme3.scene.Geometry;
+import com.jme3.scene.LightNode;
 import com.jme3.scene.Mesh.Mode;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
@@ -106,39 +109,34 @@ public class ObjectHelper extends AbstractBlenderHelper {
      *             an exception is thrown when the given data is inapropriate
      */
     public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
-        LOGGER.fine("Loading blender object.");
+        Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
+        if (loadedResult != null) {
+            return loadedResult;
+        }
 
+        LOGGER.fine("Loading blender object.");
+        if ("ID".equals(objectStructure.getType())) {
+            Node object = (Node) this.loadLibrary(objectStructure);
+            if (object.getParent() != null) {
+                LOGGER.log(Level.FINEST, "Detaching object {0}, loaded from external file, from its parent.", object);
+                object.getParent().detachChild(object);
+            }
+            return object;
+        }
         int type = ((Number) objectStructure.getFieldValue("type")).intValue();
         ObjectType objectType = ObjectType.valueOf(type);
         LOGGER.log(Level.FINE, "Type of the object: {0}.", objectType);
-        if (objectType == ObjectType.LAMP && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.LIGHTS)) {
-            LOGGER.fine("Lamps are not included in loading.");
-            return null;
-        }
-        if (objectType == ObjectType.CAMERA && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.CAMERAS)) {
-            LOGGER.fine("Cameras are not included in loading.");
-            return null;
-        }
-        if (!blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.OBJECTS)) {
-            LOGGER.fine("Objects are not included in loading.");
-            return null;
-        }
+
         int lay = ((Number) objectStructure.getFieldValue("lay")).intValue();
         if ((lay & blenderContext.getBlenderKey().getLayersToLoad()) == 0) {
             LOGGER.fine("The layer this object is located in is not included in loading.");
             return null;
         }
 
-        LOGGER.fine("Checking if the object has not been already loaded.");
-        Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
-        if (loadedResult != null) {
-            return loadedResult;
-        }
-
         blenderContext.pushParent(objectStructure);
         String name = objectStructure.getName();
         LOGGER.log(Level.FINE, "Loading obejct: {0}", name);
-        
+
         int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue();
         boolean visible = (restrictflag & 0x01) != 0;
 
@@ -171,7 +169,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
                     Pointer pMesh = (Pointer) objectStructure.getFieldValue("data");
                     List<Structure> meshesArray = pMesh.fetchData();
                     TemporalMesh temporalMesh = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext);
-                    if(temporalMesh != null) {
+                    if (temporalMesh != null) {
                         result.attachChild(temporalMesh);
                     }
                     break;
@@ -183,7 +181,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
                         CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class);
                         Structure curveData = pCurve.fetchData().get(0);
                         TemporalMesh curvesTemporalMesh = curvesHelper.toCurve(curveData, blenderContext);
-                        if(curvesTemporalMesh != null) {
+                        if (curvesTemporalMesh != null) {
                             result.attachChild(curvesTemporalMesh);
                         }
                     }
@@ -193,10 +191,12 @@ public class ObjectHelper extends AbstractBlenderHelper {
                     if (pLamp.isNotNull()) {
                         LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
                         List<Structure> lampsArray = pLamp.fetchData();
-                        result = lightHelper.toLight(lampsArray.get(0), blenderContext);
-                        if (result == null) {
+                        Light light = lightHelper.toLight(lampsArray.get(0), blenderContext);
+                        if (light == null) {
                             // probably some light type is not supported, just create a node so that we can maintain child-parent relationship for nodes
                             result = new Node(name);
+                        } else {
+                            result = new LightNode(name, light);
                         }
                     }
                     break;
@@ -205,19 +205,25 @@ public class ObjectHelper extends AbstractBlenderHelper {
                     if (pCamera.isNotNull()) {
                         CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
                         List<Structure> camerasArray = pCamera.fetchData();
-                        result = cameraHelper.toCamera(camerasArray.get(0), blenderContext);
+                        Camera camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext);
+                        if (camera == null) {
+                            // just create a node so that we can maintain child-parent relationship for nodes
+                            result = new Node(name);
+                        } else {
+                            result = new CameraNode(name, camera);
+                        }
                     }
                     break;
                 default:
                     LOGGER.log(Level.WARNING, "Unsupported object type: {0}", type);
             }
-            
+
             if (result != null) {
                 LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released).");
                 Long oma = objectStructure.getOldMemoryAddress();
                 blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, objectStructure);
                 blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result);
-                
+
                 blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress());
                 if (objectType == ObjectType.ARMATURE) {
                     blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE);
@@ -235,13 +241,13 @@ public class ObjectHelper extends AbstractBlenderHelper {
                 for (Modifier modifier : modifiers) {
                     modifier.apply(result, blenderContext);
                 }
-                
+
                 if (result.getChildren() != null && result.getChildren().size() > 0) {
-                    if(result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) {
+                    if (result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) {
                         LOGGER.fine("Converting temporal mesh into jme geometries.");
-                        ((TemporalMesh)result.getChild(0)).toGeometries();
+                        ((TemporalMesh) result.getChild(0)).toGeometries();
                     }
-                    
+
                     LOGGER.fine("Applying proper scale to the geometries.");
                     for (Spatial child : result.getChildren()) {
                         if (child instanceof Geometry) {

+ 7 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java

@@ -444,13 +444,17 @@ public class CombinedTexture {
                     case RGB8:
                         return true;// these types have no alpha by definition
                     case ABGR8:
+                    case DXT1A:
                     case DXT3:
                     case DXT5:
                     case Luminance16FAlpha16F:
                     case Luminance8Alpha8:
                     case RGBA16F:
                     case RGBA32F:
-                    case RGBA8:// with these types it is better to make sure if the texture is or is not transparent
+                    case RGBA8:
+                    case ARGB8:
+                    case BGRA8:
+                    case RGB5A1:// with these types it is better to make sure if the texture is or is not transparent
                         PixelInputOutput pixelInputOutput = PixelIOFactory.getPixelIO(image.getFormat());
                         TexturePixel pixel = new TexturePixel();
                         int depth = image.getDepth() == 0 ? 1 : image.getDepth();
@@ -465,6 +469,8 @@ public class CombinedTexture {
                             }
                         }
                         return true;
+                    default:
+                        throw new IllegalStateException("Unknown image format: " + image.getFormat());
                 }
             }
         }

+ 6 - 5
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java

@@ -41,13 +41,13 @@ public final class ImageUtils {
     public static Image createEmptyImage(Format format, int width, int height, int depth) {
         int bufferSize = width * height * (format.getBitsPerPixel() >> 3);
         if (depth < 2) {
-            return new Image(format, width, height, BufferUtils.createByteBuffer(bufferSize));
+            return new Image(format, width, height, BufferUtils.createByteBuffer(bufferSize), com.jme3.texture.image.ColorSpace.Linear);
         }
         ArrayList<ByteBuffer> data = new ArrayList<ByteBuffer>(depth);
         for (int i = 0; i < depth; ++i) {
             data.add(BufferUtils.createByteBuffer(bufferSize));
         }
-        return new Image(Format.RGB8, width, height, depth, data);
+        return new Image(Format.RGB8, width, height, depth, data, com.jme3.texture.image.ColorSpace.Linear);
     }
 
     /**
@@ -337,7 +337,7 @@ public final class ImageUtils {
                             alphas[0] = data.get() * 255.0f;
                             alphas[1] = data.get() * 255.0f;
                             //the casts to long must be done here because otherwise 32-bit integers would be shifetd by 32 and 40 bits which would result in improper values
-                            long alphaIndices = (long)data.get() | (long)data.get() << 8 | (long)data.get() << 16 | (long)data.get() << 24 | (long)data.get() << 32 | (long)data.get() << 40;
+                            long alphaIndices = data.get() | (long)data.get() << 8 | (long)data.get() << 16 | (long)data.get() << 24 | (long)data.get() << 32 | (long)data.get() << 40;
                             if (alphas[0] > alphas[1]) {// 6 interpolated alpha values.
                                 alphas[2] = (6 * alphas[0] + alphas[1]) / 7;
                                 alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7;
@@ -404,7 +404,8 @@ public final class ImageUtils {
             dataArray.add(BufferUtils.createByteBuffer(bytes));
         }
 
-        Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0));
+        Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray, com.jme3.texture.image.ColorSpace.Linear) : 
+                                   new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0), com.jme3.texture.image.ColorSpace.Linear);
         if (newMipmapSizes != null) {
             result.setMipMapSizes(newMipmapSizes);
         }
@@ -467,6 +468,6 @@ public final class ImageUtils {
     private static Image toJmeImage(BufferedImage bufferedImage, Format format) {
         ByteBuffer byteBuffer = BufferUtils.createByteBuffer(bufferedImage.getWidth() * bufferedImage.getHeight() * 3);
         ImageToAwt.convert(bufferedImage, format, byteBuffer);
-        return new Image(format, bufferedImage.getWidth(), bufferedImage.getHeight(), byteBuffer);
+        return new Image(format, bufferedImage.getWidth(), bufferedImage.getHeight(), byteBuffer, com.jme3.texture.image.ColorSpace.Linear);
     }
 }

+ 62 - 34
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java

@@ -121,7 +121,7 @@ public class TextureHelper extends AbstractBlenderHelper {
      * data. The returned texture has the name set to the value of its blender
      * type.
      * 
-     * @param tex
+     * @param textureStructure
      *            texture structure filled with data
      * @param blenderContext
      *            the blender context
@@ -130,23 +130,29 @@ public class TextureHelper extends AbstractBlenderHelper {
      *             this exception is thrown when the blend file structure is
      *             somehow invalid or corrupted
      */
-    public Texture getTexture(Structure tex, Structure mTex, BlenderContext blenderContext) throws BlenderFileException {
-        Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedDataType.FEATURE);
+    public Texture getTexture(Structure textureStructure, Structure mTex, BlenderContext blenderContext) throws BlenderFileException {
+        Texture result = (Texture) blenderContext.getLoadedFeature(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (result != null) {
             return result;
         }
-        int type = ((Number) tex.getFieldValue("type")).intValue();
-        int imaflag = ((Number) tex.getFieldValue("imaflag")).intValue();
+
+        if ("ID".equals(textureStructure.getType())) {
+            LOGGER.fine("Loading texture from external blend file.");
+            return (Texture) this.loadLibrary(textureStructure);
+        }
+
+        int type = ((Number) textureStructure.getFieldValue("type")).intValue();
+        int imaflag = ((Number) textureStructure.getFieldValue("imaflag")).intValue();
 
         switch (type) {
             case TEX_IMAGE:// (it is first because probably this will be most commonly used)
-                Pointer pImage = (Pointer) tex.getFieldValue("ima");
+                Pointer pImage = (Pointer) textureStructure.getFieldValue("ima");
                 if (pImage.isNotNull()) {
                     Structure image = pImage.fetchData().get(0);
-                    Texture loadedTexture = this.loadTexture(image, imaflag, blenderContext);
+                    Texture loadedTexture = this.loadImageAsTexture(image, imaflag, blenderContext);
                     if (loadedTexture != null) {
                         result = loadedTexture;
-                        this.applyColorbandAndColorFactors(tex, result.getImage(), blenderContext);
+                        this.applyColorbandAndColorFactors(textureStructure, result.getImage(), blenderContext);
                     }
                 }
                 break;
@@ -160,7 +166,7 @@ public class TextureHelper extends AbstractBlenderHelper {
             case TEX_MUSGRAVE:
             case TEX_VORONOI:
             case TEX_DISTNOISE:
-                result = new GeneratedTexture(tex, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext);
+                result = new GeneratedTexture(textureStructure, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext);
                 break;
             case TEX_NONE:// No texture, do nothing
                 break;
@@ -169,13 +175,13 @@ public class TextureHelper extends AbstractBlenderHelper {
             case TEX_PLUGIN:
             case TEX_ENVMAP:
             case TEX_OCEAN:
-                LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, tex.getName() });
+                LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, textureStructure.getName() });
                 break;
             default:
-                throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName());
+                throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + textureStructure.getName());
         }
         if (result != null) {
-            result.setName(tex.getName());
+            result.setName(textureStructure.getName());
             result.setWrap(WrapMode.Repeat);
 
             // decide if the mipmaps will be generated
@@ -195,14 +201,14 @@ public class TextureHelper extends AbstractBlenderHelper {
             }
 
             if (type != TEX_IMAGE) {// only generated textures should have this key
-                result.setKey(new GeneratedTextureKey(tex.getName()));
+                result.setKey(new GeneratedTextureKey(textureStructure.getName()));
             }
 
             if (LOGGER.isLoggable(Level.FINE)) {
-                LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), tex.getOldMemoryAddress() });
+                LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), textureStructure.getOldMemoryAddress() });
             }
-            blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.STRUCTURE, tex);
-            blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
+            blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, textureStructure);
+            blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
         }
         return result;
     }
@@ -222,29 +228,39 @@ public class TextureHelper extends AbstractBlenderHelper {
      *             this exception is thrown when the blend file structure is
      *             somehow invalid or corrupted
      */
-    protected Texture loadTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException {
+    public Texture loadImageAsTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException {
         LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress());
         Texture result = null;
         Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
-        if (im == null) {
-            String texturePath = imageStructure.getFieldValue("name").toString();
-            Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile");
-            if (pPackedFile.isNull()) {
-                LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath);
-                result = this.loadImageFromFile(texturePath, imaflag, blenderContext);
+       // if (im == null) {  HACK force reaload always, as constructor in else case is destroying the TextureKeys!
+            if ("ID".equals(imageStructure.getType())) {
+                LOGGER.fine("Loading texture from external blend file.");
+                result = (Texture) this.loadLibrary(imageStructure);
             } else {
-                LOGGER.fine("Packed texture. Reading directly from the blend file!");
-                Structure packedFile = pPackedFile.fetchData().get(0);
-                Pointer pData = (Pointer) packedFile.getFieldValue("data");
-                FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress());
-                blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition());
-                ImageLoader imageLoader = new ImageLoader();
-
-                // Should the texture be flipped? It works for sinbad ..
-                result = new Texture2D(imageLoader.loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true));
+                String texturePath = imageStructure.getFieldValue("name").toString();
+                Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile");
+                if (pPackedFile.isNull()) {
+                    LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath);
+                    result = this.loadImageFromFile(texturePath, imaflag, blenderContext);
+                } else {
+                    LOGGER.fine("Packed texture. Reading directly from the blend file!");
+                    Structure packedFile = pPackedFile.fetchData().get(0);
+                    Pointer pData = (Pointer) packedFile.getFieldValue("data");
+                    FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress());
+                    blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition());
+
+                    // Should the texture be flipped? It works for sinbad ..
+                    result = new Texture2D(new ImageLoader().loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true));
+                }
             }
-        } else {
-            result = new Texture2D(im);
+        //} else {
+       //     result = new Texture2D(im);
+       // }
+
+        if (result != null) {// render result is not being loaded
+            blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, imageStructure);
+            blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result.getImage());
+            result.setName(imageStructure.getName());
         }
         return result;
     }
@@ -524,6 +540,18 @@ public class TextureHelper extends AbstractBlenderHelper {
         return result;
     }
 
+    /**
+     * Reads the texture data from the given material or sky structure.
+     * @param structure
+     *            the structure of material or sky
+     * @param diffuseColorArray
+     *            array of diffuse colors
+     * @param skyTexture
+     *            indicates it we're going to read sky texture or not
+     * @return a list of combined textures
+     * @throws BlenderFileException
+     *             an exception is thrown when problems with reading the blend file occur
+     */
     @SuppressWarnings("unchecked")
     public List<CombinedTexture> readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException {
         DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");

+ 14 - 13
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java

@@ -31,6 +31,7 @@ import com.jme3.texture.Image;
 import com.jme3.texture.Image.Format;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture2D;
+import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.BufferUtils;
 
 /**
@@ -77,7 +78,7 @@ import com.jme3.util.BufferUtils;
         for (int i = 0; i < facesCount; ++i) {
             faceTextures.add(new TriangleTextureElement(i, texture2d.getImage(), uvs, true, blenderContext));
         }
-        this.format = texture2d.getImage().getFormat();
+        format = texture2d.getImage().getFormat();
     }
 
     /**
@@ -113,7 +114,7 @@ import com.jme3.util.BufferUtils;
      */
     public void blend(TextureBlender textureBlender, TriangulatedTexture baseTexture, BlenderContext blenderContext) {
         Format newFormat = null;
-        for (TriangleTextureElement triangleTextureElement : this.faceTextures) {
+        for (TriangleTextureElement triangleTextureElement : faceTextures) {
             Image baseImage = baseTexture == null ? null : baseTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image;
             triangleTextureElement.image = textureBlender.blend(triangleTextureElement.image, baseImage, blenderContext);
             if (newFormat == null) {
@@ -122,7 +123,7 @@ import com.jme3.util.BufferUtils;
                 throw new IllegalArgumentException("Face texture element images MUST have the same image format!");
             }
         }
-        this.format = newFormat;
+        format = newFormat;
     }
 
     /**
@@ -242,7 +243,7 @@ import com.jme3.util.BufferUtils;
                 resultUVS.set(entry.getKey().faceIndex * 3 + 2, uvs[2]);
             }
 
-            Image resultImage = new Image(format, resultImageWidth, resultImageHeight, BufferUtils.createByteBuffer(resultImageWidth * resultImageHeight * (format.getBitsPerPixel() >> 3)));
+            Image resultImage = new Image(format, resultImageWidth, resultImageHeight, BufferUtils.createByteBuffer(resultImageWidth * resultImageHeight * (format.getBitsPerPixel() >> 3)), ColorSpace.Linear);
             resultTexture = new Texture2D(resultImage);
             for (Entry<TriangleTextureElement, Integer[]> entry : imageLayoutData.entrySet()) {
                 if (!duplicatedFaceIndexes.contains(entry.getKey().faceIndex)) {
@@ -420,7 +421,7 @@ import com.jme3.util.BufferUtils;
                     data.put(pixel.getA8());
                 }
             }
-            image = new Image(Format.RGBA8, width, height, data);
+            image = new Image(Format.RGBA8, width, height, data, ColorSpace.Linear);
 
             // modify the UV values so that they fit the new image
             float heightUV = maxUVY - minUVY;
@@ -481,7 +482,7 @@ import com.jme3.util.BufferUtils;
                 imageHeight = 1;
             }
             ByteBuffer data = BufferUtils.createByteBuffer(imageWidth * imageHeight * (imageFormat.getBitsPerPixel() >> 3));
-            image = new Image(texture.getImage().getFormat(), imageWidth, imageHeight, data);
+            image = new Image(texture.getImage().getFormat(), imageWidth, imageHeight, data, ColorSpace.Linear);
 
             // computing the pixels
             PixelInputOutput pixelWriter = PixelIOFactory.getPixelIO(imageFormat);
@@ -529,8 +530,8 @@ import com.jme3.util.BufferUtils;
         public void computeFinalUVCoordinates(int totalImageWidth, int totalImageHeight, int xPos, int yPos, Vector2f[] result) {
             for (int i = 0; i < 3; ++i) {
                 result[i] = new Vector2f();
-                result[i].x = xPos / (float) totalImageWidth + this.uv[i].x * (this.image.getWidth() / (float) totalImageWidth);
-                result[i].y = yPos / (float) totalImageHeight + this.uv[i].y * (this.image.getHeight() / (float) totalImageHeight);
+                result[i].x = xPos / (float) totalImageWidth + uv[i].x * (image.getWidth() / (float) totalImageWidth);
+                result[i].y = yPos / (float) totalImageHeight + uv[i].y * (image.getHeight() / (float) totalImageHeight);
             }
         }
 
@@ -623,9 +624,9 @@ import com.jme3.util.BufferUtils;
          *            a position in 3D space
          */
         public RectangleEnvelope(Vector3f pointPosition) {
-            this.min = pointPosition;
-            this.h = this.w = Vector3f.ZERO;
-            this.width = this.height = 1;
+            min = pointPosition;
+            h = w = Vector3f.ZERO;
+            width = height = 1;
         }
 
         /**
@@ -642,8 +643,8 @@ import com.jme3.util.BufferUtils;
             this.min = min;
             this.h = h;
             this.w = w;
-            this.width = w.length();
-            this.height = h.length();
+            width = w.length();
+            height = h.length();
         }
 
         @Override

+ 10 - 8
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java

@@ -1,10 +1,12 @@
 package com.jme3.scene.plugins.blender.textures.blending;
 
+import java.util.logging.Logger;
+
+import jme3tools.converters.MipMapGenerator;
+
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.materials.MaterialHelper;
 import com.jme3.texture.Image;
-import java.util.logging.Logger;
-import jme3tools.converters.MipMapGenerator;
 
 /**
  * An abstract class that contains the basic methods used by the classes that
@@ -103,12 +105,12 @@ import jme3tools.converters.MipMapGenerator;
 
     public void copyBlendingData(TextureBlender textureBlender) {
         if (textureBlender instanceof AbstractTextureBlender) {
-            this.flag = ((AbstractTextureBlender) textureBlender).flag;
-            this.negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture;
-            this.blendType = ((AbstractTextureBlender) textureBlender).blendType;
-            this.materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone();
-            this.color = ((AbstractTextureBlender) textureBlender).color.clone();
-            this.blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor;
+            flag = ((AbstractTextureBlender) textureBlender).flag;
+            negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture;
+            blendType = ((AbstractTextureBlender) textureBlender).blendType;
+            materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone();
+            color = ((AbstractTextureBlender) textureBlender).color.clone();
+            blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor;
         } else {
             LOGGER.warning("Cannot copy blending data from other types than " + this.getClass());
         }

+ 3 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java

@@ -38,7 +38,9 @@ import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
 import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
 import com.jme3.texture.Image;
 import com.jme3.texture.Image.Format;
+import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.BufferUtils;
+
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 
@@ -141,7 +143,7 @@ public class TextureBlenderAWT extends AbstractTextureBlender {
             dataArray.add(newData);
         }
 
-        Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0));
+        Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray, ColorSpace.Linear) : new Image(Format.RGBA8, width, height, dataArray.get(0), ColorSpace.Linear);
         if (image.getMipMapSizes() != null) {
             result.setMipMapSizes(image.getMipMapSizes().clone());
         }

+ 4 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java

@@ -6,9 +6,12 @@ import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
 import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
 import com.jme3.texture.Image;
 import com.jme3.texture.Image.Format;
+import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.BufferUtils;
+
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+
 import jme3tools.converters.RGB565;
 
 /**
@@ -119,7 +122,7 @@ public class TextureBlenderDDS extends TextureBlenderAWT {
             dataArray.add(newData);
         }
 
-        Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray) : new Image(format, width, height, dataArray.get(0));
+        Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray, ColorSpace.Linear) : new Image(format, width, height, dataArray.get(0), ColorSpace.Linear);
         if (image.getMipMapSizes() != null) {
             result.setMipMapSizes(image.getMipMapSizes().clone());
         }

+ 3 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java

@@ -31,13 +31,13 @@
  */
 package com.jme3.scene.plugins.blender.textures.blending;
 
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.texture.Image;
 import com.jme3.texture.Image.Format;
 
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
 /**
  * This class creates the texture blending class depending on the texture type.
  * 
@@ -66,7 +66,6 @@ public class TextureBlenderFactory {
      *            the texture format
      * @return texture blending class
      */
-    @SuppressWarnings("deprecation")
     public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) {
         switch (format) {
             case Luminance8:

+ 3 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java

@@ -7,7 +7,9 @@ import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
 import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
 import com.jme3.texture.Image;
 import com.jme3.texture.Image.Format;
+import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.BufferUtils;
+
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.logging.Level;
@@ -93,7 +95,7 @@ public class TextureBlenderLuminance extends AbstractTextureBlender {
             dataArray.add(newData);
         }
 
-        Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0));
+        Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray, ColorSpace.Linear) : new Image(Format.RGBA8, width, height, dataArray.get(0), ColorSpace.Linear);
         if (image.getMipMapSizes() != null) {
             result.setMipMapSizes(image.getMipMapSizes().clone());
         }

+ 18 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java

@@ -17,12 +17,18 @@ import jme3tools.converters.RGB565;
             case RGBA8:
                 pixel.fromARGB8(data.get(index + 3), data.get(index), data.get(index + 1), data.get(index + 2));
                 break;
+            case ARGB8:
+                pixel.fromARGB8(data.get(index), data.get(index + 1), data.get(index + 2), data.get(index + 3));
+                break;
             case ABGR8:
                 pixel.fromARGB8(data.get(index), data.get(index + 3), data.get(index + 2), data.get(index + 1));
                 break;
             case BGR8:
                 pixel.fromARGB8((byte) 0xFF, data.get(index + 2), data.get(index + 1), data.get(index));
                 break;
+            case BGRA8:
+                pixel.fromARGB8(data.get(index + 3), data.get(index + 2), data.get(index + 1), data.get(index));
+                break;
             case RGB8:
                 pixel.fromARGB8((byte) 0xFF, data.get(index), data.get(index + 1), data.get(index + 2));
                 break;
@@ -72,6 +78,12 @@ import jme3tools.converters.RGB565;
                 data.put(index + 2, pixel.getB8());
                 data.put(index + 3, pixel.getA8());
                 break;
+            case ARGB8:
+                data.put(index, pixel.getA8());
+                data.put(index + 1, pixel.getR8());
+                data.put(index + 2, pixel.getG8());
+                data.put(index + 3, pixel.getB8());
+                break;
             case ABGR8:
                 data.put(index, pixel.getA8());
                 data.put(index + 1, pixel.getB8());
@@ -83,6 +95,12 @@ import jme3tools.converters.RGB565;
                 data.put(index + 1, pixel.getG8());
                 data.put(index + 2, pixel.getR8());
                 break;
+            case BGRA8:
+                data.put(index, pixel.getB8());
+                data.put(index + 1, pixel.getG8());
+                data.put(index + 2, pixel.getR8());
+                data.put(index + 3, pixel.getA8());
+                break;
             case RGB8:
                 data.put(index, pixel.getR8());
                 data.put(index + 1, pixel.getG8());

+ 2 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java

@@ -26,7 +26,9 @@ public class PixelIOFactory {
                 case ABGR8:
                 case RGBA8:
                 case BGR8:
+                case BGRA8:
                 case RGB8:
+                case ARGB8:
                 case RGB111110F:
                 case RGB16F:
                 case RGB16F_to_RGB111110F:

+ 1 - 1
jme3-bullet-native/build.gradle

@@ -175,7 +175,7 @@ binaries.withType(SharedLibraryBinary) { binary ->
     // Add depend on build
     jar.dependsOn builderTask
     // Add output to libs folder
-    task "copyBinaryToLibs${targetPlatform}"(type: Copy) {
+    task "copyBinaryToLibs${targetPlatform}"(type: Copy, dependsOn: builderTask) {
         from builderTask.outputFile
         into "libs/native/${targetPlatform.operatingSystem.name}/${targetPlatform.architecture.name}"
     }

+ 61 - 0
jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp

@@ -468,6 +468,67 @@ extern "C" {
         return;
     }
 
+
+
+    JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_sweepTest_1native
+        (JNIEnv * env, jobject object, jlong shapeId, jobject from, jobject to, jlong spaceId, jobject resultlist, jfloat allowedCcdPenetration) {
+
+        jmePhysicsSpace* space = reinterpret_cast<jmePhysicsSpace*> (spaceId);
+        if (space == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The physics space does not exist.");
+            return;
+        }
+
+        btCollisionShape* shape = reinterpret_cast<btCollisionShape*> (shapeId);
+        if (shape == NULL) {
+            jclass newExc = env->FindClass("java/lang/NullPointerException");
+            env->ThrowNew(newExc, "The shape does not exist.");
+            return;
+        }
+
+        struct AllConvexResultCallback : public btCollisionWorld::ConvexResultCallback {
+
+            AllConvexResultCallback(const btTransform& convexFromWorld, const  btTransform & convexToWorld) : m_convexFromWorld(convexFromWorld), m_convexToWorld(convexToWorld) {
+            }
+            jobject resultlist;
+            JNIEnv* env;
+            btTransform m_convexFromWorld; //used to calculate hitPointWorld from hitFraction
+            btTransform m_convexToWorld;
+
+            btVector3 m_hitNormalWorld;
+            btVector3 m_hitPointWorld;
+
+            virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) {
+                    if (normalInWorldSpace) {
+                            m_hitNormalWorld = convexResult.m_hitNormalLocal;
+                    }
+                    else {
+                            m_hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal;
+                    }
+                    m_hitPointWorld.setInterpolate3(m_convexFromWorld.getBasis() * m_convexFromWorld.getOrigin(), m_convexToWorld.getBasis() * m_convexToWorld.getOrigin(), convexResult.m_hitFraction);
+
+                    jmeBulletUtil::addSweepResult(env, resultlist, &m_hitNormalWorld, &m_hitPointWorld, convexResult.m_hitFraction, convexResult.m_hitCollisionObject);
+
+                    return 1.f;
+            }
+        };
+
+        btTransform native_to = btTransform();
+        jmeBulletUtil::convert(env, to, &native_to);
+
+        btTransform native_from = btTransform();
+        jmeBulletUtil::convert(env, from, &native_from);
+
+        btScalar native_allowed_ccd_penetration = btScalar(allowedCcdPenetration);
+
+        AllConvexResultCallback resultCallback(native_from, native_to);
+        resultCallback.env = env;
+        resultCallback.resultlist = resultlist;
+        space->getDynamicsWorld()->convexSweepTest((btConvexShape *) shape, native_from, native_to, resultCallback, native_allowed_ccd_penetration);
+        return;
+    }
+
 #ifdef __cplusplus
 }
 #endif

+ 9 - 0
jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.h

@@ -165,6 +165,15 @@ JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_initNativePhysics
 JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_finalizeNative
   (JNIEnv *, jobject, jlong);
 
+
+/*
+* Class:     com_jme3_bullet_PhysicsSpace
+* Method : sweepTest_native
+* Signature: (J;L;Lcom/jme3/math/Transform;Lcom/jme3/math/Transform;L;JLjava/util/List;F)V
+*/
+JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_sweepTest_1native
+(JNIEnv *, jobject, jlong, jobject, jobject, jlong, jobject, jfloat);
+
 #ifdef __cplusplus
 }
 #endif

+ 90 - 0
jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp

@@ -59,6 +59,38 @@ void jmeBulletUtil::convert(JNIEnv* env, jobject in, btVector3* out) {
     out->setZ(z);
 }
 
+void jmeBulletUtil::convert(JNIEnv* env, jobject in, btQuaternion* out) {
+	if (in == NULL || out == NULL) {
+		jmeClasses::throwNPE(env);
+	}
+	float x = env->GetFloatField(in, jmeClasses::Quaternion_x); 
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+	float y = env->GetFloatField(in, jmeClasses::Quaternion_y); //env->CallFloatMethod(in, jmeClasses::Vector3f_getY);
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+	float z = env->GetFloatField(in, jmeClasses::Quaternion_z); //env->CallFloatMethod(in, jmeClasses::Vector3f_getZ);
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+
+	float w = env->GetFloatField(in, jmeClasses::Quaternion_w); //env->CallFloatMethod(in, jmeClasses::Vector3f_getZ);
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+	out->setX(x);
+	out->setY(y);
+	out->setZ(z);
+	out->setW(w);
+}
+
+
 void jmeBulletUtil::convert(JNIEnv* env, const btVector3* in, jobject out) {
     if (in == NULL || out == NULL) {
         jmeClasses::throwNPE(env);
@@ -325,3 +357,61 @@ void jmeBulletUtil::addResult(JNIEnv* env, jobject resultlist, btVector3* hitnor
         return;
     }
 }
+
+
+void jmeBulletUtil::addSweepResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld, btScalar m_hitFraction, const btCollisionObject* hitobject) {
+
+	jobject singleresult = env->AllocObject(jmeClasses::PhysicsSweep_Class);
+	jobject hitnormalvec = env->AllocObject(jmeClasses::Vector3f);
+
+	convert(env, hitnormal, hitnormalvec);
+	jmeUserPointer *up1 = (jmeUserPointer*)hitobject->getUserPointer();
+
+	env->SetObjectField(singleresult, jmeClasses::PhysicsSweep_normalInWorldSpace, hitnormalvec);
+	env->SetFloatField(singleresult, jmeClasses::PhysicsSweep_hitfraction, m_hitFraction);
+
+	env->SetObjectField(singleresult, jmeClasses::PhysicsSweep_collisionObject, up1->javaCollisionObject);
+	env->CallVoidMethod(resultlist, jmeClasses::PhysicsSweep_addmethod, singleresult);
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+}
+
+void jmeBulletUtil::convert(JNIEnv* env, jobject in, btTransform* out) {
+	if (in == NULL || out == NULL) {
+		jmeClasses::throwNPE(env);
+	}
+
+	jobject translation_vec = env->CallObjectMethod(in, jmeClasses::Transform_translation);
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+
+	jobject rot_quat = env->CallObjectMethod(in, jmeClasses::Transform_rotation);
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+	
+	/*
+	//Scale currently not supported by bullet
+	//@TBD: Create an assertion somewhere to avoid scale values
+	jobject scale_vec = env->GetObjectField(in, jmeClasses::Transform_scale);
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+	*/
+	btVector3 native_translation_vec = btVector3();
+	//btVector3 native_scale_vec = btVector3();
+	btQuaternion native_rot_quat = btQuaternion();
+	
+	convert(env, translation_vec, &native_translation_vec);
+	//convert(env, scale_vec, native_scale_vec);
+	convert(env, rot_quat, &native_rot_quat);
+
+	out->setRotation(native_rot_quat);
+	out->setOrigin(native_translation_vec);
+}

+ 3 - 0
jme3-bullet-native/src/native/cpp/jmeBulletUtil.h

@@ -42,10 +42,13 @@ public:
     static void convert(JNIEnv* env, jobject in, btVector3* out);
     static void convert(JNIEnv* env, const btVector3* in, jobject out);
     static void convert(JNIEnv* env, jobject in, btMatrix3x3* out);
+    static void convert(JNIEnv* env, jobject in, btQuaternion* out);
     static void convert(JNIEnv* env, const btMatrix3x3* in, jobject out);
     static void convertQuat(JNIEnv* env, jobject in, btMatrix3x3* out);
     static void convertQuat(JNIEnv* env, const btMatrix3x3* in, jobject out);
+    static void convert(JNIEnv* env, jobject in, btTransform* out);
     static void addResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld,const btScalar  m_hitFraction,const btCollisionObject* hitobject);
+    static void addSweepResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld, const btScalar  m_hitFraction, const btCollisionObject* hitobject);
 private:
     jmeBulletUtil(){};
     ~jmeBulletUtil(){};

+ 79 - 0
jme3-bullet-native/src/native/cpp/jmeClasses.cpp

@@ -91,6 +91,21 @@ jfieldID jmeClasses::PhysicsRay_collisionObject;
 jclass jmeClasses::PhysicsRay_listresult;
 jmethodID jmeClasses::PhysicsRay_addmethod;
 
+jclass jmeClasses::PhysicsSweep_Class;
+jmethodID jmeClasses::PhysicsSweep_newSingleResult;
+
+jfieldID jmeClasses::PhysicsSweep_normalInWorldSpace;
+jfieldID jmeClasses::PhysicsSweep_hitfraction;
+jfieldID jmeClasses::PhysicsSweep_collisionObject;
+
+jclass jmeClasses::PhysicsSweep_listresult;
+jmethodID jmeClasses::PhysicsSweep_addmethod;
+
+
+jclass jmeClasses::Transform;
+jmethodID jmeClasses::Transform_rotation;
+jmethodID jmeClasses::Transform_translation;
+
 //private fields
 //JNIEnv* jmeClasses::env;
 JavaVM* jmeClasses::vm;
@@ -240,6 +255,70 @@ void jmeClasses::initJavaClasses(JNIEnv* env) {
         env->Throw(env->ExceptionOccurred());
         return;
     }
+
+	PhysicsSweep_Class = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/collision/PhysicsSweepTestResult"));
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+}
+
+	PhysicsSweep_newSingleResult = env->GetMethodID(PhysicsSweep_Class, "<init>", "()V");
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+
+	PhysicsSweep_normalInWorldSpace = env->GetFieldID(PhysicsSweep_Class, "hitNormalLocal", "Lcom/jme3/math/Vector3f;");
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+
+
+	PhysicsSweep_hitfraction = env->GetFieldID(PhysicsSweep_Class, "hitFraction", "F");
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+
+
+	PhysicsSweep_collisionObject = env->GetFieldID(PhysicsSweep_Class, "collisionObject", "Lcom/jme3/bullet/collision/PhysicsCollisionObject;");
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+
+	PhysicsSweep_listresult = env->FindClass("java/util/List");
+	PhysicsSweep_listresult = (jclass)env->NewGlobalRef(PhysicsSweep_listresult);
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+
+	PhysicsSweep_addmethod = env->GetMethodID(PhysicsSweep_listresult, "add", "(Ljava/lang/Object;)Z");
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+
+	Transform = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Transform"));
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+
+	Transform_rotation = env->GetMethodID(Transform, "getRotation", "()Lcom/jme3/math/Quaternion;");
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+	
+	Transform_translation = env->GetMethodID(Transform, "getTranslation", "()Lcom/jme3/math/Vector3f;");
+	if (env->ExceptionCheck()) {
+		env->Throw(env->ExceptionOccurred());
+		return;
+	}
+
 }
 
 void jmeClasses::throwNPE(JNIEnv* env) {

+ 12 - 0
jme3-bullet-native/src/native/cpp/jmeClasses.h

@@ -89,6 +89,18 @@ public:
     static jclass PhysicsRay_listresult;
     static jmethodID PhysicsRay_addmethod;
 
+	static jclass PhysicsSweep_Class;
+	static jmethodID PhysicsSweep_newSingleResult;
+	static jfieldID PhysicsSweep_normalInWorldSpace;
+	static jfieldID PhysicsSweep_hitfraction;
+	static jfieldID PhysicsSweep_collisionObject;
+	static jclass PhysicsSweep_listresult;
+	static jmethodID PhysicsSweep_addmethod;
+
+	static jclass Transform;
+	static jmethodID Transform_rotation;
+	static jmethodID Transform_translation;
+
     static jclass DebugMeshCallback;
     static jmethodID DebugMeshCallback_addVector;
 

+ 32 - 29
jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java

@@ -820,6 +820,10 @@ public class PhysicsSpace {
 //            return lrr.hitFraction;
 //        }
 //    }
+//
+//
+
+
     /**
      * Performs a sweep collision test and returns the results as a list of
      * PhysicsSweepTestResults<br/> You have to use different Transforms for
@@ -828,48 +832,47 @@ public class PhysicsSpace {
      * center.
      */
     public List<PhysicsSweepTestResult> sweepTest(CollisionShape shape, Transform start, Transform end) {
-        List<PhysicsSweepTestResult> results = new LinkedList<PhysicsSweepTestResult>();
-//        if (!(shape.getCShape() instanceof ConvexShape)) {
-//            logger.log(Level.WARNING, "Trying to sweep test with incompatible mesh shape!");
-//            return results;
-//        }
-//        dynamicsWorld.convexSweepTest((ConvexShape) shape.getCShape(), Converter.convert(start, sweepTrans1), Converter.convert(end, sweepTrans2), new InternalSweepListener(results));
-        return results;
+        List results = new LinkedList();
+        sweepTest(shape, start, end , results);
+        return (List<PhysicsSweepTestResult>) results;
+    }
 
+    public List<PhysicsSweepTestResult> sweepTest(CollisionShape shape, Transform start, Transform end, List<PhysicsSweepTestResult> results) {        
+        return sweepTest(shape, start, end, results, 0.0f);
     }
 
+    public native void sweepTest_native(long shape, Transform from, Transform to, long physicsSpaceId, List<PhysicsSweepTestResult> results, float allowedCcdPenetration);
     /**
      * Performs a sweep collision test and returns the results as a list of
      * PhysicsSweepTestResults<br/> You have to use different Transforms for
-     * start and end (at least distance > 0.4f). SweepTest will not see a
+     * start and end (at least distance > allowedCcdPenetration). SweepTest will not see a
      * collision if it starts INSIDE an object and is moving AWAY from its
      * center.
      */
-    public List<PhysicsSweepTestResult> sweepTest(CollisionShape shape, Transform start, Transform end, List<PhysicsSweepTestResult> results) {
+    public List<PhysicsSweepTestResult> sweepTest(CollisionShape shape, Transform start, Transform end, List<PhysicsSweepTestResult> results, float allowedCcdPenetration ) {        
         results.clear();
-//        if (!(shape.getCShape() instanceof ConvexShape)) {
-//            logger.log(Level.WARNING, "Trying to sweep test with incompatible mesh shape!");
-//            return results;
-//        }
-//        dynamicsWorld.convexSweepTest((ConvexShape) shape.getCShape(), Converter.convert(start, sweepTrans1), Converter.convert(end, sweepTrans2), new InternalSweepListener(results));
+        sweepTest_native(shape.getObjectId(), start, end, physicsSpaceId, results, allowedCcdPenetration);
         return results;
     }
 
-//    private class InternalSweepListener extends CollisionWorld.ConvexResultCallback {
-//
-//        private List<PhysicsSweepTestResult> results;
-//
-//        public InternalSweepListener(List<PhysicsSweepTestResult> results) {
-//            this.results = results;
-//        }
-//
-//        @Override
-//        public float addSingleResult(LocalConvexResult lcr, boolean bln) {
-//            PhysicsCollisionObject obj = (PhysicsCollisionObject) lcr.hitCollisionObject.getUserPointer();
-//            results.add(new PhysicsSweepTestResult(obj, Converter.convert(lcr.hitNormalLocal), lcr.hitFraction, bln));
-//            return lcr.hitFraction;
-//        }
-//    }
+/*    private class InternalSweepListener extends CollisionWorld.ConvexResultCallback {
+
+        private List<PhysicsSweepTestResult> results;
+
+        public InternalSweepListener(List<PhysicsSweepTestResult> results) {
+            this.results = results;
+        }
+
+        @Override
+        public float addSingleResult(LocalConvexResult lcr, boolean bln) {
+            PhysicsCollisionObject obj = (PhysicsCollisionObject) lcr.hitCollisionObject.getUserPointer();
+            results.add(new PhysicsSweepTestResult(obj, Converter.convert(lcr.hitNormalLocal), lcr.hitFraction, bln));
+            return lcr.hitFraction;
+        }
+    }
+
+    */
+    
     /**
      * destroys the current PhysicsSpace so that a new one can be created
      */

+ 21 - 6
jme3-core/build.gradle

@@ -26,14 +26,29 @@ import org.ajoberstar.grgit.*
 
 task updateVersion << {
     
-    def grgit = Grgit.open(project.file('.').parent)
+    def verfile = file('src/main/java/com/jme3/system/JmeVersion.java')
+    def jmeGitHash
+    def jmeShortGitHash
+    def jmeBuildDate
+    def jmeBranchName
     
-    def jmeGitHash = grgit.head().id
-    def jmeShortGitHash = grgit.head().abbreviatedId
-    def jmeBuildDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date())
-    def jmeBranchName = grgit.branch.current.name
+    try {
+        def grgit = Grgit.open(project.file('.').parent)
+        jmeGitHash = grgit.head().id
+        jmeShortGitHash = grgit.head().abbreviatedId
+        jmeBuildDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date())
+        jmeBranchName = grgit.branch.current.name
+    } catch (ex) {
+        // Failed to get repo info
+        logger.warn("Failed to get repository info: " + ex.message + ". " + \
+                    "Only partial build info will be generated.")
+        
+        jmeGitHash = ""
+        jmeShortGitHash = ""
+        jmeBuildDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date())
+        jmeBranchName = "unknown"
+    }
     
-    def verfile = file('src/main/java/com/jme3/system/JmeVersion.java')
     verfile.text = "\npackage com.jme3.system;\n\n" +
                    "/**\n * THIS IS AN AUTO-GENERATED FILE..\n * DO NOT MODIFY!\n */\n" + 
                    "public class JmeVersion {\n" + 

+ 0 - 1
jme3-core/src/main/java/com/jme3/animation/AnimChannel.java

@@ -312,7 +312,6 @@ public final class AnimChannel {
             }
         }
         animation = null;
-       // System.out.println("Setting notified false");
         notified = false;
     }
 

+ 1 - 1
jme3-core/src/main/java/com/jme3/asset/AssetConfig.java

@@ -69,7 +69,7 @@ public final class AssetConfig {
     public static void loadText(AssetManager assetManager, URL configUrl) throws IOException{
         InputStream in = configUrl.openStream();
         try {
-            Scanner scan = new Scanner(in);
+            Scanner scan = new Scanner(in, "UTF-8");
             scan.useLocale(Locale.US); // Fix commas / periods ??
             while (scan.hasNext()){
                 String cmd = scan.next();

+ 2 - 3
jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java

@@ -312,13 +312,12 @@ public class DesktopAssetManager implements AssetManager {
     protected <T> T registerAndCloneSmartAsset(AssetKey<T> key, T obj, AssetProcessor proc, AssetCache cache) {
         // object obj is the original asset
         // create an instance for user
-        T clone = (T) obj;
         if (proc == null) {
             throw new IllegalStateException("Asset implements "
                     + "CloneableSmartAsset but doesn't "
                     + "have processor to handle cloning");
         } else {
-            clone = (T) proc.createClone(obj);
+            T clone = (T) proc.createClone(obj);
             if (cache != null && clone != obj) {
                 cache.registerAssetClone(key, clone);
             } else {
@@ -326,8 +325,8 @@ public class DesktopAssetManager implements AssetManager {
                         + "CloneableSmartAsset but doesn't have cache or "
                         + "was not cloned");
             }
+            return clone;
         }
-        return clone;
     }
     
     @Override

+ 3 - 3
jme3-core/src/main/java/com/jme3/asset/ImplHandler.java

@@ -47,7 +47,7 @@ import java.util.logging.Logger;
  * This is done by keeping an instance of each asset loader and asset
  * locator object in a thread local.
  */
-public class ImplHandler {
+final class ImplHandler {
 
     private static final Logger logger = Logger.getLogger(ImplHandler.class.getName());
 
@@ -75,7 +75,7 @@ public class ImplHandler {
         this.assetManager = assetManager;
     }
 
-    protected class ImplThreadLocal<T> extends ThreadLocal {
+    protected static class ImplThreadLocal<T> extends ThreadLocal {
 
         private final Class<T> type;
         private final String path;
@@ -83,7 +83,7 @@ public class ImplHandler {
 
         public ImplThreadLocal(Class<T> type, String[] extensions){
             this.type = type;
-            this.extensions = extensions;
+            this.extensions = extensions.clone();
             this.path = null;
         }
 

+ 0 - 2
jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java

@@ -39,8 +39,6 @@ import java.util.List;
  * Used for loading {@link ShaderNodeDefinition shader nodes definition}
  *
  * Tells if the defintion has to be loaded with or without its documentation
- *
- * @author Kirill Vainer
  */
 public class ShaderNodeDefinitionKey extends AssetKey<List<ShaderNodeDefinition>> {
 

+ 1 - 5
jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java

@@ -155,11 +155,7 @@ public class WeakRefCloneAssetCache implements AssetCache {
     }
 
     public <T> T getFromCache(AssetKey<T> key) {
-        AssetRef smartInfo;
-        synchronized (smartCache){
-            smartInfo = smartCache.get(key);
-        }
-        
+        AssetRef smartInfo = smartCache.get(key);
         if (smartInfo == null) {
             return null;
         } else {

+ 4 - 1
jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java

@@ -51,6 +51,9 @@ import static com.jme3.audio.openal.EFX.*;
 public class ALAudioRenderer implements AudioRenderer, Runnable {
 
     private static final Logger logger = Logger.getLogger(ALAudioRenderer.class.getName());
+    
+    private static final String THREAD_NAME = "jME3 Audio Decoder";
+    
     private final NativeObjectManager objManager = new NativeObjectManager();
     // When multiplied by STREAMING_BUFFER_COUNT, will equal 44100 * 2 * 2
     // which is exactly 1 second of audio.
@@ -75,7 +78,7 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
     
     // Fill streaming sources every 50 ms
     private static final float UPDATE_RATE = 0.05f;
-    private final Thread decoderThread = new Thread(this, "jME3 Audio Decoding Thread");
+    private final Thread decoderThread = new Thread(this, THREAD_NAME);
     private final Object threadLock = new Object();
 
     private final AL al;

+ 1 - 2
jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java

@@ -640,8 +640,7 @@ public class BoundingSphere extends BoundingVolume {
             return rVal;
         }
 
-        return new BoundingSphere(radius,
-                (center != null ? (Vector3f) center.clone() : null));
+        return new BoundingSphere(radius, center.clone());
     }
 
     /**

+ 5 - 0
jme3-core/src/main/java/com/jme3/collision/CollisionResult.java

@@ -109,6 +109,11 @@ public class CollisionResult implements Comparable<CollisionResult> {
         return super.equals(obj);
     }
 
+    @Override
+    public int hashCode() {
+        return Float.floatToIntBits(distance);
+    }
+
     public Vector3f getContactPoint() {
         return contactPoint;
     }

+ 1 - 1
jme3-core/src/main/java/com/jme3/light/SpotLight.java

@@ -56,7 +56,7 @@ import java.io.IOException;
  * the light intensity slowly decrease between the inner cone and the outer cone.
  *  @author Nehon
  */
-public class SpotLight extends Light implements Savable {
+public class SpotLight extends Light {
 
     protected Vector3f position = new Vector3f();
     protected Vector3f direction = new Vector3f(0,-1,0);

+ 59 - 64
jme3-core/src/main/java/com/jme3/material/TechniqueDef.java

@@ -103,11 +103,10 @@ public class TechniqueDef implements Savable {
     private EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
     private String name;
 
-    private EnumMap<Shader.ShaderType,String> shaderLanguage;
-    private EnumMap<Shader.ShaderType,String> shaderName;
+    private EnumMap<Shader.ShaderType,String> shaderLanguages;
+    private EnumMap<Shader.ShaderType,String> shaderNames;
     
     private DefineList presetDefines;
-    private boolean usesShaders;
     private boolean usesNodes = false;
     private List<ShaderNode> shaderNodes;
     private ShaderGenerationInfo shaderGenerationInfo;
@@ -140,8 +139,8 @@ public class TechniqueDef implements Savable {
      * Serialization only. Do not use.
      */
     public TechniqueDef(){
-        shaderLanguage=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-        shaderName=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
+        shaderLanguages=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
+        shaderNames=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
     }
 
     /**
@@ -226,7 +225,7 @@ public class TechniqueDef implements Savable {
      */
     @Deprecated
     public boolean isUsingShaders(){
-        return usesShaders;
+        return true;
     }
     
     /**
@@ -258,36 +257,44 @@ public class TechniqueDef implements Savable {
      * @param fragLanguage The fragment shader language
      */
     public void setShaderFile(String vertexShader, String fragmentShader, String vertLanguage, String fragLanguage) {
-        this.shaderLanguage.put(Shader.ShaderType.Vertex, vertLanguage);
-        this.shaderName.put(Shader.ShaderType.Vertex, vertexShader);
-        this.shaderLanguage.put(Shader.ShaderType.Fragment, fragLanguage);
-        this.shaderName.put(Shader.ShaderType.Fragment, fragmentShader);
+        this.shaderLanguages.put(Shader.ShaderType.Vertex, vertLanguage);
+        this.shaderNames.put(Shader.ShaderType.Vertex, vertexShader);
+        this.shaderLanguages.put(Shader.ShaderType.Fragment, fragLanguage);
+        this.shaderNames.put(Shader.ShaderType.Fragment, fragmentShader);
+        
+        requiredCaps.clear();
         Caps vertCap = Caps.valueOf(vertLanguage);
         requiredCaps.add(vertCap);
         Caps fragCap = Caps.valueOf(fragLanguage);
         requiredCaps.add(fragCap);
-
-        usesShaders = true;
     }
 
 
     /**
      * Sets the shaders that this technique definition will use.
      *
-     * @param shaderName EnumMap containing all shader names for this stage
-     * @param shaderLanguage EnumMap containing all shader languages for this stage
-     */
-    public void setShaderFile(EnumMap<Shader.ShaderType, String> shaderName, EnumMap<Shader.ShaderType, String> shaderLanguage) {
-        for (Shader.ShaderType shaderType : shaderName.keySet()) {
-            this.shaderLanguage.put(shaderType,shaderLanguage.get(shaderType));
-            this.shaderName.put(shaderType,shaderName.get(shaderType));
-            if(shaderType.equals(Shader.ShaderType.Geometry)){
+     * @param shaderNames EnumMap containing all shader names for this stage
+     * @param shaderLanguages EnumMap containing all shader languages for this stage
+     */
+    public void setShaderFile(EnumMap<Shader.ShaderType, String> shaderNames, EnumMap<Shader.ShaderType, String> shaderLanguages) {
+        requiredCaps.clear();
+        
+        for (Shader.ShaderType shaderType : shaderNames.keySet()) {
+            String language = shaderLanguages.get(shaderType);
+            String shaderFile = shaderNames.get(shaderType);
+            
+            this.shaderLanguages.put(shaderType, language);
+            this.shaderNames.put(shaderType, shaderFile);
+            
+            Caps vertCap = Caps.valueOf(language);
+            requiredCaps.add(vertCap);
+            
+            if (shaderType.equals(Shader.ShaderType.Geometry)) {
                 requiredCaps.add(Caps.GeometryShader);
-            }else if(shaderType.equals(Shader.ShaderType.TessellationControl)){
+            } else if (shaderType.equals(Shader.ShaderType.TessellationControl)) {
                 requiredCaps.add(Caps.TesselationShader);
             }
         }
-        usesShaders=true;
     }
 
     /**
@@ -362,7 +369,7 @@ public class TechniqueDef implements Savable {
      * @return the name of the fragment shader to be used.
      */
     public String getFragmentShaderName() {
-        return shaderName.get(Shader.ShaderType.Fragment);
+        return shaderNames.get(Shader.ShaderType.Fragment);
     }
 
     
@@ -373,42 +380,34 @@ public class TechniqueDef implements Savable {
      * @return the name of the vertex shader to be used.
      */
     public String getVertexShaderName() {
-        return shaderName.get(Shader.ShaderType.Vertex);
-    }
-
-    /**
-     * @deprecated Use {@link #getVertexShaderLanguage() } instead.
-     */
-    @Deprecated
-    public String getShaderLanguage() {
-        return shaderLanguage.get(Shader.ShaderType.Vertex);
+        return shaderNames.get(Shader.ShaderType.Vertex);
     }
 
     /**
      * Returns the language of the fragment shader used in this technique.
      */
     public String getFragmentShaderLanguage() {
-        return shaderLanguage.get(Shader.ShaderType.Fragment);
+        return shaderLanguages.get(Shader.ShaderType.Fragment);
     }
     
     /**
      * Returns the language of the vertex shader used in this technique.
      */
     public String getVertexShaderLanguage() {
-        return shaderLanguage.get(Shader.ShaderType.Vertex);
+        return shaderLanguages.get(Shader.ShaderType.Vertex);
     }
 
     /**Returns the language for each shader program
      * @param shaderType
      */
     public String getShaderProgramLanguage(Shader.ShaderType shaderType){
-        return shaderLanguage.get(shaderType);
+        return shaderLanguages.get(shaderType);
     }
     /**Returns the name for each shader program
      * @param shaderType
      */
     public String getShaderProgramName(Shader.ShaderType shaderType){
-        return shaderName.get(shaderType);
+        return shaderNames.get(shaderType);
     }
     
     /**
@@ -453,22 +452,21 @@ public class TechniqueDef implements Savable {
         OutputCapsule oc = ex.getCapsule(this);
         oc.write(name, "name", null);
 
-        oc.write(shaderName.get(Shader.ShaderType.Vertex), "vertName", null);
-        oc.write(shaderName.get(Shader.ShaderType.Fragment), "fragName", null);
-        oc.write(shaderName.get(Shader.ShaderType.Geometry), "geomName", null);
-        oc.write(shaderName.get(Shader.ShaderType.TessellationControl), "tsctrlName", null);
-        oc.write(shaderName.get(Shader.ShaderType.TessellationEvaluation), "tsevalName", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.Vertex), "vertLanguage", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.Fragment), "fragLanguage", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.Geometry), "geomLanguage", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.TessellationControl), "tsctrlLanguage", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.TessellationEvaluation), "tsevalLanguage", null);
+        oc.write(shaderNames.get(Shader.ShaderType.Vertex), "vertName", null);
+        oc.write(shaderNames.get(Shader.ShaderType.Fragment), "fragName", null);
+        oc.write(shaderNames.get(Shader.ShaderType.Geometry), "geomName", null);
+        oc.write(shaderNames.get(Shader.ShaderType.TessellationControl), "tsctrlName", null);
+        oc.write(shaderNames.get(Shader.ShaderType.TessellationEvaluation), "tsevalName", null);
+        oc.write(shaderLanguages.get(Shader.ShaderType.Vertex), "vertLanguage", null);
+        oc.write(shaderLanguages.get(Shader.ShaderType.Fragment), "fragLanguage", null);
+        oc.write(shaderLanguages.get(Shader.ShaderType.Geometry), "geomLanguage", null);
+        oc.write(shaderLanguages.get(Shader.ShaderType.TessellationControl), "tsctrlLanguage", null);
+        oc.write(shaderLanguages.get(Shader.ShaderType.TessellationEvaluation), "tsevalLanguage", null);
 
         oc.write(presetDefines, "presetDefines", null);
         oc.write(lightMode, "lightMode", LightMode.Disable);
         oc.write(shadowMode, "shadowMode", ShadowMode.Disable);
         oc.write(renderState, "renderState", null);
-        oc.write(usesShaders, "usesShaders", false);
         oc.write(usesNodes, "usesNodes", false);
         oc.writeSavableArrayList((ArrayList)shaderNodes,"shaderNodes", null);
         oc.write(shaderGenerationInfo, "shaderGenerationInfo", null);
@@ -482,28 +480,27 @@ public class TechniqueDef implements Savable {
     public void read(JmeImporter im) throws IOException{
         InputCapsule ic = im.getCapsule(this);
         name = ic.readString("name", null);
-        shaderName.put(Shader.ShaderType.Vertex,ic.readString("vertName", null));
-        shaderName.put(Shader.ShaderType.Fragment,ic.readString("fragName", null));
-        shaderName.put(Shader.ShaderType.Geometry,ic.readString("geomName", null));
-        shaderName.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null));
-        shaderName.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalName", null));
+        shaderNames.put(Shader.ShaderType.Vertex,ic.readString("vertName", null));
+        shaderNames.put(Shader.ShaderType.Fragment,ic.readString("fragName", null));
+        shaderNames.put(Shader.ShaderType.Geometry,ic.readString("geomName", null));
+        shaderNames.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null));
+        shaderNames.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalName", null));
         presetDefines = (DefineList) ic.readSavable("presetDefines", null);
         lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable);
         shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable);
         renderState = (RenderState) ic.readSavable("renderState", null);
-        usesShaders = ic.readBoolean("usesShaders", false);
         
         if (ic.getSavableVersion(TechniqueDef.class) == 0) {
             // Old version
-            shaderLanguage.put(Shader.ShaderType.Vertex,ic.readString("shaderLang", null));
-            shaderLanguage.put(Shader.ShaderType.Fragment,shaderLanguage.get(Shader.ShaderType.Vertex));
+            shaderLanguages.put(Shader.ShaderType.Vertex,ic.readString("shaderLang", null));
+            shaderLanguages.put(Shader.ShaderType.Fragment,shaderLanguages.get(Shader.ShaderType.Vertex));
         } else {
             // New version
-            shaderLanguage.put(Shader.ShaderType.Vertex,ic.readString("vertLanguage", null));
-            shaderLanguage.put(Shader.ShaderType.Fragment,ic.readString("fragLanguage", null));
-            shaderLanguage.put(Shader.ShaderType.Geometry,ic.readString("geomLanguage", null));
-            shaderLanguage.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlLanguage", null));
-            shaderLanguage.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalLanguage", null));
+            shaderLanguages.put(Shader.ShaderType.Vertex,ic.readString("vertLanguage", null));
+            shaderLanguages.put(Shader.ShaderType.Fragment,ic.readString("fragLanguage", null));
+            shaderLanguages.put(Shader.ShaderType.Geometry,ic.readString("geomLanguage", null));
+            shaderLanguages.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlLanguage", null));
+            shaderLanguages.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalLanguage", null));
         }
         
         usesNodes = ic.readBoolean("usesNodes", false);
@@ -518,7 +515,6 @@ public class TechniqueDef implements Savable {
     public void setShaderNodes(List<ShaderNode> shaderNodes) {
         this.shaderNodes = shaderNodes;
         usesNodes = true;
-        usesShaders = true;
     }
 
     /**
@@ -526,7 +522,7 @@ public class TechniqueDef implements Savable {
      * @return
      */
     public EnumMap<Shader.ShaderType, String> getShaderProgramNames() {
-        return shaderName;
+        return shaderNames;
     }
 
     /**
@@ -534,7 +530,7 @@ public class TechniqueDef implements Savable {
      * @return
      */
     public EnumMap<Shader.ShaderType, String> getShaderProgramLanguages() {
-        return shaderLanguage;
+        return shaderLanguages;
     }
 
     public ShaderGenerationInfo getShaderGenerationInfo() {
@@ -566,5 +562,4 @@ public class TechniqueDef implements Savable {
     public void setLightSpace(LightSpace lightSpace) {
         this.lightSpace = lightSpace;
     }
- 
 }

+ 21 - 56
jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java

@@ -1321,13 +1321,6 @@ public class GLRenderer implements Renderer {
 
 
             glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, prevFBO);
-            try {
-                checkFrameBufferError();
-            } catch (IllegalStateException ex) {
-                logger.log(Level.SEVERE, "Source FBO:\n{0}", src);
-                logger.log(Level.SEVERE, "Dest FBO:\n{0}", dst);
-                throw ex;
-            }
         } else {
             throw new RendererException("Framebuffer blitting not supported by the video hardware");
         }
@@ -1488,6 +1481,8 @@ public class GLRenderer implements Renderer {
             updateFrameBufferAttachment(fb, colorBuf);
         }
 
+        checkFrameBufferError();
+        
         fb.clearUpdateNeeded();
     }
 
@@ -1645,8 +1640,6 @@ public class GLRenderer implements Renderer {
             assert context.boundFBO == fb.getId();
 
             context.boundFB = fb;
-
-            checkFrameBufferError();
         }
     }
 
@@ -2221,53 +2214,25 @@ public class GLRenderer implements Renderer {
         int usage = convertUsage(vb.getUsage());
         vb.getData().rewind();
 
-//        if (created || vb.hasDataSizeChanged()) {
-            // upload data based on format
-            switch (vb.getFormat()) {
-                case Byte:
-                case UnsignedByte:
-                    gl.glBufferData(target, (ByteBuffer) vb.getData(), usage);
-                    break;
-                //            case Half:
-                case Short:
-                case UnsignedShort:
-                    gl.glBufferData(target, (ShortBuffer) vb.getData(), usage);
-                    break;
-                case Int:
-                case UnsignedInt:
-                    glext.glBufferData(target, (IntBuffer) vb.getData(), usage);
-                    break;
-                case Float:
-                    gl.glBufferData(target, (FloatBuffer) vb.getData(), usage);
-                    break;
-                default:
-                    throw new UnsupportedOperationException("Unknown buffer format.");
-            }
-//        } else {
-//            // Invalidate buffer data (orphan) before uploading new data.
-//            switch (vb.getFormat()) {
-//                case Byte:
-//                case UnsignedByte:
-//                    gl.glBufferSubData(target, 0, (ByteBuffer) vb.getData());
-//                    break;
-//                case Short:
-//                case UnsignedShort:
-//                    gl.glBufferSubData(target, 0, (ShortBuffer) vb.getData());
-//                    break;
-//                case Int:
-//                case UnsignedInt:
-//                    gl.glBufferSubData(target, 0, (IntBuffer) vb.getData());
-//                    break;
-//                case Float:
-//                    gl.glBufferSubData(target, 0, (FloatBuffer) vb.getData());
-//                    break;
-//                case Double:
-//                    gl.glBufferSubData(target, 0, (DoubleBuffer) vb.getData());
-//                    break;
-//                default:
-//                    throw new UnsupportedOperationException("Unknown buffer format.");
-//            }
-//        }
+        switch (vb.getFormat()) {
+            case Byte:
+            case UnsignedByte:
+                gl.glBufferData(target, (ByteBuffer) vb.getData(), usage);
+                break;
+            case Short:
+            case UnsignedShort:
+                gl.glBufferData(target, (ShortBuffer) vb.getData(), usage);
+                break;
+            case Int:
+            case UnsignedInt:
+                glext.glBufferData(target, (IntBuffer) vb.getData(), usage);
+                break;
+            case Float:
+                gl.glBufferData(target, (FloatBuffer) vb.getData(), usage);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unknown buffer format.");
+        }
 
         vb.clearUpdateNeeded();
     }

+ 4 - 0
jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java

@@ -70,6 +70,7 @@ public final class GLTracer implements InvocationHandler {
 //        noEnumArgs("glTexParameteri", 2);
         noEnumArgs("glTexImage2D", 1, 3, 4, 5);
         noEnumArgs("glTexImage3D", 1, 3, 4, 5, 6);
+        noEnumArgs("glTexSubImage2D", 1, 2, 3, 4, 5);
         noEnumArgs("glTexSubImage3D", 1, 2, 3, 4, 5, 6, 7);
         noEnumArgs("glCompressedTexImage2D", 1, 3, 4, 5);
         noEnumArgs("glCompressedTexSubImage3D", 1, 2, 3, 4, 5, 6, 7);
@@ -83,6 +84,8 @@ public final class GLTracer implements InvocationHandler {
         noEnumArgs("glDrawRangeElements", 1, 2, 3, 5);
         noEnumArgs("glDrawArrays", 1, 2);
         noEnumArgs("glDeleteBuffers", 0);
+        noEnumArgs("glBindVertexArray", 0);
+        noEnumArgs("glGenVertexArrays", 0);
         
         noEnumArgs("glBindFramebufferEXT", 1);
         noEnumArgs("glBindRenderbufferEXT", 1);
@@ -110,6 +113,7 @@ public final class GLTracer implements InvocationHandler {
         noEnumArgs("glUniform1f", 0);
         noEnumArgs("glUniform2f", 0);
         noEnumArgs("glUniform3f", 0);
+        noEnumArgs("glUniform4", 0);
         noEnumArgs("glUniform4f", 0);
         noEnumArgs("glGetAttribLocation", 0, -1);
         noEnumArgs("glDetachShader", 0, 1);

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

@@ -64,7 +64,7 @@ import java.util.logging.Logger;
  * TODO more automagic (batch when needed in the updateLogicalState)
  * @author Nehon
  */
-public class BatchNode extends GeometryGroupNode implements Savable {
+public class BatchNode extends GeometryGroupNode {
 
     private static final Logger logger = Logger.getLogger(BatchNode.class.getName());
     /**

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

@@ -58,7 +58,7 @@ import java.util.logging.Logger;
  * @author Gregg Patton
  * @author Joshua Slack
  */
-public class Node extends Spatial implements Savable {
+public class Node extends Spatial {
 
     private static final Logger logger = Logger.getLogger(Node.class.getName());
 

+ 193 - 28
jme3-core/src/main/java/com/jme3/scene/UserData.java

@@ -33,6 +33,12 @@ package com.jme3.scene;
 
 import com.jme3.export.*;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * <code>UserData</code> is used to contain user data objects
@@ -48,28 +54,40 @@ public final class UserData implements Savable {
      * shape generation should ignore them.
      */
     public static final String JME_PHYSICSIGNORE = "JmePhysicsIgnore";
-    
+
     /**
      * For geometries using shared mesh, this will specify the shared
      * mesh reference.
      */
-    public static final String JME_SHAREDMESH = "JmeSharedMesh";
-    
-    protected byte type;
-    protected Object value;
+    public static final String JME_SHAREDMESH    = "JmeSharedMesh";
+
+    private static final int   TYPE_INTEGER      = 0;
+    private static final int   TYPE_FLOAT        = 1;
+    private static final int   TYPE_BOOLEAN      = 2;
+    private static final int   TYPE_STRING       = 3;
+    private static final int   TYPE_LONG         = 4;
+    private static final int   TYPE_SAVABLE      = 5;
+    private static final int   TYPE_LIST         = 6;
+    private static final int   TYPE_MAP          = 7;
+    private static final int   TYPE_ARRAY        = 8;
+
+    protected byte             type;
+    protected Object           value;
 
     public UserData() {
     }
 
     /**
-     * Creates a new <code>UserData</code> with the given 
+     * Creates a new <code>UserData</code> with the given
      * type and value.
      * 
-     * @param type Type of data, should be between 0 and 4.
-     * @param value Value of the data
+     * @param type
+     *            Type of data, should be between 0 and 8.
+     * @param value
+     *            Value of the data
      */
     public UserData(byte type, Object value) {
-        assert type >= 0 && type <= 4;
+        assert type >= 0 && type <= 8;
         this.type = type;
         this.value = value;
     }
@@ -85,15 +103,23 @@ public final class UserData implements Savable {
 
     public static byte getObjectType(Object type) {
         if (type instanceof Integer) {
-            return 0;
+            return TYPE_INTEGER;
         } else if (type instanceof Float) {
-            return 1;
+            return TYPE_FLOAT;
         } else if (type instanceof Boolean) {
-            return 2;
+            return TYPE_BOOLEAN;
         } else if (type instanceof String) {
-            return 3;
+            return TYPE_STRING;
         } else if (type instanceof Long) {
-            return 4;
+            return TYPE_LONG;
+        } else if (type instanceof Savable) {
+            return TYPE_SAVABLE;
+        } else if (type instanceof List) {
+            return TYPE_LIST;
+        } else if (type instanceof Map) {
+            return TYPE_MAP;
+        } else if (type instanceof Object[]) {
+            return TYPE_ARRAY;
         } else {
             throw new IllegalArgumentException("Unsupported type: " + type.getClass().getName());
         }
@@ -101,56 +127,195 @@ public final class UserData implements Savable {
 
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule oc = ex.getCapsule(this);
-        oc.write(type, "type", (byte)0);
+        oc.write(type, "type", (byte) 0);
 
         switch (type) {
-            case 0:
+            case TYPE_INTEGER:
                 int i = (Integer) value;
                 oc.write(i, "intVal", 0);
                 break;
-            case 1:
+            case TYPE_FLOAT:
                 float f = (Float) value;
                 oc.write(f, "floatVal", 0f);
                 break;
-            case 2:
+            case TYPE_BOOLEAN:
                 boolean b = (Boolean) value;
                 oc.write(b, "boolVal", false);
                 break;
-            case 3:
+            case TYPE_STRING:
                 String s = (String) value;
                 oc.write(s, "strVal", null);
                 break;
-            case 4:
+            case TYPE_LONG:
                 Long l = (Long) value;
                 oc.write(l, "longVal", 0l);
                 break;
+            case TYPE_SAVABLE:
+                Savable sav = (Savable) value;
+                oc.write(sav, "savableVal", null);
+                break;
+            case TYPE_LIST:
+                this.writeList(oc, (List<?>) value, "0");
+                break;
+            case TYPE_MAP:
+                Map<?, ?> map = (Map<?, ?>) value;
+                this.writeList(oc, map.keySet(), "0");
+                this.writeList(oc, map.values(), "1");
+                break;
+            case TYPE_ARRAY:
+                this.writeList(oc, Arrays.asList((Object[]) value), "0");
+                break;
             default:
-                throw new UnsupportedOperationException();
+                throw new UnsupportedOperationException("Unsupported value type: " + value.getClass());
         }
     }
 
     public void read(JmeImporter im) throws IOException {
         InputCapsule ic = im.getCapsule(this);
         type = ic.readByte("type", (byte) 0);
-
         switch (type) {
-            case 0:
+            case TYPE_INTEGER:
                 value = ic.readInt("intVal", 0);
                 break;
-            case 1:
+            case TYPE_FLOAT:
                 value = ic.readFloat("floatVal", 0f);
                 break;
-            case 2:
+            case TYPE_BOOLEAN:
                 value = ic.readBoolean("boolVal", false);
                 break;
-            case 3:
+            case TYPE_STRING:
                 value = ic.readString("strVal", null);
                 break;
-            case 4:
+            case TYPE_LONG:
                 value = ic.readLong("longVal", 0l);
                 break;
+            case TYPE_SAVABLE:
+                value = ic.readSavable("savableVal", null);
+                break;
+            case TYPE_LIST:
+                value = this.readList(ic, "0");
+                break;
+            case TYPE_MAP:
+                Map<Object, Object> map = new HashMap<Object, Object>();
+                List<?> keys = this.readList(ic, "0");
+                List<?> values = this.readList(ic, "1");
+                for (int i = 0; i < keys.size(); ++i) {
+                    map.put(keys.get(i), values.get(i));
+                }
+                value = map;
+                break;
+            case TYPE_ARRAY:
+                value = this.readList(ic, "0").toArray();
+                break;
             default:
-                throw new UnsupportedOperationException();
+                throw new UnsupportedOperationException("Unknown type of stored data: " + type);
+        }
+    }
+
+    /**
+     * The method stores a list in the capsule.
+     * @param oc
+     *            output capsule
+     * @param list
+     *            the list to be stored
+     * @throws IOException
+     */
+    private void writeList(OutputCapsule oc, Collection<?> list, String listName) throws IOException {
+        if (list != null) {
+            oc.write(list.size(), listName + "size", 0);
+            int counter = 0;
+            for (Object o : list) {
+                // t is for 'type'; v is for 'value'
+                if (o instanceof Integer) {
+                    oc.write(TYPE_INTEGER, listName + "t" + counter, 0);
+                    oc.write((Integer) o, listName + "v" + counter, 0);
+                } else if (o instanceof Float) {
+                    oc.write(TYPE_FLOAT, listName + "t" + counter, 0);
+                    oc.write((Float) o, listName + "v" + counter, 0f);
+                } else if (o instanceof Boolean) {
+                    oc.write(TYPE_BOOLEAN, listName + "t" + counter, 0);
+                    oc.write((Boolean) o, listName + "v" + counter, false);
+                } else if (o instanceof String || o == null) {// treat null's like Strings just to store them and keep the List like the user intended
+                    oc.write(TYPE_STRING, listName + "t" + counter, 0);
+                    oc.write((String) o, listName + "v" + counter, null);
+                } else if (o instanceof Long) {
+                    oc.write(TYPE_LONG, listName + "t" + counter, 0);
+                    oc.write((Long) o, listName + "v" + counter, 0L);
+                } else if (o instanceof Savable) {
+                    oc.write(TYPE_SAVABLE, listName + "t" + counter, 0);
+                    oc.write((Savable) o, listName + "v" + counter, null);
+                } else if(o instanceof Object[]) {
+                    oc.write(TYPE_ARRAY, listName + "t" + counter, 0);
+                    this.writeList(oc, Arrays.asList((Object[]) o), listName + "v" + counter);
+                } else if(o instanceof List) {
+                    oc.write(TYPE_LIST, listName + "t" + counter, 0);
+                    this.writeList(oc, (List<?>) o, listName + "v" + counter);
+                } else if(o instanceof Map) {
+                    oc.write(TYPE_MAP, listName + "t" + counter, 0);
+                    Map<?, ?> map = (Map<?, ?>) o;
+                    this.writeList(oc, map.keySet(), listName + "v(keys)" + counter);
+                    this.writeList(oc, map.values(), listName + "v(vals)" + counter);
+                } else {
+                    throw new UnsupportedOperationException("Unsupported type stored in the list: " + o.getClass());
+                }
+                
+                ++counter;
+            }
+        } else {
+            oc.write(0, "size", 0);
+        }
+    }
+
+    /**
+     * The method loads a list from the given input capsule.
+     * @param ic
+     *            the input capsule
+     * @return loaded list (an empty list in case its size is 0)
+     * @throws IOException
+     */
+    private List<?> readList(InputCapsule ic, String listName) throws IOException {
+        int size = ic.readInt(listName + "size", 0);
+        List<Object> list = new ArrayList<Object>(size);
+        for (int i = 0; i < size; ++i) {
+            int type = ic.readInt(listName + "t" + i, 0);
+            switch (type) {
+                case TYPE_INTEGER:
+                    list.add(ic.readInt(listName + "v" + i, 0));
+                    break;
+                case TYPE_FLOAT:
+                    list.add(ic.readFloat(listName + "v" + i, 0));
+                    break;
+                case TYPE_BOOLEAN:
+                    list.add(ic.readBoolean(listName + "v" + i, false));
+                    break;
+                case TYPE_STRING:
+                    list.add(ic.readString(listName + "v" + i, null));
+                    break;
+                case TYPE_LONG:
+                    list.add(ic.readLong(listName + "v" + i, 0L));
+                    break;
+                case TYPE_SAVABLE:
+                    list.add(ic.readSavable(listName + "v" + i, null));
+                    break;
+                case TYPE_ARRAY:
+                    list.add(this.readList(ic, listName + "v" + i).toArray());
+                    break;
+                case TYPE_LIST:
+                    list.add(this.readList(ic, listName + "v" + i));
+                    break;
+                case TYPE_MAP:
+                    Map<Object, Object> map = new HashMap<Object, Object>();
+                    List<?> keys = this.readList(ic, listName + "v(keys)" + i);
+                    List<?> values = this.readList(ic, listName + "v(vals)" + i);
+                    for (int j = 0; j < keys.size(); ++j) {
+                        map.put(keys.get(j), values.get(j));
+                    }
+                    list.add(map);
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unknown type of stored data in a list: " + type);
+            }
         }
+        return list;
     }
 }

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

@@ -57,7 +57,7 @@ public class InstancedNode extends GeometryGroupNode {
         setGeometryStartIndex(geom, startIndex);
     }
     
-    private static class InstanceTypeKey implements Cloneable {
+    private static final class InstanceTypeKey implements Cloneable {
 
         Mesh mesh;
         Material material;

+ 1 - 1
jme3-core/src/main/java/com/jme3/scene/shape/Dome.java

@@ -266,7 +266,7 @@ public class Dome extends Mesh {
         vars.release();
 
         // pole
-        vb.put(center.x).put(center.y + radius).put(center.z);
+        vb.put(this.center.x).put(this.center.y + radius).put(this.center.z);
         nb.put(0).put(insideView ? -1 : 1).put(0);
         tb.put(0.5f).put(1.0f);
 

+ 1 - 1
jme3-core/src/main/java/com/jme3/shader/DefineList.java

@@ -40,7 +40,7 @@ import java.io.IOException;
 import java.util.Map;
 import java.util.TreeMap;
 
-public class DefineList implements Savable, Cloneable {
+public final class DefineList implements Savable, Cloneable {
 
     private static final String ONE = "1";
     

+ 3 - 1
jme3-core/src/main/java/com/jme3/system/NullContext.java

@@ -46,6 +46,8 @@ public class NullContext implements JmeContext, Runnable {
 
     protected static final Logger logger = Logger.getLogger(NullContext.class.getName());
 
+    protected static final String THREAD_NAME = "jME3 Headless Main";
+    
     protected AtomicBoolean created = new AtomicBoolean(false);
     protected AtomicBoolean needClose = new AtomicBoolean(false);
     protected final Object createdLock = new Object();
@@ -150,7 +152,7 @@ public class NullContext implements JmeContext, Runnable {
             return;
         }
 
-        new Thread(this, "Headless Application Thread").start();
+        new Thread(this, THREAD_NAME).start();
         if (waitFor)
             waitFor(true);
     }

+ 4 - 3
jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java

@@ -72,9 +72,10 @@ import java.util.ArrayList;
  * @author Kirill Vainer
  */
 public class FrameBuffer extends NativeObject {
-    public static int SLOT_UNDEF = -1;
-    public static int SLOT_DEPTH = -100;
-    public static int SLOT_DEPTH_STENCIL = -101;
+    
+    public static final int SLOT_UNDEF = -1;
+    public static final int SLOT_DEPTH = -100;
+    public static final int SLOT_DEPTH_STENCIL = -101;
 
     private int width = 0;
     private int height = 0;

+ 3 - 6
jme3-core/src/main/java/com/jme3/texture/Texture.java

@@ -488,7 +488,8 @@ public abstract class Texture implements CloneableSmartAsset, Savable, Cloneable
 
     /**
      * @return the anisotropic filtering level for this texture. Default value
-     * is 1 (no anisotrophy), 2 means x2, 4 is x4, etc.
+     * is 0 (use value from config), 
+     * 1 means 1x (no anisotrophy), 2 means x2, 4 is x4, etc.
      */
     public int getAnisotropicFilter() {
         return anisotropicFilter;
@@ -499,11 +500,7 @@ public abstract class Texture implements CloneableSmartAsset, Savable, Cloneable
      *            the anisotropic filtering level for this texture.
      */
     public void setAnisotropicFilter(int level) {
-        if (level < 1) {
-            anisotropicFilter = 1;
-        } else {
-            anisotropicFilter = level;
-        }
+        anisotropicFilter = Math.max(0, level);
     }
 
     @Override

+ 2 - 1
jme3-core/src/main/java/com/jme3/util/IntMap.java

@@ -34,6 +34,7 @@ package com.jme3.util;
 import com.jme3.util.IntMap.Entry;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.NoSuchElementException;
 
 /**
  * Similar to a {@link Map} except that ints are used as keys.
@@ -234,7 +235,7 @@ public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
 
         public Entry next() {
             if (el >= size)
-                throw new IllegalStateException("No more elements!");
+                throw new NoSuchElementException("No more elements!");
 
             if (cur != null){
                 Entry e = cur;

+ 7 - 11
jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java

@@ -274,11 +274,9 @@ public class TangentBinormalGenerator {
                 triData.setIndex(index);
                 triData.triangleOffset = i * 3 ;
             }
-            if (triData != null) {
-                vertices.get(index[0]).triangles.add(triData);
-                vertices.get(index[1]).triangles.add(triData);
-                vertices.get(index[2]).triangles.add(triData);
-            }
+            vertices.get(index[0]).triangles.add(triData);
+            vertices.get(index[1]).triangles.add(triData);
+            vertices.get(index[2]).triangles.add(triData);
         }
         
         return vertices;
@@ -483,7 +481,7 @@ public class TangentBinormalGenerator {
             boolean isDegenerate = isDegenerateTriangle(v[0], v[1], v[2]);
             TriangleData triData = processTriangle(index, v, t);
             
-            if (triData != null && !isDegenerate) {
+            if (!isDegenerate) {
                 vertices.get(index[0]).triangles.add(triData);
                 vertices.get(index[1]).triangles.add(triData);
                 vertices.get(index[2]).triangles.add(triData);
@@ -529,11 +527,9 @@ public class TangentBinormalGenerator {
             populateFromBuffer(t[2], textureBuffer, index[2]);
             
             TriangleData triData = processTriangle(index, v, t);
-            if (triData != null) {
-                vertices.get(index[0]).triangles.add(triData);
-                vertices.get(index[1]).triangles.add(triData);
-                vertices.get(index[2]).triangles.add(triData);
-            }
+            vertices.get(index[0]).triangles.add(triData);
+            vertices.get(index[1]).triangles.add(triData);
+            vertices.get(index[2]).triangles.add(triData);
             
             Vector3f vTemp = v[1];
             v[1] = v[2];

+ 1 - 1
jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java

@@ -71,7 +71,7 @@ public class BlockLanguageParser {
     private void load(InputStream in) throws IOException{
         reset();
         
-        reader = new InputStreamReader(in);
+        reader = new InputStreamReader(in, "UTF-8");
         
         StringBuilder buffer = new StringBuilder();
         boolean insideComment = false;

+ 2 - 1
jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert

@@ -126,7 +126,8 @@ void main(){
       DiffuseSum  =  m_Diffuse  * vec4(lightColor.rgb, 1.0);
       SpecularSum = (m_Specular * lightColor).rgb;
     #else
-      AmbientSum  = g_AmbientLightColor.rgb; // Default: ambient color is dark gray
+      // Defaults: Ambient and diffuse are white, specular is black.
+      AmbientSum  = g_AmbientLightColor.rgb;
       DiffuseSum  =  vec4(lightColor.rgb, 1.0);
       SpecularSum = vec3(0.0);
     #endif

+ 1 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert

@@ -117,6 +117,7 @@ void main(){
         SpecularSum = m_Specular.rgb;
         DiffuseSum = m_Diffuse;                   
     #else
+        // Defaults: Ambient and diffuse are white, specular is black.
         AmbientSum  = g_AmbientLightColor.rgb; 
         SpecularSum = vec3(0.0);
         DiffuseSum = vec4(1.0);

+ 9 - 1
jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib

@@ -30,6 +30,14 @@ float computeSpotFalloff(in vec4 lightDirection, in vec3 lightVector){
     float innerAngleCos = floor(lightDirection.w) * 0.001;
     float outerAngleCos = fract(lightDirection.w);
     float innerMinusOuter = innerAngleCos - outerAngleCos;
-    return  clamp((curAngleCos - outerAngleCos) / innerMinusOuter, step(lightDirection.w, 0.001), 1.0);
+    float falloff = clamp((curAngleCos - outerAngleCos) / innerMinusOuter, step(lightDirection.w, 0.001), 1.0);
+
+#ifdef SRGB
+    // Use quadratic falloff (notice the ^4)
+    return pow(clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0), 4.0);
+#else
+    // Use linear falloff
+    return falloff;
+#endif
 }
 

+ 1 - 1
jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java

@@ -196,7 +196,7 @@ public class WAVLoader implements AssetLoader {
                     break;
                 case i_data:
                     // Compute duration based on data chunk size
-                    duration = len / bytesPerSec;
+                    duration = (float)(len / bytesPerSec);
 
                     if (readStream) {
                         readDataChunkForStream(inOffset, len);

+ 1 - 3
jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java

@@ -270,9 +270,7 @@ public final class BinaryImporter implements JmeImporter {
         try {
             return load(fis, listener);
         } finally {
-            if (fis != null) {
-                fis.close();
-            }
+            fis.close();
         }
     }
 

+ 1 - 0
jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java

@@ -186,6 +186,7 @@ public class J3MLoader implements AssetLoader {
                     tex.setWrap(WrapMode.Repeat);
                 }
                 tex.setKey(texKey);
+                tex.setName(texKey.getName());
             }         
             return tex;
         }else{

+ 1 - 1
jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java

@@ -71,7 +71,7 @@ public class VideoRecorderAppState extends AbstractAppState {
 
         public Thread newThread(Runnable r) {
             Thread th = new Thread(r);
-            th.setName("jME Video Processing Thread");
+            th.setName("jME3 Video Processor");
             th.setDaemon(true);
             return th;
         }

+ 15 - 7
jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java

@@ -21,8 +21,8 @@ import java.util.List;
 public class TestSweepTest extends SimpleApplication {
 
     private BulletAppState bulletAppState = new BulletAppState();
-    private CapsuleCollisionShape obstacleCollisionShape = new CapsuleCollisionShape(0.3f, 0.5f);
-    private CapsuleCollisionShape capsuleCollisionShape = new CapsuleCollisionShape(1f, 1f);
+    private CapsuleCollisionShape obstacleCollisionShape;
+    private CapsuleCollisionShape capsuleCollisionShape;
     private Node capsule;
     private Node obstacle;
     private float dist = .5f;
@@ -33,6 +33,9 @@ public class TestSweepTest extends SimpleApplication {
 
     @Override
     public void simpleInitApp() {
+        obstacleCollisionShape = new CapsuleCollisionShape(0.3f, 0.5f);
+        capsuleCollisionShape = new CapsuleCollisionShape(1f, 1f);
+        
         stateManager.attach(bulletAppState);
 
         capsule = new Node("capsule");
@@ -56,14 +59,19 @@ public class TestSweepTest extends SimpleApplication {
     public void simpleUpdate(float tpf) {
 
         float move = tpf * 1;
+        boolean colliding = false;
 
         List<PhysicsSweepTestResult> sweepTest = bulletAppState.getPhysicsSpace().sweepTest(capsuleCollisionShape, new Transform(capsule.getWorldTranslation()), new Transform(capsule.getWorldTranslation().add(dist, 0, 0)));
 
-        if (sweepTest.size() > 0) {
-            PhysicsSweepTestResult get = sweepTest.get(0);
-            PhysicsCollisionObject collisionObject = get.getCollisionObject();
-            fpsText.setText("Almost colliding with " + collisionObject.getUserObject().toString());
-        } else {
+        for (PhysicsSweepTestResult result : sweepTest) {
+            if (result.getCollisionObject().getCollisionShape() != capsuleCollisionShape) {
+                PhysicsCollisionObject collisionObject = result.getCollisionObject();
+                fpsText.setText("Almost colliding with " + collisionObject.getUserObject().toString());
+                colliding = true;
+            }
+        }
+        
+        if (!colliding) {
             // if the sweep is clear then move the spatial
             capsule.move(move, 0, 0);
         }

+ 0 - 27
jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java

@@ -759,14 +759,6 @@ public class IGLESShaderRenderer implements Renderer {
         Image[] textures = context.boundTextures;
 
         int type = convertTextureType(tex.getType());
-        if (!context.textureIndexList.moveToNew(unit)) {
-//             if (context.boundTextureUnit != unit){
-//                glActiveTexture(GL_TEXTURE0 + unit);
-//                context.boundTextureUnit = unit;
-//             }
-//             glEnable(type);
-        }
-
         if (textures[unit] != image) {
             if (context.boundTextureUnit != unit) {
                 JmeIosGLES.glActiveTexture(JmeIosGLES.GL_TEXTURE0 + unit);
@@ -1768,7 +1760,6 @@ public class IGLESShaderRenderer implements Renderer {
             JmeIosGLES.checkGLError();
         }
         clearVertexAttribs();
-        clearTextureUnits();
     }
 
     private void renderMeshDefault(Mesh mesh, int lod, int count) {
@@ -1807,7 +1798,6 @@ public class IGLESShaderRenderer implements Renderer {
             JmeIosGLES.checkGLError();
         }
         clearVertexAttribs();
-        clearTextureUnits();
     }
 
 
@@ -2085,23 +2075,6 @@ public class IGLESShaderRenderer implements Renderer {
         context.attribIndexList.copyNewToOld();
     }
 
-
-    public void clearTextureUnits() {
-        IDList textureList = context.textureIndexList;
-        Image[] textures = context.boundTextures;
-        for (int i = 0; i < textureList.oldLen; i++) {
-            int idx = textureList.oldList[i];
-//            if (context.boundTextureUnit != idx){
-//                glActiveTexture(GL_TEXTURE0 + idx);
-//                context.boundTextureUnit = idx;
-//            }
-//            glDisable(convertTextureType(textures[idx].getType()));
-            textures[idx] = null;
-        }
-        context.textureIndexList.copyNewToOld();
-    }
-
-
     public void updateFrameBuffer(FrameBuffer fb) {
         int id = fb.getId();
         if (id == -1) {

+ 3 - 3
jme3-jogl/build.gradle

@@ -5,7 +5,7 @@ if (!hasProperty('mainClass')) {
 dependencies {
     compile project(':jme3-core')
     compile project(':jme3-desktop')
-    compile 'org.jogamp.gluegen:gluegen-rt-main:2.2.0'
-    compile 'org.jogamp.jogl:jogl-all-main:2.2.0'
-    compile 'org.jogamp.joal:joal-main:2.2.0'
+    compile 'org.jogamp.gluegen:gluegen-rt-main:2.3.1'
+    compile 'org.jogamp.jogl:jogl-all-main:2.3.1'
+    compile 'org.jogamp.joal:joal-main:2.3.1'
 }

+ 5 - 5
jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java

@@ -45,11 +45,11 @@ import com.jogamp.newt.opengl.GLWindow;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.logging.Logger;
-import javax.media.nativewindow.util.Dimension;
-import javax.media.nativewindow.util.DimensionImmutable;
-import javax.media.nativewindow.util.PixelFormat;
-import javax.media.nativewindow.util.PixelRectangle;
-import javax.media.nativewindow.util.Point;
+import com.jogamp.nativewindow.util.Dimension;
+import com.jogamp.nativewindow.util.DimensionImmutable;
+import com.jogamp.nativewindow.util.PixelFormat;
+import com.jogamp.nativewindow.util.PixelRectangle;
+import com.jogamp.nativewindow.util.Point;
 
 public class NewtMouseInput  implements MouseInput, MouseListener {
     

+ 11 - 11
jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java

@@ -70,15 +70,15 @@ import java.util.EnumSet;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import javax.media.nativewindow.NativeWindowFactory;
-import javax.media.opengl.GL;
-import javax.media.opengl.GL2;
-import javax.media.opengl.GL2ES1;
-import javax.media.opengl.GL2ES2;
-import javax.media.opengl.GL2ES3;
-import javax.media.opengl.GL2GL3;
-import javax.media.opengl.GL3;
-import javax.media.opengl.GLContext;
+import com.jogamp.nativewindow.NativeWindowFactory;
+import com.jogamp.opengl.GL;
+import com.jogamp.opengl.GL2;
+import com.jogamp.opengl.GL2ES1;
+import com.jogamp.opengl.GL2ES2;
+import com.jogamp.opengl.GL2ES3;
+import com.jogamp.opengl.GL2GL3;
+import com.jogamp.opengl.GL3;
+import com.jogamp.opengl.GLContext;
 import jme3tools.converters.MipMapGenerator;
 import jme3tools.shader.ShaderDebug;
 
@@ -1800,7 +1800,7 @@ public class JoglRenderer implements Renderer {
                 if (samples > 1) {
                     return GL3.GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
                 } else {
-                    return GL.GL_TEXTURE_2D_ARRAY;
+                    return GL2ES3.GL_TEXTURE_2D_ARRAY;
                 }
             case ThreeDimensional:
                 return GL2ES2.GL_TEXTURE_3D;
@@ -2014,7 +2014,7 @@ public class JoglRenderer implements Renderer {
             for (int i = 0; i < 6; i++) {
                 TextureUtil.uploadTexture(img, GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, i, 0, linearizeSrgbImages);
             }
-        } else if (target == GL.GL_TEXTURE_2D_ARRAY) {
+        } else if (target == GL2ES3.GL_TEXTURE_2D_ARRAY) {
             if (!caps.contains(Caps.TextureArray)) {
                 throw new RendererException("Texture arrays not supported by graphics hardware");
             }

+ 11 - 8
jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java

@@ -36,14 +36,17 @@ import com.jme3.renderer.RendererException;
 import com.jme3.texture.Image;
 import com.jme3.texture.Image.Format;
 import com.jme3.texture.image.ColorSpace;
+
 import java.nio.ByteBuffer;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import javax.media.opengl.GL;
-import javax.media.opengl.GL2;
-import javax.media.opengl.GL2ES2;
-import javax.media.opengl.GL2GL3;
-import javax.media.opengl.GLContext;
+
+import com.jogamp.opengl.GL;
+import com.jogamp.opengl.GL2;
+import com.jogamp.opengl.GL2ES2;
+import com.jogamp.opengl.GL2ES3;
+import com.jogamp.opengl.GL2GL3;
+import com.jogamp.opengl.GLContext;
 
 public class TextureUtil {
     
@@ -124,9 +127,9 @@ public class TextureUtil {
         setFormat(Format.RGB32F,     GL.GL_RGB32F, GL.GL_RGB, GL.GL_FLOAT, false);
         
         // Special RGB formats
-        setFormat(Format.RGB111110F, GL.GL_R11F_G11F_B10F,    GL.GL_RGB, GL.GL_UNSIGNED_INT_10F_11F_11F_REV, false);
+        setFormat(Format.RGB111110F, GL2ES3.GL_R11F_G11F_B10F,    GL.GL_RGB, GL.GL_UNSIGNED_INT_10F_11F_11F_REV, false);
         setFormat(Format.RGB9E5,     GL2GL3.GL_RGB9_E5, GL.GL_RGB, GL2GL3.GL_UNSIGNED_INT_5_9_9_9_REV, false);
-        setFormat(Format.RGB16F_to_RGB111110F, GL.GL_R11F_G11F_B10F,    GL.GL_RGB, GL.GL_HALF_FLOAT, false);
+        setFormat(Format.RGB16F_to_RGB111110F, GL2ES3.GL_R11F_G11F_B10F,    GL.GL_RGB, GL.GL_HALF_FLOAT, false);
         setFormat(Format.RGB16F_to_RGB9E5, GL2.GL_RGB9_E5, GL.GL_RGB, GL.GL_HALF_FLOAT, false);
         
         // RGBA formats
@@ -346,7 +349,7 @@ public class TextureUtil {
                                       glFmt.format,
                                       glFmt.dataType,
                                       data);
-                }else if (target == GL.GL_TEXTURE_2D_ARRAY){
+                }else if (target == GL2ES3.GL_TEXTURE_2D_ARRAY){
                     // prepare data for 2D array
                     // or upload slice
                     if (index == -1){

+ 14 - 14
jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java

@@ -45,20 +45,20 @@ import java.awt.GraphicsDevice;
 import java.awt.GraphicsEnvironment;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
-import javax.media.opengl.DebugGL2;
-import javax.media.opengl.DebugGL3;
-import javax.media.opengl.DebugGL3bc;
-import javax.media.opengl.DebugGL4;
-import javax.media.opengl.DebugGL4bc;
-import javax.media.opengl.DebugGLES1;
-import javax.media.opengl.DebugGLES2;
-import javax.media.opengl.GL;
-import javax.media.opengl.GLAutoDrawable;
-import javax.media.opengl.GLCapabilities;
-import javax.media.opengl.GLEventListener;
-import javax.media.opengl.GLProfile;
-import javax.media.opengl.GLRunnable;
-import javax.media.opengl.awt.GLCanvas;
+import com.jogamp.opengl.DebugGL2;
+import com.jogamp.opengl.DebugGL3;
+import com.jogamp.opengl.DebugGL3bc;
+import com.jogamp.opengl.DebugGL4;
+import com.jogamp.opengl.DebugGL4bc;
+import com.jogamp.opengl.DebugGLES1;
+import com.jogamp.opengl.DebugGLES2;
+import com.jogamp.opengl.GL;
+import com.jogamp.opengl.GLAutoDrawable;
+import com.jogamp.opengl.GLCapabilities;
+import com.jogamp.opengl.GLEventListener;
+import com.jogamp.opengl.GLProfile;
+import com.jogamp.opengl.GLRunnable;
+import com.jogamp.opengl.awt.GLCanvas;
 
 public abstract class JoglAbstractDisplay extends JoglContext implements GLEventListener {
 

+ 1 - 1
jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java

@@ -35,7 +35,7 @@ package com.jme3.system.jogl;
 import com.jme3.system.JmeCanvasContext;
 import java.awt.Canvas;
 import java.util.logging.Logger;
-import javax.media.opengl.GLAutoDrawable;
+import com.jogamp.opengl.GLAutoDrawable;
 
 public class JoglCanvas extends JoglAbstractDisplay implements JmeCanvasContext {
 

+ 5 - 3
jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java

@@ -47,14 +47,16 @@ import java.nio.IntBuffer;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import javax.media.opengl.GL;
-import javax.media.opengl.GL2GL3;
-import javax.media.opengl.GLContext;
+import com.jogamp.opengl.GL;
+import com.jogamp.opengl.GL2GL3;
+import com.jogamp.opengl.GLContext;
 
 public abstract class JoglContext implements JmeContext {
 
     private static final Logger logger = Logger.getLogger(JoglContext.class.getName());
     
+    protected static final String THREAD_NAME = "jME3 Main";
+    
     protected AtomicBoolean created = new AtomicBoolean(false);
     protected AtomicBoolean renderable = new AtomicBoolean(false);
     protected final Object createdLock = new Object();

+ 1 - 1
jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java

@@ -45,7 +45,7 @@ import java.lang.reflect.InvocationTargetException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import javax.media.opengl.GLAutoDrawable;
+import com.jogamp.opengl.GLAutoDrawable;
 import javax.swing.JFrame;
 import javax.swing.SwingUtilities;
 

+ 13 - 13
jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java

@@ -44,19 +44,19 @@ import com.jogamp.opengl.util.AnimatorBase;
 import com.jogamp.opengl.util.FPSAnimator;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
-import javax.media.opengl.DebugGL2;
-import javax.media.opengl.DebugGL3;
-import javax.media.opengl.DebugGL3bc;
-import javax.media.opengl.DebugGL4;
-import javax.media.opengl.DebugGL4bc;
-import javax.media.opengl.DebugGLES1;
-import javax.media.opengl.DebugGLES2;
-import javax.media.opengl.GL;
-import javax.media.opengl.GLAutoDrawable;
-import javax.media.opengl.GLCapabilities;
-import javax.media.opengl.GLEventListener;
-import javax.media.opengl.GLProfile;
-import javax.media.opengl.GLRunnable;
+import com.jogamp.opengl.DebugGL2;
+import com.jogamp.opengl.DebugGL3;
+import com.jogamp.opengl.DebugGL3bc;
+import com.jogamp.opengl.DebugGL4;
+import com.jogamp.opengl.DebugGL4bc;
+import com.jogamp.opengl.DebugGLES1;
+import com.jogamp.opengl.DebugGLES2;
+import com.jogamp.opengl.GL;
+import com.jogamp.opengl.GLAutoDrawable;
+import com.jogamp.opengl.GLCapabilities;
+import com.jogamp.opengl.GLEventListener;
+import com.jogamp.opengl.GLProfile;
+import com.jogamp.opengl.GLRunnable;
 
 public abstract class JoglNewtAbstractDisplay extends JoglContext implements GLEventListener {
 

+ 1 - 1
jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java

@@ -35,7 +35,7 @@ package com.jme3.system.jogl;
 import com.jme3.system.JmeCanvasContext;
 import com.jogamp.newt.awt.NewtCanvasAWT;
 import java.util.logging.Logger;
-import javax.media.opengl.GLAutoDrawable;
+import com.jogamp.opengl.GLAutoDrawable;
 
 public class JoglNewtCanvas extends JoglNewtAbstractDisplay implements JmeCanvasContext {
     

+ 2 - 2
jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java

@@ -42,8 +42,8 @@ import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import javax.media.nativewindow.util.Dimension;
-import javax.media.opengl.GLAutoDrawable;
+import com.jogamp.nativewindow.util.Dimension;
+import com.jogamp.opengl.GLAutoDrawable;
 
 public class JoglNewtDisplay extends JoglNewtAbstractDisplay {
     

+ 7 - 7
jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java

@@ -41,12 +41,12 @@ import com.jogamp.newt.NewtVersion;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
-import javax.media.opengl.GL;
-import javax.media.opengl.GLCapabilities;
-import javax.media.opengl.GLContext;
-import javax.media.opengl.GLDrawableFactory;
-import javax.media.opengl.GLOffscreenAutoDrawable;
-import javax.media.opengl.GLProfile;
+import com.jogamp.opengl.GL;
+import com.jogamp.opengl.GLCapabilities;
+import com.jogamp.opengl.GLContext;
+import com.jogamp.opengl.GLDrawableFactory;
+import com.jogamp.opengl.GLOffscreenAutoDrawable;
+import com.jogamp.opengl.GLProfile;
 
 
 public class JoglOffscreenBuffer extends JoglContext implements Runnable {
@@ -146,7 +146,7 @@ public class JoglOffscreenBuffer extends JoglContext implements Runnable {
             return;
         }
 
-        new Thread(this, "JOGL Renderer Thread").start();
+        new Thread(this, THREAD_NAME).start();
         if (waitFor) {
             waitFor(true);
         }

+ 3 - 1
jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java

@@ -181,7 +181,9 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna
         
         // check input after we synchronize with framerate.
         // this reduces input lag.
-        Display.processMessages();
+        if (renderable.get()){
+            Display.processMessages();
+        }
         
         // Subclasses just call GLObjectManager clean up objects here
         // it is safe .. for now.

+ 2 - 2
jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java

@@ -96,7 +96,7 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
                 canvas.setFocusable(true);
                 canvas.setIgnoreRepaint(true);
 
-                renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread");
+                renderThread = new Thread(LwjglCanvas.this, THREAD_NAME);
                 renderThread.start();
             }else if (needClose.get()){
                 return;
@@ -162,7 +162,7 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
         if (renderThread == null){
             logger.log(Level.FINE, "MAIN: Creating OGL thread.");
 
-            renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread");
+            renderThread = new Thread(LwjglCanvas.this, THREAD_NAME);
             renderThread.start();
         }
         // do not do anything.

+ 2 - 0
jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java

@@ -67,6 +67,8 @@ public abstract class LwjglContext implements JmeContext {
 
     private static final Logger logger = Logger.getLogger(LwjglContext.class.getName());
 
+    protected static final String THREAD_NAME = "jME3 Main";
+    
     protected AtomicBoolean created = new AtomicBoolean(false);
     protected AtomicBoolean renderable = new AtomicBoolean(false);
     protected final Object createdLock = new Object();

+ 1 - 1
jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java

@@ -166,7 +166,7 @@ public class LwjglDisplay extends LwjglAbstractDisplay {
             return;
         }
 
-        new Thread(this, "LWJGL Renderer Thread").start();
+        new Thread(this, THREAD_NAME).start();
         if (waitFor)
             waitFor(true);
     }

+ 1 - 1
jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java

@@ -170,7 +170,7 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable {
             return;
         }
 
-        new Thread(this, "LWJGL Renderer Thread").start();
+        new Thread(this, THREAD_NAME).start();
         if (waitFor)
             waitFor(true);
     }

+ 3 - 2
jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java

@@ -55,6 +55,7 @@ import com.jme3.texture.Image.Format;
 import com.jme3.texture.Texture.MagFilter;
 import com.jme3.texture.Texture.MinFilter;
 import com.jme3.texture.Texture2D;
+import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.BufferUtils;
 
 import de.lessvoid.nifty.render.batch.spi.BatchRenderBackend;
@@ -302,7 +303,7 @@ public class JmeBatchRenderBackend implements BatchRenderBackend {
     initialData.rewind();
     modifyTexture(
         getTextureAtlas(atlasTextureId),
-        new com.jme3.texture.Image(Format.RGBA8, image.getWidth(), image.getHeight(), initialData),
+        new com.jme3.texture.Image(Format.RGBA8, image.getWidth(), image.getHeight(), initialData, ColorSpace.sRGB),
         x,
         y);
   }
@@ -338,7 +339,7 @@ public class JmeBatchRenderBackend implements BatchRenderBackend {
     }
     initialData.rewind();
 
-    Texture2D texture = new Texture2D(new com.jme3.texture.Image(Format.RGBA8, width, height, initialData));
+    Texture2D texture = new Texture2D(new com.jme3.texture.Image(Format.RGBA8, width, height, initialData, ColorSpace.sRGB));
     texture.setMinFilter(MinFilter.NearestNoMipMaps);
     texture.setMagFilter(MagFilter.Nearest);
     return texture;

Some files were not shown because too many files changed in this diff