812 lines
24 KiB
C++
812 lines
24 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 <osg/Types>
|
|
|
|
#include <osgDB/Registry>
|
|
#include <osgDB/ReadFile>
|
|
#include <osgDB/FileNameUtils>
|
|
#include <osgDB/FileUtils>
|
|
|
|
#include <osgUtil/MeshOptimizers>
|
|
#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>
|
|
#include <iomanip>
|
|
|
|
struct STLOptionsStruct {
|
|
bool smooth;
|
|
bool separateFiles;
|
|
bool dontSaveNormals;
|
|
bool noTriStripPolygons;
|
|
};
|
|
|
|
STLOptionsStruct parseOptions(const osgDB::ReaderWriter::Options* options) {
|
|
|
|
STLOptionsStruct localOptions;
|
|
localOptions.smooth = false;
|
|
localOptions.separateFiles = false;
|
|
localOptions.dontSaveNormals = false;
|
|
localOptions.noTriStripPolygons = false;
|
|
|
|
if (options != NULL)
|
|
{
|
|
std::istringstream iss(options->getOptionString());
|
|
std::string opt;
|
|
while (iss >> opt)
|
|
{
|
|
if (opt == "smooth")
|
|
{
|
|
localOptions.smooth = true;
|
|
}
|
|
else if (opt == "separateFiles")
|
|
{
|
|
localOptions.separateFiles = true;
|
|
}
|
|
else if (opt == "dontSaveNormals")
|
|
{
|
|
localOptions.dontSaveNormals = true;
|
|
}
|
|
else if (opt == "noTriStripPolygons")
|
|
{
|
|
localOptions.noTriStripPolygons = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return localOptions;
|
|
}
|
|
|
|
/**
|
|
* 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 readObject(const std::string& fileName, const osgDB::ReaderWriter::Options* options) const
|
|
{
|
|
return readNode(fileName, options);
|
|
}
|
|
|
|
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 osg::Referenced
|
|
{
|
|
public:
|
|
ReaderObject(bool noTriStripPolygons, bool generateNormals = true):
|
|
_noTriStripPolygons(noTriStripPolygons),
|
|
_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);
|
|
perVertexColours->push_back(*itr);
|
|
perVertexColours->push_back(*itr);
|
|
}
|
|
|
|
if(perVertexColours->size() == geom->getVertexArray()->getNumElements()) {
|
|
geom->setColorArray(perVertexColours.get(), osg::Array::BIND_PER_VERTEX);
|
|
}
|
|
}
|
|
|
|
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, _numFacets * 3));
|
|
|
|
if(!_noTriStripPolygons) {
|
|
osgUtil::optimizeMesh(geom.get());
|
|
}
|
|
|
|
return geom;
|
|
}
|
|
|
|
bool isEmpty()
|
|
{
|
|
return _numFacets == 0;
|
|
}
|
|
|
|
std::string& getName()
|
|
{
|
|
return _solidName;
|
|
}
|
|
|
|
protected:
|
|
bool _noTriStripPolygons;
|
|
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:
|
|
AsciiReaderObject(bool noTriStripPolygons)
|
|
: ReaderObject(noTriStripPolygons)
|
|
{
|
|
}
|
|
|
|
ReadResult read(FILE *fp);
|
|
};
|
|
|
|
class BinaryReaderObject : public ReaderObject
|
|
{
|
|
public:
|
|
BinaryReaderObject(unsigned int expectNumFacets, bool noTriStripPolygons, bool generateNormals = true)
|
|
: ReaderObject(noTriStripPolygons, 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_f(0)
|
|
{
|
|
m_localOptions = parseOptions(options);
|
|
if (m_localOptions.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 (m_localOptions.dontSaveNormals)
|
|
{
|
|
OSG_INFO << "ReaderWriterSTL::writeNode: Not saving normals" << std::endl;
|
|
}
|
|
}
|
|
|
|
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_localOptions.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;
|
|
|
|
*m_f << std::fixed << std::setprecision(7);
|
|
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_localOptions.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_localOptions.separateFiles)
|
|
{
|
|
m_f->close();
|
|
delete m_f;
|
|
}
|
|
|
|
++counter;
|
|
traverse(node);
|
|
}
|
|
|
|
~CreateStlVisitor()
|
|
{
|
|
if (m_localOptions.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;
|
|
std::string m_ErrorString;
|
|
STLOptionsStruct m_localOptions;
|
|
|
|
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)
|
|
{
|
|
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
|
|
|
|
// Check if the file comes from magics, and retrieve the corresponding data
|
|
// Magics files have a header with a "COLOR=" field giving the color of the whole model
|
|
bool fileComesFromMagics(FILE *fp, osg::Vec4& magicsColor)
|
|
{
|
|
std::string header(80, 0);
|
|
const float magicsColorDepth = 255.f;
|
|
|
|
::rewind(fp);
|
|
|
|
if (fread((void*) &(*header.begin()), header.size(), 1, fp) != 1)
|
|
return false;
|
|
|
|
if (::fseek(fp, sizeof_StlHeader, SEEK_SET)!=0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::string magicsColorPattern ("COLOR=");
|
|
if(size_t colorFieldPos = header.find(magicsColorPattern) != std::string::npos)
|
|
{
|
|
int colorIndex = colorFieldPos + magicsColorPattern.size() - 1;
|
|
float r = (uint8_t)header[colorIndex] / magicsColorDepth;
|
|
float g = (uint8_t)header[colorIndex + 1] / magicsColorDepth;
|
|
float b = (uint8_t)header[colorIndex + 2] / magicsColorDepth;
|
|
float a = (uint8_t)header[colorIndex + 3] / magicsColorDepth;
|
|
magicsColor = osg::Vec4(r, g, b, a);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
|
|
STLOptionsStruct localOptions = parseOptions (options);
|
|
|
|
if (sizeof(unsigned int) != 4)
|
|
{
|
|
OSG_NOTICE<<"Warning: 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;
|
|
}
|
|
|
|
std::string header_text(header.text, sizeof(header.text));
|
|
|
|
if (header_text.find("solid") == std::string::npos || stb.st_size == expectLen)
|
|
{
|
|
if(stb.st_size < expectLen)
|
|
{
|
|
unsigned int facets = (stb.st_size - sizeof_StlHeader) / sizeof_StlFacet;
|
|
OSG_WARN << "Warning: [[stl]] Incomplete file. "
|
|
<< "Attempting to read " << facets << " out of " << expectFacets << " facets expected."
|
|
<< std::endl;
|
|
expectFacets = facets;
|
|
}
|
|
isBinary = true;
|
|
}
|
|
else if (header_text.find("solid") != std::string::npos)
|
|
{
|
|
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");
|
|
|
|
if (!fp)
|
|
{
|
|
return ReadResult::FILE_NOT_FOUND;
|
|
}
|
|
}
|
|
|
|
osg::ref_ptr<osg::Group> group = new osg::Group;
|
|
|
|
// read
|
|
rewind(fp);
|
|
|
|
ReaderObject *readerObject;
|
|
|
|
if (isBinary)
|
|
readerObject = new BinaryReaderObject(expectFacets, localOptions.noTriStripPolygons);
|
|
else
|
|
readerObject = new AsciiReaderObject(localOptions.noTriStripPolygons);
|
|
|
|
osg::ref_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 (localOptions.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))
|
|
{
|
|
unsigned int len = strlen(buf);
|
|
if (len==0) continue;
|
|
|
|
// strip '\n' or '\r\n' and trailing whitespace
|
|
--len;
|
|
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;
|
|
|
|
// Check if the file comes from Magics and retrieve the global color from the header
|
|
osg::Vec4 magicsHeaderColor;
|
|
bool comesFromMagics = fileComesFromMagics(fp, magicsHeaderColor);
|
|
|
|
// seek to beginning of facets
|
|
if (::fseek(fp, sizeof_StlHeader, SEEK_SET)!=0)
|
|
{
|
|
return ReadError;
|
|
}
|
|
|
|
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
|
|
*
|
|
* The magics files may use whether per-face or per-object colors
|
|
* for a given face, according to the value of the last bit (0 = per-face, 1 = per-object)
|
|
* Moreover, magics uses RGB instead of BGR (as the other software)
|
|
*/
|
|
if (!_color.valid())
|
|
{
|
|
_color = new osg::Vec4Array;
|
|
}
|
|
|
|
// Case of a Magics file
|
|
if(comesFromMagics)
|
|
{
|
|
if(facet.color & StlHasColor) // The last bit is 1, the per-object color is used
|
|
{
|
|
_color->push_back(magicsHeaderColor);
|
|
}
|
|
else // the last bit is 0, the facet has its own unique color
|
|
{
|
|
float b = ((facet.color >> 10) & StlColorSize) / StlColorDepth;
|
|
float g = ((facet.color >> 5) & StlColorSize) / StlColorDepth;
|
|
float r = (facet.color & StlColorSize) / StlColorDepth;
|
|
_color->push_back(osg::Vec4(r, g, b, 1.0f));
|
|
}
|
|
}
|
|
// Case of a generic file
|
|
else if (facet.color & StlHasColor) // The color is valid if the last bit is 1
|
|
{
|
|
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: */
|