nuclear@0: /* nuclear@0: Open Asset Import Library (assimp) nuclear@0: ---------------------------------------------------------------------- nuclear@0: nuclear@0: Copyright (c) 2006-2010, assimp team nuclear@0: All rights reserved. nuclear@0: nuclear@0: Redistribution and use of this software in source and binary forms, nuclear@0: with or without modification, are permitted provided that the nuclear@0: following conditions are met: nuclear@0: nuclear@0: * Redistributions of source code must retain the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer. nuclear@0: nuclear@0: * Redistributions in binary form must reproduce the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer in the documentation and/or other nuclear@0: materials provided with the distribution. nuclear@0: nuclear@0: * Neither the name of the assimp team, nor the names of its nuclear@0: contributors may be used to endorse or promote products nuclear@0: derived from this software without specific prior nuclear@0: written permission of the assimp team. nuclear@0: nuclear@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS nuclear@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT nuclear@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR nuclear@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT nuclear@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, nuclear@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT nuclear@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, nuclear@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY nuclear@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT nuclear@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE nuclear@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nuclear@0: nuclear@0: ---------------------------------------------------------------------- nuclear@0: */ nuclear@0: nuclear@0: /** @file IFCGeometry.cpp nuclear@0: * @brief Geometry conversion and synthesis for IFC nuclear@0: */ nuclear@0: nuclear@0: #include "AssimpPCH.h" nuclear@0: nuclear@0: #ifndef ASSIMP_BUILD_NO_IFC_IMPORTER nuclear@0: #include "IFCUtil.h" nuclear@0: #include "PolyTools.h" nuclear@0: #include "ProcessHelper.h" nuclear@0: nuclear@0: #include "../contrib/poly2tri/poly2tri/poly2tri.h" nuclear@0: #include "../contrib/clipper/clipper.hpp" nuclear@0: nuclear@0: #include nuclear@0: nuclear@0: namespace Assimp { nuclear@0: namespace IFC { nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: bool ProcessPolyloop(const IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/) nuclear@0: { nuclear@0: size_t cnt = 0; nuclear@0: BOOST_FOREACH(const IfcCartesianPoint& c, loop.Polygon) { nuclear@0: IfcVector3 tmp; nuclear@0: ConvertCartesianPoint(tmp,c); nuclear@0: nuclear@0: meshout.verts.push_back(tmp); nuclear@0: ++cnt; nuclear@0: } nuclear@0: nuclear@0: meshout.vertcnt.push_back(cnt); nuclear@0: nuclear@0: // zero- or one- vertex polyloops simply ignored nuclear@0: if (meshout.vertcnt.back() > 1) { nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: if (meshout.vertcnt.back()==1) { nuclear@0: meshout.vertcnt.pop_back(); nuclear@0: meshout.verts.pop_back(); nuclear@0: } nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1) nuclear@0: { nuclear@0: // handle all trivial cases nuclear@0: if(inmesh.vertcnt.empty()) { nuclear@0: return; nuclear@0: } nuclear@0: if(inmesh.vertcnt.size() == 1) { nuclear@0: result.Append(inmesh); nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: ai_assert(std::count(inmesh.vertcnt.begin(), inmesh.vertcnt.end(), 0) == 0); nuclear@0: nuclear@0: typedef std::vector::const_iterator face_iter; nuclear@0: nuclear@0: face_iter begin = inmesh.vertcnt.begin(), end = inmesh.vertcnt.end(), iit; nuclear@0: std::vector::const_iterator outer_polygon_it = end; nuclear@0: nuclear@0: // major task here: given a list of nested polygon boundaries (one of which nuclear@0: // is the outer contour), reduce the triangulation task arising here to nuclear@0: // one that can be solved using the "quadrulation" algorithm which we use nuclear@0: // for pouring windows out of walls. The algorithm does not handle all nuclear@0: // cases but at least it is numerically stable and gives "nice" triangles. nuclear@0: nuclear@0: // first compute normals for all polygons using Newell's algorithm nuclear@0: // do not normalize 'normals', we need the original length for computing the polygon area nuclear@0: std::vector normals; nuclear@0: inmesh.ComputePolygonNormals(normals,false); nuclear@0: nuclear@0: // One of the polygons might be a IfcFaceOuterBound (in which case `master_bounds` nuclear@0: // is its index). Sadly we can't rely on it, the docs say 'At most one of the bounds nuclear@0: // shall be of the type IfcFaceOuterBound' nuclear@0: IfcFloat area_outer_polygon = 1e-10f; nuclear@0: if (master_bounds != (size_t)-1) { nuclear@0: ai_assert(master_bounds < inmesh.vertcnt.size()); nuclear@0: outer_polygon_it = begin + master_bounds; nuclear@0: } nuclear@0: else { nuclear@0: for(iit = begin; iit != end; iit++) { nuclear@0: // find the polygon with the largest area and take it as the outer bound. nuclear@0: IfcVector3& n = normals[std::distance(begin,iit)]; nuclear@0: const IfcFloat area = n.SquareLength(); nuclear@0: if (area > area_outer_polygon) { nuclear@0: area_outer_polygon = area; nuclear@0: outer_polygon_it = iit; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: ai_assert(outer_polygon_it != end); nuclear@0: nuclear@0: const size_t outer_polygon_size = *outer_polygon_it; nuclear@0: const IfcVector3& master_normal = normals[std::distance(begin, outer_polygon_it)]; nuclear@0: nuclear@0: // Generate fake openings to meet the interface for the quadrulate nuclear@0: // algorithm. It boils down to generating small boxes given the nuclear@0: // inner polygon and the surface normal of the outer contour. nuclear@0: // It is important that we use the outer contour's normal because nuclear@0: // this is the plane onto which the quadrulate algorithm will nuclear@0: // project the entire mesh. nuclear@0: std::vector fake_openings; nuclear@0: fake_openings.reserve(inmesh.vertcnt.size()-1); nuclear@0: nuclear@0: std::vector::const_iterator vit = inmesh.verts.begin(), outer_vit; nuclear@0: nuclear@0: for(iit = begin; iit != end; vit += *iit++) { nuclear@0: if (iit == outer_polygon_it) { nuclear@0: outer_vit = vit; nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: // Filter degenerate polygons to keep them from causing trouble later on nuclear@0: IfcVector3& n = normals[std::distance(begin,iit)]; nuclear@0: const IfcFloat area = n.SquareLength(); nuclear@0: if (area < 1e-5f) { nuclear@0: IFCImporter::LogWarn("skipping degenerate polygon (ProcessPolygonBoundaries)"); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: fake_openings.push_back(TempOpening()); nuclear@0: TempOpening& opening = fake_openings.back(); nuclear@0: nuclear@0: opening.extrusionDir = master_normal; nuclear@0: opening.solid = NULL; nuclear@0: nuclear@0: opening.profileMesh = boost::make_shared(); nuclear@0: opening.profileMesh->verts.reserve(*iit); nuclear@0: opening.profileMesh->vertcnt.push_back(*iit); nuclear@0: nuclear@0: std::copy(vit, vit + *iit, std::back_inserter(opening.profileMesh->verts)); nuclear@0: } nuclear@0: nuclear@0: // fill a mesh with ONLY the main polygon nuclear@0: TempMesh temp; nuclear@0: temp.verts.reserve(outer_polygon_size); nuclear@0: temp.vertcnt.push_back(outer_polygon_size); nuclear@0: std::copy(outer_vit, outer_vit+outer_polygon_size, nuclear@0: std::back_inserter(temp.verts)); nuclear@0: nuclear@0: GenerateOpenings(fake_openings, normals, temp, false, false); nuclear@0: result.Append(temp); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ProcessConnectedFaceSet(const IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv) nuclear@0: { nuclear@0: BOOST_FOREACH(const IfcFace& face, fset.CfsFaces) { nuclear@0: // size_t ob = -1, cnt = 0; nuclear@0: TempMesh meshout; nuclear@0: BOOST_FOREACH(const IfcFaceBound& bound, face.Bounds) { nuclear@0: nuclear@0: if(const IfcPolyLoop* const polyloop = bound.Bound->ToPtr()) { nuclear@0: if(ProcessPolyloop(*polyloop, meshout,conv)) { nuclear@0: nuclear@0: // The outer boundary is better determined by checking which nuclear@0: // polygon covers the largest area. nuclear@0: nuclear@0: //if(bound.ToPtr()) { nuclear@0: // ob = cnt; nuclear@0: //} nuclear@0: //++cnt; nuclear@0: nuclear@0: } nuclear@0: } nuclear@0: else { nuclear@0: IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is " + bound.Bound->GetClassName()); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: // And this, even though it is sometimes TRUE and sometimes FALSE, nuclear@0: // does not really improve results. nuclear@0: nuclear@0: /*if(!IsTrue(bound.Orientation)) { nuclear@0: size_t c = 0; nuclear@0: BOOST_FOREACH(unsigned int& c, meshout.vertcnt) { nuclear@0: std::reverse(result.verts.begin() + cnt,result.verts.begin() + cnt + c); nuclear@0: cnt += c; nuclear@0: } nuclear@0: }*/ nuclear@0: } nuclear@0: ProcessPolygonBoundaries(result, meshout); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ProcessRevolvedAreaSolid(const IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv) nuclear@0: { nuclear@0: TempMesh meshout; nuclear@0: nuclear@0: // first read the profile description nuclear@0: if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.verts.size()<=1) { nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: IfcVector3 axis, pos; nuclear@0: ConvertAxisPlacement(axis,pos,solid.Axis); nuclear@0: nuclear@0: IfcMatrix4 tb0,tb1; nuclear@0: IfcMatrix4::Translation(pos,tb0); nuclear@0: IfcMatrix4::Translation(-pos,tb1); nuclear@0: nuclear@0: const std::vector& in = meshout.verts; nuclear@0: const size_t size=in.size(); nuclear@0: nuclear@0: bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2; nuclear@0: const IfcFloat max_angle = solid.Angle*conv.angle_scale; nuclear@0: if(fabs(max_angle) < 1e-3) { nuclear@0: if(has_area) { nuclear@0: result = meshout; nuclear@0: } nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: const unsigned int cnt_segments = std::max(2u,static_cast(16 * fabs(max_angle)/AI_MATH_HALF_PI_F)); nuclear@0: const IfcFloat delta = max_angle/cnt_segments; nuclear@0: nuclear@0: has_area = has_area && fabs(max_angle) < AI_MATH_TWO_PI_F*0.99; nuclear@0: nuclear@0: result.verts.reserve(size*((cnt_segments+1)*4+(has_area?2:0))); nuclear@0: result.vertcnt.reserve(size*cnt_segments+2); nuclear@0: nuclear@0: IfcMatrix4 rot; nuclear@0: rot = tb0 * IfcMatrix4::Rotation(delta,axis,rot) * tb1; nuclear@0: nuclear@0: size_t base = 0; nuclear@0: std::vector& out = result.verts; nuclear@0: nuclear@0: // dummy data to simplify later processing nuclear@0: for(size_t i = 0; i < size; ++i) { nuclear@0: out.insert(out.end(),4,in[i]); nuclear@0: } nuclear@0: nuclear@0: for(unsigned int seg = 0; seg < cnt_segments; ++seg) { nuclear@0: for(size_t i = 0; i < size; ++i) { nuclear@0: const size_t next = (i+1)%size; nuclear@0: nuclear@0: result.vertcnt.push_back(4); nuclear@0: const IfcVector3& base_0 = out[base+i*4+3],base_1 = out[base+next*4+3]; nuclear@0: nuclear@0: out.push_back(base_0); nuclear@0: out.push_back(base_1); nuclear@0: out.push_back(rot*base_1); nuclear@0: out.push_back(rot*base_0); nuclear@0: } nuclear@0: base += size*4; nuclear@0: } nuclear@0: nuclear@0: out.erase(out.begin(),out.begin()+size*4); nuclear@0: nuclear@0: if(has_area) { nuclear@0: // leave the triangulation of the profile area to the ear cutting nuclear@0: // implementation in aiProcess_Triangulate - for now we just nuclear@0: // feed in two huge polygons. nuclear@0: base -= size*8; nuclear@0: for(size_t i = size; i--; ) { nuclear@0: out.push_back(out[base+i*4+3]); nuclear@0: } nuclear@0: for(size_t i = 0; i < size; ++i ) { nuclear@0: out.push_back(out[i*4]); nuclear@0: } nuclear@0: result.vertcnt.push_back(size); nuclear@0: result.vertcnt.push_back(size); nuclear@0: } nuclear@0: nuclear@0: IfcMatrix4 trafo; nuclear@0: ConvertAxisPlacement(trafo, solid.Position); nuclear@0: nuclear@0: result.Transform(trafo); nuclear@0: IFCImporter::LogDebug("generate mesh procedurally by radial extrusion (IfcRevolvedAreaSolid)"); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ProcessSweptDiskSolid(const IfcSweptDiskSolid solid, TempMesh& result, ConversionData& conv) nuclear@0: { nuclear@0: const Curve* const curve = Curve::Convert(*solid.Directrix, conv); nuclear@0: if(!curve) { nuclear@0: IFCImporter::LogError("failed to convert Directrix curve (IfcSweptDiskSolid)"); nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: const std::vector& in = result.verts; nuclear@0: nuclear@0: const unsigned int cnt_segments = 16; nuclear@0: const IfcFloat deltaAngle = AI_MATH_TWO_PI/cnt_segments; nuclear@0: nuclear@0: const size_t samples = curve->EstimateSampleCount(solid.StartParam,solid.EndParam); nuclear@0: nuclear@0: result.verts.reserve(cnt_segments * samples * 4); nuclear@0: result.vertcnt.reserve((cnt_segments - 1) * samples); nuclear@0: nuclear@0: std::vector points; nuclear@0: points.reserve(cnt_segments * samples); nuclear@0: nuclear@0: TempMesh temp; nuclear@0: curve->SampleDiscrete(temp,solid.StartParam,solid.EndParam); nuclear@0: const std::vector& curve_points = temp.verts; nuclear@0: nuclear@0: if(curve_points.empty()) { nuclear@0: IFCImporter::LogWarn("curve evaluation yielded no points (IfcSweptDiskSolid)"); nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: IfcVector3 current = curve_points[0]; nuclear@0: IfcVector3 previous = current; nuclear@0: IfcVector3 next; nuclear@0: nuclear@0: IfcVector3 startvec; nuclear@0: startvec.x = 1.0f; nuclear@0: startvec.y = 1.0f; nuclear@0: startvec.z = 1.0f; nuclear@0: nuclear@0: unsigned int last_dir = 0; nuclear@0: nuclear@0: // generate circles at the sweep positions nuclear@0: for(size_t i = 0; i < samples; ++i) { nuclear@0: nuclear@0: if(i != samples - 1) { nuclear@0: next = curve_points[i + 1]; nuclear@0: } nuclear@0: nuclear@0: // get a direction vector reflecting the approximate curvature (i.e. tangent) nuclear@0: IfcVector3 d = (current-previous) + (next-previous); nuclear@0: nuclear@0: d.Normalize(); nuclear@0: nuclear@0: // figure out an arbitrary point q so that (p-q) * d = 0, nuclear@0: // try to maximize ||(p-q)|| * ||(p_last-q_last)|| nuclear@0: IfcVector3 q; nuclear@0: bool take_any = false; nuclear@0: nuclear@0: for (unsigned int i = 0; i < 2; ++i, take_any = true) { nuclear@0: if ((last_dir == 0 || take_any) && abs(d.x) > 1e-6) { nuclear@0: q.y = startvec.y; nuclear@0: q.z = startvec.z; nuclear@0: q.x = -(d.y * q.y + d.z * q.z) / d.x; nuclear@0: last_dir = 0; nuclear@0: break; nuclear@0: } nuclear@0: else if ((last_dir == 1 || take_any) && abs(d.y) > 1e-6) { nuclear@0: q.x = startvec.x; nuclear@0: q.z = startvec.z; nuclear@0: q.y = -(d.x * q.x + d.z * q.z) / d.y; nuclear@0: last_dir = 1; nuclear@0: break; nuclear@0: } nuclear@0: else if ((last_dir == 2 && abs(d.z) > 1e-6) || take_any) { nuclear@0: q.y = startvec.y; nuclear@0: q.x = startvec.x; nuclear@0: q.z = -(d.y * q.y + d.x * q.x) / d.z; nuclear@0: last_dir = 2; nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: q *= solid.Radius / q.Length(); nuclear@0: startvec = q; nuclear@0: nuclear@0: // generate a rotation matrix to rotate q around d nuclear@0: IfcMatrix4 rot; nuclear@0: IfcMatrix4::Rotation(deltaAngle,d,rot); nuclear@0: nuclear@0: for (unsigned int seg = 0; seg < cnt_segments; ++seg, q *= rot ) { nuclear@0: points.push_back(q + current); nuclear@0: } nuclear@0: nuclear@0: previous = current; nuclear@0: current = next; nuclear@0: } nuclear@0: nuclear@0: // make quads nuclear@0: for(size_t i = 0; i < samples - 1; ++i) { nuclear@0: nuclear@0: const aiVector3D& this_start = points[ i * cnt_segments ]; nuclear@0: nuclear@0: // locate corresponding point on next sample ring nuclear@0: unsigned int best_pair_offset = 0; nuclear@0: float best_distance_squared = 1e10f; nuclear@0: for (unsigned int seg = 0; seg < cnt_segments; ++seg) { nuclear@0: const aiVector3D& p = points[ (i+1) * cnt_segments + seg]; nuclear@0: const float l = (p-this_start).SquareLength(); nuclear@0: nuclear@0: if(l < best_distance_squared) { nuclear@0: best_pair_offset = seg; nuclear@0: best_distance_squared = l; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: for (unsigned int seg = 0; seg < cnt_segments; ++seg) { nuclear@0: nuclear@0: result.verts.push_back(points[ i * cnt_segments + (seg % cnt_segments)]); nuclear@0: result.verts.push_back(points[ i * cnt_segments + (seg + 1) % cnt_segments]); nuclear@0: result.verts.push_back(points[ (i+1) * cnt_segments + ((seg + 1 + best_pair_offset) % cnt_segments)]); nuclear@0: result.verts.push_back(points[ (i+1) * cnt_segments + ((seg + best_pair_offset) % cnt_segments)]); nuclear@0: nuclear@0: IfcVector3& v1 = *(result.verts.end()-1); nuclear@0: IfcVector3& v2 = *(result.verts.end()-2); nuclear@0: IfcVector3& v3 = *(result.verts.end()-3); nuclear@0: IfcVector3& v4 = *(result.verts.end()-4); nuclear@0: nuclear@0: if (((v4-v3) ^ (v4-v1)) * (v4 - curve_points[i]) < 0.0f) { nuclear@0: std::swap(v4, v1); nuclear@0: std::swap(v3, v2); nuclear@0: } nuclear@0: nuclear@0: result.vertcnt.push_back(4); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: IFCImporter::LogDebug("generate mesh procedurally by sweeping a disk along a curve (IfcSweptDiskSolid)"); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut) nuclear@0: { nuclear@0: const std::vector& out = curmesh.verts; nuclear@0: IfcMatrix3 m; nuclear@0: nuclear@0: ok = true; nuclear@0: nuclear@0: // The input "mesh" must be a single polygon nuclear@0: const size_t s = out.size(); nuclear@0: assert(curmesh.vertcnt.size() == 1 && curmesh.vertcnt.back() == s); nuclear@0: nuclear@0: const IfcVector3 any_point = out[s-1]; nuclear@0: IfcVector3 nor; nuclear@0: nuclear@0: // The input polygon is arbitrarily shaped, therefore we might need some tries nuclear@0: // until we find a suitable normal. Note that Newell's algorithm would give nuclear@0: // a more robust result, but this variant also gives us a suitable first nuclear@0: // axis for the 2D coordinate space on the polygon plane, exploiting the nuclear@0: // fact that the input polygon is nearly always a quad. nuclear@0: bool done = false; nuclear@0: size_t i, j; nuclear@0: for (i = 0; !done && i < s-2; done || ++i) { nuclear@0: for (j = i+1; j < s-1; ++j) { nuclear@0: nor = -((out[i]-any_point)^(out[j]-any_point)); nuclear@0: if(fabs(nor.Length()) > 1e-8f) { nuclear@0: done = true; nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(!done) { nuclear@0: ok = false; nuclear@0: return m; nuclear@0: } nuclear@0: nuclear@0: nor.Normalize(); nuclear@0: norOut = nor; nuclear@0: nuclear@0: IfcVector3 r = (out[i]-any_point); nuclear@0: r.Normalize(); nuclear@0: nuclear@0: //if(d) { nuclear@0: // *d = -any_point * nor; nuclear@0: //} nuclear@0: nuclear@0: // Reconstruct orthonormal basis nuclear@0: // XXX use Gram Schmidt for increased robustness nuclear@0: IfcVector3 u = r ^ nor; nuclear@0: u.Normalize(); nuclear@0: nuclear@0: m.a1 = r.x; nuclear@0: m.a2 = r.y; nuclear@0: m.a3 = r.z; nuclear@0: nuclear@0: m.b1 = u.x; nuclear@0: m.b2 = u.y; nuclear@0: m.b3 = u.z; nuclear@0: nuclear@0: m.c1 = -nor.x; nuclear@0: m.c2 = -nor.y; nuclear@0: m.c3 = -nor.z; nuclear@0: nuclear@0: return m; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ProcessExtrudedAreaSolid(const IfcExtrudedAreaSolid& solid, TempMesh& result, nuclear@0: ConversionData& conv, bool collect_openings) nuclear@0: { nuclear@0: TempMesh meshout; nuclear@0: nuclear@0: // First read the profile description nuclear@0: if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.verts.size()<=1) { nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: IfcVector3 dir; nuclear@0: ConvertDirection(dir,solid.ExtrudedDirection); nuclear@0: nuclear@0: dir *= solid.Depth; /* nuclear@0: if(conv.collect_openings && !conv.apply_openings) { nuclear@0: dir *= 1000.0; nuclear@0: } */ nuclear@0: nuclear@0: // Outline: assuming that `meshout.verts` is now a list of vertex points forming nuclear@0: // the underlying profile, extrude along the given axis, forming new nuclear@0: // triangles. nuclear@0: nuclear@0: std::vector& in = meshout.verts; nuclear@0: const size_t size=in.size(); nuclear@0: nuclear@0: const bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2; nuclear@0: if(solid.Depth < 1e-6) { nuclear@0: if(has_area) { nuclear@0: result = meshout; nuclear@0: } nuclear@0: return; nuclear@0: } nuclear@0: nuclear@0: result.verts.reserve(size*(has_area?4:2)); nuclear@0: result.vertcnt.reserve(meshout.vertcnt.size()+2); nuclear@0: nuclear@0: // First step: transform all vertices into the target coordinate space nuclear@0: IfcMatrix4 trafo; nuclear@0: ConvertAxisPlacement(trafo, solid.Position); nuclear@0: nuclear@0: IfcVector3 vmin, vmax; nuclear@0: MinMaxChooser()(vmin, vmax); nuclear@0: BOOST_FOREACH(IfcVector3& v,in) { nuclear@0: v *= trafo; nuclear@0: nuclear@0: vmin = std::min(vmin, v); nuclear@0: vmax = std::max(vmax, v); nuclear@0: } nuclear@0: nuclear@0: vmax -= vmin; nuclear@0: const IfcFloat diag = vmax.Length(); nuclear@0: nuclear@0: IfcVector3 min = in[0]; nuclear@0: dir *= IfcMatrix3(trafo); nuclear@0: nuclear@0: std::vector nors; nuclear@0: const bool openings = !!conv.apply_openings && conv.apply_openings->size(); nuclear@0: nuclear@0: // Compute the normal vectors for all opening polygons as a prerequisite nuclear@0: // to TryAddOpenings_Poly2Tri() nuclear@0: // XXX this belongs into the aforementioned function nuclear@0: if (openings) { nuclear@0: nuclear@0: if (!conv.settings.useCustomTriangulation) { nuclear@0: // it is essential to apply the openings in the correct spatial order. The direction nuclear@0: // doesn't matter, but we would screw up if we started with e.g. a door in between nuclear@0: // two windows. nuclear@0: std::sort(conv.apply_openings->begin(),conv.apply_openings->end(), nuclear@0: TempOpening::DistanceSorter(min)); nuclear@0: } nuclear@0: nuclear@0: nors.reserve(conv.apply_openings->size()); nuclear@0: BOOST_FOREACH(TempOpening& t,*conv.apply_openings) { nuclear@0: TempMesh& bounds = *t.profileMesh.get(); nuclear@0: nuclear@0: if (bounds.verts.size() <= 2) { nuclear@0: nors.push_back(IfcVector3()); nuclear@0: continue; nuclear@0: } nuclear@0: nors.push_back(((bounds.verts[2]-bounds.verts[0])^(bounds.verts[1]-bounds.verts[0]) ).Normalize()); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: TempMesh temp; nuclear@0: TempMesh& curmesh = openings ? temp : result; nuclear@0: std::vector& out = curmesh.verts; nuclear@0: nuclear@0: size_t sides_with_openings = 0; nuclear@0: for(size_t i = 0; i < size; ++i) { nuclear@0: const size_t next = (i+1)%size; nuclear@0: nuclear@0: curmesh.vertcnt.push_back(4); nuclear@0: nuclear@0: out.push_back(in[i]); nuclear@0: out.push_back(in[i]+dir); nuclear@0: out.push_back(in[next]+dir); nuclear@0: out.push_back(in[next]); nuclear@0: nuclear@0: if(openings) { nuclear@0: if((in[i]-in[next]).Length() > diag * 0.1 && GenerateOpenings(*conv.apply_openings,nors,temp,true, true, dir)) { nuclear@0: ++sides_with_openings; nuclear@0: } nuclear@0: nuclear@0: result.Append(temp); nuclear@0: temp.Clear(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(openings) { nuclear@0: BOOST_FOREACH(TempOpening& opening, *conv.apply_openings) { nuclear@0: if (!opening.wallPoints.empty()) { nuclear@0: IFCImporter::LogError("failed to generate all window caps"); nuclear@0: } nuclear@0: opening.wallPoints.clear(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: size_t sides_with_v_openings = 0; nuclear@0: if(has_area) { nuclear@0: nuclear@0: for(size_t n = 0; n < 2; ++n) { nuclear@0: for(size_t i = size; i--; ) { nuclear@0: out.push_back(in[i]+(n?dir:IfcVector3())); nuclear@0: } nuclear@0: nuclear@0: curmesh.vertcnt.push_back(size); nuclear@0: if(openings && size > 2) { nuclear@0: if(GenerateOpenings(*conv.apply_openings,nors,temp,true, true, dir)) { nuclear@0: ++sides_with_v_openings; nuclear@0: } nuclear@0: nuclear@0: result.Append(temp); nuclear@0: temp.Clear(); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if(openings && ((sides_with_openings == 1 && sides_with_openings) || (sides_with_v_openings == 2 && sides_with_v_openings))) { nuclear@0: IFCImporter::LogWarn("failed to resolve all openings, presumably their topology is not supported by Assimp"); nuclear@0: } nuclear@0: nuclear@0: IFCImporter::LogDebug("generate mesh procedurally by extrusion (IfcExtrudedAreaSolid)"); nuclear@0: nuclear@0: // If this is an opening element, store both the extruded mesh and the 2D profile mesh nuclear@0: // it was created from. Return an empty mesh to the caller. nuclear@0: if(collect_openings && !result.IsEmpty()) { nuclear@0: ai_assert(conv.collect_openings); nuclear@0: boost::shared_ptr profile = boost::shared_ptr(new TempMesh()); nuclear@0: profile->Swap(result); nuclear@0: nuclear@0: boost::shared_ptr profile2D = boost::shared_ptr(new TempMesh()); nuclear@0: profile2D->Swap(meshout); nuclear@0: conv.collect_openings->push_back(TempOpening(&solid,dir,profile, profile2D)); nuclear@0: nuclear@0: ai_assert(result.IsEmpty()); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void ProcessSweptAreaSolid(const IfcSweptAreaSolid& swept, TempMesh& meshout, nuclear@0: ConversionData& conv) nuclear@0: { nuclear@0: if(const IfcExtrudedAreaSolid* const solid = swept.ToPtr()) { nuclear@0: ProcessExtrudedAreaSolid(*solid,meshout,conv, !!conv.collect_openings); nuclear@0: } nuclear@0: else if(const IfcRevolvedAreaSolid* const rev = swept.ToPtr()) { nuclear@0: ProcessRevolvedAreaSolid(*rev,meshout,conv); nuclear@0: } nuclear@0: else { nuclear@0: IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is " + swept.GetClassName()); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: bool ProcessGeometricItem(const IfcRepresentationItem& geo, std::vector& mesh_indices, nuclear@0: ConversionData& conv) nuclear@0: { nuclear@0: bool fix_orientation = true; nuclear@0: boost::shared_ptr< TempMesh > meshtmp = boost::make_shared(); nuclear@0: if(const IfcShellBasedSurfaceModel* shellmod = geo.ToPtr()) { nuclear@0: BOOST_FOREACH(boost::shared_ptr shell,shellmod->SbsmBoundary) { nuclear@0: try { nuclear@0: const EXPRESS::ENTITY& e = shell->To(); nuclear@0: const IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To(); nuclear@0: nuclear@0: ProcessConnectedFaceSet(fs,*meshtmp.get(),conv); nuclear@0: } nuclear@0: catch(std::bad_cast&) { nuclear@0: IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet"); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: else if(const IfcConnectedFaceSet* fset = geo.ToPtr()) { nuclear@0: ProcessConnectedFaceSet(*fset,*meshtmp.get(),conv); nuclear@0: } nuclear@0: else if(const IfcSweptAreaSolid* swept = geo.ToPtr()) { nuclear@0: ProcessSweptAreaSolid(*swept,*meshtmp.get(),conv); nuclear@0: } nuclear@0: else if(const IfcSweptDiskSolid* disk = geo.ToPtr()) { nuclear@0: ProcessSweptDiskSolid(*disk,*meshtmp.get(),conv); nuclear@0: fix_orientation = false; nuclear@0: } nuclear@0: else if(const IfcManifoldSolidBrep* brep = geo.ToPtr()) { nuclear@0: ProcessConnectedFaceSet(brep->Outer,*meshtmp.get(),conv); nuclear@0: } nuclear@0: else if(const IfcFaceBasedSurfaceModel* surf = geo.ToPtr()) { nuclear@0: BOOST_FOREACH(const IfcConnectedFaceSet& fc, surf->FbsmFaces) { nuclear@0: ProcessConnectedFaceSet(fc,*meshtmp.get(),conv); nuclear@0: } nuclear@0: } nuclear@0: else if(const IfcBooleanResult* boolean = geo.ToPtr()) { nuclear@0: ProcessBoolean(*boolean,*meshtmp.get(),conv); nuclear@0: } nuclear@0: else if(geo.ToPtr()) { nuclear@0: // silently skip over bounding boxes nuclear@0: return false; nuclear@0: } nuclear@0: else { nuclear@0: IFCImporter::LogWarn("skipping unknown IfcGeometricRepresentationItem entity, type is " + geo.GetClassName()); nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // Do we just collect openings for a parent element (i.e. a wall)? nuclear@0: // In such a case, we generate the polygonal mesh as usual, nuclear@0: // but attach it to a TempOpening instance which will later be applied nuclear@0: // to the wall it pertains to. nuclear@0: nuclear@0: // Note: swep area solids are added in ProcessExtrudedAreaSolid(), nuclear@0: // which returns an empty mesh. nuclear@0: if(conv.collect_openings) { nuclear@0: if (!meshtmp->IsEmpty()) { nuclear@0: conv.collect_openings->push_back(TempOpening(geo.ToPtr(), nuclear@0: IfcVector3(0,0,0), nuclear@0: meshtmp, nuclear@0: boost::shared_ptr())); nuclear@0: } nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: if (meshtmp->IsEmpty()) { nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: meshtmp->RemoveAdjacentDuplicates(); nuclear@0: meshtmp->RemoveDegenerates(); nuclear@0: nuclear@0: if(fix_orientation) { nuclear@0: meshtmp->FixupFaceOrientation(); nuclear@0: } nuclear@0: nuclear@0: aiMesh* const mesh = meshtmp->ToMesh(); nuclear@0: if(mesh) { nuclear@0: mesh->mMaterialIndex = ProcessMaterials(geo,conv); nuclear@0: mesh_indices.push_back(conv.meshes.size()); nuclear@0: conv.meshes.push_back(mesh); nuclear@0: return true; nuclear@0: } nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void AssignAddedMeshes(std::vector& mesh_indices,aiNode* nd, nuclear@0: ConversionData& /*conv*/) nuclear@0: { nuclear@0: if (!mesh_indices.empty()) { nuclear@0: nuclear@0: // make unique nuclear@0: std::sort(mesh_indices.begin(),mesh_indices.end()); nuclear@0: std::vector::iterator it_end = std::unique(mesh_indices.begin(),mesh_indices.end()); nuclear@0: nuclear@0: const size_t size = std::distance(mesh_indices.begin(),it_end); nuclear@0: nuclear@0: nd->mNumMeshes = size; nuclear@0: nd->mMeshes = new unsigned int[nd->mNumMeshes]; nuclear@0: for(unsigned int i = 0; i < nd->mNumMeshes; ++i) { nuclear@0: nd->mMeshes[i] = mesh_indices[i]; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: bool TryQueryMeshCache(const IfcRepresentationItem& item, nuclear@0: std::vector& mesh_indices, nuclear@0: ConversionData& conv) nuclear@0: { nuclear@0: ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(&item); nuclear@0: if (it != conv.cached_meshes.end()) { nuclear@0: std::copy((*it).second.begin(),(*it).second.end(),std::back_inserter(mesh_indices)); nuclear@0: return true; nuclear@0: } nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: void PopulateMeshCache(const IfcRepresentationItem& item, nuclear@0: const std::vector& mesh_indices, nuclear@0: ConversionData& conv) nuclear@0: { nuclear@0: conv.cached_meshes[&item] = mesh_indices; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: bool ProcessRepresentationItem(const IfcRepresentationItem& item, nuclear@0: std::vector& mesh_indices, nuclear@0: ConversionData& conv) nuclear@0: { nuclear@0: if (!TryQueryMeshCache(item,mesh_indices,conv)) { nuclear@0: if(ProcessGeometricItem(item,mesh_indices,conv)) { nuclear@0: if(mesh_indices.size()) { nuclear@0: PopulateMeshCache(item,mesh_indices,conv); nuclear@0: } nuclear@0: } nuclear@0: else return false; nuclear@0: } nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: } // ! IFC nuclear@0: } // ! Assimp nuclear@0: nuclear@0: #endif