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

@@ -378,38 +378,122 @@ std::string osgDB::findFileInDirectory(const std::string& fileName,const std::st
bool needDirectoryName = true;
osgDB::DirectoryContents dc;
if (dirName.empty())
std::string realDirName = dirName;
std::string realFileName = fileName;
// Skip case-insensitive recursion if on Windows
#ifdef WIN32
bool win32 = true;
#else
bool win32 = false;
#endif
// If the fileName contains extra path information, make that part of the
// directory name instead
if (fileName != getSimpleFileName(fileName))
{
// See if we need to add a slash between the directory and file
if (realDirName.empty())
{
realDirName = getFilePath(fileName);
}
else if (realDirName=="." || realDirName=="./" || realDirName==".\\")
{
realDirName = "./" + getFilePath(fileName);
}
else
{
char lastChar = dirName[dirName.size()-1];
if ((lastChar == '/') || (lastChar == '\\'))
realDirName = dirName + getFilePath(fileName);
else
realDirName = dirName + "/" + getFilePath(fileName);
}
// Simplify the file name
realFileName = getSimpleFileName(fileName);
}
osg::notify(osg::DEBUG_INFO) << "findFileInDirectory() : looking for " << realFileName << " in " << realDirName << "...\n";
if (realDirName.empty())
{
dc = osgDB::getDirectoryContents(".");
needFollowingBackslash = false;
needDirectoryName = false;
}
else if (dirName=="." || dirName=="./" || dirName==".\\")
else if (realDirName=="." || realDirName=="./" || realDirName==".\\")
{
dc = osgDB::getDirectoryContents(".");
needFollowingBackslash = false;
needDirectoryName = false;
}
else if (realDirName=="/")
{
dc = osgDB::getDirectoryContents("/");
needFollowingBackslash = false;
needDirectoryName = true;
}
else
{
dc = osgDB::getDirectoryContents(dirName);
char lastChar = dirName[dirName.size()-1];
if (lastChar=='/') needFollowingBackslash = false;
else if (lastChar=='\\') needFollowingBackslash = false;
else needFollowingBackslash = true;
needDirectoryName = true;
// See if we're working in case insensitive mode, and that we're not
// using Windows (the recursive search is not needed in these
// cases)
if ((caseSensitivity == CASE_INSENSITIVE) && (!win32))
{
// Split the last path element from the directory name
std::string parentPath = getFilePath(realDirName);
std::string lastElement = getSimpleFileName(realDirName);
// See if we're already at the top level of the filesystem
if ((parentPath.empty()) && (!lastElement.empty()))
{
// Search for the first path element (ignoring case) in
// the top-level directory
realDirName = findFileInDirectory(lastElement, "/",
CASE_INSENSITIVE);
dc = osgDB::getDirectoryContents(realDirName);
needFollowingBackslash = true;
needDirectoryName = true;
}
else
{
// Recursively search for the last path element (ignoring case)
// in the parent path
realDirName = findFileInDirectory(lastElement, parentPath,
CASE_INSENSITIVE);
dc = osgDB::getDirectoryContents(realDirName);
char lastChar = realDirName[realDirName.size()-1];
if (lastChar=='/') needFollowingBackslash = false;
else if (lastChar=='\\') needFollowingBackslash = false;
else needFollowingBackslash = true;
needDirectoryName = true;
}
}
else
{
// No need for recursive search if we're doing an exact comparison
dc = osgDB::getDirectoryContents(realDirName);
char lastChar = realDirName[realDirName.size()-1];
if (lastChar=='/') needFollowingBackslash = false;
else if (lastChar=='\\') needFollowingBackslash = false;
else needFollowingBackslash = true;
needDirectoryName = true;
}
}
for(osgDB::DirectoryContents::iterator itr=dc.begin();
itr!=dc.end();
++itr)
{
if ((caseSensitivity==CASE_INSENSITIVE && osgDB::equalCaseInsensitive(fileName,*itr)) ||
(fileName==*itr))
if ((caseSensitivity==CASE_INSENSITIVE && osgDB::equalCaseInsensitive(realFileName,*itr)) ||
(realFileName==*itr))
{
if (!needDirectoryName) return *itr;
else if (needFollowingBackslash) return dirName+'/'+*itr;
else return dirName+*itr;
else if (needFollowingBackslash) return realDirName+'/'+*itr;
else return realDirName+*itr;
}
}
return "";

View File

@@ -81,7 +81,7 @@ ADD_SUBDIRECTORY(dds)
ADD_SUBDIRECTORY(tga)
ADD_SUBDIRECTORY(hdr)
ADD_SUBDIRECTORY(dot)
ADD_SUBDIRECTORY(bsp)
ADD_SUBDIRECTORY(vtf)
IF(JPEG_FOUND)
ADD_SUBDIRECTORY(jpeg)
@@ -180,6 +180,9 @@ ADD_SUBDIRECTORY(txp)
ADD_SUBDIRECTORY(shp)
ADD_SUBDIRECTORY(txf)
ADD_SUBDIRECTORY(bsp)
ADD_SUBDIRECTORY(mdl)
IF(XINE_FOUND)
ADD_SUBDIRECTORY(xine)
ENDIF(XINE_FOUND)

View File

@@ -1,9 +1,10 @@
SET(TARGET_SRC
ReaderWriterBSP.cpp
ReaderWriterVTF.cpp
BITSET.cpp
Q3BSPReader.cpp
Q3BSPLoad.cpp
VBSPData.cpp
VBSPEntity.cpp
VBSPGeometry.cpp
VBSPReader.cpp
)
@@ -13,6 +14,8 @@ SET(TARGET_H
BITSET.h
Q3BSPReader.h
Q3BSPLoad.h
VBSPData.h
VBSPEntity.h
VBSPGeometry.h
VBSPReader.h
)

View File

@@ -56,11 +56,11 @@ ReaderWriter::ReadResult ReaderWriterBSP::readNode(
return ReadResult::FILE_NOT_FOUND;
// Open the file and read the magic number and version
stream.open(file.c_str(), std::ios::binary);
stream.open(fileName.c_str(), std::ios::binary);
stream.read((char *) &magicNumber, sizeof(int));
stream.read((char *) &version, sizeof(int));
stream.close();
// See which kind of BSP file this is
if ((magicNumber == VBSP_MAGIC_NUMBER) &&
(version >= 19) && (version <= 20))

View File

@@ -0,0 +1,304 @@
#include "VBSPData.h"
#include <string.h>
using namespace bsp;
using namespace osg;
VBSPData::VBSPData()
{
}
VBSPData::~VBSPData()
{
}
void VBSPData::addEntity(std::string & newEntity)
{
entity_list.push_back(newEntity);
}
const int VBSPData::getNumEntities() const
{
return entity_list.size();
}
const std::string & VBSPData::getEntity(int index) const
{
return entity_list[index];
}
void VBSPData::addModel(Model & newModel)
{
model_list.push_back(newModel);
}
const int VBSPData::getNumModels() const
{
return model_list.size();
}
const Model & VBSPData::getModel(int index) const
{
return model_list[index];
}
void VBSPData::addPlane(bsp::Plane & newPlane)
{
plane_list.push_back(newPlane);
}
const int VBSPData::getNumPlanes() const
{
return plane_list.size();
}
const bsp::Plane & VBSPData::getPlane(int index) const
{
return plane_list[index];
}
void VBSPData::addVertex(osg::Vec3f & newVertex)
{
// Scale the vertex from inches up to meter scale
vertex_list.push_back(newVertex * 0.0254);
}
const int VBSPData::getNumVertices() const
{
return vertex_list.size();
}
const osg::Vec3f & VBSPData::getVertex(int index) const
{
return vertex_list[index];
}
void VBSPData::addEdge(Edge & newEdge)
{
edge_list.push_back(newEdge);
}
const int VBSPData::getNumEdges() const
{
return edge_list.size();
}
const Edge & VBSPData::getEdge(int index) const
{
return edge_list[index];
}
void VBSPData::addSurfaceEdge(int & newSurfEdge)
{
surface_edge_list.push_back(newSurfEdge);
}
const int VBSPData::getNumSurfaceEdges() const
{
return surface_edge_list.size();
}
const int VBSPData::getSurfaceEdge(int index) const
{
return surface_edge_list[index];
}
void VBSPData::addFace(Face & newFace)
{
face_list.push_back(newFace);
}
const int VBSPData::getNumFaces() const
{
return face_list.size();
}
const Face & VBSPData::getFace(int index) const
{
return face_list[index];
}
void VBSPData::addTexInfo(TexInfo & newTexInfo)
{
texinfo_list.push_back(newTexInfo);
}
const int VBSPData::getNumTexInfos() const
{
return texinfo_list.size();
}
const TexInfo & VBSPData::getTexInfo(int index) const
{
return texinfo_list[index];
}
void VBSPData::addTexData(TexData & newTexData)
{
texdata_list.push_back(newTexData);
}
const int VBSPData::getNumTexDatas() const
{
return texdata_list.size();
}
const TexData & VBSPData::getTexData(int index) const
{
return texdata_list[index];
}
void VBSPData::addTexDataString(std::string & newTexDataString)
{
texdata_string_list.push_back(newTexDataString);
}
const int VBSPData::getNumTexDataStrings() const
{
return texdata_string_list.size();
}
const std::string & VBSPData::getTexDataString(int index) const
{
return texdata_string_list[index];
}
void VBSPData::addDispInfo(DisplaceInfo & newDispInfo)
{
dispinfo_list.push_back(newDispInfo);
}
const int VBSPData::getNumDispInfos() const
{
return dispinfo_list.size();
}
const DisplaceInfo & VBSPData::getDispInfo(int index) const
{
return dispinfo_list[index];
}
void VBSPData::addDispVertex(DisplacedVertex & newDispVertex)
{
displaced_vertex_list.push_back(newDispVertex);
}
const int VBSPData::getNumDispVertices() const
{
return displaced_vertex_list.size();
}
const DisplacedVertex & VBSPData::getDispVertex(int index) const
{
return displaced_vertex_list[index];
}
void VBSPData::addStaticPropModel(std::string & newModel)
{
static_prop_model_list.push_back(newModel);
}
const int VBSPData::getNumStaticPropModels() const
{
return static_prop_model_list.size();
}
const std::string & VBSPData::getStaticPropModel(int index) const
{
return static_prop_model_list[index];
}
void VBSPData::addStaticProp(StaticPropV4 & newProp)
{
StaticProp newPropV5;
// Create a version 5 static prop and copy the data from the given
// version 4 prop into it
memcpy(&newPropV5, &newProp, sizeof(StaticPropV4));
newPropV5.forced_fade_scale = 1.0;
// Add the new prop to the list
static_prop_list.push_back(newPropV5);
}
void VBSPData::addStaticProp(StaticProp & newProp)
{
static_prop_list.push_back(newProp);
}
const int VBSPData::getNumStaticProps() const
{
return static_prop_list.size();
}
const StaticProp & VBSPData::getStaticProp(int index) const
{
return static_prop_list[index];
}
void VBSPData::addStateSet(StateSet * stateSet)
{
state_set_list.push_back(stateSet);
}
const int VBSPData::getNumStateSets() const
{
return state_set_list.size();
}
StateSet * VBSPData::getStateSet(int index) const
{
return state_set_list[index].get();
}

View File

@@ -0,0 +1,284 @@
#ifndef __VBSP_DATA_H_
#define __VBSP_DATA_H_
#include <osg/Vec3f>
#include <osg/StateSet>
#include <osg/Referenced>
#include <string>
namespace bsp
{
struct Plane
{
osg::Vec3f plane_normal;
float origin_dist;
int type;
};
struct Edge
{
unsigned short vertex[2];
};
struct Face
{
unsigned short plane_index;
unsigned char plane_side;
unsigned char on_node;
int first_edge;
short num_edges;
short texinfo_index;
short dispinfo_index;
short surface_fog_volume_id;
unsigned char styles[4];
int light_offset;
float face_area;
int lightmap_texture_mins_in_luxels[2];
int lightmap_texture_size_in_luxels[2];
int original_face;
unsigned short num_primitives;
unsigned short first_primitive_id;
unsigned int smoothing_groups;
};
struct Model
{
osg::Vec3f bound_min;
osg::Vec3f bound_max;
osg::Vec3f model_origin;
int head_node;
int first_face;
int num_faces;
};
struct StaticPropV4
{
osg::Vec3f prop_origin;
osg::Vec3f prop_angles;
unsigned short prop_type;
unsigned short first_leaf;
unsigned short leaf_count;
unsigned char prop_solid;
unsigned char prop_flags;
unsigned int prop_skin;
float min_fade_dist;
float max_fade_dist;
osg::Vec3f lighting_origin;
};
struct StaticProp
{
osg::Vec3f prop_origin;
osg::Vec3f prop_angles;
unsigned short prop_type;
unsigned short first_leaf;
unsigned short leaf_count;
unsigned char prop_solid;
unsigned char prop_flags;
unsigned int prop_skin;
float min_fade_dist;
float max_fade_dist;
osg::Vec3f lighting_origin;
float forced_fade_scale;
};
struct TexInfo
{
float texture_vecs[2][4];
float lightmap_vecs[2][4];
int texture_flags;
int texdata_index;
};
struct TexData
{
osg::Vec3f texture_reflectivity;
int name_string_table_id;
int texture_width;
int texture_height;
int view_width;
int view_height;
};
struct DisplaceSubNeighbor
{
unsigned short neighbor_index;
unsigned char neighbor_orient;
unsigned char local_span;
unsigned char neighbor_span;
};
struct DisplaceNeighbor
{
DisplaceSubNeighbor sub_neighbors[2];
};
struct DisplaceCornerNeighbor
{
unsigned short neighbor_indices[4];
unsigned char neighbor_count;
};
struct DisplaceInfo
{
osg::Vec3f start_position;
int disp_vert_start;
int disp_tri_start;
int power;
int min_tesselation;
float smooth_angle;
int surface_contents;
unsigned short map_face;
int lightmap_alpha_start;
int lightmap_sample_pos_start;
DisplaceNeighbor edge_neighbors[4];
DisplaceCornerNeighbor corner_neighbors[4];
unsigned int allowed_verts[10];
};
struct DisplacedVertex
{
osg::Vec3f displace_vec;
float displace_dist;
float alpha_blend;
};
class VBSPData
{
protected:
typedef std::vector<std::string> EntityList;
EntityList entity_list;
typedef std::vector<Model> ModelList;
ModelList model_list;
typedef std::vector<Plane> PlaneList;
PlaneList plane_list;
typedef std::vector<osg::Vec3f> VertexList;
VertexList vertex_list;
typedef std::vector<Edge> EdgeList;
EdgeList edge_list;
typedef std::vector<int> SurfEdgeList;
SurfEdgeList surface_edge_list;
typedef std::vector<Face> FaceList;
FaceList face_list;
typedef std::vector<TexInfo> TexInfoList;
TexInfoList texinfo_list;
typedef std::vector<TexData> TexDataList;
TexDataList texdata_list;
typedef std::vector<std::string> TexDataStringList;
TexDataStringList texdata_string_list;
typedef std::vector<DisplaceInfo> DisplaceInfoList;
DisplaceInfoList dispinfo_list;
typedef std::vector<DisplacedVertex> DisplacedVertexList;
DisplacedVertexList displaced_vertex_list;
typedef std::vector<std::string> StaticPropModelList;
StaticPropModelList static_prop_model_list;
typedef std::vector<StaticProp> StaticPropList;
StaticPropList static_prop_list;
typedef std::vector< osg::ref_ptr<osg::StateSet> > StateSetList;
StateSetList state_set_list;
public:
VBSPData();
virtual ~VBSPData();
void addEntity(std::string & newEntity);
const int getNumEntities() const;
const std::string & getEntity(int index) const;
void addModel(Model & newModel);
const int getNumModels() const;
const Model & getModel(int index) const;
void addPlane(Plane & newPlane);
const int getNumPlanes() const;
const Plane & getPlane(int index) const;
void addVertex(osg::Vec3f & newVertex);
const int getNumVertices() const;
const osg::Vec3f & getVertex(int index) const;
void addEdge(Edge & newEdge);
const int getNumEdges() const;
const Edge & getEdge(int index) const;
void addSurfaceEdge(int & newSurfEdge);
const int getNumSurfaceEdges() const;
const int getSurfaceEdge(int index) const;
void addFace(Face & newFace);
const int getNumFaces() const;
const Face & getFace(int index) const;
void addTexInfo(TexInfo & newTexInfo);
const int getNumTexInfos() const;
const TexInfo & getTexInfo(int index) const;
void addTexData(TexData & newTexData);
const int getNumTexDatas() const;
const TexData & getTexData(int index) const;
void addTexDataString(std::string & newTexDataString);
const int getNumTexDataStrings() const;
const std::string & getTexDataString(int index) const;
void addDispInfo(DisplaceInfo & newDispInfo);
const int getNumDispInfos() const;
const DisplaceInfo & getDispInfo(int index) const;
void addDispVertex(DisplacedVertex & newDispVert);
const int getNumDispVertices() const;
const DisplacedVertex & getDispVertex(int index) const;
void addStaticPropModel(std::string & newModel);
const int getNumStaticPropModels() const;
const std::string & getStaticPropModel(int index) const;
void addStaticProp(StaticPropV4 & newProp);
void addStaticProp(StaticProp & newProp);
const int getNumStaticProps() const;
const StaticProp & getStaticProp(int index) const;
void addStateSet(osg::StateSet * stateSet);
const int getNumStateSets() const;
osg::StateSet * getStateSet(int index) const;
};
}
#endif

View File

@@ -0,0 +1,597 @@
#include "VBSPEntity.h"
#include "VBSPGeometry.h"
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osgDB/FileUtils>
#include <osgDB/FileNameUtils>
#include <iostream>
#include <sstream>
#include <string.h>
using namespace bsp;
using namespace osg;
using namespace osgDB;
// strcasecmp for MSVC
#ifdef _MSC_VER
#define strcasecmp _stricmp
#endif
VBSPEntity::VBSPEntity(std::string & entityText, VBSPData * bspData)
{
// Save a handle to the bsp data, as we'll need this to construct the
// entity
bsp_data = bspData;
// Assume we're not visible at first
entity_visible = false;
// Assume no transform
entity_transformed = false;
// No model (external or internal) yet
entity_model_index = -1;
entity_model.clear();
// Don't know the class yet
entity_class = ENTITY_OTHER;
// Parse the entity's text to gather parameters
parseParameters(entityText);
}
VBSPEntity::~VBSPEntity()
{
}
void VBSPEntity::processWorldSpawn()
{
// World spawn is definitely visible
entity_visible = true;
// World spawn is always centered at the origin, so there's no need for
// a transform
entity_transformed = false;
// The world spawn's internal model index is always zero
entity_model_index = 0;
}
void VBSPEntity::processEnv()
{
// We don't support these entities yet, so leave them invisible
}
void VBSPEntity::processFuncBrush()
{
// These entities are usually transformed
entity_transformed = true;
// Get the internal model index for this entity
EntityParameters::iterator param = entity_parameters.find("model");
if (param != entity_parameters.end())
{
// Get the model number
std::string value = (*param).second;
// Skip the leading asterisk (internal models are denoted with a
// leading asterisk), and then parse the model number
if (value[0] == '*')
{
value = value.substr(1, std::string::npos);
entity_model_index = atoi(value.c_str());
// Make the entity visible
entity_visible = true;
}
else
{
// This shouldn't happen (brush entities don't reference
// external models). Leave the entity invisible in this case
entity_visible = false;
}
}
else
{
// We can't locate the model for this entity, so leave it invisible
entity_visible = false;
}
// Get the origin and angles for this entity
param = entity_parameters.find("origin");
if (param != entity_parameters.end())
{
// Get the origin parameter's value
std::string value = (*param).second;
// Parse the value into a vector
entity_origin = getVector(value);
}
param = entity_parameters.find("angles");
if (param != entity_parameters.end())
{
// Get the origin parameter's value
std::string value = (*param).second;
// Parse the value into a vector
entity_angles = getVector(value);
}
}
void VBSPEntity::processProp()
{
// These entities are visible
entity_visible = true;
// These entities are usually transformed
entity_transformed = true;
// Get the model we need to load for this entity
EntityParameters::iterator param = entity_parameters.find("model");
if (param != entity_parameters.end())
{
// Get the model parameter's value
entity_model = (*param).second;
}
// Get the origin and angles for this entity
param = entity_parameters.find("origin");
if (param != entity_parameters.end())
{
// Get the origin parameter's value
std::string value = (*param).second;
// Parse the value into a vector
entity_origin = getVector(value);
}
param = entity_parameters.find("angles");
if (param != entity_parameters.end())
{
// Get the origin parameter's value
std::string value = (*param).second;
// Parse the value into a vector
entity_angles = getVector(value);
}
}
void VBSPEntity::processInfoDecal()
{
// We don't support these entities yet, so leave them invisible
}
void VBSPEntity::processItem()
{
// We don't support these entities yet, so leave them invisible
}
Vec3f VBSPEntity::getVector(std::string str)
{
double x, y, z;
// Look for the first non-whitespace
int start = str.find_first_not_of(" \t\r\n", 0);
// Look for the first whitespace after this
int end = str.find_first_of(" \t\r\n", start);
if ((end > start) && (start != std::string::npos))
x = atof(str.substr(start, end-start).c_str());
else
return Vec3f();
// Look for the next non-whitespace
start = str.find_first_not_of(" \t\r\n", end+1);
// Look for the first whitespace after this
end = str.find_first_of(" \t\r\n", start);
if ((end > start) && (start != std::string::npos))
y = atof(str.substr(start, end-start).c_str());
else
return Vec3f();
// Look for the next non-whitespace
start = str.find_first_not_of(" \t\r\n", end+1);
// Look for the first whitespace after this
end = str.find_first_of(" \t\r\n", start);
if (end == std::string::npos)
end = str.length();
if ((end > start) && (start != std::string::npos))
z = atof(str.substr(start, end-start).c_str());
else
return Vec3f();
// If we get this far, return the vector that we parsed
return Vec3f(x, y, z);
}
std::string VBSPEntity::getToken(std::string str, size_t & index)
{
size_t start, end;
std::string token;
// Look for the first quotation mark
start = str.find_first_of("\"", index);
if (start != std::string::npos)
{
// From there, look for the next occurrence of a delimiter
start++;
end = str.find_first_of("\"", start);
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.clear();
}
// 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;
}
void VBSPEntity::parseParameters(std::string & entityText)
{
// Create a string stream on the entity text
std::istringstream str(entityText, std::istringstream::in);
// Iterate over the parameters
while (!str.eof())
{
// Get the next line of text
std::string line;
std::getline(str, line);
// Look for the first quotation mark on the line
size_t start = 0;
std::string token = getToken(line, start);
// If we have a valid token it will be the parameter name (the key),
// look for a second token, which will be the parameter's value
while (!token.empty())
{
// Save the token as the key
std::string key = token;
// Get the next token
start++;
token = getToken(line, start);
// See if the token is valid
if (!token.empty())
{
// This token is the value, create an entity parameter from
// these two strings and add it to our parameters map
EntityParameter param(key, token);
entity_parameters.insert(param);
}
}
}
// Now that we have all of the parameters, figure out what kind of entity
// this is
EntityParameters::iterator param = entity_parameters.find("classname");
// See if we found the class
if (param == entity_parameters.end())
{
// We need the class to be able to do anything with this entity
return;
}
// Get the class name and process the entity appropriately
std::string className = (*param).second;
if (className.compare("worldspawn") == 0)
{
// This is the entity that represents the main geometry of the map
// (the terrain and much of the static geometry)
entity_class = ENTITY_WORLDSPAWN;
processWorldSpawn();
}
else if (className.compare(0, 3, "env") == 0)
{
// This is an environmental effect (such as a fire or dust cloud)
entity_class = ENTITY_ENV;
processEnv();
}
else if ((className.compare("func_brush") == 0) ||
(className.compare("func_illusionary") == 0) ||
(className.compare("func_wall_toggle") == 0) ||
(className.compare("func_breakable") == 0))
{
// This is secondary map geometry, created along with the main
// map geometry (not an external model)
entity_class = ENTITY_FUNC_BRUSH;
processFuncBrush();
}
else if (className.compare(0, 4, "prop") == 0)
{
// This is a "prop", an external model placed somewhere in the
// scene
entity_class = ENTITY_PROP;
processProp();
}
else if (className.compare("infodecal") == 0)
{
// This is a decal, which applies a texture to some surface in the
// scene
entity_class = ENTITY_INFO_DECAL;
processInfoDecal();
}
else if (className.compare(0, 4, "item") == 0)
{
// This is an "item". Like a prop, these are external models
// placed in the scene, but the specific model is determined
// directly by the entity's class. In HL2, these entities are
// useable by the player (ammunition and health packs are examples)
entity_class = ENTITY_ITEM;
processItem();
}
}
ref_ptr<Group> VBSPEntity::createBrushGeometry()
{
int i;
int numGeoms;
VBSPGeometry ** vbspGeomList;
Model currentModel;
Face currentFace;
TexInfo currentTexInfo;
TexData currentTexData;
const char * texName;
char currentTexName[256];
int currentGeomIndex;
VBSPGeometry * currentGeom;
ref_ptr<Group> entityGroup;
ref_ptr<Group> geomGroup;
// Create a list of VBSPGeometry objects for each texdata entry in the
// scene. These objects will hold the necessary geometry data until we
// convert them back into OSG geometry objects. We potentially will need
// one for each state set in the map
numGeoms = bsp_data->getNumStateSets();
vbspGeomList = new VBSPGeometry *[numGeoms];
// Initialize the list to all NULL for now. We'll create the geometry
// objects as we need them
memset(vbspGeomList, 0, sizeof(VBSPGeometry *) * numGeoms);
// Get this entity's internal model from the bsp data
currentModel = bsp_data->getModel(entity_model_index);
// Iterate over the face list and assign faces to the appropriate geometry
// objects
for (i = 0; i < currentModel.num_faces; i++)
{
// Get the current face
currentFace = bsp_data->getFace(currentModel.first_face + i);
// Get the texdata used by this face
currentTexInfo = bsp_data->getTexInfo(currentFace.texinfo_index);
currentTexData = bsp_data->getTexData(currentTexInfo.texdata_index);
// Get the texture name
texName = bsp_data->
getTexDataString(currentTexData.name_string_table_id).c_str();
strcpy(currentTexName, texName);
// See if this is a non-drawable surface
if ((strcasecmp(currentTexName, "tools/toolsareaportal") != 0) &&
(strcasecmp(currentTexName, "tools/toolsblocklos") != 0) &&
(strcasecmp(currentTexName, "tools/toolsblockbullets") != 0) &&
(strcasecmp(currentTexName, "tools/toolsblocklight") != 0) &&
(strcasecmp(currentTexName, "tools/toolsclip") != 0) &&
(strcasecmp(currentTexName, "tools/toolscontrolclip") != 0) &&
(strcasecmp(currentTexName, "tools/toolsdotted") != 0) &&
(strcasecmp(currentTexName, "tools/toolshint") != 0) &&
(strcasecmp(currentTexName, "tools/toolsinvisible") != 0) &&
(strcasecmp(currentTexName, "tools/toolsinvisibleladder") != 0) &&
(strcasecmp(currentTexName, "tools/toolsnodraw") != 0) &&
(strcasecmp(currentTexName, "tools/toolsnpcclip") != 0) &&
(strcasecmp(currentTexName, "tools/toolsoccluder") != 0) &&
(strcasecmp(currentTexName, "tools/toolsorigin") != 0) &&
(strcasecmp(currentTexName, "tools/toolsskip") != 0) &&
(strcasecmp(currentTexName, "tools/toolsskybox") != 0) &&
(strcasecmp(currentTexName, "tools/toolsskyfog") != 0) &&
(strcasecmp(currentTexName, "tools/toolstrigger") != 0))
{
// Get or create the corresponding VBSPGeometry object from the
// list
currentGeomIndex = currentTexInfo.texdata_index;
currentGeom = vbspGeomList[currentGeomIndex];
if (currentGeom == NULL)
{
// Create the geometry object
vbspGeomList[currentGeomIndex] = new VBSPGeometry(bsp_data);
currentGeom = vbspGeomList[currentGeomIndex];
}
// Add the face to the appropriate VBSPGeometry object
currentGeom->addFace(currentModel.first_face + i);
}
}
// Create a top-level group to hold the geometry objects
if (entity_transformed)
{
// Create a matrix transform
MatrixTransform * entityXform = new MatrixTransform();
// Set it up with the entity's transform information (scale the
// position from inches to meters)
Matrixf transMat, rotMat;
Quat roll, yaw, pitch;
transMat.makeTranslate(entity_origin * 0.0254);
pitch.makeRotate(osg::DegreesToRadians(entity_angles.x()),
Vec3f(0.0, 1.0, 0.0));
yaw.makeRotate(osg::DegreesToRadians(entity_angles.y()),
Vec3f(0.0, 0.0, 1.0));
roll.makeRotate(osg::DegreesToRadians(entity_angles.z()),
Vec3f(1.0, 0.0, 0.0));
rotMat.makeRotate(roll * pitch * yaw);
// Set the transform matrix
entityXform->setMatrix(rotMat * transMat);
// Use the transform node as the main entity group
entityGroup = entityXform;
}
else
{
// Create a group to represent the entire entity
entityGroup = new Group();
}
// Iterate over the geometry array and convert each geometry object
// into OSG geometry
for (i = 0; i < numGeoms; i++)
{
// Get the next geometry object (if any)
currentGeom = vbspGeomList[i];
if (currentGeom != NULL)
{
// Convert the BSP geometry to OSG geometry
geomGroup = currentGeom->createGeometry();
// Make sure the geometry converted properly
if (geomGroup.valid())
{
// Set this group's state set
geomGroup->setStateSet(bsp_data->getStateSet(i));
// Add the geometry group to the entity group
entityGroup->addChild(geomGroup.get());
}
}
}
// Return the group we created
return entityGroup;
}
ref_ptr<Group> VBSPEntity::createModelGeometry()
{
std::string modelFile;
ref_ptr<Node> modelNode;
ref_ptr<Group> entityGroup;
// Try to load the model
modelNode = osgDB::readNodeFile(entity_model);
if (modelNode.valid())
{
// Create a group and add the model to it
if (entity_transformed)
{
// Create a matrix transform
MatrixTransform * entityXform = new MatrixTransform();
// Set it up with the entity's transform information (scale
// the position from inches to meters)
Matrixf transMat, rotMat;
Quat roll, yaw, pitch;
transMat.makeTranslate(entity_origin * 0.0254);
pitch.makeRotate(osg::DegreesToRadians(entity_angles.x()),
Vec3f(0.0, 1.0, 0.0));
yaw.makeRotate(osg::DegreesToRadians(entity_angles.y()),
Vec3f(0.0, 0.0, 1.0));
roll.makeRotate(osg::DegreesToRadians(entity_angles.z()),
Vec3f(1.0, 0.0, 0.0));
rotMat.makeRotate(roll * pitch * yaw);
// Set the transform matrix
entityXform->setMatrix(rotMat * transMat);
// Use the transform node as the main entity group
entityGroup = entityXform;
}
else
{
// Create a group to represent the entire entity
entityGroup = new Group();
}
// Add the model node to the group
entityGroup->addChild(modelNode.get());
}
else
{
notify(WARN) << "Couldn't find prop \"" << entity_model << "\".";
notify(WARN) << std::endl;
// Leave the group empty (no model to show)
entityGroup = NULL;
}
return entityGroup;
}
EntityClass VBSPEntity::getClass()
{
return entity_class;
}
bool VBSPEntity::isVisible()
{
return entity_visible;
}
ref_ptr<Group> VBSPEntity::createGeometry()
{
// If we're not a visible entity, we have no geometry
if (!entity_visible)
return NULL;
// Create the geometry for the entity based on the class
if ((entity_class == ENTITY_WORLDSPAWN) ||
(entity_class == ENTITY_FUNC_BRUSH))
{
return createBrushGeometry();
}
else if (entity_class == ENTITY_PROP)
{
return createModelGeometry();
}
// If we get here, we don't handle this kind of entity (yet)
return NULL;
}

View File

@@ -0,0 +1,77 @@
#ifndef __VBSP_ENTITY_H_
#define __VBSP_ENTITY_H_
#include <osg/Group>
#include <osg/Vec3f>
#include "VBSPData.h"
namespace bsp
{
enum EntityClass
{
ENTITY_WORLDSPAWN,
ENTITY_ENV,
ENTITY_FUNC_BRUSH,
ENTITY_PROP,
ENTITY_INFO_DECAL,
ENTITY_ITEM,
ENTITY_OTHER
};
class VBSPEntity
{
protected:
VBSPData * bsp_data;
EntityClass entity_class;
typedef std::pair<std::string, std::string> EntityParameter;
typedef std::map<std::string, std::string> EntityParameters;
EntityParameters entity_parameters;
bool entity_visible;
bool entity_transformed;
int entity_model_index;
std::string entity_model;
osg::Vec3f entity_origin;
osg::Vec3f entity_angles;
osg::ref_ptr<osg::Group> entity_geometry;
void processWorldSpawn();
void processEnv();
void processFuncBrush();
void processProp();
void processInfoDecal();
void processItem();
osg::Vec3f getVector(std::string str);
std::string getToken(std::string str, size_t & index);
void parseParameters(std::string & entityText);
osg::ref_ptr<osg::Group> createBrushGeometry();
osg::ref_ptr<osg::Group> createModelGeometry();
public:
VBSPEntity(std::string & entityText, VBSPData * bspData);
~VBSPEntity();
EntityClass getClass();
bool isVisible();
osg::ref_ptr<osg::Group> createGeometry();
};
}
#endif

View File

@@ -1,17 +1,19 @@
#include <stdlib.h>
#include <osg/Geode>
#include "VBSPGeometry.h"
using namespace osg;
using namespace osgDB;
using namespace bsp;
VBSPGeometry::VBSPGeometry(VBSPReader * reader)
VBSPGeometry::VBSPGeometry(VBSPData * bspData)
{
// Keep track of the reader, as it has all of the data lists that we
// Keep track of the bsp data, as it has all of the data lists that we
// need
vbsp_reader = reader;
bsp_data = bspData;
// Create arrays for the vertex attributes
vertex_array = new Vec3Array();
@@ -249,17 +251,17 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo)
Vec3 texV;
float texVOffset;
float texVScale;
int i, j, k;
unsigned int i, j, k;
double dist, minDist;
int minIndex = 0;
int minIndex;
osg::Vec3 temp;
int edgeIndex;
int currentSurfEdge;
Edge currentEdge;
osg::Vec3 currentVertex;
osg::Vec3 vertices[4];
int firstVertex;
int numEdgeVertices;
unsigned int firstVertex;
unsigned int numEdgeVertices;
double subdivideScale;
osg::Vec3 leftEdge, rightEdge;
osg::Vec3 leftEdgeStep, rightEdgeStep;
@@ -276,8 +278,8 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo)
// Get the texture info for this face
currentTexInfo = vbsp_reader->getTexInfo(face.texinfo_index);
currentTexData = vbsp_reader->getTexData(currentTexInfo.texdata_index);
currentTexInfo = bsp_data->getTexInfo(face.texinfo_index);
currentTexData = bsp_data->getTexData(currentTexInfo.texdata_index);
// Get the texture vectors and offsets. These are used to calculate
// texture coordinates
@@ -290,6 +292,10 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo)
currentTexInfo.texture_vecs[1][2]);
texVOffset = currentTexInfo.texture_vecs[1][3];
// Scale the texture vectors from inches to meters
texU *= 39.37;
texV *= 39.37;
// Get the size of the texture involved, as the planar texture projection
// assumes non-normalized texture coordinates
texUScale = 1.0 / (float)currentTexData.texture_width;
@@ -304,16 +310,16 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo)
// Look up the edge specified by the surface edge index, the
// index might be negative (see below), so take the absolute
// value
currentSurfEdge = vbsp_reader->getSurfaceEdge(edgeIndex);
currentEdge = vbsp_reader->getEdge(abs(currentSurfEdge));
currentSurfEdge = bsp_data->getSurfaceEdge(edgeIndex);
currentEdge = bsp_data->getEdge(abs(currentSurfEdge));
// The sign of the surface edge index specifies which vertex is
// "first" for this face. A negative index means the edge should
// be flipped, and the second vertex treated as the first
if (currentSurfEdge < 0)
currentVertex = vbsp_reader->getVertex(currentEdge.vertex[1]);
currentVertex = bsp_data->getVertex(currentEdge.vertex[1]);
else
currentVertex = vbsp_reader->getVertex(currentEdge.vertex[0]);
currentVertex = bsp_data->getVertex(currentEdge.vertex[0]);
// Add the vertex to the array
vertices[i] = currentVertex;
@@ -328,7 +334,7 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo)
for (i = 0; i < 4; i++)
{
// Calculate the distance of the start position from this vertex
dist = (vertices[i] - dispInfo.start_position).length();
dist = (vertices[i] - dispInfo.start_position * 0.0254).length();
// If this is the smallest distance we've seen, remember it
if (dist < minDist)
@@ -388,14 +394,15 @@ void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo)
// Get the displacement info for this vertex
dispVertIndex = dispInfo.disp_vert_start;
dispVertIndex += i * numEdgeVertices + j;
dispVertInfo = vbsp_reader->getDispVertex(dispVertIndex);
dispVertInfo = bsp_data->getDispVertex(dispVertIndex);
// Calculate the flat vertex
flatVertex = leftEnd + (leftRightStep * (double) j);
// Calculate the displaced vertex
dispVertex =
dispVertInfo.displace_vec * dispVertInfo.displace_dist;
dispVertInfo.displace_vec *
(dispVertInfo.displace_dist * 0.0254);
dispVertex += flatVertex;
// Add the vertex to the displaced vertex array
@@ -510,14 +517,14 @@ void VBSPGeometry::addFace(int faceIndex)
// Make sure this face is not "on node" (an internal node of the BSP tree).
// These faces are not used for visible geometry
currentFace = vbsp_reader->getFace(faceIndex);
currentFace = bsp_data->getFace(faceIndex);
// See if this is a displacement surface
if (currentFace.dispinfo_index != -1)
{
// Get the displacement info
currentDispInfo =
vbsp_reader->getDispInfo(currentFace.dispinfo_index);
bsp_data->getDispInfo(currentFace.dispinfo_index);
// Generate the displacement surface
createDispSurface(currentFace, currentDispInfo);
@@ -525,13 +532,13 @@ void VBSPGeometry::addFace(int faceIndex)
else
{
// Get the face normal, using the plane information
normal = vbsp_reader->getPlane(currentFace.plane_index).plane_normal;
normal = bsp_data->getPlane(currentFace.plane_index).plane_normal;
if (currentFace.plane_side != 0)
normal = -normal;
// Get the texture info and data structures
currentTexInfo = vbsp_reader->getTexInfo(currentFace.texinfo_index);
currentTexData = vbsp_reader->getTexData(currentTexInfo.texdata_index);
currentTexInfo = bsp_data->getTexInfo(currentFace.texinfo_index);
currentTexData = bsp_data->getTexData(currentTexInfo.texdata_index);
// Get the texture vectors and offsets. These are used to calculate
// texture coordinates
@@ -544,6 +551,10 @@ void VBSPGeometry::addFace(int faceIndex)
currentTexInfo.texture_vecs[1][2]);
texVOffset = currentTexInfo.texture_vecs[1][3];
// Scale the texture vectors from inches to meters
texU *= 39.37;
texV *= 39.37;
// Get the texture size, as the planar texture projection results in
// non-normalized texture coordinates
texUScale = 1.0 / (float)currentTexData.texture_width;
@@ -562,16 +573,16 @@ void VBSPGeometry::addFace(int faceIndex)
// Look up the edge specified by the surface edge index, the
// index might be negative (see below), so take the absolute
// value
currentSurfEdge = vbsp_reader->getSurfaceEdge(edgeIndex);
currentEdge = vbsp_reader->getEdge(abs(currentSurfEdge));
currentSurfEdge = bsp_data->getSurfaceEdge(edgeIndex);
currentEdge = bsp_data->getEdge(abs(currentSurfEdge));
// The sign of the surface edge index specifies which vertex is
// "first" for this face. A negative index means the edge should
// be flipped, and the second vertex treated as the first
if (currentSurfEdge < 0)
currentVertex = vbsp_reader->getVertex(currentEdge.vertex[1]);
currentVertex = bsp_data->getVertex(currentEdge.vertex[1]);
else
currentVertex = vbsp_reader->getVertex(currentEdge.vertex[0]);
currentVertex = bsp_data->getVertex(currentEdge.vertex[0]);
// Add the vertex to the array
vertex_array->push_back(currentVertex);

View File

@@ -6,7 +6,7 @@
#include <osg/Array>
#include <osg/Geometry>
#include "VBSPReader.h"
#include "VBSPData.h"
namespace bsp
@@ -17,7 +17,7 @@ class VBSPGeometry
{
protected:
VBSPReader * vbsp_reader;
VBSPData * bsp_data;
osg::ref_ptr<osg::Vec3Array> vertex_array;
osg::ref_ptr<osg::Vec3Array> normal_array;
@@ -39,7 +39,7 @@ class VBSPGeometry
public:
VBSPGeometry(VBSPReader * reader);
VBSPGeometry(VBSPData * bspData);
virtual ~VBSPGeometry();
void addFace(int faceIndex);

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@
#include <osg/Geometry>
#include <osg/Matrixd>
#include <osg/Node>
#include <osg/Object>
#include <osg/StateSet>
@@ -11,6 +10,7 @@
#include <osgDB/FileNameUtils>
#include <osg/Referenced>
#include "VBSPData.h"
namespace bsp
{
@@ -178,107 +178,52 @@ struct Header
};
struct Plane
struct GameHeader
{
osg::Vec3f plane_normal;
float origin_dist;
int type;
int num_lumps;
// This is followed by this many GameLump entries (see below)
};
struct GameLump
{
int lump_id;
unsigned short lump_flags;
unsigned short lump_version;
int lump_offset;
int lump_length;
};
// This is the ID for the static prop game lump
const int STATIC_PROP_ID = (('s'<<24)+('p'<<16)+('r'<<8)+'p');
struct StaticPropModelNames
{
int num_model_names;
// This is followed by this many names, each 128 characters long
};
struct Edge
struct StaticPropLeaves
{
unsigned short vertex[2];
int num_leaf_entries;
// This is followed by this many unsigned shorts, indicating which BSP
// leaves this prop occupies
};
struct Face
struct StaticProps
{
unsigned short plane_index;
unsigned char plane_side;
unsigned char on_node;
int first_edge;
short num_edges;
short texinfo_index;
short dispinfo_index;
short surface_fog_volume_id;
unsigned char styles[4];
int light_offset;
float face_area;
int lightmap_texture_mins_in_luxels[2];
int lightmap_texture_size_in_luxels[2];
int original_face;
unsigned short num_primitives;
unsigned short first_primitive_id;
unsigned int smoothing_groups;
};
int num_static_props;
struct TexInfo
{
float texture_vecs[2][4];
float lightmap_vecs[2][4];
int texture_flags;
int texdata_index;
};
struct TexData
{
osg::Vec3f texture_reflectivity;
int name_string_table_id;
int texture_width;
int texture_height;
int view_width;
int view_height;
};
struct DisplaceSubNeighbor
{
unsigned short neighbor_index;
unsigned char neighbor_orient;
unsigned char local_span;
unsigned char neighbor_span;
};
struct DisplaceNeighbor
{
DisplaceSubNeighbor sub_neighbors[2];
};
struct DisplaceCornerNeighbor
{
unsigned short neighbor_indices[4];
unsigned char neighbor_count;
};
struct DisplaceInfo
{
osg::Vec3f start_position;
int disp_vert_start;
int disp_tri_start;
int power;
int min_tesselation;
float smooth_angle;
int surface_contents;
unsigned short map_face;
int lightmap_alpha_start;
int lightmap_sample_pos_start;
DisplaceNeighbor edge_neighbors[4];
DisplaceCornerNeighbor corner_neighbors[4];
unsigned int allowed_verts[10];
};
struct DisplacedVertex
{
osg::Vec3f displace_vec;
float displace_dist;
float alpha_blend;
// This is followed by this many StaticProp entries (see VBSPData.h), note
// that there are two possible StaticProp versions, depending on the
// version of the GameLump
};
@@ -288,48 +233,17 @@ protected:
std::string map_name;
VBSPData * bsp_data;
osg::ref_ptr<osg::Node> root_node;
char ** entity_list;
int num_entities;
Plane * plane_list;
int num_planes;
osg::Vec3f * vertex_list;
int num_vertices;
Edge * edge_list;
int num_edges;
int * surface_edges;
int num_surf_edges;
Face * face_list;
int num_faces;
TexInfo * texinfo_list;
int num_texinfo_entries;
TexData * texdata_list;
int num_texdata_entries;
char * texdata_string;
int * texdata_string_table;
int num_texdata_string_table_entries;
char ** texdata_string_data;
int num_texdata_strings;
DisplaceInfo * dispinfo_list;
int num_dispinfo_entries;
DisplacedVertex * displaced_vertex_list;
int num_displaced_vertices;
void writeBlanks(int count) const;
void processEntities(std::istream & str, int offset, int length);
void processModels(std::istream & str, int offset, int length);
void processPlanes(std::istream & str, int offset, int length);
void processVertices(std::istream & str, int offset, int length);
void processEdges(std::istream & str, int offset, int length);
@@ -339,15 +253,15 @@ protected:
void processTexData(std::istream & str, int offset, int length);
void processTexDataStringTable(std::istream & str, int offset,
int length);
void processTexDataStringData(std::istream & str, int offset,
int length);
void processTexDataStringData(std::istream & str, int offset, int length);
void processDispInfo(std::istream & str, int offset, int length);
void processDispVerts(std::istream & str, int offset, int length);
void processGameData(std::istream & str, int offset, int length);
void processStaticProps(std::istream & str, int offset, int length,
int lumpVersion);
std::string getToken(std::string str, const char * delim,
std::string::size_type & index);
std::string findFileIgnoreCase(std::string filePath);
size_t & index);
osg::ref_ptr<osg::StateSet> createBlendShader(osg::Texture * tex1,
osg::Texture * tex2);
@@ -361,18 +275,6 @@ public:
VBSPReader();
virtual ~VBSPReader();
const char * getEntity(int index) const;
const Plane & getPlane(int index) const;
const osg::Vec3f & getVertex(int index) const;
const Edge & getEdge(int index) const;
const int getSurfaceEdge(int index) const;
const Face & getFace(int index) const;
const TexInfo & getTexInfo(int index) const;
const TexData & getTexData(int index) const;
const char * getTexDataString(int index) const;
const DisplaceInfo & getDispInfo(int index) const;
const DisplacedVertex & getDispVertex(int index) const;
bool readFile(const std::string & file);

View File

@@ -21,20 +21,40 @@ is GCFScape, available at http://www.nemesis.thewavelength.net/index.php?p=26
The plugin expects the maps and materials to be arranged as they are in the
.gcf file (maps in maps/ materials and textures in materials/). Only the
maps/ and materials/ directories are used by this plugin (models aren't loaded
yet, see below).
maps/ and materials/ directories are used by this plugin. Models (in the
models/ directory) are handled by the .mdl plugin, but the .bsp plugin
typically will read model files as part of loading a .bsp map.
Make sure to preserve the file and directory structure as it is in the .gcf
files. The standard osgDB search routines don't work, because the .bsp
file and .vmt files often mix case when referring to materials, textures, etc.
The regular osgDB search routines can't do a fully case-insensitive search
for a file, so an explicit, special-purpose search is performed by the plugin
instead.
It is important to preserve the file and directory structure as it is in the
.gcf files, although you can merge the data from several files (different
games) together. For example, you can extract the models/ directory from
source models.gcf, the materials/ directory from source materials.gcf, and the
maps/, models/, and materials/ directories from half-life 2 deathmatch.gcf and
combine them together into a single parent directory (called "hl2data/", for
example). This arrangement will let you load any of the HL2 deathmatch maps,
with all of the props and materials that might be needed.
If you're confused, here's a lame ASCII art drawing to confuse you even more:
hl2data
|
+----maps
|
+----materials
|
+----models
If you want to use the OSGFILEPATH environment variable to let OSG search for
maps or models, point it to the parent directory ("hl2data" in the example
above), then load your map like this:
osgviewer maps/dm_runoff.bsp
What Works
----------
All brush geometry and faces.
All visible brush geometry and faces (except sky boxes).
Displacement surfaces.
@@ -42,6 +62,13 @@ Textures (including blended textures on displacement surfaces). This makes
use of the VTF Reader plugin which was submitted at the same time as this
plugin.
Props (including static props embedded into the map file, and dynamic and
physics props as loaded by the mdl plugin). Note that because of Source's
physics engine, you might see some of the physics props suspended in the air
when loading a map. The level designer placed them there so they fall to
the ground when the level starts. Presumably, this is quicker and safer than
trying to get them positioned exactly on the ground.
What Doesn't Work (yet)
-----------------------
@@ -62,18 +89,25 @@ seems to work well enough for now.
Water. Water shaders in the Source engine are a lot more complicated than
the generic lightmapped and vertex lit shaders. Currently, water surfaces just
show up solid white.
show up mostly white. At the very least, this needs environment map support to
work properly.
Props (both static and physics). This is a big one, as props often contribute
to the overall flow and feeling of a map. Unfortunately, this requires yet
another plugin to load the .mdl/.vvd/.vtx files that store Source models.
These formats aren't nearly as well documented as the .bsp format, which is the
main reason they're not done yet.
Sky boxes. These were left out by design (they tended to get in the way
when doing the work required by the sponsor). These could easily be put
back in, and this would probably make a good ReaderWriter option.
World lights (light sources). Should be simple to read, but I don't know
how many you'll find on a given map. If there are a lot of them, you'll
have to be creative on how they're rendered.
Environmental effects (fires, dust clouds, etc.). Each one would likely
require specific code.
Certain classes of props (collectable items, weapons, etc.) are not loaded
right now. With these props, there is an explicit mapping between the prop's
object class and the model that gets loaded for it. I just haven't set up
these mappings yet.
HDR materials, bump maps, detail props, and other eye-candy. Implement it if
you want! :-)

