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:
422
src/osgPlugins/mdl/VTXReader.cpp
Normal file
422
src/osgPlugins/mdl/VTXReader.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user