/* BBRotator.m				 
 *
 * Rotates the world. Moves the camera. Will change a lot!!
 *
 * For more interface-info see the header file. More in depth information
 * can be found in the source-code.
 *
 * Written by: 		Thomas Engel
 * Created:    		22.12.1993 (Copyleft)
 * Last modified: 	17.11.1994
 */

#import "BBRotator.h"
#import "../BBBeakerCamera.h"
#import "../BBMoleculeShape.h"
#import "BBSlider.h"
#import "../BBAppManager.h"
#import "../Preferences.subproj/BBPreferencesManager.h"
#import "../Preferences.subproj/BBDefaultApp.h"

#import "Misc3DDeviceServer.h"
#import "Misc3DDeviceDriver.h"
#import <misckit/MiscString.h>
#import <misckit/MiscRtMatrix.h>
#import <remote/NXProxy.h>

@implementation BBRotator

- makeKeyAndOrderFront:sender
{
	// If there hasnĀt been a Rotator-Panel yet. LetĀs do some init stuff.
	
	if( !panel )
	{
		// To get the right Slider behaviour we will use our own slider class.
	
		[BBSlider poseAs:[Slider class]];

		if( [NXApp loadNibSection:"Rotator.nib" owner:self] == nil )
			NXRunAlertPanel( NULL, "Couldn't load Rotator.nib",
							 "OK", NULL, NULL );
	}
	[panel makeKeyAndOrderFront:self];
	return self;
}

- setTarget:anObject
{
	target = anObject;
	return self;
}

- setCamera:anObject
{
	camera = anObject;
	return self;
}

- check3DMouse:sender
{
	// If there is a mouse and the preferences tell us to use a mouse...we will
	// use it.
	// We will check for the servers name and if we should use the mouse.
	// If anything has changed we will try to reconnect.
	// If the sender is 'nil' we will DISCONNECT !!!
	
	// The mouse preferences are a property of the app itself!
	
	id		ourPrefs;
	id 		thePortName;
	BOOL	useMouse;
	
	ourPrefs = [[[NXApp delegate] preferences] findDefaultFor:NXApp];
	useMouse = [ourPrefs use3DMouseIfPossible];
	
	// ok now if we got 'nil' as the sender we will definitly NOT use the
	// mouse too. In those cases we will simply do a nice cleanup.
	
	if( sender == nil || useMouse == NO )
		[self _disconnectFrom3DServer];
	
	// Otherwise let's try to connect to the 3DDeviceServer. 
	// If we fail it should not matter...same as no mouse.
		
	else
	{
		thePortName = [ourPrefs mouseServerPortName];
		
		// if the desired portname and our currentName don't match
		// we will have to clear all the old refs and setup the new server
		// connection.
		
		if( deviceServerName == nil ||
			[thePortName compareTo:deviceServerName caseSensitive:NO] != 0 )
		{
			[self _disconnectFrom3DServer];
			[self _connectTo3DServer:thePortName];
		}
		
		// otherwise we will free the old server name anyway.
		// We will take the prefs Sting object as our own.
		
		else [deviceServerName free];
		deviceServerName = thePortName;

		// Now if we really have a valid server and a refernce to the mouse
		// ...we will tell the mouse to use as as its target.
		
		if( deviceServer && mouse3D )
		{
			[mouse3D setProtocolForProxy:
								@protocol(Misc3DDeviceDriverProtocol)];	
			[mouse3D setTarget:self];
			[mouse3D enableEvents];
			[mouse3D setUseExternalSync:YES];
		}
		// puuuh. Well this get a little boring right now
		// but what we have to do is to disconnect again if we did not manage
		// to form a connection. Makes reconnecting with the same names
		// possible.

		else [self _disconnectFrom3DServer];
	}
	// Looks like we got it all...
	
	return self;
}

- _connectTo3DServer:thePortName
{
	// Prepare the host entries and other names and establish
	// the connection..
			
	id	sepString;
	id	theHostName;

	sepString = [MiscString newWithString:"/"];
	theHostName = [thePortName subStringLeft:sepString];

	// DO don't accept the 'localhost' as a valid hostname..so we have to take
	// care of that.

	if( [theHostName casecmp:"localhost"] == 0 )
		[theHostName setStringValue:""];

	deviceServer = [NXConnection connectToName:[thePortName stringValue] 
								 onHost:[theHostName stringValueAndFree]];
	ourPort = [deviceServer connectionForProxy];
	[ourPort runFromAppKit];
	
	[sepString free];
	
	mouse3D = [deviceServer mouse];

	return self;
}

