Browse Source

More input refactor to accomodate virtual axes, gamepads and multiple input devices

Marko Pintera 11 years ago
parent
commit
0b6f4674c7

+ 43 - 48
BansheeCore/Include/BsInput.h

@@ -29,6 +29,17 @@ namespace BansheeEngine
 			ToggledOff /**< Button has been released this frame. */
 			ToggledOff /**< Button has been released this frame. */
 		};
 		};
 
 
+		/**
+		 * @brief	Contains axis and device data per device
+		 */
+		struct DeviceData
+		{
+			DeviceData();
+
+			Vector<RawAxisState> axes;
+			ButtonState keyStates[BC_Count];
+		};
+
 	public:
 	public:
 		Input();
 		Input();
 		~Input();
 		~Input();
@@ -89,75 +100,58 @@ namespace BansheeEngine
 		void _update();
 		void _update();
 
 
 		/**
 		/**
-		 * @brief	Returns smoothed mouse/joystick input in the horizontal axis.
+		 * @brief	Returns value of the specified input axis in range [-1.0, 1.0].
 		 *
 		 *
-		 * @return	The horizontal axis value ranging [-1.0f, 1.0f].
+		 * @param	type		Type of axis to query. Usually a type from InputAxis but can be a custom value.
+		 * @param	deviceIdx	Index of the device in case more than one is hooked up (0 - primary).
+		 * @param	smooth		Should the returned value be smoothed.
 		 */
 		 */
-		float getHorizontalAxis() const;
+		float getAxisValue(UINT32 type, UINT32 deviceIdx = 0, bool smooth = false) const;
 
 
 		/**
 		/**
-		 * @brief	Returns smoothed mouse/joystick input in the vertical axis.
+		 * @brief	Query if the provided button is currently being held (this frame or previous frames).
 		 *
 		 *
-		 * @return	The vertical axis value ranging [-1.0f, 1.0f].
+		 * @param	keyCode		Code of the button to query.
+		 * @param	deviceIdx	Device to query the button on (0 - primary).
 		 */
 		 */
-		float getVerticalAxis() const;
+		bool isButtonHeld(ButtonCode keyCode, UINT32 deviceIdx = 0) const;
 
 
 		/**
 		/**
-		 * @brief	Returns value of the specified input axis in range [-1.0, 1.0].
+		 * @brief	Query if the provided button is currently being released (one true for one frame).
 		 *
 		 *
-		 * @param	device		Device from which to query the axis.
-		 * @param	type		Type of axis to query.
-		 * @param	deviceIdx	Index of the device in case more than one is hooked up.
-		 * @param	smooth		Should the returned value be smoothed.
+		 * @param	keyCode		Code of the button to query.
+		 * @param	deviceIdx	Device to query the button on (0 - primary).
 		 */
 		 */
-		float getAxisValue(AxisDevice device, AxisType type, UINT32 deviceIdx = 0, bool smooth = false);
+		bool isButtonUp(ButtonCode keyCode, UINT32 deviceIdx = 0) const;
 
 
 		/**
 		/**
-		 * @brief	Query if the provided button is currently being held (this frame or previous frames).
+		 * @brief	Query if the provided button is currently being pressed (one true for one frame).
+		 *
+		 * @param	keyCode		Code of the button to query.
+		 * @param	deviceIdx	Device to query the button on (0 - primary).
 		 */
 		 */
-		bool isButtonHeld(ButtonCode keyCode) const;
+		bool isButtonDown(ButtonCode keyCode, UINT32 deviceIdx = 0) const;
 
 
+	private:
 		/**
 		/**
-		 * @brief	Query if the provided button is currently being released (one true for one frame).
+		 * @brief	Triggered by input handler when a button is pressed.
 		 */
 		 */
-		bool isButtonUp(ButtonCode keyCode) const;
+		void buttonDown(UINT32 deviceIdx, ButtonCode code, UINT64 timestamp);
 
 
 		/**
 		/**
-		 * @brief	Query if the provided button is currently being pressed (one true for one frame).
+		 * @brief	Triggered by input handler when a button is released.
 		 */
 		 */
-		bool isButtonDown(ButtonCode keyCode) const;
+		void buttonUp(UINT32 deviceIdx, ButtonCode code, UINT64 timestamp);
 
 
 		/**
 		/**
-		 * @brief	Returns mouse cursor position.
+		 * @brief	Triggered by input handler when a single character is input.
 		 */
 		 */
-		Vector2I getCursorPosition() const { return mMouseAbsPos; }
-	private:
-		std::shared_ptr<RawInputHandler> mRawInputHandler;
-		std::shared_ptr<OSInputHandler> mOSInputHandler;
-
-		float mSmoothHorizontalAxis;
-		float mSmoothVerticalAxis;
-
-		float* mHorizontalHistoryBuffer;
-		float* mVerticalHistoryBuffer;
-		float* mTimesHistoryBuffer;
-		int	mCurrentBufferIdx;
-
-		Vector2I mMouseLastRel;
-		Vector2I mMouseAbsPos;
-
-		RawAxisState mAxes[RawInputAxis::Count];
-		ButtonState mKeyState[BC_Count];
-
-		void buttonDown(ButtonCode code, UINT64 timestamp);
-		void buttonUp(ButtonCode code, UINT64 timestamp);
-
 		void charInput(UINT32 chr);
 		void charInput(UINT32 chr);
 
 
 		/**
 		/**
-		 * @brief	Raw mouse/joystick axis input.
+		 * @brief	Triggered by input handler when a mouse/joystick axis is moved.
 		 */
 		 */
-		void axisMoved(const RawAxisState& state, RawInputAxis axis);
+		void axisMoved(UINT32 deviceIdx, const RawAxisState& state, UINT32 axis);
 
 
 		/**
 		/**
 		 * @brief	Cursor movement as OS reports it. Used for screen cursor position.
 		 * @brief	Cursor movement as OS reports it. Used for screen cursor position.
@@ -184,16 +178,17 @@ namespace BansheeEngine
 		 */
 		 */
 		void inputCommandEntered(InputCommandType commandType);
 		void inputCommandEntered(InputCommandType commandType);
 
 
-		/**
-		 * @brief	Updates the axis input values that need smoothing.
-		 */
-		void updateSmoothInput();
-
 		/**
 		/**
 		 * @brief	Called when window in focus changes, as reported by the OS.
 		 * @brief	Called when window in focus changes, as reported by the OS.
 		 */
 		 */
 		void inputWindowChanged(RenderWindow& win);
 		void inputWindowChanged(RenderWindow& win);
 
 
+	private:
+		std::shared_ptr<RawInputHandler> mRawInputHandler;
+		std::shared_ptr<OSInputHandler> mOSInputHandler;
+
+		Vector<DeviceData> mDevices;
+
 		/************************************************************************/
 		/************************************************************************/
 		/* 								STATICS		                      		*/
 		/* 								STATICS		                      		*/
 		/************************************************************************/
 		/************************************************************************/

+ 12 - 7
BansheeCore/Include/BsInputFwd.h

@@ -241,6 +241,7 @@ namespace BansheeEngine
 
 
 		ButtonCode buttonCode; /**< Button code this event is referring to. */
 		ButtonCode buttonCode; /**< Button code this event is referring to. */
 		UINT64 timestamp; /**< Timestamp in ticks when the event happened. */
 		UINT64 timestamp; /**< Timestamp in ticks when the event happened. */
+		UINT32 deviceIdx; /**< Index of the device that the event happened on. */
 
 
 		/**
 		/**
 		 * @brief	Query is the pressed button a keyboard button.
 		 * @brief	Query is the pressed button a keyboard button.
@@ -309,7 +310,7 @@ namespace BansheeEngine
 		}
 		}
 
 
 		Vector2I screenPos; /**< Screen position where the input event occurred. */
 		Vector2I screenPos; /**< Screen position where the input event occurred. */
-		bool buttonStates[PointerEventButton::Count]; /**< States of the pointer buttons (e.g. mouse buttons). */
+		bool buttonStates[(UINT32)PointerEventButton::Count]; /**< States of the pointer buttons (e.g. mouse buttons). */
 		PointerEventButton button; /**< Button that triggered the pointer event. Might be irrelevant 
 		PointerEventButton button; /**< Button that triggered the pointer event. Might be irrelevant 
 										depending on event type. (e.g. move events don't correspond to a button. */
 										depending on event type. (e.g. move events don't correspond to a button. */
 		PointerEventType type; /**< Type of the pointer event. */
 		PointerEventType type; /**< Type of the pointer event. */
@@ -337,7 +338,7 @@ namespace BansheeEngine
 	};
 	};
 
 
 	/**
 	/**
-	 * @brief	Type of special input command types.
+	 * @brief	Types of special input commands.
 	 */
 	 */
 	enum class InputCommandType
 	enum class InputCommandType
 	{
 	{
@@ -376,10 +377,11 @@ namespace BansheeEngine
 	};
 	};
 
 
 	/**
 	/**
-	 * @brief	Types of input devices that can return analog axis data.
+	 * @brief	Types of input devices.
 	 */
 	 */
