/* ** 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 . */ /* $Header: /Commando/Code/ww3d2/mesh.cpp 70 3/14/02 2:39p Greg_h $ */ /*********************************************************************************************** *** Confidential - Westwood Studios *** *********************************************************************************************** * * * Project Name : Commando / G 3D engine * * * * File Name : MESH.CPP * * * * Programmer : Greg Hjelstrom * * * * Start Date : 06/11/97 * * * * Last Update : June 12, 1997 [GH] * * * *---------------------------------------------------------------------------------------------* * Functions: * * MeshClass::MeshClass -- Constructor for MeshClass * * MeshClass::MeshClass -- Copy Constructor for MeshClass * * MeshClass::operator == -- assignment operator for MeshClass * * MeshClass::~MeshClass -- destructor * * MeshClass::Contains -- Determines whether mesh contains a (worldspace) point. * * MeshClass::Free -- Releases all memory/assets in use by this mesh * * MeshClass::Clone -- Creates a clone of this mesh * * MeshClass::Get_Name -- returns the name of the mesh * * MeshClass::Set_Name -- sets the name of this mesh * * MeshClass::Get_W3D_Flags -- access to the W3D flags * * MeshClass::Get_User_Text -- access to the text buffer * * MeshClass::Scale -- Scales the mesh * * MeshClass::Scale -- Scales the mesh * * MeshClass::Init -- Init the mesh from a MeshBuilder object * * MeshClass::Load -- creates a mesh out of a mesh chunk in a .w3d file * * MeshClass::Cast_Ray -- compute a ray intersection with this mesh * * MeshClass::Cast_AABox -- cast an AABox against this mesh * * MeshClass::Cast_OBBox -- Cast an obbox against this mesh * * MeshClass::Intersect_AABox -- test for intersection with given AABox * * MeshClass::Intersect_OBBox -- test for intersection with the given OBBox * * MeshClass::Generate_Culling_Tree -- Generates a hierarchical culling tree for the mesh * * MeshClass::Direct_Load -- read the w3d file directly into this mesh object * * MeshClass::read_chunks -- read all of the chunks from the .wtm file * * MeshClass::read_vertices -- reads the vertex chunk * * MeshClass::read_texcoords -- read in the texture coordinates chunk * * MeshClass::install_texture_coordinates -- installs the given u-v's in each channel that is* * MeshClass::read_vertex_normals -- reads a surrender normal chunk from the wtm file * * MeshClass::read_v3_materials -- Reads in version 3 materials. * * MeshClass::read_map -- Reads definition of a texture map from the file * * MeshClass::read_triangles -- read the triangles chunk * * MeshClass::read_per_tri_materials -- read the material indices for each triangle * * MeshClass::read_user_text -- read in the user text chunk * * MeshClass::read_vertex_colors -- read in the vertex colors chunk * * MeshClass::read_vertex_influences -- read in the vertex influences chunk * * MeshClass::Get_Material_Info -- returns a pointer to the material info * * MeshClass::Create_Decal -- creates a decal on this mesh * * MeshClass::Delete_Decal -- removes a decal from this mesh * * MeshClass::Get_Num_Polys -- returns the number of polys (tris) in this mesh * * MeshClass::Render -- renders this mesh * * MeshClass::Special_Render -- special render function for meshes * * MeshClass::update_skin -- deforms the mesh * * MeshClass::clone_materials -- clone the materials for this mesh * * MeshClass::install_materials -- transfers the materials into the mesh * * MeshClass::Get_Model -- user access to the mesh model * * MeshClass::Get_Obj_Space_Bounding_Sphere -- returns obj-space bounding sphere * * MeshClass::Get_Obj_Space_Bounding_Box -- returns the obj-space bounding box * * MeshClass::Get_Deformed_Vertices -- Gets the deformed vertices for a skin * * MeshClass::Get_Deformed_Vertices -- Gets the deformed vertices for a skin * * MeshClass::Render_Material_Pass -- Render a procedural material pass for this mesh * * MeshClass::Replace_VertexMaterial -- Replaces existing vertex material with a new one. Wi * * MeshClass::Make_Unique -- Makes mesh unique in the renderer, but still shares system ram * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "mesh.h" #include #include #include "w3d_file.h" #include "assetmgr.h" #include "w3derr.h" #include "wwdebug.h" #include "vertmaterial.h" #include "shader.h" #include "matinfo.h" #include "htree.h" #include "meshbuild.h" #include "tri.h" #include "aaplane.h" #include "aabtree.h" #include "chunkio.h" #include "w3d_util.h" #include "meshmdl.h" #include "meshgeometry.h" #include "ww3d.h" #include "camera.h" #include "texture.h" #include "rinfo.h" #include "coltest.h" #include "inttest.h" #include "decalmsh.h" #include "decalsys.h" #include "dx8polygonrenderer.h" #include "dx8indexbuffer.h" #include "dx8renderer.h" #include "visrasterizer.h" #include "wwmemlog.h" #include "dx8rendererdebugger.h" #include #include static unsigned MeshDebugIdCount; bool MeshClass::Legacy_Meshes_Fogged = true; static SimpleDynVecClass temp_apt; /* ** This #define causes the collision code to always recompute the triangle normals rather ** than using the ones in the model. ** TODO: ensure that the models have unit normals and start re-using them again or write ** collision code to handle non-unit normals! */ #if (OPTIMIZE_PLANEEQ_RAM) #define COMPUTE_NORMALS #endif /* ** Temporary storage used during decal creation */ static DynamicVectorClass _TempVertexBuffer; /* ** Chunk ID's for saving user lighting */ enum { CHUNKID_USER_LIGHTING_ARRAY = 0x07520213, }; /*********************************************************************************************** * MeshClass::MeshClass -- Constructor for MeshClass * * * * Initializes an empty mesh class * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/6/98 GTH : Created. * *=============================================================================================*/ MeshClass::MeshClass(void) : Model(NULL), DecalMesh(NULL), LightEnvironment(NULL), BaseVertexOffset(0), NextVisibleSkin(NULL), IsDisabledByDebugger(false), MeshDebugId(MeshDebugIdCount++), UserLighting(NULL) { } /*********************************************************************************************** * MeshClass::MeshClass -- Copy Constructor for MeshClass * * * * Creates a mesh which is a copy of the given mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * *=============================================================================================*/ MeshClass::MeshClass(const MeshClass & that) : RenderObjClass(that), Model(NULL), DecalMesh(NULL), LightEnvironment(NULL), BaseVertexOffset(that.BaseVertexOffset), NextVisibleSkin(NULL), IsDisabledByDebugger(false), MeshDebugId(MeshDebugIdCount++), UserLighting(NULL) { REF_PTR_SET(Model,that.Model); // mesh instances share models by default } /*********************************************************************************************** * operator == -- assignment operator for MeshClass * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/6/98 GTH : Created. * *=============================================================================================*/ MeshClass & MeshClass::operator = (const MeshClass & that) { if (this != &that) { TheDX8MeshRenderer.Unregister_Mesh_Type(this); RenderObjClass::operator = (that); REF_PTR_SET(Model,that.Model); // mesh instances share models by default BaseVertexOffset = that.BaseVertexOffset; // just dont copy the decals or light environment REF_PTR_RELEASE(DecalMesh); LightEnvironment = NULL; if (UserLighting != NULL) { delete[] UserLighting; UserLighting = NULL; } } return * this; } /*********************************************************************************************** * MeshClass::~MeshClass -- destructor * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/6/98 GTH : Created. * *=============================================================================================*/ MeshClass::~MeshClass(void) { TheDX8MeshRenderer.Unregister_Mesh_Type(this); Free(); } /*********************************************************************************************** * MeshClass::Contains -- Determines whether mesh contains a (worldspace) point. * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: Assumes mesh is a closed manifold - results are undefined otherwise * * This function will NOT work for skins * * * * HISTORY: * * 8/30/00 NH : Created. * *=============================================================================================*/ bool MeshClass::Contains(const Vector3 &point) { // Transform point to object space and pass on to model Vector3 obj_point; Matrix3D::Inverse_Transform_Vector(Transform, point, &obj_point); return Model->Contains(obj_point); } /*********************************************************************************************** * MeshClass::Free -- Releases all memory/assets in use by this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/6/98 GTH : Created. * *=============================================================================================*/ void MeshClass::Free(void) { REF_PTR_RELEASE(Model); REF_PTR_RELEASE(DecalMesh); if (UserLighting != NULL) { delete[] UserLighting; UserLighting = NULL; } } /*********************************************************************************************** * MeshClass::Clone -- Creates a clone of this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/6/98 GTH : Created. * *=============================================================================================*/ RenderObjClass * MeshClass::Clone(void) const { return NEW_REF( MeshClass, (*this)); } /*********************************************************************************************** * MeshClass::Get_Name -- returns the name of the mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 5/15/98 GTH : Created. * *=============================================================================================*/ const char * MeshClass::Get_Name(void) const { return Model->Get_Name(); } /*********************************************************************************************** * MeshClass::Set_Name -- sets the name of this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/9/99 GTH : Created. * *=============================================================================================*/ void MeshClass::Set_Name(const char * name) { Model->Set_Name(name); } /*********************************************************************************************** * MeshClass::Get_W3D_Flags -- access to the W3D flags * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 5/15/98 GTH : Created. * *=============================================================================================*/ uint32 MeshClass::Get_W3D_Flags(void) { return Model->W3dAttributes; } /*********************************************************************************************** * MeshClass::Get_User_Text -- access to the text buffer * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 5/15/98 GTH : Created. * *=============================================================================================*/ const char * MeshClass::Get_User_Text(void) const { return Model->Get_User_Text(); } /*********************************************************************************************** * MeshClass::Get_Material_Info -- returns a pointer to the material info * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 5/20/98 GTH : Created. * *=============================================================================================*/ MaterialInfoClass * MeshClass::Get_Material_Info(void) { if (Model) { if (Model->MatInfo) { Model->MatInfo->Add_Ref(); return Model->MatInfo; } } return NULL; } /*********************************************************************************************** * MeshClass::Get_Model -- user access to the mesh model * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 2/4/99 GTH : Created. * *=============================================================================================*/ MeshModelClass * MeshClass::Get_Model(void) { if (Model != NULL) { Model->Add_Ref(); } return Model; } /*********************************************************************************************** * MeshClass::Scale -- Scales the mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * *=============================================================================================*/ void MeshClass::Scale(float scale) { if (scale==1.0f) return; Vector3 sc; sc.X = sc.Y = sc.Z = scale; Make_Unique(); Model->Make_Geometry_Unique(); Model->Scale(sc); Invalidate_Cached_Bounding_Volumes(); // Now update the object space bounding volumes of this object's container: RenderObjClass *container = Get_Container(); if (container) container->Update_Obj_Space_Bounding_Volumes(); } /*********************************************************************************************** * MeshClass::Scale -- Scales the mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/6/98 GTH : Created. * * 12/8/98 GTH : Modified the box scaling to use the non-uniform parameters * *=============================================================================================*/ void MeshClass::Scale(float scalex, float scaley, float scalez) { // scale the surrender mesh model Vector3 sc; sc.X = scalex; sc.Y = scaley; sc.Z = scalez; Make_Unique(); Model->Make_Geometry_Unique(); Model->Scale(sc); Invalidate_Cached_Bounding_Volumes(); // Now update the object space bounding volumes of this object's container: RenderObjClass *container = Get_Container(); if (container) container->Update_Obj_Space_Bounding_Volumes(); } /*********************************************************************************************** * MeshClass::Get_Deformed_Vertices -- Gets the deformed vertices for a skin * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/4/2001 gth : Created. * *=============================================================================================*/ void MeshClass::Get_Deformed_Vertices(Vector3 *dst_vert, Vector3 *dst_norm) { WWASSERT(Model->Get_Flag(MeshGeometryClass::SKIN)); Model->get_deformed_vertices(dst_vert,dst_norm,Container->Get_HTree()); } /*********************************************************************************************** * MeshClass::Get_Deformed_Vertices -- Gets the deformed vertices for a skin * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/4/2001 gth : Created. * *=============================================================================================*/ void MeshClass::Get_Deformed_Vertices(Vector3 *dst_vert) { WWASSERT(Model->Get_Flag(MeshGeometryClass::SKIN)); WWASSERT(Container != NULL); WWASSERT(Container->Get_HTree() != NULL); Model->get_deformed_vertices(dst_vert,Container->Get_HTree()); } void MeshClass::Compose_Deformed_Vertex_Buffer( VertexFormatXYZNDUV2* verts, const Vector2* uv0, const Vector2* uv1, const unsigned* diffuse) { WWASSERT(Model->Get_Flag(MeshGeometryClass::SKIN)); Model->compose_deformed_vertex_buffer(verts,uv0,uv1,diffuse,Container->Get_HTree()); } /*********************************************************************************************** * MeshClass::Create_Decal -- creates a decal on this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/26/00 gth : Created. * *=============================================================================================*/ void MeshClass::Create_Decal(DecalGeneratorClass * generator) { WWMEMLOG(MEM_GEOMETRY); if (WW3D::Are_Decals_Enabled() == false) { return; } if (Is_Translucent() && (generator->Is_Applied_To_Translucent_Meshes() == false)) { return; } if (!Model->Get_Flag(MeshGeometryClass::SKIN)) { // Rigid mesh Matrix3D modeltm_inv; OBBoxClass localbox; Get_Transform().Get_Orthogonal_Inverse(modeltm_inv); OBBoxClass::Transform(modeltm_inv, generator->Get_Bounding_Volume(), &localbox); // generate apt, if it is not empty, add a decal. temp_apt.Delete_All(false); // reset contents Model->Generate_Rigid_APT(localbox, temp_apt); if (temp_apt.Count() > 0) { if (DecalMesh == NULL) { DecalMesh = NEW_REF(RigidDecalMeshClass, (this, generator->Peek_Decal_System())); } DecalMesh->Create_Decal(generator, localbox, temp_apt); } } else { WWDEBUG_SAY(("PERFORMANCE WARNING: Decal being applied to a SKIN mesh!\r\n")); // Skin // The deformed worldspace vertices are used both for the APT and in Create_Decal() to // generate the texture coordinates. int vertex_count = Model->Get_Vertex_Count(); if (_TempVertexBuffer.Count() < vertex_count) _TempVertexBuffer.Resize(vertex_count); Vector3 *dst_vert = &(_TempVertexBuffer[0]); Get_Deformed_Vertices(dst_vert); // generate apt, if it is not empty, add a decal. temp_apt.Delete_All(false); OBBoxClass worldbox = generator->Get_Bounding_Volume(); // We compare the worldspace box vs. the worldspace vertices Model->Generate_Skin_APT(worldbox, temp_apt, dst_vert); // if it is not empty, add a decal if (temp_apt.Count() > 0) { if (DecalMesh == NULL) { DecalMesh = NEW_REF(SkinDecalMeshClass, (this, generator->Peek_Decal_System())); } DecalMesh->Create_Decal(generator, worldbox, temp_apt, &_TempVertexBuffer); } } } /*********************************************************************************************** * MeshClass::Delete_Decal -- removes a decal from this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/26/00 gth : Created. * *=============================================================================================*/ void MeshClass::Delete_Decal(uint32 decal_id) { if (DecalMesh != NULL) { DecalMesh->Delete_Decal(decal_id); } } /*********************************************************************************************** * MeshClass::Get_Num_Polys -- returns the number of polys (tris) in this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/6/98 GTH : Created. * *=============================================================================================*/ int MeshClass::Get_Num_Polys(void) const { if (Model) { int num_passes=Model->Get_Pass_Count(); WWASSERT(num_passes>0); int poly_count=Model->Get_Polygon_Count(); return num_passes*poly_count; } else { return 0; } } /*********************************************************************************************** * MeshClass::Render -- renders this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 12/10/98 GTH : Created. * *=============================================================================================*/ void MeshClass::Render(RenderInfoClass & rinfo) { WWPROFILE("Mesh::Render"); if (Is_Not_Hidden_At_All() == false) { return; } // If static sort lists are enabled and this mesh has a sort level, put it on the list instead // of rendering it. unsigned int sort_level = (unsigned int)Model->Get_Sort_Level(); if (WW3D::Are_Static_Sort_Lists_Enabled() && sort_level != SORT_LEVEL_NONE) { WW3D::Add_To_Static_Sort_List(this, sort_level); /* ** Plug in lighting so that when this mesh gets rendered later */ Set_Lighting_Environment(rinfo.light_environment); } else { /* ** Plug in the lighting environment unless we arrived here as part of the static ** sorting system being flushed */ if (WW3D::Are_Static_Sort_Lists_Enabled()) { Set_Lighting_Environment(rinfo.light_environment); } const FrustumClass & frustum=rinfo.Camera.Get_Frustum(); if ( Model->Get_Flag(MeshGeometryClass::SKIN) || CollisionMath::Overlap_Test(frustum,Get_Bounding_Box())!=CollisionMath::OUTSIDE ) { bool rendered_something = false; /* ** If this mesh model has never been rendered, we need to generate the DX8 datastructures */ if (PolygonRendererList.Is_Empty()) { Model->Register_For_Rendering(); TheDX8MeshRenderer.Register_Mesh_Type(this); } /* ** Process texture reductions: */ // Model->Process_Texture_Reduction(); /* ** Look up the FVF container that this mesh is in */ DX8FVFCategoryContainer * fvf_container = PolygonRendererList.Peek_Head()->Get_Texture_Category()->Get_Container(); /* ** Check if we should render the base passes. One special case here: if ** the mesh is translucent (alpha) and the base passes are disabled but we ** are rendering a shadow, we go ahead and render the base pass. This is an ugly way ** to get our tree shadows and other alpha textured shadows to work. */ bool render_base_passes = ((rinfo.Current_Override_Flags() & RenderInfoClass::RINFO_OVERRIDE_ADDITIONAL_PASSES_ONLY) == 0); bool is_alpha = (Model->Get_Single_Shader().Get_Alpha_Test() == ShaderClass::ALPHATEST_ENABLE) || (Model->Get_Single_Shader().Get_Src_Blend_Func() == ShaderClass::SRCBLEND_SRC_ALPHA); if ( (rinfo.Current_Override_Flags() & RenderInfoClass::RINFO_OVERRIDE_SHADOW_RENDERING) && (is_alpha == true)) { render_base_passes = true; } if (render_base_passes) { /* ** Link each polygon renderer for this mesh into the visible list */ DX8PolygonRendererListIterator it(&(PolygonRendererList)); while (!it.Is_Done()) { DX8PolygonRendererClass* polygon_renderer=it.Peek_Obj(); polygon_renderer->Get_Texture_Category()->Add_Render_Task(polygon_renderer,this); it.Next(); } rendered_something = true; } /* ** If the rendering context specifies procedural material passes, register them ** for rendering */ for (int i=0; iIs_Enabled_On_Translucent_Meshes())) { /* ** If the base pass for this mesh has been disabled, we have to make sure ** the procedural material pass is rendered after everything else has rendered */ if (rinfo.Current_Override_Flags() & RenderInfoClass::RINFO_OVERRIDE_ADDITIONAL_PASSES_ONLY) { fvf_container->Add_Delayed_Visible_Material_Pass(matpass, this); } else { fvf_container->Add_Visible_Material_Pass(matpass,this); } rendered_something = true; } } /* ** If we rendered any base or procedural passes and this is a skin, we need ** to tell the mesh rendering system to process this skin */ if (rendered_something && Model->Get_Flag(MeshGeometryClass::SKIN)) { //WWASSERT(dynamic_cast(fvf_container) != NULL); static_cast(fvf_container)->Add_Visible_Skin(this); } /* ** If we have a decal mesh, link it into the mesh rendering system */ if ( (DecalMesh != NULL) && ((rinfo.Current_Override_Flags() & RenderInfoClass::RINFO_OVERRIDE_ADDITIONAL_PASSES_ONLY) == 0)) { const SphereClass & ws_sphere = Get_Bounding_Sphere(); Vector3 cam_space_sphere_center; rinfo.Camera.Transform_To_View_Space(cam_space_sphere_center,ws_sphere.Center); if (-cam_space_sphere_center.Z - ws_sphere.Radius < WW3D::Get_Decal_Rejection_Distance()) { TheDX8MeshRenderer.Add_To_Render_List(DecalMesh); } } DX8RendererDebugger::Add_Mesh(this); } } } /*********************************************************************************************** * MeshClass::Render_Material_Pass -- Render a procedural material pass for this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/4/2001 gth : Created. * *=============================================================================================*/ void MeshClass::Render_Material_Pass(MaterialPassClass * pass,IndexBufferClass * ib) { if (LightEnvironment != NULL) { DX8Wrapper::Set_Light_Environment(LightEnvironment); } if (Model->Get_Flag(MeshModelClass::SKIN)) { /* ** In the case of skin meshes, we need to render our polys with the identity transform */ pass->Install_Materials(); DX8Wrapper::Set_Index_Buffer(ib,0); SNAPSHOT_SAY(("Set_World_Identity\n")); DX8Wrapper::Set_World_Identity(); DX8PolygonRendererListIterator it(&PolygonRendererList); while (!it.Is_Done()) { it.Peek_Obj()->Render(BaseVertexOffset); it.Next(); } } else if ((pass->Get_Cull_Volume() != NULL) && (MaterialPassClass::Is_Per_Polygon_Culling_Enabled())) { /* ** Generate the APT */ temp_apt.Delete_All(false); Matrix3D modeltminv; Get_Transform().Get_Orthogonal_Inverse(modeltminv); OBBoxClass localbox; OBBoxClass::Transform(modeltminv,*(pass->Get_Cull_Volume()),&localbox); Vector3 view_dir; localbox.Basis.Get_Z_Vector(&view_dir); view_dir = -view_dir; if (Model->Has_Cull_Tree()) { Model->Generate_Rigid_APT(localbox,view_dir,temp_apt); } else { Model->Generate_Rigid_APT(view_dir,temp_apt); } if (temp_apt.Count() > 0) { int buftype = BUFFER_TYPE_DYNAMIC_DX8; if (Model->Get_Flag(MeshGeometryClass::SORT) && WW3D::Is_Sorting_Enabled()) { buftype = BUFFER_TYPE_DYNAMIC_SORTING; } /* ** Spew triangles in the APT into the dynamic index buffer */ int min_v = Model->Get_Vertex_Count(); int max_v = 0; DynamicIBAccessClass dynamic_ib(buftype,temp_apt.Count() * 3); { DynamicIBAccessClass::WriteLockClass lock(&dynamic_ib); unsigned short * indices = lock.Get_Index_Array(); const TriIndex * polys = Model->Get_Polygon_Array(); for (int i=0; i < temp_apt.Count(); i++) { unsigned v0 = polys[temp_apt[i]].I; unsigned v1 = polys[temp_apt[i]].J; unsigned v2 = polys[temp_apt[i]].K; indices[i*3 + 0] = (unsigned short)v0; indices[i*3 + 1] = (unsigned short)v1; indices[i*3 + 2] = (unsigned short)v2; min_v = WWMath::Min(v0,min_v); min_v = WWMath::Min(v1,min_v); min_v = WWMath::Min(v2,min_v); max_v = WWMath::Max(v0,max_v); max_v = WWMath::Max(v1,max_v); max_v = WWMath::Max(v2,max_v); } } /* ** Render */ int vertex_offset = PolygonRendererList.Peek_Head()->Get_Vertex_Offset(); pass->Install_Materials(); DX8Wrapper::Set_Transform(D3DTS_WORLD,Get_Transform()); DX8Wrapper::Set_Index_Buffer(dynamic_ib,vertex_offset); DX8Wrapper::Draw_Triangles( 0, temp_apt.Count(), min_v, max_v-min_v+1); } } else { /* ** Normal mesh case, render polys with this mesh's transform */ pass->Install_Materials(); DX8Wrapper::Set_Index_Buffer(ib,0); SNAPSHOT_SAY(("Set_World_Transform\n")); DX8Wrapper::Set_Transform(D3DTS_WORLD,Transform); DX8PolygonRendererListIterator it(&PolygonRendererList); while (!it.Is_Done()) { it.Peek_Obj()->Render(BaseVertexOffset); it.Next(); } } } /*********************************************************************************************** * MeshClass::Special_Render -- special render function for meshes * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 12/10/98 GTH : Created. * *=============================================================================================*/ void MeshClass::Special_Render(SpecialRenderInfoClass & rinfo) { if ((Is_Not_Hidden_At_All() == false) && (rinfo.RenderType != SpecialRenderInfoClass::RENDER_SHADOW)) { return; } if (rinfo.RenderType == SpecialRenderInfoClass::RENDER_VIS) { WWASSERT(rinfo.VisRasterizer != NULL); rinfo.VisRasterizer->Enable_Two_Sided_Rendering(!!Model->Get_Flag(MeshGeometryClass::TWO_SIDED)); if (Model->Get_Flag(MeshModelClass::SKIN) == 0) { rinfo.VisRasterizer->Set_Model_Transform(Transform); rinfo.VisRasterizer->Render_Triangles( Model->Get_Vertex_Array(), Model->Get_Vertex_Count(), Model->Get_Polygon_Array(), Model->Get_Polygon_Count(), Get_Bounding_Box() ); } else { int vertex_count = Model->Get_Vertex_Count(); if (_TempVertexBuffer.Count() < vertex_count) _TempVertexBuffer.Resize(vertex_count); Vector3 *dst_vert = &(_TempVertexBuffer[0]); Get_Deformed_Vertices(dst_vert); rinfo.VisRasterizer->Set_Model_Transform(Matrix3D::Identity); rinfo.VisRasterizer->Render_Triangles( dst_vert, Model->Get_Vertex_Count(), Model->Get_Polygon_Array(), Model->Get_Polygon_Count(), Get_Bounding_Box() ); } rinfo.VisRasterizer->Enable_Two_Sided_Rendering(false); } if (rinfo.RenderType == SpecialRenderInfoClass::RENDER_SHADOW) { const HTreeClass * htree = NULL; if (Container!=NULL) { htree = Container->Get_HTree(); } Model->Shadow_Render(rinfo,Transform,htree); } } void MeshClass::Replace_Texture(TextureClass* texture,TextureClass* new_texture) { Model->Replace_Texture(texture,new_texture); } /*********************************************************************************************** * MeshClass::Replace_VertexMaterial -- Replaces existing vertex material with a new one. Will * * * * * * * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/2/2001 hy : Created. * *=============================================================================================*/ void MeshClass::Replace_VertexMaterial(VertexMaterialClass* vmat,VertexMaterialClass* new_vmat) { Model->Replace_VertexMaterial(vmat,new_vmat); } /*********************************************************************************************** * MeshClass::Make_Unique -- Makes mesh unique in the renderer, but still shares system ram ge * * * * * * * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 4/2/2001 hy : Created. * *=============================================================================================*/ void MeshClass::Make_Unique() { if (Model->Num_Refs()==1) return; MeshModelClass *newmesh=NEW_REF(MeshModelClass,(*Model)); REF_PTR_SET(Model,newmesh); REF_PTR_RELEASE(newmesh); } /*********************************************************************************************** * MeshClass::Load -- creates a mesh out of a mesh chunk in a .w3d file * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 06/12/1997 GH : Created. * *=============================================================================================*/ WW3DErrorType MeshClass::Load_W3D(ChunkLoadClass & cload) { Vector3 boxmin,boxmax; /* ** Make sure this mesh is "empty" */ Free(); /* ** Create empty MaterialInfo and Model */ Model = NEW_REF(MeshModelClass,()); if (Model == NULL) { WWDEBUG_SAY(("MeshClass::Load - Failed to allocate model\r\n")); return WW3D_ERROR_LOAD_FAILED; } /* ** Create and read in the model... */ if (Model->Load_W3D(cload) != WW3D_ERROR_OK) { Free(); return WW3D_ERROR_LOAD_FAILED; } /* ** Pull interesting stuff out of the w3d attributes bits */ int col_bits = (Model->W3dAttributes & W3D_MESH_FLAG_COLLISION_TYPE_MASK) >> W3D_MESH_FLAG_COLLISION_TYPE_SHIFT; Set_Collision_Type( col_bits << 1 ); Set_Hidden(Model->W3dAttributes & W3D_MESH_FLAG_HIDDEN); /* ** 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 mainly being used by visibility preprocessing code in Renegade. */ int is_translucent = Model->Get_Flag(MeshModelClass::SORT); if (Model->Has_Shader_Array(0)) { for (int i=0; iGet_Polygon_Count(); i++) { ShaderClass shader = Model->Get_Shader(i,0); is_translucent |= (shader.Get_Alpha_Test() == ShaderClass::ALPHATEST_ENABLE); is_translucent |= (shader.Get_Dst_Blend_Func() != ShaderClass::DSTBLEND_ZERO); } } else { ShaderClass shader = Model->Get_Single_Shader(0); is_translucent |= (shader.Get_Alpha_Test() == ShaderClass::ALPHATEST_ENABLE); is_translucent |= (shader.Get_Dst_Blend_Func() != ShaderClass::DSTBLEND_ZERO); } Set_Translucent(is_translucent); return WW3D_ERROR_OK; } /*********************************************************************************************** * MeshClass::Cast_Ray -- compute a ray intersection with this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/17/98 GTH : Created. * *=============================================================================================*/ bool MeshClass::Cast_Ray(RayCollisionTestClass & raytest) { if ((Get_Collision_Type() & raytest.CollisionType) == 0) return false; if (raytest.IgnoreTranslucentMeshes && Is_Translucent()!=0) 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); WWASSERT(Model); bool hit = Model->Cast_Ray(objray); // transform result back into original coordinate system if (hit) { 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 hit; } /*********************************************************************************************** * MeshClass::Cast_AABox -- cast an AABox against this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/17/98 GTH : Created. * *=============================================================================================*/ bool MeshClass::Cast_AABox(AABoxCollisionTestClass & boxtest) { if ((Get_Collision_Type() & boxtest.CollisionType) == 0) return false; if (boxtest.Result->StartBad) return false; WWASSERT(Model); // This function analyses the tranform to call optimized functions in certain cases bool hit = Model->Cast_World_Space_AABox(boxtest, Get_Transform()); if (hit) { boxtest.CollidedRenderObj = this; } return hit; } /*********************************************************************************************** * Cast_OBBox -- Cast an obbox against this mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/17/98 GTH : Created. * *=============================================================================================*/ bool MeshClass::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); WWASSERT(Model); bool hit = Model->Cast_OBBox(localtest); /* ** If we hit, transform the result of the test back to the original coordinate system. */ if (hit) { 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 hit; } /*********************************************************************************************** * MeshClass::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 MeshClass::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); WWASSERT(Model); return Model->Intersect_OBBox(local_test); } /*********************************************************************************************** * MeshClass::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 MeshClass::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); WWASSERT(Model); return Model->Intersect_OBBox(local_test); } /*********************************************************************************************** * MeshClass::Get_Obj_Space_Bounding_Sphere -- returns obj-space bounding sphere * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/19/00 gth : Created. * *=============================================================================================*/ void MeshClass::Get_Obj_Space_Bounding_Sphere(SphereClass & sphere) const { if (Model) { Model->Get_Bounding_Sphere(&sphere); } else { sphere.Center.Set(0,0,0); sphere.Radius = 1.0f; } } /*********************************************************************************************** * MeshClass::Get_Obj_Space_Bounding_Box -- returns the obj-space bounding box * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 1/19/00 gth : Created. * *=============================================================================================*/ void MeshClass::Get_Obj_Space_Bounding_Box(AABoxClass & box) const { if (Model) { Model->Get_Bounding_Box(&box); } else { box.Init(Vector3(0,0,0),Vector3(1,1,1)); } } /*********************************************************************************************** * MeshClass::Generate_Culling_Tree -- Generates a hierarchical culling tree for the mesh * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/18/98 GTH : Created. * *=============================================================================================*/ void MeshClass::Generate_Culling_Tree(void) { Model->Generate_Culling_Tree(); } /*********************************************************************************************** * MeshClass::Add_Dependencies_To_List -- Add dependent files to the list. * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/18/99 PDS : Created. * *=============================================================================================*/ void MeshClass::Add_Dependencies_To_List ( DynamicVectorClass &file_list, bool textures_only ) { // // Get a pointer to this mesh's material information object // MaterialInfoClass *material = Get_Material_Info (); if (material != NULL) { // // Loop through all the textures and add their filenames to our list // for (int index = 0; index < material->Texture_Count (); index ++) { // // Add this texture's filename to the list // TextureClass *texture = material->Peek_Texture (index); if (texture != NULL) { file_list.Add (texture->Get_Full_Path ()); } } // // Release our hold on the material information object // material->Release_Ref (); } RenderObjClass::Add_Dependencies_To_List (file_list, textures_only); return ; } /*********************************************************************************************** * MeshClass::Update_Cached_Bounding_Volumes -- default collision sphere. * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 5/14/2001 NH : Created. * *=============================================================================================*/ void MeshClass::Update_Cached_Bounding_Volumes(void) const { Get_Obj_Space_Bounding_Sphere(CachedBoundingSphere); CachedBoundingSphere.Center = Get_Transform() * 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(); } // This utility function recurses throughout the subobjects of a renderobject, and for each // MeshClass it finds it sets the given MeshModel flag on its model. This is useful for stuff // like making a RenderObjects' polys sort. void Set_MeshModel_Flag(RenderObjClass *robj, int flag, int onoff) { if (robj->Class_ID() == RenderObjClass::CLASSID_MESH) { // Set flag on model (the assumption is that meshes don't have subobjects) MeshClass *mesh = (MeshClass *)robj; MeshModelClass *model = mesh->Get_Model(); model->Set_Flag((MeshModelClass::FlagsType)flag, onoff != 0); model->Release_Ref(); } else { // Recurse to subobjects (if any) int num_obj = robj->Get_Num_Sub_Objects(); RenderObjClass *sub_obj; for (int i = 0; i < num_obj; i++) { sub_obj = robj->Get_Sub_Object(i); if (sub_obj) { Set_MeshModel_Flag(sub_obj, flag, onoff); sub_obj->Release_Ref(); } } } } int MeshClass::Get_Sort_Level(void) const { if (Model) { return (Model->Get_Sort_Level()); } return(SORT_LEVEL_NONE); } void MeshClass::Set_Sort_Level(int level) { if (Model) { Model->Set_Sort_Level(level); } } unsigned int * MeshClass::Get_User_Lighting_Array(bool alloc) { if (alloc && (UserLighting == NULL)) { UserLighting = new unsigned int[Model->Get_Vertex_Count()]; } return UserLighting; } DX8FVFCategoryContainer* MeshClass::Peek_FVF_Category_Container() { if (PolygonRendererList.Is_Empty()) return NULL; DX8PolygonRendererClass* polygon_renderer=PolygonRendererList.Get_Head(); WWASSERT(polygon_renderer); DX8TextureCategoryClass* texture_category=polygon_renderer->Get_Texture_Category(); WWASSERT(texture_category); DX8FVFCategoryContainer* fvf_category=texture_category->Get_Container(); WWASSERT(fvf_category); return fvf_category; } void MeshClass::Install_User_Lighting_Array(Vector4 * lighting) { Get_User_Lighting_Array(true); for (int vi=0; viGet_Vertex_Count(); vi++) { UserLighting[vi] = DX8Wrapper::Convert_Color(lighting[vi]); } setup_materials_for_user_lighting(); } void MeshClass::setup_materials_for_user_lighting(void) { /* ** Modify the vertex materials to use the solve if necessary */ for (int pass=0; passGet_Pass_Count(); pass++) { if (Model->Has_Material_Array(pass)) { int vidx = 0; VertexMaterialClass * mtl = Model->Peek_Material(0,pass); setup_material_for_user_lighting(mtl); while (vidx < Model->Get_Vertex_Count()) { VertexMaterialClass * next_mtl = Model->Peek_Material(vidx,pass); if (next_mtl != mtl) { setup_material_for_user_lighting(next_mtl); mtl = next_mtl; } vidx++; } } else { setup_material_for_user_lighting(Model->Peek_Single_Material(pass)); } } } void MeshClass::setup_material_for_user_lighting(VertexMaterialClass * mtl) { // (gth) The terrain pre-lit stuff *must* use the diffuse and ambient arrays (to get vertex alpha // working). So, for now we'll plug the color array into diffuse and ambient and the light // environment will be set up so that ambient_light = 1,1,1 Vector3 emissive; mtl->Get_Emissive(&emissive); if (emissive == Vector3(0,0,0)) { mtl->Set_Ambient_Color_Source(VertexMaterialClass::COLOR1); mtl->Set_Diffuse_Color_Source(VertexMaterialClass::COLOR1); } } void MeshClass::Save_User_Lighting (ChunkSaveClass & csave) { if (UserLighting != NULL) { csave.Begin_Chunk(CHUNKID_USER_LIGHTING_ARRAY); csave.Write(&(UserLighting[0]),Model->Get_Vertex_Count() * 4); csave.End_Chunk(); } } void MeshClass::Load_User_Lighting (ChunkLoadClass & cload) { while (cload.Open_Chunk()) { if ( (cload.Cur_Chunk_ID() == CHUNKID_USER_LIGHTING_ARRAY) && (cload.Cur_Chunk_Length() == (unsigned)(Model->Get_Vertex_Count() * 4)) ) { unsigned int * lighting = Get_User_Lighting_Array(true); cload.Read(lighting,Model->Get_Vertex_Count() * 4); setup_materials_for_user_lighting(); } cload.Close_Chunk(); } Set_Has_User_Lighting(true); }