Skip to content

Commit

Permalink
Add generalized spnavdev support.
Browse files Browse the repository at this point in the history
  • Loading branch information
rpavlik authored and ruevs committed Dec 4, 2024
1 parent 7330600 commit bec8b82
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ if(ENABLE_GUI)
"${CMAKE_SOURCE_DIR}/extlib/si")
set(SPACEWARE_LIBRARIES
"${CMAKE_SOURCE_DIR}/extlib/si/siapp.lib")
else()
message(STATUS "Using libspnavdev")
add_vendored_subdirectory(extlib/libspnavdev)
set(HAVE_SPNAVDEV TRUE)
endif()
elseif(APPLE)
find_package(OpenGL REQUIRED)
Expand Down
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ if(SPACEWARE_FOUND)
${SPACEWARE_LIBRARIES})
endif()

if(HAVE_SPNAVDEV)
target_link_libraries(slvs_deps INTERFACE spnavdev)
endif()

if(ENABLE_OPENMP)
target_link_libraries(slvs_deps INTERFACE slvs_openmp)
endif()
Expand Down Expand Up @@ -265,6 +269,7 @@ if(ENABLE_GUI)
add_executable(solvespace WIN32 MACOSX_BUNDLE
${solvespace_core_gl_SOURCES}
platform/entrygui.cpp
platform/spnavdevice.cpp
$<TARGET_PROPERTY:resources,EXTRA_SOURCES>)

