diff --git a/src/osgDB/FileUtils.cpp b/src/osgDB/FileUtils.cpp index 4f8a413d7..43130acf1 100644 --- a/src/osgDB/FileUtils.cpp +++ b/src/osgDB/FileUtils.cpp @@ -378,38 +378,122 @@ std::string osgDB::findFileInDirectory(const std::string& fileName,const std::st bool needDirectoryName = true; osgDB::DirectoryContents dc; - if (dirName.empty()) + std::string realDirName = dirName; + std::string realFileName = fileName; + + // Skip case-insensitive recursion if on Windows + #ifdef WIN32 + bool win32 = true; + #else + bool win32 = false; + #endif + + // If the fileName contains extra path information, make that part of the + // directory name instead + if (fileName != getSimpleFileName(fileName)) + { + // See if we need to add a slash between the directory and file + if (realDirName.empty()) + { + realDirName = getFilePath(fileName); + } + else if (realDirName=="." || realDirName=="./" || realDirName==".\\") + { + realDirName = "./" + getFilePath(fileName); + } + else + { + char lastChar = dirName[dirName.size()-1]; + if ((lastChar == '/') || (lastChar == '\\')) + realDirName = dirName + getFilePath(fileName); + else + realDirName = dirName + "/" + getFilePath(fileName); + } + + // Simplify the file name + realFileName = getSimpleFileName(fileName); + } + + osg::notify(osg::DEBUG_INFO) << "findFileInDirectory() : looking for " << realFileName << " in " << realDirName << "...\n"; + + if (realDirName.empty()) { dc = osgDB::getDirectoryContents("."); needFollowingBackslash = false; needDirectoryName = false; } - else if (dirName=="." || dirName=="./" || dirName==".\\") + else if (realDirName=="." || realDirName=="./" || realDirName==".\\") { dc = osgDB::getDirectoryContents("."); needFollowingBackslash = false; needDirectoryName = false; } + else if (realDirName=="/") + { + dc = osgDB::getDirectoryContents("/"); + needFollowingBackslash = false; + needDirectoryName = true; + } else { - dc = osgDB::getDirectoryContents(dirName); - char lastChar = dirName[dirName.size()-1]; - if (lastChar=='/') needFollowingBackslash = false; - else if (lastChar=='\\') needFollowingBackslash = false; - else needFollowingBackslash = true; - needDirectoryName = true; + // See if we're working in case insensitive mode, and that we're not + // using Windows (the recursive search is not needed in these + // cases) + if ((caseSensitivity == CASE_INSENSITIVE) && (!win32)) + { + // Split the last path element from the directory name + std::string parentPath = getFilePath(realDirName); + std::string lastElement = getSimpleFileName(realDirName); + + // See if we're already at the top level of the filesystem + if ((parentPath.empty()) && (!lastElement.empty())) + { + // Search for the first path element (ignoring case) in + // the top-level directory + realDirName = findFileInDirectory(lastElement, "/", + CASE_INSENSITIVE); + + dc = osgDB::getDirectoryContents(realDirName); + needFollowingBackslash = true; + needDirectoryName = true; + } + else + { + // Recursively search for the last path element (ignoring case) + // in the parent path + realDirName = findFileInDirectory(lastElement, parentPath, + CASE_INSENSITIVE); + + dc = osgDB::getDirectoryContents(realDirName); + char lastChar = realDirName[realDirName.size()-1]; + if (lastChar=='/') needFollowingBackslash = false; + else if (lastChar=='\\') needFollowingBackslash = false; + else needFollowingBackslash = true; + needDirectoryName = true; + } + } + else + { + // No need for recursive search if we're doing an exact comparison + dc = osgDB::getDirectoryContents(realDirName); + char lastChar = realDirName[realDirName.size()-1]; + if (lastChar=='/') needFollowingBackslash = false; + else if (lastChar=='\\') needFollowingBackslash = false; + else needFollowingBackslash = true; + needDirectoryName = true; + } } for(osgDB::DirectoryContents::iterator itr=dc.begin(); itr!=dc.end(); ++itr) { - if ((caseSensitivity==CASE_INSENSITIVE && osgDB::equalCaseInsensitive(fileName,*itr)) || - (fileName==*itr)) + if ((caseSensitivity==CASE_INSENSITIVE && osgDB::equalCaseInsensitive(realFileName,*itr)) || + (realFileName==*itr)) { if (!needDirectoryName) return *itr; - else if (needFollowingBackslash) return dirName+'/'+*itr; - else return dirName+*itr; + else if (needFollowingBackslash) return realDirName+'/'+*itr; + else return realDirName+*itr; } } return ""; diff --git a/src/osgPlugins/CMakeLists.txt b/src/osgPlugins/CMakeLists.txt index e19be35ba..936e5fd87 100644 --- a/src/osgPlugins/CMakeLists.txt +++ b/src/osgPlugins/CMakeLists.txt @@ -81,7 +81,7 @@ ADD_SUBDIRECTORY(dds) ADD_SUBDIRECTORY(tga) ADD_SUBDIRECTORY(hdr) ADD_SUBDIRECTORY(dot) -ADD_SUBDIRECTORY(bsp) +ADD_SUBDIRECTORY(vtf) IF(JPEG_FOUND) ADD_SUBDIRECTORY(jpeg) @@ -180,6 +180,9 @@ ADD_SUBDIRECTORY(txp) ADD_SUBDIRECTORY(shp) ADD_SUBDIRECTORY(txf) +ADD_SUBDIRECTORY(bsp) +ADD_SUBDIRECTORY(mdl) + IF(XINE_FOUND) ADD_SUBDIRECTORY(xine) ENDIF(XINE_FOUND) diff --git a/src/osgPlugins/bsp/CMakeLists.txt b/src/osgPlugins/bsp/CMakeLists.txt index 78753bb68..6d06600a6 100644 --- a/src/osgPlugins/bsp/CMakeLists.txt +++ b/src/osgPlugins/bsp/CMakeLists.txt @@ -1,9 +1,10 @@ SET(TARGET_SRC ReaderWriterBSP.cpp - ReaderWriterVTF.cpp BITSET.cpp Q3BSPReader.cpp Q3BSPLoad.cpp + VBSPData.cpp + VBSPEntity.cpp VBSPGeometry.cpp VBSPReader.cpp ) @@ -13,6 +14,8 @@ SET(TARGET_H BITSET.h Q3BSPReader.h Q3BSPLoad.h + VBSPData.h + VBSPEntity.h VBSPGeometry.h VBSPReader.h ) diff --git a/src/osgPlugins/bsp/ReaderWriterBSP.cpp b/src/osgPlugins/bsp/ReaderWriterBSP.cpp index 4c60a61e4..9f28add8f 100644 --- a/src/osgPlugins/bsp/ReaderWriterBSP.cpp +++ b/src/osgPlugins/bsp/ReaderWriterBSP.cpp @@ -56,11 +56,11 @@ ReaderWriter::ReadResult ReaderWriterBSP::readNode( return ReadResult::FILE_NOT_FOUND; // Open the file and read the magic number and version - stream.open(file.c_str(), std::ios::binary); + stream.open(fileName.c_str(), std::ios::binary); stream.read((char *) &magicNumber, sizeof(int)); stream.read((char *) &version, sizeof(int)); stream.close(); - + // See which kind of BSP file this is if ((magicNumber == VBSP_MAGIC_NUMBER) && (version >= 19) && (version <= 20)) diff --git a/src/osgPlugins/bsp/VBSPData.cpp b/src/osgPlugins/bsp/VBSPData.cpp new file mode 100644 index 000000000..7cc95721f --- /dev/null +++ b/src/osgPlugins/bsp/VBSPData.cpp @@ -0,0 +1,304 @@ + +#include "VBSPData.h" +#include + + +using namespace bsp; +using namespace osg; + + +VBSPData::VBSPData() +{ +} + + +VBSPData::~VBSPData() +{ +} + + +void VBSPData::addEntity(std::string & newEntity) +{ + entity_list.push_back(newEntity); +} + + +const int VBSPData::getNumEntities() const +{ + return entity_list.size(); +} + + +const std::string & VBSPData::getEntity(int index) const +{ + return entity_list[index]; +} + + +void VBSPData::addModel(Model & newModel) +{ + model_list.push_back(newModel); +} + + +const int VBSPData::getNumModels() const +{ + return model_list.size(); +} + + +const Model & VBSPData::getModel(int index) const +{ + return model_list[index]; +} + + +void VBSPData::addPlane(bsp::Plane & newPlane) +{ + plane_list.push_back(newPlane); +} + + +const int VBSPData::getNumPlanes() const +{ + return plane_list.size(); +} + + +const bsp::Plane & VBSPData::getPlane(int index) const +{ + return plane_list[index]; +} + + +void VBSPData::addVertex(osg::Vec3f & newVertex) +{ + // Scale the vertex from inches up to meter scale + vertex_list.push_back(newVertex * 0.0254); +} + + +const int VBSPData::getNumVertices() const +{ + return vertex_list.size(); +} + + +const osg::Vec3f & VBSPData::getVertex(int index) const +{ + return vertex_list[index]; +} + + +void VBSPData::addEdge(Edge & newEdge) +{ + edge_list.push_back(newEdge); +} + + +const int VBSPData::getNumEdges() const +{ + return edge_list.size(); +} + + +const Edge & VBSPData::getEdge(int index) const +{ + return edge_list[index]; +} + + +void VBSPData::addSurfaceEdge(int & newSurfEdge) +{ + surface_edge_list.push_back(newSurfEdge); +} + + +const int VBSPData::getNumSurfaceEdges() const +{ + return surface_edge_list.size(); +} + + +const int VBSPData::getSurfaceEdge(int index) const +{ + return surface_edge_list[index]; +} + + +void VBSPData::addFace(Face & newFace) +{ + face_list.push_back(newFace); +} + + +const int VBSPData::getNumFaces() const +{ + return face_list.size(); +} + + +const Face & VBSPData::getFace(int index) const +{ + return face_list[index]; +} + + +void VBSPData::addTexInfo(TexInfo & newTexInfo) +{ + texinfo_list.push_back(newTexInfo); +} + + +const int VBSPData::getNumTexInfos() const +{ + return texinfo_list.size(); +} + + +const TexInfo & VBSPData::getTexInfo(int index) const +{ + return texinfo_list[index]; +} + + +void VBSPData::addTexData(TexData & newTexData) +{ + texdata_list.push_back(newTexData); +} + + +const int VBSPData::getNumTexDatas() const +{ + return texdata_list.size(); +} + + +const TexData & VBSPData::getTexData(int index) const +{ + return texdata_list[index]; +} + + +void VBSPData::addTexDataString(std::string & newTexDataString) +{ + texdata_string_list.push_back(newTexDataString); +} + + +const int VBSPData::getNumTexDataStrings() const +{ + return texdata_string_list.size(); +} + + +const std::string & VBSPData::getTexDataString(int index) const +{ + return texdata_string_list[index]; +} + + +void VBSPData::addDispInfo(DisplaceInfo & newDispInfo) +{ + dispinfo_list.push_back(newDispInfo); +} + + +const int VBSPData::getNumDispInfos() const +{ + return dispinfo_list.size(); +} + + +const DisplaceInfo & VBSPData::getDispInfo(int index) const +{ + return dispinfo_list[index]; +} + + +void VBSPData::addDispVertex(DisplacedVertex & newDispVertex) +{ + displaced_vertex_list.push_back(newDispVertex); +} + + +const int VBSPData::getNumDispVertices() const +{ + return displaced_vertex_list.size(); +} + + +const DisplacedVertex & VBSPData::getDispVertex(int index) const +{ + return displaced_vertex_list[index]; +} + + +void VBSPData::addStaticPropModel(std::string & newModel) +{ + static_prop_model_list.push_back(newModel); +} + + +const int VBSPData::getNumStaticPropModels() const +{ + return static_prop_model_list.size(); +} + + +const std::string & VBSPData::getStaticPropModel(int index) const +{ + return static_prop_model_list[index]; +} + + +void VBSPData::addStaticProp(StaticPropV4 & newProp) +{ + StaticProp newPropV5; + + // Create a version 5 static prop and copy the data from the given + // version 4 prop into it + memcpy(&newPropV5, &newProp, sizeof(StaticPropV4)); + newPropV5.forced_fade_scale = 1.0; + + // Add the new prop to the list + static_prop_list.push_back(newPropV5); +} + + +void VBSPData::addStaticProp(StaticProp & newProp) +{ + static_prop_list.push_back(newProp); +} + + +const int VBSPData::getNumStaticProps() const +{ + return static_prop_list.size(); +} + + +const StaticProp & VBSPData::getStaticProp(int index) const +{ + return static_prop_list[index]; +} + + +void VBSPData::addStateSet(StateSet * stateSet) +{ + state_set_list.push_back(stateSet); +} + + +const int VBSPData::getNumStateSets() const +{ + return state_set_list.size(); +} + + +StateSet * VBSPData::getStateSet(int index) const +{ + return state_set_list[index].get(); +} + + diff --git a/src/osgPlugins/bsp/VBSPData.h b/src/osgPlugins/bsp/VBSPData.h new file mode 100644 index 000000000..f1617efdb --- /dev/null +++ b/src/osgPlugins/bsp/VBSPData.h @@ -0,0 +1,284 @@ +#ifndef __VBSP_DATA_H_ +#define __VBSP_DATA_H_ + + +#include +#include +#include +#include + + +namespace bsp +{ + + +struct Plane +{ + osg::Vec3f plane_normal; + float origin_dist; + int type; +}; + + +struct Edge +{ + unsigned short vertex[2]; +}; + + +struct Face +{ + unsigned short plane_index; + unsigned char plane_side; + unsigned char on_node; + int first_edge; + short num_edges; + short texinfo_index; + short dispinfo_index; + short surface_fog_volume_id; + unsigned char styles[4]; + int light_offset; + float face_area; + int lightmap_texture_mins_in_luxels[2]; + int lightmap_texture_size_in_luxels[2]; + int original_face; + unsigned short num_primitives; + unsigned short first_primitive_id; + unsigned int smoothing_groups; +}; + + +struct Model +{ + osg::Vec3f bound_min; + osg::Vec3f bound_max; + osg::Vec3f model_origin; + int head_node; + int first_face; + int num_faces; +}; + + +struct StaticPropV4 +{ + osg::Vec3f prop_origin; + osg::Vec3f prop_angles; + unsigned short prop_type; + unsigned short first_leaf; + unsigned short leaf_count; + unsigned char prop_solid; + unsigned char prop_flags; + unsigned int prop_skin; + float min_fade_dist; + float max_fade_dist; + + osg::Vec3f lighting_origin; +}; + + +struct StaticProp +{ + osg::Vec3f prop_origin; + osg::Vec3f prop_angles; + unsigned short prop_type; + unsigned short first_leaf; + unsigned short leaf_count; + unsigned char prop_solid; + unsigned char prop_flags; + unsigned int prop_skin; + float min_fade_dist; + float max_fade_dist; + + osg::Vec3f lighting_origin; + float forced_fade_scale; +}; + + +struct TexInfo +{ + float texture_vecs[2][4]; + float lightmap_vecs[2][4]; + int texture_flags; + int texdata_index; +}; + + +struct TexData +{ + osg::Vec3f texture_reflectivity; + int name_string_table_id; + int texture_width; + int texture_height; + int view_width; + int view_height; +}; + + +struct DisplaceSubNeighbor +{ + unsigned short neighbor_index; + unsigned char neighbor_orient; + unsigned char local_span; + unsigned char neighbor_span; +}; + + +struct DisplaceNeighbor +{ + DisplaceSubNeighbor sub_neighbors[2]; +}; + + +struct DisplaceCornerNeighbor +{ + unsigned short neighbor_indices[4]; + unsigned char neighbor_count; +}; + + +struct DisplaceInfo +{ + osg::Vec3f start_position; + int disp_vert_start; + int disp_tri_start; + int power; + int min_tesselation; + float smooth_angle; + int surface_contents; + unsigned short map_face; + int lightmap_alpha_start; + int lightmap_sample_pos_start; + DisplaceNeighbor edge_neighbors[4]; + DisplaceCornerNeighbor corner_neighbors[4]; + unsigned int allowed_verts[10]; +}; + + +struct DisplacedVertex +{ + osg::Vec3f displace_vec; + float displace_dist; + float alpha_blend; +}; + + +class VBSPData +{ +protected: + + typedef std::vector EntityList; + EntityList entity_list; + + typedef std::vector ModelList; + ModelList model_list; + + typedef std::vector PlaneList; + PlaneList plane_list; + + typedef std::vector VertexList; + VertexList vertex_list; + + typedef std::vector EdgeList; + EdgeList edge_list; + + typedef std::vector SurfEdgeList; + SurfEdgeList surface_edge_list; + + typedef std::vector FaceList; + FaceList face_list; + + typedef std::vector TexInfoList; + TexInfoList texinfo_list; + + typedef std::vector TexDataList; + TexDataList texdata_list; + + typedef std::vector TexDataStringList; + TexDataStringList texdata_string_list; + + typedef std::vector DisplaceInfoList; + DisplaceInfoList dispinfo_list; + + typedef std::vector DisplacedVertexList; + DisplacedVertexList displaced_vertex_list; + + typedef std::vector StaticPropModelList; + StaticPropModelList static_prop_model_list; + + typedef std::vector StaticPropList; + StaticPropList static_prop_list; + + typedef std::vector< osg::ref_ptr > StateSetList; + StateSetList state_set_list; + +public: + + VBSPData(); + virtual ~VBSPData(); + + void addEntity(std::string & newEntity); + const int getNumEntities() const; + const std::string & getEntity(int index) const; + + void addModel(Model & newModel); + const int getNumModels() const; + const Model & getModel(int index) const; + + void addPlane(Plane & newPlane); + const int getNumPlanes() const; + const Plane & getPlane(int index) const; + + void addVertex(osg::Vec3f & newVertex); + const int getNumVertices() const; + const osg::Vec3f & getVertex(int index) const; + + void addEdge(Edge & newEdge); + const int getNumEdges() const; + const Edge & getEdge(int index) const; + + void addSurfaceEdge(int & newSurfEdge); + const int getNumSurfaceEdges() const; + const int getSurfaceEdge(int index) const; + + void addFace(Face & newFace); + const int getNumFaces() const; + const Face & getFace(int index) const; + + void addTexInfo(TexInfo & newTexInfo); + const int getNumTexInfos() const; + const TexInfo & getTexInfo(int index) const; + + void addTexData(TexData & newTexData); + const int getNumTexDatas() const; + const TexData & getTexData(int index) const; + + void addTexDataString(std::string & newTexDataString); + const int getNumTexDataStrings() const; + const std::string & getTexDataString(int index) const; + + void addDispInfo(DisplaceInfo & newDispInfo); + const int getNumDispInfos() const; + const DisplaceInfo & getDispInfo(int index) const; + + void addDispVertex(DisplacedVertex & newDispVert); + const int getNumDispVertices() const; + const DisplacedVertex & getDispVertex(int index) const; + + void addStaticPropModel(std::string & newModel); + const int getNumStaticPropModels() const; + const std::string & getStaticPropModel(int index) const; + + void addStaticProp(StaticPropV4 & newProp); + void addStaticProp(StaticProp & newProp); + const int getNumStaticProps() const; + const StaticProp & getStaticProp(int index) const; + + void addStateSet(osg::StateSet * stateSet); + const int getNumStateSets() const; + osg::StateSet * getStateSet(int index) const; +}; + + +} + +#endif diff --git a/src/osgPlugins/bsp/VBSPEntity.cpp b/src/osgPlugins/bsp/VBSPEntity.cpp new file mode 100644 index 000000000..b39af6f60 --- /dev/null +++ b/src/osgPlugins/bsp/VBSPEntity.cpp @@ -0,0 +1,597 @@ + +#include "VBSPEntity.h" +#include "VBSPGeometry.h" + +#include +#include +#include +#include +#include +#include +#include + + +using namespace bsp; +using namespace osg; +using namespace osgDB; + + +// strcasecmp for MSVC +#ifdef _MSC_VER + #define strcasecmp _stricmp +#endif + + +VBSPEntity::VBSPEntity(std::string & entityText, VBSPData * bspData) +{ + // Save a handle to the bsp data, as we'll need this to construct the + // entity + bsp_data = bspData; + + // Assume we're not visible at first + entity_visible = false; + + // Assume no transform + entity_transformed = false; + + // No model (external or internal) yet + entity_model_index = -1; + entity_model.clear(); + + // Don't know the class yet + entity_class = ENTITY_OTHER; + + // Parse the entity's text to gather parameters + parseParameters(entityText); +} + + +VBSPEntity::~VBSPEntity() +{ +} + + +void VBSPEntity::processWorldSpawn() +{ + // World spawn is definitely visible + entity_visible = true; + + // World spawn is always centered at the origin, so there's no need for + // a transform + entity_transformed = false; + + // The world spawn's internal model index is always zero + entity_model_index = 0; +} + + +void VBSPEntity::processEnv() +{ + // We don't support these entities yet, so leave them invisible +} + + +void VBSPEntity::processFuncBrush() +{ + // These entities are usually transformed + entity_transformed = true; + + // Get the internal model index for this entity + EntityParameters::iterator param = entity_parameters.find("model"); + if (param != entity_parameters.end()) + { + // Get the model number + std::string value = (*param).second; + + // Skip the leading asterisk (internal models are denoted with a + // leading asterisk), and then parse the model number + if (value[0] == '*') + { + value = value.substr(1, std::string::npos); + entity_model_index = atoi(value.c_str()); + + // Make the entity visible + entity_visible = true; + } + else + { + // This shouldn't happen (brush entities don't reference + // external models). Leave the entity invisible in this case + entity_visible = false; + } + } + else + { + // We can't locate the model for this entity, so leave it invisible + entity_visible = false; + } + + // Get the origin and angles for this entity + param = entity_parameters.find("origin"); + if (param != entity_parameters.end()) + { + // Get the origin parameter's value + std::string value = (*param).second; + + // Parse the value into a vector + entity_origin = getVector(value); + } + param = entity_parameters.find("angles"); + if (param != entity_parameters.end()) + { + // Get the origin parameter's value + std::string value = (*param).second; + + // Parse the value into a vector + entity_angles = getVector(value); + } +} + + +void VBSPEntity::processProp() +{ + // These entities are visible + entity_visible = true; + + // These entities are usually transformed + entity_transformed = true; + + // Get the model we need to load for this entity + EntityParameters::iterator param = entity_parameters.find("model"); + if (param != entity_parameters.end()) + { + // Get the model parameter's value + entity_model = (*param).second; + } + + // Get the origin and angles for this entity + param = entity_parameters.find("origin"); + if (param != entity_parameters.end()) + { + // Get the origin parameter's value + std::string value = (*param).second; + + // Parse the value into a vector + entity_origin = getVector(value); + } + param = entity_parameters.find("angles"); + if (param != entity_parameters.end()) + { + // Get the origin parameter's value + std::string value = (*param).second; + + // Parse the value into a vector + entity_angles = getVector(value); + } +} + + +void VBSPEntity::processInfoDecal() +{ + // We don't support these entities yet, so leave them invisible +} + + +void VBSPEntity::processItem() +{ + // We don't support these entities yet, so leave them invisible +} + + +Vec3f VBSPEntity::getVector(std::string str) +{ + double x, y, z; + + // Look for the first non-whitespace + int start = str.find_first_not_of(" \t\r\n", 0); + + // Look for the first whitespace after this + int end = str.find_first_of(" \t\r\n", start); + + if ((end > start) && (start != std::string::npos)) + x = atof(str.substr(start, end-start).c_str()); + else + return Vec3f(); + + // Look for the next non-whitespace + start = str.find_first_not_of(" \t\r\n", end+1); + + // Look for the first whitespace after this + end = str.find_first_of(" \t\r\n", start); + + if ((end > start) && (start != std::string::npos)) + y = atof(str.substr(start, end-start).c_str()); + else + return Vec3f(); + + // Look for the next non-whitespace + start = str.find_first_not_of(" \t\r\n", end+1); + + // Look for the first whitespace after this + end = str.find_first_of(" \t\r\n", start); + if (end == std::string::npos) + end = str.length(); + + if ((end > start) && (start != std::string::npos)) + z = atof(str.substr(start, end-start).c_str()); + else + return Vec3f(); + + // If we get this far, return the vector that we parsed + return Vec3f(x, y, z); +} + + +std::string VBSPEntity::getToken(std::string str, size_t & index) +{ + size_t start, end; + std::string token; + + // Look for the first quotation mark + start = str.find_first_of("\"", index); + if (start != std::string::npos) + { + // From there, look for the next occurrence of a delimiter + start++; + end = str.find_first_of("\"", start); + if (end != std::string::npos) + { + // Found a delimiter, so grab the string in between + token = str.substr(start, end-start); + } + else + { + // Ran off the end of the string, so just grab everything from + // the first good character + token = str.substr(start); + } + } + else + { + // No token to be found + token.clear(); + } + + // Update the index (in case we want to keep looking for tokens in this + // string) + if (end != std::string::npos) + index = end+1; + else + index = std::string::npos; + + // Return the token + return token; +} + + +void VBSPEntity::parseParameters(std::string & entityText) +{ + // Create a string stream on the entity text + std::istringstream str(entityText, std::istringstream::in); + + // Iterate over the parameters + while (!str.eof()) + { + // Get the next line of text + std::string line; + std::getline(str, line); + + // Look for the first quotation mark on the line + size_t start = 0; + std::string token = getToken(line, start); + + // If we have a valid token it will be the parameter name (the key), + // look for a second token, which will be the parameter's value + while (!token.empty()) + { + // Save the token as the key + std::string key = token; + + // Get the next token + start++; + token = getToken(line, start); + + // See if the token is valid + if (!token.empty()) + { + // This token is the value, create an entity parameter from + // these two strings and add it to our parameters map + EntityParameter param(key, token); + entity_parameters.insert(param); + } + } + } + + // Now that we have all of the parameters, figure out what kind of entity + // this is + EntityParameters::iterator param = entity_parameters.find("classname"); + + // See if we found the class + if (param == entity_parameters.end()) + { + // We need the class to be able to do anything with this entity + return; + } + + // Get the class name and process the entity appropriately + std::string className = (*param).second; + if (className.compare("worldspawn") == 0) + { + // This is the entity that represents the main geometry of the map + // (the terrain and much of the static geometry) + entity_class = ENTITY_WORLDSPAWN; + processWorldSpawn(); + } + else if (className.compare(0, 3, "env") == 0) + { + // This is an environmental effect (such as a fire or dust cloud) + entity_class = ENTITY_ENV; + processEnv(); + } + else if ((className.compare("func_brush") == 0) || + (className.compare("func_illusionary") == 0) || + (className.compare("func_wall_toggle") == 0) || + (className.compare("func_breakable") == 0)) + { + // This is secondary map geometry, created along with the main + // map geometry (not an external model) + entity_class = ENTITY_FUNC_BRUSH; + processFuncBrush(); + } + else if (className.compare(0, 4, "prop") == 0) + { + // This is a "prop", an external model placed somewhere in the + // scene + entity_class = ENTITY_PROP; + processProp(); + } + else if (className.compare("infodecal") == 0) + { + // This is a decal, which applies a texture to some surface in the + // scene + entity_class = ENTITY_INFO_DECAL; + processInfoDecal(); + } + else if (className.compare(0, 4, "item") == 0) + { + // This is an "item". Like a prop, these are external models + // placed in the scene, but the specific model is determined + // directly by the entity's class. In HL2, these entities are + // useable by the player (ammunition and health packs are examples) + entity_class = ENTITY_ITEM; + processItem(); + } +} + + +ref_ptr VBSPEntity::createBrushGeometry() +{ + int i; + int numGeoms; + VBSPGeometry ** vbspGeomList; + Model currentModel; + Face currentFace; + TexInfo currentTexInfo; + TexData currentTexData; + const char * texName; + char currentTexName[256]; + int currentGeomIndex; + VBSPGeometry * currentGeom; + ref_ptr entityGroup; + ref_ptr geomGroup; + + // Create a list of VBSPGeometry objects for each texdata entry in the + // scene. These objects will hold the necessary geometry data until we + // convert them back into OSG geometry objects. We potentially will need + // one for each state set in the map + numGeoms = bsp_data->getNumStateSets(); + vbspGeomList = new VBSPGeometry *[numGeoms]; + + // Initialize the list to all NULL for now. We'll create the geometry + // objects as we need them + memset(vbspGeomList, 0, sizeof(VBSPGeometry *) * numGeoms); + + // Get this entity's internal model from the bsp data + currentModel = bsp_data->getModel(entity_model_index); + + // Iterate over the face list and assign faces to the appropriate geometry + // objects + for (i = 0; i < currentModel.num_faces; i++) + { + // Get the current face + currentFace = bsp_data->getFace(currentModel.first_face + i); + + // Get the texdata used by this face + currentTexInfo = bsp_data->getTexInfo(currentFace.texinfo_index); + currentTexData = bsp_data->getTexData(currentTexInfo.texdata_index); + + // Get the texture name + texName = bsp_data-> + getTexDataString(currentTexData.name_string_table_id).c_str(); + strcpy(currentTexName, texName); + + // See if this is a non-drawable surface + if ((strcasecmp(currentTexName, "tools/toolsareaportal") != 0) && + (strcasecmp(currentTexName, "tools/toolsblocklos") != 0) && + (strcasecmp(currentTexName, "tools/toolsblockbullets") != 0) && + (strcasecmp(currentTexName, "tools/toolsblocklight") != 0) && + (strcasecmp(currentTexName, "tools/toolsclip") != 0) && + (strcasecmp(currentTexName, "tools/toolscontrolclip") != 0) && + (strcasecmp(currentTexName, "tools/toolsdotted") != 0) && + (strcasecmp(currentTexName, "tools/toolshint") != 0) && + (strcasecmp(currentTexName, "tools/toolsinvisible") != 0) && + (strcasecmp(currentTexName, "tools/toolsinvisibleladder") != 0) && + (strcasecmp(currentTexName, "tools/toolsnodraw") != 0) && + (strcasecmp(currentTexName, "tools/toolsnpcclip") != 0) && + (strcasecmp(currentTexName, "tools/toolsoccluder") != 0) && + (strcasecmp(currentTexName, "tools/toolsorigin") != 0) && + (strcasecmp(currentTexName, "tools/toolsskip") != 0) && + (strcasecmp(currentTexName, "tools/toolsskybox") != 0) && + (strcasecmp(currentTexName, "tools/toolsskyfog") != 0) && + (strcasecmp(currentTexName, "tools/toolstrigger") != 0)) + { + // Get or create the corresponding VBSPGeometry object from the + // list + currentGeomIndex = currentTexInfo.texdata_index; + currentGeom = vbspGeomList[currentGeomIndex]; + if (currentGeom == NULL) + { + // Create the geometry object + vbspGeomList[currentGeomIndex] = new VBSPGeometry(bsp_data); + currentGeom = vbspGeomList[currentGeomIndex]; + } + + // Add the face to the appropriate VBSPGeometry object + currentGeom->addFace(currentModel.first_face + i); + } + } + + // Create a top-level group to hold the geometry objects + if (entity_transformed) + { + // Create a matrix transform + MatrixTransform * entityXform = new MatrixTransform(); + + // Set it up with the entity's transform information (scale the + // position from inches to meters) + Matrixf transMat, rotMat; + Quat roll, yaw, pitch; + transMat.makeTranslate(entity_origin * 0.0254); + pitch.makeRotate(osg::DegreesToRadians(entity_angles.x()), + Vec3f(0.0, 1.0, 0.0)); + yaw.makeRotate(osg::DegreesToRadians(entity_angles.y()), + Vec3f(0.0, 0.0, 1.0)); + roll.makeRotate(osg::DegreesToRadians(entity_angles.z()), + Vec3f(1.0, 0.0, 0.0)); + rotMat.makeRotate(roll * pitch * yaw); + + // Set the transform matrix + entityXform->setMatrix(rotMat * transMat); + + // Use the transform node as the main entity group + entityGroup = entityXform; + } + else + { + // Create a group to represent the entire entity + entityGroup = new Group(); + } + + // Iterate over the geometry array and convert each geometry object + // into OSG geometry + for (i = 0; i < numGeoms; i++) + { + // Get the next geometry object (if any) + currentGeom = vbspGeomList[i]; + if (currentGeom != NULL) + { + // Convert the BSP geometry to OSG geometry + geomGroup = currentGeom->createGeometry(); + + // Make sure the geometry converted properly + if (geomGroup.valid()) + { + // Set this group's state set + geomGroup->setStateSet(bsp_data->getStateSet(i)); + + // Add the geometry group to the entity group + entityGroup->addChild(geomGroup.get()); + } + } + } + + // Return the group we created + return entityGroup; +} + + +ref_ptr VBSPEntity::createModelGeometry() +{ + std::string modelFile; + ref_ptr modelNode; + ref_ptr entityGroup; + + // Try to load the model + modelNode = osgDB::readNodeFile(entity_model); + if (modelNode.valid()) + { + // Create a group and add the model to it + if (entity_transformed) + { + // Create a matrix transform + MatrixTransform * entityXform = new MatrixTransform(); + + // Set it up with the entity's transform information (scale + // the position from inches to meters) + Matrixf transMat, rotMat; + Quat roll, yaw, pitch; + transMat.makeTranslate(entity_origin * 0.0254); + pitch.makeRotate(osg::DegreesToRadians(entity_angles.x()), + Vec3f(0.0, 1.0, 0.0)); + yaw.makeRotate(osg::DegreesToRadians(entity_angles.y()), + Vec3f(0.0, 0.0, 1.0)); + roll.makeRotate(osg::DegreesToRadians(entity_angles.z()), + Vec3f(1.0, 0.0, 0.0)); + rotMat.makeRotate(roll * pitch * yaw); + + // Set the transform matrix + entityXform->setMatrix(rotMat * transMat); + + // Use the transform node as the main entity group + entityGroup = entityXform; + } + else + { + // Create a group to represent the entire entity + entityGroup = new Group(); + } + + // Add the model node to the group + entityGroup->addChild(modelNode.get()); + } + else + { + notify(WARN) << "Couldn't find prop \"" << entity_model << "\"."; + notify(WARN) << std::endl; + + // Leave the group empty (no model to show) + entityGroup = NULL; + } + + return entityGroup; +} + + +EntityClass VBSPEntity::getClass() +{ + return entity_class; +} + + +bool VBSPEntity::isVisible() +{ + return entity_visible; +} + + +ref_ptr VBSPEntity::createGeometry() +{ + // If we're not a visible entity, we have no geometry + if (!entity_visible) + return NULL; + + // Create the geometry for the entity based on the class + if ((entity_class == ENTITY_WORLDSPAWN) || + (entity_class == ENTITY_FUNC_BRUSH)) + { + return createBrushGeometry(); + } + else if (entity_class == ENTITY_PROP) + { + return createModelGeometry(); + } + + // If we get here, we don't handle this kind of entity (yet) + return NULL; +} + diff --git a/src/osgPlugins/bsp/VBSPEntity.h b/src/osgPlugins/bsp/VBSPEntity.h new file mode 100644 index 000000000..8ae6da3d1 --- /dev/null +++ b/src/osgPlugins/bsp/VBSPEntity.h @@ -0,0 +1,77 @@ + +#ifndef __VBSP_ENTITY_H_ +#define __VBSP_ENTITY_H_ + + +#include +#include + +#include "VBSPData.h" + + +namespace bsp +{ + + +enum EntityClass +{ + ENTITY_WORLDSPAWN, + ENTITY_ENV, + ENTITY_FUNC_BRUSH, + ENTITY_PROP, + ENTITY_INFO_DECAL, + ENTITY_ITEM, + ENTITY_OTHER +}; + + +class VBSPEntity +{ +protected: + + VBSPData * bsp_data; + + EntityClass entity_class; + + typedef std::pair EntityParameter; + typedef std::map EntityParameters; + EntityParameters entity_parameters; + + bool entity_visible; + bool entity_transformed; + int entity_model_index; + std::string entity_model; + osg::Vec3f entity_origin; + osg::Vec3f entity_angles; + osg::ref_ptr entity_geometry; + + void processWorldSpawn(); + void processEnv(); + void processFuncBrush(); + void processProp(); + void processInfoDecal(); + void processItem(); + + osg::Vec3f getVector(std::string str); + std::string getToken(std::string str, size_t & index); + void parseParameters(std::string & entityText); + + osg::ref_ptr createBrushGeometry(); + osg::ref_ptr createModelGeometry(); + +public: + + VBSPEntity(std::string & entityText, VBSPData * bspData); + ~VBSPEntity(); + + EntityClass getClass(); + bool isVisible(); + + osg::ref_ptr createGeometry(); +}; + +} + + +#endif + diff --git a/src/osgPlugins/bsp/VBSPGeometry.cpp b/src/osgPlugins/bsp/VBSPGeometry.cpp index d9647e37d..80a4a7691 100644 --- a/src/osgPlugins/bsp/VBSPGeometry.cpp +++ b/src/osgPlugins/bsp/VBSPGeometry.cpp @@ -1,17 +1,19 @@ +#include +#include + #include "VBSPGeometry.h" using namespace osg; -using namespace osgDB; using namespace bsp; -VBSPGeometry::VBSPGeometry(VBSPReader * reader) +VBSPGeometry::VBSPGeometry(VBSPData * bspData) { - // Keep track of the reader, as it has all of the data lists that we + // Keep track of the bsp data, as it has all of the data lists that we // need - vbsp_reader = reader; + bsp_data = bspData; // Create arrays for the vertex attributes vertex_array = new Vec3Array(); @@ -249,17 +251,17 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo) Vec3 texV; float texVOffset; float texVScale; - int i, j, k; + unsigned int i, j, k; double dist, minDist; - int minIndex = 0; + int minIndex; osg::Vec3 temp; int edgeIndex; int currentSurfEdge; Edge currentEdge; osg::Vec3 currentVertex; osg::Vec3 vertices[4]; - int firstVertex; - int numEdgeVertices; + unsigned int firstVertex; + unsigned int numEdgeVertices; double subdivideScale; osg::Vec3 leftEdge, rightEdge; osg::Vec3 leftEdgeStep, rightEdgeStep; @@ -276,8 +278,8 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo) // Get the texture info for this face - currentTexInfo = vbsp_reader->getTexInfo(face.texinfo_index); - currentTexData = vbsp_reader->getTexData(currentTexInfo.texdata_index); + currentTexInfo = bsp_data->getTexInfo(face.texinfo_index); + currentTexData = bsp_data->getTexData(currentTexInfo.texdata_index); // Get the texture vectors and offsets. These are used to calculate // texture coordinates @@ -290,6 +292,10 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo) currentTexInfo.texture_vecs[1][2]); texVOffset = currentTexInfo.texture_vecs[1][3]; + // Scale the texture vectors from inches to meters + texU *= 39.37; + texV *= 39.37; + // Get the size of the texture involved, as the planar texture projection // assumes non-normalized texture coordinates texUScale = 1.0 / (float)currentTexData.texture_width; @@ -304,16 +310,16 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo) // Look up the edge specified by the surface edge index, the // index might be negative (see below), so take the absolute // value - currentSurfEdge = vbsp_reader->getSurfaceEdge(edgeIndex); - currentEdge = vbsp_reader->getEdge(abs(currentSurfEdge)); + currentSurfEdge = bsp_data->getSurfaceEdge(edgeIndex); + currentEdge = bsp_data->getEdge(abs(currentSurfEdge)); // The sign of the surface edge index specifies which vertex is // "first" for this face. A negative index means the edge should // be flipped, and the second vertex treated as the first if (currentSurfEdge < 0) - currentVertex = vbsp_reader->getVertex(currentEdge.vertex[1]); + currentVertex = bsp_data->getVertex(currentEdge.vertex[1]); else - currentVertex = vbsp_reader->getVertex(currentEdge.vertex[0]); + currentVertex = bsp_data->getVertex(currentEdge.vertex[0]); // Add the vertex to the array vertices[i] = currentVertex; @@ -328,7 +334,7 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo) for (i = 0; i < 4; i++) { // Calculate the distance of the start position from this vertex - dist = (vertices[i] - dispInfo.start_position).length(); + dist = (vertices[i] - dispInfo.start_position * 0.0254).length(); // If this is the smallest distance we've seen, remember it if (dist < minDist) @@ -388,14 +394,15 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo) // Get the displacement info for this vertex dispVertIndex = dispInfo.disp_vert_start; dispVertIndex += i * numEdgeVertices + j; - dispVertInfo = vbsp_reader->getDispVertex(dispVertIndex); + dispVertInfo = bsp_data->getDispVertex(dispVertIndex); // Calculate the flat vertex flatVertex = leftEnd + (leftRightStep * (double) j); // Calculate the displaced vertex dispVertex = - dispVertInfo.displace_vec * dispVertInfo.displace_dist; + dispVertInfo.displace_vec * + (dispVertInfo.displace_dist * 0.0254); dispVertex += flatVertex; // Add the vertex to the displaced vertex array @@ -510,14 +517,14 @@ void VBSPGeometry::addFace(int faceIndex) // Make sure this face is not "on node" (an internal node of the BSP tree). // These faces are not used for visible geometry - currentFace = vbsp_reader->getFace(faceIndex); + currentFace = bsp_data->getFace(faceIndex); // See if this is a displacement surface if (currentFace.dispinfo_index != -1) { // Get the displacement info currentDispInfo = - vbsp_reader->getDispInfo(currentFace.dispinfo_index); + bsp_data->getDispInfo(currentFace.dispinfo_index); // Generate the displacement surface createDispSurface(currentFace, currentDispInfo); @@ -525,13 +532,13 @@ void VBSPGeometry::addFace(int faceIndex) else { // Get the face normal, using the plane information - normal = vbsp_reader->getPlane(currentFace.plane_index).plane_normal; + normal = bsp_data->getPlane(currentFace.plane_index).plane_normal; if (currentFace.plane_side != 0) normal = -normal; // Get the texture info and data structures - currentTexInfo = vbsp_reader->getTexInfo(currentFace.texinfo_index); - currentTexData = vbsp_reader->getTexData(currentTexInfo.texdata_index); + currentTexInfo = bsp_data->getTexInfo(currentFace.texinfo_index); + currentTexData = bsp_data->getTexData(currentTexInfo.texdata_index); // Get the texture vectors and offsets. These are used to calculate // texture coordinates @@ -544,6 +551,10 @@ void VBSPGeometry::addFace(int faceIndex) currentTexInfo.texture_vecs[1][2]); texVOffset = currentTexInfo.texture_vecs[1][3]; + // Scale the texture vectors from inches to meters + texU *= 39.37; + texV *= 39.37; + // Get the texture size, as the planar texture projection results in // non-normalized texture coordinates texUScale = 1.0 / (float)currentTexData.texture_width; @@ -562,16 +573,16 @@ void VBSPGeometry::addFace(int faceIndex) // Look up the edge specified by the surface edge index, the // index might be negative (see below), so take the absolute // value - currentSurfEdge = vbsp_reader->getSurfaceEdge(edgeIndex); - currentEdge = vbsp_reader->getEdge(abs(currentSurfEdge)); + currentSurfEdge = bsp_data->getSurfaceEdge(edgeIndex); + currentEdge = bsp_data->getEdge(abs(currentSurfEdge)); // The sign of the surface edge index specifies which vertex is // "first" for this face. A negative index means the edge should // be flipped, and the second vertex treated as the first if (currentSurfEdge < 0) - currentVertex = vbsp_reader->getVertex(currentEdge.vertex[1]); + currentVertex = bsp_data->getVertex(currentEdge.vertex[1]); else - currentVertex = vbsp_reader->getVertex(currentEdge.vertex[0]); + currentVertex = bsp_data->getVertex(currentEdge.vertex[0]); // Add the vertex to the array vertex_array->push_back(currentVertex); diff --git a/src/osgPlugins/bsp/VBSPGeometry.h b/src/osgPlugins/bsp/VBSPGeometry.h index ba5ef5495..8bc46d432 100644 --- a/src/osgPlugins/bsp/VBSPGeometry.h +++ b/src/osgPlugins/bsp/VBSPGeometry.h @@ -6,7 +6,7 @@ #include #include -#include "VBSPReader.h" +#include "VBSPData.h" namespace bsp @@ -17,7 +17,7 @@ class VBSPGeometry { protected: - VBSPReader * vbsp_reader; + VBSPData * bsp_data; osg::ref_ptr vertex_array; osg::ref_ptr normal_array; @@ -39,7 +39,7 @@ class VBSPGeometry public: - VBSPGeometry(VBSPReader * reader); + VBSPGeometry(VBSPData * bspData); virtual ~VBSPGeometry(); void addFace(int faceIndex); diff --git a/src/osgPlugins/bsp/VBSPReader.cpp b/src/osgPlugins/bsp/VBSPReader.cpp index c77765238..c77f4c289 100644 --- a/src/osgPlugins/bsp/VBSPReader.cpp +++ b/src/osgPlugins/bsp/VBSPReader.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -6,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -14,12 +16,11 @@ #include #include #include - #include #include #include "VBSPReader.h" -#include "VBSPGeometry.h" +#include "VBSPEntity.h" using namespace bsp; @@ -38,41 +39,34 @@ VBSPReader::VBSPReader() // Start with no root node root_node = NULL; - // No map data yet - entity_list = NULL; - vertex_list = NULL; - edge_list = NULL; - surface_edges = NULL; - face_list = NULL; + // Create the map data object + bsp_data = new VBSPData(); + + // No string table yet + texdata_string = NULL; + texdata_string_table = NULL; + num_texdata_string_table_entries = 0; } VBSPReader::~VBSPReader() { - // Clean up the data from the VBSP file - delete [] entity_list; - delete [] plane_list; - delete [] vertex_list; - delete [] edge_list; - delete [] surface_edges; - delete [] face_list; - delete [] texinfo_list; - delete [] texdata_list; + // Clean up the texdata strings and such + delete [] texdata_string; delete [] texdata_string_table; - delete [] texdata_string_data; - delete [] dispinfo_list; - delete [] displaced_vertex_list; } void VBSPReader::processEntities(std::istream & str, int offset, int length) { - char * entities; - char * startPtr; - char * endPtr; - int i; - int entityLen; + char * entities; + char * startPtr; + char * endPtr; + int numEntities; + int i; + std::string entityStr; + size_t entityLen; // Create the string entities = new char[length]; @@ -87,11 +81,11 @@ void VBSPReader::processEntities(std::istream & str, int offset, // Count the number of entities startPtr = entities; endPtr = strchr(entities, '}'); - num_entities = 0; + numEntities = 0; while ((startPtr != NULL) && (endPtr != NULL)) { // Increment the count - num_entities++; + numEntities++; // Advance the pointers startPtr = strchr(endPtr, '{'); @@ -99,21 +93,17 @@ void VBSPReader::processEntities(std::istream & str, int offset, endPtr = strchr(startPtr, '}'); } - // Create the entity list - entity_list = new char * [num_entities]; - memset(entity_list, 0, num_entities * sizeof(char *)); - // Parse the entities startPtr = entities; endPtr = strchr(entities, '}'); - for (i = 0; i < num_entities; i++) + for (i = 0; i < numEntities; i++) { // Get the length of this entity entityLen = endPtr - startPtr + 1; // Create the entity list entry and copy the entity information - entity_list[i] = new char[entityLen + 1]; - strncpy(entity_list[i], startPtr, entityLen); + entityStr = std::string(startPtr, entityLen); + bsp_data->addEntity(entityStr); // Advance the pointers startPtr = strchr(endPtr, '{'); @@ -126,121 +116,213 @@ void VBSPReader::processEntities(std::istream & str, int offset, } +void VBSPReader::processModels(std::istream & str, int offset, int length) +{ + int numModels; + int i; + Model * models; + + // Calculate the number of models + numModels = length / sizeof(Model); + + // Seek to the Models lump + str.seekg(offset); + + // Read the models + models = new Model[numModels]; + str.read((char *) models, sizeof(Model) * numModels); + + // Add the models to the model list + for (i = 0; i < numModels; i++) + bsp_data->addModel(models[i]); + + // Clean up + delete [] models; +} + + void VBSPReader::processPlanes(std::istream & str, int offset, int length) { - // Calculate the number of planes - num_planes = length / sizeof(Plane); + int numPlanes; + int i; + Plane * planes; - // Create the plane list - plane_list = new Plane[num_planes]; + // Calculate the number of planes + numPlanes = length / sizeof(Plane); // Seek to the Planes lump str.seekg(offset); - // Read in the planes - str.read((char *) plane_list, sizeof(Plane) * num_planes); + // Read the planes + planes = new Plane[numPlanes]; + str.read((char *) planes, sizeof(Plane) * numPlanes); + + // Add the planes to the plane list + for (i = 0; i < numPlanes; i++) + bsp_data->addPlane(planes[i]); + + // Clean up + delete [] planes; } void VBSPReader::processVertices(std::istream & str, int offset, int length) { - // Calculate the number of vertices - num_vertices = length / 3 / sizeof(float); + int numVertices; + int i; + Vec3f * vertices; - // Create the vertex list - vertex_list = new Vec3f[num_vertices]; + // Calculate the number of vertices + numVertices = length / 3 / sizeof(float); // Seek to the Vertices lump str.seekg(offset); - // Read in the vertices - str.read((char *) vertex_list, sizeof(Vec3f) * num_vertices); + // Read the vertex + vertices = new Vec3f[numVertices]; + str.read((char *) vertices, sizeof(Vec3f) * numVertices); + + // Add it the vertices to the list + for (i = 0; i < numVertices; i++) + bsp_data->addVertex(vertices[i]); + + // Clean up + delete [] vertices; } void VBSPReader::processEdges(std::istream & str, int offset, int length) { - // Calculate the number of edges - num_edges = length / sizeof(Edge); + int numEdges; + int i; + Edge * edges; - // Create the edge list - edge_list = new Edge[num_edges]; + // Calculate the number of edges + numEdges = length / sizeof(Edge); // Seek to the Edges lump str.seekg(offset); - // Read in the edge list - str.read((char *) edge_list, sizeof(Edge) * num_edges); + // Read the edges + edges = new Edge[numEdges]; + str.read((char *) edges, sizeof(Edge) * numEdges); + + // Add the edges to the edge list + for (i = 0; i < numEdges; i++) + bsp_data->addEdge(edges[i]); + + // Clean up + delete [] edges; } void VBSPReader::processSurfEdges(std::istream & str, int offset, int length) { - // Calculate the number of edges - num_surf_edges = length / sizeof(int); + int numSurfEdges; + int i; + int * surfEdges; - // Create the surface edges list - surface_edges = new int[num_surf_edges]; + // Calculate the number of edges + numSurfEdges = length / sizeof(int); // Seek to the SurfEdges lump str.seekg(offset); - // Read in the surface edge list - str.read((char *) surface_edges, sizeof(int) * num_surf_edges); + // Read the surface edges + surfEdges = new int[numSurfEdges]; + str.read((char *) surfEdges, sizeof(int) * numSurfEdges); + + // Add the surface edges to the surface edge list + for (i = 0; i < numSurfEdges; i++) + bsp_data->addSurfaceEdge(surfEdges[i]); + + // Clean up + delete [] surfEdges; } void VBSPReader::processFaces(std::istream & str, int offset, int length) { - // Calculate the number of faces - num_faces = length / sizeof(Face); + int numFaces; + int i; + Face * faces; - // Create the face list - face_list = new Face[num_faces]; + // Calculate the number of faces + numFaces = length / sizeof(Face); // Seek to the Faces lump str.seekg(offset); - // Read in the faces - str.read((char *) face_list, sizeof(Face) * num_faces); + // Read the faces + faces = new Face[numFaces]; + str.read((char *) faces, sizeof(Face) * numFaces); + + // Add the faces to the face list + for (i = 0; i < numFaces; i++) + bsp_data->addFace(faces[i]); + + // Clean up + delete [] faces; } void VBSPReader::processTexInfo(std::istream & str, int offset, int length) { - // Calculate the number of texinfos - num_texinfo_entries = length / sizeof(TexInfo); + int numTexInfos; + int i; + TexInfo * texinfos; - // Create the texinfo list - texinfo_list = new TexInfo[num_texinfo_entries]; + // Calculate the number of texinfos + numTexInfos = length / sizeof(TexInfo); // Seek to the TexInfo lump str.seekg(offset); // Read in the texinfo entries - str.read((char *) texinfo_list, sizeof(TexInfo) * num_texinfo_entries); + texinfos = new TexInfo[numTexInfos]; + str.read((char *) texinfos, sizeof(TexInfo) * numTexInfos); + + // Add the texinfo entries to the texinfo list + for (i = 0; i < numTexInfos; i++) + bsp_data->addTexInfo(texinfos[i]); + + // Clean up + delete [] texinfos; } void VBSPReader::processTexData(std::istream & str, int offset, int length) { - // Calculate the number of texdatas - num_texdata_entries = length / sizeof(TexData); + int numTexDatas; + int i; + TexData * texdatas; - // Create the texdata list - texdata_list = new TexData[num_texdata_entries]; + // Calculate the number of texdatas + numTexDatas = length / sizeof(TexData); // Seek to the TexData lump str.seekg(offset); // Read in the texdata entries - str.read((char *) texdata_list, sizeof(TexData) * num_texdata_entries); + texdatas = new TexData[numTexDatas]; + str.read((char *) texdatas, sizeof(TexData) * numTexDatas); + + // Add the texdata entries to the texdata list + for (i = 0; i < numTexDatas; i++) + bsp_data->addTexData(texdatas[i]); + + // Clean up + delete [] texdatas; } void VBSPReader::processTexDataStringTable(std::istream & str, int offset, int length) { + int i; + int index; + std::string texStr; + // Calculate the number of table entries num_texdata_string_table_entries = length / sizeof(int); @@ -253,117 +335,196 @@ void VBSPReader::processTexDataStringTable(std::istream & str, int offset, // Read in the texdata_string_table str.read((char *) texdata_string_table, sizeof(int) * num_texdata_string_table_entries); + + // If we have a texdata string loaded, parse the texdata strings now + if (texdata_string != NULL) + { + for (i = 0; i < num_texdata_string_table_entries; i++) + { + // Add the strings from the string data, using the string table + // to index it + index = texdata_string_table[i]; + texStr = std::string(&texdata_string[index]); + bsp_data->addTexDataString(texStr); + } + } } void VBSPReader::processTexDataStringData(std::istream & str, int offset, int length) { - char * texDataString; - char * startPtr; - char * endPtr; int i; - int stringLen; + int index; + std::string texStr; - // Create the buffer to load the texdata strings - texDataString = new char[length]; - memset(texDataString, 0, length * sizeof(char)); + // Create the buffer to hold the texdata string + texdata_string = new char[length]; + memset(texdata_string, 0, length * sizeof(char)); // Seek to the TexDataStringData lump str.seekg(offset); // Read the entire texdata string (this string is actually a // NULL-delimited list of strings) - str.read((char *) texDataString, sizeof(char) * length); + str.read((char *) texdata_string, sizeof(char) * length); - // Count the number of strings - startPtr = texDataString; - endPtr = startPtr + strlen(startPtr); - num_texdata_strings = 0; - while ((startPtr - texDataString) < length) + // If we have a string table loaded, parse the texdata strings now + // (if not, num_texdata_string_table_entries will be zero and we'll + // skip this loop) + for (i = 0; i < num_texdata_string_table_entries; i++) { - // Increment the count - num_texdata_strings++; - - // Advance the pointers - startPtr = endPtr+1; - if ((startPtr - texDataString) < length) - endPtr = startPtr + strlen(startPtr); + // Add the strings from the string data, using the string table + // to index it + index = texdata_string_table[i]; + texStr = std::string(&texdata_string[index]); + bsp_data->addTexDataString(texStr); } - - // Create the texdata string list - texdata_string_data = new char * [num_texdata_strings]; - memset(texdata_string_data, 0, num_texdata_strings * sizeof(char *)); - - // Parse the texdata strings - startPtr = texDataString; - endPtr = startPtr + strlen(startPtr); - for (i = 0; i < num_texdata_strings; i++) - { - // Get the length of this string - stringLen = endPtr - startPtr + 1; - - // Create the texdata string list entry and copy the string - texdata_string_data[i] = new char[stringLen + 1]; - strncpy(texdata_string_data[i], startPtr, stringLen); - - // Advance the pointers - startPtr = endPtr+1; - if ((startPtr - texDataString) < length) - endPtr = startPtr + strlen(startPtr); - } - - // Free up the original texdata string - free(texDataString); } void VBSPReader::processDispInfo(std::istream & str, int offset, int length) { - // Calculate the number of texinfos - num_dispinfo_entries = length / sizeof(DisplaceInfo); + int numDispInfos; + int i; + DisplaceInfo * dispinfos; - // Create the texinfo list - dispinfo_list = new DisplaceInfo[num_dispinfo_entries]; + // Calculate the number of dispinfos + numDispInfos = length / sizeof(DisplaceInfo); - // Seek to the DispInfo lump + // Seek to the DisplaceInfo lump str.seekg(offset); - // Read in the texinfo entries - str.read((char *) dispinfo_list, - sizeof(DisplaceInfo) * num_dispinfo_entries); + // Read in the dispinfo entries + dispinfos = new DisplaceInfo[numDispInfos]; + str.read((char *) dispinfos, sizeof(DisplaceInfo) * numDispInfos); + + // Add the dispinfo entries to the displace info list + for (i = 0; i < numDispInfos; i++) + bsp_data->addDispInfo(dispinfos[i]); + + // Clean up + delete [] dispinfos; } void VBSPReader::processDispVerts(std::istream & str, int offset, int length) { + int numDispVerts; + int i; + DisplacedVertex * dispverts; + // Calculate the number of displaced vertices - num_displaced_vertices = length / sizeof(DisplacedVertex); + numDispVerts = length / sizeof(DisplacedVertex); - // Create the texinfo list - displaced_vertex_list = new DisplacedVertex[num_displaced_vertices]; - - // Seek to the DispVerts lump + // Seek to the DispVert lump str.seekg(offset); // Read in the displaced vertices - str.read((char *) displaced_vertex_list, - sizeof(DisplacedVertex) * num_displaced_vertices); + dispverts = new DisplacedVertex[numDispVerts]; + str.read((char *) dispverts, sizeof(DisplacedVertex) * numDispVerts); + + // Add the displaced vertices to the displaced vertex list + for (i = 0; i < numDispVerts; i++) + bsp_data->addDispVertex(dispverts[i]); + + // Clean up + delete [] dispverts; +} + + +void VBSPReader::processGameData(std::istream & str, int offset, int length) +{ + GameHeader gameHeader; + GameLump * gameLumps; + int i; + + // Read the header + str.seekg(offset); + str.read((char *) &gameHeader, sizeof(GameHeader)); + + // Create and read in the game lump list + gameLumps = new GameLump[gameHeader.num_lumps]; + str.read((char *) gameLumps, sizeof(GameLump) * gameHeader.num_lumps); + + // Iterate over the game lumps + for (i = 0; i < gameHeader.num_lumps; i++) + { + // See if this is a lump we're interested in + if (gameLumps[i].lump_id == STATIC_PROP_ID) + { + processStaticProps(str, gameLumps[i].lump_offset, + gameLumps[i].lump_length, + gameLumps[i].lump_version); + } + } + + // Clean up + delete [] gameLumps; +} + + +void VBSPReader::processStaticProps(std::istream & str, int offset, int length, + int lumpVersion) +{ + StaticPropModelNames sprpModelNames; + char modelName[130]; + std::string modelStr; + int i; + StaticPropLeaves sprpLeaves; + StaticProps sprpHeader; + StaticPropV4 sprp4; + StaticProp sprp5; + + // First, read the static prop models dictionary + str.seekg(offset); + str.read((char *) &sprpModelNames, sizeof(StaticPropModelNames)); + for (i = 0; i < sprpModelNames.num_model_names; i++) + { + str.read(modelName, 128); + modelName[128] = 0; + modelStr = std::string(modelName); + bsp_data->addStaticPropModel(modelStr); + } + + // Next, skip over the static prop leaf array + str.read((char *) &sprpLeaves, sizeof(StaticPropLeaves)); + str.seekg(sprpLeaves.num_leaf_entries * sizeof(unsigned short), + std::istream::cur); + + // Finally, read in the static prop entries + str.read((char *) &sprpHeader, sizeof(StaticProps)); + for (i = 0; i < sprpHeader.num_static_props; i++) + { + // The version number determines how much we read for each prop + if (lumpVersion == 4) + { + // Read the static prop and add it to the bsp data + str.read((char *) &sprp4, sizeof(StaticPropV4)); + bsp_data->addStaticProp(sprp4); + } + else if (lumpVersion == 5) + { + // Read the static prop and add it to the bsp data + str.read((char *) &sprp5, sizeof(StaticProp)); + bsp_data->addStaticProp(sprp5); + } + } } std::string VBSPReader::getToken(std::string str, const char * delim, - std::string::size_type & index) + size_t & index) { + size_t start, end; std::string token; // Look for the first non-occurrence of the delimiters - std::string::size_type start = str.find_first_not_of(" \t\n\r\"", index); - std::string::size_type end = std::string::npos; + start = str.find_first_not_of(delim, index); if (start != std::string::npos) { // From there, look for the first occurrence of a delimiter - end = str.find_first_of(" \t\n\r\"", start+1); + end = str.find_first_of(delim, start+1); if (end != std::string::npos) { // Found a delimiter, so grab the string in between @@ -394,52 +555,9 @@ std::string VBSPReader::getToken(std::string str, const char * delim, } -std::string VBSPReader::findFileIgnoreCase(std::string filePath) -{ - std::string path; - std::string file; - DirectoryContents dirList; - std::string result; - - // Split the filename from the directory - path = getFilePath(filePath); - file = getSimpleFileName(filePath); - - // Get the list of files in this directory - dirList = getDirectoryContents(path); - - // Recursively find the path (in case some path elements are in the wrong - // case) - if (path.empty()) - { - path = "."; - dirList = getDirectoryContents("."); - } - else - { - path = findFileIgnoreCase(path); - dirList = getDirectoryContents(path); - } - - // Look for matching file in the directory, without regard to case - DirectoryContents::iterator itr; - for (itr = dirList.begin(); itr != dirList.end(); itr++) - { - if (equalCaseInsensitive(file, *itr)) - { - // This file matches, so return it - result = concatPaths(path, *itr); - return result; - } - } - - // Return an empty string - return std::string(); -} - - ref_ptr VBSPReader::readTextureFile(std::string textureName) { + int i; std::string texFile; std::string texPath; Image * texImage; @@ -447,13 +565,13 @@ ref_ptr VBSPReader::readTextureFile(std::string textureName) // Find the texture's image file texFile = std::string(textureName) + ".vtf"; - texPath = findFileIgnoreCase(texFile); + texPath = findDataFile(texFile, CASE_INSENSITIVE); // If we don't find it right away, check in a "materials" subdirectory if (texPath.empty()) { texFile = "materials/" + std::string(textureName) + ".vtf"; - texPath = findFileIgnoreCase(texFile); + texPath = findDataFile(texFile, CASE_INSENSITIVE); // Check up one directory if we don't find it here (the map file is // usually located in the "maps" directory, adjacent to the materials @@ -461,7 +579,7 @@ ref_ptr VBSPReader::readTextureFile(std::string textureName) if (texPath.empty()) { texFile = "../materials/" + std::string(textureName) + ".vtf"; - texPath = findFileIgnoreCase(texFile); + texPath = findDataFile(texFile, CASE_INSENSITIVE); } } @@ -596,7 +714,7 @@ ref_ptr VBSPReader::createBlendShader(Texture * tex1, Texture * tex2) Uniform * tex1Sampler = new Uniform(Uniform::SAMPLER_2D, "tex1"); tex1Sampler->set(0); Uniform * tex2Sampler = new Uniform(Uniform::SAMPLER_2D, "tex2"); - tex1Sampler->set(1); + tex2Sampler->set(1); // Create the program Program * blendProgram = new Program(); @@ -627,20 +745,23 @@ ref_ptr VBSPReader::readMaterialFile(std::string materialName) bool found = false; ref_ptr stateSet; std::string shaderName; + osg::Image * texImage = 0; std::string texName; std::string tex2Name; ref_ptr texture; ref_ptr texture2; + ref_ptr blend; + bool translucent; // Find the material file mtlFileName = std::string(materialName) + ".vmt"; - mtlPath = findFileIgnoreCase(mtlFileName); + mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE); // If we don't find it right away, check in a "materials" subdirectory if (mtlPath.empty()) { mtlFileName = "materials/" + std::string(materialName) + ".vmt"; - mtlPath = findFileIgnoreCase(mtlFileName); + mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE); // Check up one directory if we don't find it here (the map file is // usually located in the "maps" directory, adjacent to the materials @@ -648,7 +769,7 @@ ref_ptr VBSPReader::readMaterialFile(std::string materialName) if (mtlPath.empty()) { mtlFileName = "../materials/" + std::string(materialName) + ".vmt"; - mtlPath = findFileIgnoreCase(mtlFileName); + mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE); } } @@ -699,6 +820,9 @@ ref_ptr VBSPReader::readMaterialFile(std::string materialName) texture = NULL; texture2 = NULL; + // Assume no transparency unless the properties say otherwise + translucent = false; + // Read the material properties next while (!mtlFile->eof()) { @@ -729,6 +853,16 @@ ref_ptr VBSPReader::readMaterialFile(std::string materialName) if (!token.empty()) texture2 = readTextureFile(token); } + else if ((equalCaseInsensitive(token, "$translucent")) || + (equalCaseInsensitive(token, "$alphatest"))) + { + // Get the translucency setting + token = getToken(line, " \t\n\r\"", start); + + // Interpret the setting + if ((token == "1") || (token == "true")) + translucent = true; + } // Try the next token token = getToken(line, " \t\n\r\"", start); @@ -758,6 +892,19 @@ ref_ptr VBSPReader::readMaterialFile(std::string materialName) { stateSet->setTextureAttributeAndModes(0, texture.get(), StateAttribute::ON); + + // See if the material is translucent + if (translucent) + { + // Create and apply a blend function attribute to the + // state set + blend = new BlendFunc(BlendFunc::SRC_ALPHA, + BlendFunc::ONE_MINUS_SRC_ALPHA); + stateSet->setAttributeAndModes(blend.get(), StateAttribute::ON); + + // Set the state set's rendering hint to transparent + stateSet->setRenderingHint(StateSet::TRANSPARENT_BIN); + } } else { @@ -779,6 +926,19 @@ ref_ptr VBSPReader::readMaterialFile(std::string materialName) { stateSet->setTextureAttributeAndModes(0, texture.get(), StateAttribute::ON); + + // See if the material is translucent + if (translucent) + { + // Create and apply a blend function attribute to the + // state set + blend = new BlendFunc(BlendFunc::SRC_ALPHA, + BlendFunc::ONE_MINUS_SRC_ALPHA); + stateSet->setAttributeAndModes(blend.get(), StateAttribute::ON); + + // Set the state set's rendering hint to transparent + stateSet->setRenderingHint(StateSet::TRANSPARENT_BIN); + } } else { @@ -798,82 +958,37 @@ ref_ptr VBSPReader::readMaterialFile(std::string materialName) void VBSPReader::createScene() { - ref_ptr group; - VBSPGeometry ** vbspGeomList = 0; - ref_ptr subGroup; - Face currentFace; - TexInfo currentTexInfo; - TexData currentTexData; - char * currentTexName = 0; - char prefix[64]; - char * mtlPtr = 0; - char * tmpPtr = 0; - char tempTex[256]; - int i; - ref_ptr stateSet; + ref_ptr group; + ref_ptr subGroup; + Face currentFace; + TexInfo currentTexInfo; + TexData currentTexData; + const char * texName; + char currentTexName[256]; + char prefix[64]; + char * mtlPtr; + char * tmpPtr; + char tempTex[256]; + std::string entityText; + VBSPEntity * currentEntity; + int i; + ref_ptr stateSet; + StaticProp staticProp; + Matrixf transMat, rotMat; + Quat yaw, pitch, roll; + ref_ptr propXform; + std::string propModel; + std::string propFile; + ref_ptr propNode; - // Create the root group for the scene - group = new Group(); - - // Create VBSPGeometry object for each texdata entry in the scene - // These objects will hold the necessary geometry data until we convert - // them back into OSG geometry objects. We need one for each texdata - // entry because we'll need separate state sets for each one when they're - // converted - vbspGeomList = new VBSPGeometry *[num_texdata_entries]; - for (i = 0; i < num_texdata_entries; i++) - vbspGeomList[i] = new VBSPGeometry(this); - - // Iterate over the face list and assign faces to the appropriate geometry - // objects - for (i = 0; i < num_faces; i++) + // Load the materials and create a StateSet for each one + for (i = 0; i < bsp_data->getNumTexDatas(); i++) { - // Get the current face - currentFace = face_list[i]; - - // Get the texdata used by this face - currentTexInfo = texinfo_list[currentFace.texinfo_index]; - currentTexData = texdata_list[currentTexInfo.texdata_index]; - - // Get the texture name - currentTexName = - texdata_string_data[currentTexData.name_string_table_id]; - - // See if this is a non-drawable surface - if ((strcasecmp(currentTexName, "tools/toolsareaportal") != 0) && - (strcasecmp(currentTexName, "tools/toolsblocklos") != 0) && - (strcasecmp(currentTexName, "tools/toolsblockbullets") != 0) && - (strcasecmp(currentTexName, "tools/toolsblocklight") != 0) && - (strcasecmp(currentTexName, "tools/toolsclip") != 0) && - (strcasecmp(currentTexName, "tools/toolscontrolclip") != 0) && - (strcasecmp(currentTexName, "tools/toolsdotted") != 0) && - (strcasecmp(currentTexName, "tools/toolshint") != 0) && - (strcasecmp(currentTexName, "tools/toolsinvisible") != 0) && - (strcasecmp(currentTexName, "tools/toolsinvisibleladder") != 0) && - (strcasecmp(currentTexName, "tools/toolsnodraw") != 0) && - (strcasecmp(currentTexName, "tools/toolsnpcclip") != 0) && - (strcasecmp(currentTexName, "tools/toolsoccluder") != 0) && - (strcasecmp(currentTexName, "tools/toolsorigin") != 0) && - (strcasecmp(currentTexName, "tools/toolsskip") != 0) && - (strcasecmp(currentTexName, "tools/toolsskybox") != 0) && - (strcasecmp(currentTexName, "tools/toolsskyfog") != 0) && - (strcasecmp(currentTexName, "tools/toolstrigger") != 0)) - { - // Add the face to the appropriate VBSPGeometry object - vbspGeomList[currentTexInfo.texdata_index]->addFace(i); - } - } - - // Assemble the geometry - for (i = 0; i < num_texdata_entries; i++) - { - // Create the OSG geometry from the VBSP geometry - subGroup = vbspGeomList[i]->createGeometry(); - // Get the texdata entry and texture name - currentTexData = texdata_list[i]; - currentTexName = - texdata_string_data[currentTexData.name_string_table_id]; + currentTexData = bsp_data->getTexData(i); + texName = bsp_data-> + getTexDataString(currentTexData.name_string_table_id).c_str(); + strcpy(currentTexName, texName); // See if this is referring to an environment mapped material (we don't // handle this yet) @@ -909,94 +1024,88 @@ void VBSPReader::createScene() *mtlPtr = 0; // That should be it, so make it the texture name - currentTexName = tempTex; + strcpy(currentTexName, tempTex); } // Read the material for this geometry stateSet = readMaterialFile(currentTexName); - // If we successfully created a StateSet, add it now - if (stateSet != NULL) - subGroup->setStateSet(stateSet.get()); - - // Add the geometry to the main group - group->addChild(subGroup.get()); + // Whether we successfully created a StateSet or not, add it to the + // bsp data list now + bsp_data->addStateSet(stateSet.get()); } - // Clean up - delete [] vbspGeomList; + // Create the root group for the scene + group = new Group(); + + // Iterate through the list of entities, and try to convert all the + // visible entities to geometry + for (i = 0; i < bsp_data->getNumEntities(); i++) + { + // Get the entity + entityText = bsp_data->getEntity(i); + currentEntity = new VBSPEntity(entityText, bsp_data); + + // See if the entity is visible + if (currentEntity->isVisible()) + { + // Create geometry for the entity + subGroup = currentEntity->createGeometry(); + + // If the entity's geometry is valid, add it to the main group + if (subGroup.valid()) + group->addChild(subGroup.get()); + } + + // Done with this entity + delete currentEntity; + } + + // Iterate through the list of static props, and add them to the scene + // as well + for (i = 0; i < bsp_data->getNumStaticProps(); i++) + { + // Get the static prop + staticProp = bsp_data->getStaticProp(i); + + // Create a MatrixTransform for this prop (scale the position from + // inches to meters) + transMat.makeTranslate(staticProp.prop_origin * 0.0254); + pitch.makeRotate(osg::DegreesToRadians(staticProp.prop_angles.x()), + Vec3f(0.0, 1.0, 0.0)); + yaw.makeRotate(osg::DegreesToRadians(staticProp.prop_angles.y()), + Vec3f(0.0, 0.0, 1.0)); + roll.makeRotate(osg::DegreesToRadians(staticProp.prop_angles.z()), + Vec3f(1.0, 0.0, 0.0)); + rotMat.makeRotate(roll * pitch * yaw); + propXform = new MatrixTransform(); + propXform->setMatrix(rotMat * transMat); + + // Load the prop's model + propModel = bsp_data->getStaticPropModel(staticProp.prop_type); + propNode = osgDB::readNodeFile(propModel); + + // If we loaded the prop correctly, add it to the scene + if (propNode.valid()) + { + propXform->addChild(propNode.get()); + group->addChild(propXform.get()); + } + else + { + notify(WARN) << "Couldn't find static prop \"" << propModel; + notify(WARN) << "\"." << std::endl; + + // Couldn't find the prop, so get rid of the transform node + propXform = NULL; + } + } // Set the root node to the result root_node = group.get(); } -const char * VBSPReader::getEntity(int index) const -{ - return entity_list[index]; -} - - -const bsp::Plane & VBSPReader::getPlane(int index) const -{ - return plane_list[index]; -} - - -const osg::Vec3f & VBSPReader::getVertex(int index) const -{ - return vertex_list[index]; -} - - -const Edge & VBSPReader::getEdge(int index) const -{ - return edge_list[index]; -} - - -const int VBSPReader::getSurfaceEdge(int index) const -{ - return surface_edges[index]; -} - - -const Face & VBSPReader::getFace(int index) const -{ - return face_list[index]; -} - - -const TexInfo & VBSPReader::getTexInfo(int index) const -{ - return texinfo_list[index]; -} - - -const TexData & VBSPReader::getTexData(int index) const -{ - return texdata_list[index]; -} - - -const char * VBSPReader::getTexDataString(int index) const -{ - return texdata_string_data[index]; -} - - -const DisplaceInfo & VBSPReader::getDispInfo(int index) const -{ - return dispinfo_list[index]; -} - - -const DisplacedVertex & VBSPReader::getDispVertex(int index) const -{ - return displaced_vertex_list[index]; -} - - bool VBSPReader::readFile(const std::string & file) { osgDB::ifstream * mapFile = 0; @@ -1042,6 +1151,10 @@ bool VBSPReader::readFile(const std::string & file) processSurfEdges(*mapFile, header.lump_table[i].file_offset, header.lump_table[i].lump_length); break; + case MODELS_LUMP: + processModels(*mapFile, header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; case FACES_LUMP: processFaces(*mapFile, header.lump_table[i].file_offset, header.lump_table[i].lump_length); @@ -1072,6 +1185,10 @@ bool VBSPReader::readFile(const std::string & file) processDispVerts(*mapFile, header.lump_table[i].file_offset, header.lump_table[i].lump_length); break; + case GAME_LUMP: + processGameData(*mapFile, header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; } } } diff --git a/src/osgPlugins/bsp/VBSPReader.h b/src/osgPlugins/bsp/VBSPReader.h index 85183c31b..16469de7e 100644 --- a/src/osgPlugins/bsp/VBSPReader.h +++ b/src/osgPlugins/bsp/VBSPReader.h @@ -3,7 +3,6 @@ #include -#include #include #include #include @@ -11,6 +10,7 @@ #include #include +#include "VBSPData.h" namespace bsp { @@ -178,107 +178,52 @@ struct Header }; -struct Plane +struct GameHeader { - osg::Vec3f plane_normal; - float origin_dist; - int type; + int num_lumps; + + // This is followed by this many GameLump entries (see below) +}; + + +struct GameLump +{ + int lump_id; + unsigned short lump_flags; + unsigned short lump_version; + int lump_offset; + int lump_length; +}; + + +// This is the ID for the static prop game lump +const int STATIC_PROP_ID = (('s'<<24)+('p'<<16)+('r'<<8)+'p'); + + +struct StaticPropModelNames +{ + int num_model_names; + + // This is followed by this many names, each 128 characters long }; -struct Edge +struct StaticPropLeaves { - unsigned short vertex[2]; + int num_leaf_entries; + + // This is followed by this many unsigned shorts, indicating which BSP + // leaves this prop occupies }; -struct Face +struct StaticProps { - unsigned short plane_index; - unsigned char plane_side; - unsigned char on_node; - int first_edge; - short num_edges; - short texinfo_index; - short dispinfo_index; - short surface_fog_volume_id; - unsigned char styles[4]; - int light_offset; - float face_area; - int lightmap_texture_mins_in_luxels[2]; - int lightmap_texture_size_in_luxels[2]; - int original_face; - unsigned short num_primitives; - unsigned short first_primitive_id; - unsigned int smoothing_groups; -}; + int num_static_props; - -struct TexInfo -{ - float texture_vecs[2][4]; - float lightmap_vecs[2][4]; - int texture_flags; - int texdata_index; -}; - - -struct TexData -{ - osg::Vec3f texture_reflectivity; - int name_string_table_id; - int texture_width; - int texture_height; - int view_width; - int view_height; -}; - - -struct DisplaceSubNeighbor -{ - unsigned short neighbor_index; - unsigned char neighbor_orient; - unsigned char local_span; - unsigned char neighbor_span; -}; - - -struct DisplaceNeighbor -{ - DisplaceSubNeighbor sub_neighbors[2]; -}; - - -struct DisplaceCornerNeighbor -{ - unsigned short neighbor_indices[4]; - unsigned char neighbor_count; -}; - - -struct DisplaceInfo -{ - osg::Vec3f start_position; - int disp_vert_start; - int disp_tri_start; - int power; - int min_tesselation; - float smooth_angle; - int surface_contents; - unsigned short map_face; - int lightmap_alpha_start; - int lightmap_sample_pos_start; - DisplaceNeighbor edge_neighbors[4]; - DisplaceCornerNeighbor corner_neighbors[4]; - unsigned int allowed_verts[10]; -}; - - -struct DisplacedVertex -{ - osg::Vec3f displace_vec; - float displace_dist; - float alpha_blend; + // This is followed by this many StaticProp entries (see VBSPData.h), note + // that there are two possible StaticProp versions, depending on the + // version of the GameLump }; @@ -288,48 +233,17 @@ protected: std::string map_name; + VBSPData * bsp_data; + osg::ref_ptr root_node; - char ** entity_list; - int num_entities; - - Plane * plane_list; - int num_planes; - - osg::Vec3f * vertex_list; - int num_vertices; - - Edge * edge_list; - int num_edges; - - int * surface_edges; - int num_surf_edges; - - Face * face_list; - int num_faces; - - TexInfo * texinfo_list; - int num_texinfo_entries; - - TexData * texdata_list; - int num_texdata_entries; - + char * texdata_string; int * texdata_string_table; int num_texdata_string_table_entries; - char ** texdata_string_data; - int num_texdata_strings; - - DisplaceInfo * dispinfo_list; - int num_dispinfo_entries; - - DisplacedVertex * displaced_vertex_list; - int num_displaced_vertices; - - - void writeBlanks(int count) const; void processEntities(std::istream & str, int offset, int length); + void processModels(std::istream & str, int offset, int length); void processPlanes(std::istream & str, int offset, int length); void processVertices(std::istream & str, int offset, int length); void processEdges(std::istream & str, int offset, int length); @@ -339,15 +253,15 @@ protected: void processTexData(std::istream & str, int offset, int length); void processTexDataStringTable(std::istream & str, int offset, int length); - void processTexDataStringData(std::istream & str, int offset, - int length); + void processTexDataStringData(std::istream & str, int offset, int length); void processDispInfo(std::istream & str, int offset, int length); void processDispVerts(std::istream & str, int offset, int length); + void processGameData(std::istream & str, int offset, int length); + void processStaticProps(std::istream & str, int offset, int length, + int lumpVersion); std::string getToken(std::string str, const char * delim, - std::string::size_type & index); - - std::string findFileIgnoreCase(std::string filePath); + size_t & index); osg::ref_ptr createBlendShader(osg::Texture * tex1, osg::Texture * tex2); @@ -361,18 +275,6 @@ public: VBSPReader(); virtual ~VBSPReader(); - - const char * getEntity(int index) const; - const Plane & getPlane(int index) const; - const osg::Vec3f & getVertex(int index) const; - const Edge & getEdge(int index) const; - const int getSurfaceEdge(int index) const; - const Face & getFace(int index) const; - const TexInfo & getTexInfo(int index) const; - const TexData & getTexData(int index) const; - const char * getTexDataString(int index) const; - const DisplaceInfo & getDispInfo(int index) const; - const DisplacedVertex & getDispVertex(int index) const; bool readFile(const std::string & file); diff --git a/src/osgPlugins/bsp/VBSP_README.txt b/src/osgPlugins/bsp/VBSP_README.txt index 08a2a15ec..a98daa687 100644 --- a/src/osgPlugins/bsp/VBSP_README.txt +++ b/src/osgPlugins/bsp/VBSP_README.txt @@ -21,20 +21,40 @@ is GCFScape, available at http://www.nemesis.thewavelength.net/index.php?p=26 The plugin expects the maps and materials to be arranged as they are in the .gcf file (maps in maps/ materials and textures in materials/). Only the -maps/ and materials/ directories are used by this plugin (models aren't loaded -yet, see below). +maps/ and materials/ directories are used by this plugin. Models (in the +models/ directory) are handled by the .mdl plugin, but the .bsp plugin +typically will read model files as part of loading a .bsp map. -Make sure to preserve the file and directory structure as it is in the .gcf -files. The standard osgDB search routines don't work, because the .bsp -file and .vmt files often mix case when referring to materials, textures, etc. -The regular osgDB search routines can't do a fully case-insensitive search -for a file, so an explicit, special-purpose search is performed by the plugin -instead. +It is important to preserve the file and directory structure as it is in the +.gcf files, although you can merge the data from several files (different +games) together. For example, you can extract the models/ directory from +source models.gcf, the materials/ directory from source materials.gcf, and the +maps/, models/, and materials/ directories from half-life 2 deathmatch.gcf and +combine them together into a single parent directory (called "hl2data/", for +example). This arrangement will let you load any of the HL2 deathmatch maps, +with all of the props and materials that might be needed. + +If you're confused, here's a lame ASCII art drawing to confuse you even more: + +hl2data + | + +----maps + | + +----materials + | + +----models + + +If you want to use the OSGFILEPATH environment variable to let OSG search for +maps or models, point it to the parent directory ("hl2data" in the example +above), then load your map like this: + + osgviewer maps/dm_runoff.bsp What Works ---------- -All brush geometry and faces. +All visible brush geometry and faces (except sky boxes). Displacement surfaces. @@ -42,6 +62,13 @@ Textures (including blended textures on displacement surfaces). This makes use of the VTF Reader plugin which was submitted at the same time as this plugin. +Props (including static props embedded into the map file, and dynamic and +physics props as loaded by the mdl plugin). Note that because of Source's +physics engine, you might see some of the physics props suspended in the air +when loading a map. The level designer placed them there so they fall to +the ground when the level starts. Presumably, this is quicker and safer than +trying to get them positioned exactly on the ground. + What Doesn't Work (yet) ----------------------- @@ -62,18 +89,25 @@ seems to work well enough for now. Water. Water shaders in the Source engine are a lot more complicated than the generic lightmapped and vertex lit shaders. Currently, water surfaces just -show up solid white. +show up mostly white. At the very least, this needs environment map support to +work properly. -Props (both static and physics). This is a big one, as props often contribute -to the overall flow and feeling of a map. Unfortunately, this requires yet -another plugin to load the .mdl/.vvd/.vtx files that store Source models. -These formats aren't nearly as well documented as the .bsp format, which is the -main reason they're not done yet. +Sky boxes. These were left out by design (they tended to get in the way +when doing the work required by the sponsor). These could easily be put +back in, and this would probably make a good ReaderWriter option. World lights (light sources). Should be simple to read, but I don't know how many you'll find on a given map. If there are a lot of them, you'll have to be creative on how they're rendered. +Environmental effects (fires, dust clouds, etc.). Each one would likely +require specific code. + +Certain classes of props (collectable items, weapons, etc.) are not loaded +right now. With these props, there is an explicit mapping between the prop's +object class and the model that gets loaded for it. I just haven't set up +these mappings yet. + HDR materials, bump maps, detail props, and other eye-candy. Implement it if you want! :-) diff --git a/src/osgPlugins/mdl/BodyPart.cpp b/src/osgPlugins/mdl/BodyPart.cpp new file mode 100644 index 000000000..0c18fb40e --- /dev/null +++ b/src/osgPlugins/mdl/BodyPart.cpp @@ -0,0 +1,48 @@ + +#include "BodyPart.h" + + +using namespace mdl; + + +BodyPart::BodyPart(MDLBodyPart * myPart) +{ + // Save the body part information + my_body_part = myPart; +} + + +BodyPart::~BodyPart() +{ + // Clean up the associated data + delete my_body_part; +} + + +MDLBodyPart * BodyPart::getBodyPart() +{ + return my_body_part; +} + + +void BodyPart::addModel(Model * newModel) +{ + // Add the new node to our list + part_models.push_back(newModel); +} + + +int BodyPart::getNumModels() +{ + return part_models.size(); +} + + +Model * BodyPart::getModel(int partIndex) +{ + if ((partIndex < 0) || (partIndex >= part_models.size())) + return NULL; + else + return part_models[partIndex]; +} + diff --git a/src/osgPlugins/mdl/BodyPart.h b/src/osgPlugins/mdl/BodyPart.h new file mode 100644 index 000000000..0693e54f9 --- /dev/null +++ b/src/osgPlugins/mdl/BodyPart.h @@ -0,0 +1,50 @@ + +#ifndef __BODY_PART_H_ +#define __BODY_PART_H_ + + +#include + +#include "Model.h" + + +namespace mdl +{ + + +struct MDLBodyPart +{ + int mdl_name_index; + int num_models; + int body_part_base; + int model_offset; +}; + + +class BodyPart +{ +protected: + + typedef std::vector ModelList; + + MDLBodyPart * my_body_part; + + ModelList part_models; + +public: + + BodyPart(MDLBodyPart * myPart); + virtual ~BodyPart(); + + MDLBodyPart * getBodyPart(); + + void addModel(Model * newModel); + int getNumModels(); + Model * getModel(int modelIndex); +}; + + +} + +#endif + diff --git a/src/osgPlugins/mdl/CMakeLists.txt b/src/osgPlugins/mdl/CMakeLists.txt new file mode 100644 index 000000000..0123e0bc4 --- /dev/null +++ b/src/osgPlugins/mdl/CMakeLists.txt @@ -0,0 +1,26 @@ +SET(TARGET_SRC + ReaderWriterMDL.cpp + BodyPart.cpp + Mesh.cpp + Model.cpp + MDLReader.cpp + MDLRoot.cpp + VTXReader.cpp + VVDReader.cpp +) + +SET(TARGET_H + ReaderWriterMDL.h + BodyPart.h + Mesh.h + Model.h + MDLLimits.h + MDLReader.h + MDLRoot.h + VTXReader.h + VVDReader.h +) + +#### end var setup ### +SETUP_PLUGIN(mdl) + diff --git a/src/osgPlugins/mdl/MDLLimits.h b/src/osgPlugins/mdl/MDLLimits.h new file mode 100644 index 000000000..3a68de141 --- /dev/null +++ b/src/osgPlugins/mdl/MDLLimits.h @@ -0,0 +1,22 @@ + +#ifndef __MDL_LIMITS_H_ +#define __MDL_LIMITS_H_ + + +namespace mdl +{ + + +// Maximum number of LODs per model +const int MAX_LODS = 8; + + +// Maximum number of bones per vertex +const int MAX_BONES_PER_VERTEX = 3; + + +} + + +#endif + diff --git a/src/osgPlugins/mdl/MDLReader.cpp b/src/osgPlugins/mdl/MDLReader.cpp new file mode 100644 index 000000000..b8c5251f8 --- /dev/null +++ b/src/osgPlugins/mdl/MDLReader.cpp @@ -0,0 +1,677 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MDLReader.h" +#include "VVDReader.h" +#include "VTXReader.h" + + +using namespace mdl; +using namespace osg; +using namespace osgDB; + + +MDLReader::MDLReader() +{ + // Start with no root node + root_node = NULL; +} + + +MDLReader::~MDLReader() +{ +} + + +std::string MDLReader::getToken(std::string str, const char * delim, + size_t & index) +{ + size_t start, end; + std::string token; + + // Look for the first non-occurrence of the delimiters + start = str.find_first_not_of(" \t\n\r\"", index); + if (start != std::string::npos) + { + // From there, look for the first occurrence of a delimiter + end = str.find_first_of(" \t\n\r\"", start+1); + if (end != std::string::npos) + { + // Found a delimiter, so grab the string in between + token = str.substr(start, end-start); + } + else + { + // Ran off the end of the string, so just grab everything from + // the first good character + token = str.substr(start); + } + } + else + { + // No token to be found + token = ""; + } + + // Update the index (in case we want to keep looking for tokens in this + // string) + if (end != std::string::npos) + index = end+1; + else + index = std::string::npos; + + // Return the token + return token; +} + + +ref_ptr MDLReader::readTextureFile(std::string textureName) +{ + int i; + std::string texFile; + std::string texPath; + Image * texImage; + Texture * texture; + + // Find the texture's image file + texFile = std::string(textureName) + ".vtf"; + texPath = findDataFile(texFile, CASE_INSENSITIVE); + + // If we don't find it right away, check in a "materials" subdirectory + if (texPath.empty()) + { + // Check for a leading slash and concatenate appropriately + if ((textureName[0] == '\\') || (textureName[0] == '/')) + texFile = "materials" + std::string(textureName) + ".vtf"; + else + texFile = "materials/" + std::string(textureName) + ".vtf"; + + // Look for the texture at this location + texPath = findDataFile(texFile, CASE_INSENSITIVE); + + // Check up one directory if we don't find it here (the map file is + // usually located in the "maps" directory, adjacent to the materials + // directory) + if (texPath.empty()) + { + // Check for a leading slash and concatenate appropriately + if ((textureName[0] == '\\') || (textureName[0] == '/')) + texFile = "../materials" + std::string(textureName) + ".vtf"; + else + texFile = "../materials/" + std::string(textureName) + ".vtf"; + + // Look for the texture at this location + texPath = findDataFile(texFile, CASE_INSENSITIVE); + } + } + + // If we found the file, read it, otherwise bail + if (!texPath.empty()) + { + texImage = readImageFile(texPath); + + // If we got the image, create the texture attribute + if (texImage != NULL) + { + // Create the texture + if (texImage->t() == 1) + { + texture = new Texture1D(); + ((Texture1D *)texture)->setImage(texImage); + } + else if (texImage->r() == 1) + { + texture = new Texture2D(); + ((Texture2D *)texture)->setImage(texImage); + } + else + { + texture = new Texture3D(); + ((Texture3D *)texture)->setImage(texImage); + } + + // Set texture attributes + texture->setWrap(Texture::WRAP_S, Texture::REPEAT); + texture->setWrap(Texture::WRAP_T, Texture::REPEAT); + texture->setWrap(Texture::WRAP_R, Texture::REPEAT); + texture->setFilter(Texture::MAG_FILTER, Texture::LINEAR); + texture->setFilter(Texture::MIN_FILTER, + Texture::LINEAR_MIPMAP_LINEAR); + } + else + { + // We were unable to find the texture file + notify(WARN) << "Couldn't find texture " << textureName; + notify(WARN) << std::endl; + + // No texture + texture = NULL; + } + } + else + { + // We were unable to find the texture file + notify(WARN) << "Couldn't find texture " << textureName; + notify(WARN) << std::endl; + + // No texture + texture = NULL; + } + + return texture; +} + + +ref_ptr MDLReader::readMaterialFile(std::string materialName) +{ + std::string mtlFileName; + std::string mtlPath; + StringList::iterator searchItr; + std::ifstream * mtlFile; + std::string line; + size_t start; + std::string token; + bool found; + ref_ptr stateSet; + std::string shaderName; + osg::Image * texImage; + std::string texName; + std::string tex2Name; + ref_ptr texture; + ref_ptr texture2; + ref_ptr blend; + bool translucent; + + // Find the material file + mtlFileName = std::string(materialName) + ".vmt"; + mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE); + + // If we don't find it right away, search the texture file search paths + if (mtlPath.empty()) + { + searchItr = texture_paths.begin(); + while ((mtlPath.empty()) && (searchItr != texture_paths.end())) + { + // The search paths assume that materials are always located in + // a "materials" subdirectory. Also, check to see if there is + // a leading slash and concatenate appropriately + if (((*searchItr)[0] == '\\') || ((*searchItr)[0] == '/')) + mtlFileName = "materials" + *searchItr + + std::string(materialName) + ".vmt"; + else + mtlFileName = "materials/" + *searchItr + + std::string(materialName) + ".vmt"; + + // Try to find the file in this path + mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE); + + // Next path + searchItr++; + } + + // If we still haven't found it, check up one directory level (the + // model file is usually located in the "models" directory, adjacent + // to the "materials" directory) + if (mtlPath.empty()) + { + searchItr = texture_paths.begin(); + while ((mtlPath.empty()) && (searchItr != texture_paths.end())) + { + // The search paths assume that materials are always located in + // a "materials" subdirectory, but this time try going up one + // level first + if (((*searchItr)[0] == '\\') || ((*searchItr)[0] == '/')) + mtlFileName = "../materials" + *searchItr + + std::string(materialName) + ".vmt"; + else + mtlFileName = "../materials/" + *searchItr + + std::string(materialName) + ".vmt"; + + // Try to find the file in this path + mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE); + + // Next path + searchItr++; + } + } + } + + // See if we found the file + if (!mtlPath.empty()) + { + // Try to open the file, bail out if we fail + mtlFile = new std::ifstream(mtlPath.c_str(), std::ifstream::in); + if (!mtlFile) + return NULL; + } + else + { + // Didn't find the material file, so return NULL + notify(WARN) << "Can't find material " << materialName << std::endl; + return NULL; + } + + // First, look for the shader name + found = false; + while ((!found) && (!mtlFile->eof())) + { + // Read a line from the file + std::getline(*mtlFile, line); + + // Try to find the shader name + start = 0; + token = getToken(line, " \t\n\r\"", start); + + // If we got something, it must be the shader + if ((!token.empty()) && (token.compare(0, 2, "//") != 0)) + { + shaderName = token; + found = true; + } + } + + // If we didn't find a shader, this isn't a valid material file + if (!found) + { + mtlFile->close(); + notify(WARN) << "Material " << materialName << " isn't valid."; + notify(WARN) << std::endl; + return NULL; + } + + // No textures loaded yet + texture = NULL; + texture2 = NULL; + + // Assume not translucent unless the properties say otherwise + translucent = false; + + // Read the material properties next + while (!mtlFile->eof()) + { + // Get the next line + std::getline(*mtlFile, line); + + // Look for tokens starting at the beginning + start = 0; + token = getToken(line, " \t\n\r\"", start); + + while ((!token.empty()) && (token.compare(0, 2, "//") != 0)) + { + if (equalCaseInsensitive(token, "$basetexture")) + { + // Get the base texture name + token = getToken(line, " \t\n\r\"", start); + + // Read the texture + if (!token.empty()) + texture = readTextureFile(token); + } + else if (equalCaseInsensitive(token, "$basetexture2")) + { + // Get the second base texture name + token = getToken(line, " \t\n\r\"", start); + + // Read the texture + if (!token.empty()) + texture2 = readTextureFile(token); + } + else if ((equalCaseInsensitive(token, "$translucent")) || + (equalCaseInsensitive(token, "$alphatest"))) + { + // Get the translucency setting + token = getToken(line, " \t\n\r\"", start); + + // Interpret the setting + if (!token.empty()) + { + if ((token == "1") || (token == "true")) + translucent = true; + } + } + + // Try the next token + token = getToken(line, " \t\n\r\"", start); + } + } + + // Start with no StateSet (in case the stuff below fails) + stateSet = NULL; + + // Check the shader's name + if (equalCaseInsensitive(shaderName, "UnlitGeneric")) + { + // Create the StateSet + stateSet = new StateSet(); + + // Disable lighting on this StateSet + stateSet->setMode(GL_LIGHTING, StateAttribute::OFF); + + // Add the texture attribute (or disable texturing if no base texture) + if (texture != NULL) + { + stateSet->setTextureAttributeAndModes(0, texture.get(), + StateAttribute::ON); + } + else + { + notify(WARN) << "No base texture for material " << materialName; + notify(WARN) << std::endl; + stateSet->setTextureMode(0, GL_TEXTURE_2D, StateAttribute::OFF); + } + + // See if the material is translucent + if (translucent) + { + // Add the blending attribute as well + blend = new BlendFunc(BlendFunc::SRC_ALPHA, + BlendFunc::ONE_MINUS_SRC_ALPHA); + stateSet->setAttributeAndModes(blend.get(), StateAttribute::ON); + + // Set the rendering hint for this stateset to transparent + stateSet->setRenderingHint(StateSet::TRANSPARENT_BIN); + } + } + else + { + // All other shaders fall back to fixed function + // TODO: LightMappedGeneric shader + + // Create the StateSet + stateSet = new StateSet(); + + // Add the texture attribute (or disable texturing if no base texture) + if (texture != NULL) + { + stateSet->setTextureAttributeAndModes(0, texture.get(), + StateAttribute::ON); + + // See if the material is translucent + if (translucent) + { + // Add the blending attribute as well + blend = new BlendFunc(BlendFunc::SRC_ALPHA, + BlendFunc::ONE_MINUS_SRC_ALPHA); + stateSet->setAttributeAndModes(blend.get(), + StateAttribute::ON); + + // Set the rendering hint for this stateset to transparent + stateSet->setRenderingHint(StateSet::TRANSPARENT_BIN); + } + } + else + { + notify(WARN) << "No base texture for material " << materialName; + notify(WARN) << std::endl; + stateSet->setTextureMode(0, GL_TEXTURE_2D, StateAttribute::OFF); + } + } + + // Close the file + mtlFile->close(); + + // Return the resulting StateSet + return stateSet; +} + + +BodyPart * MDLReader::processBodyPart(std::istream * str, int offset) +{ + int i; + MDLBodyPart * part; + BodyPart * partNode; + Model * modelNode; + + // Seek to the body part + str->seekg(offset); + + // Read it + part = new MDLBodyPart; + str->read((char *) part, sizeof(MDLBodyPart)); + + // Create the body part node + partNode = new BodyPart(part); + + // Process the models + for (i = 0; i < part->num_models; i++) + { + // Process the model + modelNode = processModel(str, offset + part->model_offset + + (i * sizeof(MDLModel))); + + // Add the model to the body part + partNode->addModel(modelNode); + } + + // Return the new node + return partNode; +} + + +Model * MDLReader::processModel(std::istream * str, int offset) +{ + int i; + MDLModel * model; + Model * modelNode; + Mesh * meshNode; + + // Seek to the model + str->seekg(offset); + + // Read it + model = new MDLModel; + str->read((char *) model, sizeof(MDLModel)); + + // Create the model node + modelNode = new Model(model); + + // Process the meshes + for (i = 0; i < model->num_meshes; i++) + { + // Process the mesh + meshNode = processMesh(str, offset + model->mesh_offset + + (i * sizeof(MDLMesh))); + + // Add the mesh to the model + modelNode->addMesh(meshNode); + } + + // Return the model node + return modelNode; +} + + +Mesh * MDLReader::processMesh(std::istream * str, int offset) +{ + int i; + MDLMesh * mesh; + Mesh * meshNode; + + // Seek to the mesh + str->seekg(offset); + + // Read it + mesh = new MDLMesh; + str->read((char *) mesh, sizeof(MDLMesh)); + + // Create the mesh node + meshNode = new Mesh(mesh); + + // Set the mesh's state set based on the material id + meshNode->setStateSet((state_sets[mesh->material_index]).get()); + + // Return the mesh node + return meshNode; +} + + +bool MDLReader::readFile(const std::string & file) +{ + std::string baseName; + std::string fileName; + std::ifstream * mdlFile; + MDLHeader header; + int i, j; + int offset; + MDLRoot * mdlRoot; + BodyPart * partNode; + std::string vvdFile; + VVDReader * vvdReader; + std::string vtxFile; + VTXReader * vtxReader; + + // Remember the model name + mdl_name = getStrippedName(file); + + // Try to open the file + fileName = findDataFile(file, CASE_INSENSITIVE); + mdlFile = new std::ifstream(fileName.c_str(), std::ios::binary); + if (!mdlFile) + { + osg::notify(osg::NOTICE) << "MDL file not found" << std::endl; + return false; + } + + // Read the header + mdlFile->read((char *) &header, sizeof(MDLHeader)); + + // Make sure the file is a valid Valve MDL file + if (header.magic_number != MDL_MAGIC_NUMBER) + { + osg::notify(osg::NOTICE) << "This is not a valid .mdl file"; + osg::notify(osg::NOTICE) << std::endl; + + // Close the file before we quit + mdlFile->close(); + delete mdlFile; + + return false; + } + + // Make sure the version is one that we handle + // TODO: Not sure which versions are valid yet + + // Get the texture paths from the file (we'll have to search these paths + // for each texture that we load) + for (i = 0; i < header.num_texture_paths; i++) + { + int texPathBase; + int texPathOffset; + char texPath[256]; + + texPathBase = header.texture_path_offset + (i * sizeof(int)); + mdlFile->seekg(texPathBase); + mdlFile->read((char *) &texPathOffset, sizeof(int)); + mdlFile->seekg(texPathOffset); + + // Read characters from the file until we reach the end of this path + j = 0; + do + { + mdlFile->get(texPath[j]); + j++; + } + while ((j < sizeof(texPath)) && (texPath[j-1] != 0)); + + // Store this path + texture_paths.push_back(texPath); + } + + // Read the texture info from the file, and create a StateSet for each + // one + for (i = 0; i < header.num_textures; i++) + { + int texBase; + MDLTexture tempTex; + char texName[256]; + ref_ptr stateSet; + + texBase = header.texture_offset + (i * sizeof(MDLTexture)); + mdlFile->seekg(texBase); + mdlFile->read((char *) &tempTex, sizeof(MDLTexture)); + mdlFile->seekg(texBase + tempTex.tex_name_offset); + j = 0; + do + { + mdlFile->get(texName[j]); + j++; + } + while ((j < sizeof(texName)) && (texName[j-1] != 0)); + + // Load this texture + stateSet = readMaterialFile(texName); + + // Add it to our list + state_sets.push_back(stateSet); + } + + // Create the root node of the MDL tree + mdlRoot = new MDLRoot(); + + // Process the main model's body parts + for (i = 0; i < header.num_body_parts; i++) + { + // Calculate the offset to the next body part + offset = header.body_part_offset + (i * sizeof(MDLBodyPart)); + + // Process the body part and get the part's node + partNode = processBodyPart(mdlFile, offset); + + // Add the body part to the MDL root node + mdlRoot->addBodyPart(partNode); + } + + // Open the VVD file that goes with this model + vvdFile = findDataFile(getNameLessExtension(file) + ".vvd", + CASE_INSENSITIVE); + vvdReader = new VVDReader(); + vvdReader->readFile(vvdFile); + + // Open the VTX file that goes with this model (at this point, I don't + // see a reason not to always just use the DX9 version) + vtxFile = findDataFile(getNameLessExtension(file) + ".dx90.vtx", + CASE_INSENSITIVE); + vtxReader = new VTXReader(vvdReader, mdlRoot); + vtxReader->readFile(vtxFile); + + // Get the root group from the VTX reader + root_node = vtxReader->getModel(); + + // Close the .mdl file + mdlFile->close(); + delete mdlFile; + + // Close the two auxiliary readers + delete vvdReader; + delete vtxReader; + + // Clean up the MDL tree (we don't need its data anymore) + delete mdlRoot; + + // Return true to indicate success + return true; +} + + +ref_ptr MDLReader::getRootNode() +{ + return root_node; +} + + diff --git a/src/osgPlugins/mdl/MDLReader.h b/src/osgPlugins/mdl/MDLReader.h new file mode 100644 index 000000000..d5c571f9d --- /dev/null +++ b/src/osgPlugins/mdl/MDLReader.h @@ -0,0 +1,193 @@ +#ifndef __MDL_READER_H_ +#define __MDL_READER_H_ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MDLLimits.h" +#include "MDLRoot.h" + + +namespace mdl +{ + + +// The magic number for a Valve MDL file is 'IDST' in little-endian +// order +const int MDL_MAGIC_NUMBER = (('T'<<24)+('S'<<16)+('D'<<8)+'I'); + + +struct MDLHeader +{ + int magic_number; + int mdl_version; + int check_sum; + char mdl_name[64]; + int mdl_length; + + osg::Vec3 eye_position; + osg::Vec3 illum_position; + osg::Vec3 hull_min; + osg::Vec3 hull_max; + osg::Vec3 view_bbox_min; + osg::Vec3 view_bbox_max; + + int mdl_flags; + + int num_bones; + int bone_offset; + + int num_bone_controllers; + int bone_controller_offset; + + int num_hitbox_sets; + int hitbox_set_offset; + + int num_local_animations; + int local_animation_offset; + + int num_local_sequences; + int local_sequence_offset; + + mutable int activity_list_version; + mutable int events_offseted; + + int num_textures; + int texture_offset; + + int num_texture_paths; + int texture_path_offset; + + int num_skin_refs; + int num_skin_families; + int skin_offset; + + int num_body_parts; + int body_part_offset; + + int num_local_attachments; + int local_attachment_offset; + + int num_local_nodes; + int local_node_offset; + int local_node_name_offset; + + int num_flex_desc; + int flex_desc_offset; + + int num_flex_controllers; + int flex_controller_offset; + + int num_flex_rules; + int flex_rule_offset; + + int num_ik_chains; + int ik_chain_offset; + + int num_mouths; + int mouth_offset; + + int num_local_pose_params; + int local_pose_param_offset; + + int surface_prop_offset; + + int key_value_offset; + int key_value_size; + + int num_local_ik_autoplay_locks; + int local_ik_autoplay_lock_offset; + + float mdl_mass; + int mdl_contents; + + int num_include_models; + int include_model_offset; + + // Originally a mutable void * (changed for portability) + mutable int virtual_model; + + int anim_block_name_offset; + int num_anim_blocks; + int anim_block_offset; + + // Originally a mutable void * (changed for portability) + mutable int anim_block_model; + + int bone_table_by_name_offset; + + // Originally both void * (changed for portability) + int vertex_base; + int offset_base; + + u_char const_direction_light_dot; + u_char root_lod; + u_char unused_byte[2]; + + int zero_frame_cache_offset; + + int unused_fields[2]; +}; + + +struct MDLTexture +{ + int tex_name_offset; + int tex_flags; + int tex_used; + + int unused_1; + + // Originally both mutable void * (changed for portability) + mutable int tex_material; + mutable int client_material; + + int unused_array[10]; +}; + + +class MDLReader +{ +protected: + + std::string mdl_name; + + osg::ref_ptr root_node; + + typedef std::vector StringList; + StringList texture_paths; + + typedef std::vector< osg::ref_ptr > StateSetList; + StateSetList state_sets; + + std::string getToken(std::string str, const char * delim, size_t & index); + std::string findFileIgnoreCase(std::string filePath); + + osg::ref_ptr readTextureFile(std::string textureName); + osg::ref_ptr readMaterialFile(std::string mtlName); + + BodyPart * processBodyPart(std::istream * str, int offset); + Model * processModel(std::istream * str, int offset); + Mesh * processMesh(std::istream * str, int offset); + +public: + + MDLReader(); + virtual ~MDLReader(); + + bool readFile(const std::string & file); + + osg::ref_ptr getRootNode(); +}; + + +} + +#endif diff --git a/src/osgPlugins/mdl/MDLRoot.cpp b/src/osgPlugins/mdl/MDLRoot.cpp new file mode 100644 index 000000000..85e4a4b2c --- /dev/null +++ b/src/osgPlugins/mdl/MDLRoot.cpp @@ -0,0 +1,38 @@ + +#include "MDLRoot.h" + + +using namespace mdl; + + +MDLRoot::MDLRoot() +{ +} + + +MDLRoot::~MDLRoot() +{ +} + + +void MDLRoot::addBodyPart(BodyPart * newPart) +{ + // Add the new part to our list + body_parts.push_back(newPart); +} + + +int MDLRoot::getNumBodyParts() +{ + return body_parts.size(); +} + + +BodyPart * MDLRoot::getBodyPart(int partIndex) +{ + if ((partIndex < 0) || (partIndex >= body_parts.size())) + return NULL; + else + return body_parts[partIndex]; +} + diff --git a/src/osgPlugins/mdl/MDLRoot.h b/src/osgPlugins/mdl/MDLRoot.h new file mode 100644 index 000000000..e5687f55c --- /dev/null +++ b/src/osgPlugins/mdl/MDLRoot.h @@ -0,0 +1,37 @@ + +#ifndef __MDL_ROOT_H_ +#define __MDL_ROOT_H_ + + +#include + +#include "BodyPart.h" + + +namespace mdl +{ + + +class MDLRoot +{ +protected: + + typedef std::vector BodyPartList; + + BodyPartList body_parts; + +public: + + MDLRoot(); + virtual ~MDLRoot(); + + void addBodyPart(BodyPart * newPart); + int getNumBodyParts(); + BodyPart * getBodyPart(int partIndex); +}; + + +} + +#endif + diff --git a/src/osgPlugins/mdl/MDL_README.txt b/src/osgPlugins/mdl/MDL_README.txt new file mode 100644 index 000000000..82b607bad --- /dev/null +++ b/src/osgPlugins/mdl/MDL_README.txt @@ -0,0 +1,97 @@ + +Source Engine MDL reader for OSG + +by Jason Daly + +Overview +-------- +This plugin allows .mdl files from games that make use of Valve's Source +engine (Half Life 2, etc) to be loaded by OSG. + +I've tested this plugin with dozens of on several HL2 models, as well as some +3rd party models. + + +Using the Plugin +---------------- +If you want to load models from the original Source engine games, you'll need +to extract the relevant models and materials from the .gcf files that come +with the game. A good tool for this is GCFScape, available at +http://www.nemesis.thewavelength.net/index.php?p=26 + +The plugin expects the models and materials to be arranged as they are in the +.gcf files (models in models/ materials and textures in materials/). Only the +models/ and materials/ directories are used by this plugin. Note that the +.mdl plugin typically will need to read additional data from companion .vvd +and .vtx files, as well as textures from other files in the materials/ +directory. + +It is important to preserve the file and directory structure as it is in the +.gcf files, although you can merge the data from several files (different +games) together. For example, you can extract the models/ directory from +source models.gcf, the materials/ directory from source materials.gcf, and the +maps/, models/, and materials/ directories from half-life 2 deathmatch.gcf and +combine them together into a single parent directory (called "hl2data/", for +example). This arrangement will let you load any of the HL2 deathmatch maps, +with all of the props and materials that might be needed. + +If you're confused, here's a lame ASCII art drawing to confuse you even more: + +hl2data + | + +----maps + | + +----materials + | + +----models + + +If you want to use the OSGFILEPATH environment variable to let OSG search for +maps or models, point it to the parent directory ("hl2data" in the example +above), then load your model like this: + + osgviewer models/alyx.mdl + + +What Works +---------- +All geometry and textures. + +Some models have multiple sub-models in them (for example, a door model might +have multiple variations of handles). These are supported with a switch, +but you'll have to find it and manipulate it yourself (I can't figure out +how the Source engine selects which sub-model to show). + + +What Doesn't Work (yet) +----------------------- +Skeletons and bone animations. My guess is that this would be possible using +osgAnimation, but I didn't have time to do it for the first cut. + +Facial animations. Would require support for vertex morphing (I don't know +if osgAnimation provides this yet or not). + +Physics. There is no OSG infrastructure yet, and I can't find much information +on the .phy file format that models use for physics calculations. Rag-doll +physics obviously doesn't work either. + +Eyes. This is one of the more ugly artifacts you'll see if you load a +character model. Only the whites of the eyes are drawn (no iris or pupil). +The eyes (as well as the teeth) use special shaders that I didn't have time to +figure out. This is ugly enough that I might come back to it soon. + + +Acknowledgements +---------------- +This plugin was written for some real-world work I'm doing at the University +of Central Florida Institute for Simulation and Training. I want to thank +our sponsors for funding our work and allowing me to contribute this code to +the OSG community. + +Portions of the code borrow heavily from the Source SDK. Most of the +file format reading came from the header files for studio models, so thanks to +Valve for making much of that code public. + +Of course, this code would be pointless without the Open Scene Graph and +all of its contributors. + diff --git a/src/osgPlugins/mdl/Mesh.cpp b/src/osgPlugins/mdl/Mesh.cpp new file mode 100644 index 000000000..6492dda09 --- /dev/null +++ b/src/osgPlugins/mdl/Mesh.cpp @@ -0,0 +1,46 @@ + +#include "Mesh.h" + + +using namespace mdl; + + +Mesh::Mesh(MDLMesh * myMesh) +{ + // Save the mesh information + my_mesh = myMesh; + + // Initialize the state set to NULL + state_set = NULL; +} + + +Mesh::~Mesh() +{ + // Clean up the associated data + delete my_mesh; +} + + +void Mesh::setStateSet(osg::StateSet * stateSet) +{ + state_set = stateSet; +} + + +osg::StateSet * Mesh::getStateSet() +{ + return state_set.get(); +} + + +MDLMesh * Mesh::getMesh() +{ + return my_mesh; +} + + +int Mesh::getNumLODVertices(int lodNum) +{ + return my_mesh->vertex_data.num_lod_vertices[lodNum]; +} diff --git a/src/osgPlugins/mdl/Mesh.h b/src/osgPlugins/mdl/Mesh.h new file mode 100644 index 000000000..c836861b7 --- /dev/null +++ b/src/osgPlugins/mdl/Mesh.h @@ -0,0 +1,76 @@ + +#ifndef __MESH_H_ +#define __MESH_H_ + + +#include +#include + +#include "MDLLimits.h" + + +namespace mdl +{ + + +struct MDLMeshVertexData +{ + // Used by the Source engine for cache purposes. This value is allocated + // in the file, but no meaningful data is stored there + int model_vertex_data_ptr; + + // Indicates the number of vertices used by each LOD of this mesh + int num_lod_vertices[MAX_LODS]; +}; + + +struct MDLMesh +{ + int material_index; + int model_index; + + int num_vertices; + int vertex_offset; + + int num_flexes; + int flex_offset; + + int material_type; + int material_param; + + int mesh_id; + + osg::Vec3f mesh_center; + + MDLMeshVertexData vertex_data; + + int unused_array[8]; +}; + + +class Mesh +{ +protected: + + MDLMesh * my_mesh; + + osg::ref_ptr state_set; + +public: + + Mesh(MDLMesh * myMesh); + virtual ~Mesh(); + + void setStateSet(osg::StateSet * stateSet); + osg::StateSet * getStateSet(); + + MDLMesh * getMesh(); + + int getNumLODVertices(int lodNum); +}; + + +} + +#endif + diff --git a/src/osgPlugins/mdl/Model.cpp b/src/osgPlugins/mdl/Model.cpp new file mode 100644 index 000000000..b95700e96 --- /dev/null +++ b/src/osgPlugins/mdl/Model.cpp @@ -0,0 +1,56 @@ + +#include "Model.h" +#include "VVDReader.h" + + +using namespace mdl; + + +Model::Model(MDLModel * myModel) +{ + // Save the model information + my_model = myModel; +} + + +Model::~Model() +{ + // Clean up the associated data + delete my_model; +} + + +MDLModel * Model::getModel() +{ + return my_model; +} + + +int Model::getVertexBase() +{ + // Return the base index for this model's vertices + return my_model->vertex_index / sizeof(VVDVertex); +} + + +void Model::addMesh(Mesh * newMesh) +{ + // Add the new node to our list + model_meshes.push_back(newMesh); +} + + +int Model::getNumMeshes() +{ + return model_meshes.size(); +} + + +Mesh * Model::getMesh(int meshIndex) +{ + if ((meshIndex < 0) || (meshIndex >= model_meshes.size())) + return NULL; + else + return model_meshes[meshIndex]; +} + diff --git a/src/osgPlugins/mdl/Model.h b/src/osgPlugins/mdl/Model.h new file mode 100644 index 000000000..1152fe5d7 --- /dev/null +++ b/src/osgPlugins/mdl/Model.h @@ -0,0 +1,76 @@ + +#ifndef __MODEL_H_ +#define __MODEL_H_ + +#include + +#include "Mesh.h" + + +namespace mdl +{ + + +struct MDLModelVertexData +{ + // No useful values are stored in the file for this structure, but we + // need the size to be right so we can properly read subsequent models + // from the file + int vertex_data_ptr; + int tangent_data_ptr; +}; + + +struct MDLModel +{ + char model_name[64]; + int model_type; + float bounding_radius; + int num_meshes; + int mesh_offset; + + int num_vertices; + int vertex_index; + int tangents_index; + + int num_attachments; + int attachment_offset; + int num_eyeballs; + int eyeball_offset; + + MDLModelVertexData vertex_data; + + int unused_array[8]; +}; + + + +class Model +{ +protected: + + typedef std::vector MeshList; + + MDLModel * my_model; + + MeshList model_meshes; + +public: + + Model(MDLModel * myModel); + virtual ~Model(); + + MDLModel * getModel(); + + int getVertexBase(); + + void addMesh(Mesh * newMesh); + int getNumMeshes(); + Mesh * getMesh(int meshIndex); +}; + + +} + +#endif + diff --git a/src/osgPlugins/mdl/ReaderWriterMDL.cpp b/src/osgPlugins/mdl/ReaderWriterMDL.cpp new file mode 100644 index 000000000..5d27ac9d1 --- /dev/null +++ b/src/osgPlugins/mdl/ReaderWriterMDL.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include + +#include "ReaderWriterMDL.h" +#include "MDLReader.h" + +using namespace mdl; +using namespace osg; +using namespace osgDB; + + +const char* ReaderWriterMDL::className() const +{ + // Return a description of this class + return "Valve/Source Engine MDL Reader"; +} + + +bool ReaderWriterMDL::acceptsExtension(const std::string& extension) const +{ + // If the extension is empty or "mdl", we accept it + return osgDB::equalCaseInsensitive(extension, "mdl") || extension.empty(); +} + + +ReaderWriter::ReadResult ReaderWriterMDL::readNode( + const std::string& file, + const ReaderWriter::Options* options) const +{ + MDLReader * mdlReader; + ref_ptr result; + + // See if we handle this kind of file + if (!acceptsExtension(osgDB::getFileExtension(file))) + return ReadResult::FILE_NOT_HANDLED; + + // See if we can find the requested file + std::string fileName = osgDB::findDataFile(file, options, CASE_INSENSITIVE); + if (fileName.empty()) + return ReadResult::FILE_NOT_FOUND; + + // Read the file (pass the base name and not the file that was found, this + // allows us to also find the .vvd and .vtx files without the leading + // path confusing things) + mdlReader = new MDLReader(); + if (mdlReader->readFile(file)) + { + // Get the results of our read + result = mdlReader->getRootNode(); + + // Clean up the reader + delete mdlReader; + + // Return the results + return ReadResult(result.get()); + } + else + { + // Clean up the reader + delete mdlReader; + + // Return the error + return ReadResult::ERROR_IN_READING_FILE; + } +} + + +REGISTER_OSGPLUGIN(mdl, ReaderWriterMDL) + diff --git a/src/osgPlugins/mdl/ReaderWriterMDL.h b/src/osgPlugins/mdl/ReaderWriterMDL.h new file mode 100644 index 000000000..799efa461 --- /dev/null +++ b/src/osgPlugins/mdl/ReaderWriterMDL.h @@ -0,0 +1,27 @@ +#ifndef __READERWRITER_MDL_H_ +#define __READERWRITER_MDL_H_ + + +#include +#include + + +namespace mdl +{ + +class ReaderWriterMDL : public osgDB::ReaderWriter +{ +public: + + virtual const char* className() const; + + virtual bool acceptsExtension(const std::string& extension) const; + + virtual ReadResult readNode(const std::string& file, + const Options* options) const; +}; + + +} + +#endif diff --git a/src/osgPlugins/mdl/VTXReader.cpp b/src/osgPlugins/mdl/VTXReader.cpp new file mode 100644 index 000000000..902c86b2f --- /dev/null +++ b/src/osgPlugins/mdl/VTXReader.cpp @@ -0,0 +1,422 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "VTXReader.h" + + +using namespace mdl; +using namespace osg; +using namespace osgDB; + + +VTXReader::VTXReader(VVDReader * vvd, MDLRoot * mdlRoot) +{ + // Save the VVD reader, as we'll need it to read vertex data + vvd_reader = vvd; + + // Save the root of the MDL tree, as we'll need it to make sure we + // index the vertex data properly + mdl_root = mdlRoot; + + // Initialize the root group + model_root = NULL; +} + + +VTXReader::~VTXReader() +{ +} + + +ref_ptr VTXReader::processBodyPart(std::istream * str, int offset, + BodyPart * currentPart) +{ + int i; + VTXBodyPart part; + Model * currentModel; + ref_ptr partSwitch; + ref_ptr modelGroup; + + // Seek to the body part + str->seekg(offset); + + // Read it + str->read((char *) &part, sizeof(VTXBodyPart)); + + // If there is more than one model, create a switch to select between them + // (it seems that only one model is supposed to be seen at a given time, + // but I don't know the mechanism in the engine that selects a desired + // model) + if (part.num_models > 1) + { + partSwitch = new Switch(); + } + + // Process the models + for (i = 0; i < part.num_models; i++) + { + // Get the corresponding MDL model from the current body part + currentModel = currentPart->getModel(i); + + // Process the model + modelGroup = processModel(str, + offset + part.model_offset + + (i * sizeof(VTXModel)), + currentModel); + + // If there is more than one model, add this model to the part group + if (part.num_models > 1) + { + // Add the model to the switch + partSwitch->addChild(modelGroup.get()); + + // If this is the first child, turn it on, otherwise turn it off + if (i == 0) + ((osg::Switch *)partSwitch.get())->setValue(i, true); + else + ((osg::Switch *)partSwitch.get())->setValue(i, false); + } + } + + // If there is only one model, just return it + if (part.num_models == 1) + return modelGroup; + else + return partSwitch; +} + + +ref_ptr VTXReader::processModel(std::istream * str, int offset, + Model * currentModel) +{ + int i; + VTXModel model; + float lastDistance; + float distance; + LOD * lodNode; + ref_ptr group; + ref_ptr result; + + // Seek to the model + str->seekg(offset); + + // Read it + str->read((char *) &model, sizeof(VTXModel)); + + // If we have multiple LODs, create an LOD node for them + if (model.num_lods > 1) + lodNode = new LOD(); + + // Initialize the distances + lastDistance = 0.0; + distance = 0.0; + + // Process the LODs + for (i = 0; i < model.num_lods; i++) + { + // Process the LOD group, passing the current MDL model through + group = processLOD(i, &distance, str, + offset + model.lod_offset + + (i * sizeof(VTXModelLOD)), + currentModel); + + // If this isn't the only LOD, add it to the LOD node + if (model.num_lods > 1) + { + lodNode->addChild(group.get()); + + // Fix the LOD distances + if (distance < 0) + { + // Fix negative distance (my best guess is that these mean + // for the LOD to never switch out) + distance = 100000.0; + } + + // Set the ranges on the previous LOD (now that we know the + // switch point for this one) + if (i > 0) + lodNode->setRange(i-1, lastDistance, distance); + + lastDistance = distance; + } + } + + if (i > 1) + lodNode->setRange(i-1, lastDistance, 100000.0); + + // Return either the LOD node or the single LOD group + if (model.num_lods > 1) + result = lodNode; + else + result = group; + + return result; +} + + +ref_ptr VTXReader::processLOD(int lodNum, float * distance, + std::istream * str, int offset, + Model * currentModel) +{ + int i; + VTXModelLOD lod; + Mesh * currentMesh; + int vertexOffset; + ref_ptr lodGroup; + ref_ptr geode; + + // Seek to the LOD + str->seekg(offset); + + // Read it + str->read((char *) &lod, sizeof(VTXModelLOD)); + + // Create a group to hold this LOD + lodGroup = new Group(); + + // Process the meshes + vertexOffset = currentModel->getVertexBase(); + for (i = 0; i < lod.num_meshes; i++) + { + // Get the corresponding MDL mesh from the current model + currentMesh = currentModel->getMesh(i); + + // Process the mesh to get a geode + geode = processMesh(lodNum, str, + offset + lod.mesh_offset + (i * VTX_MESH_SIZE), + vertexOffset); + + // Set the geode's state set based on the current mesh node's state + // set + geode->setStateSet(currentMesh->getStateSet()); + + // Add the geode to the group + lodGroup->addChild(geode.get()); + + // Add the number of vertices for this mesh at this LOD to the offset, + // so we can start the next mesh at the proper vertex ID + vertexOffset += currentMesh->getNumLODVertices(lodNum); + } + + // Set the distance for this LOD + *distance = lod.switch_point; + + // Return the LOD group that we created + return lodGroup; +} + + +ref_ptr VTXReader::processMesh(int lodNum, std::istream * str, + int offset, int vertexOffset) +{ + int i; + VTXMesh mesh; + ref_ptr geode; + ref_ptr geom; + + // Seek to the mesh + str->seekg(offset); + + // Read it + str->read((char *) &mesh, VTX_MESH_SIZE); + + // Create a geode to hold the geometry + geode = new Geode(); + + // Process the strip groups + for (i = 0; i < mesh.num_strip_groups; i++) + { + // Process the strip group to get a Geometry + geom = processStripGroup(lodNum, str, + offset + mesh.strip_group_offset + (i * VTX_STRIP_GROUP_SIZE), + vertexOffset); + + // Add the geometry to the geode + geode->addDrawable(geom.get()); + } + + // Return the geode + return geode; +} + + +ref_ptr VTXReader::processStripGroup(int lodNum, std::istream * str, + int offset, int vertexOffset) +{ + int i; + VTXStripGroup stripGroup; + VTXVertex vtxVertex; + int vertexID; + ref_ptr vertexArray; + ref_ptr normalArray; + ref_ptr texcoordArray; + unsigned short index; + unsigned short * indexArray; + ref_ptr geom; + ref_ptr primSet; + + // Seek to the strip group + str->seekg(offset); + + // Read it + str->read((char *) &stripGroup, VTX_STRIP_GROUP_SIZE); + + // Create and fill the vertex arrays + vertexArray = new Vec3Array(); + normalArray = new Vec3Array(); + texcoordArray = new Vec2Array(); + str->seekg(offset + stripGroup.vertex_offset); + for (i = 0; i < stripGroup.num_vertices; i++) + { + // Get the vertex ID from the strip group + str->read((char *) &vtxVertex, VTX_VERTEX_SIZE); + vertexID = vtxVertex.orig_mesh_vertex_id + vertexOffset; + + // Get the corresponding vertex, normal, texture coordinates from the + // VVD file + vertexArray->push_back(vvd_reader->getVertex(lodNum, vertexID)); + normalArray->push_back(vvd_reader->getNormal(lodNum, vertexID)); + texcoordArray->push_back(vvd_reader->getTexCoords(lodNum, vertexID)); + } + + // Create the geometry and add the vertex data to it + geom = new Geometry(); + geom->setVertexArray(vertexArray.get()); + geom->setNormalArray(normalArray.get()); + geom->setNormalBinding(Geometry::BIND_PER_VERTEX); + geom->setTexCoordArray(0, texcoordArray.get()); + + // Create and fill the index array + indexArray = new unsigned short[stripGroup.num_indices]; + str->seekg(offset + stripGroup.index_offset); + for (i = 0; i < stripGroup.num_indices; i++) + { + // Get the index from the file + str->read((char *) &index, sizeof(unsigned short)); + + // Add to the array + indexArray[i] = index; + } + + // Process the strips + for (i = 0; i < stripGroup.num_strips; i++) + { + // Process the strip to create a triangle list or strip + primSet = processStrip(indexArray, str, + offset + stripGroup.strip_offset + (i * VTX_STRIP_SIZE)); + + // Add the primitive set to the geometry + geom->addPrimitiveSet(primSet.get()); + } + + // Clean up + delete [] indexArray; + + // Return the geometry + return geom; +} + + +ref_ptr VTXReader::processStrip(unsigned short * indexArray, + std::istream * str, + int offset) +{ + int i; + VTXStrip strip; + ref_ptr primSet; + unsigned short * start; + unsigned short * end; + + // Seek to the strip + str->seekg(offset); + + // Read it. We have to do this in a kind of screwy way because of the + // weird byte packing. Valve uses pragma pack, but we can't do that + // because it's non-portable. Of course, I'm assuming a 4-byte alignment + // here, which might also be non-portable... + str->read((char *) &strip, VTX_STRIP_SIZE - 8); + str->read((char *) &strip.num_bone_state_changes, 8); + + // Get the range of indices in question from the strip + start = &indexArray[strip.index_offset]; + end = &indexArray[strip.index_offset + strip.num_indices]; + + // Create the primitive set (based on the flag) + if (strip.strip_flags & STRIP_IS_TRI_LIST) + primSet = + new DrawElementsUShort(PrimitiveSet::TRIANGLES, start, end); + else + primSet = + new DrawElementsUShort(PrimitiveSet::TRIANGLE_STRIP, start, end); + + // Return the primitive set + return primSet; +} + + +bool VTXReader::readFile(const std::string & file) +{ + osgDB::ifstream * vtxFile; + VTXHeader header; + int i, j; + int offset; + BodyPart * currentPart; + ref_ptr partGroup; + Group * rootGroup; + + // Remember the map name + vtx_name = getStrippedName(file); + + vtxFile = new osgDB::ifstream(file.c_str(), std::ios::binary); + if (!vtxFile || vtxFile->fail()) + { + notify(NOTICE) << "Vertex index file not found" << std::endl; + return false; + } + + // Read the header + vtxFile->read((char *) &header, sizeof(VTXHeader)); + + // Make sure the version is one that we handle + // TODO: Not sure which versions are valid yet + + // Create the root group + rootGroup = new Group(); + + // Process the body parts + for (i = 0; i < header.num_body_parts; i++) + { + // Get the corresponding body part from the MDL tree + currentPart = mdl_root->getBodyPart(i); + + // Process the body part + partGroup = processBodyPart(vtxFile, + header.body_part_offset + + (i * sizeof(VTXBodyPart)), + currentPart); + + // Add the result to the root group + rootGroup->addChild(partGroup.get()); + } + + // Set the model's root node + model_root = rootGroup; + + // Close the file + vtxFile->close(); + delete vtxFile; + + return true; +} + + +ref_ptr VTXReader::getModel() +{ + return model_root; +} diff --git a/src/osgPlugins/mdl/VTXReader.h b/src/osgPlugins/mdl/VTXReader.h new file mode 100644 index 000000000..8b92adf2d --- /dev/null +++ b/src/osgPlugins/mdl/VTXReader.h @@ -0,0 +1,222 @@ +#ifndef __VTX_READER_H_ +#define __VTX_READER_H_ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MDLLimits.h" +#include "MDLRoot.h" +#include "VVDReader.h" + + +namespace mdl +{ + +struct VTXHeader +{ + int vtx_version; + int vertex_cache_size; + unsigned short max_bones_per_strip; + unsigned short max_bones_per_tri; + int max_bones_per_vertex; + + int check_sum; + int num_lods; + + int mtl_replace_list_offset; + + int num_body_parts; + int body_part_offset; +}; + + +struct VTXMaterialReplacementList +{ + int num_replacements; + int replacement_offset; +}; + + +struct VTXMaterialReplacment +{ + short material_id; + int replacement_material_name_offset; +}; + + +struct VTXBodyPart +{ + int num_models; + int model_offset; +}; + + +struct VTXModel +{ + int num_lods; + int lod_offset; +}; + + +struct VTXModelLOD +{ + int num_meshes; + int mesh_offset; + float switch_point; +}; + + +enum VTXMeshFlags +{ + MESH_IS_TEETH = 0x01, + MESH_IS_EYES = 0x02 +}; + + +struct VTXMesh +{ + int num_strip_groups; + int strip_group_offset; + + unsigned char mesh_flags; +}; + +// Can't rely on sizeof() because Valve explicitly packs these structures to +// 1-byte alignment in the file, which isn't portable +const int VTX_MESH_SIZE = 9; + + +enum VTXStripGroupFlags +{ + STRIP_GROUP_IS_FLEXED = 0x01, + STRIP_GROUP_IS_HW_SKINNED = 0x02, + STRIP_GROUP_IS_DELTA_FLEXED = 0x04 +}; + + +struct VTXStripGroup +{ + int num_vertices; + int vertex_offset; + + int num_indices; + int index_offset; + + int num_strips; + int strip_offset; + + unsigned char strip_group_flags; +}; + +// Can't rely on sizeof() because Valve explicitly packs these structures to +// 1-byte alignment in the file, which isn't portable +const int VTX_STRIP_GROUP_SIZE = 25; + + +enum VTXStripFlags +{ + STRIP_IS_TRI_LIST = 0x01, + STRIP_IS_TRI_STRIP = 0x02 +}; + + +struct VTXStrip +{ + int num_indices; + int index_offset; + + int num_vertices; + int vertex_offset; + + short num_bones; + + unsigned char strip_flags; + + int num_bone_state_changes; + int bone_state_change_offset; +}; + + +// Can't rely on sizeof() because Valve explicitly packs these structures to +// 1-byte alignment in the .vtx file, which isn't portable +const int VTX_STRIP_SIZE = 27; + + +struct VTXVertex +{ + unsigned char bone_weight_index[MAX_BONES_PER_VERTEX]; + unsigned char num_bones; + + short orig_mesh_vertex_id; + + char bone_id[MAX_BONES_PER_VERTEX]; +}; + + +// Can't rely on sizeof() because Valve explicitly packs these structures to +// 1-byte alignment in the .vtx file, which isn't portable +const int VTX_VERTEX_SIZE = 9; + + +struct VTXBoneStateChange +{ + int hardware_id; + int new_bone_id; +}; + + +class VTXReader +{ +protected: + + std::string vtx_name; + + VVDReader * vvd_reader; + + MDLRoot * mdl_root; + + osg::ref_ptr model_root; + + osg::ref_ptr processBodyPart(std::istream * str, + int offset, + BodyPart * currentPart); + osg::ref_ptr processModel(std::istream * str, + int offset, + Model * currentModel); + osg::ref_ptr processLOD(int lodNum, float * distance, + std::istream * str, + int offset, + Model * currentModel); + osg::ref_ptr processMesh(int lodNum, + std::istream * str, + int offset, int vertexOffset); + osg::ref_ptr processStripGroup(int lodNum, + std::istream * str, + int offset, + int vertexOffset); + osg::ref_ptr processStrip(unsigned short * indexArray, + std::istream * str, + int offset); + +public: + + VTXReader(VVDReader * vvd, MDLRoot * mdlRoot); + virtual ~VTXReader(); + + bool readFile(const std::string & file); + + osg::ref_ptr getModel(); +}; + + +} + +#endif diff --git a/src/osgPlugins/mdl/VVDReader.cpp b/src/osgPlugins/mdl/VVDReader.cpp new file mode 100644 index 000000000..0d59e7f41 --- /dev/null +++ b/src/osgPlugins/mdl/VVDReader.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VVDReader.h" + + +using namespace mdl; +using namespace osg; +using namespace osgDB; + + +VVDReader::VVDReader() +{ + // Initialize the vertex buffer arrays + memset(vertex_buffer, 0, sizeof(vertex_buffer)); + memset(vertex_buffer_size, 0, sizeof(vertex_buffer_size)); +} + + +VVDReader::~VVDReader() +{ + int i; + + // Clean up the vertex buffer arrays + for (i = 0; i < MAX_LODS; i++) + delete [] vertex_buffer[i]; +} + + +bool VVDReader::readFile(const std::string & file) +{ + osgDB::ifstream * vvdFile; + VVDHeader header; + int vertIndex; + int i, j, k; + + // Remember the map name + vvd_name = getStrippedName(file); + + vvdFile = new osgDB::ifstream(file.c_str(), std::ios::binary); + if (!vvdFile) + { + notify(NOTICE) << "Vertex data file not found" << std::endl; + return false; + } + + // Read the header + memset(&header, 0xcd, sizeof(VVDHeader)); + vvdFile->read((char *) &header, sizeof(VVDHeader)); + + // Make sure the file is a valid Valve VVD file + if (header.magic_number != VVD_MAGIC_NUMBER) + { + notify(NOTICE) << "Vertex data file not valid" << std::endl; + return false; + } + + // Make sure the version is one that we handle + // TODO: Not sure which versions are valid yet + + // Read the fixup table + fixup_table = new VVDFixupEntry[header.num_fixups]; + vvdFile->seekg(header.fixup_table_offset); + for (i = 0; i < header.num_fixups; i++) + vvdFile->read((char *) &fixup_table[i], sizeof(VVDFixupEntry)); + + // Create the vertex buffers + for (i = 0; i < header.num_lods; i++) + { + // Create the vertex buffer for this LOD + vertex_buffer[i] = new VVDVertex[header.num_lod_verts[i]]; + vertex_buffer_size[i] = header.num_lod_verts[i]; + + // See if this model needs fixups + if (header.num_fixups > 0) + { + // Scan the fixup table and apply any fixups at this LOD + vertIndex = 0; + for (j = 0; j < header.num_fixups; j++) + { + // Skip this entry if the LOD number is lower (more detailed) + // than the LOD we're working on + if (fixup_table[j].lod_number >= i) + { + // Seek to the vertex indicated by the fixup table entry + vvdFile->seekg(header.vertex_data_offset + + fixup_table[j].source_vertex_id * + sizeof(VVDVertex)); + + // Read the number of vertices specified + vvdFile->read((char *) &vertex_buffer[i][vertIndex], + fixup_table[j].num_vertices * + sizeof(VVDVertex)); + + // Advance the target index + vertIndex += fixup_table[j].num_vertices; + } + } + } + else + { + // Seek to the vertex data + vvdFile->seekg(header.vertex_data_offset); + + // Just read the vertices directly + vvdFile->read((char *) &vertex_buffer[i][0], + header.num_lod_verts[i] * sizeof(VVDVertex)); + } + + // Scale the vertices from inches up to meters + for (j = 0; j < vertex_buffer_size[i]; j++) + vertex_buffer[i][j].vertex_position *= 0.0254; + } + + // Close the file + vvdFile->close(); + delete vvdFile; + + return true; +} + + +int VVDReader::getNumLODVertices(int lod) +{ + return vertex_buffer_size[lod]; +} + + +Vec3 VVDReader::getVertex(int lod, int index) +{ + return vertex_buffer[lod][index].vertex_position; +} + + +Vec3 VVDReader::getNormal(int lod, int index) +{ + return vertex_buffer[lod][index].vertex_normal; +} + + +Vec2 VVDReader::getTexCoords(int lod, int index) +{ + return vertex_buffer[lod][index].vertex_texcoord; +} + diff --git a/src/osgPlugins/mdl/VVDReader.h b/src/osgPlugins/mdl/VVDReader.h new file mode 100644 index 000000000..f99040b30 --- /dev/null +++ b/src/osgPlugins/mdl/VVDReader.h @@ -0,0 +1,98 @@ +#ifndef __VVD_READER_H_ +#define __VVD_READER_H_ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MDLLimits.h" + + +namespace mdl +{ + + +// The magic number for a Valve VVD file is 'IDSV' in little-endian +// order +const int VVD_MAGIC_NUMBER = (('V'<<24)+('S'<<16)+('D'<<8)+'I'); + + +struct VVDHeader +{ + int magic_number; + int vvd_version; + int check_sum; + + int num_lods; + int num_lod_verts[MAX_LODS]; + + int num_fixups; + int fixup_table_offset; + + int vertex_data_offset; + + int tangent_data_offset; +}; + + +struct VVDFixupEntry +{ + int lod_number; + + int source_vertex_id; + int num_vertices; +}; + + +struct VVDBoneWeight +{ + float weight[MAX_BONES_PER_VERTEX]; + char bone[MAX_BONES_PER_VERTEX]; + u_char num_bones; +}; + + +struct VVDVertex +{ + VVDBoneWeight bone_weights; + osg::Vec3 vertex_position; + osg::Vec3 vertex_normal; + osg::Vec2 vertex_texcoord; +}; + + +class VVDReader +{ +protected: + + std::string vvd_name; + + VVDVertex * vertex_buffer[MAX_LODS]; + int vertex_buffer_size[MAX_LODS]; + + VVDFixupEntry * fixup_table; + +public: + + VVDReader(); + virtual ~VVDReader(); + + bool readFile(const std::string & file); + + int getNumLODVertices(int lod); + + osg::Vec3 getVertex(int lod, int index); + osg::Vec3 getNormal(int lod, int index); + osg::Vec2 getTexCoords(int lod, int index); +}; + + +} + +#endif diff --git a/src/osgPlugins/vtf/CMakeLists.txt b/src/osgPlugins/vtf/CMakeLists.txt new file mode 100644 index 000000000..f64ddac8d --- /dev/null +++ b/src/osgPlugins/vtf/CMakeLists.txt @@ -0,0 +1,6 @@ +#this file is automatically generated + + +SET(TARGET_SRC ReaderWriterVTF.cpp ) +#### end var setup ### +SETUP_PLUGIN(vtf) diff --git a/src/osgPlugins/bsp/ReaderWriterVTF.cpp b/src/osgPlugins/vtf/ReaderWriterVTF.cpp similarity index 64% rename from src/osgPlugins/bsp/ReaderWriterVTF.cpp rename to src/osgPlugins/vtf/ReaderWriterVTF.cpp index d71848ecf..f88d38eb1 100644 --- a/src/osgPlugins/bsp/ReaderWriterVTF.cpp +++ b/src/osgPlugins/vtf/ReaderWriterVTF.cpp @@ -5,8 +5,8 @@ * DESCRIPTION: Class for reading a Valve Texture Format (VTF) file * into an osg::Image. * -* Borrows heavily from Rune Schmidt Jensen's DDS -* plugin for OSG, as well as the Valve Source SDK +* Borrows heavily from the DDS plugin for OSG, as well +* as the Valve Source SDK * * CREATED BY: Jason Daly (jdaly@ist.ucf.edu) * @@ -27,97 +27,97 @@ enum VTFFlags { - VTF_FLAGS_POINTSAMPLE = 0x00000001, - VTF_FLAGS_TRILINEAR = 0x00000002, - VTF_FLAGS_CLAMP_S = 0x00000004, - VTF_FLAGS_CLAMP_T = 0x00000008, - VTF_FLAGS_ANISOTROPIC = 0x00000010, - VTF_FLAGS_HINT_DXT5 = 0x00000020, - VTF_FLAGS_NOCOMPRESS = 0x00000040, - VTF_FLAGS_NORMAL = 0x00000080, - VTF_FLAGS_NOMIP = 0x00000100, - VTF_FLAGS_NOLOD = 0x00000200, - VTF_FLAGS_MINMIP = 0x00000400, - VTF_FLAGS_PROCEDURAL = 0x00000800, - VTF_FLAGS_ONEBITALPHA = 0x00001000, - VTF_FLAGS_EIGHTBITALPHA = 0x00002000, - VTF_FLAGS_ENVMAP = 0x00004000, - VTF_FLAGS_RENDERTARGET = 0x00008000, - VTF_FLAGS_DEPTHRENDERTARGET = 0x00010000, - VTF_FLAGS_NODEBUGOVERRIDE = 0x00020000, - VTF_FLAGS_SINGLECOPY = 0x00040000, - VTF_FLAGS_ONEOVERMIPLEVELINALPHA = 0x00080000, - VTF_FLAGS_PREMULTCOLORBYONEOVERMIPLEVEL = 0x00100000, - VTF_FLAGS_NORMALTODUDV = 0x00200000, - VTF_FLAGS_ALPHATESTMIPGENERATION = 0x00400000, - VTF_FLAGS_NODEPTHBUFFER = 0x00800000, - VTF_FLAGS_NICEFILTERED = 0x01000000, - VTF_FLAGS_CLAMP_U = 0x02000000, - VTF_FLAGS_PRESWIZZLED = 0x04000000, - VTF_FLAGS_CACHEABLE = 0x08000000, - VTF_FLAGS_UNFILTERABLE_OK = 0x10000000, - VTF_FLAGS_LASTFLAG = 0x10000000 + VTF_FLAGS_POINTSAMPLE = 0x00000001, + VTF_FLAGS_TRILINEAR = 0x00000002, + VTF_FLAGS_CLAMP_S = 0x00000004, + VTF_FLAGS_CLAMP_T = 0x00000008, + VTF_FLAGS_ANISOTROPIC = 0x00000010, + VTF_FLAGS_HINT_DXT5 = 0x00000020, + VTF_FLAGS_NOCOMPRESS = 0x00000040, + VTF_FLAGS_NORMAL = 0x00000080, + VTF_FLAGS_NOMIP = 0x00000100, + VTF_FLAGS_NOLOD = 0x00000200, + VTF_FLAGS_MINMIP = 0x00000400, + VTF_FLAGS_PROCEDURAL = 0x00000800, + VTF_FLAGS_ONEBITALPHA = 0x00001000, + VTF_FLAGS_EIGHTBITALPHA = 0x00002000, + VTF_FLAGS_ENVMAP = 0x00004000, + VTF_FLAGS_RENDERTARGET = 0x00008000, + VTF_FLAGS_DEPTHRENDERTARGET = 0x00010000, + VTF_FLAGS_NODEBUGOVERRIDE = 0x00020000, + VTF_FLAGS_SINGLECOPY = 0x00040000, + VTF_FLAGS_ONEOVERMIPLEVELINALPHA = 0x00080000, + VTF_FLAGS_PREMULTCOLORBYONEOVERMIPLEVEL = 0x00100000, + VTF_FLAGS_NORMALTODUDV = 0x00200000, + VTF_FLAGS_ALPHATESTMIPGENERATION = 0x00400000, + VTF_FLAGS_NODEPTHBUFFER = 0x00800000, + VTF_FLAGS_NICEFILTERED = 0x01000000, + VTF_FLAGS_CLAMP_U = 0x02000000, + VTF_FLAGS_PRESWIZZLED = 0x04000000, + VTF_FLAGS_CACHEABLE = 0x08000000, + VTF_FLAGS_UNFILTERABLE_OK = 0x10000000, + VTF_FLAGS_LASTFLAG = 0x10000000 }; enum VTFCubeMapFaceIndex { - VTF_CUBEMAP_FACE_RIGHT = 0, - VTF_CUBEMAP_FACE_LEFT, - VTF_CUBEMAP_FACE_BACK, - VTF_CUBEMAP_FACE_FRONT, - VTF_CUBEMAP_FACE_UP, - VTF_CUBEMAP_FACE_DOWN, - VTF_CUBEMAP_FACE_SPHEREMAP, - VTF_CUBEMAP_FACE_COUNT + VTF_CUBEMAP_FACE_RIGHT = 0, + VTF_CUBEMAP_FACE_LEFT, + VTF_CUBEMAP_FACE_BACK, + VTF_CUBEMAP_FACE_FRONT, + VTF_CUBEMAP_FACE_UP, + VTF_CUBEMAP_FACE_DOWN, + VTF_CUBEMAP_FACE_SPHEREMAP, + VTF_CUBEMAP_FACE_COUNT }; enum VTFLookDir { - VTF_LOOK_DOWN_X = 0, - VTF_LOOK_DOWN_NEGX, - VTF_LOOK_DOWN_Y = 0, - VTF_LOOK_DOWN_NEGY, - VTF_LOOK_DOWN_Z = 0, - VTF_LOOK_DOWN_NEGZ + VTF_LOOK_DOWN_X = 0, + VTF_LOOK_DOWN_NEGX, + VTF_LOOK_DOWN_Y = 0, + VTF_LOOK_DOWN_NEGY, + VTF_LOOK_DOWN_Z = 0, + VTF_LOOK_DOWN_NEGZ }; enum VTFImageFormat { - VTF_FORMAT_UNKNOWN = -1, - VTF_FORMAT_RGBA8888 = 0, - VTF_FORMAT_ABGR8888, - VTF_FORMAT_RGB888, - VTF_FORMAT_BGR888, - VTF_FORMAT_RGB565, - VTF_FORMAT_I8, - VTF_FORMAT_IA88, - VTF_FORMAT_P8, - VTF_FORMAT_A8, - VTF_FORMAT_RGB888_BLUESCREEN, - VTF_FORMAT_BGR888_BLUESCREEN, - VTF_FORMAT_ARGB8888, - VTF_FORMAT_BGRA8888, - VTF_FORMAT_DXT1, - VTF_FORMAT_DXT3, - VTF_FORMAT_DXT5, - VTF_FORMAT_BGRX8888, - VTF_FORMAT_BGR565, - VTF_FORMAT_BGRX5551, - VTF_FORMAT_BGRA4444, - VTF_FORMAT_DXT1_ONEBITALPHA, - VTF_FORMAT_BGRA5551, - VTF_FORMAT_UV88, - VTF_FORMAT_UVWQ8888, - VTF_FORMAT_RGBA16161616F, - VTF_FORMAT_RGBA16161616, - VTF_FORMAT_UVLX8888, - VTF_FORMAT_R32F, - VTF_FORMAT_RGB323232F, - VTF_FORMAT_RGBA32323232F, - VTF_NUM_IMAGE_FORMATS + VTF_FORMAT_UNKNOWN = -1, + VTF_FORMAT_RGBA8888 = 0, + VTF_FORMAT_ABGR8888, + VTF_FORMAT_RGB888, + VTF_FORMAT_BGR888, + VTF_FORMAT_RGB565, + VTF_FORMAT_I8, + VTF_FORMAT_IA88, + VTF_FORMAT_P8, + VTF_FORMAT_A8, + VTF_FORMAT_RGB888_BLUESCREEN, + VTF_FORMAT_BGR888_BLUESCREEN, + VTF_FORMAT_ARGB8888, + VTF_FORMAT_BGRA8888, + VTF_FORMAT_DXT1, + VTF_FORMAT_DXT3, + VTF_FORMAT_DXT5, + VTF_FORMAT_BGRX8888, + VTF_FORMAT_BGR565, + VTF_FORMAT_BGRX5551, + VTF_FORMAT_BGRA4444, + VTF_FORMAT_DXT1_ONEBITALPHA, + VTF_FORMAT_BGRA5551, + VTF_FORMAT_UV88, + VTF_FORMAT_UVWQ8888, + VTF_FORMAT_RGBA16161616F, + VTF_FORMAT_RGBA16161616, + VTF_FORMAT_UVLX8888, + VTF_FORMAT_R32F, + VTF_FORMAT_RGB323232F, + VTF_FORMAT_RGBA32323232F, + VTF_NUM_IMAGE_FORMATS }; @@ -126,27 +126,27 @@ enum VTFImageFormat struct VTFFileHeader { - char magic_number[4]; - unsigned int file_version[2]; - unsigned int header_size; - unsigned short image_width; - unsigned short image_height; - unsigned int image_flags; - unsigned short num_frames; - unsigned short start_frame; + char magic_number[4]; + unsigned int file_version[2]; + unsigned int header_size; + unsigned short image_width; + unsigned short image_height; + unsigned int image_flags; + unsigned short num_frames; + unsigned short start_frame; - unsigned char padding_0[4]; - osg::Vec3f reflectivity_value; - unsigned char padding_1[4]; + unsigned char padding_0[4]; + osg::Vec3f reflectivity_value; + unsigned char padding_1[4]; - float bump_scale; - unsigned int image_format; - unsigned char num_mip_levels; - unsigned char low_res_image_format; - unsigned char padding_2[3]; - unsigned char low_res_image_width; - unsigned char low_res_image_height; - unsigned short image_depth; + float bump_scale; + unsigned int image_format; + unsigned char num_mip_levels; + unsigned char low_res_image_format; + unsigned char padding_2[3]; + unsigned char low_res_image_width; + unsigned char low_res_image_height; + unsigned short image_depth; }; @@ -357,6 +357,7 @@ osg::Image* ReadVTFFile(std::istream& _istream) int s, t, r; unsigned int lrSize; unsigned char * imageData; + unsigned char * imageDataPtr; unsigned int base; unsigned int size; int mip; @@ -395,12 +396,53 @@ osg::Image* ReadVTFFile(std::istream& _istream) return NULL; } + osg::notify(osg::INFO) << "VTF Header: (" << sizeof(VTFFileHeader); + osg::notify(osg::INFO) << " bytes)" << std::endl; + osg::notify(osg::INFO) << " magic_number = "; + osg::notify(osg::INFO) << vtf_header.magic_number[0]; + osg::notify(osg::INFO) << vtf_header.magic_number[1]; + osg::notify(osg::INFO) << vtf_header.magic_number[2]; + osg::notify(osg::INFO) << vtf_header.magic_number[3] << std:: endl; + osg::notify(osg::INFO) << " file_version = "; + osg::notify(osg::INFO) << vtf_header.file_version[0] << "."; + osg::notify(osg::INFO) << vtf_header.file_version[1] << std:: endl; + osg::notify(osg::INFO) << " header_size = "; + osg::notify(osg::INFO) << vtf_header.header_size << std::endl; + osg::notify(osg::INFO) << " image_width = "; + osg::notify(osg::INFO) << vtf_header.image_width << std::endl; + osg::notify(osg::INFO) << " image_height = "; + osg::notify(osg::INFO) << vtf_header.image_height << std::endl; + osg::notify(osg::INFO) << " num_frames = "; + osg::notify(osg::INFO) << vtf_header.num_frames << std::endl; + osg::notify(osg::INFO) << " start_frame = "; + osg::notify(osg::INFO) << vtf_header.start_frame << std::endl; + osg::notify(osg::INFO) << " reflectivity = "; + osg::notify(osg::INFO) << vtf_header.reflectivity_value.x() << ", "; + osg::notify(osg::INFO) << vtf_header.reflectivity_value.y() << ", "; + osg::notify(osg::INFO) << vtf_header.reflectivity_value.z() << std::endl; + osg::notify(osg::INFO) << " bump_scale = "; + osg::notify(osg::INFO) << vtf_header.bump_scale << std::endl; + osg::notify(osg::INFO) << " image_format = "; + osg::notify(osg::INFO) << vtf_header.image_format << std::endl; + osg::notify(osg::INFO) << " num_mip_lvls = "; + osg::notify(osg::INFO) << (int)vtf_header.num_mip_levels << std::endl; + osg::notify(osg::INFO) << " lr_image_fmt = "; + osg::notify(osg::INFO) << (int)vtf_header.low_res_image_format << std::endl; + osg::notify(osg::INFO) << " lr_width = "; + osg::notify(osg::INFO) << (int)vtf_header.low_res_image_width << std::endl; + osg::notify(osg::INFO) << " lr_height = "; + osg::notify(osg::INFO) << (int)vtf_header.low_res_image_height << std::endl; + osg::notify(osg::INFO) << " image_depth = "; + osg::notify(osg::INFO) << (int)vtf_header.image_depth << std::endl; + // Before we get to the real image, we need to skip over the "low res" // image that's often stored along with VTF textures, so get the // low-res image dimensions s = vtf_header.low_res_image_width; t = vtf_header.low_res_image_height; r = 1; + osg::notify(osg::INFO) << "Low-res s = " << s << std::endl; + osg::notify(osg::INFO) << "Low-res t = " << t << std::endl; // See if the low-res image is there lrSize = 0; @@ -428,6 +470,7 @@ osg::Image* ReadVTFFile(std::istream& _istream) lrSize = loResImage->getTotalSizeInBytes(); // Skip over the low-res image data + osg::notify(osg::INFO) << "Low-res size = " << lrSize << std::endl; _istream.ignore(lrSize); } @@ -525,7 +568,7 @@ osg::Image* ReadVTFFile(std::istream& _istream) if (height == 0) height = 1; if (depth == 0) - height = 1; + depth = 1; // Compute and store the offset into the final image data offset += depth * height * @@ -551,10 +594,11 @@ osg::Image* ReadVTFFile(std::istream& _istream) osgImage->setImage(s, t, r, internalFormat, pixelFormat, dataType, 0, osg::Image::USE_NEW_DELETE); if (mipmaps.size() > 0) - osgImage->setMipmapLevels(mipmaps); + osgImage->setMipmapLevels(mipmaps); // Compute the total image size size = osgImage->getTotalSizeInBytesIncludingMipmaps(); + osg::notify(osg::INFO) << "ReadVTFFile info : size = " << size << std::endl; if(size <= 0) { osg::notify(osg::WARN) << "ReadVTFFile warning: size <= 0" << std::endl; @@ -602,6 +646,30 @@ osg::Image* ReadVTFFile(std::istream& _istream) _istream.read((char*)imageData, size); } +/* + // Check if alpha information embedded in the 8-byte encoding blocks + if (checkIfUsingOneBitAlpha) + { + const DXT1TexelsBlock *texelsBlock = + reinterpret_cast(imageData); + + // Only do the check on the first mipmap level + unsigned int numBlocks = mipmaps.size()>0 ? mipmaps[0] / 8 : size / 8; + + for (int i=numBlocks; i>0; --i, ++texelsBlock) + { + if (texelsBlock->color_0<=texelsBlock->color_1) + { + // Texture is using the 1-bit alpha encoding, so we need to + // update the assumed pixel format + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + pixelFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + break; + } + } + } +*/ + // Now, set the actual image data and mipmap levels osgImage->setImage(s,t,r, internalFormat, pixelFormat, dataType, imageData, osg::Image::USE_NEW_DELETE); @@ -612,6 +680,13 @@ osg::Image* ReadVTFFile(std::istream& _istream) } +bool WriteVTFFile(const osg::Image *img, std::ostream& fout) +{ + // Not supported + return false; +} + + class ReaderWriterVTF : public osgDB::ReaderWriter { public: @@ -625,22 +700,17 @@ public: return osgDB::equalCaseInsensitive(extension, "vtf"); } - virtual ReadResult readObject( - const std::string& file, - const osgDB::ReaderWriter::Options* options) const + virtual ReadResult readObject(const std::string& file, const osgDB::ReaderWriter::Options* options) const { return readImage(file,options); } - virtual ReadResult readObject(std::istream& fin, - const Options* options) const + virtual ReadResult readObject(std::istream& fin, const Options* options) const { return readImage(fin,options); } - virtual ReadResult readImage( - const std::string& file, - const osgDB::ReaderWriter::Options* options) const + virtual ReadResult readImage(const std::string& file, const osgDB::ReaderWriter::Options* options) const { std::string ext = osgDB::getLowerCaseFileExtension(file); if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED; @@ -649,22 +719,19 @@ public: if (fileName.empty()) return ReadResult::FILE_NOT_FOUND; - osgDB::ifstream stream(fileName.c_str(), - std::ios::in | std::ios::binary); + std::ifstream stream(fileName.c_str(), std::ios::in | std::ios::binary); if(!stream) return ReadResult::FILE_NOT_HANDLED; ReadResult rr = readImage(stream, options); if(rr.validImage()) rr.getImage()->setFileName(file); return rr; } - virtual ReadResult readImage(std::istream& fin, - const Options* options) const + virtual ReadResult readImage(std::istream& fin, const Options* options) const { osg::Image* osgImage = ReadVTFFile(fin); if (osgImage==NULL) return ReadResult::FILE_NOT_HANDLED; - if (options && - options->getOptionString().find("vtf_flip")!=std::string::npos) + if (options && options->getOptionString().find("vtf_flip")!=std::string::npos) { osgImage->flipVertical(); } @@ -672,10 +739,7 @@ public: return osgImage; } - virtual WriteResult writeObject( - const osg::Object& object, - const std::string& file, - const osgDB::ReaderWriter::Options* options) const + virtual WriteResult writeObject(const osg::Object& object,const std::string& file, const osgDB::ReaderWriter::Options* options) const { const osg::Image* image = dynamic_cast(&object); if (!image) return WriteResult::FILE_NOT_HANDLED; @@ -683,26 +747,34 @@ public: return writeImage(*image,file,options); } - virtual WriteResult writeObject(const osg::Object& object, - std::ostream& fout, - const Options* options) const + virtual WriteResult writeObject(const osg::Object& object,std::ostream& fout,const Options* options) const { - return WriteResult::FILE_NOT_HANDLED; + const osg::Image* image = dynamic_cast(&object); + if (!image) return WriteResult::FILE_NOT_HANDLED; + + return writeImage(*image,fout,options); } - virtual WriteResult writeImage( - const osg::Image &image, - const std::string& file, - const osgDB::ReaderWriter::Options* options) const + virtual WriteResult writeImage(const osg::Image &image,const std::string& file, const osgDB::ReaderWriter::Options* options) const { - return WriteResult::FILE_NOT_HANDLED; + std::string ext = osgDB::getFileExtension(file); + if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED; + + std::ofstream fout(file.c_str(), std::ios::out | std::ios::binary); + if(!fout) return WriteResult::ERROR_IN_WRITING_FILE; + + return writeImage(image,fout,options); } - virtual WriteResult writeImage(const osg::Image& image, - std::ostream& fout,const Options*) const + virtual WriteResult writeImage(const osg::Image& image,std::ostream& fout,const Options*) const { - return WriteResult::FILE_NOT_HANDLED; + bool success = WriteVTFFile(&image, fout); + + if(success) + return WriteResult::FILE_SAVED; + else + return WriteResult::ERROR_IN_WRITING_FILE; } };