Files
OpenSceneGraph/src/osgPlugins/3ds/ReaderWriter3DS.cpp
Robert Osfield 73921435e0 From Sukender, "ReaderWriter3DS.cpp fixes:
- Fixed creation of useless intermediate nodes

WriterNodeVisitor.cpp fixes:
- Fixed naming of textures (path and extension)
"

Note from Robert Osfield, this submission also came with changes to use of ref_ptr<> and removal of delete[]'s, but these were not merged as they didn't actually fix any memory leaks, and in once instance introduced one.
2010-01-08 10:52:03 +00:00

1114 lines
44 KiB
C++

#define ENABLE_3DS_WRITER 1 // Enables the 3DS writer (the define should be removed when the writer will be stable and tested enough)
#include <osg/Notify>
#include <osg/Group>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/Texture2D>
#include <osg/Material>
#include <osg/TexEnv>
#include <osg/ref_ptr>
#include <osg/MatrixTransform>
#include <osgDB/Registry>
#include <osgDB/FileUtils>
#include <osgDB/FileNameUtils>
#include <osgDB/ReadFile>
#include <osgUtil/TriStripVisitor>
//MIKEC debug only for PrintVisitor
#include <osg/NodeVisitor>
#ifdef ENABLE_3DS_WRITER
#include "WriterNodeVisitor.h"
#endif
#include "lib3ds/lib3ds.h"
#include <stdlib.h>
#include <string.h>
#include <set>
#include <map>
#include <iostream>
#include <sstream>
#include <assert.h>
using namespace std;
using namespace osg;
void copyLib3dsMatrixToOsgMatrix(osg::Matrix& osg_matrix, const Lib3dsMatrix lib3ds_matrix)
{
osg_matrix.set(
lib3ds_matrix[0][0],lib3ds_matrix[0][1],lib3ds_matrix[0][2],lib3ds_matrix[0][3],
lib3ds_matrix[1][0],lib3ds_matrix[1][1],lib3ds_matrix[1][2],lib3ds_matrix[1][3],
lib3ds_matrix[2][0],lib3ds_matrix[2][1],lib3ds_matrix[2][2],lib3ds_matrix[2][3],
lib3ds_matrix[3][0],lib3ds_matrix[3][1],lib3ds_matrix[3][2],lib3ds_matrix[3][3]);
}
osg::Matrix copyLib3dsMatrixToOsgMatrix(const Lib3dsMatrix mat)
{
osg::Matrix osgMatrix;
copyLib3dsMatrixToOsgMatrix(osgMatrix, mat);
return osgMatrix;
}
void copyLib3dsVec3ToOsgVec3(osg::Vec3f osgVec, const float vertices[3]) {
return osgVec.set(vertices[0], vertices[1], vertices[2]);
}
osg::Vec3f copyLib3dsVec3ToOsgVec3(const float vertices[3]) {
return osg::Vec3f(vertices[0], vertices[1], vertices[2]);
}
osg::Quat copyLib3dsQuatToOsgQuat(const float quat[4]) {
return osg::Quat(quat[0], quat[1], quat[2], quat[3]);
}
class PrintVisitor : public NodeVisitor
{
public:
PrintVisitor(std::ostream& out):
NodeVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN),
_out(out)
{
_indent = 0;
_step = 4;
}
inline void moveIn() { _indent += _step; }
inline void moveOut() { _indent -= _step; }
inline void writeIndent()
{
for(int i=0;i<_indent;++i) _out << " ";
}
virtual void apply(Node& node)
{
moveIn();
writeIndent(); _out << node.className() <<std::endl;
traverse(node);
moveOut();
}
virtual void apply(Geode& node) { apply((Node&)node); }
virtual void apply(Billboard& node) { apply((Geode&)node); }
virtual void apply(LightSource& node) { apply((Group&)node); }
virtual void apply(ClipNode& node) { apply((Group&)node); }
virtual void apply(Group& node) { apply((Node&)node); }
virtual void apply(Transform& node) { apply((Group&)node); }
virtual void apply(Projection& node) { apply((Group&)node); }
virtual void apply(Switch& node) { apply((Group&)node); }
virtual void apply(LOD& node) { apply((Group&)node); }
protected:
PrintVisitor& operator = (const PrintVisitor&) { return *this; }
std::ostream& _out;
int _indent;
int _step;
};
/// Possible options:
/// - noMatrixTransforms: set the plugin to apply matrices into the mesh vertices ("old behaviour") instead of restoring them ("new behaviour"). You may use this option to avoid a few rounding errors.
/// - checkForEspilonIdentityMatrices: if noMatrixTransforms is \b not set, then consider "almost identity" matrices to be identity ones (in case of rounding errors).
/// - restoreMatrixTransformsNoMeshes: makes an exception to the behaviour when 'noMatrixTransforms' is \b not set for mesh instances. When a mesh instance has a transform on it, the reader creates a MatrixTransform above the Geode. If you don't want the hierarchy to be modified, then you can use this option to merge the transform into vertices.
class ReaderWriter3DS : public osgDB::ReaderWriter
{
public:
ReaderWriter3DS();
virtual const char* className() const { return "3DS Auto Studio Reader/Writer"; }
virtual ReadResult readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) const;
virtual ReadResult readNode(std::istream& fin, const Options* options) const;
#if ENABLE_3DS_WRITER
virtual WriteResult writeNode(const osg::Node& /*node*/,const std::string& /*fileName*/,const Options* =NULL) const;
virtual WriteResult writeNode(const osg::Node& /*node*/,std::ostream& /*fout*/,const Options* =NULL) const;
#endif
protected:
ReadResult constructFrom3dsFile(Lib3dsFile *f,const std::string& filename, const Options* options) const;
#if ENABLE_3DS_WRITER
bool createFileObject(const osg::Node& node, Lib3dsFile * file3ds,const std::string& fileName, const osgDB::ReaderWriter::Options* options) const;
#endif
class ReaderObject
{
public:
ReaderObject(const osgDB::ReaderWriter::Options* options);
typedef std::vector<osg::StateSet*> StateSetMap;
typedef std::vector<int> FaceList;
typedef std::map<std::string,osg::StateSet*> GeoStateMap;
osg::Texture2D* createTexture(Lib3dsTextureMap *texture,const char* label,bool& transparancy);
osg::StateSet* createStateSet(Lib3dsMaterial *materials);
osg::Drawable* createDrawable(Lib3dsMesh *meshes,FaceList& faceList, const osg::Matrix * matrix);
std::string _directory;
bool _useSmoothingGroups;
bool _usePerVertexNormals;
// MIKEC
osg::Node* processMesh(StateSetMap& drawStateMap,osg::Group* parent,Lib3dsMesh* mesh, const osg::Matrix * matrix);
osg::Node* processNode(StateSetMap drawStateMap,Lib3dsFile *f,Lib3dsNode *node);
private:
const osgDB::ReaderWriter::Options* options;
bool noMatrixTransforms; ///< Should the plugin apply matrices into the mesh vertices ("old behaviour"), instead of restoring matrices ("new behaviour")?
bool checkForEspilonIdentityMatrices;
bool restoreMatrixTransformsNoMeshes;
typedef std::map<unsigned int,FaceList> SmoothingFaceMap;
void addDrawableFromFace(osg::Geode * geode, FaceList & faceList, Lib3dsMesh * mesh, const osg::Matrix * matrix, osg::StateSet * stateSet);
};
};
// now register with Registry to instantiate the above
// reader/writer.
REGISTER_OSGPLUGIN(3ds, ReaderWriter3DS)
ReaderWriter3DS::ReaderWriter3DS()
{
supportsExtension("3ds","3D Studio model format");
supportsOption("OutputTextureFiles","Write out the texture images to file");
setByteOrder();
#if 0
osg::notify(osg::NOTICE)<<"3DS reader sizes:"<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsBool)="<<sizeof(Lib3dsBool)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsByte)="<<sizeof(Lib3dsByte)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsWord)="<<sizeof(Lib3dsWord)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsDword)="<<sizeof(Lib3dsDword)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsIntb)="<<sizeof(Lib3dsIntb)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsIntw)="<<sizeof(Lib3dsIntw)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsIntd)="<<sizeof(Lib3dsIntd)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsFloat)="<<sizeof(Lib3dsFloat)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsDouble)="<<sizeof(Lib3dsDouble)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsVector)="<<sizeof(Lib3dsVector)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsTexel)="<<sizeof(Lib3dsTexel)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsQuat)="<<sizeof(Lib3dsQuat)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsMatrix)="<<sizeof(Lib3dsMatrix)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsRgb)="<<sizeof(Lib3dsRgb)<<std::endl;
osg::notify(osg::NOTICE)<<" sizeof(Lib3dsRgba)="<<sizeof(Lib3dsRgba)<<std::endl;
#endif
}
ReaderWriter3DS::ReaderObject::ReaderObject(const osgDB::ReaderWriter::Options* options) :
_useSmoothingGroups(true),
_usePerVertexNormals(true),
options(options),
noMatrixTransforms(false),
checkForEspilonIdentityMatrices(false),
restoreMatrixTransformsNoMeshes(false)
{
std::istringstream iss(options->getOptionString());
std::string opt;
while (iss >> opt)
{
if (opt == "noMatrixTransforms")
noMatrixTransforms = true;
if (opt == "checkForEspilonIdentityMatrices")
checkForEspilonIdentityMatrices = true;
if (opt == "restoreMatrixTransformsNoMeshes")
restoreMatrixTransformsNoMeshes = true;
}
}
/**
These print methods for 3ds hacking
*/
void pad(int level) {
for(int i=0;i<level;i++) std::cout<<" ";
}
void print(Lib3dsMesh *mesh,int level);
void print(Lib3dsUserData *user,int level);
void print(Lib3dsMeshInstanceNode *object,int level);
void print(Lib3dsNode *node, int level);
void print(Lib3dsMatrix matrix,int level) {
pad(level); cout << matrix[0][0] <<" "<< matrix[0][1] <<" "<< matrix[0][2] <<" "<< matrix[0][3] << endl;
pad(level); cout << matrix[1][0] <<" "<< matrix[1][1] <<" "<< matrix[1][2] <<" "<< matrix[1][3] << endl;
pad(level); cout << matrix[2][0] <<" "<< matrix[2][1] <<" "<< matrix[2][2] <<" "<< matrix[2][3] << endl;
pad(level); cout << matrix[3][0] <<" "<< matrix[3][1] <<" "<< matrix[3][2] <<" "<< matrix[3][3] << endl;
}
void print(Lib3dsMesh *mesh,int level) {
if (mesh) {
pad(level); cout << "mesh name " << mesh->name << endl;
print(mesh->matrix,level);
} else {
pad(level); cout << "no mesh " << endl;
}
}
void print(Lib3dsUserData *user,int level) {
if (user) {
pad(level); cout << "user data" << endl;
//print(user->mesh,level+1);
} else {
pad(level); cout << "no user data" << endl;
}
}
void print(Lib3dsMeshInstanceNode *object,int level) {
if (object) {
pad(level); cout << "objectdata instance [" << object->instance_name << "]" << endl;
pad(level); cout << "pivot " << object->pivot[0] <<" "<< object->pivot[1] <<" "<< object->pivot[2] << endl;
pad(level); cout << "pos " << object->pos[0] <<" "<< object->pos[1] <<" "<< object->pos[2] << endl;
pad(level); cout << "scl " << object->scl[0] <<" "<< object->scl[1] <<" "<< object->scl[2] << endl;
pad(level); cout << "rot " << object->rot[0] <<" "<< object->rot[1] <<" "<< object->rot[2] <<" "<< object->rot[3] << endl;
} else {
pad(level); cout << "no object data" << endl;
}
}
void print(Lib3dsNode *node, int level) {
pad(level); cout << "node name [" << node->name << "]" << endl;
pad(level); cout << "node id " << node->user_id << endl;
pad(level); cout << "node parent id " << (node->parent ? static_cast<int>(node->parent->user_id) : -1) << endl;
pad(level); cout << "node matrix:" << endl;
print(node->matrix,level+1);
if (node->type == LIB3DS_NODE_MESH_INSTANCE) {
pad(level); cout << "mesh instance data:" << endl;
print(reinterpret_cast<Lib3dsMeshInstanceNode *>(node),level+1);
} else {
pad(level); cout << "node is not a mesh instance (not handled)" << endl;
}
print(&node->user_ptr,level);
for(Lib3dsNode *child=node->childs; child; child=child->next) {
print(child,level+1);
}
}
void ReaderWriter3DS::ReaderObject::addDrawableFromFace(osg::Geode * geode, FaceList & faceList,
Lib3dsMesh * mesh,
const osg::Matrix * matrix,
osg::StateSet * stateSet)
{
if (_useSmoothingGroups)
{
SmoothingFaceMap smoothingFaceMap;
for (FaceList::iterator flitr=faceList.begin();
flitr!=faceList.end();
++flitr)
{
smoothingFaceMap[mesh->faces[*flitr].smoothing_group].push_back(*flitr);
}
for(SmoothingFaceMap::iterator sitr=smoothingFaceMap.begin();
sitr!=smoothingFaceMap.end();
++sitr)
{
// each smoothing group to have its own geom
// to ensure the vertices on adjacent groups
// don't get shared.
FaceList& smoothFaceMap = sitr->second;
osg::ref_ptr<osg::Drawable> drawable = createDrawable(mesh,smoothFaceMap,matrix);
if (drawable)
{
if (stateSet)
drawable->setStateSet(stateSet);
geode->addDrawable(drawable);
}
}
}
else // ignore smoothing groups.
{
osg::ref_ptr<osg::Drawable> drawable = createDrawable(mesh,faceList,matrix);
if (drawable)
{
if (stateSet)
drawable->setStateSet(stateSet);
geode->addDrawable(drawable);
}
}
}
// Transforms points by matrix if 'matrix' is not NULL
// Creates a Geode and Geometry (as parent,child) and adds the Geode to 'parent' parameter iff 'parent' is non-NULL
// Returns ptr to the Geode
osg::Node* ReaderWriter3DS::ReaderObject::processMesh(StateSetMap& drawStateMap,osg::Group* parent,Lib3dsMesh* mesh, const osg::Matrix * matrix) {
typedef std::vector<FaceList> MaterialFaceMap;
MaterialFaceMap materialFaceMap;
unsigned int numMaterials = drawStateMap.size();
materialFaceMap.insert(materialFaceMap.begin(), numMaterials, FaceList()); // Setup the map
FaceList defaultMaterialFaceList;
for (unsigned int i=0; i<mesh->nfaces; ++i)
{
if (mesh->faces[i].material>=0) {
materialFaceMap[mesh->faces[i].material].push_back(i);
}
else
defaultMaterialFaceList.push_back(i);
}
if (materialFaceMap.empty() && defaultMaterialFaceList.empty())
{
osg::notify(osg::NOTICE)<<"Warning : no triangles assigned to mesh '"<<mesh->name<<"'"<< std::endl;
//osg::notify(osg::INFO) << "No material assigned to mesh '" << mesh->name << "'" << std::endl;
return NULL;
}
else
{
osg::Geode* geode = new osg::Geode;
geode->setName(mesh->name);
if (!defaultMaterialFaceList.empty())
{
addDrawableFromFace(geode, defaultMaterialFaceList, mesh, matrix, NULL);
}
for(unsigned int imat=0; imat<numMaterials; ++imat)
{
addDrawableFromFace(geode, materialFaceMap[imat], mesh, matrix, drawStateMap[imat]);
}
if (parent) parent->addChild(geode);
return geode;
}
}
/// Returns true if a matrix is 'almost' identity, meaning that the difference between each value and the corresponding identity value is less than an epsilon value.
bool isIdentityEquivalent(const osg::Matrix & mat, osg::Matrix::value_type epsilon=1e-6)
{
return osg::equivalent(mat(0,0), 1, epsilon) && osg::equivalent(mat(0,1), 0, epsilon) && osg::equivalent(mat(0,2), 0, epsilon) && osg::equivalent(mat(0,3), 0, epsilon) &&
osg::equivalent(mat(1,0), 0, epsilon) && osg::equivalent(mat(1,1), 1, epsilon) && osg::equivalent(mat(1,2), 0, epsilon) && osg::equivalent(mat(1,3), 0, epsilon) &&
osg::equivalent(mat(2,0), 0, epsilon) && osg::equivalent(mat(2,1), 0, epsilon) && osg::equivalent(mat(2,2), 1, epsilon) && osg::equivalent(mat(2,3), 0, epsilon) &&
osg::equivalent(mat(3,0), 0, epsilon) && osg::equivalent(mat(3,1), 0, epsilon) && osg::equivalent(mat(3,2), 0, epsilon) && osg::equivalent(mat(3,3), 1, epsilon);
}
/**
How to cope with pivot points in 3ds (short version)
All object coordinates in 3ds are stored in world space, this is why you can just rip out the meshes and use/draw them without meddeling further
Unfortunately, this gets a bit wonky with objects with pivot points (conjecture: PP support is retro fitted into the .3ds format and so doesn't fit perfectly?)
Objects with pivot points have a position relative to their PP, so they have to undergo this transform:
invert the mesh matrix, apply this matrix to the object. This puts the object back at the origin
Transform the object by the nodes (negative) pivot point coords, this puts the PP at the origin
Transform the node by the node matrix, which does the orientation about the pivot point, (and currently) transforms the object back by a translation to the PP.
*/
osg::Node* ReaderWriter3DS::ReaderObject::processNode(StateSetMap drawStateMap,Lib3dsFile *f,Lib3dsNode *node)
{
// Get mesh
Lib3dsMeshInstanceNode * object = (node->type == LIB3DS_NODE_MESH_INSTANCE) ? reinterpret_cast<Lib3dsMeshInstanceNode *>(node) : NULL;
Lib3dsMesh * mesh = lib3ds_file_mesh_for_node(f,node);
assert(!(mesh && !object)); // Node must be a LIB3DS_NODE_MESH_INSTANCE if a mesh exists
// Retreive LOCAL transform
static const osg::Matrix::value_type MATRIX_EPSILON = 1e-10;
osg::Matrix osgNodeMatrix( copyLib3dsMatrixToOsgMatrix(node->matrix) );
if (node->parent)
{
// Matrices evaluated by lib3DS are multiplied by parents' ones
osgNodeMatrix *= osg::Matrix::inverse( copyLib3dsMatrixToOsgMatrix(node->parent->matrix) );
}
// Test if we should create an intermediate Group (or MatrixTransform) and which matrix we should apply to the vertices
osg::Group* group = NULL;
// Get pivot point
osg::Vec3 pivot( object ? copyLib3dsVec3ToOsgVec3(object->pivot) : osg::Vec3() );
bool pivoted = pivot.x()!=0 || pivot.y()!=0 || pivot.z()!=0;
osg::Matrix meshMat;
if (mesh) {
if (!noMatrixTransforms) {
// There can be a transform directly on a mesh instance (= as if a osg::MatrixTransform and a osg::Geode were merged together) in object->pos/rot/scl
if (!pivoted) {
meshMat = osg::Matrix::inverse(copyLib3dsMatrixToOsgMatrix(mesh->matrix));
} else {
meshMat = osg::Matrix::inverse(copyLib3dsMatrixToOsgMatrix(mesh->matrix)) * osg::Matrix::translate(-pivot);
}
}
else {
if (pivoted) {
meshMat = osg::Matrix::inverse(copyLib3dsMatrixToOsgMatrix(mesh->matrix)) * osg::Matrix::translate(-pivot) * copyLib3dsMatrixToOsgMatrix(node->matrix);
} else {
meshMat = osg::Matrix::inverse(copyLib3dsMatrixToOsgMatrix(mesh->matrix)) * copyLib3dsMatrixToOsgMatrix(node->matrix);
}
osgNodeMatrix = osg::Matrix::identity(); // Not sure it's useful, but it's harmless ;)
}
}
bool isOsgNodeMatrixIdentity = false;
if (osgNodeMatrix.isIdentity() || (checkForEspilonIdentityMatrices && isIdentityEquivalent(osgNodeMatrix, MATRIX_EPSILON))) {
isOsgNodeMatrixIdentity = true;
}
//if (node->childs != NULL || pivoted || (!isOsgNodeMatrixIdentity && !noMatrixTransforms)) {
if (node->childs != NULL || (!isOsgNodeMatrixIdentity && !noMatrixTransforms)) {
if (isOsgNodeMatrixIdentity || noMatrixTransforms) {
group = new osg::Group;
} else {
group = new osg::MatrixTransform(osgNodeMatrix);
}
}
if (group) {
if (strcmp(node->name, "$$$DUMMY") == 0)
{
if (node->type == LIB3DS_NODE_MESH_INSTANCE)
group->setName(reinterpret_cast<Lib3dsMeshInstanceNode *>(node)->instance_name);
}
else
group->setName(node->name);
// Handle all children of this node for hierarchical assemblies
for (Lib3dsNode *p=node->childs; p!=NULL; p=p->next) {
group->addChild(processNode(drawStateMap,f,p));
}
} else {
assert(node->childs == NULL); // Else we must have a group to put childs into
}
// Handle mesh
if (mesh) {
osg::Matrix * meshAppliedMatPtr = NULL;
if (!meshMat.isIdentity() && !(checkForEspilonIdentityMatrices && isIdentityEquivalent(meshMat, MATRIX_EPSILON))) {
meshAppliedMatPtr = &meshMat;
}
if(group) {
// add our geometry to group (where our children already are)
// creates geometry under modifier node
processMesh(drawStateMap,group,mesh,meshAppliedMatPtr);
return group;
} else {
// didnt use group for children
// return a ptr directly to the Geode for this mesh
return processMesh(drawStateMap,NULL,mesh,meshAppliedMatPtr);
}
} else {
// no mesh for this node - probably a camera or something of that persuasion
//cout << "no mesh for object " << node->name << endl;
return group; // we have no mesh, but we might have children
}
}
static long filei_seek_func(void *self, long offset, Lib3dsIoSeek origin) {
std::istream *f = reinterpret_cast<std::istream*>(self);
ios_base::seekdir o = ios_base::beg;
if (origin == LIB3DS_SEEK_CUR) o = ios_base::cur;
else if (origin == LIB3DS_SEEK_END) o = ios_base::end;
f->seekg(offset, o);
return f->fail() ? -1 : 0;
}
#if ENABLE_3DS_WRITER
static long fileo_seek_func(void *self, long offset, Lib3dsIoSeek origin) {
std::ostream *f = reinterpret_cast<std::ostream*>(self);
ios_base::seekdir o = ios_base::beg;
if (origin == LIB3DS_SEEK_CUR) o = ios_base::cur;
else if (origin == LIB3DS_SEEK_END) o = ios_base::end;
f->seekp(offset, o);
return f->fail() ? -1 : 0;
}
#endif
static long filei_tell_func(void *self) {
std::istream *f = reinterpret_cast<std::istream*>(self);
return f->tellg();
}
#if ENABLE_3DS_WRITER
static long fileo_tell_func(void *self) {
std::ostream *f = reinterpret_cast<std::ostream*>(self);
return f->tellp();
}
#endif
static size_t filei_read_func(void *self, void *buffer, size_t size) {
std::istream *f = reinterpret_cast<std::istream*>(self);
f->read(reinterpret_cast<char*>(buffer), size);
return f->gcount();
}
#if ENABLE_3DS_WRITER
static size_t fileo_write_func(void *self, const void *buffer, size_t size) {
std::ostream *f = reinterpret_cast<std::ostream*>(self);
f->write(static_cast<const char*>(buffer), size);
return f->fail() ? 0 : size;
}
#endif
static void fileio_log_func(void *self, Lib3dsLogLevel level, int indent, const char *msg)
{
osg::NotifySeverity l = osg::INFO;
if (level == LIB3DS_LOG_ERROR) l = osg::FATAL;
else if (level == LIB3DS_LOG_WARN) l = osg::WARN;
else if (level == LIB3DS_LOG_INFO) l = osg::INFO;
else if (level == LIB3DS_LOG_DEBUG) l = osg::DEBUG_INFO;
osg::notify(l) << msg << std::endl;
}
osgDB::ReaderWriter::ReadResult ReaderWriter3DS::readNode(std::istream& fin, const osgDB::ReaderWriter::Options* options) const
{
osgDB::ReaderWriter::ReadResult result = ReadResult::FILE_NOT_HANDLED;
std::string optFileName = "";
if (options)
{
optFileName = options->getPluginStringData("STREAM_FILENAME");
if (optFileName.empty()) optFileName = options->getPluginStringData("filename");
}
// Prepare io structure to tell how to read the stream
Lib3dsIo io;
io.self = &fin;
io.seek_func = filei_seek_func;
io.tell_func = filei_tell_func;
io.read_func = filei_read_func;
io.write_func = NULL;
io.log_func = fileio_log_func;
Lib3dsFile * file3ds = lib3ds_file_new();
if (lib3ds_file_read(file3ds, &io) != 0)
{
result = constructFrom3dsFile(file3ds,optFileName,options);
lib3ds_file_free(file3ds);
}
return(result);
}
osgDB::ReaderWriter::ReadResult ReaderWriter3DS::readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) const
{
osgDB::ReaderWriter::ReadResult result = ReadResult::FILE_NOT_HANDLED;
std::string ext = osgDB::getLowerCaseFileExtension(file);
if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
std::string fileName = osgDB::findDataFile( file, options );
if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
Lib3dsFile *f = lib3ds_file_open(fileName.c_str() /*,options*/);
if (f)
{
osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
local_opt->getDatabasePathList().push_front(osgDB::getFilePath(fileName));
result = constructFrom3dsFile(f,file,local_opt.get());
lib3ds_file_free(f);
}
return result;
}
osgDB::ReaderWriter::ReadResult ReaderWriter3DS::constructFrom3dsFile(Lib3dsFile *f,const std::string& fileName, const osgDB::ReaderWriter::Options* options) const
{
if (f==NULL) return ReadResult::FILE_NOT_HANDLED;
// MIKEC
// This appears to build the matrix structures for the 3ds structure
// It wasn't previously necessary because all the meshes are stored in world coordinates
// but is VERY necessary if you want to use pivot points...
lib3ds_file_eval(f,0.0f); // second param is time 't' for animated files
ReaderObject reader(options);
reader._directory = ( options && !options->getDatabasePathList().empty() ) ? options->getDatabasePathList().front() : osgDB::getFilePath(fileName);
ReaderObject::StateSetMap drawStateMap;
unsigned int numMaterials = f->nmaterials;
drawStateMap.insert(drawStateMap.begin(), numMaterials, NULL); // Setup the map
for (unsigned int imat=0; imat<numMaterials; ++imat)
{
Lib3dsMaterial * mat = f->materials[imat];
drawStateMap[imat] = reader.createStateSet(mat);
}
if (osg::getNotifyLevel()>=osg::INFO)
{
int level=0;
std::cout << "NODE TRAVERSAL of 3ds file "<<f->name<<std::endl;
for(Lib3dsNode *node=f->nodes; node; node=node->next) {
print(node,level+1);
}
std::cout << "MESH TRAVERSAL of 3ds file "<<f->name<<std::endl;
for (int imesh=0; imesh<f->nmeshes; ++imesh) {
print(f->meshes[imesh],level+1);
}
}
// We can traverse by meshes (old method, broken for pivot points, but otherwise works), or by nodes (new method, not so well tested yet)
// if your model is broken, especially wrt object positions try setting this flag. If that fixes it,
// send me the model
bool traverse_nodes=false;
// MIKEC: have found 3ds files with NO node structure - only meshes, for this case we fall back to the old traverse-by-meshes code
// Loading and re-exporting these files from 3DS produces a file with correct node structure, so perhaps these are not 100% conformant?
if (f->nodes == NULL) {
osg::notify(osg::WARN)<<"Warning: in 3ds loader: file has no nodes, traversing by meshes instead"<< std::endl;
traverse_nodes=true;
}
osg::Node* group = NULL;
if (traverse_nodes) { // old method
group = new osg::Group();
for (int imesh=0; imesh<f->nmeshes; ++imesh) {
reader.processMesh(drawStateMap,group->asGroup(),f->meshes[imesh],NULL);
}
} else { // new method
Lib3dsNode *node=f->nodes;
if (!node->next)
group = reader.processNode(drawStateMap,f,node);
else
{
group = new osg::Group();
for(; node; node=node->next) {
group->asGroup()->addChild(reader.processNode(drawStateMap,f,node));
}
}
}
if (group && group->getName().empty()) group->setName(fileName);
if (osg::getNotifyLevel()>=osg::INFO)
{
osg::notify(osg::NOTICE) << "Final OSG node structure looks like this:"<< endl;
PrintVisitor pv(osg::notify(osg::NOTICE));
group->accept(pv);
}
return group;
}
/**
use matrix to pretransform geometry, or NULL to do nothing
*/
osg::Drawable* ReaderWriter3DS::ReaderObject::createDrawable(Lib3dsMesh *m,FaceList& faceList, const osg::Matrix * matrix)
{
osg::Geometry* geom = new osg::Geometry;
unsigned int i;
std::vector<int> orig2NewMapping;
orig2NewMapping.reserve(m->nvertices);
for(i=0;i<m->nvertices;++i) orig2NewMapping.push_back(-1);
unsigned int noVertex=0;
FaceList::iterator fitr;
for (fitr=faceList.begin();
fitr!=faceList.end();
++fitr)
{
Lib3dsFace& face = m->faces[*fitr];
if (orig2NewMapping[face.index[0]]<0)
orig2NewMapping[face.index[0]] = noVertex++;
if (orig2NewMapping[face.index[1]]<0)
orig2NewMapping[face.index[1]] = noVertex++;
if (orig2NewMapping[face.index[2]]<0)
orig2NewMapping[face.index[2]] = noVertex++;
}
// create vertices.
osg::Vec3Array* osg_coords = new osg::Vec3Array(noVertex);
geom->setVertexArray(osg_coords);
for (i=0; i<m->nvertices; ++i)
{
if (orig2NewMapping[i]>=0)
{
if (matrix)
{
(*osg_coords)[orig2NewMapping[i]].set( copyLib3dsVec3ToOsgVec3(m->vertices[i]) * (*matrix) );
}
else
{
// original no transform code.
(*osg_coords)[orig2NewMapping[i]].set( copyLib3dsVec3ToOsgVec3(m->vertices[i]) );
}
}
}
// create texture coords if needed.
if (m->texcos)
{
osg::Vec2Array* osg_tcoords = new osg::Vec2Array(noVertex);
geom->setTexCoordArray(0,osg_tcoords);
for (i=0; i<m->nvertices; ++i)
{
if (orig2NewMapping[i]>=0) (*osg_tcoords)[orig2NewMapping[i]].set(m->texcos[i][0],m->texcos[i][1]);
}
}
// create normals
// Sukender: 3DS file format doesn't store normals (that is to say they're recomputed each time).
// When using per vertex normals, we could use either vertex computation, or face computation (and copy the normal to each vertex). Here we use the latter one.
if (_usePerVertexNormals)
{
//Lib3dsVector * normals = new Lib3dsVector[m->nfaces*3];
//lib3ds_mesh_calculate_vertex_normals(m, normals);
Lib3dsVector * normals = new Lib3dsVector[m->nfaces];
lib3ds_mesh_calculate_face_normals(m, normals);
osg::Vec3Array* osg_normals = new osg::Vec3Array(noVertex);
// initialize normal list to zero's.
for (i=0; i<noVertex; ++i)
{
(*osg_normals)[i].set(0.0f,0.0f,0.0f);
}
for (fitr=faceList.begin();
fitr!=faceList.end();
++fitr)
{
Lib3dsFace& face = m->faces[*fitr];
osg::Vec3f osgNormal( copyLib3dsVec3ToOsgVec3(normals[*fitr]) );
if (matrix) osgNormal = osg::Matrix::transform3x3(osgNormal, *matrix);
osgNormal.normalize();
(*osg_normals)[orig2NewMapping[face.index[0]]] = osgNormal;
(*osg_normals)[orig2NewMapping[face.index[1]]] = osgNormal;
(*osg_normals)[orig2NewMapping[face.index[2]]] = osgNormal;
}
geom->setNormalArray(osg_normals);
geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
}
else
{
Lib3dsVector * normals = new Lib3dsVector[m->nfaces];
lib3ds_mesh_calculate_face_normals(m, normals);
osg::Vec3Array* osg_normals = new osg::Vec3Array(faceList.size());
osg::Vec3Array::iterator normal_itr = osg_normals->begin();
for (fitr=faceList.begin();
fitr!=faceList.end();
++fitr)
{
osg::Vec3f osgNormal( copyLib3dsVec3ToOsgVec3(normals[*fitr]) );
if (matrix) osgNormal = osg::Matrix::transform3x3(osgNormal, *matrix);
osgNormal.normalize();
*(normal_itr++) = osgNormal;
}
geom->setNormalArray(osg_normals);
geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE);
}
osg::Vec4ubArray* osg_colors = new osg::Vec4ubArray(1);
(*osg_colors)[0].set(255,255,255,255);
geom->setColorArray(osg_colors);
geom->setColorBinding(osg::Geometry::BIND_OVERALL);
// create primitives
int numIndices = faceList.size()*3;
DrawElementsUShort* elements = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES,numIndices);
DrawElementsUShort::iterator index_itr = elements->begin();
for (fitr=faceList.begin();
fitr!=faceList.end();
++fitr)
{
Lib3dsFace& face = m->faces[*fitr];
*(index_itr++) = orig2NewMapping[face.index[0]];
*(index_itr++) = orig2NewMapping[face.index[1]];
*(index_itr++) = orig2NewMapping[face.index[2]];
}
geom->addPrimitiveSet(elements);
#if 0
osgUtil::TriStripVisitor tsv;
tsv.stripify(*geom);
#endif
return geom;
}
osg::Texture2D* ReaderWriter3DS::ReaderObject::createTexture(Lib3dsTextureMap *texture,const char* label,bool& transparancy)
{
if (texture && *(texture->name))
{
osg::notify(osg::NOTICE)<<"texture->name="<<texture->name<<", _directory="<<_directory<<std::endl;
std::string fileName = osgDB::findFileInDirectory(texture->name,_directory,osgDB::CASE_INSENSITIVE);
if (fileName.empty())
{
// file not found in .3ds file's directory, so we'll look in the datafile path list.
fileName = osgDB::findDataFile(texture->name,options, osgDB::CASE_INSENSITIVE);
osg::notify(osg::NOTICE)<<"texture->name="<<texture->name<<", _directory="<<_directory<<std::endl;
}
if (fileName.empty())
{
if (osgDB::containsServerAddress(_directory))
{
// if 3DS file is loaded from http, just attempt to load texture from same location.
fileName = _directory + "/" + texture->name;
} else {
osg::notify(osg::WARN) << "texture '"<<texture->name<<"' not found"<< std::endl;
return NULL;
}
}
if (label) osg::notify(osg::DEBUG_INFO) << label;
else osg::notify(osg::DEBUG_INFO) << "texture name";
osg::notify(osg::DEBUG_INFO) << " '"<<texture->name<<"'"<< std::endl;
osg::notify(osg::DEBUG_INFO) << " texture flag "<<texture->flags<< std::endl;
osg::notify(osg::DEBUG_INFO) << " LIB3DS_TEXTURE_DECALE "<<((texture->flags)&LIB3DS_TEXTURE_DECALE)<< std::endl;
osg::notify(osg::DEBUG_INFO) << " LIB3DS_TEXTURE_MIRROR "<<((texture->flags)&LIB3DS_TEXTURE_MIRROR)<< std::endl;
osg::notify(osg::DEBUG_INFO) << " LIB3DS_TEXTURE_NEGATE "<<((texture->flags)&LIB3DS_TEXTURE_NEGATE)<< std::endl;
osg::notify(osg::DEBUG_INFO) << " LIB3DS_TEXTURE_NO_TILE "<<((texture->flags)&LIB3DS_TEXTURE_NO_TILE)<< std::endl;
osg::notify(osg::DEBUG_INFO) << " LIB3DS_TEXTURE_SUMMED_AREA "<<((texture->flags)&LIB3DS_TEXTURE_SUMMED_AREA)<< std::endl;
osg::notify(osg::DEBUG_INFO) << " LIB3DS_TEXTURE_ALPHA_SOURCE "<<((texture->flags)&LIB3DS_TEXTURE_ALPHA_SOURCE)<< std::endl;
osg::notify(osg::DEBUG_INFO) << " LIB3DS_TEXTURE_TINT "<<((texture->flags)&LIB3DS_TEXTURE_TINT)<< std::endl;
osg::notify(osg::DEBUG_INFO) << " LIB3DS_TEXTURE_IGNORE_ALPHA "<<((texture->flags)&LIB3DS_TEXTURE_IGNORE_ALPHA)<< std::endl;
osg::notify(osg::DEBUG_INFO) << " LIB3DS_TEXTURE_RGB_TINT "<<((texture->flags)&LIB3DS_TEXTURE_RGB_TINT)<< std::endl;
osg::ref_ptr<osg::Image> osg_image = osgDB::readRefImageFile(fileName.c_str(), options); //Absolute Path
if (!osg_image.valid())
{
osg::notify(osg::NOTICE) << "Warning: Cannot create texture "<<texture->name<< std::endl;
return NULL;
}
if (osg_image->getFileName().empty()) // it should be done in OSG with osgDB::readRefImageFile(fileName.c_str());
osg_image->setFileName(fileName);
osg::Texture2D* osg_texture = new osg::Texture2D;
osg_texture->setImage(osg_image.get());
osg_texture->setName(texture->name);
// does the texture support transparancy?
transparancy = ((texture->flags)&LIB3DS_TEXTURE_ALPHA_SOURCE)!=0;
// what is the wrap mode of the texture.
osg::Texture2D::WrapMode wm = ((texture->flags)&LIB3DS_TEXTURE_NO_TILE) ?
osg::Texture2D::CLAMP :
osg::Texture2D::REPEAT;
osg_texture->setWrap(osg::Texture2D::WRAP_S,wm);
osg_texture->setWrap(osg::Texture2D::WRAP_T,wm);
osg_texture->setWrap(osg::Texture2D::WRAP_R,wm);
// bilinear.
osg_texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR_MIPMAP_NEAREST);
return osg_texture;
}
else
return NULL;
}
osg::StateSet* ReaderWriter3DS::ReaderObject::createStateSet(Lib3dsMaterial *mat)
{
if (mat==NULL) return NULL;
osg::StateSet* stateset = new osg::StateSet;
osg::Material* material = new osg::Material;
float transparency = mat->transparency;
float alpha = 1.0f-transparency;
osg::Vec4 ambient(mat->ambient[0],mat->ambient[1],mat->ambient[2],alpha);
osg::Vec4 diffuse(mat->diffuse[0],mat->diffuse[1],mat->diffuse[2],alpha);
osg::Vec4 specular(mat->specular[0],mat->specular[1],mat->specular[2],alpha);
specular *= mat->shin_strength;
float shininess = mat->shininess;
material->setName(mat->name);
material->setAmbient(osg::Material::FRONT_AND_BACK,ambient);
material->setDiffuse(osg::Material::FRONT_AND_BACK,diffuse);
material->setSpecular(osg::Material::FRONT_AND_BACK,specular);
material->setShininess(osg::Material::FRONT_AND_BACK,shininess*128.0f);
stateset->setAttribute(material);
bool textureTransparancy=false;
osg::Texture2D* texture1_map = createTexture(&(mat->texture1_map),"texture1_map",textureTransparancy);
if (texture1_map)
{
stateset->setTextureAttributeAndModes(0,texture1_map,osg::StateAttribute::ON);
if (!textureTransparancy)
{
// from an email from Eric Hamil, September 30, 2003.
// According to the 3DS spec, and other
// software (like Max, Lightwave, and Deep Exploration) a 3DS material that has
// a non-white diffuse base color and a 100% opaque bitmap texture, will show the
// texture with no influence from the base color.
// so we'll override material back to white.
// and no longer require the decal hack below...
#if 0
// Eric original fallback
osg::Vec4 white(1.0f,1.0f,1.0f,alpha);
material->setAmbient(osg::Material::FRONT_AND_BACK,white);
material->setDiffuse(osg::Material::FRONT_AND_BACK,white);
material->setSpecular(osg::Material::FRONT_AND_BACK,white);
#else
// try alternative to avoid saturating with white
// setting white as per OpenGL defaults.
material->setAmbient(osg::Material::FRONT_AND_BACK,osg::Vec4(0.2f,0.2f,0.2f,alpha));
material->setDiffuse(osg::Material::FRONT_AND_BACK,osg::Vec4(0.8f,0.8f,0.8f,alpha));
material->setSpecular(osg::Material::FRONT_AND_BACK,osg::Vec4(0.0f,0.0f,0.0f,alpha));
#endif
}
// no longer required...
// bool decal = false;
//
// // not sure exactly how to interpret what is best for .3ds
// // but the default text env MODULATE doesn't work well, and
// // DECAL seems to work better.
// osg::TexEnv* texenv = new osg::TexEnv;
// if (decal)
// {
// texenv->setMode(osg::TexEnv::DECAL);
// }
// else
// {
// texenv->setMode(osg::TexEnv::MODULATE);
// }
// stateset->setTextureAttribute(0,texenv);
}
if (transparency>0.0f || textureTransparancy)
{
stateset->setMode(GL_BLEND,osg::StateAttribute::ON);
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
/*
osg::ref_ptr<osg::Texture> texture1_mask = createTexture(&(mat->texture1_mask),"texture1_mask",textureTransparancy);
osg::ref_ptr<osg::Texture> texture2_map = createTexture(&(mat->texture2_map),"texture2_map",textureTransparancy);
osg::ref_ptr<osg::Texture> texture2_mask = createTexture(&(mat->texture2_mask),"texture2_mask",textureTransparancy);
osg::ref_ptr<osg::Texture> opacity_map = createTexture(&(mat->opacity_map),"opacity_map",textureTransparancy);
osg::ref_ptr<osg::Texture> opacity_mask = createTexture(&(mat->opacity_mask),"opacity_mask",textureTransparancy);
osg::ref_ptr<osg::Texture> bump_map = createTexture(&(mat->bump_map),"bump_map",textureTransparancy);
osg::ref_ptr<osg::Texture> bump_mask = createTexture(&(mat->bump_mask),"bump_mask",textureTransparancy);
osg::ref_ptr<osg::Texture> specular_map = createTexture(&(mat->specular_map),"specular_map",textureTransparancy);
osg::ref_ptr<osg::Texture> specular_mask = createTexture(&(mat->specular_mask),"specular_mask",textureTransparancy);
osg::ref_ptr<osg::Texture> shininess_map = createTexture(&(mat->shininess_map),"shininess_map",textureTransparancy);
osg::ref_ptr<osg::Texture> shininess_mask = createTexture(&(mat->shininess_mask),"shininess_mask",textureTransparancy);
osg::ref_ptr<osg::Texture> self_illum_map = createTexture(&(mat->self_illum_map),"self_illum_map",textureTransparancy);
osg::ref_ptr<osg::Texture> self_illum_mask = createTexture(&(mat->self_illum_mask),"self_illum_mask",textureTransparancy);
osg::ref_ptr<osg::Texture> reflection_map = createTexture(&(mat->reflection_map),"reflection_map",textureTransparancy);
osg::ref_ptr<osg::Texture> reflection_mask = createTexture(&(mat->reflection_mask),"reflection_mask",textureTransparancy);
*/
return stateset;
}
#if ENABLE_3DS_WRITER
osgDB::ReaderWriter::WriteResult ReaderWriter3DS::writeNode(const osg::Node& node,const std::string& fileName,const Options* options) const {
std::string ext = osgDB::getLowerCaseFileExtension(fileName);
if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
//osg::notify(osg::WARN) << "!!WARNING!! 3DS write support is incomplete" << std::endl;
bool ok = true;
Lib3dsFile * file3ds = lib3ds_file_new();
if (!file3ds) return WriteResult(WriteResult::ERROR_IN_WRITING_FILE);
try {
osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
local_opt->getDatabasePathList().push_front(osgDB::getFilePath(fileName));
if (!createFileObject(node, file3ds, fileName, local_opt)) ok = false;
if (ok && !lib3ds_file_save(file3ds, fileName.c_str())) ok = false;
} catch (...) {
lib3ds_file_free(file3ds);
throw;
}
lib3ds_file_free(file3ds);
return ok ? WriteResult(WriteResult::FILE_SAVED) : WriteResult(WriteResult::ERROR_IN_WRITING_FILE);
//return ok ? WriteResult(WriteResult::FILE_SAVED) : WriteResult(WriteResult::FILE_NOT_HANDLED);
}
osgDB::ReaderWriter::WriteResult ReaderWriter3DS::writeNode(const osg::Node& node,std::ostream& fout,const Options* options) const {
//osg::notify(osg::WARN) << "!!WARNING!! 3DS write support is incomplete" << std::endl;
std::string optFileName = "";
if (options)
{
optFileName = options->getPluginStringData("STREAM_FILENAME");
}
Lib3dsIo io;
io.self = &fout;
io.seek_func = fileo_seek_func;
io.tell_func = fileo_tell_func;
io.read_func = NULL;
io.write_func = fileo_write_func;
io.log_func = fileio_log_func;
Lib3dsFile * file3ds = lib3ds_file_new();
bool ok = true;
try {
osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
local_opt->getDatabasePathList().push_front(osgDB::getFilePath(optFileName));
if (!createFileObject(node, file3ds, optFileName, local_opt)) ok = false;
if (ok && !lib3ds_file_write(file3ds, &io)) ok = false;
} catch (...) {
lib3ds_file_free(file3ds);
throw;
}
lib3ds_file_free(file3ds);
return ok ? WriteResult(WriteResult::FILE_SAVED) : WriteResult(WriteResult::ERROR_IN_WRITING_FILE);
//return ok ? WriteResult(WriteResult::FILE_SAVED) : WriteResult(WriteResult::FILE_NOT_HANDLED);
}
const std::string getParent(const std::string & pathBad)
{
const std::string & path = osgDB::convertFileNameToNativeStyle(pathBad);
std::string parent = "";
std::string tmp = "";
for(std::string::const_iterator itPath = path.begin();; ++itPath)
{
if (!parent.empty())
parent += '\\';
parent += tmp;
tmp.clear();
for(;itPath != path.end() && *itPath != '\\'; ++itPath)
tmp += *itPath;
if (itPath == path.end())
break;
}
return parent;
}
bool ReaderWriter3DS::createFileObject(const osg::Node& node, Lib3dsFile * file3ds,const std::string& fileName, const osgDB::ReaderWriter::Options* options) const {
WriterNodeVisitor w(file3ds, fileName, options, getParent(node.getName()));
const_cast<osg::Node &>(node).accept(w); // TODO Remove that ugly const_cast<>. Any idea?
if (!w.suceedLastApply())
return false;
w.writeMaterials();
return true; //w.good();
}
#endif // ENABLE_3DS_WRITER