Skip to content

Commit

Permalink
#15 add osx gamepad suppport (xbox 360 mapping)
Browse files Browse the repository at this point in the history
  • Loading branch information
XProger committed Jan 11, 2019
1 parent 2aa1e13 commit 93ce97e
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ namespace Input {
case jkRT : joy[index].RT = pos.x; break;
default : return;
}
setJoyDown(index, key, pos.x > 0.0f); // gamepad LT, RT auto-down state
setJoyDown(index, key, pos.x > EPS); // gamepad LT, RT auto-down state
}

void setJoyVibration(int playerIndex, float L, float R) {
Expand Down
16 changes: 4 additions & 12 deletions src/platform/osx/OpenLara.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

/* Begin PBXBuildFile section */
523F97E41DDF7AA5006FE2FC /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 523F97E31DDF7AA5006FE2FC /* Cocoa.framework */; };
523F97E61DDF926E006FE2FC /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 523F97E51DDF926E006FE2FC /* QuartzCore.framework */; };
9938F1A421E830250056E172 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9938F1A021E81EFE0056E172 /* IOKit.framework */; };
99713CC9204CAD9900006FAC /* tinflate.c in Sources */ = {isa = PBXBuildFile; fileRef = 99713CC8204CAD9900006FAC /* tinflate.c */; };
99BF38CD21E7176900D90A38 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 99BF38CC21E7176900D90A38 /* main.mm */; };
99BF38D221E7202500D90A38 /* audio in Resources */ = {isa = PBXBuildFile; fileRef = 99BF38CE21E7202500D90A38 /* audio */; };
Expand All @@ -23,7 +23,7 @@

/* Begin PBXFileReference section */
523F97E31DDF7AA5006FE2FC /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
523F97E51DDF926E006FE2FC /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
9938F1A021E81EFE0056E172 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
995C97EF1E91A857003825B2 /* shaders */ = {isa = PBXFileReference; lastKnownFileType = folder; name = shaders; path = ../../shaders; sourceTree = "<group>"; };
99713CAE204CA3DA00006FAC /* animation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = animation.h; path = ../../animation.h; sourceTree = "<group>"; };
99713CAF204CA3DA00006FAC /* cache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cache.h; path = ../../cache.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -60,12 +60,8 @@
99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = stb_vorbis.c; path = ../../libs/stb_vorbis/stb_vorbis.c; sourceTree = "<group>"; };
99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = minimp3.cpp; path = ../../libs/minimp3/minimp3.cpp; sourceTree = "<group>"; };
99C4C0931796A96F0032DE85 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
99C4C0951796A9730032DE85 /* AGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AGL.framework; path = System/Library/Frameworks/AGL.framework; sourceTree = SDKROOT; };
99C4C09B1796AACF0032DE85 /* OpenLara.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenLara.app; sourceTree = BUILT_PRODUCTS_DIR; };
99C4C0A41796AACF0032DE85 /* OpenLara-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "OpenLara-Info.plist"; sourceTree = "<group>"; };
99C4C0B81796AB7B0032DE85 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
99DF7BBF1DCBA2BF00C40D0A /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
99DF7BC11DCBA2D100C40D0A /* CoreAudioKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudioKit.framework; path = System/Library/Frameworks/CoreAudioKit.framework; sourceTree = SDKROOT; };
99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */

