diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 38a6f2331..b99995969 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -351,13 +351,18 @@ SET(BLENDER_SRCS BlenderModifier.h BlenderModifier.cpp ) -SOURCE_GROUP( BLENDEr FILES ${BLENDER_SRCS}) +SOURCE_GROUP( BLENDER FILES ${BLENDER_SRCS}) SET(IFC_SRCS IFCLoader.cpp IFCLoader.h IFCReaderGen.cpp IFCReaderGen.h + IFCUtil.h + IFCUtil.cpp + IFCGeometry.cpp + IFCMaterial.cpp + IFCProfile.cpp STEPFile.h STEPFileReader.h STEPFileReader.cpp diff --git a/code/IFCGeometry.cpp b/code/IFCGeometry.cpp new file mode 100644 index 000000000..ca2940b5f --- /dev/null +++ b/code/IFCGeometry.cpp @@ -0,0 +1,1366 @@ +/* +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 IFCGeometry.cpp + * @brief Geometry conversion and synthesis for IFC + */ + +#include "AssimpPCH.h" + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER +#include "IFCUtil.h" +#include "PolyTools.h" +#include "ProcessHelper.h" + +namespace Assimp { + namespace IFC { + +// ------------------------------------------------------------------------------------------------ +bool ProcessPolyloop(const IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/) +{ + size_t cnt = 0; + BOOST_FOREACH(const IfcCartesianPoint& c, loop.Polygon) { + aiVector3D tmp; + ConvertCartesianPoint(tmp,c); + + meshout.verts.push_back(tmp); + ++cnt; + } + + meshout.vertcnt.push_back(cnt); + + // zero- or one- vertex polyloops simply ignored + if (meshout.vertcnt.back() > 1) { + return true; + } + + if (meshout.vertcnt.back()==1) { + meshout.vertcnt.pop_back(); + meshout.verts.pop_back(); + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +void ComputePolygonNormals(const TempMesh& meshout, std::vector& normals, bool normalize = true, size_t ofs = 0) +{ + size_t max_vcount = 0; + std::vector::const_iterator begin=meshout.vertcnt.begin()+ofs, end=meshout.vertcnt.end(), iit; + for(iit = begin; iit != end; ++iit) { + max_vcount = std::max(max_vcount,static_cast(*iit)); + } + + std::vector temp((max_vcount+2)*4); + normals.reserve( normals.size() + meshout.vertcnt.size()-ofs ); + + // `NewellNormal()` currently has a relatively strange interface and need to + // re-structure things a bit to meet them. + size_t vidx = std::accumulate(meshout.vertcnt.begin(),begin,0); + for(iit = begin; iit != end; vidx += *iit++) { + if (!*iit) { + normals.push_back(aiVector3D()); + continue; + } + 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]); + } + + if(normalize) { + BOOST_FOREACH(aiVector3D& n, normals) { + n.Normalize(); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Compute the normal of the last polygon in the given mesh +aiVector3D ComputePolygonNormal(const TempMesh& inmesh, bool normalize = true) +{ + size_t total = inmesh.vertcnt.back(), vidx = inmesh.verts.size() - total; + std::vector temp((total+2)*3); + for(size_t vofs = 0, cnt = 0; vofs < total; ++vofs) { + const aiVector3D& v = inmesh.verts[vidx+vofs]; + temp[cnt++] = v.x; + temp[cnt++] = v.y; + temp[cnt++] = v.z; + } + aiVector3D nor; + NewellNormal<3,3,3>(nor,total,&temp[0],&temp[1],&temp[2]); + return normalize ? nor.Normalize() : nor; +} + +// ------------------------------------------------------------------------------------------------ +void FixupFaceOrientation(TempMesh& result) +{ + const aiVector3D vavg = result.Center(); + + std::vector normals; + ComputePolygonNormals(result,normals); + + size_t c = 0, ofs = 0; + BOOST_FOREACH(unsigned int cnt, result.vertcnt) { + if (cnt>2){ + const aiVector3D& thisvert = result.verts[c]; + if (normals[ofs]*(thisvert-vavg) < 0) { + std::reverse(result.verts.begin()+c,result.verts.begin()+cnt+c); + } + } + c += cnt; + ++ofs; + } +} + +// ------------------------------------------------------------------------------------------------ +void RecursiveMergeBoundaries(TempMesh& final_result, const TempMesh& in, const TempMesh& boundary, std::vector& normals, const aiVector3D& nor_boundary) +{ + ai_assert(in.vertcnt.size() >= 1); + ai_assert(boundary.vertcnt.size() == 1); + std::vector::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::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 + const size_t cnt = boundary.verts.size() + *best_iit+2; + out.verts.reserve(cnt); + + 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); + } + } + out.vertcnt.push_back(cnt); + ai_assert(out.verts.size() == cnt); + + if (in.vertcnt.size()-std::count(begin,end,0) > 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_vidx_start,remaining.verts.begin()+best_vidx_start+*best_iit); + + normals.erase(normals.begin() + dist); + RecursiveMergeBoundaries(temp,remaining,out,normals,nor_boundary); + + final_result.Append(temp); + } + else final_result.Append(out); +} + +// ------------------------------------------------------------------------------------------------ +void MergePolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = -1) +{ + // standard case - only one boundary, just copy it to the result vector + if (inmesh.vertcnt.size() <= 1) { + result.Append(inmesh); + return; + } + + result.vertcnt.reserve(inmesh.vertcnt.size()+result.vertcnt.size()); + + // XXX get rid of the extra copy if possible + TempMesh meshout = inmesh; + + // 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"); + std::vector::iterator outer_polygon = meshout.vertcnt.end(), begin=meshout.vertcnt.begin(), end=outer_polygon, iit; + + // 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; + + // do not normalize 'normals', we need the original length for computing the polygon area + std::vector normals; + ComputePolygonNormals(meshout,normals,false); + + // see if one of the polygons is a IfcFaceOuterBound (in which case `master_bounds` is its index). + // sadly we can't rely on it, the docs say 'At most one of the bounds shall be of the type IfcFaceOuterBound' + float area_outer_polygon = 1e-10f; + if (master_bounds != (size_t)-1) { + outer_polygon = begin + master_bounds; + outer_polygon_start = std::accumulate(begin,outer_polygon,0); + area_outer_polygon = normals[master_bounds].SquareLength(); + } + else { + 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.SquareLength(); + if (area > area_outer_polygon) { + area_outer_polygon = area; + outer_polygon = iit; + outer_polygon_start = vidx; + } + } + } + + ai_assert(outer_polygon != meshout.vertcnt.end()); + std::vector& in = meshout.verts; + + // skip over extremely small boundaries - this is a workaround to fix cases + // in which the number of holes is so extremely large that the + // triangulation code fails. +#define IFC_VERTICAL_HOLE_SIZE_TRESHOLD 0.000001f + size_t vidx = 0, removed = 0, index = 0; + const float treshold = area_outer_polygon * IFC_VERTICAL_HOLE_SIZE_TRESHOLD; + for(iit = begin; iit != end ;++index) { + const float sqlen = normals[index].SquareLength(); + if (sqlen < treshold) { + std::vector::iterator inbase = in.begin()+vidx; + in.erase(inbase,inbase+*iit); + + outer_polygon_start -= outer_polygon_start>vidx ? *iit : 0; + *iit++ = 0; + ++removed; + + IFCImporter::LogDebug("skip small hole below treshold"); + } + else { + normals[index] /= sqrt(sqlen); + vidx += *iit++; + } + } + + // see if one or more of the hole has a face that lies directly on an outer bound. + // this happens for doors, for example. + vidx = 0; + for(iit = begin; ; vidx += *iit++) { +next_loop: + if (iit == end) { + break; + } + if (iit == outer_polygon) { + continue; + } + + for(size_t vofs = 0; vofs < *iit; ++vofs) { + if (!*iit) { + continue; + } + const size_t next = (vofs+1)%*iit; + const aiVector3D& v = in[vidx+vofs], &vnext = in[vidx+next],&vd = (vnext-v).Normalize(); + + for(size_t outer = 0; outer < *outer_polygon; ++outer) { + const aiVector3D& o = in[outer_polygon_start+outer], &onext = in[outer_polygon_start+(outer+1)%*outer_polygon], &od = (onext-o).Normalize(); + + if (fabs(vd * od) > 1.f-1e-6f && (onext-v).Normalize() * vd > 1.f-1e-6f && (onext-v)*(o-v) < 0) { + IFCImporter::LogDebug("got an inner hole that lies partly on the outer polygonal boundary, merging them to a single contour"); + + // in between outer and outer+1 insert all vertices of this loop, then drop the original altogether. + std::vector tmp(*iit); + + const size_t start = (v-o).SquareLength() > (vnext-o).SquareLength() ? vofs : next; + std::vector::iterator inbase = in.begin()+vidx, it = std::copy(inbase+start, inbase+*iit,tmp.begin()); + std::copy(inbase, inbase+start,it); + std::reverse(tmp.begin(),tmp.end()); + + in.insert(in.begin()+outer_polygon_start+(outer+1)%*outer_polygon,tmp.begin(),tmp.end()); + vidx += outer_polygon_startvidx ? *iit : 0; + + *outer_polygon += tmp.size(); + *iit++ = 0; + ++removed; + goto next_loop; + } + } + } + } + + if ( meshout.vertcnt.size() - removed <= 1) { + result.Append(meshout); + return; + } + + // extract the outer boundary and move it to a separate mesh + TempMesh boundary; + boundary.vertcnt.resize(1,*outer_polygon); + boundary.verts.resize(*outer_polygon); + + std::vector::iterator b = in.begin()+outer_polygon_start; + std::copy(b,b+*outer_polygon,boundary.verts.begin()); + in.erase(b,b+*outer_polygon); + + std::vector::iterator norit = normals.begin()+std::distance(meshout.vertcnt.begin(),outer_polygon); + const aiVector3D nor_boundary = *norit; + normals.erase(norit); + meshout.vertcnt.erase(outer_polygon); + + // 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 IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv) +{ + BOOST_FOREACH(const IfcFace& face, fset.CfsFaces) { + + // size_t ob = -1, cnt = 0; + TempMesh meshout; + BOOST_FOREACH(const IfcFaceBound& bound, face.Bounds) { + + // XXX implement proper merging for polygonal loops + if(const IfcPolyLoop* const polyloop = bound.Bound->ToPtr()) { + if(ProcessPolyloop(*polyloop, meshout,conv)) { + + //if(bound.ToPtr()) { + // ob = cnt; + //} + //++cnt; + + } + } + else { + IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is " + bound.Bound->GetClassName()); + continue; + } + + /*if(!IsTrue(bound.Orientation)) { + size_t c = 0; + BOOST_FOREACH(unsigned int& c, meshout.vertcnt) { + std::reverse(result.verts.begin() + cnt,result.verts.begin() + cnt + c); + cnt += c; + } + }*/ + + } + MergePolygonBoundaries(result,meshout); + } +} + + + + +// ------------------------------------------------------------------------------------------------ +void ProcessRevolvedAreaSolid(const IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv) +{ + TempMesh meshout; + + // first read the profile description + if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.verts.size()<=1) { + return; + } + + aiVector3D axis, pos; + ConvertAxisPlacement(axis,pos,solid.Axis); + + aiMatrix4x4 tb0,tb1; + aiMatrix4x4::Translation(pos,tb0); + aiMatrix4x4::Translation(-pos,tb1); + + const std::vector& in = meshout.verts; + const size_t size=in.size(); + + bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2; + const float max_angle = solid.Angle*conv.angle_scale; + if(fabs(max_angle) < 1e-3) { + if(has_area) { + result = meshout; + } + return; + } + + const unsigned int cnt_segments = std::max(2u,static_cast(16 * fabs(max_angle)/AI_MATH_HALF_PI_F)); + const float delta = max_angle/cnt_segments; + + has_area = has_area && fabs(max_angle) < AI_MATH_TWO_PI_F*0.99; + + result.verts.reserve(size*((cnt_segments+1)*4+(has_area?2:0))); + result.vertcnt.reserve(size*cnt_segments+2); + + aiMatrix4x4 rot; + rot = tb0 * aiMatrix4x4::Rotation(delta,axis,rot) * tb1; + + size_t base = 0; + std::vector& out = result.verts; + + // dummy data to simplify later processing + for(size_t i = 0; i < size; ++i) { + out.insert(out.end(),4,in[i]); + } + + for(unsigned int seg = 0; seg < cnt_segments; ++seg) { + for(size_t i = 0; i < size; ++i) { + const size_t next = (i+1)%size; + + result.vertcnt.push_back(4); + const aiVector3D& base_0 = out[base+i*4+3],base_1 = out[base+next*4+3]; + + out.push_back(base_0); + out.push_back(base_1); + out.push_back(rot*base_1); + out.push_back(rot*base_0); + } + base += size*4; + } + + out.erase(out.begin(),out.begin()+size*4); + + 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. + base -= size*8; + for(size_t i = size; i--; ) { + out.push_back(out[base+i*4+3]); + } + for(size_t i = 0; i < size; ++i ) { + out.push_back(out[i*4]); + } + result.vertcnt.push_back(size); + result.vertcnt.push_back(size); + } + + aiMatrix4x4 trafo; + ConvertAxisPlacement(trafo, solid.Position); + + result.Transform(trafo); + IFCImporter::LogDebug("generate mesh procedurally by radial extrusion (IfcRevolvedAreaSolid)"); +} + + +// ------------------------------------------------------------------------------------------------ +bool TryAddOpenings(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-1]; + const aiVector3D nor = ComputePolygonNormal(curmesh); ; + + bool got_openings = false; + TempMesh res; + + size_t c = 0; + BOOST_FOREACH(const TempOpening& t,openings) { + const aiVector3D& outernor = nors[c++]; + const float dot = nor * outernor; + if (fabs(dot)<1.f-1e-6f) { + 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; + + // project va[i] onto the plane formed by the current polygon [given by (any_point,nor)] + for(size_t i = 0; i < va.size(); ++i) { + const aiVector3D& v = va[i]; + out.push_back(v-(nor*(v-any_point))*nor); + } + + + curmesh.vertcnt.push_back(va.size()); + + res.Clear(); + 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; +}; + +// ------------------------------------------------------------------------------------------------ +struct XYSorter { + + // sort first by X coordinates, then by Y coordinates + bool operator () (const aiVector2D&a, const aiVector2D& b) const { + if (a.x == b.x) { + return a.y < b.y; + } + return a.x < b.x; + } +}; + +// ------------------------------------------------------------------------------------------------ +struct ProjectionInfo { + unsigned int ac, bc; + aiVector3D p,u,v; +}; + +typedef std::pair< aiVector2D, aiVector2D > BoundingBox; +typedef std::map XYSortedField; + +// ------------------------------------------------------------------------------------------------ +aiVector2D ProjectPositionVectorOntoPlane(const aiVector3D& x, const ProjectionInfo& proj) +{ + const aiVector3D xx = x-proj.p; + return aiVector2D(xx[proj.ac]/proj.u[proj.ac],xx[proj.bc]/proj.v[proj.bc]); +} + +// ------------------------------------------------------------------------------------------------ +void QuadrifyPart(const aiVector2D& pmin, const aiVector2D& pmax, XYSortedField& field, const std::vector< BoundingBox >& bbs, + std::vector& out) +{ + if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) { + return; + } + + float xs = 1e10, xe = 1e10; + bool found = false; + + // Search along the x-axis until we find an opening + XYSortedField::iterator start = field.begin(); + for(; start != field.end(); ++start) { + const BoundingBox& bb = bbs[(*start).second]; + if (bb.second.x > pmin.x && bb.first.x < pmax.x && bb.second.y > pmin.y && bb.first.y < pmax.y) { + xs = bb.first.x; + xe = bb.second.x; + found = true; + break; + } + } + xs = std::max(pmin.x,xs); + xe = std::min(pmax.x,xe); + + if (!found) { + // the rectangle [pmin,pend] is opaque, fill it + out.push_back(pmin); + out.push_back(aiVector2D(pmin.x,pmax.y)); + out.push_back(pmax); + out.push_back(aiVector2D(pmax.x,pmin.y)); + return; + } + + if (xs - pmin.x) { + out.push_back(pmin); + out.push_back(aiVector2D(pmin.x,pmax.y)); + out.push_back(aiVector2D(xs,pmax.y)); + out.push_back(aiVector2D(xs,pmin.y)); + } + + // search along the y-axis for all openings that overlap xs and our element + float ylast = pmin.y; + found = false; + for(; start != field.end(); ++start) { + const BoundingBox& bb = bbs[(*start).second]; + + if (bb.second.y > ylast && bb.first.y < pmax.y) { + + found = true; + const float ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y); + if (ys - ylast) { + // Divide et impera! + QuadrifyPart( aiVector2D(xs,ylast), aiVector2D(xe,ys) ,field,bbs,out); + } + + // the following are the window vertices + + /*wnd.push_back(aiVector2D(xs,ys)); + wnd.push_back(aiVector2D(xs,ye)); + wnd.push_back(aiVector2D(xe,ye)); + wnd.push_back(aiVector2D(xe,ys));*/ + ylast = ye; + } + + if (bb.first.x > xs) { + break; + } + } + if (!found) { + // the rectangle [pmin,pend] is opaque, fill it + out.push_back(aiVector2D(xs,pmin.y)); + out.push_back(aiVector2D(xs,pmax.y)); + out.push_back(aiVector2D(xe,pmax.y)); + out.push_back(aiVector2D(xe,pmin.y)); + return; + } + if (ylast < pmax.y) { + // Divide et impera! + QuadrifyPart( aiVector2D(xs,ylast), aiVector2D(xe,pmax.y) ,field,bbs,out); + } + + // Divide et impera! - now for the whole rest + if (pmax.x-xe) { + QuadrifyPart(aiVector2D(xe,pmin.y), pmax ,field,bbs,out); + } +} + +// ------------------------------------------------------------------------------------------------ +enum Intersect { + Intersect_No, + Intersect_LiesOnPlane, + Intersect_Yes +}; + +// ------------------------------------------------------------------------------------------------ +Intersect IntersectSegmentPlane(const aiVector3D& p,const aiVector3D& n, const aiVector3D& e0, const aiVector3D& e1, aiVector3D& out) +{ + const aiVector3D pdelta = e0 - p, seg = e1-e0; + const float dotOne = n*seg, dotTwo = -(n*pdelta); + + if (fabs(dotOne) < 1e-6) { + return fabs(dotTwo) < 1e-6f ? Intersect_LiesOnPlane : Intersect_No; + } + + const float t = dotTwo/dotOne; + // t must be in [0..1] if the intersection point is within the given segment + if (t > 1.f || t < 0.f) { + return Intersect_No; + } + out = e0+t*seg; + return Intersect_Yes; +} + + + +// ------------------------------------------------------------------------------------------------ +aiVector3D Unproject(const aiVector2D& vproj, const ProjectionInfo& proj) +{ + return vproj.x*proj.u + vproj.y*proj.v + proj.p; +} + +// ------------------------------------------------------------------------------------------------ +void InsertWindowContours(const std::vector< BoundingBox >& bbs,const std::vector< std::vector >& contours,const ProjectionInfo& proj, TempMesh& curmesh) +{ + ai_assert(contours.size() == bbs.size()); + + // fix windows - we need to insert the real, polygonal shapes into the quadratic holes that we have now + for(size_t i = 0; i < contours.size();++i) { + const BoundingBox& bb = bbs[i]; + const std::vector& contour = contours[i]; + + // check if we need to do it at all - many windows just fit perfectly into their quadratic holes, + // i.e. their contours *are* already their bounding boxes. + if (contour.size() == 4) { + std::set verts; + for(size_t n = 0; n < 4; ++n) { + verts.insert(contour[n]); + } + const std::set::const_iterator end = verts.end(); + if (verts.find(bb.first)!=end && verts.find(bb.second)!=end + && verts.find(aiVector2D(bb.first.x,bb.second.y))!=end + && verts.find(aiVector2D(bb.second.x,bb.first.y))!=end + ) { + continue; + } + } + + const float epsilon = (bb.first-bb.second).Length()/1000.f; + + // walk through all contour points and find those that lie on the BB corner + size_t last_hit = -1, very_first_hit = -1; + aiVector2D edge; + for(size_t n = 0, e=0, size = contour.size();; n=(n+1)%size, ++e) { + + // sanity checking + if (e == size*2) { + IFCImporter::LogError("encountered unexpected topology while generating window contour"); + break; + } + + const aiVector2D& v = contour[n]; + + bool hit = false; + if (fabs(v.x-bb.first.x) n ? size-(last_hit-n) : n-last_hit; + for(size_t a = last_hit, e = 0; e <= cnt; a=(a+1)%size, ++e) { + curmesh.verts.push_back(Unproject(contour[a],proj)); + } + + if (edge != contour[last_hit] && edge != contour[n]) { + curmesh.verts.push_back(Unproject(edge,proj)); + } + else if (cnt == 1) { + // avoid degenerate polygons (also known as lines or points) + curmesh.verts.erase(curmesh.verts.begin()+old,curmesh.verts.end()); + } + + if (const size_t d = curmesh.verts.size()-old) { + curmesh.vertcnt.push_back(d); + std::reverse(curmesh.verts.rbegin(),curmesh.verts.rbegin()+d); + } + if (n == very_first_hit) { + break; + } + } + else { + very_first_hit = n; + } + + last_hit = n; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +bool TryAddOpenings_Quadrulate(const std::vector& openings,const std::vector& nors, TempMesh& curmesh) +{ + std::vector& out = curmesh.verts; + + // Try to derive a solid base plane within the current surface for use as + // working coordinate system. + aiVector3D vmin,vmax; + ArrayBounds(&out[0],out.size(),vmin,vmax); + + const size_t s = out.size(); + + const aiVector3D any_point = out[s-4]; + const aiVector3D nor = ((out[s-3]-any_point)^(out[s-2]-any_point)).Normalize(); + + const aiVector3D diag = vmax-vmin; + const float ax = fabs(nor.x); + const float ay = fabs(nor.y); + const float az = fabs(nor.z); + + unsigned int ac = 0, bc = 1; /* no z coord. -> projection to xy */ + if (ax > ay) { + if (ax > az) { /* no x coord. -> projection to yz */ + ac = 1; bc = 2; + } + } + else if (ay > az) { /* no y coord. -> projection to zy */ + ac = 2; bc = 0; + } + + ProjectionInfo proj; + proj.u = proj.v = diag; + proj.u[bc]=0; + proj.v[ac]=0; + proj.ac = ac; + proj.bc = bc; + proj.p = vmin; + + // project all points into the coordinate system defined by the p+sv*tu plane + // and compute bounding boxes for them + std::vector< BoundingBox > bbs; + XYSortedField field; + + std::vector contour_flat; + contour_flat.reserve(out.size()); + BOOST_FOREACH(const aiVector3D& x, out) { + contour_flat.push_back(ProjectPositionVectorOntoPlane(x,proj)); + } + + std::vector< std::vector > contours; + + size_t c = 0; + BOOST_FOREACH(const TempOpening& t,openings) { + const aiVector3D& outernor = nors[c++]; + const float dot = nor * outernor; + if (fabs(dot)<1.f-1e-6f) { + continue; + } + + + // const aiVector3D diff = t.extrusionDir; + + const std::vector& va = t.profileMesh->verts; + if(va.size() <= 2) { + continue; + } + + aiVector2D vpmin,vpmax; + MinMaxChooser()(vpmin,vpmax); + + contours.push_back(std::vector()); + std::vector& contour = contours.back(); + + BOOST_FOREACH(const aiVector3D& x, t.profileMesh->verts) { + const aiVector2D& vproj = ProjectPositionVectorOntoPlane(x,proj); + + vpmin = std::min(vpmin,vproj); + vpmax = std::max(vpmax,vproj); + + contour.push_back(vproj); + } + + + if (field.find(vpmin) != field.end()) { + IFCImporter::LogWarn("constraint failure during generation of wall openings, results may be faulty"); + } + field[vpmin] = bbs.size(); + bbs.push_back(BoundingBox(vpmin,vpmax)); + } + + if (bbs.empty()) { + return false; + } + + + std::vector outflat; + outflat.reserve(openings.size()*4); + QuadrifyPart(aiVector2D(0.f,0.f),aiVector2D(1.f,1.f),field,bbs,outflat); + ai_assert(!(outflat.size() % 4)); + + //FixOuterBoundaries(outflat,contour_flat); + + // undo the projection, generate output quads + std::vector vold; + vold.reserve(outflat.size()); + std::swap(vold,curmesh.verts); + + std::vector iold; + iold.resize(outflat.size()/4,4); + std::swap(iold,curmesh.vertcnt); + + BOOST_FOREACH(const aiVector2D& vproj, outflat) { + out.push_back(Unproject(vproj,proj)); + } + + InsertWindowContours(bbs,contours,proj,curmesh); + return true; +} + + +// ------------------------------------------------------------------------------------------------ +void ProcessExtrudedAreaSolid(const IfcExtrudedAreaSolid& solid, TempMesh& result, ConversionData& conv) +{ + TempMesh meshout; + + // first read the profile description + if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.verts.size()<=1) { + return; + } + + aiVector3D dir; + ConvertDirection(dir,solid.ExtrudedDirection); + + dir *= solid.Depth; + + // assuming that `meshout.verts` is now a list of vertex points forming + // the underlying profile, extrude along the given axis, forming new + // triangles. + + std::vector& in = meshout.verts; + const size_t size=in.size(); + + const bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2; + if(solid.Depth < 1e-3) { + if(has_area) { + meshout = result; + } + return; + } + + 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); + BOOST_FOREACH(aiVector3D& v,in) { + v *= trafo; + } + + + aiVector3D min = in[0]; + dir *= aiMatrix3x3(trafo); + + std::vector nors; + + // 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()); + BOOST_FOREACH(TempOpening& t,*conv.apply_openings) { + TempMesh& bounds = *t.profileMesh.get(); + + 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()); + } + } + + TempMesh temp; + TempMesh& curmesh = conv.apply_openings ? temp : result; + std::vector& out = curmesh.verts; + + bool (* const gen_openings)(const std::vector&,const std::vector&, TempMesh&) = conv.settings.useCustomTriangulation + ? &TryAddOpenings_Quadrulate + : &TryAddOpenings; + + size_t sides_with_openings = 0; + for(size_t i = 0; i < size; ++i) { + const size_t next = (i+1)%size; + + 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]); + + if(conv.apply_openings) { + if(gen_openings(*conv.apply_openings,nors,temp)) { + ++sides_with_openings; + } + + result.Append(temp); + temp.Clear(); + } + } + + size_t sides_with_v_openings = 0; + if(has_area) { + + 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 && size > 2) { + // XXX here we are forced to use the un-triangulated version of TryAddOpening, with + // all the problems it causes. The reason is that vertical walls (ehm, floors) + // can have an arbitrary outer shape, so the usual approach of projecting + // the surface and all openings onto a flat quad and triangulating the quad + // fails. + if(TryAddOpenings(*conv.apply_openings,nors,temp)) { + ++sides_with_v_openings; + } + + result.Append(temp); + temp.Clear(); + } + } + } + + // add connection geometry to close the 'holes' for the openings + if(conv.apply_openings) { + //result.infacing.resize(result.verts.size()+); + BOOST_FOREACH(const TempOpening& t,*conv.apply_openings) { + const std::vector& in = t.profileMesh->verts; + std::vector& out = result.verts; + + 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]); + } + } + } + + 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"); + } + + IFCImporter::LogDebug("generate mesh procedurally by extrusion (IfcExtrudedAreaSolid)"); +} + + + +// ------------------------------------------------------------------------------------------------ +void ProcessSweptAreaSolid(const IfcSweptAreaSolid& swept, TempMesh& meshout, ConversionData& conv) +{ + if(const 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); + 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 IfcRevolvedAreaSolid* const rev = swept.ToPtr()) { + ProcessRevolvedAreaSolid(*rev,meshout,conv); + } + else { + IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is " + swept.GetClassName()); + } +} + +// ------------------------------------------------------------------------------------------------ +void ProcessBoolean(const IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv) +{ + if(const IfcBooleanClippingResult* const clip = boolean.ToPtr()) { + if(clip->Operator != "DIFFERENCE") { + IFCImporter::LogWarn("encountered unsupported boolean operator: " + (std::string)clip->Operator); + return; + } + + TempMesh meshout; + const IfcHalfSpaceSolid* const hs = clip->SecondOperand->ResolveSelectPtr(conv.db); + if(!hs) { + IFCImporter::LogError("expected IfcHalfSpaceSolid as second clipping operand"); + return; + } + + const IfcPlane* const plane = hs->BaseSurface->ToPtr(); + if(!plane) { + IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid"); + return; + } + + if(const IfcBooleanResult* const op0 = clip->FirstOperand->ResolveSelectPtr(conv.db)) { + ProcessBoolean(*op0,meshout,conv); + } + else if (const IfcSweptAreaSolid* const swept = clip->FirstOperand->ResolveSelectPtr(conv.db)) { + ProcessSweptAreaSolid(*swept,meshout,conv); + } + else { + IFCImporter::LogError("expected IfcSweptAreaSolid or IfcBooleanResult as first clipping operand"); + return; + } + + // extract plane base position vector and normal vector + aiVector3D p,n(0.f,0.f,1.f); + if (plane->Position->Axis) { + ConvertDirection(n,plane->Position->Axis.Get()); + } + ConvertCartesianPoint(p,plane->Position->Location); + + if(!IsTrue(hs->AgreementFlag)) { + n *= -1.f; + } + + // 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 begin=meshout.vertcnt.begin(), end=meshout.vertcnt.end(), iit; + + outvert.reserve(in.size()); + result.vertcnt.reserve(meshout.vertcnt.size()); + + unsigned int vidx = 0; + for(iit = begin; iit != end; vidx += *iit++) { + + unsigned int newcount = 0; + for(unsigned int i = 0; i < *iit; ++i) { + const aiVector3D& e0 = in[vidx+i], e1 = in[vidx+(i+1)%*iit]; + + // does the next segment intersect the plane? + aiVector3D isectpos; + const Intersect isect = IntersectSegmentPlane(p,n,e0,e1,isectpos); + if (isect == Intersect_No || isect == Intersect_LiesOnPlane) { + if ( (e0-p).Normalize()*n > 0 ) { + outvert.push_back(e0); + ++newcount; + } + } + else if (isect == Intersect_Yes) { + if ( (e0-p).Normalize()*n > 0 ) { + // e0 is on the right side, so keep it + outvert.push_back(e0); + outvert.push_back(isectpos); + newcount += 2; + } + else { + // e0 is on the wrong side, so drop it and keep e1 instead + outvert.push_back(isectpos); + ++newcount; + } + } + } + + if (!newcount) { + continue; + } + + aiVector3D vmin,vmax; + ArrayBounds(&*(outvert.end()-newcount),newcount,vmin,vmax); + + // filter our double points - those may happen if a point lies + // directly on the intersection line. However, due to float + // precision a bitwise comparison is not feasible to detect + // this case. + const float epsilon = (vmax-vmin).SquareLength() / 1e6f; + FuzzyVectorCompare fz(epsilon); + + std::vector::iterator e = std::unique( outvert.end()-newcount, outvert.end(), fz ); + if (e != outvert.end()) { + newcount -= static_cast(std::distance(e,outvert.end())); + outvert.erase(e,outvert.end()); + } + if (fz(*( outvert.end()-newcount),outvert.back())) { + outvert.pop_back(); + --newcount; + } + if(newcount > 2) { + result.vertcnt.push_back(newcount); + } + else while(newcount-->0)result.verts.pop_back(); + + } + IFCImporter::LogDebug("generating CSG geometry by plane clipping (IfcBooleanClippingResult)"); + } + else { + IFCImporter::LogWarn("skipping unknown IfcBooleanResult entity, type is " + boolean.GetClassName()); + } +} + + + +// ------------------------------------------------------------------------------------------------ +bool ProcessGeometricItem(const IfcRepresentationItem& geo, std::vector& mesh_indices, ConversionData& conv) +{ + TempMesh meshtmp; + if(const IfcShellBasedSurfaceModel* shellmod = geo.ToPtr()) { + BOOST_FOREACH(boost::shared_ptr shell,shellmod->SbsmBoundary) { + try { + const EXPRESS::ENTITY& e = shell->To(); + const IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To(); + + ProcessConnectedFaceSet(fs,meshtmp,conv); + } + catch(std::bad_cast&) { + IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet"); + } + } + } + else if(const IfcConnectedFaceSet* fset = geo.ToPtr()) { + ProcessConnectedFaceSet(*fset,meshtmp,conv); + } + else if(const IfcSweptAreaSolid* swept = geo.ToPtr()) { + ProcessSweptAreaSolid(*swept,meshtmp,conv); + } + else if(const IfcManifoldSolidBrep* brep = geo.ToPtr()) { + ProcessConnectedFaceSet(brep->Outer,meshtmp,conv); + } + else if(const IfcFaceBasedSurfaceModel* surf = geo.ToPtr()) { + BOOST_FOREACH(const IfcConnectedFaceSet& fc, surf->FbsmFaces) { + ProcessConnectedFaceSet(fc,meshtmp,conv); + } + } + else if(const IfcBooleanResult* boolean = geo.ToPtr()) { + ProcessBoolean(*boolean,meshtmp,conv); + } + else if(geo.ToPtr()) { + // silently skip over bounding boxes + return false; + } + else { + IFCImporter::LogWarn("skipping unknown IfcGeometricRepresentationItem entity, type is " + geo.GetClassName()); + return false; + } + + meshtmp.RemoveAdjacentDuplicates(); + FixupFaceOrientation(meshtmp); + + aiMesh* const mesh = meshtmp.ToMesh(); + if(mesh) { + mesh->mMaterialIndex = ProcessMaterials(geo,conv); + mesh_indices.push_back(conv.meshes.size()); + conv.meshes.push_back(mesh); + return true; + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +void AssignAddedMeshes(std::vector& mesh_indices,aiNode* nd,ConversionData& /*conv*/) +{ + if (!mesh_indices.empty()) { + + // make unique + std::sort(mesh_indices.begin(),mesh_indices.end()); + std::vector::iterator it_end = std::unique(mesh_indices.begin(),mesh_indices.end()); + + const size_t size = std::distance(mesh_indices.begin(),it_end); + + nd->mNumMeshes = size; + nd->mMeshes = new unsigned int[nd->mNumMeshes]; + for(unsigned int i = 0; i < nd->mNumMeshes; ++i) { + nd->mMeshes[i] = mesh_indices[i]; + } + } +} + +// ------------------------------------------------------------------------------------------------ +bool TryQueryMeshCache(const IfcRepresentationItem& item, std::vector& mesh_indices, ConversionData& conv) +{ + ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(&item); + if (it != conv.cached_meshes.end()) { + std::copy((*it).second.begin(),(*it).second.end(),std::back_inserter(mesh_indices)); + return true; + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +void PopulateMeshCache(const IfcRepresentationItem& item, const std::vector& mesh_indices, ConversionData& conv) +{ + conv.cached_meshes[&item] = mesh_indices; +} + +// ------------------------------------------------------------------------------------------------ +bool ProcessRepresentationItem(const IfcRepresentationItem& item, std::vector& mesh_indices, ConversionData& conv) +{ + if (!TryQueryMeshCache(item,mesh_indices,conv)) { + if(ProcessGeometricItem(item,mesh_indices,conv)) { + if(mesh_indices.size()) { + PopulateMeshCache(item,mesh_indices,conv); + } + } + else return false; + } + return true; +} + +} // ! IFC +} // ! Assimp + +#endif diff --git a/code/IFCLoader.cpp b/code/IFCLoader.cpp index 09c1b80ee..0296b5df9 100644 --- a/code/IFCLoader.cpp +++ b/code/IFCLoader.cpp @@ -38,7 +38,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file IFC.cpp +/** @file IFCLoad.cpp * @brief Implementation of the Industry Foundation Classes loader. */ #include "AssimpPCH.h" @@ -51,286 +51,47 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "IFCLoader.h" #include "STEPFileReader.h" -#include "IFCReaderGen.h" +#include "IFCUtil.h" #include "StreamReader.h" #include "MemoryIOWrapper.h" -#include "ProcessHelper.h" -#include "PolyTools.h" using namespace Assimp; using namespace Assimp::Formatter; namespace EXPRESS = STEP::EXPRESS; +using namespace Assimp::IFC; + template<> const std::string LogFunctions::log_prefix = "IFC: "; /* DO NOT REMOVE this comment block. The genentitylist.sh script - * just looks for names adhering to the IFC :: IfcSomething naming scheme + * just looks for names adhering to the IfcSomething naming scheme * and includes all matches in the whitelist for code-generation. Thus, * all entity classes that are only indirectly referenced need to be * mentioned explicitly. - IFC::IfcRepresentationMap - IFC::IfcProductRepresentation - IFC::IfcUnitAssignment - IFC::IfcClosedShell - IFC::IfcDoor + IfcRepresentationMap + IfcProductRepresentation + IfcUnitAssignment + IfcClosedShell + IfcDoor */ namespace { - // helper for std::for_each to delete all heap-allocated items in a container -template -struct delete_fun -{ - void operator()(T* del) { - delete del; - } -}; - -// ------------------------------------------------------------------------------------------------ -// 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; - - // ------------------------------------------------------------------------------ - 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) - : len_scale(1.0) - , angle_scale(1.0) - , db(db) - , proj(proj) - , out(out) - , settings(settings) - , apply_openings() - , collect_openings() - {} - - ~ConversionData() { - std::for_each(meshes.begin(),meshes.end(),delete_fun()); - std::for_each(materials.begin(),materials.end(),delete_fun()); - } - - float len_scale, angle_scale; - bool plane_angle_in_radians; - - const STEP::DB& db; - const IFC::IfcProject& proj; - aiScene* out; - - aiMatrix4x4 wcs; - std::vector meshes; - std::vector materials; - - typedef std::map > MeshCache; - 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; -}; - -// ------------------------------------------------------------------------------------------------ -struct FuzzyVectorCompare { - - FuzzyVectorCompare(float epsilon) : epsilon(epsilon) {} - bool operator()(const aiVector3D& a, const aiVector3D& b) { - return fabs((a-b).SquareLength()) < epsilon; - } - - const float epsilon; -}; - - -// ------------------------------------------------------------------------------------------------ -// Helper used during mesh construction. Aids at creating aiMesh'es out of relatively few polygons. -// ------------------------------------------------------------------------------------------------ -struct TempMesh -{ - std::vector verts; - std::vector vertcnt; - - // ------------------------------------------------------------------------------ - aiMesh* ToMesh() { - ai_assert(verts.size() == std::accumulate(vertcnt.begin(),vertcnt.end(),0)); - - if (verts.empty()) { - return NULL; - } - - std::auto_ptr mesh(new aiMesh()); - - // copy vertices - mesh->mNumVertices = static_cast(verts.size()); - mesh->mVertices = new aiVector3D[mesh->mNumVertices]; - std::copy(verts.begin(),verts.end(),mesh->mVertices); - - // and build up faces - mesh->mNumFaces = static_cast(vertcnt.size()); - mesh->mFaces = new aiFace[mesh->mNumFaces]; - - for(unsigned int i = 0,n=0, acc = 0; i < mesh->mNumFaces; ++n) { - aiFace& f = mesh->mFaces[i]; - if (!vertcnt[n]) { - --mesh->mNumFaces; - continue; - } - - f.mNumIndices = vertcnt[n]; - f.mIndices = new unsigned int[f.mNumIndices]; - for(unsigned int a = 0; a < f.mNumIndices; ++a) { - f.mIndices[a] = acc++; - } - - ++i; - } - - 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 Append(const TempMesh& other) { - - verts.insert(verts.end(),other.verts.begin(),other.verts.end()); - vertcnt.insert(vertcnt.end(),other.vertcnt.begin(),other.vertcnt.end()); - } - - // ------------------------------------------------------------------------------ - void RemoveAdjacentDuplicates() { - - bool drop = false; - std::vector::iterator base = verts.begin(); - BOOST_FOREACH(unsigned int& cnt, vertcnt) { - if (cnt < 2){ - base += cnt; - continue; - } - - aiVector3D vmin,vmax; - ArrayBounds(&*base, cnt ,vmin,vmax); - - - const float epsilon = (vmax-vmin).SquareLength() / 1e9f, dotepsilon = 1e-7; - - //// look for vertices that lie directly on the line between their predecessor and their - //// successor and replace them with either of them. - - //for(size_t i = 0; i < cnt; ++i) { - // aiVector3D& v1 = *(base+i), &v0 = *(base+(i?i-1:cnt-1)), &v2 = *(base+(i+1)%cnt); - // const aiVector3D& d0 = (v1-v0), &d1 = (v2-v1); - // const float l0 = d0.SquareLength(), l1 = d1.SquareLength(); - // if (!l0 || !l1) { - // continue; - // } - - // const float d = (d0/sqrt(l0))*(d1/sqrt(l1)); - // if ( d >= 1.f-dotepsilon ) { - // v1 = v0; - // } - // else if ( d0*d1 < -1.f+dotepsilon ) { - // v2 = v1; - // continue; - // } - //} - - // drop any identical, adjacent vertices. this pass will collect the dropouts - // of the previous pass as a side-effect. - FuzzyVectorCompare fz(epsilon); - std::vector::iterator end = base+cnt, e = std::unique( base, end, fz ); - if (e != end) { - cnt -= static_cast(std::distance(e, end)); - verts.erase(e,end); - drop = true; - } - - // check front and back vertices for this polygon - if (cnt > 1 && fz(*base,*(base+cnt-1))) { - verts.erase(base+ --cnt); - drop = true; - } - - // removing adjacent duplicates shouldn't erase everything :-) - ai_assert(cnt>0); - base += cnt; - } - if(drop) { - IFCImporter::LogDebug("removed duplicate vertices"); - } - } -}; - - -// ------------------------------------------------------------------------------ -void TempOpening::Transform(const aiMatrix4x4& mat) -{ - if(profileMesh) { - profileMesh->Transform(mat); - } - extrusionDir *= aiMatrix3x3(mat); -} - // forward declarations -float ConvertSIPrefix(const std::string& prefix); void SetUnits(ConversionData& conv); -void ConvertAxisPlacement(aiMatrix4x4& out, const IFC::IfcAxis2Placement& in, ConversionData& conv); void SetCoordinateSpace(ConversionData& conv); void ProcessSpatialStructures(ConversionData& conv); -aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el ,ConversionData& conv); -void ProcessProductRepresentation(const IFC::IfcProduct& el, aiNode* nd, ConversionData& conv); +aiNode* ProcessSpatialStructure(aiNode* parent, const IfcProduct& el ,ConversionData& conv); +void ProcessProductRepresentation(const 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 @@ -379,6 +140,8 @@ void IFCImporter::SetupProperties(const Importer* pImp) settings.skipSpaceRepresentations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS,true); settings.skipCurveRepresentations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_SKIP_CURVE_REPRESENTATIONS,true); settings.useCustomTriangulation = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_CUSTOM_TRIANGULATION,true); + + settings.skipAnnotations = true; } @@ -411,7 +174,7 @@ void IFCImporter::InternReadFile( const std::string& pFile, // obtain a copy of the machine-generated IFC scheme EXPRESS::ConversionSchema schema; - IFC::GetSchema(schema); + GetSchema(schema); // tell the reader which entity types to track with special care static const char* const types_to_track[] = { @@ -431,7 +194,7 @@ void IFCImporter::InternReadFile( const std::string& pFile, ThrowException("missing IfcProject entity"); } - ConversionData conv(*db,proj->To(),pScene,settings); + ConversionData conv(*db,proj->To(),pScene,settings); SetUnits(conv); SetCoordinateSpace(conv); ProcessSpatialStructures(conv); @@ -479,73 +242,11 @@ void IFCImporter::InternReadFile( const std::string& pFile, namespace { -// ------------------------------------------------------------------------------------------------ -bool IsTrue(const EXPRESS::BOOLEAN& in) -{ - return (std::string)in == "TRUE" || (std::string)in == "T"; -} // ------------------------------------------------------------------------------------------------ -float ConvertSIPrefix(const std::string& prefix) +void ConvertUnit(const IfcNamedUnit& unit,ConversionData& conv) { - if (prefix == "EXA") { - return 1e18f; - } - else if (prefix == "PETA") { - return 1e15f; - } - else if (prefix == "TERA") { - return 1e12f; - } - else if (prefix == "GIGA") { - return 1e9f; - } - else if (prefix == "MEGA") { - return 1e6f; - } - else if (prefix == "KILO") { - return 1e3f; - } - else if (prefix == "HECTO") { - return 1e2f; - } - else if (prefix == "DECA") { - return 1e-0f; - } - else if (prefix == "DECI") { - return 1e-1f; - } - else if (prefix == "CENTI") { - return 1e-2f; - } - else if (prefix == "MILLI") { - return 1e-3f; - } - else if (prefix == "MICRO") { - return 1e-6f; - } - else if (prefix == "NANO") { - return 1e-9f; - } - else if (prefix == "PICO") { - return 1e-12f; - } - else if (prefix == "FEMTO") { - return 1e-15f; - } - else if (prefix == "ATTO") { - return 1e-18f; - } - else { - IFCImporter::LogError("Unrecognized SI prefix: " + prefix); - return 1; - } -} - -// ------------------------------------------------------------------------------------------------ -void ConvertUnit(const IFC::IfcNamedUnit& unit,ConversionData& conv) -{ - if(const IFC::IfcSIUnit* const si = unit.ToPtr()) { + if(const IfcSIUnit* const si = unit.ToPtr()) { if(si->UnitType == "LENGTHUNIT") { conv.len_scale = si->Prefix ? ConvertSIPrefix(si->Prefix) : 1.f; @@ -557,7 +258,7 @@ void ConvertUnit(const IFC::IfcNamedUnit& unit,ConversionData& conv) } } } - else if(const IFC::IfcConversionBasedUnit* const convu = unit.ToPtr()) { + else if(const IfcConversionBasedUnit* const convu = unit.ToPtr()) { if(convu->UnitType == "PLANEANGLEUNIT") { try { @@ -576,9 +277,9 @@ void ConvertUnit(const IFC::IfcNamedUnit& unit,ConversionData& conv) void ConvertUnit(const EXPRESS::DataType& dt,ConversionData& conv) { try { - const EXPRESS::ENTITY& e = dt.To(); + const EXPRESS::ENTITY& e = dt.To(); - const IFC::IfcNamedUnit& unit = e.ResolveSelect(conv.db); + const IfcNamedUnit& unit = e.ResolveSelect(conv.db); if(unit.UnitType != "LENGTHUNIT" && unit.UnitType != "PLANEANGLEUNIT") { return; } @@ -600,149 +301,12 @@ void SetUnits(ConversionData& conv) } } -// ------------------------------------------------------------------------------------------------ -void ConvertColor(aiColor4D& out, const IFC::IfcColourRgb& in) -{ - out.r = in.Red; - out.g = in.Green; - out.b = in.Blue; - out.a = 1.f; -} - -// ------------------------------------------------------------------------------------------------ -void ConvertColor(aiColor4D& out, const IFC::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base) -{ - if (const EXPRESS::REAL* const r = in.ToPtr()) { - out.r = out.g = out.b = *r; - if(base) { - out.r *= base->r; - out.g *= base->g; - out.b *= base->b; - out.a = base->a; - } - else out.a = 1.0; - } - else if (const IFC::IfcColourRgb* const rgb = in.ResolveSelectPtr(conv.db)) { - ConvertColor(out,*rgb); - } - else { - IFCImporter::LogWarn("skipping unknown IfcColourOrFactor entity"); - } -} - -// ------------------------------------------------------------------------------------------------ -void ConvertCartesianPoint(aiVector3D& out, const IFC::IfcCartesianPoint& in) -{ - out = aiVector3D(); - for(size_t i = 0; i < in.Coordinates.size(); ++i) { - out[i] = in.Coordinates[i]; - } -} - -// ------------------------------------------------------------------------------------------------ -void ConvertDirection(aiVector3D& out, const IFC::IfcDirection& in) -{ - out = aiVector3D(); - for(size_t i = 0; i < in.DirectionRatios.size(); ++i) { - out[i] = in.DirectionRatios[i]; - } - const float len = out.Length(); - if (len<1e-6) { - IFCImporter::LogWarn("direction vector too small, normalizing would result in a division by zero"); - return; - } - out /= len; -} - -// ------------------------------------------------------------------------------------------------ -void AssignMatrixAxes(aiMatrix4x4& out, const aiVector3D& x, const aiVector3D& y, const aiVector3D& z) -{ - out.a1 = x.x; - out.b1 = x.y; - out.c1 = x.z; - - out.a2 = y.x; - out.b2 = y.y; - out.c2 = y.z; - - out.a3 = z.x; - out.b3 = z.y; - out.c3 = z.z; -} - -// ------------------------------------------------------------------------------------------------ -void ConvertAxisPlacement(aiMatrix4x4& out, const IFC::IfcAxis2Placement3D& in, ConversionData& /*conv*/) -{ - aiVector3D loc; - ConvertCartesianPoint(loc,in.Location); - - aiVector3D z(0.f,0.f,1.f),r(1.f,0.f,0.f),x; - - if (in.Axis) { - ConvertDirection(z,*in.Axis.Get()); - } - if (in.RefDirection) { - ConvertDirection(r,*in.RefDirection.Get()); - } - - aiVector3D v = r.Normalize(); - aiVector3D tmpx = z * (v*z); - - x = (v-tmpx).Normalize(); - aiVector3D y = (z^x); - - aiMatrix4x4::Translation(loc,out); - AssignMatrixAxes(out,x,y,z); -} - -// ------------------------------------------------------------------------------------------------ -void ConvertAxisPlacement(aiMatrix4x4& out, const IFC::IfcAxis2Placement2D& in, ConversionData& /*conv*/) -{ - aiVector3D loc; - ConvertCartesianPoint(loc,in.Location); - - aiVector3D x(1.f,0.f,0.f); - if (in.RefDirection) { - ConvertDirection(x,*in.RefDirection.Get()); - } - - const aiVector3D y = aiVector3D(x.y,-x.x,0.f); - - aiMatrix4x4::Translation(loc,out); - AssignMatrixAxes(out,x,y,aiVector3D(0.f,0.f,1.f)); -} - -// ------------------------------------------------------------------------------------------------ -void ConvertAxisPlacement(aiVector3D& axis, aiVector3D& pos, const IFC::IfcAxis1Placement& in, ConversionData& /*conv*/) -{ - ConvertCartesianPoint(pos,in.Location); - if (in.Axis) { - ConvertDirection(axis,in.Axis.Get()); - } - else { - axis = aiVector3D(0.f,0.f,1.f); - } -} - -// ------------------------------------------------------------------------------------------------ -void ConvertAxisPlacement(aiMatrix4x4& out, const IFC::IfcAxis2Placement& in, ConversionData& conv) -{ - if(const IFC::IfcAxis2Placement3D* pl3 = in.ResolveSelectPtr(conv.db)) { - ConvertAxisPlacement(out,*pl3,conv); - } - else if(const IFC::IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr(conv.db)) { - ConvertAxisPlacement(out,*pl2,conv); - } - else { - IFCImporter::LogWarn("skipping unknown IfcAxis2Placement entity"); - } -} // ------------------------------------------------------------------------------------------------ void SetCoordinateSpace(ConversionData& conv) { - const IFC::IfcRepresentationContext* fav = NULL; - BOOST_FOREACH(const IFC::IfcRepresentationContext& v, conv.proj.RepresentationContexts) { + const IfcRepresentationContext* fav = NULL; + BOOST_FOREACH(const IfcRepresentationContext& v, conv.proj.RepresentationContexts) { fav = &v; // Model should be the most suitable type of context, hence ignore the others if (v.ContextType && v.ContextType.Get() == "Model") { @@ -750,1617 +314,18 @@ void SetCoordinateSpace(ConversionData& conv) } } if (fav) { - if(const IFC::IfcGeometricRepresentationContext* const geo = fav->ToPtr()) { + if(const IfcGeometricRepresentationContext* const geo = fav->ToPtr()) { ConvertAxisPlacement(conv.wcs, *geo->WorldCoordinateSystem, conv); IFCImporter::LogDebug("got world coordinate system"); } } } + // ------------------------------------------------------------------------------------------------ -void ConvertTransformOperator(aiMatrix4x4& out, const IFC::IfcCartesianTransformationOperator& op) +void ResolveObjectPlacement(aiMatrix4x4& m, const IfcObjectPlacement& place, ConversionData& conv) { - aiVector3D loc; - ConvertCartesianPoint(loc,op.LocalOrigin); - - aiVector3D x(1.f,0.f,0.f),y(0.f,1.f,0.f),z(0.f,0.f,1.f); - if (op.Axis1) { - ConvertDirection(x,*op.Axis1.Get()); - } - if (op.Axis2) { - ConvertDirection(y,*op.Axis2.Get()); - } - if (const IFC::IfcCartesianTransformationOperator3D* op2 = op.ToPtr()) { - if(op2->Axis3) { - ConvertDirection(z,*op2->Axis3.Get()); - } - } - - aiMatrix4x4 locm; - aiMatrix4x4::Translation(loc,locm); - AssignMatrixAxes(out,x,y,z); - - - 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(vscale,s); - - out = locm * out * s; -} - -// ------------------------------------------------------------------------------------------------ -bool ProcessPolyloop(const IFC::IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/) -{ - size_t cnt = 0; - BOOST_FOREACH(const IFC::IfcCartesianPoint& c, loop.Polygon) { - aiVector3D tmp; - ConvertCartesianPoint(tmp,c); - - meshout.verts.push_back(tmp); - ++cnt; - } - - meshout.vertcnt.push_back(cnt); - - // zero- or one- vertex polyloops simply ignored - if (meshout.vertcnt.back() > 1) { - return true; - } - - if (meshout.vertcnt.back()==1) { - meshout.vertcnt.pop_back(); - meshout.verts.pop_back(); - } - return false; -} - -// ------------------------------------------------------------------------------------------------ -void ComputePolygonNormals(const TempMesh& meshout, std::vector& normals, bool normalize = true, size_t ofs = 0) -{ - size_t max_vcount = 0; - std::vector::const_iterator begin=meshout.vertcnt.begin()+ofs, end=meshout.vertcnt.end(), iit; - for(iit = begin; iit != end; ++iit) { - max_vcount = std::max(max_vcount,static_cast(*iit)); - } - - std::vector temp((max_vcount+2)*4); - normals.reserve( normals.size() + meshout.vertcnt.size()-ofs ); - - // `NewellNormal()` currently has a relatively strange interface and need to - // re-structure things a bit to meet them. - size_t vidx = std::accumulate(meshout.vertcnt.begin(),begin,0); - for(iit = begin; iit != end; vidx += *iit++) { - if (!*iit) { - normals.push_back(aiVector3D()); - continue; - } - 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]); - } - - if(normalize) { - BOOST_FOREACH(aiVector3D& n, normals) { - n.Normalize(); - } - } -} - -// ------------------------------------------------------------------------------------------------ -// Compute the normal of the last polygon in the given mesh -aiVector3D ComputePolygonNormal(const TempMesh& inmesh, bool normalize = true) -{ - size_t total = inmesh.vertcnt.back(), vidx = inmesh.verts.size() - total; - std::vector temp((total+2)*3); - for(size_t vofs = 0, cnt = 0; vofs < total; ++vofs) { - const aiVector3D& v = inmesh.verts[vidx+vofs]; - temp[cnt++] = v.x; - temp[cnt++] = v.y; - temp[cnt++] = v.z; - } - aiVector3D nor; - NewellNormal<3,3,3>(nor,total,&temp[0],&temp[1],&temp[2]); - return normalize ? nor.Normalize() : nor; -} - -// ------------------------------------------------------------------------------------------------ -void FixupFaceOrientation(TempMesh& result) -{ - const aiVector3D vavg = result.Center(); - - std::vector normals; - ComputePolygonNormals(result,normals); - - size_t c = 0, ofs = 0; - BOOST_FOREACH(unsigned int cnt, result.vertcnt) { - if (cnt>2){ - const aiVector3D& thisvert = result.verts[c]; - if (normals[ofs]*(thisvert-vavg) < 0) { - std::reverse(result.verts.begin()+c,result.verts.begin()+cnt+c); - } - } - c += cnt; - ++ofs; - } -} - -// ------------------------------------------------------------------------------------------------ -void RecursiveMergeBoundaries(TempMesh& final_result, const TempMesh& in, const TempMesh& boundary, std::vector& normals, const aiVector3D& nor_boundary) -{ - ai_assert(in.vertcnt.size() >= 1); - ai_assert(boundary.vertcnt.size() == 1); - std::vector::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::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 - const size_t cnt = boundary.verts.size() + *best_iit+2; - out.verts.reserve(cnt); - - 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); - } - } - out.vertcnt.push_back(cnt); - ai_assert(out.verts.size() == cnt); - - if (in.vertcnt.size()-std::count(begin,end,0) > 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_vidx_start,remaining.verts.begin()+best_vidx_start+*best_iit); - - normals.erase(normals.begin() + dist); - RecursiveMergeBoundaries(temp,remaining,out,normals,nor_boundary); - - final_result.Append(temp); - } - else final_result.Append(out); -} - -// ------------------------------------------------------------------------------------------------ -void MergePolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = -1) -{ - // standard case - only one boundary, just copy it to the result vector - if (inmesh.vertcnt.size() <= 1) { - result.Append(inmesh); - return; - } - - result.vertcnt.reserve(inmesh.vertcnt.size()+result.vertcnt.size()); - - // XXX get rid of the extra copy if possible - TempMesh meshout = inmesh; - - // 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"); - std::vector::iterator outer_polygon = meshout.vertcnt.end(), begin=meshout.vertcnt.begin(), end=outer_polygon, iit; - - // 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; - - // do not normalize 'normals', we need the original length for computing the polygon area - std::vector normals; - ComputePolygonNormals(meshout,normals,false); - - // see if one of the polygons is a IfcFaceOuterBound (in which case `master_bounds` is its index). - // sadly we can't rely on it, the docs say 'At most one of the bounds shall be of the type IfcFaceOuterBound' - float area_outer_polygon = 1e-10f; - if (master_bounds != (size_t)-1) { - outer_polygon = begin + master_bounds; - outer_polygon_start = std::accumulate(begin,outer_polygon,0); - area_outer_polygon = normals[master_bounds].SquareLength(); - } - else { - 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.SquareLength(); - if (area > area_outer_polygon) { - area_outer_polygon = area; - outer_polygon = iit; - outer_polygon_start = vidx; - } - } - } - - ai_assert(outer_polygon != meshout.vertcnt.end()); - std::vector& in = meshout.verts; - - // skip over extremely small boundaries - this is a workaround to fix cases - // in which the number of holes is so extremely large that the - // triangulation code fails. -#define IFC_VERTICAL_HOLE_SIZE_TRESHOLD 0.000001f - size_t vidx = 0, removed = 0, index = 0; - const float treshold = area_outer_polygon * IFC_VERTICAL_HOLE_SIZE_TRESHOLD; - for(iit = begin; iit != end ;++index) { - const float sqlen = normals[index].SquareLength(); - if (sqlen < treshold) { - std::vector::iterator inbase = in.begin()+vidx; - in.erase(inbase,inbase+*iit); - - outer_polygon_start -= outer_polygon_start>vidx ? *iit : 0; - *iit++ = 0; - ++removed; - - IFCImporter::LogDebug("skip small hole below treshold"); - } - else { - normals[index] /= sqrt(sqlen); - vidx += *iit++; - } - } - - // see if one or more of the hole has a face that lies directly on an outer bound. - // this happens for doors, for example. - vidx = 0; - for(iit = begin; ; vidx += *iit++) { -next_loop: - if (iit == end) { - break; - } - if (iit == outer_polygon) { - continue; - } - - for(size_t vofs = 0; vofs < *iit; ++vofs) { - if (!*iit) { - continue; - } - const size_t next = (vofs+1)%*iit; - const aiVector3D& v = in[vidx+vofs], &vnext = in[vidx+next],&vd = (vnext-v).Normalize(); - - for(size_t outer = 0; outer < *outer_polygon; ++outer) { - const aiVector3D& o = in[outer_polygon_start+outer], &onext = in[outer_polygon_start+(outer+1)%*outer_polygon], &od = (onext-o).Normalize(); - - if (fabs(vd * od) > 1.f-1e-6f && (onext-v).Normalize() * vd > 1.f-1e-6f && (onext-v)*(o-v) < 0) { - IFCImporter::LogDebug("got an inner hole that lies partly on the outer polygonal boundary, merging them to a single contour"); - - // in between outer and outer+1 insert all vertices of this loop, then drop the original altogether. - std::vector tmp(*iit); - - const size_t start = (v-o).SquareLength() > (vnext-o).SquareLength() ? vofs : next; - std::vector::iterator inbase = in.begin()+vidx, it = std::copy(inbase+start, inbase+*iit,tmp.begin()); - std::copy(inbase, inbase+start,it); - std::reverse(tmp.begin(),tmp.end()); - - in.insert(in.begin()+outer_polygon_start+(outer+1)%*outer_polygon,tmp.begin(),tmp.end()); - vidx += outer_polygon_startvidx ? *iit : 0; - - *outer_polygon += tmp.size(); - *iit++ = 0; - ++removed; - goto next_loop; - } - } - } - } - - if ( meshout.vertcnt.size() - removed <= 1) { - result.Append(meshout); - return; - } - - // extract the outer boundary and move it to a separate mesh - TempMesh boundary; - boundary.vertcnt.resize(1,*outer_polygon); - boundary.verts.resize(*outer_polygon); - - std::vector::iterator b = in.begin()+outer_polygon_start; - std::copy(b,b+*outer_polygon,boundary.verts.begin()); - in.erase(b,b+*outer_polygon); - - std::vector::iterator norit = normals.begin()+std::distance(meshout.vertcnt.begin(),outer_polygon); - const aiVector3D nor_boundary = *norit; - normals.erase(norit); - meshout.vertcnt.erase(outer_polygon); - - // 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) { - - // 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()) { - if(ProcessPolyloop(*polyloop, meshout,conv)) { - - //if(bound.ToPtr()) { - // ob = cnt; - //} - //++cnt; - - } - } - else { - IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is " + bound.Bound->GetClassName()); - continue; - } - - /*if(!IsTrue(bound.Orientation)) { - size_t c = 0; - BOOST_FOREACH(unsigned int& c, meshout.vertcnt) { - std::reverse(result.verts.begin() + cnt,result.verts.begin() + cnt + c); - cnt += c; - } - }*/ - - } - MergePolygonBoundaries(result,meshout); - } -} - -// ------------------------------------------------------------------------------------------------ -void ProcessPolyLine(const IFC::IfcPolyline& def, TempMesh& meshout, ConversionData& /*conv*/) -{ - // this won't produce a valid mesh, it just spits out a list of vertices - aiVector3D t; - BOOST_FOREACH(const IFC::IfcCartesianPoint& cp, def.Points) { - ConvertCartesianPoint(t,cp); - meshout.verts.push_back(t); - } - meshout.vertcnt.push_back(meshout.verts.size()); -} - -// ------------------------------------------------------------------------------------------------ -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) -{ - ProcessCurve(def.OuterCurve,meshout,conv); -} - -// ------------------------------------------------------------------------------------------------ -void ProcessOpenProfile(const IFC::IfcArbitraryOpenProfileDef& def, TempMesh& meshout, ConversionData& conv) -{ - ProcessCurve(def.Curve,meshout,conv); -} - -// ------------------------------------------------------------------------------------------------ -void ProcessParametrizedProfile(const IFC::IfcParameterizedProfileDef& def, TempMesh& meshout, ConversionData& conv) -{ - if(const IFC::IfcRectangleProfileDef* const cprofile = def.ToPtr()) { - const float x = cprofile->XDim*0.5f, y = cprofile->YDim*0.5f; - - meshout.verts.reserve(meshout.verts.size()+4); - meshout.verts.push_back( aiVector3D( x, y, 0.f )); - meshout.verts.push_back( aiVector3D(-x, y, 0.f )); - meshout.verts.push_back( aiVector3D(-x,-y, 0.f )); - meshout.verts.push_back( aiVector3D( x,-y, 0.f )); - meshout.vertcnt.push_back(4); - } - else if( const IFC::IfcCircleProfileDef* const circle = def.ToPtr()) { - if( const IFC::IfcCircleHollowProfileDef* const hollow = def.ToPtr()) { - // TODO - } - const size_t segments = 32; - const float delta = AI_MATH_TWO_PI_F/segments, radius = circle->Radius; - - meshout.verts.reserve(segments); - - float angle = 0.f; - for(size_t i = 0; i < segments; ++i, angle += delta) { - meshout.verts.push_back( aiVector3D( cos(angle)*radius, sin(angle)*radius, 0.f )); - } - - meshout.vertcnt.push_back(segments); - } - else { - IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is " + def.GetClassName()); - return; - } - - aiMatrix4x4 trafo; - ConvertAxisPlacement(trafo, *def.Position,conv); - meshout.Transform(trafo); -} - -// ------------------------------------------------------------------------------------------------ -bool ProcessProfile(const IFC::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv) -{ - if(const IFC::IfcArbitraryClosedProfileDef* const cprofile = prof.ToPtr()) { - ProcessClosedProfile(*cprofile,meshout,conv); - } - else if(const IFC::IfcArbitraryOpenProfileDef* const copen = prof.ToPtr()) { - ProcessOpenProfile(*copen,meshout,conv); - } - else if(const IFC::IfcParameterizedProfileDef* const cparam = prof.ToPtr()) { - ProcessParametrizedProfile(*cparam,meshout,conv); - } - else { - IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is " + prof.GetClassName()); - return false; - } - meshout.RemoveAdjacentDuplicates(); - if (!meshout.vertcnt.size() || meshout.vertcnt.front() <= 1) { - return false; - } - return true; -} - -// ------------------------------------------------------------------------------------------------ -void ProcessRevolvedAreaSolid(const IFC::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv) -{ - TempMesh meshout; - - // first read the profile description - if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.verts.size()<=1) { - return; - } - - aiVector3D axis, pos; - ConvertAxisPlacement(axis,pos,solid.Axis,conv); - - aiMatrix4x4 tb0,tb1; - aiMatrix4x4::Translation(pos,tb0); - aiMatrix4x4::Translation(-pos,tb1); - - const std::vector& in = meshout.verts; - const size_t size=in.size(); - - bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2; - const float max_angle = solid.Angle*conv.angle_scale; - if(fabs(max_angle) < 1e-3) { - if(has_area) { - result = meshout; - } - return; - } - - const unsigned int cnt_segments = std::max(2u,static_cast(16 * fabs(max_angle)/AI_MATH_HALF_PI_F)); - const float delta = max_angle/cnt_segments; - - has_area = has_area && fabs(max_angle) < AI_MATH_TWO_PI_F*0.99; - - result.verts.reserve(size*((cnt_segments+1)*4+(has_area?2:0))); - result.vertcnt.reserve(size*cnt_segments+2); - - aiMatrix4x4 rot; - rot = tb0 * aiMatrix4x4::Rotation(delta,axis,rot) * tb1; - - size_t base = 0; - std::vector& out = result.verts; - - // dummy data to simplify later processing - for(size_t i = 0; i < size; ++i) { - out.insert(out.end(),4,in[i]); - } - - for(unsigned int seg = 0; seg < cnt_segments; ++seg) { - for(size_t i = 0; i < size; ++i) { - const size_t next = (i+1)%size; - - result.vertcnt.push_back(4); - const aiVector3D& base_0 = out[base+i*4+3],base_1 = out[base+next*4+3]; - - out.push_back(base_0); - out.push_back(base_1); - out.push_back(rot*base_1); - out.push_back(rot*base_0); - } - base += size*4; - } - - out.erase(out.begin(),out.begin()+size*4); - - 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. - base -= size*8; - for(size_t i = size; i--; ) { - out.push_back(out[base+i*4+3]); - } - for(size_t i = 0; i < size; ++i ) { - out.push_back(out[i*4]); - } - result.vertcnt.push_back(size); - result.vertcnt.push_back(size); - } - - aiMatrix4x4 trafo; - ConvertAxisPlacement(trafo, solid.Position,conv); - - result.Transform(trafo); - IFCImporter::LogDebug("generate mesh procedurally by radial extrusion (IfcRevolvedAreaSolid)"); -} - - -// ------------------------------------------------------------------------------------------------ -bool TryAddOpenings(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-1]; - const aiVector3D nor = ComputePolygonNormal(curmesh); ; - - bool got_openings = false; - TempMesh res; - - size_t c = 0; - BOOST_FOREACH(const TempOpening& t,openings) { - const aiVector3D& outernor = nors[c++]; - const float dot = nor * outernor; - if (fabs(dot)<1.f-1e-6f) { - 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; - - // project va[i] onto the plane formed by the current polygon [given by (any_point,nor)] - for(size_t i = 0; i < va.size(); ++i) { - const aiVector3D& v = va[i]; - out.push_back(v-(nor*(v-any_point))*nor); - } - - - curmesh.vertcnt.push_back(va.size()); - - res.Clear(); - 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; -}; - -// ------------------------------------------------------------------------------------------------ -struct XYSorter { - - // sort first by X coordinates, then by Y coordinates - bool operator () (const aiVector2D&a, const aiVector2D& b) const { - if (a.x == b.x) { - return a.y < b.y; - } - return a.x < b.x; - } -}; - -// ------------------------------------------------------------------------------------------------ -struct ProjectionInfo { - unsigned int ac, bc; - aiVector3D p,u,v; -}; - -typedef std::pair< aiVector2D, aiVector2D > BoundingBox; -typedef std::map XYSortedField; - -// ------------------------------------------------------------------------------------------------ -aiVector2D ProjectPositionVectorOntoPlane(const aiVector3D& x, const ProjectionInfo& proj) -{ - const aiVector3D xx = x-proj.p; - return aiVector2D(xx[proj.ac]/proj.u[proj.ac],xx[proj.bc]/proj.v[proj.bc]); -} - -// ------------------------------------------------------------------------------------------------ -void QuadrifyPart(const aiVector2D& pmin, const aiVector2D& pmax, XYSortedField& field, const std::vector< BoundingBox >& bbs, - std::vector& out) -{ - if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) { - return; - } - - float xs = 1e10, xe = 1e10; - bool found = false; - - // Search along the x-axis until we find an opening - XYSortedField::iterator start = field.begin(); - for(; start != field.end(); ++start) { - const BoundingBox& bb = bbs[(*start).second]; - if (bb.second.x > pmin.x && bb.first.x < pmax.x && bb.second.y > pmin.y && bb.first.y < pmax.y) { - xs = bb.first.x; - xe = bb.second.x; - found = true; - break; - } - } - xs = std::max(pmin.x,xs); - xe = std::min(pmax.x,xe); - - if (!found) { - // the rectangle [pmin,pend] is opaque, fill it - out.push_back(pmin); - out.push_back(aiVector2D(pmin.x,pmax.y)); - out.push_back(pmax); - out.push_back(aiVector2D(pmax.x,pmin.y)); - return; - } - - if (xs - pmin.x) { - out.push_back(pmin); - out.push_back(aiVector2D(pmin.x,pmax.y)); - out.push_back(aiVector2D(xs,pmax.y)); - out.push_back(aiVector2D(xs,pmin.y)); - } - - // search along the y-axis for all openings that overlap xs and our element - float ylast = pmin.y; - found = false; - for(; start != field.end(); ++start) { - const BoundingBox& bb = bbs[(*start).second]; - - if (bb.second.y > ylast && bb.first.y < pmax.y) { - - found = true; - const float ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y); - if (ys - ylast) { - // Divide et impera! - QuadrifyPart( aiVector2D(xs,ylast), aiVector2D(xe,ys) ,field,bbs,out); - } - - // the following are the window vertices - - /*wnd.push_back(aiVector2D(xs,ys)); - wnd.push_back(aiVector2D(xs,ye)); - wnd.push_back(aiVector2D(xe,ye)); - wnd.push_back(aiVector2D(xe,ys));*/ - ylast = ye; - } - - if (bb.first.x > xs) { - break; - } - } - if (!found) { - // the rectangle [pmin,pend] is opaque, fill it - out.push_back(aiVector2D(xs,pmin.y)); - out.push_back(aiVector2D(xs,pmax.y)); - out.push_back(aiVector2D(xe,pmax.y)); - out.push_back(aiVector2D(xe,pmin.y)); - return; - } - if (ylast < pmax.y) { - // Divide et impera! - QuadrifyPart( aiVector2D(xs,ylast), aiVector2D(xe,pmax.y) ,field,bbs,out); - } - - // Divide et impera! - now for the whole rest - if (pmax.x-xe) { - QuadrifyPart(aiVector2D(xe,pmin.y), pmax ,field,bbs,out); - } -} - -// ------------------------------------------------------------------------------------------------ -enum Intersect { - Intersect_No, - Intersect_LiesOnPlane, - Intersect_Yes -}; - -// ------------------------------------------------------------------------------------------------ -Intersect IntersectSegmentPlane(const aiVector3D& p,const aiVector3D& n, const aiVector3D& e0, const aiVector3D& e1, aiVector3D& out) -{ - const aiVector3D pdelta = e0 - p, seg = e1-e0; - const float dotOne = n*seg, dotTwo = -(n*pdelta); - - if (fabs(dotOne) < 1e-6) { - return fabs(dotTwo) < 1e-6f ? Intersect_LiesOnPlane : Intersect_No; - } - - const float t = dotTwo/dotOne; - // t must be in [0..1] if the intersection point is within the given segment - if (t > 1.f || t < 0.f) { - return Intersect_No; - } - out = e0+t*seg; - return Intersect_Yes; -} - - - -// ------------------------------------------------------------------------------------------------ -aiVector3D Unproject(const aiVector2D& vproj, const ProjectionInfo& proj) -{ - return vproj.x*proj.u + vproj.y*proj.v + proj.p; -} - -// ------------------------------------------------------------------------------------------------ -void InsertWindowContours(const std::vector< BoundingBox >& bbs,const std::vector< std::vector >& contours,const ProjectionInfo& proj, TempMesh& curmesh) -{ - ai_assert(contours.size() == bbs.size()); - - // fix windows - we need to insert the real, polygonal shapes into the quadratic holes that we have now - for(size_t i = 0; i < contours.size();++i) { - const BoundingBox& bb = bbs[i]; - const std::vector& contour = contours[i]; - - // check if we need to do it at all - many windows just fit perfectly into their quadratic holes, - // i.e. their contours *are* already their bounding boxes. - if (contour.size() == 4) { - std::set verts; - for(size_t n = 0; n < 4; ++n) { - verts.insert(contour[n]); - } - const std::set::const_iterator end = verts.end(); - if (verts.find(bb.first)!=end && verts.find(bb.second)!=end - && verts.find(aiVector2D(bb.first.x,bb.second.y))!=end - && verts.find(aiVector2D(bb.second.x,bb.first.y))!=end - ) { - continue; - } - } - - const float epsilon = (bb.first-bb.second).Length()/1000.f; - - // walk through all contour points and find those that lie on the BB corner - size_t last_hit = -1, very_first_hit = -1; - aiVector2D edge; - for(size_t n = 0, e=0, size = contour.size();; n=(n+1)%size, ++e) { - - // sanity checking - if (e == size*2) { - IFCImporter::LogError("encountered unexpected topology while generating window contour"); - break; - } - - const aiVector2D& v = contour[n]; - - bool hit = false; - if (fabs(v.x-bb.first.x) n ? size-(last_hit-n) : n-last_hit; - for(size_t a = last_hit, e = 0; e <= cnt; a=(a+1)%size, ++e) { - curmesh.verts.push_back(Unproject(contour[a],proj)); - } - - if (edge != contour[last_hit] && edge != contour[n]) { - curmesh.verts.push_back(Unproject(edge,proj)); - } - else if (cnt == 1) { - // avoid degenerate polygons (also known as lines or points) - curmesh.verts.erase(curmesh.verts.begin()+old,curmesh.verts.end()); - } - - if (const size_t d = curmesh.verts.size()-old) { - curmesh.vertcnt.push_back(d); - std::reverse(curmesh.verts.rbegin(),curmesh.verts.rbegin()+d); - } - if (n == very_first_hit) { - break; - } - } - else { - very_first_hit = n; - } - - last_hit = n; - } - } - } -} - -// ------------------------------------------------------------------------------------------------ -bool TryAddOpenings_Quadrulate(const std::vector& openings,const std::vector& nors, TempMesh& curmesh) -{ - std::vector& out = curmesh.verts; - - // Try to derive a solid base plane within the current surface for use as - // working coordinate system. - aiVector3D vmin,vmax; - ArrayBounds(&out[0],out.size(),vmin,vmax); - - const size_t s = out.size(); - - const aiVector3D any_point = out[s-4]; - const aiVector3D nor = ((out[s-3]-any_point)^(out[s-2]-any_point)).Normalize(); - - const aiVector3D diag = vmax-vmin; - const float ax = fabs(nor.x); - const float ay = fabs(nor.y); - const float az = fabs(nor.z); - - unsigned int ac = 0, bc = 1; /* no z coord. -> projection to xy */ - if (ax > ay) { - if (ax > az) { /* no x coord. -> projection to yz */ - ac = 1; bc = 2; - } - } - else if (ay > az) { /* no y coord. -> projection to zy */ - ac = 2; bc = 0; - } - - ProjectionInfo proj; - proj.u = proj.v = diag; - proj.u[bc]=0; - proj.v[ac]=0; - proj.ac = ac; - proj.bc = bc; - proj.p = vmin; - - // project all points into the coordinate system defined by the p+sv*tu plane - // and compute bounding boxes for them - std::vector< BoundingBox > bbs; - XYSortedField field; - - std::vector contour_flat; - contour_flat.reserve(out.size()); - BOOST_FOREACH(const aiVector3D& x, out) { - contour_flat.push_back(ProjectPositionVectorOntoPlane(x,proj)); - } - - std::vector< std::vector > contours; - - size_t c = 0; - BOOST_FOREACH(const TempOpening& t,openings) { - const aiVector3D& outernor = nors[c++]; - const float dot = nor * outernor; - if (fabs(dot)<1.f-1e-6f) { - continue; - } - - - // const aiVector3D diff = t.extrusionDir; - - const std::vector& va = t.profileMesh->verts; - if(va.size() <= 2) { - continue; - } - - aiVector2D vpmin,vpmax; - MinMaxChooser()(vpmin,vpmax); - - contours.push_back(std::vector()); - std::vector& contour = contours.back(); - - BOOST_FOREACH(const aiVector3D& x, t.profileMesh->verts) { - const aiVector2D& vproj = ProjectPositionVectorOntoPlane(x,proj); - - vpmin = std::min(vpmin,vproj); - vpmax = std::max(vpmax,vproj); - - contour.push_back(vproj); - } - - - if (field.find(vpmin) != field.end()) { - IFCImporter::LogWarn("constraint failure during generation of wall openings, results may be faulty"); - } - field[vpmin] = bbs.size(); - bbs.push_back(BoundingBox(vpmin,vpmax)); - } - - if (bbs.empty()) { - return false; - } - - - std::vector outflat; - outflat.reserve(openings.size()*4); - QuadrifyPart(aiVector2D(0.f,0.f),aiVector2D(1.f,1.f),field,bbs,outflat); - ai_assert(!(outflat.size() % 4)); - - //FixOuterBoundaries(outflat,contour_flat); - - // undo the projection, generate output quads - std::vector vold; - vold.reserve(outflat.size()); - std::swap(vold,curmesh.verts); - - std::vector iold; - iold.resize(outflat.size()/4,4); - std::swap(iold,curmesh.vertcnt); - - BOOST_FOREACH(const aiVector2D& vproj, outflat) { - out.push_back(Unproject(vproj,proj)); - } - - InsertWindowContours(bbs,contours,proj,curmesh); - return true; -} - - -// ------------------------------------------------------------------------------------------------ -void ProcessExtrudedAreaSolid(const IFC::IfcExtrudedAreaSolid& solid, TempMesh& result, ConversionData& conv) -{ - TempMesh meshout; - - // first read the profile description - if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.verts.size()<=1) { - return; - } - - aiVector3D dir; - ConvertDirection(dir,solid.ExtrudedDirection); - - dir *= solid.Depth; - - // assuming that `meshout.verts` is now a list of vertex points forming - // the underlying profile, extrude along the given axis, forming new - // triangles. - - std::vector& in = meshout.verts; - const size_t size=in.size(); - - const bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2; - if(solid.Depth < 1e-3) { - if(has_area) { - meshout = result; - } - return; - } - - 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); - - std::vector nors; - - // 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()); - BOOST_FOREACH(TempOpening& t,*conv.apply_openings) { - TempMesh& bounds = *t.profileMesh.get(); - - 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()); - } - } - - TempMesh temp; - TempMesh& curmesh = conv.apply_openings ? temp : result; - std::vector& out = curmesh.verts; - - bool (* const gen_openings)(const std::vector&,const std::vector&, TempMesh&) = conv.settings.useCustomTriangulation - ? &TryAddOpenings_Quadrulate - : &TryAddOpenings; - - size_t sides_with_openings = 0; - for(size_t i = 0; i < size; ++i) { - const size_t next = (i+1)%size; - - 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]); - - if(conv.apply_openings) { - if(gen_openings(*conv.apply_openings,nors,temp)) { - ++sides_with_openings; - } - - result.Append(temp); - temp.Clear(); - } - } - - size_t sides_with_v_openings = 0; - if(has_area) { - - 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 && size > 2) { - // XXX here we are forced to use the un-triangulated version of TryAddOpening, with - // all the problems it causes. The reason is that vertical walls (ehm, floors) - // can have an arbitrary outer shape, so the usual approach of projecting - // the surface and all openings onto a flat quad and triangulating the quad - // fails. - if(TryAddOpenings(*conv.apply_openings,nors,temp)) { - ++sides_with_v_openings; - } - - result.Append(temp); - temp.Clear(); - } - } - } - - // add connection geometry to close the 'holes' for the openings - if(conv.apply_openings) { - //result.infacing.resize(result.verts.size()+); - BOOST_FOREACH(const TempOpening& t,*conv.apply_openings) { - const std::vector& in = t.profileMesh->verts; - std::vector& out = result.verts; - - 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]); - } - } - } - - 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"); - } - - IFCImporter::LogDebug("generate mesh procedurally by extrusion (IfcExtrudedAreaSolid)"); -} - -// ------------------------------------------------------------------------------------------------ -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()) { - ProcessRevolvedAreaSolid(*rev,meshout,conv); - } - else { - IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is " + swept.GetClassName()); - } -} - -// ------------------------------------------------------------------------------------------------ -void ProcessBoolean(const IFC::IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv) -{ - if(const IFC::IfcBooleanClippingResult* const clip = boolean.ToPtr()) { - if(clip->Operator != "DIFFERENCE") { - IFCImporter::LogWarn("encountered unsupported boolean operator: " + (std::string)clip->Operator); - return; - } - - TempMesh meshout; - const IFC::IfcHalfSpaceSolid* const hs = clip->SecondOperand->ResolveSelectPtr(conv.db); - if(!hs) { - IFCImporter::LogError("expected IfcHalfSpaceSolid as second clipping operand"); - return; - } - - const IFC::IfcPlane* const plane = hs->BaseSurface->ToPtr(); - if(!plane) { - 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); - } - else if (const IFC::IfcSweptAreaSolid* const swept = clip->FirstOperand->ResolveSelectPtr(conv.db)) { - ProcessSweptAreaSolid(*swept,meshout,conv); - } - else { - IFCImporter::LogError("expected IfcSweptAreaSolid or IfcBooleanResult as first clipping operand"); - return; - } - - // extract plane base position vector and normal vector - aiVector3D p,n(0.f,0.f,1.f); - if (plane->Position->Axis) { - ConvertDirection(n,plane->Position->Axis.Get()); - } - ConvertCartesianPoint(p,plane->Position->Location); - - if(!IsTrue(hs->AgreementFlag)) { - n *= -1.f; - } - - // 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 begin=meshout.vertcnt.begin(), end=meshout.vertcnt.end(), iit; - - outvert.reserve(in.size()); - result.vertcnt.reserve(meshout.vertcnt.size()); - - unsigned int vidx = 0; - for(iit = begin; iit != end; vidx += *iit++) { - - unsigned int newcount = 0; - for(unsigned int i = 0; i < *iit; ++i) { - const aiVector3D& e0 = in[vidx+i], e1 = in[vidx+(i+1)%*iit]; - - // does the next segment intersect the plane? - aiVector3D isectpos; - const Intersect isect = IntersectSegmentPlane(p,n,e0,e1,isectpos); - if (isect == Intersect_No || isect == Intersect_LiesOnPlane) { - if ( (e0-p).Normalize()*n > 0 ) { - outvert.push_back(e0); - ++newcount; - } - } - else if (isect == Intersect_Yes) { - if ( (e0-p).Normalize()*n > 0 ) { - // e0 is on the right side, so keep it - outvert.push_back(e0); - outvert.push_back(isectpos); - newcount += 2; - } - else { - // e0 is on the wrong side, so drop it and keep e1 instead - outvert.push_back(isectpos); - ++newcount; - } - } - } - - if (!newcount) { - continue; - } - - aiVector3D vmin,vmax; - ArrayBounds(&*(outvert.end()-newcount),newcount,vmin,vmax); - - // filter our double points - those may happen if a point lies - // directly on the intersection line. However, due to float - // precision a bitwise comparison is not feasible to detect - // this case. - const float epsilon = (vmax-vmin).SquareLength() / 1e6f; - FuzzyVectorCompare fz(epsilon); - - std::vector::iterator e = std::unique( outvert.end()-newcount, outvert.end(), fz ); - if (e != outvert.end()) { - newcount -= static_cast(std::distance(e,outvert.end())); - outvert.erase(e,outvert.end()); - } - if (fz(*( outvert.end()-newcount),outvert.back())) { - outvert.pop_back(); - --newcount; - } - if(newcount > 2) { - result.vertcnt.push_back(newcount); - } - else while(newcount-->0)result.verts.pop_back(); - - } - IFCImporter::LogDebug("generating CSG geometry by plane clipping (IfcBooleanClippingResult)"); - } - else { - IFCImporter::LogWarn("skipping unknown IfcBooleanResult entity, type is " + boolean.GetClassName()); - } -} - -// ------------------------------------------------------------------------------------------------ -int ConvertShadingMode(const std::string& name) -{ - if (name == "BLINN") { - return aiShadingMode_Blinn; - } - else if (name == "FLAT" || name == "NOTDEFINED") { - return aiShadingMode_NoShading; - } - else if (name == "PHONG") { - return aiShadingMode_Phong; - } - IFCImporter::LogWarn("shading mode "+name+" not recognized by Assimp, using Phong instead"); - return aiShadingMode_Phong; -} - -// ------------------------------------------------------------------------------------------------ -void FillMaterial(MaterialHelper* mat,const IFC::IfcSurfaceStyle* surf,ConversionData& conv) -{ - aiString name; - 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(boost::shared_ptr< 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):static_cast(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); - - 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()); - } - - STEP::DB::RefMapRange range = conv.db.GetRefs().equal_range(item.GetID()); - for(;range.first != range.second; ++range.first) { - if(const IFC::IfcStyledItem* const styled = conv.db.GetObject((*range.first).second)->ToPtr()) { - BOOST_FOREACH(const IFC::IfcPresentationStyleAssignment& as, styled->Styles) { - BOOST_FOREACH(boost::shared_ptr sel, as.Styles) { - - 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); - } - - std::auto_ptr mat(new MaterialHelper()); - - FillMaterial(mat.get(),surf,conv); - - conv.materials.push_back(mat.release()); - return conv.materials.size()-1; - } - } - } - } - } - return 0; -} - -// ------------------------------------------------------------------------------------------------ -bool ProcessTopologicalItem(const IFC::IfcTopologicalRepresentationItem& topo, std::vector& mesh_indices, ConversionData& conv) -{ - TempMesh meshtmp; - if(const IFC::IfcConnectedFaceSet* fset = topo.ToPtr()) { - ProcessConnectedFaceSet(*fset,meshtmp,conv); - } - else { - IFCImporter::LogWarn("skipping unknown IfcTopologicalRepresentationItem entity, type is " + topo.GetClassName()); - return false; - } - - meshtmp.RemoveAdjacentDuplicates(); - FixupFaceOrientation(meshtmp); - - aiMesh* const mesh = meshtmp.ToMesh(); - if(mesh) { - mesh->mMaterialIndex = ProcessMaterials(topo,conv); - mesh_indices.push_back(conv.meshes.size()); - conv.meshes.push_back(mesh); - return true; - } - return false; -} - -// ------------------------------------------------------------------------------------------------ -bool ProcessGeometricItem(const IFC::IfcGeometricRepresentationItem& geo, std::vector& mesh_indices, ConversionData& conv) -{ - TempMesh meshtmp; - if(const IFC::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr()) { - BOOST_FOREACH(boost::shared_ptr shell,shellmod->SbsmBoundary) { - try { - const EXPRESS::ENTITY& e = shell->To(); - const IFC::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To(); - - ProcessConnectedFaceSet(fs,meshtmp,conv); - } - catch(std::bad_cast&) { - IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet"); - } - } - } - else if(const IFC::IfcSweptAreaSolid* swept = geo.ToPtr()) { - ProcessSweptAreaSolid(*swept,meshtmp,conv); - } - else if(const IFC::IfcManifoldSolidBrep* brep = geo.ToPtr()) { - ProcessConnectedFaceSet(brep->Outer,meshtmp,conv); - } - else if(const IFC::IfcFaceBasedSurfaceModel* surf = geo.ToPtr()) { - BOOST_FOREACH(const IFC::IfcConnectedFaceSet& fc, surf->FbsmFaces) { - ProcessConnectedFaceSet(fc,meshtmp,conv); - } - } - else if(const IFC::IfcBooleanResult* boolean = geo.ToPtr()) { - ProcessBoolean(*boolean,meshtmp,conv); - } - else if(geo.ToPtr()) { - // silently skip over bounding boxes - return false; - } - else { - IFCImporter::LogWarn("skipping unknown IfcGeometricRepresentationItem entity, type is " + geo.GetClassName()); - return false; - } - - meshtmp.RemoveAdjacentDuplicates(); - FixupFaceOrientation(meshtmp); - - aiMesh* const mesh = meshtmp.ToMesh(); - if(mesh) { - mesh->mMaterialIndex = ProcessMaterials(geo,conv); - mesh_indices.push_back(conv.meshes.size()); - conv.meshes.push_back(mesh); - return true; - } - return false; -} - -// ------------------------------------------------------------------------------------------------ -void AssignAddedMeshes(std::vector& mesh_indices,aiNode* nd,ConversionData& /*conv*/) -{ - if (!mesh_indices.empty()) { - - // make unique - std::sort(mesh_indices.begin(),mesh_indices.end()); - std::vector::iterator it_end = std::unique(mesh_indices.begin(),mesh_indices.end()); - - const size_t size = std::distance(mesh_indices.begin(),it_end); - - nd->mNumMeshes = size; - nd->mMeshes = new unsigned int[nd->mNumMeshes]; - for(unsigned int i = 0; i < nd->mNumMeshes; ++i) { - nd->mMeshes[i] = mesh_indices[i]; - } - } -} - -// ------------------------------------------------------------------------------------------------ -bool TryQueryMeshCache(const IFC::IfcRepresentationItem& item, std::vector& mesh_indices, ConversionData& conv) -{ - ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(&item); - if (it != conv.cached_meshes.end()) { - std::copy((*it).second.begin(),(*it).second.end(),std::back_inserter(mesh_indices)); - return true; - } - return false; -} - -// ------------------------------------------------------------------------------------------------ -void PopulateMeshCache(const IFC::IfcRepresentationItem& item, const std::vector& mesh_indices, ConversionData& conv) -{ - conv.cached_meshes[&item] = mesh_indices; -} - -// ------------------------------------------------------------------------------------------------ -bool ProcessRepresentationItem(const IFC::IfcRepresentationItem& item, std::vector& mesh_indices, ConversionData& conv) -{ - if(const IFC::IfcTopologicalRepresentationItem* const topo = item.ToPtr()) { - if (!TryQueryMeshCache(item,mesh_indices,conv)) { - if(ProcessTopologicalItem(*topo,mesh_indices,conv)) { - if(mesh_indices.size()) { - PopulateMeshCache(item,mesh_indices,conv); - } - } - else return false; - } - return true; - } - else if(const IFC::IfcGeometricRepresentationItem* const geo = item.ToPtr()) { - if (!TryQueryMeshCache(item,mesh_indices,conv)) { - if(ProcessGeometricItem(*geo,mesh_indices,conv)) { - if(mesh_indices.size()) { - PopulateMeshCache(item,mesh_indices,conv); - } - } - else return false; - } - return true; - } - return false; -} - -// ------------------------------------------------------------------------------------------------ -void ResolveObjectPlacement(aiMatrix4x4& m, const IFC::IfcObjectPlacement& place, ConversionData& conv) -{ - if (const IFC::IfcLocalPlacement* const local = place.ToPtr()){ + if (const IfcLocalPlacement* const local = place.ToPtr()){ ConvertAxisPlacement(m, *local->RelativePlacement, conv); if (local->PlacementRelTo) { @@ -2385,7 +350,7 @@ void GetAbsTransform(aiMatrix4x4& out, const aiNode* nd, ConversionData& conv) } // ------------------------------------------------------------------------------------------------ -void ProcessMappedItem(const IFC::IfcMappedItem& mapped, aiNode* nd_src, std::vector< aiNode* >& subnodes_src, ConversionData& conv) +bool ProcessMappedItem(const IfcMappedItem& mapped, aiNode* nd_src, std::vector< aiNode* >& subnodes_src, ConversionData& conv) { // insert a custom node here, the cartesian transform operator is simply a conventional transformation matrix std::auto_ptr nd(new aiNode()); @@ -2410,11 +375,18 @@ void ProcessMappedItem(const IFC::IfcMappedItem& mapped, aiNode* nd_src, std::ve } } - const IFC::IfcRepresentation& repr = mapped.MappingSource->MappedRepresentation; - BOOST_FOREACH(const IFC::IfcRepresentationItem& item, repr.Items) { + const IfcRepresentation& repr = mapped.MappingSource->MappedRepresentation; + + bool got = false; + BOOST_FOREACH(const IfcRepresentationItem& item, repr.Items) { if(!ProcessRepresentationItem(item,meshes,conv)) { IFCImporter::LogWarn("skipping unknown mapped entity, type is " + item.GetClassName()); } + else got = true; + } + + if (!got) { + return false; } AssignAddedMeshes(meshes,nd.get(),conv); @@ -2432,47 +404,135 @@ void ProcessMappedItem(const IFC::IfcMappedItem& mapped, aiNode* nd_src, std::ve nd->mTransformation = nd_src->mTransformation * msrc; subnodes_src.push_back(nd.release()); + + return true; } // ------------------------------------------------------------------------------------------------ -void ProcessProductRepresentation(const IFC::IfcProduct& el, aiNode* nd, std::vector< aiNode* >& subnodes, ConversionData& conv) +struct RateRepresentationPredicate { + + int Rate(const IfcRepresentation* r) const { + // the smaller, the better + + if (! r->RepresentationIdentifier) { + // neutral choice if no extra information is specified + return 0; + } + + + const std::string& name = r->RepresentationIdentifier.Get(); + if (name == "MappedRepresentation") { + if (!r->Items.empty()) { + // take the first item and base our choice on it + const IfcMappedItem* const m = r->Items.front()->ToPtr(); + if (m) { + return Rate(m->MappingSource->MappedRepresentation); + } + } + return 100; + } + + return Rate(name); + } + + int Rate(const std::string& r) const { + + + if (r == "SolidModel") { + return -3; + } + + // give strong preference to extruded geometry + if (r == "SweptSolid") { + return -10; + } + + if (r == "Clipping") { + return -5; + } + + // 'Brep' is difficult to get right due to possible voids in the + // polygon boundaries, so take it only if we are forced to (i.e. + // if the only alternative is (non-clipping) boolean operations, + // which are not supported at all). + if (r == "Brep") { + return -2; + } + + // Curves, bounding boxes - those will most likely not be loaded + // as we can't make any use out of this data. So consider them + // last. + if (r == "BoundingBox" || r == "Curve2D") { + return 100; + } + return 0; + } + + bool operator() (const IfcRepresentation* a, const IfcRepresentation* b) const { + return Rate(a) <= Rate(b); + } +}; + +// ------------------------------------------------------------------------------------------------ +void ProcessProductRepresentation(const IfcProduct& el, aiNode* nd, std::vector< aiNode* >& subnodes, ConversionData& conv) { if(!el.Representation) { return; } - if(conv.settings.skipSpaceRepresentations) { - if(const IFC::IfcSpace* const space = el.ToPtr()) { - IFCImporter::LogWarn("skipping IfcSpace entity due to importer settings"); - return; - } - } std::vector meshes; - BOOST_FOREACH(const IFC::IfcRepresentation& repr, el.Representation.Get()->Representations) { - if (conv.settings.skipCurveRepresentations && repr.RepresentationType && repr.RepresentationType.Get() == "Curve2D") { - IFCImporter::LogWarn("skipping Curve2D representation item due to importer settings"); - continue; - } - BOOST_FOREACH(const IFC::IfcRepresentationItem& item, repr.Items) { - if(const IFC::IfcMappedItem* const geo = item.ToPtr()) { - ProcessMappedItem(*geo,nd,subnodes,conv); + // we want only one representation type, so bring them in a suitable order (i.e try those + // that look as if we could read them quickly at first). This way of reading + // representation is relatively generic and allows the concrete implementations + // for the different representation types to make some sensible choices what + // to load and what not to load. + const STEP::ListOf< STEP::Lazy< IfcRepresentation >, 1, 0 >& src = el.Representation.Get()->Representations; + + std::vector repr_ordered(src.size()); + std::copy(src.begin(),src.end(),repr_ordered.begin()); + std::sort(repr_ordered.begin(),repr_ordered.end(),RateRepresentationPredicate()); + + BOOST_FOREACH(const IfcRepresentation* repr, repr_ordered) { + bool res = false; + BOOST_FOREACH(const IfcRepresentationItem& item, repr->Items) { + if(const IfcMappedItem* const geo = item.ToPtr()) { + res = ProcessMappedItem(*geo,nd,subnodes,conv) || res; } else { - ProcessRepresentationItem(item,meshes,conv); + res = ProcessRepresentationItem(item,meshes,conv) || res; } } + // if we got something meaningful at this point, skip any further representations + if(res) { + break; + } } AssignAddedMeshes(meshes,nd,conv); } // ------------------------------------------------------------------------------------------------ -aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el, ConversionData& conv, std::vector* collect_openings = NULL) +aiNode* ProcessSpatialStructure(aiNode* parent, const IfcProduct& el, ConversionData& conv, std::vector* collect_openings = NULL) { const STEP::DB::RefMap& refs = conv.db.GetRefs(); + // skip over space and annotation nodes - usually, these have no meaning in Assimp's context + if(conv.settings.skipSpaceRepresentations) { + if(const IfcSpace* const space = el.ToPtr()) { + IFCImporter::LogDebug("skipping IfcSpace entity due to importer settings"); + return NULL; + } + } + + if(conv.settings.skipAnnotations) { + if(const IfcAnnotation* const ann = el.ToPtr()) { + IFCImporter::LogDebug("skipping IfcAnnotation entity due to importer settings"); + return NULL; + } + } + // add an output node for this spatial structure std::auto_ptr nd(new aiNode()); nd->mName.Set(el.GetClassName()+"_"+(el.Name?el.Name:el.GlobalId)); @@ -2499,22 +559,25 @@ aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el, Conve 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) { - if(const IFC::IfcOpeningElement* const open = pro.ToPtr()) { + if(const IfcRelContainedInSpatialStructure* const cont = obj->ToPtr()) { + BOOST_FOREACH(const IfcProduct& pro, cont->RelatedElements) { + if(const 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) ); + aiNode* const ndnew = ProcessSpatialStructure(nd.get(),pro,conv,NULL); + if(ndnew) { + subnodes.push_back( ndnew ); + } } } // 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()) { + else if(const IfcRelVoidsElement* const fills = obj->ToPtr()) { if(fills->RelatingBuildingElement->GetID() == el.GetID()) { - const IFC::IfcFeatureElementSubtraction& open = fills->RelatedOpeningElement; + const 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()); @@ -2523,33 +586,36 @@ aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el, Conve 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); - + aiNode* const ndnew = ProcessSpatialStructure( nd_aggr.get(),open, conv,&openings_local); + if (ndnew) { - if(openings_local.size()) { - if (!didinv) { - myInv = aiMatrix4x4(nd->mTransformation ).Inverse(); - didinv = true; - } + nd_aggr->mNumChildren = 1; + nd_aggr->mChildren = new aiNode*[1](); - // 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); + + nd_aggr->mChildren[0] = ndnew; + + 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() ); } - - subnodes.push_back( nd_aggr.release() ); } } } for(;range.first != range.second; ++range.first) { - if(const IFC::IfcRelAggregates* const aggr = conv.db.GetObject((*range.first).second)->ToPtr()) { + if(const 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 just 'contained' std::auto_ptr nd_aggr(new aiNode()); @@ -2559,9 +625,13 @@ aiNode* ProcessSpatialStructure(aiNode* parent, const IFC::IfcProduct& el, Conve 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,NULL); + BOOST_FOREACH(const IfcObjectDefinition& def, aggr->RelatedObjects) { + if(const IfcProduct* const prod = def.ToPtr()) { + + aiNode* const ndnew = ProcessSpatialStructure(nd_aggr.get(),*prod,conv,NULL); + if(ndnew) { + nd_aggr->mChildren[nd_aggr->mNumChildren++] = ndnew; + } } } @@ -2618,7 +688,7 @@ void ProcessSpatialStructures(ConversionData& conv) BOOST_FOREACH(const STEP::LazyObject* lz, *range) { - const IFC::IfcSpatialStructureElement* const prod = lz->ToPtr(); + const IfcSpatialStructureElement* const prod = lz->ToPtr(); if(!prod) { continue; } @@ -2628,9 +698,9 @@ void ProcessSpatialStructures(ConversionData& conv) const STEP::DB::RefMap& refs = conv.db.GetRefs(); STEP::DB::RefMapRange range = refs.equal_range(conv.proj.GetID()); for(;range.first != range.second; ++range.first) { - if(const IFC::IfcRelAggregates* const aggr = conv.db.GetObject((*range.first).second)->ToPtr()) { + if(const IfcRelAggregates* const aggr = conv.db.GetObject((*range.first).second)->ToPtr()) { - BOOST_FOREACH(const IFC::IfcObjectDefinition& def, aggr->RelatedObjects) { + BOOST_FOREACH(const 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.GetID() == prod->GetID()) { @@ -2648,7 +718,7 @@ void ProcessSpatialStructures(ConversionData& conv) IFCImporter::LogWarn("failed to determine primary site element, taking the first IfcSite"); BOOST_FOREACH(const STEP::LazyObject* lz, *range) { - const IFC::IfcSpatialStructureElement* const prod = lz->ToPtr(); + const IfcSpatialStructureElement* const prod = lz->ToPtr(); if(!prod) { continue; } diff --git a/code/IFCLoader.h b/code/IFCLoader.h index c3d51b601..71170d419 100644 --- a/code/IFCLoader.h +++ b/code/IFCLoader.h @@ -114,12 +114,14 @@ public: : skipSpaceRepresentations() , skipCurveRepresentations() , useCustomTriangulation() + , skipAnnotations() {} bool skipSpaceRepresentations; bool skipCurveRepresentations; bool useCustomTriangulation; + bool skipAnnotations; }; diff --git a/code/IFCMaterial.cpp b/code/IFCMaterial.cpp new file mode 100644 index 000000000..c1ce879e3 --- /dev/null +++ b/code/IFCMaterial.cpp @@ -0,0 +1,179 @@ +/* +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 IFCMaterial.cpp + * @brief Implementation of conversion routines to convert IFC materials to aiMaterial + */ + +#include "AssimpPCH.h" + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER +#include "IFCUtil.h" + +namespace Assimp { + namespace IFC { + +// ------------------------------------------------------------------------------------------------ +int ConvertShadingMode(const std::string& name) +{ + if (name == "BLINN") { + return aiShadingMode_Blinn; + } + else if (name == "FLAT" || name == "NOTDEFINED") { + return aiShadingMode_NoShading; + } + else if (name == "PHONG") { + return aiShadingMode_Phong; + } + IFCImporter::LogWarn("shading mode "+name+" not recognized by Assimp, using Phong instead"); + return aiShadingMode_Phong; +} + +// ------------------------------------------------------------------------------------------------ +void FillMaterial(MaterialHelper* mat,const IFC::IfcSurfaceStyle* surf,ConversionData& conv) +{ + aiString name; + 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(boost::shared_ptr< 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):static_cast(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); + + 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()); + } + + STEP::DB::RefMapRange range = conv.db.GetRefs().equal_range(item.GetID()); + for(;range.first != range.second; ++range.first) { + if(const IFC::IfcStyledItem* const styled = conv.db.GetObject((*range.first).second)->ToPtr()) { + BOOST_FOREACH(const IFC::IfcPresentationStyleAssignment& as, styled->Styles) { + BOOST_FOREACH(boost::shared_ptr sel, as.Styles) { + + 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); + } + + std::auto_ptr mat(new MaterialHelper()); + + FillMaterial(mat.get(),surf,conv); + + conv.materials.push_back(mat.release()); + return conv.materials.size()-1; + } + } + } + } + } + return 0; +} + +} // ! IFC +} // ! Assimp + +#endif diff --git a/code/IFCProfile.cpp b/code/IFCProfile.cpp new file mode 100644 index 000000000..53225ad9f --- /dev/null +++ b/code/IFCProfile.cpp @@ -0,0 +1,155 @@ +/* +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 IFCProfile.cpp + * @brief Read profile and curves entities from IFC files + */ + +#include "AssimpPCH.h" + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER +#include "IFCUtil.h" + +namespace Assimp { + namespace IFC { + +// ------------------------------------------------------------------------------------------------ +void ProcessPolyLine(const IfcPolyline& def, TempMesh& meshout, ConversionData& /*conv*/) +{ + // this won't produce a valid mesh, it just spits out a list of vertices + aiVector3D t; + BOOST_FOREACH(const IfcCartesianPoint& cp, def.Points) { + ConvertCartesianPoint(t,cp); + meshout.verts.push_back(t); + } + meshout.vertcnt.push_back(meshout.verts.size()); +} + +// ------------------------------------------------------------------------------------------------ +bool ProcessCurve(const IfcCurve& curve, TempMesh& meshout, ConversionData& conv) +{ + if(const 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 IfcArbitraryClosedProfileDef& def, TempMesh& meshout, ConversionData& conv) +{ + ProcessCurve(def.OuterCurve,meshout,conv); +} + +// ------------------------------------------------------------------------------------------------ +void ProcessOpenProfile(const IfcArbitraryOpenProfileDef& def, TempMesh& meshout, ConversionData& conv) +{ + ProcessCurve(def.Curve,meshout,conv); +} + +// ------------------------------------------------------------------------------------------------ +void ProcessParametrizedProfile(const IfcParameterizedProfileDef& def, TempMesh& meshout, ConversionData& conv) +{ + if(const IfcRectangleProfileDef* const cprofile = def.ToPtr()) { + const float x = cprofile->XDim*0.5f, y = cprofile->YDim*0.5f; + + meshout.verts.reserve(meshout.verts.size()+4); + meshout.verts.push_back( aiVector3D( x, y, 0.f )); + meshout.verts.push_back( aiVector3D(-x, y, 0.f )); + meshout.verts.push_back( aiVector3D(-x,-y, 0.f )); + meshout.verts.push_back( aiVector3D( x,-y, 0.f )); + meshout.vertcnt.push_back(4); + } + else if( const IfcCircleProfileDef* const circle = def.ToPtr()) { + if( const IfcCircleHollowProfileDef* const hollow = def.ToPtr()) { + // TODO + } + const size_t segments = 32; + const float delta = AI_MATH_TWO_PI_F/segments, radius = circle->Radius; + + meshout.verts.reserve(segments); + + float angle = 0.f; + for(size_t i = 0; i < segments; ++i, angle += delta) { + meshout.verts.push_back( aiVector3D( cos(angle)*radius, sin(angle)*radius, 0.f )); + } + + meshout.vertcnt.push_back(segments); + } + else { + IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is " + def.GetClassName()); + return; + } + + aiMatrix4x4 trafo; + ConvertAxisPlacement(trafo, *def.Position); + meshout.Transform(trafo); +} + +// ------------------------------------------------------------------------------------------------ +bool ProcessProfile(const IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv) +{ + if(const IfcArbitraryClosedProfileDef* const cprofile = prof.ToPtr()) { + ProcessClosedProfile(*cprofile,meshout,conv); + } + else if(const IfcArbitraryOpenProfileDef* const copen = prof.ToPtr()) { + ProcessOpenProfile(*copen,meshout,conv); + } + else if(const IfcParameterizedProfileDef* const cparam = prof.ToPtr()) { + ProcessParametrizedProfile(*cparam,meshout,conv); + } + else { + IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is " + prof.GetClassName()); + return false; + } + meshout.RemoveAdjacentDuplicates(); + if (!meshout.vertcnt.size() || meshout.vertcnt.front() <= 1) { + return false; + } + return true; +} + +} // ! IFC +} // ! Assimp + +#endif diff --git a/code/IFCUtil.cpp b/code/IFCUtil.cpp new file mode 100644 index 000000000..df34c190a --- /dev/null +++ b/code/IFCUtil.cpp @@ -0,0 +1,441 @@ +/* +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 IFCUtil.cpp + * @brief Implementation of conversion routines for some common Ifc helper entities. + */ + +#include "AssimpPCH.h" + +#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER +#include "IFCUtil.h" +#include "ProcessHelper.h" + +namespace Assimp { + namespace IFC { + +// ------------------------------------------------------------------------------------------------ +void TempOpening::Transform(const aiMatrix4x4& mat) +{ + if(profileMesh) { + profileMesh->Transform(mat); + } + extrusionDir *= aiMatrix3x3(mat); +} + +// ------------------------------------------------------------------------------------------------ +aiMesh* TempMesh::ToMesh() +{ + ai_assert(verts.size() == std::accumulate(vertcnt.begin(),vertcnt.end(),0)); + + if (verts.empty()) { + return NULL; + } + + std::auto_ptr mesh(new aiMesh()); + + // copy vertices + mesh->mNumVertices = static_cast(verts.size()); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + std::copy(verts.begin(),verts.end(),mesh->mVertices); + + // and build up faces + mesh->mNumFaces = static_cast(vertcnt.size()); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + + for(unsigned int i = 0,n=0, acc = 0; i < mesh->mNumFaces; ++n) { + aiFace& f = mesh->mFaces[i]; + if (!vertcnt[n]) { + --mesh->mNumFaces; + continue; + } + + f.mNumIndices = vertcnt[n]; + f.mIndices = new unsigned int[f.mNumIndices]; + for(unsigned int a = 0; a < f.mNumIndices; ++a) { + f.mIndices[a] = acc++; + } + + ++i; + } + + return mesh.release(); +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::Clear() +{ + verts.clear(); + vertcnt.clear(); +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::Transform(const aiMatrix4x4& mat) +{ + BOOST_FOREACH(aiVector3D& v, verts) { + v *= mat; + } +} + +// ------------------------------------------------------------------------------ +aiVector3D TempMesh::Center() const +{ + return std::accumulate(verts.begin(),verts.end(),aiVector3D(0.f,0.f,0.f)) / static_cast(verts.size()); +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::Append(const TempMesh& other) +{ + verts.insert(verts.end(),other.verts.begin(),other.verts.end()); + vertcnt.insert(vertcnt.end(),other.vertcnt.begin(),other.vertcnt.end()); +} + +// ------------------------------------------------------------------------------------------------ +void TempMesh::RemoveAdjacentDuplicates() +{ + + bool drop = false; + std::vector::iterator base = verts.begin(); + BOOST_FOREACH(unsigned int& cnt, vertcnt) { + if (cnt < 2){ + base += cnt; + continue; + } + + aiVector3D vmin,vmax; + ArrayBounds(&*base, cnt ,vmin,vmax); + + + const float epsilon = (vmax-vmin).SquareLength() / 1e9f; + //const float dotepsilon = 1e-9; + + //// look for vertices that lie directly on the line between their predecessor and their + //// successor and replace them with either of them. + + //for(size_t i = 0; i < cnt; ++i) { + // aiVector3D& v1 = *(base+i), &v0 = *(base+(i?i-1:cnt-1)), &v2 = *(base+(i+1)%cnt); + // const aiVector3D& d0 = (v1-v0), &d1 = (v2-v1); + // const float l0 = d0.SquareLength(), l1 = d1.SquareLength(); + // if (!l0 || !l1) { + // continue; + // } + + // const float d = (d0/sqrt(l0))*(d1/sqrt(l1)); + + // if ( d >= 1.f-dotepsilon ) { + // v1 = v0; + // } + // else if ( d < -1.f+dotepsilon ) { + // v2 = v1; + // continue; + // } + //} + + // drop any identical, adjacent vertices. this pass will collect the dropouts + // of the previous pass as a side-effect. + FuzzyVectorCompare fz(epsilon); + std::vector::iterator end = base+cnt, e = std::unique( base, end, fz ); + if (e != end) { + cnt -= static_cast(std::distance(e, end)); + verts.erase(e,end); + drop = true; + } + + // check front and back vertices for this polygon + if (cnt > 1 && fz(*base,*(base+cnt-1))) { + verts.erase(base+ --cnt); + drop = true; + } + + // removing adjacent duplicates shouldn't erase everything :-) + ai_assert(cnt>0); + base += cnt; + } + if(drop) { + IFCImporter::LogDebug("removed duplicate vertices"); + } +} + +// ------------------------------------------------------------------------------------------------ +bool IsTrue(const EXPRESS::BOOLEAN& in) +{ + return (std::string)in == "TRUE" || (std::string)in == "T"; +} + +// ------------------------------------------------------------------------------------------------ +float ConvertSIPrefix(const std::string& prefix) +{ + if (prefix == "EXA") { + return 1e18f; + } + else if (prefix == "PETA") { + return 1e15f; + } + else if (prefix == "TERA") { + return 1e12f; + } + else if (prefix == "GIGA") { + return 1e9f; + } + else if (prefix == "MEGA") { + return 1e6f; + } + else if (prefix == "KILO") { + return 1e3f; + } + else if (prefix == "HECTO") { + return 1e2f; + } + else if (prefix == "DECA") { + return 1e-0f; + } + else if (prefix == "DECI") { + return 1e-1f; + } + else if (prefix == "CENTI") { + return 1e-2f; + } + else if (prefix == "MILLI") { + return 1e-3f; + } + else if (prefix == "MICRO") { + return 1e-6f; + } + else if (prefix == "NANO") { + return 1e-9f; + } + else if (prefix == "PICO") { + return 1e-12f; + } + else if (prefix == "FEMTO") { + return 1e-15f; + } + else if (prefix == "ATTO") { + return 1e-18f; + } + else { + IFCImporter::LogError("Unrecognized SI prefix: " + prefix); + return 1; + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertColor(aiColor4D& out, const IfcColourRgb& in) +{ + out.r = in.Red; + out.g = in.Green; + out.b = in.Blue; + out.a = 1.f; +} + +// ------------------------------------------------------------------------------------------------ +void ConvertColor(aiColor4D& out, const IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base) +{ + if (const EXPRESS::REAL* const r = in.ToPtr()) { + out.r = out.g = out.b = *r; + if(base) { + out.r *= base->r; + out.g *= base->g; + out.b *= base->b; + out.a = base->a; + } + else out.a = 1.0; + } + else if (const IfcColourRgb* const rgb = in.ResolveSelectPtr(conv.db)) { + ConvertColor(out,*rgb); + } + else { + IFCImporter::LogWarn("skipping unknown IfcColourOrFactor entity"); + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertCartesianPoint(aiVector3D& out, const IfcCartesianPoint& in) +{ + out = aiVector3D(); + for(size_t i = 0; i < in.Coordinates.size(); ++i) { + out[i] = in.Coordinates[i]; + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertDirection(aiVector3D& out, const IfcDirection& in) +{ + out = aiVector3D(); + for(size_t i = 0; i < in.DirectionRatios.size(); ++i) { + out[i] = in.DirectionRatios[i]; + } + const float len = out.Length(); + if (len<1e-6) { + IFCImporter::LogWarn("direction vector too small, normalizing would result in a division by zero"); + return; + } + out /= len; +} + +// ------------------------------------------------------------------------------------------------ +void AssignMatrixAxes(aiMatrix4x4& out, const aiVector3D& x, const aiVector3D& y, const aiVector3D& z) +{ + out.a1 = x.x; + out.b1 = x.y; + out.c1 = x.z; + + out.a2 = y.x; + out.b2 = y.y; + out.c2 = y.z; + + out.a3 = z.x; + out.b3 = z.y; + out.c3 = z.z; +} + +// ------------------------------------------------------------------------------------------------ +void ConvertAxisPlacement(aiMatrix4x4& out, const IfcAxis2Placement3D& in) +{ + aiVector3D loc; + ConvertCartesianPoint(loc,in.Location); + + aiVector3D z(0.f,0.f,1.f),r(1.f,0.f,0.f),x; + + if (in.Axis) { + ConvertDirection(z,*in.Axis.Get()); + } + if (in.RefDirection) { + ConvertDirection(r,*in.RefDirection.Get()); + } + + aiVector3D v = r.Normalize(); + aiVector3D tmpx = z * (v*z); + + x = (v-tmpx).Normalize(); + aiVector3D y = (z^x); + + aiMatrix4x4::Translation(loc,out); + AssignMatrixAxes(out,x,y,z); +} + +// ------------------------------------------------------------------------------------------------ +void ConvertAxisPlacement(aiMatrix4x4& out, const IfcAxis2Placement2D& in) +{ + aiVector3D loc; + ConvertCartesianPoint(loc,in.Location); + + aiVector3D x(1.f,0.f,0.f); + if (in.RefDirection) { + ConvertDirection(x,*in.RefDirection.Get()); + } + + const aiVector3D y = aiVector3D(x.y,-x.x,0.f); + + aiMatrix4x4::Translation(loc,out); + AssignMatrixAxes(out,x,y,aiVector3D(0.f,0.f,1.f)); +} + +// ------------------------------------------------------------------------------------------------ +void ConvertAxisPlacement(aiVector3D& axis, aiVector3D& pos, const IfcAxis1Placement& in) +{ + ConvertCartesianPoint(pos,in.Location); + if (in.Axis) { + ConvertDirection(axis,in.Axis.Get()); + } + else { + axis = aiVector3D(0.f,0.f,1.f); + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertAxisPlacement(aiMatrix4x4& out, const IfcAxis2Placement& in, ConversionData& conv) +{ + if(const IfcAxis2Placement3D* pl3 = in.ResolveSelectPtr(conv.db)) { + ConvertAxisPlacement(out,*pl3); + } + else if(const IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr(conv.db)) { + ConvertAxisPlacement(out,*pl2); + } + else { + IFCImporter::LogWarn("skipping unknown IfcAxis2Placement entity"); + } +} + +// ------------------------------------------------------------------------------------------------ +void ConvertTransformOperator(aiMatrix4x4& out, const IfcCartesianTransformationOperator& op) +{ + aiVector3D loc; + ConvertCartesianPoint(loc,op.LocalOrigin); + + aiVector3D x(1.f,0.f,0.f),y(0.f,1.f,0.f),z(0.f,0.f,1.f); + if (op.Axis1) { + ConvertDirection(x,*op.Axis1.Get()); + } + if (op.Axis2) { + ConvertDirection(y,*op.Axis2.Get()); + } + if (const IfcCartesianTransformationOperator3D* op2 = op.ToPtr()) { + if(op2->Axis3) { + ConvertDirection(z,*op2->Axis3.Get()); + } + } + + aiMatrix4x4 locm; + aiMatrix4x4::Translation(loc,locm); + AssignMatrixAxes(out,x,y,z); + + + aiVector3D vscale; + if (const 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(vscale,s); + + out = locm * out * s; +} + +} // ! IFC +} // ! Assimp + +#endif diff --git a/code/IFCUtil.h b/code/IFCUtil.h new file mode 100644 index 000000000..c80922662 --- /dev/null +++ b/code/IFCUtil.h @@ -0,0 +1,194 @@ +/* +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 IFC.cpp + * @brief Implementation of the Industry Foundation Classes loader. + */ + +#ifndef INCLUDED_IFCUTIL_H +#define INCLUDED_IFCUTIL_H + +#include "IFCReaderGen.h" +#include "IFCLoader.h" + +namespace Assimp { +namespace IFC { + +// helper for std::for_each to delete all heap-allocated items in a container +template +struct delete_fun +{ + void operator()(T* del) { + delete del; + } +}; + +// ------------------------------------------------------------------------------------------------ +// 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; + + // ------------------------------------------------------------------------------ + 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) + : len_scale(1.0) + , angle_scale(1.0) + , db(db) + , proj(proj) + , out(out) + , settings(settings) + , apply_openings() + , collect_openings() + {} + + ~ConversionData() { + std::for_each(meshes.begin(),meshes.end(),delete_fun()); + std::for_each(materials.begin(),materials.end(),delete_fun()); + } + + float len_scale, angle_scale; + bool plane_angle_in_radians; + + const STEP::DB& db; + const IFC::IfcProject& proj; + aiScene* out; + + aiMatrix4x4 wcs; + std::vector meshes; + std::vector materials; + + typedef std::map > MeshCache; + 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; +}; + +// ------------------------------------------------------------------------------------------------ +struct FuzzyVectorCompare { + + FuzzyVectorCompare(float epsilon) : epsilon(epsilon) {} + bool operator()(const aiVector3D& a, const aiVector3D& b) { + return fabs((a-b).SquareLength()) < epsilon; + } + + const float epsilon; +}; + + +// ------------------------------------------------------------------------------------------------ +// Helper used during mesh construction. Aids at creating aiMesh'es out of relatively few polygons. +// ------------------------------------------------------------------------------------------------ +struct TempMesh +{ + std::vector verts; + std::vector vertcnt; + + // utilities + aiMesh* ToMesh(); + void Clear(); + void Transform(const aiMatrix4x4& mat); + aiVector3D Center() const; + void Append(const TempMesh& other); + void RemoveAdjacentDuplicates(); +}; + + + + + +// conversion routines for common IFC entities, implemented in IFCUtil.cpp +void ConvertColor(aiColor4D& out, const IFC::IfcColourRgb& in); +void ConvertColor(aiColor4D& out, const IFC::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base); +void ConvertCartesianPoint(aiVector3D& out, const IFC::IfcCartesianPoint& in); +void ConvertDirection(aiVector3D& out, const IFC::IfcDirection& in); +void AssignMatrixAxes(aiMatrix4x4& out, const aiVector3D& x, const aiVector3D& y, const aiVector3D& z); +void ConvertAxisPlacement(aiMatrix4x4& out, const IFC::IfcAxis2Placement3D& in); +void ConvertAxisPlacement(aiMatrix4x4& out, const IFC::IfcAxis2Placement2D& in); +void ConvertAxisPlacement(aiVector3D& axis, aiVector3D& pos, const IFC::IfcAxis1Placement& in); +void ConvertAxisPlacement(aiMatrix4x4& out, const IFC::IfcAxis2Placement& in, ConversionData& conv); +void ConvertTransformOperator(aiMatrix4x4& out, const IFC::IfcCartesianTransformationOperator& op); +bool IsTrue(const EXPRESS::BOOLEAN& in); +float ConvertSIPrefix(const std::string& prefix); + + +// IFCProfile.cpp +bool ProcessProfile(const IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv); + +// IFCMaterial.cpp +unsigned int ProcessMaterials(const IFC::IfcRepresentationItem& item, ConversionData& conv); + +// IFCGeometry.cpp +bool ProcessRepresentationItem(const IfcRepresentationItem& item, std::vector& mesh_indices, ConversionData& conv); +void AssignAddedMeshes(std::vector& mesh_indices,aiNode* nd,ConversionData& /*conv*/); + +} +} + +#endif diff --git a/code/STEPFile.h b/code/STEPFile.h index e678dc4cb..aa32fe8d9 100644 --- a/code/STEPFile.h +++ b/code/STEPFile.h @@ -683,6 +683,10 @@ namespace STEP { typedef Lazy Out; Lazy(const LazyObject* obj = NULL) : obj(obj) { } + + operator const T*() const { + return obj->ToPtr(); + } operator const T&() const { return obj->To(); diff --git a/workspaces/vc9/assimp.vcproj b/workspaces/vc9/assimp.vcproj index 24f5ea033..5f2d9c5c4 100644 --- a/workspaces/vc9/assimp.vcproj +++ b/workspaces/vc9/assimp.vcproj @@ -1947,6 +1947,10 @@ + + @@ -1955,6 +1959,14 @@ RelativePath="..\..\code\IFCLoader.h" > + + + + @@ -1971,6 +1983,14 @@ RelativePath="..\..\code\IFCReaderGen.h" > + + + +