Added virtual steering wheel!

This commit is contained in:
izzy2lost
2025-12-19 02:08:24 -05:00
parent f8155ce06e
commit 2435651cbd
6 changed files with 3257 additions and 1 deletions
@@ -12,6 +12,22 @@ AndroidInputSystem::AndroidInputSystem()
std::strncpy(m_mouseDetails.name, "Touchscreen", MAX_NAME_LENGTH);
m_mouseDetails.name[MAX_NAME_LENGTH] = '\0';
m_mouseDetails.isAbsolute = true;
std::memset(&m_virtualJoyDetails, 0, sizeof(m_virtualJoyDetails));
std::strncpy(m_virtualJoyDetails.name, "Touch Wheel", MAX_NAME_LENGTH);
m_virtualJoyDetails.name[MAX_NAME_LENGTH] = '\0';
m_virtualJoyDetails.numAxes = 6;
m_virtualJoyDetails.numPOVs = 0;
m_virtualJoyDetails.numButtons = 0;
m_virtualJoyDetails.hasFFeedback = false;
for (int a = 0; a < NUM_JOY_AXES; a++)
{
m_virtualJoyDetails.hasAxis[a] = false;
m_virtualJoyDetails.axisHasFF[a] = false;
std::strncpy(m_virtualJoyDetails.axisName[a], GetDefaultAxisName(a), MAX_NAME_LENGTH);
m_virtualJoyDetails.axisName[a][MAX_NAME_LENGTH] = '\0';
}
m_virtualJoyDetails.hasAxis[AXIS_X] = true;
}
AndroidInputSystem::~AndroidInputSystem()
@@ -33,6 +49,37 @@ void AndroidInputSystem::SetGunTouchEnabled(bool enabled)
std::memset(m_mouseButtonPulseUntilMs, 0, sizeof(m_mouseButtonPulseUntilMs));
}
void AndroidInputSystem::SetVirtualWheelEnabled(bool enabled)
{
if (m_virtualWheelEnabled == enabled)
return;
m_virtualWheelEnabled = enabled;
m_wheelFingerActive = false;
m_wheelFinger = 0;
m_virtualJoyX = 0;
}
bool AndroidInputSystem::UseVirtualWheel() const
{
return m_virtualWheelEnabled && m_controllers.empty();
}
void AndroidInputSystem::SetVirtualSteerFromEncoded(float encodedX)
{
// encodedX comes from Java as [(steer+1)/2], where steer is in [-1,1].
const float steer = std::clamp((encodedX - 0.5f) * 2.0f, -1.0f, 1.0f);
constexpr float deadzone = 0.08f;
if (std::abs(steer) < deadzone)
{
m_virtualJoyX = 0;
return;
}
const float scaled = (std::abs(steer) - deadzone) / (1.0f - deadzone);
const float signedScaled = (steer < 0.0f) ? -scaled : scaled;
m_virtualJoyX = (int)std::lround(std::clamp(signedScaled, -1.0f, 1.0f) * 32767.0f);
}
void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config)
{
auto get = [&](const char* key, const char* fallback) -> std::string {
@@ -81,6 +128,9 @@ bool AndroidInputSystem::InitializeSystem()
std::memset(m_mouseButtonPulseUntilMs, 0, sizeof(m_mouseButtonPulseUntilMs));
m_gunFingerActive = false;
m_gunFinger = 0;
m_wheelFingerActive = false;
m_wheelFinger = 0;
m_virtualJoyX = 0;
SDL_GameControllerEventState(SDL_ENABLE);
RefreshControllers();
@@ -215,6 +265,32 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
const float x = tf.x;
const float y = tf.y;
// Virtual steering wheel (racing games): encoded in tf.x from Java and keyed by a fixed fingerId.
if (UseVirtualWheel())
{
constexpr SDL_FingerID kWheelFingerId = 1107;
if (down)
{
if (!m_wheelFingerActive && tf.fingerId == kWheelFingerId)
{
m_wheelFingerActive = true;
m_wheelFinger = tf.fingerId;
SetVirtualSteerFromEncoded(x);
return;
}
}
else
{
if (m_wheelFingerActive && tf.fingerId == m_wheelFinger)
{
SetVirtualSteerFromEncoded(0.5f);
m_wheelFingerActive = false;
m_wheelFinger = 0;
return;
}
}
}
// Tap zones (momentary):
// - Bottom-left: Coin (KEY_5)
// - Bottom-middle: Start (KEY_1)
@@ -320,6 +396,15 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf)
{
if (UseVirtualWheel())
{
if (m_wheelFingerActive && tf.fingerId == m_wheelFinger)
{
SetVirtualSteerFromEncoded(tf.x);
return;
}
}
if (m_gunTouchEnabled)
{
if (m_gunFingerActive && tf.fingerId == m_gunFinger)
@@ -500,11 +585,20 @@ void AndroidInputSystem::RefreshControllers()
int AndroidInputSystem::GetNumJoysticks()
{
if (UseVirtualWheel())
return 1;
return static_cast<int>(m_controllers.size());
}
const JoyDetails* AndroidInputSystem::GetJoyDetails(int joyNum)
{
if (UseVirtualWheel())
{
if (joyNum == ANY_JOYSTICK || joyNum == 0)
return &m_virtualJoyDetails;
return nullptr;
}
if (joyNum < 0 || joyNum >= static_cast<int>(m_controllers.size()))
return nullptr;
return &m_controllers[static_cast<size_t>(joyNum)].details;
@@ -573,6 +667,13 @@ bool AndroidInputSystem::ButtonPressedFor(const ControllerState& c, int butNum)
int AndroidInputSystem::GetJoyAxisValue(int joyNum, int axisNum)
{
if (UseVirtualWheel())
{
if (axisNum == AXIS_X)
return m_virtualJoyX;
return 0;
}
if (m_controllers.empty())
return 0;
@@ -21,6 +21,7 @@ public:
~AndroidInputSystem() override;
void SetGunTouchEnabled(bool enabled);
void SetVirtualWheelEnabled(bool enabled);
// Called from the SDL event loop (main thread).
void HandleEvent(const SDL_Event& ev);
@@ -73,6 +74,9 @@ private:
void RefreshControllers();
void CloseControllers();
bool UseVirtualWheel() const;
void SetVirtualSteerFromEncoded(float encodedX);
void SetKey(SDL_Scancode sc, bool down);
void PulseKey(SDL_Scancode sc, uint32_t durationMs);
void SetKeys(const DualScancode& sc, bool down);
@@ -114,6 +118,12 @@ private:
SDL_FingerID m_gunFinger = 0;
bool m_gunFingerActive = false;
bool m_virtualWheelEnabled = false;
SDL_FingerID m_wheelFinger = 0;
bool m_wheelFingerActive = false;
int m_virtualJoyX = 0;
JoyDetails m_virtualJoyDetails{};
MouseDetails m_mouseDetails{};
int m_mouseX = 0;
int m_mouseY = 0;
+3
View File
@@ -316,6 +316,9 @@ struct Super3Host {
(game.inputs & (Game::INPUT_GUN1 | Game::INPUT_GUN2 | Game::INPUT_ANALOG_GUN1 | Game::INPUT_ANALOG_GUN2)) != 0;
inputSystem.SetGunTouchEnabled(gunGame);
const bool vehicleGame = (game.inputs & (Game::INPUT_VEHICLE | Game::INPUT_HARLEY)) != 0;
inputSystem.SetVirtualWheelEnabled(vehicleGame);
// Apply Supermodel.ini overrides (Global + [ game ]) after the loader has determined game->name.
ApplyIniOverrides(game.name);
ApplyAndroidHardOverrides();
@@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.RelativeLayout
import java.io.File
@@ -64,6 +65,9 @@ class Super3Activity : SDLActivity() {
overlay.findViewById<LinearLayout>(R.id.overlay_pedals)?.visibility =
if (isRacing) View.VISIBLE else View.GONE
overlay.findViewById<ImageButton>(R.id.overlay_wheel)?.visibility =
if (isRacing) View.VISIBLE else View.GONE
fun nativeTouch(action: Int, fingerId: Int, x: Float, y: Float, p: Float = 1.0f) {
SDLActivity.onNativeTouch(0, fingerId, action, x, y, p)
}
@@ -126,6 +130,44 @@ class Super3Activity : SDLActivity() {
// Match the native pedal zone (right-middle), independent of UI placement.
bindHeld(R.id.overlay_gas, fingerId = 1103, x = 0.85f, y = 0.35f)
bindHeld(R.id.overlay_brake, fingerId = 1104, x = 0.85f, y = 0.80f)
val wheel = overlay.findViewById<ImageButton>(R.id.overlay_wheel)
wheel?.setOnTouchListener { v, ev ->
val w = v.width.toFloat().coerceAtLeast(1f)
val cx = w / 2f
val dx = (ev.x - cx) / cx
val steer = dx.coerceIn(-1f, 1f)
v.rotation = steer * 75f
val encodedX = ((steer + 1f) / 2f).coerceIn(0f, 1f)
val encodedY = 0.5f
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
v.alpha = 0.75f
nativeTouch(MotionEvent.ACTION_DOWN, 1107, encodedX, encodedY)
true
}
MotionEvent.ACTION_MOVE -> {
nativeTouch(MotionEvent.ACTION_MOVE, 1107, encodedX, encodedY)
true
}
MotionEvent.ACTION_UP -> {
v.alpha = 1.0f
v.rotation = 0f
nativeTouch(MotionEvent.ACTION_UP, 1107, 0.5f, encodedY)
true
}
MotionEvent.ACTION_CANCEL -> {
v.alpha = 1.0f
v.rotation = 0f
nativeTouch(MotionEvent.ACTION_UP, 1107, 0.5f, encodedY)
true
}
else -> true
}
}
}
}
File diff suppressed because one or more lines are too long
@@ -21,6 +21,20 @@
android:scaleType="fitCenter"
android:src="@drawable/coin" />
<ImageButton
android:id="@+id/overlay_wheel"
android:layout_width="190dp"
android:layout_height="190dp"
android:layout_gravity="bottom|start"
android:layout_marginStart="24dp"
android:layout_marginBottom="64dp"
android:background="@drawable/overlay_ripple_circle"
android:contentDescription="Steering"
android:padding="12dp"
android:scaleType="fitCenter"
android:src="@drawable/wheel"
android:visibility="gone" />
<com.google.android.material.button.MaterialButton
android:id="@+id/overlay_service"
style="?attr/materialButtonOutlinedStyle"
@@ -62,7 +76,8 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="18dp"
android:minWidth="140dp"
android:minHeight="0dp"
android:minWidth="0dp"
android:text="START"
android:textAllCaps="true"
app:rippleColor="@color/overlay_ripple"