/* ** 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/Tools/LevelEdit/VisPointGenerator.cpp $Modtime:: $* * * * $Revision:: 25 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "stdafx.h" #include "vispointgenerator.h" #include "cameramgr.h" #include "utils.h" #include "mesh.h" #include "meshmdl.h" #include "vector3.h" #include "vector3i.h" #include "collisiongroups.h" #include "sceneeditor.h" #include "physcoltest.h" #include "hittestinfo.h" #include "node.h" #include "obbox.h" #include "vispointnode.h" #include "nodemgr.h" #include "staticphys.h" ////////////////////////////////////////////////////////////////////////// // // Constants // ////////////////////////////////////////////////////////////////////////// const float CEILING_CHECK_HEIGHT = 250.0F; const float FLOOR_CHECK_HEIGHT = -250.0F; const float MIN_CEILING_HEIGHT = 2.0F; const float PERCENT_FROM_COLLISION = 0.75F; const float DEF_POINT_RAISE_HEIGHT = 6.5F; const float DEF_INDOOR_POINT_RAISE_HEIGHT = 2.0f; const float FLAT_FACE_NORMAL_MIN = 0.0F; const float VIEWPLANE_BOX_HALF_DEPTH = 0.0005F; const float DEF_VIS_SAMPLE_HEIGHT = 20.0F; // we sample up to 20 meters above the vis sector const float VERTICAL_GRANULARITY = 5.0F; // granularity of sampling in the Z axis const float DEF_CAM_SIM_POINT_COUNT = 32.0F; const float CAMERA_SIM_RADIUS = 10.0F; const char * VIS_BIAS_STRING = "visbias"; ////////////////////////////////////////////////////////////////////////// // // VisPointGeneratorClass // ////////////////////////////////////////////////////////////////////////// VisPointGeneratorClass::VisPointGeneratorClass (float granularity) : m_Granularity (granularity), m_BaseGranularity (granularity), m_IgnoreBias (false), m_ViewPlaneExtent (0, 0, 0), m_WorldToGridTM (1), m_CurrentNode (NULL), m_StatWindow (NULL), m_pCameraSimOffsets (NULL), m_CurrentPointList (NULL), m_PolygonsProcessed (0), m_TotalPoints (0), m_CameraSimPointCount (DEF_CAM_SIM_POINT_COUNT), m_CurrentCeilingHeight (DEF_POINT_RAISE_HEIGHT), m_VisSampleHeight(DEF_VIS_SAMPLE_HEIGHT) { Initialize_Camera_Sim (); double hfov = 0; double vfov = 0; float znear = 0; float zfar = 0; ::Get_Scene_Editor ()->Get_Vis_Camera_FOV (hfov, vfov); ::Get_Scene_Editor ()->Get_Vis_Camera_Clip_Planes (znear, zfar); // // Calculate what the extents of a box would be if we // extruded the view plane by one millimeter. // Note: The extents are half-extents. // float y_dim = znear * ::tan (hfov / 2); float z_dim = znear * ::tan (vfov / 2); float max_dim = max (y_dim, z_dim); max_dim += 0.001F; m_ViewPlaneExtent.Set (max_dim, max_dim, max_dim); return ; } ////////////////////////////////////////////////////////////////////////// // // ~VisPointGeneratorClass // ////////////////////////////////////////////////////////////////////////// VisPointGeneratorClass::~VisPointGeneratorClass (void) { SAFE_DELETE_ARRAY (m_pCameraSimOffsets); // // Free the memory associated with the per-node point list // for (int index = 0; index < m_PointList.Count (); index ++) { VisPointListClass *sub_point_list = m_PointList[index]; SAFE_DELETE (sub_point_list); } m_PointList.Delete_All (); return ; } ////////////////////////////////////////////////////////////////////////// // // Is_Numerical // ////////////////////////////////////////////////////////////////////////// static inline bool Is_Numerical (char ch) { return ((ch >= '0' && ch <= '9') || ch == '-' || ch == '+' || ch == '.'); } ////////////////////////////////////////////////////////////////////////// // // Read_Float_Param // ////////////////////////////////////////////////////////////////////////// static bool Read_Float_Param (LPCTSTR text, LPCTSTR key, float *value) { bool retval = false; // // Attempt to find the 'key' in this text block // CString lowercase_text (text); lowercase_text.MakeLower (); LPCTSTR key_start = ::strstr (lowercase_text, key); if (key_start != NULL) { // // Move past the key designator // LPCTSTR key_value = key_start + ::lstrlen (key); // // Skip whitespace // while (key_value[0] != 0 && key_value[0] == ' ') { key_value ++; } if (key_value[0] == '=') { key_value ++; // // Skip whitespace // while (key_value[0] != 0 && key_value[0] == ' ') { key_value ++; } // // Determine how much of the string we should convert // to a number. // int index = 0; while (::Is_Numerical (key_value[index ++])) ; if (index > 1) { // // Convert the string to a float and return the value // to the caller. // char *number_str = new char[index]; ::lstrcpyn (number_str, key_value, index); (*value) = ::atof (number_str); delete [] number_str; retval = true; } } } return retval; } ////////////////////////////////////////////////////////////////////////// // // Determine_Granularity // ////////////////////////////////////////////////////////////////////////// void VisPointGeneratorClass::Determine_Granularity (MeshClass &mesh) { // // Default to the base granularity // m_Granularity = m_BaseGranularity; // // Should we use the vis-bias settings from the mesh? // if (m_IgnoreBias == false) { // // Check to see if this mesh uses a vis-bias parameter // float vis_bias = 1.0F; CString user_text = mesh.Get_User_Text (); if (::Read_Float_Param (user_text, VIS_BIAS_STRING, &vis_bias)) { // // Adjust the granularity based on the value of the // VisBias setting. // m_Granularity = m_Granularity / vis_bias; // // Ensure the bias is inside an exceptable range // m_Granularity = max (m_Granularity, 0.1F); m_Granularity = min (m_Granularity, 100.0F); } } return ; } ////////////////////////////////////////////////////////////////////////// // // Submit_Mesh // ////////////////////////////////////////////////////////////////////////// void VisPointGeneratorClass::Submit_Mesh (MeshClass &mesh) { DWORD start_ticks = ::GetTickCount (); // // Make sure we use the right granularity for this mesh // Determine_Granularity (mesh); // // Create a grid we can use to effeciently build a list // of vis points spaced 'granularity' meters apart. // AABoxClass box; mesh.Get_Obj_Space_Bounding_Box (box); int cells_x = (int)fabs(((box.Extent.X * 2) / m_Granularity)); int cells_y = (int)fabs(((box.Extent.Y * 2) / m_Granularity)); int cells_z = (int)fabs((((box.Extent.Z * 2) + DEF_POINT_RAISE_HEIGHT) / m_Granularity)); m_Grid.Create_Grid (Vector3 (cells_x + 1, cells_y + 1, cells_z + 1), 0); // // Create a transformation matrix that can convert // a vis-point from world space to 'grid' space. // Matrix3D object_tm = mesh.Get_Transform (); object_tm.Get_Orthogonal_Inverse (m_WorldToGridTM); Vector3 offset ( -(box.Center.X - box.Extent.X), -(box.Center.Y - box.Extent.Y), -(box.Center.Z - box.Extent.Z)); Vector3 pos = m_WorldToGridTM.Get_Translation (); pos += offset; m_WorldToGridTM.Set_Translation (pos); // // Get this mesh's polygon information // MeshModelClass *model = mesh.Get_Model (); if (model != NULL) { const TriIndex *poly_array = model->Get_Polygon_Array (); const Vector3 *vertex_array = model->Get_Vertex_Array (); const Vector4 *plane_array = model->Get_Plane_Array (true); // // Loop through all the polygons in the mesh // int poly_count = model->Get_Polygon_Count (); for (int index = 0; index < poly_count; index ++) { const TriIndex &poly_def = poly_array[index]; // Is this poly mostly flat? if (plane_array[index].Z > FLAT_FACE_NORMAL_MIN) { // // Get the verticies that make up the triangle // Vector3 point1 = vertex_array[poly_def.I]; Vector3 point2 = vertex_array[poly_def.J]; Vector3 point3 = vertex_array[poly_def.K]; // // Convert the points from obj-space to world space. // Matrix3D obj_tm = mesh.Get_Transform (); point1 = obj_tm * point1; point2 = obj_tm * point2; point3 = obj_tm * point3; // Determine what the center of the triangle is. Vector3 center = (point1 + point2 + point3) / 3; // // Try to submit a vis point for vertex #1 of this triangle. // Find_Valid_Points (point1 + ((center-point1) * 0.1F), center, Matrix3(1), false); // // Try to submit a vis point for vertex #2 of this triangle. // Find_Valid_Points (point2 + ((center-point2) * 0.1F), center, Matrix3(1), false); // // Try to submit a vis point for vertex #3 of this triangle. // Find_Valid_Points (point3 + ((center-point3) * 0.1F), center, Matrix3(1), false); // // Recursively subdivide and submit center points // Subdivide_And_Submit_Centers (point1, point2, point3); } m_PolygonsProcessed ++; } } MEMBER_RELEASE (model); //DWORD before_collection = ::GetTickCount (); // // Add all the vis points from the grid to the global list. // int cell_count = m_Grid.Get_Flat_Size (); for (int cell = 0; cell < cell_count; cell ++) { // // Add the vis point from this cell to the list. // //Matrix3D *vis_point = m_Grid.Get_At_Flat (cell); VisPointInfo *vis_point = m_Grid.Get_At_Flat (cell); if (vis_point != NULL) { // // Allocate a new point list for this point // m_CurrentPointList = new VisPointListClass; m_CurrentPointList->transform = vis_point->m_Transform; m_CurrentPointList->sample_point = vis_point->m_Transform.Get_Translation (); m_PointList.Add (m_CurrentPointList); m_TotalPoints ++; // // Add 'sub-points' to this point list // if (vis_point->m_DoCameraRing) { //Generate_Camera_Locations (vis_point->m_Transform); } // // Convert the transform from an obj transform to a camera transform // Matrix3D world_to_cam_tm (Vector3 (0, -1, 0), Vector3 (0, 0, 1), Vector3 (-1, 0, 0), Vector3 (0, 0, 0)); m_CurrentPointList->transform = m_CurrentPointList->transform * world_to_cam_tm; m_CurrentPointList->sample_point = m_CurrentPointList->transform.Get_Translation (); // Free the grid-vis point delete vis_point; } } /*DWORD finished_ticks = ::GetTickCount (); if (m_StatWindow != NULL) { CString stats; stats.Format ("Grid Size: %d, %d, %d.\nTotal Time: %d, Collection Time %d.", m_Grid.Get_Cells_X (), m_Grid.Get_Cells_Y (), m_Grid.Get_Cells_Z (), (finished_ticks - start_ticks), (finished_ticks - before_collection)); SetWindowText (m_StatWindow, stats); }*/ m_Grid.Free_Grid (); return ; } ///////////////////////////////////////////////////////////////////////// // // Subdivide_And_Submit_Centers // submits the center point of the given triangle and recursively // subdivides the triangle until it is smaller than the granularity // value. // ///////////////////////////////////////////////////////////////////////// void VisPointGeneratorClass::Subdivide_And_Submit_Centers ( const Vector3 & point1, const Vector3 & point2, const Vector3 & point3 ) { // submit the center point Vector3 center = (point1 + point2 + point3) / 3.0f; Find_Valid_Points (center, center, Matrix3(1), false); // recurse if the edge lengths are greater than 2*granularity float e0len = (point2 - point1).Length2(); float e1len = (point3 - point2).Length2(); float e2len = (point1 - point3).Length2(); float elen = m_Granularity * m_Granularity * 2.0f; if ( (e0len > elen) || (e1len > elen) || (e2len > elen) ) { // subdivide into four tris Vector3 p12 = (point1 + point2) * 0.5f; Vector3 p23 = (point2 + point3) * 0.5f; Vector3 p31 = (point3 + point1) * 0.5f; Subdivide_And_Submit_Centers(point1,p12,p31); Subdivide_And_Submit_Centers(p12,point2,p23); Subdivide_And_Submit_Centers(p23,point3,p31); Subdivide_And_Submit_Centers(p12,p23,p31); } } ///////////////////////////////////////////////////////////////////////// // // Find_Valid_Point // ///////////////////////////////////////////////////////////////////////// bool VisPointGeneratorClass::Find_Valid_Points ( const Vector3 &start, const Vector3 &end, const Matrix3 &orientation, bool do_camera_ring ) { bool retval = false; // // Try a few different points along the line from [start, end]. // const int MAX_ATTEMPTS = 5; // // If the start and end point are very close together, only make // one attempt. // int max_attempts = 5; if ((end - start).Length() < 0.01f) { max_attempts = 1; } for (int attempt = 0; (attempt < max_attempts) && !retval; attempt ++) { // // Build a transform to test along the provided line segment // float percent = ((float)attempt) / ((float)MAX_ATTEMPTS); Vector3 position = start + ((end - start) * percent); #if 0 // // Check to make sure this vis-point isn't under a hill or mountain. // If the vis-point's ceiling checks out, then raise the point up // to the approximate camera height in the game. // float ceiling_dist = 0; if ( Check_Ceiling (position, &ceiling_dist) && (ceiling_dist > MIN_CEILING_HEIGHT)) { bool retval = true; if (ceiling_dist < HEIGHT_SPREAD_DISTANCE) { // // The ceiling is low enough where we should just submit one point // position.Z += DEF_INDOOR_POINT_RAISE_HEIGHT; retval &= Submit_Point (Matrix3D (position), do_camera_ring); } else { // // The ceiling is high enough to try spreading points vertically // float delta_z = HEIGHT_SPREAD_DELTA; while (retval && delta_z <= HEIGHT_SPREAD_DISTANCE) { Vector3 point_pos = position; point_pos.Z += delta_z; retval &= Submit_Point (Matrix3D (point_pos), do_camera_ring); delta_z += HEIGHT_SPREAD_DELTA; } } } #else // // Search for the proper interval to sample above this sector. // We search upwards for "floor" and "roof" polygons. We start above // the highest floor encountered and sample up until we hit the lowest // "roof" encountered. // If any floor is encountered that is itself another vis sector, we don't // generate sample points at this location for this vis sector. // bool done = false; bool ok_to_sample = false; Vector3 start_point(position.X,position.Y,position.Z + 0.001f); Vector3 end_point(position.X,position.Y,position.Z + m_VisSampleHeight); while (!done) { CastResultStruct res; LineSegClass ray (start_point, end_point); PhysRayCollisionTestClass raytest (ray, &res, GAME_COLLISION_GROUP, COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VIS); raytest.CheckDynamicObjs = false; ::Get_Scene_Editor ()->Cast_Ray (raytest); if (res.Fraction == 1.0f) { done = true; } else { Vector3 point; ray.Compute_Point(res.Fraction,&point); PhysClass *physobj = raytest.CollidedPhysObj; bool is_vis_sector = (physobj->As_StaticPhysClass() && physobj->As_StaticPhysClass()->Is_Vis_Sector()); if ((is_vis_sector) || (res.Normal.Z < 0.0f)) { // // If we hit a roof or another vis sector, snap the end point down and signal that we're done // end_point.Z = point.Z; ok_to_sample = true; done = true; } else { // // If we hit a floor (or anything else), "ratchet" up past that point // start_point.Z = point.Z + 0.001f; } } } // // If we didn't find a roof within the VIS_SAMPLE_HEIGHT (20 meters) // then we'll just do a sanity check to make sure we're not underneath a backface above // the 20 meter sampling distance. The purpose of this is to try to avoid // generating sample points that will have to be rejected. These types of points can // easily be generated when a vis sector protrudes underneath a hill or something else big. // If this check passes, then it is ok to sample. // if (!ok_to_sample) { float dist = 0.0f; ok_to_sample = Check_Ceiling(end_point,&dist); } // // Now generate samples between start_point and end_point // if (ok_to_sample) { retval = true; Vector3 sample_point(start_point); if (DEF_INDOOR_POINT_RAISE_HEIGHT < end_point.Z - sample_point.Z) { sample_point.Z += DEF_INDOOR_POINT_RAISE_HEIGHT; } else { sample_point.Z = end_point.Z - 0.1f; } while (sample_point.Z < end_point.Z) { retval &= Submit_Point (Matrix3D(sample_point), do_camera_ring); sample_point.Z += VERTICAL_GRANULARITY; } } #endif } return retval; } ///////////////////////////////////////////////////////////////////////// // // Check_Ceiling // // This method casts a ray straight up from the candiate vis-point // and checks to see if the object it hits (if any) is a valid // vis 'ceiling'. This makes sure we don't generate points that are // inside of hills or mountains. // ///////////////////////////////////////////////////////////////////////// bool VisPointGeneratorClass::Check_Ceiling (const Vector3 &position, float *ceiling_dist) { bool retval = true; // // Build a ray from the given position up 100 meters in the air. // Vector3 start_point = position + Vector3 (0, 0, 0.001F); Vector3 end_point = position + Vector3 (0, 0, CEILING_CHECK_HEIGHT); LineSegClass ray (start_point, end_point); // // Cast the ray into the world and see what it hits. // CastResultStruct res; PhysRayCollisionTestClass raytest (ray, &res, GAME_COLLISION_GROUP, COLLISION_TYPE_PHYSICAL | COLLISION_TYPE_VIS); ::Get_Scene_Editor ()->Cast_Ray (raytest); // // Return how far above us the ceiling is. // if (ceiling_dist != NULL) { (*ceiling_dist) = (res.Fraction * (end_point.Z - start_point.Z)); } // // Did we hit anything? // if (res.Fraction < 1.0F) { // Get the physics object we hit PhysClass *physobj = raytest.CollidedPhysObj; if (physobj != NULL) { // // Check to see if the node we hit is a static tile. Otherwise // we don't care about it. // /*HITTESTINFO *hittest = (HITTESTINFO *)physobj->Peek_Model ()->Get_User_Data (); if ( (hittest != NULL) && (hittest->Type == HITTESTINFO::Node) && (hittest->node != NULL) && (hittest->node->Is_Static ()))*/ if (physobj->As_StaticPhysClass () != NULL) { // If this polygon is facing up, then make sure it satisfies // our 'ceiling' requirements for back-face polygons. if ((res.Normal.Z > 0) && Is_Object_Invalid_Roof (physobj->Peek_Model ())) { retval = false; } } } } return retval; } ///////////////////////////////////////////////////////////////////////// // // Is_Grid_Cell_Empty // ///////////////////////////////////////////////////////////////////////// bool VisPointGeneratorClass::Is_Grid_Cell_Empty (const Vector3 &position) { Vector3 grid_pos = m_WorldToGridTM * position; grid_pos.X = grid_pos.X / m_Granularity; grid_pos.Y = grid_pos.Y / m_Granularity; grid_pos.Z = grid_pos.Z / m_Granularity; return (m_Grid.Get_At (grid_pos.X, grid_pos.Y, grid_pos.Z) == NULL); } ///////////////////////////////////////////////////////////////////////// // // Submit_Point // ///////////////////////////////////////////////////////////////////////// bool VisPointGeneratorClass::Submit_Point ( const Matrix3D & vis_transform, bool do_camera_ring ) { bool was_valid = true; // // Convert the world-space position of the matrix to the // grid-space position. // Vector3 grid_pos = vis_transform.Get_Translation (); grid_pos = m_WorldToGridTM * grid_pos; grid_pos.X = grid_pos.X / m_Granularity; grid_pos.Y = grid_pos.Y / m_Granularity; grid_pos.Z = grid_pos.Z / m_Granularity; grid_pos.X = WWMath::Clamp (grid_pos.X, 0, (float)(m_Grid.Get_Cells_X () - 1)); grid_pos.Y = WWMath::Clamp (grid_pos.Y, 0, (float)(m_Grid.Get_Cells_Y () - 1)); grid_pos.Z = WWMath::Clamp (grid_pos.Z, 0, (float)(m_Grid.Get_Cells_Z () - 1)); /*grid_pos.X = max (0.0F, grid_pos.X); grid_pos.Y = max (0.0F, grid_pos.Y); grid_pos.Z = max (0.0F, grid_pos.Z); grid_pos.X = min (0.0F, grid_pos.X); grid_pos.Y = min (0.0F, grid_pos.Y); grid_pos.Z = min (0.0F, grid_pos.Z);*/ float half_gran = m_Granularity / 2; Vector3 cell_center ((grid_pos.X * m_Granularity) + half_gran, (grid_pos.Y * m_Granularity) + half_gran, (grid_pos.Z * m_Granularity) + half_gran); Matrix3D grid_to_world_tm; m_WorldToGridTM.Get_Orthogonal_Inverse (grid_to_world_tm); cell_center = grid_to_world_tm * cell_center; float new_point_dist = (vis_transform.Get_Translation () - cell_center).Length (); // // Only insert the point into the grid if the grid cell is // empty. // VisPointInfo *cell_contents = m_Grid.Get_At (grid_pos.X, grid_pos.Y, grid_pos.Z); if ((cell_contents == NULL) || ((cell_contents->m_Transform.Get_Translation () - cell_center).Length () > new_point_dist)) { // // See if the camera would intersect a wall if a vis point // were positioned here. // was_valid = Do_View_Planes_Pass (vis_transform); if (1 || was_valid) { // // Convert the world-space transform to a // camera space transform. // //Matrix3D world_to_cam_tm (Vector3 (0, -1, 0), Vector3 (0, 0, 1), Vector3 (-1, 0, 0), Vector3 (0, 0, 0)); VisPointInfo *vis_info = new VisPointInfo; vis_info->m_Transform = vis_transform; vis_info->m_DoCameraRing = do_camera_ring; // Insert this point into the grid m_Grid.Set_At (grid_pos.X, grid_pos.Y, grid_pos.Z, vis_info); SAFE_DELETE (cell_contents); } } return was_valid; } ///////////////////////////////////////////////////////////////////////// // // Do_View_Planes_Pass // // This method checks the view-plane in each of the six directions around // the candidate point and determines if any of them intersect a 'wall'. // ///////////////////////////////////////////////////////////////////////// bool VisPointGeneratorClass::Do_View_Planes_Pass (const Matrix3D &vis_transform) { bool retval = true; Vector3 center = vis_transform.Get_Translation (); Matrix3 orig_basis (vis_transform); // // Loop through and test each of the 6 orienations // of the view plane to make sure none of them intersect // a 'wall'. // /*CastResultStruct result; for (int index = 0; (index < VIS_RENDER_DIRECTIONS) && retval; index ++) { // // Build the orientation of the view-plane // Matrix3 basis = orig_basis; basis.Rotate_X (VIS_RENDER_TABLE[index].X); basis.Rotate_Y (VIS_RENDER_TABLE[index].Y); basis.Rotate_Z (VIS_RENDER_TABLE[index].Z);*/ // // Create a box representing the view plane // //Vector3 AABoxClass box (center, m_ViewPlaneExtent); // // Check to see if this viewplane 'box' collides // with anything. // //result.Reset (); CastResultStruct result; PhysAABoxCollisionTestClass collision_test (box, Vector3(0,0,0), &result, GAME_COLLISION_GROUP, COLLISION_TYPE_ALL); SceneEditorClass *scene = ::Get_Scene_Editor (); scene->Cast_AABox (collision_test); retval = (result.StartBad == false) && !(result.Fraction < 1.0F); //} return retval; } ///////////////////////////////////////////////////////////////////////// // // Test_Camera_Sim_Point // ///////////////////////////////////////////////////////////////////////// bool VisPointGeneratorClass::Test_Camera_Sim_Point ( const Vector3 & start_point, const Vector3 & end_point, Matrix3D * transform_result, NodeClass ** node_result ) { bool retval = false; AABoxClass box (start_point, m_ViewPlaneExtent); Vector3 sweep_vector = end_point - start_point; // // Check to see if the camera's 'box' collides with anything. // CastResultStruct result; PhysAABoxCollisionTestClass collision_test (box, sweep_vector, &result, GAME_COLLISION_GROUP, COLLISION_TYPE_ALL); SceneEditorClass *scene = ::Get_Scene_Editor (); scene->Cast_AABox (collision_test); // // If we collided with an object, then move the sample point in a little... // Vector3 test_point = end_point; if (result.Fraction < 1.0F) { test_point = start_point + (sweep_vector * result.Fraction * PERCENT_FROM_COLLISION); } // // Find the node this point lies over // NodeClass *node = Find_Floor_Node (test_point); // // If the point doesn't lie over the current node, then // we've successfully found a new point. // if ((node != m_CurrentNode) && (node != NULL)) { (*node_result) = node; (*transform_result).Make_Identity (); (*transform_result).Obj_Look_At (test_point, start_point, 0); retval = true; } return retval; } ///////////////////////////////////////////////////////////////////////// // // Find_Floor_Node // ///////////////////////////////////////////////////////////////////////// NodeClass * VisPointGeneratorClass::Find_Floor_Node (const Vector3 &start_point) { NodeClass *node = NULL; // // Build a ray from the given position down 1000 meters // Vector3 end_point = start_point + Vector3 (0, 0, FLOOR_CHECK_HEIGHT); LineSegClass ray (start_point, end_point); // // Cast the ray into the world and see what it hits. // CastResultStruct res; PhysRayCollisionTestClass raytest (ray, &res, GAME_COLLISION_GROUP, COLLISION_TYPE_ALL); ::Get_Scene_Editor ()->Cast_Ray (raytest); // // Did we hit anything? // if (res.Fraction < 1.0F) { // Get the physics object we hit PhysClass *physobj = raytest.CollidedPhysObj; if (physobj != NULL) { // // Check to see if the 'node' we hit is different then the node // we are currently processing. // HITTESTINFO *hittest = (HITTESTINFO *)physobj->Peek_Model ()->Get_User_Data (); if ( (hittest != NULL) && (hittest->Type == HITTESTINFO::Node)) { // // Pass the node we found back to the caller. // node = hittest->node; } } } return node; } ///////////////////////////////////////////////////////////////////////// // // Is_Object_Invalid_Roof // // This method determines if it is valid for a vis-point to be generated // underneath a given object. // // An object is considered 'invalid' for a vis-point's 'roof' if: // // - The mesh's polygons are visible. // - The mesh's polygons are single-sided // - The mesh's polygons can be physically collideable. // - The mesh is marked for vis generation itself. // ///////////////////////////////////////////////////////////////////////// bool VisPointGeneratorClass::Is_Object_Invalid_Roof (RenderObjClass *render_obj) { bool retval = true; // // Loop through all the render object's sub-objects // int count = render_obj->Get_Num_Sub_Objects (); for (int index = 0; (index < count) && retval; index ++) { // // Check this subobject // RenderObjClass *sub_object = render_obj->Get_Sub_Object (index); if (sub_object != NULL) { retval &= Is_Object_Invalid_Roof (sub_object); MEMBER_RELEASE (sub_object); } } // // Is this render object a mesh? // if (render_obj->Class_ID () == RenderObjClass::CLASSID_MESH) { MeshModelClass *model = ((MeshClass *)render_obj)->Get_Model (); if (model != NULL) { // // The mesh is invalid if: // // a) The mesh's polys are single-sided AND // b) The mesh is a vis-sector AND // c) The mesh is physically collideable AND // d) The mesh visible // retval &= (model->Get_Flag (MeshModelClass::TWO_SIDED) != MeshModelClass::TWO_SIDED); //retval &= ((render_obj->Get_Collision_Type () & COLLISION_TYPE_VIS) == COLLISION_TYPE_VIS); retval &= ((render_obj->Get_Collision_Type () & COLLISION_TYPE_PHYSICAL) == COLLISION_TYPE_PHYSICAL); retval &= (render_obj->Is_Not_Hidden_At_All () != 0); MEMBER_RELEASE (model); } } return retval; } ///////////////////////////////////////////////////////////////////////// // // Initialize_Camera_Sim // ///////////////////////////////////////////////////////////////////////// void VisPointGeneratorClass::Initialize_Camera_Sim (void) { SAFE_DELETE_ARRAY (m_pCameraSimOffsets); m_pCameraSimOffsets = new Vector3[m_CameraSimPointCount]; for (int index = 0; index < m_CameraSimPointCount; index ++) { // // Calculate an arbitrary number of points around a circle // and store these 'offsets' in a vector array. // float angle = ((2 * 3.14159265359F) * index) / m_CameraSimPointCount; float x = CAMERA_SIM_RADIUS * ::cos (angle); float y = CAMERA_SIM_RADIUS * ::sin (angle); m_pCameraSimOffsets[index] = Vector3 (y, -x, 0); } return ; } ///////////////////////////////////////////////////////////////////////// // // Generate_Camera_Locations // // This method generates extra phantom vis-points for a given position to // simulate areas the camera can be. These points are checked to see if // they are over a different mesh. If they are over a different mesh, // then the height information is used by the mesh when generating its // vis-points; // ///////////////////////////////////////////////////////////////////////// void VisPointGeneratorClass::Generate_Camera_Locations (const Matrix3D &real_vis_point) { Vector3 position = real_vis_point.Get_Translation (); struct CAMERA_SIM_POINTS { Matrix3D vis_point; NodeClass * node; }; CAMERA_SIM_POINTS *sim_points = new CAMERA_SIM_POINTS[m_CameraSimPointCount]; // // Loop through each camera simulation point (they lie // in a circle around the position) // for (int index = 0; index < m_CameraSimPointCount; index ++) { // // Build a ray from the given position down 1000 meters // Vector3 end_point = position + m_pCameraSimOffsets[index]; sim_points[index].vis_point.Make_Identity (); sim_points[index].node = NULL; // // Test the point to see if lies over another mesh // Test_Camera_Sim_Point ( position, end_point, &sim_points[index].vis_point, &sim_points[index].node); } // // Now submit the 'middle' point of each node series // int start_index = 0; NodeClass *current_node = NULL; for (index = 0; index < m_CameraSimPointCount; index ++) { // // Has the node changed? // if ((sim_points[index].node != current_node) || (index == m_CameraSimPointCount - 1)) { if (index > 0) { // // Attempt to find the 'middle' of the series // int middle_index = start_index + ((index - start_index) >> 1); bool found = false; int max = ((index - start_index) >> 1) + 1; for (int offset = 0; (offset < max) && !found; offset ++) { if (((middle_index + offset) < index) && (sim_points[middle_index + offset].node != NULL)) { middle_index += offset; found = true; } else if (((middle_index - offset) >= start_index) && (sim_points[middle_index - offset].node != NULL)) { middle_index -= offset; found = true; } } // // Add the point to our list // if (found) { // // Convert the transform from an obj trasform to a camera transform // Matrix3D world_to_cam_tm (Vector3 (0, -1, 0), Vector3 (0, 0, 1), Vector3 (-1, 0, 0), Vector3 (0, 0, 0)); Matrix3D camera_tm = sim_points[middle_index].vis_point * world_to_cam_tm; m_CurrentPointList->Add (camera_tm); m_TotalPoints ++; } } // // Start working on points over another node. // start_index = index; current_node = sim_points[index].node; } } // Free our 'sim-points' array delete [] sim_points; return ; } ///////////////////////////////////////////////////////////////////////// // // Set_Current_Node // ///////////////////////////////////////////////////////////////////////// void VisPointGeneratorClass::Set_Current_Node (NodeClass *node) { m_CurrentNode = node; return ; } ///////////////////////////////////////////////////////////////////////// // // Post_Process_Nodes // ///////////////////////////////////////////////////////////////////////// void VisPointGeneratorClass::Post_Process_Nodes (void) { return ; } ///////////////////////////////////////////////////////////////////////// // // Add_Manual_Nodes // ///////////////////////////////////////////////////////////////////////// void VisPointGeneratorClass::Add_Manual_Nodes (void) { /*VisPointNodeClass *vis_point = NULL; for ( vis_point = (VisPointNodeClass *)NodeMgrClass::Get_First (NODE_TYPE_VIS_POINT); vis_point != NULL; vis_point = (VisPointNodeClass *)NodeMgrClass::Get_Next (vis_point, NODE_TYPE_VIS_POINT)) { // // Convert the object transform to a camera transform // Matrix3D transform = vis_point->Get_Transform (); Matrix3D cam_transform (Vector3 (0, -1, 0), Vector3 (0, 0, 1), Vector3 (-1, 0, 0), Vector3 (0, 0, 0)); Matrix3D new_transform = transform * cam_transform; // // Allocate a new point list for this point // m_CurrentPointList = new VisPointListClass; m_CurrentPointList->transform = transform; m_CurrentPointList->sample_point = vis_point->Get_Vis_Tile_Location (); m_PointList.Add (m_CurrentPointList); m_TotalPoints ++; }*/ return ; }