This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Tools/LevelEdit/VisSectorSampler.cpp

662 lines
18 KiB
C++

/*
** 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;
}