Browse Source

Merge pull request #45771 from CherokeeLanguage/master

Fix joystick axis mapping issues with NVIDIA shield. Probably others.
Rémi Verschelde 4 years ago
parent
commit
f3d15771bf

+ 147 - 100
platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java

@@ -38,6 +38,8 @@ import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
 
 import android.os.Build;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.InputDevice;
 import android.view.InputDevice.MotionRange;
 import android.view.KeyEvent;
@@ -46,17 +48,24 @@ import android.view.MotionEvent;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Handles input related events for the {@link GodotRenderView} view.
  */
 public class GodotInputHandler implements InputDeviceListener {
-	private final ArrayList<Joystick> mJoysticksDevices = new ArrayList<Joystick>();
-
 	private final GodotRenderView mRenderView;
 	private final InputManagerCompat mInputManager;
 
+	private final String tag = this.getClass().getSimpleName();
+
+	private final SparseIntArray mJoystickIds = new SparseIntArray(4);
+	private final SparseArray<Joystick> mJoysticksDevices = new SparseArray<Joystick>(4);
+
 	public GodotInputHandler(GodotRenderView godotView) {
 		mRenderView = godotView;
 		mInputManager = InputManagerCompat.Factory.getInputManager(mRenderView.getView().getContext());
@@ -82,19 +91,20 @@ public class GodotInputHandler implements InputDeviceListener {
 
 		if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
 			return false;
-		};
+		}
 
 		int source = event.getSource();
 		if (isKeyEvent_GameDevice(source)) {
-			final int button = getGodotButton(keyCode);
-			final int device_id = findJoystickDevice(event.getDeviceId());
-
 			// Check if the device exists
-			if (device_id > -1) {
+			final int deviceId = event.getDeviceId();
+			if (mJoystickIds.indexOfKey(deviceId) >= 0) {
+				final int button = getGodotButton(keyCode);
+				final int godotJoyId = mJoystickIds.get(deviceId);
+
 				queueEvent(new Runnable() {
 					@Override
 					public void run() {
-						GodotLib.joybutton(device_id, button, false);
+						GodotLib.joybutton(godotJoyId, button, false);
 					}
 				});
 			}
@@ -107,7 +117,7 @@ public class GodotInputHandler implements InputDeviceListener {
 					GodotLib.key(keyCode, scanCode, chr, false);
 				}
 			});
-		};
+		}
 
 		return true;
 	}
@@ -122,24 +132,25 @@ public class GodotInputHandler implements InputDeviceListener {
 
 		if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
 			return false;
-		};
+		}
 
 		int source = event.getSource();
 		//Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD)));
 
+		final int deviceId = event.getDeviceId();
+		// Check if source is a game device and that the device is a registered gamepad
 		if (isKeyEvent_GameDevice(source)) {
 			if (event.getRepeatCount() > 0) // ignore key echo
 				return true;
 
-			final int button = getGodotButton(keyCode);
-			final int device_id = findJoystickDevice(event.getDeviceId());
+			if (mJoystickIds.indexOfKey(deviceId) >= 0) {
+				final int button = getGodotButton(keyCode);
+				final int godotJoyId = mJoystickIds.get(deviceId);
 
-			// Check if the device exists
-			if (device_id > -1) {
 				queueEvent(new Runnable() {
 					@Override
 					public void run() {
-						GodotLib.joybutton(device_id, button, true);
+						GodotLib.joybutton(godotJoyId, button, true);
 					}
 				});
 			}
@@ -152,7 +163,7 @@ public class GodotInputHandler implements InputDeviceListener {
 					GodotLib.key(keyCode, scanCode, chr, true);
 				}
 			});
-		};
+		}
 
 		return true;
 	}
