Merge pull request #4 from jMonkeyEngine/master

Merge branch "jmonkeyengine/master" into "scenecomposer/master"
experimental
Dokthar 10 years ago
commit d48c1e0e9d
  1. 274
      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. 319
      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. 67
      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. 17
      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. 76
      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. 26
      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. 10
      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. 38
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java
  29. 166
      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. 24
      jme3-core/src/main/java/com/jme3/texture/Image.java
  35. 20
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
  36. 13
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
  37. 19
      jme3-core/src/main/resources/joystick-mapping.properties
  38. 12
      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. 15
      jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java
  53. 15
      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. 35
      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. 29
      jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java
  64. 58
      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,314 +35,240 @@ package com.jme3.input.android;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import com.jme3.input.event.InputEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* AndroidGestureHandler uses Gesture type listeners to create jME TouchEvents
* for gestures. This class is designed to handle the gestures supported
* for gestures. This class is designed to handle the gestures supported
* on Android rev 9 (Android 2.3). Extend this class to add functionality
* added by Android after rev 9.
*
*
* @author iwgeric
*/
public class AndroidGestureHandler implements
GestureDetector.OnGestureListener,
public class AndroidGestureProcessor implements
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener,
ScaleGestureDetector.OnScaleGestureListener {
private static final Logger logger = Logger.getLogger(AndroidGestureHandler.class.getName());
private AndroidInputHandler androidInput;
private GestureDetector gestureDetector;
private ScaleGestureDetector scaleDetector;
private static final Logger logger = Logger.getLogger(AndroidGestureProcessor.class.getName());
private AndroidTouchInput touchInput;
float gestureDownX = -1f;
float gestureDownY = -1f;
float scaleStartX = -1f;
float scaleStartY = -1f;
public AndroidGestureHandler(AndroidInputHandler androidInput) {
this.androidInput = androidInput;
}
public void initialize() {
}
public void destroy() {
setView(null);
}
public void setView(View view) {
if (view != null) {
gestureDetector = new GestureDetector(view.getContext(), this);
scaleDetector = new ScaleGestureDetector(view.getContext(), this);
} else {
gestureDetector = null;
scaleDetector = null;
}
}
public void detectGesture(MotionEvent event) {
if (gestureDetector != null && scaleDetector != null) {
gestureDetector.onTouchEvent(event);
scaleDetector.onTouchEvent(event);
}
}
private int getPointerIndex(MotionEvent event) {
return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
}
private int getPointerId(MotionEvent event) {
return event.getPointerId(getPointerIndex(event));
public AndroidGestureProcessor(AndroidTouchInput touchInput) {
this.touchInput = touchInput;
}
private void processEvent(TouchEvent event) {
// Add the touch event
androidInput.addEvent(event);
if (androidInput.isSimulateMouse()) {
InputEvent mouseEvent = generateMouseEvent(event);
if (mouseEvent != null) {
// Add the mouse event
androidInput.addEvent(mouseEvent);
}
}
}
// TODO: Ring Buffer for mouse events?
private InputEvent generateMouseEvent(TouchEvent event) {
InputEvent inputEvent = null;
int newX;
int newY;
int newDX;
int newDY;
if (androidInput.isMouseEventsInvertX()) {
newX = (int) (androidInput.invertX(event.getX()));
newDX = (int)event.getDeltaX() * -1;
} else {
newX = (int) event.getX();
newDX = (int)event.getDeltaX();
}
int wheel = (int) (event.getScaleSpan()); // might need to scale to match mouse wheel
int dWheel = (int) (event.getDeltaScaleSpan()); // might need to scale to match mouse wheel
if (androidInput.isMouseEventsInvertY()) {
newY = (int) (androidInput.invertY(event.getY()));
newDY = (int)event.getDeltaY() * -1;
} else {
newY = (int) event.getY();
newDY = (int)event.getDeltaY();
}
switch (event.getType()) {
case SCALE_MOVE:
inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, wheel, dWheel);
inputEvent.setTime(event.getTime());
break;
}
return inputEvent;
}
/* Events from onGestureListener */
@Override
public boolean onDown(MotionEvent event) {
// start of all GestureListeners. Not really a gesture by itself
// so we don't create an event.
// However, reset the scaleInProgress here since this is the beginning
// of a series of gesture events.
// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
gestureDownX = androidInput.getJmeX(event.getX());
gestureDownY = androidInput.invertY(androidInput.getJmeY(event.getY()));
// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
gestureDownX = touchInput.getJmeX(event.getX());
gestureDownY = touchInput.invertY(touchInput.getJmeY(event.getY()));
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent event) {
// Up of single tap. May be followed by a double tap later.
// use onSingleTapConfirmed instead.
// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
return true;
}
@Override
public void onShowPress(MotionEvent event) {
// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
float jmeX = androidInput.getJmeX(event.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
float jmeX = touchInput.getJmeX(event.getX());
float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SHOWPRESS, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(getPointerId(event));
touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure());
processEvent(touchEvent);
touchInput.addEvent(touchEvent);
}
@Override
public void onLongPress(MotionEvent event) {
// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
float jmeX = androidInput.getJmeX(event.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
float jmeX = touchInput.getJmeX(event.getX());
float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.LONGPRESSED, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(getPointerId(event));
touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure());
processEvent(touchEvent);
touchInput.addEvent(touchEvent);
}
@Override
public boolean onScroll(MotionEvent startEvent, MotionEvent endEvent, float distX, float distY) {
// if not scaleInProgess, send scroll events. This is to avoid sending
// scroll events when one of the fingers is lifted just before the other one.
// Avoids sending the scroll for that brief period of time.
// Return true so that the next event doesn't accumulate the distX and distY values.
// Apparantly, both distX and distY are negative.
// Apparantly, both distX and distY are negative.
// Negate distX to get the real value, but leave distY negative to compensate
// for the fact that jME has y=0 at bottom where Android has y=0 at top.
// if (!scaleInProgress) {
if (!scaleDetector.isInProgress()) {
// logger.log(Level.INFO, "onScroll pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, dx: {7}, dy: {8}",
// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), distX, distY});
if (!touchInput.getScaleDetector().isInProgress()) {
// logger.log(Level.INFO, "onScroll pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, dx: {7}, dy: {8}",
// new Object[]{touchInput.getPointerId(startEvent), touchInput.getAction(startEvent), startEvent.getX(), startEvent.getY(), touchInput.getAction(endEvent), endEvent.getX(), endEvent.getY(), distX, distY});
float jmeX = androidInput.getJmeX(endEvent.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(endEvent.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, androidInput.getJmeX(-distX), androidInput.getJmeY(distY));
touchEvent.setPointerId(getPointerId(endEvent));
float jmeX = touchInput.getJmeX(endEvent.getX());
float jmeY = touchInput.invertY(touchInput.getJmeY(endEvent.getY()));
TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, touchInput.getJmeX(-distX), touchInput.getJmeY(distY));
touchEvent.setPointerId(touchInput.getPointerId(endEvent));
touchEvent.setTime(endEvent.getEventTime());
touchEvent.setPressure(endEvent.getPressure());
processEvent(touchEvent);
touchInput.addEvent(touchEvent);
}
return true;
}
@Override
public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float velocityX, float velocityY) {
// Fling happens only once at the end of the gesture (all fingers up).
// Fling returns the velocity of the finger movement in pixels/sec.
// Therefore, the dX and dY values are actually velocity instead of distance values
// Since this does not track the movement, use the start position and velocity values.
// logger.log(Level.INFO, "onFling pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, velocityX: {7}, velocityY: {8}",
// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY});
float jmeX = androidInput.getJmeX(startEvent.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(startEvent.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
// logger.log(Level.INFO, "onFling pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, velocityX: {7}, velocityY: {8}",
// new Object[]{touchInput.getPointerId(startEvent), touchInput.getAction(startEvent), startEvent.getX(), startEvent.getY(), touchInput.getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY});
float jmeX = touchInput.getJmeX(startEvent.getX());
float jmeY = touchInput.invertY(touchInput.getJmeY(startEvent.getY()));
TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.FLING, jmeX, jmeY, velocityX, velocityY);
touchEvent.setPointerId(getPointerId(endEvent));
touchEvent.setPointerId(touchInput.getPointerId(endEvent));
touchEvent.setTime(endEvent.getEventTime());
touchEvent.setPressure(endEvent.getPressure());
processEvent(touchEvent);
touchInput.addEvent(touchEvent);
return true;
}
/* Events from onDoubleTapListener */
@Override
public boolean onSingleTapConfirmed(MotionEvent event) {
// Up of single tap when no double tap followed.
// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
float jmeX = androidInput.getJmeX(event.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
float jmeX = touchInput.getJmeX(event.getX());
float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.TAP, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(getPointerId(event));
touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure());
processEvent(touchEvent);
touchInput.addEvent(touchEvent);
return true;
}
@Override
public boolean onDoubleTap(MotionEvent event) {
//The down motion event of the first tap of the double-tap
// We could use this event to fire off a double tap event, or use
// We could use this event to fire off a double tap event, or use
// DoubleTapEvent with a check for the UP action
// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
float jmeX = androidInput.getJmeX(event.getX());
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
float jmeX = touchInput.getJmeX(event.getX());
float jmeY = touchInput.invertY(touchInput.getJmeY(event.getY()));
TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.DOUBLETAP, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(getPointerId(event));
touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure());
processEvent(touchEvent);
touchInput.addEvent(touchEvent);
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent event) {
//Notified when an event within a double-tap gesture occurs, including the down, move(s), and up events.
// this means it will get called multiple times for a single double tap
// logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
// if (getAction(event) == MotionEvent.ACTION_UP) {
// TouchEvent touchEvent = touchEventPool.getNextFreeEvent();
// touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), androidInput.invertY(event.getY()), 0, 0);
// touchEvent.setPointerId(getPointerId(event));
// touchEvent.setTime(event.getEventTime());
// touchEvent.setPressure(event.getPressure());
// processEvent(touchEvent);
// }
// logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}",
// new Object[]{touchInput.getPointerId(event), touchInput.getAction(event), event.getX(), event.getY()});
if (touchInput.getAction(event) == MotionEvent.ACTION_UP) {
TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), touchInput.invertY(event.getY()), 0, 0);
touchEvent.setPointerId(touchInput.getPointerId(event));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure());
touchInput.addEvent(touchEvent);
}
return true;
}
/* Events from ScaleGestureDetector */
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
// Scale uses a focusX and focusY instead of x and y. Focus is the middle
// of the fingers. Therefore, use the x and y values from the Down event
// so that the x and y values don't jump to the middle position.
// return true or all gestures for this beginning event will be discarded
logger.log(Level.INFO, "onScaleBegin");
// logger.log(Level.INFO, "onScaleBegin");
scaleStartX = gestureDownX;
scaleStartY = gestureDownY;
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCALE_START, scaleStartX, scaleStartY, 0f, 0f);
touchEvent.setPointerId(0);
touchEvent.setTime(scaleGestureDetector.getEventTime());
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
touchEvent.setDeltaScaleSpan(0f);
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
processEvent(touchEvent);
touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress());
touchInput.addEvent(touchEvent);
return true;
}
@Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
// return true or all gestures for this event will be accumulated
logger.log(Level.INFO, "onScale");
// logger.log(Level.INFO, "onScale");
scaleStartX = gestureDownX;
scaleStartY = gestureDownY;
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCALE_MOVE, scaleStartX, scaleStartY, 0f, 0f);
touchEvent.setPointerId(0);
touchEvent.setTime(scaleGestureDetector.getEventTime());
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
processEvent(touchEvent);
touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress());
touchInput.addEvent(touchEvent);
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
logger.log(Level.INFO, "onScaleEnd");
// logger.log(Level.INFO, "onScaleEnd");
scaleStartX = gestureDownX;
scaleStartY = gestureDownY;
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
TouchEvent touchEvent = touchInput.getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.SCALE_END, scaleStartX, scaleStartY, 0f, 0f);
touchEvent.setPointerId(0);
touchEvent.setTime(scaleGestureDetector.getEventTime());
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
processEvent(touchEvent);
touchEvent.setScaleSpanInProgress(touchInput.getScaleDetector().isInProgress());
touchInput.addEvent(touchEvent);
}
}

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

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

