kinect-viewer / KVDepthView.m

/*
 * Copyright 2010 Cliff L. Biffle.  All Rights Reserved.
 * Use of this source code is governed by the Apache License 2.0,
 * which can be found in the LICENSE file.
 */

#import "KVDepthView.h"
#import "KVRenderer.h"
#import "KVDepthRecorder.h"
#import "trackball.h"
#import "utility.h"

@interface KVDepthView ()
- (void) resetCamera;
- (void) resizeGL;
- (void) updateProjectionWithOffset: (float) offset;
- (void) updateModelViewWithOffset: (float) offset;
- (GLuint) loadShaderOfType: (GLenum) type fromFiles: (NSArray *) names;

@property(assign, nonatomic) BOOL updateQueued;
- (void) requestUpdateFromBackgroundThread;
- (void) markAsDirty;

@property GLuint vertexShader;
@property GLuint fragmentShader;
@property GLuint program;

@end

@implementation KVDepthView
#pragma mark --- Properties ---

@synthesize vertexShader, fragmentShader, program;
@synthesize updateQueued;

@synthesize textured;
@synthesize renderer;

@synthesize frozen;
@synthesize anaglyph;
@synthesize recording;

#pragma mark --- Trivial NSView Overrides ---

- (BOOL) acceptsFirstResponder {
  return YES;
}

- (BOOL) becomeFirstResponder {
  return YES;
}

- (BOOL) resignFirstResponder {
  return YES;
}

#pragma mark --- Initialization ---

- (void) awakeFromNib {
  deviceWhite = [[NSColor colorWithDeviceRed: 1.F green: 1.F blue: 1.F alpha: 1.F] retain];
  
  self.renderer = [[[KVRenderer alloc] init] autorelease];
}

#pragma mark --- GL Stuff ---

- (void) prepareOpenGL {
  NSArray *vertexShaderFiles = [NSArray arrayWithObjects: @"kinect_project", @"lighting", @"main", nil];
  vertexShader = [self loadShaderOfType: GL_VERTEX_SHADER
                                              fromFiles: vertexShaderFiles];
  
  fragmentShader = [self loadShaderOfType: GL_FRAGMENT_SHADER
                                fromFiles: [NSArray arrayWithObject: @"main"]];
  
  program = glCreateProgram();
  glAttachShader(program, vertexShader);
  glAttachShader(program, fragmentShader);
  glLinkProgram(program);
  glUseProgram(program);
  
  // Sync to vertical blank.
  GLint swapInterval = 1;
  [[self openGLContext] setValues: &swapInterval forParameter: NSOpenGLCPSwapInterval];
  
  // GL state initialization
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glFrontFace(GL_CCW);
  glShadeModel(GL_SMOOTH);
  
  GLfloat diffuseColor[4] = { 1.f, 1.f, 1.f, 1.0f };
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseColor);

  GLfloat ambientColor[4] = { 0.2f, 0.2f, 0.2f, 1 };
  glLightfv(GL_LIGHT0, GL_AMBIENT, ambientColor);
  
  GLfloat specularColor[4] = { 1, 1, 1, 1 };
  glLightfv(GL_LIGHT0, GL_SPECULAR, specularColor);
  
  GLfloat globalAmbient[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, globalAmbient);
  
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
  
  GLfloat specular[4] = { 0.0f, 0.0f, 0.0f, 1 };
  glMaterialfv(GL_FRONT, GL_SPECULAR, specular);
  glEnable(GL_COLOR_MATERIAL);
  
  glClearColor(0.3f, 0, 0.5f, 0);
  
  [self resetCamera];
  [renderer initGL];
}

- (void) resetCamera {
  camera = (camera_t) {
    .fov = 57,
    .pivot = { 0, 0, 0 },
    .pos = { 0, 0, 0 },
    .target = { 0, 0, 3.f },
    .up = { 0, 1, 0 },
  };
}

- (void) resizeGL {
  NSRect bounds = [self bounds];
  
  // Note: this always happens on the first drawing call.
  if (camera.viewHeight != bounds.size.height
      || camera.viewWidth != bounds.size.width) {
    camera.viewHeight = (float) bounds.size.height;
    camera.viewWidth = (float) bounds.size.width;
    
    glViewport(0, 0, camera.viewWidth, camera.viewHeight);
  }
}

