Merge pull request #4 from jMonkeyEngine/master
Merge branch "jmonkeyengine/master" into "scenecomposer/master"experimental
commit
d48c1e0e9d
@ -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; |
||||
} |
||||
|
||||
} |
@ -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; |
||||
|
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
|
||||
} |
@ -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>(); |
||||
} |
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; |
||||
} |
||||
} |
@ -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 |
@ -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); |
||||
} |
||||
} |
@ -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)); |
||||
} |
||||
|
||||
} |
@ -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; |
||||
} |
||||
} |
||||
|
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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…
Reference in new issue