diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java
index 1d2835bc3..b103e92df 100644
--- a/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java
+++ b/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;
-
-/**
- * AndroidHarness 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->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.
- * Leave 0 (default) to disable multisampling.
- * 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.
- *
- * Android MediaPlayer / SoundPool can be used on all
- * supported Android platform versions (2.2+)
- * OpenAL Soft uses an OpenSL backend and is only supported on Android
- * versions 2.3+.
- *
- * 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;
+
+/**
+ * AndroidHarness 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->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.
+ * Leave 0 (default) to disable multisampling.
+ * 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.
+ *
+ * Android MediaPlayer / SoundPool can be used on all
+ * supported Android platform versions (2.2+)
+ * OpenAL Soft uses an OpenSL backend and is only supported on Android
+ * versions 2.3+.
+ *
+ * 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;
+ }
+}
diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java
index 1b205d6dd..5673c8609 100644
--- a/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java
+++ b/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
diff --git a/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java b/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java
index c915164b3..a388ad612 100644
--- a/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java
+++ b/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;
}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java
index 592baecaf..202907316 100644
--- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java
+++ b/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;
-
-/**
- * AndroidInput 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 InputManager.
- *
- * @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 inputEventQueue = new ConcurrentLinkedQueue();
- 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;
+
+/**
+ * AndroidInput 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 InputManager.
+ *
+ * @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 inputEventQueue = new ConcurrentLinkedQueue();
+ 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;
+ }
+
+}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java
new file mode 100644
index 000000000..2c3a1d74e
--- /dev/null
+++ b/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.
+ * 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
+ *
+ *
+ * @author iwgeric
+ */
+public class AndroidJoyInputHandler implements JoyInput {
+ private static final Logger logger = Logger.getLogger(AndroidJoyInputHandler.class.getName());
+
+ private List joystickList = new ArrayList();
+// private boolean dontSendHistory = false;
+
+
+ // Internal
+ private GLSurfaceView view;
+ private boolean initialized = false;
+ private RawInputListener listener = null;
+ private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue();
+ 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);
+ }
+ }
+ }
+
+ }
+
+
+
+}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java
index 034b068d8..823aa3d9d 100644
--- a/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java
+++ b/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
- *
- *
* @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 sensors = new IntMap();
- private AndroidJoystick[] joysticks;
private int lastRotation = 0;
- private boolean initialized = false;
private boolean loaded = false;
- private final ArrayList eventQueue = new ArrayList();
+ 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 axes = new ArrayList();
@@ -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 list = new ArrayList();
- AndroidJoystick joystick;
AndroidJoystickAxis axis;
- joystick = new AndroidJoystick(inputManager,
- this,
- list.size(),
+ AndroidJoystick joystick = new AndroidJoystick(inputManager,
+ joyHandler,
+ joyId,
"AndroidSensorsJoystick");
- list.add(joystick);
List 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}",
diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java b/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java
index cef3e9f66..462715f37 100644
--- a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java
+++ b/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) {
diff --git a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java
index cd849b1dd..5ef866188 100644
--- a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java
+++ b/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
diff --git a/jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java b/jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java
index 42c621a4f..61d0d6868 100644
--- a/jme3-android/src/main/java/jme3test/android/DemoAndroidHarness.java
+++ b/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);
}
}
diff --git a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java
index c215d0677..c412c07b3 100644
--- a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java
+++ b/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 scenes;
- /** Objects from all scenes. */
- private List objects;
- /** Materials from all objects. */
- private List materials;
- /** Textures from all objects. */
- private List textures;
- /** Animations of all objects. */
- private List animations;
- /** All cameras from the file. */
- private List cameras;
- /** All lights from the file. */
- private List lights;
- /** Loaded sky. */
- private Spatial sky;
- /** Scene filters (ie. FOG). */
- private List 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();
- }
- if ((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) {
- objects = new ArrayList();
- if ((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) {
- materials = new ArrayList();
- if ((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) {
- textures = new ArrayList();
- }
- }
- if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) {
- animations = new ArrayList();
- }
- }
- if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) {
- cameras = new ArrayList();
- }
- if ((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) {
- lights = new ArrayList();
- }
- }
-
- /**
- * 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(5);
- }
- filters.add(filter);
- }
- }
-
- /**
- * @param backgroundColor
- * the background color
- */
- public void setBackgroundColor(ColorRGBA backgroundColor) {
- this.backgroundColor = backgroundColor;
- }
-
- /**
- * @return all loaded scenes
- */
- public List getScenes() {
- return scenes;
- }
-
- /**
- * @return all loaded objects
- */
- public List getObjects() {
- return objects;
- }
-
- /**
- * @return all loaded materials
- */
- public List getMaterials() {
- return materials;
- }
-
- /**
- * @return all loaded textures
- */
- public List getTextures() {
- return textures;
- }
-
- /**
- * @return all loaded animations
- */
- public List getAnimations() {
- return animations;
- }
-
- /**
- * @return all loaded cameras
- */
- public List getCameras() {
- return cameras;
- }
-
- /**
- * @return all loaded lights
- */
- public List getLights() {
- return lights;
- }
-
- /**
- * @return the scene's sky
- */
- public Spatial getSky() {
- return sky;
- }
-
- /**
- * @return scene filters
- */
- public List 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 queue) {
- }
- }
}
diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java
index eff993f76..e11101c14 100644
--- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java
+++ b/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 pathsToCheck = new ArrayList();
+ 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> linkedData = loadedAsset.getUserData("linkedData");
+ for (Entry> entry : linkedData.entrySet()) {
+ String linkedDataFilePath = "this".equals(entry.getKey()) ? path : entry.getKey();
+
+ List scenes = (List) entry.getValue().get("scenes");
+ for (Node scene : scenes) {
+ blenderContext.addLinkedFeature(linkedDataFilePath, "SC" + scene.getName(), scene);
+ }
+ List objects = (List) entry.getValue().get("objects");
+ for (Node object : objects) {
+ blenderContext.addLinkedFeature(linkedDataFilePath, "OB" + object.getName(), object);
+ }
+ List meshes = (List) entry.getValue().get("meshes");
+ for (TemporalMesh mesh : meshes) {
+ blenderContext.addLinkedFeature(linkedDataFilePath, "ME" + mesh.getName(), mesh);
+ }
+ List materials = (List) entry.getValue().get("materials");
+ for (MaterialContext materialContext : materials) {
+ blenderContext.addLinkedFeature(linkedDataFilePath, "MA" + materialContext.getName(), materialContext);
+ }
+ List textures = (List) entry.getValue().get("textures");
+ for (Texture texture : textures) {
+ blenderContext.addLinkedFeature(linkedDataFilePath, "TE" + texture.getName(), texture);
+ }
+ List images = (List) entry.getValue().get("images");
+ for (Texture image : images) {
+ blenderContext.addLinkedFeature(linkedDataFilePath, "IM" + image.getName(), image);
+ }
+ List animations = (List) entry.getValue().get("animations");
+ for (Animation animation : animations) {
+ blenderContext.addLinkedFeature(linkedDataFilePath, "AC" + animation.getName(), animation);
+ }
+ List cameras = (List) entry.getValue().get("cameras");
+ for (Camera camera : cameras) {
+ blenderContext.addLinkedFeature(linkedDataFilePath, "CA" + camera.getName(), camera);
+ }
+ List lights = (List) 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 filters = (List) 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;
+ }
}
diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java
index cde38e327..6e8042b09 100644
--- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java
+++ b/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 blocks;
+ protected List blocks;
/**
* A map containing the file block headers. The key is the old memory address.
*/
- private Map fileBlockHeadersByOma = new HashMap();
+ private Map fileBlockHeadersByOma = new HashMap();
/** A map containing the file block headers. The key is the block code. */
- private Map> fileBlockHeadersByCode = new HashMap>();
+ private Map> fileBlockHeadersByCode = new HashMap>();
/**
* 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> loadedFeatures = new HashMap>();
+ private Map> loadedFeatures = new HashMap>();
+ /** 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> linkedFeatures = new HashMap>();
/** A stack that hold the parent structure of currently loaded feature. */
- private Stack parentStack = new Stack();
+ private Stack parentStack = new Stack();
/** A list of constraints for the specified object. */
- protected Map> constraints = new HashMap>();
+ protected Map> constraints = new HashMap>();
/** Animations loaded for features. */
- private Map> animations = new HashMap>();
+ private Map> animations = new HashMap>();
/** Loaded skeletons. */
- private Map skeletons = new HashMap();
+ private Map skeletons = new HashMap();
/** A map between skeleton and node it modifies. */
- private Map nodesWithSkeletons = new HashMap();
+ private Map nodesWithSkeletons = new HashMap();
/** A map of bone contexts. */
- protected Map boneContexts = new HashMap();
+ protected Map boneContexts = new HashMap();
/** A map og helpers that perform loading. */
- private Map helpers = new HashMap();
+ private Map helpers = new HashMap();
/** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */
- private Map> markers = new HashMap>();
+ private Map> markers = new HashMap>();
/** A map of blender actions. The key is the action name and the value is the action itself. */
- private Map actions = new HashMap();
+ private Map actions = new HashMap();
/**
* 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 headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode()));
+ List headers = fileBlockHeadersByCode.get(fileBlockHeader.getCode());
if (headers == null) {
headers = new ArrayList();
- 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 getFileBlocks(Integer code) {
+ public List 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 map = loadedFeatures.get(oldMemoryAddress);
- if(map == null) {
+ if (map == null) {
map = new HashMap();
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 linkedFeatures = this.linkedFeatures.get(blenderFilePath);
+ if (linkedFeatures == null) {
+ linkedFeatures = new HashMap();
+ 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 linkedFeatures = this.linkedFeatures.get(blenderFilePath);
+ return linkedFeatures != null ? linkedFeatures.get(featureName) : null;
+ }
+
+ /**
+ * @return all linked features for the current blend file
+ */
+ public Map> getLinkedFeatures() {
+ return linkedFeatures;
+ }
+
/**
* This method adds the structure to the parent stack.
*
diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java
index 200f83b39..dd0160063 100644
--- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java
+++ b/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 sceneBlocks = new ArrayList();
- 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> linkedData = new HashMap>();
+
+ Map thisFileData = new HashMap();
+ thisFileData.put("scenes", loadedFeatures.scenes == null ? new ArrayList