/*
**	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/Node.cpp        $*
 *                                                                                             *
 *                       Author:: Patrick Smith                                                *
 *                                                                                             *
 *                     $Modtime:: 1/18/02 11:54a                                              $*
 *                                                                                             *
 *                    $Revision:: 49                                                          $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "stdafx.h"
#include "node.h"
#include "phys.h"
#include "sceneeditor.h"
#include "utils.h"
#include "selectionbox.h"
#include "mover.h"
#include "chunkio.h"
#include "preset.h"
#include "wwstring.h"
#include "presetmgr.h"
#include "vispointgenerator.h"
#include "mesh.h"
#include "preset.h"
#include "staticphys.h"
#include "NodeInfoPage.h"
#include "EditorPropSheet.h"
#include "LevelEditView.h"
#include "NodeMgr.h"
#include "PositionPage.h"


///////////////////////////////////////////////////////////////////////
//	Constants
///////////////////////////////////////////////////////////////////////
const Vector3 SEL_COLOR_NORMAL	= Vector3 (0.8F, 0.8F, 0.8F);
const Vector3 SEL_COLOR_LOCKED	= Vector3 (0.0F, 0.0F, 0.8F);
const Vector3 SEL_COLOR_INVALID	= Vector3 (1.0F, 0.0F, 0.0F);

enum
{
	CHUNKID_VARIABLES			= 0x00000100,
	CHUNKID_COMMENTS
};

enum
{
	VARID_NAME			= 0x01,
	VARID_IS_LOCKED,
	VARID_PRESETID,
	VARID_ID,
	VARID_ROT_RESTRICT,
	VARID_THISPTR,
	VARID_TRANSFORM,
	VARID_VISID,
	VARID_COMMENTS,
	VARID_VISSECTORID,
	VARID_CULLLINK,
	VARID_NEEDS_SAVE,
	VARID_CONTAINER_ID,
	VARID_IS_PROXIED
};


//////////////////////////////////////////////////////////////////////
//
//	NodeClass
//
//////////////////////////////////////////////////////////////////////
NodeClass::NodeClass (PresetClass *preset)
	:	m_Preset (NULL),
		m_PresetID (0),
		m_ID (0),
		m_SelectionBox (NULL),
		m_IsLocked (false),
		m_RotationRestricted (false),
		m_Orientation (true),
		m_IsInScene (false),
		m_SelColor (SEL_COLOR_NORMAL),
		m_NextNode (NULL),
		m_PrevNode (NULL),
		m_HitTestInfo (this),
		m_NeedsSave (true),
		m_IsProxied (false),
		m_Transform (1),
		m_CullLink (-1),
		m_ContainerPresetID (0)
{
	Set_Preset (preset);
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	NodeClass
//
//////////////////////////////////////////////////////////////////////
NodeClass::NodeClass (const NodeClass &src)
	:	m_Preset (NULL),
		m_PresetID (0),
		m_ID (0),
		m_SelectionBox (NULL),
		m_IsLocked (false),
		m_RotationRestricted (false),
		m_Orientation (true),
		m_IsInScene (false),
		m_SelColor (SEL_COLOR_NORMAL),
		m_NextNode (NULL),
		m_PrevNode (NULL),
		m_HitTestInfo (this),
		m_NeedsSave (true),
		m_IsProxied (false),
		m_Transform (1),
		m_CullLink (-1),
		m_ContainerPresetID (0)
{
	*this = src;
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	~NodeClass
//
//////////////////////////////////////////////////////////////////////
NodeClass::~NodeClass (void)
{
	SAFE_DELETE (m_SelectionBox);
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Add_To_Scene
//
//////////////////////////////////////////////////////////////////////
void
NodeClass::Add_To_Scene (void)
{
	PhysClass *phys_obj = Peek_Physics_Obj ();
	if (phys_obj != NULL && phys_obj->Get_Culling_System () == NULL) {
		
		//
		//	Add the object to either the static or dynamic culling system
		//
		SceneEditorClass *scene = ::Get_Scene_Editor ();
		if (Is_Static ()) {
			scene->Add_Static_Object ((StaticPhysClass *)phys_obj, Get_Cull_Link ());
		} else {
			scene->Add_Dynamic_Object (phys_obj);
		}
	}
	
	m_IsInScene = true;
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Remove_From_Scene
//
//////////////////////////////////////////////////////////////////////
void
NodeClass::Remove_From_Scene (void)
{
	PhysClass *phys_obj = Peek_Physics_Obj ();
	if (phys_obj != NULL && phys_obj->Get_Culling_System () != NULL) {

		//
		//	Record the cull-link index (if necessary)
		//
		Update_Cached_Cull_Link ();
		
		//
		//	Remove this object from either the static or dynamic culling system
		//
		SceneEditorClass *scene = ::Get_Scene_Editor ();
		if (Is_Static ()) {
			scene->Remove_Object ((StaticPhysClass *)phys_obj);
		} else {
			scene->Remove_Object (phys_obj);
		}
	}
	
	m_IsInScene = false;
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Update_Cached_Cull_Link
//
//////////////////////////////////////////////////////////////////////
void
NodeClass::Update_Cached_Cull_Link (void)
{
	//
	//	We can only do this if the physics object is currently in the scene
	//
	PhysClass *phys_obj = Peek_Physics_Obj ();
	if (phys_obj != NULL && phys_obj->Get_Culling_System () != NULL) {

		//
		//	Make sure this is a static physics object
		//
		StaticPhysClass *static_phys_obj = phys_obj->As_StaticPhysClass ();
		if (static_phys_obj != NULL) {

			//
			//	Dig the cull link out from the culling system structure
			//
			AABTreeLinkClass *link = (AABTreeLinkClass *)static_phys_obj->Get_Cull_Link ();
			if (link != NULL) {
				m_CullLink = link->Node->Index;
			}
		}
	}

	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Peek_Render_Obj
//
//////////////////////////////////////////////////////////////////////
RenderObjClass *
NodeClass::Peek_Render_Obj (void) const
{
	RenderObjClass *render_obj	= NULL;
	PhysClass *phys_obj			= Peek_Physics_Obj ();
	if (phys_obj != NULL) {		
		render_obj = phys_obj->Peek_Model ();
	}
	
	return render_obj;
}


//////////////////////////////////////////////////////////////////////
//
//	Hide
//
//////////////////////////////////////////////////////////////////////
void
NodeClass::Hide (bool hide)
{
	RenderObjClass *render_obj	= Peek_Render_Obj ();
	if (render_obj != NULL) {
		render_obj->Set_Hidden (hide);
	}
	
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Is_Hidden
//
//////////////////////////////////////////////////////////////////////
bool
NodeClass::Is_Hidden (void) const
{
	bool is_hidden = false;

	RenderObjClass *render_obj	= Peek_Render_Obj ();
	if (render_obj != NULL) {
		is_hidden = (render_obj->Is_Not_Hidden_At_All () == false);
	}
	
	return is_hidden;
}


//////////////////////////////////////////////////////////////////////
//
//	Show_Selection_Box
//
//////////////////////////////////////////////////////////////////////
void
NodeClass::Show_Selection_Box (bool show)
{
	if (show) {

		//
		//	Create the selection box if necessary
		//
		if (m_SelectionBox == NULL) {
			m_SelectionBox = new SelectionBoxClass;
		}
		Update_Selection_Color ();
		m_SelectionBox->Display_Around_Node (*this);

	} else {

		//
		//	Hide and destroy the selection box
		//
		if (m_SelectionBox != NULL) {
			m_SelectionBox->Remove_From_Scene ();
			SAFE_DELETE (m_SelectionBox);
		}
	}

	return ;
}


/////////////////////////////////////////////////////////////////
//
//	Update_Selection_Color
//
/////////////////////////////////////////////////////////////////
void
NodeClass::Update_Selection_Color (void)
{
	m_SelColor = m_IsLocked ? SEL_COLOR_LOCKED : SEL_COLOR_NORMAL;

	//
	// Only perform the bounding box check if we have a valid phys obj pointer
	//
	PhysClass *collision_obj = Peek_Collision_Obj ();
	if (collision_obj != NULL) {
		
		AABoxClass aabox;
		OBBoxClass obbox;
		bool is_aabox = ::Get_Collision_Box (collision_obj->Peek_Model (), aabox, obbox);
		if (is_aabox) {
			Vector3 move (0, 0, 0);

			//
			//	See if the AABox intersects anything
			//
			collision_obj->Inc_Ignore_Counter();
			CastResultStruct res;
			PhysAABoxCollisionTestClass boxtest (aabox, move, &res, GAME_COLLISION_GROUP);
			PhysicsSceneClass::Get_Instance()->Cast_AABox (boxtest);
			collision_obj->Dec_Ignore_Counter();
			
			if (boxtest.Result->StartBad) {
				m_SelColor = SEL_COLOR_INVALID;
			}

		} else {
			Vector3 move (0, 0, 0);

			//
			//	See if the OBBox intersects anything
			//
			collision_obj->Inc_Ignore_Counter();
			CastResultStruct res;
			PhysOBBoxCollisionTestClass boxtest (obbox, move, &res, GAME_COLLISION_GROUP);
			PhysicsSceneClass::Get_Instance()->Cast_OBBox (boxtest);
			collision_obj->Dec_Ignore_Counter();
			
			if (boxtest.Result->StartBad) {
				m_SelColor = SEL_COLOR_INVALID;
			}
		}
	}

	//
	//	Update the selection box if it exists
	//
	if (m_SelectionBox != NULL) {
		m_SelectionBox->Set_Color (m_SelColor);
	}

	return ;
}


/////////////////////////////////////////////////////////////////
//
//	Update_Selection_Box
//
/////////////////////////////////////////////////////////////////
void
NodeClass::Update_Selection_Box (void)
{
	//
	// If we have a selection box, then update its color and position
	//
	if (m_SelectionBox != NULL) {
		Update_Selection_Color ();
		m_SelectionBox->Display_Around_Node (*this);
	}

	return ;	
}


/////////////////////////////////////////////////////////////////
//
//	Lock
//
/////////////////////////////////////////////////////////////////
void
NodeClass::Lock (bool lock)
{
	m_IsLocked = lock;
	Update_Selection_Color ();
	return ;
}


/////////////////////////////////////////////////////////////////
//
//	Is_Locked
//
/////////////////////////////////////////////////////////////////
bool
NodeClass::Is_Locked (void) const
{
	return m_IsLocked;
}


/////////////////////////////////////////////////////////////////
//
//	Translate
//
/////////////////////////////////////////////////////////////////
void
NodeClass::Translate (const Vector3 &vector)
{
	MoverClass::Translate_Node (this, vector);
	return ;
}


/////////////////////////////////////////////////////////////////
//
//	Rotate
//
/////////////////////////////////////////////////////////////////
void
NodeClass::Rotate
(
	const Matrix3D &rotation_matrix,
	const Matrix3D &coord_system
)
{
	MoverClass::Rotate_Node (this, rotation_matrix, coord_system);
	return ;
}


/////////////////////////////////////////////////////////////////
//
//	On_Rotate
//
/////////////////////////////////////////////////////////////////
void
NodeClass::On_Rotate (void)
{
	m_Orientation = ::Build_Quaternion (Get_Transform ());
	return ;
}


/////////////////////////////////////////////////////////////////
//
//	On_Translate
//
/////////////////////////////////////////////////////////////////
void
NodeClass::On_Translate (void)
{
	m_Orientation = ::Build_Quaternion (Get_Transform ());
	return ;
}


/////////////////////////////////////////////////////////////////
//
//	On_Transform
//
/////////////////////////////////////////////////////////////////
void
NodeClass::On_Transform (void)
{
	m_Orientation = ::Build_Quaternion (Get_Transform ());
	return ;
}


/////////////////////////////////////////////////////////////////
//
//	Save
//
/////////////////////////////////////////////////////////////////
bool
NodeClass::Save (ChunkSaveClass &csave)
{
	//
	//	Update the preset-id
	//	
	if (m_Preset != NULL) {
		m_PresetID = m_Preset->Get_ID ();
	}

	//
	//	Make sure we've got the latest cull-link information
	//
	Update_Cached_Cull_Link ();
	Update_Cached_Vis_IDs ();

	//
	//	Save the comments to their own chunk
	//
	WRITE_WWSTRING_CHUNK (csave, CHUNKID_COMMENTS, m_Comments);

	//
	//	Save the variables
	//
	csave.Begin_Chunk (CHUNKID_VARIABLES);
	
		WRITE_MICRO_CHUNK_STRING (csave, VARID_NAME, (LPCTSTR)m_Name);
		WRITE_MICRO_CHUNK (csave, VARID_IS_LOCKED,		m_IsLocked);
		WRITE_MICRO_CHUNK (csave, VARID_ID,					m_ID);
		WRITE_MICRO_CHUNK (csave, VARID_ROT_RESTRICT,	m_RotationRestricted);
		WRITE_MICRO_CHUNK (csave, VARID_TRANSFORM,		m_Transform);
		WRITE_MICRO_CHUNK (csave, VARID_PRESETID,			m_PresetID);
		WRITE_MICRO_CHUNK (csave, VARID_CULLLINK,			m_CullLink);
		WRITE_MICRO_CHUNK (csave, VARID_NEEDS_SAVE,		m_NeedsSave);
		WRITE_MICRO_CHUNK (csave, VARID_IS_PROXIED,		m_IsProxied);
		WRITE_MICRO_CHUNK (csave, VARID_CONTAINER_ID,	m_ContainerPresetID);

		//
		//	Save the node's vis ID (if it has one)
		//
		PhysClass *phys_obj = Peek_Physics_Obj ();
		if (phys_obj != NULL) {
			StaticPhysClass *static_phys_obj = phys_obj->As_StaticPhysClass ();
			if (static_phys_obj != NULL) {
				uint32 vis_id = static_phys_obj->Get_Vis_Object_ID ();
				WRITE_MICRO_CHUNK (csave, VARID_VISID, vis_id);
				vis_id = static_phys_obj->Get_Vis_Sector_ID();
				WRITE_MICRO_CHUNK (csave, VARID_VISSECTORID, vis_id);
			}
		}

		//
		//	For pointer remapping
		//
		NodeClass *this_ptr = this;
		WRITE_MICRO_CHUNK (csave, VARID_THISPTR, this_ptr);

	csave.End_Chunk ();
	return true;
}


/////////////////////////////////////////////////////////////////
//
//	Load
//
/////////////////////////////////////////////////////////////////
bool
NodeClass::Load (ChunkLoadClass &cload)
{
	while (cload.Open_Chunk ()) {		
		switch (cload.Cur_Chunk_ID ()) {

			READ_WWSTRING_CHUNK (cload, CHUNKID_COMMENTS, m_Comments);
			
			case CHUNKID_VARIABLES:
				Load_Variables (cload);
				break;
		}

		cload.Close_Chunk ();
	}

	return true;
}


/////////////////////////////////////////////////////////////////
//
//	Load_Variables
//
/////////////////////////////////////////////////////////////////
bool
NodeClass::Load_Variables (ChunkLoadClass &cload)
{
	PresetClass *old_this_ptr = NULL; 

	while (cload.Open_Micro_Chunk ()) {
		switch (cload.Cur_Micro_Chunk_ID ()) {
			
			READ_MICRO_CHUNK (cload, VARID_IS_LOCKED,				m_IsLocked);
			READ_MICRO_CHUNK (cload, VARID_ID,						m_ID);
			READ_MICRO_CHUNK (cload, VARID_ROT_RESTRICT,			m_RotationRestricted);			
			READ_MICRO_CHUNK (cload, VARID_PRESETID,				m_PresetID);
			READ_MICRO_CHUNK (cload, VARID_THISPTR,				old_this_ptr)
			READ_MICRO_CHUNK (cload, VARID_TRANSFORM,				m_Transform)
			READ_MICRO_CHUNK_WWSTRING (cload, VARID_NAME,		m_Name);
			READ_MICRO_CHUNK_WWSTRING (cload, VARID_COMMENTS,	m_Comments);
			READ_MICRO_CHUNK (cload, VARID_CULLLINK,				m_CullLink);
			READ_MICRO_CHUNK (cload, VARID_NEEDS_SAVE,			m_NeedsSave);
			READ_MICRO_CHUNK (cload, VARID_IS_PROXIED,			m_IsProxied);
			READ_MICRO_CHUNK (cload, VARID_CONTAINER_ID,			m_ContainerPresetID);

			case VARID_VISID:
			{
				uint32 vis_id = 0;
				cload.Read (&vis_id, sizeof (vis_id));

				//
				//	Pass the vis-object-id onto the physics object (if it needs one)
				//
				PhysClass *phys_obj = Peek_Physics_Obj ();
				if (phys_obj != NULL) {
					StaticPhysClass *static_phys_obj = phys_obj->As_StaticPhysClass ();
					if (static_phys_obj != NULL) {
						static_phys_obj->Set_Vis_Object_ID (vis_id);
					}
				}
			}

			case VARID_VISSECTORID:
			{
				uint32 vis_id = 0;
				cload.Read (&vis_id, sizeof (vis_id));

				//
				//	Pass the vis-sector-id onto the physics object (if it needs one)
				//
				PhysClass *phys_obj = Peek_Physics_Obj ();
				if (phys_obj != NULL) {
					StaticPhysClass *static_phys_obj = phys_obj->As_StaticPhysClass ();
					if (static_phys_obj != NULL) {
						static_phys_obj->Set_Vis_Sector_ID (vis_id);
					}
				}
			}
			break;
		}

		cload.Close_Micro_Chunk ();
	}

	//
	//	Handle pointer remapping
	//
	WWASSERT (old_this_ptr != NULL);
	SaveLoadSystemClass::Register_Pointer (old_this_ptr, this);

	//
	//	Get the preset pointer from its ID
	//
	m_Preset = PresetMgrClass::Find_Preset (m_PresetID);
	return true;
}


/////////////////////////////////////////////////////////////////
//
//	Has_Vis_Sectors
//
/////////////////////////////////////////////////////////////////
bool
NodeClass::Has_Vis_Sectors (RenderObjClass *render_obj)
{
	bool retval = false;

	//
	//	Start with the parent render obj if none is supplied.
	//
	if (render_obj == NULL) {
		render_obj = Peek_Render_Obj ();
	}

	if (render_obj != NULL) {

		//
		// Loop through all the render objects sub-objects
		//
		int count = render_obj->Get_Num_Sub_Objects ();
		for (int index = 0; (index < count) && !retval; index ++) {
			
			// Get a pointer to this subobject
			RenderObjClass *sub_object = render_obj->Get_Sub_Object (index);
			if (sub_object != NULL) {
				
				retval |= Has_Vis_Sectors (sub_object);
				MEMBER_RELEASE (sub_object);
			}
		}	

		//
		// Is this render object a mesh?
		//
		if ((render_obj->Class_ID () == RenderObjClass::CLASSID_MESH) &&
			 (render_obj->Get_Collision_Type () & COLLISION_TYPE_VIS))
		{
			retval = true;
		}
	}

	return retval;
}


/////////////////////////////////////////////////////////////////
//
//	Add_Vis_Points
//
/////////////////////////////////////////////////////////////////
void
NodeClass::Add_Vis_Points
(
	VisPointGeneratorClass &	generator,
	RenderObjClass *				render_obj
)
{
	//
	// Let the generator know which node its processing
	//
	generator.Set_Current_Node (this);

	//
	//	Start with the parent render obj if none is supplied.
	//
	if (render_obj == NULL) {
		render_obj = Peek_Render_Obj ();
	}

	//
	// Loop through all the render objects sub-objects
	//
	int count = render_obj->Get_Num_Sub_Objects ();
	for (int index = 0; index < count; index ++) {
		
		// Get a pointer to this subobject
		RenderObjClass *sub_object = render_obj->Get_Sub_Object (index);
		if (sub_object != NULL) {
			
			Add_Vis_Points (generator, sub_object);
			MEMBER_RELEASE (sub_object);
		}
	}	

	//
	// Is this render object a mesh?
	//
	if ((render_obj->Class_ID () == RenderObjClass::CLASSID_MESH) &&
		 (render_obj->Get_Collision_Type () & COLLISION_TYPE_VIS))
	{
		//
		//	Pass this mesh off to the generator
		//
		MeshClass *mesh = static_cast<MeshClass *> (render_obj);
		generator.Submit_Mesh (*mesh);
	}	

	return ;
}


/////////////////////////////////////////////////////////////////
//
//	Reload
//
/////////////////////////////////////////////////////////////////
void
NodeClass::Reload (void)
{
	CLevelEditView::Allow_Repaint (false);

	bool in_scene = m_IsInScene;
	if (in_scene) {
		Remove_From_Scene ();
	}

	//
	//	Reinitialize the node from its preset
	//
	//m_Preset = PresetMgrClass::Find_Preset (m_PresetID);
	Initialize ();

	if (in_scene) {
		Add_To_Scene ();
	}

	CLevelEditView::Allow_Repaint (true);
	return ;
}


/////////////////////////////////////////////////////////////////
//
//	Show_Settings_Dialog
//
/////////////////////////////////////////////////////////////////
bool
NodeClass::Show_Settings_Dialog (void)
{	
	NodeInfoPageClass info_tab (this);
	PositionPageClass	pos_tab (this);

	EditorPropSheetClass prop_sheet;
	prop_sheet.Add_Page (&info_tab);
	prop_sheet.Add_Page (&pos_tab);

	// Show the property sheet
	UINT ret_code = prop_sheet.DoModal ();
	
	// Return true if the user clicked OK
	return (ret_code == IDOK);
}


/////////////////////////////////////////////////////////////////
//
//	operator=
//
/////////////////////////////////////////////////////////////////
const NodeClass &
NodeClass::operator= (const NodeClass &src)
{
	//
	//	Copy the data that makes sense to copy.
	//
	m_Comments				= src.m_Comments;
	m_PresetID				= src.m_PresetID;
	m_IsLocked				= src.m_IsLocked;
	m_RotationRestricted	= src.m_RotationRestricted;
	m_Preset					= src.m_Preset;
	m_Orientation			= src.m_Orientation;
	m_SelColor				= src.m_SelColor;
	m_Transform				= src.m_Transform;

	//
	//	Make sure the node has a unique id and name
	//
	NodeMgrClass::Setup_Node_Identity (*this);

	//
	//	Give the node a chance to create any internal
	// data (game objects, phys objects, etc) it needs.
	//
	Initialize ();
	return *this;
}


/////////////////////////////////////////////////////////////////
//
//	Set_ID
//
/////////////////////////////////////////////////////////////////
void
NodeClass::Set_ID (uint32 id)
{
	m_ID = id;
	NodeMgrClass::Setup_Node_Identity (*this);
	return ;
}


/////////////////////////////////////////////////////////////////
//
//	Get_Model_Name
//
/////////////////////////////////////////////////////////////////
LPCTSTR
NodeClass::Get_Model_Name (void) const
{
	LPCTSTR name = NULL;
	RenderObjClass *render_obj = Peek_Render_Obj ();
	if (render_obj != NULL) {
		name = render_obj->Get_Name ();
	}

	return name;
}