@ -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,13 +37,14 @@ import java.util.logging.Logger;
/**
* AndroidKeyMapping is just a utility to convert the Android keyCodes into
* jME KeyCodes received in jME's KeyEvent will match between Desktop and Android.
*
* jME KeyCodes so that events received in jME's KeyEvent will match between
* Desktop and Android.
*
* @author iwgeric
*/
public class AndroidKeyMapping {
private static final Logger logger = Logger.getLogger(AndroidKeyMapping.class.getName());
private static final int[] ANDROID_TO_JME = {
0x0, // unknown
0x0, // key code soft left
@ -141,9 +142,13 @@ public class AndroidKeyMapping {
0x0,//media fastforward
0x0,//mute
};
public static int getJmeKey(int androidKey) {
return ANDROID_TO_JME[androidKey];
if (androidKey > ANDROID_TO_JME.length) {
return androidKey;
} else {
return ANDROID_TO_JME[androidKey];
}
}
}

@ -75,15 +75,15 @@ import java.util.logging.Logger;
public class AndroidSensorJoyInput implements SensorEventListener {
private final static Logger logger = Logger.getLogger(AndroidSensorJoyInput.class.getName());
private AndroidJoyInputHandler joyHandler;
private AndroidJoyInput joyInput;
private SensorManager sensorManager = null;
private WindowManager windowManager = null;
private IntMap<SensorData> sensors = new IntMap<SensorData>();
private int lastRotation = 0;
private boolean loaded = false;
public AndroidSensorJoyInput(AndroidJoyInputHandler joyHandler) {
this.joyHandler = joyHandler;
public AndroidSensorJoyInput(AndroidJoyInput joyInput) {
this.joyInput = joyInput;
}
/**
@ -96,7 +96,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
int sensorAccuracy = -1;
float[] lastValues;
final Object valuesLock = new Object();
ArrayList<AndroidJoystickAxis> axes = new ArrayList<AndroidJoystickAxis>();
ArrayList<AndroidSensorJoystickAxis> axes = new ArrayList<AndroidSensorJoystickAxis>();
boolean enabled = false;
boolean haveData = false;
@ -306,7 +306,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
*/
private boolean updateOrientation() {
SensorData sensorData;
AndroidJoystickAxis axis;
AndroidSensorJoystickAxis axis;
final float[] curInclinationMat = new float[16];
final float[] curRotationMat = new float[16];
final float[] rotatedRotationMat = new float[16];
@ -374,7 +374,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
sensorData.haveData = true;
} else {
if (axis.isChanged()) {
joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue()));
joyInput.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue()));
}
}
}
@ -401,10 +401,10 @@ public class AndroidSensorJoyInput implements SensorEventListener {
public Joystick loadJoystick(int joyId, InputManager inputManager) {
SensorData sensorData;
AndroidJoystickAxis axis;
AndroidSensorJoystickAxis axis;
AndroidJoystick joystick = new AndroidJoystick(inputManager,
joyHandler,
AndroidSensorJoystick joystick = new AndroidSensorJoystick(inputManager,
joyInput,
joyId,
"AndroidSensorsJoystick");
@ -522,15 +522,15 @@ public class AndroidSensorJoyInput implements SensorEventListener {
if (!loaded) {
return;
}
logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}",
new Object[]{se.sensor.getName(), se.accuracy, se.values});
// logger.log(Level.FINE, "onSensorChanged for {0}: accuracy: {1}, values: {2}",
// new Object[]{se.sensor.getName(), se.accuracy, se.values});
int sensorType = se.sensor.getType();
SensorData sensorData = sensors.get(sensorType);
if (sensorData != null) {
logger.log(Level.FINE, "sensorData name: {0}, enabled: {1}, unreliable: {2}",
new Object[]{sensorData.sensor.getName(), sensorData.enabled, sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE});
// logger.log(Level.FINE, "sensorData name: {0}, enabled: {1}, unreliable: {2}",
// new Object[]{sensorData.sensor.getName(), sensorData.enabled, sensorData.sensorAccuracy == SensorManager.SENSOR_STATUS_UNRELIABLE});
}
if (sensorData != null && sensorData.sensor.equals(se.sensor) && sensorData.enabled) {
@ -543,8 +543,8 @@ public class AndroidSensorJoyInput implements SensorEventListener {
}
}
if (sensorData != null && sensorData.axes.size() > 0) {
AndroidJoystickAxis axis;
if (sensorData.axes.size() > 0) {
AndroidSensorJoystickAxis axis;
for (int i=0; i<se.values.length; i++) {
axis = sensorData.axes.get(i);
if (axis != null) {
@ -554,8 +554,8 @@ public class AndroidSensorJoyInput implements SensorEventListener {
} else {
if (axis.isChanged()) {
JoyAxisEvent event = new JoyAxisEvent(axis, axis.getJoystickAxisValue());
logger.log(Level.INFO, "adding JoyAxisEvent: {0}", event);
joyHandler.addEvent(event);
// logger.log(Level.INFO, "adding JoyAxisEvent: {0}", event);
joyInput.addEvent(event);
// joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue()));
}
}
@ -585,14 +585,14 @@ public class AndroidSensorJoyInput implements SensorEventListener {
// End of SensorEventListener methods
protected class AndroidJoystick extends AbstractJoystick {
protected class AndroidSensorJoystick extends AbstractJoystick {
private JoystickAxis nullAxis;
private JoystickAxis xAxis;
private JoystickAxis yAxis;
private JoystickAxis povX;
private JoystickAxis povY;
public AndroidJoystick( InputManager inputManager, JoyInput joyInput,
public AndroidSensorJoystick( InputManager inputManager, JoyInput joyInput,
int joyId, String 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) {
AndroidJoystickAxis axis;
protected AndroidSensorJoystickAxis addAxis(String axisName, String logicalName, int axisNum, float maxRawValue) {
AndroidSensorJoystickAxis axis;
axis = new AndroidJoystickAxis(
axis = new AndroidSensorJoystickAxis(
getInputManager(), // InputManager (InputManager)
this, // parent Joystick (Joystick)
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 curRawValue = 0f;
float lastRawValue = 0f;
@ -662,7 +662,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
float maxRawValue = FastMath.HALF_PI;
boolean enabled = true;
public AndroidJoystickAxis(InputManager inputManager, Joystick parent,
public AndroidSensorJoystickAxis(InputManager inputManager, Joystick parent,
int axisIndex, String name, String logicalId,
boolean isAnalog, boolean isRelative, float deadZone,
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;
import android.view.MotionEvent;
import android.view.View;
import com.jme3.input.event.TouchEvent;
import com.jme3.math.Vector2f;
import java.util.HashMap;
@ -41,36 +40,20 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
* AndroidTouchHandler14 is an extension of AndroidTouchHander that adds the
* Android touch event functionality between Android rev 9 (Android 2.3) and
* Android rev 14 (Android 4.0).
*
* AndroidTouchHandler14 extends AndroidTouchHandler to process the onHover
* events added in Android rev 14 (Android 4.0).
*
* @author iwgeric
*/
public class AndroidTouchHandler14 extends AndroidTouchHandler implements
View.OnHoverListener {
private static final Logger logger = Logger.getLogger(AndroidTouchHandler14.class.getName());
public class AndroidTouchInput14 extends AndroidTouchInput {
private static final Logger logger = Logger.getLogger(AndroidTouchInput14.class.getName());
final private HashMap<Integer, Vector2f> lastHoverPositions = new HashMap<Integer, Vector2f>();
public AndroidTouchHandler14(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) {
super(androidInput, gestureHandler);
}
@Override
public void setView(View view) {
if (view != null) {
view.setOnHoverListener(this);
} else {
androidInput.getView().setOnHoverListener(null);
}
super.setView(view);
public AndroidTouchInput14(AndroidInputHandler androidInput) {
super(androidInput);
}
public boolean onHover(View view, MotionEvent event) {
if (view == null || view != androidInput.getView()) {
return false;
}
public boolean onHover(MotionEvent event) {
boolean consumed = false;
int action = getAction(event);
int pointerId = getPointerId(event);
@ -78,34 +61,34 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements
Vector2f lastPos = lastHoverPositions.get(pointerId);
float jmeX;
float jmeY;
numPointers = event.getPointerCount();
logger.log(Level.INFO, "onHover pointerId: {0}, action: {1}, x: {2}, y: {3}, numPointers: {4}",
new Object[]{pointerId, action, event.getX(), event.getY(), event.getPointerCount()});
// logger.log(Level.INFO, "onHover pointerId: {0}, action: {1}, x: {2}, y: {3}, numPointers: {4}",
// new Object[]{pointerId, action, event.getX(), event.getY(), event.getPointerCount()});
TouchEvent touchEvent;
switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
jmeX = androidInput.getJmeX(event.getX(pointerIndex));
jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex)));
touchEvent = androidInput.getFreeTouchEvent();
jmeX = getJmeX(event.getX(pointerIndex));
jmeY = invertY(getJmeY(event.getY(pointerIndex)));
touchEvent = getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.HOVER_START, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(pointerId);
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure(pointerIndex));
lastPos = new Vector2f(jmeX, jmeY);
lastHoverPositions.put(pointerId, lastPos);
processEvent(touchEvent);
addEvent(touchEvent);
consumed = true;
break;
case MotionEvent.ACTION_HOVER_MOVE:
// Convert all pointers into events
for (int p = 0; p < event.getPointerCount(); p++) {
jmeX = androidInput.getJmeX(event.getX(p));
jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(p)));
jmeX = getJmeX(event.getX(p));
jmeY = invertY(getJmeY(event.getY(p)));
lastPos = lastHoverPositions.get(event.getPointerId(p));
if (lastPos == null) {
lastPos = new Vector2f(jmeX, jmeY);
@ -115,38 +98,39 @@ public class AndroidTouchHandler14 extends AndroidTouchHandler implements
float dX = jmeX - lastPos.x;
float dY = jmeY - lastPos.y;
if (dX != 0 || dY != 0) {
touchEvent = androidInput.getFreeTouchEvent();
touchEvent = getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.HOVER_MOVE, jmeX, jmeY, dX, dY);
touchEvent.setPointerId(event.getPointerId(p));
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure(p));
lastPos.set(jmeX, jmeY);
processEvent(touchEvent);
addEvent(touchEvent);
}
}
consumed = true;
break;
case MotionEvent.ACTION_HOVER_EXIT:
jmeX = androidInput.getJmeX(event.getX(pointerIndex));
jmeY = androidInput.invertY(androidInput.getJmeY(event.getY(pointerIndex)));
touchEvent = androidInput.getFreeTouchEvent();
jmeX = getJmeX(event.getX(pointerIndex));
jmeY = invertY(getJmeY(event.getY(pointerIndex)));
touchEvent = getFreeTouchEvent();
touchEvent.set(TouchEvent.Type.HOVER_END, jmeX, jmeY, 0, 0);
touchEvent.setPointerId(pointerId);
touchEvent.setTime(event.getEventTime());
touchEvent.setPressure(event.getPressure(pointerIndex));
lastHoverPositions.remove(pointerId);
processEvent(touchEvent);
addEvent(touchEvent);
consumed = true;
break;
default:
consumed = false;
break;
}
return consumed;
}
}

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

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

@ -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);
for (int i = 0; i < iterations && distanceFromTarget > MIN_DISTANCE; ++i) {
for (BoneContext boneContext : bones) {
@ -150,6 +152,20 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
DTransform topBoneTransform = new DTransform(constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD));
Vector3d e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector
distanceFromTarget = e.distance(t);
if(distanceFromTarget < bestSolutionDistance) {
bestSolutionDistance = distanceFromTarget;
bestSolution.clear();
for(BoneContext boneContext : bones) {
bestSolution.add(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD));
}
}
}
// applying best solution
for(int i=0;i<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
* translation of the Spatial instead of the world traslation.
* translation of the Spatial instead of the world translation.
* @param applyPhysicsLocal
*/
public void setApplyPhysicsLocal(boolean applyPhysicsLocal) {

@ -313,6 +313,26 @@ public final class Bone implements Savable {
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()}
*/

@ -902,6 +902,25 @@ final public class FastMath {
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
* into half precision (16 bit).

@ -259,6 +259,26 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
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.
*/

@ -337,7 +337,19 @@ public enum Caps {
* <p>
* 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

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

@ -35,14 +35,18 @@ import java.nio.IntBuffer;
/**
* GL functions only available on vanilla desktop OpenGL 3.0.
*
*
* @author Kirill Vainer
*/
public interface GL3 extends GL2 {
public static final int GL_DEPTH_STENCIL_ATTACHMENT = 0x821A;
public static final int GL_GEOMETRY_SHADER=0x8DD9;
public static final int GL_GEOMETRY_SHADER = 0x8DD9;
public static final int GL_NUM_EXTENSIONS = 0x821D;
public void glBindFragDataLocation(int param1, int param2, String param3); /// GL3+
public void glBindVertexArray(int param1); /// GL3+
public void glDeleteVertexArrays(IntBuffer arrays); /// GL3+
public void glGenVertexArrays(IntBuffer param1); /// GL3+
public String glGetString(int param1, int param2); /// GL3+
}

@ -3,15 +3,17 @@ package com.jme3.renderer.opengl;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
public class GLDebugDesktop extends GLDebugES implements GL2, GL3 {
public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 {
private final GL2 gl2;
private final GL3 gl3;
private final GL4 gl4;
public GLDebugDesktop(GL gl, GLFbo glfbo) {
super(gl, glfbo);
public GLDebugDesktop(GL gl, GLExt glext, GLFbo glfbo) {
super(gl, glext, glfbo);
this.gl2 = gl instanceof GL2 ? (GL2) gl : null;
this.gl3 = gl instanceof GL3 ? (GL3) gl : null;
this.gl4 = gl instanceof GL4 ? (GL4) gl : null;
}
public void glAlphaFunc(int func, float ref) {
@ -73,5 +75,23 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3 {
gl3.glGenVertexArrays(param1);
checkError();
}
@Override
public String glGetString(int param1, int param2) {
String result = gl3.glGetString(param1, param2);
checkError();
return result;
}
@Override
public void glDeleteVertexArrays(IntBuffer arrays) {
gl3.glDeleteVertexArrays(arrays);
checkError();
}
@Override
public void glPatchParameter(int count) {
gl4.glPatchParameter(count);
checkError();
}
}

@ -10,14 +10,16 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
private final GLFbo glfbo;
private final GLExt glext;
public GLDebugES(GL gl, GLFbo glfbo) {
public GLDebugES(GL gl, GLExt glext, GLFbo glfbo) {
this.gl = gl;
// this.gl2 = gl instanceof GL2 ? (GL2) gl : null;
// this.gl3 = gl instanceof GL3 ? (GL3) gl : null;
this.glext = glext;
this.glfbo = glfbo;
this.glext = glfbo instanceof GLExt ? (GLExt) glfbo : null;
}
public void resetStats() {
gl.resetStats();
}
public void glActiveTexture(int texture) {
gl.glActiveTexture(texture);
checkError();
@ -478,7 +480,7 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
}
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
glext.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
glfbo.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
checkError();
}
@ -525,7 +527,7 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
}
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
glext.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
glfbo.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
checkError();
}

@ -41,7 +41,7 @@ import java.nio.IntBuffer;
*
* @author Kirill Vainer
*/
public interface GLExt extends GLFbo {
public interface GLExt {
public static final int GL_ALREADY_SIGNALED = 0x911A;
public static final int GL_COMPRESSED_RGB8_ETC2 = 0x9274;
@ -100,7 +100,6 @@ public interface GLExt extends GLFbo {
public static final int GL_UNSIGNED_INT_5_9_9_9_REV_EXT = 0x8C3E;
public static final int GL_WAIT_FAILED = 0x911D;
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public void glBufferData(int target, IntBuffer data, int usage);
public void glBufferSubData(int target, long offset, IntBuffer data);
public int glClientWaitSync(Object sync, int flags, long timeout);
@ -110,7 +109,6 @@ public interface GLExt extends GLFbo {
public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount);
public Object glFenceSync(int condition, int flags);
public void glGetMultisample(int pname, int index, FloatBuffer val);
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height);
public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations);
public void glVertexAttribDivisorARB(int index, int divisor);
}

@ -83,6 +83,7 @@ public interface GLFbo {
public void glBindFramebufferEXT(int param1, int param2);
public void glBindRenderbufferEXT(int param1, int param2);
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public int glCheckFramebufferStatusEXT(int param1);
public void glDeleteFramebuffersEXT(IntBuffer param1);
public void glDeleteRenderbuffersEXT(IntBuffer param1);
@ -92,5 +93,5 @@ public interface GLFbo {
public void glGenRenderbuffersEXT(IntBuffer param1);
public void glGenerateMipmapEXT(int param1);
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4);
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height);
}

@ -89,9 +89,11 @@ public final class GLImageFormats {
GLImageFormat[][] formatToGL = new GLImageFormat[2][Image.Format.values().length];
if (caps.contains(Caps.OpenGL20)) {
format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
}
format(formatToGL, Format.RGB8, GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGBA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGB565, GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_SHORT_5_6_5);
@ -108,8 +110,10 @@ public final class GLImageFormats {
formatSrgb(formatToGL, Format.RGB565, GLExt.GL_SRGB8_EXT, GL.GL_RGB, GL.GL_UNSIGNED_SHORT_5_6_5);
formatSrgb(formatToGL, Format.RGB5A1, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1);
formatSrgb(formatToGL, Format.RGBA8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
formatSrgb(formatToGL, Format.Luminance8, GLExt.GL_SLUMINANCE8_EXT, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
formatSrgb(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SLUMINANCE8_ALPHA8_EXT, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
if (!caps.contains(Caps.CoreProfile)) {
formatSrgb(formatToGL, Format.Luminance8, GLExt.GL_SLUMINANCE8_EXT, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
formatSrgb(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SLUMINANCE8_ALPHA8_EXT, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
}
formatSrgb(formatToGL, Format.BGR8, GLExt.GL_SRGB8_EXT, GL2.GL_BGR, GL.GL_UNSIGNED_BYTE);
formatSrgb(formatToGL, Format.ABGR8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL2.GL_UNSIGNED_INT_8_8_8_8);
formatSrgb(formatToGL, Format.ARGB8, GLExt.GL_SRGB8_ALPHA8_EXT, GL2.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8);
@ -124,16 +128,20 @@ public final class GLImageFormats {
}
} else if (caps.contains(Caps.Rgba8)) {
// A more limited form of 32-bit RGBA. Only GL_RGBA8 is available.
format(formatToGL, Format.Alpha8, GLExt.GL_RGBA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GLExt.GL_RGBA8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GLExt.GL_RGBA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Alpha8, GLExt.GL_RGBA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GLExt.GL_RGBA8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GLExt.GL_RGBA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
}
format(formatToGL, Format.RGB8, GLExt.GL_RGBA8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGBA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
} else {
// Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above..
format(formatToGL, Format.Alpha8, GL.GL_RGBA4, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GL.GL_RGB565, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GL.GL_RGBA4, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Alpha8, GL.GL_RGBA4, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8, GL.GL_RGB565, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.Luminance8Alpha8, GL.GL_RGBA4, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE);
}
format(formatToGL, Format.RGB8, GL.GL_RGB565, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
format(formatToGL, Format.RGBA8, GL.GL_RGBA4, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
}
@ -145,9 +153,11 @@ public final class GLImageFormats {
format(formatToGL, Format.RGB5A1, GL.GL_RGB5_A1, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1);
if (caps.contains(Caps.FloatTexture)) {
format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, GLExt.GL_HALF_FLOAT_ARB);
format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT);
format(formatToGL, Format.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, GLExt.GL_HALF_FLOAT_ARB);
if (!caps.contains(Caps.CoreProfile)) {
format(formatToGL, Format.Luminance16F, GLExt.GL_LUMINANCE16F_ARB, GL.GL_LUMINANCE, GLExt.GL_HALF_FLOAT_ARB);
format(formatToGL, Format.Luminance32F, GLExt.GL_LUMINANCE32F_ARB, GL.GL_LUMINANCE, GL.GL_FLOAT);
format(formatToGL, Format.Luminance16FAlpha16F, GLExt.GL_LUMINANCE_ALPHA16F_ARB, GL.GL_LUMINANCE_ALPHA, GLExt.GL_HALF_FLOAT_ARB);
}
format(formatToGL, Format.RGB16F, GLExt.GL_RGB16F_ARB, GL.GL_RGB, GLExt.GL_HALF_FLOAT_ARB);
format(formatToGL, Format.RGB32F, GLExt.GL_RGB32F_ARB, GL.GL_RGB, GL.GL_FLOAT);
format(formatToGL, Format.RGBA16F, GLExt.GL_RGBA16F_ARB, GL.GL_RGBA, GLExt.GL_HALF_FLOAT_ARB);

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

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

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

@ -421,7 +421,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
/**
* @return True if the image needs to have mipmaps generated
* for it (as requested by the texture).
* for it (as requested by the texture). This stays true even
* after mipmaps have been generated.
*/
public boolean isGeneratedMipmapsRequired() {
return needGeneratedMips;
@ -434,8 +435,9 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
@Override
public void setUpdateNeeded() {
super.setUpdateNeeded();
if (!isGeneratedMipmapsRequired() && !hasMipmaps()) {
setNeedGeneratedMipmaps();
if (isGeneratedMipmapsRequired() && !hasMipmaps()) {
// Mipmaps are no longer valid, since the image was changed.
setMipmapsGenerated(false);
}
}
@ -527,11 +529,13 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
this();
if (mipMapSizes != null && mipMapSizes.length <= 1) {
mipMapSizes = null;
} else {
needGeneratedMips = false;
mipsWereGenerated = true;
if (mipMapSizes != null) {
if (mipMapSizes.length <= 1) {
mipMapSizes = null;
} else {
needGeneratedMips = false;
mipsWereGenerated = true;
}
}
setFormat(format);
@ -787,8 +791,8 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
needGeneratedMips = false;
mipsWereGenerated = false;
} else {
needGeneratedMips = false;
mipsWereGenerated = true;
needGeneratedMips = true;
mipsWereGenerated = false;
}
setUpdateNeeded();

@ -72,17 +72,19 @@ uniform float m_Shininess;
#endif
void main(){
#ifdef NORMALMAP
mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz));
#if !defined(VERTEX_LIGHTING)
#if defined(NORMALMAP)
mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz));
if (!gl_FrontFacing)
{
tbnMat[2] = -tbnMat[2];
}
if (!gl_FrontFacing)
{
tbnMat[2] = -tbnMat[2];
}
vec3 viewDir = normalize(-vPos.xyz * tbnMat);
#else
vec3 viewDir = normalize(-vPos.xyz);
vec3 viewDir = normalize(-vPos.xyz * tbnMat);
#else
vec3 viewDir = normalize(-vPos.xyz);
#endif
#endif
vec2 newTexCoord;

@ -45,6 +45,9 @@ attribute vec3 inNormal;
#else
varying vec3 specularAccum;
varying vec4 diffuseAccum;
#ifdef COLORRAMP
uniform sampler2D m_ColorRamp;
#endif
#endif
#ifdef USE_REFLECTION
@ -160,14 +163,14 @@ void main(){
#if __VERSION__ >= 110
}
#endif
vec2 v = computeLighting(wvNormal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess);
vec2 light = computeLighting(wvNormal, viewDir, lightDir.xyz, lightDir.w * spotFallOff, m_Shininess);
#ifdef COLORRAMP
diffuseAccum += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor;
specularAccum += texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb * specularColor;
diffuseAccum.rgb += texture2D(m_ColorRamp, vec2(light.x, 0.0)).rgb * diffuseColor.rgb;
specularAccum.rgb += texture2D(m_ColorRamp, vec2(light.y, 0.0)).rgb * specularColor;
#else
diffuseAccum += v.x * diffuseColor;
specularAccum += v.y * specularColor;
diffuseAccum.rgb += light.x * diffuseColor.rgb;
specularAccum.rgb += light.y * specularColor;
#endif
}
#endif

@ -6,7 +6,7 @@
# the new name as it will be reported through the Joystick
# interface.
#
# Keys with spaces in them should have those spaces escaped.
# Keys with spaces in them should have those spaces escaped.
# Values do not need their spaces escaped. For example:
#
# Some\ Joystick.0=3
@ -37,22 +37,29 @@ Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).ry=rz
# requires custom code to support trigger buttons but this
# keeps it from confusing the .rx mapping.
Controller\ (Xbox\ 360\ Wireless\ Receiver\ for\ Windows).z=trigger
# Xbox 360 Controller (copied from wireless version)
Controller\ (XBOX\ 360\ For\ Windows).0=2
Controller\ (XBOX\ 360\ For\ Windows).1=1
Controller\ (XBOX\ 360\ For\ Windows).2=3
Controller\ (XBOX\ 360\ For\ Windows).3=0
Controller\ (XBOX\ 360\ For\ Windows).6=8
Controller\ (XBOX\ 360\ For\ Windows).7=9
Controller\ (XBOX\ 360\ For\ Windows).8=10
Controller\ (XBOX\ 360\ For\ Windows).9=11
Controller\ (XBOX\ 360\ For\ Windows).rx=z
Controller\ (XBOX\ 360\ For\ Windows).ry=rz
# requires custom code to support trigger buttons but this
# keeps it from confusing the .rx mapping.
Controller\ (XBOX\ 360\ For\ Windows).z=trigger
# XBOX 360 Controller connected to Android using
# the USB dongle
Xbox\ 360\ Wireless\ Receiver.AXIS_RX=z
Xbox\ 360\ Wireless\ Receiver.AXIS_RY=rz
Xbox\ 360\ Wireless\ Receiver.z=AXIS_RX
Xbox\ 360\ Wireless\ Receiver.rz=AXIS_RY

@ -569,10 +569,15 @@ public class J3MLoader implements AssetLoader {
public Object load(AssetInfo info) throws IOException {
this.assetManager = info.getManager();
InputStream in = info.openStream();
try {
key = info.getKey();
key = info.getKey();
if (key.getExtension().equals("j3m") && !(key instanceof MaterialKey)) {
throw new IOException("Material instances must be loaded via MaterialKey");
} else if (key.getExtension().equals("j3md") && key instanceof MaterialKey) {
throw new IOException("Material definitions must be loaded via AssetKey");
}
loadFromRoot(BlockLanguageParser.parse(in));
} finally {
if (in != null){
@ -581,9 +586,6 @@ public class J3MLoader implements AssetLoader {
}
if (material != null){
if (!(info.getKey() instanceof MaterialKey)){
throw new IOException("Material instances must be loaded via MaterialKey");
}
// material implementation
return material;
}else{

@ -242,21 +242,12 @@ public class DXTFlipper {
img.position(blockByteOffset);
img.limit(blockByteOffset + bpb);
img.get(colorBlock);
if (type == 4 || type == 5)
flipDXT5Block(colorBlock, h);
else
flipDXT1orDXTA3Block(colorBlock, h);
// write block (no need to flip block indexes, only pixels
// inside block
retImg.put(colorBlock);
if (alphaBlock != null){
img.get(alphaBlock);
switch (type){
case 2:
flipDXT3Block(alphaBlock, h); break;
flipDXT3Block(alphaBlock, h);
break;
case 3:
case 4:
flipDXT5Block(alphaBlock, h);
@ -264,6 +255,16 @@ public class DXTFlipper {
}
retImg.put(alphaBlock);
}
img.get(colorBlock);
if (type == 4 || type == 5)
flipDXT5Block(colorBlock, h);
else
flipDXT1orDXTA3Block(colorBlock, h);
// write block (no need to flip block indexes, only pixels
// inside block
retImg.put(colorBlock);
}
retImg.rewind();
}else if (h >= 4){

@ -162,8 +162,8 @@ public class SSAOFilter extends Filter {
};
ssaoPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth, 1, ssaoMat);
ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear);
ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);
// ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear);
// ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);
postRenderPasses.add(ssaoPass);
material = new Material(manager, "Common/MatDefs/SSAO/ssaoBlur.j3md");
material.setTexture("SSAOMap", ssaoPass.getRenderedTexture());

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

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

@ -2,3 +2,7 @@ INCLUDE com/jme3/asset/General.cfg
# IOS specific loaders
LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg
LOADER com.jme3.material.plugins.J3MLoader : j3m, j3md
LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib
LOADER com.jme3.export.binary.BinaryImporter : j3o
LOADER com.jme3.font.plugins.BitmapFontLoader : fnt

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

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

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

@ -36,6 +36,14 @@ import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext;
import com.jme3.system.JmeSystemDelegate;
import com.jme3.system.NullContext;
import com.jme3.audio.AudioRenderer;
import com.jme3.audio.ios.IosAL;
import com.jme3.audio.ios.IosALC;
//import com.jme3.audio.ios.IosEFX;
import com.jme3.audio.openal.AL;
import com.jme3.audio.openal.ALAudioRenderer;
import com.jme3.audio.openal.ALC;
import com.jme3.audio.openal.EFX;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
@ -89,8 +97,11 @@ public class JmeIosSystem extends JmeSystemDelegate {
@Override
public AudioRenderer newAudioRenderer(AppSettings settings) {
return null;
}
ALC alc = new IosALC();
AL al = new IosAL();
//EFX efx = new IosEFX();
return new ALAudioRenderer(al, alc, null);
}
@Override
public void initialize(AppSettings settings) {

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

@ -7,12 +7,8 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.opengl.ARBDrawInstanced;
import org.lwjgl.opengl.ARBInstancedArrays;
import org.lwjgl.opengl.ARBPixelBufferObject;
import org.lwjgl.opengl.ARBSync;
import org.lwjgl.opengl.ARBTextureMultisample;
import org.lwjgl.opengl.EXTFramebufferBlit;
import org.lwjgl.opengl.EXTFramebufferMultisample;
import org.lwjgl.opengl.EXTFramebufferObject;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GLSync;
@ -30,99 +26,51 @@ public class LwjglGLExt implements GLExt {
throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error");
}
}
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
@Override
public void glBufferData(int target, IntBuffer data, int usage) {
checkLimit(data);
GL15.glBufferData(target, data, usage);
}
@Override
public void glBufferSubData(int target, long offset, IntBuffer data) {
checkLimit(data);
GL15.glBufferSubData(target, offset, data);
}
@Override
public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) {
ARBDrawInstanced.glDrawArraysInstancedARB(mode, first, count, primcount);
}
@Override
public void glDrawBuffers(IntBuffer bufs) {
checkLimit(bufs);
GL20.glDrawBuffers(bufs);
}
@Override
public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) {
ARBDrawInstanced.glDrawElementsInstancedARB(mode, indices_count, type, indices_buffer_offset, primcount);
}
@Override
public void glGetMultisample(int pname, int index, FloatBuffer val) {
checkLimit(val);
ARBTextureMultisample.glGetMultisample(pname, index, val);
}
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height);
}
@Override
public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) {
ARBTextureMultisample.glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations);
}
@Override
public void glVertexAttribDivisorARB(int index, int divisor) {
ARBInstancedArrays.glVertexAttribDivisorARB(index, divisor);
}
public void glBindFramebufferEXT(int param1, int param2) {
EXTFramebufferObject.glBindFramebufferEXT(param1, param2);
}
public void glBindRenderbufferEXT(int param1, int param2) {
EXTFramebufferObject.glBindRenderbufferEXT(param1, param2);
}
public int glCheckFramebufferStatusEXT(int param1) {
return EXTFramebufferObject.glCheckFramebufferStatusEXT(param1);
}
public void glDeleteFramebuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glDeleteFramebuffersEXT(param1);
}
public void glDeleteRenderbuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glDeleteRenderbuffersEXT(param1);
}
public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
EXTFramebufferObject.glFramebufferRenderbufferEXT(param1, param2, param3, param4);
}
public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
EXTFramebufferObject.glFramebufferTexture2DEXT(param1, param2, param3, param4, param5);
}
public void glGenFramebuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glGenFramebuffersEXT(param1);
}
public void glGenRenderbuffersEXT(IntBuffer param1) {
checkLimit(param1);
EXTFramebufferObject.glGenRenderbuffersEXT(param1);
}
public void glGenerateMipmapEXT(int param1) {
EXTFramebufferObject.glGenerateMipmapEXT(param1);
}
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
EXTFramebufferObject.glRenderbufferStorageEXT(param1, param2, param3, param4);
}
@Override
public Object glFenceSync(int condition, int flags) {
return ARBSync.glFenceSync(condition, flags);

@ -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.lwjgl.LwjglGL;
import com.jme3.renderer.lwjgl.LwjglGLExt;
import com.jme3.renderer.lwjgl.LwjglGLFboEXT;
import com.jme3.renderer.lwjgl.LwjglGLFboGL3;
import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GL2;
import com.jme3.renderer.opengl.GL3;
import com.jme3.renderer.opengl.GL4;
import com.jme3.renderer.opengl.GLDebugDesktop;
import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.renderer.opengl.GLTiming;
import com.jme3.renderer.opengl.GLTimingState;
import com.jme3.renderer.opengl.GLTracer;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext;
@ -203,28 +208,44 @@ public abstract class LwjglContext implements JmeContext {
}
if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2)
|| settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
|| settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) {
GL gl = new LwjglGL();
GLFbo glfbo = new LwjglGLExt();
GLExt glext = new LwjglGLExt();
GLFbo glfbo;
if (GLContext.getCapabilities().OpenGL30) {
glfbo = new LwjglGLFboGL3();
} else {
glfbo = new LwjglGLFboEXT();
}
if (settings.getBoolean("GraphicsDebug")) {
gl = new GLDebugDesktop(gl, glfbo);
gl = new GLDebugDesktop(gl, glext, glfbo);
glext = (GLExt) gl;
glfbo = (GLFbo) gl;
}
if (settings.getBoolean("GraphicsTiming")) {
GLTimingState timingState = new GLTimingState();
gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class);
glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class);
glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class);
}
if (settings.getBoolean("GraphicsTrace")) {
gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class);
glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLExt.class);
gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class);
glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class);
glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class);
}
renderer = new GLRenderer(gl, glfbo);
renderer = new GLRenderer(gl, glext, glfbo);
renderer.initialize();
} else {
throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer());
}
if (GLContext.getCapabilities().GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) {
ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback());
ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback(new LwjglGLDebugOutputHandler()));
}
renderer.setMainFrameBufferSrgb(settings.getGammaCorrection());

