/*
**	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/SceneEditor.cpp              $*
 *                                                                                             *
 *                       Author:: Patrick Smith                                                *
 *                                                                                             *
 *                     $Modtime:: 3/12/02 3:31p                                               $*
 *                                                                                             *
 *                    $Revision:: 116                                                         $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */


#include "stdafx.h"
#include "sceneeditor.h"
#include "nodemgr.h"
#include "groupmgr.h"
#include "utils.h"
#include "matrix3d.h"
#include "vector.h"
#include "leveleditdoc.h"
#include "filemgr.h"
#include "cameramgr.h"
#include "leveleditview.h"
#include "hittestinfo.h"
#include "collisiongroups.h"
#include "mainfrm.h"
#include "wwaudio.h"
#include "audiblesound.h"
#include "mesh.h"
#include "staticphys.h"
#include "phys3.h"
#include "_assetmgr.h"
#include "editorassetmgr.h"
#include "humanphys.h"
#include "pathfindsectorbuilder.h"
#include "pathfind.h"
#include "node.h"
#include "mover.h"
#include "nodefunction.h"
#include "chunkio.h"
#include "decophys.h"
#include "VisPointNode.h"
#include "editorchunkids.h"
#include "presetmgr.h"
#include "combat.h"
#include "ccamera.h"
#include "visenum.h"
#include "staticaabtreecull.h"
#include "dynamicaabtreecull.h"
#include "soundscene.h"
#include "backgroundmgr.h"
#include "texture.h"
#include "smartgameobj.h"
#include "objectnode.h"
#include "mousemgr.h"
#include "lightnode.h"
#include "light.h"
#include "euler.h"
#include "w3d_file.h"
#include "part_emt.h"
#include "gameobjmanager.h"
#include "selectpresetdialog.h"
#include "actionparams.h"
#include "generatingvisdialog.h"
#include "generatingedgesampledvisdialog.h"
#include "generatinglightvisdialog.h"
#include "visgenprogress.h"
#include "vissectorsampler.h"
#include "vismgr.h"
#include "generatingmanualvisdialog.h"
#include "heightdb.h"
#include "staticanimphys.h"
#include "conversationmgr.h"
#include "building.h"
#include "buildingnode.h"
#include "vistable.h"
#include "rendobj.h"
#include "pscene.h"
#include "leveledit.h"
#include "visrendercontext.h"
#include "mapmgr.h"
#include "editableheightfield.h"
#include "heightfieldeditor.h"
#include "heightfieldmgr.h"
#include "pathmgr.h"


