//
//  AUOpenGLTrackballView.mm
//  SDIFSynth
//
//  Created by Cynthia Bruyns on 8/11/05.
//  Copyright 2005 __MyCompanyName__. All rights reserved.
//

#import "AUOpenGLTrackballView.h"
#import "trackball.h"


@implementation AUOpenGLTrackballView

#pragma mark ___basicPixelFormat___

+ (NSOpenGLPixelFormat*) basicPixelFormat
{
    NSOpenGLPixelFormatAttribute attributes [] = {
        NSOpenGLPFAWindow,
        NSOpenGLPFADoubleBuffer,	// double buffered
        NSOpenGLPFADepthSize, 
		(NSOpenGLPixelFormatAttribute) 16, // 16 bit depth buffer
        (NSOpenGLPixelFormatAttribute) nil
    };
    return [[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes] autorelease];
}

#pragma mark ___Initialization___

- (void) prepareOpenGLContext
{
	long swapInt = 1;
	[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; 
	
	// init GL stuff here
	glEnable(GL_LIGHTING);
	glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
	glShadeModel(GL_SMOOTH);    
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	
	//glPolygonOffset (1.0f, 1.0f);
	
	glClearColor(0.0f,0.0f, 0.0f, 0.0f);	
	
	glEnable(GL_DEPTH_TEST);
	
//	glEnable(GL_LINE_SMOOTH);
//	glLineWidth(5.0);
}

- (void) prepareCamera
{
	camera.aperture = 35.0;
		
	camera.rotPoint.x = camera.rotPoint.y = camera.rotPoint.z = 0.0;
	
	camera.viewPos.x = -0.5;
	camera.viewPos.y = 0.6;
	camera.viewPos.z = 2.0;
	
	camera.viewDir.x =  0.5; 
	camera.viewDir.y =  0.0; 
	camera.viewDir.z =  -1.0;
	
	camera.viewUp.x = 0.0;  
	camera.viewUp.y = 1.0; 
	camera.viewUp.z = 0.0;
	
	shapeSize = 100.0f;
	
}

- (void) resetCamera
{
	worldRotation [0] = worldRotation [1] = worldRotation [2] = worldRotation [3] = 0.0f;
	gTrackBallRotation [0] = gTrackBallRotation [1] = gTrackBallRotation [2] = gTrackBallRotation [3] = 0.0f;	
	camera.viewPos.x = camera.viewPos.y = 0.0;
}


- (void) initValues
{
	light_pos[0] = light_pos[1] = light_pos[2] = 1.0; light_pos[3] = 0.0;
	
	ambientIntensity[0] =  ambientIntensity[1] = ambientIntensity[2] = ambientIntensity[3] = 1.0;
	diffuseIntensity[0] =  diffuseIntensity[1] = diffuseIntensity[2] = diffuseIntensity[3] = 1.0;
	specularIntensity[0] =  specularIntensity[1] = specularIntensity[2] = specularIntensity[3] = 1.0;
	spotExponent = 128.0;

	gDollyPanStartPoint[0] = gDollyPanStartPoint[1] = 0;
	gTrackBallRotation[0] =  gTrackBallRotation[1] = gTrackBallRotation[2] = gTrackBallRotation[3] = 0.0f;

	gDolly = GL_FALSE;
	gPan = GL_FALSE;
	gTrackball = GL_FALSE;
	
	gTrackingViewInfo = NULL;
}

- (id)initWithFrame:(NSRect)frame pixelFormat: (NSOpenGLPixelFormat*) pf
{
    self = [super initWithFrame:frame pixelFormat: pf];
    if (self) {
       [self initValues];
    }
    return self;
}

#pragma mark ___MouseFunctions___

// move camera in z axis
-(void)mouseDolly: (NSPoint) location
{
	GLfloat dolly = (gDollyPanStartPoint[1] -location.y) * -camera.viewPos.z / 300.0f;
	camera.viewPos.z += dolly;
	if (camera.viewPos.z == 0.0) // do not let z = 0.0
		camera.viewPos.z = 0.0001;
	gDollyPanStartPoint[0] = (GLint) location.x;
	gDollyPanStartPoint[1] = (GLint) location.y;
}
		
// move camera in x/y plane
- (void)mousePan: (NSPoint) location
{
	GLfloat panX = (gDollyPanStartPoint[0] - location.x) / (900.0f / -camera.viewPos.z);
	GLfloat panY = (gDollyPanStartPoint[1] - location.y) / (900.0f / -camera.viewPos.z);
	camera.viewPos.x -= panX;
	camera.viewPos.y -= panY;
	gDollyPanStartPoint[0] = (GLint) location.x;
	gDollyPanStartPoint[1] = (GLint) location.y;
}

- (void) mouseDown:(NSEvent *)theEvent // trackball
{
	if ([theEvent modifierFlags] & NSControlKeyMask) // send to pan
		[self rightMouseDown:theEvent];
	else if ([theEvent modifierFlags] & NSAlternateKeyMask) // send to dolly
		[self otherMouseDown:theEvent];
	else {
		NSPoint location = [self convertPoint:[theEvent locationInWindow] fromView:nil];
		location.y = camera.viewHeight - location.y;
		gDolly = GL_FALSE; // no dolly
		gPan = GL_FALSE; // no pan
		gTrackball = GL_TRUE;
		startTrackball ((long int) location.x, (long int) location.y, 0, 0, camera.viewWidth, camera.viewHeight);
		gTrackingViewInfo = self;
	}
}

- (void)rightMouseDown:(NSEvent *)theEvent // pan
{
	NSPoint location = [self convertPoint:[theEvent locationInWindow] fromView:nil];
	location.y = camera.viewHeight - location.y;
	if (gTrackball) { // if we are currently tracking, end trackball
		if (gTrackBallRotation[0] != 0.0)
			addToRotationTrackball (gTrackBallRotation, worldRotation);
		
		gTrackBallRotation [0] = gTrackBallRotation [1] = gTrackBallRotation [2] = gTrackBallRotation [3] = 0.0f;
	}
	gDolly = GL_FALSE; // no dolly
	gPan = GL_TRUE; 
	gTrackball = GL_FALSE; // no trackball
	gDollyPanStartPoint[0] = (GLint) location.x;
	gDollyPanStartPoint[1] = (GLint) location.y;
	gTrackingViewInfo = self;
}

- (void)otherMouseDown:(NSEvent *)theEvent //dolly
{
	NSPoint location = [self convertPoint:[theEvent locationInWindow] fromView:nil];
	location.y = camera.viewHeight - location.y;
	if (gTrackball) { // if we are currently tracking, end trackball
		if (gTrackBallRotation[0] != 0.0)
			addToRotationTrackball (gTrackBallRotation, worldRotation);
		gTrackBallRotation [0] = gTrackBallRotation [1] = gTrackBallRotation [2] = gTrackBallRotation [3] = 0.0f;
	}
	gDolly = GL_TRUE;
	gPan = GL_FALSE; // no pan
	gTrackball = GL_FALSE; // no trackball
	gDollyPanStartPoint[0] = (GLint) location.x;
	gDollyPanStartPoint[1] = (GLint) location.y;
	gTrackingViewInfo = self;
}

- (void)mouseUp:(NSEvent *)theEvent
{
	if (gDolly) { // end dolly
		gDolly = GL_FALSE;
	} else if (gPan) { // end pan
		gPan = GL_FALSE;
	} else if (gTrackball) { // end trackball
		gTrackball = GL_FALSE;
		if (gTrackBallRotation[0] != 0.0)
			addToRotationTrackball (gTrackBallRotation, worldRotation);
		gTrackBallRotation [0] = gTrackBallRotation [1] = gTrackBallRotation [2] = gTrackBallRotation [3] = 0.0f;
	} 
	gTrackingViewInfo = NULL;
}

- (void)rightMouseUp:(NSEvent *)theEvent
{
	[self mouseUp:theEvent];
}

- (void)otherMouseUp:(NSEvent *)theEvent
{
	[self mouseUp:theEvent];
}


- (void)mouseDragged:(NSEvent *)theEvent
{
	NSPoint location = [self convertPoint:[theEvent locationInWindow] fromView:nil];
	location.y = camera.viewHeight - location.y;
	if (gTrackball) {
		rollToTrackball ((long int) location.x, (long int) location.y, gTrackBallRotation);
		[self setNeedsDisplay: YES];
	} else if (gDolly) {
		[self mouseDolly: location];
		[self updateProjection];  // update projection matrix (not normally done on draw)
		[self setNeedsDisplay: YES];
	} else if (gPan) {
		[self mousePan: location];
		[self setNeedsDisplay: YES];
	}
}

- (void)scrollWheel:(NSEvent *)theEvent
{
	float wheelDelta = [theEvent deltaX] +[theEvent deltaY] + [theEvent deltaZ];
	if (wheelDelta)
	{
		GLfloat deltaAperture = wheelDelta * -camera.aperture / 200.0f;
		camera.aperture += deltaAperture;
		if (camera.aperture < 0.1) // do not let aperture <= 0.1
			camera.aperture = 0.1;
		if (camera.aperture > 179.9) // do not let aperture >= 180
			camera.aperture = 179.9;
		[self updateProjection]; // update projection matrix
		[self setNeedsDisplay: YES];
	}
}


- (void)rightMouseDragged:(NSEvent *)theEvent
{
	[self mouseDragged: theEvent];
}

- (void)otherMouseDragged:(NSEvent *)theEvent
{
	[self mouseDragged: theEvent];
}

#pragma mark ___Update___

- (void) updateProjection
{
	GLdouble ratio, radians, wd2;
	GLdouble left, right, top, bottom, near, far;

	[[self openGLContext] makeCurrentContext];

	// set projection
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	near = -camera.viewPos.z - shapeSize * 0.5;
	if (near < 0.00001)
		near = 0.00001;
	far = -camera.viewPos.z + shapeSize * 0.5;
	if (near < 1.0)
		near = 1.0;
	radians = 0.0174532925 * camera.aperture / 2; 
	// half aperture degrees to radians 
	wd2 = near * tan(radians);
	ratio = camera.viewWidth / (Float32) camera.viewHeight;
	if (ratio >= 1.0) {
		left  = -ratio * wd2;
		right = ratio * wd2;
		top = wd2;
		bottom = -wd2;	
	} else {
		left  = -wd2;
		right = wd2;
		top = wd2 / ratio;
		bottom = -wd2 / ratio;	
	}
	glFrustum (left, right, bottom, top, near, far);
	
}

- (void) updateModelView
{
	[[self openGLContext] makeCurrentContext];
	
	// move view
	glMatrixMode (GL_MODELVIEW);
	glLoadIdentity ();
	gluLookAt (camera.viewPos.x, camera.viewPos.y, camera.viewPos.z,
						 camera.viewPos.x + camera.viewDir.x,
						 camera.viewPos.y + camera.viewDir.y,
						 camera.viewPos.z + camera.viewDir.z,
						 camera.viewUp.x, camera.viewUp.y ,camera.viewUp.z);
	
	if ((gTrackingViewInfo == self) && gTrackBallRotation[0] != 0.0f) 
		glRotatef (gTrackBallRotation[0], gTrackBallRotation[1], gTrackBallRotation[2], gTrackBallRotation[3]);
	else {
	}
	
	glRotatef (worldRotation[0], worldRotation[1], worldRotation[2], worldRotation[3]);
	
}

- (void) resizeGL
{
	NSRect rectView = [self bounds];
	
	// ensure camera knows size changed
	if ((camera.viewHeight != rectView.size.height) ||
	    (camera.viewWidth != rectView.size.width)) {
		camera.viewHeight = (GLint) rectView.size.height;
		camera.viewWidth = (GLint) rectView.size.width;
		
		glViewport (0, 0, camera.viewWidth, camera.viewHeight);
		[self updateProjection]; 
	}
}

#pragma mark ___ResponderFuncs___

- (BOOL)acceptsFirstResponder
{
  return YES;
}

- (BOOL)becomeFirstResponder
{
  return  YES;
}

- (BOOL)resignFirstResponder
{
  return YES;
}


@end