View File

@@ -0,0 +1,48 @@
#include "BodyPart.h"
using namespace mdl;
BodyPart::BodyPart(MDLBodyPart * myPart)
{
// Save the body part information
my_body_part = myPart;
}
BodyPart::~BodyPart()
{
// Clean up the associated data
delete my_body_part;
}
MDLBodyPart * BodyPart::getBodyPart()
{
return my_body_part;
}
void BodyPart::addModel(Model * newModel)
{
// Add the new node to our list
part_models.push_back(newModel);
}
int BodyPart::getNumModels()
{
return part_models.size();
}
Model * BodyPart::getModel(int partIndex)
{
if ((partIndex < 0) || (partIndex >= part_models.size()))
return NULL;
else
return part_models[partIndex];
}

View File

@@ -0,0 +1,50 @@
#ifndef __BODY_PART_H_
#define __BODY_PART_H_
#include <vector>
#include "Model.h"
namespace mdl
{
struct MDLBodyPart
{
int mdl_name_index;
int num_models;
int body_part_base;
int model_offset;
};
class BodyPart
{
protected:
typedef std::vector<Model *> ModelList;
MDLBodyPart * my_body_part;
ModelList part_models;
public:
BodyPart(MDLBodyPart * myPart);
virtual ~BodyPart();
MDLBodyPart * getBodyPart();
void addModel(Model * newModel);
int getNumModels();
Model * getModel(int modelIndex);
};
}
#endif

