Added lightgun laser emulation

This commit is contained in:
ergo720 2021-11-15 19:09:04 +01:00
parent c658777645
commit caf7927445
17 changed files with 140 additions and 34 deletions

View File

@ -31,7 +31,7 @@
#include <Commctrl.h>
#include <string>
#define LIGHTGUN_NUM_BUTTONS 16
#define LIGHTGUN_NUM_BUTTONS 17
#define XBOX_CTRL_NUM_BUTTONS 25
#define SBC_NUM_BUTTONS 56
#define HIGHEST_NUM_BUTTONS SBC_NUM_BUTTONS

View File

@ -178,7 +178,7 @@ const auto InputDevice::FindPort(std::string_view Port) const
});
}
void InputDevice::SetPort(std::string_view Port, bool Connect)
void InputDevice::SetPort2(std::string_view Port, bool Connect)
{
if (Connect) {
m_XboxPort.emplace_back(Port);

View File

@ -122,7 +122,8 @@ public:
// retrieves the port this device is attached to
bool GetPort(std::string_view Port) const;
// sets the port this device is attached to
void SetPort(std::string_view Port, bool Connect);
// NOTE: using SetPort2 to avoid a collision with the SetPort macro provided by Windows headers
void SetPort2(std::string_view Port, bool Connect);
// retuns true if it is a libusb device, false otherwise
virtual bool IsLibusb() const { return false; };

View File

@ -37,6 +37,7 @@
#include <core\kernel\exports\xboxkrnl.h> // For PKINTERRUPT, etc.
#include "D3dx9math.h" // For the matrix math functions
#include "SdlJoystick.h"
#include "XInputPad.h"
#include "RawDevice.h"
@ -273,7 +274,7 @@ void InputDeviceManager::UpdateDevices(std::string_view port, bool ack)
else {
auto host_dev = g_InputDeviceManager.FindDevice(port);
if (host_dev != nullptr) {
host_dev->SetPort(port, false);
host_dev->SetPort2(port, false);
}
if (type != to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID)) {
if (type != to_underlying(dev->type)) {
@ -310,7 +311,7 @@ void InputDeviceManager::DisconnectDevice(DeviceState *dev, std::string_view por
}
auto host_dev = g_InputDeviceManager.FindDevice(port);
if (host_dev != nullptr) {
host_dev->SetPort(port, false);
host_dev->SetPort2(port, false);
}
}
@ -329,7 +330,7 @@ void InputDeviceManager::BindHostDevice(int type, std::string_view port)
g_EmuShared->GetInputDevNameSettings(dev_name, port_num);
auto dev = FindDevice(std::string(dev_name));
if (dev != nullptr) {
dev->SetPort(port, true);
dev->SetPort2(port, true);
}
return;
}
@ -356,7 +357,7 @@ void InputDeviceManager::BindHostDevice(int type, std::string_view port)
});
dev->SetBindings(index, (it != controls.end()) ? *it : nullptr, port_str);
}
dev->SetPort(port, true);
dev->SetPort2(port, true);
}
}
@ -491,24 +492,36 @@ bool InputDeviceManager::UpdateInputLightgun(std::shared_ptr<InputDevice> &Devic
// We change the toggle buttons only when a press -> release input transaction is completed
// 0 -> Turbo left
// 1 -> Turbo right
// 2 -> Laser
XpadInput *in_buf = reinterpret_cast<XpadInput *>(static_cast<uint8_t *>(Buffer) + XID_PACKET_HEADER);
uint8_t last_turbo = g_devs[Port_num].info.ligthgun.turbo;
for (int i = 14, j = 0; i < 16; i++, j++) {
g_devs[Port_num].info.ligthgun.last_turbo = g_devs[Port_num].info.ligthgun.turbo;
for (int i = 14, j = 0; i < 17; i++, j++) {
ControlState state = (bindings[i] != nullptr) ? dynamic_cast<InputDevice::Input *>(bindings[i])->GetState() : 0.0;
uint8_t curr_state = static_cast<uint8_t>(!!state);
if ((~curr_state) & ((g_devs[Port_num].info.ligthgun.last_turbo_state >> j) & 1)) {
if (j == 0) {
if ((~curr_state) & ((g_devs[Port_num].info.ligthgun.last_in_state >> j) & 1)) {
switch (j)
{
case 0:
if (g_devs[Port_num].info.ligthgun.turbo != 2) {
g_devs[Port_num].info.ligthgun.turbo += 1;
}
}
else {
break;
case 1:
if (g_devs[Port_num].info.ligthgun.turbo != 0) {
g_devs[Port_num].info.ligthgun.turbo -= 1;
}
break;
case 2:
g_devs[Port_num].info.ligthgun.laser ^= 1;
if (g_devs[Port_num].info.ligthgun.laser) {
}
break;
}
}
(g_devs[Port_num].info.ligthgun.last_turbo_state &= ~(1 << j)) |= (curr_state << j);
(g_devs[Port_num].info.ligthgun.last_in_state &= ~(1 << j)) |= (curr_state << j);
}
in_buf->wButtons = XINPUT_LIGHTGUN_ONSCREEN;
@ -532,7 +545,7 @@ bool InputDeviceManager::UpdateInputLightgun(std::shared_ptr<InputDevice> &Devic
// Turbo mode 2
start_idx = 8;
++g_devs[Port_num].info.ligthgun.turbo_delay;
if (last_turbo != g_devs[Port_num].info.ligthgun.turbo) {
if (g_devs[Port_num].info.ligthgun.last_turbo != g_devs[Port_num].info.ligthgun.turbo) {
g_devs[Port_num].info.ligthgun.turbo_delay = 0;
}
if (g_devs[Port_num].info.ligthgun.turbo_delay == LIGHTGUN_GRIP_DELAY) {
@ -887,3 +900,51 @@ void InputDeviceManager::HotplugHandler(bool is_sdl)
}
}
}
ImVec2 InputDeviceManager::CalcLaserPos(int port)
{
static ImVec2 laser_coord[XBOX_NUM_PORTS] = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
// If somebody else is currently holding the lock, we won't wait and instead we report the last known laser position
if (m_Mtx.try_lock()) {
static D3DXVECTOR4 coeff_vec;
// If the rendering window was not resized, we can skip calculating the conversion matrix
if (g_bRenderWindowResized) {
g_bRenderWindowResized = false;
// We convert the laser input coordinates given by xinput (in the sThumbLXY members of XpadInput) with the procedure described in the link below
// https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/samples/jj635757(v=vs.85)?redirectedfrom=MSDN
// NOTE: the d3d math functions work even when d3d was not intialized, which happens when running with LLE GPU turned on
/*
xinput -> screen
v = M^-1 * u
width 0 0 1 0 a
height = 0 0 0 1 * b
0 -32768 32767 1 0 c
0 -32767 -32768 0 1 d
*/
RECT rect;
GetClientRect(m_hwnd, &rect);
const auto width = std::max(rect.right - rect.left, 1l);
const auto height = std::max(rect.bottom - rect.top, 1l);
D3DXMATRIX inverted_mtx, transposed_mtx;
D3DXMATRIX src_mtx(0, 0, 1, 0, 0, 0, 0, 1, -32768, 32767, 1, 0, -32767, -32768, 0, 1);
D3DXVECTOR4 screen_vec(width, height, 0, 0);
D3DXMatrixInverse(&inverted_mtx, nullptr, &src_mtx);
D3DXMatrixTranspose(&transposed_mtx, &inverted_mtx);
D3DXVec4Transform(&coeff_vec, &screen_vec, &transposed_mtx);
}
// x' = ax + by + c
// y' = bx - ay + d
int16_t laser_x = g_devs[port].info.buff.ctrl.InBuffer.sThumbLX;
int16_t laser_y = g_devs[port].info.buff.ctrl.InBuffer.sThumbLY;
laser_coord[port].x = coeff_vec.x * laser_x + coeff_vec.y * laser_y + coeff_vec.z;
laser_coord[port].y = coeff_vec.y * laser_x - coeff_vec.x * laser_y + coeff_vec.w;
m_Mtx.unlock();
}
return laser_coord[port];
}

View File

@ -31,11 +31,7 @@
#include <thread>
#include "InputDevice.h"
#include "EmuDevice.h"
// Prevent a collision with the SetPort provided by Windows
#ifdef WIN32
#undef SetPort
#endif
#include <imgui.h>
#define PORT_INVALID -1
#define PORT_1 0
@ -162,9 +158,11 @@ struct XidSBCOutput {
struct LightGunData {
xbox::short_xt offset_x;
xbox::short_xt offset_y;
uint8_t last_turbo_state;
uint8_t last_in_state;
uint8_t last_turbo;
uint8_t turbo_delay;
uint8_t turbo;
uint8_t laser;
};
struct SbcData {
@ -234,6 +232,8 @@ public:
void UpdateOpt(bool is_gui);
// device hotplug event handler
void HotplugHandler(bool is_sdl);
// converts xinput -> screen coordinates to display the lightgun laser on the rendering window
ImVec2 CalcLaserPos(int port);
private:

View File

@ -137,6 +137,7 @@ inline int button_lightgun_id[LIGHTGUN_NUM_BUTTONS] = {
IDC_LG_AIM_NEGY,
IDC_TURBO_LEFT,
IDC_TURBO_RIGHT,
IDC_LASER,
};
#endif
@ -244,6 +245,7 @@ inline constexpr const char *button_lightgun_names[LIGHTGUN_NUM_BUTTONS] = {
"Aim Y-",
"Turbo Left",
"Turbo Right",
"Laser",
};
constexpr bool check_button_name_size(unsigned max_num_buttons)

View File

@ -15,7 +15,14 @@
#include "core/kernel/init/CxbxKrnl.h"
bool ImGuiUI::Initialize()
const ImColor ImGuiUI::m_laser_col[4] = {
ImColor(ImVec4(1.0f, 0.0f, 0.0f, 1.0f)), // ply1: red
ImColor(ImVec4(0.0f, 1.0f, 0.0f, 1.0f)), // ply2: green
ImColor(ImVec4(0.0f, 0.0f, 1.0f, 1.0f)), // ply3: blue
ImColor(ImVec4(1.0f, 1.0f, 0.0f, 1.0f)) // ply4: yellow
};
bool ImGuiUI::Initialize(int backbuffer_scale)
{
IMGUI_CHECKVERSION();
m_imgui_context = ImGui::CreateContext();
@ -48,6 +55,7 @@ bool ImGuiUI::Initialize()
// Internal initialize (when necessary, move into its own function.)
fps_counter = 30.0f;
m_backbuffer_scale = backbuffer_scale;
// Miscs
m_audio.Initialize();
@ -190,3 +198,12 @@ void ImGuiUI::DrawWidgets()
m_audio.DrawWidgets(m_is_focus, input_handler);
}
void ImGuiUI::DrawLightgunLaser(int port)
{
ImGui::Begin("Laser", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoDecoration);
ImGui::GetForegroundDrawList()->AddCircleFilled(g_InputDeviceManager.CalcLaserPos(port), 10 * m_backbuffer_scale, m_laser_col[port], 32);
ImGui::End();
}

View File

@ -32,10 +32,11 @@ public:
void DrawMenu();
void DrawWidgets();
void DrawLightgunLaser(int port);
protected:
bool Initialize();
bool Initialize(int backbuffer_scale);
void Shutdown();
template<class C, class T>
@ -61,6 +62,8 @@ protected:
overlay_settings m_settings;
unsigned int m_lle_flags;
float fps_counter;
int m_backbuffer_scale;
static const ImColor m_laser_col[4];
// Make them as settings storage.
/*bool m_show_fps;
bool m_show_LLE_stats;

View File

@ -16,9 +16,9 @@
std::unique_ptr<RenderBase> g_renderbase;
bool RenderBase::Initialize()
bool RenderBase::Initialize(int backbuffer_scale)
{
if (!ImGuiUI::Initialize()) {
if (!ImGuiUI::Initialize(backbuffer_scale)) {
return false;
}

View File

@ -16,7 +16,7 @@ public:
RenderBase() = default;
virtual ~RenderBase() = default;
virtual bool Initialize();
virtual bool Initialize(int backbuffer_scale);
virtual void Shutdown();
template<class C, class T>

View File

@ -87,6 +87,7 @@ using namespace std::literals::chrono_literals;
// Global(s)
HWND g_hEmuWindow = NULL; // rendering window
bool g_bRenderWindowResized = true; // indicates that the rendering window has had its size changed
bool g_bClipCursor = false; // indicates that the mouse cursor should be confined inside the rendering window
IDirect3DDevice9Ex *g_pD3DDevice = nullptr; // Direct3D Device
@ -194,6 +195,11 @@ static void CxbxImGui_RenderD3D9(ImGuiUI* m_imgui, IDirect3DSurface9* renderTarg
m_imgui->DrawMenu();
m_imgui->DrawWidgets();
for (int port = PORT_1; port < XBOX_NUM_PORTS; ++port) {
if (g_devs[port].type == XBOX_INPUT_DEVICE::LIGHTGUN && g_devs[port].info.ligthgun.laser) {
m_imgui->DrawLightgunLaser(port);
}
}
ImGui::EndFrame();
@ -663,7 +669,7 @@ void CxbxInitWindow(bool bFullInit)
SetFocus(g_hEmuWindow);
g_renderbase = std::unique_ptr<RenderBase>(new RenderBase());
g_renderbase->Initialize();
g_renderbase->Initialize(g_RenderUpscaleFactor);
ImGui_ImplWin32_Init(g_hEmuWindow);
g_renderbase->SetWindowRelease([] {
@ -2032,6 +2038,7 @@ static LRESULT WINAPI EmuMsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar
case WM_SIZE:
{
g_bRenderWindowResized = true;
switch(wParam)
{
case SIZE_RESTORED:

View File

@ -265,8 +265,9 @@ void ConstructHleInputDevice(DeviceState *dev, DeviceState *upstream, int type,
dev->info.ucInputStateSize = sizeof(XpadInput);
dev->info.ucFeedbackSize = sizeof(XpadOutput);
dev->info.ligthgun.offset_x = dev->info.ligthgun.offset_x = 0;
dev->info.ligthgun.last_turbo_state = dev->info.ligthgun.turbo = 0;
dev->info.ligthgun.turbo_delay = 0;
dev->info.ligthgun.last_in_state = dev->info.ligthgun.turbo_delay = 0;
dev->info.ligthgun.turbo = dev->info.ligthgun.last_turbo = 0;
dev->info.ligthgun.laser = 1; // laser on by default
break;
case to_underlying(XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER):

View File

@ -187,6 +187,7 @@ bool CxbxIsElevated();
/*! kernel thunk table */
extern uint32_t CxbxKrnl_KernelThunkTable[379];
extern bool g_bRenderWindowResized;
extern bool g_bClipCursor;
extern bool g_CxbxPrintUEM;
extern ULONG g_CxbxFatalErrorCode;

View File

@ -33,6 +33,8 @@
// *
// ******************************************************************
#include "common\input\InputManager.h"
// FIXME
#define qemu_mutex_lock_iothread()
#define qemu_mutex_unlock_iothread()
@ -713,6 +715,11 @@ static void CxbxImGui_RenderOpenGL(ImGuiUI* m_imgui, std::nullptr_t unused)
m_imgui->DrawMenu();
m_imgui->DrawWidgets();
for (int port = PORT_1; port < XBOX_NUM_PORTS; ++port) {
if (g_devs[port].type == XBOX_INPUT_DEVICE::LIGHTGUN && g_devs[port].info.ligthgun.laser) {
m_imgui->DrawLightgunLaser(port);
}
}
ImGui::Render();

View File

@ -37,8 +37,8 @@
static constexpr std::array<std::array<const char *, LIGHTGUN_NUM_BUTTONS>, 2> button_lightgun_default = { {
{ "Pad N", "Pad S", "Pad W", "Pad E", "Start", "Back", "Button A", "Button B", "Button X", "Button Y", "Left X+", "Left X-",
"Left Y+", "Left Y-", "Shoulder L", "Shoulder R" },
{ "UP", "DOWN", "LEFT", "RIGHT", "RETURN", "SPACE", "Click 0", "Click 1", "W", "E", "Cursor X+", "Cursor X-", "Cursor Y+", "Cursor Y-", "S", "D" }
"Left Y+", "Left Y-", "Shoulder L", "Shoulder R", "Thumb R" },
{ "UP", "DOWN", "LEFT", "RIGHT", "RETURN", "SPACE", "Click 0", "Click 1", "W", "E", "Cursor X+", "Cursor X-", "Cursor Y+", "Cursor Y-", "S", "D", "C"}
} };
static LightgunInputWindow *g_InputWindow = nullptr;
@ -214,7 +214,8 @@ INT_PTR CALLBACK DlgLightgunConfigProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, L
case IDC_LG_AIM_POSY:
case IDC_LG_AIM_NEGY:
case IDC_TURBO_LEFT:
case IDC_TURBO_RIGHT: {
case IDC_TURBO_RIGHT:
case IDC_LASER: {
if (HIWORD(wParam) == BN_CLICKED) {
g_InputWindow->BindButton(LOWORD(wParam));
}

View File

@ -250,11 +250,14 @@ BEGIN
LTEXT "Right",IDC_STATIC,285,129,20,14,SS_CENTERIMAGE
PUSHBUTTON "Default Bindings",IDC_DEFAULT,362,200,69,14,BS_FLAT
PUSHBUTTON "Clear",IDC_CLEAR,443,200,50,14,BS_FLAT
GROUPBOX "Turbo switch",IDC_TURBO,396,66,121,121,WS_GROUP
PUSHBUTTON "",IDC_TURBO_LEFT,443,76,57,14,BS_FLAT
GROUPBOX "Turbo switch",IDC_POWER_SWITCH,396,66,121,52,WS_GROUP
PUSHBUTTON "",IDC_TURBO_LEFT,443,132,57,14,BS_FLAT
PUSHBUTTON "",IDC_TURBO_RIGHT,443,94,57,14,BS_FLAT
LTEXT "Left",IDC_STATIC,412,76,20,14,SS_CENTERIMAGE
LTEXT "Right",IDC_STATIC,412,94,20,14,SS_CENTERIMAGE
GROUPBOX "Power switch",IDC_TURBO,396,121,121,66,WS_GROUP
PUSHBUTTON "",IDC_LASER,443,76,57,14,BS_FLAT
LTEXT "Laser",IDC_STATIC,412,132,20,14,SS_CENTERIMAGE
END
IDD_LIBUSB_CFG DIALOGEX 0, 0, 250, 35

View File

@ -293,6 +293,8 @@
#define IDC_TURBO_LEFT 1339
#define IDC_TURBO_RIGHT 1340
#define IDC_TURBO 1341
#define IDC_POWER_SWITCH 1342
#define IDC_LASER 1343
#define ID_FILE_EXIT 40005
#define ID_HELP_ABOUT 40008
#define ID_EMULATION_START 40009