- IFC: slight optimization, take less memory.

# IFC: try to make normals point outwards, if possible.
# IFC: implement recursive polygon merging, but leave it disabled since it seems to fail on very complicated polygons with multiple, nested boundaries.

git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@1007 67173fc5-114c-0410-ac8e-9d2fd5bffc1f
pull/1/head
aramis_acg 2011-05-21 15:38:39 +00:00
parent f522143909
commit 78b44c3aed
4 changed files with 194 additions and 132 deletions

View File

@ -333,8 +333,13 @@ void IFCImporter::InternReadFile( const std::string& pFile,
EXPRESS::ConversionSchema schema;
IFC::GetSchema(schema);
// tell the reader which entity types to track with special care
static const char* const types_to_track[] = {
"ifcsite", "ifcbuilding", "ifcproject"
};
// feed the IFC schema into the reader and pre-parse all lines
STEP::ReadFile(*db, schema);
STEP::ReadFile(*db, schema, types_to_track);
const STEP::LazyObject* proj = db->GetObject("ifcproject");
if (!proj) {
@ -731,6 +736,116 @@ bool ProcessPolyloop(const IFC::IfcPolyLoop& loop, TempMesh& meshout, Conversion
return false;
}
// ------------------------------------------------------------------------------------------------
void FixupFaceOrientation(TempMesh& result)
{
aiVector3D vavg;
BOOST_FOREACH(aiVector3D& v, result.verts) {
vavg += v;
}
// fix face orientation - try at least.
vavg /= static_cast<float>( result.verts.size() );
size_t c = 0;
BOOST_FOREACH(unsigned int cnt, result.vertcnt) {
if (cnt>2){
const aiVector3D& thisvert = result.verts[c];
const aiVector3D normal((thisvert-result.verts[c+1])^(thisvert-result.verts[c+2]));
if (normal*(thisvert-vavg) < 0) {
std::reverse(result.verts.begin()+c,result.verts.begin()+cnt+c);
}
}
c += cnt;
}
}
// ------------------------------------------------------------------------------------------------
void RecursiveMergeBoundaries(TempMesh& final_result, const TempMesh& in, const TempMesh& boundary, std::vector<aiVector3D>& normals, const aiVector3D& nor_boundary)
{
ai_assert(in.vertcnt.size() >= 1);
ai_assert(boundary.vertcnt.size() == 1);
std::vector<unsigned int>::const_iterator end = in.vertcnt.end(), begin=in.vertcnt.begin(), iit, best_iit;
TempMesh out;
// iterate through all other bounds and find the one for which the shortest connection
// to the outer boundary is actually the shortest possible.
size_t vidx = 0, best_vidx_start = 0;
size_t best_ofs, best_outer = boundary.verts.size();
float best_dist = 1e10;
for(std::vector<unsigned int>::const_iterator iit = begin; iit != end; vidx += *iit++) {
for(size_t vofs = 0; vofs < *iit; ++vofs) {
const aiVector3D& v = in.verts[vidx+vofs];
for(size_t outer = 0; outer < boundary.verts.size(); ++outer) {
const aiVector3D& o = boundary.verts[outer];
const float d = (o-v).SquareLength();
if (d < best_dist) {
best_dist = d;
best_ofs = vofs;
best_outer = outer;
best_iit = iit;
best_vidx_start = vidx;
}
}
}
}
ai_assert(best_outer != boundary.verts.size());
// now that we collected all vertex connections to be added, build the output polygon
size_t cnt = boundary.verts.size();
for(size_t outer = 0; outer < boundary.verts.size(); ++outer) {
const aiVector3D& o = boundary.verts[outer];
out.verts.push_back(o);
if (outer == best_outer) {
for(size_t i = best_ofs; i < *best_iit; ++i) {
out.verts.push_back(in.verts[best_vidx_start + i]);
}
// we need the first vertex of the inner polygon twice as we return to the
// outer loop through the very same connection through which we got there.
for(size_t i = 0; i <= best_ofs; ++i) {
out.verts.push_back(in.verts[best_vidx_start + i]);
}
// reverse face winding if the normal of the sub-polygon points in the
// same direction as the normal of the outer polygonal boundary
if (normals[std::distance(begin,best_iit)] * nor_boundary > 0) {
std::reverse(out.verts.rbegin(),out.verts.rbegin()+*best_iit+1);
}
// also append a copy of the initial insertion point to be able to continue the outer polygon
out.verts.push_back(o);
cnt += *best_iit+2;
}
}
out.vertcnt.push_back(cnt);
if (in.vertcnt.size() > 1) {
// Recursively apply the same algorithm if there are more boundaries to merge. The
// current implementation is relatively inefficient, though.
TempMesh temp;
// drop the boundary that we just processed
const size_t dist = std::distance(begin, best_iit);
TempMesh remaining = in;
remaining.vertcnt.erase(remaining.vertcnt.begin() + dist);
remaining.verts.erase(remaining.verts.begin()+best_ofs,remaining.verts.begin()+best_ofs+*best_iit);
normals.erase(normals.begin() + dist);
RecursiveMergeBoundaries(temp,remaining,out,normals,nor_boundary);
final_result.Append(temp);
}
else final_result.Append(out);
}
// ------------------------------------------------------------------------------------------------
// Note: meshout may be modified even though the merged polygon is copied to `result`!
void MergePolygonBoundaries(TempMesh& result, TempMesh& meshout, size_t master_bounds = -1)
@ -754,7 +869,6 @@ void MergePolygonBoundaries(TempMesh& result, TempMesh& meshout, size_t master_b
result.verts.reserve(meshout.verts.size()+meshout.vertcnt.size()*2+result.verts.size());
size_t outer_polygon_start = 0;
// compute proper normals for all polygons
size_t max_vcount = 0;
std::vector<unsigned int>::iterator outer_polygon = meshout.vertcnt.end(), begin=meshout.vertcnt.begin(), end=outer_polygon, iit;
@ -767,6 +881,8 @@ void MergePolygonBoundaries(TempMesh& result, TempMesh& meshout, size_t master_b
std::vector<aiVector3D> normals;
normals.reserve( meshout.vertcnt.size() );
// `NewellNormal()` currently has a relatively strange interface and need to
// re-structure things a bit to meet them.
size_t vidx = 0;
for(iit = begin; iit != meshout.vertcnt.end(); vidx += *iit++) {
for(size_t vofs = 0, cnt = 0; vofs < *iit; ++vofs) {
@ -784,7 +900,7 @@ void MergePolygonBoundaries(TempMesh& result, TempMesh& meshout, size_t master_b
NewellNormal<4,4,4>(normals.back(),*iit,&temp[0],&temp[1],&temp[2]);
}
// see if one of the polygons is a IfcFaceOuterBound - treats this as the outer boundary.
// see if one of the polygons is a IfcFaceOuterBound (in which case master_bounds is not `-1`).
// sadly we can't rely on it, the docs say 'At most one of the bounds shall be of the type IfcFaceOuterBound'
if (master_bounds != -1) {
outer_polygon = begin + master_bounds;
@ -844,10 +960,7 @@ next_loop:
const size_t start = (v-o).SquareLength() > (vnext-o).SquareLength() ? vofs : next;
std::vector<aiVector3D>::iterator inbase = in.begin()+vidx, it = std::copy(inbase+start, inbase+*iit,tmp.begin());
std::copy(inbase, inbase+start,it);
//if(start == next) {
std::reverse(tmp.begin(),tmp.end());
//}
in.insert(in.begin()+outer_polygon_start+(outer+1)%*outer_polygon,tmp.begin(),tmp.end());
vidx += outer_polygon_start<vidx ? *iit : 0;
@ -865,86 +978,36 @@ next_loop:
}
}
typedef boost::tuple<std::vector<unsigned int>::iterator, unsigned int, unsigned int> InsertionPoint;
std::vector< std::vector<InsertionPoint> > insertions(*outer_polygon, std::vector<InsertionPoint>());
// extract the outer boundary and move it to a separate mesh
TempMesh boundary;
boundary.vertcnt.resize(1,*outer_polygon);
boundary.verts.resize(*outer_polygon);
// iterate through all other polyloops and find points in the outer polyloop that are close
vidx = 0;
for(iit = begin; iit != end; vidx += *iit++) {
if (iit == outer_polygon || !*iit) {
continue;
}
std::vector<aiVector3D>::iterator b = in.begin()+outer_polygon_start;
std::copy(b,b+*outer_polygon,boundary.verts.begin());
in.erase(b,b+*outer_polygon);
size_t best_ofs,best_outer = *outer_polygon;
float best_dist = 1e10;
for(size_t vofs = 0; vofs < *iit; ++vofs) {
const aiVector3D& v = in[vidx+vofs];
std::vector<aiVector3D>::iterator norit = normals.begin()+std::distance(meshout.vertcnt.begin(),outer_polygon);
const aiVector3D nor_boundary = *norit;
normals.erase(norit);
meshout.vertcnt.erase(outer_polygon);
for(size_t outer = 0; outer < *outer_polygon; ++outer) {
const aiVector3D& o = in[outer_polygon_start+outer];
const float d = (o-v).SquareLength();
if (d < best_dist) {
best_dist = d;
best_ofs = vofs;
best_outer = outer;
}
}
}
ai_assert(best_outer != *outer_polygon);
// we will later insert a hidden connection line right after the closest point in the outer polygon
insertions[best_outer].push_back(boost::make_tuple(iit,vidx,best_ofs));
}
// now that we collected all vertex connections to be added, build the output polygon
size_t cnt = *outer_polygon;
for(size_t outer = 0; outer < *outer_polygon; ++outer) {
const aiVector3D& o = meshout.verts[outer_polygon_start+outer];
result.verts.push_back(o);
const std::vector<InsertionPoint>& insvec = insertions[outer];
BOOST_FOREACH(const InsertionPoint& ins,insvec) {
if (!*ins.get<0>()) {
continue;
}
for(size_t i = ins.get<2>(); i < *ins.get<0>(); ++i) {
result.verts.push_back(in[ins.get<1>() + i]);
}
// we need the first vertex of the inner polygon twice as we return to the
// outer loop through the very same connection through which we got there.
for(size_t i = 0; i <= ins.get<2>(); ++i) {
result.verts.push_back(in[ins.get<1>() + i]);
}
// reverse face winding if the normal of the sub-polygon points in the
// same direction as the normal of the outer polygonal boundary
if (normals[std::distance(begin,ins.get<0>())] * normals[std::distance(begin,outer_polygon)] > 0) {
std::reverse(result.verts.rbegin(),result.verts.rbegin()+*ins.get<0>()+1);
}
// also append a copy of the initial insertion point to be able to continue the outer polygon
result.verts.push_back(o);
cnt += *ins.get<0>()+2;
}
}
result.vertcnt.push_back(cnt);
// keep merging the closest inner boundary with the outer boundary until no more boundaries are left
RecursiveMergeBoundaries(result,meshout,boundary,normals,nor_boundary);
}
// ------------------------------------------------------------------------------------------------
void ProcessConnectedFaceSet(const IFC::IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv)
{
BOOST_FOREACH(const IFC::IfcFace& face, fset.CfsFaces) {
TempMesh meshout;
size_t ob = -1, cnt = 0;
//TempMesh meshout;
BOOST_FOREACH(const IFC::IfcFaceBound& bound, face.Bounds) {
// XXX implement proper merging for polygonal loops
if(const IFC::IfcPolyLoop* const polyloop = bound.Bound->ToPtr<IFC::IfcPolyLoop>()) {
if(ProcessPolyloop(*polyloop, meshout, conv)) {
if(ProcessPolyloop(*polyloop, result,conv)) {
if(bound.ToPtr<IFC::IfcFaceOuterBound>()) {
ob = cnt;
}
@ -956,18 +1019,19 @@ void ProcessConnectedFaceSet(const IFC::IfcConnectedFaceSet& fset, TempMesh& res
continue;
}
if(!IsTrue(bound.Orientation)) {
/*if(!IsTrue(bound.Orientation)) {
size_t c = 0;
BOOST_FOREACH(unsigned int& i, meshout.vertcnt) {
std::reverse(meshout.verts.begin() + cnt,meshout.verts.begin() + cnt + c);
cnt += c;
}
}
}*/
}
MergePolygonBoundaries(result,meshout,ob);
//MergePolygonBoundaries(result,meshout,ob);
}
FixupFaceOrientation(result);
}
// ------------------------------------------------------------------------------------------------
@ -1068,30 +1132,6 @@ bool ProcessProfile(const IFC::IfcProfileDef& prof, TempMesh& meshout, Conversio
return true;
}
// ------------------------------------------------------------------------------------------------
void FixupFaceOrientation(TempMesh& result)
{
aiVector3D vavg;
BOOST_FOREACH(aiVector3D& v, result.verts) {
vavg += v;
}
// fix face orientation - try at least.
vavg /= static_cast<float>( result.verts.size() );
size_t c = 0;
BOOST_FOREACH(unsigned int cnt, result.vertcnt) {
if (cnt>2){
const aiVector3D& thisvert = result.verts[c];
const aiVector3D normal((thisvert-result.verts[c+1])^(thisvert-result.verts[c+2]));
if (normal*(thisvert-vavg) < 0) {
std::reverse(result.verts.begin()+c,result.verts.begin()+cnt+c);
}
}
c += cnt;
}
}
// ------------------------------------------------------------------------------------------------
void ProcessRevolvedAreaSolid(const IFC::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv)
{
@ -1271,7 +1311,7 @@ aiVector2D ProjectPositionVectorOntoPlane(const aiVector3D& x, const ProjectionI
}
// ------------------------------------------------------------------------------------------------
void TriangulatePart(const aiVector2D& pmin, const aiVector2D& pmax, XYSortedField& field, const std::vector< BoundingBox >& bbs,
void QuadrifyPart(const aiVector2D& pmin, const aiVector2D& pmax, XYSortedField& field, const std::vector< BoundingBox >& bbs,
std::vector<aiVector2D>& out)
{
if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) {
@ -1323,7 +1363,7 @@ void TriangulatePart(const aiVector2D& pmin, const aiVector2D& pmax, XYSortedFie
const float ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y);
if (ys - ylast) {
// Divide et impera!
TriangulatePart( aiVector2D(xs,ylast), aiVector2D(xe,ys) ,field,bbs,out);
QuadrifyPart( aiVector2D(xs,ylast), aiVector2D(xe,ys) ,field,bbs,out);
}
// the following are the window vertices
@ -1349,12 +1389,12 @@ void TriangulatePart(const aiVector2D& pmin, const aiVector2D& pmax, XYSortedFie
}
if (ylast < pmax.y) {
// Divide et impera!
TriangulatePart( aiVector2D(xs,ylast), aiVector2D(xe,pmax.y) ,field,bbs,out);
QuadrifyPart( aiVector2D(xs,ylast), aiVector2D(xe,pmax.y) ,field,bbs,out);
}
// Divide et impera! - now for the whole rest
if (pmax.x-xe) {
TriangulatePart(aiVector2D(xe,pmin.y), pmax ,field,bbs,out);
QuadrifyPart(aiVector2D(xe,pmin.y), pmax ,field,bbs,out);
}
}
@ -1581,7 +1621,7 @@ bool TryAddOpenings_Triangulate(const std::vector<TempOpening>& openings,const s
std::vector<aiVector2D> outflat;
outflat.reserve(openings.size()*4);
TriangulatePart(aiVector2D(0.f,0.f),aiVector2D(1.f,1.f),field,bbs,outflat);
QuadrifyPart(aiVector2D(0.f,0.f),aiVector2D(1.f,1.f),field,bbs,outflat);
ai_assert(!(outflat.size() % 4));
//FixOuterBoundaries(outflat,contour_flat);
@ -1652,12 +1692,14 @@ void ProcessExtrudedAreaSolid(const IFC::IfcExtrudedAreaSolid& solid, TempMesh&
// compute the normal vectors for all opening polygons
if (conv.apply_openings) {
if (!conv.settings.useCustomTriangulation) {
// it is essential to apply the openings in the correct spatial order. The direction
// doesn't matter, but we would screw up if we started with e.g. a door in between
// two windows.
std::sort(conv.apply_openings->begin(),conv.apply_openings->end(),DistanceSorter(min));
nors.reserve(conv.apply_openings->size());
}
nors.reserve(conv.apply_openings->size());
BOOST_FOREACH(TempOpening& t,*conv.apply_openings) {
TempMesh& bounds = *t.profileMesh.get();
@ -2387,8 +2429,9 @@ aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el, Conve
if(!conv.collect_openings) {
conv.apply_openings = &openings;
}
ProcessProductRepresentation(el,nd.get(),subnodes,conv);
ProcessProductRepresentation(el,nd.get(),subnodes,conv);
conv.apply_openings = conv.collect_openings = NULL;
if (subnodes.size()) {
@ -2414,19 +2457,22 @@ void ProcessSpatialStructures(ConversionData& conv)
// process all products in the file. it is reasonable to assume that a
// file that is relevant for us contains at least a site or a building.
const STEP::DB::ObjectMapByType& map = conv.db.GetObjectsByType();
STEP::DB::ObjectMapRange range = map.equal_range("ifcsite");
if (range.first == map.end()) {
range = map.equal_range("ifcbuilding");
if (range.first == map.end()) {
// no site, no building - try all ids. this will take ages, but it should rarely happen.
range = STEP::DB::ObjectMapRange(map.begin(),map.end());
ai_assert(map.find("ifcsite") != map.end());
const STEP::DB::ObjectSet* range = &map.find("ifcsite")->second;
if (range->empty()) {
ai_assert(map.find("ifcbuilding") != map.end());
range = &map.find("ifcbuilding")->second;
if (range->empty()) {
// no site, no building - fail;
IFCImporter::ThrowException("no root element found (expected IfcBuilding or preferably IfcSite)");
}
}
for(;range.first != range.second; ++range.first) {
const IFC::IfcSpatialStructureElement* const prod = (*range.first).second->ToPtr<IFC::IfcSpatialStructureElement>();
BOOST_FOREACH(const STEP::LazyObject* lz, *range) {
const IFC::IfcSpatialStructureElement* const prod = lz->ToPtr<IFC::IfcSpatialStructureElement>();
if(!prod) {
continue;
}
@ -2454,7 +2500,7 @@ void ProcessSpatialStructures(ConversionData& conv)
}
IFCImporter::ThrowException("Failed to determine primary site element");
IFCImporter::ThrowException("failed to determine primary site element");
}
// ------------------------------------------------------------------------------------------------

View File

@ -804,7 +804,7 @@ namespace STEP {
class DB
{
friend DB* ReadFileHeader(boost::shared_ptr<IOStream> stream);
friend void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme);
friend void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme,const char* const* arr, size_t len);
friend class LazyObject;
public:
@ -812,12 +812,12 @@ namespace STEP {
// objects indexed by ID
typedef std::map<uint64_t,boost::shared_ptr<const LazyObject> > ObjectMap;
// objects indexed by their declarative type
typedef std::step_unordered_multimap<std::string, const LazyObject* > ObjectMapByType;
typedef std::pair<ObjectMapByType::const_iterator,ObjectMapByType::const_iterator> ObjectMapRange;
// objects indexed by their declarative type, but only for those that we truly want
typedef std::set< const LazyObject*> ObjectSet;
typedef std::map<std::string, ObjectSet > ObjectMapByType;
// references - for each object id the ids of all objects which reference it
typedef std::multimap<uint64_t, uint64_t > RefMap;
typedef std::step_unordered_multimap<uint64_t, uint64_t > RefMap;
typedef std::pair<RefMap::const_iterator,RefMap::const_iterator> RefMapRange;
private:
@ -872,8 +872,8 @@ namespace STEP {
// get an arbitrary object out of the soup with the only restriction being its type.
const LazyObject* GetObject(const std::string& type) const {
const ObjectMapByType::const_iterator it = objects_bytype.find(type);
if (it != objects_bytype.end()) {
return (*it).second;
if (it != objects_bytype.end() && (*it).second.size()) {
return *(*it).second.begin();
}
return NULL;
}
@ -919,13 +919,24 @@ namespace STEP {
void InternInsert(boost::shared_ptr<const LazyObject> lz) {
objects[lz->id] = lz;
objects_bytype.insert(std::make_pair(lz->type,lz.get()));
const ObjectMapByType::iterator it = objects_bytype.find( lz->type );
if (it != objects_bytype.end()) {
(*it).second.insert(lz.get());
}
}
void SetSchema(const EXPRESS::ConversionSchema& _schema) {
schema = &_schema;
}
void SetTypesToTrack(const char* const* types, size_t N) {
for(size_t i = 0; i < N;++i) {
objects_bytype[types[i]] = ObjectSet();
}
}
HeaderInfo& GetHeader() {
return header;
}

View File

@ -161,9 +161,10 @@ STEP::DB* STEP::ReadFileHeader(boost::shared_ptr<IOStream> stream)
// ------------------------------------------------------------------------------------------------
void STEP::ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme)
void STEP::ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme,const char* const* arr, size_t len)
{
db.SetSchema(scheme);
db.SetTypesToTrack(arr,len);
const DB::ObjectMap& map = db.GetObjects();
LineSplitter& splitter = db.GetSplitter();

View File

@ -56,7 +56,11 @@ namespace STEP {
// --------------------------------------------------------------------------
// 2) read the actual file contents using a user-supplied set of
// conversion functions to interpret the data.
void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme);
void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const* arr, size_t len);
template <size_t N> inline void ReadFile(DB& db,const EXPRESS::ConversionSchema& scheme, const char* const (&arr)[N]) {
return ReadFile(db,scheme,arr,N);
}
} // ! STEP
} // ! Assimp