From Jason Daly, "'ve been busy working on the Source engine plugins. There are several contributions in this submission:

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

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

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

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

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

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

 osgviewer models/props_junk/gascan001a.mdl"
This commit is contained in:
Robert Osfield
2008-12-20 13:35:49 +00:00
parent 05cb054140
commit 28ca8277f8
34 changed files with 4608 additions and 670 deletions

View File

@@ -0,0 +1,677 @@
#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, end;
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)
{
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())
{
// 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;
osg::Image * texImage;
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)
{
int i;
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, 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;
}