/* ** 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/VisSectorSampler.cpp $* * * * Original Author:: Greg Hjelstrom * * * * $Author:: Patrick $* * * * $Modtime:: 11/27/01 12:21p $* * * * $Revision:: 6 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "stdafx.h" #include "VisSectorSampler.h" #include "SceneEditor.h" #include "VisGenProgress.h" #include "rendobj.h" #include "meshbuild.h" #include "vector.h" #include "pscene.h" #include "mesh.h" #include "meshmdl.h" #include "vector3.h" #include "vector4.h" #include "vector3i.h" #include "physcoltest.h" #include "phys.h" /* ** Compile time options */ #define USE_EDGE_SKIPPING 0 /* ** Constants */ const float SHRINKAGE_DISTANCE = 0.3f; // amount to move in from each edge const float FLOOR_SAMPLE_HEIGHT = 2.0f; // hieght off the floor for first sample const float CEILING_CHECK_HEIGHT = 250.0F; // how high to look for a ceiling /** ** SectorEdgeClass ** This is a description of a single edge in the sector mesh */ class SectorEdgeClass { public: SectorEdgeClass(void); SectorEdgeClass(const Vector3 & p0,const Vector3 & p1,int p0index,int p1index); SectorEdgeClass(const SectorEdgeClass & that); SectorEdgeClass & operator = (const SectorEdgeClass & that); bool operator == (const SectorEdgeClass & that) const; bool operator != (const SectorEdgeClass & that) const; const Vector3 & Get_P0(void) const { return P0; } const Vector3 & Get_P1(void) const { return P1; } const int Get_P0_Index(void) const { return P0Index; } const int Get_P1_Index(void) const { return P1Index; } void Set_P0(const Vector3 & p,int index) { P0 = p; P0Index = index; } void Set_P1(const Vector3 & p,int index) { P1 = p; P1Index = index; } void Increment_Instance_Count(void) { Counter++; } int Get_Instance_Count(void) { return Counter; } protected: Vector3 P0; Vector3 P1; int P0Index; int P1Index; int Counter; }; /** ** SectorEdgeTableClass ** This class is used to build the table of "outer" edges for a vis sector. Basically the ** goal is to have a list of all of the edges that apear in a mesh only once. */ class SectorEdgeTableClass : public DynamicVectorClass { public: SectorEdgeTableClass(int count) : DynamicVectorClass (count) { } ~SectorEdgeTableClass(void) { } void Add_Edge_Unique(const SectorEdgeClass & edge); }; /************************************************************************************************ ** ** SectorEdgeClass Implementation ** ************************************************************************************************/ SectorEdgeClass::SectorEdgeClass(void) : P0(0,0,0), P1(0,0,0), P0Index(0), P1Index(0), Counter(1) { } SectorEdgeClass::SectorEdgeClass(const Vector3 & p0,const Vector3 & p1,int p0index,int p1index) : P0(p0), P1(p1), P0Index(p0index), P1Index(p1index), Counter(1) { } SectorEdgeClass::SectorEdgeClass(const SectorEdgeClass & that) { *this = that; } SectorEdgeClass & SectorEdgeClass::operator = (const SectorEdgeClass & that) { if (this != &that) { P0 = that.P0; P1 = that.P1; P0Index = that.P0Index; P1Index = that.P1Index; Counter = 1; } return *this; } bool SectorEdgeClass::operator == (const SectorEdgeClass & that) const { return ( ((P0Index == that.P0Index) && (P1Index == that.P1Index)) || ((P0Index == that.P1Index) && (P1Index == that.P0Index)) ); } bool SectorEdgeClass::operator != (const SectorEdgeClass & that) const { return !(*this == that); } /************************************************************************************************ ** ** SectorEdgeTableClass Implementation ** ************************************************************************************************/ void SectorEdgeTableClass::Add_Edge_Unique(const SectorEdgeClass & edge) { /* ** See if we already have this edge in the table. */ for (int i=0; iGet_Num_Polys()); int count = Collect_Polygons(model); if (count > 0) { Sample_Edges(); } else { WWDEBUG_SAY(("Vis-sector %s had no up-facing polygons!\r\n",model->Get_Name())); } Stats->Increment_Processed_Node_Count(); } void VisSectorSamplerClass::Reset(int poly_count) { MeshBuilder->Reset(1,poly_count,0.5f*poly_count + 1); } int VisSectorSamplerClass::Collect_Polygons(RenderObjClass * renderobj) { WWASSERT(renderobj != NULL); int poly_count = 0; /* ** If this render object is a mesh and is marked as a VIS sector, then ** collect upward-facing polygons and submit them into the mesh builder */ if ( (renderobj->Get_Collision_Type () & COLLISION_TYPE_VIS) && (renderobj->Class_ID() == RenderObjClass::CLASSID_MESH) ) { MeshModelClass * model = ((MeshClass *)renderobj)->Get_Model(); if (model != NULL) { MeshBuilderClass::FaceClass face; /* ** Grab the relevent data from the mesh. */ 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); /* ** Add each up-facing polygon into our builder */ for (int pi = 0; pi < model->Get_Polygon_Count(); pi++) { if (plane_array[pi].Z > 0.3f) { /* ** Copy this face into the builder */ for (int vi=0; vi<3; vi++) { face.Verts[vi].Position = vertex_array[poly_array[pi][vi]]; } MeshBuilder->Add_Face(face); poly_count++; } } } } /* ** Recurse into all sub-meshes */ int count = renderobj->Get_Num_Sub_Objects(); for (int index = 0; index < count; index ++) { RenderObjClass *sub_object = renderobj->Get_Sub_Object(index); if (sub_object != NULL) { poly_count += Collect_Polygons(sub_object); REF_PTR_RELEASE(sub_object); } } return poly_count; } void VisSectorSamplerClass::Sample_Edges(void) { /* ** Tell the mesh builder to process its input. */ MeshBuilder->Build_Mesh(false); /* ** Build an array of all of the edges, marking duplicates for removal */ SectorEdgeTableClass edgetable(3 * MeshBuilder->Get_Face_Count()); for (int fi=0; fiGet_Face_Count(); fi++) { const MeshBuilderClass::FaceClass & face = MeshBuilder->Get_Face(fi); for (int vi=0; vi<3; vi++) { int v0 = vi; int v1 = (vi + 1) % 3; const MeshBuilderClass::VertClass & vertex0 = MeshBuilder->Get_Vertex(face.VertIdx[v0]); const MeshBuilderClass::VertClass & vertex1 = MeshBuilder->Get_Vertex(face.VertIdx[v1]); edgetable.Add_Edge_Unique(SectorEdgeClass(vertex0.Position,vertex1.Position,face.VertIdx[v0],face.VertIdx[v1])); } } /* ** For each mesh that has an instance count of 1, recursively sample vis along it */ for (int ei=0; ei 0.0f) { /* ** Skip the edge if it is short */ #if (USE_EDGE_SKIPPING) if (edge_len + EdgeSkipAccum < MinSampleDistance) { EdgeSkipAccum += edge_len; } else { #endif EdgeSkipAccum = 0.0f; /* ** Move "inward" and up in Z to hopefully generate valid points which don't ** cause the camera to intersect walls. */ Vector3 offset(-edge_dir.Y,edge_dir.X,0.0f); offset *= SHRINKAGE_DISTANCE; offset.Z += FLOOR_SAMPLE_HEIGHT; Vector3 p0 = edgetable[ei].Get_P0() + offset; Vector3 p1 = edgetable[ei].Get_P1() + offset; Sample_Edge(p0,p1); Stats->Increment_Edge_Count(); #if (USE_EDGE_SKIPPING) } #endif } } if (Stats->Is_Cancel_Requested()) { return; } } } void VisSectorSamplerClass::Sample_Edge(const Vector3 & p0,const Vector3 & p1) { /* ** Perform a vis sample at the center of the edge both near the ground ** and at the maximum height. */ int bits_changed = 0; Vector3 sample_point = 0.5f * (p0 + p1); bits_changed = Sample_Point(sample_point); /* ** Should we subdivide this edge and keep sampling? */ if ( (bits_changed > 0) && ((p1-p0).Quick_Length() > 2.0f*MinSampleDistance) ) { Sample_Edge(p0,sample_point); Sample_Edge(sample_point,p1); } } int VisSectorSamplerClass::Sample_Point(const Vector3 & point) { int bits_changed = 0; float ceiling_distance = 0.0f; /* ** Check the ceiling height */ if ((Check_Ceiling(point,&ceiling_distance) == true) && (ceiling_distance > 1.0f)) { if (ceiling_distance > 20.0f) { ceiling_distance = 20.0f; } Vector3 ceiling_point = point; ceiling_point.Z += ceiling_distance - 0.3f; /* ** Now, sample the bottom and top of the vertical segment, recording ** the amount of changes made to the vis vector */ int bits0 = 0,bits1 = 0; bits0 = Update_Vis(point); if (bits0 > 0) { bits1 = Update_Vis(ceiling_point); } bits_changed += bits0 + bits1; /* ** Recursively continue to sample vertically until we are making no ** more changes or the points get too close together */ if (bits1 > 0) { bits_changed += Sample_Vertical_Segment(point,ceiling_point); } } return bits_changed; } int VisSectorSamplerClass::Sample_Vertical_Segment(const Vector3 & p0,const Vector3 & p1) { Vector3 sample_point = 0.5f * (p0 + p1); int bits_changed = Update_Vis(sample_point); if ( (bits_changed > 0) && (p1.Z-p0.Z > 2.0f*MinSampleDistance) ) { bits_changed += Sample_Vertical_Segment(p0,sample_point); bits_changed += Sample_Vertical_Segment(sample_point,p1); } return bits_changed; } int VisSectorSamplerClass::Update_Vis(const Vector3 & point) { /* ** Perform the vis sample */ Matrix3D transform(Matrix3(1),point); VisSampleClass sample = Scene->Update_Vis(point,transform); Stats->Increment_Sample_Count(); /* ** Log the results with the scene editor */ VisLogClass &vis_log = Scene->Get_Vis_Log(); vis_log.Log_Sample(sample); Scene->Create_Vis_Point(transform); /* ** Return the number of bits changed by this sample */ if (sample.Sample_Rejected()) { return 0; } else { return sample.Get_Bits_Changed(); } } ///////////////////////////////////////////////////////////////////////// // // 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 VisSectorSamplerClass::Check_Ceiling (const Vector3 &position, float *ceiling_dist) { // // 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 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, CollisionGroup, COLLISION_TYPE_PHYSICAL); Scene->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) && (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_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 VisSectorSamplerClass::Is_Object_Invalid_Roof(RenderObjClass *render_obj) { bool retval = true; // // Recursively check all 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); REF_PTR_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); REF_PTR_RELEASE(model); } } return retval; } ///////////////////////////////////////////////////////////////////////// // // 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 VisSectorSamplerClass::Do_View_Planes_Pass (const Matrix3D &vis_transform) { bool retval = true; #if 0 // TODO! 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, CollisionGroup, COLLISION_TYPE_PHYSICAL); Scene->Cast_AABox (collision_test); retval = (result.StartBad == false) && !(result.Fraction < 1.0F); //} #endif return retval; }