// // GameView.m // danmaku // // Created by Sam Jaffe on 5/22/19. // Copyright © 2019 Sam Jaffe. All rights reserved. // #import "GameView.h" #include #include #include "danmaku/world.hpp" #include "game/engine/events.hpp" #include "game/engine/game_dispatch.hpp" #include "game/graphics/renderer.hpp" namespace env { namespace detail { extern void resize_screen(math::vec2i const&); }} @implementation GameView { std::shared_ptr renderer; std::shared_ptr game; } - (id)initWithFrame:(NSRect)aRect { NSOpenGLPixelFormatAttribute attr[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, 0 }; // create pixel format NSOpenGLPixelFormat *nsglFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; // create the context... if (!(self = [super initWithFrame:aRect pixelFormat:nsglFormat])) { return nil; } using graphics::direct_renderer; using graphics::driver; renderer = std::make_shared(driver::openGL); game = std::make_shared(renderer); auto world = danmaku::world::load_world("scripts/level/world.json", game); game->register_scene(world); game->activate_scene("light-1"); // make the context current NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:nsglFormat shareContext:nil]; [self setOpenGLContext:context]; [self setPixelFormat:nsglFormat]; [[self openGLContext] makeCurrentContext]; return self; } - (void)prepareOpenGL { [super prepareOpenGL]; // enable vertical sychronization to refresh rate const GLint vals = 0x01; [[self openGLContext] setValues:&vals forParameter:NSOpenGLCPSwapInterval]; const GLint opaque = 0; [[self openGLContext] setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity]; } - (void)drawRect:(NSRect)dirtyRect { // glClear(GL_COLOR_BUFFER_BIT); // glLoadIdentity(); env::detail::resize_screen({{(int)dirtyRect.size.width, (int)dirtyRect.size.height}}); if (game) { renderer->clear(); game->render(); } // glFlush(); // the correct way to do double buffering on Mac is this: [[self openGLContext] flushBuffer]; int err; if ((err = glGetError()) != 0) { NSLog(@"glGetError(): %d", err); } } - (void)reshape { [super reshape]; const CGSize dims = [[self window] frame].size; if (dims.width == 0 || dims.height == 0) return; [self setFrame:NSRect{0, 0, dims.width, dims.height}]; // window resize; width and height are in pixel coordinates // but they are floats float screen_w = dims.width; float screen_h = dims.height; // glViewport(0,0, screen_w, screen_h); // here I cast floats to ints; most systems use integer coordinate systems env::detail::resize_screen({{(int)screen_w, (int)screen_h}}); } - (BOOL)acceptsFirstResponder { return YES; } - (void) press_key:(int)key forEvent:(NSEvent *)evt ns_key:(int)local down:(BOOL)pressed { if ([evt keyCode] == local) { game->process_key_event(key, pressed); } } - (void) toggle_key:(int)key forEvent:(NSEvent *)evt ns_key:(int)local { NSUInteger flags = [evt modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; game->process_key_event(key, flags & local); } - (void)keyDown:(NSEvent *)theEvent { if (!game) { // This is a really ugly hack because, for dumb reasons, there are two // GameView objects, one that I initialize, and one that I don't return [(GameView*)[[self window] delegate] keyDown:theEvent]; } if ([theEvent isARepeat]) return; NSString *str = [theEvent charactersIgnoringModifiers]; unichar c = [str characterAtIndex:0]; // only ASCII please if (c < ' ' || c > '~') { c = 0; } //(unsigned long)[theEvent modifierFlags] using namespace engine::keys; [self toggle_key:MOD_SHIFT forEvent:theEvent ns_key:NSEventModifierFlagShift]; [self toggle_key:MOD_CONTROL forEvent:theEvent ns_key:NSEventModifierFlagControl]; [self toggle_key:MOD_ALT forEvent:theEvent ns_key:NSEventModifierFlagOption]; [self toggle_key:MOD_COMMAND forEvent:theEvent ns_key:NSEventModifierFlagCommand]; [self press_key:UP_ARROW forEvent:theEvent ns_key:0x007e down:YES]; [self press_key:DOWN_ARROW forEvent:theEvent ns_key:0x007d down:YES]; [self press_key:LEFT_ARROW forEvent:theEvent ns_key:0x007b down:YES]; [self press_key:RIGHT_ARROW forEvent:theEvent ns_key:0x007c down:YES]; NSLog(@"keyDown -> { 0x%04x, 0x%08lx, 0x%02x=%c }", [theEvent keyCode], [theEvent modifierFlags], c, c); game->process_key_event(c, true); } - (void)keyUp:(NSEvent *)theEvent { if (!game) { // This is a really ugly hack because, for dumb reasons, there are two // GameView objects, one that I initialize, and one that I don't return [(GameView*)[[self window] delegate] keyDown:theEvent]; } NSLog(@"keyUp -> { 0x%04x, 0x%08lx }", [theEvent keyCode], [theEvent modifierFlags]); NSString *str = [theEvent charactersIgnoringModifiers]; unichar c = [str characterAtIndex:0]; // only ASCII please if (c < ' ' || c > '~') { c = 0; } using namespace engine::keys; [self press_key:UP_ARROW forEvent:theEvent ns_key:0x007e down:NO]; [self press_key:DOWN_ARROW forEvent:theEvent ns_key:0x007d down:NO]; [self press_key:LEFT_ARROW forEvent:theEvent ns_key:0x007b down:NO]; [self press_key:RIGHT_ARROW forEvent:theEvent ns_key:0x007c down:NO]; game->process_key_event(c, false); } static NSTimer *timer = nil; static NSTimer *frameTimer = nil; static NSTimeInterval const FRAME_INTERVAL = 1.0 / 60.0; - (void)windowDidResignMain:(NSNotification *)notification { NSLog(@"window did resign main"); [timer invalidate]; [frameTimer invalidate]; [self setNeedsDisplay:YES]; } - (void)windowDidBecomeMain:(NSNotification *)notification { NSLog(@"window did become main"); game->set_current_timestamp(); [self setNeedsDisplay:YES]; // TODO (sjaffe): Become able to change framerate // TODO (sjaffe): Link this with env::fps::v60, etc. timer = [NSTimer timerWithTimeInterval:FRAME_INTERVAL target:self selector:@selector(timerEvent:) userInfo:nil repeats:YES]; frameTimer = [NSTimer timerWithTimeInterval:FRAME_INTERVAL target:self selector:@selector(renderEvent:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] addTimer:frameTimer forMode:NSDefaultRunLoopMode]; } - (void)renderEvent:(NSTimer *)t { game->render(); [[self openGLContext] flushBuffer]; } - (void)timerEvent:(NSTimer *)t { game->update(); [self setNeedsDisplay:YES]; } - (void) awakeFromNib { NSOpenGLPixelFormatAttribute attributes [] = { NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)24, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, (NSOpenGLPixelFormatAttribute)0 }; NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; [self setPixelFormat:pf]; } @end