Files
OpenSceneGraph/src/osgPlugins/mdl/MDLReader.cpp
Robert Osfield 418dc34776 Fixed warnings
2009-01-07 11:24:47 +00:00

677 lines
20 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/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 "MDLReader.h"
#include "VVDReader.h"
#include "VTXReader.h"
using namespace mdl;
using namespace osg;
using namespace osgDB;
MDLReader::MDLReader()
{
// Start with no root node
root_node = NULL;
}
MDLReader::~MDLReader()
{
}
std::string MDLReader::getToken(std::string str, const char * delim,
size_t & index)
{
size_t start;
size_t end = std::string::npos;
std::string token;
// Look for the first non-occurrence of the delimiters
start = str.find_first_not_of(" \t\n\r\"", index);
if (start != std::string::npos)
{
// From there, look for the first occurrence of a delimiter
end = str.find_first_of(" \t\n\r\"", start+1);
if (end != std::string::npos)
{
// Found a delimiter, so grab the string in between
token = str.substr(start, end-start);
}
else
{
// Ran off the end of the string, so just grab everything from
// the first good character
token = str.substr(start);
}
}
else
{
// No token to be found
token = "";
}
// Update the index (in case we want to keep looking for tokens in this
// string)
if (end != std::string::npos)
index = end+1;
else
index = std::string::npos;
// Return the token
return token;
}
ref_ptr<Texture> MDLReader::readTextureFile(std::string textureName)
{
std::string texFile;
std::string texPath;
Image * texImage;
Texture * texture;
// Find the texture's image file
texFile = std::string(textureName) + ".vtf";
texPath = findDataFile(texFile, CASE_INSENSITIVE);
// If we don't find it right away, check in a "materials" subdirectory
if (texPath.empty())
{
// Check for a leading slash and concatenate appropriately
if ((textureName[0] == '\\') || (textureName[0] == '/'))
texFile = "materials" + std::string(textureName) + ".vtf";
else
texFile = "materials/" + std::string(textureName) + ".vtf";
// Look for the texture at this location
texPath = findDataFile(texFile, CASE_INSENSITIVE);
// Check up one directory if we don't find it here (the map file is
// usually located in the "maps" directory, adjacent to the materials
// directory)
if (texPath.empty())
{
// Check for a leading slash and concatenate appropriately
if ((textureName[0] == '\\') || (textureName[0] == '/'))
texFile = "../materials" + std::string(textureName) + ".vtf";
else
texFile = "../materials/" + std::string(textureName) + ".vtf";
// Look for the texture at this location
texPath = findDataFile(texFile, CASE_INSENSITIVE);
}
}
// If we found the file, read it, otherwise bail
if (!texPath.empty())
{
texImage = readImageFile(texPath);
// If we got the image, create the texture attribute
if (texImage != NULL)
{
// Create the texture
if (texImage->t() == 1)
{
texture = new Texture1D();
((Texture1D *)texture)->setImage(texImage);
}
else if (texImage->r() == 1)
{
texture = new Texture2D();
((Texture2D *)texture)->setImage(texImage);
}
else
{
texture = new Texture3D();
((Texture3D *)texture)->setImage(texImage);
}
// Set texture attributes
texture->setWrap(Texture::WRAP_S, Texture::REPEAT);
texture->setWrap(Texture::WRAP_T, Texture::REPEAT);
texture->setWrap(Texture::WRAP_R, Texture::REPEAT);
texture->setFilter(Texture::MAG_FILTER, Texture::LINEAR);
texture->setFilter(Texture::MIN_FILTER,
Texture::LINEAR_MIPMAP_LINEAR);
}
else
{
// We were unable to find the texture file
notify(WARN) << "Couldn't find texture " << textureName;
notify(WARN) << std::endl;
// No texture
texture = NULL;
}
}
else
{
// We were unable to find the texture file
notify(WARN) << "Couldn't find texture " << textureName;
notify(WARN) << std::endl;
// No texture
texture = NULL;
}
return texture;
}
ref_ptr<StateSet> MDLReader::readMaterialFile(std::string materialName)
{
std::string mtlFileName;
std::string mtlPath;
StringList::iterator searchItr;
std::ifstream * mtlFile;
std::string line;
size_t start;
std::string token;
bool found;
ref_ptr<StateSet> 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, search the texture file search paths
if (mtlPath.empty())
{
searchItr = texture_paths.begin();
while ((mtlPath.empty()) && (searchItr != texture_paths.end()))
{
// The search paths assume that materials are always located in
// a "materials" subdirectory. Also, check to see if there is
// a leading slash and concatenate appropriately
if (((*searchItr)[0] == '\\') || ((*searchItr)[0] == '/'))
mtlFileName = "materials" + *searchItr +
std::string(materialName) + ".vmt";
else
mtlFileName = "materials/" + *searchItr +
std::string(materialName) + ".vmt";
// Try to find the file in this path
mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE);
// Next path
searchItr++;
}
// If we still haven't found it, check up one directory level (the
// model file is usually located in the "models" directory, adjacent
// to the "materials" directory)
if (mtlPath.empty())
{
searchItr = texture_paths.begin();
while ((mtlPath.empty()) && (searchItr != texture_paths.end()))
{
// The search paths assume that materials are always located in
// a "materials" subdirectory, but this time try going up one
// level first
if (((*searchItr)[0] == '\\') || ((*searchItr)[0] == '/'))
mtlFileName = "../materials" + *searchItr +
std::string(materialName) + ".vmt";
else
mtlFileName = "../materials/" + *searchItr +
std::string(materialName) + ".vmt";
// Try to find the file in this path
mtlPath = findDataFile(mtlFileName, CASE_INSENSITIVE);
// Next path
searchItr++;
}
}
}
// See if we found the file
if (!mtlPath.empty())
{
// Try to open the file, bail out if we fail
mtlFile = new std::ifstream(mtlPath.c_str(), std::ifstream::in);
if (!mtlFile)
return NULL;
}
else
{
// Didn't find the material file, so return NULL
notify(WARN) << "Can't find material " << materialName << std::endl;
return NULL;
}
// First, look for the shader name
found = false;
while ((!found) && (!mtlFile->eof()))
{
// Read a line from the file
std::getline(*mtlFile, line);
// Try to find the shader name
start = 0;
token = getToken(line, " \t\n\r\"", start);
// If we got something, it must be the shader
if ((!token.empty()) && (token.compare(0, 2, "//") != 0))
{
shaderName = token;
found = true;
}
}
// If we didn't find a shader, this isn't a valid material file
if (!found)
{
mtlFile->close();
notify(WARN) << "Material " << materialName << " isn't valid.";
notify(WARN) << std::endl;
return NULL;
}
// No textures loaded yet
texture = NULL;
texture2 = NULL;
// Assume not translucent unless the properties say otherwise
translucent = false;
// Read the material properties next
while (!mtlFile->eof())
{
// Get the next line
std::getline(*mtlFile, line);
// Look for tokens starting at the beginning
start = 0;
token = getToken(line, " \t\n\r\"", start);
while ((!token.empty()) && (token.compare(0, 2, "//") != 0))
{
if (equalCaseInsensitive(token, "$basetexture"))
{
// Get the base texture name
token = getToken(line, " \t\n\r\"", start);
// Read the texture
if (!token.empty())
texture = readTextureFile(token);
}
else if (equalCaseInsensitive(token, "$basetexture2"))
{
// Get the second base texture name
token = getToken(line, " \t\n\r\"", start);
// Read the texture
if (!token.empty())
texture2 = readTextureFile(token);
}
else if ((equalCaseInsensitive(token, "$translucent")) ||
(equalCaseInsensitive(token, "$alphatest")))
{
// Get the translucency setting
token = getToken(line, " \t\n\r\"", start);
// Interpret the setting
if (!token.empty())
{
if ((token == "1") || (token == "true"))
translucent = true;
}
}
// Try the next token
token = getToken(line, " \t\n\r\"", start);
}
}
// Start with no StateSet (in case the stuff below fails)
stateSet = NULL;
// Check the shader's name
if (equalCaseInsensitive(shaderName, "UnlitGeneric"))
{
// Create the StateSet
stateSet = new StateSet();
// Disable lighting on this StateSet
stateSet->setMode(GL_LIGHTING, StateAttribute::OFF);
// Add the texture attribute (or disable texturing if no base texture)
if (texture != NULL)
{
stateSet->setTextureAttributeAndModes(0, texture.get(),
StateAttribute::ON);
}
else
{
notify(WARN) << "No base texture for material " << materialName;
notify(WARN) << std::endl;
stateSet->setTextureMode(0, GL_TEXTURE_2D, StateAttribute::OFF);
}
// See if the material is translucent
if (translucent)
{
// Add the blending attribute as well
blend = new BlendFunc(BlendFunc::SRC_ALPHA,
BlendFunc::ONE_MINUS_SRC_ALPHA);
stateSet->setAttributeAndModes(blend.get(), StateAttribute::ON);
// Set the rendering hint for this stateset to transparent
stateSet->setRenderingHint(StateSet::TRANSPARENT_BIN);
}
}
else
{
// All other shaders fall back to fixed function
// TODO: LightMappedGeneric shader
// Create the StateSet
stateSet = new StateSet();
// Add the texture attribute (or disable texturing if no base texture)
if (texture != NULL)
{
stateSet->setTextureAttributeAndModes(0, texture.get(),
StateAttribute::ON);
// See if the material is translucent
if (translucent)
{
// Add the blending attribute as well
blend = new BlendFunc(BlendFunc::SRC_ALPHA,
BlendFunc::ONE_MINUS_SRC_ALPHA);
stateSet->setAttributeAndModes(blend.get(),
StateAttribute::ON);
// Set the rendering hint for this stateset to transparent
stateSet->setRenderingHint(StateSet::TRANSPARENT_BIN);
}
}
else
{
notify(WARN) << "No base texture for material " << materialName;
notify(WARN) << std::endl;
stateSet->setTextureMode(0, GL_TEXTURE_2D, StateAttribute::OFF);
}
}
// Close the file
mtlFile->close();
// Return the resulting StateSet
return stateSet;
}
BodyPart * MDLReader::processBodyPart(std::istream * str, int offset)
{
int i;
MDLBodyPart * part;
BodyPart * partNode;
Model * modelNode;
// Seek to the body part
str->seekg(offset);
// Read it
part = new MDLBodyPart;
str->read((char *) part, sizeof(MDLBodyPart));
// Create the body part node
partNode = new BodyPart(part);
// Process the models
for (i = 0; i < part->num_models; i++)
{
// Process the model
modelNode = processModel(str, offset + part->model_offset +
(i * sizeof(MDLModel)));
// Add the model to the body part
partNode->addModel(modelNode);
}
// Return the new node
return partNode;
}
Model * MDLReader::processModel(std::istream * str, int offset)
{
int i;
MDLModel * model;
Model * modelNode;
Mesh * meshNode;
// Seek to the model
str->seekg(offset);
// Read it
model = new MDLModel;
str->read((char *) model, sizeof(MDLModel));
// Create the model node
modelNode = new Model(model);
// Process the meshes
for (i = 0; i < model->num_meshes; i++)
{
// Process the mesh
meshNode = processMesh(str, offset + model->mesh_offset +
(i * sizeof(MDLMesh)));
// Add the mesh to the model
modelNode->addMesh(meshNode);
}
// Return the model node
return modelNode;
}
Mesh * MDLReader::processMesh(std::istream * str, int offset)
{
MDLMesh * mesh;
Mesh * meshNode;
// Seek to the mesh
str->seekg(offset);
// Read it
mesh = new MDLMesh;
str->read((char *) mesh, sizeof(MDLMesh));
// Create the mesh node
meshNode = new Mesh(mesh);
// Set the mesh's state set based on the material id
meshNode->setStateSet((state_sets[mesh->material_index]).get());
// Return the mesh node
return meshNode;
}
bool MDLReader::readFile(const std::string & file)
{
std::string baseName;
std::string fileName;
std::ifstream * mdlFile;
MDLHeader header;
int i;
unsigned int j;
int offset;
MDLRoot * mdlRoot;
BodyPart * partNode;
std::string vvdFile;
VVDReader * vvdReader;
std::string vtxFile;
VTXReader * vtxReader;
// Remember the model name
mdl_name = getStrippedName(file);
// Try to open the file
fileName = findDataFile(file, CASE_INSENSITIVE);
mdlFile = new std::ifstream(fileName.c_str(), std::ios::binary);
if (!mdlFile)
{
osg::notify(osg::NOTICE) << "MDL file not found" << std::endl;
return false;
}
// Read the header
mdlFile->read((char *) &header, sizeof(MDLHeader));
// Make sure the file is a valid Valve MDL file
if (header.magic_number != MDL_MAGIC_NUMBER)
{
osg::notify(osg::NOTICE) << "This is not a valid .mdl file";
osg::notify(osg::NOTICE) << std::endl;
// Close the file before we quit
mdlFile->close();
delete mdlFile;
return false;
}
// Make sure the version is one that we handle
// TODO: Not sure which versions are valid yet
// Get the texture paths from the file (we'll have to search these paths
// for each texture that we load)
for (i = 0; i < header.num_texture_paths; i++)
{
int texPathBase;
int texPathOffset;
char texPath[256];
texPathBase = header.texture_path_offset + (i * sizeof(int));
mdlFile->seekg(texPathBase);
mdlFile->read((char *) &texPathOffset, sizeof(int));
mdlFile->seekg(texPathOffset);
// Read characters from the file until we reach the end of this path
j = 0;
do
{
mdlFile->get(texPath[j]);
j++;
}
while ((j < sizeof(texPath)) && (texPath[j-1] != 0));
// Store this path
texture_paths.push_back(texPath);
}
// Read the texture info from the file, and create a StateSet for each
// one
for (i = 0; i < header.num_textures; i++)
{
int texBase;
MDLTexture tempTex;
char texName[256];
ref_ptr<StateSet> stateSet;
texBase = header.texture_offset + (i * sizeof(MDLTexture));
mdlFile->seekg(texBase);
mdlFile->read((char *) &tempTex, sizeof(MDLTexture));
mdlFile->seekg(texBase + tempTex.tex_name_offset);
j = 0;
do
{
mdlFile->get(texName[j]);
j++;
}
while ((j < sizeof(texName)) && (texName[j-1] != 0));
// Load this texture
stateSet = readMaterialFile(texName);
// Add it to our list
state_sets.push_back(stateSet);
}
// Create the root node of the MDL tree
mdlRoot = new MDLRoot();
// Process the main model's body parts
for (i = 0; i < header.num_body_parts; i++)
{
// Calculate the offset to the next body part
offset = header.body_part_offset + (i * sizeof(MDLBodyPart));
// Process the body part and get the part's node
partNode = processBodyPart(mdlFile, offset);
// Add the body part to the MDL root node
mdlRoot->addBodyPart(partNode);
}
// Open the VVD file that goes with this model
vvdFile = findDataFile(getNameLessExtension(file) + ".vvd",
CASE_INSENSITIVE);
vvdReader = new VVDReader();
vvdReader->readFile(vvdFile);
// Open the VTX file that goes with this model (at this point, I don't
// see a reason not to always just use the DX9 version)
vtxFile = findDataFile(getNameLessExtension(file) + ".dx90.vtx",
CASE_INSENSITIVE);
vtxReader = new VTXReader(vvdReader, mdlRoot);
vtxReader->readFile(vtxFile);
// Get the root group from the VTX reader
root_node = vtxReader->getModel();
// Close the .mdl file
mdlFile->close();
delete mdlFile;
// Close the two auxiliary readers
delete vvdReader;
delete vtxReader;
// Clean up the MDL tree (we don't need its data anymore)
delete mdlRoot;
// Return true to indicate success
return true;
}
ref_ptr<Node> MDLReader::getRootNode()
{
return root_node;
}