Skip to content

Commit

Permalink
Add a platform abstraction for 6-DOF input devices.
Browse files Browse the repository at this point in the history
This commit mostly just moves code around.
  • Loading branch information
whitequark committed Jul 18, 2018
1 parent 6b5db58 commit 06ab8bd
Show file tree
Hide file tree
Showing 14 changed files with 399 additions and 315 deletions.
6 changes: 2 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,8 @@ if(WIN32 OR APPLE)
else()
# On Linux and BSDs we're a good citizen and link to system libraries.

find_package(PkgConfig REQUIRED)

find_package(Backtrace)
find_package(SpaceWare)

find_package(PkgConfig REQUIRED)
find_package(ZLIB REQUIRED)
find_package(PNG REQUIRED)
find_package(Freetype REQUIRED)
Expand Down Expand Up @@ -202,6 +199,7 @@ if(ENABLE_GUI)
find_library(APPKIT_LIBRARY AppKit REQUIRED)
else()
find_package(OpenGL REQUIRED)
find_package(SpaceWare)
pkg_check_modules(FONTCONFIG REQUIRED fontconfig)
pkg_check_modules(JSONC REQUIRED json-c)
pkg_check_modules(GTKMM REQUIRED gtkmm-3.0>=3.18 pangomm-1.4 x11)
Expand Down
2 changes: 1 addition & 1 deletion cmake/FindSpaceWare.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
# SPACEWARE_INCLUDE_DIR - header location
# SPACEWARE_LIBRARIES - library to link against
# SPACEWARE_FOUND - true if pugixml was found.
# SPACEWARE_FOUND - true if libspnav was found.

if(UNIX)

