Merge pull request #4 from jMonkeyEngine/master

Merge branch "jmonkeyengine/master" into "scenecomposer/master"
experimental
Dokthar 10 years ago
commit d48c1e0e9d
  1. 236
      jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java
  2. 686
      jme3-android/src/main/java/com/jme3/input/android/AndroidInput.java
  3. 305
      jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java
  4. 159
      jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java
  5. 65
      jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java
  6. 108
      jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java
  7. 416
      jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java
  8. 140
      jme3-android/src/main/java/com/jme3/input/android/AndroidKeyHandler.java
  9. 7
      jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java
  10. 48
      jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java
  11. 257
      jme3-android/src/main/java/com/jme3/input/android/AndroidTouchHandler.java
  12. 475
      jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java
  13. 60
      jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java
  14. 3
      jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java
  15. 24
      jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java
  16. 16
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java
  17. 2
      jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java
  18. 20
      jme3-core/src/main/java/com/jme3/animation/Bone.java
  19. 19
      jme3-core/src/main/java/com/jme3/math/FastMath.java
  20. 20
      jme3-core/src/main/java/com/jme3/math/Transform.java
  21. 14
      jme3-core/src/main/java/com/jme3/renderer/Caps.java
  22. 3
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
  23. 6
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java
  24. 26
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java
  25. 14
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java
  26. 4
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java
  27. 3
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLFbo.java
  28. 10
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
  29. 162
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  30. 122
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java
  31. 41
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java
  32. 7
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java
  33. 1
      jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java
  34. 16
      jme3-core/src/main/java/com/jme3/texture/Image.java
  35. 4
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
  36. 13
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
  37. 7
      jme3-core/src/main/resources/joystick-mapping.properties
  38. 8
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  39. 23
      jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java
  40. 4
      jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java
  41. 6
      jme3-effects/src/main/resources/Common/MatDefs/Water/Water.frag
  42. 1
      jme3-effects/src/main/resources/Common/MatDefs/Water/Water.j3md
  43. 4
      jme3-ios/src/main/java/com/jme3/asset/IOS.cfg
  44. 1054
      jme3-ios/src/main/java/com/jme3/audio/android/AL.java
  45. 67
      jme3-ios/src/main/java/com/jme3/audio/android/AndroidAudioData.java
  46. 53
      jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java
  47. 26
      jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java
  48. 32
      jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java
  49. 20
      jme3-ios/src/main/java/com/jme3/audio/plugins/AndroidAudioLoader.java
  50. 8
      jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java
  51. 5
      jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java
  52. 13
      jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java
  53. 13
      jme3-ios/src/main/java/com/jme3/system/ios/JmeIosSystem.java
  54. 8
      jme3-ios/src/main/resources/com/jme3/asset/IOS.cfg
  55. 15
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java
  56. 68
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java
  57. 98
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboEXT.java
  58. 96
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLFboGL3.java
  59. 33
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java
  60. 78
      jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglGLDebugOutputHandler.java
  61. 8
      jme3-networking/src/main/java/com/jme3/network/Client.java
  62. 8
      jme3-networking/src/main/java/com/jme3/network/Server.java
  63. 27
      jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java
  64. 54
      jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java
  65. 188
      jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java
  66. 51
      jme3-networking/src/main/java/com/jme3/network/service/AbstractClientService.java
  67. 70
      jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java
  68. 111
      jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java
  69. 72
      jme3-networking/src/main/java/com/jme3/network/service/ClientService.java
  70. 100
      jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java
  71. 74
      jme3-networking/src/main/java/com/jme3/network/service/HostedService.java
  72. 140
      jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java
  73. 67
      jme3-networking/src/main/java/com/jme3/network/service/Service.java
  74. 160
      jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java
  75. 123
      jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java
  76. 260
      jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java
  77. 52
      jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHandler.java
  78. 227
      jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java
  79. 98
      jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcCallMessage.java
  80. 89
      jme3-networking/src/main/java/com/jme3/network/service/rpc/msg/RpcResponseMessage.java
  81. 69
      jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java
  82. 72
      jme3-networking/src/main/java/com/jme3/network/service/serializer/ServerSerializerRegistrationsService.java
  83. 314
      jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java
  84. 72
      jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java
  85. 103
      jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java
  86. 9
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java
  87. 413
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/FbxLoader.java
  88. 84
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java
  89. 144
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurve.java
  90. 147
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java
  91. 82
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java
  92. 111
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimStack.java
  93. 44
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java
  94. 103
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxBindPose.java
  95. 98
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxCluster.java
  96. 94
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java
  97. 66
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java
  98. 202
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxToJmeTrack.java
  99. 76
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java
  100. 124
      jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -35,9 +35,6 @@ package com.jme3.input.android;
import android.view.GestureDetector; import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.ScaleGestureDetector; 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 com.jme3.input.event.TouchEvent;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -50,152 +47,75 @@ import java.util.logging.Logger;
* *
* @author iwgeric * @author iwgeric
*/ */
public class AndroidGestureHandler implements public class AndroidGestureProcessor implements
GestureDetector.OnGestureListener, GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener, GestureDetector.OnDoubleTapListener,
ScaleGestureDetector.OnScaleGestureListener { ScaleGestureDetector.OnScaleGestureListener {
private static final Logger logger = Logger.getLogger(AndroidGestureHandler.class.getName()); private static final Logger logger = Logger.getLogger(AndroidGestureProcessor.class.getName());
private AndroidInputHandler androidInput;
private GestureDetector gestureDetector; private AndroidTouchInput touchInput;
private ScaleGestureDetector scaleDetector;
float gestureDownX = -1f; float gestureDownX = -1f;
float gestureDownY = -1f; float gestureDownY = -1f;
float scaleStartX = -1f; float scaleStartX = -1f;
float scaleStartY = -1f; float scaleStartY = -1f;
public AndroidGestureHandler(AndroidInputHandler androidInput) { public AndroidGestureProcessor(AndroidTouchInput touchInput) {
this.androidInput = androidInput; this.touchInput = touchInput;
}
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));
}
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 */ /* Events from onGestureListener */
@Override
public boolean onDown(MotionEvent event) { public boolean onDown(MotionEvent event) {
// start of all GestureListeners. Not really a gesture by itself // start of all GestureListeners. Not really a gesture by itself
// so we don't create an event. // so we don't create an event.
// However, reset the scaleInProgress here since this is the beginning // However, reset the scaleInProgress here since this is the beginning
// of a series of gesture events. // of a series of gesture events.
// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}", // logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); // new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
gestureDownX = androidInput.getJmeX(event.getX()); gestureDownX = touchInput.getJmeX(event.getX());
gestureDownY = androidInput.invertY(androidInput.getJmeY(event.getY())); gestureDownY = touchInput.invertY(touchInput.getJmeY(event.getY()));
return true; return true;
} }
@Override
public boolean onSingleTapUp(MotionEvent event) { public boolean onSingleTapUp(MotionEvent event) {
// Up of single tap. May be followed by a double tap later. // Up of single tap. May be followed by a double tap later.
// use onSingleTapConfirmed instead. // use onSingleTapConfirmed instead.
// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}", // logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); // new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
return true; return true;
} }
@Override
public void onShowPress(MotionEvent event) { public void onShowPress(MotionEvent event) {
// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}", // logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); // new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
float jmeX = androidInput.getJmeX(event.getX()); float jmeX = touchInput.getJmeX(event.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent(); TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SHOWPRESS, jmeX, jmeY, 0, 0); touchEvent.set(TouchEvent.Type.SHOWPRESS, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(getPointerId(event)); touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime()); touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure()); touchEvent.setPressure(event.getPressure());
processEvent(touchEvent); touchInput.addEvent(touchEvent);
} }
@Override
public void onLongPress(MotionEvent event) { public void onLongPress(MotionEvent event) {
// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}", // logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); // new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
float jmeX = androidInput.getJmeX(event.getX()); float jmeX = touchInput.getJmeX(event.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent(); TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.LONGPRESSED, jmeX, jmeY, 0, 0); touchEvent.set(TouchEvent.Type.LONGPRESSED, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(getPointerId(event)); touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime()); touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure()); touchEvent.setPressure(event.getPressure());
processEvent(touchEvent); touchInput.addEvent(touchEvent);
} }
@Override
public boolean onScroll(MotionEvent startEvent, MotionEvent endEvent, float distX, float distY) { public boolean onScroll(MotionEvent startEvent, MotionEvent endEvent, float distX, float distY) {
// if not scaleInProgess, send scroll events. This is to avoid sending // 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. // scroll events when one of the fingers is lifted just before the other one.
@ -204,23 +124,23 @@ public class AndroidGestureHandler implements
// 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 // 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. // for the fact that jME has y=0 at bottom where Android has y=0 at top.
// if (!scaleInProgress) { if (!touchInput.getScaleDetector().isInProgress()) {
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}", // 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}); // 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 jmeX = touchInput.getJmeX(endEvent.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(endEvent.getY())); float jmeY = touchInput.invertY(touchInput.getJmeY(endEvent.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent(); TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, androidInput.getJmeX(-distX), androidInput.getJmeY(distY)); touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, touchInput.getJmeX(-distX), touchInput.getJmeY(distY));
touchEvent.setPointerId(getPointerId(endEvent)); touchEvent.setPointerId(touchInput.getPointerId(endEvent));
touchEvent.setTime(endEvent.getEventTime()); touchEvent.setTime(endEvent.getEventTime());
touchEvent.setPressure(endEvent.getPressure()); touchEvent.setPressure(endEvent.getPressure());
processEvent(touchEvent); touchInput.addEvent(touchEvent);
} }
return true; return true;
} }
@Override
public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float velocityX, float velocityY) { 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 happens only once at the end of the gesture (all fingers up).
// Fling returns the velocity of the finger movement in pixels/sec. // Fling returns the velocity of the finger movement in pixels/sec.
@ -228,121 +148,127 @@ public class AndroidGestureHandler implements
// Since this does not track the movement, use the start position and velocity 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}", // 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}); // new Object[]{touchInput.getPointerId(startEvent), touchInput.getAction(startEvent), startEvent.getX(), startEvent.getY(), touchInput.getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY});
float jmeX = androidInput.getJmeX(startEvent.getX()); float jmeX = touchInput.getJmeX(startEvent.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(startEvent.getY())); float jmeY = touchInput.invertY(touchInput.getJmeY(startEvent.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent(); TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.FLING, jmeX, jmeY, velocityX, velocityY); touchEvent.set(TouchEvent.Type.FLING, jmeX, jmeY, velocityX, velocityY);
touchEvent.setPointerId(getPointerId(endEvent)); touchEvent.setPointerId(touchInput.getPointerId(endEvent));
touchEvent.setTime(endEvent.getEventTime()); touchEvent.setTime(endEvent.getEventTime());
touchEvent.setPressure(endEvent.getPressure()); touchEvent.setPressure(endEvent.getPressure());
processEvent(touchEvent); touchInput.addEvent(touchEvent);
return true; return true;
} }
/* Events from onDoubleTapListener */ /* Events from onDoubleTapListener */
@Override
public boolean onSingleTapConfirmed(MotionEvent event) { public boolean onSingleTapConfirmed(MotionEvent event) {
// Up of single tap when no double tap followed. // Up of single tap when no double tap followed.
// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}", // logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); // new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
float jmeX = androidInput.getJmeX(event.getX()); float jmeX = touchInput.getJmeX(event.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent(); TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.TAP, jmeX, jmeY, 0, 0); touchEvent.set(TouchEvent.Type.TAP, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(getPointerId(event)); touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime()); touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure()); touchEvent.setPressure(event.getPressure());
processEvent(touchEvent); touchInput.addEvent(touchEvent);
return true; return true;
} }
@Override
public boolean onDoubleTap(MotionEvent event) { public boolean onDoubleTap(MotionEvent event) {
//The down motion event of the first tap of the double-tap //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 // DoubleTapEvent with a check for the UP action
// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}", // logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); // new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
float jmeX = androidInput.getJmeX(event.getX()); float jmeX = touchInput.getJmeX(event.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY())); float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent(); TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.DOUBLETAP, jmeX, jmeY, 0, 0); touchEvent.set(TouchEvent.Type.DOUBLETAP, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(getPointerId(event)); touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime()); touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure()); touchEvent.setPressure(event.getPressure());
processEvent(touchEvent); touchInput.addEvent(touchEvent);
return true; return true;
} }
@Override
public boolean onDoubleTapEvent(MotionEvent event) { public boolean onDoubleTapEvent(MotionEvent event) {
//Notified when an event within a double-tap gesture occurs, including the down, move(s), and up events. //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 // 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}", // logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()}); // new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
// if (getAction(event) == MotionEvent.ACTION_UP) { if (touchInput.getAction(event) == MotionEvent.ACTION_UP) {
// TouchEvent touchEvent = touchEventPool.getNextFreeEvent(); TouchEvent touchEvent = touchInput.getFreeTouchEvent();
// touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), androidInput.invertY(event.getY()), 0, 0); touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), touchInput.invertY(event.getY()), 0, 0);
// touchEvent.setPointerId(getPointerId(event)); touchEvent.setPointerId(touchInput.getPointerId(event));
// touchEvent.setTime(event.getEventTime()); touchEvent.setTime(event.getEventTime());
// touchEvent.setPressure(event.getPressure()); touchEvent.setPressure(event.getPressure());
// processEvent(touchEvent); touchInput.addEvent(touchEvent);
// } }
return true; return true;
} }
/* Events from ScaleGestureDetector */ /* Events from ScaleGestureDetector */
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
// Scale uses a focusX and focusY instead of x and y. Focus is the middle // 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 // 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. // 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 // return true or all gestures for this beginning event will be discarded
logger.log(Level.INFO, "onScaleBegin"); // logger.log(Level.INFO, "onScaleBegin");
scaleStartX = gestureDownX; scaleStartX = gestureDownX;
scaleStartY = gestureDownY; scaleStartY = gestureDownY;
TouchEvent touchEvent = androidInput.getFreeTouchEvent(); TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCALE_START, scaleStartX, scaleStartY, 0f, 0f); touchEvent.set(TouchEvent.Type.SCALE_START, scaleStartX, scaleStartY, 0f, 0f);
touchEvent.setPointerId(0); touchEvent.setPointerId(0);
touchEvent.setTime(scaleGestureDetector.getEventTime()); touchEvent.setTime(scaleGestureDetector.getEventTime());
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan()); touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
touchEvent.setDeltaScaleSpan(0f); touchEvent.setDeltaScaleSpan(0f);
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor()); touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress()); touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress());
processEvent(touchEvent); touchInput.addEvent(touchEvent);
return true; return true;
} }
@Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) { public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
// return true or all gestures for this event will be accumulated // return true or all gestures for this event will be accumulated
logger.log(Level.INFO, "onScale"); // logger.log(Level.INFO, "onScale");
scaleStartX = gestureDownX; scaleStartX = gestureDownX;
scaleStartY = gestureDownY; scaleStartY = gestureDownY;
TouchEvent touchEvent = androidInput.getFreeTouchEvent(); TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCALE_MOVE, scaleStartX, scaleStartY, 0f, 0f); touchEvent.set(TouchEvent.Type.SCALE_MOVE, scaleStartX, scaleStartY, 0f, 0f);
touchEvent.setPointerId(0); touchEvent.setPointerId(0);
touchEvent.setTime(scaleGestureDetector.getEventTime()); touchEvent.setTime(scaleGestureDetector.getEventTime());
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan()); touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor()); touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress()); touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress());
processEvent(touchEvent); touchInput.addEvent(touchEvent);
return true; return true;
} }
@Override
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
logger.log(Level.INFO, "onScaleEnd"); // logger.log(Level.INFO, "onScaleEnd");
scaleStartX = gestureDownX; scaleStartX = gestureDownX;
scaleStartY = gestureDownY; scaleStartY = gestureDownY;
TouchEvent touchEvent = androidInput.getFreeTouchEvent(); TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCALE_END, scaleStartX, scaleStartY, 0f, 0f); touchEvent.set(TouchEvent.Type.SCALE_END, scaleStartX, scaleStartY, 0f, 0f);
touchEvent.setPointerId(0); touchEvent.setPointerId(0);
touchEvent.setTime(scaleGestureDetector.getEventTime()); touchEvent.setTime(scaleGestureDetector.getEventTime());
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan()); touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan()); touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor()); touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress()); touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress());
processEvent(touchEvent); touchInput.addEvent(touchEvent);
} }
} }