View File

@@ -0,0 +1,26 @@
SET(TARGET_SRC
ReaderWriterMDL.cpp
BodyPart.cpp
Mesh.cpp
Model.cpp
MDLReader.cpp
MDLRoot.cpp
VTXReader.cpp
VVDReader.cpp
)
SET(TARGET_H
ReaderWriterMDL.h
BodyPart.h
Mesh.h
Model.h
MDLLimits.h
MDLReader.h
MDLRoot.h
VTXReader.h
VVDReader.h
)
#### end var setup ###
SETUP_PLUGIN(mdl)

View File

@@ -0,0 +1,22 @@
#ifndef __MDL_LIMITS_H_
#define __MDL_LIMITS_H_
namespace mdl
{
// Maximum number of LODs per model
const int MAX_LODS = 8;
// Maximum number of bones per vertex
const int MAX_BONES_PER_VERTEX = 3;
}
#endif

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;
}

View File

@@ -0,0 +1,193 @@
#ifndef __MDL_READER_H_
#define __MDL_READER_H_
#include <osg/Geometry>
#include <osg/Matrixd>
#include <osg/Node>
#include <osg/Object>
#include <osg/StateSet>
#include <osgDB/Registry>
#include <osgDB/FileNameUtils>
#include <osg/Referenced>
#include "MDLLimits.h"
#include "MDLRoot.h"
namespace mdl
{
// The magic number for a Valve MDL file is 'IDST' in little-endian
// order
const int MDL_MAGIC_NUMBER = (('T'<<24)+('S'<<16)+('D'<<8)+'I');
struct MDLHeader
{
int magic_number;
int mdl_version;
int check_sum;
char mdl_name[64];
int mdl_length;
osg::Vec3 eye_position;
osg::Vec3 illum_position;
osg::Vec3 hull_min;
osg::Vec3 hull_max;
osg::Vec3 view_bbox_min;
osg::Vec3 view_bbox_max;
int mdl_flags;
int num_bones;
int bone_offset;
int num_bone_controllers;
int bone_controller_offset;
int num_hitbox_sets;
int hitbox_set_offset;
int num_local_animations;
int local_animation_offset;
int num_local_sequences;
int local_sequence_offset;
mutable int activity_list_version;
mutable int events_offseted;
int num_textures;
int texture_offset;
int num_texture_paths;
int texture_path_offset;
int num_skin_refs;
int num_skin_families;
int skin_offset;
int num_body_parts;
int body_part_offset;
int num_local_attachments;
int local_attachment_offset;
int num_local_nodes;
int local_node_offset;
int local_node_name_offset;
int num_flex_desc;
int flex_desc_offset;
int num_flex_controllers;
int flex_controller_offset;
int num_flex_rules;
int flex_rule_offset;
int num_ik_chains;
int ik_chain_offset;
int num_mouths;
int mouth_offset;
int num_local_pose_params;
int local_pose_param_offset;
int surface_prop_offset;
int key_value_offset;
int key_value_size;
int num_local_ik_autoplay_locks;
int local_ik_autoplay_lock_offset;
float mdl_mass;
int mdl_contents;
int num_include_models;
int include_model_offset;
// Originally a mutable void * (changed for portability)
mutable int virtual_model;
int anim_block_name_offset;
int num_anim_blocks;
int anim_block_offset;
// Originally a mutable void * (changed for portability)
mutable int anim_block_model;
int bone_table_by_name_offset;
// Originally both void * (changed for portability)
int vertex_base;
int offset_base;
u_char const_direction_light_dot;
u_char root_lod;
u_char unused_byte[2];
int zero_frame_cache_offset;
int unused_fields[2];
};
struct MDLTexture
{
int tex_name_offset;
int tex_flags;
int tex_used;
int unused_1;
// Originally both mutable void * (changed for portability)
mutable int tex_material;
mutable int client_material;
int unused_array[10];
};
class MDLReader
{
protected:
std::string mdl_name;
osg::ref_ptr<osg::Node> root_node;
typedef std::vector<std::string> StringList;
StringList texture_paths;
typedef std::vector< osg::ref_ptr<osg::StateSet> > StateSetList;
StateSetList state_sets;
std::string getToken(std::string str, const char * delim, size_t & index);
std::string findFileIgnoreCase(std::string filePath);
osg::ref_ptr<osg::Texture> readTextureFile(std::string textureName);
osg::ref_ptr<osg::StateSet> readMaterialFile(std::string mtlName);
BodyPart * processBodyPart(std::istream * str, int offset);
Model * processModel(std::istream * str, int offset);
Mesh * processMesh(std::istream * str, int offset);
public:
MDLReader();
virtual ~MDLReader();
bool readFile(const std::string & file);
osg::ref_ptr<osg::Node> getRootNode();
};
}
#endif

