OpenSceneGraph/src/osgPlugins/bsp/VBSPReader.cpp
Robert Osfield 28ca8277f8 From Jason Daly, "'ve been busy working on the Source engine plugins. There are several contributions in this submission:
osgDB/FileUtils.cpp:
Needed this extra code to allow a true case-insensitive search.  This is because the HL2 map and model files are often sloppy with case.  For example, the file might look for materials/models/alyx/alyx_sheet.vtf, but the file is actually in materials/Models/Alyx/alyx_sheet.vtf.  In case-insensitive mode, the new code recursively disassembles the path and checks each path element without regard to case.  In case-sensitive mode, the code behaves exactly as it used to.  The new code is also mostly skipped on Windows because of the case-insensitive file system.  Previously, I did all of this with custom search code in the .bsp plugin, but this allows the user to tailor the search using OSGFILEPATH.  There are some instructions in the plugins' README files about this.

osgPlugins/mdl:
This is a new plug-in for Half-Life 2 models (as opposed to maps).  This allows you to load Source models individually, as well as allowing the .bsp plugin to load models (props) that are embedded into maps.  Mdl files can contain simple object (crates, barrels, bottles), as well as fully articulated characters with skeletal animations.  Currently, it can load the simple objects.  It can also load the characters, but it can't load the skeletons or animations.

osgPlugins/bsp:
This contains all of the changes needed to load props along with the basic map geometry.  There are also
several bugs fixed.

osgPlugins/vtf:
This is the loader for Valve's texture format.  Previously, we had agreed to put this in with the bsp plugin, but I didn't think of the .mdl plugin at that time.  It's conceivable that a user might want to load models individually (not as part of a map), so the vtf reader does have to be separate.  I also fixed a rather significant bug.

I tested all of this code on RHEL 5.2 (32-bit), and Fedora 9 (64-bit).  I'll be testing on Windows soon.

I also attached a simple .mdl file, along with it's associated files and textures.  Just extract the tarball into it's own directory, set your OSGFILEPATH to point at that directory, and load the model like this:

 osgviewer models/props_junk/gascan001a.mdl"
2008-12-20 13:35:49 +00:00

1208 lines
37 KiB
C++