@@ -203,38 +214,52 @@ public class GodotInputHandler implements InputDeviceListener {
 	}
 
 	public boolean onGenericMotionEvent(MotionEvent event) {
-		if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
-			final int device_id = findJoystickDevice(event.getDeviceId());
-
+		if (event.isFromSource(InputDevice.SOURCE_JOYSTICK) && event.getAction() == MotionEvent.ACTION_MOVE) {
 			// Check if the device exists
-			if (device_id > -1) {
-				Joystick joy = mJoysticksDevices.get(device_id);
-
-				for (int i = 0; i < joy.axes.size(); i++) {
-					InputDevice.MotionRange range = joy.axes.get(i);
-					final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
-					final int idx = i;
-					queueEvent(new Runnable() {
-						@Override
-						public void run() {
-							GodotLib.joyaxis(device_id, idx, value);
-						}
-					});
+			final int deviceId = event.getDeviceId();
+			if (mJoystickIds.indexOfKey(deviceId) >= 0) {
+				final int godotJoyId = mJoystickIds.get(deviceId);
+				Joystick joystick = mJoysticksDevices.get(deviceId);
+
+				for (int i = 0; i < joystick.axes.size(); i++) {
+					final int axis = joystick.axes.get(i);
+					final float value = event.getAxisValue(axis);
+					/**
+					 * As all axes are polled for each event, only fire an axis event if the value has actually changed.
+					 * Prevents flooding Godot with repeated events.
+					 */
+					if (joystick.axesValues.indexOfKey(axis) < 0 || (float)joystick.axesValues.get(axis) != value) {
+						// save value to prevent repeats
+						joystick.axesValues.put(axis, value);
+						final int godotAxisIdx = i;
+						queueEvent(new Runnable() {
+							@Override
+							public void run() {
+								GodotLib.joyaxis(godotJoyId, godotAxisIdx, value);
+								//Log.i(tag, "GodotLib.joyaxis("+godotJoyId+", "+godotAxisIdx+", "+value+");");
+							}
+						});
+					}
 				}
 
-				for (int i = 0; i < joy.hats.size(); i += 2) {
-					final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
-					final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis()));
-					queueEvent(new Runnable() {
-						@Override
-						public void run() {
-							GodotLib.joyhat(device_id, hatX, hatY);
-						}
-					});
+				if (joystick.hasAxisHat) {
+					final int hatX = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_X));
+					final int hatY = Math.round(event.getAxisValue(MotionEvent.AXIS_HAT_Y));
+					if (joystick.hatX != hatX || joystick.hatY != hatY) {
+						joystick.hatX = hatX;
+						joystick.hatY = hatY;
+						queueEvent(new Runnable() {
+							@Override
+							public void run() {
+								GodotLib.joyhat(godotJoyId, hatX, hatY);
+								//Log.i(tag, "GodotLib.joyhat("+godotJoyId+", "+hatX+", "+hatY+");");
+							}
+						});
+					}
 				}
 				return true;
 			}
-		} else if ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) {
+		} else if (event.isFromSource(InputDevice.SOURCE_STYLUS)) {
 			final float x = event.getX();
 			final float y = event.getY();
 			final int type = event.getAction();
@@ -245,6 +270,7 @@ public class GodotInputHandler implements InputDeviceListener {
 				}
 			});
 			return true;
+
 		} else if (event.isFromSource(InputDevice.SOURCE_MOUSE) || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
 			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 				return handleMouseEvent(event);
@@ -266,67 +292,98 @@ public class GodotInputHandler implements InputDeviceListener {
 		}
 	}
 
