From bf8c3cc07e4485945e4f7252974c49fc721bf0af Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Mon, 24 Nov 2008 11:39:02 +0000 Subject: [PATCH] From Jason Daly, "This is a plugin (two, actually) that will allow OSG to load .bsp map files from Valve's Source Engine games (Half-Life 2, etc.). One plugin (called "vbsp" to distinguish it from the Quake 3 bsp loader) reads the .bsp file itself, and the other ("vtf") reads the texture files. The set up for this to work is a bit more complex than most files, since the engine expects all files to be in a certain place, and it tends to mix case a lot. I tried to explain everything in the VBSP_README.txt file." This plugin has been integrated with the pre-exisiting bsp plugin. --- src/osgPlugins/CMakeLists.txt | 1 + src/osgPlugins/bsp/CMakeLists.txt | 21 + src/osgPlugins/bsp/Q3BSPLoad.cpp | 20 +- src/osgPlugins/bsp/Q3BSPLoad.h | 15 +- src/osgPlugins/bsp/Q3BSPReader.cpp | 641 ++++++++++++++ src/osgPlugins/bsp/Q3BSPReader.h | 83 ++ src/osgPlugins/bsp/ReaderWriterBSP.cpp | 726 ++-------------- src/osgPlugins/bsp/ReaderWriterBSP.h | 43 +- src/osgPlugins/bsp/VBSPGeometry.cpp | 654 ++++++++++++++ src/osgPlugins/bsp/VBSPGeometry.h | 54 ++ src/osgPlugins/bsp/VBSPReader.cpp | 1097 ++++++++++++++++++++++++ src/osgPlugins/bsp/VBSPReader.h | 385 +++++++++ src/osgPlugins/bsp/VBSP_README.txt | 99 +++ 13 files changed, 3165 insertions(+), 674 deletions(-) create mode 100644 src/osgPlugins/bsp/CMakeLists.txt create mode 100644 src/osgPlugins/bsp/Q3BSPReader.cpp create mode 100644 src/osgPlugins/bsp/Q3BSPReader.h create mode 100644 src/osgPlugins/bsp/VBSPGeometry.cpp create mode 100644 src/osgPlugins/bsp/VBSPGeometry.h create mode 100644 src/osgPlugins/bsp/VBSPReader.cpp create mode 100644 src/osgPlugins/bsp/VBSPReader.h create mode 100644 src/osgPlugins/bsp/VBSP_README.txt diff --git a/src/osgPlugins/CMakeLists.txt b/src/osgPlugins/CMakeLists.txt index 33c0ce70d..c21076c4d 100644 --- a/src/osgPlugins/CMakeLists.txt +++ b/src/osgPlugins/CMakeLists.txt @@ -78,6 +78,7 @@ ADD_SUBDIRECTORY(dds) ADD_SUBDIRECTORY(tga) ADD_SUBDIRECTORY(hdr) ADD_SUBDIRECTORY(dot) +ADD_SUBDIRECTORY(bsp) IF(JPEG_FOUND) ADD_SUBDIRECTORY(jpeg) diff --git a/src/osgPlugins/bsp/CMakeLists.txt b/src/osgPlugins/bsp/CMakeLists.txt new file mode 100644 index 000000000..63cf75986 --- /dev/null +++ b/src/osgPlugins/bsp/CMakeLists.txt @@ -0,0 +1,21 @@ +SET(TARGET_SRC + ReaderWriterBSP.cpp + BITSET.cpp + Q3BSPReader.cpp + Q3BSPLoad.cpp + VBSPGeometry.cpp + VBSPReader.cpp +) + +SET(TARGET_H + ReaderWriterBSP.h + BITSET.h + Q3BSPReader.h + Q3BSPLoad.h + VBSPGeometry.h + VBSPReader.h +) + +#### end var setup ### +SETUP_PLUGIN(bsp) + diff --git a/src/osgPlugins/bsp/Q3BSPLoad.cpp b/src/osgPlugins/bsp/Q3BSPLoad.cpp index 3dba65457..18850f624 100644 --- a/src/osgPlugins/bsp/Q3BSPLoad.cpp +++ b/src/osgPlugins/bsp/Q3BSPLoad.cpp @@ -1,13 +1,11 @@ +#include "Q3BSPLoad.h" + +using namespace bsp; - -#include "BSPLoad.h" - - - -bool BSPLoad::Load(const std::string& filename, int curveTessellation) +bool Q3BSPLoad::Load(const std::string& filename, int curveTessellation) { std::ifstream file(filename.c_str(),std::ios::binary); if(!file.is_open()) @@ -72,7 +70,7 @@ bool BSPLoad::Load(const std::string& filename, int curveTessellation) -void BSPLoad::LoadVertices(std::ifstream& aFile) +void Q3BSPLoad::LoadVertices(std::ifstream& aFile) { //calculate number of vertices int num_vertices=m_header.m_directoryEntries[bspVertices].m_length/sizeof(BSP_LOAD_VERTEX); @@ -96,7 +94,7 @@ void BSPLoad::LoadVertices(std::ifstream& aFile) -void BSPLoad::LoadFaces(std::ifstream& aFile, int /*curveTessellation*/) +void Q3BSPLoad::LoadFaces(std::ifstream& aFile, int /*curveTessellation*/) { //calculate number of load faces int numTotalFaces=m_header.m_directoryEntries[bspFaces].m_length/sizeof(BSP_LOAD_FACE); @@ -123,7 +121,7 @@ void BSPLoad::LoadFaces(std::ifstream& aFile, int /*curveTessellation*/) -void BSPLoad::LoadTextures(std::ifstream& aFile) +void Q3BSPLoad::LoadTextures(std::ifstream& aFile) { //Calculate number of textures int num_textures=m_header.m_directoryEntries[bspTextures].m_length/sizeof(BSP_LOAD_TEXTURE); @@ -146,7 +144,7 @@ void BSPLoad::LoadTextures(std::ifstream& aFile) -void BSPLoad::LoadLightmaps(std::ifstream& aFile) +void Q3BSPLoad::LoadLightmaps(std::ifstream& aFile) { //Calculate number of lightmaps int num_lightmaps=m_header.m_directoryEntries[bspLightmaps].m_length/sizeof(BSP_LOAD_LIGHTMAP); @@ -206,7 +204,7 @@ void BSPLoad::LoadLightmaps(std::ifstream& aFile) -void BSPLoad::LoadBSPData(std::ifstream& aFile) +void Q3BSPLoad::LoadBSPData(std::ifstream& aFile) { //Load leaves //Calculate number of leaves diff --git a/src/osgPlugins/bsp/Q3BSPLoad.h b/src/osgPlugins/bsp/Q3BSPLoad.h index 5a3dbc242..7b79b19b6 100644 --- a/src/osgPlugins/bsp/Q3BSPLoad.h +++ b/src/osgPlugins/bsp/Q3BSPLoad.h @@ -1,7 +1,7 @@ -#ifndef BSPLOAD_H -#define BSPLOAD_H +#ifndef Q3BSPLOAD_H +#define Q3BSPLOAD_H #include @@ -12,6 +12,11 @@ #include + +namespace bsp +{ + + //Directory entry in header class BSP_DIRECTORY_ENTRY { @@ -170,7 +175,7 @@ public: -class BSPLoad +class Q3BSPLoad { public: @@ -200,6 +205,8 @@ public: }; +} -#endif // BSPLOAD_H + +#endif // Q3BSPLOAD_H diff --git a/src/osgPlugins/bsp/Q3BSPReader.cpp b/src/osgPlugins/bsp/Q3BSPReader.cpp new file mode 100644 index 000000000..30847763f --- /dev/null +++ b/src/osgPlugins/bsp/Q3BSPReader.cpp @@ -0,0 +1,641 @@ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "Q3BSPReader.h" +#include "Q3BSPLoad.h" + + +using namespace bsp; + + +Q3BSPReader::Q3BSPReader() +{ + root_node = NULL; +} + + +bool Q3BSPReader::readFile(const std::string& file, + const osgDB::ReaderWriter::Options* options) +{ + std::string ext = osgDB::getLowerCaseFileExtension(file); + + Q3BSPLoad load_data; + load_data.Load(file,8); + + osg::Geode* geode = convertFromBSP(load_data, options); + if (!geode) + return false; + + //osg::StateSet* state_set=geode->getOrCreateStateSet(); + //state_set->setMode(osg::CullFace::BACK,osg::StateAttribute::ON); + + root_node = geode; + + return true; +} + + +osg::ref_ptr Q3BSPReader::getRootNode() +{ + return root_node; +} + + + +enum BSP_FACE_TYPE +{ + bspPolygonFace=1, + bspPatch, + bspMeshFace, + bspBillboard +}; + + + + + +class BSP_VERTEX +{ +public: + osg::Vec3f m_position; + float m_decalS, m_decalT; + float m_lightmapS, m_lightmapT; + + BSP_VERTEX operator+(const BSP_VERTEX & rhs) const + { + BSP_VERTEX result; + result.m_position=m_position+rhs.m_position; + result.m_decalS=m_decalS+rhs.m_decalS; + result.m_decalT=m_decalT+rhs.m_decalT; + result.m_lightmapS=m_lightmapS+rhs.m_lightmapS; + result.m_lightmapT=m_lightmapT+rhs.m_lightmapT; + + return result; + } + + BSP_VERTEX operator*(const float rhs) const + { + BSP_VERTEX result; + result.m_position=m_position*rhs; + result.m_decalS=m_decalS*rhs; + result.m_decalT=m_decalT*rhs; + result.m_lightmapS=m_lightmapS*rhs; + result.m_lightmapT=m_lightmapT*rhs; + + return result; + } +}; + + + + + +//every patch (curved surface) is split into biquadratic (3x3) patches +class BSP_BIQUADRATIC_PATCH +{ +public: + BSP_BIQUADRATIC_PATCH():m_vertices(32),m_indices(32) + { + } + ~BSP_BIQUADRATIC_PATCH() + { + } + + bool Tessellate(int newTessellation,osg::Geometry* aGeometry); + + BSP_VERTEX m_controlPoints[9]; // Se accede a ellos en la carga + +protected: + + int m_tessellation; + std::vector m_vertices; + std::vector m_indices; + + //arrays for multi_draw_arrays + std::vector m_trianglesPerRow; + std::vector m_rowIndexPointers; + +}; + + +//curved surface +class BSP_PATCH +{ +public: + + BSP_PATCH():m_quadraticPatches(32) + { + } + ~BSP_PATCH() + { + } + + int m_textureIndex; + int m_lightmapIndex; + int m_width, m_height; + + int m_numQuadraticPatches; + std::vector m_quadraticPatches; +}; + + + + + + + + + +osg::Geode* Q3BSPReader::convertFromBSP( + Q3BSPLoad& aLoadData, + const osgDB::ReaderWriter::Options* options) const +{ + + std::vector texture_array; + loadTextures(aLoadData,texture_array); + + std::vector lightmap_array; + loadLightMaps(aLoadData,lightmap_array); + + osg::Geode* map_geode=new osg::Geode; + + // Convertir los vertices + unsigned int num_load_vertices=aLoadData.m_loadVertices.size(); + osg::Vec3Array* vertex_array = new osg::Vec3Array(num_load_vertices); + osg::Vec2Array* text_decal_array = new osg::Vec2Array(num_load_vertices); + osg::Vec2Array* text_lmap_array = new osg::Vec2Array(num_load_vertices); + + float scale = 0.03; + unsigned int i; + for(i=0; iaddDrawable(mesh_geom); + } + + + for(i=0; iaddDrawable(polygon_geom); + } + + + + for(i=0; i=0) + { + lightmap_texture=lightmap_array[current_face.m_lightmapIndex]; + } + else + { + lightmap_texture=lightmap_array[lightmap_array.size()-1]; + } + + //Create space to hold quadratic patches + int numPatchesWide=(current_patch.m_width-1)/2; + int numPatchesHigh=(current_patch.m_height-1)/2; + + current_patch.m_numQuadraticPatches = numPatchesWide*numPatchesHigh; + current_patch.m_quadraticPatches.resize(current_patch.m_numQuadraticPatches); + + //fill in the quadratic patches + for(int y=0; ygetOrCreateStateSet(); + if(texture) + { + stateset->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON); + } + + if(lightmap_texture) + { + stateset->setTextureAttributeAndModes(1,lightmap_texture,osg::StateAttribute::ON); + } + + //patch_group->addChild(map_geode); + + current_patch.m_quadraticPatches[y*numPatchesWide+x].Tessellate(8/*aCurveTessellation*/,patch_geom); + map_geode->addDrawable(patch_geom); + } + } + + + } + + + //int num_primitive_sets=geom->getNumPrimitiveSets(); + //const osg::BoundingSphere& bs=map_geom->getBound(); + + + map_geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + + return map_geode; +} + + + +osg::Geometry* Q3BSPReader::createMeshFace( const BSP_LOAD_FACE& aLoadFace,const std::vector& aTextureArray, + osg::Vec3Array& aVertexArray,std::vector& aIndices, + osg::Vec2Array& aTextureDecalCoords,osg::Vec2Array& aTextureLMapCoords + ) const +{ + + osg::Geometry* obj_geom = new osg::Geometry; + + + osg::Vec3Array* obj_vertex_array = new osg::Vec3Array(aLoadFace.m_numMeshIndices, + &(aVertexArray)[aLoadFace.m_firstVertexIndex] + ); + obj_geom->setVertexArray(obj_vertex_array); + + osg::DrawElementsUInt* face_indices = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, + aLoadFace.m_numMeshIndices, + &(aIndices)[0]+aLoadFace.m_firstMeshIndex + ); + + obj_geom->addPrimitiveSet(face_indices); + + osg::Texture2D *texture=aTextureArray[aLoadFace.m_texture]; + if(texture) + { + osg::StateSet* stateset = obj_geom->getOrCreateStateSet(); + stateset->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(1,texture,osg::StateAttribute::ON); + + osg::Vec2Array* obj_texcoords_array = new osg::Vec2Array(aLoadFace.m_numMeshIndices, + &(aTextureDecalCoords)[aLoadFace.m_firstVertexIndex] + ); + obj_geom->setTexCoordArray(0,obj_texcoords_array); + + osg::Vec2Array* obj_lmapcoords_array = new osg::Vec2Array(aLoadFace.m_numMeshIndices, + &(aTextureLMapCoords)[aLoadFace.m_firstVertexIndex] + ); + obj_geom->setTexCoordArray(1,obj_lmapcoords_array); + } + + return obj_geom; + + +} + + + + + + + +osg::Geometry* Q3BSPReader::createPolygonFace(const BSP_LOAD_FACE& aLoadFace,const std::vector& aTextureArray,const std::vector& aTextureLMapArray, + osg::Vec3Array& aVertexArray, + osg::Vec2Array& aTextureDecalCoords,osg::Vec2Array& aTextureLMapCoords + ) const +{ + osg::Texture2D *texture=aTextureArray[aLoadFace.m_texture]; + + osg::Geometry* polygon_geom = new osg::Geometry; + polygon_geom->setVertexArray(&aVertexArray); + polygon_geom->setTexCoordArray(0, &aTextureDecalCoords); + polygon_geom->setTexCoordArray(1, &aTextureLMapCoords); + + osg::DrawArrays* face_indices = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_FAN, + aLoadFace.m_firstVertexIndex, + aLoadFace.m_numVertices + ); + + osg::StateSet* stateset = polygon_geom->getOrCreateStateSet(); + if(texture) + { + stateset->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON); + if(aLoadFace.m_lightmapIndex>=0) + { + texture=aTextureLMapArray[aLoadFace.m_lightmapIndex]; + if(texture) + { + stateset->setTextureAttributeAndModes(1,texture,osg::StateAttribute::ON); + } + } + else + { + texture=aTextureLMapArray[aTextureLMapArray.size()-1]; + if(texture) + { + stateset->setTextureAttributeAndModes(1,texture,osg::StateAttribute::ON); + } + } + + } + else + { + osg::PolygonMode* polygon_mode=new osg::PolygonMode; + polygon_mode->setMode(osg::PolygonMode::FRONT_AND_BACK,osg::PolygonMode::LINE); + stateset->setAttributeAndModes(polygon_mode, osg::StateAttribute::ON); + } + + polygon_geom->addPrimitiveSet(face_indices); + return polygon_geom; +} + + + + + + + + + + + + +bool Q3BSPReader::loadTextures( + const Q3BSPLoad& aLoadData, + std::vector& aTextureArray) const +{ + int num_textures=aLoadData.m_loadTextures.size(); + + int i; + for(i=0;isetImage(image); + texture->setDataVariance(osg::Object::DYNAMIC); // protect from being optimized away as static state. + texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT); + texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT); + aTextureArray.push_back(texture); + } + + return true; +} + + + + + +bool Q3BSPReader::loadLightMaps( + const Q3BSPLoad& aLoadData, + std::vector& aTextureArray) const +{ + int num_textures=aLoadData.m_loadLightmaps.size(); + + int i; + for(i=0;isetImage(128,128,1,GL_RGBA8,GL_RGB,GL_UNSIGNED_BYTE,data,osg::Image::USE_NEW_DELETE); + + osg::Texture2D* texture= new osg::Texture2D; + texture->setImage(image); + texture->setDataVariance(osg::Object::DYNAMIC); // protect from being optimized away as static state. + texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR_MIPMAP_LINEAR); + texture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR); + texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT); + texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT); + aTextureArray.push_back(texture); + } + + + + // A continuación, añado el blanco + osg::Image* image=new osg::Image; + unsigned char *data=new unsigned char[3]; + for(int whiteidx=0;whiteidx<3;whiteidx++) + { + data[whiteidx]=255; + } + + image->setImage(1,1,1,GL_RGBA8,GL_RGB,GL_UNSIGNED_BYTE,data,osg::Image::USE_NEW_DELETE); + + osg::Texture2D* texture= new osg::Texture2D; + texture->setImage(image); + texture->setDataVariance(osg::Object::DYNAMIC); // protect from being optimized away as static state. + texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR_MIPMAP_LINEAR); + texture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR); + texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT); + texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT); + aTextureArray.push_back(texture); + + + return true; +} + + +//Tessellate a biquadratic patch +bool BSP_BIQUADRATIC_PATCH::Tessellate(int newTessellation,osg::Geometry* aGeometry) +{ + m_tessellation=newTessellation; + + float px, py; + BSP_VERTEX temp[3]; + m_vertices.resize((m_tessellation+1)*(m_tessellation+1)); + + for(int v=0; v<=m_tessellation; ++v) + { + px=(float)v/m_tessellation; + + m_vertices[v]=m_controlPoints[0]*((1.0f-px)*(1.0f-px))+ + m_controlPoints[3]*((1.0f-px)*px*2)+ + m_controlPoints[6]*(px*px); + } + + for(int u=1; u<=m_tessellation; ++u) + { + py=(float)u/m_tessellation; + + temp[0]=m_controlPoints[0]*((1.0f-py)*(1.0f-py))+ + m_controlPoints[1]*((1.0f-py)*py*2)+ + m_controlPoints[2]*(py*py); + + temp[1]=m_controlPoints[3]*((1.0f-py)*(1.0f-py))+ + m_controlPoints[4]*((1.0f-py)*py*2)+ + m_controlPoints[5]*(py*py); + + temp[2]=m_controlPoints[6]*((1.0f-py)*(1.0f-py))+ + m_controlPoints[7]*((1.0f-py)*py*2)+ + m_controlPoints[8]*(py*py); + + for(int v=0; v<=m_tessellation; ++v) + { + px=(float)v/m_tessellation; + + m_vertices[u*(m_tessellation+1)+v]=temp[0]*((1.0f-px)*(1.0f-px))+ + temp[1]*((1.0f-px)*px*2)+ + temp[2]*(px*px); + } + } + + //Create indices + m_indices.resize(m_tessellation*(m_tessellation+1)*2); + + int row; + for(row=0; rowsetVertexArray(patch_vertex_array); + aGeometry->setTexCoordArray(0,patch_textcoord_array); + aGeometry->setTexCoordArray(1,patch_lmapcoord_array); + + + for(row=0; rowaddPrimitiveSet(face_indices); + + } + + + + return true; +} + + + + + diff --git a/src/osgPlugins/bsp/Q3BSPReader.h b/src/osgPlugins/bsp/Q3BSPReader.h new file mode 100644 index 000000000..36ab1fc4a --- /dev/null +++ b/src/osgPlugins/bsp/Q3BSPReader.h @@ -0,0 +1,83 @@ +// El siguiente bloque ifdef muestra la forma estándar de crear macros que facilitan +// la exportación de archivos DLL. Todos los archivos de este archivo DLL se compilan con el símbolo Q3BSP_EXPORTS +// definido en la línea de comandos. Este símbolo no se debe definir en ningún proyecto +// que utilice este archivo DLL. De este modo, otros proyectos cuyos archivos de código fuente incluyan el archivo +// interpreta que las funciones Q3BSP_API se importan de un archivo DLL, mientras que este archivo DLL interpreta los símbolos +// definidos en esta macro como si fueran exportados. +/* +#ifdef Q3BSP_EXPORTS +#define Q3BSP_API __declspec(dllexport) +#else +#define Q3BSP_API __declspec(dllimport) +#endif + +// Clase exportada de q3bsp.dll +class Q3BSP_API Cq3bsp { +public: + Cq3bsp(void); + // TODO: agregar métodos aquí. +}; + +extern Q3BSP_API int nq3bsp; + +Q3BSP_API int fnq3bsp(void); +*/ + + +#include +#include +#include +#include + +#include "Q3BSPLoad.h" + + +namespace bsp +{ + + +class Q3BSPReader +{ +public: + + Q3BSPReader(); + + bool readFile(const std::string& fileName, + const osgDB::ReaderWriter::Options*); + + osg::ref_ptr getRootNode(); + +private: + + osg::ref_ptr root_node; + + osg::Geode* convertFromBSP(Q3BSPLoad& aLoadData, + const osgDB::ReaderWriter::Options*) const; + + osg::Geometry* createMeshFace( + const BSP_LOAD_FACE& aLoadFace, + const std::vector& aTextureArray, + osg::Vec3Array& aVertexArray, + std::vector& aIndices, + osg::Vec2Array& aTextureDecalCoords, + osg::Vec2Array& aTextureLMapCoords) const; + + osg::Geometry* createPolygonFace( + const BSP_LOAD_FACE& aLoadFace, + const std::vector& aTextureArray, + const std::vector& aTextureLMapArray, + osg::Vec3Array& aVertexArray, + osg::Vec2Array& aTextureDecalCoords, + osg::Vec2Array& aTextureLMapCoords) const; + + bool loadTextures( + const Q3BSPLoad& aLoadData, + std::vector& aTextureArray) const; + + bool loadLightMaps( + const Q3BSPLoad& aLoadData, + std::vector& aTextureArray) const; +}; + + +} diff --git a/src/osgPlugins/bsp/ReaderWriterBSP.cpp b/src/osgPlugins/bsp/ReaderWriterBSP.cpp index 695c15edc..4c60a61e4 100644 --- a/src/osgPlugins/bsp/ReaderWriterBSP.cpp +++ b/src/osgPlugins/bsp/ReaderWriterBSP.cpp @@ -1,670 +1,120 @@ - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include +#include +#include +#include #include +#include -#include -#include +#include "ReaderWriterBSP.h" +#include "VBSPReader.h" +#include "Q3BSPReader.h" -#include "BSPLoad.h" +using namespace bsp; +using namespace osg; +using namespace osgDB; -class ReaderWriterQ3BSP: public osgDB::ReaderWriter +// "VBSP" for Valve BSP files +const int VBSP_MAGIC_NUMBER = (('P'<<24)+('S'<<16)+('B'<<8)+'V'); + +// "IBSP" for id (Quake 3) BSP files +const int IBSP_MAGIC_NUMBER = (('P'<<24)+('S'<<16)+('B'<<8)+'I'); + + + +const char* ReaderWriterBSP::className() const { -public: - ReaderWriterQ3BSP() - { - supportsExtension("bsp","Quake3 BSP model format"); - } - - virtual const char* className() const - { - return "Quake3 BSP Reader"; - } - - virtual ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options* options) const; - -private: - osg::Geode* convertFromBSP(BSPLoad& aLoadData,const osgDB::ReaderWriter::Options* options) const; - osg::Geometry* createMeshFace(const BSP_LOAD_FACE& aLoadFace,const std::vector& aTextureArray, - osg::Vec3Array& aVertexArray,std::vector& aIndices, - osg::Vec2Array& aTextureDecalCoords,osg::Vec2Array& aTextureLMapCoords - ) const; - osg::Geometry* createPolygonFace(const BSP_LOAD_FACE& aLoadFace,const std::vector& aTextureArray,const std::vector& aTextureLMapArray, - osg::Vec3Array& aVertexArray, - osg::Vec2Array& aTextureDecalCoords,osg::Vec2Array& aTextureLMapCoords - ) const; - bool loadTextures(const BSPLoad& aLoadData,std::vector& aTextureArray) const; - bool loadLightMaps(const BSPLoad& aLoadData,std::vector& aTextureArray) const; -}; - -// Register with Registry to instantiate the above reader/writer. -REGISTER_OSGPLUGIN(bsp, ReaderWriterQ3BSP) - - - - - - - - - - - -// Read node -osgDB::ReaderWriter::ReadResult ReaderWriterQ3BSP::readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) const -{ - std::string ext = osgDB::getLowerCaseFileExtension(file); - if (!acceptsExtension(ext)) - return ReadResult::FILE_NOT_HANDLED; - - std::string file_name = osgDB::findDataFile( file, options ); - if (file_name.empty()) - return ReadResult::FILE_NOT_FOUND; - - //osg::notify(osg::INFO) << "ReaderWriterQ3BSP::readNode(" << fileName.c_str() << ")\n"; - BSPLoad load_data; - load_data.Load(file_name,8); - - osg::Geode* geode = convertFromBSP(load_data, options); - if (!geode) - return ReadResult::FILE_NOT_HANDLED; - - //osg::StateSet* state_set=geode->getOrCreateStateSet(); - //state_set->setMode(osg::CullFace::BACK,osg::StateAttribute::ON); - return geode; - - - return ReadResult::FILE_NOT_HANDLED; + // Return a description of this class + return "BSP File Reader"; } - - - -enum BSP_FACE_TYPE +bool ReaderWriterBSP::acceptsExtension(const std::string& extension) const { - bspPolygonFace=1, - bspPatch, - bspMeshFace, - bspBillboard -}; + // If the extension is empty or "bsp", we accept it + return osgDB::equalCaseInsensitive(extension, "bsp") || extension.empty(); +} - - - -class BSP_VERTEX +ReaderWriter::ReadResult ReaderWriterBSP::readNode( + const std::string& file, + const ReaderWriter::Options* options) const { -public: - osg::Vec3f m_position; - float m_decalS, m_decalT; - float m_lightmapS, m_lightmapT; + VBSPReader * vbspReader; + Q3BSPReader * q3bspReader; + ref_ptr result; + osgDB::ifstream stream; + int magicNumber; + int version; - BSP_VERTEX operator+(const BSP_VERTEX & rhs) const - { - BSP_VERTEX result; - result.m_position=m_position+rhs.m_position; - result.m_decalS=m_decalS+rhs.m_decalS; - result.m_decalT=m_decalT+rhs.m_decalT; - result.m_lightmapS=m_lightmapS+rhs.m_lightmapS; - result.m_lightmapT=m_lightmapT+rhs.m_lightmapT; + // See if we handle this kind of file + if (!acceptsExtension(osgDB::getFileExtension(file))) + return ReadResult::FILE_NOT_HANDLED; - return result; - } + // See if we can find the requested file + std::string fileName = osgDB::findDataFile(file, options); + if (fileName.empty()) + return ReadResult::FILE_NOT_FOUND; - BSP_VERTEX operator*(const float rhs) const - { - BSP_VERTEX result; - result.m_position=m_position*rhs; - result.m_decalS=m_decalS*rhs; - result.m_decalT=m_decalT*rhs; - result.m_lightmapS=m_lightmapS*rhs; - result.m_lightmapT=m_lightmapT*rhs; - - return result; - } -}; - - - - - -//every patch (curved surface) is split into biquadratic (3x3) patches -class BSP_BIQUADRATIC_PATCH -{ -public: - BSP_BIQUADRATIC_PATCH():m_vertices(32),m_indices(32) + // Open the file and read the magic number and version + stream.open(file.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)) { - } - ~BSP_BIQUADRATIC_PATCH() - { - } - - bool Tessellate(int newTessellation,osg::Geometry* aGeometry); - - BSP_VERTEX m_controlPoints[9]; // Se accede a ellos en la carga - -protected: - - int m_tessellation; - std::vector m_vertices; - std::vector m_indices; - - //arrays for multi_draw_arrays - std::vector m_trianglesPerRow; - std::vector m_rowIndexPointers; - -}; - - -//curved surface -class BSP_PATCH -{ -public: - - BSP_PATCH():m_quadraticPatches(32) - { - } - ~BSP_PATCH() - { - } - - int m_textureIndex; - int m_lightmapIndex; - int m_width, m_height; - - int m_numQuadraticPatches; - std::vector m_quadraticPatches; -}; - - - - - - - - - -osg::Geode* ReaderWriterQ3BSP::convertFromBSP(BSPLoad& aLoadData,const osgDB::ReaderWriter::Options*) const -{ - - std::vector texture_array; - loadTextures(aLoadData,texture_array); - - std::vector lightmap_array; - loadLightMaps(aLoadData,lightmap_array); - - osg::Geode* map_geode=new osg::Geode; - - // Convertir los vertices - unsigned int num_load_vertices=aLoadData.m_loadVertices.size(); - osg::Vec3Array* vertex_array = new osg::Vec3Array(num_load_vertices); - osg::Vec2Array* text_decal_array = new osg::Vec2Array(num_load_vertices); - osg::Vec2Array* text_lmap_array = new osg::Vec2Array(num_load_vertices); - - float scale = 0.03; - unsigned int i; - for(i=0; iaddDrawable(mesh_geom); - } - - - for(i=0; iaddDrawable(polygon_geom); - } - - - - for(i=0; i=0) + // Read the Valve file + vbspReader = new VBSPReader(); + if (vbspReader->readFile(fileName)) { - lightmap_texture=lightmap_array[current_face.m_lightmapIndex]; + // Get the results of our read + result = vbspReader->getRootNode(); + + // Clean up the reader + delete vbspReader; + + // Return the results + return ReadResult(result.get()); } - else + else { - lightmap_texture=lightmap_array[lightmap_array.size()-1]; + // Clean up the reader + delete vbspReader; + + // Return the error + return ReadResult::ERROR_IN_READING_FILE; } - - //Create space to hold quadratic patches - int numPatchesWide=(current_patch.m_width-1)/2; - int numPatchesHigh=(current_patch.m_height-1)/2; - - current_patch.m_numQuadraticPatches = numPatchesWide*numPatchesHigh; - current_patch.m_quadraticPatches.resize(current_patch.m_numQuadraticPatches); - - //fill in the quadratic patches - for(int y=0; yreadFile(file, options)) { - for(int x=0; xgetRootNode(); - osg::Vec3f vtx= (*vertex_array) [aLoadData.m_loadFaces[i].m_firstVertexIndex+(y*2*current_patch.m_width+x*2)+ - row*current_patch.m_width+point]; + // Clean up the reader + delete q3bspReader; - curr_quadraticpatch.m_controlPoints[row*3+point].m_position[0] = vtx.x(); - curr_quadraticpatch.m_controlPoints[row*3+point].m_position[1] = vtx.y(); - curr_quadraticpatch.m_controlPoints[row*3+point].m_position[2] = vtx.z(); - - } - } - - - - osg::Geometry* patch_geom = new osg::Geometry; - //tessellate the patch - - osg::StateSet* stateset = patch_geom->getOrCreateStateSet(); - if(texture) - { - stateset->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON); - } - - if(lightmap_texture) - { - stateset->setTextureAttributeAndModes(1,lightmap_texture,osg::StateAttribute::ON); - } - - //patch_group->addChild(map_geode); - - current_patch.m_quadraticPatches[y*numPatchesWide+x].Tessellate(8/*aCurveTessellation*/,patch_geom); - map_geode->addDrawable(patch_geom); - } + // Return the results + return ReadResult(result.get()); } - - - } - - - //int num_primitive_sets=geom->getNumPrimitiveSets(); - //const osg::BoundingSphere& bs=map_geom->getBound(); - - - map_geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - - - return map_geode; -} - - - -osg::Geometry* ReaderWriterQ3BSP::createMeshFace( const BSP_LOAD_FACE& aLoadFace,const std::vector& aTextureArray, - osg::Vec3Array& aVertexArray,std::vector& aIndices, - osg::Vec2Array& aTextureDecalCoords,osg::Vec2Array& aTextureLMapCoords - ) const -{ - - osg::Geometry* obj_geom = new osg::Geometry; - - - osg::Vec3Array* obj_vertex_array = new osg::Vec3Array(aLoadFace.m_numMeshIndices, - &(aVertexArray)[aLoadFace.m_firstVertexIndex] - ); - obj_geom->setVertexArray(obj_vertex_array); - - osg::DrawElementsUInt* face_indices = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLES, - aLoadFace.m_numMeshIndices, - &(aIndices)[0]+aLoadFace.m_firstMeshIndex - ); - - obj_geom->addPrimitiveSet(face_indices); - - osg::Texture2D *texture=aTextureArray[aLoadFace.m_texture]; - if(texture) - { - osg::StateSet* stateset = obj_geom->getOrCreateStateSet(); - stateset->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON); - stateset->setTextureAttributeAndModes(1,texture,osg::StateAttribute::ON); - - osg::Vec2Array* obj_texcoords_array = new osg::Vec2Array(aLoadFace.m_numMeshIndices, - &(aTextureDecalCoords)[aLoadFace.m_firstVertexIndex] - ); - obj_geom->setTexCoordArray(0,obj_texcoords_array); - - osg::Vec2Array* obj_lmapcoords_array = new osg::Vec2Array(aLoadFace.m_numMeshIndices, - &(aTextureLMapCoords)[aLoadFace.m_firstVertexIndex] - ); - obj_geom->setTexCoordArray(1,obj_lmapcoords_array); - } - - return obj_geom; - - -} - - - - - - - -osg::Geometry* ReaderWriterQ3BSP::createPolygonFace(const BSP_LOAD_FACE& aLoadFace,const std::vector& aTextureArray,const std::vector& aTextureLMapArray, - osg::Vec3Array& aVertexArray, - osg::Vec2Array& aTextureDecalCoords,osg::Vec2Array& aTextureLMapCoords - ) const -{ - osg::Texture2D *texture=aTextureArray[aLoadFace.m_texture]; - - osg::Geometry* polygon_geom = new osg::Geometry; - polygon_geom->setVertexArray(&aVertexArray); - polygon_geom->setTexCoordArray(0, &aTextureDecalCoords); - polygon_geom->setTexCoordArray(1, &aTextureLMapCoords); - - osg::DrawArrays* face_indices = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_FAN, - aLoadFace.m_firstVertexIndex, - aLoadFace.m_numVertices - ); - - osg::StateSet* stateset = polygon_geom->getOrCreateStateSet(); - if(texture) - { - stateset->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON); - if(aLoadFace.m_lightmapIndex>=0) + else { - texture=aTextureLMapArray[aLoadFace.m_lightmapIndex]; - if(texture) - { - stateset->setTextureAttributeAndModes(1,texture,osg::StateAttribute::ON); - } + // Clean up the reader + delete q3bspReader; + + // Return the error + return ReadResult::ERROR_IN_READING_FILE; } - else - { - texture=aTextureLMapArray[aTextureLMapArray.size()-1]; - if(texture) - { - stateset->setTextureAttributeAndModes(1,texture,osg::StateAttribute::ON); - } - } - } - else - { - osg::PolygonMode* polygon_mode=new osg::PolygonMode; - polygon_mode->setMode(osg::PolygonMode::FRONT_AND_BACK,osg::PolygonMode::LINE); - stateset->setAttributeAndModes(polygon_mode, osg::StateAttribute::ON); - } - - polygon_geom->addPrimitiveSet(face_indices); - return polygon_geom; + else + return ReadResult::FILE_NOT_HANDLED; } - - - - - - - - - - -bool ReaderWriterQ3BSP::loadTextures(const BSPLoad& aLoadData,std::vector& aTextureArray) const -{ - int num_textures=aLoadData.m_loadTextures.size(); - - int i; - for(i=0;isetImage(image); - texture->setDataVariance(osg::Object::DYNAMIC); // protect from being optimized away as static state. - texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT); - texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT); - aTextureArray.push_back(texture); - } - - return true; -} - - - - - -bool ReaderWriterQ3BSP::loadLightMaps(const BSPLoad& aLoadData,std::vector& aTextureArray) const -{ - int num_textures=aLoadData.m_loadLightmaps.size(); - - int i; - for(i=0;isetImage(128,128,1,GL_RGBA8,GL_RGB,GL_UNSIGNED_BYTE,data,osg::Image::USE_NEW_DELETE); - - osg::Texture2D* texture= new osg::Texture2D; - texture->setImage(image); - texture->setDataVariance(osg::Object::DYNAMIC); // protect from being optimized away as static state. - texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR_MIPMAP_LINEAR); - texture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR); - texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT); - texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT); - aTextureArray.push_back(texture); - } - - - - // A continuación, añado el blanco - osg::Image* image=new osg::Image; - unsigned char *data=new unsigned char[3]; - for(int whiteidx=0;whiteidx<3;whiteidx++) - { - data[whiteidx]=255; - } - - image->setImage(1,1,1,GL_RGBA8,GL_RGB,GL_UNSIGNED_BYTE,data,osg::Image::USE_NEW_DELETE); - - osg::Texture2D* texture= new osg::Texture2D; - texture->setImage(image); - texture->setDataVariance(osg::Object::DYNAMIC); // protect from being optimized away as static state. - texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR_MIPMAP_LINEAR); - texture->setFilter(osg::Texture2D::MAG_FILTER,osg::Texture2D::LINEAR); - texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT); - texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT); - aTextureArray.push_back(texture); - - - return true; -} - - -//Tessellate a biquadratic patch -bool BSP_BIQUADRATIC_PATCH::Tessellate(int newTessellation,osg::Geometry* aGeometry) -{ - m_tessellation=newTessellation; - - float px, py; - BSP_VERTEX temp[3]; - m_vertices.resize((m_tessellation+1)*(m_tessellation+1)); - - for(int v=0; v<=m_tessellation; ++v) - { - px=(float)v/m_tessellation; - - m_vertices[v]=m_controlPoints[0]*((1.0f-px)*(1.0f-px))+ - m_controlPoints[3]*((1.0f-px)*px*2)+ - m_controlPoints[6]*(px*px); - } - - for(int u=1; u<=m_tessellation; ++u) - { - py=(float)u/m_tessellation; - - temp[0]=m_controlPoints[0]*((1.0f-py)*(1.0f-py))+ - m_controlPoints[1]*((1.0f-py)*py*2)+ - m_controlPoints[2]*(py*py); - - temp[1]=m_controlPoints[3]*((1.0f-py)*(1.0f-py))+ - m_controlPoints[4]*((1.0f-py)*py*2)+ - m_controlPoints[5]*(py*py); - - temp[2]=m_controlPoints[6]*((1.0f-py)*(1.0f-py))+ - m_controlPoints[7]*((1.0f-py)*py*2)+ - m_controlPoints[8]*(py*py); - - for(int v=0; v<=m_tessellation; ++v) - { - px=(float)v/m_tessellation; - - m_vertices[u*(m_tessellation+1)+v]=temp[0]*((1.0f-px)*(1.0f-px))+ - temp[1]*((1.0f-px)*px*2)+ - temp[2]*(px*px); - } - } - - //Create indices - m_indices.resize(m_tessellation*(m_tessellation+1)*2); - - int row; - for(row=0; rowsetVertexArray(patch_vertex_array); - aGeometry->setTexCoordArray(0,patch_textcoord_array); - aGeometry->setTexCoordArray(1,patch_lmapcoord_array); - - - for(row=0; rowaddPrimitiveSet(face_indices); - - } - - - - return true; -} - - - - +REGISTER_OSGPLUGIN(bsp, ReaderWriterBSP) diff --git a/src/osgPlugins/bsp/ReaderWriterBSP.h b/src/osgPlugins/bsp/ReaderWriterBSP.h index 3a20c8c55..6524b99a4 100644 --- a/src/osgPlugins/bsp/ReaderWriterBSP.h +++ b/src/osgPlugins/bsp/ReaderWriterBSP.h @@ -1,27 +1,28 @@ -// El siguiente bloque ifdef muestra la forma estándar de crear macros que facilitan -// la exportación de archivos DLL. Todos los archivos de este archivo DLL se compilan con el símbolo Q3BSP_EXPORTS -// definido en la línea de comandos. Este símbolo no se debe definir en ningún proyecto -// que utilice este archivo DLL. De este modo, otros proyectos cuyos archivos de código fuente incluyan el archivo -// interpreta que las funciones Q3BSP_API se importan de un archivo DLL, mientras que este archivo DLL interpreta los símbolos -// definidos en esta macro como si fueran exportados. -/* -#ifdef Q3BSP_EXPORTS -#define Q3BSP_API __declspec(dllexport) -#else -#define Q3BSP_API __declspec(dllimport) -#endif +#ifndef __READERWRITER_VBSP_H_ +#define __READERWRITER_VBSP_H_ -// Clase exportada de q3bsp.dll -class Q3BSP_API Cq3bsp { + +#include +#include + + +namespace bsp +{ + + +class ReaderWriterBSP : public osgDB::ReaderWriter +{ public: - Cq3bsp(void); - // TODO: agregar métodos aquí. + + virtual const char* className() const; + + virtual bool acceptsExtension(const std::string& extension) const; + + virtual ReadResult readNode(const std::string& file, + const Options* options) const; }; -extern Q3BSP_API int nq3bsp; - -Q3BSP_API int fnq3bsp(void); -*/ - +} +#endif diff --git a/src/osgPlugins/bsp/VBSPGeometry.cpp b/src/osgPlugins/bsp/VBSPGeometry.cpp new file mode 100644 index 000000000..9376430a0 --- /dev/null +++ b/src/osgPlugins/bsp/VBSPGeometry.cpp @@ -0,0 +1,654 @@ + +#include "VBSPGeometry.h" + + +using namespace osg; +using namespace osgDB; +using namespace bsp; + + +VBSPGeometry::VBSPGeometry(VBSPReader * reader) +{ + // Keep track of the reader, as it has all of the data lists that we + // need + vbsp_reader = reader; + + // Create arrays for the vertex attributes + vertex_array = new Vec3Array(); + normal_array = new Vec3Array(); + texcoord_array = new Vec2Array(); + + // Create a primitive set for drawing variable length primitives (VBSP + // primitives are only guaranteed to be convex polygons) + primitive_set = new DrawArrayLengths(PrimitiveSet::POLYGON); + + // Create a second set of arrays for displacement surfaces + disp_vertex_array = new Vec3Array(); + disp_normal_array = new Vec3Array(); + disp_texcoord_array = new Vec2Array(); + disp_vertex_attr_array = new FloatArray(); + + // Create a second primitive set for drawing indexed triangles, which is + // the quickest method for drawing the displacement surfaces + disp_primitive_set = new DrawElementsUInt(PrimitiveSet::TRIANGLES); +} + + +VBSPGeometry::~VBSPGeometry() +{ +} + + +bool VBSPGeometry::doesEdgeExist(int row, int col, int direction, + int vertsPerEdge) +{ + // See if there is an edge on the displacement surface from the given + // vertex in the given direction (we only need to know the vertices + // indices, because all displacement surfaces are tessellated in the + // same way) + switch (direction) + { + case 0: + // False if we're on the left edge, otherwise true + if ((row - 1) < 0) + return false; + else + return true; + + case 1: + // False if we're on the top edge, otherwise true + if ((col + 1) >= vertsPerEdge) + return false; + else + return true; + + case 2: + // False if we're on the right edge, otherwise true + if ((row + 1) >= vertsPerEdge) + return false; + else + return true; + + case 3: + // False if we're on the bottom edge, otherwise true + if ((col - 1) < 0) + return false; + else + return true; + + default: + return false; + } +} + + +osg::Vec3 VBSPGeometry::getNormalFromEdges(int row, int col, + unsigned char edgeBits, + int firstVertex, int vertsPerEdge) +{ + osg::Vec3 * vertexData; + osg::Vec3 * surfaceVerts; + osg::Vec3 finalNormal; + osg::Vec3 v1, v2, v3; + osg::Vec3 e1, e2; + osg::Vec3 tempNormal; + int normalCount; + + // Constants for direction. If the bit is set in the edgeBits, then + // there is an edge connected to the current vertex in that direction + const unsigned char NEG_X = 1 << 0; + const unsigned char POS_Y = 1 << 1; + const unsigned char POS_X = 1 << 2; + const unsigned char NEG_Y = 1 << 3; + + // Constants for quadrants. If both bits are set, then there are + // exactly two triangles in that quadrant + const unsigned char QUAD_1 = POS_X | POS_Y; + const unsigned char QUAD_2 = NEG_X | POS_Y; + const unsigned char QUAD_3 = NEG_X | NEG_Y; + const unsigned char QUAD_4 = POS_X | NEG_Y; + + + // Grab the vertex data from the displaced vertex array (if there's a + // better way to randomly access the data in this array, I'm all ears) + vertexData = (osg::Vec3 *)disp_vertex_array->getDataPointer(); + + // Move to the surface we're interested in, and start counting vertices + // from there + surfaceVerts = &vertexData[firstVertex]; + + // Start with no normals computed + finalNormal.set(0.0, 0.0, 0.0); + normalCount = 0; + + // The process is fairly simple. For all four quadrants surrounding + // the vertex, check each quadrant to see if there are triangles there. + // If so, calculate the normals of the two triangles in that quadrant, and + // add them to the final normal. When fininshed, scale the final normal + // based on the number of contributing triangle normals + + // Check quadrant 1 (+X,+Y) + if ((edgeBits & QUAD_1) == QUAD_1) + { + // First triangle + v1 = surfaceVerts[(col+1) * vertsPerEdge + row]; + v2 = surfaceVerts[col * vertsPerEdge + row]; + v3 = surfaceVerts[col * vertsPerEdge + (row+1)]; + e1 = v1 - v2; + e2 = v3 - v2; + tempNormal = e2 ^ e1; + tempNormal.normalize(); + finalNormal += tempNormal; + normalCount++; + + // Second triangle + v1 = surfaceVerts[(col+1) * vertsPerEdge + row]; + v2 = surfaceVerts[col * vertsPerEdge + (row+1)]; + v3 = surfaceVerts[(col+1) * vertsPerEdge + (row+1)]; + e1 = v1 - v2; + e2 = v3 - v2; + tempNormal = e2 ^ e1; + tempNormal.normalize(); + finalNormal += tempNormal; + normalCount++; + } + + // Check quadrant 2 (-X,+Y) + if ((edgeBits & QUAD_2) == QUAD_2) + { + // First triangle + v1 = surfaceVerts[(col+1) * vertsPerEdge + (row-1)]; + v2 = surfaceVerts[col * vertsPerEdge + (row-1)]; + v3 = surfaceVerts[col * vertsPerEdge + row]; + e1 = v1 - v2; + e2 = v3 - v2; + tempNormal = e2 ^ e1; + tempNormal.normalize(); + finalNormal += tempNormal; + normalCount++; + + // Second triangle + v1 = surfaceVerts[(col+1) * vertsPerEdge + (row-1)]; + v2 = surfaceVerts[col * vertsPerEdge + row]; + v3 = surfaceVerts[(col+1) * vertsPerEdge + row]; + e1 = v1 - v2; + e2 = v3 - v2; + tempNormal = e2 ^ e1; + tempNormal.normalize(); + finalNormal += tempNormal; + normalCount++; + } + + // Check quadrant 3 (-X,-Y) + if ((edgeBits & QUAD_3) == QUAD_3) + { + // First triangle + v1 = surfaceVerts[col * vertsPerEdge + (row-1)]; + v2 = surfaceVerts[(col-1) * vertsPerEdge + (row-1)]; + v3 = surfaceVerts[(col-1) * vertsPerEdge + row]; + e1 = v1 - v2; + e2 = v3 - v2; + tempNormal = e2 ^ e1; + tempNormal.normalize(); + finalNormal += tempNormal; + normalCount++; + + // Second triangle + v1 = surfaceVerts[col * vertsPerEdge + (row-1)]; + v2 = surfaceVerts[(col-1) * vertsPerEdge + row]; + v3 = surfaceVerts[col * vertsPerEdge + row]; + e1 = v1 - v2; + e2 = v3 - v2; + tempNormal = e2 ^ e1; + tempNormal.normalize(); + finalNormal += tempNormal; + normalCount++; + } + + // Check quadrant 4 (+X,-Y) + if ((edgeBits & QUAD_4) == QUAD_4) + { + // First triangle + v1 = surfaceVerts[col * vertsPerEdge + row]; + v2 = surfaceVerts[(col-1) * vertsPerEdge + row]; + v3 = surfaceVerts[(col-1) * vertsPerEdge + (row+1)]; + e1 = v1 - v2; + e2 = v3 - v2; + tempNormal = e2 ^ e1; + tempNormal.normalize(); + finalNormal += tempNormal; + normalCount++; + + // Second triangle + v1 = surfaceVerts[col * vertsPerEdge + row]; + v2 = surfaceVerts[(col-1) * vertsPerEdge + (row+1)]; + v3 = surfaceVerts[col * vertsPerEdge + (row+1)]; + e1 = v1 - v2; + e2 = v3 - v2; + tempNormal = e2 ^ e1; + tempNormal.normalize(); + finalNormal += tempNormal; + normalCount++; + } + + // Scale the final normal according to how many triangle normals are + // contributing + finalNormal *= (1.0f / (float)normalCount); + + return finalNormal; +} + + +void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo) +{ + TexInfo currentTexInfo; + TexData currentTexData; + Vec3 texU; + float texUOffset; + float texUScale; + Vec3 texV; + float texVOffset; + float texVScale; + unsigned int i, j, k; + osg::Vec3 temp; + int edgeIndex; + int currentSurfEdge; + Edge currentEdge; + osg::Vec3 currentVertex; + osg::Vec3 vertices[4]; + unsigned int firstVertex; + unsigned int numEdgeVertices; + double subdivideScale; + osg::Vec3 leftEdge, rightEdge; + osg::Vec3 leftEdgeStep, rightEdgeStep; + osg::Vec3 leftEnd, rightEnd; + osg::Vec3 leftRightSeg, leftRightStep; + unsigned int dispVertIndex; + DisplacedVertex dispVertInfo; + osg::Vec3 flatVertex, dispVertex; + unsigned int index; + osg::Vec3 normal; + float u, v; + osg::Vec2 texCoord; + unsigned char edgeBits; + + + // Get the texture info for this face + currentTexInfo = vbsp_reader->getTexInfo(face.texinfo_index); + currentTexData = vbsp_reader->getTexData(currentTexInfo.texdata_index); + + // Get the texture vectors and offsets. These are used to calculate + // texture coordinates + texU.set(currentTexInfo.texture_vecs[0][0], + currentTexInfo.texture_vecs[0][1], + currentTexInfo.texture_vecs[0][2]); + texUOffset = currentTexInfo.texture_vecs[0][3]; + texV.set(currentTexInfo.texture_vecs[1][0], + currentTexInfo.texture_vecs[1][1], + currentTexInfo.texture_vecs[1][2]); + texVOffset = currentTexInfo.texture_vecs[1][3]; + + // Get the size of the texture involved, as the planar texture projection + // assumes non-normalized texture coordinates + texUScale = 1.0 / (float)currentTexData.texture_width; + texVScale = 1.0 / (float)currentTexData.texture_height; + + // Get the first edge index + edgeIndex = face.first_edge; + + // Get the base vertices for this face + for (i = 0; i < face.num_edges; i++) + { + // 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)); + + // 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]); + else + currentVertex = vbsp_reader->getVertex(currentEdge.vertex[0]); + + // Add the vertex to the array + vertices[i] = currentVertex; + + // Move on to the next vertex + edgeIndex++; + } + + // Rotate the base coordinates for the surface until the first vertex + // matches the start position + while ((fabs(vertices[0].x() - dispInfo.start_position.x()) > 0.1) || + (fabs(vertices[0].y() - dispInfo.start_position.y()) > 0.1) || + (fabs(vertices[0].z() - dispInfo.start_position.z()) > 0.1)) + { + temp = vertices[0]; + vertices[0] = vertices[1]; + vertices[1] = vertices[2]; + vertices[2] = vertices[3]; + vertices[3] = temp; + } + + // Calculate the vectors for the left and right edges of the surface + // (remembering that the surface is wound clockwise) + leftEdge = vertices[1] - vertices[0]; + rightEdge = vertices[2] - vertices[3]; + + // Calculate the number of vertices along each edge of the surface + numEdgeVertices = (1 << dispInfo.power) + 1; + + // Calculate the subdivide scale, which will tell us how far apart to + // put each vertex (relative to the length of the surface's edges) + subdivideScale = 1.0 / (double)(numEdgeVertices - 1); + + // Calculate the step size between vertices on the left and right edges + leftEdgeStep = leftEdge * subdivideScale; + rightEdgeStep = rightEdge * subdivideScale; + + // Remember the first vertex index in the vertex array + firstVertex = disp_vertex_array->size(); + + // Generate the displaced vertices (this technique comes from the + // Source SDK) + for (i = 0; i < numEdgeVertices; i++) + { + // Calculate the two endpoints for this section of the surface + leftEnd = leftEdgeStep * (double) i; + leftEnd += vertices[0]; + rightEnd = rightEdgeStep * (double) i; + rightEnd += vertices[3]; + + // Now, get the vector from left to right, and subdivide it as well + leftRightSeg = rightEnd - leftEnd; + leftRightStep = leftRightSeg * subdivideScale; + + // Generate the vertices for this section + for (j = 0; j < numEdgeVertices; j++) + { + // Get the displacement info for this vertex + dispVertIndex = dispInfo.disp_vert_start; + dispVertIndex += i * numEdgeVertices + j; + dispVertInfo = vbsp_reader->getDispVertex(dispVertIndex); + + // Calculate the flat vertex + flatVertex = leftEnd + (leftRightStep * (double) j); + + // Calculate the displaced vertex + dispVertex = + dispVertInfo.displace_vec * dispVertInfo.displace_dist; + dispVertex += flatVertex; + + // Add the vertex to the displaced vertex array + disp_vertex_array->push_back(dispVertex); + + // Calculate the texture coordinates for this vertex. Texture + // coordinates are calculated using a planar projection, so we need + // to use the non-displaced vertex position here + u = texU * flatVertex + texUOffset; + u *= texUScale; + v = texV * flatVertex + texVOffset; + v *= texVScale; + texCoord.set(u, v); + + // Add the texture coordinate to the array + disp_texcoord_array->push_back(texCoord); + + // Get the texture blend parameter for this vertex as well + disp_vertex_attr_array-> + push_back(dispVertInfo.alpha_blend / 255.0); + } + } + + // Calculate normals at each vertex (this is adapted from the Source SDK, + // including the two helper functions) + for (i = 0; i < numEdgeVertices; i++) + { + for (j = 0; j < numEdgeVertices; j++) + { + // See which of the 4 possible edges (left, up, right, or down) are + // incident on this vertex + edgeBits = 0; + for (k = 0; k < 4; k++) + { + if (doesEdgeExist(j, i, k, numEdgeVertices)) + edgeBits |= 1 << k; + } + + // Calculate the normal based on the adjacent edges + normal = getNormalFromEdges(j, i, edgeBits, firstVertex, + numEdgeVertices); + + // Add the normal to the normal array + disp_normal_array->push_back(normal); + } + } + + // Now, triangulate the surface (this technique comes from the Source SDK) + for (i = 0; i < numEdgeVertices-1; i++) + { + for (j = 0; j < numEdgeVertices-1; j++) + { + // Get the current vertex index (local to this surface) + index = i * numEdgeVertices + j; + + // See if this index is odd + if ((index % 2) == 1) + { + // Add the vertex offset (so we reference this surface's + // vertices in the array) + index += firstVertex; + + // Create two triangles on this vertex from top-left to + // bottom-right + disp_primitive_set->push_back(index); + disp_primitive_set->push_back(index + 1); + disp_primitive_set->push_back(index + numEdgeVertices); + disp_primitive_set->push_back(index + 1); + disp_primitive_set->push_back(index + numEdgeVertices + 1); + disp_primitive_set->push_back(index + numEdgeVertices); + } + else + { + // Add the vertex offset (so we reference this surface's + // vertices in the array) + index += firstVertex; + + // Create two triangles on this vertex from bottom-left to + // top-right + disp_primitive_set->push_back(index); + disp_primitive_set->push_back(index + numEdgeVertices + 1); + disp_primitive_set->push_back(index + numEdgeVertices); + disp_primitive_set->push_back(index); + disp_primitive_set->push_back(index + 1); + disp_primitive_set->push_back(index + numEdgeVertices + 1); + } + } + } +} + + +void VBSPGeometry::addFace(int faceIndex) +{ + Face currentFace; + Edge currentEdge; + DisplaceInfo currentDispInfo; + TexInfo currentTexInfo; + TexData currentTexData; + Vec3 normal; + int edgeIndex; + int i; + int currentSurfEdge; + Vec3 currentVertex; + Vec3 texU; + float texUOffset; + float texUScale; + Vec3 texV; + float texVOffset; + float texVScale; + float u, v; + Vec2f texCoord; + + // 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); + + // See if this is a displacement surface + if (currentFace.dispinfo_index != -1) + { + // Get the displacement info + currentDispInfo = + vbsp_reader->getDispInfo(currentFace.dispinfo_index); + + // Generate the displacement surface + createDispSurface(currentFace, currentDispInfo); + } + else + { + // Get the face normal, using the plane information + normal = vbsp_reader->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); + + // Get the texture vectors and offsets. These are used to calculate + // texture coordinates + texU.set(currentTexInfo.texture_vecs[0][0], + currentTexInfo.texture_vecs[0][1], + currentTexInfo.texture_vecs[0][2]); + texUOffset = currentTexInfo.texture_vecs[0][3]; + texV.set(currentTexInfo.texture_vecs[1][0], + currentTexInfo.texture_vecs[1][1], + currentTexInfo.texture_vecs[1][2]); + texVOffset = currentTexInfo.texture_vecs[1][3]; + + // Get the texture size, as the planar texture projection results in + // non-normalized texture coordinates + texUScale = 1.0 / (float)currentTexData.texture_width; + texVScale = 1.0 / (float)currentTexData.texture_height; + + // Start with the last edge index, because we need to switch from + // clockwise winding (DirectX) to counter-clockwise winding (OpenGL) + edgeIndex = currentFace.first_edge + currentFace.num_edges - 1; + + // Set the length of this primitive on the primitive set + primitive_set->push_back(currentFace.num_edges); + + // Iterate over the edges in this face, and extract the vertex data + for (i = 0; i < currentFace.num_edges; i++) + { + // 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)); + + // 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]); + else + currentVertex = vbsp_reader->getVertex(currentEdge.vertex[0]); + + // Add the vertex to the array + vertex_array->push_back(currentVertex); + + // Set the normal + normal_array->push_back(normal); + + // Calculate the texture coordinates for this vertex + u = texU * currentVertex + texUOffset; + u *= texUScale; + v = texV * currentVertex + texVOffset; + v *= texVScale; + texCoord.set(u, v); + + // Add the texture coordinate to the array + texcoord_array->push_back(texCoord); + + // Move on to the next (previous?) vertex + edgeIndex--; + } + } +} + + +ref_ptr VBSPGeometry::createGeometry() +{ + ref_ptr rootGroup; + ref_ptr geode; + ref_ptr geometry; + Vec4f color; + ref_ptr colorArray; + + // Create the root group (we'll attach everything to this group and + // return it) + rootGroup = new Group(); + + // Create a geode for the geometries + geode = new Geode(); + rootGroup->addChild(geode.get()); + + // See if there are any regular (non-displaced) faces to render + if (primitive_set->size() > 0) + { + // Create a geometry object for the regular surfaces + geometry = new Geometry(); + + // Add the vertex attributes + geometry->setVertexArray(vertex_array.get()); + geometry->setNormalArray(normal_array.get()); + geometry->setNormalBinding(Geometry::BIND_PER_VERTEX); + geometry->setTexCoordArray(0, texcoord_array.get()); + + // Add an overall color + color.set(1.0, 1.0, 1.0, 1.0); + colorArray = new Vec4Array(1, &color); + geometry->setColorArray(colorArray.get()); + geometry->setColorBinding(Geometry::BIND_OVERALL); + + // Add our primitive set to the geometry + geometry->addPrimitiveSet(primitive_set.get()); + + // Add the geometry to the geode + geode->addDrawable(geometry.get()); + } + + // Now do the same for the displacement surfaces (if any) + if (disp_primitive_set->size() > 0) + { + // Create a geometry object for the regular surfaces + geometry = new Geometry(); + + // Add the vertex attributes + geometry->setVertexArray(disp_vertex_array.get()); + geometry->setNormalArray(disp_normal_array.get()); + geometry->setNormalBinding(Geometry::BIND_PER_VERTEX); + geometry->setTexCoordArray(0, disp_texcoord_array.get()); + geometry->setVertexAttribArray(1, disp_vertex_attr_array.get()); + geometry->setVertexAttribBinding(1, Geometry::BIND_PER_VERTEX); + + // Add an overall color + color.set(1.0, 1.0, 1.0, 1.0); + colorArray = new Vec4Array(1, &color); + geometry->setColorArray(colorArray.get()); + geometry->setColorBinding(Geometry::BIND_OVERALL); + + // Add our primitive set to the geometry + geometry->addPrimitiveSet(disp_primitive_set.get()); + + // Add the geometry to the geode + geode->addDrawable(geometry.get()); + } + + // Return the root group + return rootGroup; +} + diff --git a/src/osgPlugins/bsp/VBSPGeometry.h b/src/osgPlugins/bsp/VBSPGeometry.h new file mode 100644 index 000000000..ba5ef5495 --- /dev/null +++ b/src/osgPlugins/bsp/VBSPGeometry.h @@ -0,0 +1,54 @@ + +#ifndef VBSP_GEOMETRY_H +#define VBSP_GEOMETRY_H + + +#include +#include + +#include "VBSPReader.h" + + +namespace bsp +{ + + +class VBSPGeometry +{ + protected: + + VBSPReader * vbsp_reader; + + osg::ref_ptr vertex_array; + osg::ref_ptr normal_array; + osg::ref_ptr texcoord_array; + osg::ref_ptr primitive_set; + + osg::ref_ptr disp_vertex_array; + osg::ref_ptr disp_normal_array; + osg::ref_ptr disp_texcoord_array; + osg::ref_ptr disp_vertex_attr_array; + osg::ref_ptr disp_primitive_set; + + bool doesEdgeExist(int row, int col, int direction, + int vertsPerEdge); + osg::Vec3f getNormalFromEdges(int row, int col, + unsigned char edgeBits, + int firstVertex, int vertsPerEdge); + void createDispSurface(Face & face, DisplaceInfo & dispInfo); + + public: + + VBSPGeometry(VBSPReader * reader); + virtual ~VBSPGeometry(); + + void addFace(int faceIndex); + osg::ref_ptr createGeometry(); +}; + + +} + + +#endif + diff --git a/src/osgPlugins/bsp/VBSPReader.cpp b/src/osgPlugins/bsp/VBSPReader.cpp new file mode 100644 index 000000000..ad5dd3785 --- /dev/null +++ b/src/osgPlugins/bsp/VBSPReader.cpp @@ -0,0 +1,1097 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VBSPReader.h" +#include "VBSPGeometry.h" + + +using namespace bsp; +using namespace osg; +using namespace osgDB; + + +// strcasecmp for MSVC +#ifdef _MSC_VER + #define strcasecmp _stricmp +#endif + + +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; +} + + +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; + 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; + + // Create the string + entities = new char[length]; + memset(entities, 0, length * sizeof(char)); + + // Seek to the Entities lump + str.seekg(offset); + + // Read the entities string + str.read((char *) entities, sizeof(char) * length); + + // Count the number of entities + startPtr = entities; + endPtr = strchr(entities, '}'); + num_entities = 0; + while ((startPtr != NULL) && (endPtr != NULL)) + { + // Increment the count + num_entities++; + + // Advance the pointers + startPtr = strchr(endPtr, '{'); + if (startPtr != NULL) + 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++) + { + // 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); + + // Advance the pointers + startPtr = strchr(endPtr, '{'); + if (startPtr != NULL) + endPtr = strchr(startPtr, '}'); + } + + // Free up the original entities string + delete [] entities; +} + + +void VBSPReader::processPlanes(std::istream & str, int offset, int length) +{ + // Calculate the number of planes + num_planes = length / sizeof(Plane); + + // Create the plane list + plane_list = new Plane[num_planes]; + + // Seek to the Planes lump + str.seekg(offset); + + // Read in the planes + str.read((char *) plane_list, sizeof(Plane) * num_planes); +} + + +void VBSPReader::processVertices(std::istream & str, int offset, int length) +{ + // Calculate the number of vertices + num_vertices = length / 3 / sizeof(float); + + // Create the vertex list + vertex_list = new Vec3f[num_vertices]; + + // Seek to the Vertices lump + str.seekg(offset); + + // Read in the vertices + str.read((char *) vertex_list, sizeof(Vec3f) * num_vertices); +} + + +void VBSPReader::processEdges(std::istream & str, int offset, int length) +{ + // Calculate the number of edges + num_edges = length / sizeof(Edge); + + // Create the edge list + edge_list = new Edge[num_edges]; + + // Seek to the Edges lump + str.seekg(offset); + + // Read in the edge list + str.read((char *) edge_list, sizeof(Edge) * num_edges); +} + + +void VBSPReader::processSurfEdges(std::istream & str, int offset, int length) +{ + // Calculate the number of edges + num_surf_edges = length / sizeof(int); + + // Create the surface edges list + surface_edges = new int[num_surf_edges]; + + // Seek to the SurfEdges lump + str.seekg(offset); + + // Read in the surface edge list + str.read((char *) surface_edges, sizeof(int) * num_surf_edges); +} + + +void VBSPReader::processFaces(std::istream & str, int offset, int length) +{ + // Calculate the number of faces + num_faces = length / sizeof(Face); + + // Create the face list + face_list = new Face[num_faces]; + + // Seek to the Faces lump + str.seekg(offset); + + // Read in the faces + str.read((char *) face_list, sizeof(Face) * num_faces); +} + + +void VBSPReader::processTexInfo(std::istream & str, int offset, int length) +{ + // Calculate the number of texinfos + num_texinfo_entries = length / sizeof(TexInfo); + + // Create the texinfo list + texinfo_list = new TexInfo[num_texinfo_entries]; + + // Seek to the TexInfo lump + str.seekg(offset); + + // Read in the texinfo entries + str.read((char *) texinfo_list, sizeof(TexInfo) * num_texinfo_entries); +} + + +void VBSPReader::processTexData(std::istream & str, int offset, int length) +{ + // Calculate the number of texdatas + num_texdata_entries = length / sizeof(TexData); + + // Create the texdata list + texdata_list = new TexData[num_texdata_entries]; + + // Seek to the TexData lump + str.seekg(offset); + + // Read in the texdata entries + str.read((char *) texdata_list, sizeof(TexData) * num_texdata_entries); +} + + +void VBSPReader::processTexDataStringTable(std::istream & str, int offset, + int length) +{ + // Calculate the number of table entries + num_texdata_string_table_entries = length / sizeof(int); + + // Create the texdata string table + texdata_string_table = new int[num_texdata_string_table_entries]; + + // Seek to the TexDataStringTable lump + str.seekg(offset); + + // Read in the texdata_string_table + str.read((char *) texdata_string_table, + sizeof(int) * num_texdata_string_table_entries); +} + + +void VBSPReader::processTexDataStringData(std::istream & str, int offset, + int length) +{ + char * texDataString; + char * startPtr; + char * endPtr; + int i; + int stringLen; + + // Create the buffer to load the texdata strings + texDataString = new char[length]; + memset(texDataString, 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); + + // Count the number of strings + startPtr = texDataString; + endPtr = startPtr + strlen(startPtr); + num_texdata_strings = 0; + while ((startPtr - texDataString) < length) + { + // Increment the count + num_texdata_strings++; + + // Advance the pointers + startPtr = endPtr+1; + if ((startPtr - texDataString) < length) + endPtr = startPtr + strlen(startPtr); + } + + // 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); + + // Create the texinfo list + dispinfo_list = new DisplaceInfo[num_dispinfo_entries]; + + // Seek to the DispInfo lump + str.seekg(offset); + + // Read in the texinfo entries + str.read((char *) dispinfo_list, + sizeof(DisplaceInfo) * num_dispinfo_entries); +} + + +void VBSPReader::processDispVerts(std::istream & str, int offset, int length) +{ + // Calculate the number of displaced vertices + num_displaced_vertices = length / sizeof(DisplacedVertex); + + // Create the texinfo list + displaced_vertex_list = new DisplacedVertex[num_displaced_vertices]; + + // Seek to the DispVerts lump + str.seekg(offset); + + // Read in the displaced vertices + str.read((char *) displaced_vertex_list, + sizeof(DisplacedVertex) * num_displaced_vertices); +} + + +std::string VBSPReader::getToken(std::string str, const char * delim, + int & index) +{ + int 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; +} + + +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; + Texture * texture; + + // Find the texture's image file + texFile = std::string(textureName) + ".vtf"; + texPath = findFileIgnoreCase(texFile); + + // 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); + + // 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()) + { + texFile = "../materials/" + std::string(textureName) + ".vtf"; + texPath = findFileIgnoreCase(texFile); + } + } + + // 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 VBSPReader::createBlendShader(Texture * tex1, Texture * tex2) +{ + StateSet * stateSet; + Program * blendProgram; + Shader * blendVtxShader; + Shader * blendFrgShader; + Uniform * tex1Sampler; + Uniform * tex2Sampler; + + const char * blendVtxShaderCode = + { + "attribute float vBlendParam;\n" + "\n" + "varying float fBlendParam;\n" + "\n" + "void main(void)\n" + "{\n" + " vec3 normal, lightDir;\n" + " vec4 ambient, diffuse;\n" + " float nDotL;\n" + "\n" + " // Simple directional lighting (for now). We're assuming a\n" + " // single light source\n" + " // TODO: This is only used for terrain geometry, so it should be\n" + " // lightmapped\n" + " normal = normalize(gl_NormalMatrix * gl_Normal);\n" + " lightDir = normalize(vec3(gl_LightSource[0].position));\n" + " nDotL = max(dot(normal, lightDir), 0.0);\n" + " ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;\n" + " diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;\n" + "\n" + " // Calculate the vertex color\n" + " gl_FrontColor = 0.1 + ambient + nDotL * diffuse;\n" + "\n" + " // Pass the texture blend parameter through to the fragment\n" + " // shader\n" + " fBlendParam = vBlendParam;\n" + "\n" + " // The basic transforms\n" + " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" + " gl_TexCoord[0] = vec4(gl_MultiTexCoord0.st, 0.0, 0.0);\n" + "}\n" + }; + + const char * blendFrgShaderCode = + { + "uniform sampler2D tex1;\n" + "uniform sampler2D tex2;\n" + "\n" + "varying float fBlendParam;\n" + "\n" + "void main(void)\n" + "{\n" + " vec4 tex1Color;\n" + " vec4 tex2Color;\n" + "\n" + " tex1Color = texture2D(tex1, gl_TexCoord[0].st) * fBlendParam;\n" + " tex2Color = texture2D(tex2, gl_TexCoord[0].st) *\n" + " (1.0 - fBlendParam);\n" + "\n" + " gl_FragColor = gl_Color * (tex1Color + tex2Color);\n" + "}\n" + }; + + // Create the stateset + stateSet = new StateSet(); + + // Add the two textures + stateSet->setTextureAttributeAndModes(0, tex1, StateAttribute::ON); + stateSet->setTextureAttributeAndModes(1, tex2, StateAttribute::ON); + + // Create the vertex and fragment shaders + blendVtxShader = new Shader(Shader::VERTEX); + blendVtxShader->setShaderSource(blendVtxShaderCode); + blendFrgShader = new Shader(Shader::FRAGMENT); + blendFrgShader->setShaderSource(blendFrgShaderCode); + + // Create the two texture uniforms + tex1Sampler = new Uniform(Uniform::SAMPLER_2D, "tex1"); + tex1Sampler->set(0); + tex2Sampler = new Uniform(Uniform::SAMPLER_2D, "tex2"); + tex1Sampler->set(1); + + // Create the program + blendProgram = new Program(); + blendProgram->addShader(blendVtxShader); + blendProgram->addShader(blendFrgShader); + + // The texture blending parameter will be on vertex attribute 1 + blendProgram->addBindAttribLocation("vBlendParam", (GLuint) 1); + + // Add everything to the StateSet + stateSet->addUniform(tex1Sampler); + stateSet->addUniform(tex2Sampler); + stateSet->setAttributeAndModes(blendProgram, StateAttribute::ON); + + // Return the StateSet + return stateSet; +} + + +ref_ptr VBSPReader::readMaterialFile(std::string materialName) +{ + std::string mtlFileName; + std::string mtlPath; + osgDB::ifstream * mtlFile; + std::string line; + int 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; + + // Find the material file + mtlFileName = std::string(materialName) + ".vmt"; + mtlPath = findFileIgnoreCase(mtlFileName); + + // 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); + + // 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 (mtlPath.empty()) + { + mtlFileName = "../materials/" + std::string(materialName) + ".vmt"; + mtlPath = findFileIgnoreCase(mtlFileName); + } + } + + // See if we found the file + if (!mtlPath.empty()) + { + // Try to open the file, bail out if we fail + mtlFile = new osgDB::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; + + // 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); + } + + // 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, "WorldVertexTransition")) + { + // This shader blends between two textures based on a per-vertex + // attribute. This is used for displaced terrain surfaces in HL2 maps. + stateSet = createBlendShader(texture.get(), texture2.get()); + } + else 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); + } + } + 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); + } + 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; +} + + +void VBSPReader::createScene() +{ + ref_ptr group; + VBSPGeometry ** vbspGeomList; + ref_ptr subGroup; + Face currentFace; + TexInfo currentTexInfo; + TexData currentTexData; + char * currentTexName; + char prefix[64]; + char * mtlPtr; + char * tmpPtr; + char tempTex[256]; + int i, j; + ref_ptr stateSet; + + // 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++) + { + // 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]; + + // See if this is referring to an environment mapped material (we don't + // handle this yet) + sprintf(prefix, "maps/%s/", map_name.c_str()); + if (strncmp(currentTexName, prefix, strlen(prefix)) == 0) + { + // This texture is referring to this map's PAK file, so it could + // be an environment mapped texture (an existing material that is + // modified by a cube map of the scene). If so, we just need to + // get the base material name + mtlPtr = currentTexName; + mtlPtr += strlen(prefix); + + // Now, we're pointing at the path to the material itself, so copy + // what we've got so far + strcpy(tempTex, mtlPtr); + + // Now, we just need to trim the two or three cube map coordinates + // from the end. + // This isn't a perfect solution, but it should catch most cases. + // The right way to do this would be to read the .vmt file from the + // map's PAKFILE lump, and make use of the basetexture parameter in + // it + tmpPtr = strrchr(tempTex, '/'); + mtlPtr = strrchr(tempTex, '_'); + if ((mtlPtr != NULL) && (mtlPtr > tmpPtr)) + *mtlPtr = 0; + mtlPtr = strrchr(tempTex, '_'); + if ((mtlPtr != NULL) && (mtlPtr > tmpPtr)) + *mtlPtr = 0; + mtlPtr = strrchr(tempTex, '_'); + if ((mtlPtr != NULL) && (mtlPtr > tmpPtr)) + *mtlPtr = 0; + + // That should be it, so make it the texture name + 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()); + } + + // Clean up + delete [] vbspGeomList; + + // 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; + Header header; + int i; + + // Remember the map name + map_name = getStrippedName(file); + + mapFile = new osgDB::ifstream(file.c_str(), std::ios::binary); + if (!mapFile) + return false; + + // Read the header + mapFile->read((char *) &header, sizeof(Header)); + + // Load the bsp file lumps that we care about + for (i = 0; i < MAX_LUMPS; i++) + { + if ((header.lump_table[i].file_offset != 0) && + (header.lump_table[i].lump_length != 0)) + { + // Process the lump + switch (i) + { + case ENTITIES_LUMP: + processEntities(*mapFile, header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; + case PLANES_LUMP: + processPlanes(*mapFile, header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; + case VERTICES_LUMP: + processVertices(*mapFile, header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; + case EDGES_LUMP: + processEdges(*mapFile, header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; + case SURFEDGES_LUMP: + processSurfEdges(*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); + break; + case TEXINFO_LUMP: + processTexInfo(*mapFile, header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; + case TEXDATA_LUMP: + processTexData(*mapFile, header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; + case TEXDATA_STRING_TABLE_LUMP: + processTexDataStringTable(*mapFile, + header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; + case TEXDATA_STRING_DATA_LUMP: + processTexDataStringData(*mapFile, + header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; + case DISPINFO_LUMP: + processDispInfo(*mapFile, header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; + case DISP_VERTS_LUMP: + processDispVerts(*mapFile, header.lump_table[i].file_offset, + header.lump_table[i].lump_length); + break; + } + } + } + + // Create the OSG scene from the BSP data + createScene(); + return true; +} + + +ref_ptr VBSPReader::getRootNode() +{ + return root_node; +} + + diff --git a/src/osgPlugins/bsp/VBSPReader.h b/src/osgPlugins/bsp/VBSPReader.h new file mode 100644 index 000000000..7f70ac19b --- /dev/null +++ b/src/osgPlugins/bsp/VBSPReader.h @@ -0,0 +1,385 @@ +#ifndef __VBSP_READER_H_ +#define __VBSP_READER_H_ + + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace bsp +{ + + +// The magic number for a Valve BSP file is 'VBSP' in little-endian +// order +const int MAGIC_NUMBER = (('P'<<24)+('S'<<16)+('B'<<8)+'V'); + + +enum LumpType +{ + ENTITIES_LUMP, + PLANES_LUMP, + TEXDATA_LUMP, + VERTICES_LUMP, + VISIBILITY_LUMP, + NODES_LUMP, + TEXINFO_LUMP, + FACES_LUMP, + LIGHTING_LUMP, + OCCLUSION_LUMP, + LEAFS_LUMP, + UNUSED_LUMP_11, + EDGES_LUMP, + SURFEDGES_LUMP, + MODELS_LUMP, + WORLD_LIGHTS_LUMP, + LEAF_FACES_LUMP, + LEAF_BRUSHES_LUMP, + BRUSHES_LUMP, + BRUSH_SIDES_LUMP, + AREAS_LUMP, + AREA_PORTALS_LUMP, + PORTALS_LUMP, + CLUSTERS_LUMP, + PORTAL_VERTS_LUMP, + CLUSTER_PORTALS_LUMP, + DISPINFO_LUMP, + ORIGINAL_FACES_LUMP, + UNUSED_LUMP_28, + PHYS_COLLIDE_LUMP, + VERT_NORMALS_LUMP, + VERT_NORMAL_INDICES_LUMP, + DISP_LIGHTMAP_ALPHAS_LUMP, + DISP_VERTS_LUMP, + DISP_LIGHTMAP_SAMPLE_POS_LUMP, + GAME_LUMP, + LEAF_WATER_DATA_LUMP, + PRIMITIVES_LUMP, + PRIM_VERTS_LUMP, + PRIM_INDICES_LUMP, + PAK_FILE_LUMP, + CLIP_PORTAL_VERTS_LUMP, + CUBEMAPS_LUMP, + TEXDATA_STRING_DATA_LUMP, + TEXDATA_STRING_TABLE_LUMP, + OVERLAYS_LUMP, + LEAF_MIN_DIST_TO_WATER_LUMP, + FACE_MACRO_TEXTURE_INFO_LUMP, + DISP_TRIS_LUMP, + PHYS_COLLIDE_SURFACE_LUMP, + UNUSED_LUMP_50, + UNUSED_LUMP_51, + UNUSED_LUMP_52, + LIGHTING_HDR_LUMP, + WORLD_LIGHTS_HDR_LUMP, + LEAF_LIGHT_HDR_1_LUMP, + LEAF_LIGHT_HDR_2_LUMP, + UNUSED_LUMP_57, + UNUSED_LUMP_58, + UNUSED_LUMP_59, + UNUSED_LUMP_60, + UNUSED_LUMP_61, + UNUSED_LUMP_62, + UNUSED_LUMP_63, + MAX_LUMPS +}; + + +const char LUMP_DESCRIPTION[][64] = +{ + "ENTITIES_LUMP", + "PLANES_LUMP", + "TEXDATA_LUMP", + "VERTICES_LUMP", + "VISIBILITY_LUMP", + "NODES_LUMP", + "TEXINFO_LUMP", + "FACES_LUMP", + "LIGHTING_LUMP", + "OCCLUSION_LUMP", + "LEAFS_LUMP", + "UNUSED_LUMP_11", + "EDGES_LUMP", + "SURFEDGES_LUMP", + "MODELS_LUMP", + "WORLD_LIGHTS_LUMP", + "LEAF_FACES_LUMP", + "LEAF_BRUSHES_LUMP", + "BRUSHES_LUMP", + "BRUSH_SIDES_LUMP", + "AREAS_LUMP", + "AREA_PORTALS_LUMP", + "PORTALS_LUMP", + "CLUSTERS_LUMP", + "PORTAL_VERTS_LUMP", + "CLUSTER_PORTALS_LUMP", + "DISPINFO_LUMP", + "ORIGINAL_FACES_LUMP", + "UNUSED_LUMP_28", + "PHYS_COLLIDE_LUMP", + "VERT_NORMALS_LUMP", + "VERT_NORMAL_INDICES_LUMP", + "DISP_LIGHTMAP_ALPHAS_LUMP", + "DISP_VERTS_LUMP", + "DISP_LIGHTMAP_SAMPLE_POS_LUMP", + "GAME_LUMP", + "LEAF_WATER_DATA_LUMP", + "PRIMITIVES_LUMP", + "PRIM_VERTS_LUMP", + "PRIM_INDICES_LUMP", + "PAK_FILE_LUMP", + "CLIP_PORTAL_VERTS_LUMP", + "CUBEMAPS_LUMP", + "TEXDATA_STRING_DATA_LUMP", + "TEXDATA_STRING_TABLE_LUMP", + "OVERLAYS_LUMP", + "LEAF_MIN_DIST_TO_WATER_LUMP", + "FACE_MACRO_TEXTURE_INFO_LUMP", + "DISP_TRIS_LUMP", + "PHYS_COLLIDE_SURFACE_LUMP", + "UNUSED_LUMP_50", + "UNUSED_LUMP_51", + "UNUSED_LUMP_52", + "LIGHTING_HDR_LUMP", + "WORLD_LIGHTS_HDR_LUMP", + "LEAF_LIGHT_HDR_1_LUMP", + "LEAF_LIGHT_HDR_2_LUMP", + "UNUSED_LUMP_57", + "UNUSED_LUMP_58", + "UNUSED_LUMP_59", + "UNUSED_LUMP_60", + "UNUSED_LUMP_61", + "UNUSED_LUMP_62", + "UNUSED_LUMP_63" +}; + + +struct LumpEntry +{ + int file_offset; + int lump_length; + int lump_version; + char ident_code[4]; +}; + + +struct Header +{ + int magic_number; + int bsp_version; + LumpEntry lump_table[MAX_LUMPS]; + int map_revision; +}; + + +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 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 long allowed_verts[10]; +}; + + +struct DisplacedVertex +{ + osg::Vec3f displace_vec; + float displace_dist; + float alpha_blend; +}; + + +class VBSPReader +{ +protected: + + std::string map_name; + + 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; + + 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 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); + void processSurfEdges(std::istream & str, int offset, int length); + void processFaces(std::istream & str, int offset, int length); + void processTexInfo(std::istream & str, int offset, int length); + 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 processDispInfo(std::istream & str, int offset, int length); + void processDispVerts(std::istream & str, int offset, int length); + + std::string getToken(std::string str, const char * delim, + int & index); + + std::string findFileIgnoreCase(std::string filePath); + + osg::ref_ptr createBlendShader(osg::Texture * tex1, + osg::Texture * tex2); + + osg::ref_ptr readTextureFile(std::string file); + osg::ref_ptr readMaterialFile(std::string file); + + void createScene(); + +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); + + osg::ref_ptr getRootNode(); +}; + + +} + +#endif diff --git a/src/osgPlugins/bsp/VBSP_README.txt b/src/osgPlugins/bsp/VBSP_README.txt new file mode 100644 index 000000000..08a2a15ec --- /dev/null +++ b/src/osgPlugins/bsp/VBSP_README.txt @@ -0,0 +1,99 @@ + +Source Engine BSP map reader for OSG + +by Jason Daly + +Overview +-------- +This plugin allows .bsp 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 on several HL2 deathmatch maps, as well as some 3rd +party maps. + + +Using the Plugin +---------------- +Unless you've got a 3rd party map that doesn't make use of any data from the +original Source engine games, you'll need to extract the relevant maps 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 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). + +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. + + +What Works +---------- +All brush geometry and faces. + +Displacement surfaces. + +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. + + +What Doesn't Work (yet) +----------------------- +Light maps. This requires reading the light map information lumps from the +.bsp file, and creating a shader to render them on the various surfaces. +Shouldn't be too hard. Currently, the plugin just treats lightmapped surfaces +the same as vertex lit surfaces. + +Environment maps and shiny textures. Both of these require reading cube maps. +Cube maps and environment mapping materials are typically stored inside the +.bsp file as an embedded .pak file (essentially a .zip file with no +compression. Reading them would require parsing the .pak file and reading the +embedded .vmt to find the pointers to the base texture and cube map. Then the +cube map must be read from the embedded .pak file as well. Finally, a shader +would be created to render them. Currently, the plugin cheats by guessing +the base texture from the embedded material name, and loading it. This +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. + +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. + +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. + +HDR materials, bump maps, detail props, and other eye-candy. Implement it if +you want! :-) + + +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. + +This plugin wouldn't have been possible without Rof's "The Source Engine BSP +Format" at http://www.geocities.com/cofrdrbob/bspformat.html which itself +is based on Max McGuire's "Quake 2 BSP File Format" at +http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml + +Portions of the code borrow heavily from the Source SDK (especially the +code for the displacement surfaces), so thanks to Valve for making much of +that code public. + +Of course, this code would be superfluous without the Open Scene Graph and +all of its contributors. +