@ -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;
/**
* <code>AndroidInput</code> 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<TouchEvent> eventQueue = new RingBuffer<TouchEvent>(MAX_EVENTS);
final private RingBuffer<TouchEvent> eventPoolUnConsumed = new RingBuffer<TouchEvent>(MAX_EVENTS);
final private RingBuffer<TouchEvent> eventPool = new RingBuffer<TouchEvent>(MAX_EVENTS);
final private HashMap<Integer, Vector2f> lastPositions = new HashMap<Integer, Vector2f>();
// 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;
}
}

@ -33,231 +33,206 @@
package com.jme3.input.android; package com.jme3.input.android;
import android.opengl.GLSurfaceView; 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 android.view.View;
import com.jme3.input.RawInputListener; import com.jme3.input.JoyInput;
import com.jme3.input.TouchInput; 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 com.jme3.system.AppSettings;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
* <code>AndroidInput</code> is the main class that connects the Android system * <code>AndroidInput</code> is the main class that connects the Android system
* inputs to jME. It serves as the manager that gathers inputs from the various * inputs to jME. It receives the inputs from the Android View and passes them
* Android input methods and provides them to jME's <code>InputManager</code>. * to the appropriate classes based on the source of the input.</br>
* This class is to be extended when new functionality is released in Android.
* *
* @author iwgeric * @author iwgeric
*/ */
public class AndroidInputHandler implements TouchInput { public class AndroidInputHandler implements View.OnTouchListener,
private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName()); View.OnKeyListener {
// Custom settings
private boolean mouseEventsEnabled = true;
private boolean mouseEventsInvertX = false;
private boolean mouseEventsInvertY = false;
private boolean keyboardEventsEnabled = false;
private boolean dontSendHistory = false;
private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName());
// Internal protected GLSurfaceView view;
private GLSurfaceView view; protected AndroidTouchInput touchInput;
private AndroidTouchHandler touchHandler; protected AndroidJoyInput joyInput;
private AndroidKeyHandler keyHandler;
private AndroidGestureHandler gestureHandler;
private boolean initialized = false;
private RawInputListener listener = null;
private ConcurrentLinkedQueue<InputEvent> inputEventQueue = new ConcurrentLinkedQueue<InputEvent>();
private final static int MAX_TOUCH_EVENTS = 1024;
private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS);
private float scaleX = 1f;
private float scaleY = 1f;
public AndroidInputHandler() { public AndroidInputHandler() {
int buildVersion = Build.VERSION.SDK_INT; touchInput = new AndroidTouchInput(this);
logger.log(Level.INFO, "Android Build Version: {0}", buildVersion); joyInput = new AndroidJoyInput(this);
if (buildVersion >= 14) {
// add support for onHover and GenericMotionEvent (ie. gamepads)
gestureHandler = new AndroidGestureHandler(this);
touchHandler = new AndroidTouchHandler14(this, gestureHandler);
keyHandler = new AndroidKeyHandler(this);
} else if (buildVersion >= 8){
gestureHandler = new AndroidGestureHandler(this);
touchHandler = new AndroidTouchHandler(this, gestureHandler);
keyHandler = new AndroidKeyHandler(this);
}
}
public AndroidInputHandler(AndroidTouchHandler touchInput,
AndroidKeyHandler keyInput, AndroidGestureHandler gestureHandler) {
this.touchHandler = touchInput;
this.keyHandler = keyInput;
this.gestureHandler = gestureHandler;
} }
public void setView(View view) { public void setView(View view) {
if (touchHandler != null) { if (this.view != null && view != null && this.view.equals(view)) {
touchHandler.setView(view); return;
}
if (keyHandler != null) {
keyHandler.setView(view);
}
if (gestureHandler != null) {
gestureHandler.setView(view);
}
this.view = (GLSurfaceView)view;
} }
public View getView() { if (this.view != null) {
return view; removeListeners(this.view);
} }
public float invertX(float origX) { this.view = (GLSurfaceView)view;
return getJmeX(view.getWidth()) - origX;
}
public float invertY(float origY) { if (this.view != null) {
return getJmeY(view.getHeight()) - origY; addListeners(this.view);
} }
public float getJmeX(float origX) { joyInput.setView((GLSurfaceView)view);
return origX * scaleX;
} }
public float getJmeY(float origY) { public View getView() {
return origY * scaleY; return view;
} }
public void loadSettings(AppSettings settings) { protected void removeListeners(GLSurfaceView view) {
keyboardEventsEnabled = settings.isEmulateKeyboard(); view.setOnTouchListener(null);
mouseEventsEnabled = settings.isEmulateMouse(); view.setOnKeyListener(null);
mouseEventsInvertX = settings.isEmulateMouseFlipX(); touchInput.setGestureDetector(null);
mouseEventsInvertY = settings.isEmulateMouseFlipY(); touchInput.setScaleDetector(null);
// 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});
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) {
// JME3 Input interface touchInput.loadSettings(settings);
@Override
public void initialize() {
touchEventPool.initialize();
if (touchHandler != null) {
touchHandler.initialize();
}
if (keyHandler != null) {
keyHandler.initialize();
}
if (gestureHandler != null) {
gestureHandler.initialize();
} }
initialized = true; public TouchInput getTouchInput() {
return touchInput;
} }
@Override public JoyInput getJoyInput() {
public void destroy() { return joyInput;
initialized = false;
touchEventPool.destroy();
if (touchHandler != null) {
touchHandler.destroy();
}
if (keyHandler != null) {
keyHandler.destroy();
}
if (gestureHandler != null) {
gestureHandler.destroy();
} }
setView(null); /*
} * 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.
* </br>
* 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.</br>
*
* 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
*
*
*
*/
@Override
public boolean isInitialized() {
return initialized;
}
@Override @Override
public void setInputListener(RawInputListener listener) { public boolean onTouch(View view, MotionEvent event) {
this.listener = listener; if (view != getView()) {
return false;
} }
@Override boolean consumed = false;
public long getInputTimeNanos() {
return System.nanoTime();
}
public void update() { int source = event.getSource();
if (listener != null) { // logger.log(Level.INFO, "onTouch source: {0}", source);
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);
}
}
}
}
// ----------------------------------------- boolean isTouch = ((source & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN);
// logger.log(Level.INFO, "onTouch source: {0}, isTouch: {1}",
// new Object[]{source, isTouch});
public TouchEvent getFreeTouchEvent() { if (isTouch && touchInput != null) {
return touchEventPool.getNextFreeEvent(); // send the event to the touch processor
consumed = touchInput.onTouch(event);
} }
public void addEvent(InputEvent event) { return consumed;
inputEventQueue.add(event);
if (event instanceof TouchEvent) {
touchEventPool.storeEvent((TouchEvent)event);
}
}
public void setSimulateMouse(boolean simulate) {
this.mouseEventsEnabled = simulate;
} }
public boolean isSimulateMouse() { @Override
return mouseEventsEnabled; public boolean onKey(View view, int keyCode, KeyEvent event) {
if (view != getView()) {
return false;
} }
public boolean isMouseEventsInvertX() { boolean consumed = false;
return mouseEventsInvertX;
}
public boolean isMouseEventsInvertY() { int source = event.getSource();
return mouseEventsInvertY; // logger.log(Level.INFO, "onKey source: {0}", source);
}
public void setSimulateKeyboard(boolean simulate) { boolean isTouch =
this.keyboardEventsEnabled = simulate; ((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 isSimulateKeyboard() { if (touchInput != null) {
return keyboardEventsEnabled; consumed = touchInput.onKey(event);
} }
public void setOmitHistoricEvents(boolean dontSendHistory) { return consumed;
this.dontSendHistory = dontSendHistory;
} }
} }

@ -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;
/**
* <code>AndroidInputHandler14</code> extends <code>AndroidInputHandler</code> to
* add the onHover and onGenericMotion events that where added in Android rev 14 (Android 4.0).</br>
* 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;
}
}

@ -33,9 +33,7 @@ package com.jme3.input.android;
import android.content.Context; import android.content.Context;
import android.opengl.GLSurfaceView; import android.opengl.GLSurfaceView;
import android.os.Build;
import android.os.Vibrator; import android.os.Vibrator;
import android.view.View;
import com.jme3.input.InputManager; import com.jme3.input.InputManager;
import com.jme3.input.JoyInput; import com.jme3.input.JoyInput;
import com.jme3.input.Joystick; import com.jme3.input.Joystick;
@ -79,15 +77,16 @@ import java.util.logging.Logger;
* *
* @author iwgeric * @author iwgeric
*/ */
public class AndroidJoyInputHandler implements JoyInput { public class AndroidJoyInput implements JoyInput {
private static final Logger logger = Logger.getLogger(AndroidJoyInputHandler.class.getName()); private static final Logger logger = Logger.getLogger(AndroidJoyInput.class.getName());
public static boolean disableSensors = false;
private List<Joystick> joystickList = new ArrayList<Joystick>(); protected AndroidInputHandler inputHandler;
protected List<Joystick> joystickList = new ArrayList<Joystick>();
// private boolean dontSendHistory = false; // private boolean dontSendHistory = false;
// Internal // Internal
private GLSurfaceView view;
private boolean initialized = false; private boolean initialized = false;
private RawInputListener listener = null; private RawInputListener listener = null;
private ConcurrentLinkedQueue<InputEvent> eventQueue = new ConcurrentLinkedQueue<InputEvent>(); private ConcurrentLinkedQueue<InputEvent> eventQueue = new ConcurrentLinkedQueue<InputEvent>();
@ -96,34 +95,29 @@ public class AndroidJoyInputHandler implements JoyInput {
private boolean vibratorActive = false; private boolean vibratorActive = false;
private long maxRumbleTime = 250; // 250ms private long maxRumbleTime = 250; // 250ms
public AndroidJoyInputHandler() { public AndroidJoyInput(AndroidInputHandler inputHandler) {
int buildVersion = Build.VERSION.SDK_INT; this.inputHandler = inputHandler;
logger.log(Level.INFO, "Android Build Version: {0}", buildVersion);
// if (buildVersion >= 14) {
// touchHandler = new AndroidTouchHandler14(this);
// } else if (buildVersion >= 8){
// touchHandler = new AndroidTouchHandler(this);
// }
sensorJoyInput = new AndroidSensorJoyInput(this); sensorJoyInput = new AndroidSensorJoyInput(this);
} }
public void setView(GLSurfaceView view) { public void setView(GLSurfaceView view) {
// if (touchHandler != null) { if (view == null) {
// touchHandler.setView(view); vibrator = null;
// } } else {
if (sensorJoyInput != null) { // Get instance of Vibrator from current Context
sensorJoyInput.setView(view); vibrator = (Vibrator) view.getContext().getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator == null) {
logger.log(Level.FINE, "Vibrator Service not found.");
} }
this.view = (GLSurfaceView)view;
} }
public View getView() { if (sensorJoyInput != null) {
return view; sensorJoyInput.setView(view);
}
} }
public void loadSettings(AppSettings settings) { public void loadSettings(AppSettings settings) {
// sensorEventsEnabled = settings.useSensors();
} }
public void addEvent(InputEvent event) { public void addEvent(InputEvent event) {
@ -155,20 +149,8 @@ public class AndroidJoyInputHandler implements JoyInput {
} }
@Override @Override
public void initialize() { 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; initialized = true;
} }
@ -211,8 +193,8 @@ public class AndroidJoyInputHandler implements JoyInput {
}; };
final int rumbleRepeatFrom = 0; // index into rumble pattern to repeat from final int rumbleRepeatFrom = 0; // index into rumble pattern to repeat from
logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}", // logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}",
new Object[]{amount, rumbleOnDur, rumbleOffDur}); // new Object[]{amount, rumbleOnDur, rumbleOffDur});
if (rumbleOnDur > 0) { if (rumbleOnDur > 0) {
vibrator.vibrate(rumblePattern, rumbleRepeatFrom); vibrator.vibrate(rumblePattern, rumbleRepeatFrom);
@ -226,9 +208,10 @@ public class AndroidJoyInputHandler implements JoyInput {
@Override @Override
public Joystick[] loadJoysticks(InputManager inputManager) { public Joystick[] loadJoysticks(InputManager inputManager) {
logger.log(Level.INFO, "loading joysticks for {0}", this.getClass().getName());
if (!disableSensors) {
joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager)); joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager));
}
return joystickList.toArray( new Joystick[joystickList.size()] ); return joystickList.toArray( new Joystick[joystickList.size()] );
} }
@ -252,6 +235,4 @@ public class AndroidJoyInputHandler implements JoyInput {
} }
} }

@ -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;
/**
* <code>AndroidJoyInput14</code> extends <code>AndroidJoyInput</code>
* to include support for physical joysticks/gamepads.</br>
*
* @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);
}
}

@ -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<Integer, AndroidJoystick> joystickIndex = new HashMap<Integer, AndroidJoystick>();
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<Joystick> loadJoysticks(int joyId, InputManager inputManager) {
logger.log(Level.INFO, "loading Joystick devices");
ArrayList<Joystick> joysticks = new ArrayList<Joystick>();
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<MotionRange> 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<Integer, JoystickAxis> axisIndex = new HashMap<Integer, JoystickAxis>();
private Map<Integer, JoystickButton> buttonIndex = new HashMap<Integer, JoystickButton>();
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<Integer> 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();
}
}
}

@ -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;
}
}
}

@ -37,7 +37,8 @@ import java.util.logging.Logger;
/** /**
* AndroidKeyMapping is just a utility to convert the Android keyCodes into * 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 * @author iwgeric
*/ */
@ -143,7 +144,11 @@ public class AndroidKeyMapping {
}; };
public static int getJmeKey(int androidKey) { public static int getJmeKey(int androidKey) {
if (androidKey > ANDROID_TO_JME.length) {
return androidKey;
} else {
return ANDROID_TO_JME[androidKey]; return ANDROID_TO_JME[androidKey];
} }
}
} }

