/*
** Command & Conquer Generals Zero Hour(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 : WWShade *
* *
* $Archive:: wwshade/shdmesh.cpp $*
* *
* Org Author:: Jani P *
* *
* Author : Kenny Mitchell *
* *
* $Modtime:: 07/12/02 10:31a $*
* *
* $Revision:: 1 $*
* *
*---------------------------------------------------------------------------------------------*
*---------------------------------------------------------------------------------------------*/
#include "shdmesh.h"
#include "shdsubmesh.h"
#include "shdrenderer.h"
#include "rinfo.h"
#include "camera.h"
#include "dx8wrapper.h"
#include "wwdebug.h"
#include "wwprofile.h"
#include "mesh.h"
#include "meshmdl.h"
ShdMeshClass::ShdMeshClass()
: Name("UnNamed"),
LightEnvironment(NULL),
Applying_Shadow_Map(false)
{
}
ShdMeshClass::ShdMeshClass(const ShdMeshClass & src)
: RenderObjClass(src),
Name(src.Name),
LightEnvironment(NULL),
Applying_Shadow_Map(false)
{
Free();
SubMeshes.Resize(src.SubMeshes.Length());
for (int i=0;iGet_Polygon_Count();
}
return count;
}
int ShdMeshClass::Get_Num_Vertices(void) const
{
int count = 0;
for (int i=0; iGet_Vertex_Count();
}
return count;
}
void ShdMeshClass::Render(RenderInfoClass& rinfo)
{
WWPROFILE("ShdMeshClass::Render");
if (Is_Not_Hidden_At_All() == false)
{
return;
}
// DX8_RECORD_MESH_RENDER();
// TODO: Static sort lists
Set_Lighting_Environment(rinfo.light_environment);
const FrustumClass & frustum=rinfo.Camera.Get_Frustum();
// if rendering shadow remember camera info
if ((rinfo.Current_Override_Flags()&RenderInfoClass::RINFO_OVERRIDE_SHADOW_RENDERING))
{
// is generating shadow map
Set_Is_Self_Shadowed();
// set texture projector
Texture_Projector=rinfo.Texture_Projector;
Unset_Is_Applying_Shadow_Map();
}
else if (Is_Self_Shadowed())
{
// is applying shadow map
Set_Is_Applying_Shadow_Map();
Unset_Is_Self_Shadowed();
}
else
{
Unset_Is_Applying_Shadow_Map();
}
// TODO: What to do with SKINS?
if (1)//CollisionMath::Overlap_Test(frustum,Get_Bounding_Box())!=CollisionMath::OUTSIDE )
{
// bool rendered_something = false;
// TODO: Override flags, decals and material passes (probably in the submesh rendering)
for (int i=0;iRegister_Mesh(this,SubMeshes[i].Mesh);
}
SubMeshes[i].Renderer->Render(rinfo);
}
// TODO: RendererDebugger
}
}
//void ShdMeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * ib)
//{
//
//}
void ShdMeshClass::Special_Render(SpecialRenderInfoClass & rinfo)
{
}
/***********************************************************************************************
* ShdMeshClass::Cast_Ray -- compute a ray intersection with this mesh *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 6/17/98 GTH : Created. *
*=============================================================================================*/
bool ShdMeshClass::Cast_Ray(RayCollisionTestClass & raytest)
{
if ((Get_Collision_Type() & raytest.CollisionType) == 0) return false;
if ((Is_Translucent()!=0) && (!raytest.CheckTranslucent)) return false;
if (Is_Animation_Hidden()) return false;
if (raytest.Result->StartBad) return false;
Matrix3D world_to_obj;
Matrix3D world=Get_Transform();
// if aligned or oriented rotate the mesh so that it's aligned to the ray
/* if (Model->Get_Flag(MeshModelClass::ALIGNED)) {
Vector3 mesh_position;
world.Get_Translation(&mesh_position);
world.Obj_Look_At(mesh_position,mesh_position - raytest.Ray.Get_Dir(),0.0f);
} else if (Model->Get_Flag(MeshModelClass::ORIENTED)) {
Vector3 mesh_position;
world.Get_Translation(&mesh_position);
world.Obj_Look_At(mesh_position,raytest.Ray.Get_P0(),0.0f);
}
*/
world.Get_Orthogonal_Inverse(world_to_obj);
RayCollisionTestClass objray(raytest,world_to_obj);
for (int i=0;iCast_Ray(objray)) {
// transform result back into original coordinate system
raytest.CollidedRenderObj = this;
Matrix3D::Rotate_Vector(world,raytest.Result->Normal, &(raytest.Result->Normal));
if (raytest.Result->ComputeContactPoint) {
Matrix3D::Transform_Vector(world,raytest.Result->ContactPoint, &(raytest.Result->ContactPoint));
}
return true;
}
}
return false;
}
/***********************************************************************************************
* ShdMeshClass::Cast_AABox -- cast an AABox against this mesh *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 6/17/98 GTH : Created. *
*=============================================================================================*/
bool ShdMeshClass::Cast_AABox(AABoxCollisionTestClass & boxtest)
{
if ((Get_Collision_Type() & boxtest.CollisionType) == 0) return false;
if (boxtest.Result->StartBad) return false;
for (int i=0;iCast_World_Space_AABox(boxtest, Get_Transform())) {
boxtest.CollidedRenderObj = this;
return true;
}
}
return false;
}
/***********************************************************************************************
* Cast_OBBox -- Cast an obbox against this mesh *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 6/17/98 GTH : Created. *
*=============================================================================================*/
bool ShdMeshClass::Cast_OBBox(OBBoxCollisionTestClass & boxtest)
{
if ((Get_Collision_Type() & boxtest.CollisionType) == 0) return false;
if (boxtest.Result->StartBad) return false;
/*
** transform into the local coordinate system of the mesh.
*/
const Matrix3D & tm = Get_Transform();
Matrix3D world_to_obj;
tm.Get_Orthogonal_Inverse(world_to_obj);
OBBoxCollisionTestClass localtest(boxtest,world_to_obj);
for (int i=0;iCast_OBBox(localtest)) {
/*
** If we hit, transform the result of the test back to the original coordinate system.
*/
boxtest.CollidedRenderObj = this;
Matrix3D::Rotate_Vector(tm,boxtest.Result->Normal, &(boxtest.Result->Normal));
if (boxtest.Result->ComputeContactPoint) {
Matrix3D::Transform_Vector(tm,boxtest.Result->ContactPoint, &(boxtest.Result->ContactPoint));
}
return true;
}
}
return false;
}
/***********************************************************************************************
* ShdMeshClass::Intersect_AABox -- test for intersection with given AABox *
* *
* The AAbox given is assumed to be in world space. Since meshes aren't generally in world *
* space, the test must be transformed into our local coordinate system (which turns it into *
* an OBBox...) *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/19/00 gth : Created. *
*=============================================================================================*/
bool ShdMeshClass::Intersect_AABox(AABoxIntersectionTestClass & boxtest)
{
if ((Get_Collision_Type() & boxtest.CollisionType) == 0) return false;
Matrix3D inv_tm;
Get_Transform().Get_Orthogonal_Inverse(inv_tm);
OBBoxIntersectionTestClass local_test(boxtest,inv_tm);
for (int i=0;iIntersect_OBBox(local_test)) return true;
}
return false;
}
/***********************************************************************************************
* ShdMeshClass::Intersect_OBBox -- test for intersection with the given OBBox *
* *
* The given OBBox is assumed to be in world space so we need to transform it into the mesh's *
* local coordinate system. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/19/00 gth : Created. *
*=============================================================================================*/
bool ShdMeshClass::Intersect_OBBox(OBBoxIntersectionTestClass & boxtest)
{
if ((Get_Collision_Type() & boxtest.CollisionType) == 0) return false;
Matrix3D inv_tm;
Get_Transform().Get_Orthogonal_Inverse(inv_tm);
OBBoxIntersectionTestClass local_test(boxtest,inv_tm);
for (int i=0;iIntersect_OBBox(local_test)) return true;
}
return false;
}
/***********************************************************************************************
* ShdMeshClass::Add_Dependencies_To_List -- Add dependent files to the list. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 3/18/99 PDS : Created. *
* 6/05/02 KJM : Added dependency calculation for shader system
*=============================================================================================*/
void ShdMeshClass::Add_Dependencies_To_List
(
DynamicVectorClass &file_list,
bool textures_only
)
{
// loop through sub meshes
for (int i=0;iPeek_Shader();
for (int tidx=0;tidxGet_Texture_Count();tidx++)
{
TextureClass* texture=shd->Peek_Texture(tidx);
if (texture)
{
file_list.Add(texture->Get_Full_Path());
}
}
}
RenderObjClass::Add_Dependencies_To_List (file_list, textures_only);
return ;
}
/***********************************************************************************************
* ShdMeshClass::Update_Cached_Bounding_Volumes -- default collision sphere. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/14/2001 NH : Created. *
*=============================================================================================*/
void ShdMeshClass::Update_Cached_Bounding_Volumes(void) const
{
Get_Obj_Space_Bounding_Sphere(CachedBoundingSphere);
Matrix3D::Transform_Vector(Get_Transform(),CachedBoundingSphere.Center,&CachedBoundingSphere.Center);
// If we are camera-aligned or -oriented, we don't know which way we are facing at this point,
// so the box we return needs to contain the sphere. Otherewise do the normal computation.
/* if (Model->Get_Flag(MeshModelClass::ALIGNED) || Model->Get_Flag(MeshModelClass::ORIENTED)) {
CachedBoundingBox.Center = CachedBoundingSphere.Center;
CachedBoundingBox.Extent.Set(CachedBoundingSphere.Radius, CachedBoundingSphere.Radius, CachedBoundingSphere.Radius);
} else {
*/ Get_Obj_Space_Bounding_Box(CachedBoundingBox);
CachedBoundingBox.Transform(Get_Transform());
// }
Validate_Cached_Bounding_Volumes();
}
/***********************************************************************************************
* ShdMeshClass::Get_Obj_Space_Bounding_Sphere -- returns obj-space bounding sphere *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/19/00 gth : Created. *
*=============================================================================================*/
void ShdMeshClass::Get_Obj_Space_Bounding_Sphere(SphereClass & sphere) const
{
if (SubMeshes.Length()) {
SubMeshes[0].Mesh->Get_Bounding_Sphere(&sphere);
// If there are more than one submesh, merge all bounding spheres
for (int i=1;iGet_Bounding_Sphere(&tmp_s);
sphere+=tmp_s;
}
}
else {
sphere.Center.Set(0,0,0);
sphere.Radius = 1.0f;
}
}
/***********************************************************************************************
* ShdMeshClass::Get_Obj_Space_Bounding_Box -- returns the obj-space bounding box *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 1/19/00 gth : Created. *
*=============================================================================================*/
void ShdMeshClass::Get_Obj_Space_Bounding_Box(AABoxClass & box) const
{
if (SubMeshes.Length()) {
SubMeshes[0].Mesh->Get_Bounding_Box(&box);
// If there are more than one submesh, merge all bounding boxes
for (int i=1;iGet_Bounding_Box(&tmp_b);
box.Add_Box(tmp_b);
}
}
else {
box.Init(Vector3(0,0,0),Vector3(1,1,1));
}
}
void ShdMeshClass::Init_From_Legacy_Mesh(MeshClass* mesh)
{
Set_Name(mesh->Get_Name());
MeshModelClass* model=mesh->Peek_Model();
if (model)
{
int first_poly=0;
int sub_mesh_count=0;
while (first_polyGet_Polygon_Count()) {
sub_mesh_count++;
ShdSubMeshClass * sub_mesh = NEW_REF( ShdSubMeshClass, () );
sub_mesh->Init_From_Legacy_Mesh_Model(model,first_poly);
SubMeshes.Resize(sub_mesh_count);
SubMeshes[sub_mesh_count-1].Mesh=sub_mesh;
SubMeshes[sub_mesh_count-1].Renderer=NULL;
if (sub_mesh->Get_Visible_Polygon_Count()==0) break;
first_poly+=sub_mesh->Get_Visible_Polygon_Count();
}
}
// Pull interesting stuff out of the w3d attributes bits
Set_Collision_Type(mesh->Get_Collision_Type());
Set_Hidden(mesh->Is_Hidden());
Set_Translucent(mesh->Is_Translucent());
}
/***********************************************************************************************
* ShdMeshClass::Load -- creates a shader mesh out of a shader mesh chunk in a .w3d file *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/21/2002 KM : Created. *
*=============================================================================================*/
WW3DErrorType ShdMeshClass::Load_W3D(ChunkLoadClass& cload)
{
// Open the first chunk, it should be the shader mesh name
cload.Open_Chunk();
if (cload.Cur_Chunk_ID()!=W3D_CHUNK_SHDMESH_NAME)
{
WWDEBUG_SAY(("Invalid format shader mesh.\n"));
return WW3D_ERROR_LOAD_FAILED;
}
// read name
char buf[MAX_PATH];
cload.Read(buf,cload.Cur_Chunk_Length());
cload.Close_Chunk();
Set_Name(buf);
// open header
W3dShdMeshHeaderStruct hdr;
cload.Open_Chunk();
if
(
cload.Read
(
&hdr,
sizeof(W3dShdMeshHeaderStruct)
)!=sizeof(W3dShdMeshHeaderStruct)
)
{
return WW3D_ERROR_LOAD_FAILED;
}
cload.Close_Chunk();
// Process the header
// Set Bounding Info
Vector3 min(hdr.BoxMin.X,hdr.BoxMin.Y,hdr.BoxMin.Z);
Vector3 max(hdr.BoxMax.X,hdr.BoxMax.Y,hdr.BoxMax.Z);
CachedBoundingBox.Init_Min_Max(min,max);
Vector3 cntr(hdr.SphCenter.X,hdr.SphCenter.Y,hdr.SphCenter.Z);
CachedBoundingSphere.Init(cntr,hdr.SphRadius);
// Flags (todo)
// user text
// next are the sub meshes
Free();
SubMeshes.Resize(hdr.NumSubMeshes);
for (int i=0;iLoad_W3D(cload);
// assign each sub-mesh with a name in the format: .
StringClass tmp;
tmp.Format("%s.%d",Name,i);
ssmesh->Set_Name(tmp);
i++;
}
cload.Close_Chunk();
}
// Pull interesting stuff out of the w3d attributes bits
int col_bits = (hdr.Attributes & W3D_MESH_FLAG_COLLISION_TYPE_MASK) >> W3D_MESH_FLAG_COLLISION_TYPE_SHIFT;
Set_Collision_Type( col_bits << 1 );
Set_Hidden(hdr.Attributes & W3D_MESH_FLAG_HIDDEN);
for (i=0;iSet_Flag(MeshGeometryClass::CAST_SHADOW,shadow);
}
// Indicate whether this mesh is translucent. The mesh is considered translucent
// if sorting has been enabled (alpha blending on pass 0) or if pass0 contains alpha-test.
// This flag is used to determine if the mesh can cast a geometric shadow.
bool is_translucent = false;
for (i=0;iIs_Sorting()) {
Set_Translucent(true);
}
ShdInterfaceClass * shader = SubMeshes[i].Mesh->Peek_Shader();
if ((shader) && (!shader->Is_Opaque())) {
Set_Translucent(true);
}
}
}
return WW3D_ERROR_OK;
}
int ShdMeshClass::Get_Sub_Mesh_Count(void) const
{
return SubMeshes.Length();
}
ShdSubMeshClass * ShdMeshClass::Peek_Sub_Mesh(int i) const
{
return SubMeshes[i].Mesh;
}