/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***              C O N F I D E N T I A L  ---  W E S T W O O D  S T U D I O S               ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : LevelEdit                                                    *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Tools/LevelEdit/Mover.cpp                    $*
 *                                                                                             *
 *                       Author:: Patrick Smith                                                *
 *                                                                                             *
 *                     $Modtime:: 2/15/01 10:15a                                              $*
 *                                                                                             *
 *                    $Revision:: 15                                                          $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "stdafx.h"
#include "mover.h"
#include "utils.h"
#include "cameramgr.h"
#include "leveleditdoc.h"
#include "leveleditview.h"
#include "node.h"
#include "phys3.h"


///////////////////////////////////////////////////////////////////////////
//
//	Clip_Point
//
///////////////////////////////////////////////////////////////////////////
static inline bool
Clip_Point (Vector3 *point, const AABoxClass &box)
{
	Vector3 temp_point = *point;

	//
	//	Clip the temporary point
	//
	temp_point.X = max (temp_point.X, box.Center.X - box.Extent.X);
	temp_point.Y = max (temp_point.Y, box.Center.Y - box.Extent.Y);
	temp_point.Z = max (temp_point.Z, box.Center.Z - box.Extent.Z);
	temp_point.X = min (temp_point.X, box.Center.X + box.Extent.X);
	temp_point.Y = min (temp_point.Y, box.Center.Y + box.Extent.Y);
	temp_point.Z = min (temp_point.Z, box.Center.Z + box.Extent.Z);

	//
	//	Did the clip change the point?
	//
	bool retval = (point->X != temp_point.X);
	retval		|= (point->Y != temp_point.Y);
	retval		|= (point->Z != temp_point.Z);
	
	//
	//	Pass the new point back to the caller
	//
	(*point) = temp_point;
	
	return retval;
}