View File

@@ -0,0 +1,38 @@
#include "MDLRoot.h"
using namespace mdl;
MDLRoot::MDLRoot()
{
}
MDLRoot::~MDLRoot()
{
}
void MDLRoot::addBodyPart(BodyPart * newPart)
{
// Add the new part to our list
body_parts.push_back(newPart);
}
int MDLRoot::getNumBodyParts()
{
return body_parts.size();
}
BodyPart * MDLRoot::getBodyPart(int partIndex)
{
if ((partIndex < 0) || (partIndex >= body_parts.size()))
return NULL;
else
return body_parts[partIndex];
}

View File

@@ -0,0 +1,37 @@
#ifndef __MDL_ROOT_H_
#define __MDL_ROOT_H_
#include <vector>
#include "BodyPart.h"
namespace mdl
{
class MDLRoot
{
protected:
typedef std::vector<BodyPart *> BodyPartList;
BodyPartList body_parts;
public:
MDLRoot();
virtual ~MDLRoot();
void addBodyPart(BodyPart * newPart);
int getNumBodyParts();
BodyPart * getBodyPart(int partIndex);
};
}
#endif

View File

@@ -0,0 +1,97 @@
Source Engine MDL reader for OSG
by Jason Daly
Overview
--------
This plugin allows .mdl files from games that make use of Valve's Source
engine (Half Life 2, etc) to be loaded by OSG.
I've tested this plugin with dozens of on several HL2 models, as well as some
3rd party models.
Using the Plugin
----------------
If you want to load models from the original Source engine games, you'll need
to extract the relevant models and materials from the .gcf files that come
with the game. A good tool for this is GCFScape, available at
http://www.nemesis.thewavelength.net/index.php?p=26
The plugin expects the models and materials to be arranged as they are in the
.gcf files (models in models/ materials and textures in materials/). Only the
models/ and materials/ directories are used by this plugin. Note that the
.mdl plugin typically will need to read additional data from companion .vvd
and .vtx files, as well as textures from other files in the materials/
directory.
It is important to preserve the file and directory structure as it is in the
.gcf files, although you can merge the data from several files (different
games) together. For example, you can extract the models/ directory from
source models.gcf, the materials/ directory from source materials.gcf, and the
maps/, models/, and materials/ directories from half-life 2 deathmatch.gcf and
combine them together into a single parent directory (called "hl2data/", for
example). This arrangement will let you load any of the HL2 deathmatch maps,
with all of the props and materials that might be needed.
If you're confused, here's a lame ASCII art drawing to confuse you even more:
hl2data
|
+----maps
|
+----materials
|
+----models
If you want to use the OSGFILEPATH environment variable to let OSG search for
maps or models, point it to the parent directory ("hl2data" in the example
above), then load your model like this:
osgviewer models/alyx.mdl
What Works
----------
All geometry and textures.
Some models have multiple sub-models in them (for example, a door model might
have multiple variations of handles). These are supported with a switch,
but you'll have to find it and manipulate it yourself (I can't figure out
how the Source engine selects which sub-model to show).
What Doesn't Work (yet)
-----------------------
Skeletons and bone animations. My guess is that this would be possible using
osgAnimation, but I didn't have time to do it for the first cut.
Facial animations. Would require support for vertex morphing (I don't know
if osgAnimation provides this yet or not).
Physics. There is no OSG infrastructure yet, and I can't find much information
on the .phy file format that models use for physics calculations. Rag-doll
physics obviously doesn't work either.
Eyes. This is one of the more ugly artifacts you'll see if you load a
character model. Only the whites of the eyes are drawn (no iris or pupil).
The eyes (as well as the teeth) use special shaders that I didn't have time to
figure out. This is ugly enough that I might come back to it soon.
Acknowledgements
----------------
This plugin was written for some real-world work I'm doing at the University
of Central Florida Institute for Simulation and Training. I want to thank
our sponsors for funding our work and allowing me to contribute this code to
the OSG community.
Portions of the code borrow heavily from the Source SDK. Most of the
file format reading came from the header files for studio models, so thanks to
Valve for making much of that code public.
Of course, this code would be pointless without the Open Scene Graph and
all of its contributors.