-	enum class AxisDevice
+	enum class InputDevice
 	{
 	{
+		Keyboard,
 		Mouse,
 		Mouse,
 		Gamepad,
 		Gamepad,
 		Count // Keep at end
 		Count // Keep at end
@@ -388,10 +390,13 @@ namespace BansheeEngine
 	/**
 	/**
 	 * @brief	Common input axis types.
 	 * @brief	Common input axis types.
 	 */
 	 */
-	enum class AxisType
+	enum class InputAxis
 	{
 	{
-		MainX, /**< Mouse X, Gamepad left stick X */
-		MainY, /**< Mouse Y, Gamepad left stick Y */
+		MouseX, /**< Mouse axis X. */
+		MouseY, /**< Mouse axis Y. */
+		MouseZ, /**< Mouse wheel/scroll axis. */
+		LeftStickX, /**< Gamepad left stick X */
+		LeftStickY, /**<  Gamepad left stick Y */
 		RightStickX, /**< Gamepad right stick X */
 		RightStickX, /**< Gamepad right stick X */
 		RightStickY, /**< Gamepad right stick Y */
 		RightStickY, /**< Gamepad right stick Y */
 		LeftTrigger, /**< Gamepad left trigger */
 		LeftTrigger, /**< Gamepad left trigger */

+ 13 - 34
BansheeCore/Include/BsRawInputHandler.h

@@ -14,34 +14,12 @@ namespace BansheeEngine
 	 */
 	 */
 	struct RawAxisState
 	struct RawAxisState
 	{
 	{
-		Vector2I rel;
-		Vector2I abs;
-	};
+		RawAxisState()
+		 :rel(0.0f), abs(0.0f)
+		{ }
 
 
-	/**
-	 * @brief	Different types of input axes.
-	 */
-	enum class RawInputAxis
-	{
-		Mouse_XY,
-		Mouse_Z,
-		Joy_1,
-		Joy_2,
-		Joy_3,
-		Joy_4,
-		Joy_5,
-		Joy_6,
-		Joy_7,
-		Joy_8,
-		Joy_9,
-		Joy_10,
-		Joy_11,
-		Joy_12,
-		Joy_13,
-		Joy_14,
-		Joy_15,
-		Joy_16,
-		Count
+		float rel;
+		float abs;
 	};
 	};
 
 
 	/**
 	/**
@@ -59,22 +37,23 @@ namespace BansheeEngine
 
 
 		/**
 		/**
 		 * @brief	Triggered when user presses a button. Parameters
 		 * @brief	Triggered when user presses a button. Parameters
-		 * 			include button code of the pressed button, and a timestamp of
-		 * 			the button press event.
+		 * 			include device index, button code of the pressed button, 
+		 *			and a timestamp of the button press event.
 		 */
 		 */
-		Event<void(ButtonCode, UINT64)> onButtonDown;
+		Event<void(UINT32, ButtonCode, UINT64)> onButtonDown;
 
 
 		/**
 		/**
 		 * @brief	Triggered when user releases a button. Parameters
 		 * @brief	Triggered when user releases a button. Parameters
-		 * 			include button code of the released button, and a timestamp of
-		 * 			the button release event.
+		 * 			include device index, button code of the released button, 
+		 *			and a timestamp of the button release event.
 		 */
 		 */
-		Event<void(ButtonCode, UINT64)> onButtonUp;
+		Event<void(UINT32, ButtonCode, UINT64)> onButtonUp;
 
 
 		/**
 		/**
 		 * @brief	Triggered whenever the specified axis state changes.
 		 * @brief	Triggered whenever the specified axis state changes.
+		 *			Parameters include device index, axis state data, and axis type.
 		 */
 		 */
-		Event<void(const RawAxisState&, RawInputAxis)> onAxisMoved;
+		Event<void(UINT32, const RawAxisState&, UINT32)> onAxisMoved;
 
 
 		/**
 		/**
 		 * @brief	Called once per frame. Capture input here if needed.
 		 * @brief	Called once per frame. Capture input here if needed.

+ 0 - 2
BansheeCore/Source/BsCoreApplication.cpp

@@ -224,11 +224,9 @@ namespace BansheeEngine
 	{
 	{
 		String name = pluginName;
 		String name = pluginName;
 #if BS_PLATFORM == BS_PLATFORM_LINUX
 #if BS_PLATFORM == BS_PLATFORM_LINUX
-		// dlopen() does not add .so to the filename, like windows does for .dll
 		if (name.substr(name.length() - 3, 3) != ".so")
 		if (name.substr(name.length() - 3, 3) != ".so")
 			name += ".so";
 			name += ".so";
 #elif BS_PLATFORM == BS_PLATFORM_APPLE
 #elif BS_PLATFORM == BS_PLATFORM_APPLE
-		// dlopen() does not add .dylib to the filename, like windows does for .dll
 		if (name.substr(name.length() - 6, 6) != ".dylib")
 		if (name.substr(name.length() - 6, 6) != ".dylib")
 			name += ".dylib";
 			name += ".dylib";
 #elif BS_PLATFORM == BS_PLATFORM_WIN32
 #elif BS_PLATFORM == BS_PLATFORM_WIN32

+ 63 - 93
BansheeCore/Source/BsInput.cpp

@@ -12,23 +12,14 @@ namespace BansheeEngine
 	const int Input::HISTORY_BUFFER_SIZE = 10; // Size of buffer used for input smoothing
 	const int Input::HISTORY_BUFFER_SIZE = 10; // Size of buffer used for input smoothing
 	const float Input::WEIGHT_MODIFIER = 0.5f;
 	const float Input::WEIGHT_MODIFIER = 0.5f;
 
 
+	Input::DeviceData::DeviceData()
+	{
+		for (int i = 0; i < BC_Count; i++)
+			keyStates[i] = ButtonState::Off;
+	}
+
 	Input::Input()
 	Input::Input()
-		:mSmoothHorizontalAxis(0.0f), mSmoothVerticalAxis(0.0f), mCurrentBufferIdx(0), mMouseLastRel(0, 0), mRawInputHandler(nullptr)
 	{ 
 	{ 
-		mHorizontalHistoryBuffer = bs_newN<float>(HISTORY_BUFFER_SIZE);
-		mVerticalHistoryBuffer = bs_newN<float>(HISTORY_BUFFER_SIZE);
-		mTimesHistoryBuffer = bs_newN<float>(HISTORY_BUFFER_SIZE);
-
-		for(int i = 0; i < HISTORY_BUFFER_SIZE; i++)
-		{
-			mHorizontalHistoryBuffer[i] = 0.0f;
-			mVerticalHistoryBuffer[i] = 0.0f;
-			mTimesHistoryBuffer[i] = 0.0f;
-		}
-
-		for(int i = 0; i < BC_Count; i++)
-			mKeyState[i] = ButtonState::Off;
-
 		mOSInputHandler = bs_shared_ptr<OSInputHandler>();
 		mOSInputHandler = bs_shared_ptr<OSInputHandler>();
 
 
 		mOSInputHandler->onCharInput.connect(std::bind(&Input::charInput, this, _1));
 		mOSInputHandler->onCharInput.connect(std::bind(&Input::charInput, this, _1));
@@ -42,11 +33,7 @@ namespace BansheeEngine
 	}
 	}
 
 
 	Input::~Input()
 	Input::~Input()
-	{
-		bs_deleteN(mHorizontalHistoryBuffer, HISTORY_BUFFER_SIZE);
-		bs_deleteN(mVerticalHistoryBuffer, HISTORY_BUFFER_SIZE);
-		bs_deleteN(mTimesHistoryBuffer, HISTORY_BUFFER_SIZE);
-	}
+	{ }
 
 
 	void Input::_registerRawInputHandler(std::shared_ptr<RawInputHandler> inputHandler)
 	void Input::_registerRawInputHandler(std::shared_ptr<RawInputHandler> inputHandler)
 	{
 	{
@@ -56,10 +43,10 @@ namespace BansheeEngine
 
 
 			if(mRawInputHandler != nullptr)
 			if(mRawInputHandler != nullptr)
 			{
 			{
-				mRawInputHandler->onButtonDown.connect(std::bind(&Input::buttonDown, this, _1, _2));
-				mRawInputHandler->onButtonUp.connect(std::bind(&Input::buttonUp, this, _1, _2));
+				mRawInputHandler->onButtonDown.connect(std::bind(&Input::buttonDown, this, _1, _2, _3));
+				mRawInputHandler->onButtonUp.connect(std::bind(&Input::buttonUp, this, _1, _2, _3));
 
 
-				mRawInputHandler->onAxisMoved.connect(std::bind(&Input::axisMoved, this, _1, _2));
+				mRawInputHandler->onAxisMoved.connect(std::bind(&Input::axisMoved, this, _1, _2, _3));
 			}
 			}
 		}
 		}
 	}
 	}
@@ -68,12 +55,16 @@ namespace BansheeEngine
 	{
 	{
 		// Toggle states only remain active for a single frame before they are transitioned
 		// Toggle states only remain active for a single frame before they are transitioned
 		// into permanent state
 		// into permanent state
-		for(UINT32 i = 0; i < BC_Count; i++)
+
+		for (auto& deviceData : mDevices)
 		{
 		{
-			if(mKeyState[i] == ButtonState::ToggledOff)
-				mKeyState[i] = ButtonState::Off;
-			else if(mKeyState[i] == ButtonState::ToggledOn)
-				mKeyState[i] = ButtonState::On;
+			for (UINT32 i = 0; i < BC_Count; i++)
+			{
+				if (deviceData.keyStates[i] == ButtonState::ToggledOff)
+					deviceData.keyStates[i] = ButtonState::Off;
+				else if (deviceData.keyStates[i] == ButtonState::ToggledOn)
+					deviceData.keyStates[i] = ButtonState::On;
+			}
 		}
 		}
 
 
 		if(mRawInputHandler == nullptr)
 		if(mRawInputHandler == nullptr)
@@ -92,7 +83,7 @@ namespace BansheeEngine
 		else
 		else
 			mOSInputHandler->_update();
 			mOSInputHandler->_update();
 
 
-		updateSmoothInput();
+		// TODO - Perform smoothing
 	}
 	}
 
 
 	void Input::inputWindowChanged(RenderWindow& win)
 	void Input::inputWindowChanged(RenderWindow& win)
@@ -104,62 +95,68 @@ namespace BansheeEngine
 			mOSInputHandler->_inputWindowChanged(win);
 			mOSInputHandler->_inputWindowChanged(win);
 	}
 	}
 
 
-	void Input::buttonDown(ButtonCode code, UINT64 timestamp)
+	void Input::buttonDown(UINT32 deviceIdx, ButtonCode code, UINT64 timestamp)
 	{
 	{
-		mKeyState[code & 0x0000FFFF] = ButtonState::ToggledOn;
+		while (deviceIdx >= (UINT32)mDevices.size())
+			mDevices.push_back(DeviceData());
+
+		mDevices[deviceIdx].keyStates[code & 0x0000FFFF] = ButtonState::ToggledOn;
 
 
 		if(!onButtonDown.empty())
 		if(!onButtonDown.empty())
 		{
 		{
 			ButtonEvent btnEvent;
 			ButtonEvent btnEvent;
 			btnEvent.buttonCode = code;
 			btnEvent.buttonCode = code;
 			btnEvent.timestamp = timestamp;
 			btnEvent.timestamp = timestamp;
+			btnEvent.deviceIdx = deviceIdx;
 
 
 			onButtonDown(btnEvent);
 			onButtonDown(btnEvent);
 		}
 		}
 	}
 	}
 
 
-	void Input::buttonUp(ButtonCode code, UINT64 timestamp)
+	void Input::buttonUp(UINT32 deviceIdx, ButtonCode code, UINT64 timestamp)
 	{
 	{
-		mKeyState[code & 0x0000FFFF] = ButtonState::ToggledOff;
+		while (deviceIdx >= (UINT32)mDevices.size())
+			mDevices.push_back(DeviceData());
+
+		mDevices[deviceIdx].keyStates[code & 0x0000FFFF] = ButtonState::ToggledOff;
 
 
 		if(!onButtonUp.empty())
 		if(!onButtonUp.empty())
 		{
 		{
 			ButtonEvent btnEvent;
 			ButtonEvent btnEvent;
 			btnEvent.buttonCode = code;
 			btnEvent.buttonCode = code;
 			btnEvent.timestamp = timestamp;
 			btnEvent.timestamp = timestamp;
+			btnEvent.deviceIdx = deviceIdx;
 
 
 			onButtonUp(btnEvent);
 			onButtonUp(btnEvent);
 		}
 		}
 	}
 	}
 
 
-	void Input::axisMoved(const RawAxisState& state, RawInputAxis axis)
+	void Input::axisMoved(UINT32 deviceIdx, const RawAxisState& state, UINT32 axis)
 	{
 	{
-		if(axis == RawInputAxis::Mouse_XY)
-			mMouseLastRel = Vector2I(-state.rel.x, -state.rel.y);
+		while (deviceIdx >= (UINT32)mDevices.size())
+			mDevices.push_back(DeviceData());
 
 
-		mAxes[(int)axis] = state;
+		Vector<RawAxisState>& axes = mDevices[deviceIdx].axes;
+		while (axis >= (UINT32)axes.size())
+			axes.push_back(RawAxisState());
+
+		mDevices[deviceIdx].axes[axis] = state;
 	}
 	}
 
 
 	void Input::cursorMoved(const PointerEvent& event)
 	void Input::cursorMoved(const PointerEvent& event)
 	{
 	{
-		mMouseAbsPos = event.screenPos;
-
 		if(!onPointerMoved.empty())
 		if(!onPointerMoved.empty())
 			onPointerMoved(event);
 			onPointerMoved(event);
 	}
 	}
 
 
 	void Input::cursorPressed(const PointerEvent& event)
 	void Input::cursorPressed(const PointerEvent& event)
 	{
 	{
-		mMouseAbsPos = event.screenPos;
-
 		if(!onPointerPressed.empty())
 		if(!onPointerPressed.empty())
 			onPointerPressed(event);
 			onPointerPressed(event);
 	}
 	}
 
 
 	void Input::cursorReleased(const PointerEvent& event)
 	void Input::cursorReleased(const PointerEvent& event)
 	{
 	{
-		mMouseAbsPos = event.screenPos;
-
 		if(!onPointerReleased.empty())
 		if(!onPointerReleased.empty())
 			onPointerReleased(event);
 			onPointerReleased(event);
 	}
 	}
@@ -187,70 +184,43 @@ namespace BansheeEngine
 		}
 		}
 	}
 	}
 
 
-	float Input::getHorizontalAxis() const
+	float Input::getAxisValue(UINT32 type, UINT32 deviceIdx, bool smooth) const
 	{
 	{
-		return mSmoothHorizontalAxis;
-	}
+		if (deviceIdx >= (UINT32)mDevices.size())
+			return 0.0f;
 
 
-	float Input::getVerticalAxis() const
-	{
-		return mSmoothVerticalAxis;
-	}
+		// TODO - Smooth parameter is ignored
 
 
-	float Input::getAxisValue(AxisDevice device, AxisType type, UINT32 deviceIdx, bool smooth)
-	{
-		// TODO
-		return 0.0f;
-	}
+		const Vector<RawAxisState>& axes = mDevices[deviceIdx].axes;
+		if (type >= (UINT32)axes.size())
+			return 0.0f;
 
 
-	bool Input::isButtonHeld(ButtonCode button) const
-	{
-		return mKeyState[button & 0x0000FFFF] == ButtonState::On || mKeyState[button & 0x0000FFFF] == ButtonState::ToggledOn;
+		return axes[type].abs;
 	}
 	}
 
 
-	bool Input::isButtonUp(ButtonCode button) const
+	bool Input::isButtonHeld(ButtonCode button, UINT32 deviceIdx) const
 	{
 	{
-		return mKeyState[button & 0x0000FFFF] == ButtonState::ToggledOff;
-	}
+		if (deviceIdx >= (UINT32)mDevices.size())
+			return false;
 
 
-	bool Input::isButtonDown(ButtonCode button) const
-	{
-		return mKeyState[button & 0x0000FFFF] == ButtonState::ToggledOn;
+		return mDevices[deviceIdx].keyStates[button & 0x0000FFFF] == ButtonState::On || 
+			mDevices[deviceIdx].keyStates[button & 0x0000FFFF] == ButtonState::ToggledOn;
 	}
 	}
 
 
-	void Input::updateSmoothInput()
+	bool Input::isButtonUp(ButtonCode button, UINT32 deviceIdx) const
 	{
 	{
-		float currentTime = gTime().getTime();
-
-		mHorizontalHistoryBuffer[mCurrentBufferIdx] = (float)mMouseLastRel.x;
-		mVerticalHistoryBuffer[mCurrentBufferIdx] = (float)mMouseLastRel.y;
-		mTimesHistoryBuffer[mCurrentBufferIdx] = currentTime;
-
-		int i = 0;
-		int idx = mCurrentBufferIdx;
-		float currentWeight = 1.0f;
-		float horizontalTotal = 0.0f;
-		float verticalTotal = 0.0f;
-		while(i < HISTORY_BUFFER_SIZE)
-		{
-			float timeWeight = 1.0f - (currentTime - mTimesHistoryBuffer[idx]) * 10.0f;
-			if(timeWeight < 0.0f)
-				timeWeight = 0.0f;
-
-			horizontalTotal += mHorizontalHistoryBuffer[idx] * currentWeight * timeWeight;
-			verticalTotal += mVerticalHistoryBuffer[idx] * currentWeight * timeWeight;
+		if (deviceIdx >= (UINT32)mDevices.size())
+			return false;
 
 
-			currentWeight *= WEIGHT_MODIFIER;
-			idx = (idx + 1) % HISTORY_BUFFER_SIZE;
-			i++;
-		}
-
-		mCurrentBufferIdx = (mCurrentBufferIdx + 1) % HISTORY_BUFFER_SIZE;
+		return mDevices[deviceIdx].keyStates[button & 0x0000FFFF] == ButtonState::ToggledOff;
+	}
 
 
-		mSmoothHorizontalAxis = Math::clamp(horizontalTotal / HISTORY_BUFFER_SIZE, -1.0f, 1.0f);
-		mSmoothVerticalAxis = Math::clamp(verticalTotal / HISTORY_BUFFER_SIZE, -1.0f, 1.0f);
+	bool Input::isButtonDown(ButtonCode button, UINT32 deviceIdx) const
+	{
+		if (deviceIdx >= (UINT32)mDevices.size())
+			return false;
 
 
-		mMouseLastRel = Vector2I(0, 0);
+		return mDevices[deviceIdx].keyStates[button & 0x0000FFFF] == ButtonState::ToggledOn;
 	}
 	}
 
 
 	Input& gInput()
 	Input& gInput()

+ 2 - 2
BansheeEditor/Source/BsDebugCamera.cpp

@@ -77,8 +77,8 @@ namespace BansheeEngine
 
 
 		if(camRotating)
 		if(camRotating)
 		{
 		{
-			mYaw += Degree(gInput().getHorizontalAxis() * ROTATION_SPEED);
-			mPitch += Degree(gInput().getVerticalAxis() * ROTATION_SPEED);
+			mYaw += Degree(gInput().getAxisValue(InputDevice::Mouse, InputAxis::MainX) * ROTATION_SPEED);
+			mPitch += Degree(gInput().getAxisValue(InputDevice::Mouse, InputAxis::MainY) * ROTATION_SPEED);
 
 
 			Quaternion yRot;
 			Quaternion yRot;
 			yRot.fromAxisAngle(Vector3::UNIT_Y, Radian(mYaw));
 			yRot.fromAxisAngle(Vector3::UNIT_Y, Radian(mYaw));

+ 2 - 1
BansheeEditor/Source/BsEditorWidgetContainer.cpp

@@ -8,6 +8,7 @@
 #include "BsInput.h"
 #include "BsInput.h"
 #include "BsGUIWidget.h"
 #include "BsGUIWidget.h"
 #include "BsGUILayout.h"
 #include "BsGUILayout.h"
+#include "BsCursor.h"
 
 
 using namespace std::placeholders;
 using namespace std::placeholders;
 
 
@@ -233,7 +234,7 @@ namespace BansheeEngine
 
 
 			newWindow->widgets().add(*draggedWidget);
 			newWindow->widgets().add(*draggedWidget);
 
 
-			Vector2I mousePos = Input::instance().getCursorPosition();
+			Vector2I mousePos = Cursor::instance().getScreenPosition();
 			newWindow->setPosition(mousePos.x, mousePos.y);
 			newWindow->setPosition(mousePos.x, mousePos.y);
 		}
 		}
 	}
 	}

+ 1 - 1
BansheeEngine/Include/BsGUIManager.h

@@ -197,7 +197,7 @@ namespace BansheeEngine
 		void onPointerDoubleClick(const PointerEvent& event);
 		void onPointerDoubleClick(const PointerEvent& event);
 		void onTextInput(const TextInputEvent& event);
 		void onTextInput(const TextInputEvent& event);
 		void onInputCommandEntered(InputCommandType commandType);
 		void onInputCommandEntered(InputCommandType commandType);
-		void onVirtualButtonDown(const VirtualButton& button);
+		void onVirtualButtonDown(const VirtualButton& button, UINT32 deviceIdx);
 
 
 		void onMouseDragEnded(const PointerEvent& event, DragCallbackInfo& dragInfo);
 		void onMouseDragEnded(const PointerEvent& event, DragCallbackInfo& dragInfo);
 
 

+ 4 - 7
BansheeEngine/Include/BsInputConfiguration.h

@@ -30,16 +30,13 @@ namespace BansheeEngine
 	struct VIRTUAL_AXIS_DESC
 	struct VIRTUAL_AXIS_DESC
 	{
 	{
 		VIRTUAL_AXIS_DESC();
 		VIRTUAL_AXIS_DESC();
-		VIRTUAL_AXIS_DESC(AxisType type, AxisDevice device, float deadZone = 0.0001f, UINT32 deviceIndex = 0, 
-			float sensitivity = 1.0f, bool invert = false, bool smooth = true);
+		VIRTUAL_AXIS_DESC(InputAxis type, float deadZone = 0.0001f, float sensitivity = 1.0f, bool invert = false, bool smooth = true);
 
 
 		float deadZone;
 		float deadZone;
 		float sensitivity;
 		float sensitivity;
 		bool invert;
 		bool invert;
 		bool smooth;
 		bool smooth;
-		AxisDevice device;
-		AxisType type;
-		UINT32 deviceIndex;
+		InputAxis type;
 	};
 	};
 
 
 	/**
 	/**
@@ -99,7 +96,7 @@ namespace BansheeEngine
 	class BS_EXPORT InputConfiguration
 	class BS_EXPORT InputConfiguration
 	{
 	{
 		static const int MAX_NUM_DEVICES_PER_TYPE = 8;
 		static const int MAX_NUM_DEVICES_PER_TYPE = 8;
-		static const int MAX_NUM_DEVICES = (UINT32)AxisDevice::Count * MAX_NUM_DEVICES_PER_TYPE;
+		static const int MAX_NUM_DEVICES = (UINT32)InputDevice::Count * MAX_NUM_DEVICES_PER_TYPE;
 
 
 		struct VirtualButtonData
 		struct VirtualButtonData
 		{
 		{
@@ -117,7 +114,7 @@ namespace BansheeEngine
 
 
 		struct DeviceAxisData
 		struct DeviceAxisData
 		{
 		{
-			VirtualAxisData axes[(UINT32)AxisType::Count];
+			VirtualAxisData axes[(UINT32)InputAxis::Count];
 		};
 		};
 
 
 	public:
 	public:

+ 17 - 11
BansheeEngine/Include/BsVirtualInput.h

@@ -24,10 +24,16 @@ namespace BansheeEngine
 			bool allowRepeat;
 			bool allowRepeat;
 		};
 		};
 
 
+		struct DeviceData
+		{
+			Map<UINT32, ButtonData> cachedStates;
+		};
+
 		struct VirtualButtonEvent
 		struct VirtualButtonEvent
 		{
 		{
 			VirtualButton button;
 			VirtualButton button;
 			ButtonState state;
 			ButtonState state;
+			UINT32 deviceIdx;
 		};
 		};
 
 
 	public:
 	public:
@@ -38,26 +44,26 @@ namespace BansheeEngine
 		void setConfiguration(const std::shared_ptr<InputConfiguration>& input);
 		void setConfiguration(const std::shared_ptr<InputConfiguration>& input);
 		std::shared_ptr<InputConfiguration> getConfiguration() const { return mInputConfiguration; }
 		std::shared_ptr<InputConfiguration> getConfiguration() const { return mInputConfiguration; }
 
 
-		bool isButtonDown(const VirtualButton& button) const;
-		bool isButtonUp(const VirtualButton& button) const;
-		bool isButtonHeld(const VirtualButton& button) const;
+		bool isButtonDown(const VirtualButton& button, UINT32 deviceIdx = 0) const;
+		bool isButtonUp(const VirtualButton& button, UINT32 deviceIdx = 0) const;
+		bool isButtonHeld(const VirtualButton& button, UINT32 deviceIdx = 0) const;
 
 
-		float getAxisValue(const VirtualAxis& axis) const;
+		float getAxisValue(const VirtualAxis& axis, UINT32 deviceIdx = 0) const;
 
 
 		void update();
 		void update();
 
 
-		Event<void(const VirtualButton&)> onButtonDown;
-		Event<void(const VirtualButton&)> onButtonUp;
-		Event<void(const VirtualButton&)> onButtonHeld;
+		Event<void(const VirtualButton&, UINT32 deviceIdx)> onButtonDown;
+		Event<void(const VirtualButton&, UINT32 deviceIdx)> onButtonUp;
+		Event<void(const VirtualButton&, UINT32 deviceIdx)> onButtonHeld;
 	private:
 	private:
 		friend class VirtualButton;
 		friend class VirtualButton;
 
 
+		void buttonDown(const ButtonEvent& event);
+		void buttonUp(const ButtonEvent& event);
+
 		std::shared_ptr<InputConfiguration> mInputConfiguration;
 		std::shared_ptr<InputConfiguration> mInputConfiguration;
-		Map<UINT32, ButtonData> mCachedStates;
+		Vector<DeviceData> mDevices;
 		Queue<VirtualButtonEvent> mEvents;
 		Queue<VirtualButtonEvent> mEvents;
 		UINT32 mActiveModifiers;
 		UINT32 mActiveModifiers;
-
-		void buttonDown(const ButtonEvent& event);
-		void buttonUp(const ButtonEvent& event);
 	};
 	};
 }
 }

+ 2 - 2
BansheeEngine/Source/BsGUIManager.cpp

@@ -77,7 +77,7 @@ namespace BansheeEngine
 		mOnPointerDoubleClick = gInput().onPointerDoubleClick.connect(std::bind(&GUIManager::onPointerDoubleClick, this, _1));
 		mOnPointerDoubleClick = gInput().onPointerDoubleClick.connect(std::bind(&GUIManager::onPointerDoubleClick, this, _1));
 		mOnTextInputConn = gInput().onCharInput.connect(std::bind(&GUIManager::onTextInput, this, _1)); 
 		mOnTextInputConn = gInput().onCharInput.connect(std::bind(&GUIManager::onTextInput, this, _1)); 
 		mOnInputCommandConn = gInput().onInputCommand.connect(std::bind(&GUIManager::onInputCommandEntered, this, _1)); 
 		mOnInputCommandConn = gInput().onInputCommand.connect(std::bind(&GUIManager::onInputCommandEntered, this, _1)); 
-		mOnVirtualButtonDown = VirtualInput::instance().onButtonDown.connect(std::bind(&GUIManager::onVirtualButtonDown, this, _1));
+		mOnVirtualButtonDown = VirtualInput::instance().onButtonDown.connect(std::bind(&GUIManager::onVirtualButtonDown, this, _1, _2));
 
 
 		mWindowGainedFocusConn = RenderWindowManager::instance().onFocusGained.connect(std::bind(&GUIManager::onWindowFocusGained, this, _1));
 		mWindowGainedFocusConn = RenderWindowManager::instance().onFocusGained.connect(std::bind(&GUIManager::onWindowFocusGained, this, _1));
 		mWindowLostFocusConn = RenderWindowManager::instance().onFocusLost.connect(std::bind(&GUIManager::onWindowFocusLost, this, _1));
 		mWindowLostFocusConn = RenderWindowManager::instance().onFocusLost.connect(std::bind(&GUIManager::onWindowFocusLost, this, _1));
@@ -1074,7 +1074,7 @@ namespace BansheeEngine
 		}		
 		}		
 	}
 	}
 
 
-	void GUIManager::onVirtualButtonDown(const VirtualButton& button)
+	void GUIManager::onVirtualButtonDown(const VirtualButton& button, UINT32 deviceIdx)
 	{
 	{
 		mVirtualButtonEvent.setButton(button);
 		mVirtualButtonEvent.setButton(button);
 		
 		

+ 3 - 6
BansheeEngine/Source/BsInputConfiguration.cpp

@@ -17,14 +17,11 @@ namespace BansheeEngine
 	{ }
 	{ }
 
 
 	VIRTUAL_AXIS_DESC::VIRTUAL_AXIS_DESC()
 	VIRTUAL_AXIS_DESC::VIRTUAL_AXIS_DESC()
-		: type(AxisType::MainX), device(AxisDevice::Mouse), deviceIndex(0), deadZone(0.0001f),
-		sensitivity(1.0f), invert(false), smooth(true)
+		: type(InputAxis::MouseX), deadZone(0.0001f), sensitivity(1.0f), invert(false), smooth(true)
 	{ }
 	{ }
 
 
-	VIRTUAL_AXIS_DESC::VIRTUAL_AXIS_DESC(AxisType type, AxisDevice device, float deadZone, UINT32 deviceIndex, 
-		float sensitivity, bool invert, bool smooth)
-		:type(type), device(device), deadZone(deadZone), deviceIndex(deviceIndex), sensitivity(sensitivity),
-		invert(invert), smooth(smooth)
+	VIRTUAL_AXIS_DESC::VIRTUAL_AXIS_DESC(InputAxis type, float deadZone, float sensitivity, bool invert, bool smooth)
+		:type(type), deadZone(deadZone), sensitivity(sensitivity), invert(invert), smooth(smooth)
 	{ }
 	{ }
 
 
 	VirtualButton::VirtualButton()
 	VirtualButton::VirtualButton()

+ 70 - 37
BansheeEngine/Source/BsVirtualInput.cpp

@@ -25,46 +25,60 @@ namespace BansheeEngine
 	{
 	{
 		mInputConfiguration = input;
 		mInputConfiguration = input;
 
 
-		mCachedStates.clear(); // Note: Technically this is slightly wrong as it will
+		// Note: Technically this is slightly wrong as it will
 		// "forget" any buttons currently held down, but shouldn't matter much in practice.
 		// "forget" any buttons currently held down, but shouldn't matter much in practice.
+		for (auto& deviceData : mDevices)
+			deviceData.cachedStates.clear(); 
 	}
 	}
 
 
-	bool VirtualInput::isButtonDown(const VirtualButton& button) const
+	bool VirtualInput::isButtonDown(const VirtualButton& button, UINT32 deviceIdx) const
 	{
 	{
-		auto iterFind = mCachedStates.find(button.buttonIdentifier);
+		if (deviceIdx >= (UINT32)mDevices.size())
+			return false;
 
 
-		if(iterFind != mCachedStates.end())
+		const Map<UINT32, ButtonData>& cachedStates = mDevices[deviceIdx].cachedStates;
+		auto iterFind = cachedStates.find(button.buttonIdentifier);
+
+		if (iterFind != cachedStates.end())
 			return iterFind->second.state == ButtonState::ToggledOn;
 			return iterFind->second.state == ButtonState::ToggledOn;
 		
 		
 		return false;
 		return false;
 	}
 	}
 
 
-	bool VirtualInput::isButtonUp(const VirtualButton& button) const
+	bool VirtualInput::isButtonUp(const VirtualButton& button, UINT32 deviceIdx) const
 	{
 	{
-		auto iterFind = mCachedStates.find(button.buttonIdentifier);
+		if (deviceIdx >= (UINT32)mDevices.size())
+			return false;
+
+		const Map<UINT32, ButtonData>& cachedStates = mDevices[deviceIdx].cachedStates;
+		auto iterFind = cachedStates.find(button.buttonIdentifier);
 
 
-		if(iterFind != mCachedStates.end())
+		if (iterFind != cachedStates.end())
 			return iterFind->second.state == ButtonState::ToggledOff;
 			return iterFind->second.state == ButtonState::ToggledOff;
 
 
 		return false;
 		return false;
 	}
 	}
 
 
-	bool VirtualInput::isButtonHeld(const VirtualButton& button) const
+	bool VirtualInput::isButtonHeld(const VirtualButton& button, UINT32 deviceIdx) const
 	{
 	{
-		auto iterFind = mCachedStates.find(button.buttonIdentifier);
+		if (deviceIdx >= (UINT32)mDevices.size())
+			return false;
+
+		const Map<UINT32, ButtonData>& cachedStates = mDevices[deviceIdx].cachedStates;
+		auto iterFind = cachedStates.find(button.buttonIdentifier);
 
 
-		if(iterFind != mCachedStates.end())
+		if (iterFind != cachedStates.end())
 			return iterFind->second.state == ButtonState::On || iterFind->second.state == ButtonState::ToggledOn;
 			return iterFind->second.state == ButtonState::On || iterFind->second.state == ButtonState::ToggledOn;
 
 
 		return false;
 		return false;
 	}
 	}
 
 
-	float VirtualInput::getAxisValue(const VirtualAxis& axis) const
+	float VirtualInput::getAxisValue(const VirtualAxis& axis, UINT32 deviceIdx) const
 	{
 	{
 		VIRTUAL_AXIS_DESC axisDesc;
 		VIRTUAL_AXIS_DESC axisDesc;
 		if (mInputConfiguration->_getAxis(axis, axisDesc))
 		if (mInputConfiguration->_getAxis(axis, axisDesc))
 		{
 		{
-			float axisValue = gInput().getAxisValue(axisDesc.device, axisDesc.type, axisDesc.deviceIndex, axisDesc.smooth);
+			float axisValue = gInput().getAxisValue((UINT32)axisDesc.type, deviceIdx, axisDesc.smooth);
 
 
 			if (axisDesc.deadZone > 0.0f)
 			if (axisDesc.deadZone > 0.0f)
 			{
 			{
@@ -88,12 +102,15 @@ namespace BansheeEngine
 
 
 	void VirtualInput::update()
 	void VirtualInput::update()
 	{
 	{
-		for(auto& state : mCachedStates)
+		for (auto& deviceData : mDevices)
 		{
 		{
-			if(state.second.state == ButtonState::ToggledOff)
-				state.second.state = ButtonState::Off;
-			else if(state.second.state == ButtonState::ToggledOn)
-				state.second.state = ButtonState::On;
+			for (auto& state : deviceData.cachedStates)
+			{
+				if (state.second.state == ButtonState::ToggledOff)
+					state.second.state = ButtonState::Off;
+				else if (state.second.state == ButtonState::ToggledOn)
+					state.second.state = ButtonState::On;
+			}
 		}
 		}
 
 
 		bool hasEvents = true;
 		bool hasEvents = true;
@@ -110,12 +127,12 @@ namespace BansheeEngine
 				if(event.state == ButtonState::On)
 				if(event.state == ButtonState::On)
 				{
 				{
 					if(!onButtonDown.empty())
 					if(!onButtonDown.empty())
-						onButtonDown(event.button);
+						onButtonDown(event.button, event.deviceIdx);
 				}
 				}
 				else if(event.state == ButtonState::Off)
 				else if(event.state == ButtonState::Off)
 				{
 				{
 					if(!onButtonUp.empty())
 					if(!onButtonUp.empty())
-						onButtonUp(event.button);
+						onButtonUp(event.button, event.deviceIdx);
 				}
 				}
 
 
 				mEvents.pop();
 				mEvents.pop();
@@ -123,27 +140,33 @@ namespace BansheeEngine
 
 
 			// Queue up any repeatable events
 			// Queue up any repeatable events
 			hasEvents = false;
 			hasEvents = false;
-			
-			for(auto& state : mCachedStates)
+
+			for (auto& deviceData : mDevices)
 			{
 			{
-				if(state.second.state != ButtonState::On)
-					continue;
+				for (auto& state : deviceData.cachedStates)
+				{
+					if (state.second.state != ButtonState::On)
+						continue;
 
 
-				if(!state.second.allowRepeat)
-					continue;
+					if (!state.second.allowRepeat)
+						continue;
 
 
-				UINT64 diff = currentTime - state.second.timestamp;
-				if(diff >= repeatInternal)
-				{
-					state.second.timestamp += repeatInternal;
+					UINT64 diff = currentTime - state.second.timestamp;
+					if (diff >= repeatInternal)
+					{
+						state.second.timestamp += repeatInternal;
+
+						VirtualButtonEvent event;
+						event.button = state.second.button;
+						event.state = ButtonState::On;
+						event.deviceIdx = 0;
 
 
-					VirtualButtonEvent event;
-					event.button = state.second.button;
-					event.state = ButtonState::On;
+						mEvents.push(event);
+						hasEvents = true;
+					}
+				}
 
 
-					mEvents.push(event);
-					hasEvents = true;
-				}				
+				break; // Only repeat the first device. Repeat only makes sense for keyboard which there is only one of.
 			}
 			}
 		}
 		}
 	}
 	}
@@ -162,7 +185,11 @@ namespace BansheeEngine
 			VIRTUAL_BUTTON_DESC btnDesc;
 			VIRTUAL_BUTTON_DESC btnDesc;
 			if(mInputConfiguration->_getButton(event.buttonCode, mActiveModifiers, btn, btnDesc))
 			if(mInputConfiguration->_getButton(event.buttonCode, mActiveModifiers, btn, btnDesc))
 			{
 			{
-				ButtonData& data = mCachedStates[btn.buttonIdentifier];
+				while (event.deviceIdx >= (UINT32)mDevices.size())
+					mDevices.push_back(DeviceData());
+
+				Map<UINT32, ButtonData>& cachedStates = mDevices[event.deviceIdx].cachedStates;
+				ButtonData& data = cachedStates[btn.buttonIdentifier];
 
 
 				data.button = btn;
 				data.button = btn;
 				data.state = ButtonState::ToggledOn;
 				data.state = ButtonState::ToggledOn;
@@ -172,6 +199,7 @@ namespace BansheeEngine
 				VirtualButtonEvent virtualEvent;
 				VirtualButtonEvent virtualEvent;
 				virtualEvent.button = btn;
 				virtualEvent.button = btn;
 				virtualEvent.state = ButtonState::On;
 				virtualEvent.state = ButtonState::On;
+				virtualEvent.deviceIdx = event.deviceIdx;
 
 
 				mEvents.push(virtualEvent);
 				mEvents.push(virtualEvent);
 			}
 			}
@@ -192,7 +220,11 @@ namespace BansheeEngine
 			VIRTUAL_BUTTON_DESC btnDesc;
 			VIRTUAL_BUTTON_DESC btnDesc;
 			if(mInputConfiguration->_getButton(event.buttonCode, mActiveModifiers, btn, btnDesc))
 			if(mInputConfiguration->_getButton(event.buttonCode, mActiveModifiers, btn, btnDesc))
 			{
 			{
-				ButtonData& data = mCachedStates[btn.buttonIdentifier];
+				while (event.deviceIdx >= (UINT32)mDevices.size())
+					mDevices.push_back(DeviceData());
+
+				Map<UINT32, ButtonData>& cachedStates = mDevices[event.deviceIdx].cachedStates;
+				ButtonData& data = cachedStates[btn.buttonIdentifier];
 
 
 				data.button = btn;
 				data.button = btn;
 				data.state = ButtonState::ToggledOff;
 				data.state = ButtonState::ToggledOff;
@@ -202,6 +234,7 @@ namespace BansheeEngine
 				VirtualButtonEvent virtualEvent;
 				VirtualButtonEvent virtualEvent;
 				virtualEvent.button = btn;
 				virtualEvent.button = btn;
 				virtualEvent.state = ButtonState::Off;
 				virtualEvent.state = ButtonState::Off;
+				virtualEvent.deviceIdx = event.deviceIdx;
 
 
 				mEvents.push(virtualEvent);
 				mEvents.push(virtualEvent);
 			}
 			}

+ 54 - 3
BansheeOISInput/Include/BsInputHandlerOIS.h

@@ -7,14 +7,55 @@
 #include <OIS/OISInputManager.h>
 #include <OIS/OISInputManager.h>
 #include <OIS/OISKeyboard.h>
 #include <OIS/OISKeyboard.h>
 #include <OIS/OISMouse.h>
 #include <OIS/OISMouse.h>
+#include <OIS/OISJoystick.h>
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
+	class InputHandlerOIS;
+
+	/**
+	 * @brief	Listens for events from a specific OIS joystick device.
+	 */
+	class BS_OIS_EXPORT GamepadEventListener : public OIS::JoyStickListener
+	{
+	public:
+		GamepadEventListener(InputHandlerOIS* parentHandler, UINT32 joystickIdx);
+
+		/**
+		 * @brief	Called by OIS whenever a gamepad/joystick button is pressed.
+		 */
+		virtual bool buttonPressed(const OIS::JoyStickEvent& arg, int button);
+
+		/**
+		 * @brief	Called by OIS whenever a gamepad/joystick button is released.
+		 */
+		virtual bool buttonReleased(const OIS::JoyStickEvent& arg, int button);
+
+		/**
+		 * @brief	Called by OIS whenever a gamepad/joystick axis is moved.
+		 */
+		virtual bool axisMoved(const OIS::JoyStickEvent& arg, int axis);
+
+	private:
+		UINT32 mGamepadIdx;
+		InputHandlerOIS* mParentHandler;
+	};
+
 	/**
 	/**
 	 * @brief	Raw input handler using OIS library for acquiring input.
 	 * @brief	Raw input handler using OIS library for acquiring input.
 	 */
 	 */
-	class BS_OIS_EXPORT InputHandlerOIS : public RawInputHandler, public OIS::KeyListener, public OIS::MouseListener
+	class BS_OIS_EXPORT InputHandlerOIS : public RawInputHandler, public OIS::KeyListener, 
+		public OIS::MouseListener
 	{
 	{
+		/**
+		 * @brief	Holding data about an active gamepad object.
+		 */
+		struct GamepadData
+		{
+			OIS::JoyStick* gamepad;
+			GamepadEventListener* listener;
+		};
+
 	public:
 	public:
 		InputHandlerOIS(unsigned int hWnd);
 		InputHandlerOIS(unsigned int hWnd);
 		virtual ~InputHandlerOIS();
 		virtual ~InputHandlerOIS();
@@ -62,17 +103,27 @@ namespace BansheeEngine
 		/**
 		/**
 		 * @brief	Converts an OIS key code into engine button code.
 		 * @brief	Converts an OIS key code into engine button code.
 		 */
 		 */
-		ButtonCode keyCodeToButtonCode(OIS::KeyCode keyCode) const;
+		static ButtonCode keyCodeToButtonCode(OIS::KeyCode keyCode);
 
 
 		/**
 		/**
 		 * @brief	Converts an OIS mouse button code into engine button code.
 		 * @brief	Converts an OIS mouse button code into engine button code.
 		 */
 		 */
-		ButtonCode mouseButtonToButtonCode(OIS::MouseButtonID mouseBtn) const;
+		static ButtonCode mouseButtonToButtonCode(OIS::MouseButtonID mouseBtn);
+
+		/**
+		 * @brief	Converts an OIS gamepad button code into engine button code.
+		 */
+		static ButtonCode gamepadButtonToButtonCode(INT32 joystickCode);
 
 
 	private:
 	private:
+		friend class GamepadEventListener;
+
+		static const UINT32 MOUSE_SENSITIVITY; /**< Converts arbitrary mouse axis range to [-1, 1] range. */
+
 		OIS::InputManager* mInputManager;
 		OIS::InputManager* mInputManager;
 		OIS::Mouse*	mMouse;
 		OIS::Mouse*	mMouse;
 		OIS::Keyboard* mKeyboard;
 		OIS::Keyboard* mKeyboard;
+		Vector<GamepadData> mGamepads;
 
 
 		UINT64 mTimestampClockOffset;
 		UINT64 mTimestampClockOffset;
 	};
 	};

+ 104 - 22
BansheeOISInput/Source/BsInputHandlerOIS.cpp

@@ -3,9 +3,49 @@
 #include "OIS/OISException.h"
 #include "OIS/OISException.h"
 #include "BsRenderWindow.h"
 #include "BsRenderWindow.h"
 #include "BsTime.h"
 #include "BsTime.h"
+#include "BsMath.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
+	const UINT32 InputHandlerOIS::MOUSE_SENSITIVITY = 1;
+
+	GamepadEventListener::GamepadEventListener(InputHandlerOIS* parentHandler, UINT32 joystickIdx)
+		:mParentHandler(parentHandler), mGamepadIdx(joystickIdx)
+	{ }
+
+	bool GamepadEventListener::buttonPressed(const OIS::JoyStickEvent& arg, int button)
+	{
+		ButtonCode bc = InputHandlerOIS::gamepadButtonToButtonCode(button);
+
+		mParentHandler->onButtonDown(mGamepadIdx, bc, 0); // TODO - No timestamps
+		return true;
+	}
+
+	bool GamepadEventListener::buttonReleased(const OIS::JoyStickEvent& arg, int button)
+	{
+		ButtonCode bc = InputHandlerOIS::gamepadButtonToButtonCode(button);
+
+		mParentHandler->onButtonUp(mGamepadIdx, bc, 0); // TODO - No timestamps
+		return true;
+	}
+
+	bool GamepadEventListener::axisMoved(const OIS::JoyStickEvent& arg, int axis)
+	{
+		// Move axis values into [-1.0f, 1.0f] range
+		float axisRange = Math::abs((float)OIS::JoyStick::MAX_AXIS) + Math::abs((float)OIS::JoyStick::MIN_AXIS);
+
+		INT32 axisRel = arg.state.mAxes[axis].rel;
+		INT32 axisAbs = arg.state.mAxes[axis].abs;
+
+		RawAxisState axisState;
+		axisState.rel = ((axisRel + OIS::JoyStick::MIN_AXIS) / axisRange) * 2.0f - 1.0f;
+		axisState.abs = ((axisAbs + OIS::JoyStick::MIN_AXIS) / axisRange) * 2.0f - 1.0f;
+
+		mParentHandler->onAxisMoved(mGamepadIdx, axisState, (UINT32)axis);
+
+		return true;
+	}
+
 	InputHandlerOIS::InputHandlerOIS(unsigned int hWnd)
 	InputHandlerOIS::InputHandlerOIS(unsigned int hWnd)
 		:mInputManager(nullptr), mKeyboard(nullptr), mMouse(nullptr), mTimestampClockOffset(0)
 		:mInputManager(nullptr), mKeyboard(nullptr), mMouse(nullptr), mTimestampClockOffset(0)
 	{
 	{
@@ -34,26 +74,48 @@ namespace BansheeEngine
 			std::cout << e.eText << std::endl;
 			std::cout << e.eText << std::endl;
 		}
 		}
 
 
-		mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject(OIS::OISKeyboard, true));
-		mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject(OIS::OISMouse, true));
+		if (mInputManager->getNumberOfDevices(OIS::OISKeyboard) > 0)
+		{
+			mKeyboard = static_cast<OIS::Keyboard*>(mInputManager->createInputObject(OIS::OISKeyboard, true));
+			mKeyboard->setEventCallback(this);
+		}
+
+		if (mInputManager->getNumberOfDevices(OIS::OISMouse) > 0)
+		{
+			mMouse = static_cast<OIS::Mouse*>(mInputManager->createInputObject(OIS::OISMouse, true));
+			mMouse->setEventCallback(this);
+		}
+
+		UINT32 numGamepads = mInputManager->getNumberOfDevices(OIS::OISJoyStick);
+		for (UINT32 i = 0; i < numGamepads; i++)
+		{
+			mGamepads.push_back(GamepadData());
+			GamepadData& gamepadData = mGamepads.back();
+
+			gamepadData.gamepad = static_cast<OIS::JoyStick*>(mInputManager->createInputObject(OIS::OISJoyStick, true));
+			gamepadData.listener = bs_new<GamepadEventListener>(this, i);
+		}
 
 
 		// OIS reports times since system start but we use time since program start
 		// OIS reports times since system start but we use time since program start
 		mTimestampClockOffset = gTime().getStartTimeMs();
 		mTimestampClockOffset = gTime().getStartTimeMs();
-
-		mMouse->setEventCallback(this);
-		mKeyboard->setEventCallback(this);
 	}
 	}
 
 
 	InputHandlerOIS::~InputHandlerOIS()
 	InputHandlerOIS::~InputHandlerOIS()
 	{
 	{
 		if(mInputManager)
 		if(mInputManager)
 		{		
 		{		
-			if(mMouse)
+			if(mMouse != nullptr)
 				mInputManager->destroyInputObject(mMouse);
 				mInputManager->destroyInputObject(mMouse);
 
 
-			if(mKeyboard)
+			if(mKeyboard != nullptr)
 				mInputManager->destroyInputObject(mKeyboard);
 				mInputManager->destroyInputObject(mKeyboard);
 
 
+			for (auto& gamepadData : mGamepads)
+			{
+				mInputManager->destroyInputObject(gamepadData.gamepad);
+				bs_delete(gamepadData.listener);
+			}
+
 			OIS::InputManager::destroyInputSystem(mInputManager);
 			OIS::InputManager::destroyInputSystem(mInputManager);
 			mInputManager = nullptr;
 			mInputManager = nullptr;
 		}
 		}
@@ -61,8 +123,16 @@ namespace BansheeEngine
 
 
 	void InputHandlerOIS::_update()
 	void InputHandlerOIS::_update()
 	{
 	{
-		mMouse->capture();
-		mKeyboard->capture();
+		if (mMouse != nullptr)
+			mMouse->capture();
+
+		if (mKeyboard != nullptr)
+			mKeyboard->capture();
+
+		for (auto& gamepadData : mGamepads)
+		{
+			gamepadData.gamepad->capture();
+		}
 	}
 	}
 
 
 	void InputHandlerOIS::_inputWindowChanged(const RenderWindow& win)
 	void InputHandlerOIS::_inputWindowChanged(const RenderWindow& win)
@@ -77,54 +147,66 @@ namespace BansheeEngine
 
 
 	bool InputHandlerOIS::keyPressed(const OIS::KeyEvent &arg)
 	bool InputHandlerOIS::keyPressed(const OIS::KeyEvent &arg)
 	{
 	{
-		onButtonDown(keyCodeToButtonCode(arg.key), arg.timestamp - mTimestampClockOffset);
+		onButtonDown(0, keyCodeToButtonCode(arg.key), arg.timestamp - mTimestampClockOffset);
 		return true;
 		return true;
 	}
 	}
 
 
 	bool InputHandlerOIS::keyReleased(const OIS::KeyEvent& arg)
 	bool InputHandlerOIS::keyReleased(const OIS::KeyEvent& arg)
 	{
 	{
-		onButtonUp(keyCodeToButtonCode(arg.key), arg.timestamp - mTimestampClockOffset);
+		onButtonUp(0, keyCodeToButtonCode(arg.key), arg.timestamp - mTimestampClockOffset);
 		return true;
 		return true;
 	}
 	}
 
 
 	bool InputHandlerOIS::mouseMoved(const OIS::MouseEvent& arg)
 	bool InputHandlerOIS::mouseMoved(const OIS::MouseEvent& arg)
 	{
 	{
-		RawAxisState xyState;
-		xyState.abs = Vector2I(arg.state.X.abs, arg.state.Y.abs);
-		xyState.rel = Vector2I(arg.state.X.rel, arg.state.Y.rel);
+		RawAxisState xState;
+		xState.rel = Math::clamp(arg.state.X.rel / (float)MOUSE_SENSITIVITY, -1.0f, 1.0f);
+		xState.abs = xState.rel; // Abs value irrelevant for mouse
+		
+		onAxisMoved(0, xState, (UINT32)InputAxis::MouseX);
 
 
-		onAxisMoved(xyState, RawInputAxis::Mouse_XY);
+		RawAxisState yState;
+		yState.rel = Math::clamp(arg.state.Y.rel / (float)MOUSE_SENSITIVITY, -1.0f, 1.0f);
+		yState.abs = yState.rel; // Abs value irrelevant for mouse
+
+		onAxisMoved(0, yState, (UINT32)InputAxis::MouseY);
 
 
 		RawAxisState zState;
 		RawAxisState zState;
-		zState.abs = Vector2I(arg.state.Z.abs, 0);
-		zState.rel = Vector2I(arg.state.Z.rel, 0);
+		zState.abs = (float)arg.state.Z.abs;
+		zState.rel = (float)arg.state.Z.rel;
 
 
-		onAxisMoved(zState, RawInputAxis::Mouse_Z);
+		onAxisMoved(0, zState, (UINT32)InputAxis::MouseZ);
 
 
 		return true;
 		return true;
 	}
 	}
 
 
 	bool InputHandlerOIS::mousePressed(const OIS::MouseEvent& arg, OIS::MouseButtonID id)
 	bool InputHandlerOIS::mousePressed(const OIS::MouseEvent& arg, OIS::MouseButtonID id)
 	{
 	{
-		onButtonDown(mouseButtonToButtonCode(id), arg.timestamp - mTimestampClockOffset);
+		onButtonDown(0, mouseButtonToButtonCode(id), arg.timestamp - mTimestampClockOffset);
 
 
 		return true;
 		return true;
 	}
 	}
 
 
 	bool InputHandlerOIS::mouseReleased(const OIS::MouseEvent& arg, OIS::MouseButtonID id)
 	bool InputHandlerOIS::mouseReleased(const OIS::MouseEvent& arg, OIS::MouseButtonID id)
 	{
 	{
-		onButtonUp(mouseButtonToButtonCode(id), arg.timestamp - mTimestampClockOffset);
+		onButtonUp(0, mouseButtonToButtonCode(id), arg.timestamp - mTimestampClockOffset);
 
 
 		return true;
 		return true;
 	}
 	}
 
 
-	ButtonCode InputHandlerOIS::keyCodeToButtonCode(OIS::KeyCode keyCode) const
+	ButtonCode InputHandlerOIS::keyCodeToButtonCode(OIS::KeyCode keyCode)
 	{
 	{
 		return (ButtonCode)keyCode;
 		return (ButtonCode)keyCode;
 	}
 	}
 
 
-	ButtonCode InputHandlerOIS::mouseButtonToButtonCode(OIS::MouseButtonID mouseBtn) const
+	ButtonCode InputHandlerOIS::mouseButtonToButtonCode(OIS::MouseButtonID mouseBtn)
 	{
 	{
 		return (ButtonCode)(((int)mouseBtn + BC_NumKeys) | 0x80000000);
 		return (ButtonCode)(((int)mouseBtn + BC_NumKeys) | 0x80000000);
 	}
 	}
+
+	ButtonCode InputHandlerOIS::gamepadButtonToButtonCode(INT32 joystickCode)
+	{
+		// TODO
+		return BC_0;
+	}
 }
 }

