#include #include #include #include #include #include #include #include #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 VTXReader::processBodyPart(std::istream * str, int offset, BodyPart * currentPart) { int i; VTXBodyPart part; Model * currentModel; ref_ptr partSwitch; ref_ptr 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 VTXReader::processModel(std::istream * str, int offset, Model * currentModel) { int i; VTXModel model; float lastDistance; float distance; LOD * lodNode = 0; ref_ptr group; ref_ptr 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 VTXReader::processLOD(int lodNum, float * distance, std::istream * str, int offset, Model * currentModel) { int i; VTXModelLOD lod; Mesh * currentMesh; int vertexOffset; ref_ptr lodGroup; ref_ptr 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 VTXReader::processMesh(int lodNum, std::istream * str, int offset, int vertexOffset) { int i; VTXMesh mesh; ref_ptr geode; ref_ptr 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 VTXReader::processStripGroup(int lodNum, std::istream * str, int offset, int vertexOffset) { int i; VTXStripGroup stripGroup; VTXVertex vtxVertex; int vertexID; ref_ptr vertexArray; ref_ptr normalArray; ref_ptr texcoordArray; unsigned short index; unsigned short * indexArray; ref_ptr geom; ref_ptr 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 VTXReader::processStrip(unsigned short * indexArray, std::istream * str, int offset) { VTXStrip strip; DrawElementsUShort * drawElements; ref_ptr 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) drawElements = new DrawElementsUShort(PrimitiveSet::TRIANGLES, start, end); else drawElements = new DrawElementsUShort(PrimitiveSet::TRIANGLE_STRIP, start, end); // Flip the indices to get the front faces correct std::reverse(drawElements->begin(), drawElements->end()); // Return the primitive set primSet = drawElements; return primSet; } bool VTXReader::readFile(const std::string & file) { osgDB::ifstream * vtxFile; VTXHeader header; int i; BodyPart * currentPart; ref_ptr 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()) { OSG_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 VTXReader::getModel() { return model_root; }