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:
@@ -7,18 +7,20 @@
|
||||
objects = {
|
||||
|
||||
/* 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 */; };
|
||||
99BFB6A61DCA872D00E2E997 /* 05.ogg in Resources */ = {isa = PBXBuildFile; fileRef = 99BFB6A41DCA872D00E2E997 /* 05.ogg */; };
|
||||
99BFB6A91DCA87BF00E2E997 /* stb_vorbis.c in Sources */ = {isa = PBXBuildFile; fileRef = 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */; };
|
||||
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 */; };
|
||||
99DF7BC41DCBA30B00C40D0A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */; };
|
||||
/* End PBXBuildFile 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>"; };
|
||||
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>"; };
|
||||
@@ -37,7 +39,7 @@
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
@@ -57,10 +59,10 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
523F97E61DDF926E006FE2FC /* QuartzCore.framework in Frameworks */,
|
||||
523F97E41DDF7AA5006FE2FC /* Cocoa.framework in Frameworks */,
|
||||
99DF7BC41DCBA30B00C40D0A /* AudioToolbox.framework in Frameworks */,
|
||||
99C4C0BA1796AB810032DE85 /* OpenGL.framework in Frameworks */,
|
||||
99C4C0B91796AB7B0032DE85 /* Carbon.framework in Frameworks */,
|
||||
99C4C0B71796AB730032DE85 /* AGL.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -73,7 +75,7 @@
|
||||
99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */,
|
||||
99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */,
|
||||
99BFB69E1DCA7F1700E2E997 /* libs */,
|
||||
99BFB6A11DCA7F5300E2E997 /* main.cpp */,
|
||||
99BFB6A11DCA7F5300E2E997 /* main.mm */,
|
||||
99BFB68D1DCA7F1700E2E997 /* lara.h */,
|
||||
99BFB68E1DCA7F1700E2E997 /* format.h */,
|
||||
99BFB68F1DCA7F1700E2E997 /* level.h */,
|
||||
@@ -108,6 +110,8 @@
|
||||
99C4C0821796A8230032DE85 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
523F97E51DDF926E006FE2FC /* QuartzCore.framework */,
|
||||
523F97E31DDF7AA5006FE2FC /* Cocoa.framework */,
|
||||
99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */,
|
||||
99DF7BC11DCBA2D100C40D0A /* CoreAudioKit.framework */,
|
||||
99DF7BBF1DCBA2BF00C40D0A /* CoreAudio.framework */,
|
||||
@@ -193,7 +197,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
99BFB6AB1DCA87C500E2E997 /* minimp3.cpp in Sources */,
|
||||
99BFB6A21DCA7F5300E2E997 /* main.cpp in Sources */,
|
||||
99BFB6A21DCA7F5300E2E997 /* main.mm in Sources */,
|
||||
99BFB6A91DCA87BF00E2E997 /* stb_vorbis.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@@ -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
315
src/platform/osx/main.mm
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user