View File

@@ -0,0 +1,46 @@
#include "Mesh.h"
using namespace mdl;
Mesh::Mesh(MDLMesh * myMesh)
{
// Save the mesh information
my_mesh = myMesh;
// Initialize the state set to NULL
state_set = NULL;
}
Mesh::~Mesh()
{
// Clean up the associated data
delete my_mesh;
}
void Mesh::setStateSet(osg::StateSet * stateSet)
{
state_set = stateSet;
}
osg::StateSet * Mesh::getStateSet()
{
return state_set.get();
}
MDLMesh * Mesh::getMesh()
{
return my_mesh;
}
int Mesh::getNumLODVertices(int lodNum)
{
return my_mesh->vertex_data.num_lod_vertices[lodNum];
}

76
src/osgPlugins/mdl/Mesh.h Normal file
View File

@@ -0,0 +1,76 @@
#ifndef __MESH_H_
#define __MESH_H_
#include <osg/Vec3f>
#include <osg/StateSet>
#include "MDLLimits.h"
namespace mdl
{
struct MDLMeshVertexData
{
// Used by the Source engine for cache purposes. This value is allocated
// in the file, but no meaningful data is stored there
int model_vertex_data_ptr;
// Indicates the number of vertices used by each LOD of this mesh
int num_lod_vertices[MAX_LODS];
};
struct MDLMesh
{
int material_index;
int model_index;
int num_vertices;
int vertex_offset;
int num_flexes;
int flex_offset;
int material_type;
int material_param;
int mesh_id;
osg::Vec3f mesh_center;
MDLMeshVertexData vertex_data;
int unused_array[8];
};
class Mesh
{
protected:
MDLMesh * my_mesh;
osg::ref_ptr<osg::StateSet> state_set;
public:
Mesh(MDLMesh * myMesh);
virtual ~Mesh();
void setStateSet(osg::StateSet * stateSet);
osg::StateSet * getStateSet();
MDLMesh * getMesh();
int getNumLODVertices(int lodNum);
};
}
#endif

