OpenSceneGraph/src/osgPlugins/ac/ac3d.cpp
2010-05-28 16:30:36 +00:00

1389 lines
48 KiB
C++

// 30 Oct 2002
// AC3D loader for models generated by the AC3D modeller (www.ac3d.org)
// part of this source code were supplied by the AC3D project (Andy Colebourne)
// eg the basic parsing of an AC3D file.
// Conversion from AC3D scenegraph to OSG by GW Michel.
#include <vector>
#include <iostream>
#include <stdlib.h>
#include <osg/GL>
#include <osg/GLU>
#include <osg/Math>
#include <osg/BlendFunc>
#include <osg/CullFace>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>
#include <osg/Light>
#include <osg/LightSource>
#include <osg/Material>
#include <osg/Math>
#include <osg/Texture2D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osg/ShadeModel>
#include <osg/Math>
#include <osg/Notify>
#include <osgUtil/Tessellator>
#include <osgDB/FileNameUtils>
#include <osgDB/Registry>
#include <osgDB/ReadFile>
#include <osgDB/FileUtils>
#include <osgDB/fstream>
#include "Exception.h"
#include "Geode.h"
namespace ac3d {
osg::Node*
readFile(std::istream& stream, const osgDB::ReaderWriter::Options* options);
}
class geodeVisitor : public osg::NodeVisitor { // collects geodes from scene sub-graph attached to 'this'
public:
geodeVisitor():
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {}
~geodeVisitor() { _geodelist.clear();}
// one apply for each type of Node that might be a user transform
virtual void apply(osg::Geode& geode) {
_geodelist.push_back(&geode);
}
virtual void apply(osg::Group& gp){
traverse(gp); // must continue subgraph traversal.
}
std::vector<const osg::Geode *> getGeodes() {return _geodelist;}
protected:
typedef std::vector<const osg::Geode *> Geodelist;
Geodelist _geodelist;
};
class ReaderWriterAC : public osgDB::ReaderWriter
{
public:
ReaderWriterAC()
{
supportsExtension("ac","AC3D Database format");
}
virtual const char* className() const { return "AC3D Database Reader"; }
virtual ReadResult readNode(const std::string& file,const Options* options) const
{
std::string ext = osgDB::getFileExtension(file);
if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;
// GWM added Dec 2003 - get full path name (change in osgDB handling of files).
std::string fileName = osgDB::findDataFile( file, options );
OSG_INFO << "osgDB ac3d reader: starting reading \"" << fileName << "\"" << std::endl;
// Anders Backmann - correct return if path not found
if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;
// allocate per file data and start reading
osgDB::ifstream fin;
fin.open(fileName.c_str(), std::ios::in);
if (!fin.is_open()) return ReadResult::FILE_NOT_FOUND;
// code for setting up the database path so that internally referenced file are
// searched for on relative paths.
osg::ref_ptr<Options> local_opt;
if (options)
local_opt = static_cast<Options*>(options->clone(osg::CopyOp::DEEP_COPY_ALL));
else
local_opt = new Options;
local_opt->getDatabasePathList().push_back(osgDB::getFilePath(fileName));
ReadResult result = readNode(fin, local_opt.get());
if (result.validNode())
result.getNode()->setName(fileName);
return result;
}
virtual ReadResult readNode(std::istream& fin, const Options* options) const
{
std::string header;
fin >> header;
if (header.substr(0, 4) != "AC3D")
return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
return ac3d::readFile(fin, options);
}
virtual WriteResult writeNode(const osg::Node& node,const std::string& fileName, const Options* /*options*/) const
{
std::string ext = osgDB::getFileExtension(fileName);
if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
geodeVisitor vs; // this collects geodes.
std::vector<unsigned int>iNumMaterials;
const_cast<osg::Node&>(node).accept(vs); // this parses the tree to streamd Geodes
std::vector<const osg::Geode *> glist=vs.getGeodes();
osgDB::ofstream fout(fileName.c_str(), std::ios::out | std::ios::binary);
// Write out the file header
std::vector<const osg::Geode *>::iterator itr;
fout << "AC3Db" << std::endl;
// output the Materials
int iNumGeodesWithGeometry = 0;
for (itr=glist.begin();itr!= glist.end();itr++)
{
iNumMaterials.push_back(const_cast<ac3d::Geode*>(static_cast<const ac3d::Geode*>(*itr))->ProcessMaterial(fout,itr-glist.begin()));
unsigned int iNumDrawables = (*itr)->getNumDrawables();
int iNumGeometries = 0;
for (unsigned int i = 0; i < iNumDrawables; i++)
{
const osg::Drawable* pDrawable = (*itr)->getDrawable(i);
if (NULL != pDrawable)
{
const osg::Geometry *pGeometry = pDrawable->asGeometry();
if (NULL != pGeometry)
iNumGeometries++;
}
}
if (iNumGeometries > 0)
iNumGeodesWithGeometry++;
}
// output the Geometry
unsigned int nfirstmat=0;
fout << "OBJECT world" << std::endl;
fout << "kids " << iNumGeodesWithGeometry << std::endl;
for (itr=glist.begin();itr!= glist.end();itr++) {
const_cast<ac3d::Geode*>(static_cast<const ac3d::Geode*>(*itr))->ProcessGeometry(fout,nfirstmat);
nfirstmat+=iNumMaterials[itr-glist.begin()];
}
fout.close();
return WriteResult::FILE_SAVED;
}
virtual WriteResult writeNode(const osg::Node& node,std::ostream& fout, const Options* opts) const
{
// write ac file.
if(dynamic_cast<const osg::Group*>(&node))
{
const osg::Group *gp=dynamic_cast<const osg::Group*>(&node);
const unsigned int nch=gp->getNumChildren();
for (unsigned int i=0; i<nch; i++)
{
writeNode(*(gp->getChild(i)), fout, opts);
}
}
else
OSG_WARN<<"File must start with a geode "<<std::endl;
fout.flush();
return WriteResult::FILE_SAVED;
}
private:
};
// now register with osg::Registry to instantiate the above
// reader/writer.
REGISTER_OSGPLUGIN(ac, ReaderWriterAC)
namespace ac3d {
enum {
ObjectTypeNormal = 0,
ObjectTypeGroup = 1,
ObjectTypeLight = 2,
SurfaceTypePolygon = 0,
SurfaceTypeLineLoop = 1,
SurfaceTypeLineStrip = 2,
SurfaceShaded = 1<<4,
SurfaceTwoSided = 1<<5
};
/// Returns a possibly quoted string given in the end of the current line in the stream
static
std::string
readString(std::istream& stream)
{
std::string s;
stream >> std::ws;
if (stream.peek() != '\"')
{
// Not quoted, just read the string
stream >> s;
}
else
{
// look for quoted strings
// throw away the quote
stream.get();
// extract characters until either an error happens or a quote is found
while (stream.good())
{
std::istream::char_type c;
stream.get(c);
if (c == '\"')
break;
s += c;
}
}
return s;
}
static void
setTranslucent(osg::StateSet* stateSet)
{
osg::BlendFunc* blendFunc = new osg::BlendFunc;
blendFunc->setDataVariance(osg::Object::STATIC);
blendFunc->setSource(osg::BlendFunc::SRC_ALPHA);
blendFunc->setDestination(osg::BlendFunc::ONE_MINUS_SRC_ALPHA);
stateSet->setAttribute(blendFunc);
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
// Just a container to store an ac3d material
class MaterialData
{
public:
MaterialData() :
mMaterial(new osg::Material),
mColorArray(new osg::Vec4Array(1))
{
mMaterial->setDataVariance(osg::Object::STATIC);
mColorArray->setDataVariance(osg::Object::STATIC);
}
void readMaterial(std::istream& stream)
{
// note that this might be quoted
std::string name = readString(stream);
mMaterial->setName(name);
std::string tmp;
stream >> tmp;
osg::Vec4 diffuse;
stream >> diffuse[0] >> diffuse[1] >> diffuse[2];
mMaterial->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse);
stream >> tmp;
osg::Vec4 ambient;
stream >> ambient[0] >> ambient[1] >> ambient[2];
mMaterial->setAmbient(osg::Material::FRONT_AND_BACK, ambient);
stream >> tmp;
osg::Vec4 emmissive;
stream >> emmissive[0] >> emmissive[1] >> emmissive[2];
mMaterial->setEmission(osg::Material::FRONT_AND_BACK, emmissive);
stream >> tmp;
osg::Vec4 specular;
stream >> specular[0] >> specular[1] >> specular[2];
mMaterial->setSpecular(osg::Material::FRONT_AND_BACK, specular);
stream >> tmp;
float shininess;
stream >> shininess;
mMaterial->setShininess(osg::Material::FRONT_AND_BACK, shininess);
stream >> tmp;
float transparency;
stream >> transparency;
mMaterial->setTransparency(osg::Material::FRONT_AND_BACK, transparency);
mTranslucent = 0 < transparency;
// must correspond to the material we use for the color array below
mMaterial->setColorMode(osg::Material::DIFFUSE);
// this must be done past the transparency setting ...
(*mColorArray)[0] = mMaterial->getDiffuse(osg::Material::FRONT_AND_BACK);
}
void toStateSet(osg::StateSet* stateSet) const
{
stateSet->setAttribute(mMaterial.get());
if (mTranslucent)
setTranslucent(stateSet);
}
osg::Vec4Array* getColorArray() const
{
return mColorArray.get();
}
private:
osg::ref_ptr<osg::Material> mMaterial;
osg::ref_ptr<osg::Vec4Array> mColorArray;
bool mTranslucent;
};
class TextureData
{
public:
TextureData() :
mTranslucent(false),
mRepeat(true)
{
}
bool setTexture(const std::string& name, const osgDB::ReaderWriter::Options* options, osg::TexEnv* modulateTexEnv)
{
mTexture2DRepeat = new osg::Texture2D;
mTexture2DRepeat->setDataVariance(osg::Object::STATIC);
mTexture2DRepeat->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT);
mTexture2DRepeat->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT);
mTexture2DClamp = new osg::Texture2D;
mTexture2DClamp->setDataVariance(osg::Object::STATIC);
mTexture2DClamp->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
mTexture2DClamp->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
std::string absFileName = osgDB::findDataFile(name, options);
if (absFileName.empty())
{
OSG_FATAL << "osgDB ac3d reader: could not find texture \"" << name << "\"" << std::endl;
return false;
}
mImage = osgDB::readRefImageFile(absFileName, options);
if (!mImage.valid())
{
OSG_FATAL << "osgDB ac3d reader: could not read texture \"" << name << "\"" << std::endl;
return false;
}
mTexture2DRepeat->setImage(mImage.get());
mTexture2DClamp->setImage(mImage.get());
mTranslucent = mImage->isImageTranslucent();
// Use a shared modulate TexEnv
mModulateTexEnv = modulateTexEnv;
return true;
}
void setRepeat(bool repeat)
{
mRepeat = repeat;
}
bool valid() const
{
return mImage.valid();
}
std::string getFileName() const
{
if (!mImage.valid())
return std::string();
return mImage->getFileName();
}
void toTextureStateSet(osg::StateSet* stateSet) const
{
if (!valid())
return;
stateSet->setTextureAttribute(0, mModulateTexEnv.get());
if (mRepeat)
stateSet->setTextureAttribute(0, mTexture2DRepeat.get());
else
stateSet->setTextureAttribute(0, mTexture2DClamp.get());
stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
if (mTranslucent)
setTranslucent(stateSet);
}
private:
osg::ref_ptr<osg::TexEnv> mModulateTexEnv;
osg::ref_ptr<osg::Texture2D> mTexture2DClamp;
osg::ref_ptr<osg::Texture2D> mTexture2DRepeat;
osg::ref_ptr<osg::Image> mImage;
bool mTranslucent;
bool mRepeat;
};
class FileData
{
public:
FileData(const osgDB::ReaderWriter::Options* options) :
mOptions(options),
mLightIndex(1)
{
mModulateTexEnv = new osg::TexEnv;
mModulateTexEnv->setDataVariance(osg::Object::STATIC);
mModulateTexEnv->setMode(osg::TexEnv::MODULATE);
}
TextureData toTextureData(const std::string& texName)
{
// If it is already there, use this
TextureDataMap::iterator i = mTextureStates.find(texName);
if (i != mTextureStates.end())
return i->second;
// Try to load that texture.
TextureData textureData;
textureData.setTexture(texName, mOptions.get(), mModulateTexEnv.get());
if (textureData.valid()) {
mTextureStates[texName] = textureData;
return textureData;
}
// still no joy?, try with the stripped filename if this is different
// Try the pure file name if it is different
std::string simpleTexName = osgDB::getSimpleFileName(texName);
if (simpleTexName != texName)
return toTextureData(simpleTexName);
// Nothing that worked, return invalid data
return TextureData();
}
osg::Light* getNextLight()
{
osg::Light* light = new osg::Light;
light->setDataVariance(osg::Object::STATIC);
light->setLightNum(mLightIndex++);
return light;
}
void addMaterial(const MaterialData& material)
{
mMaterials.push_back(material);
}
unsigned getNumMaterials() const
{
return mMaterials.size();
}
const MaterialData& getMaterial(unsigned idx) const
{
return mMaterials[idx];
}
private:
/// Stores the ac3d file reader options, only used for reading texture files
osg::ref_ptr<osgDB::ReaderWriter::Options const> mOptions;
/// The list of ac3d MATERIALS
std::vector<MaterialData> mMaterials;
/// Local per model texture attribute cache.
/// ... images are usualy cached in the registries object cache
typedef std::map<std::string, TextureData> TextureDataMap;
TextureDataMap mTextureStates;
/// A common shared TexEnv set to modulate
osg::ref_ptr<osg::TexEnv> mModulateTexEnv;
/// Hack to include light nodes from ac3d into the scenegraph
unsigned mLightIndex;
};
struct RefData {
RefData(const osg::Vec3& _weightedNormal, const osg::Vec2& _texCoord, bool _smooth) :
weightedFlatNormal(_weightedNormal),
weightedFlatNormalLength(_weightedNormal.length()),
texCoord(_texCoord),
smooth(_smooth)
{ }
// weighted flat surface normal
osg::Vec3 weightedFlatNormal;
float weightedFlatNormalLength;
osg::Vec2 texCoord;
// resulting vertex normal
osg::Vec3 finalNormal;
// if zero no need to smooth
unsigned smooth;
};
struct VertexData {
VertexData(const osg::Vec3& vertex) : _vertex(vertex) {}
unsigned addRefData(const RefData& refData)
{
unsigned index = _refs.size();
_refs.push_back(refData);
return index;
}
void collect(float cosCreaseAngle, const RefData& ref)
{
unsigned size = _refs.size();
for (unsigned i = 0; i < size; ++i)
{
if (_refs[i].smooth == ~0u)
{
float dot = _refs[i].weightedFlatNormal*ref.weightedFlatNormal;
float lengths = _refs[i].weightedFlatNormalLength*ref.weightedFlatNormalLength;
if (cosCreaseAngle*lengths <= dot)
{
// Ok put that into the current set
_refs[i].smooth = ref.smooth;
collect(cosCreaseAngle, _refs[i]);
}
}
}
}
void smoothNormals(float cosCreaseAngle)
{
// compute sets of vertices smoothed to the same normal
// if smooth is zero we do not need to smooth
// in a first pass mark all refs not yet in a set to ~0u
unsigned size = _refs.size();
for (unsigned i = 0; i < size; ++i)
{
if (_refs[i].smooth)
{
_refs[i].smooth = ~0u;
}
}
// Now collect the sets
unsigned currentSet = 1;
for (unsigned i = 0; i < size; ++i)
{
if (_refs[i].smooth == ~0u)
{
_refs[i].smooth = currentSet++;
collect(cosCreaseAngle, _refs[i]);
}
}
// smooth and normalize the sets
for (--currentSet; 0 < currentSet; --currentSet)
{
osg::Vec3 normal(0, 0, 0);
for (unsigned i = 0; i < size; ++i)
{
if (_refs[i].smooth == currentSet)
{
normal += _refs[i].weightedFlatNormal;
}
}
normal.normalize();
for (unsigned i = 0; i < size; ++i)
{
if (_refs[i].smooth == currentSet)
{
_refs[i].finalNormal = normal;
}
}
}
// normalize the ones which do not need smoothing
for (unsigned i = 0; i < size; ++i)
{
if (_refs[i].smooth == 0)
{
_refs[i].finalNormal = _refs[i].weightedFlatNormal;
_refs[i].finalNormal.normalize();
}
}
}
osg::Vec3 _vertex;
std::vector<RefData> _refs;
};
struct VertexIndex {
VertexIndex(unsigned _vertexIndex = 0, unsigned _refIndex = 0) :
vertexIndex(_vertexIndex), refIndex(_refIndex)
{ }
unsigned vertexIndex;
unsigned refIndex;
};
class VertexSet : public osg::Referenced {
public:
VertexSet() : _dirty(true)
{ }
void reserve(unsigned n)
{
_vertices.reserve(n);
}
unsigned size() const
{
return _vertices.size();
}
void setCreaseAngle(float crease)
{
_dirty = true;
if (crease <= 0)
_cosCreaseAngle = 1;
else if (180 <= crease)
_cosCreaseAngle = -1;
else
_cosCreaseAngle = cosf(osg::DegreesToRadians(crease));
}
void addVertex(const osg::Vec3& vertex)
{
_dirty = true;
_vertices.push_back(vertex);
}
const osg::Vec3& getVertex(unsigned index)
{
return _vertices[index]._vertex;
}
const osg::Vec3& getVertex(const VertexIndex& vertexIndex)
{
return _vertices[vertexIndex.vertexIndex]._vertex;
}
const osg::Vec3& getNormal(const VertexIndex& vertexIndex)
{
if (_dirty)
smoothNormals();
return _vertices[vertexIndex.vertexIndex]._refs[vertexIndex.refIndex].finalNormal;
}
const osg::Vec2& getTexCoord(const VertexIndex& vertexIndex)
{
return _vertices[vertexIndex.vertexIndex]._refs[vertexIndex.refIndex].texCoord;
}
VertexIndex addRefData(unsigned i, const RefData& refData)
{
if (_vertices.size() <= i)
{
OSG_FATAL << "osgDB ac3d reader: internal error, got invalid vertex index!" << std::endl;
return VertexIndex(0, 0);
}
_dirty = true;
return VertexIndex(i, _vertices[i].addRefData(refData));
}
private:
void smoothNormals()
{
std::vector<VertexData>::iterator i;
for (i = _vertices.begin(); i != _vertices.end(); ++i)
{
i->smoothNormals(_cosCreaseAngle);
}
_dirty = false;
}
std::vector<VertexData> _vertices;
float _cosCreaseAngle;
bool _dirty;
};
class PrimitiveBin : public osg::Referenced
{
public:
PrimitiveBin(unsigned flags, VertexSet* vertexSet) :
_geode(new osg::Geode),
_vertexSet(vertexSet),
_flags(flags)
{
_geode->setDataVariance(osg::Object::STATIC);
}
virtual bool beginPrimitive(unsigned nRefs) = 0;
virtual bool vertex(unsigned vertexIndex, const osg::Vec2& texCoord) = 0;
virtual bool endPrimitive() = 0;
virtual osg::Geode* finalize(const MaterialData& material, const TextureData& textureData) = 0;
protected:
bool isLineLoop() const
{
return (_flags & SurfaceTypeLineLoop)!=0;
}
bool isLineStrip() const
{
return (_flags & SurfaceTypeLineStrip)!=0;
}
bool isTwoSided() const
{
return (_flags & SurfaceTwoSided)!=0;
}
bool isSmooth() const
{
return (_flags & SurfaceShaded)!=0;
}
osg::ref_ptr<osg::Geode> _geode;
osg::ref_ptr<VertexSet> _vertexSet;
private:
unsigned _flags;
};
class LineBin : public PrimitiveBin
{
private:
osg::ref_ptr<osg::Geometry> _geometry;
osg::ref_ptr<osg::Vec3Array> _vertices;
osg::ref_ptr<osg::Vec2Array> _texCoords;
struct Ref {
osg::Vec2 texCoord;
unsigned index;
};
std::vector<Ref> _refs;
public:
LineBin(unsigned flags, VertexSet* vertexSet) :
PrimitiveBin(flags, vertexSet),
_geometry(new osg::Geometry),
_vertices(new osg::Vec3Array),
_texCoords(new osg::Vec2Array)
{
_geometry->setDataVariance(osg::Object::STATIC);
_vertices->setDataVariance(osg::Object::STATIC);
_texCoords->setDataVariance(osg::Object::STATIC);
_geometry->setVertexArray(_vertices.get());
_geometry->setTexCoordArray(0, _texCoords.get());
osg::StateSet* stateSet = _geode->getOrCreateStateSet();
stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
}
virtual bool beginPrimitive(unsigned nRefs)
{
// Check if we have enough for a line or someting broken ...
if (nRefs < 2) {
OSG_WARN << "osgDB ac3d reader: detected line with less than 2 vertices!" << std::endl;
return false;
}
_refs.reserve(nRefs);
_refs.resize(0);
return true;
}
virtual bool vertex(unsigned vertexIndex, const osg::Vec2& texCoord)
{
Ref ref;
ref.index = vertexIndex;
ref.texCoord = texCoord;
_refs.push_back(ref);
return true;
}
virtual bool endPrimitive()
{
GLint type;
if (isLineLoop())
type = osg::PrimitiveSet::LINE_LOOP;
else if (isLineStrip())
type = osg::PrimitiveSet::LINE_STRIP;
else {
OSG_FATAL << "osgDB ac3d reader: non surface flags in surface bin!" << std::endl;
return false;
}
unsigned nRefs = _refs.size();
unsigned start = _vertices->size();
for (unsigned i = 0; i < nRefs; ++i) {
osg::Vec3 vertex = _vertexSet->getVertex(_refs[i].index);
_vertices->push_back(vertex);
_texCoords->push_back(_refs[i].texCoord);
}
_geometry->addPrimitiveSet(new osg::DrawArrays(type, start, nRefs));
return true;
}
virtual osg::Geode* finalize(const MaterialData& material, const TextureData& textureData)
{
_geode->addDrawable(_geometry.get());
material.toStateSet(_geode->getOrCreateStateSet());
_geometry->setColorArray(material.getColorArray());
_geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
_geometry->setNormalBinding(osg::Geometry::BIND_OFF);
return _geode.get();
}
};
class SurfaceBin : public PrimitiveBin {
private:
struct Ref {
osg::Vec2 texCoord;
unsigned index;
};
std::vector<Ref> _refs;
struct TriangleData {
VertexIndex index[3];
};
std::vector<TriangleData> _triangles;
struct QuadData {
VertexIndex index[4];
};
std::vector<QuadData> _quads;
struct PolygonData {
std::vector<VertexIndex> index;
};
std::vector<PolygonData> _polygons;
std::vector<PolygonData> _toTessellatePolygons;
public:
SurfaceBin(unsigned flags, VertexSet *vertexSet) :
PrimitiveBin(flags, vertexSet)
{ }
virtual bool beginPrimitive(unsigned nRefs)
{
_refs.reserve(nRefs);
_refs.clear();
// Check if we have enough for a line or someting broken ...
if (nRefs < 3) {
OSG_WARN << "osgDB ac3d reader: detected surface with less than 3 vertices!" << std::endl;
return false;
}
return true;
}
virtual bool vertex(unsigned vertexIndex, const osg::Vec2& texCoord)
{
Ref ref;
ref.index = vertexIndex;
ref.texCoord = texCoord;
_refs.push_back(ref);
return true;
}
virtual bool endPrimitive()
{
unsigned nRefs = _refs.size();
// Compute the normal times the enclosed area.
// During that check if the surface is convex. If so, put in the surface as such.
bool needTessellation = false;
osg::Vec3 prevEdgeNormal;
osg::Vec3 weightedNormal(0, 0, 0);
osg::Vec3 v0 = _vertexSet->getVertex(_refs[0].index);
for (unsigned i = 2; i < nRefs; ++i) {
osg::Vec3 side1 = _vertexSet->getVertex(_refs[i-1].index) - v0;
osg::Vec3 side2 = _vertexSet->getVertex(_refs[i].index) - v0;
osg::Vec3 newNormal = side1^side2;
if (!needTessellation)
{
if (3 < nRefs && newNormal*weightedNormal < 0)
{
needTessellation = true;
}
if (i < 3)
{
prevEdgeNormal = newNormal;
}
else // if (3 <= i) // due to the for loop
{
osg::Vec3 sideim1 = _vertexSet->getVertex(_refs[i-1].index) - _vertexSet->getVertex(_refs[i-2].index);
osg::Vec3 sidei = _vertexSet->getVertex(_refs[i].index) - _vertexSet->getVertex(_refs[i-2].index);
osg::Vec3 edgeNormal = sideim1^sidei;
if (edgeNormal*prevEdgeNormal < 0)
{
needTessellation = true;
}
prevEdgeNormal = edgeNormal;
}
}
weightedNormal += newNormal;
}
if (needTessellation)
{
unsigned polygonIndex = _toTessellatePolygons.size();
_toTessellatePolygons.resize(polygonIndex + 1);
for (unsigned i = 0; i < nRefs; ++i) {
RefData refData(weightedNormal, _refs[i].texCoord, isSmooth());
VertexIndex vertexIndex = _vertexSet->addRefData(_refs[i].index, refData);
_toTessellatePolygons[polygonIndex].index.push_back(vertexIndex);
}
}
else if (nRefs == 3)
{
unsigned triangleIndex = _triangles.size();
_triangles.resize(triangleIndex + 1);
for (unsigned i = 0; i < 3; ++i) {
RefData refData(weightedNormal, _refs[i].texCoord, isSmooth());
VertexIndex vertexIndex = _vertexSet->addRefData(_refs[i].index, refData);
_triangles[triangleIndex].index[i] = vertexIndex;
}
}
else if (nRefs == 4)
{
unsigned quadIndex = _quads.size();
_quads.resize(quadIndex + 1);
for (unsigned i = 0; i < 4; ++i) {
RefData refData(weightedNormal, _refs[i].texCoord, isSmooth());
VertexIndex vertexIndex = _vertexSet->addRefData(_refs[i].index, refData);
_quads[quadIndex].index[i] = vertexIndex;
}
}
else
{
unsigned polygonIndex = _polygons.size();
_polygons.resize(polygonIndex + 1);
for (unsigned i = 0; i < nRefs; ++i) {
RefData refData(weightedNormal, _refs[i].texCoord, isSmooth());
VertexIndex vertexIndex = _vertexSet->addRefData(_refs[i].index, refData);
_polygons[polygonIndex].index.push_back(vertexIndex);
}
}
return true;
}
void pushVertex(const VertexIndex& vertexIndex, osg::Vec3Array* vertexArray,
osg::Vec3Array* normalArray, osg::Vec2Array* texcoordArray)
{
vertexArray->push_back(_vertexSet->getVertex(vertexIndex));
normalArray->push_back(_vertexSet->getNormal(vertexIndex));
if (texcoordArray)
texcoordArray->push_back(_vertexSet->getTexCoord(vertexIndex));
}
virtual osg::Geode* finalize(const MaterialData& material, const TextureData& textureData)
{
osg::StateSet* stateSet = _geode->getOrCreateStateSet();
material.toStateSet(stateSet);
textureData.toTextureStateSet(stateSet);
stateSet->setMode(GL_LIGHTING, osg::StateAttribute::ON);
// Single- or doublesided culling
if (isTwoSided()) {
stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
} else {
osg::CullFace* cullFace = new osg::CullFace;
cullFace->setDataVariance(osg::Object::STATIC);
cullFace->setMode(osg::CullFace::BACK);
stateSet->setAttribute(cullFace);
stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
}
// Flat or smooth shading
osg::ShadeModel* shadeModel = new osg::ShadeModel;
shadeModel->setDataVariance(osg::Object::STATIC);
if (isSmooth())
shadeModel->setMode(osg::ShadeModel::SMOOTH);
else
shadeModel->setMode(osg::ShadeModel::FLAT);
stateSet->setAttribute(shadeModel);
// Set up the arrays, allways store texture coords, may be we need them later ...
osg::Geometry* geometry = new osg::Geometry;
_geode->addDrawable(geometry);
geometry->setDataVariance(osg::Object::STATIC);
geometry->setColorArray(material.getColorArray());
geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
osg::Vec3Array* normalArray = new osg::Vec3Array;
normalArray->setDataVariance(osg::Object::STATIC);
geometry->setNormalArray(normalArray);
osg::Vec3Array* vertexArray = new osg::Vec3Array;
vertexArray->setDataVariance(osg::Object::STATIC);
geometry->setVertexArray(vertexArray);
osg::Vec2Array* texcoordArray = 0;
if (textureData.valid())
{
texcoordArray = new osg::Vec2Array;
texcoordArray->setDataVariance(osg::Object::STATIC);
geometry->setTexCoordArray(0, texcoordArray);
}
// At first handle the the polygons to tessellate, fix them and append the other polygons later
if (!_toTessellatePolygons.empty())
{
unsigned start = vertexArray->size();
osg::DrawArrayLengths* drawArrayLengths = new osg::DrawArrayLengths(osg::PrimitiveSet::POLYGON, start);
drawArrayLengths->reserve(_toTessellatePolygons.size());
for (unsigned i = 0; i < _toTessellatePolygons.size(); ++i)
{
for (unsigned j = 0; j < _toTessellatePolygons[i].index.size(); ++j)
{
pushVertex(_toTessellatePolygons[i].index[j], vertexArray, normalArray, texcoordArray);
}
drawArrayLengths->push_back(_toTessellatePolygons[i].index.size());
}
geometry->addPrimitiveSet(drawArrayLengths);
osgUtil::Tessellator Tessellator;
Tessellator.retessellatePolygons(*geometry);
}
// handle triangles
if (!_triangles.empty())
{
unsigned start = vertexArray->size();
for (unsigned i = 0; i < _triangles.size(); ++i)
{
for (unsigned j = 0; j < 3; ++j)
{
pushVertex(_triangles[i].index[j], vertexArray, normalArray, texcoordArray);
}
}
osg::DrawArrays* drawArray = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, start, 3*_triangles.size());
geometry->addPrimitiveSet(drawArray);
}
// handle quads
if (!_quads.empty())
{
unsigned start = vertexArray->size();
for (unsigned i = 0; i < _quads.size(); ++i)
{
for (unsigned j = 0; j < 4; ++j)
{
pushVertex(_quads[i].index[j], vertexArray, normalArray, texcoordArray);
}
}
osg::DrawArrays* drawArray = new osg::DrawArrays(osg::PrimitiveSet::QUADS, start, 4*_quads.size());
geometry->addPrimitiveSet(drawArray);
}
// handle polygons
if (!_polygons.empty())
{
unsigned start = vertexArray->size();
osg::DrawArrayLengths* drawArrayLengths = new osg::DrawArrayLengths(osg::PrimitiveSet::POLYGON, start);
drawArrayLengths->reserve(_polygons.size());
for (unsigned i = 0; i < _polygons.size(); ++i)
{
for (unsigned j = 0; j < _polygons[i].index.size(); ++j)
{
pushVertex(_polygons[i].index[j], vertexArray, normalArray, texcoordArray);
}
drawArrayLengths->push_back(_polygons[i].index.size());
}
geometry->addPrimitiveSet(drawArrayLengths);
}
return _geode.get();
}
};
struct Bins
{
PrimitiveBin* getOrCreatePrimitiveBin(unsigned flags, VertexSet* vertexSet)
{
if ((flags & SurfaceTypeLineLoop) || (flags & SurfaceTypeLineStrip))
{
if (!lineBin.valid())
{
lineBin = new LineBin(flags, vertexSet);
}
return lineBin.get();
}
else if (flags & SurfaceShaded)
{
if (flags & SurfaceTwoSided)
{
if (!smoothDoubleSurfaceBin.valid())
{
smoothDoubleSurfaceBin = new SurfaceBin(flags, vertexSet);
}
return smoothDoubleSurfaceBin.get();
}
else
{
if (!smoothSingleSurfaceBin.valid())
{
smoothSingleSurfaceBin = new SurfaceBin(flags, vertexSet);
}
return smoothSingleSurfaceBin.get();
}
}
else
{
if (flags & SurfaceTwoSided)
{
if (!flatDoubleSurfaceBin.valid())
{
flatDoubleSurfaceBin = new SurfaceBin(flags, vertexSet);
}
return flatDoubleSurfaceBin.get();
}
else
{
if (!flatSingleSurfaceBin.valid())
{
flatSingleSurfaceBin = new SurfaceBin(flags, vertexSet);
}
return flatSingleSurfaceBin.get();
}
}
}
void finalize(osg::Group* group, const MaterialData& material, const TextureData& textureData)
{
if (lineBin.valid())
{
group->addChild(lineBin->finalize(material, textureData));
}
if (smoothDoubleSurfaceBin.valid())
{
group->addChild(smoothDoubleSurfaceBin->finalize(material, textureData));
}
if (smoothSingleSurfaceBin.valid())
{
group->addChild(smoothSingleSurfaceBin->finalize(material, textureData));
}
if (flatDoubleSurfaceBin.valid())
{
group->addChild(flatDoubleSurfaceBin->finalize(material, textureData));
}
if (flatSingleSurfaceBin.valid())
{
group->addChild(flatSingleSurfaceBin->finalize(material, textureData));
}
}
private:
osg::ref_ptr<LineBin> lineBin;
osg::ref_ptr<SurfaceBin> flatDoubleSurfaceBin;
osg::ref_ptr<SurfaceBin> flatSingleSurfaceBin;
osg::ref_ptr<SurfaceBin> smoothDoubleSurfaceBin;
osg::ref_ptr<SurfaceBin> smoothSingleSurfaceBin;
};
osg::Node*
readObject(std::istream& stream, FileData& fileData, const osg::Matrix& parentTransform, TextureData textureData)
{
// most of this logic came from Andy Colebourne (developer of the AC3D editor) so it had better be right!
// The transform configured in this current object level
osg::Matrix transform;
// The vertex pool in this object
osg::ref_ptr<VertexSet> vertexSet = new VertexSet;
osg::ref_ptr<osg::Group> group = new osg::Group;
group->setDataVariance(osg::Object::STATIC);
osg::Vec2 textureOffset(0, 0);
osg::Vec2 textureRepeat(1, 1);
float creaseAngle = 61;
unsigned objectType = ObjectTypeGroup;
while (!stream.eof() && stream.good()) {
std::string token;
stream >> token;
if (token == "MATERIAL") {
MaterialData mat;
mat.readMaterial(stream);
fileData.addMaterial(mat);
}
else if (token == "OBJECT") {
std::string type;
stream >> type;
if (type == "group")
objectType = ObjectTypeGroup;
else if (type == "light")
objectType = ObjectTypeLight;
else if (type == "world")
objectType = ObjectTypeGroup;
else
objectType = ObjectTypeNormal;
}
else if (token == "crease") {
stream >> creaseAngle;
}
else if (token == "data") {
int len;
stream >> len;
std::vector<char> tmp(len);
stream.read(&(tmp[0]), len);
}
else if (token == "name") {
group->setName(readString(stream));
}
else if (token == "texture") {
// read the texture name
textureData = fileData.toTextureData(readString(stream));
}
else if (token == "texrep") {
stream >> textureRepeat[0] >> textureRepeat[1];
// if (textureRepeat[0] == 0.0f && textureRepeat[1] == 0.0f)
// textureData.setRepeat(false);
// else
// textureData.setRepeat(true);
}
else if (token == "texoff") {
stream >> textureOffset[0] >> textureOffset[1];
}
else if (token == "rot") {
for (unsigned n = 0; n < 3; ++n)
for (unsigned m = 0; m < 3; ++m)
stream >> transform(m, n);
}
else if (token == "loc") {
for (unsigned n = 0; n < 3; ++n)
stream >> transform(3, n);
}
else if (token == "url") {
std::string url;
stream >> url;
group->addDescription(url);
}
else if (token == "numvert") {
osg::Matrix currentTransform = transform*parentTransform;
unsigned num;
stream >> num;
if (num != 0) {
vertexSet->reserve(num);
for (unsigned n = 0; n < num; ++n) {
osg::Vec3 p;
stream >> p[0] >> p[1] >> p[2];
vertexSet->addVertex(currentTransform.preMult(p));
}
}
}
else if (token == "numsurf") {
unsigned num;
stream >> num;
if (0 < num) {
// list of materials required- generate one geode per material
std::vector<Bins> primitiveBins(fileData.getNumMaterials());
vertexSet->setCreaseAngle(creaseAngle);
for (unsigned n = 0; n < num; ++n) {
std::string token;
stream >> token;
if (token != "SURF") {
OSG_FATAL << "osgDB ac3d reader: expected SURF line while reading object \""
<< group->getName() << "\"!" << std::endl;
return group.release();
}
stream >> token;
unsigned flags = strtol(token.c_str(), NULL, 0);
stream >> token;
if (token != "mat") {
OSG_FATAL << "osgDB ac3d reader: expected mat line while reading object \""
<< group->getName() << "\"!" << std::endl;
return group.release();
}
// read the material index
unsigned matIdx;
stream >> matIdx;
if (primitiveBins.size() <= matIdx) {
OSG_FATAL << "osgDB ac3d reader: invalid material number while reading object \""
<< group->getName() << "\"" << std::endl;
return group.release();
}
// now get the correct PrimitiveBin
PrimitiveBin* primitiveBin = 0;
primitiveBin = primitiveBins[matIdx].getOrCreatePrimitiveBin(flags, vertexSet.get());
if (!primitiveBin) {
OSG_FATAL << "osgDB ac3d reader: unexpected primitive flags while reading object \""
<< group->getName() << "\"" << std::endl;
return group.release();
}
// read the refs
stream >> token;
if (token != "refs") {
OSG_FATAL << "osgDB ac3d reader: expected refs line while reading object \""
<< group->getName() << "\"" << std::endl;
return group.release();
}
unsigned nRefs = 0;
stream >> nRefs;
if (!stream) {
OSG_FATAL << "osgDB ac3d reader: could not read number of refs while reading object \""
<< group->getName() << "\"" << std::endl;
return group.release();
}
// in case this is an invalid refs count for this primitive
// read further, but do not store that primitive
bool acceptPrimitive = primitiveBin->beginPrimitive(nRefs);
for (unsigned i = 0; i < nRefs; ++i) {
// Read the vertex index
unsigned index;
stream >> index;
if (vertexSet->size() <= index)
{
OSG_FATAL << "osgDB ac3d reader: invalid ref vertex index while reading object \""
<< group->getName() << "\"" << std::endl;
return group.release();
}
// Read the texture corrdinates
osg::Vec2 texCoord;
stream >> texCoord[0] >> texCoord[1];
if (!stream) {
OSG_WARN << "osgDB ac3d reader: could not parse texture coords while reading object \""
<< group->getName() << "\" setting to (0,0)" << std::endl;
stream.clear();
std::string dummy;
std::getline(stream, dummy);
}
if (acceptPrimitive)
{
texCoord[0] = textureOffset[0] + texCoord[0]*textureRepeat[0];
texCoord[1] = textureOffset[1] + texCoord[1]*textureRepeat[1];
if (!primitiveBin->vertex(index, texCoord))
{
return group.release();
}
}
}
if (acceptPrimitive)
{
if (!primitiveBin->endPrimitive())
{
return group.release();
}
}
}
for (unsigned i = 0; i < primitiveBins.size(); ++i)
primitiveBins[i].finalize(group.get(), fileData.getMaterial(i), textureData);
}
}
else if (token == "kids") {
unsigned num;
stream >> num;
if (num != 0) {
for (unsigned n = 0; n < num; n++) {
osg::Node *k = readObject(stream, fileData, transform*parentTransform, textureData);
if (k == 0) {
OSG_FATAL << "osgDB ac3d reader: error reading child object" << std::endl;
return group.release();
}
else {
osg::LightSource *ls = dynamic_cast<osg::LightSource*>(k);
if (ls) {
osg::StateSet* lightStateSet = group->getOrCreateStateSet();
group->setStateSet(lightStateSet);
group->setCullingActive(false);
ls->setStateSetModes(*lightStateSet, osg::StateAttribute::ON);
}
group->addChild(k);
}
}
}
else if (objectType == ObjectTypeLight) { // add a light source to the scene 1 Nov 2003
osg::Light* ac3dLight = fileData.getNextLight();
osg::Matrix tt = transform*parentTransform;
ac3dLight->setPosition(osg::Vec4(tt(3, 0), tt(3, 1), tt(3, 2), 1));
ac3dLight->setDirection(osg::Matrix::transform3x3(osg::Vec3(0.0f, 0.0f, -1.0f), tt));
ac3dLight->setAmbient(osg::Vec4(0.5f,0.5f,0.5f,1.0f));
ac3dLight->setDiffuse(osg::Vec4(0.5f,0.5f,0.5f,1.0f));
ac3dLight->setSpecular(osg::Vec4(1.0f,1.0f,0.5f,1.0f));
osg::LightSource* ac3dLightSource = new osg::LightSource;
ac3dLightSource->setDataVariance(osg::Object::STATIC);
ac3dLightSource->setLight(ac3dLight);
ac3dLightSource->setLocalStateSetModes(osg::StateAttribute::ON);
// for some mad reason, you need to set this so that the light works. WHY?
return ac3dLightSource;
}
return group.release();
}
}
return group.release();
}
osg::Node*
readFile(std::istream& stream, const osgDB::ReaderWriter::Options* options)
{
FileData fileData(options);
osg::Matrix identityTransform;
osg::Node* node = readObject(stream, fileData, identityTransform, TextureData());
if (node)
node->setName("World");
return node;
}
} // namespace ac3d