+	private int assignJoystickIdNumber(int deviceId) {
+		int godotJoyId = 0;
+		while (mJoystickIds.indexOfValue(godotJoyId) >= 0) {
+			godotJoyId++;
+		}
+		mJoystickIds.put(deviceId, godotJoyId);
+		return godotJoyId;
+	}
+
 	@Override
 	public void onInputDeviceAdded(int deviceId) {
-		int id = findJoystickDevice(deviceId);
-
 		// Check if the device has not been already added
-		if (id < 0) {
-			InputDevice device = mInputManager.getInputDevice(deviceId);
-			//device can be null if deviceId is not found
-			if (device != null) {
-				int sources = device.getSources();
-				if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
-						((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) {
-					id = mJoysticksDevices.size();
-
-					Joystick joy = new Joystick();
-					joy.device_id = deviceId;
-					joy.name = device.getName();
-					joy.axes = new ArrayList<InputDevice.MotionRange>();
-					joy.hats = new ArrayList<InputDevice.MotionRange>();
-
-					List<InputDevice.MotionRange> ranges = device.getMotionRanges();
-					Collections.sort(ranges, new RangeComparator());
-
-					for (InputDevice.MotionRange range : ranges) {
-						if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
-							joy.hats.add(range);
-						} else {
-							joy.axes.add(range);
-						}
-					}
 
-					mJoysticksDevices.add(joy);
+		if (mJoystickIds.indexOfKey(deviceId) >= 0) {
+			return;
+		}
+
+		InputDevice device = mInputManager.getInputDevice(deviceId);
+		//device can be null if deviceId is not found
+		if (device == null) {
+			return;
+		}
+
+		int sources = device.getSources();
+
+		// Device may not be a joystick or gamepad
+		if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD &&
+				(sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
+			return;
+		}
+
+		// Assign first available number. Re-use numbers where possible.
+		final int id = assignJoystickIdNumber(deviceId);
+
+		final Joystick joystick = new Joystick();
+		joystick.device_id = deviceId;
+		joystick.name = device.getName();
+
+		//Helps with creating new joypad mappings.
+		Log.i(tag, "=== New Input Device: " + joystick.name);
 
-					final int device_id = id;
-					final String name = joy.name;
-					queueEvent(new Runnable() {
-						@Override
-						public void run() {
-							GodotLib.joyconnectionchanged(device_id, true, name);
-						}
-					});
+		Set<Integer> already = new HashSet<Integer>();
+		for (InputDevice.MotionRange range : device.getMotionRanges()) {
+			boolean isJoystick = range.isFromSource(InputDevice.SOURCE_JOYSTICK);
+			boolean isGamepad = range.isFromSource(InputDevice.SOURCE_GAMEPAD);
+			//Log.i(tag, "axis: "+range.getAxis()+ ", isJoystick: "+isJoystick+", isGamepad: "+isGamepad);
+			if (!isJoystick && !isGamepad) {
+				continue;
+			}
+			final int axis = range.getAxis();
+			if (axis == MotionEvent.AXIS_HAT_X || axis == MotionEvent.AXIS_HAT_Y) {
+				joystick.hasAxisHat = true;
+			} else {
+				if (!already.contains(axis)) {
+					already.add(axis);
+					joystick.axes.add(axis);
+				} else {
+					Log.w(tag, " - DUPLICATE AXIS VALUE IN LIST: " + axis);
 				}
 			}
 		}
+		Collections.sort(joystick.axes);
+		for (int idx = 0; idx < joystick.axes.size(); idx++) {
+			//Helps with creating new joypad mappings.
+			Log.i(tag, " - Mapping Android axis " + joystick.axes.get(idx) + " to Godot axis " + idx);
+		}
+		mJoysticksDevices.put(deviceId, joystick);
+
+		queueEvent(new Runnable() {
+			@Override
+			public void run() {
+				GodotLib.joyconnectionchanged(id, true, joystick.name);
+			}
+		});
 	}
 
 	@Override
 	public void onInputDeviceRemoved(int deviceId) {
-		final int device_id = findJoystickDevice(deviceId);
-
-		// Check if the evice has not been already removed
-		if (device_id > -1) {
-			mJoysticksDevices.remove(device_id);
-
-			queueEvent(new Runnable() {
-				@Override
-				public void run() {
-					GodotLib.joyconnectionchanged(device_id, false, "");
-				}
-			});
+		// Check if the device has not been already removed
+		if (mJoystickIds.indexOfKey(deviceId) < 0) {
+			return;
 		}
+		final int godotJoyId = mJoystickIds.get(deviceId);
+		mJoystickIds.delete(deviceId);
+		mJoysticksDevices.delete(deviceId);
+
+		queueEvent(new Runnable() {
+			@Override
+			public void run() {
+				GodotLib.joyconnectionchanged(godotJoyId, false, "");
+			}
+		});
 	}
 
 	@Override
@@ -407,16 +464,6 @@ public class GodotInputHandler implements InputDeviceListener {
 		return button;
 	}
 
-	private int findJoystickDevice(int device_id) {
-		for (int i = 0; i < mJoysticksDevices.size(); i++) {
-			if (mJoysticksDevices.get(i).device_id == device_id) {
-				return i;
-			}
-		}
-
-		return -1;
-	}
-
 	private boolean handleMouseEvent(final MotionEvent event) {
 		switch (event.getActionMasked()) {
 			case MotionEvent.ACTION_HOVER_ENTER:

+ 10 - 3
platform/android/java/lib/src/org/godotengine/godot/input/Joystick.java

@@ -30,9 +30,10 @@
 
 package org.godotengine.godot.input;
 
-import android.view.InputDevice.MotionRange;
+import android.util.SparseArray;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * POJO class to represent a Joystick input device.
@@ -40,6 +41,12 @@ import java.util.ArrayList;
 class Joystick {
 	int device_id;
 	String name;
-	ArrayList<MotionRange> axes;
-	ArrayList<MotionRange> hats;
+	List<Integer> axes = new ArrayList<Integer>();
+	protected boolean hasAxisHat = false;
+	/*
+	 * Keep track of values so we can prevent flooding the engine with useless events.
+	 */
+	protected final SparseArray axesValues = new SparseArray<Float>(4);
+	protected int hatX;
+	protected int hatY;
 }