diff --git a/dtool/src/prc/configVariableBool.cxx b/dtool/src/prc/configVariableBool.cxx index 701f40c65d..27556f3f2b 100644 --- a/dtool/src/prc/configVariableBool.cxx +++ b/dtool/src/prc/configVariableBool.cxx @@ -12,6 +12,7 @@ */ #include "configVariableBool.h" +#include "mutexImpl.h" /** * Refreshes the cached value. @@ -21,7 +22,10 @@ reload_value() const { // NB. MSVC doesn't guarantee that this mutex is initialized in a // thread-safe manner. But chances are that the first time this is called // is at static init time, when there is no risk of data races. - static MutexImpl lock; + + // This needs to be a recursive mutex, because get_bool_word() may end up + // indirectly querying another bool config variable. + static ReMutexImpl lock; lock.lock(); // We check again for cache validity since another thread may have beaten diff --git a/panda/src/collide/collisionInvSphere.cxx b/panda/src/collide/collisionInvSphere.cxx index 8a75135e55..1428078d09 100644 --- a/panda/src/collide/collisionInvSphere.cxx +++ b/panda/src/collide/collisionInvSphere.cxx @@ -288,6 +288,119 @@ test_intersection_from_segment(const CollisionEntry &entry) const { return new_entry; } +/** + * + */ +PT(CollisionEntry) CollisionInvSphere:: +test_intersection_from_capsule(const CollisionEntry &entry) const { + const CollisionCapsule *capsule; + DCAST_INTO_R(capsule, entry.get_from(), nullptr); + + const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + + LPoint3 from_a = capsule->get_point_a() * wrt_mat; + LPoint3 from_b = capsule->get_point_b() * wrt_mat; + + LVector3 from_radius_v = + LVector3(capsule->get_radius(), 0.0f, 0.0f) * wrt_mat; + PN_stdfloat from_radius = from_radius_v.length(); + + LPoint3 center = get_center(); + PN_stdfloat radius = get_radius(); + + // Check which one of the points lies furthest inside the sphere. + PN_stdfloat dist_a = (from_a - center).length(); + PN_stdfloat dist_b = (from_b - center).length(); + if (dist_b > dist_a) { + // Store the furthest point into from_a/dist_a. + dist_a = dist_b; + from_a = from_b; + } + + // from_a now contains the furthest point. Is it inside? + if (dist_a < radius - from_radius) { + return nullptr; + } + + if (collide_cat.is_debug()) { + collide_cat.debug() + << "intersection detected from " << entry.get_from_node_path() + << " into " << entry.get_into_node_path() << "\n"; + } + PT(CollisionEntry) new_entry = new CollisionEntry(entry); + + LVector3 normal = center - from_a; + normal.normalize(); + new_entry->set_surface_point(get_center() - normal * radius); + new_entry->set_interior_point(from_a - normal * from_radius); + + if (has_effective_normal() && capsule->get_respect_effective_normal()) { + new_entry->set_surface_normal(get_effective_normal()); + } else { + new_entry->set_surface_normal(normal); + } + + return new_entry; +} + +/** + * Double dispatch point for box as a FROM object + */ +PT(CollisionEntry) CollisionInvSphere:: +test_intersection_from_box(const CollisionEntry &entry) const { + const CollisionBox *box; + DCAST_INTO_R(box, entry.get_from(), nullptr); + + const LMatrix4 &wrt_mat = entry.get_wrt_mat(); + + LPoint3 center = get_center(); + PN_stdfloat radius_sq = get_radius(); + radius_sq *= radius_sq; + + // Just figure out which box point is furthest from the center. If it + // exceeds the radius, the furthest point wins. + + PN_stdfloat max_dist_sq = -1.0; + LPoint3 deepest_vertex; + + for (int i = 0; i < 8; ++i) { + LPoint3 point = wrt_mat.xform_point(box->get_point(i)); + + PN_stdfloat dist_sq = (point - center).length_squared(); + if (dist_sq > max_dist_sq) { + deepest_vertex = point; + max_dist_sq = dist_sq; + } + } + + if (max_dist_sq < radius_sq) { + // The point furthest away from the center is still inside the sphere. + // Therefore, no collision. + return nullptr; + } + + if (collide_cat.is_debug()) { + collide_cat.debug() + << "intersection detected from " << entry.get_from_node_path() + << " into " << entry.get_into_node_path() << "\n"; + } + + PT(CollisionEntry) new_entry = new CollisionEntry(entry); + + // The interior point is just the deepest cube vertex. + new_entry->set_interior_point(deepest_vertex); + + // Now extrapolate the surface point and normal from that. + LVector3 normal = center - deepest_vertex; + normal.normalize(); + new_entry->set_surface_point(center - normal * get_radius()); + new_entry->set_surface_normal( + (has_effective_normal() && box->get_respect_effective_normal()) + ? get_effective_normal() : normal); + + return new_entry; +} + /** * Fills the _viz_geom GeomNode up with Geoms suitable for rendering this * solid. diff --git a/panda/src/collide/collisionInvSphere.h b/panda/src/collide/collisionInvSphere.h index fe8b43b10d..b716f412d6 100644 --- a/panda/src/collide/collisionInvSphere.h +++ b/panda/src/collide/collisionInvSphere.h @@ -55,6 +55,10 @@ protected: test_intersection_from_ray(const CollisionEntry &entry) const; virtual PT(CollisionEntry) test_intersection_from_segment(const CollisionEntry &entry) const; + virtual PT(CollisionEntry) + test_intersection_from_capsule(const CollisionEntry &entry) const; + virtual PT(CollisionEntry) + test_intersection_from_box(const CollisionEntry &entry) const; virtual void fill_viz_geom(); diff --git a/panda/src/device/evdevInputDevice.cxx b/panda/src/device/evdevInputDevice.cxx index b137888d6d..6c1a09b866 100644 --- a/panda/src/device/evdevInputDevice.cxx +++ b/panda/src/device/evdevInputDevice.cxx @@ -82,7 +82,7 @@ static const struct DeviceMapping { {0x28de, 0x1142, InputDevice::DeviceClass::unknown, QB_steam_controller}, // Jess Tech Colour Rumble Pad {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0}, - // Trust GXT 24 + // SPEED Link SL-6535-SBK-01 {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, 0}, // 3Dconnexion Space Traveller 3D Mouse {0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0}, @@ -521,7 +521,10 @@ init_device() { } break; case ABS_THROTTLE: - if (quirks & QB_rudder_from_throttle) { + if (_device_class == DeviceClass::gamepad) { + // Apparently needed for 8bitdo N30 Pro controller + axis = InputDevice::Axis::right_x; + } else if (quirks & QB_rudder_from_throttle) { axis = InputDevice::Axis::rudder; } else { axis = InputDevice::Axis::throttle; diff --git a/panda/src/device/phidsdi.h b/panda/src/device/phidsdi.h new file mode 100644 index 0000000000..647b3ed322 --- /dev/null +++ b/panda/src/device/phidsdi.h @@ -0,0 +1,166 @@ +/** + * PANDA 3D SOFTWARE + * Copyright (c) Carnegie Mellon University. All rights reserved. + * + * All use of this software is subject to the terms of the revised BSD + * license. You should have received a copy of this license along + * with this source code in a file named "LICENSE." + * + * @file phidsdi.h + * @author rdb + * @date 2019-02-05 + */ + +#ifndef PHIDSDI_H +#define PHIDSDI_H + +#if defined(_WIN32) && !defined(CPPPARSER) + +// Copy definitions from hidusage.h, until we can drop support for the 7.1 SDK +typedef USHORT USAGE, *PUSAGE; + +#define HID_USAGE_PAGE_UNDEFINED ((USAGE) 0x00) +#define HID_USAGE_PAGE_GENERIC ((USAGE) 0x01) +#define HID_USAGE_PAGE_SIMULATION ((USAGE) 0x02) +#define HID_USAGE_PAGE_VR ((USAGE) 0x03) +#define HID_USAGE_PAGE_SPORT ((USAGE) 0x04) +#define HID_USAGE_PAGE_GAME ((USAGE) 0x05) +#define HID_USAGE_PAGE_KEYBOARD ((USAGE) 0x07) +#define HID_USAGE_PAGE_LED ((USAGE) 0x08) +#define HID_USAGE_PAGE_BUTTON ((USAGE) 0x09) + +#define HID_USAGE_GENERIC_POINTER ((USAGE) 0x01) +#define HID_USAGE_GENERIC_MOUSE ((USAGE) 0x02) +#define HID_USAGE_GENERIC_JOYSTICK ((USAGE) 0x04) +#define HID_USAGE_GENERIC_GAMEPAD ((USAGE) 0x05) +#define HID_USAGE_GENERIC_KEYBOARD ((USAGE) 0x06) +#define HID_USAGE_GENERIC_KEYPAD ((USAGE) 0x07) +#define HID_USAGE_GENERIC_SYSTEM_CTL ((USAGE) 0x80) + +#define HID_USAGE_GENERIC_X ((USAGE) 0x30) +#define HID_USAGE_GENERIC_Y ((USAGE) 0x31) +#define HID_USAGE_GENERIC_Z ((USAGE) 0x32) +#define HID_USAGE_GENERIC_RX ((USAGE) 0x33) +#define HID_USAGE_GENERIC_RY ((USAGE) 0x34) +#define HID_USAGE_GENERIC_RZ ((USAGE) 0x35) +#define HID_USAGE_GENERIC_SLIDER ((USAGE) 0x36) +#define HID_USAGE_GENERIC_DIAL ((USAGE) 0x37) +#define HID_USAGE_GENERIC_WHEEL ((USAGE) 0x38) +#define HID_USAGE_GENERIC_HATSWITCH ((USAGE) 0x39) + +// Copy definitions from hidpi.h, until we can drop support for the 7.1 SDK +#define HIDP_STATUS_SUCCESS ((NTSTATUS)(0x11 << 16)) + +typedef enum _HIDP_REPORT_TYPE { + HidP_Input, + HidP_Output, + HidP_Feature +} HIDP_REPORT_TYPE; + +typedef struct _HIDP_BUTTON_CAPS { + USAGE UsagePage; + UCHAR ReportID; + BOOLEAN IsAlias; + USHORT BitField; + USHORT LinkCollection; + USAGE LinkUsage; + USAGE LinkUsagePage; + BOOLEAN IsRange; + BOOLEAN IsStringRange; + BOOLEAN IsDesignatorRange; + BOOLEAN IsAbsolute; + ULONG Reserved[10]; + union { + struct { + USAGE UsageMin, UsageMax; + USHORT StringMin, StringMax; + USHORT DesignatorMin, DesignatorMax; + USHORT DataIndexMin, DataIndexMax; + } Range; + struct { + USAGE Usage, Reserved1; + USHORT StringIndex, Reserved2; + USHORT DesignatorIndex, Reserved3; + USHORT DataIndex, Reserved4; + } NotRange; + }; +} HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS; + +typedef struct _HIDP_VALUE_CAPS { + USAGE UsagePage; + UCHAR ReportID; + BOOLEAN IsAlias; + USHORT BitField; + USHORT LinkCollection; + USAGE LinkUsage; + USAGE LinkUsagePage; + BOOLEAN IsRange; + BOOLEAN IsStringRange; + BOOLEAN IsDesignatorRange; + BOOLEAN IsAbsolute; + BOOLEAN HasNull; + UCHAR Reserved; + USHORT BitSize; + USHORT ReportCount; + USHORT Reserved2[5]; + ULONG UnitsExp; + ULONG Units; + LONG LogicalMin, LogicalMax; + LONG PhysicalMin, PhysicalMax; + union { + struct { + USAGE UsageMin, UsageMax; + USHORT StringMin, StringMax; + USHORT DesignatorMin, DesignatorMax; + USHORT DataIndexMin, DataIndexMax; + } Range; + struct { + USAGE Usage, Reserved1; + USHORT StringIndex, Reserved2; + USHORT DesignatorIndex, Reserved3; + USHORT DataIndex, Reserved4; + } NotRange; + }; +} HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS; + +typedef PUCHAR PHIDP_REPORT_DESCRIPTOR; +typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA; + +typedef struct _HIDP_CAPS { + USAGE Usage; + USAGE UsagePage; + USHORT InputReportByteLength; + USHORT OutputReportByteLength; + USHORT FeatureReportByteLength; + USHORT Reserved[17]; + USHORT NumberLinkCollectionNodes; + USHORT NumberInputButtonCaps; + USHORT NumberInputValueCaps; + USHORT NumberInputDataIndices; + USHORT NumberOutputButtonCaps; + USHORT NumberOutputValueCaps; + USHORT NumberOutputDataIndices; + USHORT NumberFeatureButtonCaps; + USHORT NumberFeatureValueCaps; + USHORT NumberFeatureDataIndices; +} HIDP_CAPS, *PHIDP_CAPS; + +typedef struct _HIDP_DATA { + USHORT DataIndex; + USHORT Reserved; + union { + ULONG RawValue; + BOOLEAN On; + }; +} HIDP_DATA, *PHIDP_DATA; + +typedef LONG NTSTATUS; +typedef NTSTATUS (__stdcall *pHidP_GetCaps)(PHIDP_PREPARSED_DATA, PHIDP_CAPS); +typedef NTSTATUS (__stdcall *pHidP_GetButtonCaps)(HIDP_REPORT_TYPE, PHIDP_BUTTON_CAPS, PUSHORT, PHIDP_PREPARSED_DATA); +typedef NTSTATUS (__stdcall *pHidP_GetValueCaps)(HIDP_REPORT_TYPE, PHIDP_VALUE_CAPS, PUSHORT, PHIDP_PREPARSED_DATA); +typedef NTSTATUS (__stdcall *pHidP_GetData)(HIDP_REPORT_TYPE, PHIDP_DATA, PULONG, PHIDP_PREPARSED_DATA, PCHAR, ULONG); +typedef ULONG (__stdcall *pHidP_MaxDataListLength)(HIDP_REPORT_TYPE, PHIDP_PREPARSED_DATA); + +#endif + +#endif diff --git a/panda/src/device/winInputDeviceManager.cxx b/panda/src/device/winInputDeviceManager.cxx index 95f73549dd..515dc376d8 100644 --- a/panda/src/device/winInputDeviceManager.cxx +++ b/panda/src/device/winInputDeviceManager.cxx @@ -289,8 +289,8 @@ on_input_device_arrival(HANDLE handle) { // Some devices insert quite some trailing space here. wchar_t *wbuffer = (wchar_t *)buffer; - size_t wlen = wcslen(wbuffer); - while (iswspace(wbuffer[wlen - 1])) { + size_t wlen = wcsnlen_s(wbuffer, sizeof(buffer) / sizeof(wchar_t)); + while (wlen > 0 && iswspace(wbuffer[wlen - 1])) { wbuffer[--wlen] = 0; } TextEncoder encoder; @@ -391,6 +391,19 @@ on_input_device_removal(HANDLE handle) { } } +/** + * Polls the system to see if there are any new devices. In some + * implementations this is a no-op. + */ +void WinInputDeviceManager:: +update() { + MSG msg; + while (PeekMessage(&msg, _message_hwnd, WM_INPUT_DEVICE_CHANGE, WM_INPUT, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + /** * Implementation of the message loop. */ diff --git a/panda/src/device/winInputDeviceManager.h b/panda/src/device/winInputDeviceManager.h index 6c3fefd8c1..e99a01f761 100644 --- a/panda/src/device/winInputDeviceManager.h +++ b/panda/src/device/winInputDeviceManager.h @@ -52,9 +52,11 @@ private: pmap _raw_devices; pmap _raw_devices_by_path; + virtual void update() override; + static LRESULT WINAPI window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); - typedef CONFIGRET (*pCM_Get_DevNode_Property)(DEVINST, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE, PULONG, ULONG); + typedef CONFIGRET (WINAPI *pCM_Get_DevNode_Property)(DEVINST, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE, PULONG, ULONG); pCM_Get_DevNode_Property _CM_Get_DevNode_PropertyW; friend class InputDeviceManager; diff --git a/panda/src/device/winRawInputDevice.cxx b/panda/src/device/winRawInputDevice.cxx index 9c49d83122..583ad920da 100644 --- a/panda/src/device/winRawInputDevice.cxx +++ b/panda/src/device/winRawInputDevice.cxx @@ -14,156 +14,56 @@ #include "winRawInputDevice.h" #include "gamepadButton.h" #include "mouseButton.h" +#include "buttonRegistry.h" #if defined(_WIN32) && !defined(CPPPARSER) #include #include +#include "phidsdi.h" -// Copy definitions from hidusage.h, until we can drop support for the 7.1 SDK -typedef USHORT USAGE, *PUSAGE; +enum QuirkBits : int { + // Has no trigger axes. + QB_no_analog_triggers = 1, -#define HID_USAGE_PAGE_UNDEFINED ((USAGE) 0x00) -#define HID_USAGE_PAGE_GENERIC ((USAGE) 0x01) -#define HID_USAGE_PAGE_SIMULATION ((USAGE) 0x02) -#define HID_USAGE_PAGE_VR ((USAGE) 0x03) -#define HID_USAGE_PAGE_SPORT ((USAGE) 0x04) -#define HID_USAGE_PAGE_GAME ((USAGE) 0x05) -#define HID_USAGE_PAGE_KEYBOARD ((USAGE) 0x07) -#define HID_USAGE_PAGE_LED ((USAGE) 0x08) -#define HID_USAGE_PAGE_BUTTON ((USAGE) 0x09) + // Throttle goes from -1 to 1 rather than from 0 to 1. + QB_centered_throttle = 2, -#define HID_USAGE_GENERIC_POINTER ((USAGE) 0x01) -#define HID_USAGE_GENERIC_MOUSE ((USAGE) 0x02) -#define HID_USAGE_GENERIC_JOYSTICK ((USAGE) 0x04) -#define HID_USAGE_GENERIC_GAMEPAD ((USAGE) 0x05) -#define HID_USAGE_GENERIC_KEYBOARD ((USAGE) 0x06) -#define HID_USAGE_GENERIC_KEYPAD ((USAGE) 0x07) -#define HID_USAGE_GENERIC_SYSTEM_CTL ((USAGE) 0x80) + // Throttle is reversed. + QB_reversed_throttle = 4, +}; -#define HID_USAGE_GENERIC_X ((USAGE) 0x30) -#define HID_USAGE_GENERIC_Y ((USAGE) 0x31) -#define HID_USAGE_GENERIC_Z ((USAGE) 0x32) -#define HID_USAGE_GENERIC_RX ((USAGE) 0x33) -#define HID_USAGE_GENERIC_RY ((USAGE) 0x34) -#define HID_USAGE_GENERIC_RZ ((USAGE) 0x35) -#define HID_USAGE_GENERIC_SLIDER ((USAGE) 0x36) -#define HID_USAGE_GENERIC_DIAL ((USAGE) 0x37) -#define HID_USAGE_GENERIC_WHEEL ((USAGE) 0x38) -#define HID_USAGE_GENERIC_HATSWITCH ((USAGE) 0x39) +// Some nonstandard gamepads have different button mappings. +static const struct DeviceMapping { + unsigned short vendor; + unsigned short product; + InputDevice::DeviceClass device_class; + int quirks; + const char *buttons[16]; +} mapping_presets[] = { + // SNES-style USB gamepad + {0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers, + {"face_x", "face_a", "face_b", "face_y", "lshoulder", "rshoulder", "none", "none", "back", "start"} + }, + // SPEED Link SL-6535-SBK-01 + {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers, + {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"} + }, + // T.Flight Hotas X + {0x044f, 0xb108, InputDevice::DeviceClass::flight_stick, QB_centered_throttle | QB_reversed_throttle, + {0} + }, + // NVIDIA Shield Controller + {0x0955, 0x7214, InputDevice::DeviceClass::gamepad, 0, + {"face_a", "face_b", "n", "face_x", "face_y", "rshoulder", "lshoulder", "rshoulder", "e", "f", "g", "start", "h", "lstick", "rstick", "i"} + }, + {0}, +}; -// Copy definitions from hidpi.h, until we can drop support for the 7.1 SDK -#define HIDP_STATUS_SUCCESS ((NTSTATUS)(0x11 << 16)) - -typedef enum _HIDP_REPORT_TYPE { - HidP_Input, - HidP_Output, - HidP_Feature -} HIDP_REPORT_TYPE; - -typedef struct _HIDP_BUTTON_CAPS { - USAGE UsagePage; - UCHAR ReportID; - BOOLEAN IsAlias; - USHORT BitField; - USHORT LinkCollection; - USAGE LinkUsage; - USAGE LinkUsagePage; - BOOLEAN IsRange; - BOOLEAN IsStringRange; - BOOLEAN IsDesignatorRange; - BOOLEAN IsAbsolute; - ULONG Reserved[10]; - union { - struct { - USAGE UsageMin, UsageMax; - USHORT StringMin, StringMax; - USHORT DesignatorMin, DesignatorMax; - USHORT DataIndexMin, DataIndexMax; - } Range; - struct { - USAGE Usage, Reserved1; - USHORT StringIndex, Reserved2; - USHORT DesignatorIndex, Reserved3; - USHORT DataIndex, Reserved4; - } NotRange; - }; -} HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS; - -typedef struct _HIDP_VALUE_CAPS { - USAGE UsagePage; - UCHAR ReportID; - BOOLEAN IsAlias; - USHORT BitField; - USHORT LinkCollection; - USAGE LinkUsage; - USAGE LinkUsagePage; - BOOLEAN IsRange; - BOOLEAN IsStringRange; - BOOLEAN IsDesignatorRange; - BOOLEAN IsAbsolute; - BOOLEAN HasNull; - UCHAR Reserved; - USHORT BitSize; - USHORT ReportCount; - USHORT Reserved2[5]; - ULONG UnitsExp; - ULONG Units; - LONG LogicalMin, LogicalMax; - LONG PhysicalMin, PhysicalMax; - union { - struct { - USAGE UsageMin, UsageMax; - USHORT StringMin, StringMax; - USHORT DesignatorMin, DesignatorMax; - USHORT DataIndexMin, DataIndexMax; - } Range; - struct { - USAGE Usage, Reserved1; - USHORT StringIndex, Reserved2; - USHORT DesignatorIndex, Reserved3; - USHORT DataIndex, Reserved4; - } NotRange; - }; -} HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS; - -typedef PUCHAR PHIDP_REPORT_DESCRIPTOR; -typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA; - -typedef struct _HIDP_CAPS { - USAGE Usage; - USAGE UsagePage; - USHORT InputReportByteLength; - USHORT OutputReportByteLength; - USHORT FeatureReportByteLength; - USHORT Reserved[17]; - USHORT NumberLinkCollectionNodes; - USHORT NumberInputButtonCaps; - USHORT NumberInputValueCaps; - USHORT NumberInputDataIndices; - USHORT NumberOutputButtonCaps; - USHORT NumberOutputValueCaps; - USHORT NumberOutputDataIndices; - USHORT NumberFeatureButtonCaps; - USHORT NumberFeatureValueCaps; - USHORT NumberFeatureDataIndices; -} HIDP_CAPS, *PHIDP_CAPS; - -typedef struct _HIDP_DATA { - USHORT DataIndex; - USHORT Reserved; - union { - ULONG RawValue; - BOOLEAN On; - }; -} HIDP_DATA, *PHIDP_DATA; - -typedef LONG NTSTATUS; -typedef NTSTATUS (*pHidP_GetCaps)(PHIDP_PREPARSED_DATA, PHIDP_CAPS); -typedef NTSTATUS (*pHidP_GetButtonCaps)(HIDP_REPORT_TYPE, PHIDP_BUTTON_CAPS, PUSHORT, PHIDP_PREPARSED_DATA); -typedef NTSTATUS (*pHidP_GetValueCaps)(HIDP_REPORT_TYPE, PHIDP_VALUE_CAPS, PUSHORT, PHIDP_PREPARSED_DATA); -typedef NTSTATUS (*pHidP_GetData)(HIDP_REPORT_TYPE, PHIDP_DATA, PULONG, PHIDP_PREPARSED_DATA, PCHAR, ULONG); -typedef ULONG (*pHidP_MaxDataListLength)(HIDP_REPORT_TYPE, PHIDP_PREPARSED_DATA); +// This is our fallback button mapping, used with Xbox 360 and other devices. +static const char *default_gamepad_mapping[16] = { + "face_a", "face_b", "face_x", "face_y", "lshoulder", "rshoulder", "back", "start", "lstick", "rstick" +}; static pHidP_GetCaps _HidP_GetCaps = nullptr; static pHidP_GetButtonCaps _HidP_GetButtonCaps = nullptr; @@ -246,6 +146,9 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { _name = std::move(name); + int quirks = 0; + const char *const *gamepad_buttons = default_gamepad_mapping; + switch (info.dwType) { case RIM_TYPEMOUSE: _device_class = DeviceClass::mouse; @@ -264,7 +167,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { info.hid.usUsage == HID_USAGE_GENERIC_GAMEPAD) { _device_class = DeviceClass::gamepad; - // Flight sticks + // Various game controllers, incl. flight sticks and some gamepads } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) { _device_class = DeviceClass::flight_stick; @@ -273,11 +176,6 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { // Well, it claims to be a gamepad... _device_class = DeviceClass::gamepad; } - //TODO: better solution for this - if (_vendor_id == 0x0079 && _product_id == 0x0006) { - // Trust GXT 24 - _device_class = DeviceClass::gamepad; - } // Mice } else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC && @@ -306,6 +204,28 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { return false; } + if (_device_class == DeviceClass::gamepad || + _device_class == DeviceClass::flight_stick) { + // Do we have a built-in mapping? + const DeviceMapping *mapping = mapping_presets; + while (mapping->vendor != 0) { + if (info.hid.dwVendorId == mapping->vendor && + info.hid.dwProductId == mapping->product) { + _device_class = mapping->device_class; + gamepad_buttons = mapping->buttons; + quirks = mapping->quirks; + if (device_cat.is_debug()) { + device_cat.debug() + << "Using preset mapping for " << mapping->device_class + << " with VID=" << std::hex << mapping->vendor + << " PID=" << mapping->product << std::dec << "\n"; + } + break; + } + ++mapping; + } + } + // Initialize hid.dll, which provides the HID parser functions. static bool hid_initialized = false; if (!hid_initialized) { @@ -342,38 +262,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { << caps.NumberInputValueCaps << " value caps\n"; } - // Do we have a button mapping? - static const ButtonHandle gamepad_buttons_common[] = { - ButtonHandle::none(), - GamepadButton::face_a(), - GamepadButton::face_b(), - GamepadButton::face_x(), - GamepadButton::face_y(), - GamepadButton::lshoulder(), - GamepadButton::rshoulder(), - GamepadButton::start(), - GamepadButton::back(), - GamepadButton::lstick(), - GamepadButton::rstick(), - }; - const ButtonHandle *gamepad_buttons = gamepad_buttons_common; - if (_vendor_id == 0x0810 && _product_id == 0xe501) { - // SNES-style USB gamepad - static const ButtonHandle gamepad_buttons_snes[] = { - ButtonHandle::none(), - GamepadButton::face_x(), - GamepadButton::face_a(), - GamepadButton::face_b(), - GamepadButton::face_y(), - GamepadButton::lshoulder(), - GamepadButton::rshoulder(), - ButtonHandle::none(), - ButtonHandle::none(), - GamepadButton::back(), - GamepadButton::start(), - }; - gamepad_buttons = gamepad_buttons_snes; - } + ButtonRegistry *registry = ButtonRegistry::ptr(); // Prepare a mapping of data indices to button/axis indices. _indices.resize(caps.NumberInputDataIndices); @@ -414,7 +303,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { } } - nassertd(cap.Range.DataIndexMin + upper < _indices.size()) continue; + nassertd(cap.Range.DataIndexMin + upper < (int)_indices.size()) continue; // Windows will only tell us which buttons in a report are "on", so we // need to keep track of which buttons exist in which report so that we @@ -429,8 +318,10 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { switch (cap.UsagePage) { case HID_USAGE_PAGE_BUTTON: if (_device_class == DeviceClass::gamepad) { - if (usage < sizeof(gamepad_buttons_common) / sizeof(ButtonHandle)) { - handle = gamepad_buttons[usage]; + if (usage > 0 && usage - 1 < _countof(default_gamepad_mapping)) { + if (gamepad_buttons[usage - 1] != nullptr) { + handle = registry->find_button(gamepad_buttons[usage - 1]); + } } } else if (_device_class == DeviceClass::flight_stick) { if (usage > 0) { @@ -491,7 +382,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { } } - nassertd(cap.Range.DataIndexMin + upper < _indices.size()) continue; + nassertd(cap.Range.DataIndexMin + upper < (int)_indices.size()) continue; for (int j = 0; j <= upper; ++j) { USAGE usage = j + cap.Range.UsageMin; @@ -530,9 +421,17 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { break; case HID_USAGE_GENERIC_Z: if (_device_class == DeviceClass::gamepad) { - axis = Axis::left_trigger; + if ((quirks & QB_no_analog_triggers) == 0) { + axis = Axis::left_trigger; + } } else if (_device_class == DeviceClass::flight_stick) { axis = Axis::throttle; + if ((quirks & QB_reversed_throttle) != 0) { + std::swap(cap.LogicalMin, cap.LogicalMax); + } + if ((quirks & QB_centered_throttle) != 0) { + is_signed = false; + } } else { axis = Axis::z; swap(cap.LogicalMin, cap.LogicalMax); @@ -555,7 +454,9 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { break; case HID_USAGE_GENERIC_RZ: if (_device_class == DeviceClass::gamepad) { - axis = Axis::right_trigger; + if ((quirks & QB_no_analog_triggers) == 0) { + axis = Axis::right_trigger; + } } else { // Flip to match Panda's convention for heading. axis = Axis::yaw; @@ -580,10 +481,7 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) { } int axis_index; - if (_vendor_id == 0x044f && _product_id == 0xb108 && axis == Axis::throttle) { - // T.Flight Hotas X throttle is reversed and can go backwards. - axis_index = add_axis(axis, cap.LogicalMax, cap.LogicalMin, true); - } else if (!is_signed) { + if (!is_signed) { // All axes on the weird XInput-style mappings go from -1 to 1 axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax, true); } else { diff --git a/panda/src/device/xInputDevice.cxx b/panda/src/device/xInputDevice.cxx index 9e8035fb0e..b86a5cd90b 100644 --- a/panda/src/device/xInputDevice.cxx +++ b/panda/src/device/xInputDevice.cxx @@ -99,12 +99,12 @@ typedef struct _XINPUT_CAPABILITIES_EX { WORD Unknown2; } XINPUT_CAPABILITIES_EX; -typedef DWORD (*pXInputGetState)(DWORD, XINPUT_STATE *); -typedef DWORD (*pXInputSetState)(DWORD, XINPUT_VIBRATION *); -typedef DWORD (*pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); -typedef DWORD (*pXInputGetCapabilitiesEx)(DWORD, DWORD, DWORD, XINPUT_CAPABILITIES_EX *); -typedef DWORD (*pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); -typedef DWORD (*pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *); +typedef DWORD (WINAPI *pXInputGetState)(DWORD, XINPUT_STATE *); +typedef DWORD (WINAPI *pXInputSetState)(DWORD, XINPUT_VIBRATION *); +typedef DWORD (WINAPI *pXInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); +typedef DWORD (WINAPI *pXInputGetCapabilitiesEx)(DWORD, DWORD, DWORD, XINPUT_CAPABILITIES_EX *); +typedef DWORD (WINAPI *pXInputGetBatteryInformation)(DWORD, BYTE, XINPUT_BATTERY_INFORMATION *); +typedef DWORD (WINAPI *pXInputGetBaseBusInformation)(DWORD, XINPUT_BUSINFO *); static pXInputGetState get_state = nullptr; static pXInputSetState set_state = nullptr; diff --git a/panda/src/egg/eggVertexUV.I b/panda/src/egg/eggVertexUV.I index 05f6f8609f..eef97e52e3 100644 --- a/panda/src/egg/eggVertexUV.I +++ b/panda/src/egg/eggVertexUV.I @@ -97,6 +97,14 @@ has_tangent() const { return (_flags & F_has_tangent) != 0; } +/** + * + */ +INLINE bool EggVertexUV:: +has_tangent4() const { + return (_flags & F_has_tangent4) != 0; +} + /** * */ @@ -106,6 +114,19 @@ get_tangent() const { return _tangent; } +/** + * + */ +INLINE LVecBase4d EggVertexUV:: +get_tangent4() const { + LVecBase4d tangent4(_tangent, 1.0); + nassertr_always(has_tangent(), tangent4); + if (_flags & F_flip_computed_binormal) { + tangent4[3] = -1.0; + } + return tangent4; +} + /** * */ @@ -113,6 +134,22 @@ INLINE void EggVertexUV:: set_tangent(const LNormald &tangent) { _tangent = tangent; _flags |= F_has_tangent; + _flags &= ~(F_has_tangent4 | F_flip_computed_binormal); +} + +/** + * Sets the tangent vector, along with a fourth parameter that is multiplied + * with the result of cross(normal, tangent) when computing the binormal. + */ +INLINE void EggVertexUV:: +set_tangent4(const LVecBase4d &tangent) { + _tangent = tangent.get_xyz(); + _flags |= F_has_tangent4 | F_has_tangent; + if (tangent[3] < 0.0) { + _flags |= F_flip_computed_binormal; + } else { + _flags &= ~F_flip_computed_binormal; + } } /** diff --git a/panda/src/egg/eggVertexUV.cxx b/panda/src/egg/eggVertexUV.cxx index 49a3c39ace..bcc0c1055e 100644 --- a/panda/src/egg/eggVertexUV.cxx +++ b/panda/src/egg/eggVertexUV.cxx @@ -145,7 +145,10 @@ write(std::ostream &out, int indent_level) const { } else { indent(out, indent_level+2) << get_uv() << "\n"; } - if (has_tangent()) { + if (has_tangent4()) { + indent(out, indent_level + 2) + << " { " << get_tangent4() << " }\n"; + } else if (has_tangent()) { indent(out, indent_level + 2) << " { " << get_tangent() << " }\n"; } diff --git a/panda/src/egg/eggVertexUV.h b/panda/src/egg/eggVertexUV.h index 2ab0447639..e729f28101 100644 --- a/panda/src/egg/eggVertexUV.h +++ b/panda/src/egg/eggVertexUV.h @@ -45,8 +45,11 @@ PUBLISHED: INLINE void set_uvw(const LTexCoord3d &texCoord); INLINE bool has_tangent() const; + INLINE bool has_tangent4() const; INLINE const LNormald &get_tangent() const; + INLINE LVecBase4d get_tangent4() const; INLINE void set_tangent(const LNormald &tangent); + INLINE void set_tangent4(const LVecBase4d &tangent); INLINE void clear_tangent(); INLINE bool has_binormal() const; @@ -69,6 +72,10 @@ private: F_has_tangent = 0x001, F_has_binormal = 0x002, F_has_w = 0x004, + F_has_tangent4 = 0x008, + + // Only defined temporarily as we can't add a float to this class in 1.10. + F_flip_computed_binormal = 0x010, }; int _flags; diff --git a/panda/src/egg/parser.yxx b/panda/src/egg/parser.yxx index 1ffaa3a078..6125ead479 100644 --- a/panda/src/egg/parser.yxx +++ b/panda/src/egg/parser.yxx @@ -1103,6 +1103,14 @@ vertex_uv_body: } else { DCAST(EggVertexUV, egg_stack.back())->set_tangent(LNormald($4, $5, $6)); } +} + | vertex_uv_body TANGENT '{' real real real real '}' +{ + if (DCAST(EggVertexUV, egg_stack.back())->has_tangent()) { + eggyywarning("Ignoring repeated tangent"); + } else { + DCAST(EggVertexUV, egg_stack.back())->set_tangent4(LVecBase4d($4, $5, $6, $7)); + } } | vertex_uv_body BINORMAL '{' real real real '}' { diff --git a/tests/egg/test_egg_vertex_uv.py b/tests/egg/test_egg_vertex_uv.py new file mode 100644 index 0000000000..e2f1deeebf --- /dev/null +++ b/tests/egg/test_egg_vertex_uv.py @@ -0,0 +1,84 @@ +import pytest +from panda3d import core + +# Skip these tests if we can't import egg. +egg = pytest.importorskip("panda3d.egg") + + +def read_egg_vertex(string): + """Reads an EggVertex from a string.""" + data = " pool { 1 { %s } }" % (string) + stream = core.StringStream(data.encode('utf-8')) + data = egg.EggData() + assert data.read(stream) + pool, = data.get_children() + return pool.get_vertex(1) + + +def test_egg_vertex_uv_empty(): + vertex = read_egg_vertex(""" + 0 0 0 + { + 0 0 + } + """) + + obj = vertex.get_uv_obj("") + assert not obj.has_tangent() + assert not obj.has_tangent4() + + assert '' not in str(obj) + + +def test_egg_vertex_tangent(): + vertex = read_egg_vertex(""" + 0 0 0 + { + 0 0 + { 2 3 4 } + } + """) + + obj = vertex.get_uv_obj("") + assert obj.has_tangent() + assert not obj.has_tangent4() + assert obj.get_tangent() == (2, 3, 4) + assert obj.get_tangent4() == (2, 3, 4, 1) + + assert '{ 2 3 4 }' in str(obj) + + +def test_egg_vertex_tangent4_pos(): + vertex = read_egg_vertex(""" + 0 0 0 + { + 0 0 + { 2 3 4 1 } + } + """) + + obj = vertex.get_uv_obj("") + assert obj.has_tangent() + assert obj.has_tangent4() + assert obj.get_tangent() == (2, 3, 4) + assert obj.get_tangent4() == (2, 3, 4, 1) + + assert '{ 2 3 4 1 }' in str(obj) + + +def test_egg_vertex_tangent4_neg(): + vertex = read_egg_vertex(""" + 0 0 0 + { + 0 0 + { 2 3 4 -1 } + } + """) + + obj = vertex.get_uv_obj("") + assert obj.has_tangent() + assert obj.has_tangent4() + assert obj.get_tangent() == (2, 3, 4) + assert obj.get_tangent4() == (2, 3, 4, -1) + + assert '{ 2 3 4 -1 }' in str(obj)