28ca8277f8
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"
1208 lines
37 KiB
C++
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;
|
|
}
|
|
|
|
|