677 lines
20 KiB
C++
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;
|
|
}
|
|
|
|
|