The vtf plugin wasn't working in Windows due to OS differences in the byte-packing of the header structure (on Windows, the big block read was causing a buffer overrun). I fixed this by reading the structure from the file field by field. It's now happy on both Linux and Windows."
1201 lines
36 KiB
C++
1201 lines
36 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)
|
|
{
|
|
std::string token;
|
|
size_t end = std::string::npos;
|
|
|
|
// Look for the first non-occurrence of the delimiters
|
|
size_t 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)
|
|
{
|
|
std::string texFile;
|
|
std::string texPath;
|
|
osg::ref_ptr<Image> texImage;
|
|
osg::ref_ptr<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 = readRefImageFile(texPath);
|
|
|
|
// If we got the image, create the texture attribute
|
|
if (texImage != NULL)
|
|
{
|
|
// Create the texture
|
|
if (texImage->t() == 1)
|
|
{
|
|
texture = new Texture1D(texImage.get());
|
|
}
|
|
else if (texImage->r() == 1)
|
|
{
|
|
texture = new Texture2D(texImage.get());
|
|
}
|
|
else
|
|
{
|
|
texture = new Texture3D(texImage.get());
|
|
}
|
|
|
|
// 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) *\n"
|
|
" (1.0 - fBlendParam);\n"
|
|
" tex2Color = texture2D(tex2, gl_TexCoord[0].st) * 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;
|
|
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;
|
|
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;
|
|
}
|
|
|
|
|