1229 lines
34 KiB
C++
1229 lines
34 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/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 ;
|
|
}
|
|
|