Expand Down
1 change: 1 addition & 0 deletions src/graphicswin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ void GraphicsWindow::Init() {
window->onRender = std::bind(&GraphicsWindow::Paint, this);
window->onKeyboardEvent = std::bind(&GraphicsWindow::KeyboardEvent, this, _1);
window->onMouseEvent = std::bind(&GraphicsWindow::MouseEvent, this, _1);
window->onSixDofEvent = std::bind(&GraphicsWindow::SixDofEvent, this, _1);
window->onEditingDone = std::bind(&GraphicsWindow::EditControlDone, this, _1);
window->SetMinContentSize(720, 670);
PopulateMainMenu();
Expand Down
41 changes: 19 additions & 22 deletions src/mouse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1495,15 +1495,18 @@ void GraphicsWindow::MouseLeave() {
SS.extraLine.draw = false;
}

void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz,
double rx, double ry, double rz,
bool shiftDown)
{
void GraphicsWindow::SixDofEvent(Platform::SixDofEvent event) {
if(event.type == Platform::SixDofEvent::Type::RELEASE) {
ZoomToFit(/*includingInvisibles=*/false, /*useSelection=*/true);
Invalidate();
return;
}

if(!havePainted) return;
Vector out = projRight.Cross(projUp);

// rotation vector is axis of rotation, and its magnitude is angle
Vector aa = Vector::From(rx, ry, rz);
Vector aa = Vector::From(event.rotationX, event.rotationY, event.rotationZ);
// but it's given with respect to screen projection frame
aa = aa.ScaleOutOfCsys(projRight, projUp, out);
double aam = aa.Magnitude();
Expand All @@ -1516,38 +1519,38 @@ void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz,
if(gs.points == 1 && gs.n == 1) e = SK.GetEntity(gs.point [0]);
if(gs.entities == 1 && gs.n == 1) e = SK.GetEntity(gs.entity[0]);
if(e) g = SK.GetGroup(e->group);
if(g && g->type == Group::Type::LINKED && !shiftDown) {
if(g && g->type == Group::Type::LINKED && !event.shiftDown) {
// Apply the transformation to a linked part. Gain down the Z
// axis, since it's hard to see what you're doing on that one since
// it's normal to the screen.
Vector t = projRight.ScaledBy(tx/scale).Plus(
projUp .ScaledBy(ty/scale).Plus(
out .ScaledBy(0.1*tz/scale)));
Vector t = projRight.ScaledBy(event.translationX/scale).Plus(
projUp .ScaledBy(event.translationY/scale).Plus(
out .ScaledBy(0.1*event.translationZ/scale)));
Quaternion q = Quaternion::From(aa, aam);

// If we go five seconds without SpaceNavigator input, or if we've
// switched groups, then consider that a new action and save an undo
// point.
int64_t now = GetMilliseconds();
if(now - lastSpaceNavigatorTime > 5000 ||
lastSpaceNavigatorGroup.v != g->h.v)
if(now - last6DofTime > 5000 ||
last6DofGroup.v != g->h.v)
{
SS.UndoRemember();
}

g->TransformImportedBy(t, q);

lastSpaceNavigatorTime = now;
lastSpaceNavigatorGroup = g->h;
last6DofTime = now;
last6DofGroup = g->h;
SS.MarkGroupDirty(g->h);
} else {
// Apply the transformation to the view of the everything. The
// x and y components are translation; but z component is scale,
// not translation, or else it would do nothing in a parallel
// projection
offset = offset.Plus(projRight.ScaledBy(tx/scale));
offset = offset.Plus(projUp.ScaledBy(ty/scale));
scale *= exp(0.001*tz);
offset = offset.Plus(projRight.ScaledBy(event.translationX/scale));
offset = offset.Plus(projUp.ScaledBy(event.translationY/scale));
scale *= exp(0.001*event.translationZ);

if(aam > 0.0) {
projRight = projRight.RotatedAbout(aa, -aam);
Expand All @@ -1559,9 +1562,3 @@ void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz,
havePainted = false;
Invalidate();
}

void GraphicsWindow::SpaceNavigatorButtonUp() {
ZoomToFit(/*includingInvisibles=*/false, /*useSelection=*/true);
Invalidate();
}

152 changes: 2 additions & 150 deletions src/platform/cocoamain.mm
Original file line number Diff line number Diff line change
Expand Up @@ -63,154 +63,6 @@ - (void)applicationTerminatePrompt {
}
@end

/*
* Normally we would just link to the 3DconnexionClient framework.
* We don't want to (are not allowed to) distribute the official
* framework, so we're trying to use the one installed on the users
* computer. There are some different versions of the framework,
* the official one and re-implementations using an open source driver
* for older devices (spacenav-plus). So weak-linking isn't an option,
* either. The only remaining way is using CFBundle to dynamically
* load the library at runtime, and also detect its availability.
*
* We're also defining everything needed from the 3DconnexionClientAPI,
* so we're not depending on the API headers.
*/

#pragma pack(push,2)

enum {
kConnexionClientModeTakeOver = 1,
kConnexionClientModePlugin = 2
};

#define kConnexionMsgDeviceState '3dSR'
#define kConnexionMaskButtons 0x00FF
#define kConnexionMaskAxis 0x3F00

typedef struct {
uint16_t version;
uint16_t client;
uint16_t command;
int16_t param;
int32_t value;
UInt64 time;
uint8_t report[8];
uint16_t buttons8;
int16_t axis[6];
uint16_t address;
uint32_t buttons;
} ConnexionDeviceState, *ConnexionDeviceStatePtr;

#pragma pack(pop)

typedef void (*ConnexionAddedHandlerProc)(io_connect_t);
typedef void (*ConnexionRemovedHandlerProc)(io_connect_t);
typedef void (*ConnexionMessageHandlerProc)(io_connect_t, natural_t, void *);

typedef OSErr (*InstallConnexionHandlersProc)(ConnexionMessageHandlerProc, ConnexionAddedHandlerProc, ConnexionRemovedHandlerProc);
typedef void (*CleanupConnexionHandlersProc)(void);
typedef UInt16 (*RegisterConnexionClientProc)(UInt32, UInt8 *, UInt16, UInt32);
typedef void (*UnregisterConnexionClientProc)(UInt16);

static BOOL connexionShiftIsDown = NO;
static UInt16 connexionClient = 0;
static UInt32 connexionSignature = 'SoSp';
static UInt8 *connexionName = (UInt8 *)"\x10SolveSpace";
static CFBundleRef spaceBundle = NULL;
static InstallConnexionHandlersProc installConnexionHandlers = NULL;
static CleanupConnexionHandlersProc cleanupConnexionHandlers = NULL;
static RegisterConnexionClientProc registerConnexionClient = NULL;
static UnregisterConnexionClientProc unregisterConnexionClient = NULL;

static void connexionAdded(io_connect_t con) {}
static void connexionRemoved(io_connect_t con) {}
static void connexionMessage(io_connect_t con, natural_t type, void *arg) {
if (type != kConnexionMsgDeviceState) {
return;
}

ConnexionDeviceState *device = (ConnexionDeviceState *)arg;

dispatch_async(dispatch_get_main_queue(), ^(void){
SolveSpace::SS.GW.SpaceNavigatorMoved(
(double)device->axis[0] * -0.25,
(double)device->axis[1] * -0.25,
(double)device->axis[2] * 0.25,
(double)device->axis[3] * -0.0005,
(double)device->axis[4] * -0.0005,
(double)device->axis[5] * -0.0005,
(connexionShiftIsDown == YES) ? 1 : 0
);
});
}

static void connexionInit() {
NSString *bundlePath = @"/Library/Frameworks/3DconnexionClient.framework";
NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath];
spaceBundle = CFBundleCreate(kCFAllocatorDefault, (__bridge CFURLRef)bundleURL);

// Don't continue if no Spacemouse driver is installed on this machine
if (spaceBundle == NULL) {
return;
}

installConnexionHandlers = (InstallConnexionHandlersProc)
CFBundleGetFunctionPointerForName(spaceBundle,
CFSTR("InstallConnexionHandlers"));

cleanupConnexionHandlers = (CleanupConnexionHandlersProc)
CFBundleGetFunctionPointerForName(spaceBundle,
CFSTR("CleanupConnexionHandlers"));

registerConnexionClient = (RegisterConnexionClientProc)
CFBundleGetFunctionPointerForName(spaceBundle,
CFSTR("RegisterConnexionClient"));

unregisterConnexionClient = (UnregisterConnexionClientProc)
CFBundleGetFunctionPointerForName(spaceBundle,
CFSTR("UnregisterConnexionClient"));

// Only continue if all required symbols have been loaded
if ((installConnexionHandlers == NULL) || (cleanupConnexionHandlers == NULL)
|| (registerConnexionClient == NULL) || (unregisterConnexionClient == NULL)) {
CFRelease(spaceBundle);
spaceBundle = NULL;
return;
}

installConnexionHandlers(&connexionMessage, &connexionAdded, &connexionRemoved);
connexionClient = registerConnexionClient(connexionSignature, connexionName,
kConnexionClientModeTakeOver, kConnexionMaskButtons | kConnexionMaskAxis);

// Monitor modifier flags to detect Shift button state changes
[NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyDownMask | NSFlagsChangedMask)
handler:^(NSEvent *event) {
if (event.modifierFlags & NSShiftKeyMask) {
connexionShiftIsDown = YES;
}
return event;
}];

[NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyUpMask | NSFlagsChangedMask)
handler:^(NSEvent *event) {
if (!(event.modifierFlags & NSShiftKeyMask)) {
connexionShiftIsDown = NO;
}
return event;
}];
}

static void connexionClose() {
if (spaceBundle == NULL) {
return;
}

unregisterConnexionClient(connexionClient);
cleanupConnexionHandlers();

CFRelease(spaceBundle);
}

int main(int argc, const char *argv[]) {
ApplicationDelegate *delegate = [[ApplicationDelegate alloc] init];
Expand All @@ -226,12 +78,12 @@ int main(int argc, const char *argv[]) {
SolveSpace::SetLocale("en_US");
}

connexionInit();
SolveSpace::Platform::Open3DConnexion();
SolveSpace::SS.Init();

[NSApp run];

connexionClose();
SolveSpace::Platform::Close3DConnexion();
SolveSpace::SK.Clear();
SolveSpace::SS.Clear();

Expand Down
51 changes: 0 additions & 51 deletions src/platform/gtkmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@
#include "solvespace.h"
#include "config.h"

#ifdef HAVE_SPACEWARE
#include <spnav.h>
#endif

namespace SolveSpace {

void OpenWebsite(const char *url) {
Expand Down Expand Up @@ -78,40 +74,6 @@ std::vector<Platform::Path> GetFontFiles() {
return fonts;
}


/* Space Navigator support */

#ifdef HAVE_SPACEWARE
static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gxevent, GdkEvent *, gpointer) {
XEvent *xevent = (XEvent*) gxevent;

spnav_event sev;
if(!spnav_x11_event(xevent, &sev))
return GDK_FILTER_CONTINUE;

switch(sev.type) {
case SPNAV_EVENT_MOTION:
SS.GW.SpaceNavigatorMoved(
(double)sev.motion.x,
(double)sev.motion.y,
(double)sev.motion.z * -1.0,
(double)sev.motion.rx * 0.001,
(double)sev.motion.ry * 0.001,
(double)sev.motion.rz * -0.001,
xevent->xmotion.state & ShiftMask);
break;

case SPNAV_EVENT_BUTTON:
if(!sev.button.press && sev.button.bnum == 0) {
SS.GW.SpaceNavigatorButtonUp();
}
break;
}

return GDK_FILTER_REMOVE;
}
#endif

};

int main(int argc, char** argv) {
Expand Down Expand Up @@ -149,10 +111,6 @@ int main(int argc, char** argv) {
style_provider,
600 /*Gtk::STYLE_PROVIDER_PRIORITY_APPLICATION*/);

#ifdef HAVE_SPACEWARE
gdk_window_add_filter(NULL, GdkSpnavFilter, NULL);
#endif

const char* const* langNames = g_get_language_names();
while(*langNames) {
if(SetLocale(*langNames++)) break;
Expand All @@ -163,15 +121,6 @@ int main(int argc, char** argv) {

SS.Init();

#if defined(HAVE_SPACEWARE) && defined(GDK_WINDOWING_X11)
if(GDK_IS_X11_DISPLAY(Gdk::Display::get_default()->gobj())) {
// We don't care if it can't be opened; just continue without.
spnav_x11_open(gdk_x11_get_default_xdisplay(),
gdk_x11_window_get_xid(((Gtk::Window *)SS.GW.window->NativePtr())
->get_window()->gobj()));
}
#endif

if(argc >= 2) {
if(argc > 2) {
dbp("Only the first file passed on command line will be opened.");
Expand Down

0 comments on commit 06ab8bd

Please sign in to comment.