Files
OpenSceneGraph/src/osgPlugins/stl/ReaderWriterSTL.cpp
Robert Osfield 60d4b71d2b From Björn Hein, "it seems that for generating "per vertex normals" as stated in the
comment, two of them are missing. This results in wrong display of
STL-files regarding normals. Following simple fix seems to work:

Index: ReaderWriterSTL.cpp
===================================================================
--- ReaderWriterSTL.cpp    (Revision 13797)
+++ ReaderWriterSTL.cpp    (Arbeitskopie)
@@ -108,6 +108,8 @@
                     ++itr)
                 {
                     perVertexNormals->push_back(*itr);
+                    perVertexNormals->push_back(*itr);
+                    perVertexNormals->push_back(*itr);
                 }

                 geom->setNormalArray(perVertexNormals.get(),
osg::Array::BIND_PER_VERTEX);
"
2013-10-02 11:09:29 +00:00

671 lines
20 KiB
C++

// -*-c++-*-
/*
* $Id$
*
* STL importer for OpenSceneGraph.
*
* Copyright (c) 2004 Ulrich Hertlein <u.hertlein@sandbox.de>
* Copyright (c) 2012 Piotr Domagalski <piotr@domagalski.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <osg/Notify>
#include <osg/Endian>
#include <osgDB/Registry>
#include <osgDB/ReadFile>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgUtil/TriStripVisitor>
#include <osgUtil/SmoothingVisitor>
#include <osg/TriangleFunctor>
#include <osg/Geode>
#include <osg/Geometry>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <memory>
/**
* STL importer for OpenSceneGraph.
*/
class ReaderWriterSTL : public osgDB::ReaderWriter
{
public:
ReaderWriterSTL()
{
supportsExtension("stl", "STL binary format");
supportsExtension("sta", "STL ASCII format");
supportsOption("smooth", "Run SmoothingVisitor");
supportsOption("separateFiles", "Save each geode in a different file. Can result in a huge amount of files!");
supportsOption("dontSaveNormals", "Set all normals to [0 0 0] when saving to a file.");
}
virtual const char* className() const
{
return "STL Reader";
}
virtual ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options*) const;
virtual WriteResult writeNode(const osg::Node& node, const std::string& fileName, const Options* = NULL) const;
private:
class ReaderObject
{
public:
ReaderObject(bool generateNormals = true):
_generateNormal(generateNormals),
_numFacets(0)
{
}
virtual ~ReaderObject()
{
}
enum ReadResult
{
ReadSuccess,
ReadError,
ReadEOF
};
virtual ReadResult read(FILE *fp) = 0;
osg::ref_ptr<osg::Geometry> asGeometry() const
{
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
geom->setVertexArray(_vertex.get());
if (_normal.valid())
{
// need to convert per triangle normals to per vertex
osg::ref_ptr<osg::Vec3Array> perVertexNormals = new osg::Vec3Array;
perVertexNormals->reserveArray(_normal->size() * 3);
for(osg::Vec3Array::iterator itr = _normal->begin();
itr != _normal->end();
++itr)
{
perVertexNormals->push_back(*itr);
perVertexNormals->push_back(*itr);
perVertexNormals->push_back(*itr);
}
geom->setNormalArray(perVertexNormals.get(), osg::Array::BIND_PER_VERTEX);
}
if (_color.valid())
{
// need to convert per triangle colours to per vertex
OSG_INFO << "STL file with color" << std::endl;
osg::ref_ptr<osg::Vec4Array> perVertexColours = new osg::Vec4Array;
perVertexColours->reserveArray(_color->size() * 3);
for(osg::Vec4Array::iterator itr = _color->begin();
itr != _color->end();
++itr)
{
perVertexColours->push_back(*itr);
}
geom->setColorArray(perVertexColours.get(), osg::Array::BIND_PER_VERTEX);
}
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, _numFacets * 3));
osgUtil::TriStripVisitor tristripper;
tristripper.stripify(*geom);
return geom;
}
bool isEmpty()
{
return _numFacets == 0;
}
std::string& getName()
{
return _solidName;
}
protected:
bool _generateNormal;
unsigned int _numFacets;
std::string _solidName;
osg::ref_ptr<osg::Vec3Array> _vertex;
osg::ref_ptr<osg::Vec3Array> _normal;
osg::ref_ptr<osg::Vec4Array> _color;
void clear()
{
_solidName = "";
_numFacets = 0;
_vertex = osg::ref_ptr<osg::Vec3Array>();
_normal = osg::ref_ptr<osg::Vec3Array>();
_color = osg::ref_ptr<osg::Vec4Array>();
}
};
class AsciiReaderObject : public ReaderObject
{
public:
ReadResult read(FILE *fp);
};
class BinaryReaderObject : public ReaderObject
{
public:
BinaryReaderObject(unsigned int expectNumFacets, bool generateNormals = true)
: ReaderObject(generateNormals),
_expectNumFacets(expectNumFacets)
{
}
ReadResult read(FILE *fp);
protected:
unsigned int _expectNumFacets;
};
class CreateStlVisitor : public osg::NodeVisitor
{
public:
CreateStlVisitor(std::string const & fout, const osgDB::ReaderWriter::Options* options = 0):
osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN),
counter(0),
m_options(options),
m_dontSaveNormals(false)
{
if (options && (options->getOptionString() == "separateFiles"))
{
OSG_INFO << "ReaderWriterSTL::writeNode: Files are written separately" << std::endl;
m_fout_ext = osgDB::getLowerCaseFileExtension(fout);
m_fout = fout.substr(0, fout.rfind(m_fout_ext) - 1);
}
else
{
m_fout = fout;
m_f = new osgDB::ofstream(m_fout.c_str());
}
if (options && (options->getOptionString() == "dontSaveNormals"))
{
OSG_INFO << "ReaderWriterSTL::writeNode: Not saving normals" << std::endl;
m_dontSaveNormals = true;
}
}
std::string i2s(int i)
{
char buf[16]; // -2^31 == -2147483648 needs 11 chars + \0 -> 12 (+4 for security ;-)
sprintf(buf, "%d", i);
return buf;
}
virtual void apply(osg::Geode& node)
{
osg::Matrix mat = osg::computeLocalToWorld(getNodePath());
if (m_options && (m_options->getOptionString() == "separateFiles"))
{
std::string sepFile = m_fout + i2s(counter) + "." + m_fout_ext;
m_f = new osgDB::ofstream(sepFile.c_str());
}
if (node.getName().empty())
*m_f << "solid " << counter << std::endl;
else
*m_f << "solid " << node.getName() << std::endl;
for (unsigned int i = 0; i < node.getNumDrawables(); ++i)
{
osg::TriangleFunctor<PushPoints> tf;
tf.m_stream = m_f;
tf.m_mat = mat;
tf.m_dontSaveNormals = m_dontSaveNormals;
node.getDrawable(i)->accept(tf);
}
if (node.getName().empty())
*m_f << "endsolid " << counter << std::endl;
else
*m_f << "endsolid " << node.getName() << std::endl;
if (m_options && (m_options->getOptionString() == "separateFiles"))
{
m_f->close();
delete m_f;
}
++counter;
traverse(node);
}
~CreateStlVisitor()
{
if (m_options && (m_options->getOptionString() == "separateFiles"))
{
OSG_INFO << "ReaderWriterSTL::writeNode: " << counter - 1 << " files were written" << std::endl;
}
else
{
m_f->close();
delete m_f;
}
}
const std::string& getErrorString() const { return m_ErrorString; }
private:
int counter;
std::ofstream* m_f;
std::string m_fout;
std::string m_fout_ext;
osgDB::ReaderWriter::Options const * m_options;
std::string m_ErrorString;
bool m_dontSaveNormals;
struct PushPoints
{
std::ofstream* m_stream;
osg::Matrix m_mat;
bool m_dontSaveNormals;
inline void operator () (const osg::Vec3& _v1, const osg::Vec3& _v2, const osg::Vec3& _v3, bool treatVertexDataAsTemporary)
{
osg::Vec3 v1 = _v1 * m_mat;
osg::Vec3 v2 = _v2 * m_mat;
osg::Vec3 v3 = _v3 * m_mat;
osg::Vec3 vV1V2 = v2 - v1;
osg::Vec3 vV1V3 = v3 - v1;
osg::Vec3 vNormal = vV1V2.operator ^(vV1V3);
if (m_dontSaveNormals)
*m_stream << "facet normal 0 0 0" << std::endl;
else
*m_stream << "facet normal " << vNormal[0] << " " << vNormal[1] << " " << vNormal[2] << std::endl;
*m_stream << "outer loop" << std::endl;
*m_stream << "vertex " << v1[0] << " " << v1[1] << " " << v1[2] << std::endl;
*m_stream << "vertex " << v2[0] << " " << v2[1] << " " << v2[2] << std::endl;
*m_stream << "vertex " << v3[0] << " " << v3[1] << " " << v3[2] << std::endl;
*m_stream << "endloop" << std::endl;
*m_stream << "endfacet" << std::endl;
}
};
};
};
// Register with Registry to instantiate the above reader/writer.
REGISTER_OSGPLUGIN(stl, ReaderWriterSTL)
struct StlHeader
{
char text[80];
unsigned int numFacets;
};
const unsigned int sizeof_StlHeader = 84;
struct StlVector
{
float x, y, z;
};
struct StlFacet
{
StlVector normal;
StlVector vertex[3];
unsigned short color;
};
const unsigned int sizeof_StlFacet = 50;
const unsigned short StlHasColor = 0x8000;
const unsigned short StlColorSize = 0x1f; // 5 bit
const float StlColorDepth = float(StlColorSize); // 2^5 - 1
osgDB::ReaderWriter::ReadResult ReaderWriterSTL::readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) const
{
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;
if (sizeof(unsigned int) != 4)
{
OSG_NOTICE<<"Waring: STL reading not supported as unsigned int is not 4 bytes on this system."<<std::endl;
return ReadResult::ERROR_IN_READING_FILE;
}
OSG_INFO << "ReaderWriterSTL::readNode(" << fileName.c_str() << ")" << std::endl;
// determine ASCII vs. binary mode
FILE* fp = osgDB::fopen(fileName.c_str(), "rb");
if (!fp)
{
return ReadResult::FILE_NOT_FOUND;
}
// assumes "unsigned int" is 4 bytes...
StlHeader header;
if (fread((void*) &header, sizeof(header), 1, fp) != 1)
{
fclose(fp);
return ReadResult::ERROR_IN_READING_FILE;
}
bool isBinary = false;
// calculate expected file length from number of facets
unsigned int expectFacets = header.numFacets;
if (osg::getCpuByteOrder() == osg::BigEndian)
{
osg::swapBytes4((char*) &expectFacets);
}
off_t expectLen = sizeof_StlHeader + expectFacets * sizeof_StlFacet;
struct stat stb;
if (fstat(fileno(fp), &stb) < 0)
{
OSG_FATAL << "ReaderWriterSTL::readNode: Unable to stat '" << fileName << "'" << std::endl;
fclose(fp);
return ReadResult::ERROR_IN_READING_FILE;
}
if (stb.st_size == expectLen)
{
isBinary = true;
}
else if (strstr(header.text, "solid") != 0)
{
isBinary = false;
}
else
{
OSG_FATAL << "ReaderWriterSTL::readNode(" << fileName.c_str() << ") unable to determine file format" << std::endl;
fclose(fp);
return ReadResult::ERROR_IN_READING_FILE;
}
if (!isBinary)
{
fclose(fp);
fp = osgDB::fopen(fileName.c_str(), "r");
}
osg::ref_ptr<osg::Group> group = new osg::Group;
// read
rewind(fp);
ReaderObject *readerObject;
if (isBinary)
readerObject = new BinaryReaderObject(expectFacets);
else
readerObject = new AsciiReaderObject();
std::auto_ptr<ReaderObject> readerPtr(readerObject);
while (1)
{
ReaderObject::ReadResult result;
if ((result = readerPtr->read(fp)) == ReaderObject::ReadError)
{
fclose(fp);
return ReadResult::FILE_NOT_HANDLED;
}
if (!readerPtr->isEmpty())
{
osg::ref_ptr<osg::Geometry> geom = readerPtr->asGeometry();
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(geom.get());
geode->setName(readerPtr->getName());
group->addChild(geode.get());
}
if (result == ReaderObject::ReadEOF)
break;
}
fclose(fp);
if (options && (options->getOptionString() == "smooth"))
{
osgUtil::SmoothingVisitor smoother;
group->accept(smoother);
}
return group.get();
}
/**********************************************************************
*
* Private
*
**********************************************************************/
ReaderWriterSTL::ReaderObject::ReadResult ReaderWriterSTL::AsciiReaderObject::read(FILE* fp)
{
unsigned int vertexCount = 0;
unsigned int facetIndex[] = { 0, 0, 0 };
unsigned int vertexIndex = 0;
unsigned int normalIndex = 0;
const int MaxLineSize = 256;
char buf[MaxLineSize];
char sx[MaxLineSize], sy[MaxLineSize], sz[MaxLineSize];
if (!isEmpty())
{
clear();
}
while (fgets(buf, sizeof(buf), fp))
{
// strip '\n' or '\r\n' and trailing whitespace
unsigned int len = strlen(buf) - 1;
while (len && (buf[len] == '\n' || buf[len] == '\r' || isspace(buf[len])))
{
buf[len--] = '\0';
}
if (len == 0 || buf[0] == '\0')
{
continue;
}
// strip leading whitespace
char* bp = buf;
while (isspace(*bp))
{
++bp;
}
if (strncmp(bp, "vertex", 6) == 0)
{
if (sscanf(bp + 6, "%s %s %s", sx, sy, sz) == 3)
{
if (!_vertex.valid())
_vertex = new osg::Vec3Array;
float vx = osg::asciiToFloat(sx);
float vy = osg::asciiToFloat(sy);
float vz = osg::asciiToFloat(sz);
vertexIndex = _vertex->size();
if (vertexCount < 3)
{
_vertex->push_back(osg::Vec3(vx, vy, vz));
facetIndex[vertexCount++] = vertexIndex;
}
else
{
/*
* There are some invalid ASCII files around (at least one ;-)
* that have more than three vertices per facet - add an
* additional triangle.
*/
_normal->push_back((*_normal)[normalIndex]);
_vertex->push_back((*_vertex)[facetIndex[0]]);
_vertex->push_back((*_vertex)[facetIndex[2]]);
_vertex->push_back(osg::Vec3(vx, vy, vz));
facetIndex[1] = facetIndex[2];
facetIndex[2] = vertexIndex;
_numFacets++;
}
}
}
else if (strncmp(bp, "facet", 5) == 0)
{
if (sscanf(bp + 5, "%*s %s %s %s", sx, sy, sz) == 3)
{
float nx = osg::asciiToFloat(sx);
float ny = osg::asciiToFloat(sy);
float nz = osg::asciiToFloat(sz);
if (!_normal.valid())
_normal = new osg::Vec3Array;
osg::Vec3 normal(nx, ny, nz);
normal.normalize();
normalIndex = _normal->size();
_normal->push_back(normal);
_numFacets++;
vertexCount = 0;
}
}
else if (strncmp(bp, "solid", 5) == 0)
{
OSG_INFO << "STL loader parsing '" << bp + 6 << "'" << std::endl;
_solidName = bp + 6;
}
else if (strncmp(bp, "endsolid", 8) == 0)
{
OSG_INFO << "STL loader done parsing '" << _solidName << "'" << std::endl;
return ReadSuccess;
}
}
return ReadEOF;
}
ReaderWriterSTL::ReaderObject::ReadResult ReaderWriterSTL::BinaryReaderObject::read(FILE* fp)
{
if (isEmpty())
{
clear();
}
_numFacets = _expectNumFacets;
// seek to beginning of facets
::fseek(fp, sizeof_StlHeader, SEEK_SET);
StlFacet facet;
for (unsigned int i = 0; i < _expectNumFacets; ++i)
{
if (::fread((void*) &facet, sizeof_StlFacet, 1, fp) != 1)
{
OSG_FATAL << "ReaderWriterSTL::readStlBinary: Failed to read facet " << i << std::endl;
return ReadError;
}
// vertices
if (!_vertex.valid())
_vertex = new osg::Vec3Array;
osg::Vec3 v0(facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0].z);
osg::Vec3 v1(facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z);
osg::Vec3 v2(facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z);
_vertex->push_back(v0);
_vertex->push_back(v1);
_vertex->push_back(v2);
// per-facet normal
osg::Vec3 normal;
if (_generateNormal)
{
osg::Vec3 d01 = v1 - v0;
osg::Vec3 d02 = v2 - v0;
normal = d01 ^ d02;
normal.normalize();
}
else
{
normal.set(facet.normal.x, facet.normal.y, facet.normal.z);
}
if (!_normal.valid())
_normal = new osg::Vec3Array;
_normal->push_back(normal);
/*
* color extension
* RGB555 with most-significat bit indicating if color is present
*/
if (facet.color & StlHasColor)
{
if (!_color.valid())
{
_color = new osg::Vec4Array;
}
float r = ((facet.color >> 10) & StlColorSize) / StlColorDepth;
float g = ((facet.color >> 5) & StlColorSize) / StlColorDepth;
float b = (facet.color & StlColorSize) / StlColorDepth;
_color->push_back(osg::Vec4(r, g, b, 1.0f));
}
}
return ReadEOF;
}
osgDB::ReaderWriter::WriteResult ReaderWriterSTL::writeNode(const osg::Node& node, const std::string& fileName, const Options* opts) const
{
std::string ext = osgDB::getLowerCaseFileExtension(fileName);
if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
if (ext != "stl")
{
OSG_FATAL << "ReaderWriterSTL::writeNode: Only STL ASCII files supported" << std::endl;
return WriteResult::FILE_NOT_HANDLED;
}
CreateStlVisitor createStlVisitor(fileName, opts);
const_cast<osg::Node&>(node).accept(createStlVisitor);
if (createStlVisitor.getErrorString().empty())
{
return WriteResult::FILE_SAVED;
}
else
{
OSG_FATAL << "Error: " << createStlVisitor.getErrorString() << std::endl;
return WriteResult::ERROR_IN_WRITING_FILE;
}
}
/* vim: set ts=4 sw=4 expandtab: */