- _disconnectFrom3DServer
{
	// We will completely disconnect from the 3DmouseServer.
	
	if( mouse3D )
	{
		[mouse3D setTarget:nil];
		// [mouse3D free]; I don't know if this is wise...
		//					we don't own that object.
	}
	mouse3D = nil;

	// if( deviceServer ) [deviceServer free]  I'm not sure about freeing...
	deviceServer = nil;
	
	// if( ourPort ) [ourPort free]; we shouldn't free anything..
	ourPort = nil;
	
	if( deviceServerName ) [deviceServerName free];
	deviceServerName = nil;

	return self;
}

- xRotation:sender;
{
	float 	angle;
	RtPoint xAxis = {1.0,0.0,0.0};
	
	angle = [sender floatValue] - lastSliderValue;
	[rotValue setIntValue:[sender intValue]];
	
	if( angle != 0.0 )
	{
		[target rotateAngle:angle axis:xAxis];
		[camera display];
		NXPing();
	}
	lastSliderValue = [sender floatValue];
	return self;
}

- yRotation:sender;
{
	float 	angle;
	RtPoint yAxis = { 0.0, 1.0, 0.0 };
	
	angle = [sender floatValue] - lastSliderValue;
	[rotValue setIntValue:[sender intValue]];
	
	if( angle != 0.0 )
	{
		[target rotateAngle:angle axis:yAxis];
		[camera display];
		NXPing();
	}
	lastSliderValue = [sender floatValue];
	return self;
}

- zRotation:sender;
{
	float 	angle;
	RtPoint zAxis = { 0.0, 0.0, 1.0 };
	
	angle = [sender floatValue] - lastSliderValue;
	[rotValue setIntValue:[sender intValue]];
	
	if( angle != 0.0 )
	{
		[target rotateAngle:angle axis:zAxis];
		[camera display];
		NXPing();
	}
	lastSliderValue = [sender floatValue];
	return self;
}

- xTranslation:sender
{
	// xTranslation will move our camera AND its toPoint across the xAxis.

	RtPoint fromPoint;
	RtPoint toPoint;
	float	angle;
	float 	delta;
	
	delta = ( [sender floatValue] - lastSliderValue );
	[transValue setIntValue:[sender intValue]];
	
	if( delta != 0.0 )
	{
		[camera getEyeAt:&fromPoint toward:&toPoint roll:&angle];
		fromPoint[0] -= delta;
		toPoint[0] -= delta;
		[camera setEyeAt:fromPoint toward:toPoint roll:angle];
		[camera display];	
		NXPing();
	}
	lastSliderValue = [sender floatValue];
	return self;
}

- yTranslation:sender
{
	// yTranslation will move our camera AND its toPoint across the yAxis.
	
	RtPoint fromPoint;
	RtPoint toPoint;
	float	angle;
	float 	delta;
	
	delta = ( [sender floatValue] - lastSliderValue );
	[transValue setIntValue:[sender intValue]];
	
	if( delta != 0.0 )
	{
		[camera getEyeAt:&fromPoint toward:&toPoint roll:&angle];
		fromPoint[1] -= delta;
		toPoint[1] -= delta;
		[camera setEyeAt:fromPoint toward:toPoint roll:angle];
		[camera display];
		NXPing();
	}
	lastSliderValue = [sender floatValue];
	return self;
}

- zTranslation:sender
{
	// zTranslation will ONLY move our camera across the zAxis.
	// So our focus will always remain at z=0 !!!
	
	RtPoint fromPoint;
	RtPoint toPoint;
	float	angle;
	float 	delta;
	
	delta = ( [sender floatValue] - lastSliderValue );
	[transValue setIntValue:[sender intValue]];
	
	if( delta != 0.0 )
	{
		[camera getEyeAt:&fromPoint toward:&toPoint roll:&angle];
		fromPoint[2] -= delta;
		[camera setEyeAt:fromPoint toward:toPoint roll:angle];
		[camera display];
		NXPing();
	}
	lastSliderValue = [sender floatValue];
	return self;
}

- benchMark:sender
{
	// We will use this method to trigger a redraw and then to estimate the
	// time consumpt.
	
	struct timezone tzone;
	struct timeval	realtime1;
	struct timeval	realtime2;
	double	myTime;
	
	gettimeofday(&realtime1,&tzone);
	[camera display];
	NXPing();
	gettimeofday(&realtime2,&tzone);
	
	myTime = ((realtime2.tv_sec + realtime2.tv_usec / 1.0E6 ) -
			  (realtime1.tv_sec + realtime1.tv_usec / 1.0E6 ));
	
	[benchTime setDoubleValue:myTime];
	return self;
}