//////////////////////////////////////////////////////////////////////
//
//	SceneEditorClass
//
//////////////////////////////////////////////////////////////////////
SceneEditorClass::SceneEditorClass (void)
	: m_SelectionMgr (NULL),
	  m_bLightsOn (true),
	  m_bAggregateChildrenVisible (true),
	  m_bVisPointsDisplayed (0),
	  m_TotalVisPoints (0),
	  m_ManualVisPointsVisible (true),
	  m_MovingObject (NULL),
	  m_DisplaySoundSpheres (false),
	  m_DisplayLightSpheres (false),
	  m_ShowStaticAnimPhys (true),
	  m_ShowEditorObjects (true),
	  m_BuildingPowerEnabled (true),
	  m_CreateProxies (true),
	  PhysicsSceneClass ()
{
	//
	// Ensure that we are initialized
	//
	Initialize ();

	//
	//	Set some default light settings
	//
	Enable_Sun_Light (true);	
	Set_Ambient_Light (Vector3 (0.3F, 0.3F, 0.3F));
	Set_Sun_Light_Orientation (0.0F, DEG_TO_RADF (50.0F));	
	Update_Lighting ();
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	~SceneEditorClass
//
//////////////////////////////////////////////////////////////////////
SceneEditorClass::~SceneEditorClass (void)
{
	Empty_Local_Clipboard ();
	Cleanup_Resources ();
	Remove_All_Vis_Points ();
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Initialize
//
//////////////////////////////////////////////////////////////////////
void
SceneEditorClass::Initialize (void)
{
	//
	//	Load the lighting mode from the registry
	//
	int prelit_mode = theApp.GetProfileInt ("Config", "PrelitMode", WW3D::PRELIT_MODE_LIGHTMAP_MULTI_PASS);
	WW3D::Set_Prelit_Mode ((WW3D::PrelitModeEnum)prelit_mode);

	//
	//	Initialize some misc variables
	//
	ParticleEmitterClass::Set_Default_Remove_On_Complete (false);
	Cleanup_Resources ();
	m_ManualVisPointsVisible	= true;
	m_BuildingPowerEnabled		= true;

	PathfindClass::Get_Instance ()->Reset_Sectors ();

	//
	// Create the node and selection managers
	//
	m_SelectionMgr	= new SelectionMgrClass;	

	//
	// Reset our undo stack
	//
	m_UndoMgr.Purge_Buffers ();

	//
	// Set-up our collision groups
	//
	Disable_All_Collision_Detections (EDITOR_COLLISION_GROUP);
	Disable_All_Collision_Detections (DEF_COLLISION_GROUP);
	Enable_Collision_Detection (DEF_COLLISION_GROUP, DEF_COLLISION_GROUP);
	Enable_All_Collision_Detections (MOUSE_CLICK_COLLISION_GROUP);
	Enable_All_Collision_Detections (15);
	Disable_All_Collision_Detections (STATIC_OBJ_COLLISION_GROUP);
	Enable_Collision_Detection (STATIC_OBJ_COLLISION_GROUP, 15);
	Disable_Collision_Detection (MOUSE_CLICK_COLLISION_GROUP, 15);

	Enable_All_Collision_Detections (GAME_COLLISION_GROUP);
	Disable_Collision_Detection (GAME_COLLISION_GROUP, EDITOR_COLLISION_GROUP);

	//
	// Refresh the per-level include file list
	//
	STRING_LIST &level_includes =::Get_File_Mgr ()->Get_Include_File_List ();
	level_includes.Delete_All ();

	//
	//	Reset the pathfind data
	//
	PathfindClass::Get_Instance ()->Reset_Sectors ();
	PathfindClass::Get_Instance ()->Reset_Portals ();

	//
	// Reset the list of conversations
	//
	ConversationMgrClass::Reset_Conversations (ConversationMgrClass::CATEGORY_LEVEL, true);

	//
	//	Reset the map
	//
	MapMgrClass::Set_Map_Title (0);
	MapMgrClass::Set_Map_Texture ("");
	MapMgrClass::Set_Map_Center (Vector2 (0.0F, 0.0F));
	MapMgrClass::Set_Map_Scale (Vector2 (0.0F, 0.0F));
	MapMgrClass::Cloud_All_Cells ();

	//
	//	Initialize the heightfield system
	//
	HeightfieldMgrClass::Initialize ();
	HeightfieldEditorClass::Initialize ();
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Cleanup_Resources
//
//////////////////////////////////////////////////////////////////////
void
SceneEditorClass::Cleanup_Resources (void)
{
	//
	//	Free the background sound
	//
	if (WWAudioClass::Get_Instance () != NULL) {
		WWAudioClass::Get_Instance ()->Set_Background_Music (NULL);
	}

	//
	//	Cleanup the different managers
	//	
	m_UndoMgr.Purge_Buffers ();
	SAFE_DELETE (m_SelectionMgr);

	NodeMgrClass::Free_Nodes ();
	NetworkObjectMgrClass::Delete_Pending ();

	// Remove all entries from the group list
	Reset_Global_Groups_List ();

	m_MovingObject = NULL;

	//
	//	Shutdown the heightfield system
	//
	HeightfieldEditorClass::Shutdown ();
	HeightfieldMgrClass::Shutdown ();	
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Create_Node
//
//////////////////////////////////////////////////////////////////////
NodeClass *
SceneEditorClass::Create_Node
(
	PresetClass *	preset,
	Matrix3D *		transform,
	DWORD				node_id,
	bool				add_to_scene
)
{
	::Get_Main_View ()->Allow_Repaint (false);
	CWaitCursor wait_cursor;

	//
	// Create the new node
	//
	NodeClass *node = NodeMgrClass::Create_Node (preset, node_id);
	ASSERT (node != NULL);
	if (node != NULL) {

		//
		//	Find a good starting position for this node in the scene
		//
		if (transform != NULL) {
			node->Set_Transform (*transform);
		} else {
			MoverClass::Position_Node (node);
		}

		//
		//	Add the new node to the scene
		//
		if (add_to_scene) {
			node->Add_To_Scene ();
		}
	
		//
		//	Update the file manager's database
		//	
		Update_File_Mgr (node);

		//
		//	Repartition the static culling systems if we are dropping
		// in a terrain.  
		//
		if (node->Get_Type () == NODE_TYPE_TERRAIN) {
			Re_Partition_Static_Lights ();
			Re_Partition_Audio_System ();
		} else if (node->Get_Type () == NODE_TYPE_WAYPATH) {
			::Get_Mouse_Mgr ()->Set_Mouse_Mode (MouseMgrClass::MODE_WAYPATH_EDIT);
			MMWaypathEditClass *mode = (MMWaypathEditClass *)::Get_Mouse_Mgr ()->Get_Mode_Mgr ();
			if (mode != NULL) {
				mode->Set_Waypath ((WaypathNodeClass *)node);
			}
		}

		//
		//	Create any linked nodes and put them into the scene
		//
		if (preset != NULL) {
			preset->Create_Linked_Nodes (node);
		}

		//
		//	Release our hold on the node
		//
		node->Release_Ref ();
	}

	Set_Modified (true);
	::Get_Main_View ()->Allow_Repaint (true);

	return node;
}


//////////////////////////////////////////////////////////////////////
//
//	Clone_Node
//
//////////////////////////////////////////////////////////////////////
NodeClass *
SceneEditorClass::Clone_Node (NodeClass *node)
{
	NodeClass *new_node = node->Clone ();
	if (new_node != NULL) {

		//
		//	Make sure the new node has a name and ID
		//
		NodeMgrClass::Setup_Node_Identity (*new_node);
		NodeMgrClass::Add_Node (new_node);

		//
		//	Add the new node to the scene
		//
		new_node->Add_To_Scene ();
	
		//
		//	Update the file manager's database
		//	
		Update_File_Mgr (new_node);
	}

	return new_node;
}


//////////////////////////////////////////////////////////////////////
//
//	Reload_Lightmap_Models
//
//////////////////////////////////////////////////////////////////////
void
SceneEditorClass::Reload_Lightmap_Models (void)
{
	//
	//	Save the setting in the registry
	//
	theApp.WriteProfileInt ("Config", "PrelitMode", WW3D::Get_Prelit_Mode ());

	//
	//	Force free the assets...
	//
	WW3DAssetManager::Get_Instance ()->Free_Assets ();

	//
	//	Reload the necessary "tool" assets
	//
	CLevelEditDoc *doc = ::Get_Current_Document ();
	if (doc != NULL) {
		doc->Preload_Human_Data ();
		doc->Preload_Tool_Assets ();
	}

	//
	//	Reload all the nodes
	//
	NodeMgrClass::Reload_Nodes ();

	//
	//	Make sure we don't toss vis
	//
	Validate_Vis ();
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Add_Node
//
//////////////////////////////////////////////////////////////////////
void
SceneEditorClass::Add_Node (NodeClass *node)
{
	NodeMgrClass::Add_Node (node);
	node->Add_To_Scene ();
	Update_File_Mgr (node);
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Delete_Node
//
//////////////////////////////////////////////////////////////////////
bool
SceneEditorClass::Delete_Node (NodeClass *node, bool allow_undo)
{
	// Add this operation to our undo manager
	if (allow_undo) {		
		Begin_Operation (OPERATION_DELETE, node);
	}
	
	//
	// Remove all files that this node was dependent on from the file manager's list
	//
	Update_File_Mgr (node, false);

	//
	// Remove the node from the level
	//
	node->Remove_From_Scene ();
	node->Show_Selection_Box (false);
	node->On_Delete ();
	NodeMgrClass::Remove_Node (node);	

	// Notify the undo manager that this operation is over
	if (allow_undo) {		
		End_Operation ();
	}

	Set_Modified (true);
	return true;
}


//////////////////////////////////////////////////////////////////////
//
//	Delete_Nodes
//
//////////////////////////////////////////////////////////////////////
bool
SceneEditorClass::Delete_Nodes (PresetClass *preset)
{
	bool retval = true;

	//
	//	Build a list of nodes to that are dependent on this preset
	//
	DynamicVectorClass<NodeClass *> node_removal_list;
	for (	NodeClass *node = NodeMgrClass::Get_First ();
			node != NULL;
			node = NodeMgrClass::Get_Next (node))
	{
		if (node->Get_Preset () == preset) {
			node_removal_list.Add (node);
		}
	}

	//
	//	Remove these nodes from the level
	//
	for (int index = 0; index < node_removal_list.Count (); index ++) {
		retval &= Delete_Node (node_removal_list[index], false);
	}

	return retval;
}


//////////////////////////////////////////////////////////////////////
//
//	Update_File_Mgr
//
//////////////////////////////////////////////////////////////////////
void
SceneEditorClass::Update_File_Mgr
(
	NodeClass *	node,
	bool			add_node
)
{
	::Get_File_Mgr ()->Update (node, add_node);
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Clone_Objects
//
//////////////////////////////////////////////////////////////////////
bool
SceneEditorClass::Clone_Objects (void)
{
	m_SelectionMgr->Clone_Group ();

	//
	// Loop through all the objects in the current selection
	// and update the file manager's information based on them
	//
	int count = m_SelectionMgr->Get_Count ();
	for (int index = 0; index < count; index ++) {
		NodeClass *node = m_SelectionMgr->Get_At (index);
		Update_File_Mgr (node);
	}

	Set_Modified ();
	return true;
}


//////////////////////////////////////////////////////////////////////
//
//	Add_Groups_To_List
//
//////////////////////////////////////////////////////////////////////
void
SceneEditorClass::Add_Groups_To_List
(
	NodeClass &	node,
	GROUP_LIST &group_list
)
{	
	/*group_list += node.Get_Groups ();

	// Loop through all the children of this node and
	// add their groups to the list as well
	for (NodeInstanceClass *child = node.Get_Child ();
		  child != NULL;
		  child = child->Get_Sibling ()) {
		Add_Groups_To_List (*child, group_list);
	}*/

	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Build_Group_List
//
//////////////////////////////////////////////////////////////////////
void
SceneEditorClass::Build_Group_List
(
	GROUP_LIST &	group_list,
	NodeClass *		node
)
{	
	if (node != NULL) {
		Add_Groups_To_List (*node, group_list);
	} else {

		//
		// Loop through all the objects in the current selection
		// and add thier groups to this list
		//
		for (int index = 0; index < m_SelectionMgr->Get_Count (); index ++) {
			NodeClass *node = m_SelectionMgr->Get_At (index);
			if (node != NULL) {
				Add_Groups_To_List (*node, group_list);
			}
		}
	}
	
	return ;
}


//////////////////////////////////////////////////////////////////////
//
//	Add_Nodes_To_List
//
//////////////////////////////////////////////////////////////////////
void
SceneEditorClass::Add_Nodes_To_List
(
	NodeClass &node,
	NODE_LIST &node_list
)
{
	//
	// Loop through all the children of this node and
	// add them and their children to the list as well
	//
	/*for (NodeInstanceClass *child = node.Get_Child ();
		  child != NULL;
		  child = child->Get_Sibling ()) {
		
		if (child->Is_Owner_Only () == false) {
			node_list.Add_Unique (child);
		}
		
		Add_Nodes_To_List (*child, node_list);
	}*/
	
	return ;	
}


//////////////////////////////////////////////////////////////////////
//
//	Build_Node_List
//
//////////////////////////////////////////////////////////////////////
Vector3
SceneEditorClass::Build_Node_List (NODE_LIST &node_list)
{	
	//
	//	Add all the nodes from the selection set to the list
	//
	for (int index = 0; index < m_SelectionMgr->Get_Count (); index ++) {
		NodeClass *node = m_SelectionMgr->Get_At (index);
		if (node != NULL) {
			node_list.Add_Unique (node);
		}
	}

	m_SelectionMgr->Set_Dirty ();
	return m_SelectionMgr->Get_Center ();
}


//////////////////////////////////////////////////////////////////////
//
//	Build_Node_List
//
//////////////////////////////////////////////////////////////////////
void
SceneEditorClass::Build_Node_List
(
	NODE_LIST &	node_list,
	NodeClass *	parent_node
)
{	
	//
	// Build a complete list of all the groups in the selection set
	//
	GROUP_LIST group_list;
	Build_Group_List (group_list, parent_node);

	// Loop through all the objects of all the groups in the selection
	// set and add them to the list
	for (int group = 0; group < group_list.Count (); group ++) {
		GroupMgrClass *group_mgr = group_list[group];
		group_mgr->Set_Dirty ();

		for (int index = 0; index < group_mgr->Get_Count (); index ++) {
			NodeClass *node = group_mgr->Get_At (index);
			if (node != NULL) {

				node_list.Add_Unique (node);
				Add_Nodes_To_List (*node, node_list);
			}
		}
	}

	//
	// Don't forget to add the nodes from the selection group as well!
	//
	if (parent_node == NULL) {			
		
		for (int index = 0; index < m_SelectionMgr->Get_Count (); index ++) {
			NodeClass *node = m_SelectionMgr->Get_At (index);
			if (node != NULL) {

				node_list.Add_Unique (node);
				Add_Nodes_To_List (*node, node_list);
			}
		}

	} else {

		/*if (parent_node->Is_Owner_Only () == false) {
			node_list.Add_Unique (pparent_node);
		}
		Add_Nodes_To_List (*pparent_node, node_list);*/
	}

	return ;
}


///////////////////////////////////////////////////////////////
//
//  Delete_Nodes
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Delete_Nodes (void)
{
	NODE_LIST affected_list;

	//
	// Build a list of all the nodes that are affected by this operation
	//
	for (int index = 0; index < m_SelectionMgr->Get_Count (); index ++) {
		NodeClass *node = m_SelectionMgr->Get_At (index);
		if (node != NULL) {
			
			// Add this node to our list of affected nodes
			affected_list.Add_Unique (node);
		}
	}

	// Add this operation to our undo manager
	Begin_Operation (OPERATION_DELETE, &affected_list);

	//
	// Perfrom the actual delete operation
	//
	for (index = 0; index < affected_list.Count (); index ++) {
		Delete_Node (affected_list[index], false);
	}
	
	// Notify the undo manager that this operation is over
	End_Operation ();

	// Reset the contents of the selection.
	// Note:  We create a temporary group here to keep a refcount
	// locked on the selected nodes while we delete the selection group.
	// Remember, freeing the selection group decrements the node's refcounts.
	//GroupMgrClass temp_group = *m_SelectionMgr;
	m_SelectionMgr->Free_List ();
	return ;
}


///////////////////////////////////////////////////////////////
//
//  Set_Selection
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Set_Selection (NodeClass *node)
{
	// Is this item already in the list?
	if (m_SelectionMgr->Is_Item_In_Group (node) == false) {		

		//
		// Make this node the only selected node
		//
		m_SelectionMgr->Reset ();
		m_SelectionMgr->Add_Node (node);
		Update_Toolbars ();
	}
	
	return ;
}


///////////////////////////////////////////////////////////////
//
//  Toggle_Selection
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Toggle_Selection (NodeClass *node)
{
	//
	// If this node isn't already in the selection set, then
	// add it.  However, if its in the selection set, remove it.
	//
	if (m_SelectionMgr->Is_Item_In_Group (node)) {
		m_SelectionMgr->Remove_Node (node);
	} else {
		m_SelectionMgr->Add_Node (node);
	}

	Update_Toolbars ();
	return ;
}


///////////////////////////////////////////////////////////////
//
//  Update_Toolbars
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Update_Toolbars (void)
{
	bool show_ani_toolbar	= false;
	int frame					= 0;
	int max_frames				= 0;

	if (m_SelectionMgr->Get_Count () > 0) {
		
		//
		// Loop through all the objects in the current selection
		// and output their names to the debug window...
		//
		CString selection_set = "Selection set: ";
		int sel_count = min (m_SelectionMgr->Get_Count (), 4);
		for (int index = 0; index < sel_count; index ++) {
			NodeClass *node	= m_SelectionMgr->Get_At (index);
			selection_set		+= node->Get_Name ();
			
			//
			// Print vis-id and occluder status if we're only printing one object
			//
			PhysClass *phys_obj = node->Peek_Physics_Obj ();
			if (m_SelectionMgr->Get_Count () == 1 && phys_obj != NULL) {
				StaticPhysClass *static_phys_obj = phys_obj->As_StaticPhysClass();
				if (static_phys_obj != NULL) {
					selection_set += (" (");
					CString visid_string;
					visid_string.Format(" VisObjectId = %d, ",static_phys_obj->Get_Vis_Object_ID());
					selection_set += visid_string;
					visid_string.Format(" VisSectorId = %d, ",static_phys_obj->Get_Vis_Sector_ID());
					selection_set += visid_string;

					AABTreeLinkClass *link = (AABTreeLinkClass *)static_phys_obj->Get_Cull_Link ();
					if (link != NULL) {
						visid_string.Format(" CullLink = %d, ",link->Node->Index);
						selection_set += visid_string;
					}

					if (!static_phys_obj->Is_Occluder()) {
						selection_set += CString(" Non-Occluder ");
					}
					selection_set += (")");
				} else if (node->Get_Type () == NODE_TYPE_LIGHT) {
					selection_set += (" (");
					CString visid_string;
					visid_string.Format(" VisSectorId = %d, ",((LightNodeClass *)node)->Get_Vis_Sector_ID());
					selection_set += visid_string;
					selection_set += (")");						
				} else {
					selection_set += (" (");
					CString visid_string;
					visid_string.Format(" VisObjectId = %d, ",phys_obj->Get_Vis_Object_ID());
					selection_set += visid_string;
					selection_set += (")");					
				}
			}

			selection_set += ", ";
		}

		if (sel_count < m_SelectionMgr->Get_Count ()) {
			selection_set += "and many more....";
		}

		//
		// Now output this string to the debug window
		//
		selection_set += "\r\n";
		::Output_Message (selection_set);
	}

	// Show/hide the animation toolbar depending on the current state
	CMainFrame *pmainwnd = (CMainFrame *)::AfxGetMainWnd ();
	if (pmainwnd != NULL) {
		pmainwnd->Show_Ani_Toolbar (show_ani_toolbar);
		pmainwnd->Update_Ani_Frame (frame, max_frames);
	}

	return ;		
}

///////////////////////////////////////////////////////////////
//
//  Find_Node_At_Point
//
///////////////////////////////////////////////////////////////
NodeClass *
SceneEditorClass::Find_Node_At_Point (CPoint point, Vector3 *intersect_pt)
{
	//
	//	Calculate the ray from the current camera pos through
	// the mouse cursor.
	//
	Vector3 ray_start;
	Vector3 ray_end;
	MoverClass::Get_Mouse_Ray (point, 250.0F, ray_start, ray_end);

	//
	// Cast the ray into the world
	//
	CastResultStruct res;
	if (intersect_pt != NULL) {
		res.ComputeContactPoint = true;
	}

	PhysClass *physobj = MoverClass::Cast_Ray (res, ray_start, ray_end, MOUSE_CLICK_COLLISION_GROUP);	
	if ((physobj != NULL) && (physobj->Peek_Model() != NULL)) {
		WWDEBUG_SAY(("Picked model: %s\r\n",physobj->Peek_Model()->Get_Name()));
	}

	//
	// Get the node's pointer that was hit from the raycast
	//
	NodeClass *node = NULL;
	if (physobj != NULL) {

		HITTESTINFO *hit_info = (HITTESTINFO *)physobj->Peek_Model ()->Get_User_Data ();
		if (hit_info != NULL && hit_info->Type == HITTESTINFO::Node) {
			node = hit_info->node;

			//
			//	Return the intersection point (if requested)
			//
			if (intersect_pt != NULL) {
				(*intersect_pt) = res.ContactPoint;
			}
		}
	}
	
	// Return a pointer to the node
	return node;
}


///////////////////////////////////////////////////////////////
//
//  Execute_Function_At_Point
//
///////////////////////////////////////////////////////////////
bool
SceneEditorClass::Execute_Function_At_Point (CPoint point)
{
	bool processed = false;

	//
	//	Calculate the ray from the current camera pos through
	// the mouse cursor.
	//
	Vector3 ray_start;
	Vector3 ray_end;
	MoverClass::Get_Mouse_Ray (point, 250.0F, ray_start, ray_end);
	

	//
	// Cast the ray into the world
	//
	CastResultStruct res;
	PhysClass *physobj = MoverClass::Cast_Ray (res, ray_start, ray_end, MOUSE_CLICK_COLLISION_GROUP);

	//
	// Determine if the user clicked on a 'function'
	//
	NodeClass *node = NULL;
	if (physobj != NULL) {
		
		HITTESTINFO *hit_info = (HITTESTINFO *)physobj->Peek_Model ()->Get_User_Data ();
		if ((hit_info != NULL) && hit_info->Type == HITTESTINFO::Function) {

			hit_info->function->On_Click ();
			processed = true;
		}
	}
	
	// Return the true/false result code
	return processed;
}


///////////////////////////////////////////////////////////////
//
//  Move_Selected_Nodes
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Move_Selected_Nodes (void)
{
	float speed_modifier		= ::Get_Camera_Mgr ()->Get_Speed_Modifier ();
	float modifier				= (::GetAsyncKeyState (VK_CONTROL) < 0) ? speed_modifier : 1.0F;	
	Matrix3D coord_system	= ::Get_Camera_Mgr ()->Get_Camera ()->Get_Transform ();		
	bool move_object			= false;
	Vector3 delta (0, 0, 0);

	if (::GetAsyncKeyState (VK_LEFT) < 0) {

		//
		//	Move the objects along the x-axis of the current view
		//
		Vector3 movement (-0.05F * modifier, 0, 0);
		delta += coord_system.Rotate_Vector (movement);
		move_object = true;

	} else if (::GetAsyncKeyState (VK_RIGHT) < 0) {

		//
		//	Move the objects along the x-axis of the current view
		//
		Vector3 movement (0.05F * modifier, 0, 0);
		delta += coord_system.Rotate_Vector (movement);
		move_object = true;
	}

	if (::GetAsyncKeyState (VK_UP) < 0) {

		//
		//	Should we move the object 'up'?
		//		
		if (::GetAsyncKeyState (VK_SHIFT) < 0) {
			delta += Vector3 (0, 0, 0.05F * modifier);
		} else {

			//
			//	Move the object along the 'ground-plane' at an axis orthogonal to the view's x-axis.
			//
			Vector3 y_vector = Vector3::Cross_Product (coord_system.Get_X_Vector (), Vector3 (0, 0, 1));
			y_vector.Normalize ();
			delta += y_vector * (-0.05F * modifier);
		}

		move_object = true;

	} else if (::GetAsyncKeyState (VK_DOWN) < 0) {

		//
		//	Should we move the object 'up'?
		//		
		if (::GetAsyncKeyState (VK_SHIFT) < 0) {
			delta += Vector3 (0, 0, -0.05F * modifier);
		} else {

			//
			//	Move the object along the 'ground-plane' at an axis orthogonal to the view's x-axis.
			//
			Vector3 y_vector = Vector3::Cross_Product (coord_system.Get_X_Vector (), Vector3 (0, 0, 1));
			y_vector.Normalize ();
			delta += y_vector * (0.05F * modifier);			
		}

		move_object = true;
	}

	//
	//	Move the selected nodes if necessary
	//
	if (move_object) {
		MoverClass::Move_Nodes (delta);
	}
	
	return ;
}


///////////////////////////////////////////////////////////////
//
//  On_Frame
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::On_Frame (void)
{
	//
	//	Let the heightfield editor think
	//
	HeightfieldEditorClass::On_Frame_Update ();

	//
	//	Get the currently 'focussed' process
	//
	HWND current_focus_wnd = ::GetFocus ();
	DWORD process_id = 0;
	if (current_focus_wnd != NULL) {
		::GetWindowThreadProcessId (current_focus_wnd, &process_id);
	}

	//
	//	If we are the current process, then move the nodes
	//
	if (process_id == ::GetCurrentProcessId ()) {
		Move_Selected_Nodes ();
	}

	if (m_MovingObject != NULL) {
		PathMgrClass::Resolve_Paths (Vector3 (0, 0, 0));
		PhysicalGameObj *game_obj = ((ObjectNodeClass *)m_MovingObject)->Peek_Game_Obj ();
		((SmartGameObj *)game_obj)->Generate_Control ();
		game_obj->Think ();

		PhysClass *phys_obj = game_obj->Peek_Physical_Object ();
		if (phys_obj != NULL) {
			phys_obj->Timestep (TimeManager::Get_Frame_Seconds ());
		}			

		game_obj->Post_Think ();

#if 0
		ActionClass	*action = ((SmartGameObj *)game_obj)->Get_Action ();
		if (action != NULL && action->Get_Movement () == NULL) {
			//m_MovingObject = NULL;
		}
#endif
	}

	return ;
}


///////////////////////////////////////////////////////////////
//
//  Record_Vis_Info
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Record_Vis_Info (const Matrix3D &view_transform, const Vector3 &sample_location)
{
	// Put up an hour glass
	CWaitCursor wait_cursor;

	PresetClass *preset = PresetMgrClass::Get_First (CLASSID_VIS_POINT_DEF, PresetMgrClass::ID_CLASS);
	if (preset == NULL) {
		CString message = "Missing 'Manual Vis Point' preset.  Please create a default preset under the 'Manual Vis Point' folder in the preset library.";
		::MessageBox (::AfxGetMainWnd ()->m_hWnd, message, "Missing Preset", MB_OK | MB_ICONEXCLAMATION);
	} else {

		//
		// When doing a manual vis, use the FOV and clip settings of the in-game camera
		//
		CameraClass *scene_cam = CombatManager::Get_Camera ();
		WWASSERT (scene_cam != NULL);
		
		float scene_znear = 0;
		float scene_zfar = 0;
		scene_cam->Get_Clip_Planes (scene_znear, scene_zfar);
		scene_cam->Set_Clip_Planes (scene_znear, VIS_FAR_CLIP);

		//
		// Generate the 'vis' information
		//
		VisSampleClass vis_sample = Update_Vis (view_transform * Vector3 (0, 0, -scene_znear), view_transform, (VisDirBitsType)(VIS_FORWARD_BIT | VIS_DONT_RECENTER), scene_cam);
		if (vis_sample.Get_Bits_Changed () > 0 && vis_sample.Sample_Rejected () == false) {
			
			//
			//	Convert the matrix to an obj-space transform
			//
			Matrix3D cam_to_world (Vector3 (0, 0, -1), Vector3 (-1, 0, 0), Vector3 (0, 1, 0), Vector3 (0, 0, 0));
			Matrix3D obj_tm = view_transform * cam_to_world;

			//
			//	Create the UI
			//
			Create_Vis_Point (view_transform);	
			VisPointNodeClass *vis_point = new VisPointNodeClass (preset);
			vis_point->Set_ID (::Get_Node_Mgr ().Get_Node_ID (vis_point->Get_Type ()));
			vis_point->Initialize ();
			vis_point->Add_To_Scene ();
			vis_point->Set_Transform (obj_tm);
			vis_point->Set_Vis_Tile_Location (sample_location);
			vis_point->Save_Camera_Settings (*scene_cam);
			vis_point->Hide (!m_ManualVisPointsVisible);
			::Get_Node_Mgr ().Add_Node (vis_point);
			vis_point->Release_Ref ();
			Set_Modified (true);
		}

		// 
		// Put the normal settings back into the scene camera
		//
		scene_cam->Set_Clip_Planes (scene_znear, scene_zfar);				
	}

	return ;
}


///////////////////////////////////////////////////////////////
//
//  Begin_Operation
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Begin_Operation
(
	OPERATION_TYPE		type,
	NodeClass *			node
)
{
	if (node != NULL) {
		
		// Build a list containing the node
		NODE_LIST node_list;
		node_list.Add_Unique (node);

		// Pass this list onto the undo manager
		Begin_Operation (type, &node_list);
	}

	return ;
}


///////////////////////////////////////////////////////////////
//
//  Begin_Operation
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Begin_Operation
(
	OPERATION_TYPE	type,
	NODE_LIST *		affected_list
)
{
	NODE_LIST node_list;

	//
	// If necessary, build a complete list of all the nodes that will be
	// affected by this operation
	//
	if (affected_list == NULL) {
		Build_Node_List (node_list, NULL);
		affected_list = &node_list;
	}

	m_UndoMgr.Begin_Operation (type, *affected_list);
	return ;
}


///////////////////////////////////////////////////////////////
//
//  End_Operation
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::End_Operation (void)
{
	m_UndoMgr.End_Operation ();
	return ;
}


///////////////////////////////////////////////////////////////
//
//  Undo
//
///////////////////////////////////////////////////////////////
bool
SceneEditorClass::Undo (void)
{
	m_UndoMgr.Perform_Undo ();
	return true;
}


///////////////////////////////////////////////////////////////
//
//  Cut_Objects
//
///////////////////////////////////////////////////////////////
bool
SceneEditorClass::Cut_Objects (void)
{
	bool retval = false;

	// Turn painting off
	CLevelEditView::Allow_Repaint (false);

	// Copy the objects to the clipboard, then delete them
	if (Copy_Objects ()) {
		Delete_Nodes ();
	}

	// Turn painting back on
	CLevelEditView::Allow_Repaint (true);

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


///////////////////////////////////////////////////////////////
//
//  Copy_Objects
//
///////////////////////////////////////////////////////////////
bool
SceneEditorClass::Copy_Objects (void)
{	
	Empty_Local_Clipboard ();
	
	// Loop through all the nodes in the current selection
	// and 'copy' them.
	int count = m_SelectionMgr->Get_Count ();
	for (int index = 0; index < count; index ++) {
		NodeClass *node = m_SelectionMgr->Get_At (index);

		//
		//	Copy the node and add it to our 'clipboard'
		//
		NodeClass *node_copy = node->Clone ();
		ASSERT (node_copy != NULL);
		if (node_copy != NULL) {
			node_copy->Remove_From_Scene ();
			m_LocalClipboard.Add (node_copy);
		}
	}		
	
	return true;
}


///////////////////////////////////////////////////////////////
//
//  Paste_Objects
//
///////////////////////////////////////////////////////////////
bool
SceneEditorClass::Paste_Objects (void)
{
	bool retval = (m_LocalClipboard.Count () > 0);

	// Turn painting off
	CLevelEditView::Allow_Repaint (false);

	// Reset the selection group
	m_SelectionMgr->Reset ();
	for (int index = 0; index < m_LocalClipboard.Count (); index ++) {
		
		//
		//	Copy the node on the clipboard and add it to the scene
		//
		NodeClass *node		= m_LocalClipboard[index];
		NodeClass *node_copy	= Clone_Node (node);
		if (node_copy != NULL) {
			m_SelectionMgr->Add_Node (node_copy);
			MEMBER_RELEASE (node_copy);
		}
	}
	
	//
	//	Cast the nodes along the line-of-sight ray for 500 meters
	//
	Vector3 ray_start;
	Vector3 ray_end;
	MoverClass::Get_LOS_Ray (500.0F, ray_start, ray_end);
	MoverClass::Position_Nodes_Along_Ray (ray_start, ray_end);

	// Turn painting back on
	CLevelEditView::Allow_Repaint (true);
	Update_Toolbars ();
	
	// Return the true/false result code
	return retval;
}


///////////////////////////////////////////////////////////////
//
//  Can_Paste
//
///////////////////////////////////////////////////////////////
bool
SceneEditorClass::Can_Paste (void)
{
	// Return the true/false result code
	return bool(m_LocalClipboard.Count () > 0);
}


///////////////////////////////////////////////////////////////
//
//  Empty_Local_Clipboard
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Empty_Local_Clipboard (void)
{
	// Loop through all the entries in the clipboard and delete them
	for (int index = 0; index < m_LocalClipboard.Count (); index ++) {
		MEMBER_RELEASE (m_LocalClipboard[index]);
	}

	// Reset the list
	m_LocalClipboard.Delete_All ();
	return ;
}


///////////////////////////////////////////////////////////////
//
//  Add_Global_Group
//
///////////////////////////////////////////////////////////////
GroupMgrClass *
SceneEditorClass::Add_Global_Group
(
	const CString &	name,
	NODE_LIST *			initial_list
)
{
	// Create a new group with the speicfied name
	UserGroupMgrClass *group = new UserGroupMgrClass;
	group->Set_Name (name);

	// If we have a list of nodes to add to this group,
	// then do so now.
	if (initial_list != NULL) {
		for (int index = 0; index < initial_list->Count (); index ++) {
			group->Add_Node ((*initial_list)[index]);
		}
	}

	// Add this new group manager to our global list
	m_GroupsList.Add_Unique (group);

	// Return a pointer to the new group
	return group;
}


///////////////////////////////////////////////////////////////
//
//  Remove_Global_Group
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Remove_Global_Group (GroupMgrClass *group)
{
	// If this group is in our global list, then remove
	// it from the list, remove it from any UI, and free its memory.
	if (m_GroupsList.Is_Item_In_List (group)) {
		m_GroupsList.Remove (group);
		Remove_Group_From_Toolbar (group);
		SAFE_DELETE (group);
	}

	return ;
}


///////////////////////////////////////////////////////////////
//
//  Reset_Global_Groups_List
//
///////////////////////////////////////////////////////////////
void
SceneEditorClass::Reset_Global_Groups_List (void)
{
	// Loop through all the groups in our list and
	// free their associated memory.
	for (int index = 0; index < m_GroupsList.Count (); index ++) {		
		GroupMgrClass *group = m_GroupsList[index];
		if (group != NULL) {
			Remove_Group_From_Toolbar (group);
			SAFE_DELETE (group);
		}
	}

	// Remove all the entries from the list
	m_GroupsList.Delete_All ();
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Add_Group_To_Toolbar
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Add_Group_To_Toolbar (GroupMgrClass *group)
{
	ASSERT (group != NULL);
			
	// Get the form representing the groups list, and add this group to it
	/*GroupsPageClass *group_page = ::Get_Groups_Form ();
	if (group_page != NULL) {
		group_page->Add_Group (group);
	}*/

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Remove_Group_From_Toolbar
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Remove_Group_From_Toolbar (GroupMgrClass *group)
{
	ASSERT (group != NULL);
			
	// Get the form representing the groups list, and remove this group from it
	/*GroupsPageClass *group_page = ::Get_Groups_Form ();
	if (group_page != NULL) {
		group_page->Remove_Group (group);
	}*/

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Turn_Lights_On
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Turn_Lights_On (bool onoff)
{
	m_bLightsOn = onoff;

	//
	// Loop through all the lights in the world
	//
	/*LightInstanceClass *plight = NULL;
	for (plight = (LightInstanceClass *)::Get_Node_Mgr ().Get_First (NODE_TYPE_LIGHT);
		  plight != NULL;
		  plight = (LightInstanceClass *)::Get_Node_Mgr ().Get_Next (NODE_TYPE_LIGHT)) {		
		plight->Turn_Light_On (onoff);
	}*/
		
	::Refresh_Main_View ();
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Lock_Nodes
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Lock_Nodes (bool lock)
{
	//
	// Loop through all the nodes in the current selection
	//
	for (int index = 0; index < m_SelectionMgr->Get_Count (); index ++) {
		NodeClass *node = m_SelectionMgr->Get_At (index);
		if (node != NULL) {
			
			//
			// Lock the node, this keep the node from moving during
			// drag operations.
			//
			node->Lock (lock);
		}
	}
	
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Set_Background_Music
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Set_Background_Music (LPCTSTR filename)
{
	//
	//	Stop the old sound
	//
	WWAudioClass::Get_Instance ()->Set_Background_Music (NULL);

	//
	// Remove the sound file from the file manager
	//
	if (m_BackgroundSoundFilename.GetLength () > 0) {
		
		CString old_path = ::Get_File_Mgr ()->Make_Full_Path (m_BackgroundSoundFilename);
		::Get_File_Mgr ()->Remove_File (::Get_File_Mgr ()->Find_File (old_path));
		m_BackgroundSoundFilename.Empty ();
	}

	//
	// Set the new background music
	//
	if ((filename != NULL) && (filename[0] != 0)) {
		CString path = ::Get_File_Mgr ()->Make_Full_Path (filename);
		if (::Get_File_Mgr ()->Does_File_Exist (path, true)) {
			
			//
			//	Set the background music
			//
			::Set_Current_Directory (::Strip_Filename_From_Path (path));
			WWAudioClass::Get_Instance ()->Set_Background_Music (::Get_Filename_From_Path (path));

			//
			// Add this sound to the file manager
			//
			::Get_File_Mgr ()->Add_File (path);
			m_BackgroundSoundFilename = path;
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	View_Aggregate_Children
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::View_Aggregate_Children (bool view)
{
	// Did the state change?
	/*if (view != m_bAggregateChildrenVisible) {
		m_bAggregateChildrenVisible = view;

		// Make sure to reset the selection for this to work
		m_SelectionMgr->Reset ();

		// Update all the aggregates in the level
		::Get_Node_Mgr ().Update_Aggregate_Instances ();
	}*/

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Display_Vis_Points
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Display_Vis_Points (bool onoff)
{
	// Did the state change?
	if (onoff != m_bVisPointsDisplayed) {
		m_bVisPointsDisplayed = onoff;

		//
		//	Show/hide all the vis points
		//
		for (int index = 0; index < m_VisPoints.Count (); index ++) {
			DecorationPhysClass *vis_point = m_VisPoints[index];
			if (vis_point != NULL) {
				if (onoff) {
					Add_Dynamic_Object (vis_point);
				} else {
					Remove_Object (vis_point);
				}
			}
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Create_Vis_Point
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Create_Vis_Point (const Matrix3D &transform)
{	
	//
	//	Create a new vis-point marker at the location
	//
	DecorationPhysClass *new_point = new DecorationPhysClass;
	new_point->Set_Model_By_Name ("WAYSTART");
	new_point->Set_Transform (transform);
	new_point->Set_Collision_Group (0);

	//
	//	Add this new point to our list
	//
	m_VisPoints.Add (new_point);
	if (m_bVisPointsDisplayed) {
		Add_Dynamic_Object (new_point);
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Remove_All_Vis_Points
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Remove_All_Vis_Points (void)
{
	::Get_Main_View ()->Allow_Repaint (false);

	CWaitCursor wait_cursor;

	//
	//	Build a list of all the vis points in the world
	//
	for (int index = 0; index < m_VisPoints.Count (); index ++) {
		DecorationPhysClass *vis_point = m_VisPoints[index];
		if (vis_point != NULL) {
			Remove_Object (vis_point);
			vis_point->Release_Ref ();
		}
	}

	m_VisPoints.Delete_All ();

	::Get_Main_View ()->Allow_Repaint (true);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Discard_Vis
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Discard_Vis (void)
{
	m_VisLog.Reset_Log ();
	Remove_All_Vis_Points ();
	PhysicsSceneClass::Internal_Vis_Reset ();
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Reset_Vis
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Reset_Vis (bool doitnow)
{
	m_VisLog.Reset_Log ();
	Remove_All_Vis_Points ();
	PhysicsSceneClass::Reset_Vis (doitnow);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Internal_Vis_Reset
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Internal_Vis_Reset (void)
{
	if (VisResetNeeded) {
		PhysicsSceneClass::Internal_Vis_Reset ();

		//
		//	Ask all the nodes 
		//
		NodeClass *node = NULL;
		for (	node = NodeMgrClass::Get_First ();
				node != NULL;
				node = NodeMgrClass::Get_Next (node))
		{
			node->Update_Cached_Vis_IDs ();
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Get_Vis_Camera_FOV
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Get_Vis_Camera_FOV (double &hfov, double &vfov)
{
	CameraClass *vis_camera = Get_Vis_Camera ();
	WWASSERT (vis_camera != NULL);
	if (vis_camera != NULL) {
		hfov = vis_camera->Get_Horizontal_FOV ();
		vfov = vis_camera->Get_Vertical_FOV ();		
		MEMBER_RELEASE (vis_camera);
	} else {		
		hfov = 0;
		vfov = 0;
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Get_Vis_Camera_Clip_Planes
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Get_Vis_Camera_Clip_Planes (float &znear, float &zfar)
{
	CameraClass *vis_camera = Get_Vis_Camera ();
	WWASSERT (vis_camera != NULL);
	if (vis_camera != NULL) {
		vis_camera->Get_Clip_Planes (znear, zfar);
		MEMBER_RELEASE (vis_camera);
	} else {
		znear = 0;
		zfar = 0;
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Get_Render_Camera
//
////////////////////////////////////////////////////////////////
CameraClass *
SceneEditorClass::Get_Render_Camera (void)
{
	CameraClass *camera = NULL;

	if (::Get_Camera_Mgr ()->Get_Camera_Mode () == CameraMgr::MODE_WALK_THROUGH) {
		camera = CombatManager::Get_Camera ();
	} else {
		camera = ::Get_Camera_Mgr ()->Get_Camera ();
	}

	return camera;
}


////////////////////////////////////////////////////////////////
//
//	Generate_Pathfind_Portals
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Generate_Pathfind_Portals (void)
{
	CWaitCursor wait_cursor;

	//
	//	Create a pathfind floodfiller object
	//
	PathfindSectorBuilderClass builder;
	builder.Allow_Water_Floodfill (true);
	builder.Initialize ();	

	//
	//	Find all the pathfind start-points in the level and add
	// them to the floodfiller
	//
	NodeClass *node = NULL;
	for (	node = NodeMgrClass::Get_First ();
			node != NULL;
			node = NodeMgrClass::Get_Next (node))
	{
		if (node->Get_Type () == NODE_TYPE_PATHFIND_START) {
			builder.Add_Start_Point (node->Get_Transform ().Get_Translation ());
		}
	}
	
	//
	//	Perform the floodfill from each of the start locations
	//
	builder.Generate_Sectors ();
	builder.Compress_Sectors_For_Pathfind ();

	//
	//	Build height data for flying vehicles
	//
	HeightDBClass::Generate ();

	//
	//	Cleanup
	//
	builder.Shutdown ();
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Export_VIS
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Export_VIS (LPCTSTR filename)
{
	//
	//	Create the file
	//
	HANDLE hfile = ::CreateFile (filename,
										  GENERIC_WRITE,
										  0,
										  NULL,
										  CREATE_ALWAYS,
										  0L,
										  NULL);

	ASSERT (hfile != INVALID_HANDLE_VALUE);
	if (hfile != INVALID_HANDLE_VALUE) {

		RawFileClass file_obj;
		file_obj.Attach (hfile);
		ChunkSaveClass chunk_save (&file_obj);

		//
		//	Export the data to this file
		//
		Export_Vis_Data (chunk_save);
	}
	
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Update_Selection_Boxes
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Update_Selection_Boxes (void)
{
	//
	// Loop through all the nodes in the current selection
	//
	for (int index = 0; index < m_SelectionMgr->Get_Count (); index ++) {
		NodeClass *node = m_SelectionMgr->Get_At (index);
		if (node != NULL) {
			
			//
			// Ask the node to update its selection box (color and pos)
			//
			node->Update_Selection_Box ();
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Re_Partition_Audio_System
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Re_Partition_Audio_System (void)
{
	const AABoxClass & level_bounds = StaticCullingSystem->Get_Bounding_Box();
	Vector3 min,max;
	min = level_bounds.Center - level_bounds.Extent;
	max = level_bounds.Center + level_bounds.Extent;

	SoundSceneClass *sound_scene = WWAudioClass::Get_Instance ()->Get_Sound_Scene ();
	if (sound_scene != NULL) {
		sound_scene->Re_Partition (min, max);
	}
	
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Are_Manual_Vis_Points_Visible
//
////////////////////////////////////////////////////////////////
bool
SceneEditorClass::Are_Manual_Vis_Points_Visible (void)
{	
	return m_ManualVisPointsVisible;
}


////////////////////////////////////////////////////////////////
//
//	Show_Manual_Vis_Points
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Show_Manual_Vis_Points (bool show_points)
{
	if (m_ManualVisPointsVisible != show_points) {
		m_ManualVisPointsVisible = show_points;

		NodeClass *node = NULL;
		for (	node = NodeMgrClass::Get_First ();
				node != NULL;
				node = NodeMgrClass::Get_Next (node))
		{
			if (node->Get_Type () == NODE_TYPE_VIS_POINT) {
				node->Hide (!m_ManualVisPointsVisible);
			}
		}
	}
		
	return ;
}


////////////////////////////////////////////////////////////////
//
//	DoObjectGoto
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::DoObjectGoto (NodeClass *node1, NodeClass *node2)
{
	SANITY_CHECK(node1 != NULL && node2 != NULL && node1->Get_Type () == NODE_TYPE_OBJECT) {
		return ;
	}

	//
	//	Register all the waypaths with the pathfinding system
	//
	for (	NodeClass *node = NodeMgrClass::Get_First (NODE_TYPE_WAYPATH);
			node != NULL;
			node = NodeMgrClass::Get_Next (node, NODE_TYPE_WAYPATH))
	{
		node->Pre_Export ();
	}

	//
	//	Force the pathfind system to use any available waypath data
	// in its evaluation
	//
	PathfindClass::Get_Instance ()->Generate_Waypath_Sectors_And_Portals ();

	m_MovingObject					= node1;
	PhysicalGameObj *game_obj	= ((ObjectNodeClass *)m_MovingObject)->Peek_Game_Obj ();

	if (game_obj != NULL && game_obj->As_SmartGameObj () != NULL) {
		ActionClass	*action = ((SmartGameObj *)game_obj)->Get_Action ();
		
		ActionParamsStruct parameters;
		parameters.MoveLocation				= node2->Get_Transform ().Get_Translation ();
		parameters.MoveArrivedDistance	= 3.0F;
		action->Goto( parameters );
	}

	//
	//	Unregister all the waypaths with the pathfinding system
	//
	for (	node = NodeMgrClass::Get_First (NODE_TYPE_WAYPATH);
			node != NULL;
			node = NodeMgrClass::Get_Next (node, NODE_TYPE_WAYPATH))
	{
		node->Post_Export ();
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Export_Lights
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Export_Lights (LPCTSTR filename)
{
	FileClass *file = _TheFileFactory->Get_File (filename);
	if (file != NULL && file->Create () && file->Open (FileClass::WRITE)) {

		//
		//	Write the first chunk header
		//
		ChunkSaveClass csave (file);
		csave.Begin_Chunk (W3D_CHUNK_LIGHTSCAPE);
		
			//
			//	Loop over all the lights in the scene
			//
			for (	NodeClass *node = NodeMgrClass::Get_First (NODE_TYPE_LIGHT);
					node != NULL;
					node = NodeMgrClass::Get_Next (node, NODE_TYPE_LIGHT))
			{
				LightNodeClass *light_node = (LightNodeClass *)node;

				//
				//	Get the actual light from the editor object
				//
				LightClass *light = light_node->Peek_Light ();
				if (light != NULL) {
					
					//
					//	Save this light
					//
					csave.Begin_Chunk (W3D_CHUNK_LIGHTSCAPE_LIGHT);
						light->Save_W3D (csave);
					
						//
						//	Save the light's transform as well
						//
						csave.Begin_Chunk (W3D_CHUNK_LIGHT_TRANSFORM);

							Matrix3D tm = light->Get_Transform ();
							W3dLightTransformStruct w3d_tm ={	tm[0][0], tm[0][1], tm[0][2], tm[0][3],
																		tm[1][0], tm[1][1], tm[1][2], tm[1][3],
																		tm[2][0], tm[2][1], tm[2][2], tm[2][3] };
							csave.Write (&w3d_tm, sizeof (w3d_tm));

						csave.End_Chunk ();
					csave.End_Chunk ();
				}
			}

		csave.End_Chunk ();

		//
		//	Close the file
		//
		file->Close ();		
	}

	_TheFileFactory->Return_File (file);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Import_Lights
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Import_Lights
(
	DynamicVectorClass<StringClass> &		filename_list,
	DynamicVectorClass<LightNodeClass *> *	node_list
)
{
	DynamicVectorClass<LightClass *> light_list;
	::Get_Main_View ()->Allow_Repaint (false);

	//
	//	Build a list of light objects from the LIG files
	//
	for (int index = 0; index < filename_list.Count (); index ++) {		
		FileClass *file = _TheFileFactory->Get_File (filename_list[index]);
		if (file != NULL && file->Open ()) {

			//
			//	Read the lights from this file
			//
			ChunkLoadClass cload (file);
			int group_id = 0;

			while (cload.Open_Chunk ()) {
				switch (cload.Cur_Chunk_ID ()) {
					
					case W3D_CHUNK_LIGHTSCAPE:
						Build_Light_List (cload, light_list, group_id ++);
						break;

					default:
						ASSERT (0);
						TRACE ("Unrecognized chunk id found while importing lights!\n");
						break;
				}

				cload.Close_Chunk ();
			}

			file->Close ();			
		}

		//
		//	Delete the file object
		//
		if (file != NULL) {
			_TheFileFactory->Return_File (file);
		}
	}

	//
	//	Make sure we don't get duplicate lights
	//
	Filter_Lights (light_list);

	//
	//	Get the preset we will use to derive each imported light-node from
	//
	PresetClass *preset = PresetMgrClass::Find_Preset ("Lightscape Imported");
	ASSERT (preset != NULL);

	CString base_name = ::Asset_Name_From_Filename (filename_list[0]);

	//
	//	Add the lights to the level
	//
	for (index = 0; index < light_list.Count (); index ++) {
		
		LightClass *light = light_list[index];
		if (light != NULL) {
			
			//
			//	Create an editor 'node' for this light and configure it from the
			// light render-object.
			//
			if (light->Get_Type () != LightClass::DIRECTIONAL) {
				Matrix3D tm						= light->Get_Transform ();
				LightNodeClass *light_node	= (LightNodeClass *)Create_Node (preset, &tm);
				light_node->Initialize_From_Light (light);				
				light_node->Set_Group_ID ((int)light->Get_User_Data ());
				
				CString light_name;
				light_name.Format ("%s%04d", (LPCTSTR)base_name, index + 1);
				light_node->Peek_Light_Phys ()->Set_Name (light_name);

				//
				//	Add this light to the list the caller supplied us with (if necessary)
				//
				if (node_list != NULL) {
					node_list->Add (light_node);
					light_node->Add_Ref ();
				}
			}			

			//
			//	Free our hold on the light
			//
			MEMBER_RELEASE (light);
		}
	}

	::Get_Main_View ()->Allow_Repaint (true);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Build_Light_List
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Build_Light_List
(
	ChunkLoadClass &							cload,
	DynamicVectorClass<LightClass *> &	light_list,
	int											group_id
)
{
	while (cload.Open_Chunk ()) {
		switch (cload.Cur_Chunk_ID ()) {

			case W3D_CHUNK_LIGHTSCAPE_LIGHT:
			{
				//
				//	Load the light and add it to the list
				//
				cload.Open_Chunk ();
				LightClass *light = new LightClass;
				light->Load_W3D (cload);
				light->Set_User_Data ((void *)group_id);
				light_list.Add (light);
				cload.Close_Chunk ();

				//
				//	Read the light's transform from the file
				//
				cload.Open_Chunk ();
				ASSERT (cload.Cur_Chunk_ID () == W3D_CHUNK_LIGHT_TRANSFORM);
				if (cload.Cur_Chunk_ID () == W3D_CHUNK_LIGHT_TRANSFORM) {
					
					//
					//	Read the transform
					//
					W3dLightTransformStruct w3d_tm = { 0 };
					cload.Read (&w3d_tm, sizeof (w3d_tm));
					
					//
					//	Convert the persistent-safe transform to a
					// render-obj friendly transform;
					//
					Matrix3D tm (	w3d_tm.Transform [0][0], w3d_tm.Transform [0][1], w3d_tm.Transform [0][2], w3d_tm.Transform [0][3],
										w3d_tm.Transform [1][0], w3d_tm.Transform [1][1], w3d_tm.Transform [1][2], w3d_tm.Transform [1][3],
										w3d_tm.Transform [2][0], w3d_tm.Transform [2][1], w3d_tm.Transform [2][2], w3d_tm.Transform [2][3]);
					light->Set_Transform (tm);
				}
				cload.Close_Chunk ();
			}
			break;
			
			default:
				::Output_Message ("Found invalid chunk in light importer.\r\n");
				break;
		}

		cload.Close_Chunk ();
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Filter_Lights
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Filter_Lights (DynamicVectorClass<LightClass *> &light_list)
{
	//
	//	Loop over every light
	//
	for (int index1 = 0; index1 < light_list.Count (); index1 ++) {
		LightClass *light1	= light_list[index1];
		bool is_unique			= true;
		
		//
		//	Compare this light against every other light
		//
		for (int index2 = 0; index2 < light_list.Count () && is_unique; index2 ++) {
			
			//
			//	Don't check against itself
			//
			if (index2 != index1) {
				LightClass *light2 = light_list[index2];
				is_unique = (Compare_Lights (light1, light2) == false);
			}
		}

		//
		//	Remove this light from the list if it is not unique
		//
		if (is_unique == false) {
			MEMBER_RELEASE (light1);
			light_list.Delete (index1);
			index1 --;
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Display_Sound_Spheres
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Display_Sound_Spheres (bool onoff)
{
	if (m_DisplaySoundSpheres != onoff) {
		m_DisplaySoundSpheres = onoff;

		//
		//	Loop over all the sounds and tell them to turn the spheres
		// on or off.
		//
		for (	NodeClass *node = NodeMgrClass::Get_First (NODE_TYPE_SOUND);
				node != NULL;
				node = NodeMgrClass::Get_Next (node, NODE_TYPE_SOUND))
		{
			node->Show_Attenuation_Spheres (m_DisplaySoundSpheres);
		}
	}

	return ;
}



////////////////////////////////////////////////////////////////
//
//	Display_Light_Spheres
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Display_Light_Spheres (bool onoff)
{
	if (m_DisplayLightSpheres != onoff) {
		m_DisplayLightSpheres = onoff;

		//
		//	Loop over all the lights and tell them to turn the spheres
		// on or off.
		//
		for (	NodeClass *node = NodeMgrClass::Get_First (NODE_TYPE_LIGHT);
				node != NULL;
				node = NodeMgrClass::Get_Next (node, NODE_TYPE_LIGHT))
		{
			node->Show_Attenuation_Spheres (m_DisplayLightSpheres);
		}
	}
		
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Import_Sunlight
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Import_Sunlight (LPCTSTR filename)
{
	DynamicVectorClass<LightClass *> light_list;
	
	//
	//	Open the file
	//
	FileClass *file = _TheFileFactory->Get_File (filename);
	if (file != NULL && file->Open ()) {

		//
		//	Read the lights from this file
		//
		ChunkLoadClass cload (file);
		cload.Open_Chunk ();
			ASSERT (cload.Cur_Chunk_ID () == W3D_CHUNK_LIGHTSCAPE);
			Build_Light_List (cload, light_list);
		cload.Close_Chunk ();

		file->Close ();
		_TheFileFactory->Return_File (file);
	}

	//
	//	Try to find all the directional lights
	//
	LightClass *sunlight		= NULL;
	bool more_than_one		= false;
	for (int index = 0; index < light_list.Count () && !more_than_one; index ++) {
		LightClass *light = light_list[index];
		if (light != NULL) {
			
			//
			//	We assume that every directional light is the 'sunlight'.
			//
			if (light->Get_Type () == LightClass::DIRECTIONAL) {

				if (sunlight == NULL) {
					sunlight = light;
				} else {
					
					//
					//	Check to make sure all directional lights are the same
					//
					if (Compare_Lights (sunlight, light) == false) {
						more_than_one = true;
					}

					MEMBER_RELEASE (light);
				}
			}
		}
	}

	bool apply_changes = (sunlight != NULL);
	if (more_than_one) {

		//
		//	Ask the user if they wish to make these changes
		//
		if (::MessageBox (	::AfxGetMainWnd ()->m_hWnd,
									"There are multiple sunlight definitions in this file which have different settings.  Do you wish to continue?",
									"Ambiguous Lights",
									MB_ICONQUESTION | MB_YESNO) == IDNO)
		{
			apply_changes = false;
		}
	}

	//
	//	Reconfigure the sunlight (if necessary)
	//
	if (apply_changes) {
		LightClass *global_sunlight_obj = Get_Sun_Light ();
		Vector3 diffuse;
		sunlight->Get_Diffuse (&diffuse);
		global_sunlight_obj->Set_Diffuse (diffuse);
		global_sunlight_obj->Set_Intensity (sunlight->Get_Intensity ());
		global_sunlight_obj->Set_Transform (sunlight->Get_Transform ());
		MEMBER_RELEASE (global_sunlight_obj);

		//
		//	Convert the sun's 3x3 roational matrix to a
		// 'rotation' and 'elevation'.
		//
		Matrix3D tm = sunlight->Get_Transform ();
		
		Vector3 test_vector (0, 0, -1);
		test_vector = tm.Rotate_Vector (test_vector);
		test_vector.Normalize ();

		float rotation		= ::atan2 (test_vector.Y, test_vector.X);
		float elevation	= ::asin (test_vector.Z);
		
		Set_Sun_Light_Orientation (rotation, elevation);
		Update_Lighting ();
	}
	
	MEMBER_RELEASE (sunlight);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Compare_Lights
//
////////////////////////////////////////////////////////////////
bool
SceneEditorClass::Compare_Lights (LightClass *light1, LightClass *light2)
{
	bool retval = false;

	//
	//	Are the positions and orientations approximately the same?
	//	Are they both the same type of light?
	//
	const Matrix3D &tm1 = light1->Get_Transform ();
	const Matrix3D &tm2 = light2->Get_Transform ();
	if (	tm1 == tm2 &&
			light1->Get_Type () == light2->Get_Type ())
	{					
		//
		//	Are their color's and intensities approx the same?
		//
		Vector3 ambient1;
		Vector3 ambient2;
		Vector3 specular1;
		Vector3 specular2;
		Vector3 diffuse1;
		Vector3 diffuse2;
		light1->Get_Ambient (&ambient1);
		light2->Get_Ambient (&ambient2);
		light1->Get_Specular (&specular1);
		light2->Get_Specular (&specular2);
		light1->Get_Diffuse (&diffuse1);
		light2->Get_Diffuse (&diffuse2);

		float intensity1 = light1->Get_Intensity ();
		float intensity2 = light2->Get_Intensity ();

		if (	ambient1 == ambient2 &&
				specular1 == specular2 &&
				diffuse1 == diffuse2 &&
				intensity1 == intensity2)
		{
			//
			//	Are their attenuation ranges the same?
			//
			double inner1 = 0;
			double inner2 = 0;
			double outer1 = 0;
			double outer2 = 0;
			light1->Get_Far_Attenuation_Range (inner1, outer1);
			light2->Get_Far_Attenuation_Range (inner2, outer2);
			if (inner1 == inner2 && outer1 == outer2) {
				retval = true;
			}
		}
	}

	return retval;
}


////////////////////////////////////////////////////////////////
//
//	Replace_Selection
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Replace_Selection (void)
{
	//
	//	Is there a selection set to replace?
	//
	int count = m_SelectionMgr->Get_Count ();
	if (count > 0) {

		//
		//	Configure a dialog that will allow the user to select a preset
		//
		SelectPresetDialogClass dialog (::AfxGetMainWnd ());
		dialog.Set_Title ("Object Replace");
		dialog.Set_Message ("Select the new preset you want to replace the selected objects with.");
		dialog.Allow_None_Selection (false);

		//
		//	Let the user pick a preset
		//
		if (dialog.DoModal () == IDOK) {

			PresetClass *preset = dialog.Get_Selection ();
			DynamicVectorClass<NodeClass *> new_nodes;
			CWaitCursor wait_cursor;

			//
			//	Loop over all the selected nodes
			//
			for (int index = 0; index < count; index ++) {
				
				//
				//	Get the transform of this selected node
				//
				NodeClass *node	= m_SelectionMgr->Get_At (index);
				Matrix3D tm			= node->Get_Transform ();

				//
				//	Create a new node with this same transform
				//
				NodeClass *new_node = Create_Node (preset, &tm, 0, true);
				new_node->Set_ID (node->Get_ID ());
				new_nodes.Add (new_node);

				//
				//	Test to see if both node's are game objects of some sort.
				// If they are, then we need to copy the scripts.
				//
				ObjectNodeClass *old_obj_node	= node->As_ObjectNodeClass ();
				ObjectNodeClass *new_obj_node	= new_node->As_ObjectNodeClass ();
				if (old_obj_node != NULL && new_obj_node != NULL) {
					new_obj_node->Copy_Scripts (*old_obj_node);					
				}

				//
				//	Delete the old node
				//
				Delete_Node (node, false);
			}
			
			//
			//	Clear the selection
			//
			m_SelectionMgr->Reset ();

			//
			//	Add the new nodes to the selection set
			//
			for (index = 0; index < new_nodes.Count (); index ++) {
				NodeClass *new_node = new_nodes[index];
				m_SelectionMgr->Add_Node (new_node);
			}
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Generate_Uniform_Sampled_Vis
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Generate_Uniform_Sampled_Vis
(
	float	granularity,
	float	sample_height,
	bool	ignore_bias,
	bool	selection_only,
	bool	farm_mode,
	int	farm_cpu_index,
	int	farm_cpu_total
)
{		
	GeneratingVisDialogClass dialog (granularity, ::AfxGetMainWnd ());
	dialog.Set_Sample_Height (sample_height);
	dialog.Set_Ignore_Bias (ignore_bias);
	dialog.Do_Selection_Only (selection_only);
	if (farm_mode) {
		dialog.Set_Farm_Mode (farm_cpu_index,farm_cpu_total);
	}
	dialog.DoModal ();
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Generate_Edge_Sampled_Vis
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Generate_Edge_Sampled_Vis
(
	float	granularity,
	bool	ignore_bias,
	bool  farm_mode,
	int	farm_cpu_index,
	int	farm_cpu_total
)
{
	//
	//	Build a list of nodes that should be vis-sampled
	//
	DynamicVectorClass<NodeClass *> node_list;
	VisMgrClass::Build_Node_List (node_list);

	// 
	// Determine the first and last node that this cpu is to process.  In farm
	// mode, the processing is split up amongst several computers...
	//
	int first_node = 0;
	int last_node = node_list.Count();
	if (farm_mode) {
		int node_count = node_list.Count();
		float nodes_per_processor = (float)node_count / (float)farm_cpu_total;
		first_node	= (int)::floor (nodes_per_processor * (float)farm_cpu_index);
		last_node	= (int)::ceil (nodes_per_processor * (float)(farm_cpu_index+1));
	}
		  
	VisGenProgressClass progress_obj;
	progress_obj.Set_Node_Count (last_node - first_node);	

	GeneratingEdgeSampledVisDialogClass *dialog = GeneratingEdgeSampledVisDialogClass::Display ();
	dialog->Set_Progress_Obj (&progress_obj);
	dialog->Enable_Farm_Mode (farm_mode);

	//
	//	Perform the per-node vis sampling
	//
	VisSectorSamplerClass sampler (this, &progress_obj, granularity, MOUSE_CLICK_COLLISION_GROUP);
	for (	int index = first_node;
			index < last_node && !progress_obj.Is_Cancel_Requested ();
			index ++)
	{
		NodeClass *node = node_list[index];
		if (node != NULL) {
			
			//
			// Simply pass off the render object onto the sampler class
			// for point generation
			//
			RenderObjClass *render_obj = node->Peek_Render_Obj ();
			if (render_obj != NULL) {
				sampler.Process (render_obj);
			}
		}

		::General_Pump_Messages ();
	}

	//
	//	Now render any manual vis points
	//
	VisMgrClass::Render_Manual_Vis_Points ();

	dialog->Set_Progress_Obj (NULL);
	dialog->Set_Finished (true);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Generate_Light_Vis
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Generate_Light_Vis
(
	bool	farm_mode,
	int	farm_cpu_index,
	int	farm_cpu_total
)
{
	GeneratingLightVisDialogClass dialog (::AfxGetMainWnd ());
	if (farm_mode) {
		dialog.Set_Farm_Mode (farm_cpu_index,farm_cpu_total);
	}
	dialog.DoModal ();
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Generate_Manual_Vis
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Generate_Manual_Vis
(
	bool	farm_mode,
	int	farm_cpu_index,
	int	farm_cpu_total
)
{
	GeneratingManualVisDialogClass dialog (::AfxGetMainWnd ());
	if (farm_mode) {
		dialog.Set_Farm_Mode (farm_cpu_index, farm_cpu_total);
	}

	dialog.DoModal ();
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Display_Editor_Objects
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Display_Editor_Objects (bool onoff)
{
	m_ShowEditorObjects = onoff;

	//
	//	Loop over all the nodes, picking out the editor-only
	// nodes to show or hide.
	//
	DynamicVectorClass<NodeClass *> node_list;
	NodeMgrClass::Build_Full_Node_List (node_list);
	for (int index = 0; index < node_list.Count (); index ++) {
		NodeClass *node = node_list[index];
		if (node != NULL) {
			int type = node->Get_Type ();

			//
			//	Does this node have an editor-only display object?
			//
			if (	type == NODE_TYPE_ZONE ||
					type == NODE_TYPE_WAYPATH ||
					type == NODE_TYPE_WAYPOINT ||
					type == NODE_TYPE_LIGHT ||
					type == NODE_TYPE_TRANSITION ||
					type == NODE_TYPE_TRANSITION_CHARACTER ||
					type == NODE_TYPE_SOUND ||
					type == NODE_TYPE_VIS ||
					type == NODE_TYPE_VIS_POINT ||
					type == NODE_TYPE_PATHFIND_START ||
					type == NODE_TYPE_DUMMY_OBJECT ||
					type == NODE_TYPE_COVER_SPOT ||
					type == NODE_TYPE_COVER_ATTACK_POINT ||
					type == NODE_TYPE_DAMAGE_ZONE ||
					type == NODE_TYPE_BUILDING ||
					type == NODE_TYPE_EDITOR_ONLY_OBJ)
			{
				node->Hide (!onoff);
			}
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Display_Static_Anim_Phys
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Display_Static_Anim_Phys (bool onoff)
{
	m_ShowStaticAnimPhys = onoff;

	//
	//	Get the list of all static objects from the physics scene
	//
	RefPhysListIterator iterator = ::Get_Scene_Editor ()->Get_Static_Object_Iterator ();
	for (iterator.First (); !iterator.Is_Done (); iterator.Next ()) {

		//
		//	Is this object a static anim phys?
		//
		PhysClass *phys_obj							= iterator.Peek_Obj ();
		StaticAnimPhysClass *static_phys_obj	= phys_obj->As_StaticAnimPhysClass ();
		if (static_phys_obj != NULL && static_phys_obj->Peek_Model () != NULL) {
			
			//
			//	Hide the static anim phys object and turn off its collision
			//
			static_phys_obj->Peek_Model ()->Set_Hidden (!onoff);
			if (onoff == false) {
				phys_obj->Inc_Ignore_Counter ();
			} else if (phys_obj->Is_Ignore_Me ()) {
				phys_obj->Dec_Ignore_Counter ();
			}
		}
	}


	/*for (	NodeClass *node = NodeMgrClass::Get_First ();
			node != NULL;
			node = NodeMgrClass::Get_Next (node))
	{
		PhysClass *phys_obj = node->Peek_Physics_Obj ();
		if (phys_obj != NULL && phys_obj->As_StaticAnimPhysClass () != NULL) {
			node->Hide (!onoff);
		}
	}*/

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Enable_Building_Power
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Enable_Building_Power (bool onoff)
{
	//
	//	Make sure the buildings are in a good state
	//
	GameObjManager::Update_Building_Collection_Spheres ();
	GameObjManager::Init_Buildings ();

	//
	//	Loop over all the lights and tell them to turn the spheres
	// on or off.
	//
	for (	NodeClass *node = NodeMgrClass::Get_First (NODE_TYPE_BUILDING);
			node != NULL;
			node = NodeMgrClass::Get_Next (node, NODE_TYPE_BUILDING))
	{
		((BuildingNodeClass *)node)->Enable_Power (onoff);
		((BuildingNodeClass *)node)->Set_Normalized_Health (onoff * 1.0F);
	}

	
	m_BuildingPowerEnabled = onoff;
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Reset_Dynamic_Object_Visibility_Status
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Reset_Dynamic_Object_Visibility_Status (void)
{
	//
	//	Get the list of all dynamic objects from the physics scene
	//
	RefPhysListIterator iterator = ::Get_Scene_Editor ()->Get_Dynamic_Object_Iterator ();
	for (iterator.First (); !iterator.Is_Done (); iterator.Next ()) {

		//
		//	Make sure this phys obj is a dynamic object
		//
		PhysClass *phys_obj					= iterator.Peek_Obj ();
		DynamicPhysClass *dyn_phys_obj	= phys_obj->As_DynamicPhysClass ();
		if (dyn_phys_obj != NULL) {

			//
			//	Reset this dynamic object's visibility
			//
			dyn_phys_obj->Update_Visibility_Status ();			
		}
	}
	
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Reset_Vis_For_Node
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Reset_Vis_For_Node (NodeClass *node)
{
	if (node == NULL || node->Is_Static () == false) {
		return ;
	}

	//
	//	Make sure this is a static phys object
	//
	PhysClass *phys = node->Peek_Physics_Obj ();
	if (phys != NULL && phys->As_StaticPhysClass () != NULL) {
		StaticPhysClass *static_phys = phys->As_StaticPhysClass ();
		
		//
		//	Get this sector's id
		//
		int vis_id = static_phys->Get_Vis_Sector_ID ();
		if (vis_id > 0) {

			//
			//	Get the vis table for this sector
			//
			VisTableClass *vis_table = VisTableManager.Get_Vis_Table(vis_id, false);
			if (vis_table != NULL) {
				
				//
				//	Reset the visibility data for this sector
				//
				vis_table->Reset_All ();
				VisTableManager.Update_Vis_Table(vis_id,vis_table);
				REF_PTR_RELEASE(vis_table);
			}
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Update_Lighting
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Update_Lighting (void)
{
	Vector3 lev_min;
	Vector3 lev_max;
	Vector3 sundirection;
	
	Get_Level_Extents (lev_min, lev_max);

	AABoxClass box;
	box.Center = lev_min + ((lev_max - lev_min) * 0.5F);
	box.Extent = ((lev_max - lev_min) * 0.5F);
	Invalidate_Lighting_Caches (box);

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Set_Sun_Light_Orientation
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Set_Sun_Light_Orientation (float yaw, float pitch)
{
	PhysicsSceneClass::Set_Sun_Light_Orientation (yaw, pitch);
}


////////////////////////////////////////////////////////////////
//
//	Show_Vis_Window
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::Show_Vis_Window (bool onoff)
{
	if (m_VisWindow.m_hWnd == NULL) {
		m_VisWindow.Create ();
	}

	m_VisWindow.ShowWindow (onoff ? SW_SHOW : SW_HIDE);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Is_Vis_Window_Visible
//
////////////////////////////////////////////////////////////////
bool
SceneEditorClass::Is_Vis_Window_Visible (void)
{
	bool retval = true;

	if (	m_VisWindow.m_hWnd == NULL ||
			m_VisWindow.IsWindowVisible () == false)
	{
		retval = false;
	}

	return retval;
}


////////////////////////////////////////////////////////////////
//
//	On_Vis_Occluders_Rendered
//
////////////////////////////////////////////////////////////////
void
SceneEditorClass::On_Vis_Occluders_Rendered
(
	VisRenderContextClass &	context,
	VisSampleClass &			sample
)
{
	m_VisWindow.Update_Display (*context.VisRasterizer);
	return ;
}