View File

@@ -0,0 +1,56 @@
#include "Model.h"
#include "VVDReader.h"
using namespace mdl;
Model::Model(MDLModel * myModel)
{
// Save the model information
my_model = myModel;
}
Model::~Model()
{
// Clean up the associated data
delete my_model;
}
MDLModel * Model::getModel()
{
return my_model;
}
int Model::getVertexBase()
{
// Return the base index for this model's vertices
return my_model->vertex_index / sizeof(VVDVertex);
}
void Model::addMesh(Mesh * newMesh)
{
// Add the new node to our list
model_meshes.push_back(newMesh);
}
int Model::getNumMeshes()
{
return model_meshes.size();
}
Mesh * Model::getMesh(int meshIndex)
{
if ((meshIndex < 0) || (meshIndex >= model_meshes.size()))
return NULL;
else
return model_meshes[meshIndex];
}

View File

@@ -0,0 +1,76 @@
#ifndef __MODEL_H_
#define __MODEL_H_
#include <vector>
#include "Mesh.h"
namespace mdl
{
struct MDLModelVertexData
{
// No useful values are stored in the file for this structure, but we
// need the size to be right so we can properly read subsequent models
// from the file
int vertex_data_ptr;
int tangent_data_ptr;
};
struct MDLModel
{
char model_name[64];
int model_type;
float bounding_radius;
int num_meshes;
int mesh_offset;
int num_vertices;
int vertex_index;
int tangents_index;
int num_attachments;
int attachment_offset;
int num_eyeballs;
int eyeball_offset;
MDLModelVertexData vertex_data;
int unused_array[8];
};
class Model
{
protected:
typedef std::vector<Mesh *> MeshList;
MDLModel * my_model;
MeshList model_meshes;
public:
Model(MDLModel * myModel);
virtual ~Model();
MDLModel * getModel();
int getVertexBase();
void addMesh(Mesh * newMesh);
int getNumMeshes();
Mesh * getMesh(int meshIndex);
};
}
#endif

View File

@@ -0,0 +1,72 @@
#include <osg/Node>
#include <osg/Notify>
#include <osgDB/Registry>
#include <osgDB/FileUtils>
#include <osgDB/ReadFile>
#include "ReaderWriterMDL.h"
#include "MDLReader.h"
using namespace mdl;
using namespace osg;
using namespace osgDB;
const char* ReaderWriterMDL::className() const
{
// Return a description of this class
return "Valve/Source Engine MDL Reader";
}
bool ReaderWriterMDL::acceptsExtension(const std::string& extension) const
{
// If the extension is empty or "mdl", we accept it
return osgDB::equalCaseInsensitive(extension, "mdl") || extension.empty();
}
ReaderWriter::ReadResult ReaderWriterMDL::readNode(
const std::string& file,
const ReaderWriter::Options* options) const
{
MDLReader * mdlReader;
ref_ptr<Node> result;
// See if we handle this kind of file
if (!acceptsExtension(osgDB::getFileExtension(file)))
return ReadResult::FILE_NOT_HANDLED;
// See if we can find the requested file
std::string fileName = osgDB::findDataFile(file, options, CASE_INSENSITIVE);
if (fileName.empty())
return ReadResult::FILE_NOT_FOUND;
// Read the file (pass the base name and not the file that was found, this
// allows us to also find the .vvd and .vtx files without the leading
// path confusing things)
mdlReader = new MDLReader();
if (mdlReader->readFile(file))
{
// Get the results of our read
result = mdlReader->getRootNode();
// Clean up the reader
delete mdlReader;
// Return the results
return ReadResult(result.get());
}
else
{
// Clean up the reader
delete mdlReader;
// Return the error
return ReadResult::ERROR_IN_READING_FILE;
}
}
REGISTER_OSGPLUGIN(mdl, ReaderWriterMDL)

View File

@@ -0,0 +1,27 @@
#ifndef __READERWRITER_MDL_H_
#define __READERWRITER_MDL_H_
#include <osgDB/Registry>
#include <osgDB/FileNameUtils>
namespace mdl
{
class ReaderWriterMDL : public osgDB::ReaderWriter
{
public:
virtual const char* className() const;
virtual bool acceptsExtension(const std::string& extension) const;
virtual ReadResult readNode(const std::string& file,
const Options* options) const;
};
}
#endif

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;
}

View File

@@ -0,0 +1,222 @@
#ifndef __VTX_READER_H_
#define __VTX_READER_H_
#include <osg/Array>
#include <osg/Geometry>
#include <osg/Matrixd>
#include <osg/Node>
#include <osg/Object>
#include <osg/StateSet>
#include <osgDB/Registry>
#include <osgDB/FileNameUtils>
#include <osg/Referenced>
#include "MDLLimits.h"
#include "MDLRoot.h"
#include "VVDReader.h"
namespace mdl
{
struct VTXHeader
{
int vtx_version;
int vertex_cache_size;
unsigned short max_bones_per_strip;
unsigned short max_bones_per_tri;
int max_bones_per_vertex;
int check_sum;
int num_lods;
int mtl_replace_list_offset;
int num_body_parts;
int body_part_offset;
};
struct VTXMaterialReplacementList
{
int num_replacements;
int replacement_offset;
};
struct VTXMaterialReplacment
{
short material_id;
int replacement_material_name_offset;
};
struct VTXBodyPart
{
int num_models;
int model_offset;
};
struct VTXModel
{
int num_lods;
int lod_offset;
};
struct VTXModelLOD
{
int num_meshes;
int mesh_offset;
float switch_point;
};
enum VTXMeshFlags
{
MESH_IS_TEETH = 0x01,
MESH_IS_EYES = 0x02
};
struct VTXMesh
{
int num_strip_groups;
int strip_group_offset;
unsigned char mesh_flags;
};
// Can't rely on sizeof() because Valve explicitly packs these structures to
// 1-byte alignment in the file, which isn't portable
const int VTX_MESH_SIZE = 9;
enum VTXStripGroupFlags
{
STRIP_GROUP_IS_FLEXED = 0x01,
STRIP_GROUP_IS_HW_SKINNED = 0x02,
STRIP_GROUP_IS_DELTA_FLEXED = 0x04
};
struct VTXStripGroup
{
int num_vertices;
int vertex_offset;
int num_indices;
int index_offset;
int num_strips;
int strip_offset;
unsigned char strip_group_flags;
};
// Can't rely on sizeof() because Valve explicitly packs these structures to
// 1-byte alignment in the file, which isn't portable
const int VTX_STRIP_GROUP_SIZE = 25;
enum VTXStripFlags
{
STRIP_IS_TRI_LIST = 0x01,
STRIP_IS_TRI_STRIP = 0x02
};
struct VTXStrip
{
int num_indices;
int index_offset;
int num_vertices;
int vertex_offset;
short num_bones;
unsigned char strip_flags;
int num_bone_state_changes;
int bone_state_change_offset;
};
// Can't rely on sizeof() because Valve explicitly packs these structures to
// 1-byte alignment in the .vtx file, which isn't portable
const int VTX_STRIP_SIZE = 27;
struct VTXVertex
{
unsigned char bone_weight_index[MAX_BONES_PER_VERTEX];
unsigned char num_bones;
short orig_mesh_vertex_id;
char bone_id[MAX_BONES_PER_VERTEX];
};
// Can't rely on sizeof() because Valve explicitly packs these structures to
// 1-byte alignment in the .vtx file, which isn't portable
const int VTX_VERTEX_SIZE = 9;
struct VTXBoneStateChange
{
int hardware_id;
int new_bone_id;
};
class VTXReader
{
protected:
std::string vtx_name;
VVDReader * vvd_reader;
MDLRoot * mdl_root;
osg::ref_ptr<osg::Node> model_root;
osg::ref_ptr<osg::Group> processBodyPart(std::istream * str,
int offset,
BodyPart * currentPart);
osg::ref_ptr<osg::Group> processModel(std::istream * str,
int offset,
Model * currentModel);
osg::ref_ptr<osg::Group> processLOD(int lodNum, float * distance,
std::istream * str,
int offset,
Model * currentModel);
osg::ref_ptr<osg::Geode> processMesh(int lodNum,
std::istream * str,
int offset, int vertexOffset);
osg::ref_ptr<osg::Geometry> processStripGroup(int lodNum,
std::istream * str,
int offset,
int vertexOffset);
osg::ref_ptr<osg::PrimitiveSet> processStrip(unsigned short * indexArray,
std::istream * str,
int offset);
public:
VTXReader(VVDReader * vvd, MDLRoot * mdlRoot);
virtual ~VTXReader();
bool readFile(const std::string & file);
osg::ref_ptr<osg::Node> getModel();
};
}
#endif

View File

