| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662 |
- /*
- ** 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/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<SectorEdgeClass>
- {
- public:
- SectorEdgeTableClass(int count) : DynamicVectorClass<SectorEdgeClass> (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; i<Count(); i++) {
- if (edge == (*this)[i]) {
- (*this)[i].Increment_Instance_Count();
- return;
- }
- }
-
- /*
- ** If we fall through to here, add the edge to our array
- */
- Add(edge);
- }
- /************************************************************************************************
- **
- ** VisSectorSamplerClass Implementation
- **
- ************************************************************************************************/
- VisSectorSamplerClass::VisSectorSamplerClass
- (
- SceneEditorClass * scene,
- VisGenProgressClass * stats,
- float min_sample_distance,
- int collision_group
- ) :
- Scene(NULL),
- MeshBuilder(NULL),
- Stats(stats),
- MinSampleDistance(min_sample_distance),
- CollisionGroup(collision_group),
- EdgeSkipAccum(0.0f)
- {
- REF_PTR_SET(Scene,scene);
- MeshBuilder = new MeshBuilderClass;
- }
- VisSectorSamplerClass::~VisSectorSamplerClass(void)
- {
- if (MeshBuilder != NULL) {
- delete MeshBuilder;
- MeshBuilder = NULL;
- }
- REF_PTR_RELEASE(Scene);
- }
- void VisSectorSamplerClass::Process(RenderObjClass * model)
- {
- Reset(model->Get_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; fi<MeshBuilder->Get_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<edgetable.Count(); ei++) {
- if (edgetable[ei].Get_Instance_Count() == 1) {
-
- Vector3 edge_dir = edgetable[ei].Get_P1() - edgetable[ei].Get_P0();
- edge_dir.Z = 0.0f;
- float edge_len = edge_dir.Length();
- edge_dir /= edge_len;
-
- if (edge_len > 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;
- }
|