Expand All @@ -74,7 +70,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
523F97E61DDF926E006FE2FC /* QuartzCore.framework in Frameworks */,
9938F1A421E830250056E172 /* IOKit.framework in Frameworks */,
523F97E41DDF7AA5006FE2FC /* Cocoa.framework in Frameworks */,
99DF7BC41DCBA30B00C40D0A /* AudioToolbox.framework in Frameworks */,
99C4C0BA1796AB810032DE85 /* OpenGL.framework in Frameworks */,
Expand Down Expand Up @@ -139,14 +135,10 @@
99C4C0821796A8230032DE85 /* Frameworks */ = {
isa = PBXGroup;
children = (
523F97E51DDF926E006FE2FC /* QuartzCore.framework */,
9938F1A021E81EFE0056E172 /* IOKit.framework */,
523F97E31DDF7AA5006FE2FC /* Cocoa.framework */,
99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */,
99DF7BC11DCBA2D100C40D0A /* CoreAudioKit.framework */,
99DF7BBF1DCBA2BF00C40D0A /* CoreAudio.framework */,
99C4C0B81796AB7B0032DE85 /* Carbon.framework */,
99C4C0931796A96F0032DE85 /* OpenGL.framework */,
99C4C0951796A9730032DE85 /* AGL.framework */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down
169 changes: 146 additions & 23 deletions src/platform/osx/main.mm
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
#include <Cocoa/Cocoa.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <IOKit/hid/IOHidLib.h>

#include "game.h"

// timing
int osGetTime() {
static mach_timebase_info_data_t timebaseInfo;
if (timebaseInfo.denom == 0) {
mach_timebase_info(&timebaseInfo);
}

uint64_t absolute = mach_absolute_time();
uint64_t milliseconds = absolute * timebaseInfo.numer / (timebaseInfo.denom * 1000000ULL);
return int(milliseconds);
}

// sound
#define SND_SIZE 2352

static AudioQueueRef audioQueue;

void soundFill(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
void sndFill(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
void* frames = inBuffer->mAudioData;
UInt32 count = inBuffer->mAudioDataBytesCapacity / 4;
Sound::fill((Sound::Frame*)frames, count);
inBuffer->mAudioDataByteSize = count * 4;
AudioQueueEnqueueBuffer(audioQueue, inBuffer, 0, NULL);
}

void soundInit() {
void sndInit() {
AudioStreamBasicDescription deviceFormat;
deviceFormat.mSampleRate = 44100;
deviceFormat.mFormatID = kAudioFormatLinearPCM;
Expand All @@ -29,12 +42,12 @@ void soundInit() {
deviceFormat.mBitsPerChannel = 16;
deviceFormat.mReserved = 0;

AudioQueueNewOutput(&deviceFormat, soundFill, NULL, NULL, NULL, 0, &audioQueue);
AudioQueueNewOutput(&deviceFormat, sndFill, NULL, NULL, NULL, 0, &audioQueue);

for (int i = 0; i < 2; i++) {
AudioQueueBufferRef mBuffer;
AudioQueueAllocateBuffer(audioQueue, SND_SIZE, &mBuffer);
soundFill(NULL, audioQueue, mBuffer);
sndFill(NULL, audioQueue, mBuffer);
}
AudioQueueStart(audioQueue, NULL);
}
Expand All @@ -51,7 +64,6 @@ InputKey keyToInputKey(int code) {
for (int i = 0; i < sizeof(codes) / sizeof(codes[0]); i++)
if (codes[i] == code) {
return (InputKey)(ikLeft + i);
LOG("%d\n", code);
}
return ikNone;
}
Expand All @@ -65,24 +77,133 @@ InputKey mouseToInputKey(int btn) {
return ikNone;
}

IOHIDDeviceRef joyDevices[4] = { 0 };
IOHIDManagerRef hidManager;

JoyKey joyButtonToKey(uint32_t button) {
static const JoyKey keys[] = { jkA, jkB, jkX, jkY, jkLB, jkRB, jkLT, jkRT, jkStart, jkSelect, jkNone, jkUp, jkDown, jkLeft, jkRight };

if (button >= 0 || button < COUNT(keys))
return keys[button];
return jkNone;
}

bool osJoyReady(int index) {
return false;
if (index < 0 || index >= COUNT(joyDevices))
return false;
return joyDevices[index] != NULL;
}

void osJoyVibrate(int index, float L, float R) {
// TODO
}

// timing
int osGetTime() {
static mach_timebase_info_data_t timebaseInfo;
if (timebaseInfo.denom == 0) {
mach_timebase_info(&timebaseInfo);
float joyAxisValue(IOHIDValueRef value) {
IOHIDElementRef element = IOHIDValueGetElement(value);
CFIndex val = IOHIDValueGetIntegerValue(value);
CFIndex min = IOHIDElementGetLogicalMin(element);
CFIndex max = IOHIDElementGetLogicalMax(element);

float v = float(val - min) / float(max - min);
if (v < 0.2f) v = 0.0f; // check for deadzone
return v * 2.0f - 1.0f;
}

void hidValueCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) {
if (result != kIOReturnSuccess)
return;

IOHIDElementRef element = IOHIDValueGetElement(value);
IOHIDDeviceRef device = IOHIDElementGetDevice(element);

int joyIndex = -1;
for (int i = 0; i < COUNT(joyDevices); i++) {
if (joyDevices[i] == device) {
joyIndex = i;
break;
}
}

uint64_t absolute = mach_absolute_time();
uint64_t milliseconds = absolute * timebaseInfo.numer / (timebaseInfo.denom * 1000000ULL);
return int(milliseconds);
if (joyIndex == -1) return;

// TODO: add mapping for most popular gamepads by kIOHIDVendorIDKey / kIOHIDProductIDKey
switch (IOHIDElementGetUsagePage(element)) {
case kHIDPage_GenericDesktop : {
switch (IOHIDElementGetUsage(IOHIDValueGetElement(value))) {
case kHIDUsage_GD_X :
Input::setJoyPos(joyIndex, jkL, vec2(joyAxisValue(value), Input::joy[joyIndex].L.y));
break;
case kHIDUsage_GD_Y :
Input::setJoyPos(joyIndex, jkL, vec2(Input::joy[joyIndex].L.x, joyAxisValue(value)));
break;
case kHIDUsage_GD_Rx :
Input::setJoyPos(joyIndex, jkR, vec2(joyAxisValue(value), Input::joy[joyIndex].R.y));
break;
case kHIDUsage_GD_Ry :
Input::setJoyPos(joyIndex, jkR, vec2(Input::joy[joyIndex].R.x, joyAxisValue(value)));
break;
case kHIDUsage_GD_Z :
Input::setJoyPos(joyIndex, jkLT, vec2(joyAxisValue(value) * 0.5f + 0.5f, 0.0f));
break;
case kHIDUsage_GD_Rz :
Input::setJoyPos(joyIndex, jkRT, vec2(joyAxisValue(value) * 0.5f + 0.5f, 0.0f));
break;
default : LOG("! joy: unknown joy 0x%x (%d)\n", IOHIDElementGetUsage(IOHIDValueGetElement(value)), (int)IOHIDValueGetIntegerValue(value));
}
break;
}
case kHIDPage_Button : {
uint32_t button = IOHIDElementGetUsage(IOHIDValueGetElement(value)) - kHIDUsage_Button_1;
bool down = IOHIDValueGetIntegerValue(value) != 0;
JoyKey key = joyButtonToKey(button);
Input::setJoyDown(joyIndex, key, down);
if (key == jkNone) LOG("! joy: unknown button %d\n", button);
break;
}
default : ;
}
}

void joyAdd(void* context, IOReturn, void*, IOHIDDeviceRef device) {
for (int i = 0; i < COUNT(joyDevices); i++) {
if (joyDevices[i] == NULL) {
joyDevices[i] = device;
break;
}
}
}

void joyRemove(void* context, IOReturn, void*, IOHIDDeviceRef device) {
for (int i = 0; i < COUNT(joyDevices); i++) {
if (joyDevices[i] == device) {
joyDevices[i] = NULL;
break;
}
}
}

void joyInit() {
hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone);

NSDictionary *matchingGamepad = @{
@(kIOHIDDeviceUsagePageKey): @(kHIDPage_GenericDesktop),
@(kIOHIDDeviceUsageKey): @(kHIDUsage_GD_GamePad)
};
NSArray *matchDicts = @[ matchingGamepad ];

IOHIDManagerSetDeviceMatchingMultiple(hidManager, (__bridge CFArrayRef) matchDicts);
IOHIDManagerRegisterDeviceMatchingCallback(hidManager, joyAdd, NULL);
IOHIDManagerRegisterDeviceRemovalCallback(hidManager, joyRemove, NULL);
IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone);
IOHIDManagerRegisterInputValueCallback(hidManager, hidValueCallback, NULL);
}

void joyFree() {
IOHIDManagerUnscheduleFromRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDManagerRegisterDeviceMatchingCallback(hidManager, NULL, 0);
IOHIDManagerRegisterDeviceRemovalCallback(hidManager, NULL, 0);
IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
}

@interface OpenLaraGLView : NSOpenGLView
Expand Down Expand Up @@ -216,10 +337,10 @@ int main() {

// init window
NSRect rect = NSMakeRect(0, 0, 1280, 720);
NSWindow *mainWindow = [[NSWindow alloc] initWithContentRect:rect styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask backing:NSBackingStoreBuffered defer:YES];
mainWindow.title = @"OpenLara";
mainWindow.acceptsMouseMovedEvents = YES;
mainWindow.delegate = [[OpenLaraWindowDelegate alloc] init];
NSWindow *window = [[NSWindow alloc] initWithContentRect:rect styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask backing:NSBackingStoreBuffered defer:YES];
window.title = @"OpenLara";
window.acceptsMouseMovedEvents = YES;
window.delegate = [[OpenLaraWindowDelegate alloc] init];

// init OpenGL context
NSOpenGLPixelFormatAttribute attribs[] = {
Expand All @@ -232,9 +353,9 @@ int main() {
};
NSOpenGLPixelFormat *format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];

OpenLaraGLView *view = [[OpenLaraGLView alloc] initWithFrame:mainWindow.contentLayoutRect pixelFormat:format];
OpenLaraGLView *view = [[OpenLaraGLView alloc] initWithFrame:window.contentLayoutRect pixelFormat:format];
view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
mainWindow.contentView = view;
window.contentView = view;
[view.openGLContext makeCurrentContext];

// get path to game content
Expand All @@ -244,17 +365,19 @@ int main() {
strcat(contentDir, "/");

// show window
[mainWindow center];
[mainWindow makeKeyAndOrderFront:nil];
[window center];
[window makeKeyAndOrderFront:nil];

soundInit();
joyInit();
sndInit();
Game::init();

if (!Core::isQuit) {
[application run];
}

Game::deinit();
joyFree();
// TODO: sndFree

return 0;
Expand Down

0 comments on commit 93ce97e

Please sign in to comment.