diff --git a/engine/src/core/com/jme3/system/JmeVersion.java b/engine/src/core/com/jme3/system/JmeVersion.java index 0b73e390c..5eb87164d 100644 --- a/engine/src/core/com/jme3/system/JmeVersion.java +++ b/engine/src/core/com/jme3/system/JmeVersion.java @@ -32,5 +32,5 @@ package com.jme3.system; public class JmeVersion { - public static final String FULL_NAME = "jMonkeyEngine 3.0.8"; + public static final String FULL_NAME = "jMonkeyEngine 3.0.9"; } diff --git a/engine/src/ios/com/jme3/input/ios/IosInputHandler.java b/engine/src/ios/com/jme3/input/ios/IosInputHandler.java new file mode 100644 index 000000000..ea462f43d --- /dev/null +++ b/engine/src/ios/com/jme3/input/ios/IosInputHandler.java @@ -0,0 +1,203 @@ +package com.jme3.input.ios; + +import com.jme3.input.RawInputListener; +import com.jme3.input.TouchInput; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import com.jme3.system.AppSettings; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class IosInputHandler implements TouchInput { + private static final Logger logger = Logger.getLogger(IosInputHandler.class.getName()); + + private final static int MAX_TOUCH_EVENTS = 1024; + + // Custom settings + private boolean mouseEventsEnabled = true; + private boolean mouseEventsInvertX = false; + private boolean mouseEventsInvertY = false; + private boolean keyboardEventsEnabled = false; + private boolean dontSendHistory = false; + + // Internal + private boolean initialized = false; + private RawInputListener listener = null; + private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue(); + private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS); + private IosTouchHandler touchHandler; + private float scaleX = 1f; + private float scaleY = 1f; + private int width = 0; + private int height = 0; + + public IosInputHandler() { + touchHandler = new IosTouchHandler(this); + } + @Override + public void initialize() { + touchEventPool.initialize(); + if (touchHandler != null) { + touchHandler.initialize(); + } + initialized = true; + } + + @Override + public void update() { + logger.log(Level.FINE, "InputEvent update : {0}", + new Object[]{listener}); + 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); + } + } + } + } + + @Override + public void destroy() { + initialized = false; + touchEventPool.destroy(); + if (touchHandler != null) { + touchHandler.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 setSimulateMouse(boolean simulate) { + this.mouseEventsEnabled = simulate; + } + + @Override + public boolean getSimulateMouse() { + return mouseEventsEnabled; + } + + @Override + public boolean isSimulateMouse() { + return mouseEventsEnabled; + } + + @Override + public void setSimulateKeyboard(boolean simulate) { + this.keyboardEventsEnabled = simulate; + } + + @Override + public void setOmitHistoricEvents(boolean dontSendHistory) { + this.dontSendHistory = dontSendHistory; + } + +// @Override + public void showVirtualKeyboard(boolean visible) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + // ---------------- + + public void loadSettings(AppSettings settings) { + // TODO: add simulate keyboard to settings +// keyboardEventsEnabled = true; + mouseEventsEnabled = true;//settings.isEmulateMouse(); + mouseEventsInvertX = settings.isEmulateMouseFlipX(); + mouseEventsInvertY = settings.isEmulateMouseFlipY(); + + // view width and height are 0 until the view is displayed on the screen + //if (view.getWidth() != 0 && view.getHeight() != 0) { + // scaleX = (float)settings.getWidth() / (float)view.getWidth(); + // scaleY = (float)settings.getHeight() / (float)view.getHeight(); + //} + scaleX = 1.0f; + scaleY = 1.0f; + width = settings.getWidth(); + height = settings.getHeight(); + logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}, width: {2}, height: {3}", + new Object[]{scaleX, scaleY, width, height}); + } + + public boolean isMouseEventsInvertX() { + return mouseEventsInvertX; + } + + public boolean isMouseEventsInvertY() { + return mouseEventsInvertY; + } + + public float invertX(float origX) { + return getJmeX(width) - origX; + } + + public float invertY(float origY) { + return getJmeY(height) - origY; + } + + public float getJmeX(float origX) { + return origX * scaleX; + } + + public float getJmeY(float origY) { + return origY * scaleY; + } + + public TouchEvent getFreeTouchEvent() { + return touchEventPool.getNextFreeEvent(); + } + + public void addEvent(InputEvent event) { + inputEventQueue.add(event); + if (event instanceof TouchEvent) { + touchEventPool.storeEvent((TouchEvent)event); + } + } + + // ---------------- + + public void injectTouchDown(int pointerId, long time, float x, float y) { + logger.log(Level.FINE, "Using input scaling, scaleX: {0}, scaleY: {1}, width: {2}, height: {3}", + new Object[]{scaleX, scaleY, width, height}); + if (touchHandler != null) { + touchHandler.actionDown(pointerId, time, x, y); + } + } + + public void injectTouchUp(int pointerId, long time, float x, float y) { + if (touchHandler != null) { + touchHandler.actionUp(pointerId, time, x, y); + } + } + + public void injectTouchMove(int pointerId, long time, float x, float y) { + if (touchHandler != null) { + touchHandler.actionMove(pointerId, time, x, y); + } + } +} diff --git a/engine/src/ios/com/jme3/input/ios/IosTouchHandler.java b/engine/src/ios/com/jme3/input/ios/IosTouchHandler.java new file mode 100644 index 000000000..512aa8b66 --- /dev/null +++ b/engine/src/ios/com/jme3/input/ios/IosTouchHandler.java @@ -0,0 +1,191 @@ +/* + * 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.ios; + +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.Level; +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 IosTouchHandler { + private static final Logger logger = Logger.getLogger(IosTouchHandler.class.getName()); + + final private HashMap lastPositions = new HashMap(); + + protected int numPointers = 1; + + protected IosInputHandler iosInput; + + public IosTouchHandler(IosInputHandler iosInput) { + this.iosInput = iosInput; + } + + public void initialize() { + } + + public void destroy() { + } + + public void actionDown(int pointerId, long time, float x, float y) { + logger.log(Level.FINE, "Inject input pointer: {0}, time: {1}, x: {2}, y: {3}", + new Object[]{pointerId, time, x, y}); + float jmeX = iosInput.getJmeX(x); + float jmeY = iosInput.invertY(iosInput.getJmeY(y)); + TouchEvent touch = iosInput.getFreeTouchEvent(); + touch.set(TouchEvent.Type.DOWN, jmeX, jmeY, 0, 0); + touch.setPointerId(pointerId);//TODO: pointer ID + touch.setTime(time); + touch.setPressure(1.0f); + //touch.setPressure(event.getPressure(pointerIndex)); //TODO: preassure + + lastPositions.put(pointerId, new Vector2f(jmeX, jmeY)); + + processEvent(touch); + } + + public void actionUp(int pointerId, long time, float x, float y) { + float jmeX = iosInput.getJmeX(x); + float jmeY = iosInput.invertY(iosInput.getJmeY(y)); + TouchEvent touch = iosInput.getFreeTouchEvent(); + touch.set(TouchEvent.Type.UP, jmeX, jmeY, 0, 0); + touch.setPointerId(pointerId);//TODO: pointer ID + touch.setTime(time); + touch.setPressure(1.0f); + //touch.setPressure(event.getPressure(pointerIndex)); //TODO: preassure + lastPositions.remove(pointerId); + + processEvent(touch); + } + + public void actionMove(int pointerId, long time, float x, float y) { + float jmeX = iosInput.getJmeX(x); + float jmeY = iosInput.invertY(iosInput.getJmeY(y)); + Vector2f lastPos = lastPositions.get(pointerId); + if (lastPos == null) { + lastPos = new Vector2f(jmeX, jmeY); + lastPositions.put(pointerId, lastPos); + } + + float dX = jmeX - lastPos.x; + float dY = jmeY - lastPos.y; + if (dX != 0 || dY != 0) { + TouchEvent touch = iosInput.getFreeTouchEvent(); + touch.set(TouchEvent.Type.MOVE, jmeX, jmeY, dX, dY); + touch.setPointerId(pointerId); + touch.setTime(time); + touch.setPressure(1.0f); + //touch.setPressure(event.getPressure(p)); + lastPos.set(jmeX, jmeY); + + processEvent(touch); + } + } + + protected void processEvent(TouchEvent event) { + // Add the touch event + iosInput.addEvent(event); + // MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events + if (iosInput.isSimulateMouse() && numPointers == 1) { + InputEvent mouseEvent = generateMouseEvent(event); + if (mouseEvent != null) { + // Add the mouse event + iosInput.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 (iosInput.isMouseEventsInvertX()) { + newX = (int) (iosInput.invertX(event.getX())); + newDX = (int)event.getDeltaX() * -1; + } else { + newX = (int) event.getX(); + newDX = (int)event.getDeltaX(); + } + + if (iosInput.isMouseEventsInvertY()) { + newY = (int) (iosInput.invertY(event.getY())); + newDY = (int)event.getDeltaY() * -1; + } else { + newY = (int) event.getY(); + newDY = (int)event.getDeltaY(); + } + + switch (event.getType()) { + case DOWN: + // Handle mouse down event + inputEvent = new MouseButtonEvent(0, true, newX, newY); + inputEvent.setTime(event.getTime()); + break; + + case UP: + // Handle mouse up event + inputEvent = new MouseButtonEvent(0, false, newX, newY); + inputEvent.setTime(event.getTime()); + break; + +// case HOVER_MOVE: + case MOVE: + inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan()); + inputEvent.setTime(event.getTime()); + break; + } + + return inputEvent; + } + +} diff --git a/engine/src/ios/com/jme3/input/ios/TouchEventPool.java b/engine/src/ios/com/jme3/input/ios/TouchEventPool.java new file mode 100644 index 000000000..f986db155 --- /dev/null +++ b/engine/src/ios/com/jme3/input/ios/TouchEventPool.java @@ -0,0 +1,121 @@ +/* + * 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.ios; + +import com.jme3.input.event.TouchEvent; +import com.jme3.util.RingBuffer; +import java.util.logging.Logger; + +/** + * TouchEventPool provides a RingBuffer of jME TouchEvents to help with garbage + * collection on Android. Each TouchEvent is stored in the RingBuffer and is + * reused if the TouchEvent has been consumed. + * + * If a TouchEvent has not been consumed, it is placed back into the pool at the + * end for later use. If a TouchEvent has been consumed, it is reused to avoid + * creating lots of little objects. + * + * If the pool is full of unconsumed events, then a new event is created and provided. + * + * + * @author iwgeric + */ +public class TouchEventPool { + private static final Logger logger = Logger.getLogger(TouchEventPool.class.getName()); + private final RingBuffer eventPool; + private final int maxEvents; + + public TouchEventPool (int maxEvents) { + eventPool = new RingBuffer(maxEvents); + this.maxEvents = maxEvents; + } + + public void initialize() { + TouchEvent newEvent; + while (!eventPool.isEmpty()) { + eventPool.pop(); + } + for (int i = 0; i < maxEvents; i++) { + newEvent = new TouchEvent(); + newEvent.setConsumed(); + eventPool.push(newEvent); + } + } + + public void destroy() { + // Clean up queues + while (!eventPool.isEmpty()) { + eventPool.pop(); + } + } + + /** + * Fetches a touch event from the reuse pool + * + * @return a usable TouchEvent + */ + public TouchEvent getNextFreeEvent() { + TouchEvent evt = null; + int curSize = eventPool.size(); + while (curSize > 0) { + evt = (TouchEvent)eventPool.pop(); + if (evt.isConsumed()) { + break; + } else { + eventPool.push(evt); + evt = null; + } + curSize--; + } + + if (evt == null) { + logger.warning("eventPool full of unconsumed events"); + evt = new TouchEvent(); + } + return evt; + } + + /** + * Stores the TouchEvent back in the pool for later reuse. It is only reused + * if the TouchEvent has been consumed. + * + * @param event TouchEvent to store for later use if consumed. + */ + public void storeEvent(TouchEvent event) { + if (eventPool.size() < maxEvents) { + eventPool.push(event); + } else { + logger.warning("eventPool full"); + } + } + +} diff --git a/engine/src/ios/com/jme3/system/ios/IGLESContext.java b/engine/src/ios/com/jme3/system/ios/IGLESContext.java index d96962244..07e041eb8 100644 --- a/engine/src/ios/com/jme3/system/ios/IGLESContext.java +++ b/engine/src/ios/com/jme3/system/ios/IGLESContext.java @@ -37,6 +37,7 @@ import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; import com.jme3.renderer.ios.IGLESShaderRenderer; import com.jme3.system.*; +import com.jme3.input.ios.IosInputHandler; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -56,6 +57,7 @@ public class IGLESContext implements JmeContext { protected IGLESShaderRenderer renderer; protected Timer timer; protected SystemListener listener; + protected IosInputHandler input; protected int minFrameDuration = 0; // No FPS cap public IGLESContext() { @@ -63,20 +65,17 @@ public class IGLESContext implements JmeContext { } @Override - public Type getType() { - return Type.Display; + public JmeContext.Type getType() { + return JmeContext.Type.Display; } @Override public void setSettings(AppSettings settings) { logger.log(Level.FINE, "IGLESContext setSettings"); this.settings.copyFrom(settings); - /* - if (androidInput != null) { - androidInput.loadSettings(settings); + if (input != null) { + input.loadSettings(settings); } - */ - } @Override @@ -119,8 +118,7 @@ public class IGLESContext implements JmeContext { @Override public TouchInput getTouchInput() { - //return androidInput; - return null;// new DummyTouchInput(); + return input; } @Override @@ -153,6 +151,7 @@ public class IGLESContext implements JmeContext { public void create(boolean waitFor) { logger.log(Level.FINE, "IGLESContext create"); renderer = new IGLESShaderRenderer(); + input = new IosInputHandler(); timer = new NanoTimer(); //synchronized (createdLock){ diff --git a/engine/src/ios/com/jme3/util/RingBuffer.java b/engine/src/ios/com/jme3/util/RingBuffer.java new file mode 100644 index 000000000..1d3c22d7e --- /dev/null +++ b/engine/src/ios/com/jme3/util/RingBuffer.java @@ -0,0 +1,76 @@ +package com.jme3.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Ring buffer (fixed size queue) implementation using a circular array (array + * with wrap-around). + */ +// suppress unchecked warnings in Java 1.5.0_6 and later +@SuppressWarnings("unchecked") +public class RingBuffer implements Iterable { + + private T[] buffer; // queue elements + private int count = 0; // number of elements on queue + private int indexOut = 0; // index of first element of queue + private int indexIn = 0; // index of next available slot + + // cast needed since no generic array creation in Java + public RingBuffer(int capacity) { + buffer = (T[]) new Object[capacity]; + } + + public boolean isEmpty() { + return count == 0; + } + + public int size() { + return count; + } + + public void push(T item) { + if (count == buffer.length) { + throw new RuntimeException("Ring buffer overflow"); + } + buffer[indexIn] = item; + indexIn = (indexIn + 1) % buffer.length; // wrap-around + count++; + } + + public T pop() { + if (isEmpty()) { + throw new RuntimeException("Ring buffer underflow"); + } + T item = buffer[indexOut]; + buffer[indexOut] = null; // to help with garbage collection + count--; + indexOut = (indexOut + 1) % buffer.length; // wrap-around + return item; + } + + public Iterator iterator() { + return new RingBufferIterator(); + } + + // an iterator, doesn't implement remove() since it's optional + private class RingBufferIterator implements Iterator { + + private int i = 0; + + public boolean hasNext() { + return i < count; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return buffer[i++]; + } + } +} diff --git a/sdk/nbproject/project.properties b/sdk/nbproject/project.properties index f20d3ad86..8c0bcd441 100644 --- a/sdk/nbproject/project.properties +++ b/sdk/nbproject/project.properties @@ -6,7 +6,7 @@ app.icon.icns=jmonkeyplatform.icns #version name used for application and settings folder, no spaces! app.version=3.0 #version number used for plugins, only 3 numbers (e.g. 3.1.3) -plugins.version=3.0.8 +plugins.version=3.0.9 #used netbeans platform netbeans.platform.url=http://download.netbeans.org/netbeans/7.3.1/final/zip/netbeans-7.3.1-201306052037-javase.zip #command line args