@@ -0,0 +1,161 @@
#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 <string.h>
#include "VVDReader.h"
using namespace mdl;
using namespace osg;
using namespace osgDB;
VVDReader::VVDReader()
{
// Initialize the vertex buffer arrays
memset(vertex_buffer, 0, sizeof(vertex_buffer));
memset(vertex_buffer_size, 0, sizeof(vertex_buffer_size));
}
VVDReader::~VVDReader()
{
int i;
// Clean up the vertex buffer arrays
for (i = 0; i < MAX_LODS; i++)
delete [] vertex_buffer[i];
}
bool VVDReader::readFile(const std::string & file)
{
osgDB::ifstream * vvdFile;
VVDHeader header;
int vertIndex;
int i, j, k;
// Remember the map name
vvd_name = getStrippedName(file);
vvdFile = new osgDB::ifstream(file.c_str(), std::ios::binary);
if (!vvdFile)
{
notify(NOTICE) << "Vertex data file not found" << std::endl;
return false;
}
// Read the header
memset(&header, 0xcd, sizeof(VVDHeader));
vvdFile->read((char *) &header, sizeof(VVDHeader));
// Make sure the file is a valid Valve VVD file
if (header.magic_number != VVD_MAGIC_NUMBER)
{
notify(NOTICE) << "Vertex data file not valid" << std::endl;
return false;
}
// Make sure the version is one that we handle
// TODO: Not sure which versions are valid yet
// Read the fixup table
fixup_table = new VVDFixupEntry[header.num_fixups];
vvdFile->seekg(header.fixup_table_offset);
for (i = 0; i < header.num_fixups; i++)
vvdFile->read((char *) &fixup_table[i], sizeof(VVDFixupEntry));
// Create the vertex buffers
for (i = 0; i < header.num_lods; i++)
{
// Create the vertex buffer for this LOD
vertex_buffer[i] = new VVDVertex[header.num_lod_verts[i]];
vertex_buffer_size[i] = header.num_lod_verts[i];
// See if this model needs fixups
if (header.num_fixups > 0)
{
// Scan the fixup table and apply any fixups at this LOD
vertIndex = 0;
for (j = 0; j < header.num_fixups; j++)
{
// Skip this entry if the LOD number is lower (more detailed)
// than the LOD we're working on
if (fixup_table[j].lod_number >= i)
{
// Seek to the vertex indicated by the fixup table entry
vvdFile->seekg(header.vertex_data_offset +
fixup_table[j].source_vertex_id *
sizeof(VVDVertex));
// Read the number of vertices specified
vvdFile->read((char *) &vertex_buffer[i][vertIndex],
fixup_table[j].num_vertices *
sizeof(VVDVertex));
// Advance the target index
vertIndex += fixup_table[j].num_vertices;
}
}
}
else
{
// Seek to the vertex data
vvdFile->seekg(header.vertex_data_offset);
// Just read the vertices directly
vvdFile->read((char *) &vertex_buffer[i][0],
header.num_lod_verts[i] * sizeof(VVDVertex));
}
// Scale the vertices from inches up to meters
for (j = 0; j < vertex_buffer_size[i]; j++)
vertex_buffer[i][j].vertex_position *= 0.0254;
}
// Close the file
vvdFile->close();
delete vvdFile;
return true;
}
int VVDReader::getNumLODVertices(int lod)
{
return vertex_buffer_size[lod];
}
Vec3 VVDReader::getVertex(int lod, int index)
{
return vertex_buffer[lod][index].vertex_position;
}
Vec3 VVDReader::getNormal(int lod, int index)
{
return vertex_buffer[lod][index].vertex_normal;
}
Vec2 VVDReader::getTexCoords(int lod, int index)
{
return vertex_buffer[lod][index].vertex_texcoord;
}

View File

@@ -0,0 +1,98 @@
#ifndef __VVD_READER_H_
#define __VVD_READER_H_
#include <osg/Geometry>
#include <osg/Matrixd>
#include <osg/Node>
#include <osg/Object>
#include <osg/StateSet>
#include <osgDB/Registry>
#include <osgDB/FileNameUtils>
#include <osg/Referenced>
#include "MDLLimits.h"
namespace mdl
{
// The magic number for a Valve VVD file is 'IDSV' in little-endian
// order
const int VVD_MAGIC_NUMBER = (('V'<<24)+('S'<<16)+('D'<<8)+'I');
struct VVDHeader
{
int magic_number;
int vvd_version;
int check_sum;
int num_lods;
int num_lod_verts[MAX_LODS];
int num_fixups;
int fixup_table_offset;
int vertex_data_offset;
int tangent_data_offset;
};
struct VVDFixupEntry
{
int lod_number;
int source_vertex_id;
int num_vertices;
};
struct VVDBoneWeight
{
float weight[MAX_BONES_PER_VERTEX];
char bone[MAX_BONES_PER_VERTEX];
u_char num_bones;
};
struct VVDVertex
{
VVDBoneWeight bone_weights;
osg::Vec3 vertex_position;
osg::Vec3 vertex_normal;
osg::Vec2 vertex_texcoord;
};
class VVDReader
{
protected:
std::string vvd_name;
VVDVertex * vertex_buffer[MAX_LODS];
int vertex_buffer_size[MAX_LODS];
VVDFixupEntry * fixup_table;
public:
VVDReader();
virtual ~VVDReader();
bool readFile(const std::string & file);
int getNumLODVertices(int lod);
osg::Vec3 getVertex(int lod, int index);
osg::Vec3 getNormal(int lod, int index);
osg::Vec2 getTexCoords(int lod, int index);
};
}
#endif

View File

@@ -0,0 +1,6 @@
#this file is automatically generated
SET(TARGET_SRC ReaderWriterVTF.cpp )
#### end var setup ###
SETUP_PLUGIN(vtf)

View File

