From bbd7547fff1b7838141482b9c47bff38a26dd13d Mon Sep 17 00:00:00 2001 From: aramis_acg Date: Fri, 13 May 2011 00:52:50 +0000 Subject: [PATCH] - Further work on IFC, fix transformations, support non-uniform transformations, optimize loading, use recursive algorithm to resolve holes in polygons, implement CSG logic to generate wall openings. The latter is currently disabled. - Triangulation step now automatically drops polygons with an area of zero. - Add debug preprocessor switch to dump all triangulations to a separate file. - Refactoring, collect some polygon related functions in a separate header, PolyTools.h git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@1002 67173fc5-114c-0410-ac8e-9d2fd5bffc1f --- code/IFCLoader.cpp | 870 ++++++++++++++++++++--------- code/IFCReaderGen.cpp | 51 +- code/IFCReaderGen.h | 9 +- code/PolyTools.h | 223 ++++++++ code/ProcessHelper.h | 88 +-- code/TriangulateProcess.cpp | 205 +++++-- scripts/IFCImporter/entitylist.txt | 6 + workspaces/vc9/assimp.vcproj | 4 + 8 files changed, 1065 insertions(+), 391 deletions(-) create mode 100644 code/PolyTools.h diff --git a/code/IFCLoader.cpp b/code/IFCLoader.cpp index c8cdd77ac..29a00a495 100644 --- a/code/IFCLoader.cpp +++ b/code/IFCLoader.cpp @@ -45,16 +45,20 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER +#include +#include + + #include "IFCLoader.h" #include "STEPFileReader.h" #include "IFCReaderGen.h" + #include "StreamReader.h" #include "MemoryIOWrapper.h" #include "ProcessHelper.h" +#include "PolyTools.h" -#include -#include using namespace Assimp; using namespace Assimp::Formatter; @@ -88,8 +92,32 @@ struct delete_fun } }; +// ------------------------------------------------------------------------------------------------ +// Temporary representation of an opening in a wall or a floor +// ------------------------------------------------------------------------------------------------ +struct TempMesh; +struct TempOpening +{ + const IFC::IfcExtrudedAreaSolid* solid; + aiVector3D extrusionDir; + boost::shared_ptr profileMesh; - // intermediate data dump during conversion + // ------------------------------------------------------------------------------ + TempOpening(const IFC::IfcExtrudedAreaSolid* solid,aiVector3D extrusionDir,boost::shared_ptr profileMesh) + : solid(solid) + , extrusionDir(extrusionDir) + , profileMesh(profileMesh) + { + } + + // ------------------------------------------------------------------------------ + void Transform(const aiMatrix4x4& mat); // defined later since TempMesh is not complete yet +}; + + +// ------------------------------------------------------------------------------------------------ +// Intermediate data storage during conversion. Keeps everything and a bit more. +// ------------------------------------------------------------------------------------------------ struct ConversionData { ConversionData(const STEP::DB& db, const IFC::IfcProject& proj, aiScene* out,const IFCImporter::Settings& settings) @@ -99,6 +127,8 @@ struct ConversionData , proj(proj) , out(out) , settings(settings) + , apply_openings() + , collect_openings() {} ~ConversionData() { @@ -121,15 +151,26 @@ struct ConversionData MeshCache cached_meshes; const IFCImporter::Settings& settings; + + // Intermediate arrays used to resolve openings in walls: only one of them + // can be given at a time. apply_openings if present if the current element + // is a wall and needs its openings to be poured into its geometry while + // collect_openings is present only if the current element is an + // IfcOpeningElement, for which all the geometry needs to be preserved + // for later processing by a parent, which is a wall. + std::vector* apply_openings; + std::vector* collect_openings; }; - // helper used during mesh construction +// ------------------------------------------------------------------------------------------------ +// Helper used during mesh construction. Aids at creating aiMesh'es out of relatively few polygons. +// ------------------------------------------------------------------------------------------------ struct TempMesh { std::vector verts; std::vector vertcnt; - std::vector mat_idx; + // ------------------------------------------------------------------------------ aiMesh* ToMesh() { ai_assert(verts.size() == std::accumulate(vertcnt.begin(),vertcnt.end(),0)); @@ -158,13 +199,40 @@ struct TempMesh } } - // XXX materials - mesh->mMaterialIndex = UINT_MAX; return mesh.release(); } + + // ------------------------------------------------------------------------------ + void Clear() { + verts.clear(); + vertcnt.clear(); + } + + // ------------------------------------------------------------------------------ + void Transform(const aiMatrix4x4& mat) { + BOOST_FOREACH(aiVector3D& v, verts) { + v *= mat; + } + } + + // ------------------------------------------------------------------------------ + aiVector3D Center() { + return std::accumulate(verts.begin(),verts.end(),aiVector3D(0.f,0.f,0.f)) / static_cast(verts.size()); + } + + }; +// ------------------------------------------------------------------------------ +void TempOpening::Transform(const aiMatrix4x4& mat) +{ + if(profileMesh) { + profileMesh->Transform(mat); + } + extrusionDir *= aiMatrix3x3(mat); +} + // forward declarations float ConvertSIPrefix(const std::string& prefix); @@ -176,6 +244,7 @@ aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el ,Conve void ProcessProductRepresentation(const IFC::IfcProduct& el, aiNode* nd, ConversionData& conv); void MakeTreeRelative(ConversionData& conv); void ConvertUnit(const EXPRESS::DataType* dt,ConversionData& conv); +void ProcessSweptAreaSolid(const IFC::IfcSweptAreaSolid& swept, TempMesh& meshout, ConversionData& conv); } // anon @@ -271,6 +340,10 @@ void IFCImporter::InternReadFile( const std::string& pFile, ProcessSpatialStructures(conv); MakeTreeRelative(conv); + // NOTE - this is a stress test for the importer, but it works only + // in a build with no entities disabled. See + // scripts/IFCImporter/CPPGenerator.py + // for more information. #ifdef ASSIMP_IFC_TEST db->EvaluateAll(); #endif @@ -506,7 +579,7 @@ void ConvertAxisPlacement(aiMatrix4x4& out, const IFC::IfcAxis2Placement3D& in, aiVector3D loc; ConvertCartesianPoint(loc,in.Location); - aiVector3D z(0.f,0.f,1.f),r(0.f,1.f,0.f),x; + aiVector3D z(0.f,0.f,1.f),r(1.f,0.f,0.f),x; if (in.Axis) { ConvertDirection(z,*in.Axis.Get()); @@ -531,7 +604,7 @@ void ConvertAxisPlacement(aiMatrix4x4& out, const IFC::IfcAxis2Placement2D& in, aiVector3D loc; ConvertCartesianPoint(loc,in.Location); - aiVector3D x(1.f,0.f,1.f); + aiVector3D x(1.f,0.f,0.f); if (in.RefDirection) { ConvertDirection(x,*in.RefDirection.Get()); } @@ -610,10 +683,20 @@ void ConvertTransformOperator(aiMatrix4x4& out, const IFC::IfcCartesianTransform aiMatrix4x4::Translation(loc,locm); AssignMatrixAxes(out,x,y,z); - const float sc = op.Scale?op.Scale.Get():1.f; + + aiVector3D vscale; + if (const IFC::IfcCartesianTransformationOperator3DnonUniform* nuni = op.ToPtr()) { + vscale.x = nuni->Scale?op.Scale.Get():1.f; + vscale.y = nuni->Scale2?nuni->Scale2.Get():1.f; + vscale.z = nuni->Scale3?nuni->Scale3.Get():1.f; + } + else { + const float sc = op.Scale?op.Scale.Get():1.f; + vscale = aiVector3D(sc,sc,sc); + } aiMatrix4x4 s; - aiMatrix4x4::Scaling(aiVector3D(sc,sc,sc),s); + aiMatrix4x4::Scaling(vscale,s); out = locm * out * s; } @@ -641,13 +724,161 @@ bool ProcessPolyloop(const IFC::IfcPolyLoop& loop, TempMesh& meshout, Conversion return false; } +// ------------------------------------------------------------------------------------------------ +void MergePolygonBoundaries(TempMesh& result, const TempMesh& meshout, size_t master_bounds = -1) +{ + // standard case - only one boundary, just copy it to the result vector + result.vertcnt.reserve(meshout.vertcnt.size()+result.vertcnt.size()); + if (meshout.vertcnt.size() <= 1) { + result.verts.reserve(meshout.verts.size()+result.verts.size()); + + std::copy(meshout.verts.begin(),meshout.verts.end(),std::back_inserter(result.verts)); + std::copy(meshout.vertcnt.begin(),meshout.vertcnt.end(),std::back_inserter(result.vertcnt)); + return; + } + + // handle polygons with holes. Our built in triangulation won't handle them as is, but + // the ear cutting algorithm is solid enough to deal with them if we join the inner + // holes with the outer boundaries by dummy connections. + IFCImporter::LogDebug("fixing polygon with holes for triangulation via ear-cutting"); + + // each hole results in two extra vertices + 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::const_iterator outer_polygon = meshout.vertcnt.end(), begin=meshout.vertcnt.begin(), iit; + for(iit = begin; iit != meshout.vertcnt.end(); ++iit) { + ai_assert(*iit); + max_vcount = std::max(max_vcount,static_cast(*iit)); + } + + std::vector temp((max_vcount+2)*4); + std::vector normals; + normals.reserve( meshout.vertcnt.size() ); + + size_t vidx = 0; + for(iit = begin; iit != meshout.vertcnt.end(); vidx += *iit++) { + for(size_t vofs = 0, cnt = 0; vofs < *iit; ++vofs) { + const aiVector3D& v = meshout.verts[vidx+vofs]; + temp[cnt++] = v.x; + temp[cnt++] = v.y; + temp[cnt++] = v.z; +#ifdef _DEBUG + temp[cnt] = std::numeric_limits::quiet_NaN(); +#endif + ++cnt; + } + + normals.push_back(aiVector3D()); + 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. + // 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; + outer_polygon_start = std::accumulate(begin,outer_polygon,0); + BOOST_FOREACH(aiVector3D& n, normals) { + n.Normalize(); + } + } + else { + float area_outer_polygon = 1e-10f; + size_t vidx = 0; + for(iit = begin; iit != meshout.vertcnt.end(); vidx += *iit++) { + // find the polygon with the largest area, it must be the outer bound. + aiVector3D& n = normals[std::distance(begin,iit)]; + const float area = n.Length(); + if (area > area_outer_polygon) { + area_outer_polygon = area; + outer_polygon = iit; + outer_polygon_start = vidx; + } + + n /= area; + } + } + + ai_assert(outer_polygon != meshout.vertcnt.end()); + + typedef boost::tuple::const_iterator, unsigned int, unsigned int> InsertionPoint; + std::vector< std::vector > insertions(*outer_polygon, std::vector()); + + // iterate through all other polyloops and find points in the outer polyloop that are close + vidx = 0; + for(iit = begin; iit != meshout.vertcnt.end(); vidx += *iit++) { + if (iit == outer_polygon) { + continue; + } + + size_t best_ofs,best_outer = *outer_polygon; + float best_dist = 1e10; + for(size_t vofs = 0; vofs < *iit; ++vofs) { + const aiVector3D& v = meshout.verts[vidx+vofs]; + + for(size_t outer = 0; outer < *outer_polygon; ++outer) { + const aiVector3D& o = meshout.verts[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& 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(meshout.verts[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(meshout.verts[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); +} + // ------------------------------------------------------------------------------------------------ void ProcessConnectedFaceSet(const IFC::IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv) { BOOST_FOREACH(const IFC::IfcFace& face, fset.CfsFaces) { TempMesh meshout; - size_t ob = face.Bounds.size(), cnt = 0; + size_t ob = -1, cnt = 0; BOOST_FOREACH(const IFC::IfcFaceBound& bound, face.Bounds) { if(const IFC::IfcPolyLoop* const polyloop = bound.Bound->ToPtr()) { @@ -673,131 +904,7 @@ void ProcessConnectedFaceSet(const IFC::IfcConnectedFaceSet& fset, TempMesh& res } - result.vertcnt.reserve(meshout.vertcnt.size()+result.vertcnt.size()); - if (meshout.vertcnt.size() <= 1) { - result.verts.reserve(meshout.verts.size()+result.verts.size()); - - std::copy(meshout.verts.begin(),meshout.verts.end(),std::back_inserter(result.verts)); - std::copy(meshout.vertcnt.begin(),meshout.vertcnt.end(),std::back_inserter(result.vertcnt)); - continue; - } - - IFCImporter::LogDebug("fixing polygon with holes for triangulation via ear-cutting"); - - // each hole results in two extra vertices - result.verts.reserve(meshout.verts.size()+cnt*2+result.verts.size()); - - // handle polygons with holes. our built in triangulation won't handle them as is, but - // the ear cutting algorithm is solid enough to deal with them if we join the inner - // holes with the outer boundaries by dummy connections. - size_t outer_polygon_start = 0; - - // see if one of the polygons is a IfcFaceOuterBound - treats this as the outer boundary. - // sadly we can't rely on it, the docs say 'At most one of the bounds shall be of the type IfcFaceOuterBound' - std::vector::iterator outer_polygon = meshout.vertcnt.end(), begin=meshout.vertcnt.begin(), iit; - if (ob < face.Bounds.size()) { - outer_polygon = begin + ob; - outer_polygon_start = std::accumulate(begin,outer_polygon,0); - } - else { - float area_outer_polygon = 1e-10f; - - // find the polygon with the largest area, it must be the outer bound. - size_t max_vcount = 0; - for(iit = begin; iit != meshout.vertcnt.end(); ++iit) { - ai_assert(*iit); - max_vcount = std::max(max_vcount,static_cast(*iit)); - } - std::vector temp((max_vcount+2)*4); - size_t vidx = 0; - for(iit = begin; iit != meshout.vertcnt.end(); vidx += *iit++) { - - for(size_t vofs = 0, cnt = 0; vofs < *iit; ++vofs) { - const aiVector3D& v = meshout.verts[vidx+vofs]; - temp[cnt++] = v.x; - temp[cnt++] = v.y; - temp[cnt++] = v.z; -#ifdef _DEBUG - temp[cnt] = std::numeric_limits::quiet_NaN(); -#endif - ++cnt; - } - - aiVector3D nor; - NewellNormal<4,4,4>(nor,*iit,&temp[0],&temp[1],&temp[2]); - const float area = nor.SquareLength(); - - if (area > area_outer_polygon) { - area_outer_polygon = area; - outer_polygon = iit; - outer_polygon_start = vidx; - } - } - } - - ai_assert(outer_polygon != meshout.vertcnt.end()); - - typedef boost::tuple InsertionPoint; - std::vector< InsertionPoint > insertions(*outer_polygon,boost::make_tuple(0u,0u,0u)); - - // iterate through all other polyloops and find points in the outer polyloop that are close - size_t vidx = 0; - for(iit = begin; iit != meshout.vertcnt.end(); vidx += *iit++) { - if (iit == outer_polygon) { - continue; - } - - size_t best_ofs,best_outer; - float best_dist = 1e10; - for(size_t vofs = 0; vofs < *iit; ++vofs) { - const aiVector3D& v = meshout.verts[vidx+vofs]; - - for(size_t outer = 0; outer < *outer_polygon; ++outer) { - if (insertions[outer].get<0>()) { - continue; - } - const aiVector3D& o = meshout.verts[outer_polygon_start+outer]; - const float d = (o-v).SquareLength(); - - if (d < best_dist) { - best_dist = d; - best_ofs = vofs; - best_outer = outer; - } - } - } - - // we will later insert a hidden connection line right after the closest point in the outer polygon - insertions[best_outer] = boost::make_tuple(*iit,vidx,best_ofs); - } - - // now that we collected all vertex connections to be added, build the output polygon - 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 InsertionPoint& ins = insertions[outer]; - if (!ins.get<0>()) { - continue; - } - - for(size_t i = ins.get<2>(); i < ins.get<0>(); ++i) { - result.verts.push_back(meshout.verts[ins.get<1>() + i]); - } - for(size_t i = 0; i < ins.get<2>(); ++i) { - result.verts.push_back(meshout.verts[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. - result.verts.push_back(meshout.verts[ins.get<1>() + ins.get<2>()]); - - // 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); + MergePolygonBoundaries(result,meshout,ob); } } @@ -812,31 +919,33 @@ void ProcessPolyLine(const IFC::IfcPolyline& def, TempMesh& meshout, ConversionD } } +// ------------------------------------------------------------------------------------------------ +bool ProcessCurve(const IFC::IfcCurve& curve, TempMesh& meshout, ConversionData& conv) +{ + if(const IFC::IfcPolyline* poly = curve.ToPtr()) { + ProcessPolyLine(*poly,meshout,conv); + } + else { + IFCImporter::LogWarn("skipping unknown IfcCurve entity, type is " + curve.GetClassName()); + return false; + } + return true; +} + // ------------------------------------------------------------------------------------------------ void ProcessClosedProfile(const IFC::IfcArbitraryClosedProfileDef& def, TempMesh& meshout, ConversionData& conv) { - if(const IFC::IfcPolyline* poly = def.OuterCurve->ToPtr()) { - ProcessPolyLine(*poly,meshout,conv); + if(ProcessCurve(def.OuterCurve,meshout,conv)) { if(meshout.verts.size()>2 && meshout.verts.front() == meshout.verts.back()) { meshout.verts.pop_back(); // duplicate element, first==last } } - else { - IFCImporter::LogWarn("skipping unknown IfcCurve entity, type is " + def.OuterCurve->GetClassName()); - return; - } } // ------------------------------------------------------------------------------------------------ void ProcessOpenProfile(const IFC::IfcArbitraryOpenProfileDef& def, TempMesh& meshout, ConversionData& conv) { - if(const IFC::IfcPolyline* poly = def.Curve->ToPtr()) { - ProcessPolyLine(*poly,meshout,conv); - } - else { - IFCImporter::LogWarn("skipping unknown IfcBoundedCurve entity, type is " + def.Curve->GetClassName()); - return; - } + ProcessCurve(def.Curve,meshout,conv); } // ------------------------------------------------------------------------------------------------ @@ -875,10 +984,7 @@ void ProcessParametrizedProfile(const IFC::IfcParameterizedProfileDef& def, Temp aiMatrix4x4 trafo; ConvertAxisPlacement(trafo, *def.Position,conv); - - BOOST_FOREACH(aiVector3D& v, meshout.verts) { - v *= trafo; - } + meshout.Transform(trafo); } // ------------------------------------------------------------------------------------------------ @@ -908,7 +1014,7 @@ void FixupFaceOrientation(TempMesh& result) vavg += v; } - // fixup face orientation. + // fix face orientation - try at least. vavg /= static_cast( result.verts.size() ); size_t c = 0; @@ -1006,14 +1112,75 @@ void ProcessRevolvedAreaSolid(const IFC::IfcRevolvedAreaSolid& solid, TempMesh& aiMatrix4x4 trafo; ConvertAxisPlacement(trafo, solid.Position,conv); - BOOST_FOREACH(aiVector3D& v, out) { - v *= trafo; - } + + result.Transform(trafo); FixupFaceOrientation(result); IFCImporter::LogDebug("generate mesh procedurally by radial extrusion (IfcRevolvedAreaSolid)"); } +// ------------------------------------------------------------------------------------------------ +bool TryAddOpening(const std::vector& openings,const std::vector& nors, TempMesh& curmesh) +{ + std::vector& out = curmesh.verts; + + const size_t s = out.size(); + + const aiVector3D any_point = out[s-3]; + const aiVector3D nor = ((out[s-2]-any_point)^(out[s-1]-any_point)).Normalize(); + + bool got_openings = false; + + size_t c = 0; + BOOST_FOREACH(const TempOpening& t,openings) { + const aiVector3D& outernor = nors[c++]; + const float dot = nor * outernor; + if (fabs(dot)<0.98) { + continue; + } + + const aiVector3D diff = t.extrusionDir; + const std::vector& va = t.profileMesh->verts; + if(va.size() <= 2) { + continue; + } + + const float dd = t.extrusionDir*nor; + IFCImporter::LogDebug("apply an IfcOpeningElement linked via IfcRelVoidsElement to this polygon"); + + got_openings = true; + if ( fabs((any_point-va[0]).Normalize()*nor) > 1e-3f) { + for(size_t i = 0; i < va.size(); ++i) { + out.push_back(va[i]+diff); + } + } + else { + for(size_t i = 0; i < va.size(); ++i) { + out.push_back(va[i]); + } + } + + curmesh.vertcnt.push_back(va.size()); + + TempMesh res; + MergePolygonBoundaries(res,curmesh,0); + curmesh = res; + } + return got_openings; +} + +// ------------------------------------------------------------------------------------------------ +struct DistanceSorter { + + DistanceSorter(const aiVector3D& base) : base(base) {} + + bool operator () (const TempOpening& a, const TempOpening& b) const { + return (a.profileMesh->Center()-base).SquareLength() < (b.profileMesh->Center()-base).SquareLength(); + } + + aiVector3D base; +}; + // ------------------------------------------------------------------------------------------------ void ProcessExtrudedAreaSolid(const IFC::IfcExtrudedAreaSolid& solid, TempMesh& result, ConversionData& conv) { @@ -1033,7 +1200,7 @@ void ProcessExtrudedAreaSolid(const IFC::IfcExtrudedAreaSolid& solid, TempMesh& // the underlying profile, extrude along the given axis, forming new // triangles. - const std::vector& in = meshout.verts; + std::vector& in = meshout.verts; const size_t size=in.size(); const bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2; @@ -1047,36 +1214,117 @@ void ProcessExtrudedAreaSolid(const IFC::IfcExtrudedAreaSolid& solid, TempMesh& result.verts.reserve(size*(has_area?4:2)); result.vertcnt.reserve(meshout.vertcnt.size()+2); + // transform to target space + aiMatrix4x4 trafo; + ConvertAxisPlacement(trafo, solid.Position,conv); + BOOST_FOREACH(aiVector3D& v,in) { + v *= trafo; + } + + + aiVector3D min = in[0]; + dir *= aiMatrix3x3(trafo); + + float cy = 0.f; + + // recompute the normal vectors for all openings + std::vector nors; + if (conv.apply_openings) { + // 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()); + + //std::reverse(conv.apply_openings->begin(),conv.apply_openings->end()); + BOOST_FOREACH(TempOpening& t,*conv.apply_openings) { + TempMesh& bounds = *t.profileMesh.get(); + //bounds.Transform(trafo); + + if (bounds.verts.size() <= 2) { + nors.push_back(aiVector3D()); + continue; + } + nors.push_back(((bounds.verts[2]-bounds.verts[0])^(bounds.verts[1]-bounds.verts[0]) ).Normalize()); + cy += nors.back().y; + } + + } + + bool rev = cy<0.f; + + // XXX disable all openings for now + conv.apply_openings = NULL; + + TempMesh temp; + TempMesh& curmesh = conv.apply_openings ? temp : result; + std::vector& out = curmesh.verts; + + size_t sides_with_openings = 0; for(size_t i = 0; i < size; ++i) { const size_t next = (i+1)%size; - result.vertcnt.push_back(4); + curmesh.vertcnt.push_back(4); + + out.push_back(in[i]); + out.push_back(in[i]+dir); + out.push_back(in[next]+dir); + out.push_back(in[next]); - result.verts.push_back(in[i]); - result.verts.push_back(in[next]); - result.verts.push_back(in[next]+dir); - result.verts.push_back(in[i]+dir); + if(conv.apply_openings) { + if(TryAddOpening(*conv.apply_openings,nors,curmesh)) { + ++sides_with_openings; + } + + MergePolygonBoundaries(result,temp,0); + temp.Clear(); + } } - + + size_t sides_with_v_openings = 0; if(has_area) { // leave the triangulation of the profile area to the ear cutting // implementation in aiProcess_Triangulate - for now we just // feed in two huge polygons. - for(size_t i = size; i--; ) { - result.verts.push_back(in[i]+dir); + for(size_t n = 0; n < 2; ++n) { + for(size_t i = size; i--; ) { + out.push_back(in[i]+(n?dir:aiVector3D())); + } + + curmesh.vertcnt.push_back(size); + if(conv.apply_openings) { + if(TryAddOpening(*conv.apply_openings,nors,curmesh)) { + ++sides_with_v_openings; + } + + MergePolygonBoundaries(result,temp,0); + temp.Clear(); + } } - for(size_t i = 0; i < size; ++i ) { - result.verts.push_back(in[i]); - } - result.vertcnt.push_back(size); - result.vertcnt.push_back(size); } - aiMatrix4x4 trafo; - ConvertAxisPlacement(trafo, solid.Position,conv); + // add connection geometry to close the 'holes' for the openings + if(conv.apply_openings) { + BOOST_FOREACH(const TempOpening& t,*conv.apply_openings) { + const std::vector& in = t.profileMesh->verts; + std::vector& out = result.verts; - BOOST_FOREACH(aiVector3D& v, result.verts) { - v *= trafo; + const aiVector3D dir = t.extrusionDir; + for(size_t i = 0, size = in.size(); i < size; ++i) { + const size_t next = (i+1)%size; + + result.vertcnt.push_back(4); + + out.push_back(in[i]); + out.push_back(in[i]+dir); + out.push_back(in[next]+dir); + out.push_back(in[next]-dir); + } + } + } + + if(conv.apply_openings && (sides_with_openings != 2 && sides_with_openings || sides_with_v_openings != 2 && sides_with_v_openings)) { + IFCImporter::LogWarn("failed to resolve all openings, presumably their topology is not supported by Assimp"); } FixupFaceOrientation(result); @@ -1087,6 +1335,22 @@ void ProcessExtrudedAreaSolid(const IFC::IfcExtrudedAreaSolid& solid, TempMesh& void ProcessSweptAreaSolid(const IFC::IfcSweptAreaSolid& swept, TempMesh& meshout, ConversionData& conv) { if(const IFC::IfcExtrudedAreaSolid* const solid = swept.ToPtr()) { + // Do we just collect openings for a parent element (i.e. a wall)? + // In this case we don't extrude the surface yet, just keep the profile and transform it correctly + if(conv.collect_openings) { + boost::shared_ptr meshtmp(new TempMesh()); + ProcessProfile(swept.SweptArea,*meshtmp,conv); + + aiMatrix4x4 m; + ConvertAxisPlacement(m,solid->Position,conv); + meshtmp->Transform(m); + + aiVector3D dir; + ConvertDirection(dir,solid->ExtrudedDirection); + conv.collect_openings->push_back(TempOpening(solid, aiMatrix3x3(m) * (dir*solid->Depth),meshtmp)); + return; + } + ProcessExtrudedAreaSolid(*solid,meshout,conv); } else if(const IFC::IfcRevolvedAreaSolid* const rev = swept.ToPtr()) { @@ -1123,6 +1387,7 @@ Intersect IntersectSegmentPlane(const aiVector3D& p,const aiVector3D& n, const a return Intersect_Yes; } + // ------------------------------------------------------------------------------------------------ void ProcessBoolean(const IFC::IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv) { @@ -1144,7 +1409,7 @@ void ProcessBoolean(const IFC::IfcBooleanResult& boolean, TempMesh& result, Conv IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid"); return; } - + if(const IFC::IfcBooleanResult* const op0 = clip->FirstOperand->ResolveSelectPtr(conv.db)) { ProcessBoolean(*op0,meshout,conv); } @@ -1170,10 +1435,10 @@ void ProcessBoolean(const IFC::IfcBooleanResult& boolean, TempMesh& result, Conv // clip the current contents of `meshout` against the plane we obtained from the second operand const std::vector& in = meshout.verts; std::vector& outvert = result.verts; - std::vector::const_iterator outer_polygon = meshout.vertcnt.end(), begin=meshout.vertcnt.begin(), iit; + std::vector::const_iterator begin=meshout.vertcnt.begin(), end=meshout.vertcnt.end(), iit; unsigned int vidx = 0; - for(iit = begin; iit != meshout.vertcnt.end(); vidx += *iit++) { + for(iit = begin; iit != end; vidx += *iit++) { unsigned int newcount = 0; for(unsigned int i = 0; i < *iit; ++i) { @@ -1231,18 +1496,81 @@ int ConvertShadingMode(const std::string& name) } // ------------------------------------------------------------------------------------------------ -unsigned int ProcessMaterials(const IFC::IfcRepresentationItem& item, ConversionData& conv) +void FillMaterial(MaterialHelper* mat,const IFC::IfcSurfaceStyle* surf,ConversionData& conv) { aiString name; - aiColor4D col; + name.Set((surf->Name? surf->Name.Get() : "IfcSurfaceStyle_Unnamed")); + mat->AddProperty(&name,AI_MATKEY_NAME); + // now see which kinds of surface information are present + BOOST_FOREACH(const IFC::IfcSurfaceStyleElementSelect* sel2, surf->Styles) { + if (const IFC::IfcSurfaceStyleShading* shade = sel2->ResolveSelectPtr(conv.db)) { + aiColor4D col_base,col; + + ConvertColor(col_base, shade->SurfaceColour); + mat->AddProperty(&col_base,1, AI_MATKEY_COLOR_DIFFUSE); + + if (const IFC::IfcSurfaceStyleRendering* ren = shade->ToPtr()) { + + if (ren->Transparency) { + const float t = 1.f-ren->Transparency.Get(); + mat->AddProperty(&t,1, AI_MATKEY_OPACITY); + } + + if (ren->DiffuseColour) { + ConvertColor(col, ren->DiffuseColour.Get(),conv,&col_base); + mat->AddProperty(&col,1, AI_MATKEY_COLOR_DIFFUSE); + } + + if (ren->SpecularColour) { + ConvertColor(col, ren->SpecularColour.Get(),conv,&col_base); + mat->AddProperty(&col,1, AI_MATKEY_COLOR_SPECULAR); + } + + if (ren->TransmissionColour) { + ConvertColor(col, ren->TransmissionColour.Get(),conv,&col_base); + mat->AddProperty(&col,1, AI_MATKEY_COLOR_TRANSPARENT); + } + + if (ren->ReflectionColour) { + ConvertColor(col, ren->ReflectionColour.Get(),conv,&col_base); + mat->AddProperty(&col,1, AI_MATKEY_COLOR_REFLECTIVE); + } + + const int shading = (ren->SpecularHighlight && ren->SpecularColour)?ConvertShadingMode(ren->ReflectanceMethod):aiShadingMode_Gouraud; + mat->AddProperty(&shading,1, AI_MATKEY_SHADING_MODEL); + + if (ren->SpecularHighlight) { + if(const EXPRESS::REAL* rt = ren->SpecularHighlight.Get()->ToPtr()) { + // at this point we don't distinguish between the two distinct ways of + // specifying highlight intensities. leave this to the user. + const float e = *rt; + mat->AddProperty(&e,1,AI_MATKEY_SHININESS); + } + else { + IFCImporter::LogWarn("unexpected type error, SpecularHighlight should be a REAL"); + } + } + } + } + else if (const IFC::IfcSurfaceStyleWithTextures* tex = sel2->ResolveSelectPtr(conv.db)) { + // XXX + } + } + +} + +// ------------------------------------------------------------------------------------------------ +unsigned int ProcessMaterials(const IFC::IfcRepresentationItem& item, ConversionData& conv) +{ if (conv.materials.empty()) { + aiString name; std::auto_ptr mat(new MaterialHelper()); name.Set(""); mat->AddProperty(&name,AI_MATKEY_NAME); - col = aiColor4D(0.6f,0.6f,0.6f,1.0f); + aiColor4D col = aiColor4D(0.6f,0.6f,0.6f,1.0f); mat->AddProperty(&col,1, AI_MATKEY_COLOR_DIFFUSE); conv.materials.push_back(mat.release()); @@ -1254,7 +1582,7 @@ unsigned int ProcessMaterials(const IFC::IfcRepresentationItem& item, Conversion BOOST_FOREACH(const IFC::IfcPresentationStyleAssignment& as, styled->Styles) { BOOST_FOREACH(const IFC::IfcPresentationStyleSelect* sel, as.Styles) { - if (const IFC::IfcSurfaceStyle* surf = sel->ResolveSelectPtr(conv.db)) { + if (const IFC::IfcSurfaceStyle* const surf = sel->ResolveSelectPtr(conv.db)) { const std::string side = static_cast(surf->Side); if (side != "BOTH") { IFCImporter::LogWarn("ignoring surface side marker on IFC::IfcSurfaceStyle: " + side); @@ -1262,61 +1590,8 @@ unsigned int ProcessMaterials(const IFC::IfcRepresentationItem& item, Conversion std::auto_ptr mat(new MaterialHelper()); - name.Set((surf->Name? surf->Name.Get() : "IfcSurfaceStyle_Unnamed")); - mat->AddProperty(&name,AI_MATKEY_NAME); - - // now see which kinds of surface information are present - BOOST_FOREACH(const IFC::IfcSurfaceStyleElementSelect* sel2, surf->Styles) { - - if (const IFC::IfcSurfaceStyleShading* shade = sel2->ResolveSelectPtr(conv.db)) { - aiColor4D col_base; - - ConvertColor(col_base, shade->SurfaceColour); - mat->AddProperty(&col,1, AI_MATKEY_COLOR_DIFFUSE); - - if (const IFC::IfcSurfaceStyleRendering* ren = shade->ToPtr()) { - - if (ren->DiffuseColour) { - ConvertColor(col, ren->DiffuseColour.Get(),conv,&col_base); - mat->AddProperty(&col,1, AI_MATKEY_COLOR_DIFFUSE); - } - - if (ren->SpecularColour) { - ConvertColor(col, ren->SpecularColour.Get(),conv,&col_base); - mat->AddProperty(&col,1, AI_MATKEY_COLOR_SPECULAR); - } - - if (ren->TransmissionColour) { - ConvertColor(col, ren->TransmissionColour.Get(),conv,&col_base); - mat->AddProperty(&col,1, AI_MATKEY_COLOR_TRANSPARENT); - } - - if (ren->ReflectionColour) { - ConvertColor(col, ren->ReflectionColour.Get(),conv,&col_base); - mat->AddProperty(&col,1, AI_MATKEY_COLOR_REFLECTIVE); - } - - const int shading = (ren->SpecularHighlight && ren->SpecularColour)?ConvertShadingMode(ren->ReflectanceMethod):aiShadingMode_Gouraud; - mat->AddProperty(&shading,1, AI_MATKEY_SHADING_MODEL); - - if (ren->SpecularHighlight) { - if(const EXPRESS::REAL* rt = ren->SpecularHighlight.Get()->ToPtr()) { - // at this point we don't distinguish between the two distinct ways of - // specifying highlight intensities. leave this to the user. - const float e = *rt; - mat->AddProperty(&e,1,AI_MATKEY_SHININESS); - } - else { - IFCImporter::LogWarn("unexpected type error, SpecularHighlight should be a REAL"); - } - } - } - } - else if (const IFC::IfcSurfaceStyleWithTextures* tex = sel2->ResolveSelectPtr(conv.db)) { - // XXX - } - } - + FillMaterial(mat.get(),surf,conv); + conv.materials.push_back(mat.release()); return conv.materials.size()-1; } @@ -1442,7 +1717,9 @@ bool ProcessRepresentationItem(const IFC::IfcRepresentationItem& item, std::vect if(const IFC::IfcTopologicalRepresentationItem* const topo = item.ToPtr()) { if (!TryQueryMeshCache(item,mesh_indices,conv)) { if(ProcessTopologicalItem(*topo,mesh_indices,conv)) { - PopulateMeshCache(item,mesh_indices,conv); + if(mesh_indices.size()) { + PopulateMeshCache(item,mesh_indices,conv); + } } else return false; } @@ -1451,8 +1728,9 @@ bool ProcessRepresentationItem(const IFC::IfcRepresentationItem& item, std::vect else if(const IFC::IfcGeometricRepresentationItem* const geo = item.ToPtr()) { if (!TryQueryMeshCache(item,mesh_indices,conv)) { if(ProcessGeometricItem(*geo,mesh_indices,conv)) { - PopulateMeshCache(item,mesh_indices,conv); - + if(mesh_indices.size()) { + PopulateMeshCache(item,mesh_indices,conv); + } } else return false; } @@ -1493,9 +1771,10 @@ void ProcessMappedItem(const IFC::IfcMappedItem& mapped, aiNode* nd_src, std::ve { // insert a custom node here, the cartesian transform operator is simply a conventional transformation matrix std::auto_ptr nd(new aiNode()); - nd->mName.Set("MappedItem"); + nd->mName.Set("IfcMappedItem"); std::vector meshes; + const size_t old_openings = conv.collect_openings ? conv.collect_openings->size() : 0; const IFC::IfcRepresentation& repr = mapped.MappingSource->MappedRepresentation; BOOST_FOREACH(const IFC::IfcRepresentationItem& item, repr.Items) { @@ -1515,10 +1794,25 @@ void ProcessMappedItem(const IFC::IfcMappedItem& mapped, aiNode* nd_src, std::ve aiMatrix4x4 minv = msrc; minv.Inverse(); - //aiMatrix4x4 correct; - //GetAbsTransform(correct,nd_src,conv); + minv = m*msrc; + if (conv.collect_openings) { + // if this pass serves us only to collect opening geometry, + // make sure we transform the TempMesh's which we need to + // preserve as well. + if(const size_t diff = conv.collect_openings->size() - old_openings) { + for(size_t i = 0; i < diff; ++i) { + (*conv.collect_openings)[old_openings+i].Transform(minv); + } + } + } - nd->mTransformation = nd_src->mTransformation * minv * m * msrc; + if (conv.apply_openings) { + BOOST_FOREACH(TempOpening& open,*conv.apply_openings){ + open.Transform(minv); + } + } + + nd->mTransformation = nd_src->mTransformation * minv; subnodes_src.push_back(nd.release()); } @@ -1557,7 +1851,7 @@ void ProcessProductRepresentation(const IFC::IfcProduct& el, aiNode* nd, std::ve } // ------------------------------------------------------------------------------------------------ -aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el, ConversionData& conv) +aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el, ConversionData& conv, std::vector* collect_openings = NULL) { const STEP::DB::RefMap& refs = conv.db.GetRefs(); @@ -1570,47 +1864,101 @@ aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el, Conve ResolveObjectPlacement(nd->mTransformation,el.ObjectPlacement.Get(),conv); } + std::vector openings; + + aiMatrix4x4 myInv; + bool didinv = false; + // convert everything contained directly within this structure, // this may result in more nodes. std::vector< aiNode* > subnodes; try { - - ProcessProductRepresentation(el,nd.get(),subnodes,conv); - // locate aggregates and 'contained-in-here'-elements of this spatial structure and add them in recursively + // on our way, collect openings in *this* element STEP::DB::RefMapRange range = refs.equal_range(el.GetID()); - for(STEP::DB::RefMapRange range2=range;range2.first != range.second; ++range2.first) { - if(const IFC::IfcRelContainedInSpatialStructure* const cont = conv.db.GetObject((*range2.first).second)-> - ToPtr()) { - + for(STEP::DB::RefMapRange range2 = range; range2.first != range.second; ++range2.first) { + const STEP::LazyObject& obj = conv.db.MustGetObject((*range2.first).second); + + // handle regularly-contained elements + if(const IFC::IfcRelContainedInSpatialStructure* const cont = obj->ToPtr()) { BOOST_FOREACH(const IFC::IfcProduct& pro, cont->RelatedElements) { - subnodes.push_back( ProcessSpatialStructure(nd.get(),pro,conv) ); + if(const IFC::IfcOpeningElement* const open = pro.ToPtr()) { + // IfcOpeningElement is handled below. Sadly we can't use it here as is: + // The docs say that opening elements are USUALLY attached to building storeys + // but we want them for the building elements to which they belong to. + continue; + } + + subnodes.push_back( ProcessSpatialStructure(nd.get(),pro,conv,NULL) ); + } + } + // handle openings, which we collect in a list rather than adding them to the node graph + else if(const IFC::IfcRelVoidsElement* const fills = obj->ToPtr()) { + if(fills->RelatingBuildingElement->GetID() == el.GetID()) { + const IFC::IfcFeatureElementSubtraction& open = fills->RelatedOpeningElement; + + // move opening elements to a separate node since they are semantically different than elements that are just 'contained' + std::auto_ptr nd_aggr(new aiNode()); + nd_aggr->mName.Set("$RelVoidsElement"); + nd_aggr->mParent = nd.get(); + + nd_aggr->mTransformation = nd->mTransformation; + + nd_aggr->mNumChildren = 1; + nd_aggr->mChildren = new aiNode*[1](); + + std::vector openings_local; + nd_aggr->mChildren[0] = ProcessSpatialStructure( nd_aggr.get(),open, conv,&openings_local); + + + if(openings_local.size()) { + if (!didinv) { + myInv = aiMatrix4x4(nd->mTransformation ).Inverse(); + didinv = true; + } + + // we need all openings to be in the local space of *this* node, so transform them + BOOST_FOREACH(TempOpening& op,openings_local) { + op.Transform( myInv*nd_aggr->mChildren[0]->mTransformation); + openings.push_back(op); + } + } + + subnodes.push_back( nd_aggr.release() ); } - break; } } for(;range.first != range.second; ++range.first) { if(const IFC::IfcRelAggregates* const aggr = conv.db.GetObject((*range.first).second)->ToPtr()) { - // move aggregate elements to a separate node since they are semantically different than elements that are merely 'contained' + // move aggregate elements to a separate node since they are semantically different than elements that are just 'contained' std::auto_ptr nd_aggr(new aiNode()); - nd_aggr->mName.Set("$Aggregates"); + nd_aggr->mName.Set("$RelAggregates"); nd_aggr->mParent = nd.get(); + nd_aggr->mTransformation = nd->mTransformation; + nd_aggr->mChildren = new aiNode*[aggr->RelatedObjects.size()](); BOOST_FOREACH(const IFC::IfcObjectDefinition& def, aggr->RelatedObjects) { if(const IFC::IfcProduct* const prod = def.ToPtr()) { - nd_aggr->mChildren[nd_aggr->mNumChildren++] = ProcessSpatialStructure(nd_aggr.get(),*prod,conv); + nd_aggr->mChildren[nd_aggr->mNumChildren++] = ProcessSpatialStructure(nd_aggr.get(),*prod,conv,NULL); } } subnodes.push_back( nd_aggr.release() ); - break; } } + conv.collect_openings = collect_openings; + if(!conv.collect_openings) { + conv.apply_openings = &openings; + } + ProcessProductRepresentation(el,nd.get(),subnodes,conv); + + conv.apply_openings = conv.collect_openings = NULL; + if (subnodes.size()) { nd->mChildren = new aiNode*[subnodes.size()](); BOOST_FOREACH(aiNode* nd2, subnodes) { @@ -1661,10 +2009,10 @@ void ProcessSpatialStructures(ConversionData& conv) BOOST_FOREACH(const IFC::IfcObjectDefinition& def, aggr->RelatedObjects) { // comparing pointer values is not sufficient, we would need to cast them to the same type first // as there is multiple inheritance in the game. - if (def.GlobalId == prod->GlobalId) { + if (def.GetID() == prod->GetID()) { IFCImporter::LogDebug("selecting this spatial structure as root structure"); // got it, this is the primary site. - conv.out->mRootNode = ProcessSpatialStructure(NULL,*prod,conv); + conv.out->mRootNode = ProcessSpatialStructure(NULL,*prod,conv,NULL); return; } } diff --git a/code/IFCReaderGen.cpp b/code/IFCReaderGen.cpp index 86fc9f69e..2895e05cc 100644 --- a/code/IFCReaderGen.cpp +++ b/code/IFCReaderGen.cpp @@ -654,7 +654,7 @@ namespace { , SchemaEntry("ifcreldefinesbyproperties",&STEP::ObjectHelper::Construct ) , SchemaEntry("ifccondition",&STEP::ObjectHelper::Construct ) , SchemaEntry("ifcgridaxis",&STEP::ObjectHelper::Construct ) -, SchemaEntry("ifcrelvoidselement",&STEP::ObjectHelper::Construct ) +, SchemaEntry("ifcrelvoidselement",&STEP::ObjectHelper::Construct ) , SchemaEntry("ifcwindow",&STEP::ObjectHelper::Construct ) , SchemaEntry("ifcrelflowcontrolelements",&STEP::ObjectHelper::Construct ) , SchemaEntry("ifcrelconnectsporttoelement",&STEP::ObjectHelper::Construct ) @@ -1329,7 +1329,16 @@ template <> size_t GenericFill(const DB& db, const LIST& para template <> size_t GenericFill(const DB& db, const LIST& params, IfcPolygonalBoundedHalfSpace* in) { size_t base = GenericFill(db,params,static_cast(in)); -// this data structure is not used yet, so there is no code generated to fill its members + if (params.GetSize() < 4) { throw STEP::TypeError("expected 4 arguments to IfcPolygonalBoundedHalfSpace"); } do { // convert the 'Position' argument + const DataType* arg = params[base++]; + try { GenericConvert( in->Position, *arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 2 to IfcPolygonalBoundedHalfSpace to be a `IfcAxis2Placement3D`")); } + } while(0); + do { // convert the 'PolygonalBoundary' argument + const DataType* arg = params[base++]; + try { GenericConvert( in->PolygonalBoundary, *arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 3 to IfcPolygonalBoundedHalfSpace to be a `IfcBoundedCurve`")); } + } while(0); return base; } // ----------------------------------------------------------------------------------------------------------- @@ -1427,22 +1436,19 @@ template <> size_t GenericFill(const DB& db, const LIST& param template <> size_t GenericFill(const DB& db, const LIST& params, IfcFeatureElement* in) { size_t base = GenericFill(db,params,static_cast(in)); -// this data structure is not used yet, so there is no code generated to fill its members - return base; + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcFeatureElement"); } return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcFeatureElementSubtraction* in) { size_t base = GenericFill(db,params,static_cast(in)); -// this data structure is not used yet, so there is no code generated to fill its members - return base; + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcFeatureElementSubtraction"); } return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcOpeningElement* in) { size_t base = GenericFill(db,params,static_cast(in)); -// this data structure is not used yet, so there is no code generated to fill its members - return base; + if (params.GetSize() < 8) { throw STEP::TypeError("expected 8 arguments to IfcOpeningElement"); } return base; } // ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcConditionCriterion* in) @@ -2635,6 +2641,22 @@ template <> size_t GenericFill(const DB& db, const LIST& params, I return base; } // ----------------------------------------------------------------------------------------------------------- +template <> size_t GenericFill(const DB& db, const LIST& params, IfcRelVoidsElement* in) +{ + size_t base = GenericFill(db,params,static_cast(in)); + if (params.GetSize() < 6) { throw STEP::TypeError("expected 6 arguments to IfcRelVoidsElement"); } do { // convert the 'RelatingBuildingElement' argument + const DataType* arg = params[base++]; + try { GenericConvert( in->RelatingBuildingElement, *arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 4 to IfcRelVoidsElement to be a `IfcElement`")); } + } while(0); + do { // convert the 'RelatedOpeningElement' argument + const DataType* arg = params[base++]; + try { GenericConvert( in->RelatedOpeningElement, *arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcRelVoidsElement to be a `IfcFeatureElementSubtraction`")); } + } while(0); + return base; +} +// ----------------------------------------------------------------------------------------------------------- template <> size_t GenericFill(const DB& db, const LIST& params, IfcWindow* in) { size_t base = GenericFill(db,params,static_cast(in)); @@ -2814,7 +2836,18 @@ template <> size_t GenericFill(const DB& d template <> size_t GenericFill(const DB& db, const LIST& params, IfcCartesianTransformationOperator3DnonUniform* in) { size_t base = GenericFill(db,params,static_cast(in)); -// this data structure is not used yet, so there is no code generated to fill its members + if (params.GetSize() < 7) { throw STEP::TypeError("expected 7 arguments to IfcCartesianTransformationOperator3DnonUniform"); } do { // convert the 'Scale2' argument + const DataType* arg = params[base++]; + if (dynamic_cast(&*arg)) break; + try { GenericConvert( in->Scale2, *arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 5 to IfcCartesianTransformationOperator3DnonUniform to be a `REAL`")); } + } while(0); + do { // convert the 'Scale3' argument + const DataType* arg = params[base++]; + if (dynamic_cast(&*arg)) break; + try { GenericConvert( in->Scale3, *arg, db ); break; } + catch (const TypeError& t) { throw TypeError(t.what() + std::string(" - expected argument 6 to IfcCartesianTransformationOperator3DnonUniform to be a `REAL`")); } + } while(0); return base; } // ----------------------------------------------------------------------------------------------------------- diff --git a/code/IFCReaderGen.h b/code/IFCReaderGen.h index 7bbd2b96f..d2f979e46 100644 --- a/code/IFCReaderGen.h +++ b/code/IFCReaderGen.h @@ -994,7 +994,7 @@ namespace IFC { typedef NotImplemented IfcRelDefinesByProperties; // (not currently used by Assimp) struct IfcCondition; typedef NotImplemented IfcGridAxis; // (not currently used by Assimp) - typedef NotImplemented IfcRelVoidsElement; // (not currently used by Assimp) + struct IfcRelVoidsElement; struct IfcWindow; typedef NotImplemented IfcRelFlowControlElements; // (not currently used by Assimp) typedef NotImplemented IfcRelConnectsPortToElement; // (not currently used by Assimp) @@ -2376,6 +2376,12 @@ namespace IFC { }; + // C++ wrapper for IfcRelVoidsElement + struct IfcRelVoidsElement : IfcRelConnects, ObjectHelper { IfcRelVoidsElement() : Object("IfcRelVoidsElement") {} + Lazy< IfcElement > RelatingBuildingElement; + Lazy< IfcFeatureElementSubtraction > RelatedOpeningElement; + }; + // C++ wrapper for IfcWindow struct IfcWindow : IfcBuildingElement, ObjectHelper { IfcWindow() : Object("IfcWindow") {} Maybe< IfcPositiveLengthMeasure::Out > OverallHeight; @@ -3980,6 +3986,7 @@ namespace STEP { DECL_CONV_STUB(IfcWorkControl); DECL_CONV_STUB(IfcWorkPlan); DECL_CONV_STUB(IfcCondition); + DECL_CONV_STUB(IfcRelVoidsElement); DECL_CONV_STUB(IfcWindow); DECL_CONV_STUB(IfcProtectiveDeviceType); DECL_CONV_STUB(IfcJunctionBoxType); diff --git a/code/PolyTools.h b/code/PolyTools.h new file mode 100644 index 000000000..4d1879bfd --- /dev/null +++ b/code/PolyTools.h @@ -0,0 +1,223 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2010, ASSIMP Development Team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the ASSIMP team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the ASSIMP Development Team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file PolyTools.h, various utilities for our dealings with arbitrary polygons */ + +#ifndef AI_POLYTOOLS_H_INCLUDED +#define AI_POLYTOOLS_H_INCLUDED + +namespace Assimp { + +// ------------------------------------------------------------------------------- +/** Test if a given point p2 is on the left side of the line formed by p0-p1. + * The function accepts an unconstrained template parameter for use with + * both aiVector3D and aiVector2D, but generally ignores the third coordinate.*/ +template +inline bool OnLeftSideOfLine2D(const T& p0, const T& p1,const T& p2) +{ + return ( (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y) ) > 0; +} + +// ------------------------------------------------------------------------------- +/** Test if a given point is inside a given triangle in R2. + * The function accepts an unconstrained template parameter for use with + * both aiVector3D and aiVector2D, but generally ignores the third coordinate.*/ +template +inline bool PointInTriangle2D(const T& p0, const T& p1,const T& p2, const T& pp) +{ + // Point in triangle test using baryzentric coordinates + const aiVector2D v0 = p1 - p0; + const aiVector2D v1 = p2 - p0; + const aiVector2D v2 = pp - p0; + + float dot00 = v0 * v0; + float dot01 = v0 * v1; + float dot02 = v0 * v2; + float dot11 = v1 * v1; + float dot12 = v1 * v2; + + const float invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + dot11 = (dot11 * dot02 - dot01 * dot12) * invDenom; + dot00 = (dot00 * dot12 - dot01 * dot02) * invDenom; + + return (dot11 >= 0) && (dot00 >= 0) && (dot11 + dot00 <= 1); +} + + +// ------------------------------------------------------------------------------- +/** Compute the signed area of a triangle. + * The function accepts an unconstrained template parameter for use with + * both aiVector3D and aiVector2D, but generally ignores the third coordinate.*/ +template +inline float GetArea2D(const T& v1, const T& v2, const T& v3) +{ + return 0.5f * (v1.x * (v3.y - v2.y) + v2.x * (v1.y - v3.y) + v3.x * (v2.y - v1.y)); +} + + +// ------------------------------------------------------------------------------- +/** Check whether the winding order of a given polygon is counter-clockwise. + * The function accepts an unconstrained template parameter, but is intended + * to be used only with aiVector2D and aiVector3D (z axis is ignored, only + * x and y are taken into account). + * @note Code taken from http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/applet1.html and translated to C++ + */ +template +inline bool IsCCW(T* in, size_t npoints) { + double aa, bb, cc, b, c, theta; + double convex_turn; + double convex_sum = 0; + + for (int i = 0; i < npoints - 2; i++) { + aa = ((in[i+2].x - in[i].x) * (in[i+2].x - in[i].x)) + + ((-in[i+2].y + in[i].y) * (-in[i+2].y + in[i].y)); + + bb = ((in[i+1].x - in[i].x) * (in[i+1].x - in[i].x)) + + ((-in[i+1].y + in[i].y) * (-in[i+1].y + in[i].y)); + + cc = ((in[i+2].x - in[i+1].x) * + (in[i+2].x - in[i+1].x)) + + ((-in[i+2].y + in[i+1].y) * + (-in[i+2].y + in[i+1].y)); + + b = sqrt(bb); + c = sqrt(cc); + theta = acos((bb + cc - aa) / (2 * b * c)); + + if (OnLeftSideOfLine2D(in[i],in[i+2],in[i+1])) { + // if (convex(in[i].x, in[i].y, + // in[i+1].x, in[i+1].y, + // in[i+2].x, in[i+2].y)) { + convex_turn = AI_MATH_PI_F - theta; + convex_sum += convex_turn; + } + else { + convex_sum -= AI_MATH_PI_F - theta; + } + } + aa = ((in[1].x - in[npoints-2].x) * + (in[1].x - in[npoints-2].x)) + + ((-in[1].y + in[npoints-2].y) * + (-in[1].y + in[npoints-2].y)); + + bb = ((in[0].x - in[npoints-2].x) * + (in[0].x - in[npoints-2].x)) + + ((-in[0].y + in[npoints-2].y) * + (-in[0].y + in[npoints-2].y)); + + cc = ((in[1].x - in[0].x) * (in[1].x - in[0].x)) + + ((-in[1].y + in[0].y) * (-in[1].y + in[0].y)); + + b = sqrt(bb); + c = sqrt(cc); + theta = acos((bb + cc - aa) / (2 * b * c)); + + //if (convex(in[npoints-2].x, in[npoints-2].y, + // in[0].x, in[0].y, + // in[1].x, in[1].y)) { + if (OnLeftSideOfLine2D(in[npoints-2],in[1],in[0])) { + convex_turn = AI_MATH_PI_F - theta; + convex_sum += convex_turn; + } + else { + convex_sum -= AI_MATH_PI_F - theta; + } + + return convex_sum >= (2 * AI_MATH_PI_F); +} + + +// ------------------------------------------------------------------------------- +/** Compute the normal of an arbitrary polygon in R3. + * + * The code is based on Newell's formula, that is a polygons normal is the ratio + * of its area when projected onto the three coordinate axes. + * + * @param out Receives the output normal + * @param num Number of input vertices + * @param x X data source. x[ofs_x*n] is the n'th element. + * @param y Y data source. y[ofs_y*n] is the y'th element + * @param z Z data source. z[ofs_z*n] is the z'th element + * + * @note The data arrays must have storage for at least num+2 elements. Using + * this method is much faster than the 'other' NewellNormal() + */ +template +inline void NewellNormal (aiVector3D& out, int num, float* x, float* y, float* z) +{ + // Duplicate the first two vertices at the end + x[(num+0)*ofs_x] = x[0]; + x[(num+1)*ofs_x] = x[ofs_x]; + + y[(num+0)*ofs_y] = y[0]; + y[(num+1)*ofs_y] = y[ofs_y]; + + z[(num+0)*ofs_z] = z[0]; + z[(num+1)*ofs_z] = z[ofs_z]; + + float sum_xy = 0.0, sum_yz = 0.0, sum_zx = 0.0; + + float *xptr = x +ofs_x, *xlow = x, *xhigh = x + ofs_x*2; + float *yptr = y +ofs_y, *ylow = y, *yhigh = y + ofs_y*2; + float *zptr = z +ofs_z, *zlow = z, *zhigh = z + ofs_z*2; + + for (int tmp=0; tmp < num; tmp++) { + sum_xy += (*xptr) * ( (*yhigh) - (*ylow) ); + sum_yz += (*yptr) * ( (*zhigh) - (*zlow) ); + sum_zx += (*zptr) * ( (*xhigh) - (*xlow) ); + + xptr += ofs_x; + xlow += ofs_x; + xhigh += ofs_x; + + yptr += ofs_y; + ylow += ofs_y; + yhigh += ofs_y; + + zptr += ofs_z; + zlow += ofs_z; + zhigh += ofs_z; + } + out = aiVector3D(sum_yz,sum_zx,sum_xy); +} + +} // ! Assimp + +#endif diff --git a/code/ProcessHelper.h b/code/ProcessHelper.h index 6a091613c..b08730d44 100644 --- a/code/ProcessHelper.h +++ b/code/ProcessHelper.h @@ -64,6 +64,16 @@ namespace std { return ::aiVector3D (max(a.x,b.x),max(a.y,b.y),max(a.z,b.z)); } + // std::min for aiVector2D + inline ::aiVector2D min (const ::aiVector2D& a, const ::aiVector2D& b) { + return ::aiVector2D (min(a.x,b.x),min(a.y,b.y)); + } + + // std::max for aiVector2D + inline ::aiVector2D max (const ::aiVector2D& a, const ::aiVector2D& b) { + return ::aiVector2D (max(a.x,b.x),max(a.y,b.y)); + } + // std::min for aiColor4D inline ::aiColor4D min (const ::aiColor4D& a, const ::aiColor4D& b) { return ::aiColor4D (min(a.r,b.r),min(a.g,b.g),min(a.b,b.b),min(a.a,b.a)); @@ -126,13 +136,13 @@ struct MinMaxChooser; template <> struct MinMaxChooser { void operator ()(float& min,float& max) { - max = -10e10f; - min = 10e10f; + max = -1e10f; + min = 1e10f; }}; template <> struct MinMaxChooser { void operator ()(double& min,double& max) { - max = -10e10; - min = 10e10; + max = -1e10; + min = 1e10; }}; template <> struct MinMaxChooser { void operator ()(unsigned int& min,unsigned int& max) { @@ -142,19 +152,24 @@ template <> struct MinMaxChooser { template <> struct MinMaxChooser { void operator ()(aiVector3D& min,aiVector3D& max) { - max = aiVector3D(-10e10f,-10e10f,-10e10f); - min = aiVector3D( 10e10f, 10e10f, 10e10f); + max = aiVector3D(-1e10f,-1e10f,-1e10f); + min = aiVector3D( 1e10f, 1e10f, 1e10f); }}; +template <> struct MinMaxChooser { + void operator ()(aiVector2D& min,aiVector2D& max) { + max = aiVector2D(-1e10f,-1e10f); + min = aiVector2D( 1e10f, 1e10f); + }}; template <> struct MinMaxChooser { void operator ()(aiColor4D& min,aiColor4D& max) { - max = aiColor4D(-10e10f,-10e10f,-10e10f,-10e10f); - min = aiColor4D( 10e10f, 10e10f, 10e10f, 10e10f); + max = aiColor4D(-1e10f,-1e10f,-1e10f,-1e10f); + min = aiColor4D( 1e10f, 1e10f, 1e10f, 1e10f); }}; template <> struct MinMaxChooser { void operator ()(aiQuaternion& min,aiQuaternion& max) { - max = aiQuaternion(-10e10f,-10e10f,-10e10f,-10e10f); - min = aiQuaternion( 10e10f, 10e10f, 10e10f, 10e10f); + max = aiQuaternion(-1e10f,-1e10f,-1e10f,-1e10f); + min = aiQuaternion( 1e10f, 1e10f, 1e10f, 1e10f); }}; template <> struct MinMaxChooser { @@ -192,59 +207,6 @@ inline void ArrayBounds(const T* in, unsigned int size, T& min, T& max) } -// ------------------------------------------------------------------------------- -/** @brief Compute the newell normal of a polygon regardless of its shape - * - * @param out Receives the output normal - * @param num Number of input vertices - * @param x X data source. x[ofs_x*n] is the n'th element. - * @param y Y data source. y[ofs_y*n] is the y'th element - * @param z Z data source. z[ofs_z*n] is the z'th element - * - * @note The data arrays must have storage for at least num+2 elements. Using - * this method is much faster than the 'other' NewellNormal() - */ -template -inline void NewellNormal (aiVector3D& out, int num, float* x, float* y, float* z) -{ - // Duplicate the first two vertices at the end - x[(num+0)*ofs_x] = x[0]; - x[(num+1)*ofs_x] = x[ofs_x]; - - y[(num+0)*ofs_y] = y[0]; - y[(num+1)*ofs_y] = y[ofs_y]; - - z[(num+0)*ofs_z] = z[0]; - z[(num+1)*ofs_z] = z[ofs_z]; - - float sum_xy = 0.0, sum_yz = 0.0, sum_zx = 0.0; - - float *xptr = x +ofs_x, *xlow = x, *xhigh = x + ofs_x*2; - float *yptr = y +ofs_y, *ylow = y, *yhigh = y + ofs_y*2; - float *zptr = z +ofs_z, *zlow = z, *zhigh = z + ofs_z*2; - - for (int tmp=0; tmp < num; tmp++) { - sum_xy += (*xptr) * ( (*yhigh) - (*ylow) ); - sum_yz += (*yptr) * ( (*zhigh) - (*zlow) ); - sum_zx += (*zptr) * ( (*xhigh) - (*xlow) ); - - xptr += ofs_x; - xlow += ofs_x; - xhigh += ofs_x; - - yptr += ofs_y; - ylow += ofs_y; - yhigh += ofs_y; - - zptr += ofs_z; - zlow += ofs_z; - zhigh += ofs_z; - } - out = aiVector3D(sum_yz,sum_zx,sum_xy); -} - - - // ------------------------------------------------------------------------------- /** Little helper function to calculate the quadratic difference * of two colours. diff --git a/code/TriangulateProcess.cpp b/code/TriangulateProcess.cpp index 9e2c70b01..8ec229e94 100644 --- a/code/TriangulateProcess.cpp +++ b/code/TriangulateProcess.cpp @@ -48,9 +48,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * Self-intersecting or non-planar polygons are not rejected, but * they're probably not triangulated correctly. * + * DEBUG SWITCHES - do not enable any of them in release builds: + * * AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING * - generates vertex colors to represent the face winding order. * the first vertex of a polygon becomes red, the last blue. + * AI_BUILD_TRIANGULATE_DEBUG_POLYS + * - dump all polygons and their triangulation sequences to + * a file */ #include "AssimpPCH.h" @@ -58,8 +63,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_TRIANGULATE_PROCESS #include "TriangulateProcess.h" #include "ProcessHelper.h" +#include "PolyTools.h" //#define AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING +//#define AI_BUILD_TRIANGULATE_DEBUG_POLYS + +#define POLY_GRID_Y 40 +#define POLY_GRID_X 70 +#define POLY_GRID_XPAD 20 +#define POLY_OUTPUT_FILE "assimp_polygons_debug.txt" + using namespace Assimp; // ------------------------------------------------------------------------------------------------ @@ -99,34 +112,6 @@ void TriangulateProcess::Execute( aiScene* pScene) else DefaultLogger::get()->debug("TriangulateProcess finished. There was nothing to be done."); } -// ------------------------------------------------------------------------------------------------ -// Test whether a point p2 is on the left side of the line formed by p0-p1 -inline bool OnLeftSideOfLine(const aiVector2D& p0, const aiVector2D& p1,const aiVector2D& p2) -{ - return ( (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y) ) > 0; -} - -// ------------------------------------------------------------------------------------------------ -// Test whether a point is inside a given triangle in R2 -inline bool PointInTriangle2D(const aiVector2D& p0, const aiVector2D& p1,const aiVector2D& p2, const aiVector2D& pp) -{ - // Point in triangle test using baryzentric coordinates - const aiVector2D v0 = p1 - p0; - const aiVector2D v1 = p2 - p0; - const aiVector2D v2 = pp - p0; - - float dot00 = v0 * v0; - float dot01 = v0 * v1; - float dot02 = v0 * v2; - float dot11 = v1 * v1; - float dot12 = v1 * v2; - - const float invDenom = 1 / (dot00 * dot11 - dot01 * dot01); - dot11 = (dot11 * dot02 - dot01 * dot12) * invDenom; - dot00 = (dot00 * dot12 - dot01 * dot02) * invDenom; - - return (dot11 > 0) && (dot00 > 0) && (dot11 + dot00 < 1); -} // ------------------------------------------------------------------------------------------------ // Triangulates the given mesh. @@ -150,17 +135,18 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) return false; } - // the output mesh will contain triangles, but no polys anymore - pMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; - pMesh->mPrimitiveTypes &= ~aiPrimitiveType_POLYGON; - // Find out how many output faces we'll get unsigned int numOut = 0, max_out = 0; + bool get_normals = true; for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { aiFace& face = pMesh->mFaces[a]; - if( face.mNumIndices <= 3) + if (face.mNumIndices <= 4) { + get_normals = false; + } + if( face.mNumIndices <= 3) { numOut++; + } else { numOut += face.mNumIndices-2; max_out = std::max(max_out,face.mNumIndices); @@ -171,12 +157,21 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) assert(numOut != pMesh->mNumFaces); aiVector3D* nor_out = NULL; - if (!pMesh->mNormals && pMesh->mPrimitiveTypes == aiPrimitiveType_POLYGON) { - nor_out = pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + + // if we don't have normals yet, but expect them to be a cheap side + // product of triangulation anyway, allocate storage for them. + if (!pMesh->mNormals && get_normals) { + // XXX need a mechanism to inform the GenVertexNormals process to treat these normals as preprocessed per-face normals + // nor_out = pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; } - aiFace* out = new aiFace[numOut], *curOut = out; - std::vector temp_verts(max_out+2); /* temporary storage for vertices */ + // the output mesh will contain triangles, but no polys anymore + pMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + pMesh->mPrimitiveTypes &= ~aiPrimitiveType_POLYGON; + + aiFace* out = new aiFace[numOut](), *curOut = out; + std::vector temp_verts3d(max_out+2); /* temporary storage for vertices */ + std::vector temp_verts(max_out+2); // Apply vertex colors to represent the face winding? #ifdef AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING @@ -188,6 +183,11 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) aiColor4D* clr = pMesh->mColors[0]; #endif + +#ifdef AI_BUILD_TRIANGULATE_DEBUG_POLYS + FILE* fout = fopen(POLY_OUTPUT_FILE,"a"); +#endif + // use boost::scoped_array to avoid slow std::vector specialiations boost::scoped_array done(new bool[max_out]); for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { @@ -205,12 +205,17 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) } #endif + aiFace* const last_face = curOut; + // if it's a simple point,line or triangle: just copy it if( face.mNumIndices <= 3) { aiFace& nface = *curOut++; nface.mNumIndices = face.mNumIndices; nface.mIndices = face.mIndices; + + face.mIndices = NULL; + continue; } // quadrilaterals can't have ears. trifanning will always work else if ( face.mNumIndices == 4) { @@ -225,6 +230,9 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) sface.mIndices[0] = face.mIndices[0]; sface.mIndices[1] = face.mIndices[2]; sface.mIndices[2] = face.mIndices[3]; + + face.mIndices = NULL; + continue; } else { @@ -241,12 +249,12 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // Collect all vertices of of the polygon. aiVector3D* verts = pMesh->mVertices; for (tmp = 0; tmp < max; ++tmp) { - temp_verts[tmp] = verts[idx[tmp]]; + temp_verts3d[tmp] = verts[idx[tmp]]; } // Get newell normal of the polygon. Store it for future use if it's a polygon-only mesh aiVector3D n; - NewellNormal<3,3,3>(n,max,&temp_verts.front().x,&temp_verts.front().y,&temp_verts.front().z); + NewellNormal<3,3,3>(n,max,&temp_verts3d.front().x,&temp_verts3d.front().y,&temp_verts3d.front().z); if (nor_out) { for (tmp = 0; tmp < max; ++tmp) nor_out[idx[tmp]] = n; @@ -281,6 +289,35 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) done[tmp] = false; } + +#ifdef AI_BUILD_TRIANGULATE_DEBUG_POLYS + // plot the plane onto which we mapped the polygon to a 2D ASCII pic + aiVector2D bmin,bmax; + ArrayBounds(&temp_verts[0],max,bmin,bmax); + + char grid[POLY_GRID_Y][POLY_GRID_X+POLY_GRID_XPAD]; + std::fill_n((char*)grid,POLY_GRID_Y*(POLY_GRID_X+POLY_GRID_XPAD),' '); + + for (size_t i =0; i < max; ++i) { + const aiVector2D& v = (temp_verts[i] - bmin) / (bmax-bmin); + const size_t x = static_cast(v.x*(POLY_GRID_X-1)), y = static_cast(v.y*(POLY_GRID_Y-1)); + char* loc = grid[y]+x; + if (grid[y][x] != ' ') { + for(;*loc != ' '; ++loc); + *loc++ = '_'; + } + *(loc+sprintf(loc,"%i",i)) = ' '; + } + + + for(size_t y = 0; y < POLY_GRID_Y; ++y) { + grid[y][POLY_GRID_X+POLY_GRID_XPAD-1] = '\0'; + fprintf(fout,"%s\n",grid[y]); + } + + fprintf(fout,"\ntriangulation sequence: "); +#endif + // // FIXME: currently this is the slow O(kn) variant with a worst case // complexity of O(n^2) (I think). Can be done in O(n). @@ -297,12 +334,12 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) break; } } - const aiVector2D* pnt1 = (const aiVector2D*)&temp_verts[ear], - *pnt0 = (const aiVector2D*)&temp_verts[prev], - *pnt2 = (const aiVector2D*)&temp_verts[next]; + const aiVector2D* pnt1 = &temp_verts[ear], + *pnt0 = &temp_verts[prev], + *pnt2 = &temp_verts[next]; // Must be a convex point. Assuming ccw winding, it must be on the right of the line between p-1 and p+1. - if (OnLeftSideOfLine (*pnt0,*pnt2,*pnt1)) { + if (OnLeftSideOfLine2D(*pnt0,*pnt2,*pnt1)) { continue; } @@ -310,7 +347,7 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) for ( tmp = 0; tmp < max; ++tmp) { // We need to compare the actual values because it's possible that multiple indexes in - // the polygon are refering to the same position. concave_polygon.obj is a sample + // the polygon are referring to the same position. concave_polygon.obj is a sample // // FIXME: Use 'epsiloned' comparisons instead? Due to numeric inaccuracies in // PointInTriangle() I'm guessing that it's actually possible to construct @@ -324,12 +361,12 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) if (tmp != max) { continue; } - + // this vertex is an ear break; } if (num_found == 2) { - + // Due to the 'two ear theorem', every simple polygon with more than three points must // have 2 'ears'. Here's definitely someting wrong ... but we don't give up yet. // @@ -337,6 +374,13 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // Instead we're continuting with the standard trifanning algorithm which we'd // use if we had only convex polygons. That's life. DefaultLogger::get()->error("Failed to triangulate polygon (no ear found). Probably not a simple polygon?"); + + +#ifdef AI_BUILD_TRIANGULATE_DEBUG_POLYS + fprintf(fout,"critical error here, no ear found! "); +#endif + num = 0; + break; curOut -= (max-num); /* undo all previous work */ for (tmp = 0; tmp < max-2; ++tmp) { @@ -346,9 +390,10 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) if (!nface.mIndices) nface.mIndices = new unsigned int[3]; - nface.mIndices[0] = idx[0]; - nface.mIndices[1] = idx[tmp+1]; - nface.mIndices[2] = idx[tmp+2]; + nface.mIndices[0] = 0; + nface.mIndices[1] = tmp+1; + nface.mIndices[2] = tmp+2; + } num = 0; break; @@ -362,9 +407,9 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) } // setup indices for the new triangle ... - nface.mIndices[0] = idx[prev]; - nface.mIndices[1] = idx[ear]; - nface.mIndices[2] = idx[next]; + nface.mIndices[0] = prev; + nface.mIndices[1] = ear; + nface.mIndices[2] = next; // exclude the ear from most further processing done[ear] = true; @@ -374,21 +419,67 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // We have three indices forming the last 'ear' remaining. Collect them. aiFace& nface = *curOut++; nface.mNumIndices = 3; - nface.mIndices = face.mIndices; + if (!nface.mIndices) { + nface.mIndices = new unsigned int[3]; + } for (tmp = 0; done[tmp]; ++tmp); - idx[0] = idx[tmp]; + nface.mIndices[0] = tmp; for (++tmp; done[tmp]; ++tmp); - idx[1] = idx[tmp]; + nface.mIndices[1] = tmp; for (++tmp; done[tmp]; ++tmp); - idx[2] = idx[tmp]; + nface.mIndices[2] = tmp; + } } - face.mIndices = NULL; /* prevent unintended deletion of our awesome results. would be a pity */ + +#ifdef AI_BUILD_TRIANGULATE_DEBUG_POLYS + + for(aiFace* f = last_face; f != curOut; ++f) { + unsigned int* i = f->mIndices; + fprintf(fout," (%i %i %i)",i[0],i[1],i[2]); + } + + fprintf(fout,"\n*********************************************************************\n"); + fflush(fout); + +#endif + + for(aiFace* f = last_face; f != curOut; ) { + unsigned int* i = f->mIndices; + + // drop dumb 0-area triangles + if (fabs(GetArea2D(temp_verts[i[0]],temp_verts[i[1]],temp_verts[i[2]])) < 1e-5f) { + DefaultLogger::get()->debug("Dropping triangle with area 0"); + --curOut; + + delete[] f->mIndices; + f->mIndices = NULL; + + for(aiFace* ff = f; ff != curOut; ++ff) { + ff->mNumIndices = (ff+1)->mNumIndices; + ff->mIndices = (ff+1)->mIndices; + (ff+1)->mIndices = NULL; + } + continue; + } + + i[0] = idx[i[0]]; + i[1] = idx[i[1]]; + i[2] = idx[i[2]]; + ++f; + } + + delete[] face.mIndices; + face.mIndices = NULL; } +#ifdef AI_BUILD_TRIANGULATE_DEBUG_POLYS + fclose(fout); +#endif + // kill the old faces delete [] pMesh->mFaces; diff --git a/scripts/IFCImporter/entitylist.txt b/scripts/IFCImporter/entitylist.txt index a212acd8a..9d073867a 100644 --- a/scripts/IFCImporter/entitylist.txt +++ b/scripts/IFCImporter/entitylist.txt @@ -8,6 +8,12 @@ # code generator. Also, the names of all used entities need to be present # in the source code for this to work. +IfcCartesianTransformationOperator3DnonUniform +IfcFeatureElementSubtraction +IfcRelVoidsElement +IfcOpeningElement +# IfcRelFillsElement +IfcPolygonalBoundedHalfSpace IfcPlane IfcHalfSpaceSolid IfcAxis1Placement diff --git a/workspaces/vc9/assimp.vcproj b/workspaces/vc9/assimp.vcproj index 844219bb9..0a9209d68 100644 --- a/workspaces/vc9/assimp.vcproj +++ b/workspaces/vc9/assimp.vcproj @@ -2518,6 +2518,10 @@ RelativePath="..\..\code\ParsingUtils.h" > + +