1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-13 16:44:50 +02:00

Switch OpenLara to use Cocoa on OS X

Yes, that does amount to pretty much a rewrite of the main.cpp. Don’t
worry, it’s not that long. It also got renamed to main.mm because it’s
an Objective-C++ file now.

There are still a few rough edges, since I didn’t want to add a nib to
any of this. Among other things, the display link probably starts a
tiny bit too early, resulting in gameplay noises being heard before the
game is actually visible. Resizing may leave white flashes; not quite
sure what that’s about but they are brief. Finally, no menus of any
kind. At least a system and window menu would be necessary (to support
Cmd+Q).
This commit is contained in:
cochrane
2016-11-18 21:19:11 +01:00
parent 1393193a73
commit 4d1738dd0a
3 changed files with 327 additions and 252 deletions

View File

@@ -7,18 +7,20 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
99BFB6A21DCA7F5300E2E997 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A11DCA7F5300E2E997 /* main.cpp */; }; 523F97E41DDF7AA5006FE2FC /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 523F97E31DDF7AA5006FE2FC /* Cocoa.framework */; };
523F97E61DDF926E006FE2FC /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 523F97E51DDF926E006FE2FC /* QuartzCore.framework */; };
99BFB6A21DCA7F5300E2E997 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A11DCA7F5300E2E997 /* main.mm */; };
99BFB6A51DCA872D00E2E997 /* LEVEL2_DEMO.PHD in Resources */ = {isa = PBXBuildFile; fileRef = 99BFB6A31DCA872D00E2E997 /* LEVEL2_DEMO.PHD */; }; 99BFB6A51DCA872D00E2E997 /* LEVEL2_DEMO.PHD in Resources */ = {isa = PBXBuildFile; fileRef = 99BFB6A31DCA872D00E2E997 /* LEVEL2_DEMO.PHD */; };
99BFB6A61DCA872D00E2E997 /* 05.ogg in Resources */ = {isa = PBXBuildFile; fileRef = 99BFB6A41DCA872D00E2E997 /* 05.ogg */; }; 99BFB6A61DCA872D00E2E997 /* 05.ogg in Resources */ = {isa = PBXBuildFile; fileRef = 99BFB6A41DCA872D00E2E997 /* 05.ogg */; };
99BFB6A91DCA87BF00E2E997 /* stb_vorbis.c in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */; }; 99BFB6A91DCA87BF00E2E997 /* stb_vorbis.c in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */; };
99BFB6AB1DCA87C500E2E997 /* minimp3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */; }; 99BFB6AB1DCA87C500E2E997 /* minimp3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */; };
99C4C0B71796AB730032DE85 /* AGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99C4C0951796A9730032DE85 /* AGL.framework */; };
99C4C0B91796AB7B0032DE85 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99C4C0B81796AB7B0032DE85 /* Carbon.framework */; };
99C4C0BA1796AB810032DE85 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99C4C0931796A96F0032DE85 /* OpenGL.framework */; }; 99C4C0BA1796AB810032DE85 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99C4C0931796A96F0032DE85 /* OpenGL.framework */; };
99DF7BC41DCBA30B00C40D0A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */; }; 99DF7BC41DCBA30B00C40D0A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* 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; };
99BFB68D1DCA7F1700E2E997 /* lara.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lara.h; path = ../../lara.h; sourceTree = "<group>"; }; 99BFB68D1DCA7F1700E2E997 /* lara.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lara.h; path = ../../lara.h; sourceTree = "<group>"; };
99BFB68E1DCA7F1700E2E997 /* format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = format.h; path = ../../format.h; sourceTree = "<group>"; }; 99BFB68E1DCA7F1700E2E997 /* format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = format.h; path = ../../format.h; sourceTree = "<group>"; };
99BFB68F1DCA7F1700E2E997 /* level.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = level.h; path = ../../level.h; sourceTree = "<group>"; }; 99BFB68F1DCA7F1700E2E997 /* level.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = level.h; path = ../../level.h; sourceTree = "<group>"; };
@@ -37,7 +39,7 @@
99BFB69C1DCA7F1700E2E997 /* game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = game.h; path = ../../game.h; sourceTree = "<group>"; }; 99BFB69C1DCA7F1700E2E997 /* game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = game.h; path = ../../game.h; sourceTree = "<group>"; };
99BFB69D1DCA7F1700E2E997 /* texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = texture.h; path = ../../texture.h; sourceTree = "<group>"; }; 99BFB69D1DCA7F1700E2E997 /* texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = texture.h; path = ../../texture.h; sourceTree = "<group>"; };
99BFB69E1DCA7F1700E2E997 /* libs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = libs; path = ../../libs; sourceTree = "<group>"; }; 99BFB69E1DCA7F1700E2E997 /* libs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = libs; path = ../../libs; sourceTree = "<group>"; };
99BFB6A11DCA7F5300E2E997 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; }; 99BFB6A11DCA7F5300E2E997 /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
99BFB6A31DCA872D00E2E997 /* LEVEL2_DEMO.PHD */ = {isa = PBXFileReference; lastKnownFileType = file; name = LEVEL2_DEMO.PHD; path = ../../../bin/LEVEL2_DEMO.PHD; sourceTree = SOURCE_ROOT; }; 99BFB6A31DCA872D00E2E997 /* LEVEL2_DEMO.PHD */ = {isa = PBXFileReference; lastKnownFileType = file; name = LEVEL2_DEMO.PHD; path = ../../../bin/LEVEL2_DEMO.PHD; sourceTree = SOURCE_ROOT; };
99BFB6A41DCA872D00E2E997 /* 05.ogg */ = {isa = PBXFileReference; lastKnownFileType = file; name = 05.ogg; path = ../../../bin/05.ogg; sourceTree = SOURCE_ROOT; }; 99BFB6A41DCA872D00E2E997 /* 05.ogg */ = {isa = PBXFileReference; lastKnownFileType = file; name = 05.ogg; path = ../../../bin/05.ogg; sourceTree = SOURCE_ROOT; };
99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = stb_vorbis.c; path = ../../libs/stb_vorbis/stb_vorbis.c; sourceTree = "<group>"; }; 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = stb_vorbis.c; path = ../../libs/stb_vorbis/stb_vorbis.c; sourceTree = "<group>"; };
@@ -57,10 +59,10 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
523F97E61DDF926E006FE2FC /* QuartzCore.framework in Frameworks */,
523F97E41DDF7AA5006FE2FC /* Cocoa.framework in Frameworks */,
99DF7BC41DCBA30B00C40D0A /* AudioToolbox.framework in Frameworks */, 99DF7BC41DCBA30B00C40D0A /* AudioToolbox.framework in Frameworks */,
99C4C0BA1796AB810032DE85 /* OpenGL.framework in Frameworks */, 99C4C0BA1796AB810032DE85 /* OpenGL.framework in Frameworks */,
99C4C0B91796AB7B0032DE85 /* Carbon.framework in Frameworks */,
99C4C0B71796AB730032DE85 /* AGL.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -73,7 +75,7 @@
99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */, 99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */,
99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */, 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */,
99BFB69E1DCA7F1700E2E997 /* libs */, 99BFB69E1DCA7F1700E2E997 /* libs */,
99BFB6A11DCA7F5300E2E997 /* main.cpp */, 99BFB6A11DCA7F5300E2E997 /* main.mm */,
99BFB68D1DCA7F1700E2E997 /* lara.h */, 99BFB68D1DCA7F1700E2E997 /* lara.h */,
99BFB68E1DCA7F1700E2E997 /* format.h */, 99BFB68E1DCA7F1700E2E997 /* format.h */,
99BFB68F1DCA7F1700E2E997 /* level.h */, 99BFB68F1DCA7F1700E2E997 /* level.h */,
@@ -108,6 +110,8 @@
99C4C0821796A8230032DE85 /* Frameworks */ = { 99C4C0821796A8230032DE85 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
523F97E51DDF926E006FE2FC /* QuartzCore.framework */,
523F97E31DDF7AA5006FE2FC /* Cocoa.framework */,
99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */, 99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */,
99DF7BC11DCBA2D100C40D0A /* CoreAudioKit.framework */, 99DF7BC11DCBA2D100C40D0A /* CoreAudioKit.framework */,
99DF7BBF1DCBA2BF00C40D0A /* CoreAudio.framework */, 99DF7BBF1DCBA2BF00C40D0A /* CoreAudio.framework */,
@@ -193,7 +197,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
99BFB6AB1DCA87C500E2E997 /* minimp3.cpp in Sources */, 99BFB6AB1DCA87C500E2E997 /* minimp3.cpp in Sources */,
99BFB6A21DCA7F5300E2E997 /* main.cpp in Sources */, 99BFB6A21DCA7F5300E2E997 /* main.mm in Sources */,
99BFB6A91DCA87BF00E2E997 /* stb_vorbis.c in Sources */, 99BFB6A91DCA87BF00E2E997 /* stb_vorbis.c in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View File

@@ -1,244 +0,0 @@
#include "game.h"
#include <mach/mach.h>
#include <mach/mach_time.h>
bool isQuit = false;
WindowRef window;
AGLContext context;
#define SND_SIZE 8192
static AudioQueueRef audioQueue;
void soundFill(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);
// TODO: mutex
}
void soundInit() {
AudioStreamBasicDescription deviceFormat;
deviceFormat.mSampleRate = 44100;
deviceFormat.mFormatID = kAudioFormatLinearPCM;
deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
deviceFormat.mBytesPerPacket = 4;
deviceFormat.mFramesPerPacket = 1;
deviceFormat.mBytesPerFrame = 4;
deviceFormat.mChannelsPerFrame = 2;
deviceFormat.mBitsPerChannel = 16;
deviceFormat.mReserved = 0;
AudioQueueNewOutput(&deviceFormat, soundFill, NULL, NULL, NULL, 0, &audioQueue);
for (int i = 0; i < 2; i++) {
AudioQueueBufferRef mBuffer;
AudioQueueAllocateBuffer(audioQueue, SND_SIZE, &mBuffer);
soundFill(NULL, audioQueue, mBuffer);
}
AudioQueueStart(audioQueue, NULL);
}
// common input functions
InputKey keyToInputKey(int code) {
static const int codes[] = {
0x7B, 0x7C, 0x7E, 0x7D, 0x31, 0x24, 0x35, 0x38, 0x3B, 0x3A,
0x1D, 0x12, 0x13, 0x14, 0x15, 0x17, 0x16, 0x1A, 0x1C, 0x19, // 0..9
0x00, 0x0B, 0x08, 0x02, 0x0E, 0x03, 0x05, 0x04, 0x22, 0x26, 0x28, 0x25, 0x2E, // A..M
0x2D, 0x1F, 0x23, 0x0C, 0x0F, 0x01, 0x11, 0x20, 0x09, 0x0D, 0x07, 0x10, 0x06, // N..Z
};
for (int i = 0; i < sizeof(codes) / sizeof(codes[0]); i++)
if (codes[i] == code)
return (InputKey)(ikLeft + i);
return ikNone;
}
InputKey mouseToInputKey(int btn) {
switch (btn) {
case 1 : return ikMouseL;
case 2 : return ikMouseR;
case 3 : return ikMouseM;
}
return ikNone;
}
OSStatus eventHandler(EventHandlerCallRef handler, EventRef event, void* userData) {
OSType eventClass = GetEventClass(event);
UInt32 eventKind = GetEventKind(event);
switch (eventClass) {
case kEventClassWindow :
switch (eventKind) {
case kEventWindowClosed :
isQuit = true;
break;
case kEventWindowBoundsChanged : {
aglUpdateContext(context);
Rect rect;
GetWindowPortBounds(window, &rect);
Core::width = rect.right - rect.left;
Core::height = rect.bottom - rect.top;
break;
}
}
break;
case kEventClassMouse : {
EventMouseButton mouseButton;
CGPoint mousePos;
Rect wndRect;
GetEventParameter(event, kEventParamMouseLocation, typeHIPoint, NULL, sizeof(mousePos), NULL, &mousePos);
GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(mouseButton), nil, &mouseButton);
GetWindowBounds(window, kWindowContentRgn, &wndRect);
mousePos.x -= wndRect.left;
mousePos.y -= wndRect.top;
vec2 pos(mousePos.x, mousePos.y);
switch (eventKind) {
case kEventMouseDown :
case kEventMouseUp : {
InputKey key = mouseToInputKey(mouseButton);
Input::setPos(key, pos);
Input::setDown(key, eventKind == kEventMouseDown);
break;
}
case kEventMouseDragged :
Input::setPos(ikMouseL, pos);
break;
}
break;
}
case kEventClassKeyboard : {
switch (eventKind) {
case kEventRawKeyDown :
case kEventRawKeyUp : {
uint32 keyCode;
if (GetEventParameter(event, kEventParamKeyCode, typeUInt32, NULL, sizeof(keyCode), NULL, &keyCode) == noErr)
Input::setDown(keyToInputKey(keyCode), eventKind != kEventRawKeyUp);
break;
}
case kEventRawKeyModifiersChanged : {
uint32 modifiers;
if (GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(modifiers), NULL, &modifiers) == noErr) {
Input::setDown(ikShift, modifiers & shiftKey);
Input::setDown(ikCtrl, modifiers & controlKey);
Input::setDown(ikAlt, modifiers & optionKey);
}
break;
}
}
break;
}
}
return CallNextEventHandler(handler, event);
}
int getTime() {
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);
}
char *contentPath;
int main() {
// init window
Rect rect = {0, 0, 720, 1280};
CreateNewWindow(kDocumentWindowClass, kWindowCloseBoxAttribute | kWindowCollapseBoxAttribute | kWindowFullZoomAttribute | kWindowResizableAttribute | kWindowStandardHandlerAttribute, &rect, &window);
SetWTitle(window, "\pOpenLara");
// init OpenGL context
GLint attribs[] = {
AGL_RGBA,
AGL_DOUBLEBUFFER,
AGL_BUFFER_SIZE, 32,
AGL_DEPTH_SIZE, 24,
AGL_STENCIL_SIZE, 8,
AGL_NONE
};
AGLPixelFormat format = aglChoosePixelFormat(NULL, 0, (GLint*)&attribs);
context = aglCreateContext(format, NULL);
aglDestroyPixelFormat(format);
aglSetDrawable(context, GetWindowPort(window));
aglSetCurrentContext(context);
// get path to game content
CFBundleRef bundle = CFBundleGetMainBundle();
CFURLRef bundleURL = CFBundleCopyBundleURL(bundle);
CFStringRef pathStr = CFURLCopyFileSystemPath(bundleURL, kCFURLPOSIXPathStyle);
contentPath = new char[1024];
CFStringGetFileSystemRepresentation(pathStr, contentPath, 1024);
strcat(contentPath, "/Contents/Resources/");
soundInit();
Game::init();
// show window
const int events[][2] = {
{ kEventClassWindow, kEventWindowClosed },
{ kEventClassWindow, kEventWindowBoundsChanged },
{ kEventClassKeyboard, kEventRawKeyDown },
{ kEventClassKeyboard, kEventRawKeyUp },
{ kEventClassKeyboard, kEventRawKeyModifiersChanged },
{ kEventClassMouse, kEventMouseDown },
{ kEventClassMouse, kEventMouseUp },
{ kEventClassMouse, kEventMouseDragged },
};
InstallEventHandler(GetApplicationEventTarget(), (EventHandlerUPP)eventHandler, sizeof(events) / sizeof(events[0]), (EventTypeSpec*)&events, NULL, NULL);
SelectWindow(window);
ShowWindow(window);
int lastTime = getTime(), fpsTime = lastTime + 1000, fps = 0;
EventRecord event;
while (!isQuit)
if (!GetNextEvent(0xffff, &event)) {
int time = getTime();
if (time <= lastTime)
continue;
float delta = (time - lastTime) * 0.001f;
while (delta > EPS) {
Core::deltaTime = min(delta, 1.0f / 30.0f);
Game::update();
delta -= Core::deltaTime;
}
lastTime = time;
Core::stats.dips = 0;
Core::stats.tris = 0;
Game::render();
aglSwapBuffers(context);
if (fpsTime < getTime()) {
LOG("FPS: %d DIP: %d TRI: %d\n", fps, Core::stats.dips, Core::stats.tris);
fps = 0;
fpsTime = getTime() + 1000;
} else
fps++;
}
Game::free();
delete[] contentPath;
// TODO: sndFree
aglSetCurrentContext(NULL);
ReleaseWindow(window);
return 0;
}

315
src/platform/osx/main.mm Normal file
View File

@@ -0,0 +1,315 @@
#include "game.h"
#include <Cocoa/Cocoa.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
bool isQuit = false;
WindowRef window;
AGLContext context;
#define SND_SIZE 8192
static AudioQueueRef audioQueue;
void soundFill(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);
// TODO: mutex
}
void soundInit() {
AudioStreamBasicDescription deviceFormat;
deviceFormat.mSampleRate = 44100;
deviceFormat.mFormatID = kAudioFormatLinearPCM;
deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
deviceFormat.mBytesPerPacket = 4;
deviceFormat.mFramesPerPacket = 1;
deviceFormat.mBytesPerFrame = 4;
deviceFormat.mChannelsPerFrame = 2;
deviceFormat.mBitsPerChannel = 16;
deviceFormat.mReserved = 0;
AudioQueueNewOutput(&deviceFormat, soundFill, NULL, NULL, NULL, 0, &audioQueue);
for (int i = 0; i < 2; i++) {
AudioQueueBufferRef mBuffer;
AudioQueueAllocateBuffer(audioQueue, SND_SIZE, &mBuffer);
soundFill(NULL, audioQueue, mBuffer);
}
AudioQueueStart(audioQueue, NULL);
}
// common input functions
InputKey keyToInputKey(int code) {
static const int codes[] = {
0x7B, 0x7C, 0x7E, 0x7D, 0x31, 0x24, 0x35, 0x38, 0x3B, 0x3A,
0x1D, 0x12, 0x13, 0x14, 0x15, 0x17, 0x16, 0x1A, 0x1C, 0x19, // 0..9
0x00, 0x0B, 0x08, 0x02, 0x0E, 0x03, 0x05, 0x04, 0x22, 0x26, 0x28, 0x25, 0x2E, // A..M
0x2D, 0x1F, 0x23, 0x0C, 0x0F, 0x01, 0x11, 0x20, 0x09, 0x0D, 0x07, 0x10, 0x06, // N..Z
};
for (int i = 0; i < sizeof(codes) / sizeof(codes[0]); i++)
if (codes[i] == code)
return (InputKey)(ikLeft + i);
return ikNone;
}
InputKey mouseToInputKey(int btn) {
switch (btn) {
case 1 : return ikMouseL;
case 2 : return ikMouseR;
case 3 : return ikMouseM;
}
return ikNone;
}
int lastTime;
int fpsTime;
int fps;
CVDisplayLinkRef displayLink;
int getTime() {
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);
}
/*
* Specific OpenGLView. This subclass is necessary primarily to handle input.
* Capturing and dispatching events manually on OS X is definitely not worth it.
*/
@interface OpenLaraGLView : NSOpenGLView
@end
@implementation OpenLaraGLView
- (InputKey)inputKeyForMouseEvent:(NSEvent *)theEvent {
switch (theEvent.buttonNumber) {
case 0: return ikMouseL;
case 1: return ikMouseR;
case 2: return ikMouseM;
default: return ikNone;
}
}
- (vec2)inputPositionForMouseEvent:(NSEvent *)theEvent {
NSPoint inWindow = theEvent.locationInWindow;
NSPoint inView = [self convertPoint:inWindow fromView:nil];
// TODO Do we need to flip y, due to OS X having the origin at the bottom
// left as opposed to top left in every single other system? The original
// code didn't so I won't either for now.
return vec2(inView.x, inView.y);
}
- (void)mouseDown:(NSEvent *)theEvent {
InputKey inputKey = [self inputKeyForMouseEvent:theEvent];
Input::setPos(inputKey, [self inputPositionForMouseEvent:theEvent]);
Input::setDown(inputKey, true);
}
- (void)rightMouseDown:(NSEvent *)theEvent {
[self mouseDown:theEvent];
}
- (void)otherMouseDown:(NSEvent *)theEvent {
[self mouseDown:theEvent];
}
- (void)mouseUp:(NSEvent *)theEvent {
InputKey inputKey = [self inputKeyForMouseEvent:theEvent];
Input::setPos(inputKey, [self inputPositionForMouseEvent:theEvent]);
Input::setDown(inputKey, false);
}
- (void)rightMouseUp:(NSEvent *)theEvent {
[self mouseUp:theEvent];
}
- (void)otherMouseUp:(NSEvent *)theEvent {
[self mouseUp:theEvent];
}
- (void)mouseDragged:(NSEvent *)theEvent {
InputKey inputKey = [self inputKeyForMouseEvent:theEvent];
Input::setPos(inputKey, [self inputPositionForMouseEvent:theEvent]);
}
- (void)rightMouseDragged:(NSEvent *)theEvent {
[self mouseDragged:theEvent];
}
- (void)otherMouseDragged:(NSEvent *)theEvent {
[self mouseDragged:theEvent];
}
- (void)keyDown:(NSEvent *)theEvent {
unsigned short keyCode = theEvent.keyCode;
Input::setDown(keyToInputKey(keyCode), true);
}
- (void)keyUp:(NSEvent *)theEvent {
unsigned short keyCode = theEvent.keyCode;
Input::setDown(keyToInputKey(keyCode), false);
}
- (void)flagsChanged:(NSEvent *)theEvent {
NSEventModifierFlags modifiers = theEvent.modifierFlags;
Input::setDown(ikShift, modifiers & NSShiftKeyMask);
Input::setDown(ikCtrl, modifiers & NSControlKeyMask);
Input::setDown(ikAlt, modifiers & NSAlternateKeyMask);
}
- (BOOL)acceptsFirstResponder {
return YES;
}
- (void)reshape {
NSRect bounds = self.bounds;
Core::width = bounds.size.width;
Core::height = bounds.size.height;
}
@end
/*
* Delegate to deal with things that happen at the window level
*/
@interface OpenLaraWindowDelegate : NSObject<NSWindowDelegate>
@end
@implementation OpenLaraWindowDelegate
- (void)windowWillClose:(NSNotification *)notification {
[[NSApplication sharedApplication] terminate:self];
}
- (void)windowDidMiniaturize:(NSNotification *)notification {
// Pause game
CVDisplayLinkStop(displayLink);
Input::reset();
}
- (void)windowDidDeminiaturize:(NSNotification *)notification {
// End paused game.
lastTime = getTime();
CVDisplayLinkStart(displayLink);
}
@end
char *contentPath;
/*
* Callback for the CVDisplayLink, an OS X mechanism to get precise timing for
* multi-media applications. This runs the whole game loop, for simplicitly's
* sake. This is not really the idea of the displayLinkCallback, which should
* more or less just swap the OpenGL buffer here and at least have the update
* running in a different thread entirely. But it works.
*/
CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *context) {
OpenLaraGLView *view = (OpenLaraGLView *) context;
[view.openGLContext makeCurrentContext];
// TODO: This should probably get the time from the outputTime parameter
int time = getTime();
if (time <= lastTime)
return kCVReturnUnsupported;
// TODO: This should probably run the update in a separate thread
// and only do rendering here
float delta = (time - lastTime) * 0.001f;
while (delta > EPS) {
Core::deltaTime = min(delta, 1.0f / 30.0f);
Game::update();
delta -= Core::deltaTime;
}
lastTime = time;
// TODO: Rendering should probably happen a bit in advance with only the
// flushBuffer here
Core::stats.dips = 0;
Core::stats.tris = 0;
Game::render();
[view.openGLContext flushBuffer];
if (fpsTime < getTime()) {
LOG("FPS: %d DIP: %d TRI: %d\n", fps, Core::stats.dips, Core::stats.tris);
fps = 0;
fpsTime = getTime() + 1000;
} else
fps++;
return kCVReturnSuccess;
}
int main() {
NSApplication *application = [NSApplication sharedApplication];
// 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];
// init OpenGL context
NSOpenGLPixelFormatAttribute attribs[] = {
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAColorSize, 32,
NSOpenGLPFADepthSize, 24,
NSOpenGLPFAStencilSize, 8,
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy,
0
};
NSOpenGLPixelFormat *format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
OpenLaraGLView *view = [[OpenLaraGLView alloc] initWithFrame:mainWindow.contentLayoutRect pixelFormat:format];
view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
mainWindow.contentView = view;
[view.openGLContext makeCurrentContext];
// get path to game content
NSBundle *bundle = [NSBundle mainBundle];
NSURL *bundleURL = bundle.bundleURL;
contentPath = new char[1024];
[bundleURL getFileSystemRepresentation:contentPath maxLength:1024];
strcat(contentPath, "/Contents/Resources/");
soundInit();
Game::init();
// show window
[mainWindow makeKeyAndOrderFront:nil];
// Set up DisplayLink. This will call our callback in time with display
// refresh rate.
CVReturn cvreturn = CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
if (cvreturn != kCVReturnSuccess) {
NSLog(@"Could not create Display Link: %d", (int) cvreturn);
}
cvreturn = CVDisplayLinkSetOutputCallback(displayLink, displayLinkCallback, view);
if (cvreturn != kCVReturnSuccess) {
NSLog(@"Could not create set callback for display link: %d", (int) cvreturn);
}
lastTime = getTime();
fpsTime = lastTime + 1000;
cvreturn = CVDisplayLinkStart(displayLink);
if (cvreturn != kCVReturnSuccess) {
NSLog(@"Could not start display link: %d", (int) cvreturn);
}
// Start application main loop
[application run];
return 0;
}