@ -75,15 +75,15 @@ import java.util.logging.Logger;
public class AndroidSensorJoyInput implements SensorEventListener { public class AndroidSensorJoyInput implements SensorEventListener {
private final static Logger logger = Logger.getLogger(AndroidSensorJoyInput.class.getName()); private final static Logger logger = Logger.getLogger(AndroidSensorJoyInput.class.getName());
private AndroidJoyInputHandler joyHandler; private AndroidJoyInput joyInput;
private SensorManager sensorManager = null; private SensorManager sensorManager = null;
private WindowManager windowManager = null; private WindowManager windowManager = null;
private IntMap<SensorData> sensors = new IntMap<SensorData>(); private IntMap<SensorData> sensors = new IntMap<SensorData>();
private int lastRotation = 0; private int lastRotation = 0;
private boolean loaded = false; private boolean loaded = false;
public AndroidSensorJoyInput(AndroidJoyInputHandler joyHandler) { public AndroidSensorJoyInput(AndroidJoyInput joyInput) {
this.joyHandler = joyHandler; this.joyInput = joyInput;
} }
/** /**
@ -96,7 +96,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
int sensorAccuracy = -1; int sensorAccuracy = -1;
float[] lastValues; float[] lastValues;
final Object valuesLock = new Object(); final Object valuesLock = new Object();
ArrayList<AndroidJoystickAxis> axes = new ArrayList<AndroidJoystickAxis>(); ArrayList<AndroidSensorJoystickAxis> axes = new ArrayList<AndroidSensorJoystickAxis>();
boolean enabled = false; boolean enabled = false;
boolean haveData = false; boolean haveData = false;
@ -306,7 +306,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
*/ */
private boolean updateOrientation() { private boolean updateOrientation() {
SensorData sensorData; SensorData sensorData;
AndroidJoystickAxis axis; AndroidSensorJoystickAxis axis;
final float[] curInclinationMat = new float[16]; final float[] curInclinationMat = new float[16];
final float[] curRotationMat = new float[16]; final float[] curRotationMat = new float[16];
final float[] rotatedRotationMat = new float[16]; final float[] rotatedRotationMat = new float[16];
@ -374,7 +374,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
sensorData.haveData = true; sensorData.haveData = true;
} else { } else {
if (axis.isChanged()) { 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) { public Joystick loadJoystick(int joyId, InputManager inputManager) {
SensorData sensorData; SensorData sensorData;
AndroidJoystickAxis axis; AndroidSensorJoystickAxis axis;
AndroidJoystick joystick = new AndroidJoystick(inputManager, AndroidSensorJoystick joystick = new AndroidSensorJoystick(inputManager,
joyHandler, joyInput,
joyId, joyId,
"AndroidSensorsJoystick"); "AndroidSensorsJoystick");
@ -522,15 +522,15 @@ public class AndroidSensorJoyInput implements SensorEventListener {
if (!loaded) { if (!loaded) {
return; return;
} }
logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}", // logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}",
new Object[]{se.sensor.getName(), se.accuracy, se.values}); // new Object[]{se.sensor.getName(), se.accuracy, se.values});
int sensorType = se.sensor.getType(); int sensorType = se.sensor.getType();
SensorData sensorData = sensors.get(sensorType); SensorData sensorData = sensors.get(sensorType);
if (sensorData != null) { if (sensorData != null) {
logger.log(Level.FINE, "sensorData name: {0}, enabled: {1}, unreliable: {2}", // logger.log(Level.FINE, "sensorData name: {0}, enabled: {1}, unreliable: {2}",
new Object[]{sensorData.sensor.getName(), sensorData.enabled, sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE}); // new Object[]{sensorData.sensor.getName(), sensorData.enabled, sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE});
} }
if (sensorData != null && sensorData.sensor.equals(se.sensor) && sensorData.enabled) { if (sensorData != null && sensorData.sensor.equals(se.sensor) && sensorData.enabled) {
@ -543,8 +543,8 @@ public class AndroidSensorJoyInput implements SensorEventListener {
} }
} }
if (sensorData != null && sensorData.axes.size() > 0) { if (sensorData.axes.size() > 0) {
AndroidJoystickAxis axis; AndroidSensorJoystickAxis axis;
for (int i=0; i<se.values.length; i++) { for (int i=0; i<se.values.length; i++) {
axis = sensorData.axes.get(i); axis = sensorData.axes.get(i);
if (axis != null) { if (axis != null) {
@ -554,8 +554,8 @@ public class AndroidSensorJoyInput implements SensorEventListener {
} else { } else {
if (axis.isChanged()) { if (axis.isChanged()) {
JoyAxisEvent event = new JoyAxisEvent(axis, axis.getJoystickAxisValue()); JoyAxisEvent event = new JoyAxisEvent(axis, axis.getJoystickAxisValue());
logger.log(Level.INFO, "adding JoyAxisEvent: {0}", event); // logger.log(Level.INFO, "adding JoyAxisEvent: {0}", event);
joyHandler.addEvent(event); joyInput.addEvent(event);
// joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); // joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue()));
} }
} }
@ -585,14 +585,14 @@ public class AndroidSensorJoyInput implements SensorEventListener {
// End of SensorEventListener methods // End of SensorEventListener methods
protected class AndroidJoystick extends AbstractJoystick { protected class AndroidSensorJoystick extends AbstractJoystick {
private JoystickAxis nullAxis; private JoystickAxis nullAxis;
private JoystickAxis xAxis; private JoystickAxis xAxis;
private JoystickAxis yAxis; private JoystickAxis yAxis;
private JoystickAxis povX; private JoystickAxis povX;
private JoystickAxis povY; private JoystickAxis povY;
public AndroidJoystick( InputManager inputManager, JoyInput joyInput, public AndroidSensorJoystick( InputManager inputManager, JoyInput joyInput,
int joyId, String name){ int joyId, String name){
super( inputManager, joyInput, joyId, name ); super( inputManager, joyInput, joyId, name );
@ -606,10 +606,10 @@ public class AndroidSensorJoyInput implements SensorEventListener {
} }
protected AndroidJoystickAxis addAxis(String axisName, String logicalName, int axisNum, float maxRawValue) { protected AndroidSensorJoystickAxis addAxis(String axisName, String logicalName, int axisNum, float maxRawValue) {
AndroidJoystickAxis axis; AndroidSensorJoystickAxis axis;
axis = new AndroidJoystickAxis( axis = new AndroidSensorJoystickAxis(
getInputManager(), // InputManager (InputManager) getInputManager(), // InputManager (InputManager)
this, // parent Joystick (Joystick) this, // parent Joystick (Joystick)
axisNum, // Axis Index (int) axisNum, // Axis Index (int)
@ -654,7 +654,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
} }
public class AndroidJoystickAxis extends DefaultJoystickAxis implements SensorJoystickAxis { public class AndroidSensorJoystickAxis extends DefaultJoystickAxis implements SensorJoystickAxis {
float zeroRawValue = 0f; float zeroRawValue = 0f;
float curRawValue = 0f; float curRawValue = 0f;
float lastRawValue = 0f; float lastRawValue = 0f;
@ -662,7 +662,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
float maxRawValue = FastMath.HALF_PI; float maxRawValue = FastMath.HALF_PI;
boolean enabled = true; boolean enabled = true;
public AndroidJoystickAxis(InputManager inputManager, Joystick parent, public AndroidSensorJoystickAxis(InputManager inputManager, Joystick parent,
int axisIndex, String name, String logicalId, int axisIndex, String name, String logicalId,
boolean isAnalog, boolean isRelative, float deadZone, boolean isAnalog, boolean isRelative, float deadZone,
float maxRawValue) { float maxRawValue) {

@ -1,257 +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.view.MotionEvent;
import android.view.View;
import com.jme3.input.event.InputEvent;
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 java.util.HashMap;
import java.util.logging.Logger;
/**
* AndroidTouchHandler 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 AndroidTouchHandler implements View.OnTouchListener {
private static final Logger logger = Logger.getLogger(AndroidTouchHandler.class.getName());
final private HashMap<Integer, Vector2f> lastPositions = new HashMap<Integer, Vector2f>();
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;
}
}

@ -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<Integer, Vector2f> lastPositions = new HashMap<Integer, Vector2f>();
final private ConcurrentLinkedQueue<InputEvent> inputEventQueue = new ConcurrentLinkedQueue<InputEvent>();
private final static int MAX_TOUCH_EVENTS = 1024;
private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS);
private float scaleX = 1f;
private float scaleY = 1f;
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;
}
}

@ -33,7 +33,6 @@
package com.jme3.input.android; package com.jme3.input.android;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import com.jme3.input.event.TouchEvent; import com.jme3.input.event.TouchEvent;
import com.jme3.math.Vector2f; import com.jme3.math.Vector2f;
import java.util.HashMap; import java.util.HashMap;
@ -41,36 +40,20 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
* AndroidTouchHandler14 is an extension of AndroidTouchHander that adds the * AndroidTouchHandler14 extends AndroidTouchHandler to process the onHover
* Android touch event functionality between Android rev 9 (Android 2.3) and * events added in Android rev 14 (Android 4.0).
* Android rev 14 (Android 4.0).
* *
* @author iwgeric * @author iwgeric
*/ */
public class AndroidTouchHandler14 extends AndroidTouchHandler implements public class AndroidTouchInput14 extends AndroidTouchInput {
View.OnHoverListener { private static final Logger logger = Logger.getLogger(AndroidTouchInput14.class.getName());
private static final Logger logger = Logger.getLogger(AndroidTouchHandler14.class.getName());
final private HashMap<Integer, Vector2f> lastHoverPositions = new HashMap<Integer, Vector2f>(); final private HashMap<Integer, Vector2f> lastHoverPositions = new HashMap<Integer, Vector2f>();
public AndroidTouchHandler14(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) { public AndroidTouchInput14(AndroidInputHandler androidInput) {
super(androidInput, gestureHandler); super(androidInput);
}
@Override
public void setView(View view) {
if (view != null) {
view.setOnHoverListener(this);
} else {
androidInput.getView().setOnHoverListener(null);
}
super.setView(view);
}
public boolean onHover(View view, MotionEvent event) {
if (view == null || view != androidInput.getView()) {
return false;
} }
public boolean onHover(MotionEvent event) {
boolean consumed = false; boolean consumed = false;
int action = getAction(event); int action = getAction(event);
int pointerId = getPointerId(event); int pointerId = getPointerId(event);
@ -81,15 +64,15 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements
numPointers = event.getPointerCount(); numPointers = event.getPointerCount();
logger.log(Level.INFO, "onHover pointerId: {0}, action: {1}, x: {2}, y: {3}, numPointers: {4}", // 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()}); // new Object[]{pointerId, action, event.getX(), event.getY(), event.getPointerCount()});
TouchEvent touchEvent; TouchEvent touchEvent;
switch (action) { switch (action) {
case MotionEvent.ACTION_HOVER_ENTER: case MotionEvent.ACTION_HOVER_ENTER:
jmeX = androidInput.getJmeX(event.getX(pointerIndex)); jmeX = getJmeX(event.getX(pointerIndex));
jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex))); jmeY = invertY(getJmeY(event.getY(pointerIndex)));
touchEvent = androidInput.getFreeTouchEvent(); touchEvent = getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.HOVER_START, jmeX, jmeY, 0, 0); touchEvent.set(TouchEvent.Type.HOVER_START, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(pointerId); touchEvent.setPointerId(pointerId);
touchEvent.setTime(event.getEventTime()); touchEvent.setTime(event.getEventTime());
@ -98,14 +81,14 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements
lastPos = new Vector2f(jmeX, jmeY); lastPos = new Vector2f(jmeX, jmeY);
lastHoverPositions.put(pointerId, lastPos); lastHoverPositions.put(pointerId, lastPos);
processEvent(touchEvent); addEvent(touchEvent);
consumed = true; consumed = true;
break; break;
case MotionEvent.ACTION_HOVER_MOVE: case MotionEvent.ACTION_HOVER_MOVE:
// Convert all pointers into events // Convert all pointers into events
for (int p = 0; p < event.getPointerCount(); p++) { for (int p = 0; p < event.getPointerCount(); p++) {
jmeX = androidInput.getJmeX(event.getX(p)); jmeX = getJmeX(event.getX(p));
jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(p))); jmeY = invertY(getJmeY(event.getY(p)));
lastPos = lastHoverPositions.get(event.getPointerId(p)); lastPos = lastHoverPositions.get(event.getPointerId(p));
if (lastPos == null) { if (lastPos == null) {
lastPos = new Vector2f(jmeX, jmeY); lastPos = new Vector2f(jmeX, jmeY);
@ -115,30 +98,30 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements
float dX = jmeX - lastPos.x; float dX = jmeX - lastPos.x;
float dY = jmeY - lastPos.y; float dY = jmeY - lastPos.y;
if (dX != 0 || dY != 0) { if (dX != 0 || dY != 0) {
touchEvent = androidInput.getFreeTouchEvent(); touchEvent = getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.HOVER_MOVE, jmeX, jmeY, dX, dY); touchEvent.set(TouchEvent.Type.HOVER_MOVE, jmeX, jmeY, dX, dY);
touchEvent.setPointerId(event.getPointerId(p)); touchEvent.setPointerId(event.getPointerId(p));
touchEvent.setTime(event.getEventTime()); touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure(p)); touchEvent.setPressure(event.getPressure(p));
lastPos.set(jmeX, jmeY); lastPos.set(jmeX, jmeY);
processEvent(touchEvent); addEvent(touchEvent);
} }
} }
consumed = true; consumed = true;
break; break;
case MotionEvent.ACTION_HOVER_EXIT: case MotionEvent.ACTION_HOVER_EXIT:
jmeX = androidInput.getJmeX(event.getX(pointerIndex)); jmeX = getJmeX(event.getX(pointerIndex));
jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex))); jmeY = invertY(getJmeY(event.getY(pointerIndex)));
touchEvent = androidInput.getFreeTouchEvent(); touchEvent = getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.HOVER_END, jmeX, jmeY, 0, 0); touchEvent.set(TouchEvent.Type.HOVER_END, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(pointerId); touchEvent.setPointerId(pointerId);
touchEvent.setTime(event.getEventTime()); touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure(pointerIndex)); touchEvent.setPressure(event.getPressure(pointerIndex));
lastHoverPositions.remove(pointerId); lastHoverPositions.remove(pointerId);
processEvent(touchEvent); addEvent(touchEvent);
consumed = true; consumed = true;
break; break;
default: default:
@ -147,6 +130,7 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements
} }
return consumed; return consumed;
} }
} }

@ -43,6 +43,9 @@ import java.nio.ShortBuffer;
public class AndroidGL implements GL, GLExt { public class AndroidGL implements GL, GLExt {
public void resetStats() {
}
private static int getLimitBytes(ByteBuffer buffer) { private static int getLimitBytes(ByteBuffer buffer) {
checkLimit(buffer); checkLimit(buffer);
return buffer.limit(); return buffer.limit();

@ -47,13 +47,14 @@ import android.widget.EditText;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.jme3.input.*; import com.jme3.input.*;
import com.jme3.input.android.AndroidInputHandler; 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.controls.SoftTextDialogInputListener;
import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyKeyInput;
import com.jme3.input.dummy.DummyMouseInput; import com.jme3.input.dummy.DummyMouseInput;
import com.jme3.renderer.android.AndroidGL; import com.jme3.renderer.android.AndroidGL;
import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLExt; import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer; import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.system.*; import com.jme3.system.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -75,7 +76,6 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
protected SystemListener listener; protected SystemListener listener;
protected boolean autoFlush = true; protected boolean autoFlush = true;
protected AndroidInputHandler androidInput; protected AndroidInputHandler androidInput;
protected AndroidJoyInputHandler androidJoyInput = null;
protected long minFrameDuration = 0; // No FPS cap protected long minFrameDuration = 0; // No FPS cap
protected long lastUpdateTime = 0; protected long lastUpdateTime = 0;
@ -111,18 +111,17 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
// Start to set up the view // Start to set up the view
GLSurfaceView view = new GLSurfaceView(context); GLSurfaceView view = new GLSurfaceView(context);
logger.log(Level.INFO, "Android Build Version: {0}", Build.VERSION.SDK_INT);
if (androidInput == null) { if (androidInput == null) {
if (Build.VERSION.SDK_INT >= 14) {
androidInput = new AndroidInputHandler14();
} else if (Build.VERSION.SDK_INT >= 9){
androidInput = new AndroidInputHandler(); androidInput = new AndroidInputHandler();
} }
}
androidInput.setView(view); androidInput.setView(view);
androidInput.loadSettings(settings); androidInput.loadSettings(settings);
if (androidJoyInput == null) {
androidJoyInput = new AndroidJoyInputHandler();
}
androidJoyInput.setView(view);
androidJoyInput.loadSettings(settings);
// setEGLContextClientVersion must be set before calling setRenderer // setEGLContextClientVersion must be set before calling setRenderer
// this means it cannot be set in AndroidConfigChooser (too late) // this means it cannot be set in AndroidConfigChooser (too late)
view.setEGLContextClientVersion(2); view.setEGLContextClientVersion(2);
@ -198,7 +197,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
Object gl = new AndroidGL(); Object gl = new AndroidGL();
// gl = GLTracer.createGlesTracer((GL)gl, (GLExt)gl); // gl = GLTracer.createGlesTracer((GL)gl, (GLExt)gl);
// gl = new GLDebugES((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(); renderer.initialize();
JmeSystem.setSoftTextDialogInput(this); JmeSystem.setSoftTextDialogInput(this);
@ -235,9 +234,6 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
if (androidInput != null) { if (androidInput != null) {
androidInput.loadSettings(settings); androidInput.loadSettings(settings);
} }
if (androidJoyInput != null) {
androidJoyInput.loadSettings(settings);
}
if (settings.getFrameRate() > 0) { if (settings.getFrameRate() > 0) {
minFrameDuration = (long)(1000d / (double)settings.getFrameRate()); // ms minFrameDuration = (long)(1000d / (double)settings.getFrameRate()); // ms
@ -274,12 +270,12 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
@Override @Override
public JoyInput getJoyInput() { public JoyInput getJoyInput() {
return androidJoyInput; return androidInput.getJoyInput();
} }
@Override @Override
public TouchInput getTouchInput() { public TouchInput getTouchInput() {
return androidInput; return androidInput.getTouchInput();
} }
@Override @Override

@ -110,6 +110,8 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
} }
} }
List<Transform> bestSolution = new ArrayList<Transform>(bones.size());
double bestSolutionDistance = Double.MAX_VALUE;
BoneContext topBone = bones.get(0); BoneContext topBone = bones.get(0);
for (int i = 0; i < iterations && distanceFromTarget > MIN_DISTANCE; ++i) { for (int i = 0; i < iterations && distanceFromTarget > MIN_DISTANCE; ++i) {
for (BoneContext boneContext : bones) { 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)); 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 Vector3d e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector
distanceFromTarget = e.distance(t); 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<bestSolution.size();++i) {
BoneContext boneContext = bones.get(i);
constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, bestSolution.get(i));
} }
} }

@ -195,7 +195,7 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl
/** /**
* When set to true, the physics coordinates will be applied to the local * When set to true, the physics coordinates will be applied to the local
* translation of the Spatial instead of the world traslation. * translation of the Spatial instead of the world translation.
* @param applyPhysicsLocal * @param applyPhysicsLocal
*/ */
public void setApplyPhysicsLocal(boolean applyPhysicsLocal) { public void setApplyPhysicsLocal(boolean applyPhysicsLocal) {

@ -313,6 +313,26 @@ public final class Bone implements Savable {
return modelBindInverseScale; return modelBindInverseScale;
} }
public Transform getModelBindInverseTransform() {
Transform t = new Transform();
t.setTranslation(modelBindInversePos);
t.setRotation(modelBindInverseRot);
if (modelBindInverseScale != null) {
t.setScale(modelBindInverseScale);
}
return t;
}
public Transform getBindInverseTransform() {
Transform t = new Transform();
t.setTranslation(bindPos);
t.setRotation(bindRot);
if (bindScale != null) {
t.setScale(bindScale);
}
return t.invert();
}
/** /**
* @deprecated use {@link #getBindPosition()} * @deprecated use {@link #getBindPosition()}
*/ */

@ -902,6 +902,25 @@ final public class FastMath {
return clamp(input, 0f, 1f); return clamp(input, 0f, 1f);
} }
/**
* Determine if two floats are approximately equal.
* This takes into account the magnitude of the floats, since
* large numbers will have larger differences be close to each other.
*
* Should return true for a=100000, b=100001, but false for a=10000, b=10001.
*
* @param a The first float to compare
* @param b The second float to compare
* @return True if a and b are approximately equal, false otherwise.
*/
public static boolean approximateEquals(float a, float b) {
if (a == b) {
return true;
} else {
return (abs(a - b) / Math.max(abs(a), abs(b))) <= 0.00001f;
}
}
/** /**
* Converts a single precision (32 bit) floating point value * Converts a single precision (32 bit) floating point value
* into half precision (16 bit). * into half precision (16 bit).

@ -259,6 +259,26 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
return store; return store;
} }
public Matrix4f toTransformMatrix() {
Matrix4f trans = new Matrix4f();
trans.setTranslation(translation);
trans.setRotationQuaternion(rot);
trans.setScale(scale);
return trans;
}
public void fromTransformMatrix(Matrix4f mat) {
translation.set(mat.toTranslationVector());
rot.set(mat.toRotationQuat());
scale.set(mat.toScaleVector());
}
public Transform invert() {
Transform t = new Transform();
t.fromTransformMatrix(toTransformMatrix().invertLocal());
return t;
}
/** /**
* Loads the identity. Equal to translation=0,0,0 scale=1,1,1 rot=0,0,0,1. * Loads the identity. Equal to translation=0,0,0 scale=1,1,1 rot=0,0,0,1.
*/ */

@ -337,7 +337,19 @@ public enum Caps {
* <p> * <p>
* Improves the quality of environment mapping. * 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 * Returns true if given the renderer capabilities, the texture

@ -32,7 +32,6 @@
package com.jme3.renderer.opengl; package com.jme3.renderer.opengl;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.nio.ShortBuffer; import java.nio.ShortBuffer;
@ -178,6 +177,8 @@ public interface GL {
public static final int GL_VERTEX_SHADER = 0x8B31; public static final int GL_VERTEX_SHADER = 0x8B31;
public static final int GL_ZERO = 0x0; public static final int GL_ZERO = 0x0;
public void resetStats();
public void glActiveTexture(int texture); public void glActiveTexture(int texture);
public void glAttachShader(int program, int shader); public void glAttachShader(int program, int shader);
public void glBindBuffer(int target, int buffer); public void glBindBuffer(int target, int buffer);

@ -41,8 +41,12 @@ import java.nio.IntBuffer;
public interface GL3 extends GL2 { public interface GL3 extends GL2 {
public static final int GL_DEPTH_STENCIL_ATTACHMENT = 0x821A; 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 glBindFragDataLocation(int param1, int param2, String param3); /// GL3+
public void glBindVertexArray(int param1); /// GL3+ public void glBindVertexArray(int param1); /// GL3+
public void glDeleteVertexArrays(IntBuffer arrays); /// GL3+
public void glGenVertexArrays(IntBuffer param1); /// GL3+ public void glGenVertexArrays(IntBuffer param1); /// GL3+
public String glGetString(int param1, int param2); /// GL3+
} }

@ -3,15 +3,17 @@ package com.jme3.renderer.opengl;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.IntBuffer; 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 GL2 gl2;
private final GL3 gl3; private final GL3 gl3;
private final GL4 gl4;
public GLDebugDesktop(GL gl, GLFbo glfbo) { public GLDebugDesktop(GL gl, GLExt glext, GLFbo glfbo) {
super(gl, glfbo); super(gl, glext, glfbo);
this.gl2 = gl instanceof GL2 ? (GL2) gl : null; this.gl2 = gl instanceof GL2 ? (GL2) gl : null;
this.gl3 = gl instanceof GL3 ? (GL3) 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) { public void glAlphaFunc(int func, float ref) {
@ -74,4 +76,22 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3 {
checkError(); 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();
}
} }

@ -10,12 +10,14 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
private final GLFbo glfbo; private final GLFbo glfbo;
private final GLExt glext; private final GLExt glext;
public GLDebugES(GL gl, GLFbo glfbo) { public GLDebugES(GL gl, GLExt glext, GLFbo glfbo) {
this.gl = gl; this.gl = gl;
// this.gl2 = gl instanceof GL2 ? (GL2) gl : null; this.glext = glext;
// this.gl3 = gl instanceof GL3 ? (GL3) gl : null;
this.glfbo = glfbo; this.glfbo = glfbo;
this.glext = glfbo instanceof GLExt ? (GLExt) glfbo : null; }
public void resetStats() {
gl.resetStats();
} }
public void glActiveTexture(int texture) { public void glActiveTexture(int texture) {
@ -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) { 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(); 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) { 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(); checkError();
} }

@ -41,7 +41,7 @@ import java.nio.IntBuffer;
* *
* @author Kirill Vainer * @author Kirill Vainer
*/ */
public interface GLExt extends GLFbo { public interface GLExt {
public static final int GL_ALREADY_SIGNALED = 0x911A; public static final int GL_ALREADY_SIGNALED = 0x911A;
public static final int GL_COMPRESSED_RGB8_ETC2 = 0x9274; 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_UNSIGNED_INT_5_9_9_9_REV_EXT = 0x8C3E;
public static final int GL_WAIT_FAILED = 0x911D; 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 glBufferData(int target, IntBuffer data, int usage);
public void glBufferSubData(int target, long offset, IntBuffer data); public void glBufferSubData(int target, long offset, IntBuffer data);
public int glClientWaitSync(Object sync, int flags, long timeout); 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 void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount);
public Object glFenceSync(int condition, int flags); public Object glFenceSync(int condition, int flags);
public void glGetMultisample(int pname, int index, FloatBuffer val); 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 glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations);
public void glVertexAttribDivisorARB(int index, int divisor); public void glVertexAttribDivisorARB(int index, int divisor);
} }

@ -83,6 +83,7 @@ public interface GLFbo {
public void glBindFramebufferEXT(int param1, int param2); public void glBindFramebufferEXT(int param1, int param2);
public void glBindRenderbufferEXT(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 int glCheckFramebufferStatusEXT(int param1);
public void glDeleteFramebuffersEXT(IntBuffer param1); public void glDeleteFramebuffersEXT(IntBuffer param1);
public void glDeleteRenderbuffersEXT(IntBuffer param1); public void glDeleteRenderbuffersEXT(IntBuffer param1);
@ -92,5 +93,5 @@ public interface GLFbo {
public void glGenRenderbuffersEXT(IntBuffer param1); public void glGenRenderbuffersEXT(IntBuffer param1);
public void glGenerateMipmapEXT(int param1); public void glGenerateMipmapEXT(int param1);
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4); public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4);
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height);
} }

@ -89,9 +89,11 @@ public final class GLImageFormats {
GLImageFormat[][] formatToGL = new GLImageFormat[2][Image.Format.values().length]; GLImageFormat[][] formatToGL = new GLImageFormat[2][Image.Format.values().length];
if (caps.contains(Caps.OpenGL20)) { if (caps.contains(Caps.OpenGL20)) {
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); 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.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.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.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.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); 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.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.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.RGBA8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, 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.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.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.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.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); 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)) { } else if (caps.contains(Caps.Rgba8)) {
// A more limited form of 32-bit RGBA. Only GL_RGBA8 is available. // A more limited form of 32-bit RGBA. Only GL_RGBA8 is available.
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Alpha8, GLExt.GL_RGBA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); 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.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.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.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); format(formatToGL, Format.RGBA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
} else { } else {
// Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above.. // Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above..
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Alpha8, GL.GL_RGBA4, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); 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.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.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.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); 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); format(formatToGL, Format.RGB5A1, GL.GL_RGB5_A1, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1);
if (caps.contains(Caps.FloatTexture)) { if (caps.contains(Caps.FloatTexture)) {
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, GLExt.GL_HALF_FLOAT_ARB); 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.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.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.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.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); format(formatToGL, Format.RGBA16F, GLExt.GL_RGBA16F_ARB, GL.GL_RGBA, GLExt.GL_HALF_FLOAT_ARB);

@ -56,6 +56,7 @@ import com.jme3.util.BufferUtils;
import com.jme3.util.ListMap; import com.jme3.util.ListMap;
import com.jme3.util.NativeObjectManager; import com.jme3.util.NativeObjectManager;
import java.nio.*; import java.nio.*;
import java.util.Arrays;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashSet; import java.util.HashSet;
@ -112,13 +113,13 @@ public class GLRenderer implements Renderer {
private final GLFbo glfbo; private final GLFbo glfbo;
private final TextureUtil texUtil; private final TextureUtil texUtil;
public GLRenderer(GL gl, GLFbo glfbo) { public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) {
this.gl = gl; this.gl = gl;
this.gl2 = gl instanceof GL2 ? (GL2)gl : null; this.gl2 = gl instanceof GL2 ? (GL2)gl : null;
this.gl3 = gl instanceof GL3 ? (GL3)gl : null; this.gl3 = gl instanceof GL3 ? (GL3)gl : null;
this.gl4 = gl instanceof GL4 ? (GL4)gl : null; this.gl4 = gl instanceof GL4 ? (GL4)gl : null;
this.glfbo = glfbo; this.glfbo = glfbo;
this.glext = glfbo instanceof GLExt ? (GLExt)glfbo : null; this.glext = glext;
this.texUtil = new TextureUtil(gl, gl2, glext, context); this.texUtil = new TextureUtil(gl, gl2, glext, context);
} }
@ -137,11 +138,20 @@ public class GLRenderer implements Renderer {
return limits; return limits;
} }
private static HashSet<String> loadExtensions(String extensions) { private HashSet<String> loadExtensions() {
HashSet<String> extensionSet = new HashSet<String>(64); HashSet<String> extensionSet = new HashSet<String>(64);
for (String extension : extensions.split(" ")) { 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); extensionSet.add(extension);
} }
} else {
extensionSet.addAll(Arrays.asList(gl.glGetString(GL.GL_EXTENSIONS).split(" ")));
}
return extensionSet; return extensionSet;
} }
@ -185,10 +195,12 @@ public class GLRenderer implements Renderer {
caps.add(Caps.OpenGL31); caps.add(Caps.OpenGL31);
if (oglVer >= 320) { if (oglVer >= 320) {
caps.add(Caps.OpenGL32); caps.add(Caps.OpenGL32);
}if(oglVer>=330){ }
if (oglVer >= 330) {
caps.add(Caps.OpenGL33); caps.add(Caps.OpenGL33);
caps.add(Caps.GeometryShader); caps.add(Caps.GeometryShader);
}if(oglVer>=400){ }
if (oglVer >= 400) {
caps.add(Caps.OpenGL40); caps.add(Caps.OpenGL40);
caps.add(Caps.TesselationShader); caps.add(Caps.TesselationShader);
} }
@ -243,7 +255,7 @@ public class GLRenderer implements Renderer {
} }
private void loadCapabilitiesCommon() { private void loadCapabilitiesCommon() {
extensions = loadExtensions(gl.glGetString(GL.GL_EXTENSIONS)); extensions = loadExtensions();
limits.put(Limits.VertexTextureUnits, getInteger(GL.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS)); limits.put(Limits.VertexTextureUnits, getInteger(GL.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS));
if (limits.get(Limits.VertexTextureUnits) > 0) { if (limits.get(Limits.VertexTextureUnits) > 0) {
@ -279,7 +291,7 @@ public class GLRenderer implements Renderer {
// == texture format extensions == // == texture format extensions ==
boolean hasFloatTexture = false; boolean hasFloatTexture;
hasFloatTexture = hasExtension("GL_OES_texture_half_float") && hasFloatTexture = hasExtension("GL_OES_texture_half_float") &&
hasExtension("GL_OES_texture_float"); hasExtension("GL_OES_texture_float");
@ -375,11 +387,11 @@ public class GLRenderer implements Renderer {
caps.add(Caps.TextureFilterAnisotropic); caps.add(Caps.TextureFilterAnisotropic);
} }
if (hasExtension("GL_EXT_framebuffer_object")) { if (hasExtension("GL_EXT_framebuffer_object") || gl3 != null) {
caps.add(Caps.FrameBuffer); caps.add(Caps.FrameBuffer);
limits.put(Limits.RenderBufferSize, getInteger(GLExt.GL_MAX_RENDERBUFFER_SIZE_EXT)); limits.put(Limits.RenderBufferSize, getInteger(GLFbo.GL_MAX_RENDERBUFFER_SIZE_EXT));
limits.put(Limits.FrameBufferAttachments, getInteger(GLExt.GL_MAX_COLOR_ATTACHMENTS_EXT)); limits.put(Limits.FrameBufferAttachments, getInteger(GLFbo.GL_MAX_COLOR_ATTACHMENTS_EXT));
if (hasExtension("GL_EXT_framebuffer_blit")) { if (hasExtension("GL_EXT_framebuffer_blit")) {
caps.add(Caps.FrameBufferBlit); caps.add(Caps.FrameBufferBlit);
@ -434,21 +446,30 @@ public class GLRenderer implements Renderer {
caps.add(Caps.SeamlessCubemap); caps.add(Caps.SeamlessCubemap);
} }
// if (hasExtension("GL_ARB_get_program_binary")) { if (caps.contains(Caps.OpenGL32) && !hasExtension("GL_ARB_compatibility")) {
// int binaryFormats = getInteger(GLExt.GL_NUM_PROGRAM_BINARY_FORMATS); 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 // Print context information
logger.log(Level.INFO, "OpenGL Renderer Information\n" + logger.log(Level.INFO, "OpenGL Renderer Information\n" +
" * Vendor: {0}\n" + " * Vendor: {0}\n" +
" * Renderer: {1}\n" + " * Renderer: {1}\n" +
" * OpenGL Version: {2}\n" + " * OpenGL Version: {2}\n" +
" * GLSL Version: {3}", " * GLSL Version: {3}\n" +
" * Profile: {4}",
new Object[]{ new Object[]{
gl.glGetString(GL.GL_VENDOR), gl.glGetString(GL.GL_VENDOR),
gl.glGetString(GL.GL_RENDERER), gl.glGetString(GL.GL_RENDERER),
gl.glGetString(GL.GL_VERSION), 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) // Print capabilities (if fine logging is enabled)
@ -491,6 +512,20 @@ public class GLRenderer implements Renderer {
// Initialize default state.. // Initialize default state..
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); 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() { public void invalidateState() {
@ -610,31 +645,6 @@ public class GLRenderer implements Renderer {
context.colorWriteEnabled = false; 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 (state.isPolyOffset()) {
if (!context.polyOffsetEnabled) { if (!context.polyOffsetEnabled) {
gl.glEnable(GL.GL_POLYGON_OFFSET_FILL); gl.glEnable(GL.GL_POLYGON_OFFSET_FILL);
@ -704,9 +714,6 @@ public class GLRenderer implements Renderer {
case AlphaAdditive: case AlphaAdditive:
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
break; break;
case Color:
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR);
break;
case Alpha: case Alpha:
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
break; break;
@ -719,6 +726,7 @@ public class GLRenderer implements Renderer {
case ModulateX2: case ModulateX2:
gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR); gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR);
break; break;
case Color:
case Screen: case Screen:
gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR); gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR);
break; break;
@ -862,6 +870,7 @@ public class GLRenderer implements Renderer {
public void postFrame() { public void postFrame() {
objManager.deleteUnused(this); objManager.deleteUnused(this);
gl.resetStats();
} }
/*********************************************************************\ /*********************************************************************\
@ -1290,24 +1299,24 @@ public class GLRenderer implements Renderer {
} }
if (src == null) { if (src == null) {
glfbo.glBindFramebufferEXT(GLExt.GL_READ_FRAMEBUFFER_EXT, 0); glfbo.glBindFramebufferEXT(GLFbo.GL_READ_FRAMEBUFFER_EXT, 0);
srcX0 = vpX; srcX0 = vpX;
srcY0 = vpY; srcY0 = vpY;
srcX1 = vpX + vpW; srcX1 = vpX + vpW;
srcY1 = vpY + vpH; srcY1 = vpY + vpH;
} else { } else {
glfbo.glBindFramebufferEXT(GLExt.GL_READ_FRAMEBUFFER_EXT, src.getId()); glfbo.glBindFramebufferEXT(GLFbo.GL_READ_FRAMEBUFFER_EXT, src.getId());
srcX1 = src.getWidth(); srcX1 = src.getWidth();
srcY1 = src.getHeight(); srcY1 = src.getHeight();
} }
if (dst == null) { if (dst == null) {
glfbo.glBindFramebufferEXT(GLExt.GL_DRAW_FRAMEBUFFER_EXT, 0); glfbo.glBindFramebufferEXT(GLFbo.GL_DRAW_FRAMEBUFFER_EXT, 0);
dstX0 = vpX; dstX0 = vpX;
dstY0 = vpY; dstY0 = vpY;
dstX1 = vpX + vpW; dstX1 = vpX + vpW;
dstY1 = vpY + vpH; dstY1 = vpY + vpH;
} else { } else {
glfbo.glBindFramebufferEXT(GLExt.GL_DRAW_FRAMEBUFFER_EXT, dst.getId()); glfbo.glBindFramebufferEXT(GLFbo.GL_DRAW_FRAMEBUFFER_EXT, dst.getId());
dstX1 = dst.getWidth(); dstX1 = dst.getWidth();
dstY1 = dst.getHeight(); dstY1 = dst.getHeight();
} }
@ -1315,12 +1324,12 @@ public class GLRenderer implements Renderer {
if (copyDepth) { if (copyDepth) {
mask |= GL.GL_DEPTH_BUFFER_BIT; mask |= GL.GL_DEPTH_BUFFER_BIT;
} }
glext.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, glfbo.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1,
dstX0, dstY0, dstX1, dstY1, mask, dstX0, dstY0, dstX1, dstY1, mask,
GL.GL_NEAREST); GL.GL_NEAREST);
glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, prevFBO); glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, prevFBO);
} else { } else {
throw new RendererException("Framebuffer blitting not supported by the video hardware"); throw new RendererException("Framebuffer blitting not supported by the video hardware");
} }
@ -1366,7 +1375,7 @@ public class GLRenderer implements Renderer {
} }
if (context.boundRB != id) { if (context.boundRB != id) {
glfbo.glBindRenderbufferEXT(GLExt.GL_RENDERBUFFER_EXT, id); glfbo.glBindRenderbufferEXT(GLFbo.GL_RENDERBUFFER_EXT, id);
context.boundRB = id; context.boundRB = id;
} }
@ -1384,13 +1393,13 @@ public class GLRenderer implements Renderer {
if (maxSamples < samples) { if (maxSamples < samples) {
samples = maxSamples; samples = maxSamples;
} }
glext.glRenderbufferStorageMultisampleEXT(GLExt.GL_RENDERBUFFER_EXT, glfbo.glRenderbufferStorageMultisampleEXT(GLFbo.GL_RENDERBUFFER_EXT,
samples, samples,
glFmt.internalFormat, glFmt.internalFormat,
fb.getWidth(), fb.getWidth(),
fb.getHeight()); fb.getHeight());
} else { } else {
glfbo.glRenderbufferStorageEXT(GLExt.GL_RENDERBUFFER_EXT, glfbo.glRenderbufferStorageEXT(GLFbo.GL_RENDERBUFFER_EXT,
glFmt.internalFormat, glFmt.internalFormat,
fb.getWidth(), fb.getWidth(),
fb.getHeight()); fb.getHeight());
@ -1400,7 +1409,7 @@ public class GLRenderer implements Renderer {
private int convertAttachmentSlot(int attachmentSlot) { private int convertAttachmentSlot(int attachmentSlot) {
// can also add support for stencil here // can also add support for stencil here
if (attachmentSlot == FrameBuffer.SLOT_DEPTH) { if (attachmentSlot == FrameBuffer.SLOT_DEPTH) {
return GLExt.GL_DEPTH_ATTACHMENT_EXT; return GLFbo.GL_DEPTH_ATTACHMENT_EXT;
} else if (attachmentSlot == FrameBuffer.SLOT_DEPTH_STENCIL) { } else if (attachmentSlot == FrameBuffer.SLOT_DEPTH_STENCIL) {
// NOTE: Using depth stencil format requires GL3, this is already // NOTE: Using depth stencil format requires GL3, this is already
// checked via render caps. // checked via render caps.
@ -1409,7 +1418,7 @@ public class GLRenderer implements Renderer {
throw new UnsupportedOperationException("Invalid FBO attachment slot: " + attachmentSlot); 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) { public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) {
@ -1427,7 +1436,7 @@ public class GLRenderer implements Renderer {
setupTextureParams(tex); setupTextureParams(tex);
} }
glfbo.glFramebufferTexture2DEXT(GLExt.GL_FRAMEBUFFER_EXT, glfbo.glFramebufferTexture2DEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()), convertAttachmentSlot(rb.getSlot()),
convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()), convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()),
image.getId(), image.getId(),
@ -1445,9 +1454,9 @@ public class GLRenderer implements Renderer {
updateRenderTexture(fb, rb); updateRenderTexture(fb, rb);
} }
if (needAttach) { if (needAttach) {
glfbo.glFramebufferRenderbufferEXT(GLExt.GL_FRAMEBUFFER_EXT, glfbo.glFramebufferRenderbufferEXT(GLFbo.GL_FRAMEBUFFER_EXT,
convertAttachmentSlot(rb.getSlot()), convertAttachmentSlot(rb.getSlot()),
GLExt.GL_RENDERBUFFER_EXT, GLFbo.GL_RENDERBUFFER_EXT,
rb.getId()); rb.getId());
} }
} }
@ -1465,7 +1474,7 @@ public class GLRenderer implements Renderer {
} }
if (context.boundFBO != id) { 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 // binding an FBO automatically sets draw buf to GL_COLOR_ATTACHMENT0
context.boundDrawBuf = 0; context.boundDrawBuf = 0;
context.boundFBO = id; context.boundFBO = id;
@ -1545,7 +1554,7 @@ public class GLRenderer implements Renderer {
if (fb == null) { if (fb == null) {
// unbind any fbos // unbind any fbos
if (context.boundFBO != 0) { if (context.boundFBO != 0) {
glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, 0); glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, 0);
statistics.onFrameBufferUse(null, true); statistics.onFrameBufferUse(null, true);
context.boundFBO = 0; context.boundFBO = 0;
@ -1577,7 +1586,7 @@ public class GLRenderer implements Renderer {
setViewPort(0, 0, fb.getWidth(), fb.getHeight()); setViewPort(0, 0, fb.getWidth(), fb.getHeight());
if (context.boundFBO != fb.getId()) { 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); statistics.onFrameBufferUse(fb, true);
context.boundFBO = fb.getId(); context.boundFBO = fb.getId();
@ -1617,7 +1626,7 @@ public class GLRenderer implements Renderer {
if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()) { if (context.boundDrawBuf != 100 + fb.getNumColorBuffers()) {
intBuf16.clear(); intBuf16.clear();
for (int i = 0; i < fb.getNumColorBuffers(); i++) { 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(); intBuf16.flip();
@ -1629,7 +1638,7 @@ public class GLRenderer implements Renderer {
// select this draw buffer // select this draw buffer
if (gl2 != null) { if (gl2 != null) {
if (context.boundDrawBuf != rb.getSlot()) { 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(); context.boundDrawBuf = rb.getSlot();
} }
} }
@ -1658,7 +1667,7 @@ public class GLRenderer implements Renderer {
setFrameBuffer(fb); setFrameBuffer(fb);
if (gl2 != null) { if (gl2 != null) {
if (context.boundReadBuf != rb.getSlot()) { 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(); context.boundReadBuf = rb.getSlot();
} }
} }
@ -1682,7 +1691,7 @@ public class GLRenderer implements Renderer {
public void deleteFrameBuffer(FrameBuffer fb) { public void deleteFrameBuffer(FrameBuffer fb) {
if (fb.getId() != -1) { if (fb.getId() != -1) {
if (context.boundFBO == fb.getId()) { if (context.boundFBO == fb.getId()) {
glfbo.glBindFramebufferEXT(GLExt.GL_FRAMEBUFFER_EXT, 0); glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, 0);
context.boundFBO = 0; context.boundFBO = 0;
} }
@ -2620,32 +2629,13 @@ public class GLRenderer implements Renderer {
return; 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()) { if (context.lineWidth != mesh.getLineWidth()) {
gl.glLineWidth(mesh.getLineWidth()); gl.glLineWidth(mesh.getLineWidth());
context.lineWidth = 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()); gl4.glPatchParameter(mesh.getPatchVertexCount());
} }
statistics.onMeshDrawn(mesh, lod, count); statistics.onMeshDrawn(mesh, lod, count);

@ -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<Map.Entry<String, Long>> {
@Override
public int compare(Map.Entry<String, Long> o1, Map.Entry<String, Long> 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<String, Long>[] callTimes = new Map.Entry[state.callTiming.size()];
int i = 0;
for (Map.Entry<String, Long> callTime : state.callTiming.entrySet()) {
callTimes[i++] = callTime;
}
Arrays.sort(callTimes, new CallTimingComparator());
int limit = 10;
for (Map.Entry<String, Long> 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<String, Long> 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;
}
}
}

@ -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<String, Long> callTiming = new HashMap<String, Long>();
}

@ -64,6 +64,7 @@ public final class GLTracer implements InvocationHandler {
noEnumArgs("glScissor", 0, 1, 2, 3); noEnumArgs("glScissor", 0, 1, 2, 3);
noEnumArgs("glClear", 0); noEnumArgs("glClear", 0);
noEnumArgs("glGetInteger", 1); noEnumArgs("glGetInteger", 1);
noEnumArgs("glGetString", 1);
noEnumArgs("glBindTexture", 1); noEnumArgs("glBindTexture", 1);
noEnumArgs("glPixelStorei", 1); noEnumArgs("glPixelStorei", 1);
@ -95,8 +96,6 @@ public final class GLTracer implements InvocationHandler {
noEnumArgs("glFramebufferTexture2DEXT", 3, 4); noEnumArgs("glFramebufferTexture2DEXT", 3, 4);
noEnumArgs("glBlitFramebufferEXT", 0, 1, 2, 3, 4, 5, 6, 7, 8); noEnumArgs("glBlitFramebufferEXT", 0, 1, 2, 3, 4, 5, 6, 7, 8);
noEnumArgs("glCreateProgram", -1); noEnumArgs("glCreateProgram", -1);
noEnumArgs("glCreateShader", -1); noEnumArgs("glCreateShader", -1);
noEnumArgs("glShaderSource", 0); noEnumArgs("glShaderSource", 0);
@ -155,7 +154,7 @@ public final class GLTracer implements InvocationHandler {
* @return A tracer that implements the given interface * @return A tracer that implements the given interface
*/ */
public static Object createGlesTracer(Object glInterface, Class<?> glInterfaceClass) { public static Object createGlesTracer(Object glInterface, Class<?> glInterfaceClass) {
IntMap<String> constMap = generateConstantMap(GL.class, GLExt.class); IntMap<String> constMap = generateConstantMap(GL.class, GLFbo.class, GLExt.class);
return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(), return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(),
new Class<?>[] { glInterfaceClass }, new Class<?>[] { glInterfaceClass },
new GLTracer(glInterface, constMap)); new GLTracer(glInterface, constMap));
@ -169,7 +168,7 @@ public final class GLTracer implements InvocationHandler {
* @return A tracer that implements the given interface * @return A tracer that implements the given interface
*/ */
public static Object createDesktopGlTracer(Object glInterface, Class<?> ... glInterfaceClasses) { public static Object createDesktopGlTracer(Object glInterface, Class<?> ... glInterfaceClasses) {
IntMap<String> constMap = generateConstantMap(GL2.class, GLExt.class); IntMap<String> constMap = generateConstantMap(GL2.class, GL3.class, GL4.class, GLFbo.class, GLExt.class);
return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(), return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(),
glInterfaceClasses, glInterfaceClasses,
new GLTracer(glInterface, constMap)); new GLTracer(glInterface, constMap));

@ -127,6 +127,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator {
unIndent(); unIndent();
startCondition(shaderNode.getCondition(), source); startCondition(shaderNode.getCondition(), source);
source.append(nodeSource); source.append(nodeSource);
source.append("\n");
endCondition(shaderNode.getCondition(), source); endCondition(shaderNode.getCondition(), source);
indent(); indent();
} }

@ -421,7 +421,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
/** /**
* @return True if the image needs to have mipmaps generated * @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() { public boolean isGeneratedMipmapsRequired() {
return needGeneratedMips; return needGeneratedMips;
@ -434,8 +435,9 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
@Override @Override
public void setUpdateNeeded() { public void setUpdateNeeded() {
super.setUpdateNeeded(); super.setUpdateNeeded();
if (!isGeneratedMipmapsRequired() && !hasMipmaps()) { if (isGeneratedMipmapsRequired() && !hasMipmaps()) {
setNeedGeneratedMipmaps(); // Mipmaps are no longer valid, since the image was changed.
setMipmapsGenerated(false);
} }
} }
@ -527,12 +529,14 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
this(); this();
if (mipMapSizes != null && mipMapSizes.length <= 1) { if (mipMapSizes != null) {
if (mipMapSizes.length <= 1) {
mipMapSizes = null; mipMapSizes = null;
} else { } else {
needGeneratedMips = false; needGeneratedMips = false;
mipsWereGenerated = true; mipsWereGenerated = true;
} }
}
setFormat(format); setFormat(format);
this.width = width; this.width = width;
@ -787,8 +791,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
needGeneratedMips = false; needGeneratedMips = false;
mipsWereGenerated = false; mipsWereGenerated = false;
} else { } else {
needGeneratedMips = false; needGeneratedMips = true;
mipsWereGenerated = true; mipsWereGenerated = false;
} }
setUpdateNeeded(); setUpdateNeeded();

@ -72,7 +72,8 @@ uniform float m_Shininess;
#endif #endif
void main(){ void main(){
#ifdef NORMALMAP #if !defined(VERTEX_LIGHTING)
#if defined(NORMALMAP)
mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz)); mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz));
if (!gl_FrontFacing) if (!gl_FrontFacing)
@ -84,6 +85,7 @@ void main(){
#else #else
vec3 viewDir = normalize(-vPos.xyz); vec3 viewDir = normalize(-vPos.xyz);
#endif #endif
#endif
vec2 newTexCoord; vec2 newTexCoord;

@ -45,6 +45,9 @@ attribute vec3 inNormal;
#else #else
varying vec3 specularAccum; varying vec3 specularAccum;
varying vec4 diffuseAccum; varying vec4 diffuseAccum;
#ifdef COLORRAMP
uniform sampler2D m_ColorRamp;
#endif
#endif #endif
#ifdef USE_REFLECTION #ifdef USE_REFLECTION
@ -160,14 +163,14 @@ void main(){
#if __VERSION__ >= 110 #if __VERSION__ >= 110
} }
#endif #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 #ifdef COLORRAMP
diffuseAccum += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor; diffuseAccum.rgb += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor.rgb;
specularAccum += texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb * specularColor; specularAccum.rgb += texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb * specularColor;
#else #else
diffuseAccum += v.x * diffuseColor; diffuseAccum.rgb += light.x * diffuseColor.rgb;
specularAccum += v.y * specularColor; specularAccum.rgb += light.y * specularColor;
#endif #endif
} }
#endif #endif

@ -56,3 +56,10 @@ Controller\ (XBOX\ 360\ For\ Windows).ry=rz
# requires custom code to support trigger buttons but this # requires custom code to support trigger buttons but this
# keeps it from confusing the .rx mapping. # keeps it from confusing the .rx mapping.
Controller\ (XBOX\ 360\ For\ Windows).z=trigger 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

@ -573,6 +573,11 @@ public class J3MLoader implements AssetLoader {
InputStream in = info.openStream(); InputStream in = info.openStream();
try { 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)); loadFromRoot(BlockLanguageParser.parse(in));
} finally { } finally {
if (in != null){ if (in != null){
@ -581,9 +586,6 @@ public class J3MLoader implements AssetLoader {
} }
if (material != null){ if (material != null){
if (!(info.getKey() instanceof MaterialKey)){
throw new IOException("Material instances must be loaded via MaterialKey");
}
// material implementation // material implementation
return material; return material;
}else{ }else{

@ -242,21 +242,12 @@ public class DXTFlipper {
img.position(blockByteOffset); img.position(blockByteOffset);
img.limit(blockByteOffset + bpb); 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){ if (alphaBlock != null){
img.get(alphaBlock); img.get(alphaBlock);
switch (type){ switch (type){
case 2: case 2:
flipDXT3Block(alphaBlock, h); break; flipDXT3Block(alphaBlock, h);
break;
case 3: case 3:
case 4: case 4:
flipDXT5Block(alphaBlock, h); flipDXT5Block(alphaBlock, h);
@ -264,6 +255,16 @@ public class DXTFlipper {
} }
retImg.put(alphaBlock); 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(); retImg.rewind();
}else if (h >= 4){ }else if (h >= 4){

@ -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.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth, 1, ssaoMat);
ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear); // ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear);
ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear); // ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);
postRenderPasses.add(ssaoPass); postRenderPasses.add(ssaoPass);
material = new Material(manager, "Common/MatDefs/SSAO/ssaoBlur.j3md"); material = new Material(manager, "Common/MatDefs/SSAO/ssaoBlur.j3md");
material.setTexture("SSAOMap", ssaoPass.getRenderedTexture()); material.setTexture("SSAOMap", ssaoPass.getRenderedTexture());

@ -413,10 +413,6 @@ void main(){
// to calculate the derivatives for all these pixels by using step()! // to calculate the derivatives for all these pixels by using step()!
// That way we won't get pixels around the edges of the terrain, // That way we won't get pixels around the edges of the terrain,
// Where the derivatives are undefined // Where the derivatives are undefined
if(position.y > level){ gl_FragColor = vec4(mix(color, color2, step(level, position.y)), 1.0);
color = color2;
}
gl_FragColor = vec4(color,1.0);
} }

@ -77,6 +77,7 @@ MaterialDef Advanced Water {
FragmentShader GLSL120 : Common/MatDefs/Water/Water.frag FragmentShader GLSL120 : Common/MatDefs/Water/Water.frag
WorldParameters { WorldParameters {
ViewProjectionMatrixInverse
} }
Defines { Defines {
ENABLE_RIPPLES : UseRipples ENABLE_RIPPLES : UseRipples

@ -2,3 +2,7 @@ INCLUDE com/jme3/asset/General.cfg
# IOS specific loaders # IOS specific loaders
LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg 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

File diff suppressed because it is too large Load Diff

@ -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);
}
}

@ -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);
}

@ -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();
}

@ -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);
}

@ -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;
/**
* <code>AndroidAudioLoader</code> 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;
}
}

@ -34,6 +34,7 @@ package com.jme3.renderer.ios;
import com.jme3.renderer.RendererException; import com.jme3.renderer.RendererException;
import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLExt; import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import java.nio.Buffer; import java.nio.Buffer;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -46,10 +47,13 @@ import java.nio.ShortBuffer;
* *
* @author Kirill Vainer * @author Kirill Vainer
*/ */
public class IosGL implements GL, GLExt { public class IosGL implements GL, GLExt, GLFbo {
private final int[] temp_array = new int[16]; private final int[] temp_array = new int[16];
public void resetStats() {
}
private static int getLimitBytes(ByteBuffer buffer) { private static int getLimitBytes(ByteBuffer buffer) {
checkLimit(buffer); checkLimit(buffer);
return buffer.limit(); return buffer.limit();
@ -90,7 +94,9 @@ public class IosGL implements GL, GLExt {
if (buffer.remaining() < n) { if (buffer.remaining() < n) {
throw new BufferOverflowException(); throw new BufferOverflowException();
} }
int pos = buffer.position();
buffer.put(array, 0, n); buffer.put(array, 0, n);
buffer.position(pos);
} }
private static void checkLimit(Buffer buffer) { private static void checkLimit(Buffer buffer) {

@ -40,6 +40,7 @@ import com.jme3.renderer.ios.IosGL;
import com.jme3.renderer.opengl.GL; import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLDebugES; import com.jme3.renderer.opengl.GLDebugES;
import com.jme3.renderer.opengl.GLExt; import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer; import com.jme3.renderer.opengl.GLRenderer;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
@ -158,11 +159,11 @@ public class IGLESContext implements JmeContext {
GLExt glext = (GLExt) gl; GLExt glext = (GLExt) gl;
// if (settings.getBoolean("GraphicsDebug")) { // if (settings.getBoolean("GraphicsDebug")) {
gl = new GLDebugES(gl, glext); gl = new GLDebugES(gl, glext, (GLFbo) glext);
glext = (GLExt) gl; glext = (GLExt) gl;
// } // }
renderer = new GLRenderer(gl, glext); renderer = new GLRenderer(gl, glext, (GLFbo) glext);
renderer.initialize(); renderer.initialize();
input = new IosInputHandler(); input = new IosInputHandler();

@ -33,6 +33,7 @@ package com.jme3.system.ios;
import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetLoader; import com.jme3.asset.AssetLoader;
import com.jme3.asset.TextureKey;
import com.jme3.texture.Image; import com.jme3.texture.Image;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import java.io.IOException; import java.io.IOException;
@ -45,15 +46,17 @@ import java.io.InputStream;
public class IosImageLoader implements AssetLoader { public class IosImageLoader implements AssetLoader {
public Object load(AssetInfo info) throws IOException { public Object load(AssetInfo info) throws IOException {
InputStream in = info.openStream(); boolean flip = ((TextureKey) info.getKey()).isFlipY();
Image img = null; Image img = null;
InputStream in = null;
try { try {
img = loadImageData(Image.Format.RGBA8, in); in = info.openStream();
} catch (Exception e) { img = loadImageData(Format.RGBA8, flip, in);
e.printStackTrace();
} finally { } finally {
if (in != null) {
in.close(); in.close();
} }
}
return img; return img;
} }
@ -64,5 +67,5 @@ public class IosImageLoader implements AssetLoader {
* @param inputStream the InputStream to load the image data from * @param inputStream the InputStream to load the image data from
* @return the loaded Image * @return the loaded Image
*/ */
private static native Image loadImageData(Format format, InputStream inputStream); private static native Image loadImageData(Format format, boolean flipY, InputStream inputStream);
} }

@ -36,6 +36,14 @@ import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext; import com.jme3.system.JmeContext;
import com.jme3.system.JmeSystemDelegate; import com.jme3.system.JmeSystemDelegate;
import com.jme3.system.NullContext; 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.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URL; import java.net.URL;
@ -89,7 +97,10 @@ public class JmeIosSystem extends JmeSystemDelegate {
@Override @Override
public AudioRenderer newAudioRenderer(AppSettings settings) { 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 @Override

@ -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

@ -13,7 +13,7 @@ import java.nio.ShortBuffer;
import com.jme3.renderer.opengl.GL4; import com.jme3.renderer.opengl.GL4;
import org.lwjgl.opengl.*; 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) { private static void checkLimit(Buffer buffer) {
if (buffer == null) { if (buffer == null) {
@ -27,6 +27,9 @@ public class LwjglGL implements GL, GL2, GL3,GL4 {
} }
} }
public void resetStats() {
}
public void glActiveTexture(int param1) { public void glActiveTexture(int param1) {
GL13.glActiveTexture(param1); GL13.glActiveTexture(param1);
} }
@ -238,6 +241,10 @@ public class LwjglGL implements GL, GL2, GL3,GL4 {
return GL11.glGetString(param1); return GL11.glGetString(param1);
} }
public String glGetString(int param1, int param2) {
return GL30.glGetStringi(param1, param2);
}
public boolean glIsEnabled(int param1) { public boolean glIsEnabled(int param1) {
return GL11.glIsEnabled(param1); return GL11.glIsEnabled(param1);
} }
@ -444,4 +451,10 @@ public class LwjglGL implements GL, GL2, GL3,GL4 {
public void glPatchParameter(int count) { public void glPatchParameter(int count) {
GL40.glPatchParameteri(GL40.GL_PATCH_VERTICES,count); GL40.glPatchParameteri(GL40.GL_PATCH_VERTICES,count);
} }
@Override
public void glDeleteVertexArrays(IntBuffer arrays) {
checkLimit(arrays);
ARBVertexArrayObject.glDeleteVertexArrays(arrays);
}
} }

@ -7,12 +7,8 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import org.lwjgl.opengl.ARBDrawInstanced; import org.lwjgl.opengl.ARBDrawInstanced;
import org.lwjgl.opengl.ARBInstancedArrays; import org.lwjgl.opengl.ARBInstancedArrays;
import org.lwjgl.opengl.ARBPixelBufferObject;
import org.lwjgl.opengl.ARBSync; import org.lwjgl.opengl.ARBSync;
import org.lwjgl.opengl.ARBTextureMultisample; 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.GL15;
import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GLSync; import org.lwjgl.opengl.GLSync;
@ -31,98 +27,50 @@ public class LwjglGLExt implements GLExt {
} }
} }
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { @Override
EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
public void glBufferData(int target, IntBuffer data, int usage) { public void glBufferData(int target, IntBuffer data, int usage) {
checkLimit(data); checkLimit(data);
GL15.glBufferData(target, data, usage); GL15.glBufferData(target, data, usage);
} }
@Override
public void glBufferSubData(int target, long offset, IntBuffer data) { public void glBufferSubData(int target, long offset, IntBuffer data) {
checkLimit(data); checkLimit(data);
GL15.glBufferSubData(target, offset, data); GL15.glBufferSubData(target, offset, data);
} }
@Override
public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) { public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) {
ARBDrawInstanced.glDrawArraysInstancedARB(mode, first, count, primcount); ARBDrawInstanced.glDrawArraysInstancedARB(mode, first, count, primcount);
} }
@Override
public void glDrawBuffers(IntBuffer bufs) { public void glDrawBuffers(IntBuffer bufs) {
checkLimit(bufs); checkLimit(bufs);
GL20.glDrawBuffers(bufs); GL20.glDrawBuffers(bufs);
} }
@Override
public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) { 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); ARBDrawInstanced.glDrawElementsInstancedARB(mode, indices_count, type, indices_buffer_offset, primcount);
} }
@Override
public void glGetMultisample(int pname, int index, FloatBuffer val) { public void glGetMultisample(int pname, int index, FloatBuffer val) {
checkLimit(val); checkLimit(val);
ARBTextureMultisample.glGetMultisample(pname, index, val); ARBTextureMultisample.glGetMultisample(pname, index, val);
} }
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { @Override
EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
}
public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) { public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) {
ARBTextureMultisample.glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations); ARBTextureMultisample.glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations);
} }
@Override
public void glVertexAttribDivisorARB(int index, int divisor) { public void glVertexAttribDivisorARB(int index, int divisor) {
ARBInstancedArrays.glVertexAttribDivisorARB(index, 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 @Override
public Object glFenceSync(int condition, int flags) { public Object glFenceSync(int condition, int flags) {
return ARBSync.glFenceSync(condition, flags); return ARBSync.glFenceSync(condition, flags);

@ -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);
}
}

@ -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);
}
}

@ -39,13 +39,18 @@ import com.jme3.renderer.Renderer;
import com.jme3.renderer.RendererException; import com.jme3.renderer.RendererException;
import com.jme3.renderer.lwjgl.LwjglGL; import com.jme3.renderer.lwjgl.LwjglGL;
import com.jme3.renderer.lwjgl.LwjglGLExt; 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.GL;
import com.jme3.renderer.opengl.GL2; import com.jme3.renderer.opengl.GL2;
import com.jme3.renderer.opengl.GL3; import com.jme3.renderer.opengl.GL3;
import com.jme3.renderer.opengl.GL4;
import com.jme3.renderer.opengl.GLDebugDesktop; import com.jme3.renderer.opengl.GLDebugDesktop;
import com.jme3.renderer.opengl.GLExt; import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo; import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer; 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.renderer.opengl.GLTracer;
import com.jme3.system.AppSettings; import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext; import com.jme3.system.JmeContext;
@ -205,26 +210,42 @@ public abstract class LwjglContext implements JmeContext {
if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2) if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2)
|| settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) { || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
GL gl = new LwjglGL(); 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")) { if (settings.getBoolean("GraphicsDebug")) {
gl = new GLDebugDesktop(gl, glfbo); gl = new GLDebugDesktop(gl, glext, glfbo);
glext = (GLExt) gl;
glfbo = (GLFbo) 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")) { if (settings.getBoolean("GraphicsTrace")) {
gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class); gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class);
glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLExt.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(); renderer.initialize();
} else { } else {
throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer());
} }
if (GLContext.getCapabilities().GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) { if (GLContext.getCapabilities().GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) {
ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback()); ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback(new LwjglGLDebugOutputHandler()));
} }
renderer.setMainFrameBufferSrgb(settings.getGammaCorrection()); renderer.setMainFrameBufferSrgb(settings.getGammaCorrection());

@ -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<Integer, String> constMap = new HashMap<Integer, String>();
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));
}
}

