/* ** 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 . */ /*********************************************************************************************** *** 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/wwphys/Path.cpp $* * * * Author:: Patrick Smith * * * * $Modtime:: 3/20/02 6:52p $* * * * $Revision:: 52 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "path.h" #include #include "colmathaabox.h" #include "pathfind.h" #include "pathfindportal.h" #include "pathsolve.h" #include "pathdebugplotter.h" #include "cardinalspline.h" #include "pathobject.h" #include "vehiclecurve.h" #include "waypath.h" #include "waypoint.h" #include "chunkio.h" #include "persistfactory.h" #include "assetmgr.h" #include "wwmemlog.h" //////////////////////////////////////////////////////////////// // Save/Load constants //////////////////////////////////////////////////////////////// enum { CHUNKID_VARIABLES = 0x11040535, CHUNKID_SPLINE }; enum { VARID_STATE = 1, VARID_TRAVERSAL_TYPE, VARID_START_POS, VARID_DEST_POS, VARID_EXPECTED_POS, VARID_LOOK_AHEAD_DIST, VARID_LOOK_AHEAD_TIME, VARID_MOVEMENT_RADIUS, VARID_SPLINE_TIME, VARID_VELOCITY, VARID_CURRENT_ACTION, VARID_MOVEMENT_DIRECTIONS, VARID_PATH_OBJECT, VARID_START_TIME, VARID_END_TIME, VARID_ISLOOPING, VARID_PATH_ACTION, VARID_OLD_PTR, VARID_TOTAL_DIST }; /////////////////////////////////////////////////////////////////////////// // Local macros /////////////////////////////////////////////////////////////////////////// #define SAFE_DELETE(pobject) \ if (pobject) { \ delete pobject; \ pobject = NULL; \ } \ /////////////////////////////////////////////////////////////////////////// // Constants /////////////////////////////////////////////////////////////////////////// static const float DEF_VEHICLE_VELOCITY = 40.0F; static const float DEF_HUMAN_VELOCITY = 2.5F; //5.0F; static const float ASSUMED_FPS = 30.0F; //15.0F; /////////////////////////////////////////////////////////////////////////// // Static member initialization /////////////////////////////////////////////////////////////////////////// bool PathClass::m_DisplayMoveVectors = false; static int _InstanceCount = 0; /////////////////////////////////////////////////////////////////////////// // // PathClass // /////////////////////////////////////////////////////////////////////////// PathClass::PathClass (void) : m_ExpectedPos (0, 0, 0), m_StartPos (0, 0, 0), m_DestPos (0, 0, 0), m_StartTime (0), m_EndTime (1.0F), m_LookAheadTime (0), m_LookAheadDist (0), m_SplineTime (0), m_State (ERROR_NOT_INITIALIZED), m_TraversalType (SPLINE), m_Velocity (DEF_VEHICLE_VELOCITY), m_Spline (NULL), m_MovementRadius (0.1F), m_MovementDirections (MOVE_X | MOVE_Y), m_CurrentAction (-1), m_IsLooping (false), m_TotalDist (0) { _InstanceCount ++; return ; } /////////////////////////////////////////////////////////////////////////// // // ~PathClass // /////////////////////////////////////////////////////////////////////////// PathClass::~PathClass (void) { _InstanceCount --; SAFE_DELETE (m_Spline); return ; } /////////////////////////////////////////////////////////////////////////// // // Initialize // /////////////////////////////////////////////////////////////////////////// void PathClass::Initialize (const Vector3 &start, const Vector3 &end) { m_State = STATE_TRAVERSING_PATH; m_SplineTime = 0; m_LookAheadTime = 0; m_TotalDist = 0; m_LookAheadDist = 0; m_CurrentAction = -1; m_StartTime = 0; m_EndTime = 1.0F; m_IsLooping = false; PATH_NODE node1; node1.time = 0; node1.next_time = 1.0F; node1.action_id = ACTION_NONE; node1.mechanism_id = 0; node1.tighten_spline = false; node1.pos = start; node1.dest_pos.Set (0, 0, 0); PATH_NODE node2; node2.time = 0; node2.next_time = 1.0F; node2.action_id = ACTION_NONE; node2.mechanism_id = 0; node2.tighten_spline = false; node2.pos = end; node2.dest_pos.Set (0, 0, 0); DynamicVectorClass node_list; node_list.Add (node1); node_list.Add (node2); // // Convert the nodes into a spline and a set of action requests // Initialize_Spline (node_list); return ; } /////////////////////////////////////////////////////////////////////////// // // Initialize // /////////////////////////////////////////////////////////////////////////// void PathClass::Initialize (PathSolveClass &path_solve) { WWMEMLOG(MEM_PATHFIND); m_State = STATE_TRAVERSING_PATH; m_SplineTime = 0; m_LookAheadTime = 0; m_TotalDist = 0; m_LookAheadDist = 0; m_CurrentAction = -1; m_StartTime = 0; m_EndTime = 1.0F; m_IsLooping = false; DynamicVectorClass node_list; // // Get the raw path data from the solver, and convert it into // a format we can digest // PathSolveClass::PATHPOINT_LIST &raw_path = path_solve.Get_Raw_Path (); for (int index = 0; index < raw_path.Count (); index ++) { // // Setup a path node that contains the information we // need from the path solver // PATH_NODE node; node.time = 0; node.next_time = 1.0F; node.action_id = ACTION_NONE; node.mechanism_id = 0; node.tighten_spline = false; node.pos = raw_path[index].m_Point; node.dest_pos.Set (0, 0, 0); // // Check for any action requirements // bool is_valid = true; PathfindPortalClass *portal = raw_path[index].m_Portal; if (portal != NULL) { // // See what kind of portal this is // PathfindActionPortalClass *action_portal = portal->As_PathfindActionPortalClass (); PathfindWaypathPortalClass *waypath_portal = portal->As_PathfindWaypathPortalClass (); if (action_portal != NULL) { // // Check to see if we are inadvertantly starting on an "exit" portal // if (index == 1 && action_portal->Get_Action_Type () == PathClass::ACTION_NONE) { // // Find the entrance portal // PathfindActionPortalClass *enter_portal = action_portal->Get_Enter_Portal (); if (enter_portal != NULL) { AABoxClass box; enter_portal->Get_Bounding_Box (box); // // Insert a node that will force the unit to do the entrance portal // PATH_NODE temp_node; temp_node.time = 0; temp_node.next_time = 1.0F; temp_node.tighten_spline = true; temp_node.pos = box.Center; temp_node.action_id = enter_portal->Get_Action_Type (); temp_node.dest_pos = enter_portal->Get_Destination (); temp_node.mechanism_id = enter_portal->Get_Mechanism_ID (); node_list.Add (temp_node); } } // // This is an action portal so set the action parameters // node.action_id = action_portal->Get_Action_Type (); node.dest_pos = action_portal->Get_Destination (); node.mechanism_id = action_portal->Get_Mechanism_ID (); node.tighten_spline = true; // // Override the positions so that its in the center // of the entrance portal. (This ensures guys won't // get stuck when the door opens). // AABoxClass box; action_portal->Get_Bounding_Box (box); node.pos = box.Center; } else if (waypath_portal != NULL) { is_valid = false; // // Lookup the next portal in the list (it should be the waypath exit portal) // if (index + 1 < raw_path.Count ()) { PathfindWaypathPortalClass *next_portal = raw_path[index + 1].m_Portal->As_PathfindWaypathPortalClass (); if (next_portal != NULL) { // // Create a temporary waypath segment from the portal information // const WaypathPositionClass &start_pos = waypath_portal->Get_Waypath_Pos (); const WaypathPositionClass &end_pos = next_portal->Get_Waypath_Pos (); WaypathClass *path_segment = new WaypathClass (start_pos, end_pos); // // Insert all the data from this waypath segment into our path // Add_Waypath_Data (node_list, path_segment, 0, path_segment->Get_Point_Count () - 1); REF_PTR_RELEASE (path_segment); // // Skip past the next portal // index ++; } } } else if (portal->Is_Two_Way_Portal () == false) { /*if (index < (raw_path.Count () - 1) && raw_path[index+1].m_Portal != NULL) { AABoxClass curr_box; AABoxClass next_box; portal->Get_Bounding_Box (curr_box); raw_path[index+1].m_Portal->Get_Bounding_Box (next_box); if (curr_box.Center.Z > (next_box.Center.Z + 1.5F)) { curr_box.Center.Z = 0; next_box.Center.Z = 0; float portal_dist = (curr_box.Center - next_box.Center).Length (); if (portal_dist < 665.0F) { // // Calculate an intermediate point that is one meter towards // the center of the next sector // Vector3 temp_delta = (raw_path[index+1].m_SectorCenter - next_box.Center); temp_delta.Normalize (); Vector3 intermediate_point = next_box.Center + temp_delta; // // Insert a node that will force the unit to jump down // PATH_NODE temp_node; temp_node.time = 0; temp_node.next_time = 1.0F; temp_node.tighten_spline = true; temp_node.pos = intermediate_point; temp_node.action_id = ACTION_JUMP; temp_node.mechanism_id = 0; temp_node.dest_pos.Set (intermediate_point); node_list.Add (temp_node); } } }*/ } } if (is_valid) { node_list.Add (node); } } // // Convert the nodes into a spline and a set of action requests // Initialize_Spline (node_list); // // Clip the spline to the sectors and portals that the solver knows is safe // if (m_PathObject.Is_Flag_Set (PathObjectClass::IS_VEHICLE) == false) { Clip_Spline_To_Pathfind_Data (node_list, path_solve); } return ; } /////////////////////////////////////////////////////////////////////////// // // Add_Waypoint_Info_To_Node_List // /////////////////////////////////////////////////////////////////////////// void PathClass::Add_Waypoint_Info_To_Node_List ( DynamicVectorClass & node_list, WaypointClass * waypoint, WaypointClass * next_point ) { // // Fill in a path node structure for this waypoint // PathClass::PATH_NODE node; node.time = 0; node.next_time = 1.0F; node.action_id = ACTION_NONE; node.mechanism_id = 0; node.dest_pos.Set (0, 0, 0); node.pos = waypoint->Get_Position (); // // Check to see if there are any special actions to perform at // this waypoint // if (waypoint->Get_Flag (WaypointClass::FLAG_REQUIRES_JUMP)) { node.action_id = ACTION_JUMP; node.tighten_spline = true; // // Record the jump-to point (if there is one) // if (next_point != NULL) { node.dest_pos = next_point->Get_Position (); } } else if (waypoint->Get_Flag (WaypointClass::FLAG_REQUIRES_ACTION)) { // // Lookup the action portal from the waypoint // PathfindActionPortalClass *action_portal = waypoint->Get_Action_Portal (); if (action_portal != NULL) { // // Configure the node's action parameters // node.action_id = action_portal->Get_Action_Type (); node.dest_pos = action_portal->Get_Destination (); node.mechanism_id = action_portal->Get_Mechanism_ID (); node.tighten_spline = true; // // Override the positions so that its in the center // of the entrance portal. (This ensures guys won't // get stuck when the door opens). // AABoxClass box; action_portal->Get_Bounding_Box (box); node.pos = box.Center; } } // // Add the node to the list // node_list.Add (node); return ; } /////////////////////////////////////////////////////////////////////////// // // Add_Waypath_Data // /////////////////////////////////////////////////////////////////////////// void PathClass::Add_Waypath_Data ( DynamicVectorClass & node_list, WaypathClass * waypath, int start_index, int end_index ) { // // If this is a looping path, then add the last waypoint first, so we can // wrap the spline from "last_point-1" all the way through "first_point+1". // If we don't do this, the spline will not look continuous. // if (m_IsLooping) { Add_Waypoint_Info_To_Node_List (node_list, waypath->Get_Point (end_index), waypath->Get_Point (start_index)); } // // Loop along the waypath either forwards or backwards, depending // on what the caller has requested // int count = WWMath::Fabs (end_index - start_index); int inc = (end_index > start_index) ? 1 : -1; for (int index = start_index; count >= 0; index += inc, count --) { WaypointClass *waypoint = waypath->Get_Point (index); WaypointClass *next_point = waypath->Get_Point (index + inc); // // Add data from each waypoint into our path node list // if ((waypoint == NULL || next_point == NULL) || waypoint->Get_Position () != next_point->Get_Position ()) { Add_Waypoint_Info_To_Node_List (node_list, waypoint, next_point); } } // // If this is a looping path, then add the first and second waypoints again. // This is done so we can wrap the spline from "last_point-1" all the way // through "first_point+1". If we don't do this, the spline will not look // continuous. // if (m_IsLooping) { Add_Waypoint_Info_To_Node_List (node_list, waypath->Get_Point (start_index), waypath->Get_Point (start_index + inc)); Add_Waypoint_Info_To_Node_List (node_list, waypath->Get_Point (start_index + inc), waypath->Get_Point (start_index + inc + inc)); } return ; } /////////////////////////////////////////////////////////////////////////// // // Initialize // /////////////////////////////////////////////////////////////////////////// void PathClass::Initialize (WaypathClass *waypath, int start_pt_id, int end_pt_id) { WWMEMLOG(MEM_PATHFIND); m_State = ERROR_NOT_INITIALIZED; m_SplineTime = 0; m_LookAheadTime = 0; m_LookAheadDist = 0; m_CurrentAction = -1; m_StartTime = 0; m_EndTime = 1.0F; m_IsLooping = false; // // Initialize the path from this waypath // if (waypath != NULL) { // // Determine if this is a looping path or not // m_IsLooping = waypath->Get_Flag (WaypathClass::FLAG_LOOPING); // // Get the number of waypoints on this path // int count = waypath->Get_Point_Count (); if (count > 0) { // // Determine the starting and ending indicies // int start_index = 0; int end_index = count - 1; for (int index = 0; index < count; index ++) { if (waypath->Get_Point (index)->Get_ID () == start_pt_id) { start_index = index; } if (waypath->Get_Point (index)->Get_ID () == end_pt_id) { end_index = index; } } // // Initialize our first position // m_ExpectedPos = waypath->Get_Point (start_index)->Get_Position (); m_State = STATE_TRAVERSING_PATH; // // Add the waypath data to our node list // DynamicVectorClass node_list; Add_Waypath_Data (node_list, waypath, start_index, end_index); // // Now create a spline from the waypath data // Initialize_Spline (node_list); } } return ; } /////////////////////////////////////////////////////////////////////////// // // Evaluate_Next_Point // /////////////////////////////////////////////////////////////////////////// bool PathClass::Evaluate_Next_Point (const Vector3 &curr_pos, Vector3 &new_pos) { // // Be careful not to evaluate when we are in a invalid state // if (m_State >= FIRST_ERROR) { new_pos = curr_pos; return false; } else if (m_State == STATE_PATH_COMPLETE) { new_pos = m_ExpectedPos; return true; } // // Get the delta from our current position to the point where we // should be heading // Vector3 delta = m_ExpectedPos - curr_pos; // // Zero out any directions we aren't moving in // if ((m_MovementDirections & MOVE_X) == 0) { delta.X = 0; } if ((m_MovementDirections & MOVE_Y) == 0) { delta.Y = 0; } if ((m_MovementDirections & MOVE_Z) == 0) { delta.Z = 0; } // // Calculate the distance we have yet to travel to // reach the next point // float delta_len = delta.Length (); delta_len -= m_MovementRadius; // // Reset our state // m_State = STATE_TRAVERSING_PATH; // // Recalculate our look-ahead distance // //m_LookAheadDist = (m_Velocity * 4.0F) / ASSUMED_FPS; // // Did we reach the expected position? // bool advance_dest = delta_len < m_LookAheadDist; if (advance_dest) { float new_time = m_SplineTime + m_LookAheadTime; if (new_time > (m_EndTime - (m_LookAheadTime * 0.5F))) { new_time = m_EndTime; } // // Have we successfully traversed the spline? // if (new_time >= m_EndTime) { // // Is this a looping path? // if (m_IsLooping) { // // If this path is looping, then wrap from the end of the // path to the beginning of the path // m_SplineTime = (new_time - m_EndTime) + m_StartTime; m_CurrentAction = -1; } else { // // If we are close to the destination, then consider us 'complete'. // //if (delta_len < 0.25F) { m_State = STATE_PATH_COMPLETE; m_SplineTime = m_EndTime; m_CurrentAction = -1; //} } } else { // // Check to see if the object needs to perform an action at this point // if (m_CurrentAction + 1 < m_PathActions.Count ()) { PATH_NODE &node = m_PathActions[m_CurrentAction + 1]; // // Should we activate this action request? // float action_time = node.time; if (WWMath::Fabs (action_time - m_SplineTime) < (m_LookAheadTime * 0.5F)) { // // Set the new state and remember which action we are to perform // m_State = STATE_ACTION_REQUIRED; m_CurrentAction ++; // // Snap to the action point // new_time = node.next_time; } } // // Increase our position along the spline // m_SplineTime = new_time; } // // Evaluate the spline at the given 'time' to determine our new position // m_Spline->Evaluate (m_SplineTime, &m_ExpectedPos); if (m_PathObject.Is_Flag_Set (PathObjectClass::IS_VEHICLE)) { m_SplineTime = ((VehicleCurveClass *)m_Spline)->Get_Last_Eval_Time (); } } new_pos = m_ExpectedPos; /*static PhysClass *_DebugObj = NULL; if (_DebugObj == NULL) { _DebugObj = new DecorationPhysClass; _DebugObj->Set_Model (WW3DAssetManager::Get_Instance ()->Create_Render_Obj ("C_HAVOC")); _DebugObj->Inc_Ignore_Counter (); PhysicsSceneClass::Get_Instance ()->Add_Dynamic_Object (_DebugObj); } _DebugObj->Set_Transform (Matrix3D (m_ExpectedPos));*/ if (m_DisplayMoveVectors) { PhysicsSceneClass::Get_Instance ()->Add_Debug_AABox (AABoxClass (curr_pos, Vector3 (0.25F,0.25F,0.25F)), Vector3 (1, 0, 0)); PhysicsSceneClass::Get_Instance ()->Add_Debug_AABox (AABoxClass (m_ExpectedPos, Vector3 (0.25F,0.25F,0.25F)), Vector3 (0, 1, 0)); PhysicsSceneClass::Get_Instance ()->Add_Debug_Vector (curr_pos, m_ExpectedPos - curr_pos, Vector3 (0, 0.6F, 1)); } return true; } /////////////////////////////////////////////////////////////////////////// // // Get_Curve_Sharpness // /////////////////////////////////////////////////////////////////////////// float PathClass::Get_Curve_Sharpness (Vector3 *position) const { float sharpness = 0; if (m_PathObject.Is_Flag_Set (PathObjectClass::IS_VEHICLE)) { Vector3 foo; m_Spline->Evaluate (m_SplineTime, &foo); sharpness = ((VehicleCurveClass *)m_Spline)->Get_Current_Sharpness (position); } return sharpness; } /////////////////////////////////////////////////////////////////////////// // // Get_Last_Eval_Time // /////////////////////////////////////////////////////////////////////////// float PathClass::Get_Last_Eval_Time (void) const { return m_SplineTime; } /////////////////////////////////////////////////////////////////////////// // // Get_Remaining_Path_Length // /////////////////////////////////////////////////////////////////////////// float PathClass::Get_Remaining_Path_Length (void) { float length = m_TotalDist; // // The length remaining should correspond to the amount of time // remaining. // if (m_IsLooping == false && m_EndTime > 0) { length = m_TotalDist * ((m_EndTime - m_SplineTime) / m_EndTime); } return length; } /////////////////////////////////////////////////////////////////////////// // // Display_Path // /////////////////////////////////////////////////////////////////////////// void PathClass::Display_Path (bool onoff) { if (onoff == false) { PathDebugPlotterClass::Get_Instance ()->Display (false); } else if (m_State < FIRST_ERROR) { // // Turn off painting // PathDebugPlotterClass::Get_Instance ()->Display (false); PathDebugPlotterClass::Get_Instance ()->Reset (); // // Get the first position on the spline // Vector3 last_pos; m_Spline->Evaluate (0, &last_pos); // // Plot the spline // for (float t = m_LookAheadTime; t <= 1.0F; t += m_LookAheadTime) { //for (float t = m_LookAheadTime; t <= 1.0F; t += m_LookAheadTime / 2.0F) { Vector3 curr_pos; m_Spline->Evaluate (t, &curr_pos); if (m_PathObject.Is_Flag_Set (PathObjectClass::IS_VEHICLE)) { t = ((VehicleCurveClass *)m_Spline)->Get_Last_Eval_Time (); } PathDebugPlotterClass::Get_Instance ()->Add (last_pos, curr_pos, Vector3 (0, 0.5F, 1)); last_pos = curr_pos; } // // Turn painting back on // PathDebugPlotterClass::Get_Instance ()->Display (true); } return ; } ///////////////////////////////////////////////////////////////////////////////// // // Set_Traversal_Type // ///////////////////////////////////////////////////////////////////////////////// void PathClass::Set_Traversal_Type (TRAVERSAL_TYPE type) { // // Do we need to re-initialize any data? // if (m_TraversalType != type) { m_TraversalType = type; } return ; } ///////////////////////////////////////////////////////////////////////////////// // // Find_Intersection_X // ///////////////////////////////////////////////////////////////////////////////// bool Find_Intersection_X ( const Vector3 & start, const Vector3 & end, const Vector3 & rgn_center, const Vector3 & rgn_extent, Vector3 * intersect_point ) { float percent = (rgn_center.X - start.X) / (end.X - start.X); (*intersect_point) = start + ((end - start) * percent); bool retval = false; if (percent >= 0 && percent < 1.0F) { retval = (intersect_point->Y >= (rgn_center.Y - rgn_extent.Y)); retval &= (intersect_point->Y <= (rgn_center.Y + rgn_extent.Y)); retval &= (intersect_point->Z <= (rgn_center.Z + rgn_extent.Z)); retval &= (intersect_point->Z <= (rgn_center.Z + rgn_extent.Z)); } return retval; } ///////////////////////////////////////////////////////////////////////////////// // // Find_Intersection_Y // ///////////////////////////////////////////////////////////////////////////////// bool Find_Intersection_Y ( const Vector3 & start, const Vector3 & end, const Vector3 & rgn_center, const Vector3 & rgn_extent, Vector3 * intersect_point ) { float percent = (rgn_center.Y - start.Y) / (end.Y - start.Y); (*intersect_point) = start + ((end - start) * percent); bool retval = false; if (percent >= 0 && percent < 1.0F) { retval = (intersect_point->X >= (rgn_center.X - rgn_extent.X)); retval &= (intersect_point->X <= (rgn_center.X + rgn_extent.X)); retval &= (intersect_point->Z <= (rgn_center.Z + rgn_extent.Z)); retval &= (intersect_point->Z <= (rgn_center.Z + rgn_extent.Z)); } return retval; } ///////////////////////////////////////////////////////////////////////////////// // // Find_Side_Intersection // ///////////////////////////////////////////////////////////////////////////////// bool Find_Side_Intersection ( int index, const AABoxClass &box, const Vector3 & start, const Vector3 & end, Vector3 * intersection_point ) { bool retval = false; if (index == 0) { Vector3 center = box.Center; center.X += box.Extent.X; Vector3 extent = Vector3 (0, box.Extent.Y, box.Extent.Z); retval = Find_Intersection_X (start, end, center, extent, intersection_point); } else if (index == 1) { Vector3 center = box.Center; center.X -= box.Extent.X; Vector3 extent = Vector3 (0, box.Extent.Y, box.Extent.Z); retval = Find_Intersection_X (start, end, center, extent, intersection_point); } else if (index == 2) { Vector3 center = box.Center; center.Y += box.Extent.Y; Vector3 extent = Vector3 (box.Extent.X, 0, box.Extent.Z); retval = Find_Intersection_Y (start, end, center, extent, intersection_point); } else if (index == 3) { Vector3 center = box.Center; center.Y -= box.Extent.Y; Vector3 extent = Vector3 (box.Extent.X, 0, box.Extent.Z); retval = Find_Intersection_Y (start, end, center, extent, intersection_point); } return retval; } ///////////////////////////////////////////////////////////////////////////////// // // Is_Point_In_Boxes // ///////////////////////////////////////////////////////////////////////////////// bool PathClass::Is_Point_In_Boxes ( const Vector3 & point, BOX_LIST & box_list ) { bool retval = false; // // Test each box in the list // for (int index = 0; (index < box_list.Count ()) && !retval; index ++) { retval |= box_list[index]->Contains (point); } return retval; } ///////////////////////////////////////////////////////////////////////////////// // // Clip_Control_Point // ///////////////////////////////////////////////////////////////////////////////// void PathClass::Clip_Control_Point ( const Vector3 &start_point, Vector3 * point, BOX_LIST & sector_list, BOX_LIST & portal_list ) { // // Loop through all the boxes we need to clip against // for (int index = 0; index < sector_list.Count (); index ++) { AABoxClass &box = *(sector_list[index]); // // Clip the line to each of the four sides of the box (don't care about // top or bottom for the moment). // for (int side_index = 0; side_index < 4; side_index ++) { // // Does the line pass through this side? // Vector3 intersect_point; if (Find_Side_Intersection (side_index, box, start_point, *point, &intersect_point)) { // // If there isn't a portal where this line passes through the side, then // clip the line to the side. // if (Is_Point_In_Boxes (intersect_point, portal_list) == false) { (*point) = intersect_point; } } } } return ; } ///////////////////////////////////////////////////////////////////////////////// // // Initialize_Vehicle_Spline // ///////////////////////////////////////////////////////////////////////////////// void PathClass::Initialize_Vehicle_Spline (DynamicVectorClass &node_list) { m_Spline = new VehicleCurveClass (m_PathObject.Get_Turn_Radius ()); Vector3 point; Vector3 last_point = m_StartPos; // // Setup the spline using each 'point' on the path // as a key along the spline. // float current_dist = 0; for (int index = 0; index < node_list.Count (); index ++) { point = node_list[index].pos; // // Add this point as a key along the spline // current_dist += (point - last_point).Length (); float curr_time = current_dist / m_TotalDist; m_Spline->Add_Key (point, curr_time); // // Is this a looping path? // if (m_IsLooping) { // // Record where the loop starts and ends... // if (index == 1) { m_StartTime = curr_time; } else if (index == node_list.Count () - 2) { m_EndTime = curr_time; } } last_point = point; } return ; } ///////////////////////////////////////////////////////////////////////////////// // // Initialize_Human_Spline // ///////////////////////////////////////////////////////////////////////////////// void PathClass::Initialize_Human_Spline(DynamicVectorClass &node_list) { CardinalSpline3DClass temp_spline; float tightness = (m_TraversalType == SPLINE) ? 0.3F : 1.0F; // // Assume we do the whole path (looping paths only do a subset of // the path) // Vector3 point; Vector3 last_point = m_StartPos; // // Setup the temporary spline using each 'point' on the path // as a key along the spline. // float current_dist = 0; bool fixup_last_action = false; for (int index = 0; index < node_list.Count (); index ++) { point = node_list[index].pos; // // Add this point as a key along the spline // current_dist += (point - last_point).Length (); float curr_time = current_dist / m_TotalDist; temp_spline.Add_Key (point, curr_time); // // Is this a looping path? // if (m_IsLooping) { // // Record where the loop starts and ends... // if (index == 1) { m_StartTime = curr_time; } else if (index == node_list.Count () - 2) { m_EndTime = curr_time; } } // // Do we need to fix-up the time values for the last action node? // if (fixup_last_action) { PATH_NODE &node = m_PathActions[m_PathActions.Count () - 1]; node.next_time = curr_time; fixup_last_action = false; } // // Do we have an action at this node that we need to store? // if (node_list[index].action_id != 0) { // // Add an action node to our list so when the unit gets // to this point along the spline, we can have it perform // the requested action. // PATH_NODE node; node.time = curr_time; node.next_time = 1.0F; node.action_id = node_list[index].action_id; node.mechanism_id = node_list[index].mechanism_id; node.dest_pos = node_list[index].dest_pos; node.pos = node_list[index].pos; m_PathActions.Add (node); // // We need to fix up the 'next_time' member during the next iteration // fixup_last_action = true; } // // For some type of nodes we need to tighten the spline so its // as sharp as possible (actions generally require this) // if (node_list[index].tighten_spline) { temp_spline.Set_Tightness (index, 1.0F); } else { temp_spline.Set_Tightness (index, tightness); } last_point = point; } // // Convert the temp spline to a hermite spline so we // can adjust the tangent points if necessary (for clipping). // temp_spline.Update_Tangents (); m_Spline = new HermiteSpline3DClass; (*((HermiteSpline3DClass*)m_Spline)) = temp_spline; return ; } ///////////////////////////////////////////////////////////////////////////////// // // Clip_Spline_To_Pathfind_Data // ///////////////////////////////////////////////////////////////////////////////// void PathClass::Clip_Spline_To_Pathfind_Data ( DynamicVectorClass &node_list, PathSolveClass & path_solve ) { // // Get the portal and sector list from the pathsolver // BOX_LIST sector_list; BOX_LIST portal_list; path_solve.Get_Volumes (sector_list, portal_list); // // Loop through the points in the hermite spline, convert // each tangent to bezier curve control points, clip these // points to the sectors and portals the path contains, // and convert the clipped points back into hermite spline // tangents. // for (int index = 0; index < node_list.Count () - 1; index ++) { // // Get the current and next points on the path // Vector3 point = node_list[index].pos; Vector3 next_point = node_list[index + 1].pos; // // Get the in and out tangents for each of these points // Vector3 tangent_in; Vector3 tangent_out; Vector3 next_tangent_in; Vector3 next_tangent_out; ((HermiteSpline3DClass*)m_Spline)->Get_Tangents (index, &tangent_in, &tangent_out); ((HermiteSpline3DClass*)m_Spline)->Get_Tangents (index + 1, &next_tangent_in, &next_tangent_out); // // Convert the tangents to bezier curve control points // float one_third = 1.0F / 3.0F; Vector3 ctrl_pt1 = point + (tangent_out * one_third); Vector3 ctrl_pt2 = next_point - (next_tangent_in * one_third); // // Clip the control points to the pathfind sectors and portals // Clip_Control_Point (point, &ctrl_pt1, sector_list, portal_list); Clip_Control_Point (next_point, &ctrl_pt2, sector_list, portal_list); // // Convert the control points back into tangents // tangent_out = (ctrl_pt1 - point) * 3.0F; next_tangent_in = (next_point - ctrl_pt2) * 3.0F; // // Pass the tangents back to the hermite spline // ((HermiteSpline3DClass*)m_Spline)->Set_Tangents (index, tangent_in, tangent_out); ((HermiteSpline3DClass*)m_Spline)->Set_Tangents (index + 1, next_tangent_in, next_tangent_out); } // // Free the temporary portal-box list // for (index = 0; index < portal_list.Count (); index ++) { AABoxClass *portal_box = portal_list[index]; delete portal_box; } // // Free the temporary sector-box list // for (index = 0; index < sector_list.Count (); index ++) { AABoxClass *box = sector_list[index]; delete box; } return ; } ///////////////////////////////////////////////////////////////////////////////// // // Initialize_Spline // ///////////////////////////////////////////////////////////////////////////////// void PathClass::Initialize_Spline (DynamicVectorClass &node_list) { if (node_list.Count () > 0) { // // Start fresh // m_PathActions.Delete_All (); SAFE_DELETE (m_Spline); // // Get the first and last positions from node list // m_StartPos = node_list[0].pos; m_DestPos = node_list[node_list.Count () - 1].pos; if (m_IsLooping && node_list.Count () > 1) { m_StartPos = node_list[1].pos; m_DestPos = m_StartPos; } Vector3 point; Vector3 last_point = m_StartPos; // // Determine how long the spline is // m_TotalDist = 0; for (int index = 0; index < node_list.Count (); index ++) { point = node_list[index].pos; // // Add the distance of this point from the last point // into the total // m_TotalDist += (point - last_point).Length (); last_point = point; } // // Initialize the spline depending on the path object type // if (m_PathObject.Is_Flag_Set (PathObjectClass::IS_VEHICLE)) { Initialize_Vehicle_Spline (node_list); m_Velocity = DEF_VEHICLE_VELOCITY; } else { Initialize_Human_Spline (node_list); m_Velocity = DEF_HUMAN_VELOCITY; } // // Setup the variables to look-ahead 8 frames and switch // the new look-ahead after the unit has traveled 4 frames // float approx_frames = (m_TotalDist * ASSUMED_FPS) / m_Velocity; m_LookAheadTime = 8.0F / approx_frames; m_LookAheadDist = (m_Velocity * 4.0F) / ASSUMED_FPS; // // Vehicles automatically look ahead to each turn, so make sure // we don't skip one... // if (m_PathObject.Is_Flag_Set (PathObjectClass::IS_VEHICLE)) { m_LookAheadTime = m_LookAheadTime / 5.0F; } /*if (m_TotalDist > 0) { m_LookAheadTime = 2.0F / m_TotalDist; m_LookAheadDist = 4.0F; }*/ m_SplineTime = m_StartTime; m_ExpectedPos = m_StartPos; } return ; } ///////////////////////////////////////////////////////////////////////////////// // // Set_Path_Object // ///////////////////////////////////////////////////////////////////////////////// void PathClass::Set_Path_Object (PathObjectClass &path_object) { m_PathObject = path_object; return ; } ///////////////////////////////////////////////////////////////////////////////// // // Get_Path_Object // ///////////////////////////////////////////////////////////////////////////////// void PathClass::Get_Path_Object (PathObjectClass &path_object) const { path_object = m_PathObject; return ; } //////////////////////////////////////////////////////////////////////////////////////////// // // Save // //////////////////////////////////////////////////////////////////////////////////////////// void PathClass::Save (ChunkSaveClass &csave) { if (m_Spline != NULL) { // // Use the spline's factory to save it to its own chunk // csave.Begin_Chunk (CHUNKID_SPLINE); csave.Begin_Chunk (m_Spline->Get_Factory ().Chunk_ID ()); m_Spline->Get_Factory ().Save (csave, m_Spline); csave.End_Chunk (); csave.End_Chunk (); } csave.Begin_Chunk (CHUNKID_VARIABLES); // // Save each variable to its own microchunk // WRITE_MICRO_CHUNK (csave, VARID_STATE, m_State); WRITE_MICRO_CHUNK (csave, VARID_TRAVERSAL_TYPE, m_TraversalType); WRITE_MICRO_CHUNK (csave, VARID_START_POS, m_StartPos); WRITE_MICRO_CHUNK (csave, VARID_DEST_POS, m_DestPos); WRITE_MICRO_CHUNK (csave, VARID_EXPECTED_POS, m_ExpectedPos); WRITE_MICRO_CHUNK (csave, VARID_LOOK_AHEAD_DIST, m_LookAheadDist); WRITE_MICRO_CHUNK (csave, VARID_LOOK_AHEAD_TIME, m_LookAheadTime); WRITE_MICRO_CHUNK (csave, VARID_MOVEMENT_RADIUS, m_MovementRadius); WRITE_MICRO_CHUNK (csave, VARID_SPLINE_TIME, m_SplineTime); WRITE_MICRO_CHUNK (csave, VARID_VELOCITY, m_Velocity); WRITE_MICRO_CHUNK (csave, VARID_CURRENT_ACTION, m_CurrentAction); WRITE_MICRO_CHUNK (csave, VARID_MOVEMENT_DIRECTIONS, m_MovementDirections); WRITE_MICRO_CHUNK (csave, VARID_PATH_OBJECT, m_PathObject); WRITE_MICRO_CHUNK (csave, VARID_START_TIME, m_StartTime); WRITE_MICRO_CHUNK (csave, VARID_END_TIME, m_EndTime); WRITE_MICRO_CHUNK (csave, VARID_ISLOOPING, m_IsLooping); WRITE_MICRO_CHUNK (csave, VARID_TOTAL_DIST, m_TotalDist); PathClass *this_ptr = this; WRITE_MICRO_CHUNK (csave, VARID_OLD_PTR, this_ptr); // // Save each of the action nodes for this path // for (int index = 0; index < m_PathActions.Count (); index ++) { PATH_NODE &node = m_PathActions[index]; WRITE_MICRO_CHUNK (csave, VARID_PATH_ACTION, node); } csave.End_Chunk (); return ; } //////////////////////////////////////////////////////////////////////////////////////////// // // Load // //////////////////////////////////////////////////////////////////////////////////////////// void PathClass::Load (ChunkLoadClass &cload) { while (cload.Open_Chunk ()) { switch (cload.Cur_Chunk_ID ()) { case CHUNKID_SPLINE: { // // Use the spline's factory to load it from disk // if (cload.Open_Chunk ()) { PersistFactoryClass *factory = SaveLoadSystemClass::Find_Persist_Factory (cload.Cur_Chunk_ID ()); if (factory != NULL) { m_Spline = (Curve3DClass *)factory->Load (cload); } cload.Close_Chunk (); } } break; case CHUNKID_VARIABLES: Load_Variables (cload); break; } cload.Close_Chunk (); } return ; } /////////////////////////////////////////////////////////////////////// // // Load_Variables // /////////////////////////////////////////////////////////////////////// void PathClass::Load_Variables (ChunkLoadClass &cload) { PathClass *old_ptr = NULL; // // Loop through all the microchunks that define the variables // while (cload.Open_Micro_Chunk ()) { switch (cload.Cur_Micro_Chunk_ID ()) { READ_MICRO_CHUNK (cload, VARID_STATE, m_State); READ_MICRO_CHUNK (cload, VARID_TRAVERSAL_TYPE, m_TraversalType); READ_MICRO_CHUNK (cload, VARID_START_POS, m_StartPos); READ_MICRO_CHUNK (cload, VARID_DEST_POS, m_DestPos); READ_MICRO_CHUNK (cload, VARID_EXPECTED_POS, m_ExpectedPos); READ_MICRO_CHUNK (cload, VARID_LOOK_AHEAD_DIST, m_LookAheadDist); READ_MICRO_CHUNK (cload, VARID_LOOK_AHEAD_TIME, m_LookAheadTime); READ_MICRO_CHUNK (cload, VARID_MOVEMENT_RADIUS, m_MovementRadius); READ_MICRO_CHUNK (cload, VARID_SPLINE_TIME, m_SplineTime); READ_MICRO_CHUNK (cload, VARID_VELOCITY, m_Velocity); READ_MICRO_CHUNK (cload, VARID_CURRENT_ACTION, m_CurrentAction); READ_MICRO_CHUNK (cload, VARID_MOVEMENT_DIRECTIONS, m_MovementDirections); READ_MICRO_CHUNK (cload, VARID_PATH_OBJECT, m_PathObject); READ_MICRO_CHUNK (cload, VARID_START_TIME, m_StartTime); READ_MICRO_CHUNK (cload, VARID_END_TIME, m_EndTime); READ_MICRO_CHUNK (cload, VARID_ISLOOPING, m_IsLooping); READ_MICRO_CHUNK (cload, VARID_TOTAL_DIST, m_TotalDist); READ_MICRO_CHUNK (cload, VARID_OLD_PTR, old_ptr); case VARID_PATH_ACTION: { // // Read the action information from disk and add it to our list // PATH_NODE node; LOAD_MICRO_CHUNK (cload, node); m_PathActions.Add (node); } break; } cload.Close_Micro_Chunk (); } // // Register our old ptr so other objects can remap to us // if (old_ptr != NULL) { SaveLoadSystemClass::Register_Pointer( old_ptr, this ); } return ; }