Files
OpenSceneGraph/src/osgPlugins/obj/ReaderWriterOBJ.cpp

404 lines
12 KiB
C++

// -*-c++-*-
/*
* Wavefront OBJ loader for Open Scene Graph
*
* Copyright (C) 2001 Ulrich Hertlein <u.hertlein@web.de>
*
* Modified by Robert Osfield to support per Drawable coord, normal and
* texture coord arrays, bug fixes, and support for texture mapping.
*
* The Open Scene Graph (OSG) is a cross platform C++/OpenGL library for
* real-time rendering of large 3D photo-realistic models.
* The OSG homepage is http://www.openscenegraph.org/
*/
#if defined(_MSC_VER)
#pragma warning( disable : 4786 )
#endif
#include <string>
#include <osg/Notify>
#include <osg/Node>
#include <osg/Transform>
#include <osg/Geode>
#include <osg/GeoSet>
#include <osg/StateSet>
#include <osg/Material>
#include <osg/Texture>
#include <osg/TexGen>
#include <osgDB/Registry>
#include <osgDB/ReadFile>
#include <osgDB/FileUtils>
#include <osgDB/FileNameUtils>
#include "glm.h"
class ReaderWriterOBJ : public osgDB::ReaderWriter
{
public:
ReaderWriterOBJ() { }
virtual const char* className() { return "Wavefront OBJ Reader"; }
virtual bool acceptsExtension(const std::string& extension) {
return (extension == "obj");
}
virtual ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options*);
protected:
osg::Drawable* makeDrawable(GLMmodel* obj, GLMgroup* grp, osg::StateSet**);
};
// register with Registry to instantiate the above reader/writer.
osgDB::RegisterReaderWriterProxy<ReaderWriterOBJ> g_objReaderWriterProxy;
// read file and convert to OSG.
osgDB::ReaderWriter::ReadResult ReaderWriterOBJ::readNode(const std::string& fileName, const osgDB::ReaderWriter::Options*)
{
GLMmodel* obj = glmReadOBJ((char*) fileName.c_str());
if (!obj)
return ReadResult::FILE_NOT_HANDLED;
std::string directory = osgDB::getFilePath(fileName);
osg::notify(osg::INFO) << "vertices " << obj->numvertices << std::endl;
osg::notify(osg::INFO) << "normals " << obj->numnormals << std::endl;
osg::notify(osg::INFO) << "texcoords " << obj->numtexcoords << std::endl;
osg::notify(osg::INFO) << "face normals " << obj->numfacetnorms << std::endl;
osg::notify(osg::INFO) << "tris " << obj->numtriangles << std::endl;
osg::notify(osg::INFO) << "materials " << obj->nummaterials << std::endl;
osg::notify(osg::INFO) << "groups " << obj->numgroups << std::endl;
if (obj->numnormals==0)
{
osg::notify(osg::NOTICE) << "No normals in .obj file, automatically calculating normals..."<< std::endl;
glmFacetNormals(obj);
glmVertexNormals(obj,90.0f);
}
unsigned int i;
// materials
osg::StateSet** osg_mtl = NULL;
if (obj->nummaterials > 0) {
osg_mtl = new osg::StateSet*[obj->nummaterials];
for (i = 0; i < obj->nummaterials; i++) {
GLMmaterial* omtl = &(obj->materials[i]);
osg::notify(osg::DEBUG_INFO) << "mtl: " << omtl->name << std::endl;
osg::StateSet* stateset = new osg::StateSet;
osg_mtl[i] = stateset;
osg::Material* mtl = new osg::Material;
mtl->setAmbient(osg::Material::FRONT_AND_BACK,
osg::Vec4(omtl->ambient[0], omtl->ambient[1],
omtl->ambient[2], omtl->ambient[3]));
mtl->setDiffuse(osg::Material::FRONT_AND_BACK,
osg::Vec4(omtl->diffuse[0], omtl->diffuse[1],
omtl->diffuse[2], omtl->diffuse[3]));
mtl->setSpecular(osg::Material::FRONT_AND_BACK,
osg::Vec4(omtl->specular[0], omtl->specular[1],
omtl->specular[2], omtl->specular[3]));
mtl->setEmission(osg::Material::FRONT_AND_BACK,
osg::Vec4(omtl->emmissive[0], omtl->emmissive[1],
omtl->emmissive[2], omtl->emmissive[3]));
// note, osg shininess scales between 0.0 and 1.0.
mtl->setShininess(osg::Material::FRONT_AND_BACK, omtl->shininess/128.0f);
stateset->setAttribute(mtl);
if (omtl->textureName)
{
std::string fileName = osgDB::findFileInDirectory(omtl->textureName,directory,true);
if (!fileName.empty())
{
osg::Image* osg_image = osgDB::readImageFile(fileName.c_str());
if (osg_image)
{
osg::Texture* osg_texture = new osg::Texture;
osg_texture->setImage(osg_image);
stateset->setAttributeAndModes(osg_texture,osg::StateAttribute::ON);
if (omtl->textureReflection)
{
osg::TexGen* osg_texgen = new osg::TexGen;
osg_texgen->setMode(osg::TexGen::SPHERE_MAP);
stateset->setAttributeAndModes(osg_texgen,osg::StateAttribute::ON);
}
}
else
{
osg::notify(osg::NOTICE) << "Warning: Cannot create texture "<<omtl->textureName<< std::endl;
}
}
else
{
osg::notify(osg::WARN) << "texture '"<<omtl->textureName<<"' not found"<< std::endl;
}
}
}
}
// toplevel group or transform
osg::Group* osg_top = NULL;
if (obj->position[0] != 0.0f || obj->position[2] != 0.0f || obj->position[2] != 0.0f) {
osg::Transform* xform = new osg::Transform;
// note obj_x -> osg_x,
// obj_y -> osg_z,
// obj_z -> osg_y,
xform->setMatrix(osg::Matrix::translate(obj->position[0], obj->position[2], obj->position[1]));
osg_top = xform;
}
else
osg_top = new osg::Group;
osg_top->setName(obj->pathname);
// subgroups
// XXX one Geode per group is probably not necessary...
GLMgroup* ogrp = obj->groups;
while (ogrp) {
if (ogrp->numtriangles > 0) {
osg::Geode* osg_geo = new osg::Geode;
osg_geo->setName(ogrp->name);
osg_geo->addDrawable(makeDrawable(obj,ogrp, osg_mtl));
osg_top->addChild(osg_geo);
}
ogrp = ogrp->next;
}
// free
glmDelete(obj);
if (osg_mtl)
delete[] osg_mtl;
return osg_top;
}
// make drawable from OBJ group
osg::Drawable* ReaderWriterOBJ::makeDrawable(GLMmodel* obj,
GLMgroup* grp,
osg::StateSet** mtl)
{
GLMtriangle* tris = obj->triangles;
unsigned int ntris = grp->numtriangles;
unsigned int i = 0;
// geoset
osg::GeoSet* gset = new osg::GeoSet;
// primitives are only triangles
gset->setPrimType(osg::GeoSet::TRIANGLES);
gset->setNumPrims(ntris);
int* primLen = new int[ntris];
gset->setPrimLengths(primLen);
// the following code for mapping the coords, normals and texcoords
// is complicated greatly by the need to create seperate out the
// sets of coords etc for each drawable.
typedef std::vector<int> IndexVec;
// fill an vector full of 0's, one for each vertex.
IndexVec vcount(obj->numvertices+1,0);
IndexVec ncount(obj->numnormals+1,0);
IndexVec tcount(obj->numtexcoords+1,0);
bool needNormals = obj->normals && obj->normals>0;
bool needTexcoords = obj->texcoords && obj->numtexcoords>0;
// first count the number of vertices used in this group.
for (i = 0; i < ntris; i++)
{
GLMtriangle* tri = &(tris[grp->triangles[i]]);
// increment the count once for each traingle corner.
++vcount[tri->vindices[0]];
++vcount[tri->vindices[1]];
++vcount[tri->vindices[2]];
if (needNormals)
{
++ncount[tri->nindices[0]];
++ncount[tri->nindices[1]];
++ncount[tri->nindices[2]];
}
if (needTexcoords)
{
++tcount[tri->tindices[0]];
++tcount[tri->tindices[1]];
++tcount[tri->tindices[2]];
}
}
IndexVec::iterator itr;
int numCoords = 0;
for(itr=vcount.begin();
itr!=vcount.end();
++itr)
{
if ((*itr)>0) ++numCoords;
}
if (numCoords==0) return NULL;
int numNormals = 0;
for(itr=ncount.begin();
itr!=ncount.end();
++itr)
{
if ((*itr)>0) ++numNormals;
}
int numTexcoords = 0;
for(itr=tcount.begin();
itr!=tcount.end();
++itr)
{
if ((*itr)>0) ++numTexcoords;
}
// allocate drawables vertices.
osg::Vec3* coords = new osg::Vec3[numCoords];
// allocate drawables normals.
osg::Vec3* normals = NULL;
if (numNormals>0) normals = new osg::Vec3[numNormals];
// allocate drawables textcoords.
osg::Vec2* texcoords = NULL;
if (numTexcoords>0) texcoords = new osg::Vec2[numTexcoords];
// fill an vector full of 0's, one for each vertex.
IndexVec vmapping(obj->numvertices+1,-1);
IndexVec nmapping(obj->numnormals+1,-1);
IndexVec tmapping(obj->numtexcoords+1,-1);
int coordCount = 0;
for (i = 0; i < vcount.size(); i++)
{
if (vcount[i]>0)
{
// note obj_x -> osg_x,
// obj_y -> -osg_z,
// obj_z -> osg_y,
coords[coordCount].set(obj->vertices[i*3+0],
-obj->vertices[i*3+2],
obj->vertices[i*3+1]);
vmapping[i]=coordCount;
++coordCount;
}
}
int normCount = 0;
for (i = 0; i < ncount.size(); i++)
{
if (ncount[i]>0)
{
// note obj_x -> osg_x,
// obj_y -> osg_z,
// obj_z -> osg_y,
// to rotate the about x axis to have model z upwards.
normals[normCount].set(obj->normals[i*3+0],
-obj->normals[i*3+2],
obj->normals[i*3+1]);
nmapping[i]=normCount;
++normCount;
}
}
int texCount = 0;
for (i = 0; i < tcount.size(); i++)
{
if (tcount[i]>0)
{
texcoords[texCount].set(obj->texcoords[i*2+0], obj->texcoords[i*2+1]);
tmapping[i]=texCount;
++texCount;
}
}
// index arrays
unsigned int* cindex = NULL;
cindex = new unsigned int[ntris * 3];
unsigned int* nindex = NULL;
if (normals)
nindex = new unsigned int[ntris * 3];
unsigned int* tindex = NULL;
if (texcoords)
tindex = new unsigned int[ntris * 3];
// setup arrays
for (i = 0; i < ntris; i++) {
primLen[i] = 3;
GLMtriangle* tri = &(tris[grp->triangles[i]]);
cindex[i*3+0] = vmapping[tri->vindices[0]];
cindex[i*3+1] = vmapping[tri->vindices[1]];
cindex[i*3+2] = vmapping[tri->vindices[2]];
if (nindex) {
nindex[i*3+0] = nmapping[tri->nindices[0]];
nindex[i*3+1] = nmapping[tri->nindices[1]];
nindex[i*3+2] = nmapping[tri->nindices[2]];
}
if (tindex) {
tindex[i*3+0] = tmapping[tri->tindices[0]];
tindex[i*3+1] = tmapping[tri->tindices[1]];
tindex[i*3+2] = tmapping[tri->tindices[2]];
}
}
if (coords && cindex)
gset->setCoords(coords, cindex);
if (normals && nindex) {
gset->setNormals(normals, nindex);
gset->setNormalBinding(osg::GeoSet::BIND_PERVERTEX);
}
if (texcoords && tindex) {
gset->setTextureCoords(texcoords, tindex);
gset->setTextureBinding(osg::GeoSet::BIND_PERVERTEX);
}
// state and material (if any)
if (mtl) {
gset->setStateSet(mtl[grp->material]);
}
else
osg::notify(osg::INFO) << "Group " << grp->name << " has no material" << std::endl;
return gset;
}