///////////////////////////////////////////////////////////////////////
//
//	Get_Mouse_Ray
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Get_Mouse_Ray
(
	const POINT &	mouse_pos, 
	float				length,
	Vector3 &		start,
	Vector3 &		end
)
{
	// The start of the ray is simply the camera's position
	start = ::Get_Camera_Mgr ()->Get_Camera ()->Get_Transform ().Get_Translation ();

	//
	// Ensure the 'point' is correct for this mode (fullscreen/windowed)
	//
	float xpos = mouse_pos.x;
	float ypos = mouse_pos.y;
	::Constrain_Point_To_Aspect_Ratio (xpos, ypos);
	
	// The 'end' of the ray is the world coordinates of the supplied point
	::Get_Camera_Mgr ()->Get_Camera ()->Device_To_World_Space (Vector2 (xpos, ypos), &end);

	//
	// Recalculate the length of the ray
	//
	end = start + ((end - start) * length);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Get_Mouse_Ray
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Get_Mouse_Ray
(
	float		length,
	Vector3 &start,
	Vector3 &end
)
{
	//
	// Ensure the 'point' is correct for this mode (fullscreen/windowed)
	//
	CPoint point;
	::GetCursorPos (&point);
	::Get_Main_View ()->ScreenToClient (&point);

	Get_Mouse_Ray (point, length, start, end);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Get_LOS_Ray
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Get_LOS_Ray
(
	const POINT &	mouse_pos, 
	float				length,
	Vector3 &		start,
	Vector3 &		end
)
{
	// Get the transformation matrix for the camera
	Matrix3D camera_tm = ::Get_Camera_Mgr ()->Get_Camera ()->Get_Transform ();
	
	//
	//	The start and end's of the ray are the camera's position
	// and 'length' down the camera's -z axis.
	//
	start = camera_tm.Get_Translation ();
	end	= camera_tm * Vector3 (0, 0, -length);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Get_LOS_Ray
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Get_LOS_Ray
(
	float		length,
	Vector3 &start,
	Vector3 &end
)
{
	//
	// Ensure the 'point' is correct for this mode (fullscreen/windowed)
	//
	CPoint point;
	::GetCursorPos (&point);
	::Get_Main_View ()->ScreenToClient (&point);

	Get_LOS_Ray (point, length, start, end);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Position_Node
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Position_Node (NodeClass *node)
{	
	float distance			= 1000.0F;

	//
	//	Determine what ray to cast the node along
	//
	Vector3 ray_start;
	Vector3 ray_end;
	Get_LOS_Ray (distance, ray_start, ray_end);

	Position_Node_Along_Ray (node, ray_start, ray_end);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Position_Node_Along_Ray
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Position_Node_Along_Ray (NodeClass *node)
{	
	//
	//	Determine what ray to cast the node along
	//
	Vector3 ray_start;
	Vector3 ray_end;
	Get_Mouse_Ray (350.00F, ray_start, ray_end);

	Position_Node_Along_Ray (node, ray_start, ray_end);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Position_Nodes_Along_Ray
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Position_Nodes_Along_Ray (void)
{
	//
	//	Determine what ray to cast the node along
	//
	Vector3 ray_start;
	Vector3 ray_end;
	Get_Mouse_Ray (350.00F, ray_start, ray_end);

	Position_Nodes_Along_Ray (ray_start, ray_end);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Position_Nodes_Along_Ray
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Position_Nodes_Along_Ray (const Vector3 &start, const Vector3 &end)
{
	NODE_LIST list;
	Vector3 center = ::Get_Scene_Editor ()->Build_Node_List (list);

	if (list.Count () == 1) {
		Position_Node_Along_Ray (list[0], start, end);
	} else {
		Position_Nodes_Along_Ray (list, center, start, end);
	}
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Position_Nodes_Along_Ray
//
///////////////////////////////////////////////////////////////////////
Vector3
MoverClass::Position_Nodes_Along_Ray
(
	NODE_LIST &		list,
	const	Vector3 &tracking_pt,
	const Vector3 &start,
	const Vector3 &end
)
{
	if (list.Count () == 1) {
		return Position_Node_Along_Ray (list[0], start, end);
	}

	Vector3 position (tracking_pt);

	//
	//	Turn off collision detection
	//
	for (int index = 0; index < list.Count (); index ++) {
		PhysClass *phys_obj = list[index]->Peek_Physics_Obj ();
		if (phys_obj != NULL) {			
			phys_obj->Inc_Ignore_Counter();
		}
	}	

	//
	// Cast the ray into the world so we can stop the objects at whatever it hits
	//
	CastResultStruct res;							
	if (Cast_Ray (res, start, end, GAME_COLLISION_GROUP) != NULL) {
		position = start + (end - start) * res.Fraction;
	}

	// Calculate a delta to move each object
	Vector3 delta = position - tracking_pt;

	//
	//	Re-position the nodes
	//
	for (index = 0; index < list.Count (); index ++) {
		NodeClass *node = list[index];

		PhysClass *phys_obj = node->Peek_Physics_Obj ();
		if (phys_obj != NULL) {			
			
			//
			//	Move the node and turn collision back on
			//
			node->Translate (delta);
			phys_obj->Dec_Ignore_Counter();
		}
	}

	::Set_Modified ();
	return position;
}


///////////////////////////////////////////////////////////////////////
//
//	Position_Node_Along_Ray
//
///////////////////////////////////////////////////////////////////////
Vector3
MoverClass::Position_Node_Along_Ray
(
	NodeClass *		node,
	const Vector3 &start,
	const Vector3 &end
)
{
	Vector3 position (0, 0, 0);

	PhysClass *phys_obj = node->Peek_Physics_Obj ();
	if (phys_obj != NULL) {

		position = node->Get_Transform ().Get_Translation ();

		// Don't include this node in the collision test
		phys_obj->Inc_Ignore_Counter();

		//
		// Cast the ray into the world so we can stop the object at whatever it hits
		//
		CastResultStruct res;							
		if (Cast_Ray (res, start, end, GAME_COLLISION_GROUP) != NULL) {

			//
			// Determine the new position based on whether or not the
			// ray-cast hit anything.
			//			
			position = start + (end - start) * res.Fraction;
			
			//
			//	Get the collision box for this render obj
			//
			AABoxClass box;
			RenderObjClass *model = phys_obj->Peek_Model ();		
			RenderObjClass *world_box = model->Get_Sub_Object_By_Name ("WORLDBOX");
			if (world_box != NULL) {
				world_box->Get_Obj_Space_Bounding_Box (box);
				MEMBER_RELEASE (world_box);
			} else {
				model->Get_Obj_Space_Bounding_Box (box);
			}
			box.Extent.Z = WWMATH_EPSILON;
			
			//
			//	Move the object so its not embedded in the surface
			//
			Vector3 temp_pt = box.Center - res.Normal;
			::Clip_Point (&temp_pt, box);
			float len	= (temp_pt - box.Center).Length () + WWMATH_EPSILON;
			position		+= (res.Normal * len);

			//
			//	Move the node
			//
			node->Translate (position - node->Get_Transform ().Get_Translation ());
		}

		phys_obj->Dec_Ignore_Counter();
		::Set_Modified ();
	}

	return position;
}


///////////////////////////////////////////////////////////////////////
//
//	Calc_Ray_Intersection_XY
//
///////////////////////////////////////////////////////////////////////
bool
MoverClass::Calc_Ray_Intersection_XY
(
	float				plane,
	const Vector3 &ray_start,
	const Vector3 &ray_end,
	Vector3 &		result
)
{
	// Assume no intersection
	bool intersected = false;

	if ((ray_end.Z - ray_start.Z) != 0.00F) {

		// Calculate the fraction of the distance along the ray where the
		// Z value of the ray is the same as the Z value of the plane.
		// This simulates an intersection of the ray with the x-y plane at depth 'z'.
		double fraction = double(plane - ray_start.Z) / double(ray_end.Z - ray_start.Z);

		// If the fraction is between 0 and 1, then the ray intersects the plane
		if ((fraction >= 0) && (fraction <= 1.0F)) {

			// Now calcuate the intersection point based on this fraction.
			// To do this we use the parameterized form of a line equation:
			// P = P1 + (P2 - P1) * t
			// (Where t is a percentage between 0 and 1)
			result = ray_start + ((ray_end - ray_start) * fraction);

			// Success!
			intersected = true;
		}
	}

	// Return the true/false result code
	return intersected;
}


///////////////////////////////////////////////////////////////////////
//
//	Calc_Ray_Intersection_XZ
//
///////////////////////////////////////////////////////////////////////
bool
MoverClass::Calc_Ray_Intersection_XZ
(
	float				plane,
	const Vector3 &ray_start,
	const Vector3 &ray_end,
	Vector3 &		result
)
{
	// Assume no intersection
	bool intersected = false;

	if ((ray_end.Y - ray_start.Y) != 0.00F) {

		// Calculate the fraction of the distance along the ray where the
		// Y value of the ray is the same as the Y value of the plane.
		// This simulates an intersection of the ray with the x-z plane at depth 'y'.
		double fraction = double(plane - ray_start.Y) / double(ray_end.Y - ray_start.Y);

		// If the fraction is between 0 and 1, then the ray intersects the plane
		if ((fraction >= 0) && (fraction <= 1.0F)) {

			// Now calcuate the intersection point based on this fraction.
			// To do this we use the parameterized form of a line equation:
			// P = P1 + (P2 - P1) * t
			// (Where t is a percentage between 0 and 1)
			result = ray_start + ((ray_end - ray_start) * fraction);

			// Success!
			intersected = true;
		}
	}

	// Return the true/false result code
	return intersected;
}


///////////////////////////////////////////////////////////////////////
//
//	Calc_Ray_Intersection_YZ
//
///////////////////////////////////////////////////////////////////////
bool
MoverClass::Calc_Ray_Intersection_YZ
(
	float				plane,
	const Vector3 &ray_start,
	const Vector3 &ray_end,
	Vector3 &		result
)
{
	// Assume no intersection
	bool intersected = false;

	if ((ray_end.X - ray_start.X) != 0.00F) {

		// Calculate the fraction of the distance along the ray where the
		// X value of the ray is the same as the X value of the plane.
		// This simulates an intersection of the ray with the y-z plane at depth 'x'.
		double fraction = double(plane - ray_start.X) / double(ray_end.X - ray_start.X);

		// If the fraction is between 0 and 1, then the ray intersects the plane
		if ((fraction >= 0) && (fraction <= 1.0F)) {

			// Now calcuate the intersection point based on this fraction.
			// To do this we use the parameterized form of a line equation:
			// P = P1 + (P2 - P1) * t
			// (Where t is a percentage between 0 and 1)
			result = ray_start + ((ray_end - ray_start) * fraction);

			// Success!
			intersected = true;
		}
	}

	// Return the true/false result code
	return intersected;
}


///////////////////////////////////////////////////////////////////////
//
//	Transform_Node
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Transform_Node
(
	NodeClass *			node,
	const Matrix3D &	transform
)
{
	//
	// Move this node by the translation delta
	//	
	Matrix3D curr_transform = node->Get_Transform ();
	Move_Node (node, transform.Get_Translation () - curr_transform.Get_Translation ());
	
	//
	// Determine a rotation matrix that is relative to the node's current transform
	//
	Matrix3D relative_transform;
	curr_transform.Get_Orthogonal_Inverse (relative_transform);
	Matrix3D rotation_matrix = relative_transform * transform;
	rotation_matrix.Set_Translation (Vector3 (0, 0, 0));
	
	//
	// Rotate the node by this matrix, centered about the new position.
	//
	Matrix3D coord_system (transform.Get_Translation ());
	Rotate_Node (node, rotation_matrix, coord_system);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Transform_Nodes
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Transform_Nodes
(
	NodeClass *			node,
	const Matrix3D &	transform
)
{
	NODE_LIST list;
	::Get_Scene_Editor ()->Build_Node_List (list);
	Transform_Nodes (list, node, transform);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Transform_Nodes
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Transform_Nodes
(
	NODE_LIST &			list,
	NodeClass *			node,
	const Matrix3D &	transform
)
{
	Matrix3D curr_transform = node->Get_Transform ();

	//
	// Move this list of nodes by the translation delta
	//
	Move_Nodes (list, transform.Get_Translation () - curr_transform.Get_Translation ());

	//
	// Determine a rotation matrix that is relative to the node's current transform
	//
	Matrix3D relative_transform;
	curr_transform.Get_Orthogonal_Inverse (relative_transform);
	Matrix3D rotation_matrix = relative_transform * transform;
	rotation_matrix.Set_Translation (Vector3 (0, 0, 0));
	
	//
	// Rotate the list of nodes by this matrix, centered about the new position.
	//
	Rotate_Nodes (list, rotation_matrix, transform.Get_Translation ());
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Move_Node
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Move_Node (NodeClass *node, const Vector3 &translation)
{
	node->Translate (translation);
	::Set_Modified ();
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Move_Nodes
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Simple_Move_Nodes (NODE_LIST &list, const Vector3 &translation)
{
	for (int index = 0; index < list.Count (); index ++) {
		NodeClass *node = list[index];
		node->Translate (translation);
	}

	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Move_Nodes
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Move_Nodes (const Vector3 &translation)
{
	NODE_LIST list;
	::Get_Scene_Editor ()->Build_Node_List (list);

	Simple_Move_Nodes (list, translation);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Move_Nodes
//
///////////////////////////////////////////////////////////////////////
Vector3
MoverClass::Move_Nodes (NODE_LIST &list, const Vector3 &tracking_point)
{
	//
	// Calculate the new position for the group
	//
	Matrix3D coord_system (1);		
	Vector3 new_pos = Calc_New_Position (coord_system, tracking_point);

	//
	// Loop through all the nodes and move them by the calculated delta
	//
	for (int index = 0; index < list.Count (); index ++) {
		NodeClass *node = list[index];
		
		//
		// Move the node
		//
		if (node != NULL) {
			node->Translate (new_pos - tracking_point);
		}
	}

	Set_Modified ();
	return new_pos;
}


///////////////////////////////////////////////////////////////////////
//
//	Calc_New_Position
//
///////////////////////////////////////////////////////////////////////
Vector3
MoverClass::Calc_New_Position
(
	const Matrix3D &	coord_system,
	const Vector3 &	start_in_world_coords,
	LPPOINT				mouse_pos
)
{
	Vector3 return_point	= start_in_world_coords;
	Vector3 start_point	= start_in_world_coords;

	//
	// The start of the ray is simply the camera's position
	//
	Vector3 ray_start = ::Get_Camera_Mgr ()->Get_Camera ()->Get_Transform ().Get_Translation ();

	//
	// Ensure the 'mouse-point' is correct for this mode (fullscreen/windowed)
	//
	CPoint point;

	if (mouse_pos == NULL) {		
		::GetCursorPos (&point);
		::Get_Main_View()->ScreenToClient (&point);
	} else {
		point = (*mouse_pos);
	}
	
	float xpos = point.x;
	float ypos = point.y;
	::Constrain_Point_To_Aspect_Ratio (xpos, ypos);
	
	//
	// The 'end' of the ray is the world coordinates of the mouse point
	//
	Vector3 ray_end;				
	::Get_Camera_Mgr ()->Get_Camera ()->Device_To_World_Space (Vector2 (xpos, ypos), &ray_end);

	//
	// However, now we must transform all our coords into the coordinate system we were passed.
	//
	Matrix3D coord_system_inv;
	coord_system.Get_Orthogonal_Inverse (coord_system_inv);
	ray_start			= coord_system_inv * ray_start;
	ray_end				= coord_system_inv * ray_end;
	start_point			= coord_system_inv * start_point;
	return_point		= coord_system_inv * return_point;
	
	//
	// Move the ray's endpoint along the line 1000 meters
	//
	ray_end = ray_start + ((ray_end - ray_start) * 1000.00F);

	//
	// Should we restrict movement to the z-axis?
	//
	if ((::GetKeyState (VK_SHIFT) < 0) ||
	    (::Get_Current_Document ()->Get_Axis_Restriction () == CLevelEditDoc::RESTRICT_Z)) {

		// Determine which plane (y-z or x-z) to base the movement on
		float deltax = ::fabs (ray_end.X - ray_start.X);
		float deltay = ::fabs (ray_end.Y - ray_start.Y);
		
		// Determine where the ray intersects this plane
		if (deltax > deltay) {

			// Determine where the ray intersects the YZ plane
			Vector3 result;
			if (Calc_Ray_Intersection_YZ (start_point.X, ray_start, ray_end, result)) {
				return_point.Z = result.Z;
			}
			
		} else {

			// Determine where the ray intersects the XZ plane
			Vector3 result;
			if (Calc_Ray_Intersection_XZ (start_point.Y, ray_start, ray_end, result)) {
				return_point.Z = result.Z;
			}
		}	

	} else {
		
		// Determine where the ray intersects the XY plane
		Vector3 result;
		if (Calc_Ray_Intersection_XY (start_point.Z, ray_start, ray_end, result)) {
			
			//
			// Now apply the axis restrictions
			//
			if (::Get_Current_Document ()->Get_Axis_Restriction () == CLevelEditDoc::RESTRICT_X) {
				return_point.X = result.X;
			} else if (::Get_Current_Document ()->Get_Axis_Restriction () == CLevelEditDoc::RESTRICT_Y) {
				return_point.Y = result.Y;
			} else {
				return_point = result;
			}
		}
	}	
	
	//
	// Now convert the return value to world coords
	//
	return_point = coord_system * return_point;

	// Return the new point
	return return_point;
}


///////////////////////////////////////////////////////////////////////
//
//	Rotate_Nodes
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Rotate_Nodes
(
	float				deltax,
	float				deltay,
	const Quaternion &	rotation
)
{
	NODE_LIST list;
	Vector3 center = ::Get_Scene_Editor ()->Build_Node_List (list);

	Rotate_Nodes (list, center, deltax, deltay, rotation);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Rotate_Nodes
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Rotate_Nodes
(
	NODE_LIST &			list,
	const Vector3 &		center,
	float				deltax,
	float				deltay,
	const Quaternion &	rotation
)
{
	Matrix3D rotation_matrix (1);

	//
	// Are we restricting movement to a specific axis?
	//
	switch (::Get_Current_Document ()->Get_Axis_Restriction ()) {
	
		case CLevelEditDoc::RESTRICT_X:
			rotation_matrix.Rotate_X (deltax * 3.0F);
			break;

		case CLevelEditDoc::RESTRICT_Y:
			rotation_matrix.Rotate_Y (deltay * 3.0F);
			break;

		case CLevelEditDoc::RESTRICT_Z:
			rotation_matrix.Rotate_Z (deltay * 3.0F);
			break;

		case CLevelEditDoc::RESTRICT_NONE:
			rotation_matrix.Set_Rotation (rotation);
			break;
	}

	Rotate_Nodes (list, rotation_matrix, center);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Rotate_Nodes
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Rotate_Nodes
(
	NODE_LIST &			list,
	const Matrix3D &	rotation_matrix,
	const Vector3 &		center
)
{
	//
	//	Setup the rotation
	//
	Matrix3D coord_system (1);
	coord_system.Set_Translation (center);

	//
	// Determine if we should restrict rotation or not
	//
	bool restrict_rotation = false;
	for (int index = 0; index < list.Count (); index ++) {
		restrict_rotation |= list[index]->Is_Rotation_Restricted ();
	}

	//
	// Loop through all the nodes and rotate them
	//
	for (index = 0; index < list.Count (); index ++) {
		NodeClass *node = list[index];

		//
		// Rotate the node
		//
		if (restrict_rotation == false) {
			Rotate_Node_Freely (node, rotation_matrix, coord_system);
		} else {
			Rotate_Node_Z90 (node, rotation_matrix, coord_system);
		}
	}

	Set_Modified ();
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Rotate_Nodes_Z
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Rotate_Nodes_Z (float multiplier)
{
	NODE_LIST list;
	Vector3 center = ::Get_Scene_Editor ()->Build_Node_List (list);

	Rotate_Nodes_Z (list, center, multiplier);
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Rotate_Nodes_Z
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Rotate_Nodes_Z
(
	NODE_LIST &		list,
	const Vector3 &	center,
	float			multiplier
)
{
	Matrix3D coord_system (1);	
	coord_system.Set_Translation (center);

	//
	// Determine if we should restrict rotation or not
	//
	bool restrict_rotation = false;
	for (int index = 0; index < list.Count (); index ++) {
		restrict_rotation |= list[index]->Is_Rotation_Restricted ();
	}

	//
	// Loop through all the nodes and rotate them
	//
	for (index = 0; index < list.Count (); index ++) {
		NodeClass *node = list[index];

		//
		// Determine which rotation matrix to use (Z90 lock, or normal)
		//
		Matrix3D rotation_matrix (1);
		if (restrict_rotation == false) {
			rotation_matrix.Rotate_Z (DEG_TO_RAD (5.0F) * multiplier);
		} else if (multiplier > 0.0F) {
			rotation_matrix.Rotate_Z ((float)DEG_TO_RAD (-90));
		} else {
			rotation_matrix.Rotate_Z ((float)DEG_TO_RAD (90));
		}

		// Rotate the node
		node->Rotate (rotation_matrix, coord_system);
	}

	Set_Modified ();		
	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Rotate_Node_Z90
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Rotate_Node_Z90
(
	NodeClass *			node,
	const Matrix3D &	rotation_matrix,
	const Matrix3D &	coord_system
)
{
	if (node->Is_Locked () == false) {

		//
		// Get the transformation matrix
		//
		Matrix3D transform		= node->Get_Transform ();
		Vector3 translation		= transform.Get_Translation ();
		bool allow_translation	= bool(coord_system.Get_Translation () != translation);

		Quaternion orientation = node->Get_Orientation ();
		transform.Set_Rotation (orientation);

		//
		// Rotate the transformation matrix in the provided coordinate system
		//
		::Rotate_Matrix (transform, rotation_matrix, coord_system, &transform);

		//
		// Remember the orientation of the new matrix
		//
		orientation = ::Build_Quaternion (transform);
		node->Set_Orientation (orientation);

		double new_z_rot = transform.Get_Z_Rotation ();
		if (new_z_rot < 0) {
			new_z_rot += DEG_TO_RAD (360);
		}

		if (allow_translation) {
			translation = transform.Get_Translation ();
		}

		// Restrict translation if necessary
		transform.Make_Identity ();

		// Based on the z oomponent of the rotation, determine which
		// direction the object should be facing
		if ((new_z_rot > DEG_TO_RAD (45)) && (new_z_rot <= DEG_TO_RAD (135))) {
			transform = Matrix3D::RotateZ90;
		} else if ((new_z_rot > DEG_TO_RAD (135)) && (new_z_rot <= DEG_TO_RAD (225))) {
			transform = Matrix3D::RotateZ180;
		} else if ((new_z_rot > DEG_TO_RAD (225)) && (new_z_rot <= DEG_TO_RAD (315))) {
			transform = Matrix3D::RotateZ270;
		}

		//
		// Translate the matrix as necessary
		//
		transform.Set_Translation (translation);	

		//
		// Pass the new transformation matrix onto the object
		//
		node->Set_Transform (transform);
		node->On_Rotate ();
	}

	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Rotate_Node_Freely
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Rotate_Node_Freely
(
	NodeClass *			node,
	const Matrix3D &	rotation_matrix,
	const Matrix3D &	coord_system
)
{
	if (node->Is_Locked () == false) {

		Matrix3D tm = node->Get_Transform ();

		//
		// Translate this node in the specified coordinate system
		//
		Matrix3D coord_inv;
		Matrix3D coord_to_obj;
		
		coord_system.Get_Orthogonal_Inverse (coord_inv);
		Matrix3D::Multiply (coord_inv, tm, &coord_to_obj);

		Matrix3D::Multiply (coord_system, rotation_matrix, &tm);
		Matrix3D::Multiply (tm, coord_to_obj, &tm);

		if (!tm.Is_Orthogonal ()) {
			tm.Re_Orthogonalize ();
			TRACE ("Matrix wasn't orthogonal.\n");
		}

		//
		// Rotate the object and do post processing
		//
		node->Set_Transform (tm);
		node->On_Rotate ();
	}

	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Rotate_Node
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Rotate_Node
(
	NodeClass *			node,
	const Matrix3D &	rotation_matrix,
	const Matrix3D &	coord_system
)
{
	//
	//	Determine whether the node can be rotated free-form or
	// is restricted to 90 deg rotations about the Z axis.
	//
	if (node->Is_Rotation_Restricted () == false) {
		Rotate_Node_Freely (node, rotation_matrix, coord_system);
	} else {
		Rotate_Node_Z90 (node, rotation_matrix, coord_system);
	}

	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Translate_Node
//
///////////////////////////////////////////////////////////////////////
void
MoverClass::Translate_Node (NodeClass *node, const Vector3 &vector)
{
	if (node->Is_Locked () == false) {
		
		//
		//	Move the physics object by the requested amount
		//
		Matrix3D tm = node->Get_Transform ();
		tm.Set_Translation (tm.Get_Translation () + vector);
		node->Set_Transform (tm);
		//phys_obj->Set_Position (tm.Get_Translation () + vector);

		/*Phys3Class *phys3_obj = phys_obj->As_Phys3Class ();
		if (phys3_obj != NULL) {
			phys3_obj->Set_Velocity (vector);
		}*/

		// Notify the node
		node->On_Translate ();
	}

	return ;
}

