#include "munga.h"
#pragma hdrstop

#include "caminst.h"
#include "notation.h"

//##########################################################################
//#############################    CameraInstance    #######################
//##########################################################################

//##########################################################################
// Construction and Destruction
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance::CameraInstance(
	int	camera_ID,
	const Origin &camera_origin,
	Enumeration	camera_type
)
{
	Str_Copy(cameraName, "camera", sizeof(cameraName));
	itoa(camera_ID, cameraName+6, 10);
	cameraID = camera_ID;
	cameraDataType = camera_type;
	SetLocalOrigin(camera_origin);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance::CameraInstance(StreamedInstance *model)
{
	cameraID = model->cameraID;
	localOrigin = model->localOrigin;
	cameraToWorld = localOrigin;
	cameraDataType = model->cameraType;
	for (int i=0; i<4; ++i)
	{
		clampValues[i] = model->clampValues[i];
	}
	Str_Copy(cameraName, "camera", sizeof(cameraName));
	itoa(cameraID, cameraName+6, 10);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define READ_CAMERA_ENTRY(name,value)\
	if (\
		!cam_file->GetEntry(\
			camera_page_name,\
			name,\
			&value\
		)\
	)\
	{ \
		DEBUG_STREAM << camera_page_name << " missing " name "!\n" << std::flush;\
		Fail("Bad cameras!");\
	}

CameraInstance::CameraInstance(
	NotationFile	*cam_file,
	const char *camera_page_name
)
{
	Check(this);
	Check(cam_file);
	Check_Pointer(camera_page_name);

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// PageName represents the cameraName
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	Str_Copy(cameraName, camera_page_name, sizeof(cameraName));

	//
	//~~~~~~~~~~~~~~~~~~~~~
	// Read in the position
	//~~~~~~~~~~~~~~~~~~~~~
	//
	READ_CAMERA_ENTRY("tranx", localOrigin.linearPosition.x);
	READ_CAMERA_ENTRY("trany", localOrigin.linearPosition.y);
	READ_CAMERA_ENTRY("tranz", localOrigin.linearPosition.z);

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~
	// Read in the Orientation
	//~~~~~~~~~~~~~~~~~~~~~~~~
	//
	READ_CAMERA_ENTRY("quatx", localOrigin.angularPosition.x);
	READ_CAMERA_ENTRY("quaty", localOrigin.angularPosition.y);
	READ_CAMERA_ENTRY("quatz", localOrigin.angularPosition.z);
	READ_CAMERA_ENTRY("quatw", localOrigin.angularPosition.w);
	cameraToWorld = localOrigin;

	//
	//---------------------
	// Read in camera clamp
	//---------------------
	//
	clampValues[0] = -PI;
	cam_file->GetEntry(
		camera_page_name,
		"minYawClamp",
		&clampValues[0].angle
	);
	clampValues[1] = PI;
	cam_file->GetEntry(
		camera_page_name,
		"maxYawClamp",
		&clampValues[1].angle
	);
	clampValues[2] = -PI_OVER_2;
	cam_file->GetEntry(
		camera_page_name,
		"minPitchClamp",
		&clampValues[2].angle
	);
	clampValues[3] = PI_OVER_2;
	cam_file->GetEntry(
		camera_page_name,
		"maxPitchClamp",
		&clampValues[3].angle
	);

	//
	// Overwrite default cameraID
	//
	const char *camera_data_entry;
	if(
		!cam_file->GetEntry(
			camera_page_name,
			"cameraType",
			&camera_data_entry
		)
	)
	{
		cameraDataType = DefaultCameraType;
	}
	else
	{
		Check_Pointer(camera_data_entry);
		cameraDataType = FindCameraType(camera_data_entry);
	}

	READ_CAMERA_ENTRY("cameraID", cameraID);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
CameraInstance::~CameraInstance()
{
}

//##########################################################################
// Data Access Funtions
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	CameraInstance::FindCameraType(const char *camera_data_type)
{

	if(strcmp("DefaultCameraType", camera_data_type) == 0)
	{
		return DefaultCameraType;
	}
	else if(strcmp("AlwaysSeesCameraType", camera_data_type) == 0)
	{
		return AlwaysSeesCameraType;
	}
	return ErrorCameraType;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

void
	CameraInstance::GetCameraTypeString(char *camera_type_string)
{
	switch(cameraDataType)
	{
	case DefaultCameraType:
		Str_Copy(
			camera_type_string,
			"DefaultCameraType",
			(sizeof(char) * 128)
		);
		break;
	case AlwaysSeesCameraType:
		Str_Copy(
			camera_type_string,
			"AlwaysSeesCameraType",
			(sizeof(char) * 128)
		);
		break;
	default:
		Tell(cameraDataType);
		Warn(" Invalid cameraType !\n");
		break;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraInstance::CalculateCameraRotation(
		YawPitchRoll *result,
		const Point3D &world
	)
{
	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Given a Vector this function finds a rotation involving only
	// pitch and yaw, roll will not be affected.  In addition the rotation
	// chosed will always be in the direction of least rotation
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	Check(this);
	Check(result);
	Check(&world);

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// find the vector from where we are to where we want to point
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//
	Point3D local;
	local.MultiplyByInverse(world, cameraToWorld);
	if (Small_Enough(local.LengthSquared()))
	{
		*result = YawPitchRoll::Identity;
		return;
	}

	//
	//~~~~~~~~~~~~~~~~~~~~~
	// Calculate the angles
	//~~~~~~~~~~~~~~~~~~~~~
	//
	result->yaw = Radian::Normalize(Arctan(-local.x, -local.z));
	Scalar xz_direction = Sqrt((local.x * local.x) + (local.z *local.z));
	result->pitch = Radian::Normalize(Arctan(local.y, xz_direction));
	result->roll = 0.0f;

	Check_Fpu();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraInstance::SetLocalOrigin(
		const Origin &new_origin
	)
{
	Check(this);
	Check(&new_origin);

	YawPitchRoll ypr;
	ypr = new_origin.angularPosition;
	clampValues[0] = clampValues[1] = 0.0f;
	clampValues[2] = clampValues[3] = ypr.pitch;
	ypr.pitch = 0.0f;
	localOrigin.linearPosition = new_origin.linearPosition;
	localOrigin.angularPosition = ypr;
	cameraToWorld = localOrigin;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraInstance::LookAt(
		Origin *result,
		const Point3D &world_point
	)
{
	result->linearPosition = localOrigin.linearPosition;
	YawPitchRoll first,local;
	first = localOrigin.angularPosition;
	CalculateCameraRotation(&local, world_point);
	Clamp(local.yaw, clampValues[0], clampValues[1]);
	Clamp(local.pitch, clampValues[2], clampValues[3]);
	first.yaw += local.yaw;
	first.pitch = local.pitch;
	result->angularPosition = first;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraInstance::AllowLookingAt(const Point3D &world)
{
	YawPitchRoll local;
	CalculateCameraRotation(&local, world);
	if (local.yaw < clampValues[0])
	{
		clampValues[0] = local.yaw;
	}
	else if (local.yaw > clampValues[1])
	{
		clampValues[1] = local.yaw;
	}
	if (local.pitch < clampValues[2])
	{
		clampValues[2] = local.pitch;
	}
	else if (local.pitch > clampValues[3])
	{
		clampValues[3] = local.pitch;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#define YAW_SLOP (PI/12.0)
#define PITCH_SLOP (0.75*(YAW_SLOP))

Logical
	CameraInstance::CanCameraSee(const Point3D &world_point)
{
	YawPitchRoll ypr;
	CalculateCameraRotation(&ypr, world_point);
	return
		clampValues[0]-YAW_SLOP <= ypr.yaw
		 && clampValues[1]+YAW_SLOP >= ypr.yaw
		 && clampValues[2]-PITCH_SLOP <= ypr.pitch
		 && clampValues[3]+PITCH_SLOP >= ypr.pitch;
}

//##########################################################################
// Test Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	CameraInstance::TestInstance() const
{
	return True;
}

//##########################################################################
// Tool Support
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	CameraInstance::WriteNotationPage(NotationFile	*camera_stream)
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Output this camera's information to the given notation file
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
	Check(this);
	Check(camera_stream);
	camera_stream->SetEntry(
		cameraName,
		"cameraID",
		cameraID
	);

	char camera_type[128];
	GetCameraTypeString(camera_type);
	camera_stream->SetEntry(
		cameraName,
		"cameraType",
		camera_type
	);

	camera_stream->SetEntry(
		cameraName,
		"tranx",
		localOrigin.linearPosition.x
	);
	camera_stream->SetEntry(
		cameraName,
		"trany",
		localOrigin.linearPosition.y
	);
	camera_stream->SetEntry(
		cameraName,
		"tranz",
		localOrigin.linearPosition.z
	);
	camera_stream->SetEntry(
		cameraName,
		"quatx",
		localOrigin.angularPosition.x
	);
	camera_stream->SetEntry(
		cameraName,
		"quaty",
		localOrigin.angularPosition.y
	);
	camera_stream->SetEntry(
		cameraName,
		"quatz",
		localOrigin.angularPosition.z
	);
	camera_stream->SetEntry(
		cameraName,
		"quatw",
		localOrigin.angularPosition.w
	);
	camera_stream->SetEntry(
		cameraName,
		"minYawClamp",
		clampValues[0]
	);
	camera_stream->SetEntry(
		cameraName,
		"maxYawClamp",
		clampValues[1]
	);
	camera_stream->SetEntry(
		cameraName,
		"minPitchClamp",
		clampValues[2]
	);
	camera_stream->SetEntry(
		cameraName,
		"maxPitchClamp",
		clampValues[3]
	);
	camera_stream->AppendEntry(cameraName, NULL, (char *)NULL);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#undef READ_CAMERA_ENTRY
#define READ_CAMERA_ENTRY(name,value)\
	if (\
		!cam_file->GetEntry(\
			camera_page_name,\
			name,\
			&value\
		)\
	)\
	{ \
		DEBUG_STREAM << camera_page_name << " missing " name "!\n" << std::flush;\
		return False;\
	}

Logical
	CameraInstance::CreateStreamedInstance(
		StreamedInstance *model,
		NotationFile *cam_file,
		const char *camera_page_name
	)
{
	Check_Pointer(model);
	Check(cam_file);
	Check_Pointer(camera_page_name);

	//
	//~~~~~~~~~~~~~~~~~~~~~
	// Read in the position
	//~~~~~~~~~~~~~~~~~~~~~
	//
	READ_CAMERA_ENTRY("tranx", model->localOrigin.linearPosition.x);
	READ_CAMERA_ENTRY("trany", model->localOrigin.linearPosition.y);
	READ_CAMERA_ENTRY("tranz", model->localOrigin.linearPosition.z);

	//
	//~~~~~~~~~~~~~~~~~~~~~~~~
	// Read in the Orientation
	//~~~~~~~~~~~~~~~~~~~~~~~~
	//
	READ_CAMERA_ENTRY("quatx", model->localOrigin.angularPosition.x);
	READ_CAMERA_ENTRY("quaty", model->localOrigin.angularPosition.y);
	READ_CAMERA_ENTRY("quatz", model->localOrigin.angularPosition.z);
	READ_CAMERA_ENTRY("quatw", model->localOrigin.angularPosition.w);

	//
	//---------------------
	// Read in camera clamp
	//---------------------
	//
	model->clampValues[0] = -PI;
	cam_file->GetEntry(
		camera_page_name,
		"minYawClamp",
		&model->clampValues[0].angle
	);
	model->clampValues[1] = PI;
	cam_file->GetEntry(
		camera_page_name,
		"maxYawClamp",
		&model->clampValues[1].angle
	);
	model->clampValues[2] = -PI_OVER_2;
	cam_file->GetEntry(
		camera_page_name,
		"minPitchClamp",
		&model->clampValues[2].angle
	);
	model->clampValues[3] = PI_OVER_2;
	cam_file->GetEntry(
		camera_page_name,
		"maxPitchClamp",
		&model->clampValues[3].angle
	);

	//
	// Overwrite default cameraID
	//
	const char *camera_data_entry;
	if(
		!cam_file->GetEntry(
			camera_page_name,
			"cameraType",
			&camera_data_entry
		)
	)
	{
		model->cameraType = DefaultCameraType;
	}
	else
	{
		Check_Pointer(camera_data_entry);
		model->cameraType = FindCameraType(camera_data_entry);
	}

	READ_CAMERA_ENTRY("cameraID", model->cameraID);

	model->instanceSize = sizeof(*model);
	return True;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AlwaysSeesCameraInstance::AlwaysSeesCameraInstance(
	int	camera_ID,
	const Origin &camera_origin,
	Enumeration	camera_type
):
	CameraInstance(camera_ID, camera_origin, camera_type)
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AlwaysSeesCameraInstance::AlwaysSeesCameraInstance(StreamedInstance *model):
	CameraInstance(model)
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AlwaysSeesCameraInstance::AlwaysSeesCameraInstance(
	NotationFile	*cam_file,
	const char *camera_page_name
):
	CameraInstance(cam_file, camera_page_name)
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	AlwaysSeesCameraInstance::CanCameraSee(const Point3D &)
{
	return True;
}