add_dependencies(solvespace
Expand Down Expand Up @@ -376,6 +381,7 @@ if(ENABLE_GUI)
set_target_properties(solvespace PROPERTIES
LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF")
endif()

endif()

# solvespace headless library
Expand Down
3 changes: 3 additions & 0 deletions src/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
/* Do we have the si library on win32, or libspnav on *nix? */
#cmakedefine HAVE_SPACEWARE

/* Do we have libspnavdev? */
#cmakedefine HAVE_SPNAVDEV

/* What OpenGL version do we use? */
#define HAVE_OPENGL @OPENGL@

Expand Down
53 changes: 53 additions & 0 deletions src/platform/guiwin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
# undef uint32_t
#endif

#include "spnavdevice.h"

#if defined(__GNUC__)
// Disable bogus warning emitted by GCC on GetProcAddress, since there seems to be no way
// of restructuring the code to easily disable it just at the call site.
Expand Down Expand Up @@ -541,6 +543,8 @@ class WindowImplWin32 final : public Window {
SiHdl hSpaceWare = SI_NO_HANDLE;
#endif

std::unique_ptr<NavDeviceWrapper> navDev;

std::shared_ptr<MenuBarImplWin32> menuBar;
std::string tooltipText;
bool scrollbarVisible = false;
Expand Down Expand Up @@ -695,6 +699,11 @@ class WindowImplWin32 final : public Window {
// Make sure any of our child windows get destroyed before we call DestroyWindow, or their
// own destructors may fail.
menuBar.reset();

if (navDev != NULL) {
KillTimer(hWindow, (UINT_PTR)this);
}
navDev.reset();

sscheck(DestroyWindow(hWindow));
#if defined(HAVE_SPACEWARE)
Expand Down Expand Up @@ -736,6 +745,8 @@ class WindowImplWin32 final : public Window {
event.rotationX = sse.u.spwData.mData[SI_RX]*0.001,
event.rotationY = sse.u.spwData.mData[SI_RY]*0.001,
event.rotationZ = -sse.u.spwData.mData[SI_RZ]*0.001;
dbp("spacemouse %f, %f, %f, %f, %f, %f", event.translationX, event.translationY,
event.translationZ, event.rotationX, event.rotationY, event.rotationZ);
} else if(sse.type == SI_BUTTON_EVENT) {
if(SiButtonPressed(&sse) == SI_APP_FIT_BUTTON) {
event.type = SixDofEvent::Type::PRESS;
Expand Down Expand Up @@ -1064,6 +1075,28 @@ class WindowImplWin32 final : public Window {
break;
}

case WM_TIMER: {
//! @todo where to put this? We don't actually need to handle window messages,
//! just poll it periodically.
if(window->navDev != nullptr && wParam == (WPARAM)window) {

SixDofEvent event = {};
event.shiftDown = ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0);
event.controlDown = ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0);
if(window->navDev->process(event)) {
if(event.type == SixDofEvent::Type::MOTION) {
dbp("spnavdev %f, %f, %f, %f, %f, %f", event.translationX,
event.translationY, event.translationZ, event.rotationX,
event.rotationY, event.rotationZ);
}
if(window->onSixDofEvent) {
window->onSixDofEvent(event);
}
}
}
break;
}

default:
return DefWindowProcW(h, msg, wParam, lParam);
}
Expand Down Expand Up @@ -1437,6 +1470,26 @@ void Request3DConnexionEventsForWindow(WindowRef window) {
SiSetUiMode(windowImpl->hSpaceWare, SI_UI_NO_CONTROLS);
}
}
#elif defined(HAVE_SPNAVDEV)
static std::unique_ptr<NavDeviceWrapper> navWrapper;
void Open3DConnexion() {
navWrapper = std::make_unique<NavDeviceWrapper>();
if(!navWrapper->active()) {
navWrapper.reset();
}
}
void Close3DConnexion() {
navWrapper.reset();
}
void Request3DConnexionEventsForWindow(WindowRef window) {
std::shared_ptr<WindowImplWin32> windowImpl = std::static_pointer_cast<WindowImplWin32>(window);
if(navWrapper) {
// Have the window adopt our object
windowImpl->navDev = std::move(navWrapper);
navWrapper.reset();
SetTimer(windowImpl->hWindow, (UINT_PTR)windowImpl.get(), USER_TIMER_MINIMUM, (TIMERPROC) NULL);
}
}
#else
void Open3DConnexion() {}
void Close3DConnexion() {}
Expand Down
146 changes: 146 additions & 0 deletions src/platform/spnavdevice.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//-----------------------------------------------------------------------------
// Cross-platform handling of spnavdev 6-dof input.
//
// Copyright 2021 Collabora, Ltd.
//-----------------------------------------------------------------------------

#include "spnavdevice.h"
#include "config.h"

#ifdef HAVE_SPNAVDEV
# include "spnavdev.h"
# include <string>
# include <solvespace.h>

NavDeviceWrapper::NavDeviceWrapper(const char* device) {
dev = spndev_open(device);
if(dev == nullptr) {
return;
}

int fd = spndev_fd(dev);
populateAxes();
populateButtons();
}
NavDeviceWrapper::~NavDeviceWrapper() {
if(dev != nullptr) {
spndev_close(dev);
dev = nullptr;
}
}

static double transformIndex(union spndev_event const& ev,
NavDeviceWrapper::AxisData const& axisData) {
ssassert(ev.type == SPNDEV_MOTION, "shouldn't be in here if not a motion event");
if(axisData.spnavdevIndex < 0) {
return 0;
}
return double(ev.mot.v[axisData.spnavdevIndex]);
}
bool NavDeviceWrapper::process(SolveSpace::Platform::SixDofEvent& event) {
using SolveSpace::Platform::SixDofEvent;
union spndev_event ev;
if(0 == spndev_process(dev, &ev)) {
return false;
}
if(ctrlPressed) {
event.controlDown = true;
}
if(shiftPressed) {
event.shiftDown = true;
}
switch(ev.type) {
case SPNDEV_MOTION:
event.type = SixDofEvent::Type::MOTION;
event.translationX = transformIndex(ev, axes[0]);
event.translationY = transformIndex(ev, axes[1]);
event.translationZ = transformIndex(ev, axes[2]);
event.rotationX = transformIndex(ev, axes[3]) * 0.001;
event.rotationY = transformIndex(ev, axes[4]) * 0.001;
event.rotationZ = transformIndex(ev, axes[5]) * 0.001;
return true;
case SPNDEV_BUTTON: {
if(ev.bn.num >= buttons.size()) {
return false;
}
const NavButton meaning = buttons[ev.bn.num];
auto type = ev.bn.press ? SixDofEvent::Type::PRESS : SixDofEvent::Type::RELEASE;
switch(meaning) {
case NavButton::UNUSED:
// we don't handle this button.
return false;

case NavButton::SHIFT:
// handled internally to this class
shiftPressed = type == SixDofEvent::Type::PRESS;
return false;

case NavButton::CTRL:
// handled internally to this class
ctrlPressed = type == SixDofEvent::Type::PRESS;
return false;

case NavButton::FIT:
event.button = SixDofEvent::Button::FIT;
event.type = type;
return true;
}
break;
}
default: return false;
}
return false;
}

void NavDeviceWrapper::populateAxes() {
using std::begin;
using std::end;
const std::string axis_names[] = {"Tx", "Ty", "Tz", "Rx", "Ry", "Rz"};
const auto b = begin(axis_names);
const auto e = end(axis_names);
const auto num_axes = spndev_num_axes(dev);
for(int axis_idx = 0; axis_idx < num_axes; ++axis_idx) {
auto axis_name = spndev_axis_name(dev, axis_idx);
auto it = std::find_if(b, e, [&](std::string const& name) { return name == axis_name; });
if(it != e) {
ptrdiff_t remapped_index = std::distance(b, it);
axes[remapped_index] = AxisData{axis_idx};
}
}
}

void NavDeviceWrapper::populateButtons() {
using std::begin;
using std::end;
using ButtonData = std::pair<std::string, NavButton>;
const ButtonData button_name_pairs[] = {
{"CTRL", NavButton::CTRL},
{"FIT", NavButton::FIT},
{"SHIFT", NavButton::SHIFT},
};

const auto b = begin(button_name_pairs);
const auto e = end(button_name_pairs);
const auto num_buttons = spndev_num_buttons(dev);
buttons.resize(num_buttons);
for(int button_idx = 0; button_idx < num_buttons; ++button_idx) {
auto button_name = spndev_button_name(dev, button_idx);
auto it =
std::find_if(b, e, [&](ButtonData const& data) { return data.first == button_name; });
if(it != e) {
buttons[button_idx] = it->second;
}
}
}

#else

NavDeviceWrapper::NavDeviceWrapper(const char* device) {
}
NavDeviceWrapper::~NavDeviceWrapper() {
}

bool NavDeviceWrapper::process(SolveSpace::Platform::SixDofEvent&) {
return false;
}
#endif
62 changes: 62 additions & 0 deletions src/platform/spnavdevice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//-----------------------------------------------------------------------------
// Cross-platform handling of spnavdev 6-dof input.
//
// Copyright 2021 Collabora, Ltd.
//-----------------------------------------------------------------------------

#ifndef SOLVESPACE_SPNAVDEVICE_H
#define SOLVESPACE_SPNAVDEVICE_H
#include <vector>
#include <array>

namespace SolveSpace {
namespace Platform {
struct SixDofEvent;
}
} // namespace SolveSpace
struct spndev;

class NavDeviceWrapper {
public:
explicit NavDeviceWrapper(const char* device = nullptr);
~NavDeviceWrapper();

// no copy, no move
NavDeviceWrapper(NavDeviceWrapper const&) = delete;
NavDeviceWrapper& operator=(NavDeviceWrapper const&) = delete;
NavDeviceWrapper(NavDeviceWrapper&&) = delete;
NavDeviceWrapper& operator=(NavDeviceWrapper&&) = delete;

bool active() const noexcept {
return dev != nullptr;
}

//! true when the event has data in it to deal with.
bool process(SolveSpace::Platform::SixDofEvent& event);

enum class NavButton {
UNUSED,
FIT,
CTRL,
SHIFT,
};
struct AxisData {
AxisData() = default;
AxisData(int spnavdevIndex_) : spnavdevIndex(spnavdevIndex_) {
}

int spnavdevIndex = -1;
};

private:
void populateAxes();
void populateButtons();
struct spndev* dev = nullptr;
//! indexed by the spnavdev index
std::vector<NavButton> buttons;
//! indexed by our internal axis index.
std::array<AxisData, 6> axes;
bool ctrlPressed = false;
bool shiftPressed = false;
};
#endif // !SOLVESPACE_SPNAVDEVICE_H

0 comments on commit bec8b82

Please sign in to comment.