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,422 @@
#include <osg/Geometry>
#include <osg/Group>
#include <osg/Node>
#include <osg/Notify>
#include <osg/StateSet>
#include <osg/Switch>
#include <iostream>
#include "VTXReader.h"
using namespace mdl;
using namespace osg;
using namespace osgDB;
VTXReader::VTXReader(VVDReader * vvd, MDLRoot * mdlRoot)
{
// Save the VVD reader, as we'll need it to read vertex data
vvd_reader = vvd;
// Save the root of the MDL tree, as we'll need it to make sure we
// index the vertex data properly
mdl_root = mdlRoot;
// Initialize the root group
model_root = NULL;
}
VTXReader::~VTXReader()
{
}
ref_ptr<Group> VTXReader::processBodyPart(std::istream * str, int offset,
BodyPart * currentPart)
{
int i;
VTXBodyPart part;
Model * currentModel;
ref_ptr<Group> partSwitch;
ref_ptr<Group> modelGroup;
// Seek to the body part
str->seekg(offset);
// Read it
str->read((char *) &part, sizeof(VTXBodyPart));
// If there is more than one model, create a switch to select between them
// (it seems that only one model is supposed to be seen at a given time,
// but I don't know the mechanism in the engine that selects a desired
// model)
if (part.num_models > 1)
{
partSwitch = new Switch();
}
// Process the models
for (i = 0; i < part.num_models; i++)
{
// Get the corresponding MDL model from the current body part
currentModel = currentPart->getModel(i);
// Process the model
modelGroup = processModel(str,
offset + part.model_offset +
(i * sizeof(VTXModel)),
currentModel);
// If there is more than one model, add this model to the part group
if (part.num_models > 1)
{
// Add the model to the switch
partSwitch->addChild(modelGroup.get());
// If this is the first child, turn it on, otherwise turn it off
if (i == 0)
((osg::Switch *)partSwitch.get())->setValue(i, true);
else
((osg::Switch *)partSwitch.get())->setValue(i, false);
}
}
// If there is only one model, just return it
if (part.num_models == 1)
return modelGroup;
else
return partSwitch;
}
ref_ptr<Group> VTXReader::processModel(std::istream * str, int offset,
Model * currentModel)
{
int i;
VTXModel model;
float lastDistance;
float distance;
LOD * lodNode;
ref_ptr<Group> group;
ref_ptr<Group> result;
// Seek to the model
str->seekg(offset);
// Read it
str->read((char *) &model, sizeof(VTXModel));
// If we have multiple LODs, create an LOD node for them
if (model.num_lods > 1)
lodNode = new LOD();
// Initialize the distances
lastDistance = 0.0;
distance = 0.0;
// Process the LODs
for (i = 0; i < model.num_lods; i++)
{
// Process the LOD group, passing the current MDL model through
group = processLOD(i, &distance, str,
offset + model.lod_offset +
(i * sizeof(VTXModelLOD)),
currentModel);
// If this isn't the only LOD, add it to the LOD node
if (model.num_lods > 1)
{
lodNode->addChild(group.get());
// Fix the LOD distances
if (distance < 0)
{
// Fix negative distance (my best guess is that these mean
// for the LOD to never switch out)
distance = 100000.0;
}
// Set the ranges on the previous LOD (now that we know the
// switch point for this one)
if (i > 0)
lodNode->setRange(i-1, lastDistance, distance);
lastDistance = distance;
}
}
if (i > 1)
lodNode->setRange(i-1, lastDistance, 100000.0);
// Return either the LOD node or the single LOD group
if (model.num_lods > 1)
result = lodNode;
else
result = group;
return result;
}
ref_ptr<Group> VTXReader::processLOD(int lodNum, float * distance,
std::istream * str, int offset,
Model * currentModel)
{
int i;
VTXModelLOD lod;
Mesh * currentMesh;
int vertexOffset;
ref_ptr<Group> lodGroup;
ref_ptr<Geode> geode;
// Seek to the LOD
str->seekg(offset);
// Read it
str->read((char *) &lod, sizeof(VTXModelLOD));
// Create a group to hold this LOD
lodGroup = new Group();
// Process the meshes
vertexOffset = currentModel->getVertexBase();
for (i = 0; i < lod.num_meshes; i++)
{
// Get the corresponding MDL mesh from the current model
currentMesh = currentModel->getMesh(i);
// Process the mesh to get a geode
geode = processMesh(lodNum, str,
offset + lod.mesh_offset + (i * VTX_MESH_SIZE),
vertexOffset);
// Set the geode's state set based on the current mesh node's state
// set
geode->setStateSet(currentMesh->getStateSet());
// Add the geode to the group
lodGroup->addChild(geode.get());
// Add the number of vertices for this mesh at this LOD to the offset,
// so we can start the next mesh at the proper vertex ID
vertexOffset += currentMesh->getNumLODVertices(lodNum);
}
// Set the distance for this LOD
*distance = lod.switch_point;
// Return the LOD group that we created
return lodGroup;
}
ref_ptr<Geode> VTXReader::processMesh(int lodNum, std::istream * str,
int offset, int vertexOffset)
{
int i;
VTXMesh mesh;
ref_ptr<Geode> geode;
ref_ptr<Geometry> geom;
// Seek to the mesh
str->seekg(offset);
// Read it
str->read((char *) &mesh, VTX_MESH_SIZE);
// Create a geode to hold the geometry
geode = new Geode();
// Process the strip groups
for (i = 0; i < mesh.num_strip_groups; i++)
{
// Process the strip group to get a Geometry
geom = processStripGroup(lodNum, str,
offset + mesh.strip_group_offset + (i * VTX_STRIP_GROUP_SIZE),
vertexOffset);
// Add the geometry to the geode
geode->addDrawable(geom.get());
}
// Return the geode
return geode;
}
ref_ptr<Geometry> VTXReader::processStripGroup(int lodNum, std::istream * str,
int offset, int vertexOffset)
{
int i;
VTXStripGroup stripGroup;
VTXVertex vtxVertex;
int vertexID;
ref_ptr<Vec3Array> vertexArray;
ref_ptr<Vec3Array> normalArray;
ref_ptr<Vec2Array> texcoordArray;
unsigned short index;
unsigned short * indexArray;
ref_ptr<Geometry> geom;
ref_ptr<PrimitiveSet> primSet;
// Seek to the strip group
str->seekg(offset);
// Read it
str->read((char *) &stripGroup, VTX_STRIP_GROUP_SIZE);
// Create and fill the vertex arrays
vertexArray = new Vec3Array();
normalArray = new Vec3Array();
texcoordArray = new Vec2Array();
str->seekg(offset + stripGroup.vertex_offset);
for (i = 0; i < stripGroup.num_vertices; i++)
{
// Get the vertex ID from the strip group
str->read((char *) &vtxVertex, VTX_VERTEX_SIZE);
vertexID = vtxVertex.orig_mesh_vertex_id + vertexOffset;
// Get the corresponding vertex, normal, texture coordinates from the
// VVD file
vertexArray->push_back(vvd_reader->getVertex(lodNum, vertexID));
normalArray->push_back(vvd_reader->getNormal(lodNum, vertexID));
texcoordArray->push_back(vvd_reader->getTexCoords(lodNum, vertexID));
}
// Create the geometry and add the vertex data to it
geom = new Geometry();
geom->setVertexArray(vertexArray.get());
geom->setNormalArray(normalArray.get());
geom->setNormalBinding(Geometry::BIND_PER_VERTEX);
geom->setTexCoordArray(0, texcoordArray.get());
// Create and fill the index array
indexArray = new unsigned short[stripGroup.num_indices];
str->seekg(offset + stripGroup.index_offset);
for (i = 0; i < stripGroup.num_indices; i++)
{
// Get the index from the file
str->read((char *) &index, sizeof(unsigned short));
// Add to the array
indexArray[i] = index;
}
// Process the strips
for (i = 0; i < stripGroup.num_strips; i++)
{
// Process the strip to create a triangle list or strip
primSet = processStrip(indexArray, str,
offset + stripGroup.strip_offset + (i * VTX_STRIP_SIZE));
// Add the primitive set to the geometry
geom->addPrimitiveSet(primSet.get());
}
// Clean up
delete [] indexArray;
// Return the geometry
return geom;
}
ref_ptr<PrimitiveSet> VTXReader::processStrip(unsigned short * indexArray,
std::istream * str,
int offset)
{
int i;
VTXStrip strip;
ref_ptr<PrimitiveSet> primSet;
unsigned short * start;
unsigned short * end;
// Seek to the strip
str->seekg(offset);
// Read it. We have to do this in a kind of screwy way because of the
// weird byte packing. Valve uses pragma pack, but we can't do that
// because it's non-portable. Of course, I'm assuming a 4-byte alignment
// here, which might also be non-portable...
str->read((char *) &strip, VTX_STRIP_SIZE - 8);
str->read((char *) &strip.num_bone_state_changes, 8);
// Get the range of indices in question from the strip
start = &indexArray[strip.index_offset];
end = &indexArray[strip.index_offset + strip.num_indices];
// Create the primitive set (based on the flag)
if (strip.strip_flags & STRIP_IS_TRI_LIST)
primSet =
new DrawElementsUShort(PrimitiveSet::TRIANGLES, start, end);
else
primSet =
new DrawElementsUShort(PrimitiveSet::TRIANGLE_STRIP, start, end);
// Return the primitive set
return primSet;
}
bool VTXReader::readFile(const std::string & file)
{
osgDB::ifstream * vtxFile;
VTXHeader header;
int i, j;
int offset;
BodyPart * currentPart;
ref_ptr<Group> partGroup;
Group * rootGroup;
// Remember the map name
vtx_name = getStrippedName(file);
vtxFile = new osgDB::ifstream(file.c_str(), std::ios::binary);
if (!vtxFile || vtxFile->fail())
{
notify(NOTICE) << "Vertex index file not found" << std::endl;
return false;
}
// Read the header
vtxFile->read((char *) &header, sizeof(VTXHeader));
// Make sure the version is one that we handle
// TODO: Not sure which versions are valid yet
// Create the root group
rootGroup = new Group();
// Process the body parts
for (i = 0; i < header.num_body_parts; i++)
{
// Get the corresponding body part from the MDL tree
currentPart = mdl_root->getBodyPart(i);
// Process the body part
partGroup = processBodyPart(vtxFile,
header.body_part_offset +
(i * sizeof(VTXBodyPart)),
currentPart);
// Add the result to the root group
rootGroup->addChild(partGroup.get());
}
// Set the model's root node
model_root = rootGroup;
// Close the file
vtxFile->close();
delete vtxFile;
return true;
}
ref_ptr<Node> VTXReader::getModel()
{
return model_root;
}