- (void) updateProjectionWithOffset: (float) offset {
  [[self openGLContext] makeCurrentContext];
  
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  
  GLdouble near = -camera.pos.z + 0.5;  
  GLdouble far = -camera.pos.z + 8.0;
  
  // I do not fully understand this.
  if (near < 0.0001) near = 0.0001;
  if (far < 1.0) far = 1.0;
  
  GLdouble fovRadians = camera.fov / 180. * M_PI;
  GLdouble fovYRadians = fovRadians / 4. * 3.;
  GLdouble aspectRatio = camera.viewWidth / (GLdouble) camera.viewHeight;
  
  GLdouble left, top;
  if (aspectRatio > (4./3.)) {  // landscape
    GLdouble halfHeight = near * tan(fovYRadians / 2);
    left = -aspectRatio * halfHeight;
    top = halfHeight;
  } else {  // portrait
    GLdouble halfWidth = near * tan(fovRadians / 2);
    left = -halfWidth;
    top = halfWidth / aspectRatio;
  }
  
  double zeroParallax = 1;
  glFrustum(left - offset * near / zeroParallax,
            -left - offset * near / zeroParallax,
            -top,
            top,
            near,
            far);
}

- (void) updateModelViewWithOffset: (float) offset {
  [[self openGLContext] makeCurrentContext];
  
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  gluLookAt(camera.pos.x - offset, camera.pos.y, camera.pos.z,
            camera.target.x - offset, camera.target.y, camera.target.z,
            camera.up.x, camera.up.y, camera.up.z);
  
  glRotatef(trackballRotation[0], trackballRotation[1], trackballRotation[2], trackballRotation[3]);
  glRotatef(worldRotation[0], worldRotation[1], worldRotation[2], worldRotation[3]);
  glRotatef(spinY, 0, 1, 0);
  glRotatef(spinX, 1, 0, 0);
}

#define INTEROCULAR_DISTANCE (0.0635F/2)
- (void) drawRect: (NSRect)dirtyRect {
  [self resizeGL];
    
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  if (self.anaglyph) {
    [self updateProjectionWithOffset: INTEROCULAR_DISTANCE / 2];
    [self updateModelViewWithOffset: INTEROCULAR_DISTANCE / 2];
    glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE);
  } else {
    [self updateProjectionWithOffset: 0];
    [self updateModelViewWithOffset: 0];
  }
  
  // Draw right eye or center.
  [renderer drawInCurrentOpenGLContextWithOptions: nil];
  
  if (self.anaglyph) {
    // Draw left eye.
    glClear(GL_DEPTH_BUFFER_BIT);
    
    [self updateProjectionWithOffset: -INTEROCULAR_DISTANCE / 2];
    [self updateModelViewWithOffset: -INTEROCULAR_DISTANCE / 2];
    glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
    [renderer drawInCurrentOpenGLContextWithOptions: nil];
    
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  }
  
  [[self openGLContext] flushBuffer];
  self.updateQueued = NO;
}

- (GLuint) loadShaderOfType: (GLenum) type fromFiles: (NSArray *) names {
  NSString *extension;
  if (type == GL_VERTEX_SHADER) {
    extension = @"vs";
  } else if (type == GL_FRAGMENT_SHADER) {
    extension = @"fs";
  } else if (type == GL_GEOMETRY_SHADER_EXT) {
    extension = @"gs";
  } else {
    @throw [NSException exceptionWithName: @"KVContractError" reason: @"Bad value for shader type" userInfo: nil];
  }

  const char *sources[[names count]];
  GLint lengths[[names count]];
  int i = 0;
  for (NSString *name in names) {
    NSData *source = [NSData dataWithContentsOfFile: [[NSBundle mainBundle] pathForResource: name ofType: extension]];
    sources[i] = [source bytes];
    lengths[i] = (GLint) [source length];
    i++;
  }
  
  GLuint shader = glCreateShader(type);
  glShaderSource(shader, i, &sources[0], &lengths[0]);
  glCompileShader(shader);
  
  return shader;
}