@ -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;
import com.jme3.network.service.ClientServiceManager;
/**
* Represents a remote connection to a server that can be used
@ -72,6 +74,12 @@ public interface Client extends MessageConnection
* be able to connect to.
*/
public int getVersion();
/**
* Returns the manager for client services. Client services extend
* the functionality of the client.
*/
public ClientServiceManager getServices();
/**
* Sends a message to the server.

@ -33,6 +33,8 @@ package com.jme3.network;
import java.util.Collection;
import com.jme3.network.service.HostedServiceManager;
/**
* Represents a host that can send and receive messages to
* a set of remote client connections.
@ -54,6 +56,12 @@ public interface Server
*/
public int getVersion();
/**
* Returns the manager for hosted services. Hosted services extend
* the functionality of the server.
*/
public HostedServiceManager getServices();
/**
* Sends the specified message to all connected clients.
*/

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

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

@ -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;
import com.jme3.asset.TextureKey;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.WeakRefCloneAssetCache;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
@ -56,6 +58,13 @@ public class ContentTextureKey extends TextureKey {
return content;
}
@Override
public Class<? extends AssetCache> getCacheType(){
// Need to override this so that textures embedded in FBX
// don't get cached by the asset manager.
return null;
}
@Override
public String toString() {
return super.toString() + " " + content.length + " bytes";

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

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