cocos-engine-external/sources/android-gamesdk/GameController/GameController.cpp

458 lines
20 KiB
C++

/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "GameController.h"
#include <android/input.h>
#include <math.h>
#include <chrono>
#include <cstdlib>
#include <memory>
#include "GameControllerInternalConstants.h"
namespace paddleboat {
struct ControllerButtonMap {
int32_t buttonKeycodes[PADDLEBOAT_BUTTON_COUNT];
};
const ControllerButtonMap defaultButtonMap = {{
AKEYCODE_DPAD_UP, // PADDLEBOAT_BUTTON_DPAD_UP
AKEYCODE_DPAD_LEFT, // PADDLEBOAT_BUTTON_DPAD_LEFT
AKEYCODE_DPAD_DOWN, // PADDLEBOAT_BUTTON_DPAD_DOWN
AKEYCODE_DPAD_RIGHT, // PADDLEBOAT_BUTTON_DPAD_RIGHT
AKEYCODE_BUTTON_A, // PADDLEBOAT_BUTTON_A
AKEYCODE_BUTTON_B, // PADDLEBOAT_BUTTON_B
AKEYCODE_BUTTON_X, // PADDLEBOAT_BUTTON_X
AKEYCODE_BUTTON_Y, // PADDLEBOAT_BUTTON_Y
AKEYCODE_BUTTON_L1, // PADDLEBOAT_BUTTON_L1
AKEYCODE_BUTTON_L2, // PADDLEBOAT_BUTTON_L2
AKEYCODE_BUTTON_THUMBL, // PADDLEBOAT_BUTTON_L3
AKEYCODE_BUTTON_R1, // PADDLEBOAT_BUTTON_R1
AKEYCODE_BUTTON_R2, // PADDLEBOAT_BUTTON_R2
AKEYCODE_BUTTON_THUMBR, // PADDLEBOAT_BUTTON_R3
AKEYCODE_BUTTON_SELECT, // PADDLEBOAT_BUTTON_SELECT
AKEYCODE_BUTTON_START, // PADDLEBOAT_BUTTON_START
AKEYCODE_BUTTON_MODE, // PADDLEBOAT_BUTTON_SYSTEM
0, // PADDLEBOAT_BUTTON_TOUCHPAD
0, // PADDLEBOAT_BUTTON_AUX1
0, // PADDLEBOAT_BUTTON_AUX2
0, // PADDLEBOAT_BUTTON_AUX3
0 // PADDLEBOAT_BUTTON_AUX4
}};
// Axis must be at least this value to trigger a mapped button press
constexpr float AXIS_BUTTON_THRESHOLD = 0.1f;
void resetData(Paddleboat_Controller_Data &pbData) {
pbData.timestamp = 0;
pbData.buttonsDown = 0;
pbData.leftStick.stickX = 0.0f;
pbData.leftStick.stickY = 0.0f;
pbData.rightStick.stickX = 0.0f;
pbData.rightStick.stickY = 0.0f;
pbData.triggerL1 = 0.0f;
pbData.triggerL2 = 0.0f;
pbData.triggerR1 = 0.0f;
pbData.triggerR2 = 0.0f;
pbData.virtualPointer.pointerX = 0.0f;
pbData.virtualPointer.pointerY = 0.0f;
}
void resetInfo(Paddleboat_Controller_Info &pbInfo) {
pbInfo.controllerNumber = -1;
pbInfo.controllerFlags = 0;
pbInfo.vendorId = 0;
pbInfo.productId = 0;
pbInfo.deviceId = -1;
pbInfo.leftStickPrecision.stickFlatX = 0.0f;
pbInfo.leftStickPrecision.stickFlatY = 0.0f;
pbInfo.leftStickPrecision.stickFuzzX = 0.0f;
pbInfo.leftStickPrecision.stickFuzzY = 0.0f;
pbInfo.rightStickPrecision.stickFlatX = 0.0f;
pbInfo.rightStickPrecision.stickFlatY = 0.0f;
pbInfo.rightStickPrecision.stickFuzzX = 0.0f;
pbInfo.rightStickPrecision.stickFuzzY = 0.0f;
}
GameController::GameController()
: mConnectionIndex(-1),
mControllerData(),
mControllerInfo(),
mAxisInfo(),
mDeviceInfo(),
mControllerDataDirty(true) {
memset(mButtonKeycodes, 0, sizeof(mButtonKeycodes));
resetControllerData();
}
void GameController::resetControllerData() {
resetData(mControllerData);
resetInfo(mControllerInfo);
}
void GameController::setupController(
const Paddleboat_Controller_Mapping_Data *mappingData) {
const GameControllerDeviceInfo::InfoFields &infoFields =
*(mDeviceInfo.getInfo());
uint64_t axisLow = static_cast<uint64_t>(infoFields.mAxisBitsLow);
uint64_t axisHigh = static_cast<uint64_t>(infoFields.mAxisBitsHigh);
mControllerAxisMask = axisLow | (axisHigh << 32ULL);
mControllerInfo.controllerFlags = infoFields.mControllerFlags;
mControllerInfo.controllerNumber = infoFields.mControllerNumber;
mControllerInfo.deviceId = infoFields.mDeviceId;
mControllerInfo.productId = infoFields.mProductId;
mControllerInfo.vendorId = infoFields.mVendorId;
if (mappingData != nullptr) {
mControllerInfo.controllerFlags |= mappingData->flags;
for (int32_t i = 0; i < PADDLEBOAT_BUTTON_COUNT; ++i) {
if (mappingData->buttonMapping[i] != PADDLEBOAT_BUTTON_IGNORED) {
mButtonKeycodes[i] = mappingData->buttonMapping[i];
}
}
for (int32_t i = 0; i < PADDLEBOAT_MAPPING_AXIS_COUNT; ++i) {
if (mappingData->axisMapping[i] != PADDLEBOAT_AXIS_IGNORED) {
const GameControllerAxis gcAxis =
static_cast<GameControllerAxis>(i);
const int32_t nativeAxisId = mappingData->axisMapping[i];
const int32_t positiveButton =
(mappingData->axisPositiveButtonMapping[i] ==
PADDLEBOAT_AXIS_BUTTON_IGNORED)
? 0
: (1 << mappingData->axisPositiveButtonMapping[i]);
const int32_t negativeButton =
(mappingData->axisNegativeButtonMapping[i] ==
PADDLEBOAT_AXIS_BUTTON_IGNORED)
? 0
: (1 << mappingData->axisNegativeButtonMapping[i]);
setupAxis(gcAxis, nativeAxisId, nativeAxisId, positiveButton,
negativeButton);
}
}
adjustAxisConstants();
} else {
// Fallback defaults if there wasn't mapping data provided
initializeDefaultAxisMapping();
memcpy(mButtonKeycodes, &defaultButtonMap, sizeof(ControllerButtonMap));
mControllerInfo.controllerFlags |=
PADDLEBOAT_CONTROLLER_FLAG_GENERIC_PROFILE;
}
}
void GameController::initializeDefaultAxisMapping() {
setupAxis(GAMECONTROLLER_AXIS_LSTICK_X, AMOTION_EVENT_AXIS_X,
AMOTION_EVENT_AXIS_X, 0, 0);
setupAxis(GAMECONTROLLER_AXIS_LSTICK_Y, AMOTION_EVENT_AXIS_Y,
AMOTION_EVENT_AXIS_Y, 0, 0);
setupAxis(GAMECONTROLLER_AXIS_RSTICK_X, AMOTION_EVENT_AXIS_Z,
AMOTION_EVENT_AXIS_RX, 0, 0);
setupAxis(GAMECONTROLLER_AXIS_RSTICK_Y, AMOTION_EVENT_AXIS_RZ,
AMOTION_EVENT_AXIS_RY, 0, 0);
setupAxis(GAMECONTROLLER_AXIS_L2, AMOTION_EVENT_AXIS_LTRIGGER,
AMOTION_EVENT_AXIS_BRAKE, PADDLEBOAT_BUTTON_L2, 0);
setupAxis(GAMECONTROLLER_AXIS_R2, AMOTION_EVENT_AXIS_RTRIGGER,
AMOTION_EVENT_AXIS_GAS, PADDLEBOAT_BUTTON_R2, 0);
setupAxis(GAMECONTROLLER_AXIS_HAT_X, AMOTION_EVENT_AXIS_HAT_X,
AMOTION_EVENT_AXIS_HAT_X, PADDLEBOAT_BUTTON_DPAD_RIGHT,
PADDLEBOAT_BUTTON_DPAD_LEFT);
setupAxis(GAMECONTROLLER_AXIS_HAT_Y, AMOTION_EVENT_AXIS_HAT_Y,
AMOTION_EVENT_AXIS_HAT_Y, PADDLEBOAT_BUTTON_DPAD_DOWN,
PADDLEBOAT_BUTTON_DPAD_UP);
adjustAxisConstants();
}
void GameController::adjustAxisConstants() {
if (mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X].axisIndex >= 0) {
const bool stickAxisAdjust =
((mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X].axisFlags &
GAMECONTROLLER_AXIS_FLAG_APPLY_ADJUSTMENTS) != 0);
mControllerInfo.leftStickPrecision.stickFlatX =
mDeviceInfo.getFlatArray()[mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X]
.axisIndex];
mControllerInfo.leftStickPrecision.stickFlatY =
mDeviceInfo.getFlatArray()[mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_Y]
.axisIndex];
mControllerInfo.leftStickPrecision.stickFuzzX =
mDeviceInfo.getFuzzArray()[mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X]
.axisIndex];
mControllerInfo.leftStickPrecision.stickFuzzY =
mDeviceInfo.getFuzzArray()[mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_Y]
.axisIndex];
if (stickAxisAdjust) {
// We are adjusting the raw axis values, so we also adjust the
// 'flat' and 'fuzz' values for the sticks
mControllerInfo.leftStickPrecision.stickFlatX *=
mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X].axisMultiplier;
mControllerInfo.leftStickPrecision.stickFlatY *=
mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_Y].axisMultiplier;
mControllerInfo.leftStickPrecision.stickFuzzX *=
mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_X].axisMultiplier;
mControllerInfo.leftStickPrecision.stickFuzzY *=
mAxisInfo[GAMECONTROLLER_AXIS_LSTICK_Y].axisMultiplier;
}
}
if (mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X].axisIndex >= 0) {
const bool stickAxisAdjust =
((mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X].axisFlags &
GAMECONTROLLER_AXIS_FLAG_APPLY_ADJUSTMENTS) != 0);
mControllerInfo.rightStickPrecision.stickFlatX =
mDeviceInfo.getFlatArray()[mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X]
.axisIndex];
mControllerInfo.rightStickPrecision.stickFlatY =
mDeviceInfo.getFlatArray()[mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_Y]
.axisIndex];
mControllerInfo.rightStickPrecision.stickFuzzX =
mDeviceInfo.getFuzzArray()[mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X]
.axisIndex];
mControllerInfo.rightStickPrecision.stickFuzzY =
mDeviceInfo.getFuzzArray()[mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_Y]
.axisIndex];
if (stickAxisAdjust) {
// We are adjusting the raw axis values, so we also adjust the
// 'flat' and 'fuzz' values for the sticks
mControllerInfo.rightStickPrecision.stickFlatX *=
mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X].axisMultiplier;
mControllerInfo.rightStickPrecision.stickFlatY *=
mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_Y].axisMultiplier;
mControllerInfo.rightStickPrecision.stickFuzzX *=
mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_X].axisMultiplier;
mControllerInfo.rightStickPrecision.stickFuzzY *=
mAxisInfo[GAMECONTROLLER_AXIS_RSTICK_Y].axisMultiplier;
}
}
}
void GameController::setupAxis(const GameControllerAxis gcAxis,
const int32_t preferredNativeAxisId,
const int32_t secondaryNativeAxisId,
const int32_t buttonMask,
const int32_t buttonNegativeMask) {
mAxisInfo[gcAxis].resetInfo();
mAxisInfo[gcAxis].axisButtonMask = buttonMask;
mAxisInfo[gcAxis].axisButtonNegativeMask = buttonNegativeMask;
// Do we have a mapping for the preferred native axis?
if (preferredNativeAxisId < MAX_AXIS_COUNT &&
secondaryNativeAxisId < MAX_AXIS_COUNT) {
const int32_t preferredMask = (1U << preferredNativeAxisId);
const int32_t secondaryMask = (1U << secondaryNativeAxisId);
if ((mControllerAxisMask & preferredMask) != 0) {
// preferred axis is mapped
mAxisInfo[gcAxis].axisIndex = preferredNativeAxisId;
} else if ((preferredNativeAxisId != secondaryNativeAxisId) &&
((mControllerAxisMask & secondaryMask) != 0)) {
// secondary axis is mapped
mAxisInfo[gcAxis].axisIndex = secondaryNativeAxisId;
} else if (buttonMask) {
// There wasn't a matching axis,
// but we will fake this axis using a digital button mapping
mAxisInfo[gcAxis].axisFlags |=
GAMECONTROLLER_AXIS_FLAG_DIGITAL_TRIGGER;
}
// If we found a native axis, check its ranges and see if we need to set
// up adjustment values if the ranges aren't -1.0 to 1.0 for a stick or
// 0.0 to 1.0 for a trigger
if (mAxisInfo[gcAxis].axisIndex >= 0) {
bool isStickAxis = (!(gcAxis >= GAMECONTROLLER_AXIS_L1 &&
gcAxis <= GAMECONTROLLER_AXIS_R2));
const float minAdjust = isStickAxis ? 1.0f : 0.0f;
const float rawMin =
mDeviceInfo.getMinArray()[mAxisInfo[gcAxis].axisIndex];
const float rawMax =
mDeviceInfo.getMaxArray()[mAxisInfo[gcAxis].axisIndex];
const float diffMin = fabsf(rawMin + minAdjust);
const float diffMax = fabsf(1.0f - rawMax);
if (!(diffMin <= FLT_MIN && diffMax <= FLT_MIN)) {
mAxisInfo[gcAxis].axisFlags |=
GAMECONTROLLER_AXIS_FLAG_APPLY_ADJUSTMENTS;
if (isStickAxis) {
// normalize min and max axis to 1.0
mAxisInfo[gcAxis].axisMultiplier = 1.0f / rawMax;
// Make sure center of stick is 0.0
const float rawCenter = ((rawMax - rawMin) * 0.5f) + rawMin;
if (rawCenter >= FLT_MIN) {
mAxisInfo[gcAxis].axisAdjust =
-(rawCenter * mAxisInfo[gcAxis].axisMultiplier);
}
} else {
// This case is hit on PS5 API <= 30 having weird trigger
// axis mappings of: L2=RX R2=RY with a -1.0 to 1.0 range
mAxisInfo[gcAxis].axisMultiplier = 1.0f / (rawMax - rawMin);
mAxisInfo[gcAxis].axisAdjust =
(-rawMin) * mAxisInfo[gcAxis].axisMultiplier;
}
}
}
}
}
int32_t GameController::processGameActivityKeyEvent(
const Paddleboat_GameActivityKeyEvent *event, const size_t eventSize) {
return processKeyEventInternal(event->keyCode, event->action);
}
int32_t GameController::processGameActivityMotionEvent(
const Paddleboat_GameActivityMotionEvent *event, const size_t eventSize) {
int32_t handledEvent = IGNORED_EVENT;
if (event->pointerCount > 0) {
handledEvent =
processMotionEventInternal(event->pointers->axisValues, nullptr);
}
return handledEvent;
}
int32_t GameController::processKeyEvent(const AInputEvent *event) {
const int32_t eventKeyCode = AKeyEvent_getKeyCode(event);
const int32_t eventKeyAction = AKeyEvent_getAction(event);
return processKeyEventInternal(eventKeyCode, eventKeyAction);
}
int32_t GameController::processKeyEventInternal(const int32_t eventKeyCode,
const int32_t eventKeyAction) {
int32_t handledEvent = IGNORED_EVENT;
int32_t buttonMask = 0;
const bool bDown = (eventKeyAction == AKEY_EVENT_ACTION_DOWN);
for (uint32_t i = 0; i < PADDLEBOAT_BUTTON_COUNT; ++i) {
if (eventKeyCode == mButtonKeycodes[i]) {
int32_t newButtonFlag = (1 << i);
buttonMask |= newButtonFlag;
// If we have no analog axis mapped for a trigger, allow the
// button events to set the analog trigger data
switch (newButtonFlag) {
case PADDLEBOAT_BUTTON_L1:
if (mAxisInfo[GAMECONTROLLER_AXIS_L1].axisIndex == -1) {
mControllerData.triggerL1 = bDown ? 1.0f : 0.0f;
}
break;
case PADDLEBOAT_BUTTON_L2:
if (mAxisInfo[GAMECONTROLLER_AXIS_L2].axisIndex == -1) {
mControllerData.triggerL2 = bDown ? 1.0f : 0.0f;
}
break;
case PADDLEBOAT_BUTTON_R1:
if (mAxisInfo[GAMECONTROLLER_AXIS_R1].axisIndex == -1) {
mControllerData.triggerR1 = bDown ? 1.0f : 0.0f;
}
break;
case PADDLEBOAT_BUTTON_R2:
if (mAxisInfo[GAMECONTROLLER_AXIS_R2].axisIndex == -1) {
mControllerData.triggerR2 = bDown ? 1.0f : 0.0f;
}
break;
default:
break;
}
break;
}
}
if (buttonMask != 0) {
if (bDown) {
mControllerData.buttonsDown |= buttonMask;
setControllerDataDirty(true);
} else {
mControllerData.buttonsDown &= (~buttonMask);
setControllerDataDirty(true);
}
handledEvent = HANDLED_EVENT;
}
return handledEvent;
}
int32_t GameController::processMotionEvent(const AInputEvent *event) {
return processMotionEventInternal(nullptr, event);
}
int32_t GameController::processMotionEventInternal(const float *axisArray,
const AInputEvent *event) {
int32_t handledEvent = IGNORED_EVENT;
for (uint32_t axis = GAMECONTROLLER_AXIS_LSTICK_X;
axis < GAMECONTROLLER_AXIS_COUNT; ++axis) {
if (mAxisInfo[axis].axisIndex >= 0 &&
mAxisInfo[axis].axisIndex <
PADDLEBOAT_GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
float axisValue = 0.0f;
if (axisArray != nullptr) {
axisValue = axisArray[mAxisInfo[axis].axisIndex];
} else if (event != nullptr) {
axisValue = AMotionEvent_getAxisValue(
event, mAxisInfo[axis].axisIndex, 0);
}
if ((mAxisInfo[axis].axisFlags &
GAMECONTROLLER_AXIS_FLAG_APPLY_ADJUSTMENTS) != 0) {
axisValue = ((axisValue * mAxisInfo[axis].axisMultiplier) +
mAxisInfo[axis].axisAdjust);
}
// We take advantage of the GameControllerAxis matching the axis
// order in the Paddleboat_Controller_Data struct to use the current
// axis as an index into the axis entries in the
// Paddleboat_Controller_Data struct
if (axis < GAMECONTROLLER_AXIS_HAT_X) {
float *axisData = &mControllerData.leftStick.stickX;
axisData[axis] = axisValue;
}
// If this axis has a button associated with it, set/clear the flags
// as appropriate
if (mAxisInfo[axis].axisButtonMask != 0 ||
mAxisInfo[axis].axisButtonNegativeMask) {
if (axisValue > -AXIS_BUTTON_THRESHOLD &&
axisValue < AXIS_BUTTON_THRESHOLD) {
const uint32_t buttonMask =
mAxisInfo[axis].axisButtonMask |
mAxisInfo[axis].axisButtonNegativeMask;
mControllerData.buttonsDown &= (~buttonMask);
} else if (axisValue > AXIS_BUTTON_THRESHOLD) {
mControllerData.buttonsDown |=
mAxisInfo[axis].axisButtonMask;
} else if (axisValue < -AXIS_BUTTON_THRESHOLD) {
mControllerData.buttonsDown |=
mAxisInfo[axis].axisButtonNegativeMask;
}
}
setControllerDataDirty(true);
handledEvent = HANDLED_EVENT;
}
}
return handledEvent;
}
void GameController::setControllerDataDirty(const bool dirty) {
mControllerDataDirty = dirty;
if (dirty) {
// update the timestamp any time we mark dirty
const auto timestamp =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
mControllerData.timestamp = static_cast<uint64_t>(timestamp);
}
}
} // namespace paddleboat