#include <osg/BlendFunc>
#include <osg/BoundingSphere>
#include <osg/Geometry>
#include <osg/Group>
#include <osg/Object>
#include <osg/Material>
#include <osg/MatrixTransform>
#include <osg/Node>
#include <osg/Notify>
#include <osg/Quat>
#include <osg/StateSet>
#include <osg/Texture1D>
#include <osg/Texture2D>
#include <osg/Texture3D>
#include <osgDB/Registry>
#include <osgDB/FileUtils>
#include <osgDB/ReadFile>
#include <osg/io_utils>
#include <iostream>
#include <string.h>
#include "VBSPReader.h"
#include "VBSPEntity.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;
// Create the map data object
bsp_data = new VBSPData();
// No string table yet
texdata_string = NULL;
texdata_string_table = NULL;
num_texdata_string_table_entries = 0;
}
VBSPReader::~VBSPReader()
{
// Clean up the texdata strings and such
delete [] texdata_string;
delete [] texdata_string_table;
}
void VBSPReader::processEntities(std::istream & str, int offset,
int length)
{
char * entities;
char * startPtr;
char * endPtr;
int numEntities;
int i;
std::string entityStr;
size_t 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, '}');
numEntities = 0;
while ((startPtr != NULL) && (endPtr != NULL))
{
// Increment the count
numEntities++;
// Advance the pointers
startPtr = strchr(endPtr, '{');
if (startPtr != NULL)
endPtr = strchr(startPtr, '}');
}
// Parse the entities
startPtr = entities;
endPtr = strchr(entities, '}');
for (i = 0; i < numEntities; i++)
{
// Get the length of this entity
entityLen = endPtr - startPtr + 1;
// Create the entity list entry and copy the entity information
entityStr = std::string(startPtr, entityLen);
bsp_data->addEntity(entityStr);
// Advance the pointers
startPtr = strchr(endPtr, '{');
if (startPtr != NULL)
endPtr = strchr(startPtr, '}');
}
// Free up the original entities string
delete [] entities;
}
void VBSPReader::processModels(std::istream & str, int offset, int length)
{
int numModels;
int i;
Model * models;
// Calculate the number of models
numModels = length / sizeof(Model);
// Seek to the Models lump
str.seekg(offset);
// Read the models
models = new Model[numModels];
str.read((char *) models, sizeof(Model) * numModels);
// Add the models to the model list
for (i = 0; i < numModels; i++)
bsp_data->addModel(models[i]);
// Clean up
delete [] models;
}
void VBSPReader::processPlanes(std::istream & str, int offset, int length)
{
int numPlanes;
int i;
Plane * planes;
// Calculate the number of planes
numPlanes = length / sizeof(Plane);
// Seek to the Planes lump
str.seekg(offset);
// Read the planes
planes = new Plane[numPlanes];
str.read((char *) planes, sizeof(Plane) * numPlanes);
// Add the planes to the plane list
for (i = 0; i < numPlanes; i++)
bsp_data->addPlane(planes[i]);
// Clean up
delete [] planes;
}
void VBSPReader::processVertices(std::istream & str, int offset, int length)
{
int numVertices;
int i;
Vec3f * vertices;
// Calculate the number of vertices
numVertices = length / 3 / sizeof(float);
// Seek to the Vertices lump
str.seekg(offset);
// Read the vertex
vertices = new Vec3f[numVertices];
str.read((char *) vertices, sizeof(Vec3f) * numVertices);
// Add it the vertices to the list
for (i = 0; i < numVertices; i++)
bsp_data->addVertex(vertices[i]);
// Clean up
delete [] vertices;
}
void VBSPReader::processEdges(std::istream & str, int offset, int length)
{
int numEdges;
int i;
Edge * edges;
// Calculate the number of edges
numEdges = length / sizeof(Edge);
// Seek to the Edges lump
str.seekg(offset);
// Read the edges
edges = new Edge[numEdges];
str.read((char *) edges, sizeof(Edge) * numEdges);
// Add the edges to the edge list
for (i = 0; i < numEdges; i++)
bsp_data->addEdge(edges[i]);
// Clean up
delete [] edges;
}
void VBSPReader::processSurfEdges(std::istream & str, int offset, int length)
{
int numSurfEdges;
int i;
int * surfEdges;
// Calculate the number of edges
numSurfEdges = length / sizeof(int);
// Seek to the SurfEdges lump
str.seekg(offset);
// Read the surface edges
surfEdges = new int[numSurfEdges];
str.read((char *) surfEdges, sizeof(int) * numSurfEdges);
// Add the surface edges to the surface edge list
for (i = 0; i < numSurfEdges; i++)
bsp_data->addSurfaceEdge(surfEdges[i]);
// Clean up
delete [] surfEdges;
}
void VBSPReader::processFaces(std::istream & str, int offset, int length)
{
int numFaces;
int i;
Face * faces;
// Calculate the number of faces
numFaces = length / sizeof(Face);
// Seek to the Faces lump
str.seekg(offset);
// Read the faces
faces = new Face[numFaces];
str.read((char *) faces, sizeof(Face) * numFaces);
// Add the faces to the face list
for (i = 0; i < numFaces; i++)
bsp_data->addFace(faces[i]);
// Clean up
delete [] faces;
}
void VBSPReader::processTexInfo(std::istream & str, int offset, int length)
{
int numTexInfos;
int i;
TexInfo * texinfos;
// Calculate the number of texinfos
numTexInfos = length / sizeof(TexInfo);
// Seek to the TexInfo lump
str.seekg(offset);
// Read in the texinfo entries
texinfos = new TexInfo[numTexInfos];
str.read((char *) texinfos, sizeof(TexInfo) * numTexInfos);
// Add the texinfo entries to the texinfo list
for (i = 0; i < numTexInfos; i++)
bsp_data->addTexInfo(texinfos[i]);
// Clean up
delete [] texinfos;
}
void VBSPReader::processTexData(std::istream & str, int offset, int length)
{
int numTexDatas;
int i;
TexData * texdatas;
// Calculate the number of texdatas
numTexDatas = length / sizeof(TexData);
// Seek to the TexData lump
str.seekg(offset);
// Read in the texdata entries
texdatas = new TexData[numTexDatas];
str.read((char *) texdatas, sizeof(TexData) * numTexDatas);
// Add the texdata entries to the texdata list
for (i = 0; i < numTexDatas; i++)
bsp_data->addTexData(texdatas[i]);
// Clean up
delete [] texdatas;
}
void VBSPReader::processTexDataStringTable(std::istream & str, int offset,
int length)
{
int i;
int index;
std::string texStr;
// Calculate the number of table entries
num_texdata_string_table_entries = length / sizeof(int);
// 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);
// If we have a texdata string loaded, parse the texdata strings now
if (texdata_string != NULL)
{
for (i = 0; i < num_texdata_string_table_entries; i++)
{
// Add the strings from the string data, using the string table
// to index it
index = texdata_string_table[i];
texStr = std::string(&texdata_string[index]);
bsp_data->addTexDataString(texStr);
}
}
}
void VBSPReader::processTexDataStringData(std::istream & str, int offset,
int length)
{
int i;
int index;
std::string texStr;
// Create the buffer to hold the texdata string
texdata_string = new char[length];
memset(texdata_string, 0, length * sizeof(char));
// Seek to the TexDataStringData lump
str.seekg(offset);
// Read the entire texdata string (this string is actually a
// NULL-delimited list of strings)
str.read((char *) texdata_string, sizeof(char) * length);
// If we have a string table loaded, parse the texdata strings now
// (if not, num_texdata_string_table_entries will be zero and we'll
// skip this loop)
for (i = 0; i < num_texdata_string_table_entries; i++)
{
// Add the strings from the string data, using the string table
// to index it
index = texdata_string_table[i];
texStr = std::string(&texdata_string[index]);
bsp_data->addTexDataString(texStr);
}
}
void VBSPReader::processDispInfo(std::istream & str, int offset, int length)
{
int numDispInfos;
int i;
DisplaceInfo * dispinfos;
// Calculate the number of dispinfos
numDispInfos = length / sizeof(DisplaceInfo);
// Seek to the DisplaceInfo lump
str.seekg(offset);
// Read in the dispinfo entries
dispinfos = new DisplaceInfo[numDispInfos];
str.read((char *) dispinfos, sizeof(DisplaceInfo) * numDispInfos);
// Add the dispinfo entries to the displace info list
for (i = 0; i < numDispInfos; i++)
bsp_data->addDispInfo(dispinfos[i]);
// Clean up
delete [] dispinfos;
}
void VBSPReader::processDispVerts(std::istream & str, int offset, int length)
{
int numDispVerts;
int i;
DisplacedVertex * dispverts;
// Calculate the number of displaced vertices
numDispVerts = length / sizeof(DisplacedVertex);
// Seek to the DispVert lump
str.seekg(offset);
// Read in the displaced vertices
dispverts = new DisplacedVertex[numDispVerts];
str.read((char *) dispverts, sizeof(DisplacedVertex) * numDispVerts);
// Add the displaced vertices to the displaced vertex list
for (i = 0; i < numDispVerts; i++)
bsp_data->addDispVertex(dispverts[i]);
// Clean up
delete [] dispverts;
}
void VBSPReader::processGameData(std::istream & str, int offset, int length)
{
GameHeader gameHeader;
GameLump * gameLumps;
int i;
// Read the header
str.seekg(offset);
str.read((char *) &gameHeader, sizeof(GameHeader));
// Create and read in the game lump list
gameLumps = new GameLump[gameHeader.num_lumps];
str.read((char *) gameLumps, sizeof(GameLump) * gameHeader.num_lumps);
// Iterate over the game lumps
for (i = 0; i < gameHeader.num_lumps; i++)
{
// See if this is a lump we're interested in
if (gameLumps[i].lump_id == STATIC_PROP_ID)
{
processStaticProps(str, gameLumps[i].lump_offset,
gameLumps[i].lump_length,
gameLumps[i].lump_version);
}
}
// Clean up
delete [] gameLumps;
}
void VBSPReader::processStaticProps(std::istream & str, int offset, int length,
int lumpVersion)
{
StaticPropModelNames sprpModelNames;
char modelName[130];
std::string modelStr;
int i;
StaticPropLeaves sprpLeaves;
StaticProps sprpHeader;
StaticPropV4 sprp4;
StaticProp sprp5;
// First, read the static prop models dictionary
str.seekg(offset);
str.read((char *) &sprpModelNames, sizeof(StaticPropModelNames));
for (i = 0; i < sprpModelNames.num_model_names; i++)
{
str.read(modelName, 128);
modelName[128] = 0;
modelStr = std::string(modelName);
bsp_data->addStaticPropModel(modelStr);
}
// Next, skip over the static prop leaf array
str.read((char *) &sprpLeaves, sizeof(StaticPropLeaves));
str.seekg(sprpLeaves.num_leaf_entries * sizeof(unsigned short),
std::istream::cur);
// Finally, read in the static prop entries
str.read((char *) &sprpHeader, sizeof(StaticProps));
for (i = 0; i < sprpHeader.num_static_props; i++)
{
// The version number determines how much we read for each prop
if (lumpVersion == 4)
{
// Read the static prop and add it to the bsp data
str.read((char *) &sprp4, sizeof(StaticPropV4));
bsp_data->addStaticProp(sprp4);
}
else if (lumpVersion == 5)
{
// Read the static prop and add it to the bsp data
str.read((char *) &sprp5, sizeof(StaticProp));
bsp_data->addStaticProp(sprp5);
}
}
}
std::string VBSPReader::getToken(std::string str, const char * delim,
size_t & index)
{
size_t start, end;
std::string token;
// Look for the first non-occurrence of the delimiters
start = str.find_first_not_of(delim, index);
if (start != std::string::npos)
{
// From there, look for the first occurrence of a delimiter
end = str.find_first_of(delim, start+1);
if (end != std::string::npos)
{
// Found a delimiter, so grab the string in between
token = str.substr(start, end-start);
}
else
{
// Ran off the end of the string, so just grab everything from
// the first good character
token = str.substr(start);
}
}
else
{
// No token to be found
token = "";
}
// Update the index (in case we want to keep looking for tokens in this
// string)
if (end != std::string::npos)
index = end+1;
else
index = std::string::npos;
// Return the token
return token;
}
ref_ptr<Texture> 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 = findDataFile(texFile, CASE_INSENSITIVE);
// If we don't find it right away, check in a "materials" subdirectory
if (texPath.empty())
{
texFile = "materials/" + std::string(textureName) + ".vtf";
texPath = findDataFile(texFile, CASE_INSENSITIVE);
// Check up one directory if we don't find it here (the map file is
// usually located in the "maps" directory, adjacent to the materials
// directory)
if (texPath.empty())
{
texFile = "../materials/" + std::string(textureName) + ".vtf";
texPath = findDataFile(texFile, CASE_INSENSITIVE);
}
}
// If we found the file, read it, otherwise bail
if (!texPath.empty())
{
texImage = readImageFile(texPath);
// If we got the image, create the texture attribute
if (texImage != NULL)
{
// Create the texture
if (texImage->t() == 1)
{
texture = new Texture1D();
((Texture1D *)texture)->setImage(texImage);
}
else if (texImage->r() == 1)
{
texture = new Texture2D();
((Texture2D *)texture)->setImage(texImage);
}
else
{
texture = new Texture3D();
((Texture3D *)texture)->setImage(texImage);
}
// Set texture attributes
texture->setWrap(Texture::WRAP_S, Texture::REPEAT);
texture->setWrap(Texture::WRAP_T, Texture::REPEAT);
texture->setWrap(Texture::WRAP_R, Texture::REPEAT);
texture->setFilter(Texture::MAG_FILTER, Texture::LINEAR);
texture->setFilter(Texture::MIN_FILTER,
Texture::LINEAR_MIPMAP_LINEAR);
}
else
{
// We were unable to find the texture file
notify(WARN) << "Couldn't find texture " << textureName;
notify(WARN) << std::endl;
// No texture
texture = NULL;
}
}
else
{
// We were unable to find the texture file
notify(WARN) << "Couldn't find texture " << textureName;
notify(WARN) << std::endl;
// No texture
texture = NULL;
}
return texture;
}
ref_ptr<StateSet> VBSPReader::createBlendShader(Texture * tex1, Texture * tex2)
{
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 * 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
Shader * blendVtxShader = new Shader(Shader::VERTEX);
blendVtxShader->setShaderSource(blendVtxShaderCode);
Shader * blendFrgShader = new Shader(Shader::FRAGMENT);
blendFrgShader->setShaderSource(blendFrgShaderCode);
// Create the two texture uniforms
Uniform * tex1Sampler = new Uniform(Uniform::SAMPLER_2D, "tex1");
tex1Sampler->set(0);
Uniform * tex2Sampler = new Uniform(Uniform::SAMPLER_2D, "tex2");
tex2Sampler->set(1);
// Create the program
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<StateSet> VBSPReader::readMaterialFile(std::string materialName)
{
std::string mtlFileName;
std::string mtlPath;
osgDB::ifstream * mtlFile;
std::string line;
std::string::size_type start = std::string::npos;
std::string token;
bool found = false;
ref_ptr<StateSet> stateSet;
std::string shaderName;
osg::Image * texImage = 0;
std::string texName;
std::string tex2Name;
ref_ptr<Texture> texture;
ref_ptr<Texture> texture2;
ref_ptr<BlendFunc> blend;
bool translucent;
// Find the material file
mtlFileName = std::string(materialName) + ".vmt";
mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE);
// If we don't find it right away, check in a "materials" subdirectory
if (mtlPath.empty())
{
mtlFileName = "materials/" + std::string(materialName) + ".vmt";
mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE);
// Check up one directory if we don't find it here (the map file is
// usually located in the "maps" directory, adjacent to the materials
// directory)
if (mtlPath.empty())
{
mtlFileName = "../materials/" + std::string(materialName) + ".vmt";
mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE);
}
}
// 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;
// Assume no transparency unless the properties say otherwise
translucent = false;
// Read the material properties next
while (!mtlFile->eof())
{
// Get the next line
std::getline(*mtlFile, line);
// Look for tokens starting at the beginning
start = 0;
token = getToken(line, " \t\n\r\"", start);
while ((!token.empty()) && (token.compare(0, 2, "//") != 0))
{
if (equalCaseInsensitive(token, "$basetexture"))
{
// Get the base texture name
token = getToken(line, " \t\n\r\"", start);
// Read the texture
if (!token.empty())
texture = readTextureFile(token);
}
else if (equalCaseInsensitive(token, "$basetexture2"))
{
// Get the second base texture name
token = getToken(line, " \t\n\r\"", start);
// Read the texture
if (!token.empty())
texture2 = readTextureFile(token);
}
else if ((equalCaseInsensitive(token, "$translucent")) ||
(equalCaseInsensitive(token, "$alphatest")))
{
// Get the translucency setting
token = getToken(line, " \t\n\r\"", start);
// Interpret the setting
if ((token == "1") || (token == "true"))
translucent = true;
}
// Try the next token
token = getToken(line, " \t\n\r\"", start);
}
}
// Start with no StateSet (in case the stuff below fails)
stateSet = NULL;
// Check the shader's name
if (equalCaseInsensitive(shaderName, "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);
// See if the material is translucent
if (translucent)
{
// Create and apply a blend function attribute to the
// state set
blend = new BlendFunc(BlendFunc::SRC_ALPHA,
BlendFunc::ONE_MINUS_SRC_ALPHA);
stateSet->setAttributeAndModes(blend.get(), StateAttribute::ON);
// Set the state set's rendering hint to transparent
stateSet->setRenderingHint(StateSet::TRANSPARENT_BIN);
}
}
else
{
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);
// See if the material is translucent
if (translucent)
{
// Create and apply a blend function attribute to the
// state set
blend = new BlendFunc(BlendFunc::SRC_ALPHA,
BlendFunc::ONE_MINUS_SRC_ALPHA);
stateSet->setAttributeAndModes(blend.get(), StateAttribute::ON);
// Set the state set's rendering hint to transparent
stateSet->setRenderingHint(StateSet::TRANSPARENT_BIN);
}
}
else
{
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> group;
ref_ptr<Group> subGroup;
Face currentFace;
TexInfo currentTexInfo;
TexData currentTexData;
const char * texName;
char currentTexName[256];
char prefix[64];
char * mtlPtr;
char * tmpPtr;
char tempTex[256];
std::string entityText;
VBSPEntity * currentEntity;
int i;
ref_ptr<StateSet> stateSet;
StaticProp staticProp;
Matrixf transMat, rotMat;
Quat yaw, pitch, roll;
ref_ptr<MatrixTransform> propXform;
std::string propModel;
std::string propFile;
ref_ptr<Node> propNode;
// Load the materials and create a StateSet for each one
for (i = 0; i < bsp_data->getNumTexDatas(); i++)
{
// Get the texdata entry and texture name
currentTexData = bsp_data->getTexData(i);
texName = bsp_data->
getTexDataString(currentTexData.name_string_table_id).c_str();
strcpy(currentTexName, texName);
// See if this is referring to an environment mapped material (we don't
// handle this yet)
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
strcpy(currentTexName, tempTex);
}
// Read the material for this geometry
stateSet = readMaterialFile(currentTexName);
// Whether we successfully created a StateSet or not, add it to the
// bsp data list now
bsp_data->addStateSet(stateSet.get());
}
// Create the root group for the scene
group = new Group();
// Iterate through the list of entities, and try to convert all the
// visible entities to geometry
for (i = 0; i < bsp_data->getNumEntities(); i++)
{
// Get the entity
entityText = bsp_data->getEntity(i);
currentEntity = new VBSPEntity(entityText, bsp_data);
// See if the entity is visible
if (currentEntity->isVisible())
{
// Create geometry for the entity
subGroup = currentEntity->createGeometry();
// If the entity's geometry is valid, add it to the main group
if (subGroup.valid())
group->addChild(subGroup.get());
}
// Done with this entity
delete currentEntity;
}
// Iterate through the list of static props, and add them to the scene
// as well
for (i = 0; i < bsp_data->getNumStaticProps(); i++)
{
// Get the static prop
staticProp = bsp_data->getStaticProp(i);
// Create a MatrixTransform for this prop (scale the position from
// inches to meters)
transMat.makeTranslate(staticProp.prop_origin * 0.0254);
pitch.makeRotate(osg::DegreesToRadians(staticProp.prop_angles.x()),
Vec3f(0.0, 1.0, 0.0));
yaw.makeRotate(osg::DegreesToRadians(staticProp.prop_angles.y()),
Vec3f(0.0, 0.0, 1.0));
roll.makeRotate(osg::DegreesToRadians(staticProp.prop_angles.z()),
Vec3f(1.0, 0.0, 0.0));
rotMat.makeRotate(roll * pitch * yaw);
propXform = new MatrixTransform();
propXform->setMatrix(rotMat * transMat);
// Load the prop's model
propModel = bsp_data->getStaticPropModel(staticProp.prop_type);
propNode = osgDB::readNodeFile(propModel);
// If we loaded the prop correctly, add it to the scene
if (propNode.valid())
{
propXform->addChild(propNode.get());
group->addChild(propXform.get());
}
else
{
notify(WARN) << "Couldn't find static prop \"" << propModel;
notify(WARN) << "\"." << std::endl;
// Couldn't find the prop, so get rid of the transform node
propXform = NULL;
}
}
// Set the root node to the result
root_node = group.get();
}
bool VBSPReader::readFile(const std::string & file)
{
osgDB::ifstream * mapFile = 0;
Header header;
int i = 0;
// 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 MODELS_LUMP:
processModels(*mapFile, header.lump_table[i].file_offset,
header.lump_table[i].lump_length);
break;
case FACES_LUMP:
processFaces(*mapFile, header.lump_table[i].file_offset,
header.lump_table[i].lump_length);
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;
case GAME_LUMP:
processGameData(*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<Node> VBSPReader::getRootNode()
{
return root_node;
}