@ -31,6 +31,8 @@
*/ */
package com.jme3.network; package com.jme3.network;
import com.jme3.network.service.ClientServiceManager;
/** /**
* Represents a remote connection to a server that can be used * Represents a remote connection to a server that can be used
@ -73,6 +75,12 @@ public interface Client extends MessageConnection
*/ */
public int getVersion(); 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. * Sends a message to the server.
*/ */

@ -33,6 +33,8 @@ package com.jme3.network;
import java.util.Collection; import java.util.Collection;
import com.jme3.network.service.HostedServiceManager;
/** /**
* Represents a host that can send and receive messages to * Represents a host that can send and receive messages to
* a set of remote client connections. * a set of remote client connections.
@ -54,6 +56,12 @@ public interface Server
*/ */
public int getVersion(); 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. * Sends the specified message to all connected clients.
*/ */

@ -37,6 +37,8 @@ import com.jme3.network.kernel.Connector;
import com.jme3.network.message.ChannelInfoMessage; import com.jme3.network.message.ChannelInfoMessage;
import com.jme3.network.message.ClientRegistrationMessage; import com.jme3.network.message.ClientRegistrationMessage;
import com.jme3.network.message.DisconnectMessage; 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.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
@ -54,7 +56,7 @@ import java.util.logging.Logger;
*/ */
public class DefaultClient implements Client 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 // First two channels are reserved for reliable and
// unreliable. Note: channels are endpoint specific so these // unreliable. Note: channels are endpoint specific so these
@ -80,10 +82,14 @@ public class DefaultClient implements Client
private ConnectorFactory connectorFactory; private ConnectorFactory connectorFactory;
private ClientServiceManager services;
public DefaultClient( String gameName, int version ) public DefaultClient( String gameName, int version )
{ {
this.gameName = gameName; this.gameName = gameName;
this.version = version; this.version = version;
this.services = new ClientServiceManager(this);
addStandardServices();
} }
public DefaultClient( String gameName, int version, Connector reliable, Connector fast, public DefaultClient( String gameName, int version, Connector reliable, Connector fast,
@ -93,6 +99,10 @@ public class DefaultClient implements Client
setPrimaryConnectors( reliable, fast, connectorFactory ); setPrimaryConnectors( reliable, fast, connectorFactory );
} }
protected void addStandardServices() {
services.addService(new ClientSerializerRegistrationsService());
}
protected void setPrimaryConnectors( Connector reliable, Connector fast, ConnectorFactory connectorFactory ) protected void setPrimaryConnectors( Connector reliable, Connector fast, ConnectorFactory connectorFactory )
{ {
if( reliable == null ) if( reliable == null )
@ -201,6 +211,11 @@ public class DefaultClient implements Client
return version; return version;
} }
public ClientServiceManager getServices()
{
return services;
}
public void send( Message message ) public void send( Message message )
{ {
if( message.isReliable() || channels.get(CH_UNRELIABLE) == null ) { if( message.isReliable() || channels.get(CH_UNRELIABLE) == null ) {
@ -268,6 +283,10 @@ public class DefaultClient implements Client
if( !isRunning ) if( !isRunning )
return; return;
// Let the services get a chance to stop before we
// kill the connection.
services.stop();
// Send a close message // Send a close message
// Tell the thread it's ok to die // Tell the thread it's ok to die
@ -285,6 +304,9 @@ public class DefaultClient implements Client
fireDisconnected(info); fireDisconnected(info);
isRunning = false; isRunning = false;
// Terminate the services
services.terminate();
} }
public void addClientStateListener( ClientStateListener listener ) public void addClientStateListener( ClientStateListener listener )
@ -329,6 +351,9 @@ public class DefaultClient implements Client
protected void fireConnected() protected void fireConnected()
{ {
// Let the services know we are finally started
services.start();
for( ClientStateListener l : stateListeners ) { for( ClientStateListener l : stateListeners ) {
l.clientConnected( this ); l.clientConnected( this );
} }

@ -37,6 +37,8 @@ import com.jme3.network.kernel.Kernel;
import com.jme3.network.message.ChannelInfoMessage; import com.jme3.network.message.ChannelInfoMessage;
import com.jme3.network.message.ClientRegistrationMessage; import com.jme3.network.message.ClientRegistrationMessage;
import com.jme3.network.message.DisconnectMessage; 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.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
@ -55,7 +57,7 @@ import java.util.logging.Logger;
*/ */
public class DefaultServer implements Server 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 // First two channels are reserved for reliable and
// unreliable // unreliable
@ -85,6 +87,8 @@ public class DefaultServer implements Server
= new MessageListenerRegistry<HostedConnection>(); = new MessageListenerRegistry<HostedConnection>();
private List<ConnectionListener> connectionListeners = new CopyOnWriteArrayList<ConnectionListener>(); private List<ConnectionListener> connectionListeners = new CopyOnWriteArrayList<ConnectionListener>();
private HostedServiceManager services;
public DefaultServer( String gameName, int version, Kernel reliable, Kernel fast ) public DefaultServer( String gameName, int version, Kernel reliable, Kernel fast )
{ {
if( reliable == null ) if( reliable == null )
@ -92,6 +96,8 @@ public class DefaultServer implements Server
this.gameName = gameName; this.gameName = gameName;
this.version = version; this.version = version;
this.services = new HostedServiceManager(this);
addStandardServices();
reliableAdapter = new KernelAdapter( this, reliable, dispatcher, true ); reliableAdapter = new KernelAdapter( this, reliable, dispatcher, true );
channels.add( reliableAdapter ); channels.add( reliableAdapter );
@ -101,6 +107,10 @@ public class DefaultServer implements Server
} }
} }
protected void addStandardServices() {
services.addService(new ServerSerializerRegistrationsService());
}
public String getGameName() public String getGameName()
{ {
return gameName; return gameName;
@ -111,6 +121,11 @@ public class DefaultServer implements Server
return version; return version;
} }
public HostedServiceManager getServices()
{
return services;
}
public int addChannel( int port ) public int addChannel( int port )
{ {
if( isRunning ) if( isRunning )
@ -165,6 +180,9 @@ public class DefaultServer implements Server
} }
isRunning = true; isRunning = true;
// Start the services
services.start();
} }
public boolean isRunning() public boolean isRunning()
@ -177,6 +195,10 @@ public class DefaultServer implements Server
if( !isRunning ) if( !isRunning )
throw new IllegalStateException( "Server is not started." ); 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 { try {
// Kill the adpaters, they will kill the kernels // Kill the adpaters, they will kill the kernels
for( KernelAdapter ka : channels ) { for( KernelAdapter ka : channels ) {
@ -184,6 +206,9 @@ public class DefaultServer implements Server
} }
isRunning = false; isRunning = false;
// Now terminate all of the services
services.terminate();
} catch( InterruptedException e ) { } catch( InterruptedException e ) {
throw new RuntimeException( "Interrupted while closing", e ); throw new RuntimeException( "Interrupted while closing", e );
} }
@ -396,6 +421,18 @@ public class DefaultServer implements Server
return endpointConnections.get(endpoint); return endpointConnections.get(endpoint);
} }
protected void removeConnecting( Endpoint p )
{
// No easy lookup for connecting Connections
// from endpoint.
for( Map.Entry<Long,Connection> e : connecting.entrySet() ) {
if( e.getValue().hasEndpoint(p) ) {
connecting.remove(e.getKey());
return;
}
}
}
protected void connectionClosed( Endpoint p ) protected void connectionClosed( Endpoint p )
{ {
if( p.isConnected() ) { if( p.isConnected() ) {
@ -411,10 +448,10 @@ public class DefaultServer implements Server
// Also note: this method will be called multiple times per // Also note: this method will be called multiple times per
// HostedConnection if it has multiple endpoints. // HostedConnection if it has multiple endpoints.
Connection removed = null; Connection removed;
synchronized( this ) { synchronized( this ) {
// Just in case the endpoint was still connecting // Just in case the endpoint was still connecting
connecting.values().remove(p); removeConnecting(p);
// And the regular management // And the regular management
removed = (Connection)endpointConnections.remove(p); removed = (Connection)endpointConnections.remove(p);
@ -453,6 +490,16 @@ public class DefaultServer implements Server
channels = new Endpoint[channelCount]; 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 ) void setChannel( int channel, Endpoint p )
{ {
if( channels[channel] != null && channels[channel] != p ) { if( channels[channel] != null && channels[channel] != p ) {
@ -557,6 +604,7 @@ public class DefaultServer implements Server
return Collections.unmodifiableSet(sessionData.keySet()); return Collections.unmodifiableSet(sessionData.keySet());
} }
@Override
public String toString() public String toString()
{ {
return "Connection[ id=" + id + ", reliable=" + channels[CH_RELIABLE] return "Connection[ id=" + id + ", reliable=" + channels[CH_RELIABLE]

@ -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.
*
* <p>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.</p>
*
* @author Paul Speed
*/
@Serializable
public class SerializerRegistrationsMessage extends AbstractMessage {
static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName());
public static final Set<Class> ignore = new HashSet<Class>();
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<Registration> list = new ArrayList<Registration>();
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 + "]";
}
}
}

@ -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<ClientServiceManager>
implements ClientService {
protected AbstractClientService() {
}
}

@ -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<HostedServiceManager>
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) {
}
}

@ -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<S extends ServiceManager> implements Service<S> {
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 extends Service<S>> T getService( Class<T> 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 + "]";
}
}

@ -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<ClientServiceManager> {
/**
* 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 );
}

@ -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<ClientServiceManager> {
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);
}
}

@ -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<HostedServiceManager>, 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 );
}

@ -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<HostedServiceManager> {
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);
}
}
}

@ -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<S> {
/**
* 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 );
}

@ -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<T> {
private List<Service<T>> services = new CopyOnWriteArrayList<Service<T>>();
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<Service<T>> 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<T> 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<T> 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 <S extends Service<T>> 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 <S extends Service<T>> 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<T> s : services ) {
s.terminate(getParent());
}
}
/**
* Retrieves the first service of the specified type.
*/
public <S extends Service<T>> S getService( Class<S> type ) {
for( Service s : services ) {
if( type.isInstance(s) ) {
return type.cast(s);
}
}
return null;
}
@Override
public String toString() {
return getClass().getName() + "[services=" + services + "]";
}
}

@ -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);
}
}

@ -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<Short, RpcHandler> handlers = new ConcurrentHashMap<Short, RpcHandler>();
/**
* 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<Long, ResponseHolder> responses = new ConcurrentHashMap<Long, ResponseHolder>();
/**
* 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;
}
}
}

@ -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 );
}

@ -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().
*
* <p>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.</p>
*/
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);
}
}

