diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java
similarity index 51%
rename from jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java
rename to jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java
index d233836a5..2c0367946 100644
--- a/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureHandler.java
+++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java
@@ -35,314 +35,240 @@ package com.jme3.input.android;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
-import android.view.View;
-import com.jme3.input.event.InputEvent;
-import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* AndroidGestureHandler uses Gesture type listeners to create jME TouchEvents
- * for gestures. This class is designed to handle the gestures supported
+ * for gestures. This class is designed to handle the gestures supported
* on Android rev 9 (Android 2.3). Extend this class to add functionality
* added by Android after rev 9.
- *
+ *
* @author iwgeric
*/
-public class AndroidGestureHandler implements
- GestureDetector.OnGestureListener,
+public class AndroidGestureProcessor implements
+ GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener,
ScaleGestureDetector.OnScaleGestureListener {
- private static final Logger logger = Logger.getLogger(AndroidGestureHandler.class.getName());
- private AndroidInputHandler androidInput;
- private GestureDetector gestureDetector;
- private ScaleGestureDetector scaleDetector;
+ private static final Logger logger = Logger.getLogger(AndroidGestureProcessor.class.getName());
+
+ private AndroidTouchInput touchInput;
float gestureDownX = -1f;
float gestureDownY = -1f;
float scaleStartX = -1f;
float scaleStartY = -1f;
- public AndroidGestureHandler(AndroidInputHandler androidInput) {
- this.androidInput = androidInput;
- }
-
- public void initialize() {
- }
-
- public void destroy() {
- setView(null);
- }
-
- public void setView(View view) {
- if (view != null) {
- gestureDetector = new GestureDetector(view.getContext(), this);
- scaleDetector = new ScaleGestureDetector(view.getContext(), this);
- } else {
- gestureDetector = null;
- scaleDetector = null;
- }
- }
-
- public void detectGesture(MotionEvent event) {
- if (gestureDetector != null && scaleDetector != null) {
- gestureDetector.onTouchEvent(event);
- scaleDetector.onTouchEvent(event);
- }
- }
-
- private int getPointerIndex(MotionEvent event) {
- return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
- >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- }
-
- private int getPointerId(MotionEvent event) {
- return event.getPointerId(getPointerIndex(event));
+ public AndroidGestureProcessor(AndroidTouchInput touchInput) {
+ this.touchInput = touchInput;
}
-
- private void processEvent(TouchEvent event) {
- // Add the touch event
- androidInput.addEvent(event);
- if (androidInput.isSimulateMouse()) {
- InputEvent mouseEvent = generateMouseEvent(event);
- if (mouseEvent != null) {
- // Add the mouse event
- androidInput.addEvent(mouseEvent);
- }
- }
- }
-
- // TODO: Ring Buffer for mouse events?
- private InputEvent generateMouseEvent(TouchEvent event) {
- InputEvent inputEvent = null;
- int newX;
- int newY;
- int newDX;
- int newDY;
- if (androidInput.isMouseEventsInvertX()) {
- newX = (int) (androidInput.invertX(event.getX()));
- newDX = (int)event.getDeltaX() * -1;
- } else {
- newX = (int) event.getX();
- newDX = (int)event.getDeltaX();
- }
- int wheel = (int) (event.getScaleSpan()); // might need to scale to match mouse wheel
- int dWheel = (int) (event.getDeltaScaleSpan()); // might need to scale to match mouse wheel
-
- if (androidInput.isMouseEventsInvertY()) {
- newY = (int) (androidInput.invertY(event.getY()));
- newDY = (int)event.getDeltaY() * -1;
- } else {
- newY = (int) event.getY();
- newDY = (int)event.getDeltaY();
- }
-
- switch (event.getType()) {
- case SCALE_MOVE:
- inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, wheel, dWheel);
- inputEvent.setTime(event.getTime());
- break;
- }
-
- return inputEvent;
- }
-
/* Events from onGestureListener */
-
+
+ @Override
public boolean onDown(MotionEvent event) {
// start of all GestureListeners. Not really a gesture by itself
// so we don't create an event.
// However, reset the scaleInProgress here since this is the beginning
// of a series of gesture events.
-// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}",
-// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
- gestureDownX = androidInput.getJmeX(event.getX());
- gestureDownY = androidInput.invertY(androidInput.getJmeY(event.getY()));
+// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
+ gestureDownX = touchInput.getJmeX(event.getX());
+ gestureDownY = touchInput.invertY(touchInput.getJmeY(event.getY()));
return true;
}
+ @Override
public boolean onSingleTapUp(MotionEvent event) {
// Up of single tap. May be followed by a double tap later.
// use onSingleTapConfirmed instead.
-// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}",
-// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
+// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
return true;
}
+ @Override
public void onShowPress(MotionEvent event) {
-// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
-// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
- float jmeX = androidInput.getJmeX(event.getX());
- float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
- TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
+ float jmeX = touchInput.getJmeX(event.getX());
+ float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
+ TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SHOWPRESS, jmeX, jmeY, 0, 0);
- touchEvent.setPointerId(getPointerId(event));
+ touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure());
- processEvent(touchEvent);
+ touchInput.addEvent(touchEvent);
}
+ @Override
public void onLongPress(MotionEvent event) {
-// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
-// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
- float jmeX = androidInput.getJmeX(event.getX());
- float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
- TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
+ float jmeX = touchInput.getJmeX(event.getX());
+ float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
+ TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.LONGPRESSED, jmeX, jmeY, 0, 0);
- touchEvent.setPointerId(getPointerId(event));
+ touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure());
- processEvent(touchEvent);
+ touchInput.addEvent(touchEvent);
}
+ @Override
public boolean onScroll(MotionEvent startEvent, MotionEvent endEvent, float distX, float distY) {
// if not scaleInProgess, send scroll events. This is to avoid sending
// scroll events when one of the fingers is lifted just before the other one.
// Avoids sending the scroll for that brief period of time.
// Return true so that the next event doesn't accumulate the distX and distY values.
- // Apparantly, both distX and distY are negative.
+ // Apparantly, both distX and distY are negative.
// Negate distX to get the real value, but leave distY negative to compensate
// for the fact that jME has y=0 at bottom where Android has y=0 at top.
-// if (!scaleInProgress) {
- if (!scaleDetector.isInProgress()) {
-// logger.log(Level.INFO, "onScroll pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, dx: {7}, dy: {8}",
-// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), distX, distY});
+ if (!touchInput.getScaleDetector().isInProgress()) {
+// logger.log(Level.INFO, "onScroll pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, dx: {7}, dy: {8}",
+// new Object[]{touchInput.getPointerId(startEvent), touchInput.getAction(startEvent), startEvent.getX(), startEvent.getY(), touchInput.getAction(endEvent), endEvent.getX(), endEvent.getY(), distX, distY});
- float jmeX = androidInput.getJmeX(endEvent.getX());
- float jmeY = androidInput.invertY(androidInput.getJmeY(endEvent.getY()));
- TouchEvent touchEvent = androidInput.getFreeTouchEvent();
- touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, androidInput.getJmeX(-distX), androidInput.getJmeY(distY));
- touchEvent.setPointerId(getPointerId(endEvent));
+ float jmeX = touchInput.getJmeX(endEvent.getX());
+ float jmeY = touchInput.invertY(touchInput.getJmeY(endEvent.getY()));
+ TouchEvent touchEvent = touchInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, touchInput.getJmeX(-distX), touchInput.getJmeY(distY));
+ touchEvent.setPointerId(touchInput.getPointerId(endEvent));
touchEvent.setTime(endEvent.getEventTime());
touchEvent.setPressure(endEvent.getPressure());
- processEvent(touchEvent);
+ touchInput.addEvent(touchEvent);
}
return true;
}
+ @Override
public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float velocityX, float velocityY) {
// Fling happens only once at the end of the gesture (all fingers up).
// Fling returns the velocity of the finger movement in pixels/sec.
// Therefore, the dX and dY values are actually velocity instead of distance values
// Since this does not track the movement, use the start position and velocity values.
-
-// logger.log(Level.INFO, "onFling pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, velocityX: {7}, velocityY: {8}",
-// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY});
- float jmeX = androidInput.getJmeX(startEvent.getX());
- float jmeY = androidInput.invertY(androidInput.getJmeY(startEvent.getY()));
- TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+// logger.log(Level.INFO, "onFling pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, velocityX: {7}, velocityY: {8}",
+// new Object[]{touchInput.getPointerId(startEvent), touchInput.getAction(startEvent), startEvent.getX(), startEvent.getY(), touchInput.getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY});
+
+ float jmeX = touchInput.getJmeX(startEvent.getX());
+ float jmeY = touchInput.invertY(touchInput.getJmeY(startEvent.getY()));
+ TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.FLING, jmeX, jmeY, velocityX, velocityY);
- touchEvent.setPointerId(getPointerId(endEvent));
+ touchEvent.setPointerId(touchInput.getPointerId(endEvent));
touchEvent.setTime(endEvent.getEventTime());
touchEvent.setPressure(endEvent.getPressure());
- processEvent(touchEvent);
+ touchInput.addEvent(touchEvent);
return true;
}
/* Events from onDoubleTapListener */
-
+
+ @Override
public boolean onSingleTapConfirmed(MotionEvent event) {
// Up of single tap when no double tap followed.
-// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}",
-// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
- float jmeX = androidInput.getJmeX(event.getX());
- float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
- TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
+ float jmeX = touchInput.getJmeX(event.getX());
+ float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
+ TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.TAP, jmeX, jmeY, 0, 0);
- touchEvent.setPointerId(getPointerId(event));
+ touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure());
- processEvent(touchEvent);
+ touchInput.addEvent(touchEvent);
return true;
}
+ @Override
public boolean onDoubleTap(MotionEvent event) {
//The down motion event of the first tap of the double-tap
- // We could use this event to fire off a double tap event, or use
+ // We could use this event to fire off a double tap event, or use
// DoubleTapEvent with a check for the UP action
-// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}",
-// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
- float jmeX = androidInput.getJmeX(event.getX());
- float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
- TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
+ float jmeX = touchInput.getJmeX(event.getX());
+ float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
+ TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.DOUBLETAP, jmeX, jmeY, 0, 0);
- touchEvent.setPointerId(getPointerId(event));
+ touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure());
- processEvent(touchEvent);
+ touchInput.addEvent(touchEvent);
return true;
}
+ @Override
public boolean onDoubleTapEvent(MotionEvent event) {
//Notified when an event within a double-tap gesture occurs, including the down, move(s), and up events.
// this means it will get called multiple times for a single double tap
-// logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}",
-// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
-// if (getAction(event) == MotionEvent.ACTION_UP) {
-// TouchEvent touchEvent = touchEventPool.getNextFreeEvent();
-// touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), androidInput.invertY(event.getY()), 0, 0);
-// touchEvent.setPointerId(getPointerId(event));
-// touchEvent.setTime(event.getEventTime());
-// touchEvent.setPressure(event.getPressure());
-// processEvent(touchEvent);
-// }
+// logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}",
+// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
+ if (touchInput.getAction(event) == MotionEvent.ACTION_UP) {
+ TouchEvent touchEvent = touchInput.getFreeTouchEvent();
+ touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), touchInput.invertY(event.getY()), 0, 0);
+ touchEvent.setPointerId(touchInput.getPointerId(event));
+ touchEvent.setTime(event.getEventTime());
+ touchEvent.setPressure(event.getPressure());
+ touchInput.addEvent(touchEvent);
+ }
return true;
}
/* Events from ScaleGestureDetector */
-
+
+ @Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
// Scale uses a focusX and focusY instead of x and y. Focus is the middle
// of the fingers. Therefore, use the x and y values from the Down event
// so that the x and y values don't jump to the middle position.
// return true or all gestures for this beginning event will be discarded
- logger.log(Level.INFO, "onScaleBegin");
+// logger.log(Level.INFO, "onScaleBegin");
scaleStartX = gestureDownX;
scaleStartY = gestureDownY;
- TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCALE_START, scaleStartX, scaleStartY, 0f, 0f);
touchEvent.setPointerId(0);
touchEvent.setTime(scaleGestureDetector.getEventTime());
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
touchEvent.setDeltaScaleSpan(0f);
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
- touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
- processEvent(touchEvent);
-
+ touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress());
+ touchInput.addEvent(touchEvent);
+
return true;
}
+ @Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
// return true or all gestures for this event will be accumulated
- logger.log(Level.INFO, "onScale");
+// logger.log(Level.INFO, "onScale");
scaleStartX = gestureDownX;
scaleStartY = gestureDownY;
- TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCALE_MOVE, scaleStartX, scaleStartY, 0f, 0f);
touchEvent.setPointerId(0);
touchEvent.setTime(scaleGestureDetector.getEventTime());
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
- touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
- processEvent(touchEvent);
+ touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress());
+ touchInput.addEvent(touchEvent);
return true;
}
+ @Override
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
- logger.log(Level.INFO, "onScaleEnd");
+// logger.log(Level.INFO, "onScaleEnd");
scaleStartX = gestureDownX;
scaleStartY = gestureDownY;
- TouchEvent touchEvent = androidInput.getFreeTouchEvent();
+ TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCALE_END, scaleStartX, scaleStartY, 0f, 0f);
touchEvent.setPointerId(0);
touchEvent.setTime(scaleGestureDetector.getEventTime());
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
- touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
- processEvent(touchEvent);
+ touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress());
+ touchInput.addEvent(touchEvent);
}
}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java
deleted file mode 100644
index 02fef6b0f..000000000
--- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java
+++ /dev/null
@@ -1,686 +0,0 @@
-package com.jme3.input.android;
-
-import android.view.*;
-import com.jme3.input.KeyInput;
-import com.jme3.input.RawInputListener;
-import com.jme3.input.TouchInput;
-import com.jme3.input.event.MouseButtonEvent;
-import com.jme3.input.event.MouseMotionEvent;
-import com.jme3.input.event.TouchEvent;
-import com.jme3.input.event.TouchEvent.Type;
-import com.jme3.math.Vector2f;
-import com.jme3.system.AppSettings;
-import com.jme3.util.RingBuffer;
-import java.util.HashMap;
-import java.util.logging.Logger;
-
-/**
- * AndroidInput is one of the main components that connect jme with android. Is derived from GLSurfaceView and handles all Inputs
- * @author larynx
- *
- */
-public class AndroidInput implements
- TouchInput,
- View.OnTouchListener,
- View.OnKeyListener,
- GestureDetector.OnGestureListener,
- GestureDetector.OnDoubleTapListener,
- ScaleGestureDetector.OnScaleGestureListener {
-
- final private static int MAX_EVENTS = 1024;
- // Custom settings
- public boolean mouseEventsEnabled = true;
- public boolean mouseEventsInvertX = false;
- public boolean mouseEventsInvertY = false;
- public boolean keyboardEventsEnabled = false;
- public boolean dontSendHistory = false;
- // Used to transfer events from android thread to GLThread
- final private RingBuffer eventQueue = new RingBuffer(MAX_EVENTS);
- final private RingBuffer eventPoolUnConsumed = new RingBuffer(MAX_EVENTS);
- final private RingBuffer eventPool = new RingBuffer(MAX_EVENTS);
- final private HashMap lastPositions = new HashMap();
- // Internal
- private View view;
- private ScaleGestureDetector scaledetector;
- private boolean scaleInProgress = false;
- private GestureDetector detector;
- private int lastX;
- private int lastY;
- private final static Logger logger = Logger.getLogger(AndroidInput.class.getName());
- private boolean isInitialized = false;
- private RawInputListener listener = null;
- private static final int[] ANDROID_TO_JME = {
- 0x0, // unknown
- 0x0, // key code soft left
- 0x0, // key code soft right
- KeyInput.KEY_HOME,
- KeyInput.KEY_ESCAPE, // key back
- 0x0, // key call
- 0x0, // key endcall
- KeyInput.KEY_0,
- KeyInput.KEY_1,
- KeyInput.KEY_2,
- KeyInput.KEY_3,
- KeyInput.KEY_4,
- KeyInput.KEY_5,
- KeyInput.KEY_6,
- KeyInput.KEY_7,
- KeyInput.KEY_8,
- KeyInput.KEY_9,
- KeyInput.KEY_MULTIPLY,
- 0x0, // key pound
- KeyInput.KEY_UP,
- KeyInput.KEY_DOWN,
- KeyInput.KEY_LEFT,
- KeyInput.KEY_RIGHT,
- KeyInput.KEY_RETURN, // dpad center
- 0x0, // volume up
- 0x0, // volume down
- KeyInput.KEY_POWER, // power (?)
- 0x0, // camera
- 0x0, // clear
- KeyInput.KEY_A,
- KeyInput.KEY_B,
- KeyInput.KEY_C,
- KeyInput.KEY_D,
- KeyInput.KEY_E,
- KeyInput.KEY_F,
- KeyInput.KEY_G,
- KeyInput.KEY_H,
- KeyInput.KEY_I,
- KeyInput.KEY_J,
- KeyInput.KEY_K,
- KeyInput.KEY_L,
- KeyInput.KEY_M,
- KeyInput.KEY_N,
- KeyInput.KEY_O,
- KeyInput.KEY_P,
- KeyInput.KEY_Q,
- KeyInput.KEY_R,
- KeyInput.KEY_S,
- KeyInput.KEY_T,
- KeyInput.KEY_U,
- KeyInput.KEY_V,
- KeyInput.KEY_W,
- KeyInput.KEY_X,
- KeyInput.KEY_Y,
- KeyInput.KEY_Z,
- KeyInput.KEY_COMMA,
- KeyInput.KEY_PERIOD,
- KeyInput.KEY_LMENU,
- KeyInput.KEY_RMENU,
- KeyInput.KEY_LSHIFT,
- KeyInput.KEY_RSHIFT,
- // 0x0, // fn
- // 0x0, // cap (?)
-
- KeyInput.KEY_TAB,
- KeyInput.KEY_SPACE,
- 0x0, // sym (?) symbol
- 0x0, // explorer
- 0x0, // envelope
- KeyInput.KEY_RETURN, // newline/enter
- KeyInput.KEY_DELETE,
- KeyInput.KEY_GRAVE,
- KeyInput.KEY_MINUS,
- KeyInput.KEY_EQUALS,
- KeyInput.KEY_LBRACKET,
- KeyInput.KEY_RBRACKET,
- KeyInput.KEY_BACKSLASH,
- KeyInput.KEY_SEMICOLON,
- KeyInput.KEY_APOSTROPHE,
- KeyInput.KEY_SLASH,
- KeyInput.KEY_AT, // at (@)
- KeyInput.KEY_NUMLOCK, //0x0, // num
- 0x0, //headset hook
- 0x0, //focus
- KeyInput.KEY_ADD,
- KeyInput.KEY_LMETA, //menu
- 0x0,//notification
- 0x0,//search
- 0x0,//media play/pause
- 0x0,//media stop
- 0x0,//media next
- 0x0,//media previous
- 0x0,//media rewind
- 0x0,//media fastforward
- 0x0,//mute
- };
-
- public AndroidInput() {
- }
-
- public void setView(View view) {
- this.view = view;
- if (view != null) {
- detector = new GestureDetector(null, this, null, false);
- scaledetector = new ScaleGestureDetector(view.getContext(), this);
- view.setOnTouchListener(this);
- view.setOnKeyListener(this);
- }
- }
-
- private TouchEvent getNextFreeTouchEvent() {
- return getNextFreeTouchEvent(false);
- }
-
- /**
- * Fetches a touch event from the reuse pool
- * @param wait if true waits for a reusable event to get available/released
- * by an other thread, if false returns a new one if needed.
- *
- * @return a usable TouchEvent
- */
- private TouchEvent getNextFreeTouchEvent(boolean wait) {
- TouchEvent evt = null;
- synchronized (eventPoolUnConsumed) {
- int size = eventPoolUnConsumed.size();
- while (size > 0) {
- evt = eventPoolUnConsumed.pop();
- if (!evt.isConsumed()) {
- eventPoolUnConsumed.push(evt);
- evt = null;
- } else {
- break;
- }
- size--;
- }
- }
-
- if (evt == null) {
- if (eventPool.isEmpty() && wait) {
- logger.warning("eventPool buffer underrun");
- boolean isEmpty;
- do {
- synchronized (eventPool) {
- isEmpty = eventPool.isEmpty();
- }
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- }
- } while (isEmpty);
- synchronized (eventPool) {
- evt = eventPool.pop();
- }
- } else if (eventPool.isEmpty()) {
- evt = new TouchEvent();
- logger.warning("eventPool buffer underrun");
- } else {
- synchronized (eventPool) {
- evt = eventPool.pop();
- }
- }
- }
- return evt;
- }
-
- /**
- * onTouch gets called from android thread on touchpad events
- */
- public boolean onTouch(View view, MotionEvent event) {
- if (view != this.view) {
- return false;
- }
- boolean bWasHandled = false;
- TouchEvent touch;
- // System.out.println("native : " + event.getAction());
- int action = event.getAction() & MotionEvent.ACTION_MASK;
- int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
- >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- int pointerId = event.getPointerId(pointerIndex);
- Vector2f lastPos = lastPositions.get(pointerId);
-
- // final int historySize = event.getHistorySize();
- //final int pointerCount = event.getPointerCount();
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_DOWN:
- touch = getNextFreeTouchEvent();
- touch.set(Type.DOWN, event.getX(pointerIndex), view.getHeight() - event.getY(pointerIndex), 0, 0);
- touch.setPointerId(pointerId);
- touch.setTime(event.getEventTime());
- touch.setPressure(event.getPressure(pointerIndex));
- processEvent(touch);
-
- lastPos = new Vector2f(event.getX(pointerIndex), view.getHeight() - event.getY(pointerIndex));
- lastPositions.put(pointerId, lastPos);
-
- bWasHandled = true;
- break;
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- touch = getNextFreeTouchEvent();
- touch.set(Type.UP, event.getX(pointerIndex), view.getHeight() - event.getY(pointerIndex), 0, 0);
- touch.setPointerId(pointerId);
- touch.setTime(event.getEventTime());
- touch.setPressure(event.getPressure(pointerIndex));
- processEvent(touch);
- lastPositions.remove(pointerId);
-
- bWasHandled = true;
- break;
- case MotionEvent.ACTION_MOVE:
- // Convert all pointers into events
- for (int p = 0; p < event.getPointerCount(); p++) {
- lastPos = lastPositions.get(event.getPointerId(p));
- if (lastPos == null) {
- lastPos = new Vector2f(event.getX(p), view.getHeight() - event.getY(p));
- lastPositions.put(event.getPointerId(p), lastPos);
- }
-
- float dX = event.getX(p) - lastPos.x;
- float dY = view.getHeight() - event.getY(p) - lastPos.y;
- if (dX != 0 || dY != 0) {
- touch = getNextFreeTouchEvent();
- touch.set(Type.MOVE, event.getX(p), view.getHeight() - event.getY(p), dX, dY);
- touch.setPointerId(event.getPointerId(p));
- touch.setTime(event.getEventTime());
- touch.setPressure(event.getPressure(p));
- touch.setScaleSpanInProgress(scaleInProgress);
- processEvent(touch);
- lastPos.set(event.getX(p), view.getHeight() - event.getY(p));
- }
- }
- bWasHandled = true;
- break;
- case MotionEvent.ACTION_OUTSIDE:
- break;
-
- }
-
- // Try to detect gestures
- this.detector.onTouchEvent(event);
- this.scaledetector.onTouchEvent(event);
-
- return bWasHandled;
- }
-
- /**
- * onKey gets called from android thread on key events
- */
- public boolean onKey(View view, int keyCode, KeyEvent event) {
- if (view != this.view) {
- return false;
- }
-
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- TouchEvent evt;
- evt = getNextFreeTouchEvent();
- evt.set(TouchEvent.Type.KEY_DOWN);
- evt.setKeyCode(keyCode);
- evt.setCharacters(event.getCharacters());
- evt.setTime(event.getEventTime());
-
- // Send the event
- processEvent(evt);
-
- // Handle all keys ourself except Volume Up/Down
- if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
- return false;
- } else {
- return true;
- }
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- TouchEvent evt;
- evt = getNextFreeTouchEvent();
- evt.set(TouchEvent.Type.KEY_UP);
- evt.setKeyCode(keyCode);
- evt.setCharacters(event.getCharacters());
- evt.setTime(event.getEventTime());
-
- // Send the event
- processEvent(evt);
-
- // Handle all keys ourself except Volume Up/Down
- if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
- return false;
- } else {
- return true;
- }
- } else {
- return false;
- }
- }
-
- public void loadSettings(AppSettings settings) {
- mouseEventsEnabled = settings.isEmulateMouse();
- mouseEventsInvertX = settings.isEmulateMouseFlipX();
- mouseEventsInvertY = settings.isEmulateMouseFlipY();
- }
-
- // -----------------------------------------
- // JME3 Input interface
- @Override
- public void initialize() {
- TouchEvent item;
- for (int i = 0; i < MAX_EVENTS; i++) {
- item = new TouchEvent();
- eventPool.push(item);
- }
- isInitialized = true;
- }
-
- @Override
- public void destroy() {
- isInitialized = false;
-
- // Clean up queues
- while (!eventPool.isEmpty()) {
- eventPool.pop();
- }
- while (!eventQueue.isEmpty()) {
- eventQueue.pop();
- }
-
-
- this.view = null;
- }
-
- @Override
- public boolean isInitialized() {
- return isInitialized;
- }
-
- @Override
- public void setInputListener(RawInputListener listener) {
- this.listener = listener;
- }
-
- @Override
- public long getInputTimeNanos() {
- return System.nanoTime();
- }
- // -----------------------------------------
-
- private void processEvent(TouchEvent event) {
- synchronized (eventQueue) {
- //Discarding events when the ring buffer is full to avoid buffer overflow.
- if(eventQueue.size()< MAX_EVENTS){
- eventQueue.push(event);
- }
-
- }
- }
-
- // --------------- INSIDE GLThread ---------------
- @Override
- public void update() {
- generateEvents();
- }
-
- private void generateEvents() {
- if (listener != null) {
- TouchEvent event;
- MouseButtonEvent btn;
- MouseMotionEvent mot;
- int newX;
- int newY;
-
- while (!eventQueue.isEmpty()) {
- synchronized (eventQueue) {
- event = eventQueue.pop();
- }
- if (event != null) {
- listener.onTouchEvent(event);
-
- if (mouseEventsEnabled) {
- if (mouseEventsInvertX) {
- newX = view.getWidth() - (int) event.getX();
- } else {
- newX = (int) event.getX();
- }
-
- if (mouseEventsInvertY) {
- newY = view.getHeight() - (int) event.getY();
- } else {
- newY = (int) event.getY();
- }
-
- switch (event.getType()) {
- case DOWN:
- // Handle mouse down event
- btn = new MouseButtonEvent(0, true, newX, newY);
- btn.setTime(event.getTime());
- listener.onMouseButtonEvent(btn);
- // Store current pos
- lastX = -1;
- lastY = -1;
- break;
-
- case UP:
- // Handle mouse up event
- btn = new MouseButtonEvent(0, false, newX, newY);
- btn.setTime(event.getTime());
- listener.onMouseButtonEvent(btn);
- // Store current pos
- lastX = -1;
- lastY = -1;
- break;
-
- case SCALE_MOVE:
- if (lastX != -1 && lastY != -1) {
- newX = lastX;
- newY = lastY;
- }
- int wheel = (int) (event.getScaleSpan() / 4f); // scale to match mouse wheel
- int dwheel = (int) (event.getDeltaScaleSpan() / 4f); // scale to match mouse wheel
- mot = new MouseMotionEvent(newX, newX, 0, 0, wheel, dwheel);
- mot.setTime(event.getTime());
- listener.onMouseMotionEvent(mot);
- lastX = newX;
- lastY = newY;
-
- break;
-
- case MOVE:
- if (event.isScaleSpanInProgress()) {
- break;
- }
-
- int dx;
- int dy;
- if (lastX != -1) {
- dx = newX - lastX;
- dy = newY - lastY;
- } else {
- dx = 0;
- dy = 0;
- }
-
- mot = new MouseMotionEvent(newX, newY, dx, dy, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan());
- mot.setTime(event.getTime());
- listener.onMouseMotionEvent(mot);
- lastX = newX;
- lastY = newY;
-
- break;
- }
- }
- }
-
- if (event.isConsumed() == false) {
- synchronized (eventPoolUnConsumed) {
- eventPoolUnConsumed.push(event);
- }
-
- } else {
- synchronized (eventPool) {
- eventPool.push(event);
- }
- }
- }
-
- }
- }
- // --------------- ENDOF INSIDE GLThread ---------------
-
- // --------------- Gesture detected callback events ---------------
- public boolean onDown(MotionEvent event) {
- return false;
- }
-
- public void onLongPress(MotionEvent event) {
- TouchEvent touch = getNextFreeTouchEvent();
- touch.set(Type.LONGPRESSED, event.getX(), view.getHeight() - event.getY(), 0f, 0f);
- touch.setPointerId(0);
- touch.setTime(event.getEventTime());
- processEvent(touch);
- }
-
- public boolean onFling(MotionEvent event, MotionEvent event2, float vx, float vy) {
- TouchEvent touch = getNextFreeTouchEvent();
- touch.set(Type.FLING, event.getX(), view.getHeight() - event.getY(), vx, vy);
- touch.setPointerId(0);
- touch.setTime(event.getEventTime());
- processEvent(touch);
-
- return true;
- }
-
- public boolean onSingleTapConfirmed(MotionEvent event) {
- //Nothing to do here the tap has already been detected.
- return false;
- }
-
- public boolean onDoubleTap(MotionEvent event) {
- TouchEvent touch = getNextFreeTouchEvent();
- touch.set(Type.DOUBLETAP, event.getX(), view.getHeight() - event.getY(), 0f, 0f);
- touch.setPointerId(0);
- touch.setTime(event.getEventTime());
- processEvent(touch);
- return true;
- }
-
- public boolean onDoubleTapEvent(MotionEvent event) {
- return false;
- }
-
- public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
- scaleInProgress = true;
- TouchEvent touch = getNextFreeTouchEvent();
- touch.set(Type.SCALE_START, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f);
- touch.setPointerId(0);
- touch.setTime(scaleGestureDetector.getEventTime());
- touch.setScaleSpan(scaleGestureDetector.getCurrentSpan());
- touch.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
- touch.setScaleFactor(scaleGestureDetector.getScaleFactor());
- touch.setScaleSpanInProgress(scaleInProgress);
- processEvent(touch);
- // System.out.println("scaleBegin");
-
- return true;
- }
-
- public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
- TouchEvent touch = getNextFreeTouchEvent();
- touch.set(Type.SCALE_MOVE, scaleGestureDetector.getFocusX(), view.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f);
- touch.setPointerId(0);
- touch.setTime(scaleGestureDetector.getEventTime());
- touch.setScaleSpan(scaleGestureDetector.getCurrentSpan());
- touch.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
- touch.setScaleFactor(scaleGestureDetector.getScaleFactor());
- touch.setScaleSpanInProgress(scaleInProgress);
- processEvent(touch);
- // System.out.println("scale");
-
- return false;
- }
-
- public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
- scaleInProgress = false;
- TouchEvent touch = getNextFreeTouchEvent();
- touch.set(Type.SCALE_END, scaleGestureDetector.getFocusX(), view.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f);
- touch.setPointerId(0);
- touch.setTime(scaleGestureDetector.getEventTime());
- touch.setScaleSpan(scaleGestureDetector.getCurrentSpan());
- touch.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
- touch.setScaleFactor(scaleGestureDetector.getScaleFactor());
- touch.setScaleSpanInProgress(scaleInProgress);
- processEvent(touch);
- }
-
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- TouchEvent touch = getNextFreeTouchEvent();
- touch.set(Type.SCROLL, e1.getX(), view.getHeight() - e1.getY(), distanceX, distanceY * (-1));
- touch.setPointerId(0);
- touch.setTime(e1.getEventTime());
- processEvent(touch);
- //System.out.println("scroll " + e1.getPointerCount());
- return false;
- }
-
- public void onShowPress(MotionEvent event) {
- TouchEvent touch = getNextFreeTouchEvent();
- touch.set(Type.SHOWPRESS, event.getX(), view.getHeight() - event.getY(), 0f, 0f);
- touch.setPointerId(0);
- touch.setTime(event.getEventTime());
- processEvent(touch);
- }
-
- public boolean onSingleTapUp(MotionEvent event) {
- TouchEvent touch = getNextFreeTouchEvent();
- touch.set(Type.TAP, event.getX(), view.getHeight() - event.getY(), 0f, 0f);
- touch.setPointerId(0);
- touch.setTime(event.getEventTime());
- processEvent(touch);
- return true;
- }
-
- @Override
- public void setSimulateKeyboard(boolean simulate) {
- keyboardEventsEnabled = simulate;
- }
-
- @Override
- public void setOmitHistoricEvents(boolean dontSendHistory) {
- this.dontSendHistory = dontSendHistory;
- }
-
- /**
- * @deprecated Use {@link #getSimulateMouse()};
- */
- @Deprecated
- public boolean isMouseEventsEnabled() {
- return mouseEventsEnabled;
- }
-
- @Deprecated
- public void setMouseEventsEnabled(boolean mouseEventsEnabled) {
- this.mouseEventsEnabled = mouseEventsEnabled;
- }
-
- public boolean isMouseEventsInvertY() {
- return mouseEventsInvertY;
- }
-
- public void setMouseEventsInvertY(boolean mouseEventsInvertY) {
- this.mouseEventsInvertY = mouseEventsInvertY;
- }
-
- public boolean isMouseEventsInvertX() {
- return mouseEventsInvertX;
- }
-
- public void setMouseEventsInvertX(boolean mouseEventsInvertX) {
- this.mouseEventsInvertX = mouseEventsInvertX;
- }
-
- public void setSimulateMouse(boolean simulate) {
- mouseEventsEnabled = simulate;
- }
-
- public boolean getSimulateMouse() {
- return isSimulateMouse();
- }
-
- public boolean isSimulateMouse() {
- return mouseEventsEnabled;
- }
-
- public boolean isSimulateKeyboard() {
- return keyboardEventsEnabled;
- }
-
-}
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 202907316..9f4729c66 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
@@ -33,231 +33,206 @@
package com.jme3.input.android;
import android.opengl.GLSurfaceView;
-import android.os.Build;
+import android.view.GestureDetector;
+import android.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
import android.view.View;
-import com.jme3.input.RawInputListener;
+import com.jme3.input.JoyInput;
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.
+ * inputs to jME. It receives the inputs from the Android View and passes them
+ * to the appropriate classes based on the source of the input.
+ * This class is to be extended when new functionality is released in Android.
*
* @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;
+public class AndroidInputHandler implements View.OnTouchListener,
+ View.OnKeyListener {
+ private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName());
- // 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;
+ protected GLSurfaceView view;
+ protected AndroidTouchInput touchInput;
+ protected AndroidJoyInput joyInput;
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;
+ touchInput = new AndroidTouchInput(this);
+ joyInput = new AndroidJoyInput(this);
}
public void setView(View view) {
- if (touchHandler != null) {
- touchHandler.setView(view);
+ if (this.view != null && view != null && this.view.equals(view)) {
+ return;
}
- if (keyHandler != null) {
- keyHandler.setView(view);
- }
- if (gestureHandler != null) {
- gestureHandler.setView(view);
+
+ if (this.view != null) {
+ removeListeners(this.view);
}
+
this.view = (GLSurfaceView)view;
- }
- public View getView() {
- return view;
- }
+ if (this.view != null) {
+ addListeners(this.view);
+ }
- public float invertX(float origX) {
- return getJmeX(view.getWidth()) - origX;
+ joyInput.setView((GLSurfaceView)view);
}
- public float invertY(float origY) {
- return getJmeY(view.getHeight()) - origY;
+ public View getView() {
+ return view;
}
- public float getJmeX(float origX) {
- return origX * scaleX;
+ protected void removeListeners(GLSurfaceView view) {
+ view.setOnTouchListener(null);
+ view.setOnKeyListener(null);
+ touchInput.setGestureDetector(null);
+ touchInput.setScaleDetector(null);
}
- public float getJmeY(float origY) {
- return origY * scaleY;
+ protected void addListeners(GLSurfaceView view) {
+ view.setOnTouchListener(this);
+ view.setOnKeyListener(this);
+ AndroidGestureProcessor gestureHandler = new AndroidGestureProcessor(touchInput);
+ touchInput.setGestureDetector(new GestureDetector(
+ view.getContext(), gestureHandler));
+ touchInput.setScaleDetector(new ScaleGestureDetector(
+ view.getContext(), gestureHandler));
}
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});
+ touchInput.loadSettings(settings);
+ }
+
+ public TouchInput getTouchInput() {
+ return touchInput;
+ }
+
+ public JoyInput getJoyInput() {
+ return joyInput;
+ }
+
+ /*
+ * Android input events include the source from which the input came from.
+ * We must look at the source of the input event to determine which type
+ * of jME input it belongs to.
+ * If the input is from a gamepad or joystick source, the event is sent
+ * to the JoyInput class to convert the event into jME joystick events.
+ *
+ * If the input is from a touchscreen source, the event is sent to the
+ * TouchProcessor to convert the event into touch events.
+ * The TouchProcessor also converts the events into Mouse and Key events
+ * if AppSettings is set to simulate Mouse or Keyboard events.
+ *
+ * Android reports the source as a bitmask as shown below.
+ *
+ * InputDevice Sources
+ * 0000 0000 0000 0000 0000 0000 0000 0000 - 32 bit bitmask
+ *
+ * 0000 0000 0000 0000 0000 0000 1111 1111 - SOURCE_CLASS_MASK (0x000000ff)
+ * 0000 0000 0000 0000 0000 0000 0000 0000 - SOURCE_CLASS_NONE (0x00000000)
+ * 0000 0000 0000 0000 0000 0000 0000 0001 - SOURCE_CLASS_BUTTON (0x00000001)
+ * 0000 0000 0000 0000 0000 0000 0000 0010 - SOURCE_CLASS_POINTER (0x00000002)
+ * 0000 0000 0000 0000 0000 0000 0000 0100 - SOURCE_CLASS_TRACKBALL (0x00000004)
+ * 0000 0000 0000 0000 0000 0000 0000 1000 - SOURCE_CLASS_POSITION (0x00000008)
+ * 0000 0000 0000 0000 0000 0000 0001 0000 - SOURCE_CLASS_JOYSTICK (0x00000010)
+ *
+ * 1111 1111 1111 1111 1111 1111 0000 0000 - Source_Any (0xffffff00)
+ * 0000 0000 0000 0000 0000 0000 0000 0000 - SOURCE_UNKNOWN (0x00000000)
+ * 0000 0000 0000 0000 0000 0001 0000 0001 - SOURCE_KEYBOARD (0x00000101)
+ * 0000 0000 0000 0000 0000 0010 0000 0001 - SOURCE_DPAD (0x00000201)
+ * 0000 0000 0000 0000 0000 0100 0000 0001 - SOURCE_GAMEPAD (0x00000401)
+ * 0000 0000 0000 0000 0001 0000 0000 0010 - SOURCE_TOUCHSCREEN (0x00001002)
+ * 0000 0000 0000 0000 0010 0000 0000 0010 - SOURCE_MOUSE (0x00002002)
+ * 0000 0000 0000 0000 0100 0000 0000 0010 - SOURCE_STYLUS (0x00004002)
+ * 0000 0000 0000 0001 0000 0000 0000 0100 - SOURCE_TRACKBALL (0x00010004)
+ * 0000 0000 0001 0000 0000 0000 0000 1000 - SOURCE_TOUCHPAD (0x00100008)
+ * 0000 0000 0010 0000 0000 0000 0000 0000 - SOURCE_TOUCH_NAVIGATION (0x00200000)
+ * 0000 0001 0000 0000 0000 0000 0001 0000 - SOURCE_JOYSTICK (0x01000010)
+ * 0000 0010 0000 0000 0000 0000 0000 0001 - SOURCE_HDMI (0x02000001)
+ *
+ * Example values reported by Android for Source
+ * 4,098 = 0x00001002 =
+ * 0000 0000 0000 0000 0001 0000 0000 0010 - SOURCE_CLASS_POINTER
+ * SOURCE_TOUCHSCREEN
+ * 1,281 = 0x00000501 =
+ * 0000 0000 0000 0000 0000 0101 0000 0001 - SOURCE_CLASS_BUTTON
+ * SOURCE_KEYBOARD
+ * SOURCE_GAMEPAD
+ * 16,777,232 = 0x01000010 =
+ * 0000 0001 0000 0000 0000 0000 0001 0000 - SOURCE_CLASS_JOYSTICK
+ * SOURCE_JOYSTICK
+ *
+ * 16,778,513 = 0x01000511 =
+ * 0000 0001 0000 0000 0000 0101 0001 0001 - SOURCE_CLASS_BUTTON
+ * SOURCE_CLASS_JOYSTICK
+ * SOURCE_GAMEPAD
+ * SOURCE_KEYBOARD
+ * SOURCE_JOYSTICK
+ *
+ * 257 = 0x00000101 =
+ * 0000 0000 0000 0000 0000 0001 0000 0001 - SOURCE_CLASS_BUTTON
+ * SOURCE_KEYBOARD
+ *
+ *
+ *
+ */
- }
- // -----------------------------------------
- // JME3 Input interface
@Override
- public void initialize() {
- touchEventPool.initialize();
- if (touchHandler != null) {
- touchHandler.initialize();
- }
- if (keyHandler != null) {
- keyHandler.initialize();
- }
- if (gestureHandler != null) {
- gestureHandler.initialize();
+ public boolean onTouch(View view, MotionEvent event) {
+ if (view != getView()) {
+ return false;
}
- initialized = true;
- }
+ boolean consumed = false;
- @Override
- public void destroy() {
- initialized = false;
+ int source = event.getSource();
+// logger.log(Level.INFO, "onTouch source: {0}", source);
- 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();
- }
+ boolean isTouch = ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN);
+// logger.log(Level.INFO, "onTouch source: {0}, isTouch: {1}",
+// new Object[]{source, isTouch});
- 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);
- }
- }
+ if (isTouch && touchInput != null) {
+ // send the event to the touch processor
+ consumed = touchInput.onTouch(event);
}
- }
- // -----------------------------------------
+ return consumed;
- public TouchEvent getFreeTouchEvent() {
- return touchEventPool.getNextFreeEvent();
}
- public void addEvent(InputEvent event) {
- inputEventQueue.add(event);
- if (event instanceof TouchEvent) {
- touchEventPool.storeEvent((TouchEvent)event);
+ @Override
+ public boolean onKey(View view, int keyCode, KeyEvent event) {
+ if (view != getView()) {
+ return false;
}
- }
- public void setSimulateMouse(boolean simulate) {
- this.mouseEventsEnabled = simulate;
- }
+ boolean consumed = false;
- public boolean isSimulateMouse() {
- return mouseEventsEnabled;
- }
+ int source = event.getSource();
+// logger.log(Level.INFO, "onKey source: {0}", source);
- public boolean isMouseEventsInvertX() {
- return mouseEventsInvertX;
- }
+ boolean isTouch =
+ ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) ||
+ ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD);
+// logger.log(Level.INFO, "onKey source: {0}, isTouch: {1}",
+// new Object[]{source, isTouch});
- public boolean isMouseEventsInvertY() {
- return mouseEventsInvertY;
- }
-
- public void setSimulateKeyboard(boolean simulate) {
- this.keyboardEventsEnabled = simulate;
- }
+ if (touchInput != null) {
+ consumed = touchInput.onKey(event);
+ }
- public boolean isSimulateKeyboard() {
- return keyboardEventsEnabled;
- }
+ return consumed;
- public void setOmitHistoricEvents(boolean dontSendHistory) {
- this.dontSendHistory = dontSendHistory;
}
}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java
new file mode 100644
index 000000000..4b39ed24c
--- /dev/null
+++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java
@@ -0,0 +1,159 @@
+/*
+ * 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.view.InputDevice;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * AndroidInputHandler14 extends AndroidInputHandler to
+ * add the onHover and onGenericMotion events that where added in Android rev 14 (Android 4.0).
+ * The onGenericMotion events are the main interface to Joystick axes. They
+ * were actually released in Android rev 12.
+ *
+ * @author iwgeric
+ */
+public class AndroidInputHandler14 extends AndroidInputHandler implements View.OnHoverListener,
+ View.OnGenericMotionListener {
+
+ private static final Logger logger = Logger.getLogger(AndroidInputHandler14.class.getName());
+
+ public AndroidInputHandler14() {
+ touchInput = new AndroidTouchInput14(this);
+ joyInput = new AndroidJoyInput14(this);
+ }
+
+ @Override
+ protected void removeListeners(GLSurfaceView view) {
+ super.removeListeners(view);
+ view.setOnHoverListener(null);
+ view.setOnGenericMotionListener(null);
+ }
+
+ @Override
+ protected void addListeners(GLSurfaceView view) {
+ super.addListeners(view);
+ view.setOnHoverListener(this);
+ view.setOnGenericMotionListener(this);
+ }
+
+ @Override
+ public boolean onHover(View view, MotionEvent event) {
+ if (view != getView()) {
+ return false;
+ }
+
+ boolean consumed = false;
+
+ int source = event.getSource();
+// logger.log(Level.INFO, "onTouch source: {0}", source);
+
+ boolean isTouch = ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN);
+// logger.log(Level.INFO, "onTouch source: {0}, isTouch: {1}",
+// new Object[]{source, isTouch});
+
+ if (isTouch && touchInput != null) {
+ // send the event to the touch processor
+ consumed = ((AndroidTouchInput14)touchInput).onHover(event);
+ }
+
+ return consumed;
+ }
+
+ @Override
+ public boolean onGenericMotion(View view, MotionEvent event) {
+ if (view != getView()) {
+ return false;
+ }
+
+ boolean consumed = false;
+
+ int source = event.getSource();
+// logger.log(Level.INFO, "onGenericMotion source: {0}", source);
+
+ boolean isJoystick =
+ ((source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
+ ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK);
+
+ if (isJoystick && joyInput != null) {
+// logger.log(Level.INFO, "onGenericMotion source: {0}, isJoystick: {1}",
+// new Object[]{source, isJoystick});
+ // send the event to the touch processor
+ consumed = consumed || ((AndroidJoyInput14)joyInput).onGenericMotion(event);
+ }
+
+ return consumed;
+ }
+
+ @Override
+ public boolean onKey(View view, int keyCode, KeyEvent event) {
+ if (view != getView()) {
+ return false;
+ }
+
+ boolean consumed = false;
+
+// logger.log(Level.INFO, "onKey keyCode: {0}, action: {1}, event: {2}",
+// new Object[]{KeyEvent.keyCodeToString(keyCode), event.getAction(), event});
+ int source = event.getSource();
+// logger.log(Level.INFO, "onKey source: {0}", source);
+
+ boolean isTouch =
+ ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) ||
+ ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD);
+ boolean isJoystick =
+ ((source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
+ ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK);
+
+ if (isTouch && touchInput != null) {
+// logger.log(Level.INFO, "onKey source: {0}, isTouch: {1}",
+// new Object[]{source, isTouch});
+ consumed = touchInput.onKey(event);
+ }
+ if (isJoystick && joyInput != null) {
+// logger.log(Level.INFO, "onKey source: {0}, isJoystick: {1}",
+// new Object[]{source, isJoystick});
+ // use inclusive OR to make sure the onKey method is called.
+ consumed = consumed | ((AndroidJoyInput14)joyInput).onKey(event);
+ }
+
+ return consumed;
+
+ }
+
+}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java
similarity index 83%
rename from jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java
rename to jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java
index 2c3a1d74e..1e610d24e 100644
--- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInputHandler.java
+++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java
@@ -33,9 +33,7 @@ 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;
@@ -79,15 +77,16 @@ import java.util.logging.Logger;
*
* @author iwgeric
*/
-public class AndroidJoyInputHandler implements JoyInput {
- private static final Logger logger = Logger.getLogger(AndroidJoyInputHandler.class.getName());
+public class AndroidJoyInput implements JoyInput {
+ private static final Logger logger = Logger.getLogger(AndroidJoyInput.class.getName());
+ public static boolean disableSensors = false;
- private List joystickList = new ArrayList();
+ protected AndroidInputHandler inputHandler;
+ protected 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();
@@ -96,34 +95,29 @@ public class AndroidJoyInputHandler implements JoyInput {
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);
-// }
+ public AndroidJoyInput(AndroidInputHandler inputHandler) {
+ this.inputHandler = inputHandler;
sensorJoyInput = new AndroidSensorJoyInput(this);
}
public void setView(GLSurfaceView view) {
-// if (touchHandler != null) {
-// touchHandler.setView(view);
-// }
+ if (view == null) {
+ vibrator = null;
+ } else {
+ // 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.");
+ }
+ }
+
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) {
@@ -155,20 +149,8 @@ public class AndroidJoyInputHandler implements JoyInput {
}
-
-
-
-
@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;
}
@@ -211,8 +193,8 @@ public class AndroidJoyInputHandler implements JoyInput {
};
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});
+// logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}",
+// new Object[]{amount, rumbleOnDur, rumbleOffDur});
if (rumbleOnDur > 0) {
vibrator.vibrate(rumblePattern, rumbleRepeatFrom);
@@ -226,9 +208,10 @@ public class AndroidJoyInputHandler implements JoyInput {
@Override
public Joystick[] loadJoysticks(InputManager inputManager) {
- joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager));
-
-
+ logger.log(Level.INFO, "loading joysticks for {0}", this.getClass().getName());
+ if (!disableSensors) {
+ joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager));
+ }
return joystickList.toArray( new Joystick[joystickList.size()] );
}
@@ -252,6 +235,4 @@ public class AndroidJoyInputHandler implements JoyInput {
}
-
-
}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java
new file mode 100644
index 000000000..00478aea1
--- /dev/null
+++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java
@@ -0,0 +1,108 @@
+/*
+ * 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.view.KeyEvent;
+import android.view.MotionEvent;
+import com.jme3.input.InputManager;
+import com.jme3.input.Joystick;
+import java.util.logging.Logger;
+
+/**
+ * AndroidJoyInput14 extends AndroidJoyInput
+ * to include support for physical joysticks/gamepads.
+ *
+ * @author iwgeric
+ */
+public class AndroidJoyInput14 extends AndroidJoyInput {
+ private static final Logger logger = Logger.getLogger(AndroidJoyInput14.class.getName());
+
+ private AndroidJoystickJoyInput14 joystickJoyInput;
+
+ public AndroidJoyInput14(AndroidInputHandler inputHandler) {
+ super(inputHandler);
+ joystickJoyInput = new AndroidJoystickJoyInput14(this);
+ }
+
+ /**
+ * Pauses the joystick device listeners to save battery life if they are not needed.
+ * Used to pause when the activity pauses
+ */
+ @Override
+ public void pauseJoysticks() {
+ super.pauseJoysticks();
+
+ if (joystickJoyInput != null) {
+ joystickJoyInput.pauseJoysticks();
+ }
+ }
+
+ /**
+ * Resumes the joystick device listeners.
+ * Used to resume when the activity comes to the top of the stack
+ */
+ @Override
+ public void resumeJoysticks() {
+ super.resumeJoysticks();
+ if (joystickJoyInput != null) {
+ joystickJoyInput.resumeJoysticks();
+ }
+
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ if (joystickJoyInput != null) {
+ joystickJoyInput.destroy();
+ }
+ }
+
+ @Override
+ public Joystick[] loadJoysticks(InputManager inputManager) {
+ // load the simulated joystick for device orientation
+ super.loadJoysticks(inputManager);
+ // load physical gamepads/joysticks
+ joystickList.addAll(joystickJoyInput.loadJoysticks(joystickList.size(), inputManager));
+ // return the list of joysticks back to InputManager
+ return joystickList.toArray( new Joystick[joystickList.size()] );
+ }
+
+ public boolean onGenericMotion(MotionEvent event) {
+ return joystickJoyInput.onGenericMotion(event);
+ }
+
+ public boolean onKey(KeyEvent event) {
+ return joystickJoyInput.onKey(event);
+ }
+
+}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java
new file mode 100644
index 000000000..1ed98977c
--- /dev/null
+++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java
@@ -0,0 +1,416 @@
+/*
+ * 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.view.InputDevice;
+import android.view.InputDevice.MotionRange;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import com.jme3.input.AbstractJoystick;
+import com.jme3.input.DefaultJoystickAxis;
+import com.jme3.input.DefaultJoystickButton;
+import com.jme3.input.InputManager;
+import com.jme3.input.JoyInput;
+import com.jme3.input.Joystick;
+import com.jme3.input.JoystickAxis;
+import com.jme3.input.JoystickButton;
+import com.jme3.input.JoystickCompatibilityMappings;
+import com.jme3.input.event.JoyAxisEvent;
+import com.jme3.input.event.JoyButtonEvent;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Main class that creates and manages Android inputs for physical gamepads/joysticks.
+ *
+ * @author iwgeric
+ */
+public class AndroidJoystickJoyInput14 {
+ private static final Logger logger = Logger.getLogger(AndroidJoystickJoyInput14.class.getName());
+
+ private boolean loaded = false;
+ private AndroidJoyInput joyInput;
+ private Map joystickIndex = new HashMap();
+
+ private static int[] AndroidGamepadButtons = {
+ // Dpad buttons
+ KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_DPAD_CENTER,
+
+ // pressing joystick down
+ KeyEvent.KEYCODE_BUTTON_THUMBL, KeyEvent.KEYCODE_BUTTON_THUMBR,
+
+ // buttons
+ KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B,
+ KeyEvent.KEYCODE_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y,
+
+ // buttons on back of device
+ KeyEvent.KEYCODE_BUTTON_L1, KeyEvent.KEYCODE_BUTTON_R1,
+ KeyEvent.KEYCODE_BUTTON_L2, KeyEvent.KEYCODE_BUTTON_R2,
+
+ // start / select buttons
+ KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_BUTTON_SELECT,
+ KeyEvent.KEYCODE_BUTTON_MODE,
+
+ };
+
+ public AndroidJoystickJoyInput14(AndroidJoyInput joyInput) {
+ this.joyInput = joyInput;
+ }
+
+
+ public void pauseJoysticks() {
+
+ }
+
+ public void resumeJoysticks() {
+
+ }
+
+ public void destroy() {
+
+ }
+
+ public List loadJoysticks(int joyId, InputManager inputManager) {
+ logger.log(Level.INFO, "loading Joystick devices");
+ ArrayList joysticks = new ArrayList();
+ joysticks.clear();
+ joystickIndex.clear();
+
+ ArrayList gameControllerDeviceIds = new ArrayList();
+ int[] deviceIds = InputDevice.getDeviceIds();
+ for (int deviceId : deviceIds) {
+ InputDevice dev = InputDevice.getDevice(deviceId);
+ int sources = dev.getSources();
+ logger.log(Level.FINE, "deviceId[{0}] sources: {1}", new Object[]{deviceId, sources});
+
+ // Verify that the device has gamepad buttons, control sticks, or both.
+ if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
+ ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) {
+ // This device is a game controller. Store its device ID.
+ if (!gameControllerDeviceIds.contains(deviceId)) {
+ gameControllerDeviceIds.add(deviceId);
+ logger.log(Level.FINE, "Attempting to create joystick for device: {0}", dev);
+ // Create an AndroidJoystick and store the InputDevice so we
+ // can later correspond the input from the InputDevice to the
+ // appropriate jME Joystick event
+ AndroidJoystick joystick = new AndroidJoystick(inputManager,
+ joyInput,
+ dev,
+ joyId+joysticks.size(),
+ dev.getName());
+ joystickIndex.put(deviceId, joystick);
+ joysticks.add(joystick);
+
+ // Each analog input is reported as a MotionRange
+ // The axis number corresponds to the type of axis
+ // The AndroidJoystick.addAxis(MotionRange) converts the axis
+ // type reported by Android into the jME Joystick axis
+ List motionRanges = dev.getMotionRanges();
+ for (MotionRange motionRange: motionRanges) {
+ logger.log(Level.INFO, "motion range: {0}", motionRange.toString());
+ logger.log(Level.INFO, "axis: {0}", motionRange.getAxis());
+ JoystickAxis axis = joystick.addAxis(motionRange);
+ logger.log(Level.INFO, "added axis: {0}", axis);
+ }
+
+ // InputDevice has a method for determining if a keyCode is
+ // supported (InputDevice public boolean[] hasKeys (int... keys)).
+ // But this method wasn't added until rev 19 (Android 4.4)
+ // Therefore, we only can query the entire device and see if
+ // any InputDevice supports the keyCode. This may result in
+ // buttons being configured that don't exist on the specific
+ // device, but I haven't found a better way yet.
+ for (int keyCode: AndroidGamepadButtons) {
+ logger.log(Level.INFO, "button[{0}]: {1}",
+ new Object[]{keyCode, KeyCharacterMap.deviceHasKey(keyCode)});
+ if (KeyCharacterMap.deviceHasKey(keyCode)) {
+ // add button even though we aren't sure if the button
+ // actually exists on this InputDevice
+ logger.log(Level.INFO, "button[{0}] exists somewhere", keyCode);
+ JoystickButton button = joystick.addButton(keyCode);
+ logger.log(Level.INFO, "added button: {0}", button);
+ }
+ }
+
+ }
+ }
+ }
+
+
+ loaded = true;
+ return joysticks;
+ }
+
+ public boolean onGenericMotion(MotionEvent event) {
+ boolean consumed = false;
+// logger.log(Level.INFO, "onGenericMotion event: {0}", event);
+ event.getDeviceId();
+ event.getSource();
+// logger.log(Level.INFO, "deviceId: {0}, source: {1}", new Object[]{event.getDeviceId(), event.getSource()});
+ AndroidJoystick joystick = joystickIndex.get(event.getDeviceId());
+ if (joystick != null) {
+ for (int androidAxis: joystick.getAndroidAxes()) {
+ String axisName = MotionEvent.axisToString(androidAxis);
+ float value = event.getAxisValue(androidAxis);
+ int action = event.getAction();
+ if (action == MotionEvent.ACTION_MOVE) {
+// logger.log(Level.INFO, "MOVE axis num: {0}, axisName: {1}, value: {2}",
+// new Object[]{androidAxis, axisName, value});
+ JoystickAxis axis = joystick.getAxis(androidAxis);
+ if (axis != null) {
+// logger.log(Level.INFO, "MOVE axis num: {0}, axisName: {1}, value: {2}, deadzone: {3}",
+// new Object[]{androidAxis, axisName, value, axis.getDeadZone()});
+ JoyAxisEvent axisEvent = new JoyAxisEvent(axis, value);
+ joyInput.addEvent(axisEvent);
+ consumed = true;
+ } else {
+// logger.log(Level.INFO, "axis was null for axisName: {0}", axisName);
+ }
+ } else {
+// logger.log(Level.INFO, "action: {0}", action);
+ }
+ }
+ }
+
+ return consumed;
+ }
+
+ public boolean onKey(KeyEvent event) {
+ boolean consumed = false;
+// logger.log(Level.INFO, "onKey event: {0}", event);
+
+ event.getDeviceId();
+ event.getSource();
+ AndroidJoystick joystick = joystickIndex.get(event.getDeviceId());
+ if (joystick != null) {
+ JoystickButton button = joystick.getButton(event.getKeyCode());
+ boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
+ if (button != null) {
+ JoyButtonEvent buttonEvent = new JoyButtonEvent(button, pressed);
+ joyInput.addEvent(buttonEvent);
+ consumed = true;
+ } else {
+ JoystickButton newButton = joystick.addButton(event.getKeyCode());
+ JoyButtonEvent buttonEvent = new JoyButtonEvent(newButton, pressed);
+ joyInput.addEvent(buttonEvent);
+ consumed = true;
+ }
+ }
+
+ return consumed;
+ }
+
+ protected class AndroidJoystick extends AbstractJoystick {
+
+ private JoystickAxis nullAxis;
+ private InputDevice device;
+ private JoystickAxis xAxis;
+ private JoystickAxis yAxis;
+ private JoystickAxis povX;
+ private JoystickAxis povY;
+ private Map axisIndex = new HashMap();
+ private Map buttonIndex = new HashMap();
+
+ public AndroidJoystick( InputManager inputManager, JoyInput joyInput, InputDevice device,
+ int joyId, String name ) {
+ super( inputManager, joyInput, joyId, name );
+
+ this.device = device;
+
+ this.nullAxis = new DefaultJoystickAxis( getInputManager(), this, -1,
+ "Null", "null", false, false, 0 );
+ this.xAxis = nullAxis;
+ this.yAxis = nullAxis;
+ this.povX = nullAxis;
+ this.povY = nullAxis;
+ }
+
+ protected JoystickAxis getAxis(int androidAxis) {
+ return axisIndex.get(androidAxis);
+ }
+
+ protected Set getAndroidAxes() {
+ return axisIndex.keySet();
+ }
+
+ protected JoystickButton getButton(int keyCode) {
+ return buttonIndex.get(keyCode);
+ }
+
+ protected JoystickButton addButton( int keyCode ) {
+
+// logger.log(Level.FINE, "Adding button: {0}", keyCode);
+
+ String name = KeyEvent.keyCodeToString(keyCode);
+ String original = KeyEvent.keyCodeToString(keyCode);
+ // A/B/X/Y buttons
+ if (keyCode == KeyEvent.KEYCODE_BUTTON_Y) {
+ original = JoystickButton.BUTTON_0;
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_B) {
+ original = JoystickButton.BUTTON_1;
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_A) {
+ original = JoystickButton.BUTTON_2;
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_X) {
+ original = JoystickButton.BUTTON_3;
+ // Front buttons Some of these have the top ones and the bottoms ones flipped.
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_L1) {
+ original = JoystickButton.BUTTON_4;
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_R1) {
+ original = JoystickButton.BUTTON_5;
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_L2) {
+ original = JoystickButton.BUTTON_6;
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_R2) {
+ original = JoystickButton.BUTTON_7;
+// // Dpad buttons
+// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+// original = JoystickButton.BUTTON_8;
+// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+// original = JoystickButton.BUTTON_9;
+// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+// original = JoystickButton.BUTTON_8;
+// } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+// original = JoystickButton.BUTTON_9;
+ // Select and start buttons
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_SELECT) {
+ original = JoystickButton.BUTTON_8;
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_START) {
+ original = JoystickButton.BUTTON_9;
+ // Joystick push buttons
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBL) {
+ original = JoystickButton.BUTTON_10;
+ } else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBR) {
+ original = JoystickButton.BUTTON_11;
+ }
+
+ String logicalId = JoystickCompatibilityMappings.remapComponent( getName(), original );
+ if( logicalId == null ? original != null : !logicalId.equals(original) ) {
+ logger.log(Level.FINE, "Remapped: {0} to: {1}",
+ new Object[]{original, logicalId});
+ }
+
+ JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(),
+ name, logicalId );
+ addButton(button);
+ buttonIndex.put( keyCode, button );
+ return button;
+ }
+
+ protected JoystickAxis addAxis(MotionRange motionRange) {
+
+ String name = MotionEvent.axisToString(motionRange.getAxis());
+
+ String original = MotionEvent.axisToString(motionRange.getAxis());
+ if (motionRange.getAxis() == MotionEvent.AXIS_X) {
+ original = JoystickAxis.X_AXIS;
+ } else if (motionRange.getAxis() == MotionEvent.AXIS_Y) {
+ original = JoystickAxis.Y_AXIS;
+ } else if (motionRange.getAxis() == MotionEvent.AXIS_Z) {
+ original = JoystickAxis.Z_AXIS;
+ } else if (motionRange.getAxis() == MotionEvent.AXIS_RZ) {
+ original = JoystickAxis.Z_ROTATION;
+ } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_X) {
+ original = JoystickAxis.POV_X;
+ } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_Y) {
+ original = JoystickAxis.POV_Y;
+ }
+ String logicalId = JoystickCompatibilityMappings.remapComponent( getName(), original );
+ if( logicalId == null ? original != null : !logicalId.equals(original) ) {
+ logger.log(Level.FINE, "Remapped: {0} to: {1}",
+ new Object[]{original, logicalId});
+ }
+
+ JoystickAxis axis = new DefaultJoystickAxis(getInputManager(),
+ this,
+ getAxisCount(),
+ name,
+ logicalId,
+ true,
+ true,
+ motionRange.getFlat());
+
+ if (motionRange.getAxis() == MotionEvent.AXIS_X) {
+ xAxis = axis;
+ }
+ if (motionRange.getAxis() == MotionEvent.AXIS_Y) {
+ yAxis = axis;
+ }
+ if (motionRange.getAxis() == MotionEvent.AXIS_HAT_X) {
+ povX = axis;
+ }
+ if (motionRange.getAxis() == MotionEvent.AXIS_HAT_Y) {
+ povY = axis;
+ }
+
+ addAxis(axis);
+ axisIndex.put(motionRange.getAxis(), axis);
+ return axis;
+ }
+
+ @Override
+ public JoystickAxis getXAxis() {
+ return xAxis;
+ }
+
+ @Override
+ public JoystickAxis getYAxis() {
+ return yAxis;
+ }
+
+ @Override
+ public JoystickAxis getPovXAxis() {
+ return povX;
+ }
+
+ @Override
+ public JoystickAxis getPovYAxis() {
+ return povY;
+ }
+
+ @Override
+ public int getXAxisIndex(){
+ return xAxis.getAxisId();
+ }
+
+ @Override
+ public int getYAxisIndex(){
+ return yAxis.getAxisId();
+ }
+ }
+}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java
deleted file mode 100644
index 997974c31..000000000
--- a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (c) 2009-2012 jMonkeyEngine
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package com.jme3.input.android;
-
-import android.content.Context;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import com.jme3.input.event.KeyInputEvent;
-import com.jme3.input.event.TouchEvent;
-import java.util.logging.Logger;
-
-/**
- * AndroidKeyHandler recieves onKey events from the Android system and creates
- * the jME KeyEvents. onKey is used by Android to receive keys from the keyboard
- * or device buttons. All key events are consumed by jME except for the Volume
- * buttons and menu button.
- *
- * This class also provides the functionality to display or hide the soft keyboard
- * for inputing single key events. Use OGLESContext to display an dialog to type
- * in complete strings.
- *
- * @author iwgeric
- */
-public class AndroidKeyHandler implements View.OnKeyListener {
- private static final Logger logger = Logger.getLogger(AndroidKeyHandler.class.getName());
-
- private AndroidInputHandler androidInput;
- private boolean sendKeyEvents = true;
-
- public AndroidKeyHandler(AndroidInputHandler androidInput) {
- this.androidInput = androidInput;
- }
-
- public void initialize() {
- }
-
- public void destroy() {
- }
-
- public void setView(View view) {
- if (view != null) {
- view.setOnKeyListener(this);
- } else {
- androidInput.getView().setOnKeyListener(null);
- }
- }
-
- /**
- * onKey gets called from android thread on key events
- */
- public boolean onKey(View view, int keyCode, KeyEvent event) {
- if (androidInput.isInitialized() && view != androidInput.getView()) {
- return false;
- }
-
- TouchEvent evt;
- // TODO: get touch event from pool
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- evt = new TouchEvent();
- evt.set(TouchEvent.Type.KEY_DOWN);
- evt.setKeyCode(keyCode);
- evt.setCharacters(event.getCharacters());
- evt.setTime(event.getEventTime());
-
- // Send the event
- androidInput.addEvent(evt);
-
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- evt = new TouchEvent();
- evt.set(TouchEvent.Type.KEY_UP);
- evt.setKeyCode(keyCode);
- evt.setCharacters(event.getCharacters());
- evt.setTime(event.getEventTime());
-
- // Send the event
- androidInput.addEvent(evt);
-
- }
-
- if (androidInput.isSimulateKeyboard()) {
- KeyInputEvent kie;
- char unicodeChar = (char)event.getUnicodeChar();
- int jmeKeyCode = AndroidKeyMapping.getJmeKey(keyCode);
-
- boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
- boolean repeating = pressed && event.getRepeatCount() > 0;
-
- kie = new KeyInputEvent(jmeKeyCode, unicodeChar, pressed, repeating);
- kie.setTime(event.getEventTime());
- androidInput.addEvent(kie);
-// logger.log(Level.FINE, "onKey keyCode: {0}, jmeKeyCode: {1}, pressed: {2}, repeating: {3}",
-// new Object[]{keyCode, jmeKeyCode, pressed, repeating});
-// logger.log(Level.FINE, "creating KeyInputEvent: {0}", kie);
- }
-
- // consume all keys ourself except Volume Up/Down and Menu
- // Don't do Menu so that typical Android Menus can be created and used
- // by the user in MainActivity
- if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) ||
- (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) ||
- (keyCode == KeyEvent.KEYCODE_MENU)) {
- return false;
- } else {
- return true;
- }
-
- }
-
-}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java
index e99d8e9fc..32d1e0008 100644
--- a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java
+++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java
@@ -37,13 +37,14 @@ import java.util.logging.Logger;
/**
* AndroidKeyMapping is just a utility to convert the Android keyCodes into
- * jME KeyCodes received in jME's KeyEvent will match between Desktop and Android.
- *
+ * jME KeyCodes so that events received in jME's KeyEvent will match between
+ * Desktop and Android.
+ *
* @author iwgeric
*/
public class AndroidKeyMapping {
private static final Logger logger = Logger.getLogger(AndroidKeyMapping.class.getName());
-
+
private static final int[] ANDROID_TO_JME = {
0x0, // unknown
0x0, // key code soft left
@@ -141,9 +142,13 @@ public class AndroidKeyMapping {
0x0,//media fastforward
0x0,//mute
};
-
+
public static int getJmeKey(int androidKey) {
- return ANDROID_TO_JME[androidKey];
+ if (androidKey > ANDROID_TO_JME.length) {
+ return androidKey;
+ } else {
+ return ANDROID_TO_JME[androidKey];
+ }
}
-
+
}
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 823aa3d9d..cdd7e6494 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
@@ -75,15 +75,15 @@ import java.util.logging.Logger;
public class AndroidSensorJoyInput implements SensorEventListener {
private final static Logger logger = Logger.getLogger(AndroidSensorJoyInput.class.getName());
- private AndroidJoyInputHandler joyHandler;
+ private AndroidJoyInput joyInput;
private SensorManager sensorManager = null;
private WindowManager windowManager = null;
private IntMap sensors = new IntMap();
private int lastRotation = 0;
private boolean loaded = false;
- public AndroidSensorJoyInput(AndroidJoyInputHandler joyHandler) {
- this.joyHandler = joyHandler;
+ public AndroidSensorJoyInput(AndroidJoyInput joyInput) {
+ this.joyInput = joyInput;
}
/**
@@ -96,7 +96,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
int sensorAccuracy = -1;
float[] lastValues;
final Object valuesLock = new Object();
- ArrayList axes = new ArrayList();
+ ArrayList axes = new ArrayList();
boolean enabled = false;
boolean haveData = false;
@@ -306,7 +306,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
*/
private boolean updateOrientation() {
SensorData sensorData;
- AndroidJoystickAxis axis;
+ AndroidSensorJoystickAxis axis;
final float[] curInclinationMat = new float[16];
final float[] curRotationMat = new float[16];
final float[] rotatedRotationMat = new float[16];
@@ -374,7 +374,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
sensorData.haveData = true;
} else {
if (axis.isChanged()) {
- joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue()));
+ joyInput.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue()));
}
}
}
@@ -401,10 +401,10 @@ public class AndroidSensorJoyInput implements SensorEventListener {
public Joystick loadJoystick(int joyId, InputManager inputManager) {
SensorData sensorData;
- AndroidJoystickAxis axis;
+ AndroidSensorJoystickAxis axis;
- AndroidJoystick joystick = new AndroidJoystick(inputManager,
- joyHandler,
+ AndroidSensorJoystick joystick = new AndroidSensorJoystick(inputManager,
+ joyInput,
joyId,
"AndroidSensorsJoystick");
@@ -522,15 +522,15 @@ public class AndroidSensorJoyInput implements SensorEventListener {
if (!loaded) {
return;
}
- logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}",
- new Object[]{se.sensor.getName(), se.accuracy, se.values});
+// 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});
+// 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) {
@@ -543,8 +543,8 @@ public class AndroidSensorJoyInput implements SensorEventListener {
}
}
- if (sensorData != null && sensorData.axes.size() > 0) {
- AndroidJoystickAxis axis;
+ if (sensorData.axes.size() > 0) {
+ AndroidSensorJoystickAxis axis;
for (int i=0; i lastPositions = new HashMap();
-
- protected int numPointers = 0;
-
- protected AndroidInputHandler androidInput;
- protected AndroidGestureHandler gestureHandler;
-
- public AndroidTouchHandler(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) {
- this.androidInput = androidInput;
- this.gestureHandler = gestureHandler;
- }
-
- public void initialize() {
- }
-
- public void destroy() {
- setView(null);
- }
-
- public void setView(View view) {
- if (view != null) {
- view.setOnTouchListener(this);
- } else {
- androidInput.getView().setOnTouchListener(null);
- }
- }
-
- protected int getPointerIndex(MotionEvent event) {
- return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
- >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- }
-
- protected int getPointerId(MotionEvent event) {
- return event.getPointerId(getPointerIndex(event));
- }
-
- protected int getAction(MotionEvent event) {
- return event.getAction() & MotionEvent.ACTION_MASK;
- }
-
- /**
- * onTouch gets called from android thread on touch events
- */
- public boolean onTouch(View view, MotionEvent event) {
- if (!androidInput.isInitialized() || view != androidInput.getView()) {
- return false;
- }
-
- boolean bWasHandled = false;
- TouchEvent touch = null;
- // System.out.println("native : " + event.getAction());
- int action = getAction(event);
- int pointerIndex = getPointerIndex(event);
- int pointerId = getPointerId(event);
- Vector2f lastPos = lastPositions.get(pointerId);
- float jmeX;
- float jmeY;
-
- numPointers = event.getPointerCount();
-
- // final int historySize = event.getHistorySize();
- //final int pointerCount = event.getPointerCount();
- switch (getAction(event)) {
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_DOWN:
- jmeX = androidInput.getJmeX(event.getX(pointerIndex));
- jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex)));
- touch = androidInput.getFreeTouchEvent();
- touch.set(TouchEvent.Type.DOWN, jmeX, jmeY, 0, 0);
- touch.setPointerId(pointerId);
- touch.setTime(event.getEventTime());
- touch.setPressure(event.getPressure(pointerIndex));
-
- lastPos = new Vector2f(jmeX, jmeY);
- lastPositions.put(pointerId, lastPos);
-
- processEvent(touch);
-
- bWasHandled = true;
- break;
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- jmeX = androidInput.getJmeX(event.getX(pointerIndex));
- jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex)));
- touch = androidInput.getFreeTouchEvent();
- touch.set(TouchEvent.Type.UP, jmeX, jmeY, 0, 0);
- touch.setPointerId(pointerId);
- touch.setTime(event.getEventTime());
- touch.setPressure(event.getPressure(pointerIndex));
- lastPositions.remove(pointerId);
-
- processEvent(touch);
-
- bWasHandled = true;
- break;
- case MotionEvent.ACTION_MOVE:
- // Convert all pointers into events
- for (int p = 0; p < event.getPointerCount(); p++) {
- jmeX = androidInput.getJmeX(event.getX(p));
- jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(p)));
- lastPos = lastPositions.get(event.getPointerId(p));
- if (lastPos == null) {
- lastPos = new Vector2f(jmeX, jmeY);
- lastPositions.put(event.getPointerId(p), lastPos);
- }
-
- float dX = jmeX - lastPos.x;
- float dY = jmeY - lastPos.y;
- if (dX != 0 || dY != 0) {
- touch = androidInput.getFreeTouchEvent();
- touch.set(TouchEvent.Type.MOVE, jmeX, jmeY, dX, dY);
- touch.setPointerId(event.getPointerId(p));
- touch.setTime(event.getEventTime());
- touch.setPressure(event.getPressure(p));
- lastPos.set(jmeX, jmeY);
-
- processEvent(touch);
-
- bWasHandled = true;
- }
- }
- break;
- case MotionEvent.ACTION_OUTSIDE:
- break;
-
- }
-
- // Try to detect gestures
- if (gestureHandler != null) {
- gestureHandler.detectGesture(event);
- }
-
- return bWasHandled;
- }
-
- protected void processEvent(TouchEvent event) {
- // Add the touch event
- androidInput.addEvent(event);
- // MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events
- if (androidInput.isSimulateMouse() && numPointers == 1) {
- InputEvent mouseEvent = generateMouseEvent(event);
- if (mouseEvent != null) {
- // Add the mouse event
- androidInput.addEvent(mouseEvent);
- }
- }
-
- }
-
- // TODO: Ring Buffer for mouse events?
- protected InputEvent generateMouseEvent(TouchEvent event) {
- InputEvent inputEvent = null;
- int newX;
- int newY;
- int newDX;
- int newDY;
-
- if (androidInput.isMouseEventsInvertX()) {
- newX = (int) (androidInput.invertX(event.getX()));
- newDX = (int)event.getDeltaX() * -1;
- } else {
- newX = (int) event.getX();
- newDX = (int)event.getDeltaX();
- }
-
- if (androidInput.isMouseEventsInvertY()) {
- newY = (int) (androidInput.invertY(event.getY()));
- newDY = (int)event.getDeltaY() * -1;
- } else {
- newY = (int) event.getY();
- newDY = (int)event.getDeltaY();
- }
-
- switch (event.getType()) {
- case DOWN:
- // Handle mouse down event
- inputEvent = new MouseButtonEvent(0, true, newX, newY);
- inputEvent.setTime(event.getTime());
- break;
-
- case UP:
- // Handle mouse up event
- inputEvent = new MouseButtonEvent(0, false, newX, newY);
- inputEvent.setTime(event.getTime());
- break;
-
- case HOVER_MOVE:
- case MOVE:
- inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan());
- inputEvent.setTime(event.getTime());
- break;
- }
-
- return inputEvent;
- }
-
-}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java
new file mode 100644
index 000000000..bddc5fab3
--- /dev/null
+++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java
@@ -0,0 +1,475 @@
+/*
+ * 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.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+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 static com.jme3.input.event.TouchEvent.Type.DOWN;
+import static com.jme3.input.event.TouchEvent.Type.MOVE;
+import static com.jme3.input.event.TouchEvent.Type.UP;
+import com.jme3.math.Vector2f;
+import com.jme3.system.AppSettings;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * AndroidTouchInput is the base class that receives touch inputs from the
+ * Android system and creates the TouchEvents for jME. This class is designed
+ * to handle the base touch events for Android rev 9 (Android 2.3). This is
+ * extended by other classes to add features that were introducted after
+ * Android rev 9.
+ *
+ * @author iwgeric
+ */
+public class AndroidTouchInput implements TouchInput {
+ private static final Logger logger = Logger.getLogger(AndroidTouchInput.class.getName());
+
+ private boolean mouseEventsEnabled = true;
+ private boolean mouseEventsInvertX = false;
+ private boolean mouseEventsInvertY = false;
+ private boolean keyboardEventsEnabled = false;
+ private boolean dontSendHistory = false;
+
+ protected int numPointers = 0;
+ final private HashMap lastPositions = new HashMap();
+ final 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;
+
+ private boolean initialized = false;
+ private RawInputListener listener = null;
+
+ private GestureDetector gestureDetector;
+ private ScaleGestureDetector scaleDetector;
+
+ protected AndroidInputHandler androidInput;
+
+ public AndroidTouchInput(AndroidInputHandler androidInput) {
+ this.androidInput = androidInput;
+ }
+
+ public GestureDetector getGestureDetector() {
+ return gestureDetector;
+ }
+
+ public void setGestureDetector(GestureDetector gestureDetector) {
+ this.gestureDetector = gestureDetector;
+ }
+
+ public ScaleGestureDetector getScaleDetector() {
+ return scaleDetector;
+ }
+
+ public void setScaleDetector(ScaleGestureDetector scaleDetector) {
+ this.scaleDetector = scaleDetector;
+ }
+
+ public float invertX(float origX) {
+ return getJmeX(androidInput.getView().getWidth()) - origX;
+ }
+
+ public float invertY(float origY) {
+ return getJmeY(androidInput.getView().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 (androidInput.getView().getWidth() != 0 && androidInput.getView().getHeight() != 0) {
+ scaleX = (float)settings.getWidth() / (float)androidInput.getView().getWidth();
+ scaleY = (float)settings.getHeight() / (float)androidInput.getView().getHeight();
+ }
+ logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}",
+ new Object[]{scaleX, scaleY});
+
+
+ }
+
+
+ protected int getPointerIndex(MotionEvent event) {
+ return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ protected int getPointerId(MotionEvent event) {
+ return event.getPointerId(getPointerIndex(event));
+ }
+
+ protected int getAction(MotionEvent event) {
+ return event.getAction() & MotionEvent.ACTION_MASK;
+ }
+
+ public boolean onTouch(MotionEvent event) {
+ if (!isInitialized()) {
+ return false;
+ }
+
+ boolean bWasHandled = false;
+ TouchEvent touch = null;
+ // System.out.println("native : " + event.getAction());
+ int action = getAction(event);
+ int pointerIndex = getPointerIndex(event);
+ int pointerId = getPointerId(event);
+ Vector2f lastPos = lastPositions.get(pointerId);
+ float jmeX;
+ float jmeY;
+
+ numPointers = event.getPointerCount();
+
+ // final int historySize = event.getHistorySize();
+ //final int pointerCount = event.getPointerCount();
+ switch (getAction(event)) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_DOWN:
+ jmeX = getJmeX(event.getX(pointerIndex));
+ jmeY = invertY(getJmeY(event.getY(pointerIndex)));
+ touch = getFreeTouchEvent();
+ touch.set(TouchEvent.Type.DOWN, jmeX, jmeY, 0, 0);
+ touch.setPointerId(pointerId);
+ touch.setTime(event.getEventTime());
+ touch.setPressure(event.getPressure(pointerIndex));
+
+ lastPos = new Vector2f(jmeX, jmeY);
+ lastPositions.put(pointerId, lastPos);
+
+ addEvent(touch);
+ addEvent(generateMouseEvent(touch));
+
+ bWasHandled = true;
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ jmeX = getJmeX(event.getX(pointerIndex));
+ jmeY = invertY(getJmeY(event.getY(pointerIndex)));
+ touch = getFreeTouchEvent();
+ touch.set(TouchEvent.Type.UP, jmeX, jmeY, 0, 0);
+ touch.setPointerId(pointerId);
+ touch.setTime(event.getEventTime());
+ touch.setPressure(event.getPressure(pointerIndex));
+ lastPositions.remove(pointerId);
+
+ addEvent(touch);
+ addEvent(generateMouseEvent(touch));
+
+ bWasHandled = true;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Convert all pointers into events
+ for (int p = 0; p < event.getPointerCount(); p++) {
+ jmeX = getJmeX(event.getX(p));
+ jmeY = invertY(getJmeY(event.getY(p)));
+ lastPos = lastPositions.get(event.getPointerId(p));
+ if (lastPos == null) {
+ lastPos = new Vector2f(jmeX, jmeY);
+ lastPositions.put(event.getPointerId(p), lastPos);
+ }
+
+ float dX = jmeX - lastPos.x;
+ float dY = jmeY - lastPos.y;
+ if (dX != 0 || dY != 0) {
+ touch = getFreeTouchEvent();
+ touch.set(TouchEvent.Type.MOVE, jmeX, jmeY, dX, dY);
+ touch.setPointerId(event.getPointerId(p));
+ touch.setTime(event.getEventTime());
+ touch.setPressure(event.getPressure(p));
+ lastPos.set(jmeX, jmeY);
+
+ addEvent(touch);
+ addEvent(generateMouseEvent(touch));
+
+ bWasHandled = true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_OUTSIDE:
+ break;
+
+ }
+
+ // Try to detect gestures
+ if (gestureDetector != null) {
+ gestureDetector.onTouchEvent(event);
+ }
+ if (scaleDetector != null) {
+ scaleDetector.onTouchEvent(event);
+ }
+
+ return bWasHandled;
+ }
+
+ // TODO: Ring Buffer for mouse events?
+ public InputEvent generateMouseEvent(TouchEvent event) {
+ InputEvent inputEvent = null;
+ int newX;
+ int newY;
+ int newDX;
+ int newDY;
+
+ // MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events
+ if (!isSimulateMouse() || numPointers > 1) {
+ return null;
+ }
+
+
+ if (isMouseEventsInvertX()) {
+ newX = (int) (invertX(event.getX()));
+ newDX = (int)event.getDeltaX() * -1;
+ } else {
+ newX = (int) event.getX();
+ newDX = (int)event.getDeltaX();
+ }
+
+ if (isMouseEventsInvertY()) {
+ newY = (int) (invertY(event.getY()));
+ newDY = (int)event.getDeltaY() * -1;
+ } else {
+ newY = (int) event.getY();
+ newDY = (int)event.getDeltaY();
+ }
+
+ switch (event.getType()) {
+ case DOWN:
+ // Handle mouse down event
+ inputEvent = new MouseButtonEvent(0, true, newX, newY);
+ inputEvent.setTime(event.getTime());
+ break;
+
+ case UP:
+ // Handle mouse up event
+ inputEvent = new MouseButtonEvent(0, false, newX, newY);
+ inputEvent.setTime(event.getTime());
+ break;
+
+ case HOVER_MOVE:
+ case MOVE:
+ inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan());
+ inputEvent.setTime(event.getTime());
+ break;
+ }
+
+ return inputEvent;
+ }
+
+
+ public boolean onKey(KeyEvent event) {
+ if (!isInitialized()) {
+ return false;
+ }
+
+ TouchEvent evt;
+ // TODO: get touch event from pool
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ evt = new TouchEvent();
+ evt.set(TouchEvent.Type.KEY_DOWN);
+ evt.setKeyCode(event.getKeyCode());
+ evt.setCharacters(event.getCharacters());
+ evt.setTime(event.getEventTime());
+
+ // Send the event
+ addEvent(evt);
+
+ } else if (event.getAction() == KeyEvent.ACTION_UP) {
+ evt = new TouchEvent();
+ evt.set(TouchEvent.Type.KEY_UP);
+ evt.setKeyCode(event.getKeyCode());
+ evt.setCharacters(event.getCharacters());
+ evt.setTime(event.getEventTime());
+
+ // Send the event
+ addEvent(evt);
+
+ }
+
+ if (isSimulateKeyboard()) {
+ KeyInputEvent kie;
+ char unicodeChar = (char)event.getUnicodeChar();
+ int jmeKeyCode = AndroidKeyMapping.getJmeKey(event.getKeyCode());
+
+ boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN;
+ boolean repeating = pressed && event.getRepeatCount() > 0;
+
+ kie = new KeyInputEvent(jmeKeyCode, unicodeChar, pressed, repeating);
+ kie.setTime(event.getEventTime());
+ addEvent(kie);
+// logger.log(Level.FINE, "onKey keyCode: {0}, jmeKeyCode: {1}, pressed: {2}, repeating: {3}",
+// new Object[]{event.getKeyCode(), jmeKeyCode, pressed, repeating});
+// logger.log(Level.FINE, "creating KeyInputEvent: {0}", kie);
+ }
+
+ // consume all keys ourself except Volume Up/Down and Menu
+ // Don't do Menu so that typical Android Menus can be created and used
+ // by the user in MainActivity
+ if ((event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP) ||
+ (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN) ||
+ (event.getKeyCode() == KeyEvent.KEYCODE_MENU)) {
+ return false;
+ } else {
+ return true;
+ }
+
+ }
+
+
+
+
+ // -----------------------------------------
+ // JME3 Input interface
+ @Override
+ public void initialize() {
+ touchEventPool.initialize();
+
+ initialized = true;
+ }
+
+ @Override
+ public void destroy() {
+ initialized = false;
+
+ touchEventPool.destroy();
+
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ @Override
+ public void setInputListener(RawInputListener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ public long getInputTimeNanos() {
+ return System.nanoTime();
+ }
+
+ @Override
+ 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) {
+ if (event == null) {
+ return;
+ }
+
+ logger.log(Level.INFO, "event: {0}", event);
+
+ inputEventQueue.add(event);
+ if (event instanceof TouchEvent) {
+ touchEventPool.storeEvent((TouchEvent)event);
+ }
+
+ }
+
+ @Override
+ public void setSimulateMouse(boolean simulate) {
+ this.mouseEventsEnabled = simulate;
+ }
+
+ @Override
+ public boolean isSimulateMouse() {
+ return mouseEventsEnabled;
+ }
+
+ public boolean isMouseEventsInvertX() {
+ return mouseEventsInvertX;
+ }
+
+ public boolean isMouseEventsInvertY() {
+ return mouseEventsInvertY;
+ }
+
+ @Override
+ public void setSimulateKeyboard(boolean simulate) {
+ this.keyboardEventsEnabled = simulate;
+ }
+
+ @Override
+ public boolean isSimulateKeyboard() {
+ return keyboardEventsEnabled;
+ }
+
+ @Override
+ public void setOmitHistoricEvents(boolean dontSendHistory) {
+ this.dontSendHistory = dontSendHistory;
+ }
+
+}
diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java
similarity index 67%
rename from jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java
rename to jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java
index 1a785a5e9..617a4b719 100644
--- a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler14.java
+++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java
@@ -33,7 +33,6 @@
package com.jme3.input.android;
import android.view.MotionEvent;
-import android.view.View;
import com.jme3.input.event.TouchEvent;
import com.jme3.math.Vector2f;
import java.util.HashMap;
@@ -41,36 +40,20 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
- * AndroidTouchHandler14 is an extension of AndroidTouchHander that adds the
- * Android touch event functionality between Android rev 9 (Android 2.3) and
- * Android rev 14 (Android 4.0).
- *
+ * AndroidTouchHandler14 extends AndroidTouchHandler to process the onHover
+ * events added in Android rev 14 (Android 4.0).
+ *
* @author iwgeric
*/
-public class AndroidTouchHandler14 extends AndroidTouchHandler implements
- View.OnHoverListener {
- private static final Logger logger = Logger.getLogger(AndroidTouchHandler14.class.getName());
+public class AndroidTouchInput14 extends AndroidTouchInput {
+ private static final Logger logger = Logger.getLogger(AndroidTouchInput14.class.getName());
final private HashMap lastHoverPositions = new HashMap();
-
- public AndroidTouchHandler14(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) {
- super(androidInput, gestureHandler);
- }
- @Override
- public void setView(View view) {
- if (view != null) {
- view.setOnHoverListener(this);
- } else {
- androidInput.getView().setOnHoverListener(null);
- }
- super.setView(view);
+ public AndroidTouchInput14(AndroidInputHandler androidInput) {
+ super(androidInput);
}
-
- public boolean onHover(View view, MotionEvent event) {
- if (view == null || view != androidInput.getView()) {
- return false;
- }
-
+
+ public boolean onHover(MotionEvent event) {
boolean consumed = false;
int action = getAction(event);
int pointerId = getPointerId(event);
@@ -78,34 +61,34 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements
Vector2f lastPos = lastHoverPositions.get(pointerId);
float jmeX;
float jmeY;
-
+
numPointers = event.getPointerCount();
-
- logger.log(Level.INFO, "onHover pointerId: {0}, action: {1}, x: {2}, y: {3}, numPointers: {4}",
- new Object[]{pointerId, action, event.getX(), event.getY(), event.getPointerCount()});
+
+// logger.log(Level.INFO, "onHover pointerId: {0}, action: {1}, x: {2}, y: {3}, numPointers: {4}",
+// new Object[]{pointerId, action, event.getX(), event.getY(), event.getPointerCount()});
TouchEvent touchEvent;
switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
- jmeX = androidInput.getJmeX(event.getX(pointerIndex));
- jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex)));
- touchEvent = androidInput.getFreeTouchEvent();
+ jmeX = getJmeX(event.getX(pointerIndex));
+ jmeY = invertY(getJmeY(event.getY(pointerIndex)));
+ touchEvent = getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.HOVER_START, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(pointerId);
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure(pointerIndex));
-
+
lastPos = new Vector2f(jmeX, jmeY);
lastHoverPositions.put(pointerId, lastPos);
-
- processEvent(touchEvent);
+
+ addEvent(touchEvent);
consumed = true;
break;
case MotionEvent.ACTION_HOVER_MOVE:
// Convert all pointers into events
for (int p = 0; p < event.getPointerCount(); p++) {
- jmeX = androidInput.getJmeX(event.getX(p));
- jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(p)));
+ jmeX = getJmeX(event.getX(p));
+ jmeY = invertY(getJmeY(event.getY(p)));
lastPos = lastHoverPositions.get(event.getPointerId(p));
if (lastPos == null) {
lastPos = new Vector2f(jmeX, jmeY);
@@ -115,38 +98,39 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements
float dX = jmeX - lastPos.x;
float dY = jmeY - lastPos.y;
if (dX != 0 || dY != 0) {
- touchEvent = androidInput.getFreeTouchEvent();
+ touchEvent = getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.HOVER_MOVE, jmeX, jmeY, dX, dY);
touchEvent.setPointerId(event.getPointerId(p));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure(p));
lastPos.set(jmeX, jmeY);
- processEvent(touchEvent);
+ addEvent(touchEvent);
}
}
consumed = true;
break;
case MotionEvent.ACTION_HOVER_EXIT:
- jmeX = androidInput.getJmeX(event.getX(pointerIndex));
- jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex)));
- touchEvent = androidInput.getFreeTouchEvent();
+ jmeX = getJmeX(event.getX(pointerIndex));
+ jmeY = invertY(getJmeY(event.getY(pointerIndex)));
+ touchEvent = getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.HOVER_END, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(pointerId);
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure(pointerIndex));
lastHoverPositions.remove(pointerId);
- processEvent(touchEvent);
+ addEvent(touchEvent);
consumed = true;
break;
default:
consumed = false;
break;
}
-
+
return consumed;
+
}
-
+
}
diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java
index 2a7b54023..62741253a 100644
--- a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java
+++ b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java
@@ -43,6 +43,9 @@ import java.nio.ShortBuffer;
public class AndroidGL implements GL, GLExt {
+ public void resetStats() {
+ }
+
private static int getLimitBytes(ByteBuffer buffer) {
checkLimit(buffer);
return buffer.limit();
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 5ef866188..991ad25c0 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
@@ -47,13 +47,14 @@ import android.widget.EditText;
import android.widget.FrameLayout;
import com.jme3.input.*;
import com.jme3.input.android.AndroidInputHandler;
-import com.jme3.input.android.AndroidJoyInputHandler;
+import com.jme3.input.android.AndroidInputHandler14;
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.GLExt;
+import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.system.*;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -75,7 +76,6 @@ 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;
@@ -111,18 +111,17 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
// Start to set up the view
GLSurfaceView view = new GLSurfaceView(context);
+ logger.log(Level.INFO, "Android Build Version: {0}", Build.VERSION.SDK_INT);
if (androidInput == null) {
- androidInput = new AndroidInputHandler();
+ if (Build.VERSION.SDK_INT >= 14) {
+ androidInput = new AndroidInputHandler14();
+ } else if (Build.VERSION.SDK_INT >= 9){
+ androidInput = new AndroidInputHandler();
+ }
}
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);
@@ -198,7 +197,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
Object gl = new AndroidGL();
// gl = GLTracer.createGlesTracer((GL)gl, (GLExt)gl);
// gl = new GLDebugES((GL)gl, (GLExt)gl);
- renderer = new GLRenderer((GL)gl, (GLExt)gl);
+ renderer = new GLRenderer((GL)gl, (GLExt)gl, (GLFbo)gl);
renderer.initialize();
JmeSystem.setSoftTextDialogInput(this);
@@ -235,9 +234,6 @@ 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
@@ -274,12 +270,12 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
@Override
public JoyInput getJoyInput() {
- return androidJoyInput;
+ return androidInput.getJoyInput();
}
@Override
public TouchInput getTouchInput() {
- return androidInput;
+ return androidInput.getTouchInput();
}
@Override
diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java
index af1367e4a..35a4e5618 100644
--- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java
+++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java
@@ -110,6 +110,8 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
}
}
+ List bestSolution = new ArrayList(bones.size());
+ double bestSolutionDistance = Double.MAX_VALUE;
BoneContext topBone = bones.get(0);
for (int i = 0; i < iterations && distanceFromTarget > MIN_DISTANCE; ++i) {
for (BoneContext boneContext : bones) {
@@ -150,6 +152,20 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
DTransform topBoneTransform = new DTransform(constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD));
Vector3d e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector
distanceFromTarget = e.distance(t);
+
+ if(distanceFromTarget < bestSolutionDistance) {
+ bestSolutionDistance = distanceFromTarget;
+ bestSolution.clear();
+ for(BoneContext boneContext : bones) {
+ bestSolution.add(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD));
+ }
+ }
+ }
+
+ // applying best solution
+ for(int i=0;i
* Improves the quality of environment mapping.
*/
- SeamlessCubemap;
+ SeamlessCubemap,
+
+ /**
+ * Running with OpenGL 3.2+ core profile.
+ *
+ * Compatibility features will not be available.
+ */
+ CoreProfile,
+
+ /**
+ * GPU can provide and accept binary shaders.
+ */
+ BinaryShader;
/**
* Returns true if given the renderer capabilities, the texture
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
index 9c73838e2..76eedb521 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
@@ -32,7 +32,6 @@
package com.jme3.renderer.opengl;
import java.nio.ByteBuffer;
-import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
@@ -178,6 +177,8 @@ public interface GL {
public static final int GL_VERTEX_SHADER = 0x8B31;
public static final int GL_ZERO = 0x0;
+ public void resetStats();
+
public void glActiveTexture(int texture);
public void glAttachShader(int program, int shader);
public void glBindBuffer(int target, int buffer);
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java
index 50eb065ab..190ed4547 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java
@@ -35,14 +35,18 @@ import java.nio.IntBuffer;
/**
* GL functions only available on vanilla desktop OpenGL 3.0.
- *
+ *
* @author Kirill Vainer
*/
public interface GL3 extends GL2 {
-
+
public static final int GL_DEPTH_STENCIL_ATTACHMENT = 0x821A;
- public static final int GL_GEOMETRY_SHADER=0x8DD9;
+ public static final int GL_GEOMETRY_SHADER = 0x8DD9;
+ public static final int GL_NUM_EXTENSIONS = 0x821D;
+
public void glBindFragDataLocation(int param1, int param2, String param3); /// GL3+
public void glBindVertexArray(int param1); /// GL3+
+ public void glDeleteVertexArrays(IntBuffer arrays); /// GL3+
public void glGenVertexArrays(IntBuffer param1); /// GL3+
+ public String glGetString(int param1, int param2); /// GL3+
}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java
index e13402bd5..a0511f6ad 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java
@@ -3,15 +3,17 @@ package com.jme3.renderer.opengl;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
-public class GLDebugDesktop extends GLDebugES implements GL2, GL3 {
+public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 {
private final GL2 gl2;
private final GL3 gl3;
+ private final GL4 gl4;
- public GLDebugDesktop(GL gl, GLFbo glfbo) {
- super(gl, glfbo);
+ public GLDebugDesktop(GL gl, GLExt glext, GLFbo glfbo) {
+ super(gl, glext, glfbo);
this.gl2 = gl instanceof GL2 ? (GL2) gl : null;
this.gl3 = gl instanceof GL3 ? (GL3) gl : null;
+ this.gl4 = gl instanceof GL4 ? (GL4) gl : null;
}
public void glAlphaFunc(int func, float ref) {
@@ -73,5 +75,23 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3 {
gl3.glGenVertexArrays(param1);
checkError();
}
+
+ @Override
+ public String glGetString(int param1, int param2) {
+ String result = gl3.glGetString(param1, param2);
+ checkError();
+ return result;
+ }
+
+ @Override
+ public void glDeleteVertexArrays(IntBuffer arrays) {
+ gl3.glDeleteVertexArrays(arrays);
+ checkError();
+ }
+ @Override
+ public void glPatchParameter(int count) {
+ gl4.glPatchParameter(count);
+ checkError();
+ }
}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java
index 259c56406..2348bd3cd 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java
@@ -10,14 +10,16 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
private final GLFbo glfbo;
private final GLExt glext;
- public GLDebugES(GL gl, GLFbo glfbo) {
+ public GLDebugES(GL gl, GLExt glext, GLFbo glfbo) {
this.gl = gl;
-// this.gl2 = gl instanceof GL2 ? (GL2) gl : null;
-// this.gl3 = gl instanceof GL3 ? (GL3) gl : null;
+ this.glext = glext;
this.glfbo = glfbo;
- this.glext = glfbo instanceof GLExt ? (GLExt) glfbo : null;
}
+ public void resetStats() {
+ gl.resetStats();
+ }
+
public void glActiveTexture(int texture) {
gl.glActiveTexture(texture);
checkError();
@@ -478,7 +480,7 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
}
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
- glext.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
+ glfbo.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
checkError();
}
@@ -525,7 +527,7 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
}
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
- glext.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
+ glfbo.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
checkError();
}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java
index b0e0b5f78..27f0eb8bd 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java
@@ -41,7 +41,7 @@ import java.nio.IntBuffer;
*
* @author Kirill Vainer
*/
-public interface GLExt extends GLFbo {
+public interface GLExt {
public static final int GL_ALREADY_SIGNALED = 0x911A;
public static final int GL_COMPRESSED_RGB8_ETC2 = 0x9274;
@@ -100,7 +100,6 @@ public interface GLExt extends GLFbo {
public static final int GL_UNSIGNED_INT_5_9_9_9_REV_EXT = 0x8C3E;
public static final int GL_WAIT_FAILED = 0x911D;
- public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public void glBufferData(int target, IntBuffer data, int usage);
public void glBufferSubData(int target, long offset, IntBuffer data);
public int glClientWaitSync(Object sync, int flags, long timeout);
@@ -110,7 +109,6 @@ public interface GLExt extends GLFbo {
public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount);
public Object glFenceSync(int condition, int flags);
public void glGetMultisample(int pname, int index, FloatBuffer val);
- public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height);
public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations);
public void glVertexAttribDivisorARB(int index, int divisor);
}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java
index 252619db8..737019ce2 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java
@@ -83,6 +83,7 @@ public interface GLFbo {
public void glBindFramebufferEXT(int param1, int param2);
public void glBindRenderbufferEXT(int param1, int param2);
+ public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public int glCheckFramebufferStatusEXT(int param1);
public void glDeleteFramebuffersEXT(IntBuffer param1);
public void glDeleteRenderbuffersEXT(IntBuffer param1);
@@ -92,5 +93,5 @@ public interface GLFbo {
public void glGenRenderbuffersEXT(IntBuffer param1);
public void glGenerateMipmapEXT(int param1);
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4);
-
+ public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height);
}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
index 423ae909e..a7ef9f52d 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
@@ -89,9 +89,11 @@ public final class GLImageFormats {
GLImageFormat[][] formatToGL = new GLImageFormat[2][Image.Format.values().length];
if (caps.contains(Caps.OpenGL20)) {
- format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
- format(formatToGL, Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
- format(formatToGL, Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
+ if (!caps.contains(Caps.CoreProfile)) {
+ format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
+ format(formatToGL, Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
+ format(formatToGL, Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
+ }
format(formatToGL, Format.RGB8, GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGBA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGB565, GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_SHORT_5_6_5);
@@ -108,8 +110,10 @@ public final class GLImageFormats {
formatSrgb(formatToGL, Format.RGB565, GLExt.GL_SRGB8_EXT, GL.GL_RGB, GL.GL_UNSIGNED_SHORT_5_6_5);
formatSrgb(formatToGL, Format.RGB5A1, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1);
formatSrgb(formatToGL, Format.RGBA8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
- formatSrgb(formatToGL, Format.Luminance8, GLExt.GL_SLUMINANCE8_EXT, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
- formatSrgb(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SLUMINANCE8_ALPHA8_EXT, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
+ if (!caps.contains(Caps.CoreProfile)) {
+ formatSrgb(formatToGL, Format.Luminance8, GLExt.GL_SLUMINANCE8_EXT, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
+ formatSrgb(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SLUMINANCE8_ALPHA8_EXT, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
+ }
formatSrgb(formatToGL, Format.BGR8, GLExt.GL_SRGB8_EXT, GL2.GL_BGR, GL.GL_UNSIGNED_BYTE);
formatSrgb(formatToGL, Format.ABGR8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL2.GL_UNSIGNED_INT_8_8_8_8);
formatSrgb(formatToGL, Format.ARGB8, GLExt.GL_SRGB8_ALPHA8_EXT, GL2.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8);
@@ -124,16 +128,20 @@ public final class GLImageFormats {
}
} else if (caps.contains(Caps.Rgba8)) {
// A more limited form of 32-bit RGBA. Only GL_RGBA8 is available.
- format(formatToGL, Format.Alpha8, GLExt.GL_RGBA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
- format(formatToGL, Format.Luminance8, GLExt.GL_RGBA8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
- format(formatToGL, Format.Luminance8Alpha8, GLExt.GL_RGBA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
+ if (!caps.contains(Caps.CoreProfile)) {
+ format(formatToGL, Format.Alpha8, GLExt.GL_RGBA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
+ format(formatToGL, Format.Luminance8, GLExt.GL_RGBA8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
+ format(formatToGL, Format.Luminance8Alpha8, GLExt.GL_RGBA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
+ }
format(formatToGL, Format.RGB8, GLExt.GL_RGBA8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGBA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
} else {
// Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above..
- format(formatToGL, Format.Alpha8, GL.GL_RGBA4, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
- format(formatToGL, Format.Luminance8, GL.GL_RGB565, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
- format(formatToGL, Format.Luminance8Alpha8, GL.GL_RGBA4, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
+ if (!caps.contains(Caps.CoreProfile)) {
+ format(formatToGL, Format.Alpha8, GL.GL_RGBA4, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
+ format(formatToGL, Format.Luminance8, GL.GL_RGB565, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
+ format(formatToGL, Format.Luminance8Alpha8, GL.GL_RGBA4, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
+ }
format(formatToGL, Format.RGB8, GL.GL_RGB565, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGBA8, GL.GL_RGBA4, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
}
@@ -145,9 +153,11 @@ public final class GLImageFormats {
format(formatToGL, Format.RGB5A1, GL.GL_RGB5_A1, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1);
if (caps.contains(Caps.FloatTexture)) {
- format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, GLExt.GL_HALF_FLOAT_ARB);
- format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT);
- format(formatToGL, Format.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, GLExt.GL_HALF_FLOAT_ARB);
+ if (!caps.contains(Caps.CoreProfile)) {
+ format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, GLExt.GL_HALF_FLOAT_ARB);
+ format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT);
+ format(formatToGL, Format.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, GLExt.GL_HALF_FLOAT_ARB);
+ }
format(formatToGL, Format.RGB16F, GLExt.GL_RGB16F_ARB, GL.GL_RGB, GLExt.GL_HALF_FLOAT_ARB);
format(formatToGL, Format.RGB32F, GLExt.GL_RGB32F_ARB, GL.GL_RGB, GL.GL_FLOAT);
format(formatToGL, Format.RGBA16F, GLExt.GL_RGBA16F_ARB, GL.GL_RGBA, GLExt.GL_HALF_FLOAT_ARB);
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
index 9ae1c8cc7..db9b743f0 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
@@ -56,6 +56,7 @@ import com.jme3.util.BufferUtils;
import com.jme3.util.ListMap;
import com.jme3.util.NativeObjectManager;
import java.nio.*;
+import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
@@ -112,13 +113,13 @@ public class GLRenderer implements Renderer {
private final GLFbo glfbo;
private final TextureUtil texUtil;
- public GLRenderer(GL gl, GLFbo glfbo) {
+ public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) {
this.gl = gl;
this.gl2 = gl instanceof GL2 ? (GL2)gl : null;
this.gl3 = gl instanceof GL3 ? (GL3)gl : null;
this.gl4 = gl instanceof GL4 ? (GL4)gl : null;
this.glfbo = glfbo;
- this.glext = glfbo instanceof GLExt ? (GLExt)glfbo : null;
+ this.glext = glext;
this.texUtil = new TextureUtil(gl, gl2, glext, context);
}
@@ -137,10 +138,19 @@ public class GLRenderer implements Renderer {
return limits;
}
- private static HashSet loadExtensions(String extensions) {
+ private HashSet loadExtensions() {
HashSet extensionSet = new HashSet(64);
- for (String extension : extensions.split(" ")) {
- extensionSet.add(extension);
+ if (gl3 != null) {
+ // If OpenGL3+ is available, use the non-deprecated way
+ // of getting supported extensions.
+ gl3.glGetInteger(GL3.GL_NUM_EXTENSIONS, intBuf16);
+ int extensionCount = intBuf16.get(0);
+ for (int i = 0; i < extensionCount; i++) {
+ String extension = gl3.glGetString(GL.GL_EXTENSIONS, i);
+ extensionSet.add(extension);
+ }
+ } else {
+ extensionSet.addAll(Arrays.asList(gl.glGetString(GL.GL_EXTENSIONS).split(" ")));
}
return extensionSet;
}
@@ -185,10 +195,12 @@ public class GLRenderer implements Renderer {
caps.add(Caps.OpenGL31);
if (oglVer >= 320) {
caps.add(Caps.OpenGL32);
- }if(oglVer>=330){
+ }
+ if (oglVer >= 330) {
caps.add(Caps.OpenGL33);
caps.add(Caps.GeometryShader);
- }if(oglVer>=400){
+ }
+ if (oglVer >= 400) {
caps.add(Caps.OpenGL40);
caps.add(Caps.TesselationShader);
}
@@ -243,7 +255,7 @@ public class GLRenderer implements Renderer {
}
private void loadCapabilitiesCommon() {
- extensions = loadExtensions(gl.glGetString(GL.GL_EXTENSIONS));
+ extensions = loadExtensions();
limits.put(Limits.VertexTextureUnits, getInteger(GL.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS));
if (limits.get(Limits.VertexTextureUnits) > 0) {
@@ -251,7 +263,7 @@ public class GLRenderer implements Renderer {
}
limits.put(Limits.FragmentTextureUnits, getInteger(GL.GL_MAX_TEXTURE_IMAGE_UNITS));
-
+
// gl.glGetInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16);
// vertexUniforms = intBuf16.get(0);
// logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms);
@@ -279,7 +291,7 @@ public class GLRenderer implements Renderer {
// == texture format extensions ==
- boolean hasFloatTexture = false;
+ boolean hasFloatTexture;
hasFloatTexture = hasExtension("GL_OES_texture_half_float") &&
hasExtension("GL_OES_texture_float");
@@ -375,11 +387,11 @@ public class GLRenderer implements Renderer {
caps.add(Caps.TextureFilterAnisotropic);
}
- if (hasExtension("GL_EXT_framebuffer_object")) {
+ if (hasExtension("GL_EXT_framebuffer_object") || gl3 != null) {
caps.add(Caps.FrameBuffer);
- limits.put(Limits.RenderBufferSize, getInteger(GLExt.GL_MAX_RENDERBUFFER_SIZE_EXT));
- limits.put(Limits.FrameBufferAttachments, getInteger(GLExt.GL_MAX_COLOR_ATTACHMENTS_EXT));
+ limits.put(Limits.RenderBufferSize, getInteger(GLFbo.GL_MAX_RENDERBUFFER_SIZE_EXT));
+ limits.put(Limits.FrameBufferAttachments, getInteger(GLFbo.GL_MAX_COLOR_ATTACHMENTS_EXT));
if (hasExtension("GL_EXT_framebuffer_blit")) {
caps.add(Caps.FrameBufferBlit);
@@ -434,21 +446,30 @@ public class GLRenderer implements Renderer {
caps.add(Caps.SeamlessCubemap);
}
-// if (hasExtension("GL_ARB_get_program_binary")) {
-// int binaryFormats = getInteger(GLExt.GL_NUM_PROGRAM_BINARY_FORMATS);
-// }
+ if (caps.contains(Caps.OpenGL32) && !hasExtension("GL_ARB_compatibility")) {
+ caps.add(Caps.CoreProfile);
+ }
+
+ if (hasExtension("GL_ARB_get_program_binary")) {
+ int binaryFormats = getInteger(GLExt.GL_NUM_PROGRAM_BINARY_FORMATS);
+ if (binaryFormats > 0) {
+ caps.add(Caps.BinaryShader);
+ }
+ }
// Print context information
logger.log(Level.INFO, "OpenGL Renderer Information\n" +
" * Vendor: {0}\n" +
" * Renderer: {1}\n" +
" * OpenGL Version: {2}\n" +
- " * GLSL Version: {3}",
+ " * GLSL Version: {3}\n" +
+ " * Profile: {4}",
new Object[]{
gl.glGetString(GL.GL_VENDOR),
gl.glGetString(GL.GL_RENDERER),
gl.glGetString(GL.GL_VERSION),
- gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION)
+ gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION),
+ caps.contains(Caps.CoreProfile) ? "Core" : "Compatibility"
});
// Print capabilities (if fine logging is enabled)
@@ -491,6 +512,20 @@ public class GLRenderer implements Renderer {
// Initialize default state..
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
+
+ if (caps.contains(Caps.CoreProfile)) {
+ // Core Profile requires VAO to be bound.
+ gl3.glGenVertexArrays(intBuf16);
+ int vaoId = intBuf16.get(0);
+ gl3.glBindVertexArray(vaoId);
+ }
+ if (gl2 != null) {
+ gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
+ if (!caps.contains(Caps.CoreProfile)) {
+ gl2.glEnable(GL2.GL_POINT_SPRITE);
+ context.pointSprite = true;
+ }
+ }
}
public void invalidateState() {
@@ -610,31 +645,6 @@ public class GLRenderer implements Renderer {
context.colorWriteEnabled = false;
}
- if (gl2 != null) {
- if (state.isPointSprite() && !context.pointSprite) {
- // Only enable/disable sprite
- if (context.boundTextures[0] != null) {
- if (context.boundTextureUnit != 0) {
- gl.glActiveTexture(GL.GL_TEXTURE0);
- context.boundTextureUnit = 0;
- }
- gl2.glEnable(GL2.GL_POINT_SPRITE);
- gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
- }
- context.pointSprite = true;
- } else if (!state.isPointSprite() && context.pointSprite) {
- if (context.boundTextures[0] != null) {
- if (context.boundTextureUnit != 0) {
- gl.glActiveTexture(GL.GL_TEXTURE0);
- context.boundTextureUnit = 0;
- }
- gl2.glDisable(GL2.GL_POINT_SPRITE);
- gl2.glDisable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
- context.pointSprite = false;
- }
- }
- }
-
if (state.isPolyOffset()) {
if (!context.polyOffsetEnabled) {
gl.glEnable(GL.GL_POLYGON_OFFSET_FILL);
@@ -704,9 +714,6 @@ public class GLRenderer implements Renderer {
case AlphaAdditive:
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
break;
- case Color:
- gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR);
- break;
case Alpha:
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
break;
@@ -719,6 +726,7 @@ public class GLRenderer implements Renderer {
case ModulateX2:
gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR);
break;
+ case Color:
case Screen:
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR);
break;
@@ -862,6 +870,7 @@ public class GLRenderer implements Renderer {
public void postFrame() {
objManager.deleteUnused(this);
+ gl.resetStats();
}
/*********************************************************************\
@@ -1290,24 +1299,24 @@ public class GLRenderer implements Renderer {
}
if (src == null) {
- glfbo.glBindFramebufferEXT(GLExt.GL_READ_FRAMEBUFFER_EXT, 0);
+ glfbo.glBindFramebufferEXT(GLFbo.GL_READ_FRAMEBUFFER_EXT, 0);
srcX0 = vpX;
srcY0 = vpY;
srcX1 = vpX + vpW;
srcY1 = vpY + vpH;
} else {
- glfbo.glBindFramebufferEXT(GLExt.GL_READ_FRAMEBUFFER_EXT, src.getId());
+ glfbo.glBindFramebufferEXT(GLFbo.GL_READ_FRAMEBUFFER_EXT, src.getId());
srcX1 = src.getWidth();
srcY1 = src.getHeight();
}
if (dst == null) {
- glfbo.glBindFramebufferEXT(GLExt.GL_DRAW_FRAMEBUFFER_EXT, 0);
+ glfbo.glBindFramebufferEXT(GLFbo.GL_DRAW_FRAMEBUFFER_EXT, 0);
dstX0 = vpX;
dstY0 = vpY;
dstX1 = vpX + vpW;
dstY1 = vpY + vpH;
} else {
- glfbo.glBindFramebufferEXT(GLExt.GL_DRAW_FRAMEBUFFER_EXT, dst.getId());
+ glfbo.glBindFramebufferEXT(GLFbo.GL_DRAW_FRAMEBUFFER_EXT, dst.getId());
dstX1 = dst.getWidth();
dstY1 = dst.getHeight();
}
@@ -1315,12 +1324,12 @@ public class GLRenderer implements Renderer {
if (copyDepth) {
mask |= GL.GL_DEPTH_BUFFER_BIT;
}
- glext.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1,
+ glfbo.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1,
dstX0, dstY0, dstX1, dstY1, mask,
GL.GL_NEAREST);
- glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, prevFBO);
+ glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, prevFBO);
} else {
throw new RendererException("Framebuffer blitting not supported by the video hardware");
}
@@ -1366,7 +1375,7 @@ public class GLRenderer implements Renderer {
}
if (context.boundRB != id) {
- glfbo.glBindRenderbufferEXT(GLExt.GL_RENDERBUFFER_EXT, id);
+ glfbo.glBindRenderbufferEXT(GLFbo.GL_RENDERBUFFER_EXT, id);
context.boundRB = id;
}
@@ -1384,13 +1393,13 @@ public class GLRenderer implements Renderer {
if (maxSamples < samples) {
samples = maxSamples;
}
- glext.glRenderbufferStorageMultisampleEXT(GLExt.GL_RENDERBUFFER_EXT,
+ glfbo.glRenderbufferStorageMultisampleEXT(GLFbo.GL_RENDERBUFFER_EXT,
samples,
glFmt.internalFormat,
fb.getWidth(),
fb.getHeight());
} else {
- glfbo.glRenderbufferStorageEXT(GLExt.GL_RENDERBUFFER_EXT,
+ glfbo.glRenderbufferStorageEXT(GLFbo.GL_RENDERBUFFER_EXT,
glFmt.internalFormat,
fb.getWidth(),
fb.getHeight());
@@ -1400,7 +1409,7 @@ public class GLRenderer implements Renderer {
private int convertAttachmentSlot(int attachmentSlot) {
// can also add support for stencil here
if (attachmentSlot == FrameBuffer.SLOT_DEPTH) {
- return GLExt.GL_DEPTH_ATTACHMENT_EXT;
+ return GLFbo.GL_DEPTH_ATTACHMENT_EXT;
} else if (attachmentSlot == FrameBuffer.SLOT_DEPTH_STENCIL) {
// NOTE: Using depth stencil format requires GL3, this is already
// checked via render caps.
@@ -1409,7 +1418,7 @@ public class GLRenderer implements Renderer {
throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot);
}
- return GLExt.GL_COLOR_ATTACHMENT0_EXT + attachmentSlot;
+ return GLFbo.GL_COLOR_ATTACHMENT0_EXT + attachmentSlot;
}
public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) {
@@ -1427,7 +1436,7 @@ public class GLRenderer implements Renderer {
setupTextureParams(tex);
}
- glfbo.glFramebufferTexture2DEXT(GLExt.GL_FRAMEBUFFER_EXT,
+ glfbo.glFramebufferTexture2DEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()),
convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()),
image.getId(),
@@ -1445,9 +1454,9 @@ public class GLRenderer implements Renderer {
updateRenderTexture(fb, rb);
}
if (needAttach) {
- glfbo.glFramebufferRenderbufferEXT(GLExt.GL_FRAMEBUFFER_EXT,
+ glfbo.glFramebufferRenderbufferEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()),
- GLExt.GL_RENDERBUFFER_EXT,
+ GLFbo.GL_RENDERBUFFER_EXT,
rb.getId());
}
}
@@ -1465,7 +1474,7 @@ public class GLRenderer implements Renderer {
}
if (context.boundFBO != id) {
- glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, id);
+ glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, id);
// binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0
context.boundDrawBuf = 0;
context.boundFBO = id;
@@ -1545,7 +1554,7 @@ public class GLRenderer implements Renderer {
if (fb == null) {
// unbind any fbos
if (context.boundFBO != 0) {
- glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, 0);
+ glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, 0);
statistics.onFrameBufferUse(null, true);
context.boundFBO = 0;
@@ -1577,7 +1586,7 @@ public class GLRenderer implements Renderer {
setViewPort(0, 0, fb.getWidth(), fb.getHeight());
if (context.boundFBO != fb.getId()) {
- glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, fb.getId());
+ glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, fb.getId());
statistics.onFrameBufferUse(fb, true);
context.boundFBO = fb.getId();
@@ -1617,7 +1626,7 @@ public class GLRenderer implements Renderer {
if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()) {
intBuf16.clear();
for (int i = 0; i < fb.getNumColorBuffers(); i++) {
- intBuf16.put(GLExt.GL_COLOR_ATTACHMENT0_EXT + i);
+ intBuf16.put(GLFbo.GL_COLOR_ATTACHMENT0_EXT + i);
}
intBuf16.flip();
@@ -1629,7 +1638,7 @@ public class GLRenderer implements Renderer {
// select this draw buffer
if (gl2 != null) {
if (context.boundDrawBuf != rb.getSlot()) {
- gl2.glDrawBuffer(GLExt.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());
+ gl2.glDrawBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());
context.boundDrawBuf = rb.getSlot();
}
}
@@ -1658,7 +1667,7 @@ public class GLRenderer implements Renderer {
setFrameBuffer(fb);
if (gl2 != null) {
if (context.boundReadBuf != rb.getSlot()) {
- gl2.glReadBuffer(GLExt.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());
+ gl2.glReadBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot());
context.boundReadBuf = rb.getSlot();
}
}
@@ -1682,7 +1691,7 @@ public class GLRenderer implements Renderer {
public void deleteFrameBuffer(FrameBuffer fb) {
if (fb.getId() != -1) {
if (context.boundFBO == fb.getId()) {
- glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, 0);
+ glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, 0);
context.boundFBO = 0;
}
@@ -2620,32 +2629,13 @@ public class GLRenderer implements Renderer {
return;
}
- if (context.pointSprite && mesh.getMode() != Mode.Points) {
- // XXX: Hack, disable point sprite mode if mesh not in point mode
- if (context.boundTextures[0] != null) {
- if (context.boundTextureUnit != 0) {
- gl.glActiveTexture(GL.GL_TEXTURE0);
- context.boundTextureUnit = 0;
- }
- if (gl2 != null) {
- gl2.glDisable(GL2.GL_POINT_SPRITE);
- gl2.glDisable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
- }
- context.pointSprite = false;
- }
- }
- if (gl2 != null) {
- if (context.pointSize != mesh.getPointSize()) {
- gl2.glPointSize(mesh.getPointSize());
- context.pointSize = mesh.getPointSize();
- }
- }
if (context.lineWidth != mesh.getLineWidth()) {
gl.glLineWidth(mesh.getLineWidth());
context.lineWidth = mesh.getLineWidth();
}
- if(gl4!=null && mesh.getMode().equals(Mode.Patch)){
+
+ if (gl4 != null && mesh.getMode().equals(Mode.Patch)) {
gl4.glPatchParameter(mesh.getPatchVertexCount());
}
statistics.onMeshDrawn(mesh, lod, count);
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java
new file mode 100644
index 000000000..7e6833b8b
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java
@@ -0,0 +1,122 @@
+/*
+ * 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.renderer.opengl;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Map;
+
+public class GLTiming implements InvocationHandler {
+
+ private final Object obj;
+ private final GLTimingState state;
+
+ public GLTiming(Object obj, GLTimingState state) {
+ this.obj = obj;
+ this.state = state;
+ }
+
+ public static Object createGLTiming(Object glInterface, GLTimingState state, Class> ... glInterfaceClasses) {
+ return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(),
+ glInterfaceClasses,
+ new GLTiming(glInterface, state));
+ }
+
+ private static class CallTimingComparator implements Comparator> {
+ @Override
+ public int compare(Map.Entry o1, Map.Entry o2) {
+ return (int) (o2.getValue() - o1.getValue());
+ }
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ String methodName = method.getName();
+ if (methodName.equals("resetStats")) {
+ if (state.lastPrintOutTime + 1000000000 <= System.nanoTime() && state.sampleCount > 0) {
+ state.timeSpentInGL /= state.sampleCount;
+ System.out.println("--- TOTAL TIME SPENT IN GL CALLS: " + (state.timeSpentInGL/1000) + "us");
+
+ Map.Entry[] callTimes = new Map.Entry[state.callTiming.size()];
+ int i = 0;
+ for (Map.Entry callTime : state.callTiming.entrySet()) {
+ callTimes[i++] = callTime;
+ }
+ Arrays.sort(callTimes, new CallTimingComparator());
+ int limit = 10;
+ for (Map.Entry callTime : callTimes) {
+ long val = callTime.getValue() / state.sampleCount;
+ String name = callTime.getKey();
+ String pad = " ".substring(0, 30 - name.length());
+ System.out.println("\t" + callTime.getKey() + pad + (val/1000) + "us");
+ if (limit-- == 0) break;
+ }
+ for (Map.Entry callTime : callTimes) {
+ state.callTiming.put(callTime.getKey(), Long.valueOf(0));
+ }
+
+ state.sampleCount = 0;
+ state.timeSpentInGL = 0;
+ state.lastPrintOutTime = System.nanoTime();
+ } else {
+ state.sampleCount++;
+ }
+ return null;
+ } else {
+ Long currentTimeObj = state.callTiming.get(methodName);
+ long currentTime = 0;
+ if (currentTimeObj != null) currentTime = currentTimeObj;
+
+
+ long startTime = System.nanoTime();
+ Object result = method.invoke(obj, args);
+ long delta = System.nanoTime() - startTime;
+
+ currentTime += delta;
+ state.timeSpentInGL += delta;
+
+ state.callTiming.put(methodName, currentTime);
+
+ if (delta > 1000000 && !methodName.equals("glClear")) {
+ // More than 1ms
+ // Ignore glClear as it cannot be avoided.
+ System.out.println("GL call " + methodName + " took " + (delta/1000) + "us to execute!");
+ }
+
+ return result;
+ }
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java
new file mode 100644
index 000000000..ca25810d4
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java
@@ -0,0 +1,41 @@
+/*
+ * 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.renderer.opengl;
+
+import java.util.HashMap;
+
+public class GLTimingState {
+ long timeSpentInGL = 0;
+ int sampleCount = 0;
+ long lastPrintOutTime = 0;
+ final HashMap callTiming = new HashMap();
+}
\ No newline at end of file
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java
index b69d524b4..1b0d70749 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java
@@ -64,6 +64,7 @@ public final class GLTracer implements InvocationHandler {
noEnumArgs("glScissor", 0, 1, 2, 3);
noEnumArgs("glClear", 0);
noEnumArgs("glGetInteger", 1);
+ noEnumArgs("glGetString", 1);
noEnumArgs("glBindTexture", 1);
noEnumArgs("glPixelStorei", 1);
@@ -95,8 +96,6 @@ public final class GLTracer implements InvocationHandler {
noEnumArgs("glFramebufferTexture2DEXT", 3, 4);
noEnumArgs("glBlitFramebufferEXT", 0, 1, 2, 3, 4, 5, 6, 7, 8);
-
-
noEnumArgs("glCreateProgram", -1);
noEnumArgs("glCreateShader", -1);
noEnumArgs("glShaderSource", 0);
@@ -155,7 +154,7 @@ public final class GLTracer implements InvocationHandler {
* @return A tracer that implements the given interface
*/
public static Object createGlesTracer(Object glInterface, Class> glInterfaceClass) {
- IntMap constMap = generateConstantMap(GL.class, GLExt.class);
+ IntMap constMap = generateConstantMap(GL.class, GLFbo.class, GLExt.class);
return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(),
new Class>[] { glInterfaceClass },
new GLTracer(glInterface, constMap));
@@ -169,7 +168,7 @@ public final class GLTracer implements InvocationHandler {
* @return A tracer that implements the given interface
*/
public static Object createDesktopGlTracer(Object glInterface, Class> ... glInterfaceClasses) {
- IntMap constMap = generateConstantMap(GL2.class, GLExt.class);
+ IntMap constMap = generateConstantMap(GL2.class, GL3.class, GL4.class, GLFbo.class, GLExt.class);
return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(),
glInterfaceClasses,
new GLTracer(glInterface, constMap));
diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java
index ed53e89bc..a7a53b915 100644
--- a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java
+++ b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java
@@ -127,6 +127,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
unIndent();
startCondition(shaderNode.getCondition(), source);
source.append(nodeSource);
+ source.append("\n");
endCondition(shaderNode.getCondition(), source);
indent();
}
diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java
index 2bbf746ff..96ab62819 100644
--- a/jme3-core/src/main/java/com/jme3/texture/Image.java
+++ b/jme3-core/src/main/java/com/jme3/texture/Image.java
@@ -421,7 +421,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
/**
* @return True if the image needs to have mipmaps generated
- * for it (as requested by the texture).
+ * for it (as requested by the texture). This stays true even
+ * after mipmaps have been generated.
*/
public boolean isGeneratedMipmapsRequired() {
return needGeneratedMips;
@@ -434,8 +435,9 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
@Override
public void setUpdateNeeded() {
super.setUpdateNeeded();
- if (!isGeneratedMipmapsRequired() && !hasMipmaps()) {
- setNeedGeneratedMipmaps();
+ if (isGeneratedMipmapsRequired() && !hasMipmaps()) {
+ // Mipmaps are no longer valid, since the image was changed.
+ setMipmapsGenerated(false);
}
}
@@ -527,11 +529,13 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
this();
- if (mipMapSizes != null && mipMapSizes.length <= 1) {
- mipMapSizes = null;
- } else {
- needGeneratedMips = false;
- mipsWereGenerated = true;
+ if (mipMapSizes != null) {
+ if (mipMapSizes.length <= 1) {
+ mipMapSizes = null;
+ } else {
+ needGeneratedMips = false;
+ mipsWereGenerated = true;
+ }
}
setFormat(format);
@@ -787,8 +791,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
needGeneratedMips = false;
mipsWereGenerated = false;
} else {
- needGeneratedMips = false;
- mipsWereGenerated = true;
+ needGeneratedMips = true;
+ mipsWereGenerated = false;
}
setUpdateNeeded();
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
index 5d207cf83..26b68a533 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
+++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
@@ -72,17 +72,19 @@ uniform float m_Shininess;
#endif
void main(){
- #ifdef NORMALMAP
- mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz));
+ #if !defined(VERTEX_LIGHTING)
+ #if defined(NORMALMAP)
+ mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz));
- if (!gl_FrontFacing)
- {
- tbnMat[2] = -tbnMat[2];
- }
+ if (!gl_FrontFacing)
+ {
+ tbnMat[2] = -tbnMat[2];
+ }
- vec3 viewDir = normalize(-vPos.xyz * tbnMat);
- #else
- vec3 viewDir = normalize(-vPos.xyz);
+ vec3 viewDir = normalize(-vPos.xyz * tbnMat);
+ #else
+ vec3 viewDir = normalize(-vPos.xyz);
+ #endif
#endif
vec2 newTexCoord;
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
index 62b206b7e..6ad224d9b 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
+++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
@@ -45,6 +45,9 @@ attribute vec3 inNormal;
#else
varying vec3 specularAccum;
varying vec4 diffuseAccum;
+ #ifdef COLORRAMP
+ uniform sampler2D m_ColorRamp;
+ #endif
#endif
#ifdef USE_REFLECTION
@@ -160,14 +163,14 @@ void main(){
#if __VERSION__ >= 110
}
#endif
- vec2 v = computeLighting(wvNormal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess);
+ vec2 light = computeLighting(wvNormal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess);
#ifdef COLORRAMP
- diffuseAccum += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor;
- specularAccum += texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb * specularColor;
+ diffuseAccum.rgb += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor.rgb;
+ specularAccum.rgb += texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb * specularColor;
#else
- diffuseAccum += v.x * diffuseColor;
- specularAccum += v.y * specularColor;
+ diffuseAccum.rgb += light.x * diffuseColor.rgb;
+ specularAccum.rgb += light.y * specularColor;
#endif
}
#endif
diff --git a/jme3-core/src/main/resources/joystick-mapping.properties b/jme3-core/src/main/resources/joystick-mapping.properties
index 5f29e4105..d7ce2f50a 100644
--- a/jme3-core/src/main/resources/joystick-mapping.properties
+++ b/jme3-core/src/main/resources/joystick-mapping.properties
@@ -6,7 +6,7 @@
# the new name as it will be reported through the Joystick
# interface.
#
-# Keys with spaces in them should have those spaces escaped.
+# Keys with spaces in them should have those spaces escaped.
# Values do not need their spaces escaped. For example:
#
# Some\ Joystick.0=3
@@ -37,22 +37,29 @@ Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).ry=rz
# requires custom code to support trigger buttons but this
# keeps it from confusing the .rx mapping.
Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).z=trigger
-
+
# Xbox 360 Controller (copied from wireless version)
Controller\ (XBOX\ 360\ For\ Windows).0=2
Controller\ (XBOX\ 360\ For\ Windows).1=1
Controller\ (XBOX\ 360\ For\ Windows).2=3
Controller\ (XBOX\ 360\ For\ Windows).3=0
-
+
Controller\ (XBOX\ 360\ For\ Windows).6=8
Controller\ (XBOX\ 360\ For\ Windows).7=9
-
+
Controller\ (XBOX\ 360\ For\ Windows).8=10
Controller\ (XBOX\ 360\ For\ Windows).9=11
-
+
Controller\ (XBOX\ 360\ For\ Windows).rx=z
Controller\ (XBOX\ 360\ For\ Windows).ry=rz
-
+
# requires custom code to support trigger buttons but this
# keeps it from confusing the .rx mapping.
Controller\ (XBOX\ 360\ For\ Windows).z=trigger
+
+# XBOX 360 Controller connected to Android using
+# the USB dongle
+Xbox\ 360\ Wireless\ Receiver.AXIS_RX=z
+Xbox\ 360\ Wireless\ Receiver.AXIS_RY=rz
+Xbox\ 360\ Wireless\ Receiver.z=AXIS_RX
+Xbox\ 360\ Wireless\ Receiver.rz=AXIS_RY
diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
index 5d7f5ca8c..0c81c3e14 100644
--- a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
+++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
@@ -569,10 +569,15 @@ public class J3MLoader implements AssetLoader {
public Object load(AssetInfo info) throws IOException {
this.assetManager = info.getManager();
-
+
InputStream in = info.openStream();
try {
- key = info.getKey();
+ key = info.getKey();
+ if (key.getExtension().equals("j3m") && !(key instanceof MaterialKey)) {
+ throw new IOException("Material instances must be loaded via MaterialKey");
+ } else if (key.getExtension().equals("j3md") && key instanceof MaterialKey) {
+ throw new IOException("Material definitions must be loaded via AssetKey");
+ }
loadFromRoot(BlockLanguageParser.parse(in));
} finally {
if (in != null){
@@ -581,9 +586,6 @@ public class J3MLoader implements AssetLoader {
}
if (material != null){
- if (!(info.getKey() instanceof MaterialKey)){
- throw new IOException("Material instances must be loaded via MaterialKey");
- }
// material implementation
return material;
}else{
diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java
index 9c71aa132..726cf3e19 100644
--- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java
+++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java
@@ -242,21 +242,12 @@ public class DXTFlipper {
img.position(blockByteOffset);
img.limit(blockByteOffset + bpb);
- img.get(colorBlock);
- if (type == 4 || type == 5)
- flipDXT5Block(colorBlock, h);
- else
- flipDXT1orDXTA3Block(colorBlock, h);
-
- // write block (no need to flip block indexes, only pixels
- // inside block
- retImg.put(colorBlock);
-
if (alphaBlock != null){
img.get(alphaBlock);
switch (type){
case 2:
- flipDXT3Block(alphaBlock, h); break;
+ flipDXT3Block(alphaBlock, h);
+ break;
case 3:
case 4:
flipDXT5Block(alphaBlock, h);
@@ -264,6 +255,16 @@ public class DXTFlipper {
}
retImg.put(alphaBlock);
}
+
+ img.get(colorBlock);
+ if (type == 4 || type == 5)
+ flipDXT5Block(colorBlock, h);
+ else
+ flipDXT1orDXTA3Block(colorBlock, h);
+
+ // write block (no need to flip block indexes, only pixels
+ // inside block
+ retImg.put(colorBlock);
}
retImg.rewind();
}else if (h >= 4){
diff --git a/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java b/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java
index 75d6b1c86..7e9012b6d 100644
--- a/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java
+++ b/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java
@@ -162,8 +162,8 @@ public class SSAOFilter extends Filter {
};
ssaoPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth, 1, ssaoMat);
- ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear);
- ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);
+// ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear);
+// ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);
postRenderPasses.add(ssaoPass);
material = new Material(manager, "Common/MatDefs/SSAO/ssaoBlur.j3md");
material.setTexture("SSAOMap", ssaoPass.getRenderedTexture());
diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.frag b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.frag
index 07a4c6429..cb393dbaa 100644
--- a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.frag
+++ b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.frag
@@ -413,10 +413,6 @@ void main(){
// to calculate the derivatives for all these pixels by using step()!
// That way we won't get pixels around the edges of the terrain,
// Where the derivatives are undefined
- if(position.y > level){
- color = color2;
- }
-
- gl_FragColor = vec4(color,1.0);
+ gl_FragColor = vec4(mix(color, color2, step(level, position.y)), 1.0);
}
\ No newline at end of file
diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md
index 2f4b2b39c..2f188934b 100644
--- a/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md
+++ b/jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md
@@ -77,6 +77,7 @@ MaterialDef Advanced Water {
FragmentShader GLSL120 : Common/MatDefs/Water/Water.frag
WorldParameters {
+ ViewProjectionMatrixInverse
}
Defines {
ENABLE_RIPPLES : UseRipples
diff --git a/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg b/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg
index 715eab985..4e4052447 100644
--- a/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg
+++ b/jme3-ios/src/main/java/com/jme3/asset/IOS.cfg
@@ -2,3 +2,7 @@ INCLUDE com/jme3/asset/General.cfg
# IOS specific loaders
LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg
+LOADER com.jme3.material.plugins.J3MLoader : j3m, j3md
+LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib
+LOADER com.jme3.export.binary.BinaryImporter : j3o
+LOADER com.jme3.font.plugins.BitmapFontLoader : fnt
diff --git a/jme3-ios/src/main/java/com/jme3/audio/android/AL.java b/jme3-ios/src/main/java/com/jme3/audio/android/AL.java
deleted file mode 100644
index d8fea3933..000000000
--- a/jme3-ios/src/main/java/com/jme3/audio/android/AL.java
+++ /dev/null
@@ -1,1054 +0,0 @@
-package com.jme3.audio.android;
-
-/**
- *
- * @author iwgeric
- */
-public class AL {
-
-
-
- /* ********** */
- /* FROM ALC.h */
- /* ********** */
-
-// typedef struct ALCdevice_struct ALCdevice;
-// typedef struct ALCcontext_struct ALCcontext;
-
-
- /**
- * No error
- */
- static final int ALC_NO_ERROR = 0;
-
- /**
- * No device
- */
- static final int ALC_INVALID_DEVICE = 0xA001;
-
- /**
- * invalid context ID
- */
- static final int ALC_INVALID_CONTEXT = 0xA002;
-
- /**
- * bad enum
- */
- static final int ALC_INVALID_ENUM = 0xA003;
-
- /**
- * bad value
- */
- static final int ALC_INVALID_VALUE = 0xA004;
-
- /**
- * Out of memory.
- */
- static final int ALC_OUT_OF_MEMORY = 0xA005;
-
-
- /**
- * The Specifier string for default device
- */
- static final int ALC_DEFAULT_DEVICE_SPECIFIER = 0x1004;
- static final int ALC_DEVICE_SPECIFIER = 0x1005;
- static final int ALC_EXTENSIONS = 0x1006;
-
- static final int ALC_MAJOR_VERSION = 0x1000;
- static final int ALC_MINOR_VERSION = 0x1001;
-
- static final int ALC_ATTRIBUTES_SIZE = 0x1002;
- static final int ALC_ALL_ATTRIBUTES = 0x1003;
-
-
- /**
- * Capture extension
- */
- static final int ALC_EXT_CAPTURE = 1;
- static final int ALC_CAPTURE_DEVICE_SPECIFIER = 0x310;
- static final int ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER = 0x311;
- static final int ALC_CAPTURE_SAMPLES = 0x312;
-
-
- /**
- * ALC_ENUMERATE_ALL_EXT enums
- */
- static final int ALC_ENUMERATE_ALL_EXT = 1;
- static final int ALC_DEFAULT_ALL_DEVICES_SPECIFIER = 0x1012;
- static final int ALC_ALL_DEVICES_SPECIFIER = 0x1013;
-
-
- /* ********** */
- /* FROM AL.h */
- /* ********** */
-
-/** Boolean False. */
- static final int AL_FALSE = 0;
-
-/** Boolean True. */
- static final int AL_TRUE = 1;
-
-/* "no distance model" or "no buffer" */
- static final int AL_NONE = 0;
-
-/** Indicate Source has relative coordinates. */
- static final int AL_SOURCE_RELATIVE = 0x202;
-
-
-
-/**
- * Directional source, inner cone angle, in degrees.
- * Range: [0-360]
- * Default: 360
- */
- static final int AL_CONE_INNER_ANGLE = 0x1001;
-
-/**
- * Directional source, outer cone angle, in degrees.
- * Range: [0-360]
- * Default: 360
- */
- static final int AL_CONE_OUTER_ANGLE = 0x1002;
-
-/**
- * Specify the pitch to be applied at source.
- * Range: [0.5-2.0]
- * Default: 1.0
- */
- static final int AL_PITCH = 0x1003;
-
-/**
- * Specify the current location in three dimensional space.
- * OpenAL, like OpenGL, uses a right handed coordinate system,
- * where in a frontal default view X (thumb) points right,
- * Y points up (index finger), and Z points towards the
- * viewer/camera (middle finger).
- * To switch from a left handed coordinate system, flip the
- * sign on the Z coordinate.
- * Listener position is always in the world coordinate system.
- */
- static final int AL_POSITION = 0x1004;
-
-/** Specify the current direction. */
- static final int AL_DIRECTION = 0x1005;
-
-/** Specify the current velocity in three dimensional space. */
- static final int AL_VELOCITY = 0x1006;
-
-/**
- * Indicate whether source is looping.
- * Type: ALboolean?
- * Range: [AL_TRUE, AL_FALSE]
- * Default: FALSE.
- */
- static final int AL_LOOPING = 0x1007;
-
-/**
- * Indicate the buffer to provide sound samples.
- * Type: ALuint.
- * Range: any valid Buffer id.
- */
- static final int AL_BUFFER = 0x1009;
-
-/**
- * Indicate the gain (volume amplification) applied.
- * Type: ALfloat.
- * Range: ]0.0- ]
- * A value of 1.0 means un-attenuated/unchanged.
- * Each division by 2 equals an attenuation of -6dB.
- * Each multiplicaton with 2 equals an amplification of +6dB.
- * A value of 0.0 is meaningless with respect to a logarithmic
- * scale; it is interpreted as zero volume - the channel
- * is effectively disabled.
- */
- static final int AL_GAIN = 0x100A;
-
-/*
- * Indicate minimum source attenuation
- * Type: ALfloat
- * Range: [0.0 - 1.0]
- *
- * Logarthmic
- */
- static final int AL_MIN_GAIN = 0x100D;
-
-/**
- * Indicate maximum source attenuation
- * Type: ALfloat
- * Range: [0.0 - 1.0]
- *
- * Logarthmic
- */
- static final int AL_MAX_GAIN = 0x100E;
-
-/**
- * Indicate listener orientation.
- *
- * at/up
- */
- static final int AL_ORIENTATION = 0x100F;
-
-/**
- * Source state information.
- */
- static final int AL_SOURCE_STATE = 0x1010;
- static final int AL_INITIAL = 0x1011;
- static final int AL_PLAYING = 0x1012;
- static final int AL_PAUSED = 0x1013;
- static final int AL_STOPPED = 0x1014;
-
-/**
- * Buffer Queue params
- */
- static final int AL_BUFFERS_QUEUED = 0x1015;
- static final int AL_BUFFERS_PROCESSED = 0x1016;
-
-/**
- * Source buffer position information
- */
- static final int AL_SEC_OFFSET = 0x1024;
- static final int AL_SAMPLE_OFFSET = 0x1025;
- static final int AL_BYTE_OFFSET = 0x1026;
-
-/*
- * Source type (Static, Streaming or undetermined)
- * Source is Static if a Buffer has been attached using AL_BUFFER
- * Source is Streaming if one or more Buffers have been attached using alSourceQueueBuffers
- * Source is undetermined when it has the NULL buffer attached
- */
- static final int AL_SOURCE_TYPE = 0x1027;
- static final int AL_STATIC = 0x1028;
- static final int AL_STREAMING = 0x1029;
- static final int AL_UNDETERMINED = 0x1030;
-
-/** Sound samples: format specifier. */
- static final int AL_FORMAT_MONO8 = 0x1100;
- static final int AL_FORMAT_MONO16 = 0x1101;
- static final int AL_FORMAT_STEREO8 = 0x1102;
- static final int AL_FORMAT_STEREO16 = 0x1103;
-
-/**
- * source specific reference distance
- * Type: ALfloat
- * Range: 0.0 - +inf
- *
- * At 0.0, no distance attenuation occurs. Default is
- * 1.0.
- */
- static final int AL_REFERENCE_DISTANCE = 0x1020;
-
-/**
- * source specific rolloff factor
- * Type: ALfloat
- * Range: 0.0 - +inf
- *
- */
- static final int AL_ROLLOFF_FACTOR = 0x1021;
-
-/**
- * Directional source, outer cone gain.
- *
- * Default: 0.0
- * Range: [0.0 - 1.0]
- * Logarithmic
- */
- static final int AL_CONE_OUTER_GAIN = 0x1022;
-
-/**
- * Indicate distance above which sources are not
- * attenuated using the inverse clamped distance model.
- *
- * Default: +inf
- * Type: ALfloat
- * Range: 0.0 - +inf
- */
- static final int AL_MAX_DISTANCE = 0x1023;
-
-/**
- * Sound samples: frequency, in units of Hertz [Hz].
- * This is the number of samples per second. Half of the
- * sample frequency marks the maximum significant
- * frequency component.
- */
- static final int AL_FREQUENCY = 0x2001;
- static final int AL_BITS = 0x2002;
- static final int AL_CHANNELS = 0x2003;
- static final int AL_SIZE = 0x2004;
-
-/**
- * Buffer state.
- *
- * Not supported for public use (yet).
- */
- static final int AL_UNUSED = 0x2010;
- static final int AL_PENDING = 0x2011;
- static final int AL_PROCESSED = 0x2012;
-
-
-/** Errors: No Error. */
- static final int AL_NO_ERROR = 0;
-
-/**
- * Invalid Name paramater passed to AL call.
- */
- static final int AL_INVALID_NAME = 0xA001;
-
-/**
- * Invalid parameter passed to AL call.
- */
- static final int AL_INVALID_ENUM = 0xA002;
-
-/**
- * Invalid enum parameter value.
- */
- static final int AL_INVALID_VALUE = 0xA003;
-
-/**
- * Illegal call.
- */
- static final int AL_INVALID_OPERATION = 0xA004;
-
-
-/**
- * No mojo.
- */
- static final int AL_OUT_OF_MEMORY = 0xA005;
-
-
-/** Context strings: Vendor Name. */
- static final int AL_VENDOR = 0xB001;
- static final int AL_VERSION = 0xB002;
- static final int AL_RENDERER = 0xB003;
- static final int AL_EXTENSIONS = 0xB004;
-
-/** Global tweakage. */
-
-/**
- * Doppler scale. Default 1.0
- */
- static final int AL_DOPPLER_FACTOR = 0xC000;
-
-/**
- * Tweaks speed of propagation.
- */
- static final int AL_DOPPLER_VELOCITY = 0xC001;
-
-/**
- * Speed of Sound in units per second
- */
- static final int AL_SPEED_OF_SOUND = 0xC003;
-
-/**
- * Distance models
- *
- * used in conjunction with DistanceModel
- *
- * implicit: NONE, which disances distance attenuation.
- */
- static final int AL_DISTANCE_MODEL = 0xD000;
- static final int AL_INVERSE_DISTANCE = 0xD001;
- static final int AL_INVERSE_DISTANCE_CLAMPED = 0xD002;
- static final int AL_LINEAR_DISTANCE = 0xD003;
- static final int AL_LINEAR_DISTANCE_CLAMPED = 0xD004;
- static final int AL_EXPONENT_DISTANCE = 0xD005;
- static final int AL_EXPONENT_DISTANCE_CLAMPED = 0xD006;
-
- /* ********** */
- /* FROM efx.h */
- /* ********** */
-
- static final String ALC_EXT_EFX_NAME = "ALC_EXT_EFX";
-
- static final int ALC_EFX_MAJOR_VERSION = 0x20001;
- static final int ALC_EFX_MINOR_VERSION = 0x20002;
- static final int ALC_MAX_AUXILIARY_SENDS = 0x20003;
-
-
-///* Listener properties. */
-//#define AL_METERS_PER_UNIT 0x20004
-//
-///* Source properties. */
- static final int AL_DIRECT_FILTER = 0x20005;
- static final int AL_AUXILIARY_SEND_FILTER = 0x20006;
-//#define AL_AIR_ABSORPTION_FACTOR 0x20007
-//#define AL_ROOM_ROLLOFF_FACTOR 0x20008
-//#define AL_CONE_OUTER_GAINHF 0x20009
- static final int AL_DIRECT_FILTER_GAINHF_AUTO = 0x2000A;
-//#define AL_AUXILIARY_SEND_FILTER_GAIN_AUTO 0x2000B
-//#define AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO 0x2000C
-//
-//
-///* Effect properties. */
-//
-///* Reverb effect parameters */
- static final int AL_REVERB_DENSITY = 0x0001;
- static final int AL_REVERB_DIFFUSION = 0x0002;
- static final int AL_REVERB_GAIN = 0x0003;
- static final int AL_REVERB_GAINHF = 0x0004;
- static final int AL_REVERB_DECAY_TIME = 0x0005;
- static final int AL_REVERB_DECAY_HFRATIO = 0x0006;
- static final int AL_REVERB_REFLECTIONS_GAIN = 0x0007;
- static final int AL_REVERB_REFLECTIONS_DELAY = 0x0008;
- static final int AL_REVERB_LATE_REVERB_GAIN = 0x0009;
- static final int AL_REVERB_LATE_REVERB_DELAY = 0x000A;
- static final int AL_REVERB_AIR_ABSORPTION_GAINHF = 0x000B;
- static final int AL_REVERB_ROOM_ROLLOFF_FACTOR = 0x000C;
- static final int AL_REVERB_DECAY_HFLIMIT = 0x000D;
-
-///* EAX Reverb effect parameters */
-//#define AL_EAXREVERB_DENSITY 0x0001
-//#define AL_EAXREVERB_DIFFUSION 0x0002
-//#define AL_EAXREVERB_GAIN 0x0003
-//#define AL_EAXREVERB_GAINHF 0x0004
-//#define AL_EAXREVERB_GAINLF 0x0005
-//#define AL_EAXREVERB_DECAY_TIME 0x0006
-//#define AL_EAXREVERB_DECAY_HFRATIO 0x0007
-//#define AL_EAXREVERB_DECAY_LFRATIO 0x0008
-//#define AL_EAXREVERB_REFLECTIONS_GAIN 0x0009
-//#define AL_EAXREVERB_REFLECTIONS_DELAY 0x000A
-//#define AL_EAXREVERB_REFLECTIONS_PAN 0x000B
-//#define AL_EAXREVERB_LATE_REVERB_GAIN 0x000C
-//#define AL_EAXREVERB_LATE_REVERB_DELAY 0x000D
-//#define AL_EAXREVERB_LATE_REVERB_PAN 0x000E
-//#define AL_EAXREVERB_ECHO_TIME 0x000F
-//#define AL_EAXREVERB_ECHO_DEPTH 0x0010
-//#define AL_EAXREVERB_MODULATION_TIME 0x0011
-//#define AL_EAXREVERB_MODULATION_DEPTH 0x0012
-//#define AL_EAXREVERB_AIR_ABSORPTION_GAINHF 0x0013
-//#define AL_EAXREVERB_HFREFERENCE 0x0014
-//#define AL_EAXREVERB_LFREFERENCE 0x0015
-//#define AL_EAXREVERB_ROOM_ROLLOFF_FACTOR 0x0016
-//#define AL_EAXREVERB_DECAY_HFLIMIT 0x0017
-//
-///* Chorus effect parameters */
-//#define AL_CHORUS_WAVEFORM 0x0001
-//#define AL_CHORUS_PHASE 0x0002
-//#define AL_CHORUS_RATE 0x0003
-//#define AL_CHORUS_DEPTH 0x0004
-//#define AL_CHORUS_FEEDBACK 0x0005
-//#define AL_CHORUS_DELAY 0x0006
-//
-///* Distortion effect parameters */
-//#define AL_DISTORTION_EDGE 0x0001
-//#define AL_DISTORTION_GAIN 0x0002
-//#define AL_DISTORTION_LOWPASS_CUTOFF 0x0003
-//#define AL_DISTORTION_EQCENTER 0x0004
-//#define AL_DISTORTION_EQBANDWIDTH 0x0005
-//
-///* Echo effect parameters */
-//#define AL_ECHO_DELAY 0x0001
-//#define AL_ECHO_LRDELAY 0x0002
-//#define AL_ECHO_DAMPING 0x0003
-//#define AL_ECHO_FEEDBACK 0x0004
-//#define AL_ECHO_SPREAD 0x0005
-//
-///* Flanger effect parameters */
-//#define AL_FLANGER_WAVEFORM 0x0001
-//#define AL_FLANGER_PHASE 0x0002
-//#define AL_FLANGER_RATE 0x0003
-//#define AL_FLANGER_DEPTH 0x0004
-//#define AL_FLANGER_FEEDBACK 0x0005
-//#define AL_FLANGER_DELAY 0x0006
-//
-///* Frequency shifter effect parameters */
-//#define AL_FREQUENCY_SHIFTER_FREQUENCY 0x0001
-//#define AL_FREQUENCY_SHIFTER_LEFT_DIRECTION 0x0002
-//#define AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION 0x0003
-//
-///* Vocal morpher effect parameters */
-//#define AL_VOCAL_MORPHER_PHONEMEA 0x0001
-//#define AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING 0x0002
-//#define AL_VOCAL_MORPHER_PHONEMEB 0x0003
-//#define AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING 0x0004
-//#define AL_VOCAL_MORPHER_WAVEFORM 0x0005
-//#define AL_VOCAL_MORPHER_RATE 0x0006
-//
-///* Pitchshifter effect parameters */
-//#define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001
-//#define AL_PITCH_SHIFTER_FINE_TUNE 0x0002
-//
-///* Ringmodulator effect parameters */
-//#define AL_RING_MODULATOR_FREQUENCY 0x0001
-//#define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002
-//#define AL_RING_MODULATOR_WAVEFORM 0x0003
-//
-///* Autowah effect parameters */
-//#define AL_AUTOWAH_ATTACK_TIME 0x0001
-//#define AL_AUTOWAH_RELEASE_TIME 0x0002
-//#define AL_AUTOWAH_RESONANCE 0x0003
-//#define AL_AUTOWAH_PEAK_GAIN 0x0004
-//
-///* Compressor effect parameters */
-//#define AL_COMPRESSOR_ONOFF 0x0001
-//
-///* Equalizer effect parameters */
-//#define AL_EQUALIZER_LOW_GAIN 0x0001
-//#define AL_EQUALIZER_LOW_CUTOFF 0x0002
-//#define AL_EQUALIZER_MID1_GAIN 0x0003
-//#define AL_EQUALIZER_MID1_CENTER 0x0004
-//#define AL_EQUALIZER_MID1_WIDTH 0x0005
-//#define AL_EQUALIZER_MID2_GAIN 0x0006
-//#define AL_EQUALIZER_MID2_CENTER 0x0007
-//#define AL_EQUALIZER_MID2_WIDTH 0x0008
-//#define AL_EQUALIZER_HIGH_GAIN 0x0009
-//#define AL_EQUALIZER_HIGH_CUTOFF 0x000A
-//
-///* Effect type */
-//#define AL_EFFECT_FIRST_PARAMETER 0x0000
-//#define AL_EFFECT_LAST_PARAMETER 0x8000
- static final int AL_EFFECT_TYPE = 0x8001;
-//
-///* Effect types, used with the AL_EFFECT_TYPE property */
-//#define AL_EFFECT_NULL 0x0000
- static final int AL_EFFECT_REVERB = 0x0001;
-//#define AL_EFFECT_CHORUS 0x0002
-//#define AL_EFFECT_DISTORTION 0x0003
-//#define AL_EFFECT_ECHO 0x0004
-//#define AL_EFFECT_FLANGER 0x0005
-//#define AL_EFFECT_FREQUENCY_SHIFTER 0x0006
-//#define AL_EFFECT_VOCAL_MORPHER 0x0007
-//#define AL_EFFECT_PITCH_SHIFTER 0x0008
-//#define AL_EFFECT_RING_MODULATOR 0x0009
-//#define AL_EFFECT_AUTOWAH 0x000A
-//#define AL_EFFECT_COMPRESSOR 0x000B
-//#define AL_EFFECT_EQUALIZER 0x000C
-//#define AL_EFFECT_EAXREVERB 0x8000
-//
-///* Auxiliary Effect Slot properties. */
- static final int AL_EFFECTSLOT_EFFECT = 0x0001;
-//#define AL_EFFECTSLOT_GAIN 0x0002
-//#define AL_EFFECTSLOT_AUXILIARY_SEND_AUTO 0x0003
-//
-///* NULL Auxiliary Slot ID to disable a source send. */
-//#define AL_EFFECTSLOT_NULL 0x0000
-//
-//
-///* Filter properties. */
-//
-///* Lowpass filter parameters */
- static final int AL_LOWPASS_GAIN = 0x0001;
- static final int AL_LOWPASS_GAINHF = 0x0002;
-//
-///* Highpass filter parameters */
-//#define AL_HIGHPASS_GAIN 0x0001
-//#define AL_HIGHPASS_GAINLF 0x0002
-//
-///* Bandpass filter parameters */
-//#define AL_BANDPASS_GAIN 0x0001
-//#define AL_BANDPASS_GAINLF 0x0002
-//#define AL_BANDPASS_GAINHF 0x0003
-//
-///* Filter type */
-//#define AL_FILTER_FIRST_PARAMETER 0x0000
-//#define AL_FILTER_LAST_PARAMETER 0x8000
- static final int AL_FILTER_TYPE = 0x8001;
-//
-///* Filter types, used with the AL_FILTER_TYPE property */
- static final int AL_FILTER_NULL = 0x0000;
- static final int AL_FILTER_LOWPASS = 0x0001;
- static final int AL_FILTER_HIGHPASS = 0x0002;
-//#define AL_FILTER_BANDPASS 0x0003
-//
-///* Filter ranges and defaults. */
-//
-///* Lowpass filter */
-//#define AL_LOWPASS_MIN_GAIN (0.0f)
-//#define AL_LOWPASS_MAX_GAIN (1.0f)
-//#define AL_LOWPASS_DEFAULT_GAIN (1.0f)
-//
-//#define AL_LOWPASS_MIN_GAINHF (0.0f)
-//#define AL_LOWPASS_MAX_GAINHF (1.0f)
-//#define AL_LOWPASS_DEFAULT_GAINHF (1.0f)
-//
-///* Highpass filter */
-//#define AL_HIGHPASS_MIN_GAIN (0.0f)
-//#define AL_HIGHPASS_MAX_GAIN (1.0f)
-//#define AL_HIGHPASS_DEFAULT_GAIN (1.0f)
-//
-//#define AL_HIGHPASS_MIN_GAINLF (0.0f)
-//#define AL_HIGHPASS_MAX_GAINLF (1.0f)
-//#define AL_HIGHPASS_DEFAULT_GAINLF (1.0f)
-//
-///* Bandpass filter */
-//#define AL_BANDPASS_MIN_GAIN (0.0f)
-//#define AL_BANDPASS_MAX_GAIN (1.0f)
-//#define AL_BANDPASS_DEFAULT_GAIN (1.0f)
-//
-//#define AL_BANDPASS_MIN_GAINHF (0.0f)
-//#define AL_BANDPASS_MAX_GAINHF (1.0f)
-//#define AL_BANDPASS_DEFAULT_GAINHF (1.0f)
-//
-//#define AL_BANDPASS_MIN_GAINLF (0.0f)
-//#define AL_BANDPASS_MAX_GAINLF (1.0f)
-//#define AL_BANDPASS_DEFAULT_GAINLF (1.0f)
-//
-//
-///* Effect parameter ranges and defaults. */
-//
-///* Standard reverb effect */
-//#define AL_REVERB_MIN_DENSITY (0.0f)
-//#define AL_REVERB_MAX_DENSITY (1.0f)
-//#define AL_REVERB_DEFAULT_DENSITY (1.0f)
-//
-//#define AL_REVERB_MIN_DIFFUSION (0.0f)
-//#define AL_REVERB_MAX_DIFFUSION (1.0f)
-//#define AL_REVERB_DEFAULT_DIFFUSION (1.0f)
-//
-//#define AL_REVERB_MIN_GAIN (0.0f)
-//#define AL_REVERB_MAX_GAIN (1.0f)
-//#define AL_REVERB_DEFAULT_GAIN (0.32f)
-//
-//#define AL_REVERB_MIN_GAINHF (0.0f)
-//#define AL_REVERB_MAX_GAINHF (1.0f)
-//#define AL_REVERB_DEFAULT_GAINHF (0.89f)
-//
-//#define AL_REVERB_MIN_DECAY_TIME (0.1f)
-//#define AL_REVERB_MAX_DECAY_TIME (20.0f)
-//#define AL_REVERB_DEFAULT_DECAY_TIME (1.49f)
-//
-//#define AL_REVERB_MIN_DECAY_HFRATIO (0.1f)
-//#define AL_REVERB_MAX_DECAY_HFRATIO (2.0f)
-//#define AL_REVERB_DEFAULT_DECAY_HFRATIO (0.83f)
-//
-//#define AL_REVERB_MIN_REFLECTIONS_GAIN (0.0f)
-//#define AL_REVERB_MAX_REFLECTIONS_GAIN (3.16f)
-//#define AL_REVERB_DEFAULT_REFLECTIONS_GAIN (0.05f)
-//
-//#define AL_REVERB_MIN_REFLECTIONS_DELAY (0.0f)
-//#define AL_REVERB_MAX_REFLECTIONS_DELAY (0.3f)
-//#define AL_REVERB_DEFAULT_REFLECTIONS_DELAY (0.007f)
-//
-//#define AL_REVERB_MIN_LATE_REVERB_GAIN (0.0f)
-//#define AL_REVERB_MAX_LATE_REVERB_GAIN (10.0f)
-//#define AL_REVERB_DEFAULT_LATE_REVERB_GAIN (1.26f)
-//
-//#define AL_REVERB_MIN_LATE_REVERB_DELAY (0.0f)
-//#define AL_REVERB_MAX_LATE_REVERB_DELAY (0.1f)
-//#define AL_REVERB_DEFAULT_LATE_REVERB_DELAY (0.011f)
-//
-//#define AL_REVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f)
-//#define AL_REVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f)
-//#define AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f)
-//
-//#define AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f)
-//#define AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f)
-//#define AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f)
-//
-//#define AL_REVERB_MIN_DECAY_HFLIMIT AL_FALSE
-//#define AL_REVERB_MAX_DECAY_HFLIMIT AL_TRUE
-//#define AL_REVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE
-//
-///* EAX reverb effect */
-//#define AL_EAXREVERB_MIN_DENSITY (0.0f)
-//#define AL_EAXREVERB_MAX_DENSITY (1.0f)
-//#define AL_EAXREVERB_DEFAULT_DENSITY (1.0f)
-//
-//#define AL_EAXREVERB_MIN_DIFFUSION (0.0f)
-//#define AL_EAXREVERB_MAX_DIFFUSION (1.0f)
-//#define AL_EAXREVERB_DEFAULT_DIFFUSION (1.0f)
-//
-//#define AL_EAXREVERB_MIN_GAIN (0.0f)
-//#define AL_EAXREVERB_MAX_GAIN (1.0f)
-//#define AL_EAXREVERB_DEFAULT_GAIN (0.32f)
-//
-//#define AL_EAXREVERB_MIN_GAINHF (0.0f)
-//#define AL_EAXREVERB_MAX_GAINHF (1.0f)
-//#define AL_EAXREVERB_DEFAULT_GAINHF (0.89f)
-//
-//#define AL_EAXREVERB_MIN_GAINLF (0.0f)
-//#define AL_EAXREVERB_MAX_GAINLF (1.0f)
-//#define AL_EAXREVERB_DEFAULT_GAINLF (1.0f)
-//
-//#define AL_EAXREVERB_MIN_DECAY_TIME (0.1f)
-//#define AL_EAXREVERB_MAX_DECAY_TIME (20.0f)
-//#define AL_EAXREVERB_DEFAULT_DECAY_TIME (1.49f)
-//
-//#define AL_EAXREVERB_MIN_DECAY_HFRATIO (0.1f)
-//#define AL_EAXREVERB_MAX_DECAY_HFRATIO (2.0f)
-//#define AL_EAXREVERB_DEFAULT_DECAY_HFRATIO (0.83f)
-//
-//#define AL_EAXREVERB_MIN_DECAY_LFRATIO (0.1f)
-//#define AL_EAXREVERB_MAX_DECAY_LFRATIO (2.0f)
-//#define AL_EAXREVERB_DEFAULT_DECAY_LFRATIO (1.0f)
-//
-//#define AL_EAXREVERB_MIN_REFLECTIONS_GAIN (0.0f)
-//#define AL_EAXREVERB_MAX_REFLECTIONS_GAIN (3.16f)
-//#define AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN (0.05f)
-//
-//#define AL_EAXREVERB_MIN_REFLECTIONS_DELAY (0.0f)
-//#define AL_EAXREVERB_MAX_REFLECTIONS_DELAY (0.3f)
-//#define AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY (0.007f)
-//
-//#define AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ (0.0f)
-//
-//#define AL_EAXREVERB_MIN_LATE_REVERB_GAIN (0.0f)
-//#define AL_EAXREVERB_MAX_LATE_REVERB_GAIN (10.0f)
-//#define AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN (1.26f)
-//
-//#define AL_EAXREVERB_MIN_LATE_REVERB_DELAY (0.0f)
-//#define AL_EAXREVERB_MAX_LATE_REVERB_DELAY (0.1f)
-//#define AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY (0.011f)
-//
-//#define AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ (0.0f)
-//
-//#define AL_EAXREVERB_MIN_ECHO_TIME (0.075f)
-//#define AL_EAXREVERB_MAX_ECHO_TIME (0.25f)
-//#define AL_EAXREVERB_DEFAULT_ECHO_TIME (0.25f)
-//
-//#define AL_EAXREVERB_MIN_ECHO_DEPTH (0.0f)
-//#define AL_EAXREVERB_MAX_ECHO_DEPTH (1.0f)
-//#define AL_EAXREVERB_DEFAULT_ECHO_DEPTH (0.0f)
-//
-//#define AL_EAXREVERB_MIN_MODULATION_TIME (0.04f)
-//#define AL_EAXREVERB_MAX_MODULATION_TIME (4.0f)
-//#define AL_EAXREVERB_DEFAULT_MODULATION_TIME (0.25f)
-//
-//#define AL_EAXREVERB_MIN_MODULATION_DEPTH (0.0f)
-//#define AL_EAXREVERB_MAX_MODULATION_DEPTH (1.0f)
-//#define AL_EAXREVERB_DEFAULT_MODULATION_DEPTH (0.0f)
-//
-//#define AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f)
-//#define AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f)
-//#define AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f)
-//
-//#define AL_EAXREVERB_MIN_HFREFERENCE (1000.0f)
-//#define AL_EAXREVERB_MAX_HFREFERENCE (20000.0f)
-//#define AL_EAXREVERB_DEFAULT_HFREFERENCE (5000.0f)
-//
-//#define AL_EAXREVERB_MIN_LFREFERENCE (20.0f)
-//#define AL_EAXREVERB_MAX_LFREFERENCE (1000.0f)
-//#define AL_EAXREVERB_DEFAULT_LFREFERENCE (250.0f)
-//
-//#define AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f)
-//#define AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f)
-//#define AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f)
-//
-//#define AL_EAXREVERB_MIN_DECAY_HFLIMIT AL_FALSE
-//#define AL_EAXREVERB_MAX_DECAY_HFLIMIT AL_TRUE
-//#define AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE
-//
-///* Chorus effect */
-//#define AL_CHORUS_WAVEFORM_SINUSOID (0)
-//#define AL_CHORUS_WAVEFORM_TRIANGLE (1)
-//
-//#define AL_CHORUS_MIN_WAVEFORM (0)
-//#define AL_CHORUS_MAX_WAVEFORM (1)
-//#define AL_CHORUS_DEFAULT_WAVEFORM (1)
-//
-//#define AL_CHORUS_MIN_PHASE (-180)
-//#define AL_CHORUS_MAX_PHASE (180)
-//#define AL_CHORUS_DEFAULT_PHASE (90)
-//
-//#define AL_CHORUS_MIN_RATE (0.0f)
-//#define AL_CHORUS_MAX_RATE (10.0f)
-//#define AL_CHORUS_DEFAULT_RATE (1.1f)
-//
-//#define AL_CHORUS_MIN_DEPTH (0.0f)
-//#define AL_CHORUS_MAX_DEPTH (1.0f)
-//#define AL_CHORUS_DEFAULT_DEPTH (0.1f)
-//
-//#define AL_CHORUS_MIN_FEEDBACK (-1.0f)
-//#define AL_CHORUS_MAX_FEEDBACK (1.0f)
-//#define AL_CHORUS_DEFAULT_FEEDBACK (0.25f)
-//
-//#define AL_CHORUS_MIN_DELAY (0.0f)
-//#define AL_CHORUS_MAX_DELAY (0.016f)
-//#define AL_CHORUS_DEFAULT_DELAY (0.016f)
-//
-///* Distortion effect */
-//#define AL_DISTORTION_MIN_EDGE (0.0f)
-//#define AL_DISTORTION_MAX_EDGE (1.0f)
-//#define AL_DISTORTION_DEFAULT_EDGE (0.2f)
-//
-//#define AL_DISTORTION_MIN_GAIN (0.01f)
-//#define AL_DISTORTION_MAX_GAIN (1.0f)
-//#define AL_DISTORTION_DEFAULT_GAIN (0.05f)
-//
-//#define AL_DISTORTION_MIN_LOWPASS_CUTOFF (80.0f)
-//#define AL_DISTORTION_MAX_LOWPASS_CUTOFF (24000.0f)
-//#define AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF (8000.0f)
-//
-//#define AL_DISTORTION_MIN_EQCENTER (80.0f)
-//#define AL_DISTORTION_MAX_EQCENTER (24000.0f)
-//#define AL_DISTORTION_DEFAULT_EQCENTER (3600.0f)
-//
-//#define AL_DISTORTION_MIN_EQBANDWIDTH (80.0f)
-//#define AL_DISTORTION_MAX_EQBANDWIDTH (24000.0f)
-//#define AL_DISTORTION_DEFAULT_EQBANDWIDTH (3600.0f)
-//
-///* Echo effect */
-//#define AL_ECHO_MIN_DELAY (0.0f)
-//#define AL_ECHO_MAX_DELAY (0.207f)
-//#define AL_ECHO_DEFAULT_DELAY (0.1f)
-//
-//#define AL_ECHO_MIN_LRDELAY (0.0f)
-//#define AL_ECHO_MAX_LRDELAY (0.404f)
-//#define AL_ECHO_DEFAULT_LRDELAY (0.1f)
-//
-//#define AL_ECHO_MIN_DAMPING (0.0f)
-//#define AL_ECHO_MAX_DAMPING (0.99f)
-//#define AL_ECHO_DEFAULT_DAMPING (0.5f)
-//
-//#define AL_ECHO_MIN_FEEDBACK (0.0f)
-//#define AL_ECHO_MAX_FEEDBACK (1.0f)
-//#define AL_ECHO_DEFAULT_FEEDBACK (0.5f)
-//
-//#define AL_ECHO_MIN_SPREAD (-1.0f)
-//#define AL_ECHO_MAX_SPREAD (1.0f)
-//#define AL_ECHO_DEFAULT_SPREAD (-1.0f)
-//
-///* Flanger effect */
-//#define AL_FLANGER_WAVEFORM_SINUSOID (0)
-//#define AL_FLANGER_WAVEFORM_TRIANGLE (1)
-//
-//#define AL_FLANGER_MIN_WAVEFORM (0)
-//#define AL_FLANGER_MAX_WAVEFORM (1)
-//#define AL_FLANGER_DEFAULT_WAVEFORM (1)
-//
-//#define AL_FLANGER_MIN_PHASE (-180)
-//#define AL_FLANGER_MAX_PHASE (180)
-//#define AL_FLANGER_DEFAULT_PHASE (0)
-//
-//#define AL_FLANGER_MIN_RATE (0.0f)
-//#define AL_FLANGER_MAX_RATE (10.0f)
-//#define AL_FLANGER_DEFAULT_RATE (0.27f)
-//
-//#define AL_FLANGER_MIN_DEPTH (0.0f)
-//#define AL_FLANGER_MAX_DEPTH (1.0f)
-//#define AL_FLANGER_DEFAULT_DEPTH (1.0f)
-//
-//#define AL_FLANGER_MIN_FEEDBACK (-1.0f)
-//#define AL_FLANGER_MAX_FEEDBACK (1.0f)
-//#define AL_FLANGER_DEFAULT_FEEDBACK (-0.5f)
-//
-//#define AL_FLANGER_MIN_DELAY (0.0f)
-//#define AL_FLANGER_MAX_DELAY (0.004f)
-//#define AL_FLANGER_DEFAULT_DELAY (0.002f)
-//
-///* Frequency shifter effect */
-//#define AL_FREQUENCY_SHIFTER_MIN_FREQUENCY (0.0f)
-//#define AL_FREQUENCY_SHIFTER_MAX_FREQUENCY (24000.0f)
-//#define AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY (0.0f)
-//
-//#define AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION (0)
-//#define AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION (2)
-//#define AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION (0)
-//
-//#define AL_FREQUENCY_SHIFTER_DIRECTION_DOWN (0)
-//#define AL_FREQUENCY_SHIFTER_DIRECTION_UP (1)
-//#define AL_FREQUENCY_SHIFTER_DIRECTION_OFF (2)
-//
-//#define AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION (0)
-//#define AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION (2)
-//#define AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION (0)
-//
-///* Vocal morpher effect */
-//#define AL_VOCAL_MORPHER_MIN_PHONEMEA (0)
-//#define AL_VOCAL_MORPHER_MAX_PHONEMEA (29)
-//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA (0)
-//
-//#define AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING (-24)
-//#define AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING (24)
-//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING (0)
-//
-//#define AL_VOCAL_MORPHER_MIN_PHONEMEB (0)
-//#define AL_VOCAL_MORPHER_MAX_PHONEMEB (29)
-//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB (10)
-//
-//#define AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING (-24)
-//#define AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING (24)
-//#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING (0)
-//
-//#define AL_VOCAL_MORPHER_PHONEME_A (0)
-//#define AL_VOCAL_MORPHER_PHONEME_E (1)
-//#define AL_VOCAL_MORPHER_PHONEME_I (2)
-//#define AL_VOCAL_MORPHER_PHONEME_O (3)
-//#define AL_VOCAL_MORPHER_PHONEME_U (4)
-//#define AL_VOCAL_MORPHER_PHONEME_AA (5)
-//#define AL_VOCAL_MORPHER_PHONEME_AE (6)
-//#define AL_VOCAL_MORPHER_PHONEME_AH (7)
-//#define AL_VOCAL_MORPHER_PHONEME_AO (8)
-//#define AL_VOCAL_MORPHER_PHONEME_EH (9)
-//#define AL_VOCAL_MORPHER_PHONEME_ER (10)
-//#define AL_VOCAL_MORPHER_PHONEME_IH (11)
-//#define AL_VOCAL_MORPHER_PHONEME_IY (12)
-//#define AL_VOCAL_MORPHER_PHONEME_UH (13)
-//#define AL_VOCAL_MORPHER_PHONEME_UW (14)
-//#define AL_VOCAL_MORPHER_PHONEME_B (15)
-//#define AL_VOCAL_MORPHER_PHONEME_D (16)
-//#define AL_VOCAL_MORPHER_PHONEME_F (17)
-//#define AL_VOCAL_MORPHER_PHONEME_G (18)
-//#define AL_VOCAL_MORPHER_PHONEME_J (19)
-//#define AL_VOCAL_MORPHER_PHONEME_K (20)
-//#define AL_VOCAL_MORPHER_PHONEME_L (21)
-//#define AL_VOCAL_MORPHER_PHONEME_M (22)
-//#define AL_VOCAL_MORPHER_PHONEME_N (23)
-//#define AL_VOCAL_MORPHER_PHONEME_P (24)
-//#define AL_VOCAL_MORPHER_PHONEME_R (25)
-//#define AL_VOCAL_MORPHER_PHONEME_S (26)
-//#define AL_VOCAL_MORPHER_PHONEME_T (27)
-//#define AL_VOCAL_MORPHER_PHONEME_V (28)
-//#define AL_VOCAL_MORPHER_PHONEME_Z (29)
-//
-//#define AL_VOCAL_MORPHER_WAVEFORM_SINUSOID (0)
-//#define AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE (1)
-//#define AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH (2)
-//
-//#define AL_VOCAL_MORPHER_MIN_WAVEFORM (0)
-//#define AL_VOCAL_MORPHER_MAX_WAVEFORM (2)
-//#define AL_VOCAL_MORPHER_DEFAULT_WAVEFORM (0)
-//
-//#define AL_VOCAL_MORPHER_MIN_RATE (0.0f)
-//#define AL_VOCAL_MORPHER_MAX_RATE (10.0f)
-//#define AL_VOCAL_MORPHER_DEFAULT_RATE (1.41f)
-//
-///* Pitch shifter effect */
-//#define AL_PITCH_SHIFTER_MIN_COARSE_TUNE (-12)
-//#define AL_PITCH_SHIFTER_MAX_COARSE_TUNE (12)
-//#define AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE (12)
-//
-//#define AL_PITCH_SHIFTER_MIN_FINE_TUNE (-50)
-//#define AL_PITCH_SHIFTER_MAX_FINE_TUNE (50)
-//#define AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE (0)
-//
-///* Ring modulator effect */
-//#define AL_RING_MODULATOR_MIN_FREQUENCY (0.0f)
-//#define AL_RING_MODULATOR_MAX_FREQUENCY (8000.0f)
-//#define AL_RING_MODULATOR_DEFAULT_FREQUENCY (440.0f)
-//
-//#define AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF (0.0f)
-//#define AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF (24000.0f)
-//#define AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF (800.0f)
-//
-//#define AL_RING_MODULATOR_SINUSOID (0)
-//#define AL_RING_MODULATOR_SAWTOOTH (1)
-//#define AL_RING_MODULATOR_SQUARE (2)
-//
-//#define AL_RING_MODULATOR_MIN_WAVEFORM (0)
-//#define AL_RING_MODULATOR_MAX_WAVEFORM (2)
-//#define AL_RING_MODULATOR_DEFAULT_WAVEFORM (0)
-//
-///* Autowah effect */
-//#define AL_AUTOWAH_MIN_ATTACK_TIME (0.0001f)
-//#define AL_AUTOWAH_MAX_ATTACK_TIME (1.0f)
-//#define AL_AUTOWAH_DEFAULT_ATTACK_TIME (0.06f)
-//
-//#define AL_AUTOWAH_MIN_RELEASE_TIME (0.0001f)
-//#define AL_AUTOWAH_MAX_RELEASE_TIME (1.0f)
-//#define AL_AUTOWAH_DEFAULT_RELEASE_TIME (0.06f)
-//
-//#define AL_AUTOWAH_MIN_RESONANCE (2.0f)
-//#define AL_AUTOWAH_MAX_RESONANCE (1000.0f)
-//#define AL_AUTOWAH_DEFAULT_RESONANCE (1000.0f)
-//
-//#define AL_AUTOWAH_MIN_PEAK_GAIN (0.00003f)
-//#define AL_AUTOWAH_MAX_PEAK_GAIN (31621.0f)
-//#define AL_AUTOWAH_DEFAULT_PEAK_GAIN (11.22f)
-//
-///* Compressor effect */
-//#define AL_COMPRESSOR_MIN_ONOFF (0)
-//#define AL_COMPRESSOR_MAX_ONOFF (1)
-//#define AL_COMPRESSOR_DEFAULT_ONOFF (1)
-//
-///* Equalizer effect */
-//#define AL_EQUALIZER_MIN_LOW_GAIN (0.126f)
-//#define AL_EQUALIZER_MAX_LOW_GAIN (7.943f)
-//#define AL_EQUALIZER_DEFAULT_LOW_GAIN (1.0f)
-//
-//#define AL_EQUALIZER_MIN_LOW_CUTOFF (50.0f)
-//#define AL_EQUALIZER_MAX_LOW_CUTOFF (800.0f)
-//#define AL_EQUALIZER_DEFAULT_LOW_CUTOFF (200.0f)
-//
-//#define AL_EQUALIZER_MIN_MID1_GAIN (0.126f)
-//#define AL_EQUALIZER_MAX_MID1_GAIN (7.943f)
-//#define AL_EQUALIZER_DEFAULT_MID1_GAIN (1.0f)
-//
-//#define AL_EQUALIZER_MIN_MID1_CENTER (200.0f)
-//#define AL_EQUALIZER_MAX_MID1_CENTER (3000.0f)
-//#define AL_EQUALIZER_DEFAULT_MID1_CENTER (500.0f)
-//
-//#define AL_EQUALIZER_MIN_MID1_WIDTH (0.01f)
-//#define AL_EQUALIZER_MAX_MID1_WIDTH (1.0f)
-//#define AL_EQUALIZER_DEFAULT_MID1_WIDTH (1.0f)
-//
-//#define AL_EQUALIZER_MIN_MID2_GAIN (0.126f)
-//#define AL_EQUALIZER_MAX_MID2_GAIN (7.943f)
-//#define AL_EQUALIZER_DEFAULT_MID2_GAIN (1.0f)
-//
-//#define AL_EQUALIZER_MIN_MID2_CENTER (1000.0f)
-//#define AL_EQUALIZER_MAX_MID2_CENTER (8000.0f)
-//#define AL_EQUALIZER_DEFAULT_MID2_CENTER (3000.0f)
-//
-//#define AL_EQUALIZER_MIN_MID2_WIDTH (0.01f)
-//#define AL_EQUALIZER_MAX_MID2_WIDTH (1.0f)
-//#define AL_EQUALIZER_DEFAULT_MID2_WIDTH (1.0f)
-//
-//#define AL_EQUALIZER_MIN_HIGH_GAIN (0.126f)
-//#define AL_EQUALIZER_MAX_HIGH_GAIN (7.943f)
-//#define AL_EQUALIZER_DEFAULT_HIGH_GAIN (1.0f)
-//
-//#define AL_EQUALIZER_MIN_HIGH_CUTOFF (4000.0f)
-//#define AL_EQUALIZER_MAX_HIGH_CUTOFF (16000.0f)
-//#define AL_EQUALIZER_DEFAULT_HIGH_CUTOFF (6000.0f)
-//
-//
-///* Source parameter value ranges and defaults. */
-//#define AL_MIN_AIR_ABSORPTION_FACTOR (0.0f)
-//#define AL_MAX_AIR_ABSORPTION_FACTOR (10.0f)
-//#define AL_DEFAULT_AIR_ABSORPTION_FACTOR (0.0f)
-//
-//#define AL_MIN_ROOM_ROLLOFF_FACTOR (0.0f)
-//#define AL_MAX_ROOM_ROLLOFF_FACTOR (10.0f)
-//#define AL_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f)
-//
-//#define AL_MIN_CONE_OUTER_GAINHF (0.0f)
-//#define AL_MAX_CONE_OUTER_GAINHF (1.0f)
-//#define AL_DEFAULT_CONE_OUTER_GAINHF (1.0f)
-//
-//#define AL_MIN_DIRECT_FILTER_GAINHF_AUTO AL_FALSE
-//#define AL_MAX_DIRECT_FILTER_GAINHF_AUTO AL_TRUE
-//#define AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO AL_TRUE
-//
-//#define AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_FALSE
-//#define AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE
-//#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE
-//
-//#define AL_MIN_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_FALSE
-//#define AL_MAX_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE
-//#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE
-//
-//
-///* Listener parameter value ranges and defaults. */
-//#define AL_MIN_METERS_PER_UNIT FLT_MIN
-//#define AL_MAX_METERS_PER_UNIT FLT_MAX
-//#define AL_DEFAULT_METERS_PER_UNIT (1.0f)
-
-
- public static String GetALErrorMsg(int errorCode) {
- String errorText;
- switch (errorCode) {
- case AL_NO_ERROR:
- errorText = "No Error";
- break;
- case AL_INVALID_NAME:
- errorText = "Invalid Name";
- break;
- case AL_INVALID_ENUM:
- errorText = "Invalid Enum";
- break;
- case AL_INVALID_VALUE:
- errorText = "Invalid Value";
- break;
- case AL_INVALID_OPERATION:
- errorText = "Invalid Operation";
- break;
- case AL_OUT_OF_MEMORY:
- errorText = "Out of Memory";
- break;
- default:
- errorText = "Unknown Error Code: " + String.valueOf(errorCode);
- }
- return errorText;
- }
-}
-
diff --git a/jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java b/jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java
deleted file mode 100644
index e7f4a0f98..000000000
--- a/jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.jme3.audio.android;
-
-import com.jme3.asset.AssetKey;
-import com.jme3.audio.AudioData;
-import com.jme3.audio.AudioRenderer;
-import com.jme3.util.NativeObject;
-
-public class AndroidAudioData extends AudioData {
-
- protected AssetKey> assetKey;
- protected float currentVolume = 0f;
-
- public AndroidAudioData(){
- super();
- }
-
- protected AndroidAudioData(int id){
- super(id);
- }
-
- public AssetKey> getAssetKey() {
- return assetKey;
- }
-
- public void setAssetKey(AssetKey> assetKey) {
- this.assetKey = assetKey;
- }
-
- @Override
- public DataType getDataType() {
- return DataType.Buffer;
- }
-
- @Override
- public float getDuration() {
- return 0; // TODO: ???
- }
-
- @Override
- public void resetObject() {
- this.id = -1;
- setUpdateNeeded();
- }
-
- @Override
- public void deleteObject(Object rendererObject) {
- ((AudioRenderer)rendererObject).deleteAudioData(this);
- }
-
- public float getCurrentVolume() {
- return currentVolume;
- }
-
- public void setCurrentVolume(float currentVolume) {
- this.currentVolume = currentVolume;
- }
-
- @Override
- public NativeObject createDestructableClone() {
- return new AndroidAudioData(id);
- }
-
- @Override
- public long getUniqueId() {
- return ((long)OBJTYPE_AUDIOBUFFER << 32) | ((long)id);
- }
-}
diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java
new file mode 100644
index 000000000..7812dc748
--- /dev/null
+++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java
@@ -0,0 +1,53 @@
+package com.jme3.audio.ios;
+
+import com.jme3.audio.openal.AL;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+public final class IosAL implements AL {
+
+ public IosAL() {
+ }
+
+ public native String alGetString(int parameter);
+
+ public native int alGenSources();
+
+ public native int alGetError();
+
+ public native void alDeleteSources(int numSources, IntBuffer sources);
+
+ public native void alGenBuffers(int numBuffers, IntBuffer buffers);
+
+ public native void alDeleteBuffers(int numBuffers, IntBuffer buffers);
+
+ public native void alSourceStop(int source);
+
+ public native void alSourcei(int source, int param, int value);
+
+ public native void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency);
+
+ public native void alSourcePlay(int source);
+
+ public native void alSourcePause(int source);
+
+ public native void alSourcef(int source, int param, float value);
+
+ public native void alSource3f(int source, int param, float value1, float value2, float value3);
+
+ public native int alGetSourcei(int source, int param);
+
+ public native void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers);
+
+ public native void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers);
+
+ public native void alListener(int param, FloatBuffer data);
+
+ public native void alListenerf(int param, float value);
+
+ public native void alListener3f(int param, float value1, float value2, float value3);
+
+ public native void alSource3i(int source, int param, int value1, int value2, int value3);
+
+}
diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java
new file mode 100644
index 000000000..f1579c9e5
--- /dev/null
+++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java
@@ -0,0 +1,26 @@
+package com.jme3.audio.ios;
+
+import com.jme3.audio.openal.ALC;
+import java.nio.IntBuffer;
+
+public final class IosALC implements ALC {
+
+ public IosALC() {
+ }
+
+ public native void createALC();
+
+ public native void destroyALC();
+
+ public native boolean isCreated();
+
+ public native String alcGetString(int parameter);
+
+ public native boolean alcIsExtensionPresent(String extension);
+
+ public native void alcGetInteger(int param, IntBuffer buffer, int size);
+
+ public native void alcDevicePauseSOFT();
+
+ public native void alcDeviceResumeSOFT();
+}
diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java
new file mode 100644
index 000000000..d7a569c1f
--- /dev/null
+++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java
@@ -0,0 +1,32 @@
+package com.jme3.audio.ios;
+
+import com.jme3.audio.openal.EFX;
+import java.nio.IntBuffer;
+
+public class IosEFX implements EFX {
+
+ public IosEFX() {
+ }
+
+ public native void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers);
+
+ public native void alGenEffects(int numEffects, IntBuffer buffers);
+
+ public native void alEffecti(int effect, int param, int value);
+
+ public native void alAuxiliaryEffectSloti(int effectSlot, int param, int value);
+
+ public native void alDeleteEffects(int numEffects, IntBuffer buffers);
+
+ public native void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers);
+
+ public native void alGenFilters(int numFilters, IntBuffer buffers);
+
+ public native void alFilteri(int filter, int param, int value);
+
+ public native void alFilterf(int filter, int param, float value);
+
+ public native void alDeleteFilters(int numFilters, IntBuffer buffers);
+
+ public native void alEffectf(int effect, int param, float value);
+}
diff --git a/jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java b/jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java
deleted file mode 100644
index a11425b23..000000000
--- a/jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.jme3.audio.plugins;
-
-import com.jme3.asset.AssetInfo;
-import com.jme3.asset.AssetLoader;
-import com.jme3.audio.android.AndroidAudioData;
-import java.io.IOException;
-
-/**
- * AndroidAudioLoader will create an
- * {@link AndroidAudioData} object with the specified asset key.
- */
-public class AndroidAudioLoader implements AssetLoader {
-
- @Override
- public Object load(AssetInfo assetInfo) throws IOException {
- AndroidAudioData result = new AndroidAudioData();
- result.setAssetKey(assetInfo.getKey());
- return result;
- }
-}
diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java
index d3276972d..a9398f159 100644
--- a/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java
+++ b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java
@@ -34,6 +34,7 @@ package com.jme3.renderer.ios;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLExt;
+import com.jme3.renderer.opengl.GLFbo;
import java.nio.Buffer;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
@@ -46,10 +47,13 @@ import java.nio.ShortBuffer;
*
* @author Kirill Vainer
*/
-public class IosGL implements GL, GLExt {
+public class IosGL implements GL, GLExt, GLFbo {
private final int[] temp_array = new int[16];
+ public void resetStats() {
+ }
+
private static int getLimitBytes(ByteBuffer buffer) {
checkLimit(buffer);
return buffer.limit();
@@ -90,7 +94,9 @@ public class IosGL implements GL, GLExt {
if (buffer.remaining() < n) {
throw new BufferOverflowException();
}
+ int pos = buffer.position();
buffer.put(array, 0, n);
+ buffer.position(pos);
}
private static void checkLimit(Buffer buffer) {
diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java
index 7feaeae5b..2d046550e 100644
--- a/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java
+++ b/jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java
@@ -40,6 +40,7 @@ import com.jme3.renderer.ios.IosGL;
import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLDebugES;
import com.jme3.renderer.opengl.GLExt;
+import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
@@ -158,11 +159,11 @@ public class IGLESContext implements JmeContext {
GLExt glext = (GLExt) gl;
// if (settings.getBoolean("GraphicsDebug")) {
- gl = new GLDebugES(gl, glext);
+ gl = new GLDebugES(gl, glext, (GLFbo) glext);
glext = (GLExt) gl;
// }
- renderer = new GLRenderer(gl, glext);
+ renderer = new GLRenderer(gl, glext, (GLFbo) glext);
renderer.initialize();
input = new IosInputHandler();
diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java b/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java
index 4ec649086..e47de3a50 100644
--- a/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java
+++ b/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java
@@ -33,6 +33,7 @@ package com.jme3.system.ios;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetLoader;
+import com.jme3.asset.TextureKey;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import java.io.IOException;
@@ -45,14 +46,16 @@ import java.io.InputStream;
public class IosImageLoader implements AssetLoader {
public Object load(AssetInfo info) throws IOException {
- InputStream in = info.openStream();
+ boolean flip = ((TextureKey) info.getKey()).isFlipY();
Image img = null;
+ InputStream in = null;
try {
- img = loadImageData(Image.Format.RGBA8, in);
- } catch (Exception e) {
- e.printStackTrace();
+ in = info.openStream();
+ img = loadImageData(Format.RGBA8, flip, in);
} finally {
- in.close();
+ if (in != null) {
+ in.close();
+ }
}
return img;
}
@@ -64,5 +67,5 @@ public class IosImageLoader implements AssetLoader {
* @param inputStream the InputStream to load the image data from
* @return the loaded Image
*/
- private static native Image loadImageData(Format format, InputStream inputStream);
+ private static native Image loadImageData(Format format, boolean flipY, InputStream inputStream);
}
diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java b/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java
index 904db71cf..d29f76f62 100644
--- a/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java
+++ b/jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java
@@ -36,6 +36,14 @@ import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext;
import com.jme3.system.JmeSystemDelegate;
import com.jme3.system.NullContext;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.audio.ios.IosAL;
+import com.jme3.audio.ios.IosALC;
+//import com.jme3.audio.ios.IosEFX;
+import com.jme3.audio.openal.AL;
+import com.jme3.audio.openal.ALAudioRenderer;
+import com.jme3.audio.openal.ALC;
+import com.jme3.audio.openal.EFX;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
@@ -89,8 +97,11 @@ public class JmeIosSystem extends JmeSystemDelegate {
@Override
public AudioRenderer newAudioRenderer(AppSettings settings) {
- return null;
- }
+ ALC alc = new IosALC();
+ AL al = new IosAL();
+ //EFX efx = new IosEFX();
+ return new ALAudioRenderer(al, alc, null);
+ }
@Override
public void initialize(AppSettings settings) {
diff --git a/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg b/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg
new file mode 100644
index 000000000..4e4052447
--- /dev/null
+++ b/jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg
@@ -0,0 +1,8 @@
+INCLUDE com/jme3/asset/General.cfg
+
+# IOS specific loaders
+LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg
+LOADER com.jme3.material.plugins.J3MLoader : j3m, j3md
+LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib
+LOADER com.jme3.export.binary.BinaryImporter : j3o
+LOADER com.jme3.font.plugins.BitmapFontLoader : fnt
diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java
index 82b8ee72b..bf99c84eb 100644
--- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java
+++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java
@@ -13,7 +13,7 @@ import java.nio.ShortBuffer;
import com.jme3.renderer.opengl.GL4;
import org.lwjgl.opengl.*;
-public class LwjglGL implements GL, GL2, GL3,GL4 {
+public class LwjglGL implements GL, GL2, GL3, GL4 {
private static void checkLimit(Buffer buffer) {
if (buffer == null) {
@@ -27,6 +27,9 @@ public class LwjglGL implements GL, GL2, GL3,GL4 {
}
}
+ public void resetStats() {
+ }
+
public void glActiveTexture(int param1) {
GL13.glActiveTexture(param1);
}
@@ -237,6 +240,10 @@ public class LwjglGL implements GL, GL2, GL3,GL4 {
public String glGetString(int param1) {
return GL11.glGetString(param1);
}
+
+ public String glGetString(int param1, int param2) {
+ return GL30.glGetStringi(param1, param2);
+ }
public boolean glIsEnabled(int param1) {
return GL11.glIsEnabled(param1);
@@ -444,4 +451,10 @@ public class LwjglGL implements GL, GL2, GL3,GL4 {
public void glPatchParameter(int count) {
GL40.glPatchParameteri(GL40.GL_PATCH_VERTICES,count);
}
+
+ @Override
+ public void glDeleteVertexArrays(IntBuffer arrays) {
+ checkLimit(arrays);
+ ARBVertexArrayObject.glDeleteVertexArrays(arrays);
+ }
}
diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java
index 89139282a..2c6a63fd7 100644
--- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java
+++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java
@@ -7,12 +7,8 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.opengl.ARBDrawInstanced;
import org.lwjgl.opengl.ARBInstancedArrays;
-import org.lwjgl.opengl.ARBPixelBufferObject;
import org.lwjgl.opengl.ARBSync;
import org.lwjgl.opengl.ARBTextureMultisample;
-import org.lwjgl.opengl.EXTFramebufferBlit;
-import org.lwjgl.opengl.EXTFramebufferMultisample;
-import org.lwjgl.opengl.EXTFramebufferObject;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GLSync;
@@ -30,99 +26,51 @@ public class LwjglGLExt implements GLExt {
throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
}
}
-
- public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
- EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
- }
+ @Override
public void glBufferData(int target, IntBuffer data, int usage) {
checkLimit(data);
GL15.glBufferData(target, data, usage);
}
+ @Override
public void glBufferSubData(int target, long offset, IntBuffer data) {
checkLimit(data);
GL15.glBufferSubData(target, offset, data);
}
+ @Override
public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) {
ARBDrawInstanced.glDrawArraysInstancedARB(mode, first, count, primcount);
}
+ @Override
public void glDrawBuffers(IntBuffer bufs) {
checkLimit(bufs);
GL20.glDrawBuffers(bufs);
}
+ @Override
public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) {
ARBDrawInstanced.glDrawElementsInstancedARB(mode, indices_count, type, indices_buffer_offset, primcount);
}
+ @Override
public void glGetMultisample(int pname, int index, FloatBuffer val) {
checkLimit(val);
ARBTextureMultisample.glGetMultisample(pname, index, val);
}
- public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
- EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
- }
-
+ @Override
public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) {
ARBTextureMultisample.glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations);
}
+ @Override
public void glVertexAttribDivisorARB(int index, int divisor) {
ARBInstancedArrays.glVertexAttribDivisorARB(index, divisor);
}
- public void glBindFramebufferEXT(int param1, int param2) {
- EXTFramebufferObject.glBindFramebufferEXT(param1, param2);
- }
-
- public void glBindRenderbufferEXT(int param1, int param2) {
- EXTFramebufferObject.glBindRenderbufferEXT(param1, param2);
- }
-
- public int glCheckFramebufferStatusEXT(int param1) {
- return EXTFramebufferObject.glCheckFramebufferStatusEXT(param1);
- }
-
- public void glDeleteFramebuffersEXT(IntBuffer param1) {
- checkLimit(param1);
- EXTFramebufferObject.glDeleteFramebuffersEXT(param1);
- }
-
- public void glDeleteRenderbuffersEXT(IntBuffer param1) {
- checkLimit(param1);
- EXTFramebufferObject.glDeleteRenderbuffersEXT(param1);
- }
-
- public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
- EXTFramebufferObject.glFramebufferRenderbufferEXT(param1, param2, param3, param4);
- }
-
- public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
- EXTFramebufferObject.glFramebufferTexture2DEXT(param1, param2, param3, param4, param5);
- }
-
- public void glGenFramebuffersEXT(IntBuffer param1) {
- checkLimit(param1);
- EXTFramebufferObject.glGenFramebuffersEXT(param1);
- }
-
- public void glGenRenderbuffersEXT(IntBuffer param1) {
- checkLimit(param1);
- EXTFramebufferObject.glGenRenderbuffersEXT(param1);
- }
-
- public void glGenerateMipmapEXT(int param1) {
- EXTFramebufferObject.glGenerateMipmapEXT(param1);
- }
-
- public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
- EXTFramebufferObject.glRenderbufferStorageEXT(param1, param2, param3, param4);
- }
-
@Override
public Object glFenceSync(int condition, int flags) {
return ARBSync.glFenceSync(condition, flags);
diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java
new file mode 100644
index 000000000..159000a6c
--- /dev/null
+++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java
@@ -0,0 +1,98 @@
+package com.jme3.renderer.lwjgl;
+
+import com.jme3.renderer.RendererException;
+import com.jme3.renderer.opengl.GLFbo;
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+import org.lwjgl.opengl.EXTFramebufferBlit;
+import org.lwjgl.opengl.EXTFramebufferMultisample;
+import org.lwjgl.opengl.EXTFramebufferObject;
+
+/**
+ * Implements GLFbo via GL_EXT_framebuffer_object.
+ *
+ * @author Kirill Vainer
+ */
+public class LwjglGLFboEXT implements GLFbo {
+
+ private static void checkLimit(Buffer buffer) {
+ if (buffer == null) {
+ return;
+ }
+ if (buffer.limit() == 0) {
+ throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error");
+ }
+ if (buffer.remaining() == 0) {
+ throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
+ }
+ }
+
+ @Override
+ public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
+ EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
+ }
+
+ @Override
+ public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
+ EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
+ }
+
+ @Override
+ public void glBindFramebufferEXT(int param1, int param2) {
+ EXTFramebufferObject.glBindFramebufferEXT(param1, param2);
+ }
+
+ @Override
+ public void glBindRenderbufferEXT(int param1, int param2) {
+ EXTFramebufferObject.glBindRenderbufferEXT(param1, param2);
+ }
+
+ @Override
+ public int glCheckFramebufferStatusEXT(int param1) {
+ return EXTFramebufferObject.glCheckFramebufferStatusEXT(param1);
+ }
+
+ @Override
+ public void glDeleteFramebuffersEXT(IntBuffer param1) {
+ checkLimit(param1);
+ EXTFramebufferObject.glDeleteFramebuffersEXT(param1);
+ }
+
+ @Override
+ public void glDeleteRenderbuffersEXT(IntBuffer param1) {
+ checkLimit(param1);
+ EXTFramebufferObject.glDeleteRenderbuffersEXT(param1);
+ }
+
+ @Override
+ public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
+ EXTFramebufferObject.glFramebufferRenderbufferEXT(param1, param2, param3, param4);
+ }
+
+ @Override
+ public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
+ EXTFramebufferObject.glFramebufferTexture2DEXT(param1, param2, param3, param4, param5);
+ }
+
+ @Override
+ public void glGenFramebuffersEXT(IntBuffer param1) {
+ checkLimit(param1);
+ EXTFramebufferObject.glGenFramebuffersEXT(param1);
+ }
+
+ @Override
+ public void glGenRenderbuffersEXT(IntBuffer param1) {
+ checkLimit(param1);
+ EXTFramebufferObject.glGenRenderbuffersEXT(param1);
+ }
+
+ @Override
+ public void glGenerateMipmapEXT(int param1) {
+ EXTFramebufferObject.glGenerateMipmapEXT(param1);
+ }
+
+ @Override
+ public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
+ EXTFramebufferObject.glRenderbufferStorageEXT(param1, param2, param3, param4);
+ }
+}
diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java
new file mode 100644
index 000000000..acc540273
--- /dev/null
+++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java
@@ -0,0 +1,96 @@
+package com.jme3.renderer.lwjgl;
+
+import com.jme3.renderer.RendererException;
+import com.jme3.renderer.opengl.GLFbo;
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+import org.lwjgl.opengl.GL30;
+
+/**
+ * Implements GLFbo via OpenGL3+.
+ *
+ * @author Kirill Vainer
+ */
+public class LwjglGLFboGL3 implements GLFbo {
+
+ private static void checkLimit(Buffer buffer) {
+ if (buffer == null) {
+ return;
+ }
+ if (buffer.limit() == 0) {
+ throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error");
+ }
+ if (buffer.remaining() == 0) {
+ throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
+ }
+ }
+
+ @Override
+ public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
+ GL30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
+ }
+
+ @Override
+ public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
+ GL30.glRenderbufferStorageMultisample(target, samples, internalformat, width, height);
+ }
+
+ @Override
+ public void glBindFramebufferEXT(int param1, int param2) {
+ GL30.glBindFramebuffer(param1, param2);
+ }
+
+ @Override
+ public void glBindRenderbufferEXT(int param1, int param2) {
+ GL30.glBindRenderbuffer(param1, param2);
+ }
+
+ @Override
+ public int glCheckFramebufferStatusEXT(int param1) {
+ return GL30.glCheckFramebufferStatus(param1);
+ }
+
+ @Override
+ public void glDeleteFramebuffersEXT(IntBuffer param1) {
+ checkLimit(param1);
+ GL30.glDeleteFramebuffers(param1);
+ }
+
+ @Override
+ public void glDeleteRenderbuffersEXT(IntBuffer param1) {
+ checkLimit(param1);
+ GL30.glDeleteRenderbuffers(param1);
+ }
+
+ @Override
+ public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
+ GL30.glFramebufferRenderbuffer(param1, param2, param3, param4);
+ }
+
+ @Override
+ public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
+ GL30.glFramebufferTexture2D(param1, param2, param3, param4, param5);
+ }
+
+ @Override
+ public void glGenFramebuffersEXT(IntBuffer param1) {
+ checkLimit(param1);
+ GL30.glGenFramebuffers(param1);
+ }
+
+ @Override
+ public void glGenRenderbuffersEXT(IntBuffer param1) {
+ checkLimit(param1);
+ GL30.glGenRenderbuffers(param1);
+ }
+
+ @Override
+ public void glGenerateMipmapEXT(int param1) {
+ GL30.glGenerateMipmap(param1);
+ }
+
+ @Override
+ public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
+ GL30.glRenderbufferStorage(param1, param2, param3, param4);
+ }
+}
diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
index 1286323ef..c88f7b734 100644
--- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
+++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
@@ -39,13 +39,18 @@ import com.jme3.renderer.Renderer;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.lwjgl.LwjglGL;
import com.jme3.renderer.lwjgl.LwjglGLExt;
+import com.jme3.renderer.lwjgl.LwjglGLFboEXT;
+import com.jme3.renderer.lwjgl.LwjglGLFboGL3;
import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GL2;
import com.jme3.renderer.opengl.GL3;
+import com.jme3.renderer.opengl.GL4;
import com.jme3.renderer.opengl.GLDebugDesktop;
import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer;
+import com.jme3.renderer.opengl.GLTiming;
+import com.jme3.renderer.opengl.GLTimingState;
import com.jme3.renderer.opengl.GLTracer;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext;
@@ -203,28 +208,44 @@ public abstract class LwjglContext implements JmeContext {
}
if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2)
- || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
+ || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
GL gl = new LwjglGL();
- GLFbo glfbo = new LwjglGLExt();
+ GLExt glext = new LwjglGLExt();
+ GLFbo glfbo;
+
+ if (GLContext.getCapabilities().OpenGL30) {
+ glfbo = new LwjglGLFboGL3();
+ } else {
+ glfbo = new LwjglGLFboEXT();
+ }
if (settings.getBoolean("GraphicsDebug")) {
- gl = new GLDebugDesktop(gl, glfbo);
+ gl = new GLDebugDesktop(gl, glext, glfbo);
+ glext = (GLExt) gl;
glfbo = (GLFbo) gl;
}
+ if (settings.getBoolean("GraphicsTiming")) {
+ GLTimingState timingState = new GLTimingState();
+ gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class);
+ glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class);
+ glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class);
+ }
+
if (settings.getBoolean("GraphicsTrace")) {
- gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class);
- glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLExt.class);
+ gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class);
+ glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class);
+ glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class);
}
- renderer = new GLRenderer(gl, glfbo);
+ renderer = new GLRenderer(gl, glext, glfbo);
renderer.initialize();
} else {
throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer());
}
if (GLContext.getCapabilities().GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) {
- ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback());
+ ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback(new LwjglGLDebugOutputHandler()));
}
renderer.setMainFrameBufferSrgb(settings.getGammaCorrection());
diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java
new file mode 100644
index 000000000..c8f329f13
--- /dev/null
+++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java
@@ -0,0 +1,78 @@
+/*
+ * 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.system.lwjgl;
+
+import java.util.HashMap;
+import org.lwjgl.opengl.ARBDebugOutput;
+import org.lwjgl.opengl.ARBDebugOutputCallback;
+
+class LwjglGLDebugOutputHandler implements ARBDebugOutputCallback.Handler {
+
+ private static final HashMap constMap = new HashMap();
+ private static final String MESSAGE_FORMAT =
+ "[JME3] OpenGL debug message\r\n" +
+ " ID: %d\r\n" +
+ " Source: %s\r\n" +
+ " Type: %s\r\n" +
+ " Severity: %s\r\n" +
+ " Message: %s";
+
+ static {
+ constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_API_ARB, "API");
+ constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_APPLICATION_ARB, "APPLICATION");
+ constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_OTHER_ARB, "OTHER");
+ constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_SHADER_COMPILER_ARB, "SHADER_COMPILER");
+ constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_THIRD_PARTY_ARB, "THIRD_PARTY");
+ constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB, "WINDOW_SYSTEM");
+
+ constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB, "DEPRECATED_BEHAVIOR");
+ constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_ERROR_ARB, "ERROR");
+ constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_OTHER_ARB, "OTHER");
+ constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_PERFORMANCE_ARB, "PERFORMANCE");
+ constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_PORTABILITY_ARB, "PORTABILITY");
+ constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB, "UNDEFINED_BEHAVIOR");
+
+ constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_HIGH_ARB, "HIGH");
+ constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_MEDIUM_ARB, "MEDIUM");
+ constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_LOW_ARB, "LOW");
+ }
+
+ @Override
+ public void handleMessage(int source, int type, int id, int severity, String message) {
+ String sourceStr = constMap.get(source);
+ String typeStr = constMap.get(type);
+ String severityStr = constMap.get(severity);
+
+ System.err.println(String.format(MESSAGE_FORMAT, id, sourceStr, typeStr, severityStr, message));
+ }
+
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/Client.java b/jme3-networking/src/main/java/com/jme3/network/Client.java
index 6837a4d86..dd9873238 100644
--- a/jme3-networking/src/main/java/com/jme3/network/Client.java
+++ b/jme3-networking/src/main/java/com/jme3/network/Client.java
@@ -31,6 +31,8 @@
*/
package com.jme3.network;
+import com.jme3.network.service.ClientServiceManager;
+
/**
* Represents a remote connection to a server that can be used
@@ -72,6 +74,12 @@ public interface Client extends MessageConnection
* be able to connect to.
*/
public int getVersion();
+
+ /**
+ * Returns the manager for client services. Client services extend
+ * the functionality of the client.
+ */
+ public ClientServiceManager getServices();
/**
* Sends a message to the server.
diff --git a/jme3-networking/src/main/java/com/jme3/network/Server.java b/jme3-networking/src/main/java/com/jme3/network/Server.java
index 72926eab7..a52cb33bf 100644
--- a/jme3-networking/src/main/java/com/jme3/network/Server.java
+++ b/jme3-networking/src/main/java/com/jme3/network/Server.java
@@ -33,6 +33,8 @@ package com.jme3.network;
import java.util.Collection;
+import com.jme3.network.service.HostedServiceManager;
+
/**
* Represents a host that can send and receive messages to
* a set of remote client connections.
@@ -54,6 +56,12 @@ public interface Server
*/
public int getVersion();
+ /**
+ * Returns the manager for hosted services. Hosted services extend
+ * the functionality of the server.
+ */
+ public HostedServiceManager getServices();
+
/**
* Sends the specified message to all connected clients.
*/
diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java
index 54e8fd7f9..c0cc2e616 100644
--- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java
+++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java
@@ -37,6 +37,8 @@ import com.jme3.network.kernel.Connector;
import com.jme3.network.message.ChannelInfoMessage;
import com.jme3.network.message.ClientRegistrationMessage;
import com.jme3.network.message.DisconnectMessage;
+import com.jme3.network.service.ClientServiceManager;
+import com.jme3.network.service.serializer.ClientSerializerRegistrationsService;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
@@ -54,7 +56,7 @@ import java.util.logging.Logger;
*/
public class DefaultClient implements Client
{
- static Logger log = Logger.getLogger(DefaultClient.class.getName());
+ static final Logger log = Logger.getLogger(DefaultClient.class.getName());
// First two channels are reserved for reliable and
// unreliable. Note: channels are endpoint specific so these
@@ -80,10 +82,14 @@ public class DefaultClient implements Client
private ConnectorFactory connectorFactory;
+ private ClientServiceManager services;
+
public DefaultClient( String gameName, int version )
{
this.gameName = gameName;
this.version = version;
+ this.services = new ClientServiceManager(this);
+ addStandardServices();
}
public DefaultClient( String gameName, int version, Connector reliable, Connector fast,
@@ -93,6 +99,10 @@ public class DefaultClient implements Client
setPrimaryConnectors( reliable, fast, connectorFactory );
}
+ protected void addStandardServices() {
+ services.addService(new ClientSerializerRegistrationsService());
+ }
+
protected void setPrimaryConnectors( Connector reliable, Connector fast, ConnectorFactory connectorFactory )
{
if( reliable == null )
@@ -200,6 +210,11 @@ public class DefaultClient implements Client
{
return version;
}
+
+ public ClientServiceManager getServices()
+ {
+ return services;
+ }
public void send( Message message )
{
@@ -260,7 +275,7 @@ public class DefaultClient implements Client
{
checkRunning();
- closeConnections( null );
+ closeConnections( null );
}
protected void closeConnections( DisconnectInfo info )
@@ -268,6 +283,10 @@ public class DefaultClient implements Client
if( !isRunning )
return;
+ // Let the services get a chance to stop before we
+ // kill the connection.
+ services.stop();
+
// Send a close message
// Tell the thread it's ok to die
@@ -285,6 +304,9 @@ public class DefaultClient implements Client
fireDisconnected(info);
isRunning = false;
+
+ // Terminate the services
+ services.terminate();
}
public void addClientStateListener( ClientStateListener listener )
@@ -329,6 +351,9 @@ public class DefaultClient implements Client
protected void fireConnected()
{
+ // Let the services know we are finally started
+ services.start();
+
for( ClientStateListener l : stateListeners ) {
l.clientConnected( this );
}
diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java
index 3816fbace..0a9ac0ef1 100644
--- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java
+++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java
@@ -37,6 +37,8 @@ import com.jme3.network.kernel.Kernel;
import com.jme3.network.message.ChannelInfoMessage;
import com.jme3.network.message.ClientRegistrationMessage;
import com.jme3.network.message.DisconnectMessage;
+import com.jme3.network.service.HostedServiceManager;
+import com.jme3.network.service.serializer.ServerSerializerRegistrationsService;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
@@ -55,7 +57,7 @@ import java.util.logging.Logger;
*/
public class DefaultServer implements Server
{
- static Logger log = Logger.getLogger(DefaultServer.class.getName());
+ static final Logger log = Logger.getLogger(DefaultServer.class.getName());
// First two channels are reserved for reliable and
// unreliable
@@ -85,6 +87,8 @@ public class DefaultServer implements Server
= new MessageListenerRegistry();
private List connectionListeners = new CopyOnWriteArrayList();
+ private HostedServiceManager services;
+
public DefaultServer( String gameName, int version, Kernel reliable, Kernel fast )
{
if( reliable == null )
@@ -92,6 +96,8 @@ public class DefaultServer implements Server
this.gameName = gameName;
this.version = version;
+ this.services = new HostedServiceManager(this);
+ addStandardServices();
reliableAdapter = new KernelAdapter( this, reliable, dispatcher, true );
channels.add( reliableAdapter );
@@ -101,6 +107,10 @@ public class DefaultServer implements Server
}
}
+ protected void addStandardServices() {
+ services.addService(new ServerSerializerRegistrationsService());
+ }
+
public String getGameName()
{
return gameName;
@@ -110,6 +120,11 @@ public class DefaultServer implements Server
{
return version;
}
+
+ public HostedServiceManager getServices()
+ {
+ return services;
+ }
public int addChannel( int port )
{
@@ -164,7 +179,10 @@ public class DefaultServer implements Server
ka.start();
}
- isRunning = true;
+ isRunning = true;
+
+ // Start the services
+ services.start();
}
public boolean isRunning()
@@ -177,13 +195,20 @@ public class DefaultServer implements Server
if( !isRunning )
throw new IllegalStateException( "Server is not started." );
+ // First stop the services since we are about to
+ // kill the connections they are using
+ services.stop();
+
try {
// Kill the adpaters, they will kill the kernels
for( KernelAdapter ka : channels ) {
ka.close();
}
- isRunning = false;
+ isRunning = false;
+
+ // Now terminate all of the services
+ services.terminate();
} catch( InterruptedException e ) {
throw new RuntimeException( "Interrupted while closing", e );
}
@@ -396,6 +421,18 @@ public class DefaultServer implements Server
return endpointConnections.get(endpoint);
}
+ protected void removeConnecting( Endpoint p )
+ {
+ // No easy lookup for connecting Connections
+ // from endpoint.
+ for( Map.Entry e : connecting.entrySet() ) {
+ if( e.getValue().hasEndpoint(p) ) {
+ connecting.remove(e.getKey());
+ return;
+ }
+ }
+ }
+
protected void connectionClosed( Endpoint p )
{
if( p.isConnected() ) {
@@ -411,10 +448,10 @@ public class DefaultServer implements Server
// Also note: this method will be called multiple times per
// HostedConnection if it has multiple endpoints.
- Connection removed = null;
+ Connection removed;
synchronized( this ) {
// Just in case the endpoint was still connecting
- connecting.values().remove(p);
+ removeConnecting(p);
// And the regular management
removed = (Connection)endpointConnections.remove(p);
@@ -452,6 +489,16 @@ public class DefaultServer implements Server
id = nextId.getAndIncrement();
channels = new Endpoint[channelCount];
}
+
+ boolean hasEndpoint( Endpoint p )
+ {
+ for( Endpoint e : channels ) {
+ if( p == e ) {
+ return true;
+ }
+ }
+ return false;
+ }
void setChannel( int channel, Endpoint p )
{
@@ -557,6 +604,7 @@ public class DefaultServer implements Server
return Collections.unmodifiableSet(sessionData.keySet());
}
+ @Override
public String toString()
{
return "Connection[ id=" + id + ", reliable=" + channels[CH_RELIABLE]
diff --git a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java
new file mode 100644
index 000000000..da7f2a7cf
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java
@@ -0,0 +1,188 @@
+/*
+ * $Id: SerializerRegistrationsMessage.java 3829 2014-11-24 07:25:43Z pspeed $
+ *
+ * Copyright (c) 2012, Paul Speed
+ * All rights reserved.
+ */
+
+package com.jme3.network.message;
+
+import com.jme3.network.AbstractMessage;
+import com.jme3.network.serializing.Serializable;
+import com.jme3.network.serializing.Serializer;
+import com.jme3.network.serializing.SerializerRegistration;
+import com.jme3.network.serializing.serializers.FieldSerializer;
+import java.util.*;
+import java.util.jar.Attributes;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * Holds a compiled set of message registration information that
+ * can be sent over the wire. The received message can then be
+ * used to register all of the classes using the same IDs and
+ * same ordering, etc.. The intent is that the server compiles
+ * this message once it is sure that all serializable classes have
+ * been registered. It can then send this to each new client and
+ * they can use it to register all of the classes without requiring
+ * exactly reproducing the same calls that the server did to register
+ * messages.
+ *
+ *
Normally, JME recommends that apps have a common utility method
+ * that they call on both client and server. However, this makes
+ * pluggable services nearly impossible as some central class has to
+ * know about all registered serializers. This message implementation
+ * gets around by only requiring registration on the server.
+ *
+ * @author Paul Speed
+ */
+@Serializable
+public class SerializerRegistrationsMessage extends AbstractMessage {
+
+ static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName());
+
+ public static final Set ignore = new HashSet();
+ static {
+ // We could build this automatically but then we
+ // risk making a client and server out of date simply because
+ // their JME versions are out of date.
+ ignore.add(Boolean.class);
+ ignore.add(Float.class);
+ ignore.add(Boolean.class);
+ ignore.add(Byte.class);
+ ignore.add(Character.class);
+ ignore.add(Short.class);
+ ignore.add(Integer.class);
+ ignore.add(Long.class);
+ ignore.add(Float.class);
+ ignore.add(Double.class);
+ ignore.add(String.class);
+
+ ignore.add(DisconnectMessage.class);
+ ignore.add(ClientRegistrationMessage.class);
+
+ ignore.add(Date.class);
+ ignore.add(AbstractCollection.class);
+ ignore.add(AbstractList.class);
+ ignore.add(AbstractSet.class);
+ ignore.add(ArrayList.class);
+ ignore.add(HashSet.class);
+ ignore.add(LinkedHashSet.class);
+ ignore.add(LinkedList.class);
+ ignore.add(TreeSet.class);
+ ignore.add(Vector.class);
+ ignore.add(AbstractMap.class);
+ ignore.add(Attributes.class);
+ ignore.add(HashMap.class);
+ ignore.add(Hashtable.class);
+ ignore.add(IdentityHashMap.class);
+ ignore.add(TreeMap.class);
+ ignore.add(WeakHashMap.class);
+ ignore.add(Enum.class);
+
+ ignore.add(GZIPCompressedMessage.class);
+ ignore.add(ZIPCompressedMessage.class);
+
+ ignore.add(ChannelInfoMessage.class);
+
+ ignore.add(SerializerRegistrationsMessage.class);
+ ignore.add(SerializerRegistrationsMessage.Registration.class);
+ }
+
+ public static SerializerRegistrationsMessage INSTANCE;
+ public static Registration[] compiled;
+ private static final Serializer fieldSerializer = new FieldSerializer();
+
+ private Registration[] registrations;
+
+ public SerializerRegistrationsMessage() {
+ setReliable(true);
+ }
+
+ public SerializerRegistrationsMessage( Registration... registrations ) {
+ setReliable(true);
+ this.registrations = registrations;
+ }
+
+ public static void compile() {
+
+ // Let's just see what they are here
+ List list = new ArrayList();
+ for( SerializerRegistration reg : Serializer.getSerializerRegistrations() ) {
+ Class type = reg.getType();
+ if( ignore.contains(type) )
+ continue;
+ if( type.isPrimitive() )
+ continue;
+
+ list.add(new Registration(reg));
+ }
+
+ if( log.isLoggable(Level.FINE) ) {
+ log.log( Level.FINE, "Number of registered classes:{0}", list.size());
+ for( Registration reg : list ) {
+ log.log( Level.FINE, " {0}", reg);
+ }
+ }
+ compiled = list.toArray(new Registration[list.size()]);
+
+ INSTANCE = new SerializerRegistrationsMessage(compiled);
+ }
+
+ public void registerAll() {
+ for( Registration reg : registrations ) {
+ log.log( Level.INFO, "Registering:{0}", reg);
+ reg.register();
+ }
+ }
+
+ @Serializable
+ public static final class Registration {
+
+ private short id;
+ private String className;
+ private String serializerClassName;
+
+ public Registration() {
+ }
+
+ public Registration( SerializerRegistration reg ) {
+
+ this.id = reg.getId();
+ this.className = reg.getType().getName();
+ if( reg.getSerializer().getClass() != FieldSerializer.class ) {
+ this.serializerClassName = reg.getSerializer().getClass().getName();
+ }
+ }
+
+ public void register() {
+ try {
+ Class type = Class.forName(className);
+ Serializer serializer;
+ if( serializerClassName == null ) {
+ serializer = fieldSerializer;
+ } else {
+ Class serializerType = Class.forName(serializerClassName);
+ serializer = (Serializer)serializerType.newInstance();
+ }
+ SerializerRegistration result = Serializer.registerClassForId(id, type, serializer);
+ log.log( Level.FINE, " result:{0}", result);
+ } catch( ClassNotFoundException e ) {
+ throw new RuntimeException( "Class not found attempting to register:" + this, e );
+ } catch( InstantiationException e ) {
+ throw new RuntimeException( "Error instantiating serializer registering:" + this, e );
+ } catch( IllegalAccessException e ) {
+ throw new RuntimeException( "Error instantiating serializer registering:" + this, e );
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Registration[" + id + " = " + className + ", serializer=" + serializerClassName + "]";
+ }
+ }
+}
+
+
+
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java
new file mode 100644
index 000000000..bd1836451
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 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.network.service;
+
+
+/**
+ * Convenient base class for ClientServices providing some default ClientService
+ * interface implementations as well as a few convenience methods
+ * such as getServiceManager() and getService(type). Subclasses
+ * must at least override the onInitialize() method to handle
+ * service initialization.
+ *
+ * @author Paul Speed
+ */
+public abstract class AbstractClientService extends AbstractService
+ implements ClientService {
+
+ protected AbstractClientService() {
+ }
+
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java
new file mode 100644
index 000000000..789767b73
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 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.network.service;
+
+import com.jme3.network.HostedConnection;
+import com.jme3.network.Server;
+
+
+/**
+ * Convenient base class for HostedServices providing some default HostedService
+ * interface implementations as well as a few convenience methods
+ * such as getServiceManager() and getService(type). Subclasses
+ * must at least override the onInitialize() method to handle
+ * service initialization.
+ *
+ * @author Paul Speed
+ */
+public abstract class AbstractHostedService extends AbstractService
+ implements HostedService {
+
+ protected AbstractHostedService() {
+ }
+
+ /**
+ * Default implementation does nothing. Implementations can
+ * override this to peform custom new connection behavior.
+ */
+ @Override
+ public void connectionAdded(Server server, HostedConnection hc) {
+ }
+
+ /**
+ * Default implementation does nothing. Implementations can
+ * override this to peform custom leaving connection behavior.
+ */
+ @Override
+ public void connectionRemoved(Server server, HostedConnection hc) {
+ }
+
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java
new file mode 100644
index 000000000..84df3d81b
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 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.network.service;
+
+
+/**
+ * Base class providing some default Service interface implementations
+ * as well as a few convenience methods such as getServiceManager()
+ * and getService(type). Subclasses must at least override the
+ * onInitialize() method to handle service initialization.
+ *
+ * @author Paul Speed
+ */
+public abstract class AbstractService implements Service {
+
+ private S serviceManager;
+
+ protected AbstractService() {
+ }
+
+ /**
+ * Returns the ServiceManager that was passed to
+ * initialize() during service initialization.
+ */
+ protected S getServiceManager() {
+ return serviceManager;
+ }
+
+ /**
+ * Retrieves the first sibling service of the specified
+ * type.
+ */
+ protected > T getService( Class type ) {
+ return type.cast(serviceManager.getService(type));
+ }
+
+ /**
+ * Initializes this service by keeping a reference to
+ * the service manager and calling onInitialize().
+ */
+ @Override
+ public final void initialize( S serviceManager ) {
+ this.serviceManager = serviceManager;
+ onInitialize(serviceManager);
+ }
+
+ /**
+ * Called during initialize() for the subclass to perform
+ * implementation specific initialization.
+ */
+ protected abstract void onInitialize( S serviceManager );
+
+ /**
+ * Default implementation does nothing. Implementations can
+ * override this to peform custom startup behavior.
+ */
+ @Override
+ public void start() {
+ }
+
+ /**
+ * Default implementation does nothing. Implementations can
+ * override this to peform custom stop behavior.
+ */
+ @Override
+ public void stop() {
+ }
+
+ /**
+ * Default implementation does nothing. Implementations can
+ * override this to peform custom termination behavior.
+ */
+ @Override
+ public void terminate( S serviceManager ) {
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + "[serviceManager=" + serviceManager + "]";
+ }
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java
new file mode 100644
index 000000000..c8057cb31
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientService.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 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.network.service;
+
+
+/**
+ * Interface implemented by Client-side services that augment
+ * a network Client's functionality.
+ *
+ * @author Paul Speed
+ */
+public interface ClientService extends Service {
+
+ /**
+ * Called when the service is first attached to the service
+ * manager.
+ */
+ @Override
+ public void initialize( ClientServiceManager serviceManager );
+
+ /**
+ * Called when the service manager is started or if the
+ * service is added to an already started service manager.
+ */
+ @Override
+ public void start();
+
+ /**
+ * Called when the service is shutting down. All services
+ * are stopped and any service manager resources are closed
+ * before the services are terminated.
+ */
+ @Override
+ public void stop();
+
+ /**
+ * The service manager is fully shutting down. All services
+ * have been stopped and related connections closed.
+ */
+ @Override
+ public void terminate( ClientServiceManager serviceManager );
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java
new file mode 100644
index 000000000..dc204fb2f
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 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.network.service;
+
+import com.jme3.network.Client;
+
+
+/**
+ * Manages ClientServices on behalf of a network Client object.
+ *
+ * @author Paul Speed
+ */
+public class ClientServiceManager extends ServiceManager {
+
+ private Client client;
+
+ /**
+ * Creates a new ClientServiceManager for the specified network Client.
+ */
+ public ClientServiceManager( Client client ) {
+ this.client = client;
+ }
+
+ /**
+ * Returns the network Client associated with this ClientServiceManager.
+ */
+ public Client getClient() {
+ return client;
+ }
+
+ /**
+ * Returns 'this' and is what is passed to ClientService.initialize()
+ * and ClientService.termnate();
+ */
+ @Override
+ protected final ClientServiceManager getParent() {
+ return this;
+ }
+
+ /**
+ * Adds the specified ClientService and initializes it. If the service manager
+ * has already been started then the service will also be started.
+ */
+ public void addService( ClientService s ) {
+ super.addService(s);
+ }
+
+ /**
+ * Adds all of the specified ClientServices and initializes them. If the service manager
+ * has already been started then the services will also be started.
+ * This is a convenience method that delegates to addService(), thus each
+ * service will be initialized (and possibly started) in sequence rather
+ * than doing them all at the end.
+ */
+ public void addServices( ClientService... services ) {
+ for( ClientService s : services ) {
+ super.addService(s);
+ }
+ }
+
+ /**
+ * Removes the specified ClientService from this service manager, stopping
+ * and terminating it as required. If this service manager is in a
+ * started state then the service will be stopped. After removal,
+ * the service will be terminated.
+ */
+ public void removeService( ClientService s ) {
+ super.removeService(s);
+ }
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java
new file mode 100644
index 000000000..f522b72d6
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedService.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 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.network.service;
+
+import com.jme3.network.ConnectionListener;
+
+
+/**
+ * Interface implemented by Server-side services that augment
+ * a network Server's functionality.
+ *
+ * @author Paul Speed
+ */
+public interface HostedService extends Service, ConnectionListener {
+
+ /**
+ * Called when the service is first attached to the service
+ * manager.
+ */
+ @Override
+ public void initialize( HostedServiceManager serviceManager );
+
+ /**
+ * Called when the service manager is started or if the
+ * service is added to an already started service manager.
+ */
+ @Override
+ public void start();
+
+ /**
+ * Called when the service is shutting down. All services
+ * are stopped and any service manager resources are closed
+ * before the services are terminated.
+ */
+ @Override
+ public void stop();
+
+ /**
+ * The service manager is fully shutting down. All services
+ * have been stopped and related connections closed.
+ */
+ @Override
+ public void terminate( HostedServiceManager serviceManager );
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java
new file mode 100644
index 000000000..3606ba44b
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 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.network.service;
+
+import com.jme3.network.ConnectionListener;
+import com.jme3.network.HostedConnection;
+import com.jme3.network.Server;
+
+
+/**
+ * Manages HostedServices on behalf of a network Server object.
+ * All HostedServices are automatically informed about new and
+ * leaving connections.
+ *
+ * @author Paul Speed
+ */
+public class HostedServiceManager extends ServiceManager {
+
+ private Server server;
+ private ConnectionObserver connectionObserver;
+
+ /**
+ * Creates a HostedServiceManager for the specified network Server.
+ */
+ public HostedServiceManager( Server server ) {
+ this.server = server;
+ this.connectionObserver = new ConnectionObserver();
+ server.addConnectionListener(connectionObserver);
+ }
+
+ /**
+ * Returns the network Server associated with this HostedServiceManager.
+ */
+ public Server getServer() {
+ return server;
+ }
+
+ /**
+ * Returns 'this' and is what is passed to HostedService.initialize()
+ * and HostedService.termnate();
+ */
+ @Override
+ protected final HostedServiceManager getParent() {
+ return this;
+ }
+
+ /**
+ * Adds the specified HostedService and initializes it. If the service manager
+ * has already been started then the service will also be started.
+ */
+ public void addService( HostedService s ) {
+ super.addService(s);
+ }
+
+ /**
+ * Adds all of the specified HostedServices and initializes them. If the service manager
+ * has already been started then the services will also be started.
+ * This is a convenience method that delegates to addService(), thus each
+ * service will be initialized (and possibly started) in sequence rather
+ * than doing them all at the end.
+ */
+ public void addServices( HostedService... services ) {
+ for( HostedService s : services ) {
+ super.addService(s);
+ }
+ }
+
+ /**
+ * Removes the specified HostedService from this service manager, stopping
+ * and terminating it as required. If this service manager is in a
+ * started state then the service will be stopped. After removal,
+ * the service will be terminated.
+ */
+ public void removeService( HostedService s ) {
+ super.removeService(s);
+ }
+
+ /**
+ * Called internally when a new connection has been added so that the
+ * services can be notified.
+ */
+ protected void addConnection( HostedConnection hc ) {
+ for( Service s : getServices() ) {
+ ((HostedService)s).connectionAdded(server, hc);
+ }
+ }
+
+ /**
+ * Called internally when a connection has been removed so that the
+ * services can be notified.
+ */
+ protected void removeConnection( HostedConnection hc ) {
+ for( Service s : getServices() ) {
+ ((HostedService)s).connectionRemoved(server, hc);
+ }
+ }
+
+ protected class ConnectionObserver implements ConnectionListener {
+
+ @Override
+ public void connectionAdded(Server server, HostedConnection hc) {
+ addConnection(hc);
+ }
+
+ @Override
+ public void connectionRemoved(Server server, HostedConnection hc) {
+ removeConnection(hc);
+ }
+ }
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/Service.java b/jme3-networking/src/main/java/com/jme3/network/service/Service.java
new file mode 100644
index 000000000..f56c90dda
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/Service.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 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.network.service;
+
+
+/**
+ * The base interface for managed services.
+ *
+ * @author Paul Speed
+ */
+public interface Service {
+
+ /**
+ * Called when the service is first attached to the service
+ * manager.
+ */
+ public void initialize( S serviceManager );
+
+ /**
+ * Called when the service manager is started or if the
+ * service is added to an already started service manager.
+ */
+ public void start();
+
+ /**
+ * Called when the service manager is shutting down. All services
+ * are stopped and any service manager resources are closed
+ * before the services are terminated.
+ */
+ public void stop();
+
+ /**
+ * The service manager is fully shutting down. All services
+ * have been stopped and related connections closed.
+ */
+ public void terminate( S serviceManager );
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java
new file mode 100644
index 000000000..b8ee7d3c4
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 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.network.service;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * The base service manager class from which the HostedServiceManager
+ * and ClientServiceManager classes are derived. This manages the
+ * the underlying services and their life cycles.
+ *
+ * @author Paul Speed
+ */
+public abstract class ServiceManager {
+
+ private List> services = new CopyOnWriteArrayList>();
+ private volatile boolean started = false;
+
+ protected ServiceManager() {
+ }
+
+ /**
+ * Retreives the 'parent' of this service manager, usually
+ * a more specifically typed version of 'this' but it can be
+ * anything the seervices are expecting.
+ */
+ protected abstract T getParent();
+
+ /**
+ * Returns the complete list of services managed by this
+ * service manager. This list is thread safe following the
+ * CopyOnWriteArrayList semantics.
+ */
+ protected List> getServices() {
+ return services;
+ }
+
+ /**
+ * Starts this service manager and all services that it contains.
+ * Any services added after the service manager has started will have
+ * their start() methods called.
+ */
+ public void start() {
+ if( started ) {
+ return;
+ }
+ for( Service s : services ) {
+ s.start();
+ }
+ started = true;
+ }
+
+ /**
+ * Returns true if this service manager has been started.
+ */
+ public boolean isStarted() {
+ return started;
+ }
+
+ /**
+ * Stops all services and puts the service manager into a stopped state.
+ */
+ public void stop() {
+ if( !started ) {
+ throw new IllegalStateException(getClass().getSimpleName() + " not started.");
+ }
+ for( Service s : services ) {
+ s.stop();
+ }
+ started = false;
+ }
+
+ /**
+ * Adds the specified service and initializes it. If the service manager
+ * has already been started then the service will also be started.
+ */
+ public > void addService( S s ) {
+ services.add(s);
+ s.initialize(getParent());
+ if( started ) {
+ s.start();
+ }
+ }
+
+ /**
+ * Removes the specified service from this service manager, stopping
+ * and terminating it as required. If this service manager is in a
+ * started state then the service will be stopped. After removal,
+ * the service will be terminated.
+ */
+ public > void removeService( S s ) {
+ if( started ) {
+ s.stop();
+ }
+ services.remove(s);
+ s.terminate(getParent());
+ }
+
+ /**
+ * Terminates all services. If the service manager has not been
+ * stopped yet then it will be stopped.
+ */
+ public void terminate() {
+ if( started ) {
+ stop();
+ }
+ for( Service s : services ) {
+ s.terminate(getParent());
+ }
+ }
+
+ /**
+ * Retrieves the first service of the specified type.
+ */
+ public > S getService( Class type ) {
+ for( Service s : services ) {
+ if( type.isInstance(s) ) {
+ return type.cast(s);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + "[services=" + services + "]";
+ }
+}
+
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java
new file mode 100644
index 000000000..d9ea134e1
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 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.network.service.rpc;
+
+import com.jme3.network.Client;
+import com.jme3.network.util.ObjectMessageDelegator;
+import com.jme3.network.service.AbstractClientService;
+import com.jme3.network.service.ClientServiceManager;
+
+
+/**
+ * RPC service that can be added to a network Client to
+ * add RPC send/receive capabilities. Remote procedure
+ * calls can be made to the server and responses retrieved.
+ * Any remote procedure calls that the server performs for
+ * this connection will be received by this service and delegated
+ * to the appropriate RpcHandlers.
+ *
+ * @author Paul Speed
+ */
+public class RpcClientService extends AbstractClientService {
+
+ private RpcConnection rpc;
+ private ObjectMessageDelegator delegator;
+
+ /**
+ * Creates a new RpcClientService that can be registered
+ * with the network Client object.
+ */
+ public RpcClientService() {
+ }
+
+ /**
+ * Used internally to setup the RpcConnection and MessageDelegator.
+ */
+ @Override
+ protected void onInitialize( ClientServiceManager serviceManager ) {
+ Client client = serviceManager.getClient();
+ this.rpc = new RpcConnection(client);
+
+ delegator = new ObjectMessageDelegator(rpc, true);
+ client.addMessageListener(delegator, delegator.getMessageTypes());
+ }
+
+ /**
+ * Used internally to unregister the RPC MessageDelegator that
+ * was previously added to the network Client.
+ */
+ @Override
+ public void terminate( ClientServiceManager serviceManager ) {
+ Client client = serviceManager.getClient();
+ client.removeMessageListener(delegator, delegator.getMessageTypes());
+ }
+
+ /**
+ * Performs a synchronous call on the server against the specified
+ * object using the specified procedure ID. Both inboud and outbound
+ * communication is done on the specified channel.
+ */
+ public Object callAndWait( byte channel, short objId, short procId, Object... args ) {
+ return rpc.callAndWait(channel, objId, procId, args);
+ }
+
+ /**
+ * Performs an asynchronous call on the server against the specified
+ * object using the specified procedure ID. Communication is done
+ * over the specified channel. No responses are received and none
+ * are waited for.
+ */
+ public void callAsync( byte channel, short objId, short procId, Object... args ) {
+ rpc.callAsync(channel, objId, procId, args);
+ }
+
+ /**
+ * Register a handler that will be called when the server
+ * performs a remove procedure call against this client.
+ * Only one handler per object ID can be registered at any given time,
+ * though the same handler can be registered for multiple object
+ * IDs.
+ */
+ public void registerHandler( short objId, RpcHandler handler ) {
+ rpc.registerHandler(objId, handler);
+ }
+
+ /**
+ * Removes a previously registered handler for the specified
+ * object ID.
+ */
+ public void removeHandler( short objId, RpcHandler handler ) {
+ rpc.removeHandler(objId, handler);
+ }
+
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java
new file mode 100644
index 000000000..b78316bf3
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 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.network.service.rpc;
+
+import com.jme3.network.MessageConnection;
+import com.jme3.network.service.rpc.msg.RpcCallMessage;
+import com.jme3.network.service.rpc.msg.RpcResponseMessage;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * Wraps a message connection to provide RPC call support. This
+ * is used internally by the RpcClientService and RpcHostedService to manage
+ * network messaging.
+ *
+ * @author Paul Speed
+ */
+public class RpcConnection {
+
+ static final Logger log = Logger.getLogger(RpcConnection.class.getName());
+
+ /**
+ * The underlying connection upon which RPC call messages are sent
+ * and RPC response messages are received. It can be a Client or
+ * a HostedConnection depending on the mode of the RPC service.
+ */
+ private MessageConnection connection;
+
+ /**
+ * The objectId index of RpcHandler objects that are used to perform the
+ * RPC calls for a particular object.
+ */
+ private Map handlers = new ConcurrentHashMap();
+
+ /**
+ * Provides unique messages IDs for outbound synchronous call
+ * messages. These are then used in the responses index to
+ * locate the proper ResponseHolder objects.
+ */
+ private AtomicLong sequenceNumber = new AtomicLong();
+
+ /**
+ * Tracks the ResponseHolder objects for sent message IDs. When the
+ * response is received, the appropriate handler is found here and the
+ * response or error set, thus releasing the waiting caller.
+ */
+ private Map responses = new ConcurrentHashMap();
+
+ /**
+ * Creates a new RpcConnection for the specified network connection.
+ */
+ public RpcConnection( MessageConnection connection ) {
+ this.connection = connection;
+ }
+
+ /**
+ * Clears any pending synchronous calls causing them to
+ * throw an exception with the message "Closing connection".
+ */
+ public void close() {
+ // Let any pending waits go free
+ for( ResponseHolder holder : responses.values() ) {
+ holder.release();
+ }
+ }
+
+ /**
+ * Performs a remote procedure call with the specified arguments and waits
+ * for the response. Both the outbound message and inbound response will
+ * be sent on the specified channel.
+ */
+ public Object callAndWait( byte channel, short objId, short procId, Object... args ) {
+
+ RpcCallMessage msg = new RpcCallMessage(sequenceNumber.getAndIncrement(),
+ channel, objId, procId, args);
+
+ // Need to register an object so we can wait for the response.
+ // ...before we send it. Just in case.
+ ResponseHolder holder = new ResponseHolder(msg);
+ responses.put(msg.getMessageId(), holder);
+
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel});
+ }
+ if( channel >= 0 ) {
+ connection.send(channel, msg);
+ } else {
+ connection.send(msg);
+ }
+
+ return holder.getResponse();
+ }
+
+ /**
+ * Performs a remote procedure call with the specified arguments but does
+ * not wait for a response. The outbound message is sent on the specified channel.
+ * There is no inbound response message.
+ */
+ public void callAsync( byte channel, short objId, short procId, Object... args ) {
+
+ RpcCallMessage msg = new RpcCallMessage(-1, channel, objId, procId, args);
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel});
+ }
+ connection.send(channel, msg);
+ }
+
+ /**
+ * Register a handler that can be called by the other end
+ * of the connection using the specified object ID. Only one
+ * handler per object ID can be registered at any given time,
+ * though the same handler can be registered for multiple object
+ * IDs.
+ */
+ public void registerHandler( short objId, RpcHandler handler ) {
+ handlers.put(objId, handler);
+ }
+
+ /**
+ * Removes a previously registered handler for the specified
+ * object ID.
+ */
+ public void removeHandler( short objId, RpcHandler handler ) {
+ RpcHandler removing = handlers.get(objId);
+ if( handler != removing ) {
+ throw new IllegalArgumentException("Handler not registered for object ID:"
+ + objId + ", handler:" + handler );
+ }
+ handlers.remove(objId);
+ }
+
+ protected void send( byte channel, RpcResponseMessage msg ) {
+ if( channel >= 0 ) {
+ connection.send(channel, msg);
+ } else {
+ connection.send(msg);
+ }
+ }
+
+ /**
+ * Called internally when an RpcCallMessage is received from
+ * the remote connection.
+ */
+ public void handleMessage( RpcCallMessage msg ) {
+
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "handleMessage({0})", msg);
+ }
+ RpcHandler handler = handlers.get(msg.getObjectId());
+ try {
+ if( handler == null ) {
+ throw new RuntimeException("Handler not found for objectID:" + msg.getObjectId());
+ }
+ Object result = handler.call(this, msg.getObjectId(), msg.getProcedureId(), msg.getArguments());
+ if( !msg.isAsync() ) {
+ send(msg.getChannel(), new RpcResponseMessage(msg.getMessageId(), result));
+ }
+ } catch( Exception e ) {
+ if( !msg.isAsync() ) {
+ send(msg.getChannel(), new RpcResponseMessage(msg.getMessageId(), e));
+ } else {
+ log.log(Level.SEVERE, "Error invoking async call for:" + msg, e);
+ }
+ }
+ }
+
+ /**
+ * Called internally when an RpcResponseMessage is received from
+ * the remote connection.
+ */
+ public void handleMessage( RpcResponseMessage msg ) {
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "handleMessage({0})", msg);
+ }
+ ResponseHolder holder = responses.remove(msg.getMessageId());
+ if( holder == null ) {
+ return;
+ }
+ holder.setResponse(msg);
+ }
+
+ /**
+ * Sort of like a Future, holds a locked reference to a response
+ * until the remote call has completed and returned a response.
+ */
+ private class ResponseHolder {
+ private Object response;
+ private String error;
+ private RpcCallMessage msg;
+ boolean received = false;
+
+ public ResponseHolder( RpcCallMessage msg ) {
+ this.msg = msg;
+ }
+
+ public synchronized void setResponse( RpcResponseMessage msg ) {
+ this.response = msg.getResult();
+ this.error = msg.getError();
+ this.received = true;
+ notifyAll();
+ }
+
+ public synchronized Object getResponse() {
+ try {
+ while(!received) {
+ wait();
+ }
+ } catch( InterruptedException e ) {
+ throw new RuntimeException("Interrupted waiting for respone to:" + msg, e);
+ }
+ if( error != null ) {
+ throw new RuntimeException("Error calling remote procedure:" + msg + "\n" + error);
+ }
+ return response;
+ }
+
+ public synchronized void release() {
+ if( received ) {
+ return;
+ }
+ // Else signal an error for the callers
+ this.error = "Closing connection";
+ this.received = true;
+ }
+ }
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java
new file mode 100644
index 000000000..d7d99981d
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 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.network.service.rpc;
+
+
+/**
+ * Implementations of this interface can be registered with
+ * the RpcClientService or RpcHostService to handle the
+ * remote procedure calls for a given object or objects.
+ *
+ * @author Paul Speed
+ */
+public interface RpcHandler {
+
+ /**
+ * Called when a remote procedure call request is received for a particular
+ * object from the other end of the network connection.
+ */
+ public Object call( RpcConnection conn, short objectId, short procId, Object... args );
+}
+
+
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java
new file mode 100644
index 000000000..4a347b5a3
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 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.network.service.rpc;
+
+import com.jme3.network.HostedConnection;
+import com.jme3.network.Server;
+import com.jme3.network.serializing.Serializer;
+import com.jme3.network.util.SessionDataDelegator;
+import com.jme3.network.service.AbstractHostedService;
+import com.jme3.network.service.HostedServiceManager;
+import com.jme3.network.service.rpc.msg.RpcCallMessage;
+import com.jme3.network.service.rpc.msg.RpcResponseMessage;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * RPC service that can be added to a network Server to
+ * add RPC send/receive capabilities. For a particular
+ * HostedConnection, Remote procedure calls can be made to the
+ * associated Client and responses retrieved. Any remote procedure
+ * calls that the Client performs for this connection will be
+ * received by this service and delegated to the appropriate RpcHandlers.
+ *
+ * Note: it can be dangerous for a server to perform synchronous
+ * RPC calls to a client but especially so if not done as part
+ * of the response to some other message. ie: iterating over all
+ * or some HostedConnections to perform synchronous RPC calls
+ * will be slow and potentially block the server's threads in ways
+ * that can cause deadlocks or odd contention.
+ *
+ * @author Paul Speed
+ */
+public class RpcHostedService extends AbstractHostedService {
+
+ private static final String ATTRIBUTE_NAME = "rpcSession";
+
+ static final Logger log = Logger.getLogger(RpcHostedService.class.getName());
+
+ private boolean autoHost;
+ private SessionDataDelegator delegator;
+
+ /**
+ * Creates a new RPC host service that can be registered
+ * with the Network server and will automatically 'host'
+ * RPC services and each new network connection.
+ */
+ public RpcHostedService() {
+ this(true);
+ }
+
+ /**
+ * Creates a new RPC host service that can be registered
+ * with the Network server and will optionally 'host'
+ * RPC services and each new network connection depending
+ * on the specified 'autoHost' flag.
+ */
+ public RpcHostedService( boolean autoHost ) {
+ this.autoHost = autoHost;
+
+ // This works for me... has to be different in
+ // the general case
+ Serializer.registerClasses(RpcCallMessage.class, RpcResponseMessage.class);
+ }
+
+ /**
+ * Used internally to setup the message delegator that will
+ * handle HostedConnection specific messages and forward them
+ * to that connection's RpcConnection.
+ */
+ @Override
+ protected void onInitialize( HostedServiceManager serviceManager ) {
+ Server server = serviceManager.getServer();
+
+ // A general listener for forwarding the messages
+ // to the client-specific handler
+ this.delegator = new SessionDataDelegator(RpcConnection.class,
+ ATTRIBUTE_NAME,
+ true);
+ server.addMessageListener(delegator, delegator.getMessageTypes());
+
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "Registered delegator for message types:{0}", Arrays.asList(delegator.getMessageTypes()));
+ }
+ }
+
+ /**
+ * When set to true, all new connections will automatically have
+ * RPC hosting services attached to them, meaning they can send
+ * and receive RPC calls. If this is set to false then it is up
+ * to other services to eventually call startHostingOnConnection().
+ *
+ *
Reasons for doing this vary but usually would be because
+ * the client shouldn't be allowed to perform any RPC calls until
+ * it has provided more information. In general, this is unnecessary
+ * because the RpcHandler registries are not shared. Each client
+ * gets their own and RPC calls will fail until the appropriate
+ * objects have been registtered.
+ */
+ public void setAutoHost( boolean b ) {
+ this.autoHost = b;
+ }
+
+ /**
+ * Returns true if this service automatically attaches RPC
+ * hosting capabilities to new connections.
+ */
+ public boolean getAutoHost() {
+ return autoHost;
+ }
+
+ /**
+ * Retrieves the RpcConnection for the specified HostedConnection
+ * if that HostedConnection has had RPC services started using
+ * startHostingOnConnection() (or via autohosting). Returns null
+ * if the connection currently doesn't have RPC hosting services
+ * attached.
+ */
+ public RpcConnection getRpcConnection( HostedConnection hc ) {
+ return hc.getAttribute(ATTRIBUTE_NAME);
+ }
+
+ /**
+ * Sets up RPC hosting services for the hosted connection allowing
+ * getRpcConnection() to return a valid RPC connection object.
+ * This method is called automatically for all new connections if
+ * autohost is set to true.
+ */
+ public void startHostingOnConnection( HostedConnection hc ) {
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "startHostingOnConnection:{0}", hc);
+ }
+ hc.setAttribute(ATTRIBUTE_NAME, new RpcConnection(hc));
+ }
+
+ /**
+ * Removes any RPC hosting services associated with the specified
+ * connection. Calls to getRpcConnection() will return null for
+ * this connection. The connection's RpcConnection is also closed,
+ * releasing any waiting synchronous calls with a "Connection closing"
+ * error.
+ * This method is called automatically for all leaving connections if
+ * autohost is set to true.
+ */
+ public void stopHostingOnConnection( HostedConnection hc ) {
+ RpcConnection rpc = hc.getAttribute(ATTRIBUTE_NAME);
+ if( rpc == null ) {
+ return;
+ }
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "stopHostingOnConnection:{0}", hc);
+ }
+ hc.setAttribute(ATTRIBUTE_NAME, null);
+ rpc.close();
+ }
+
+ /**
+ * Used internally to remove the message delegator from the
+ * server.
+ */
+ @Override
+ public void terminate(HostedServiceManager serviceManager) {
+ Server server = serviceManager.getServer();
+ server.removeMessageListener(delegator, delegator.getMessageTypes());
+ }
+
+ /**
+ * Called internally when a new connection is detected for
+ * the server. If the current autoHost property is true then
+ * startHostingOnConnection(hc) is called.
+ */
+ @Override
+ public void connectionAdded(Server server, HostedConnection hc) {
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "connectionAdded({0}, {1})", new Object[]{server, hc});
+ }
+ if( autoHost ) {
+ startHostingOnConnection(hc);
+ }
+ }
+
+ /**
+ * Called internally when an existing connection is leaving
+ * the server. If the current autoHost property is true then
+ * stopHostingOnConnection(hc) is called.
+ */
+ @Override
+ public void connectionRemoved(Server server, HostedConnection hc) {
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "connectionRemoved({0}, {1})", new Object[]{server, hc});
+ }
+ stopHostingOnConnection(hc);
+ }
+
+}
+
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java
new file mode 100644
index 000000000..70f12f1e0
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 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.network.service.rpc.msg;
+
+import com.jme3.network.AbstractMessage;
+import com.jme3.network.serializing.Serializable;
+
+
+/**
+ * Used internally to send RPC call information to
+ * the other end of a connection for execution.
+ *
+ * @author Paul Speed
+ */
+@Serializable
+public class RpcCallMessage extends AbstractMessage {
+
+ private long msgId;
+ private byte channel;
+ private short objId;
+ private short procId;
+ private Object[] args;
+
+ public RpcCallMessage() {
+ }
+
+ public RpcCallMessage( long msgId, byte channel, short objId, short procId, Object... args ) {
+ this.msgId = msgId;
+ this.channel = channel;
+ this.objId = objId;
+ this.procId = procId;
+ this.args = args;
+ }
+
+ public long getMessageId() {
+ return msgId;
+ }
+
+ public byte getChannel() {
+ return channel;
+ }
+
+ public boolean isAsync() {
+ return msgId == -1;
+ }
+
+ public short getObjectId() {
+ return objId;
+ }
+
+ public short getProcedureId() {
+ return procId;
+ }
+
+ public Object[] getArguments() {
+ return args;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[#" + msgId + ", channel=" + channel
+ + (isAsync() ? ", async" : ", sync")
+ + ", objId=" + objId
+ + ", procId=" + procId
+ + ", args.length=" + args.length
+ + "]";
+ }
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java
new file mode 100644
index 000000000..efb0def6a
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 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.network.service.rpc.msg;
+
+import com.jme3.network.AbstractMessage;
+import com.jme3.network.serializing.Serializable;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+
+/**
+ * Used internally to send an RPC call's response back to
+ * the caller.
+ *
+ * @author Paul Speed
+ */
+@Serializable
+public class RpcResponseMessage extends AbstractMessage {
+
+ private long msgId;
+ private Object result;
+ private String error;
+
+ public RpcResponseMessage() {
+ }
+
+ public RpcResponseMessage( long msgId, Object result ) {
+ this.msgId = msgId;
+ this.result = result;
+ }
+
+ public RpcResponseMessage( long msgId, Throwable t ) {
+ this.msgId = msgId;
+
+ StringWriter sOut = new StringWriter();
+ PrintWriter out = new PrintWriter(sOut);
+ t.printStackTrace(out);
+ out.close();
+ this.error = sOut.toString();
+ }
+
+ public long getMessageId() {
+ return msgId;
+ }
+
+ public Object getResult() {
+ return result;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[#" + msgId + ", result=" + result
+ + "]";
+ }
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java
new file mode 100644
index 000000000..911ce0fb1
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 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.network.service.serializer;
+
+import com.jme3.network.Client;
+import com.jme3.network.Message;
+import com.jme3.network.MessageListener;
+import com.jme3.network.message.SerializerRegistrationsMessage;
+import com.jme3.network.serializing.Serializer;
+import com.jme3.network.service.AbstractClientService;
+import com.jme3.network.service.ClientServiceManager;
+
+
+/**
+ *
+ *
+ * @author Paul Speed
+ */
+public class ClientSerializerRegistrationsService extends AbstractClientService
+ implements MessageListener {
+
+ @Override
+ protected void onInitialize( ClientServiceManager serviceManager ) {
+ // Make sure our message type is registered
+ // This is the minimum we'd need just to be able to register
+ // the rest... otherwise we can't even receive this message.
+ Serializer.registerClass(SerializerRegistrationsMessage.class);
+ Serializer.registerClass(SerializerRegistrationsMessage.Registration.class);
+
+ // Add our listener for that message type
+ serviceManager.getClient().addMessageListener(this, SerializerRegistrationsMessage.class);
+ }
+
+ public void messageReceived( Client source, Message m ) {
+ // We only wait for one kind of message...
+ SerializerRegistrationsMessage msg = (SerializerRegistrationsMessage)m;
+ msg.registerAll();
+ }
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java
new file mode 100644
index 000000000..b24a8db5f
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 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.network.service.serializer;
+
+import com.jme3.network.HostedConnection;
+import com.jme3.network.Server;
+import com.jme3.network.message.SerializerRegistrationsMessage;
+import com.jme3.network.serializing.Serializer;
+import com.jme3.network.service.AbstractHostedService;
+import com.jme3.network.service.HostedServiceManager;
+
+
+/**
+ *
+ *
+ * @author Paul Speed
+ */
+public class ServerSerializerRegistrationsService extends AbstractHostedService {
+
+ @Override
+ protected void onInitialize( HostedServiceManager serviceManager ) {
+ // Make sure our message type is registered
+ Serializer.registerClass(SerializerRegistrationsMessage.class);
+ Serializer.registerClass(SerializerRegistrationsMessage.Registration.class);
+ }
+
+ @Override
+ public void start() {
+ // Compile the registrations into a message we will
+ // send to all connecting clients
+ SerializerRegistrationsMessage.compile();
+ }
+
+ @Override
+ public void connectionAdded(Server server, HostedConnection hc) {
+ // Just in case
+ super.connectionAdded(server, hc);
+
+ // Send the client the registration information
+ hc.send(SerializerRegistrationsMessage.INSTANCE);
+ }
+}
diff --git a/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java
new file mode 100644
index 000000000..a73496ea8
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 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.network.util;
+
+import com.jme3.network.Message;
+import com.jme3.network.MessageConnection;
+import com.jme3.network.MessageListener;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * A MessageListener implementation that will forward messages to methods
+ * of a delegate object. These methods can be automapped or manually
+ * specified. Subclasses provide specific implementations for how to
+ * find the actual delegate object.
+ *
+ * @author Paul Speed
+ */
+public abstract class AbstractMessageDelegator
+ implements MessageListener {
+
+ static final Logger log = Logger.getLogger(AbstractMessageDelegator.class.getName());
+
+ private Class delegateType;
+ private Map methods = new HashMap();
+ private Class[] messageTypes;
+
+ /**
+ * Creates an AbstractMessageDelegator that will forward received
+ * messages to methods of the specified delegate type. If automap
+ * is true then reflection is used to lookup probably message handling
+ * methods.
+ */
+ protected AbstractMessageDelegator( Class delegateType, boolean automap ) {
+ this.delegateType = delegateType;
+ if( automap ) {
+ automap();
+ }
+ }
+
+ /**
+ * Returns the array of messages known to be handled by this message
+ * delegator.
+ */
+ public Class[] getMessageTypes() {
+ if( messageTypes == null ) {
+ messageTypes = methods.keySet().toArray(new Class[methods.size()]);
+ }
+ return messageTypes;
+ }
+
+ /**
+ * Returns true if the specified method is valid for the specified
+ * message type. This is used internally during automapping to
+ * provide implementation specific filting of methods.
+ * This implementation checks for methods that take either the connection and message
+ * type arguments (in that order) or just the message type.
+ */
+ protected boolean isValidMethod( Method m, Class messageType ) {
+
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "isValidMethod({0}, {1})", new Object[]{m, messageType});
+ }
+
+ // Parameters must be S and message type or just message type
+ Class>[] parms = m.getParameterTypes();
+ if( parms.length != 2 && parms.length != 1 ) {
+ log.finest("Parameter count is not 1 or 2");
+ return false;
+ }
+ int messageIndex = 0;
+ if( parms.length > 1 ) {
+ if( MessageConnection.class.isAssignableFrom(parms[0]) ) {
+ messageIndex++;
+ } else {
+ log.finest("First paramter is not a MessageConnection or subclass.");
+ return false;
+ }
+ }
+
+ if( messageType == null && !Message.class.isAssignableFrom(parms[messageIndex]) ) {
+ log.finest("Second paramter is not a Message or subclass.");
+ return false;
+ }
+ if( messageType != null && !parms[messageIndex].isAssignableFrom(messageType) ) {
+ log.log(Level.FINEST, "Second paramter is not a {0}", messageType);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Convenience method that returns the message type as
+ * reflecively determined for a particular method. This
+ * only works with methods that actually have arguments.
+ * This implementation returns the last element of the method's
+ * getParameterTypes() array, thus supporting both
+ * method(connection, messageType) as well as just method(messageType)
+ * calling forms.
+ */
+ protected Class getMessageType( Method m ) {
+ Class>[] parms = m.getParameterTypes();
+ return parms[parms.length-1];
+ }
+
+ /**
+ * Goes through all of the delegate type's methods to find
+ * a method of the specified name that may take the specified
+ * message type.
+ */
+ protected Method findDelegate( String name, Class messageType ) {
+ // We do an exhaustive search because it's easier to
+ // check for a variety of parameter types and it's all
+ // that Class would be doing in getMethod() anyway.
+ for( Method m : delegateType.getDeclaredMethods() ) {
+
+ if( !m.getName().equals(name) ) {
+ continue;
+ }
+
+ if( isValidMethod(m, messageType) ) {
+ return m;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns true if the specified method name is allowed.
+ * This is used by automapping to determine if a method
+ * should be rejected purely on name. Default implemention
+ * always returns true.
+ */
+ protected boolean allowName( String name ) {
+ return true;
+ }
+
+ /**
+ * Calls the map(Set) method with a null argument causing
+ * all available matching methods to mapped to message types.
+ */
+ protected final void automap() {
+ map((Set)null);
+ if( methods.isEmpty() ) {
+ throw new RuntimeException("No message handling methods found for class:" + delegateType);
+ }
+ }
+
+ /**
+ * Specifically maps the specified methods names, autowiring
+ * the parameters.
+ */
+ public AbstractMessageDelegator map( String... methodNames ) {
+ Set names = new HashSet( Arrays.asList(methodNames) );
+ map(names);
+ return this;
+ }
+
+ /**
+ * Goes through all of the delegate type's declared methods
+ * mapping methods that match the current constraints.
+ * If the constraints set is null then allowName() is
+ * checked for names otherwise only names in the constraints
+ * set are allowed.
+ * For each candidate method that passes the above checks,
+ * isValidMethod() is called with a null message type argument.
+ * All methods are made accessible thus supporting non-public
+ * methods as well as public methods.
+ */
+ protected void map( Set constraints ) {
+
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "map({0})", constraints);
+ }
+ for( Method m : delegateType.getDeclaredMethods() ) {
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "Checking method:{0}", m);
+ }
+
+ if( constraints == null && !allowName(m.getName()) ) {
+ log.finest("Name is not allowed.");
+ continue;
+ }
+ if( constraints != null && !constraints.contains(m.getName()) ) {
+ log.finest("Name is not in constraints set.");
+ continue;
+ }
+
+ if( isValidMethod(m, null) ) {
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "Adding method mapping:{0} = {1}", new Object[]{getMessageType(m), m});
+ }
+ // Make sure we can access the method even if it's not public or
+ // is in a non-public inner class.
+ m.setAccessible(true);
+ methods.put(getMessageType(m), m);
+ }
+ }
+
+ messageTypes = null;
+ }
+
+ /**
+ * Manually maps a specified method to the specified message type.
+ */
+ public AbstractMessageDelegator map( Class messageType, String methodName ) {
+ // Lookup the method
+ Method m = findDelegate( methodName, messageType );
+ if( m == null ) {
+ throw new RuntimeException( "Method:" + methodName
+ + " not found matching signature (MessageConnection, "
+ + messageType.getName() + ")" );
+ }
+
+ if( log.isLoggable(Level.FINEST) ) {
+ log.log(Level.FINEST, "Adding method mapping:{0} = {1}", new Object[]{messageType, m});
+ }
+ methods.put( messageType, m );
+ messageTypes = null;
+ return this;
+ }
+
+ /**
+ * Returns the mapped method for the specified message type.
+ */
+ protected Method getMethod( Class c ) {
+ Method m = methods.get(c);
+ return m;
+ }
+
+ /**
+ * Implemented by subclasses to provide the actual delegate object
+ * against which the mapped message type methods will be called.
+ */
+ protected abstract Object getSourceDelegate( S source );
+
+ /**
+ * Implementation of the MessageListener's messageReceived()
+ * method that will use the current message type mapping to
+ * find an appropriate message handling method and call it
+ * on the delegate returned by getSourceDelegate().
+ */
+ @Override
+ public void messageReceived( S source, Message msg ) {
+ if( msg == null ) {
+ return;
+ }
+
+ Object delegate = getSourceDelegate(source);
+ if( delegate == null ) {
+ // Means ignore this message/source
+ return;
+ }
+
+ Method m = getMethod(msg.getClass());
+ if( m == null ) {
+ throw new RuntimeException("Delegate method not found for message class:"
+ + msg.getClass());
+ }
+
+ try {
+ if( m.getParameterTypes().length > 1 ) {
+ m.invoke( delegate, source, msg );
+ } else {
+ m.invoke( delegate, msg );
+ }
+ } catch( IllegalAccessException e ) {
+ throw new RuntimeException("Error executing:" + m, e);
+ } catch( InvocationTargetException e ) {
+ throw new RuntimeException("Error executing:" + m, e.getCause());
+ }
+ }
+}
+
+
diff --git a/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java
new file mode 100644
index 000000000..b92ee09a8
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 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.network.util;
+
+import com.jme3.network.MessageConnection;
+
+
+/**
+ * A MessageListener implementation that will forward messages to methods
+ * of a specified delegate object. These methods can be automapped or manually
+ * specified.
+ *
+ * @author Paul Speed
+ */
+public class ObjectMessageDelegator extends AbstractMessageDelegator {
+
+ private Object delegate;
+
+ /**
+ * Creates a MessageListener that will forward mapped message types
+ * to methods of the specified object.
+ * If automap is true then all methods with the proper signature will
+ * be mapped.
+ *
Methods of the following signatures are allowed:
+ *
+ *
void someName(S conn, SomeMessage msg)
+ *
void someName(Message msg)
+ *
+ * Where S is the type of MessageConnection and SomeMessage is some
+ * specific concreate Message subclass.
+ */
+ public ObjectMessageDelegator( Object delegate, boolean automap ) {
+ super(delegate.getClass(), automap);
+ this.delegate = delegate;
+ }
+
+ @Override
+ protected Object getSourceDelegate( MessageConnection source ) {
+ return delegate;
+ }
+}
+
diff --git a/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java
new file mode 100644
index 000000000..f2bee3373
--- /dev/null
+++ b/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 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.network.util;
+
+import com.jme3.network.HostedConnection;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * A MessageListener implementation that will forward messages to methods
+ * of a delegate specified as a HostedConnection session attribute. This is
+ * useful for handling connection-specific messages from clients that must
+ * delegate to client-specific data objects.
+ * The delegate methods can be automapped or manually specified.
+ *
+ * @author Paul Speed
+ */
+public class SessionDataDelegator extends AbstractMessageDelegator {
+
+ static final Logger log = Logger.getLogger(SessionDataDelegator.class.getName());
+
+ private String attributeName;
+
+ /**
+ * Creates a MessageListener that will forward mapped message types
+ * to methods of an object specified as a HostedConnection attribute.
+ * If automap is true then all methods with the proper signature will
+ * be mapped.
+ *
Methods of the following signatures are allowed:
+ *
+ *
void someName(S conn, SomeMessage msg)
+ *
void someName(Message msg)
+ *
+ * Where S is the type of MessageConnection and SomeMessage is some
+ * specific concreate Message subclass.
+ */
+ public SessionDataDelegator( Class delegateType, String attributeName, boolean automap ) {
+ super(delegateType, automap);
+ this.attributeName = attributeName;
+ }
+
+ /**
+ * Returns the attribute name that will be used to look up the
+ * delegate object.
+ */
+ public String getAttributeName() {
+ return attributeName;
+ }
+
+ /**
+ * Called internally when there is no session object
+ * for the current attribute name attached to the passed source
+ * HostConnection. Default implementation logs a warning.
+ */
+ protected void miss( HostedConnection source ) {
+ log.log(Level.WARNING, "Session data is null for:{0} on connection:{1}", new Object[]{attributeName, source});
+ }
+
+ /**
+ * Returns the attributeName attribute of the supplied source
+ * HostConnection. If there is no value at that attribute then
+ * the miss() method is called.
+ */
+ protected Object getSourceDelegate( HostedConnection source ) {
+ Object result = source.getAttribute(attributeName);
+ if( result == null ) {
+ miss(source);
+ }
+ return result;
+ }
+}
+
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java
index 06a4ec795..35375f9c5 100644
--- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java
@@ -32,6 +32,8 @@
package com.jme3.scene.plugins.fbx;
import com.jme3.asset.TextureKey;
+import com.jme3.asset.cache.AssetCache;
+import com.jme3.asset.cache.WeakRefCloneAssetCache;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
@@ -56,6 +58,13 @@ public class ContentTextureKey extends TextureKey {
return content;
}
+ @Override
+ public Class extends AssetCache> getCacheType(){
+ // Need to override this so that textures embedded in FBX
+ // don't get cached by the asset manager.
+ return null;
+ }
+
@Override
public String toString() {
return super.toString() + " " + content.length + " bytes";
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java
new file mode 100644
index 000000000..fd157cb48
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java
@@ -0,0 +1,413 @@
+/*
+ * 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.scene.plugins.fbx;
+
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.Animation;
+import com.jme3.animation.Bone;
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.Skeleton;
+import com.jme3.animation.Track;
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetLoadException;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.ModelKey;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.fbx.anim.FbxToJmeTrack;
+import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode;
+import com.jme3.scene.plugins.fbx.anim.FbxAnimLayer;
+import com.jme3.scene.plugins.fbx.anim.FbxAnimStack;
+import com.jme3.scene.plugins.fbx.anim.FbxBindPose;
+import com.jme3.scene.plugins.fbx.anim.FbxLimbNode;
+import com.jme3.scene.plugins.fbx.file.FbxDump;
+import com.jme3.scene.plugins.fbx.file.FbxElement;
+import com.jme3.scene.plugins.fbx.file.FbxFile;
+import com.jme3.scene.plugins.fbx.file.FbxReader;
+import com.jme3.scene.plugins.fbx.file.FbxId;
+import com.jme3.scene.plugins.fbx.misc.FbxGlobalSettings;
+import com.jme3.scene.plugins.fbx.node.FbxNode;
+import com.jme3.scene.plugins.fbx.node.FbxRootNode;
+import com.jme3.scene.plugins.fbx.obj.FbxObjectFactory;
+import com.jme3.scene.plugins.fbx.obj.FbxObject;
+import java.io.IOException;
+import java.io.InputStream;
+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;
+
+public class FbxLoader implements AssetLoader {
+
+ private static final Logger logger = Logger.getLogger(FbxLoader.class.getName());
+
+ private AssetManager assetManager;
+
+ private String sceneName;
+ private String sceneFilename;
+ private String sceneFolderName;
+ private FbxGlobalSettings globalSettings;
+ private final Map objectMap = new HashMap();
+
+ private final List animStacks = new ArrayList();
+ private final List bindPoses = new ArrayList();
+
+ @Override
+ public Object load(AssetInfo assetInfo) throws IOException {
+ this.assetManager = assetInfo.getManager();
+ AssetKey> assetKey = assetInfo.getKey();
+ if (!(assetKey instanceof ModelKey)) {
+ throw new AssetLoadException("Invalid asset key");
+ }
+
+ InputStream stream = assetInfo.openStream();
+ try {
+ sceneFilename = assetKey.getName();
+ sceneFolderName = assetKey.getFolder();
+ String ext = assetKey.getExtension();
+
+ sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1);
+ if (sceneFolderName != null && sceneFolderName.length() > 0) {
+ sceneName = sceneName.substring(sceneFolderName.length());
+ }
+
+ reset();
+
+ // Load the data from the stream.
+ loadData(stream);
+
+ // Bind poses are needed to compute world transforms.
+ applyBindPoses();
+
+ // Need world transforms for skeleton creation.
+ updateWorldTransforms();
+
+ // Need skeletons for meshs to be created in scene graph construction.
+ // Mesh bone indices require skeletons to determine bone index.
+ constructSkeletons();
+
+ // Create the jME3 scene graph from the FBX scene graph.
+ // Also creates SkeletonControls based on the constructed skeletons.
+ Spatial scene = constructSceneGraph();
+
+ // Load animations into AnimControls
+ constructAnimations();
+
+ return scene;
+ } finally {
+ releaseObjects();
+ if (stream != null) {
+ stream.close();
+ }
+ }
+ }
+
+ private void reset() {
+ globalSettings = new FbxGlobalSettings();
+ }
+
+ private void releaseObjects() {
+ globalSettings = null;
+ objectMap.clear();
+ animStacks.clear();
+ }
+
+ private void loadData(InputStream stream) throws IOException {
+ FbxFile scene = FbxReader.readFBX(stream);
+
+ FbxDump.dumpFile(scene);
+
+ // TODO: Load FBX object templates
+
+ for (FbxElement e : scene.rootElements) {
+ if (e.id.equals("FBXHeaderExtension")) {
+ loadHeader(e);
+ } else if (e.id.equals("GlobalSettings")) {
+ loadGlobalSettings(e);
+ } else if (e.id.equals("Objects")) {
+ loadObjects(e);
+ } else if (e.id.equals("Connections")) {
+ connectObjects(e);
+ }
+ }
+ }
+
+ private void loadHeader(FbxElement element) {
+ for (FbxElement e : element.children) {
+ if (e.id.equals("FBXVersion")) {
+ Integer version = (Integer) e.properties.get(0);
+ if (version < 7100) {
+ logger.log(Level.WARNING, "FBX file version is older than 7.1. "
+ + "Some features may not work.");
+ }
+ }
+ }
+ }
+
+ private void loadGlobalSettings(FbxElement element) {
+ globalSettings = new FbxGlobalSettings();
+ globalSettings.fromElement(element);
+ }
+
+ private void loadObjects(FbxElement element) {
+ // Initialize the FBX root element.
+ objectMap.put(FbxId.ROOT, new FbxRootNode(assetManager, sceneFolderName));
+
+ for(FbxElement e : element.children) {
+ if (e.id.equals("GlobalSettings")) {
+ // Old FBX files seem to have the GlobalSettings element
+ // under Objects (??)
+ globalSettings.fromElement(e);
+ } else {
+ FbxObject object = FbxObjectFactory.createObject(e, assetManager, sceneFolderName);
+ if (object != null) {
+ if (objectMap.containsKey(object.getId())) {
+ logger.log(Level.WARNING, "An object with ID \"{0}\" has "
+ + "already been defined. "
+ + "Ignoring.",
+ object.getId());
+ }
+
+ objectMap.put(object.getId(), object);
+
+ if (object instanceof FbxAnimStack) {
+ // NOTE: animation stacks are implicitly global.
+ // Capture them here.
+ animStacks.add((FbxAnimStack) object);
+ } else if (object instanceof FbxBindPose) {
+ bindPoses.add((FbxBindPose) object);
+ }
+ } else {
+ throw new UnsupportedOperationException("Failed to create FBX object of type: " + e.id);
+ }
+ }
+ }
+ }
+
+ private void removeUnconnectedObjects() {
+ for (FbxObject object : new ArrayList(objectMap.values())) {
+ if (!object.isJmeObjectCreated()) {
+ logger.log(Level.WARNING, "Purging orphan FBX object: {0}", object);
+ objectMap.remove(object.getId());
+ }
+ }
+ }
+
+ private void connectObjects(FbxElement element) {
+ if (objectMap.isEmpty()) {
+ logger.log(Level.WARNING, "FBX file is missing object information");
+ return;
+ } else if (objectMap.size() == 1) {
+ // Only root node (automatically added by jME3)
+ logger.log(Level.WARNING, "FBX file has no objects");
+ return;
+ }
+
+ for (FbxElement el : element.children) {
+ if (!el.id.equals("C") && !el.id.equals("Connect")) {
+ continue;
+ }
+ String type = (String) el.properties.get(0);
+ FbxId childId;
+ FbxId parentId;
+ if (type.equals("OO")) {
+ childId = FbxId.create(el.properties.get(1));
+ parentId = FbxId.create(el.properties.get(2));
+ FbxObject child = objectMap.get(childId);
+ FbxObject parent;
+
+ if (parentId.isNull()) {
+ // TODO: maybe clean this up a bit..
+ parent = objectMap.get(FbxId.ROOT);
+ } else {
+ parent = objectMap.get(parentId);
+ }
+
+ if (parent == null) {
+ throw new UnsupportedOperationException("Cannot find parent object ID \"" + parentId + "\"");
+ }
+
+ parent.connectObject(child);
+ } else if (type.equals("OP")) {
+ childId = FbxId.create(el.properties.get(1));
+ parentId = FbxId.create(el.properties.get(2));
+ String propName = (String) el.properties.get(3);
+ FbxObject child = objectMap.get(childId);
+ FbxObject parent = objectMap.get(parentId);
+ parent.connectObjectProperty(child, propName);
+ } else {
+ logger.log(Level.WARNING, "Unknown connection type: {0}. Ignoring.", type);
+ }
+ }
+ }
+
+ /**
+ * Copies the bind poses from FBX BindPose objects to FBX nodes.
+ * Must be called prior to {@link #updateWorldTransforms()}.
+ */
+ private void applyBindPoses() {
+ for (FbxBindPose bindPose : bindPoses) {
+ Map bindPoseData = bindPose.getJmeObject();
+ logger.log(Level.INFO, "Applying {0} bind poses", bindPoseData.size());
+ for (Map.Entry entry : bindPoseData.entrySet()) {
+ FbxObject obj = objectMap.get(entry.getKey());
+ if (obj instanceof FbxNode) {
+ FbxNode node = (FbxNode) obj;
+ node.setWorldBindPose(entry.getValue());
+ } else {
+ logger.log(Level.WARNING, "Bind pose can only be applied to FBX nodes. Ignoring.");
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates world transforms and bind poses for the FBX scene graph.
+ */
+ private void updateWorldTransforms() {
+ FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT);
+ fbxRoot.updateWorldTransforms(null, null);
+ }
+
+ private void constructAnimations() {
+ // In FBX, animation are not attached to any root.
+ // They are implicitly global.
+ // So, we need to use hueristics to find which node(s)
+ // an animation is associated with, so we can create the AnimControl
+ // in the appropriate location in the scene.
+ Map pairs = new HashMap();
+ for (FbxAnimStack stack : animStacks) {
+ for (FbxAnimLayer layer : stack.getLayers()) {
+ for (FbxAnimCurveNode curveNode : layer.getAnimationCurveNodes()) {
+ for (Map.Entry nodePropertyEntry : curveNode.getInfluencedNodeProperties().entrySet()) {
+ FbxToJmeTrack lookupPair = new FbxToJmeTrack();
+ lookupPair.animStack = stack;
+ lookupPair.animLayer = layer;
+ lookupPair.node = nodePropertyEntry.getKey();
+
+ // Find if this pair is already stored
+ FbxToJmeTrack storedPair = pairs.get(lookupPair);
+ if (storedPair == null) {
+ // If not, store it.
+ storedPair = lookupPair;
+ pairs.put(storedPair, storedPair);
+ }
+
+ String property = nodePropertyEntry.getValue();
+ storedPair.animCurves.put(property, curveNode);
+ }
+ }
+ }
+ }
+
+ // At this point we can construct the animation for all pairs ...
+ for (FbxToJmeTrack pair : pairs.values()) {
+ String animName = pair.animStack.getName();
+ float duration = pair.animStack.getDuration();
+
+ System.out.println("ANIMATION: " + animName + ", duration = " + duration);
+ System.out.println("NODE: " + pair.node.getName());
+
+ duration = pair.getDuration();
+
+ if (pair.node instanceof FbxLimbNode) {
+ // Find the spatial that has the skeleton for this limb.
+ FbxLimbNode limbNode = (FbxLimbNode) pair.node;
+ Bone bone = limbNode.getJmeBone();
+ Spatial jmeSpatial = limbNode.getSkeletonHolder().getJmeObject();
+ Skeleton skeleton = limbNode.getSkeletonHolder().getJmeSkeleton();
+
+ // Get the animation control (create if missing).
+ AnimControl animControl = jmeSpatial.getControl(AnimControl.class);
+ if (animControl.getSkeleton() != skeleton) {
+ throw new UnsupportedOperationException();
+ }
+
+ // Get the animation (create if missing).
+ Animation anim = animControl.getAnim(animName);
+ if (anim == null) {
+ anim = new Animation(animName, duration);
+ animControl.addAnim(anim);
+ }
+
+ // Find the bone index from the spatial's skeleton.
+ int boneIndex = skeleton.getBoneIndex(bone);
+
+ // Generate the bone track.
+ BoneTrack bt = pair.toJmeBoneTrack(boneIndex, bone.getBindInverseTransform());
+
+ // Add the bone track to the animation.
+ anim.addTrack(bt);
+ } else {
+ // Create the spatial animation
+ Animation anim = new Animation(animName, duration);
+ anim.setTracks(new Track[]{ pair.toJmeSpatialTrack() });
+
+ // Get the animation control (create if missing).
+ Spatial jmeSpatial = pair.node.getJmeObject();
+ AnimControl animControl = jmeSpatial.getControl(AnimControl.class);
+
+ if (animControl == null) {
+ animControl = new AnimControl(null);
+ jmeSpatial.addControl(animControl);
+ }
+
+ // Add the spatial animation
+ animControl.addAnim(anim);
+ }
+ }
+ }
+
+ private void constructSkeletons() {
+ FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT);
+ FbxNode.createSkeletons(fbxRoot);
+ }
+
+ private Spatial constructSceneGraph() {
+ // Acquire the implicit root object.
+ FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT);
+
+ // Convert it into a jME3 scene
+ Node jmeRoot = (Node) FbxNode.createScene(fbxRoot);
+
+ // Fix the name (will probably be set to something like "-node")
+ jmeRoot.setName(sceneName + "-scene");
+
+ return jmeRoot;
+ }
+}
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java
index e767763e1..491301c22 100644
--- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java
@@ -73,9 +73,9 @@ import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.fbx.AnimationList.AnimInverval;
-import com.jme3.scene.plugins.fbx.file.FBXElement;
-import com.jme3.scene.plugins.fbx.file.FBXFile;
-import com.jme3.scene.plugins.fbx.file.FBXReader;
+import com.jme3.scene.plugins.fbx.file.FbxElement;
+import com.jme3.scene.plugins.fbx.file.FbxFile;
+import com.jme3.scene.plugins.fbx.file.FbxReader;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
@@ -165,8 +165,8 @@ public class SceneLoader implements AssetLoader {
private void loadScene(InputStream stream) throws IOException {
logger.log(Level.FINE, "Loading scene {0}", sceneFilename);
long startTime = System.currentTimeMillis();
- FBXFile scene = FBXReader.readFBX(stream);
- for(FBXElement e : scene.rootElements) {
+ FbxFile scene = FbxReader.readFBX(stream);
+ for(FbxElement e : scene.rootElements) {
if(e.id.equals("GlobalSettings"))
loadGlobalSettings(e);
else if(e.id.equals("Objects"))
@@ -178,10 +178,10 @@ public class SceneLoader implements AssetLoader {
logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime);
}
- private void loadGlobalSettings(FBXElement element) {
- for(FBXElement e : element.children) {
+ private void loadGlobalSettings(FbxElement element) {
+ for(FbxElement e : element.children) {
if(e.id.equals("Properties70")) {
- for(FBXElement e2 : e.children) {
+ for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0);
if(propName.equals("UnitScaleFactor"))
@@ -197,8 +197,8 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadObjects(FBXElement element) {
- for(FBXElement e : element.children) {
+ private void loadObjects(FbxElement element) {
+ for(FbxElement e : element.children) {
if(e.id.equals("Geometry"))
loadGeometry(e);
else if(e.id.equals("Material"))
@@ -222,12 +222,12 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadGeometry(FBXElement element) {
+ private void loadGeometry(FbxElement element) {
long id = (Long) element.properties.get(0);
String type = (String) element.properties.get(2);
if(type.equals("Mesh")) {
MeshData data = new MeshData();
- for(FBXElement e : element.children) {
+ for(FbxElement e : element.children) {
if(e.id.equals("Vertices"))
data.vertices = (double[]) e.properties.get(0);
else if(e.id.equals("PolygonVertexIndex"))
@@ -236,7 +236,7 @@ public class SceneLoader implements AssetLoader {
//else if(e.id.equals("Edges"))
// data.edges = (int[]) e.properties.get(0);
else if(e.id.equals("LayerElementNormal"))
- for(FBXElement e2 : e.children) {
+ for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) {
data.normalsMapping = (String) e2.properties.get(0);
if(!data.normalsMapping.equals("ByVertice") && !data.normalsMapping.equals("ByPolygonVertex"))
@@ -249,7 +249,7 @@ public class SceneLoader implements AssetLoader {
data.normals = (double[]) e2.properties.get(0);
}
else if(e.id.equals("LayerElementTangent"))
- for(FBXElement e2 : e.children) {
+ for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) {
data.tangentsMapping = (String) e2.properties.get(0);
if(!data.tangentsMapping.equals("ByVertice") && !data.tangentsMapping.equals("ByPolygonVertex"))
@@ -262,7 +262,7 @@ public class SceneLoader implements AssetLoader {
data.tangents = (double[]) e2.properties.get(0);
}
else if(e.id.equals("LayerElementBinormal"))
- for(FBXElement e2 : e.children) {
+ for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) {
data.binormalsMapping = (String) e2.properties.get(0);
if(!data.binormalsMapping.equals("ByVertice") && !data.binormalsMapping.equals("ByPolygonVertex"))
@@ -275,7 +275,7 @@ public class SceneLoader implements AssetLoader {
data.binormals = (double[]) e2.properties.get(0);
}
else if(e.id.equals("LayerElementUV"))
- for(FBXElement e2 : e.children) {
+ for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) {
data.uvMapping = (String) e2.properties.get(0);
if(!data.uvMapping.equals("ByPolygonVertex"))
@@ -291,7 +291,7 @@ public class SceneLoader implements AssetLoader {
}
// TODO smoothing is not used now
//else if(e.id.equals("LayerElementSmoothing"))
- // for(FBXElement e2 : e.children) {
+ // for(FbxElement e2 : e.children) {
// if(e2.id.equals("MappingInformationType")) {
// data.smoothingMapping = (String) e2.properties.get(0);
// if(!data.smoothingMapping.equals("ByEdge"))
@@ -304,7 +304,7 @@ public class SceneLoader implements AssetLoader {
// data.smoothing = (int[]) e2.properties.get(0);
// }
else if(e.id.equals("LayerElementMaterial"))
- for(FBXElement e2 : e.children) {
+ for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) {
data.materialsMapping = (String) e2.properties.get(0);
if(!data.materialsMapping.equals("AllSame"))
@@ -321,18 +321,18 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadMaterial(FBXElement element) {
+ private void loadMaterial(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
if(type.equals("")) {
MaterialData data = new MaterialData();
data.name = path.substring(0, path.indexOf(0));
- for(FBXElement e : element.children) {
+ for(FbxElement e : element.children) {
if(e.id.equals("ShadingModel")) {
data.shadingModel = (String) e.properties.get(0);
} else if(e.id.equals("Properties70")) {
- for(FBXElement e2 : e.children) {
+ for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0);
if(propName.equals("AmbientColor")) {
@@ -368,16 +368,16 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadModel(FBXElement element) {
+ private void loadModel(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
ModelData data = new ModelData();
data.name = path.substring(0, path.indexOf(0));
data.type = type;
- for(FBXElement e : element.children) {
+ for(FbxElement e : element.children) {
if(e.id.equals("Properties70")) {
- for(FBXElement e2 : e.children) {
+ for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0);
if(propName.equals("Lcl Translation")) {
@@ -408,17 +408,17 @@ public class SceneLoader implements AssetLoader {
modelDataMap.put(id, data);
}
- private void loadPose(FBXElement element) {
+ private void loadPose(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
if(type.equals("BindPose")) {
BindPoseData data = new BindPoseData();
data.name = path.substring(0, path.indexOf(0));
- for(FBXElement e : element.children) {
+ for(FbxElement e : element.children) {
if(e.id.equals("PoseNode")) {
NodeTransformData item = new NodeTransformData();
- for(FBXElement e2 : e.children) {
+ for(FbxElement e2 : e.children) {
if(e2.id.equals("Node"))
item.nodeId = (Long) e2.properties.get(0);
else if(e2.id.equals("Matrix"))
@@ -431,14 +431,14 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadTexture(FBXElement element) {
+ private void loadTexture(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
if(type.equals("")) {
TextureData data = new TextureData();
data.name = path.substring(0, path.indexOf(0));
- for(FBXElement e : element.children) {
+ for(FbxElement e : element.children) {
if(e.id.equals("Type"))
data.bindType = (String) e.properties.get(0);
else if(e.id.equals("FileName"))
@@ -448,14 +448,14 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadImage(FBXElement element) {
+ private void loadImage(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
if(type.equals("Clip")) {
ImageData data = new ImageData();
data.name = path.substring(0, path.indexOf(0));
- for(FBXElement e : element.children) {
+ for(FbxElement e : element.children) {
if(e.id.equals("Type"))
data.type = (String) e.properties.get(0);
else if(e.id.equals("FileName"))
@@ -471,19 +471,19 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadDeformer(FBXElement element) {
+ private void loadDeformer(FbxElement element) {
long id = (Long) element.properties.get(0);
String type = (String) element.properties.get(2);
if(type.equals("Skin")) {
SkinData skinData = new SkinData();
- for(FBXElement e : element.children) {
+ for(FbxElement e : element.children) {
if(e.id.equals("SkinningType"))
skinData.type = (String) e.properties.get(0);
}
skinMap.put(id, skinData);
} else if(type.equals("Cluster")) {
ClusterData clusterData = new ClusterData();
- for(FBXElement e : element.children) {
+ for(FbxElement e : element.children) {
if(e.id.equals("Indexes"))
clusterData.indexes = (int[]) e.properties.get(0);
else if(e.id.equals("Weights"))
@@ -497,7 +497,7 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadAnimLayer(FBXElement element) {
+ private void loadAnimLayer(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
@@ -508,12 +508,12 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadAnimCurve(FBXElement element) {
+ private void loadAnimCurve(FbxElement element) {
long id = (Long) element.properties.get(0);
String type = (String) element.properties.get(2);
if(type.equals("")) {
AnimCurveData data = new AnimCurveData();
- for(FBXElement e : element.children) {
+ for(FbxElement e : element.children) {
if(e.id.equals("KeyTime"))
data.keyTimes = (long[]) e.properties.get(0);
else if(e.id.equals("KeyValueFloat"))
@@ -523,15 +523,15 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadAnimNode(FBXElement element) {
+ private void loadAnimNode(FbxElement element) {
long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2);
if(type.equals("")) {
Double x = null, y = null, z = null;
- for(FBXElement e : element.children) {
+ for(FbxElement e : element.children) {
if(e.id.equals("Properties70")) {
- for(FBXElement e2 : e.children) {
+ for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0);
if(propName.equals("d|X"))
@@ -554,8 +554,8 @@ public class SceneLoader implements AssetLoader {
}
}
- private void loadConnections(FBXElement element) {
- for(FBXElement e : element.children) {
+ private void loadConnections(FbxElement element) {
+ for(FbxElement e : element.children) {
if(e.id.equals("C")) {
String type = (String) e.properties.get(0);
long objId, refId;
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java
new file mode 100644
index 000000000..d266dcf95
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java
@@ -0,0 +1,144 @@
+/*
+ * 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.scene.plugins.fbx.anim;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.math.FastMath;
+import com.jme3.scene.plugins.fbx.file.FbxElement;
+import com.jme3.scene.plugins.fbx.obj.FbxObject;
+
+public class FbxAnimCurve extends FbxObject {
+
+ private long[] keyTimes;
+ private float[] keyValues;
+
+ public FbxAnimCurve(AssetManager assetManager, String sceneFolderName) {
+ super(assetManager, sceneFolderName);
+ }
+
+ @Override
+ public void fromElement(FbxElement element) {
+ super.fromElement(element);
+
+ for (FbxElement e : element.children) {
+ if (e.id.equals("KeyTime")) {
+ keyTimes = (long[]) e.properties.get(0);
+ } else if (e.id.equals("KeyValueFloat")) {
+ keyValues = (float[]) e.properties.get(0);
+ }
+ }
+
+ long time = -1;
+ for (int i = 0; i < keyTimes.length; i++) {
+ if (time >= keyTimes[i]) {
+ throw new UnsupportedOperationException("Keyframe times must be sequential, but they are not.");
+ }
+ time = keyTimes[i];
+ }
+ }
+
+ /**
+ * Get the times for the keyframes.
+ * @return Keyframe times.
+ */
+ public long[] getKeyTimes() {
+ return keyTimes;
+ }
+
+ /**
+ * Retrieve the curve value at the given time.
+ * If the curve has no data, 0 is returned.
+ * If the time is outside the curve, then the closest value is returned.
+ * If the time isn't on an exact keyframe, linear interpolation is used
+ * to determine the value between the keyframes at the given time.
+ * @param time The time to get the curve value at (in FBX time units).
+ * @return The value at the given time.
+ */
+ public float getValueAtTime(long time) {
+ if (keyTimes.length == 0) {
+ return 0;
+ }
+
+ // If the time is outside the range,
+ // we just return the closest value. (No extrapolation)
+ if (time <= keyTimes[0]) {
+ return keyValues[0];
+ } else if (time >= keyTimes[keyTimes.length - 1]) {
+ return keyValues[keyValues.length - 1];
+ }
+
+
+
+ int startFrame = 0;
+ int endFrame = 1;
+ int lastFrame = keyTimes.length - 1;
+
+ for (int i = 0; i < lastFrame && keyTimes[i] < time; ++i) {
+ startFrame = i;
+ endFrame = i + 1;
+ }
+
+ long keyTime1 = keyTimes[startFrame];
+ float keyValue1 = keyValues[startFrame];
+ long keyTime2 = keyTimes[endFrame];
+ float keyValue2 = keyValues[endFrame];
+
+ if (keyTime2 == time) {
+ return keyValue2;
+ }
+
+ long prevToNextDelta = keyTime2 - keyTime1;
+ long prevToCurrentDelta = time - keyTime1;
+ float lerpAmount = (float)prevToCurrentDelta / prevToNextDelta;
+
+ return FastMath.interpolateLinear(lerpAmount, keyValue1, keyValue2);
+ }
+
+ @Override
+ protected Object toJmeObject() {
+ // An AnimCurve has no jME3 representation.
+ // The parent AnimCurveNode is responsible to create the jME3
+ // representation.
+ throw new UnsupportedOperationException("No jME3 object conversion available");
+ }
+
+ @Override
+ public void connectObject(FbxObject object) {
+ unsupportedConnectObject(object);
+ }
+
+ @Override
+ public void connectObjectProperty(FbxObject object, String property) {
+ unsupportedConnectObjectProperty(object, property);
+ }
+
+}
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java
new file mode 100644
index 000000000..e8dbe7fc0
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java
@@ -0,0 +1,147 @@
+/*
+ * 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.scene.plugins.fbx.anim;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.plugins.fbx.file.FbxElement;
+import com.jme3.scene.plugins.fbx.node.FbxNode;
+import com.jme3.scene.plugins.fbx.obj.FbxObject;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class FbxAnimCurveNode extends FbxObject {
+
+ private static final Logger logger = Logger.getLogger(FbxAnimCurveNode.class.getName());
+
+ private final Map influencedNodePropertiesMap = new HashMap();
+ private final Map propertyToCurveMap = new HashMap();
+ private final Map propertyToDefaultMap = new HashMap();
+
+ public FbxAnimCurveNode(AssetManager assetManager, String sceneFolderName) {
+ super(assetManager, sceneFolderName);
+ }
+
+ @Override
+ public void fromElement(FbxElement element) {
+ super.fromElement(element);
+ for (FbxElement prop : element.getFbxProperties()) {
+ String propName = (String) prop.properties.get(0);
+ String propType = (String) prop.properties.get(1);
+ if (propType.equals("Number")) {
+ float propValue = ((Double) prop.properties.get(4)).floatValue();
+ propertyToDefaultMap.put(propName, propValue);
+ }
+ }
+ }
+
+ public void addInfluencedNode(FbxNode node, String property) {
+ influencedNodePropertiesMap.put(node, property);
+ }
+
+ public Map getInfluencedNodeProperties() {
+ return influencedNodePropertiesMap;
+ }
+
+ public Collection getCurves() {
+ return propertyToCurveMap.values();
+ }
+
+ public Vector3f getVector3Value(long time) {
+ Vector3f value = new Vector3f();
+ FbxAnimCurve xCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_X);
+ FbxAnimCurve yCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Y);
+ FbxAnimCurve zCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Z);
+ Float xDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_X);
+ Float yDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Y);
+ Float zDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Z);
+ value.x = xCurve != null ? xCurve.getValueAtTime(time) : xDefault;
+ value.y = yCurve != null ? yCurve.getValueAtTime(time) : yDefault;
+ value.z = zCurve != null ? zCurve.getValueAtTime(time) : zDefault;
+ return value;
+ }
+
+ /**
+ * Converts the euler angles from {@link #getVector3Value(long)} to
+ * a quaternion rotation.
+ * @param time Time at which to get the euler angles.
+ * @return The rotation at time
+ */
+ public Quaternion getQuaternionValue(long time) {
+ Vector3f eulerAngles = getVector3Value(time);
+ System.out.println("\tT: " + time + ". Rotation: " +
+ eulerAngles.x + ", " +
+ eulerAngles.y + ", " + eulerAngles.z);
+ Quaternion q = new Quaternion();
+ q.fromAngles(eulerAngles.x * FastMath.DEG_TO_RAD,
+ eulerAngles.y * FastMath.DEG_TO_RAD,
+ eulerAngles.z * FastMath.DEG_TO_RAD);
+ return q;
+ }
+
+ @Override
+ protected Object toJmeObject() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void connectObject(FbxObject object) {
+ unsupportedConnectObject(object);
+ }
+
+ @Override
+ public void connectObjectProperty(FbxObject object, String property) {
+ if (!(object instanceof FbxAnimCurve)) {
+ unsupportedConnectObjectProperty(object, property);
+ }
+ if (!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_X) &&
+ !property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_Y) &&
+ !property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_Z) &&
+ !property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_VISIBILITY)) {
+ logger.log(Level.WARNING, "Animating the dimension ''{0}'' is not "
+ + "supported yet. Ignoring.", property);
+ return;
+ }
+
+ if (propertyToCurveMap.containsKey(property)) {
+ throw new UnsupportedOperationException("!");
+ }
+
+ propertyToCurveMap.put(property, (FbxAnimCurve) object);
+ }
+
+}
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java
new file mode 100644
index 000000000..872626615
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java
@@ -0,0 +1,82 @@
+/*
+ * 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.scene.plugins.fbx.anim;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.scene.plugins.fbx.file.FbxElement;
+import com.jme3.scene.plugins.fbx.obj.FbxObject;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Logger;
+
+public class FbxAnimLayer extends FbxObject {
+
+ private static final Logger logger = Logger.getLogger(FbxAnimLayer.class.getName());
+
+ private final List animCurves = new ArrayList();
+
+ public FbxAnimLayer(AssetManager assetManager, String sceneFolderName) {
+ super(assetManager, sceneFolderName);
+ }
+
+ @Override
+ public void fromElement(FbxElement element) {
+ super.fromElement(element);
+ // No known properties for layers..
+ // Also jME3 doesn't support multiple layers anyway.
+ }
+
+ public List getAnimationCurveNodes() {
+ return Collections.unmodifiableList(animCurves);
+ }
+
+ @Override
+ protected Object toJmeObject() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void connectObject(FbxObject object) {
+ if (!(object instanceof FbxAnimCurveNode)) {
+ unsupportedConnectObject(object);
+ }
+
+ animCurves.add((FbxAnimCurveNode) object);
+ }
+
+ @Override
+ public void connectObjectProperty(FbxObject object, String property) {
+ unsupportedConnectObjectProperty(object, property);
+ }
+}
+
\ No newline at end of file
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java
new file mode 100644
index 000000000..ea7dbb814
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java
@@ -0,0 +1,111 @@
+/*
+ * 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.scene.plugins.fbx.anim;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.scene.plugins.fbx.file.FbxElement;
+import com.jme3.scene.plugins.fbx.obj.FbxObject;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class FbxAnimStack extends FbxObject {
+
+ private static final Logger logger = Logger.getLogger(FbxAnimStack.class.getName());
+
+ private float duration;
+ private FbxAnimLayer layer0;
+
+ public FbxAnimStack(AssetManager assetManager, String sceneFolderName) {
+ super(assetManager, sceneFolderName);
+ }
+
+ @Override
+ public void fromElement(FbxElement element) {
+ super.fromElement(element);
+ for (FbxElement child : element.getFbxProperties()) {
+ String propName = (String) child.properties.get(0);
+ if (propName.equals("LocalStop")) {
+ long durationLong = (Long)child.properties.get(4);
+ duration = (float) (durationLong * FbxAnimUtil.SECONDS_PER_UNIT);
+ }
+ }
+ }
+
+// /**
+// * Finds out which FBX nodes this animation is going to influence.
+// *
+// * @return A list of FBX nodes that the stack's curves are influencing.
+// */
+// public Set getInfluencedNodes() {
+// HashSet influencedNodes = new HashSet();
+// if (layer0 == null) {
+// return influencedNodes;
+// }
+// for (FbxAnimCurveNode curveNode : layer0.getAnimationCurveNodes()) {
+// influencedNodes.addAll(curveNode.getInfluencedNodes());
+// }
+// return influencedNodes;
+// }
+
+ public float getDuration() {
+ return duration;
+ }
+
+ public FbxAnimLayer[] getLayers() {
+ return new FbxAnimLayer[]{ layer0 };
+ }
+
+ @Override
+ protected Object toJmeObject() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void connectObject(FbxObject object) {
+ if (!(object instanceof FbxAnimLayer)) {
+ unsupportedConnectObject(object);
+ }
+
+ if (layer0 != null) {
+ logger.log(Level.WARNING, "jME3 does not support layered animation. "
+ + "Only first layer has been loaded.");
+ return;
+ }
+
+ layer0 = (FbxAnimLayer) object;
+ }
+
+ @Override
+ public void connectObjectProperty(FbxObject object, String property) {
+ unsupportedConnectObjectProperty(object, property);
+ }
+}
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java
new file mode 100644
index 000000000..c376757de
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java
@@ -0,0 +1,44 @@
+/*
+ * 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.scene.plugins.fbx.anim;
+
+public class FbxAnimUtil {
+ /**
+ * Conversion factor from FBX animation time unit to seconds.
+ */
+ public static final double SECONDS_PER_UNIT = 1 / 46186158000d;
+
+ public static final String CURVE_NODE_PROPERTY_X = "d|X";
+ public static final String CURVE_NODE_PROPERTY_Y = "d|Y";
+ public static final String CURVE_NODE_PROPERTY_Z = "d|Z";
+ public static final String CURVE_NODE_PROPERTY_VISIBILITY = "d|Visibility";
+}
diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java
new file mode 100644
index 000000000..78d37c74e
--- /dev/null
+++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java
@@ -0,0 +1,103 @@
+/*
+ * 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.scene.plugins.fbx.anim;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.math.Matrix4f;
+import com.jme3.scene.plugins.fbx.file.FbxElement;
+import com.jme3.scene.plugins.fbx.file.FbxId;
+import com.jme3.scene.plugins.fbx.obj.FbxObject;
+import java.util.HashMap;
+import java.util.Map;
+
+public class FbxBindPose extends FbxObject