@@ -5,8 +5,8 @@
* DESCRIPTION: Class for reading a Valve Texture Format (VTF) file
* into an osg::Image.
*
* Borrows heavily from Rune Schmidt Jensen's DDS
* plugin for OSG, as well as the Valve Source SDK
* Borrows heavily from the DDS plugin for OSG, as well
* as the Valve Source SDK
*
* CREATED BY: Jason Daly (jdaly@ist.ucf.edu)
*
@@ -27,97 +27,97 @@
enum VTFFlags
{
VTF_FLAGS_POINTSAMPLE = 0x00000001,
VTF_FLAGS_TRILINEAR = 0x00000002,
VTF_FLAGS_CLAMP_S = 0x00000004,
VTF_FLAGS_CLAMP_T = 0x00000008,
VTF_FLAGS_ANISOTROPIC = 0x00000010,
VTF_FLAGS_HINT_DXT5 = 0x00000020,
VTF_FLAGS_NOCOMPRESS = 0x00000040,
VTF_FLAGS_NORMAL = 0x00000080,
VTF_FLAGS_NOMIP = 0x00000100,
VTF_FLAGS_NOLOD = 0x00000200,
VTF_FLAGS_MINMIP = 0x00000400,
VTF_FLAGS_PROCEDURAL = 0x00000800,
VTF_FLAGS_ONEBITALPHA = 0x00001000,
VTF_FLAGS_EIGHTBITALPHA = 0x00002000,
VTF_FLAGS_ENVMAP = 0x00004000,
VTF_FLAGS_RENDERTARGET = 0x00008000,
VTF_FLAGS_DEPTHRENDERTARGET = 0x00010000,
VTF_FLAGS_NODEBUGOVERRIDE = 0x00020000,
VTF_FLAGS_SINGLECOPY = 0x00040000,
VTF_FLAGS_ONEOVERMIPLEVELINALPHA = 0x00080000,
VTF_FLAGS_PREMULTCOLORBYONEOVERMIPLEVEL = 0x00100000,
VTF_FLAGS_NORMALTODUDV = 0x00200000,
VTF_FLAGS_ALPHATESTMIPGENERATION = 0x00400000,
VTF_FLAGS_NODEPTHBUFFER = 0x00800000,
VTF_FLAGS_NICEFILTERED = 0x01000000,
VTF_FLAGS_CLAMP_U = 0x02000000,
VTF_FLAGS_PRESWIZZLED = 0x04000000,
VTF_FLAGS_CACHEABLE = 0x08000000,
VTF_FLAGS_UNFILTERABLE_OK = 0x10000000,
VTF_FLAGS_LASTFLAG = 0x10000000
VTF_FLAGS_POINTSAMPLE = 0x00000001,
VTF_FLAGS_TRILINEAR = 0x00000002,
VTF_FLAGS_CLAMP_S = 0x00000004,
VTF_FLAGS_CLAMP_T = 0x00000008,
VTF_FLAGS_ANISOTROPIC = 0x00000010,
VTF_FLAGS_HINT_DXT5 = 0x00000020,
VTF_FLAGS_NOCOMPRESS = 0x00000040,
VTF_FLAGS_NORMAL = 0x00000080,
VTF_FLAGS_NOMIP = 0x00000100,
VTF_FLAGS_NOLOD = 0x00000200,
VTF_FLAGS_MINMIP = 0x00000400,
VTF_FLAGS_PROCEDURAL = 0x00000800,
VTF_FLAGS_ONEBITALPHA = 0x00001000,
VTF_FLAGS_EIGHTBITALPHA = 0x00002000,
VTF_FLAGS_ENVMAP = 0x00004000,
VTF_FLAGS_RENDERTARGET = 0x00008000,
VTF_FLAGS_DEPTHRENDERTARGET = 0x00010000,
VTF_FLAGS_NODEBUGOVERRIDE = 0x00020000,
VTF_FLAGS_SINGLECOPY = 0x00040000,
VTF_FLAGS_ONEOVERMIPLEVELINALPHA = 0x00080000,
VTF_FLAGS_PREMULTCOLORBYONEOVERMIPLEVEL = 0x00100000,
VTF_FLAGS_NORMALTODUDV = 0x00200000,
VTF_FLAGS_ALPHATESTMIPGENERATION = 0x00400000,
VTF_FLAGS_NODEPTHBUFFER = 0x00800000,
VTF_FLAGS_NICEFILTERED = 0x01000000,
VTF_FLAGS_CLAMP_U = 0x02000000,
VTF_FLAGS_PRESWIZZLED = 0x04000000,
VTF_FLAGS_CACHEABLE = 0x08000000,
VTF_FLAGS_UNFILTERABLE_OK = 0x10000000,
VTF_FLAGS_LASTFLAG = 0x10000000
};
enum VTFCubeMapFaceIndex
{
VTF_CUBEMAP_FACE_RIGHT = 0,
VTF_CUBEMAP_FACE_LEFT,
VTF_CUBEMAP_FACE_BACK,
VTF_CUBEMAP_FACE_FRONT,
VTF_CUBEMAP_FACE_UP,
VTF_CUBEMAP_FACE_DOWN,
VTF_CUBEMAP_FACE_SPHEREMAP,
VTF_CUBEMAP_FACE_COUNT
VTF_CUBEMAP_FACE_RIGHT = 0,
VTF_CUBEMAP_FACE_LEFT,
VTF_CUBEMAP_FACE_BACK,
VTF_CUBEMAP_FACE_FRONT,
VTF_CUBEMAP_FACE_UP,
VTF_CUBEMAP_FACE_DOWN,
VTF_CUBEMAP_FACE_SPHEREMAP,
VTF_CUBEMAP_FACE_COUNT
};
enum VTFLookDir
{
VTF_LOOK_DOWN_X = 0,
VTF_LOOK_DOWN_NEGX,
VTF_LOOK_DOWN_Y = 0,
VTF_LOOK_DOWN_NEGY,
VTF_LOOK_DOWN_Z = 0,
VTF_LOOK_DOWN_NEGZ
VTF_LOOK_DOWN_X = 0,
VTF_LOOK_DOWN_NEGX,
VTF_LOOK_DOWN_Y = 0,
VTF_LOOK_DOWN_NEGY,
VTF_LOOK_DOWN_Z = 0,
VTF_LOOK_DOWN_NEGZ
};
enum VTFImageFormat
{
VTF_FORMAT_UNKNOWN = -1,
VTF_FORMAT_RGBA8888 = 0,
VTF_FORMAT_ABGR8888,
VTF_FORMAT_RGB888,
VTF_FORMAT_BGR888,
VTF_FORMAT_RGB565,
VTF_FORMAT_I8,
VTF_FORMAT_IA88,
VTF_FORMAT_P8,
VTF_FORMAT_A8,
VTF_FORMAT_RGB888_BLUESCREEN,
VTF_FORMAT_BGR888_BLUESCREEN,
VTF_FORMAT_ARGB8888,
VTF_FORMAT_BGRA8888,
VTF_FORMAT_DXT1,
VTF_FORMAT_DXT3,
VTF_FORMAT_DXT5,
VTF_FORMAT_BGRX8888,
VTF_FORMAT_BGR565,
VTF_FORMAT_BGRX5551,
VTF_FORMAT_BGRA4444,
VTF_FORMAT_DXT1_ONEBITALPHA,
VTF_FORMAT_BGRA5551,
VTF_FORMAT_UV88,
VTF_FORMAT_UVWQ8888,
VTF_FORMAT_RGBA16161616F,
VTF_FORMAT_RGBA16161616,
VTF_FORMAT_UVLX8888,
VTF_FORMAT_R32F,
VTF_FORMAT_RGB323232F,
VTF_FORMAT_RGBA32323232F,
VTF_NUM_IMAGE_FORMATS
VTF_FORMAT_UNKNOWN = -1,
VTF_FORMAT_RGBA8888 = 0,
VTF_FORMAT_ABGR8888,
VTF_FORMAT_RGB888,
VTF_FORMAT_BGR888,
VTF_FORMAT_RGB565,
VTF_FORMAT_I8,
VTF_FORMAT_IA88,
VTF_FORMAT_P8,
VTF_FORMAT_A8,
VTF_FORMAT_RGB888_BLUESCREEN,
VTF_FORMAT_BGR888_BLUESCREEN,
VTF_FORMAT_ARGB8888,
VTF_FORMAT_BGRA8888,
VTF_FORMAT_DXT1,
VTF_FORMAT_DXT3,
VTF_FORMAT_DXT5,
VTF_FORMAT_BGRX8888,
VTF_FORMAT_BGR565,
VTF_FORMAT_BGRX5551,
VTF_FORMAT_BGRA4444,
VTF_FORMAT_DXT1_ONEBITALPHA,
VTF_FORMAT_BGRA5551,
VTF_FORMAT_UV88,
VTF_FORMAT_UVWQ8888,
VTF_FORMAT_RGBA16161616F,
VTF_FORMAT_RGBA16161616,
VTF_FORMAT_UVLX8888,
VTF_FORMAT_R32F,
VTF_FORMAT_RGB323232F,
VTF_FORMAT_RGBA32323232F,
VTF_NUM_IMAGE_FORMATS
};
@@ -126,27 +126,27 @@ enum VTFImageFormat
struct VTFFileHeader
{
char magic_number[4];
unsigned int file_version[2];
unsigned int header_size;
unsigned short image_width;
unsigned short image_height;
unsigned int image_flags;
unsigned short num_frames;
unsigned short start_frame;
char magic_number[4];
unsigned int file_version[2];
unsigned int header_size;
unsigned short image_width;
unsigned short image_height;
unsigned int image_flags;
unsigned short num_frames;
unsigned short start_frame;
unsigned char padding_0[4];
osg::Vec3f reflectivity_value;
unsigned char padding_1[4];
unsigned char padding_0[4];
osg::Vec3f reflectivity_value;
unsigned char padding_1[4];
float bump_scale;
unsigned int image_format;
unsigned char num_mip_levels;
unsigned char low_res_image_format;
unsigned char padding_2[3];
unsigned char low_res_image_width;
unsigned char low_res_image_height;
unsigned short image_depth;
float bump_scale;
unsigned int image_format;
unsigned char num_mip_levels;
unsigned char low_res_image_format;
unsigned char padding_2[3];
unsigned char low_res_image_width;
unsigned char low_res_image_height;
unsigned short image_depth;
};
@@ -357,6 +357,7 @@ osg::Image* ReadVTFFile(std::istream& _istream)
int s, t, r;
unsigned int lrSize;
unsigned char * imageData;
unsigned char * imageDataPtr;
unsigned int base;
unsigned int size;
int mip;
@@ -395,12 +396,53 @@ osg::Image* ReadVTFFile(std::istream& _istream)
return NULL;
}
osg::notify(osg::INFO) << "VTF Header: (" << sizeof(VTFFileHeader);
osg::notify(osg::INFO) << " bytes)" << std::endl;
osg::notify(osg::INFO) << " magic_number = ";
osg::notify(osg::INFO) << vtf_header.magic_number[0];
osg::notify(osg::INFO) << vtf_header.magic_number[1];
osg::notify(osg::INFO) << vtf_header.magic_number[2];
osg::notify(osg::INFO) << vtf_header.magic_number[3] << std:: endl;
osg::notify(osg::INFO) << " file_version = ";
osg::notify(osg::INFO) << vtf_header.file_version[0] << ".";
osg::notify(osg::INFO) << vtf_header.file_version[1] << std:: endl;
osg::notify(osg::INFO) << " header_size = ";
osg::notify(osg::INFO) << vtf_header.header_size << std::endl;
osg::notify(osg::INFO) << " image_width = ";
osg::notify(osg::INFO) << vtf_header.image_width << std::endl;
osg::notify(osg::INFO) << " image_height = ";
osg::notify(osg::INFO) << vtf_header.image_height << std::endl;
osg::notify(osg::INFO) << " num_frames = ";
osg::notify(osg::INFO) << vtf_header.num_frames << std::endl;
osg::notify(osg::INFO) << " start_frame = ";
osg::notify(osg::INFO) << vtf_header.start_frame << std::endl;
osg::notify(osg::INFO) << " reflectivity = ";
osg::notify(osg::INFO) << vtf_header.reflectivity_value.x() << ", ";
osg::notify(osg::INFO) << vtf_header.reflectivity_value.y() << ", ";
osg::notify(osg::INFO) << vtf_header.reflectivity_value.z() << std::endl;
osg::notify(osg::INFO) << " bump_scale = ";
osg::notify(osg::INFO) << vtf_header.bump_scale << std::endl;
osg::notify(osg::INFO) << " image_format = ";
osg::notify(osg::INFO) << vtf_header.image_format << std::endl;
osg::notify(osg::INFO) << " num_mip_lvls = ";
osg::notify(osg::INFO) << (int)vtf_header.num_mip_levels << std::endl;
osg::notify(osg::INFO) << " lr_image_fmt = ";
osg::notify(osg::INFO) << (int)vtf_header.low_res_image_format << std::endl;
osg::notify(osg::INFO) << " lr_width = ";
osg::notify(osg::INFO) << (int)vtf_header.low_res_image_width << std::endl;
osg::notify(osg::INFO) << " lr_height = ";
osg::notify(osg::INFO) << (int)vtf_header.low_res_image_height << std::endl;
osg::notify(osg::INFO) << " image_depth = ";
osg::notify(osg::INFO) << (int)vtf_header.image_depth << std::endl;
// Before we get to the real image, we need to skip over the "low res"
// image that's often stored along with VTF textures, so get the
// low-res image dimensions
s = vtf_header.low_res_image_width;
t = vtf_header.low_res_image_height;
r = 1;
osg::notify(osg::INFO) << "Low-res s = " << s << std::endl;
osg::notify(osg::INFO) << "Low-res t = " << t << std::endl;
// See if the low-res image is there
lrSize = 0;
@@ -428,6 +470,7 @@ osg::Image* ReadVTFFile(std::istream& _istream)
lrSize = loResImage->getTotalSizeInBytes();
// Skip over the low-res image data
osg::notify(osg::INFO) << "Low-res size = " << lrSize << std::endl;
_istream.ignore(lrSize);
}
@@ -525,7 +568,7 @@ osg::Image* ReadVTFFile(std::istream& _istream)
if (height == 0)
height = 1;
if (depth == 0)
height = 1;
depth = 1;
// Compute and store the offset into the final image data
offset += depth * height *
@@ -551,10 +594,11 @@ osg::Image* ReadVTFFile(std::istream& _istream)
osgImage->setImage(s, t, r, internalFormat, pixelFormat, dataType,
0, osg::Image::USE_NEW_DELETE);
if (mipmaps.size() > 0)
osgImage->setMipmapLevels(mipmaps);
osgImage->setMipmapLevels(mipmaps);
// Compute the total image size
size = osgImage->getTotalSizeInBytesIncludingMipmaps();
osg::notify(osg::INFO) << "ReadVTFFile info : size = " << size << std::endl;
if(size <= 0)
{
osg::notify(osg::WARN) << "ReadVTFFile warning: size <= 0" << std::endl;
@@ -602,6 +646,30 @@ osg::Image* ReadVTFFile(std::istream& _istream)
_istream.read((char*)imageData, size);
}
/*
// Check if alpha information embedded in the 8-byte encoding blocks
if (checkIfUsingOneBitAlpha)
{
const DXT1TexelsBlock *texelsBlock =
reinterpret_cast<const DXT1TexelsBlock*>(imageData);
// Only do the check on the first mipmap level
unsigned int numBlocks = mipmaps.size()>0 ? mipmaps[0] / 8 : size / 8;
for (int i=numBlocks; i>0; --i, ++texelsBlock)
{
if (texelsBlock->color_0<=texelsBlock->color_1)
{
// Texture is using the 1-bit alpha encoding, so we need to
// update the assumed pixel format
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
pixelFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
break;
}
}
}
*/
// Now, set the actual image data and mipmap levels
osgImage->setImage(s,t,r, internalFormat, pixelFormat, dataType,
imageData, osg::Image::USE_NEW_DELETE);
@@ -612,6 +680,13 @@ osg::Image* ReadVTFFile(std::istream& _istream)
}
bool WriteVTFFile(const osg::Image *img, std::ostream& fout)
{
// Not supported
return false;
}
class ReaderWriterVTF : public osgDB::ReaderWriter
{
public:
@@ -625,22 +700,17 @@ public:
return osgDB::equalCaseInsensitive(extension, "vtf");
}
virtual ReadResult readObject(
const std::string& file,
const osgDB::ReaderWriter::Options* options) const
virtual ReadResult readObject(const std::string& file, const osgDB::ReaderWriter::Options* options) const
{
return readImage(file,options);
}
virtual ReadResult readObject(std::istream& fin,
const Options* options) const
virtual ReadResult readObject(std::istream& fin, const Options* options) const
{
return readImage(fin,options);
}
virtual ReadResult readImage(
const std::string& file,
const osgDB::ReaderWriter::Options* options) const
virtual ReadResult readImage(const std::string& file, const osgDB::ReaderWriter::Options* options) const
{
std::string ext = osgDB::getLowerCaseFileExtension(file);
if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
@@ -649,22 +719,19 @@ public:
if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
osgDB::ifstream stream(fileName.c_str(),
std::ios::in | std::ios::binary);
std::ifstream stream(fileName.c_str(), std::ios::in | std::ios::binary);
if(!stream) return ReadResult::FILE_NOT_HANDLED;
ReadResult rr = readImage(stream, options);
if(rr.validImage()) rr.getImage()->setFileName(file);
return rr;
}
virtual ReadResult readImage(std::istream& fin,
const Options* options) const
virtual ReadResult readImage(std::istream& fin, const Options* options) const
{
osg::Image* osgImage = ReadVTFFile(fin);
if (osgImage==NULL) return ReadResult::FILE_NOT_HANDLED;
if (options &&
options->getOptionString().find("vtf_flip")!=std::string::npos)
if (options && options->getOptionString().find("vtf_flip")!=std::string::npos)
{
osgImage->flipVertical();
}
@@ -672,10 +739,7 @@ public:
return osgImage;
}
virtual WriteResult writeObject(
const osg::Object& object,
const std::string& file,
const osgDB::ReaderWriter::Options* options) const
virtual WriteResult writeObject(const osg::Object& object,const std::string& file, const osgDB::ReaderWriter::Options* options) const
{
const osg::Image* image = dynamic_cast<const osg::Image*>(&object);
if (!image) return WriteResult::FILE_NOT_HANDLED;
@@ -683,26 +747,34 @@ public:
return writeImage(*image,file,options);
}
virtual WriteResult writeObject(const osg::Object& object,
std::ostream& fout,
const Options* options) const
virtual WriteResult writeObject(const osg::Object& object,std::ostream& fout,const Options* options) const
{
return WriteResult::FILE_NOT_HANDLED;
const osg::Image* image = dynamic_cast<const osg::Image*>(&object);
if (!image) return WriteResult::FILE_NOT_HANDLED;
return writeImage(*image,fout,options);
}
virtual WriteResult writeImage(
const osg::Image &image,
const std::string& file,
const osgDB::ReaderWriter::Options* options) const
virtual WriteResult writeImage(const osg::Image &image,const std::string& file, const osgDB::ReaderWriter::Options* options) const
{
return WriteResult::FILE_NOT_HANDLED;
std::string ext = osgDB::getFileExtension(file);
if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
std::ofstream fout(file.c_str(), std::ios::out | std::ios::binary);
if(!fout) return WriteResult::ERROR_IN_WRITING_FILE;
return writeImage(image,fout,options);
}
virtual WriteResult writeImage(const osg::Image& image,
std::ostream& fout,const Options*) const
virtual WriteResult writeImage(const osg::Image& image,std::ostream& fout,const Options*) const
{
return WriteResult::FILE_NOT_HANDLED;
bool success = WriteVTFFile(&image, fout);
if(success)
return WriteResult::FILE_SAVED;
else
return WriteResult::ERROR_IN_WRITING_FILE;
}
};