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