- fastStyle:sender
{
	[self transformationWillStart:self];
	return self;
}

- niceStyle:sender
{
	[self transformationDidEnd:self];
	return self;
}

/*
 * Here comes the part where we implement the Misc3DMouseTarget protocol.
 * Together with the connectTo3DMouse method it provides the whole bunch of
 * code necessary to support any kind of 3D mouse.
 */

- (BOOL)worldIsRightHanded
{
	return NO;
}

- (oneway void)transformationEvent:(bycopy id)aMatrix
{
	// This method tries to aply the 3dmouse matrix to our data.
	// This method should ONLY be use by the 3DDriver mouse !!
	// Otherwise there might be problems with the sync. (I could solve
	// that by using som kind of internal flag...but..this object will need
	// a complete redesign anyway..)
	//
	// Only the rotation will be applied to the mol. Translation will be
	// extracted and set seperatly at our camera.
	// This is not perfect in any way. But it should be enough for a while.
	//
	// There is a problem when move the camera to the positiv z area.
	// The the transformation won't fit the reality. This is because of our
	// 'hacked' rotation stuff.
	// I should really apply the rotation to the world this camera does see.
	// This is not a bug in the 3DDeviceServer. Remember that you always to
	// transformation from the cameras point of view!! And the camera should be
	// on the negativ z-axis in a left-handed world; and on the positiv z-axis
	// in a right handed world!!
	
	RtMatrix	m;
	float		x, y, z;
	RtPoint 	fromPoint;
	RtPoint 	toPoint;
	float		angle;

	[aMatrix getTransformMatrix:m];
	
	x = -m[3][0];
	y = -m[3][1];
	z = -m[3][2];	// This is because the camera has to move differently
					// to show us the intended move of the object!
					// This will change later.
	m[3][0] = 0;
	m[3][1] = 0;
	m[3][2] = 0;

	if( x != 0 || y != 0 || z != 0 )
	{
		[camera getEyeAt:&fromPoint toward:&toPoint roll:&angle];
		fromPoint[0] += x;
		fromPoint[1] += y;
		fromPoint[2] += z;
		toPoint[0] += x;
		toPoint[1] += y;
		[camera setEyeAt:fromPoint toward:toPoint roll:angle];
	}

	if( [aMatrix doesRotate] )
		[target concatTransformMatrix:m premultiply:NO];
		
	[camera display];
	NXPing();
	[mouse3D syncEvents];
	return;
}

- (oneway void)transformationWillStart:sender
{
	[[camera worldShape] rotationStyleOn:YES];
//	[camera setHider:N3D_InOrderRendering];
	[camera setHider:N3D_HiddenRendering];
	[camera display];
	NXPing();
	return;
}

- (oneway void)transformationDidEnd:sender
{
	// If the sender is one of our sliders we will reset it to the
	// Zero point.
	
	if( sender == aSlider ||
		sender == bSlider ||
		sender == cSlider )
	{
		[sender setFloatValue:0];
		[rotValue setIntValue:0];
		lastSliderValue = 0;
	}
	else if( sender == xSlider ||
			 sender == ySlider ||
			 sender == zSlider )
	{
		[sender setFloatValue:0];
		[transValue setIntValue:0];
		lastSliderValue = 0;
	}
	
	[[camera worldShape] rotationStyleOn:NO];
	[camera setHider:N3D_HiddenRendering];
	[camera display];
	NXPing();
	return;
}

- (oneway void)keyEvent:(char *)theKeys
{
	return;
}

@end

/*
 * History: 21.11.94 Programmed around NeXT bug with the DO not accepting
 *					 localhost as a useful machine name..
 *
 *			17.11.94 Moved the 3DMosue support right here where it belongs.
 *
 *			07.05.94 Switched to BB..., and moved the PDO part to the main
 *					 AppManager.
 *
 *			12.04.94 Included 3DDeviceServer support.
 *
 *			15.01.94 Added the zoom mode.
 *
 *			22.12.93 First code written.
 *
 *
 * Bugs: - Well not really a bug. But there might be problems when check3DMouse
 *		   is called when the program is not the active app. Which might cause
 *		   to stop events being deliverd to the currently active app.
 */