@ -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
+ "]";
}
}

@ -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
+ "]";
}
}

@ -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<Client> {
@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();
}
}

@ -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);
}
}

@ -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<S extends MessageConnection>
implements MessageListener<S> {
static final Logger log = Logger.getLogger(AbstractMessageDelegator.class.getName());
private Class delegateType;
private Map<Class, Method> methods = new HashMap<Class, Method>();
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<String>)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<S> map( String... methodNames ) {
Set<String> names = new HashSet<String>( 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<String> 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<S> 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());
}
}
}

@ -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<S extends MessageConnection> extends AbstractMessageDelegator<S> {
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.
* <p>Methods of the following signatures are allowed:
* <ul>
* <li>void someName(S conn, SomeMessage msg)
* <li>void someName(Message msg)
* </ul>
* 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;
}
}

@ -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<HostedConnection> {
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.
* <p>Methods of the following signatures are allowed:
* <ul>
* <li>void someName(S conn, SomeMessage msg)
* <li>void someName(Message msg)
* </ul>
* 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;
}
}

@ -32,6 +32,8 @@
package com.jme3.scene.plugins.fbx; package com.jme3.scene.plugins.fbx;
import com.jme3.asset.TextureKey; 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.InputCapsule;
import com.jme3.export.JmeExporter; import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter; import com.jme3.export.JmeImporter;
@ -56,6 +58,13 @@ public class ContentTextureKey extends TextureKey {
return content; 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 @Override
public String toString() { public String toString() {
return super.toString() + " " + content.length + " bytes"; return super.toString() + " " + content.length + " bytes";

@ -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<FbxId, FbxObject> objectMap = new HashMap<FbxId, FbxObject>();
private final List<FbxAnimStack> animStacks = new ArrayList<FbxAnimStack>();
private final List<FbxBindPose> bindPoses = new ArrayList<FbxBindPose>();
@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<FbxObject>(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<FbxId, Matrix4f> bindPoseData = bindPose.getJmeObject();
logger.log(Level.INFO, "Applying {0} bind poses", bindPoseData.size());
for (Map.Entry<FbxId, Matrix4f> 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<FbxToJmeTrack, FbxToJmeTrack> pairs = new HashMap<FbxToJmeTrack, FbxToJmeTrack>();
for (FbxAnimStack stack : animStacks) {
for (FbxAnimLayer layer : stack.getLayers()) {
for (FbxAnimCurveNode curveNode : layer.getAnimationCurveNodes()) {
for (Map.Entry<FbxNode, String> 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;
}
}

@ -73,9 +73,9 @@ import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.plugins.fbx.AnimationList.AnimInverval; import com.jme3.scene.plugins.fbx.AnimationList.AnimInverval;
import com.jme3.scene.plugins.fbx.file.FBXElement; import com.jme3.scene.plugins.fbx.file.FbxElement;
import com.jme3.scene.plugins.fbx.file.FBXFile; import com.jme3.scene.plugins.fbx.file.FbxFile;
import com.jme3.scene.plugins.fbx.file.FBXReader; import com.jme3.scene.plugins.fbx.file.FbxReader;
import com.jme3.texture.Image; import com.jme3.texture.Image;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D; import com.jme3.texture.Texture2D;
@ -165,8 +165,8 @@ public class SceneLoader implements AssetLoader {
private void loadScene(InputStream stream) throws IOException { private void loadScene(InputStream stream) throws IOException {
logger.log(Level.FINE, "Loading scene {0}", sceneFilename); logger.log(Level.FINE, "Loading scene {0}", sceneFilename);
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
FBXFile scene = FBXReader.readFBX(stream); FbxFile scene = FbxReader.readFBX(stream);
for(FBXElement e : scene.rootElements) { for(FbxElement e : scene.rootElements) {
if(e.id.equals("GlobalSettings")) if(e.id.equals("GlobalSettings"))
loadGlobalSettings(e); loadGlobalSettings(e);
else if(e.id.equals("Objects")) 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); logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime);
} }
private void loadGlobalSettings(FBXElement element) { private void loadGlobalSettings(FbxElement element) {
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("Properties70")) { if(e.id.equals("Properties70")) {
for(FBXElement e2 : e.children) { for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) { if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0); String propName = (String) e2.properties.get(0);
if(propName.equals("UnitScaleFactor")) if(propName.equals("UnitScaleFactor"))
@ -197,8 +197,8 @@ public class SceneLoader implements AssetLoader {
} }
} }
private void loadObjects(FBXElement element) { private void loadObjects(FbxElement element) {
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("Geometry")) if(e.id.equals("Geometry"))
loadGeometry(e); loadGeometry(e);
else if(e.id.equals("Material")) 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); long id = (Long) element.properties.get(0);
String type = (String) element.properties.get(2); String type = (String) element.properties.get(2);
if(type.equals("Mesh")) { if(type.equals("Mesh")) {
MeshData data = new MeshData(); MeshData data = new MeshData();
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("Vertices")) if(e.id.equals("Vertices"))
data.vertices = (double[]) e.properties.get(0); data.vertices = (double[]) e.properties.get(0);
else if(e.id.equals("PolygonVertexIndex")) else if(e.id.equals("PolygonVertexIndex"))
@ -236,7 +236,7 @@ public class SceneLoader implements AssetLoader {
//else if(e.id.equals("Edges")) //else if(e.id.equals("Edges"))
// data.edges = (int[]) e.properties.get(0); // data.edges = (int[]) e.properties.get(0);
else if(e.id.equals("LayerElementNormal")) else if(e.id.equals("LayerElementNormal"))
for(FBXElement e2 : e.children) { for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) { if(e2.id.equals("MappingInformationType")) {
data.normalsMapping = (String) e2.properties.get(0); data.normalsMapping = (String) e2.properties.get(0);
if(!data.normalsMapping.equals("ByVertice") && !data.normalsMapping.equals("ByPolygonVertex")) 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); data.normals = (double[]) e2.properties.get(0);
} }
else if(e.id.equals("LayerElementTangent")) else if(e.id.equals("LayerElementTangent"))
for(FBXElement e2 : e.children) { for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) { if(e2.id.equals("MappingInformationType")) {
data.tangentsMapping = (String) e2.properties.get(0); data.tangentsMapping = (String) e2.properties.get(0);
if(!data.tangentsMapping.equals("ByVertice") && !data.tangentsMapping.equals("ByPolygonVertex")) 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); data.tangents = (double[]) e2.properties.get(0);
} }
else if(e.id.equals("LayerElementBinormal")) else if(e.id.equals("LayerElementBinormal"))
for(FBXElement e2 : e.children) { for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) { if(e2.id.equals("MappingInformationType")) {
data.binormalsMapping = (String) e2.properties.get(0); data.binormalsMapping = (String) e2.properties.get(0);
if(!data.binormalsMapping.equals("ByVertice") && !data.binormalsMapping.equals("ByPolygonVertex")) 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); data.binormals = (double[]) e2.properties.get(0);
} }
else if(e.id.equals("LayerElementUV")) else if(e.id.equals("LayerElementUV"))
for(FBXElement e2 : e.children) { for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) { if(e2.id.equals("MappingInformationType")) {
data.uvMapping = (String) e2.properties.get(0); data.uvMapping = (String) e2.properties.get(0);
if(!data.uvMapping.equals("ByPolygonVertex")) if(!data.uvMapping.equals("ByPolygonVertex"))
@ -291,7 +291,7 @@ public class SceneLoader implements AssetLoader {
} }
// TODO smoothing is not used now // TODO smoothing is not used now
//else if(e.id.equals("LayerElementSmoothing")) //else if(e.id.equals("LayerElementSmoothing"))
// for(FBXElement e2 : e.children) { // for(FbxElement e2 : e.children) {
// if(e2.id.equals("MappingInformationType")) { // if(e2.id.equals("MappingInformationType")) {
// data.smoothingMapping = (String) e2.properties.get(0); // data.smoothingMapping = (String) e2.properties.get(0);
// if(!data.smoothingMapping.equals("ByEdge")) // if(!data.smoothingMapping.equals("ByEdge"))
@ -304,7 +304,7 @@ public class SceneLoader implements AssetLoader {
// data.smoothing = (int[]) e2.properties.get(0); // data.smoothing = (int[]) e2.properties.get(0);
// } // }
else if(e.id.equals("LayerElementMaterial")) else if(e.id.equals("LayerElementMaterial"))
for(FBXElement e2 : e.children) { for(FbxElement e2 : e.children) {
if(e2.id.equals("MappingInformationType")) { if(e2.id.equals("MappingInformationType")) {
data.materialsMapping = (String) e2.properties.get(0); data.materialsMapping = (String) e2.properties.get(0);
if(!data.materialsMapping.equals("AllSame")) 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); long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1); String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2); String type = (String) element.properties.get(2);
if(type.equals("")) { if(type.equals("")) {
MaterialData data = new MaterialData(); MaterialData data = new MaterialData();
data.name = path.substring(0, path.indexOf(0)); data.name = path.substring(0, path.indexOf(0));
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("ShadingModel")) { if(e.id.equals("ShadingModel")) {
data.shadingModel = (String) e.properties.get(0); data.shadingModel = (String) e.properties.get(0);
} else if(e.id.equals("Properties70")) { } else if(e.id.equals("Properties70")) {
for(FBXElement e2 : e.children) { for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) { if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0); String propName = (String) e2.properties.get(0);
if(propName.equals("AmbientColor")) { 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); long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1); String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2); String type = (String) element.properties.get(2);
ModelData data = new ModelData(); ModelData data = new ModelData();
data.name = path.substring(0, path.indexOf(0)); data.name = path.substring(0, path.indexOf(0));
data.type = type; data.type = type;
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("Properties70")) { if(e.id.equals("Properties70")) {
for(FBXElement e2 : e.children) { for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) { if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0); String propName = (String) e2.properties.get(0);
if(propName.equals("Lcl Translation")) { if(propName.equals("Lcl Translation")) {
@ -408,17 +408,17 @@ public class SceneLoader implements AssetLoader {
modelDataMap.put(id, data); modelDataMap.put(id, data);
} }
private void loadPose(FBXElement element) { private void loadPose(FbxElement element) {
long id = (Long) element.properties.get(0); long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1); String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2); String type = (String) element.properties.get(2);
if(type.equals("BindPose")) { if(type.equals("BindPose")) {
BindPoseData data = new BindPoseData(); BindPoseData data = new BindPoseData();
data.name = path.substring(0, path.indexOf(0)); data.name = path.substring(0, path.indexOf(0));
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("PoseNode")) { if(e.id.equals("PoseNode")) {
NodeTransformData item = new NodeTransformData(); NodeTransformData item = new NodeTransformData();
for(FBXElement e2 : e.children) { for(FbxElement e2 : e.children) {
if(e2.id.equals("Node")) if(e2.id.equals("Node"))
item.nodeId = (Long) e2.properties.get(0); item.nodeId = (Long) e2.properties.get(0);
else if(e2.id.equals("Matrix")) 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); long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1); String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2); String type = (String) element.properties.get(2);
if(type.equals("")) { if(type.equals("")) {
TextureData data = new TextureData(); TextureData data = new TextureData();
data.name = path.substring(0, path.indexOf(0)); data.name = path.substring(0, path.indexOf(0));
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("Type")) if(e.id.equals("Type"))
data.bindType = (String) e.properties.get(0); data.bindType = (String) e.properties.get(0);
else if(e.id.equals("FileName")) 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); long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1); String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2); String type = (String) element.properties.get(2);
if(type.equals("Clip")) { if(type.equals("Clip")) {
ImageData data = new ImageData(); ImageData data = new ImageData();
data.name = path.substring(0, path.indexOf(0)); data.name = path.substring(0, path.indexOf(0));
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("Type")) if(e.id.equals("Type"))
data.type = (String) e.properties.get(0); data.type = (String) e.properties.get(0);
else if(e.id.equals("FileName")) 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); long id = (Long) element.properties.get(0);
String type = (String) element.properties.get(2); String type = (String) element.properties.get(2);
if(type.equals("Skin")) { if(type.equals("Skin")) {
SkinData skinData = new SkinData(); SkinData skinData = new SkinData();
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("SkinningType")) if(e.id.equals("SkinningType"))
skinData.type = (String) e.properties.get(0); skinData.type = (String) e.properties.get(0);
} }
skinMap.put(id, skinData); skinMap.put(id, skinData);
} else if(type.equals("Cluster")) { } else if(type.equals("Cluster")) {
ClusterData clusterData = new ClusterData(); ClusterData clusterData = new ClusterData();
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("Indexes")) if(e.id.equals("Indexes"))
clusterData.indexes = (int[]) e.properties.get(0); clusterData.indexes = (int[]) e.properties.get(0);
else if(e.id.equals("Weights")) 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); long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1); String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2); 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); long id = (Long) element.properties.get(0);
String type = (String) element.properties.get(2); String type = (String) element.properties.get(2);
if(type.equals("")) { if(type.equals("")) {
AnimCurveData data = new AnimCurveData(); AnimCurveData data = new AnimCurveData();
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("KeyTime")) if(e.id.equals("KeyTime"))
data.keyTimes = (long[]) e.properties.get(0); data.keyTimes = (long[]) e.properties.get(0);
else if(e.id.equals("KeyValueFloat")) 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); long id = (Long) element.properties.get(0);
String path = (String) element.properties.get(1); String path = (String) element.properties.get(1);
String type = (String) element.properties.get(2); String type = (String) element.properties.get(2);
if(type.equals("")) { if(type.equals("")) {
Double x = null, y = null, z = null; Double x = null, y = null, z = null;
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("Properties70")) { if(e.id.equals("Properties70")) {
for(FBXElement e2 : e.children) { for(FbxElement e2 : e.children) {
if(e2.id.equals("P")) { if(e2.id.equals("P")) {
String propName = (String) e2.properties.get(0); String propName = (String) e2.properties.get(0);
if(propName.equals("d|X")) if(propName.equals("d|X"))
@ -554,8 +554,8 @@ public class SceneLoader implements AssetLoader {
} }
} }
private void loadConnections(FBXElement element) { private void loadConnections(FbxElement element) {
for(FBXElement e : element.children) { for(FbxElement e : element.children) {
if(e.id.equals("C")) { if(e.id.equals("C")) {
String type = (String) e.properties.get(0); String type = (String) e.properties.get(0);
long objId, refId; long objId, refId;

@ -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);
}
}

@ -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<FbxNode, String> influencedNodePropertiesMap = new HashMap<FbxNode, String>();
private final Map<String, FbxAnimCurve> propertyToCurveMap = new HashMap<String, FbxAnimCurve>();
private final Map<String, Float> propertyToDefaultMap = new HashMap<String, Float>();
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<FbxNode, String> getInfluencedNodeProperties() {
return influencedNodePropertiesMap;
}
public Collection<FbxAnimCurve> 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);
}
}

@ -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<FbxAnimCurveNode> animCurves = new ArrayList<FbxAnimCurveNode>();
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<FbxAnimCurveNode> 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);
}
}

@ -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<FbxNode> getInfluencedNodes() {
// HashSet<FbxNode> influencedNodes = new HashSet<FbxNode>();
// 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);
}
}

@ -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";
}

@ -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<Map<FbxId, Matrix4f>> {
private final Map<FbxId, Matrix4f> bindPose = new HashMap<FbxId, Matrix4f>();
public FbxBindPose(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
public void fromElement(FbxElement element) {
super.fromElement(element);
for (FbxElement child : element.children) {
if (!child.id.equals("PoseNode")) {
continue;
}
FbxId node = null;
float[] matData = null;
for (FbxElement e : child.children) {
if (e.id.equals("Node")) {
node = FbxId.create(e.properties.get(0));
} else if (e.id.equals("Matrix")) {
double[] matDataDoubles = (double[]) e.properties.get(0);
if (matDataDoubles.length != 16) {
// corrupt
throw new UnsupportedOperationException("Bind pose matrix "
+ "must have 16 doubles, but it has "
+ matDataDoubles.length + ". Data is corrupt");
}
matData = new float[16];
for (int i = 0; i < matDataDoubles.length; i++) {
matData[i] = (float) matDataDoubles[i];
}
}
}
if (node != null && matData != null) {
Matrix4f matrix = new Matrix4f(matData);
bindPose.put(node, matrix);
}
}
}
@Override
protected Map<FbxId, Matrix4f> toJmeObject() {
return bindPose;
}
@Override
public void connectObject(FbxObject object) {
unsupportedConnectObject(object);
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,98 @@
/*
* 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 FbxCluster extends FbxObject {
private static final Logger logger = Logger.getLogger(FbxCluster.class.getName());
private int[] indexes;
private double[] weights;
private FbxLimbNode limb;
public FbxCluster(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("Indexes")) {
indexes = (int[]) e.properties.get(0);
} else if (e.id.equals("Weights")) {
weights = (double[]) e.properties.get(0);
}
}
}
public int[] getVertexIndices() {
return indexes;
}
public double[] getWeights() {
return weights;
}
public FbxLimbNode getLimb() {
return limb;
}
@Override
protected Object toJmeObject() {
throw new UnsupportedOperationException();
}
@Override
public void connectObject(FbxObject object) {
if (object instanceof FbxLimbNode) {
if (limb != null) {
logger.log(Level.WARNING, "This cluster already has a limb attached. Ignoring.");
return;
}
limb = (FbxLimbNode) object;
} else {
unsupportedConnectObject(object);
}
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,94 @@
/*
* 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.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.asset.AssetManager;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import java.util.ArrayList;
import java.util.List;
public class FbxLimbNode extends FbxNode {
protected FbxNode skeletonHolder;
protected Bone bone;
public FbxLimbNode(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
private static void createBones(FbxNode skeletonHolderNode, FbxLimbNode limb, List<Bone> bones) {
limb.skeletonHolder = skeletonHolderNode;
Bone parentBone = limb.getJmeBone();
bones.add(parentBone);
for (FbxNode child : limb.children) {
if (child instanceof FbxLimbNode) {
FbxLimbNode childLimb = (FbxLimbNode) child;
createBones(skeletonHolderNode, childLimb, bones);
parentBone.addChild(childLimb.getJmeBone());
}
}
}
public static Skeleton createSkeleton(FbxNode skeletonHolderNode) {
if (skeletonHolderNode instanceof FbxLimbNode) {
throw new UnsupportedOperationException("Limb nodes cannot be skeleton holders");
}
List<Bone> bones = new ArrayList<Bone>();
for (FbxNode child : skeletonHolderNode.getChildren()) {
if (child instanceof FbxLimbNode) {
createBones(skeletonHolderNode, (FbxLimbNode) child, bones);
}
}
return new Skeleton(bones.toArray(new Bone[0]));
}
public FbxNode getSkeletonHolder() {
return skeletonHolder;
}
public Bone getJmeBone() {
if (bone == null) {
bone = new Bone(name);
bone.setBindTransforms(jmeLocalBindPose.getTranslation(),
jmeLocalBindPose.getRotation(),
jmeLocalBindPose.getScale());
}
return bone;
}
}

@ -0,0 +1,66 @@
/*
* 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.obj.FbxObject;
import java.util.ArrayList;
import java.util.List;
public class FbxSkinDeformer extends FbxObject<List<FbxCluster>> {
private final List<FbxCluster> clusters = new ArrayList<FbxCluster>();
public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) {
super(assetManager, sceneFolderName);
}
@Override
protected List<FbxCluster> toJmeObject() {
return clusters;
}
@Override
public void connectObject(FbxObject object) {
if (object instanceof FbxCluster) {
clusters.add((FbxCluster) object);
} else {
unsupportedConnectObject(object);
}
}
@Override
public void connectObjectProperty(FbxObject object, String property) {
unsupportedConnectObjectProperty(object, property);
}
}

@ -0,0 +1,202 @@
/*
* 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.animation.BoneTrack;
import com.jme3.animation.SpatialTrack;
import com.jme3.animation.Track;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.fbx.node.FbxNode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Maps animation stacks to influenced nodes.
* Will be used later to create jME3 tracks.
*/
public final class FbxToJmeTrack {
public FbxAnimStack animStack;
public FbxAnimLayer animLayer;
public FbxNode node;
// These are not used in map lookups.
public transient final Map<String, FbxAnimCurveNode> animCurves = new HashMap<String, FbxAnimCurveNode>();
public long[] getKeyTimes() {
Set<Long> keyFrameTimesSet = new HashSet<Long>();
for (FbxAnimCurveNode curveNode : animCurves.values()) {
for (FbxAnimCurve curve : curveNode.getCurves()) {
for (long keyTime : curve.getKeyTimes()) {
keyFrameTimesSet.add(keyTime);
}
}
}
long[] keyFrameTimes = new long[keyFrameTimesSet.size()];
int i = 0;
for (Long keyFrameTime : keyFrameTimesSet) {
keyFrameTimes[i++] = keyFrameTime;
}
Arrays.sort(keyFrameTimes);
return keyFrameTimes;
}
/**
* Generate a {@link BoneTrack} from the animation data, for the given
* boneIndex.
*
* @param boneIndex The bone index for which track data is generated for.
* @param inverseBindPose Inverse bind pose of the bone (in world space).
* @return A BoneTrack containing the animation data, for the specified
* boneIndex.
*/
public BoneTrack toJmeBoneTrack(int boneIndex, Transform inverseBindPose) {
return (BoneTrack) toJmeTrackInternal(boneIndex, inverseBindPose);
}
public SpatialTrack toJmeSpatialTrack() {
return (SpatialTrack) toJmeTrackInternal(-1, null);
}
public float getDuration() {
long[] keyframes = getKeyTimes();
return (float) (keyframes[keyframes.length - 1] * FbxAnimUtil.SECONDS_PER_UNIT);
}
private static void applyInverse(Vector3f translation, Quaternion rotation, Vector3f scale, Transform inverseBindPose) {
Transform t = new Transform();
t.setTranslation(translation);
t.setRotation(rotation);
if (scale != null) {
t.setScale(scale);
}
t.combineWithParent(inverseBindPose);
t.getTranslation(translation);
t.getRotation(rotation);
if (scale != null) {
t.getScale(scale);
}
}
private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) {
float duration = animStack.getDuration();
FbxAnimCurveNode translationCurve = animCurves.get("Lcl Translation");
FbxAnimCurveNode rotationCurve = animCurves.get("Lcl Rotation");
FbxAnimCurveNode scalingCurve = animCurves.get("Lcl Scaling");
long[] fbxTimes = getKeyTimes();
float[] times = new float[fbxTimes.length];
// Translations / Rotations must be set on all tracks.
// (Required for jME3)
Vector3f[] translations = new Vector3f[fbxTimes.length];
Quaternion[] rotations = new Quaternion[fbxTimes.length];
Vector3f[] scales = null;
if (scalingCurve != null) {
scales = new Vector3f[fbxTimes.length];
}
for (int i = 0; i < fbxTimes.length; i++) {
long fbxTime = fbxTimes[i];
float time = (float) (fbxTime * FbxAnimUtil.SECONDS_PER_UNIT);
if (time > duration) {
// Expand animation duration to fit the curve.
duration = time;
System.out.println("actual duration: " + duration);
}
times[i] = time;
if (translationCurve != null) {
translations[i] = translationCurve.getVector3Value(fbxTime);
} else {
translations[i] = new Vector3f();
}
if (rotationCurve != null) {
rotations[i] = rotationCurve.getQuaternionValue(fbxTime);
if (i > 0) {
if (rotations[i - 1].dot(rotations[i]) < 0) {
System.out.println("rotation will go the long way, oh noes");
rotations[i - 1].negate();
}
}
} else {
rotations[i] = new Quaternion();
}
if (scalingCurve != null) {
scales[i] = scalingCurve.getVector3Value(fbxTime);
}
if (inverseBindPose != null) {
applyInverse(translations[i], rotations[i], scales != null ? scales[i] : null, inverseBindPose);
}
}
if (boneIndex == -1) {
return new SpatialTrack(times, translations, rotations, scales);
} else {
if (scales != null) {
return new BoneTrack(boneIndex, times, translations, rotations, scales);
} else {
return new BoneTrack(boneIndex, times, translations, rotations);
}
}
}
@Override
public int hashCode() {
int hash = 3;
hash = 79 * hash + this.animStack.hashCode();
hash = 79 * hash + this.animLayer.hashCode();
hash = 79 * hash + this.node.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
final FbxToJmeTrack other = (FbxToJmeTrack) obj;
return this.node == other.node
&& this.animStack == other.animStack
&& this.animLayer == other.animLayer;
}
}

@ -37,7 +37,8 @@ import java.lang.reflect.Array;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static org.omg.IOP.IORHelper.id; import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Quick n' dirty dumper of FBX binary files. * Quick n' dirty dumper of FBX binary files.
@ -46,12 +47,14 @@ import static org.omg.IOP.IORHelper.id;
* *
* @author Kirill Vainer * @author Kirill Vainer
*/ */
public final class FBXDump { public final class FbxDump {
private static final Logger logger = Logger.getLogger(FbxDump.class.getName());
private static final DecimalFormat DECIMAL_FORMAT private static final DecimalFormat DECIMAL_FORMAT
= new DecimalFormat("0.0000000000"); = new DecimalFormat("0.0000000000");
private FBXDump() { } private FbxDump() { }
/** /**
* Creates a map between object UIDs and the objects themselves. * Creates a map between object UIDs and the objects themselves.
@ -59,16 +62,17 @@ public final class FBXDump {
* @param file The file to create the mappings for. * @param file The file to create the mappings for.
* @return The UID to object map. * @return The UID to object map.
*/ */
private static Map<Long, FBXElement> createUidToObjectMap(FBXFile file) { private static Map<FbxId, FbxElement> createUidToObjectMap(FbxFile file) {
Map<Long, FBXElement> uidToObjectMap = new HashMap<Long, FBXElement>(); Map<FbxId, FbxElement> uidToObjectMap = new HashMap<FbxId, FbxElement>();
for (FBXElement rootElement : file.rootElements) { for (FbxElement rootElement : file.rootElements) {
if (rootElement.id.equals("Objects")) { if (rootElement.id.equals("Objects")) {
for (FBXElement fbxObj : rootElement.children) { for (FbxElement fbxObj : rootElement.children) {
if (fbxObj.propertiesTypes[0] != 'L') { FbxId uid = FbxId.getObjectId(fbxObj);
continue; // error if (uid != null) {
}
Long uid = (Long) fbxObj.properties.get(0);
uidToObjectMap.put(uid, fbxObj); uidToObjectMap.put(uid, fbxObj);
} else {
logger.log(Level.WARNING, "Cannot determine ID for object: {0}", fbxObj);
}
} }
} }
} }
@ -80,8 +84,8 @@ public final class FBXDump {
* *
* @param file the file to dump. * @param file the file to dump.
*/ */
public static void dumpFBX(FBXFile file) { public static void dumpFile(FbxFile file) {
dumpFBX(file, System.out); dumpFile(file, System.out);
} }
/** /**
@ -90,11 +94,11 @@ public final class FBXDump {
* @param file the file to dump. * @param file the file to dump.
* @param out the output stream where to output. * @param out the output stream where to output.
*/ */
public static void dumpFBX(FBXFile file, OutputStream out) { public static void dumpFile(FbxFile file, OutputStream out) {
Map<Long, FBXElement> uidToObjectMap = createUidToObjectMap(file); Map<FbxId, FbxElement> uidToObjectMap = createUidToObjectMap(file);
PrintStream ps = new PrintStream(out); PrintStream ps = new PrintStream(out);
for (FBXElement rootElement : file.rootElements) { for (FbxElement rootElement : file.rootElements) {
dumpFBXElement(rootElement, ps, 0, uidToObjectMap); dumpElement(rootElement, ps, 0, uidToObjectMap);
} }
} }
@ -113,9 +117,9 @@ public final class FBXDump {
return string.replaceAll("\u0000\u0001", "::"); return string.replaceAll("\u0000\u0001", "::");
} }
protected static void dumpFBXProperty(String id, char propertyType, protected static void dumpProperty(String id, char propertyType,
Object property, PrintStream ps, Object property, PrintStream ps,
Map<Long, FBXElement> uidToObjectMap) { Map<FbxId, FbxElement> uidToObjectMap) {
switch (propertyType) { switch (propertyType) {
case 'S': case 'S':
// String // String
@ -125,13 +129,19 @@ public final class FBXDump {
case 'R': case 'R':
// RAW data. // RAW data.
byte[] bytes = (byte[]) property; byte[] bytes = (byte[]) property;
ps.print("["); int numToPrint = Math.min(10 * 1024, bytes.length);
for (int j = 0; j < bytes.length; j++) { ps.print("(size = ");
ps.print(bytes.length);
ps.print(") [");
for (int j = 0; j < numToPrint; j++) {
ps.print(String.format("%02X", bytes[j] & 0xff)); ps.print(String.format("%02X", bytes[j] & 0xff));
if (j != bytes.length - 1) { if (j != bytes.length - 1) {
ps.print(" "); ps.print(" ");
} }
} }
if (numToPrint < bytes.length) {
ps.print(" ...");
}
ps.print("]"); ps.print("]");
break; break;
case 'D': case 'D':
@ -159,7 +169,7 @@ public final class FBXDump {
// If this is a connection, decode UID into object name. // If this is a connection, decode UID into object name.
if (id.equals("C")) { if (id.equals("C")) {
Long uid = (Long) property; Long uid = (Long) property;
FBXElement element = uidToObjectMap.get(uid); FbxElement element = uidToObjectMap.get(FbxId.create(uid));
if (element != null) { if (element != null) {
String name = (String) element.properties.get(1); String name = (String) element.properties.get(1);
ps.print("\"" + convertFBXString(name) + "\""); ps.print("\"" + convertFBXString(name) + "\"");
@ -178,7 +188,7 @@ public final class FBXDump {
int length = Array.getLength(property); int length = Array.getLength(property);
for (int j = 0; j < length; j++) { for (int j = 0; j < length; j++) {
Object arrayEntry = Array.get(property, j); Object arrayEntry = Array.get(property, j);
dumpFBXProperty(id, Character.toUpperCase(propertyType), arrayEntry, ps, uidToObjectMap); dumpProperty(id, Character.toUpperCase(propertyType), arrayEntry, ps, uidToObjectMap);
if (j != length - 1) { if (j != length - 1) {
ps.print(","); ps.print(",");
} }
@ -189,24 +199,24 @@ public final class FBXDump {
} }
} }
protected static void dumpFBXElement(FBXElement el, PrintStream ps, protected static void dumpElement(FbxElement el, PrintStream ps,
int indent, Map<Long, FBXElement> uidToObjectMap) { int indent, Map<FbxId, FbxElement> uidToObjectMap) {
// 4 spaces per tab should be OK. // 4 spaces per tab should be OK.
String indentStr = indent(indent * 4); String indentStr = indent(indent * 4);
String textId = el.id; String textId = el.id;
// Properties are called 'P' and connections are called 'C'. // Properties are called 'P' and connections are called 'C'.
if (el.id.equals("P")) { // if (el.id.equals("P")) {
textId = "Property"; // textId = "Property";
} else if (el.id.equals("C")) { // } else if (el.id.equals("C")) {
textId = "Connect"; // textId = "Connect";
} // }
ps.print(indentStr + textId + ": "); ps.print(indentStr + textId + ": ");
for (int i = 0; i < el.properties.size(); i++) { for (int i = 0; i < el.properties.size(); i++) {
Object property = el.properties.get(i); Object property = el.properties.get(i);
char propertyType = el.propertiesTypes[i]; char propertyType = el.propertiesTypes[i];
dumpFBXProperty(el.id, propertyType, property, ps, uidToObjectMap); dumpProperty(el.id, propertyType, property, ps, uidToObjectMap);
if (i != el.properties.size() - 1) { if (i != el.properties.size() - 1) {
ps.print(", "); ps.print(", ");
} }
@ -215,8 +225,8 @@ public final class FBXDump {
ps.println(); ps.println();
} else { } else {
ps.println(" {"); ps.println(" {");
for (FBXElement childElement : el.children) { for (FbxElement childElement : el.children) {
dumpFBXElement(childElement, ps, indent + 1, uidToObjectMap); dumpElement(childElement, ps, indent + 1, uidToObjectMap);
} }
ps.println(indentStr + "}"); ps.println(indentStr + "}");
} }

@ -0,0 +1,124 @@
/*
* Copyright (c) 2009-2014 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.scene.plugins.fbx.file;
import java.util.ArrayList;
import java.util.List;
public class FbxElement {
public String id;
public List<Object> properties;
/*
* Y - signed short
* C - boolean
* I - signed integer
* F - float
* D - double
* L - long
* R - byte array
* S - string
* f - array of floats
* i - array of ints
* d - array of doubles
* l - array of longs
* b - array of boleans
* c - array of unsigned bytes (represented as array of ints)
*/
public char[] propertiesTypes;
public List<FbxElement> children = new ArrayList<FbxElement>();
public FbxElement(int propsCount) {
this.properties = new ArrayList<Object>(propsCount);
this.propertiesTypes = new char[propsCount];
}
public FbxElement getChildById(String name) {
for (FbxElement element : children) {
if (element.id.equals(name)) {
return element;
}
}
return null;
}
public List<FbxElement> getFbxProperties() {
List<FbxElement> props = new ArrayList<FbxElement>();
FbxElement propsElement = null;
boolean legacy = false;
for (FbxElement element : children) {
if (element.id.equals("Properties70")) {
propsElement = element;
break;
} else if (element.id.equals("Properties60")) {
legacy = true;
propsElement = element;
break;
}
}
if (propsElement == null) {
return props;
}
for (FbxElement prop : propsElement.children) {
if (prop.id.equals("P") || prop.id.equals("Property")) {
if (legacy) {
char[] types = new char[prop.propertiesTypes.length + 1];
types[0] = prop.propertiesTypes[0];
types[1] = prop.propertiesTypes[0];
System.arraycopy(prop.propertiesTypes, 1, types, 2, types.length - 2);
List<Object> values = new ArrayList<Object>(prop.properties);
values.add(1, values.get(0));
FbxElement dummyProp = new FbxElement(types.length);
dummyProp.children = prop.children;
dummyProp.id = prop.id;
dummyProp.propertiesTypes = types;
dummyProp.properties = values;
props.add(dummyProp);
} else {
props.add(prop);
}
}
}
return props;
}
@Override
public String toString() {
return "FBXElement[id=" + id + ", numProps=" + properties.size() + ", numChildren=" + children.size() + "]";
}
}

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

Loading…
Cancel
Save