+ 1 - 1
ExampleProject/Main/Main.cpp

@@ -72,7 +72,7 @@ namespace BansheeEngine
 		fullscreen = !fullscreen;
 		fullscreen = !fullscreen;
 	}
 	}
 
 
-	void buttonUp(const VirtualButton& button)
+	void buttonUp(const VirtualButton& button, UINT32 deviceIdx)
 	{
 	{
 		if (button == toggleCPUProfilerBtn)
 		if (button == toggleCPUProfilerBtn)
 		{
 		{

+ 8 - 10
Polish.txt

@@ -12,20 +12,18 @@ Polish TODO:
  - Make a separate release branch with no editor/script stuff, and without .txt files and other development data
  - Make a separate release branch with no editor/script stuff, and without .txt files and other development data
  - (HIGHLY OPTIONAL) Make a Getting Started guide, along with the example project. Or just finish up the manual.
  - (HIGHLY OPTIONAL) Make a Getting Started guide, along with the example project. Or just finish up the manual.
 
 
-Rename Joystick -> Gamepad
-OIS has getNumberOfDevices and listFreeDevices that I don't currently use. I should use that for Gamepad input.
-
-Refactor Input so that axis state isn't received in integers but in [-1.0, 1.0] float range
-Refactor input so X and Y axes are received separately
-Refactor OIS input so it properly weights Mouse state according to MouseState.width, MouseState.height
 Rebuild OIS with XINPUT support (OIS_WIN32_XINPUT_SUPPORT)
 Rebuild OIS with XINPUT support (OIS_WIN32_XINPUT_SUPPORT)
 Rename CamelotOIS to BansheeOIS
 Rename CamelotOIS to BansheeOIS
-Add Joystick support to OIS input handler and make sure the axes are properly weighed in OISJoystick::MIN_AXIS, OISJoystick::MAX_AXIS range.
+
+TODO - How do I convert joystick buttons to in-engine button codes?
+TODO - I don't have timestamps for joystick buttons
+
+Mouse sensitivity/sampling rate/whatever needs to be tweaked
+ - Use DirectInput DIPROPRANGE and SetProperty/GetProperty?
+
 Add smoothing
 Add smoothing
   - See http://www.flipcode.com/archives/Smooth_Mouse_Filtering.shtml or UE4 implementation
   - See http://www.flipcode.com/archives/Smooth_Mouse_Filtering.shtml or UE4 implementation
-
-Ensure that all button methods accept a DeviceIdx so that multiple joysticks can work.
- - When button events are triggered from Input and VirtualInput include device type and index in them
+  - UE4 seems to receive multiple mouse samples per frame. I only sample input once per frame
 
 
 -----------------
 -----------------