- (void) requestUpdateFromBackgroundThread {
  @synchronized (self) {
    if (!self.updateQueued) {
      self.updateQueued = YES;
      [self performSelectorOnMainThread: @selector(markAsDirty) withObject: nil waitUntilDone: NO];
    }
  }
}

- (void) markAsDirty {
  [self setNeedsDisplay: YES];
}

#pragma mark --- User Interaction ---

- (void) mouseDown: (NSEvent *)theEvent {
  NSPoint location = [self convertPoint: [theEvent locationInWindow] fromView: nil];
  location.y = camera.viewHeight - location.y;  // Flip Y axis
  usingTrackball = YES;
  startTrackball(location.x, location.y, 0, 0, camera.viewWidth, camera.viewHeight);
}

- (void) mouseUp: (NSEvent *)theEvent {
  if (usingTrackball) {
    usingTrackball = NO;
    addToRotationTrackball(trackballRotation, worldRotation);
    for (int i = 0; i < 4; i++) trackballRotation[i] = 0;
  }
}

- (void) mouseDragged: (NSEvent *)theEvent {
  NSPoint location = [self convertPoint: [theEvent locationInWindow] fromView: nil];
  location.y = camera.viewHeight - location.y;  // Flip Y axis

  if (usingTrackball) {
    rollToTrackball(location.x, location.y, trackballRotation);
    [self setNeedsDisplay: YES];
  }
}

#define SCROLL_SPEED (50.F)

- (void) scrollWheel: (NSEvent *)theEvent {
  float deltaV = (float) [theEvent deltaY];
  BOOL changes = NO;
  if (deltaV) {
    if ([theEvent modifierFlags] & NSCommandKeyMask) {
      camera.pos.z -= deltaV / SCROLL_SPEED;
      camera.target.z -= deltaV / SCROLL_SPEED;
    } else {
      camera.pos.y -= deltaV / SCROLL_SPEED;
      camera.target.y -= deltaV / SCROLL_SPEED;
    }
    changes = YES;
  }
  
  float deltaH = (float) [theEvent deltaX];
  if (deltaH) {
    camera.pos.x -= deltaH / SCROLL_SPEED;
    camera.target.x -= deltaH / SCROLL_SPEED;
    changes = YES;
  }
  
  if (changes) {
    [self setNeedsDisplay: YES];
  }
}

- (void) magnifyWithEvent: (NSEvent *)theEvent {
  float mag = (float) [theEvent magnification];
  camera.fov -= mag * 10.f;
  if (camera.fov < 5.f) camera.fov = 5.f;
  [self setNeedsDisplay: YES];
}

-(void)keyDown: (NSEvent *)theEvent
{
  NSString *characters = [theEvent characters];
  if ([characters length]) {
    unichar character = [characters characterAtIndex:0];
		switch (character) {
			case 'a':
				spinY += 10;
				[self setNeedsDisplay: YES];
				break;
			case 'd':
				spinY -= 10;
				[self setNeedsDisplay: YES];
				break;
			case 'w':
				spinX += 10;
				[self setNeedsDisplay: YES];
				break;
			case 's':
				spinX -= 10;
				[self setNeedsDisplay: YES];
				break;
        
		}
	}
}

#pragma mark --- Actions ---

- (IBAction) toggleDepthFieldUpdates: sender {
  self.frozen = !self.frozen;
  [sender setState: self.frozen? NSOnState : NSOffState];
}

- (IBAction) toggleAnaglyph: sender {
  self.anaglyph = !self.anaglyph;
  [self setNeedsDisplay: YES];
}

- (IBAction) toggleTextures: sender {
  self.textured = !textured;
  [self setNeedsDisplay: YES];
  [sender setState: textured? NSOnState : NSOffState];
}

#pragma mark --- KFKinect Delegate Implementation ---

- (void) setLatestDepthSamples: (NSData *)data {
  if (!self.frozen) {
    [renderer setDepthSamples: data];
    [self setNeedsDisplay: YES];
  }
}

- (NSData *)latestDepthSamples {
  return nil;
}

- (void) setLatestColorSamples: (NSData *)data {
  if (!self.frozen) {
    [renderer setColorSamples: data];
    [self setNeedsDisplay: YES];
  }
}

- (NSData *)latestColorSamples {
  return nil;
}

@end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.