Model loading and texturing
This commit is contained in:
829
thirdparty/assimp/code/Importer/IFC/IFCBoolean.cpp
vendored
Normal file
829
thirdparty/assimp/code/Importer/IFC/IFCBoolean.cpp
vendored
Normal file
@@ -0,0 +1,829 @@
|
||||
/*
|
||||
Open Asset Import Library (assimp)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2010, assimp 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 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 IFCBoolean.cpp
|
||||
* @brief Implements a subset of Ifc boolean operations
|
||||
*/
|
||||
|
||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||
#include "code/Importer/IFC/IFCUtil.h"
|
||||
#include "code/Common/PolyTools.h"
|
||||
#include "code/PostProcessing/ProcessHelper.h"
|
||||
#include <assimp/Defines.h>
|
||||
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
|
||||
namespace Assimp {
|
||||
namespace IFC {
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Calculates intersection between line segment and plane. To catch corner cases, specify which side you prefer.
|
||||
// The function then generates a hit only if the end is beyond a certain margin in that direction, filtering out
|
||||
// "very close to plane" ghost hits as long as start and end stay directly on or within the given plane side.
|
||||
bool IntersectSegmentPlane(const IfcVector3& p,const IfcVector3& n, const IfcVector3& e0,
|
||||
const IfcVector3& e1, bool assumeStartOnWhiteSide, IfcVector3& out)
|
||||
{
|
||||
const IfcVector3 pdelta = e0 - p, seg = e1 - e0;
|
||||
const IfcFloat dotOne = n*seg, dotTwo = -(n*pdelta);
|
||||
|
||||
// if segment ends on plane, do not report a hit. We stay on that side until a following segment starting at this
|
||||
// point leaves the plane through the other side
|
||||
if( std::abs(dotOne + dotTwo) < 1e-6 )
|
||||
return false;
|
||||
|
||||
// if segment starts on the plane, report a hit only if the end lies on the *other* side
|
||||
if( std::abs(dotTwo) < 1e-6 )
|
||||
{
|
||||
if( (assumeStartOnWhiteSide && dotOne + dotTwo < 1e-6) || (!assumeStartOnWhiteSide && dotOne + dotTwo > -1e-6) )
|
||||
{
|
||||
out = e0;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore if segment is parallel to plane and far away from it on either side
|
||||
// Warning: if there's a few thousand of such segments which slowly accumulate beyond the epsilon, no hit would be registered
|
||||
if( std::abs(dotOne) < 1e-6 )
|
||||
return false;
|
||||
|
||||
// t must be in [0..1] if the intersection point is within the given segment
|
||||
const IfcFloat t = dotTwo / dotOne;
|
||||
if( t > 1.0 || t < 0.0 )
|
||||
return false;
|
||||
|
||||
out = e0 + t*seg;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void FilterPolygon(std::vector<IfcVector3>& resultpoly)
|
||||
{
|
||||
if( resultpoly.size() < 3 )
|
||||
{
|
||||
resultpoly.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
IfcVector3 vmin, vmax;
|
||||
ArrayBounds(resultpoly.data(), static_cast<unsigned int>(resultpoly.size()), vmin, vmax);
|
||||
|
||||
// filter our IfcFloat points - those may happen if a point lies
|
||||
// directly on the intersection line or directly on the clipping plane
|
||||
const IfcFloat epsilon = (vmax - vmin).SquareLength() / 1e6f;
|
||||
FuzzyVectorCompare fz(epsilon);
|
||||
std::vector<IfcVector3>::iterator e = std::unique(resultpoly.begin(), resultpoly.end(), fz);
|
||||
|
||||
if( e != resultpoly.end() )
|
||||
resultpoly.erase(e, resultpoly.end());
|
||||
|
||||
if( !resultpoly.empty() && fz(resultpoly.front(), resultpoly.back()) )
|
||||
resultpoly.pop_back();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void WritePolygon(std::vector<IfcVector3>& resultpoly, TempMesh& result)
|
||||
{
|
||||
FilterPolygon(resultpoly);
|
||||
|
||||
if( resultpoly.size() > 2 )
|
||||
{
|
||||
result.mVerts.insert(result.mVerts.end(), resultpoly.begin(), resultpoly.end());
|
||||
result.mVertcnt.push_back(static_cast<unsigned int>(resultpoly.size()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessBooleanHalfSpaceDifference(const Schema_2x3::IfcHalfSpaceSolid* hs, TempMesh& result,
|
||||
const TempMesh& first_operand,
|
||||
ConversionData& /*conv*/)
|
||||
{
|
||||
ai_assert(hs != NULL);
|
||||
|
||||
const Schema_2x3::IfcPlane* const plane = hs->BaseSurface->ToPtr<Schema_2x3::IfcPlane>();
|
||||
if(!plane) {
|
||||
IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid");
|
||||
return;
|
||||
}
|
||||
|
||||
// extract plane base position vector and normal vector
|
||||
IfcVector3 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<IfcVector3>& in = first_operand.mVerts;
|
||||
std::vector<IfcVector3>& outvert = result.mVerts;
|
||||
|
||||
std::vector<unsigned int>::const_iterator begin = first_operand.mVertcnt.begin(),
|
||||
end = first_operand.mVertcnt.end(), iit;
|
||||
|
||||
outvert.reserve(in.size());
|
||||
result.mVertcnt.reserve(first_operand.mVertcnt.size());
|
||||
|
||||
unsigned int vidx = 0;
|
||||
for(iit = begin; iit != end; vidx += *iit++) {
|
||||
|
||||
unsigned int newcount = 0;
|
||||
bool isAtWhiteSide = (in[vidx] - p) * n > -1e-6;
|
||||
for( unsigned int i = 0; i < *iit; ++i ) {
|
||||
const IfcVector3& e0 = in[vidx + i], e1 = in[vidx + (i + 1) % *iit];
|
||||
|
||||
// does the next segment intersect the plane?
|
||||
IfcVector3 isectpos;
|
||||
if( IntersectSegmentPlane(p, n, e0, e1, isAtWhiteSide, isectpos) ) {
|
||||
if( isAtWhiteSide ) {
|
||||
// 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;
|
||||
}
|
||||
isAtWhiteSide = !isAtWhiteSide;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( isAtWhiteSide ) {
|
||||
outvert.push_back(e0);
|
||||
++newcount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!newcount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IfcVector3 vmin,vmax;
|
||||
ArrayBounds(&*(outvert.end()-newcount),newcount,vmin,vmax);
|
||||
|
||||
// filter our IfcFloat points - those may happen if a point lies
|
||||
// directly on the intersection line. However, due to IfcFloat
|
||||
// precision a bitwise comparison is not feasible to detect
|
||||
// this case.
|
||||
const IfcFloat epsilon = (vmax-vmin).SquareLength() / 1e6f;
|
||||
FuzzyVectorCompare fz(epsilon);
|
||||
|
||||
std::vector<IfcVector3>::iterator e = std::unique( outvert.end()-newcount, outvert.end(), fz );
|
||||
|
||||
if (e != outvert.end()) {
|
||||
newcount -= static_cast<unsigned int>(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.mVertcnt.push_back(newcount);
|
||||
}
|
||||
else while(newcount-->0) {
|
||||
result.mVerts.pop_back();
|
||||
}
|
||||
|
||||
}
|
||||
IFCImporter::LogDebug("generating CSG geometry by plane clipping (IfcBooleanClippingResult)");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Check if e0-e1 intersects a sub-segment of the given boundary line.
|
||||
// note: this functions works on 3D vectors, but performs its intersection checks solely in xy.
|
||||
// New version takes the supposed inside/outside state as a parameter and treats corner cases as if
|
||||
// the line stays on that side. This should make corner cases more stable.
|
||||
// Two million assumptions! Boundary should have all z at 0.0, will be treated as closed, should not have
|
||||
// segments with length <1e-6, self-intersecting might break the corner case handling... just don't go there, ok?
|
||||
bool IntersectsBoundaryProfile(const IfcVector3& e0, const IfcVector3& e1, const std::vector<IfcVector3>& boundary,
|
||||
const bool isStartAssumedInside, std::vector<std::pair<size_t, IfcVector3> >& intersect_results,
|
||||
const bool halfOpen = false)
|
||||
{
|
||||
ai_assert(intersect_results.empty());
|
||||
|
||||
// determine winding order - necessary to detect segments going "inwards" or "outwards" from a point directly on the border
|
||||
// positive sum of angles means clockwise order when looking down the -Z axis
|
||||
IfcFloat windingOrder = 0.0;
|
||||
for( size_t i = 0, bcount = boundary.size(); i < bcount; ++i ) {
|
||||
IfcVector3 b01 = boundary[(i + 1) % bcount] - boundary[i];
|
||||
IfcVector3 b12 = boundary[(i + 2) % bcount] - boundary[(i + 1) % bcount];
|
||||
IfcVector3 b1_side = IfcVector3(b01.y, -b01.x, 0.0); // rotated 90° clockwise in Z plane
|
||||
// Warning: rough estimate only. A concave poly with lots of small segments each featuring a small counter rotation
|
||||
// could fool the accumulation. Correct implementation would be sum( acos( b01 * b2) * sign( b12 * b1_side))
|
||||
windingOrder += (b1_side.x*b12.x + b1_side.y*b12.y);
|
||||
}
|
||||
windingOrder = windingOrder > 0.0 ? 1.0 : -1.0;
|
||||
|
||||
const IfcVector3 e = e1 - e0;
|
||||
|
||||
for( size_t i = 0, bcount = boundary.size(); i < bcount; ++i ) {
|
||||
// boundary segment i: b0-b1
|
||||
const IfcVector3& b0 = boundary[i];
|
||||
const IfcVector3& b1 = boundary[(i + 1) % bcount];
|
||||
IfcVector3 b = b1 - b0;
|
||||
|
||||
// segment-segment intersection
|
||||
// solve b0 + b*s = e0 + e*t for (s,t)
|
||||
const IfcFloat det = (-b.x * e.y + e.x * b.y);
|
||||
if( std::abs(det) < 1e-6 ) {
|
||||
// no solutions (parallel lines)
|
||||
continue;
|
||||
}
|
||||
IfcFloat b_sqlen_inv = 1.0 / b.SquareLength();
|
||||
|
||||
const IfcFloat x = b0.x - e0.x;
|
||||
const IfcFloat y = b0.y - e0.y;
|
||||
const IfcFloat s = (x*e.y - e.x*y) / det; // scale along boundary edge
|
||||
const IfcFloat t = (x*b.y - b.x*y) / det; // scale along given segment
|
||||
const IfcVector3 p = e0 + e*t;
|
||||
#ifdef ASSIMP_BUILD_DEBUG
|
||||
const IfcVector3 check = b0 + b*s - p;
|
||||
ai_assert((IfcVector2(check.x, check.y)).SquareLength() < 1e-5);
|
||||
#endif
|
||||
|
||||
// also calculate the distance of e0 and e1 to the segment. We need to detect the "starts directly on segment"
|
||||
// and "ends directly at segment" cases
|
||||
bool startsAtSegment, endsAtSegment;
|
||||
{
|
||||
// calculate closest point to each end on the segment, clamp that point to the segment's length, then check
|
||||
// distance to that point. This approach is like testing if e0 is inside a capped cylinder.
|
||||
IfcFloat et0 = (b.x*(e0.x - b0.x) + b.y*(e0.y - b0.y)) * b_sqlen_inv;
|
||||
IfcVector3 closestPosToE0OnBoundary = b0 + std::max(IfcFloat(0.0), std::min(IfcFloat(1.0), et0)) * b;
|
||||
startsAtSegment = (closestPosToE0OnBoundary - IfcVector3(e0.x, e0.y, 0.0)).SquareLength() < 1e-12;
|
||||
IfcFloat et1 = (b.x*(e1.x - b0.x) + b.y*(e1.y - b0.y)) * b_sqlen_inv;
|
||||
IfcVector3 closestPosToE1OnBoundary = b0 + std::max(IfcFloat(0.0), std::min(IfcFloat(1.0), et1)) * b;
|
||||
endsAtSegment = (closestPosToE1OnBoundary - IfcVector3(e1.x, e1.y, 0.0)).SquareLength() < 1e-12;
|
||||
}
|
||||
|
||||
// Line segment ends at boundary -> ignore any hit, it will be handled by possibly following segments
|
||||
if( endsAtSegment && !halfOpen )
|
||||
continue;
|
||||
|
||||
// Line segment starts at boundary -> generate a hit only if following that line would change the INSIDE/OUTSIDE
|
||||
// state. This should catch the case where a connected set of segments has a point directly on the boundary,
|
||||
// one segment not hitting it because it ends there and the next segment not hitting it because it starts there
|
||||
// Should NOT generate a hit if the segment only touches the boundary but turns around and stays inside.
|
||||
if( startsAtSegment )
|
||||
{
|
||||
IfcVector3 inside_dir = IfcVector3(b.y, -b.x, 0.0) * windingOrder;
|
||||
bool isGoingInside = (inside_dir * e) > 0.0;
|
||||
if( isGoingInside == isStartAssumedInside )
|
||||
continue;
|
||||
|
||||
// only insert the point into the list if it is sufficiently far away from the previous intersection point.
|
||||
// This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
|
||||
if( !intersect_results.empty() && intersect_results.back().first == i - 1 )
|
||||
{
|
||||
const IfcVector3 diff = intersect_results.back().second - e0;
|
||||
if( IfcVector2(diff.x, diff.y).SquareLength() < 1e-10 )
|
||||
continue;
|
||||
}
|
||||
intersect_results.push_back(std::make_pair(i, e0));
|
||||
continue;
|
||||
}
|
||||
|
||||
// for a valid intersection, s and t should be in range [0,1]. Including a bit of epsilon on s, potential double
|
||||
// hits on two consecutive boundary segments are filtered
|
||||
if( s >= -1e-6 * b_sqlen_inv && s <= 1.0 + 1e-6*b_sqlen_inv && t >= 0.0 && (t <= 1.0 || halfOpen) )
|
||||
{
|
||||
// only insert the point into the list if it is sufficiently far away from the previous intersection point.
|
||||
// This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
|
||||
if( !intersect_results.empty() && intersect_results.back().first == i - 1 )
|
||||
{
|
||||
const IfcVector3 diff = intersect_results.back().second - p;
|
||||
if( IfcVector2(diff.x, diff.y).SquareLength() < 1e-10 )
|
||||
continue;
|
||||
}
|
||||
intersect_results.push_back(std::make_pair(i, p));
|
||||
}
|
||||
}
|
||||
|
||||
return !intersect_results.empty();
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// note: this functions works on 3D vectors, but performs its intersection checks solely in xy.
|
||||
bool PointInPoly(const IfcVector3& p, const std::vector<IfcVector3>& boundary)
|
||||
{
|
||||
// even-odd algorithm: take a random vector that extends from p to infinite
|
||||
// and counts how many times it intersects edges of the boundary.
|
||||
// because checking for segment intersections is prone to numeric inaccuracies
|
||||
// or double detections (i.e. when hitting multiple adjacent segments at their
|
||||
// shared vertices) we do it thrice with different rays and vote on it.
|
||||
|
||||
// the even-odd algorithm doesn't work for points which lie directly on
|
||||
// the border of the polygon. If any of our attempts produces this result,
|
||||
// we return false immediately.
|
||||
|
||||
std::vector<std::pair<size_t, IfcVector3> > intersected_boundary;
|
||||
size_t votes = 0;
|
||||
|
||||
IntersectsBoundaryProfile(p, p + IfcVector3(1.0, 0, 0), boundary, true, intersected_boundary, true);
|
||||
votes += intersected_boundary.size() % 2;
|
||||
|
||||
intersected_boundary.clear();
|
||||
IntersectsBoundaryProfile(p, p + IfcVector3(0, 1.0, 0), boundary, true, intersected_boundary, true);
|
||||
votes += intersected_boundary.size() % 2;
|
||||
|
||||
intersected_boundary.clear();
|
||||
IntersectsBoundaryProfile(p, p + IfcVector3(0.6, -0.6, 0.0), boundary, true, intersected_boundary, true);
|
||||
votes += intersected_boundary.size() % 2;
|
||||
|
||||
return votes > 1;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPolygonalBoundedHalfSpace* hs, TempMesh& result,
|
||||
const TempMesh& first_operand,
|
||||
ConversionData& conv)
|
||||
{
|
||||
ai_assert(hs != NULL);
|
||||
|
||||
const Schema_2x3::IfcPlane* const plane = hs->BaseSurface->ToPtr<Schema_2x3::IfcPlane>();
|
||||
if(!plane) {
|
||||
IFCImporter::LogError("expected IfcPlane as base surface for the IfcHalfSpaceSolid");
|
||||
return;
|
||||
}
|
||||
|
||||
// extract plane base position vector and normal vector
|
||||
IfcVector3 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;
|
||||
}
|
||||
|
||||
n.Normalize();
|
||||
|
||||
// obtain the polygonal bounding volume
|
||||
std::shared_ptr<TempMesh> profile = std::shared_ptr<TempMesh>(new TempMesh());
|
||||
if(!ProcessCurve(hs->PolygonalBoundary, *profile.get(), conv)) {
|
||||
IFCImporter::LogError("expected valid polyline for boundary of boolean halfspace");
|
||||
return;
|
||||
}
|
||||
|
||||
// determine winding order by calculating the normal.
|
||||
IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(profile->mVerts.data(), profile->mVerts.size());
|
||||
|
||||
IfcMatrix4 proj_inv;
|
||||
ConvertAxisPlacement(proj_inv,hs->Position);
|
||||
|
||||
// and map everything into a plane coordinate space so all intersection
|
||||
// tests can be done in 2D space.
|
||||
IfcMatrix4 proj = proj_inv;
|
||||
proj.Inverse();
|
||||
|
||||
// clip the current contents of `meshout` against the plane we obtained from the second operand
|
||||
const std::vector<IfcVector3>& in = first_operand.mVerts;
|
||||
std::vector<IfcVector3>& outvert = result.mVerts;
|
||||
std::vector<unsigned int>& outvertcnt = result.mVertcnt;
|
||||
|
||||
outvert.reserve(in.size());
|
||||
outvertcnt.reserve(first_operand.mVertcnt.size());
|
||||
|
||||
unsigned int vidx = 0;
|
||||
std::vector<unsigned int>::const_iterator begin = first_operand.mVertcnt.begin();
|
||||
std::vector<unsigned int>::const_iterator end = first_operand.mVertcnt.end();
|
||||
std::vector<unsigned int>::const_iterator iit;
|
||||
for( iit = begin; iit != end; vidx += *iit++ )
|
||||
{
|
||||
// Our new approach: we cut the poly along the plane, then we intersect the part on the black side of the plane
|
||||
// against the bounding polygon. All the white parts, and the black part outside the boundary polygon, are kept.
|
||||
std::vector<IfcVector3> whiteside, blackside;
|
||||
|
||||
{
|
||||
const IfcVector3* srcVertices = &in[vidx];
|
||||
const size_t srcVtxCount = *iit;
|
||||
if( srcVtxCount == 0 )
|
||||
continue;
|
||||
|
||||
IfcVector3 polyNormal = TempMesh::ComputePolygonNormal(srcVertices, srcVtxCount, true);
|
||||
|
||||
// if the poly is parallel to the plane, put it completely on the black or white side
|
||||
if( std::abs(polyNormal * n) > 0.9999 )
|
||||
{
|
||||
bool isOnWhiteSide = (srcVertices[0] - p) * n > -1e-6;
|
||||
std::vector<IfcVector3>& targetSide = isOnWhiteSide ? whiteside : blackside;
|
||||
targetSide.insert(targetSide.end(), srcVertices, srcVertices + srcVtxCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise start building one polygon for each side. Whenever the current line segment intersects the plane
|
||||
// we put a point there as an end of the current segment. Then we switch to the other side, put a point there, too,
|
||||
// as a beginning of the current segment, and simply continue accumulating vertices.
|
||||
bool isCurrentlyOnWhiteSide = ((srcVertices[0]) - p) * n > -1e-6;
|
||||
for( size_t a = 0; a < srcVtxCount; ++a )
|
||||
{
|
||||
IfcVector3 e0 = srcVertices[a];
|
||||
IfcVector3 e1 = srcVertices[(a + 1) % srcVtxCount];
|
||||
IfcVector3 ei;
|
||||
|
||||
// put starting point to the current mesh
|
||||
std::vector<IfcVector3>& trgt = isCurrentlyOnWhiteSide ? whiteside : blackside;
|
||||
trgt.push_back(srcVertices[a]);
|
||||
|
||||
// if there's an intersection, put an end vertex there, switch to the other side's mesh,
|
||||
// and add a starting vertex there, too
|
||||
bool isPlaneHit = IntersectSegmentPlane(p, n, e0, e1, isCurrentlyOnWhiteSide, ei);
|
||||
if( isPlaneHit )
|
||||
{
|
||||
if( trgt.empty() || (trgt.back() - ei).SquareLength() > 1e-12 )
|
||||
trgt.push_back(ei);
|
||||
isCurrentlyOnWhiteSide = !isCurrentlyOnWhiteSide;
|
||||
std::vector<IfcVector3>& newtrgt = isCurrentlyOnWhiteSide ? whiteside : blackside;
|
||||
newtrgt.push_back(ei);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the part on the white side can be written into the target mesh right away
|
||||
WritePolygon(whiteside, result);
|
||||
|
||||
// The black part is the piece we need to get rid of, but only the part of it within the boundary polygon.
|
||||
// So we now need to construct all the polygons that result from BlackSidePoly minus BoundaryPoly.
|
||||
FilterPolygon(blackside);
|
||||
|
||||
// Complicated, II. We run along the polygon. a) When we're inside the boundary, we run on until we hit an
|
||||
// intersection, which means we're leaving it. We then start a new out poly there. b) When we're outside the
|
||||
// boundary, we start collecting vertices until we hit an intersection, then we run along the boundary until we hit
|
||||
// an intersection, then we switch back to the poly and run on on this one again, and so on until we got a closed
|
||||
// loop. Then we continue with the path we left to catch potential additional polys on the other side of the
|
||||
// boundary as described in a)
|
||||
if( !blackside.empty() )
|
||||
{
|
||||
// poly edge index, intersection point, edge index in boundary poly
|
||||
std::vector<std::tuple<size_t, IfcVector3, size_t> > intersections;
|
||||
bool startedInside = PointInPoly(proj * blackside.front(), profile->mVerts);
|
||||
bool isCurrentlyInside = startedInside;
|
||||
|
||||
std::vector<std::pair<size_t, IfcVector3> > intersected_boundary;
|
||||
|
||||
for( size_t a = 0; a < blackside.size(); ++a )
|
||||
{
|
||||
const IfcVector3 e0 = proj * blackside[a];
|
||||
const IfcVector3 e1 = proj * blackside[(a + 1) % blackside.size()];
|
||||
|
||||
intersected_boundary.clear();
|
||||
IntersectsBoundaryProfile(e0, e1, profile->mVerts, isCurrentlyInside, intersected_boundary);
|
||||
// sort the hits by distance from e0 to get the correct in/out/in sequence. Manually :-( I miss you, C++11.
|
||||
if( intersected_boundary.size() > 1 )
|
||||
{
|
||||
bool keepSorting = true;
|
||||
while( keepSorting )
|
||||
{
|
||||
keepSorting = false;
|
||||
for( size_t b = 0; b < intersected_boundary.size() - 1; ++b )
|
||||
{
|
||||
if( (intersected_boundary[b + 1].second - e0).SquareLength() < (intersected_boundary[b].second - e0).SquareLength() )
|
||||
{
|
||||
keepSorting = true;
|
||||
std::swap(intersected_boundary[b + 1], intersected_boundary[b]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// now add them to the list of intersections
|
||||
for( size_t b = 0; b < intersected_boundary.size(); ++b )
|
||||
intersections.push_back(std::make_tuple(a, proj_inv * intersected_boundary[b].second, intersected_boundary[b].first));
|
||||
|
||||
// and calculate our new inside/outside state
|
||||
if( intersected_boundary.size() & 1 )
|
||||
isCurrentlyInside = !isCurrentlyInside;
|
||||
}
|
||||
|
||||
// we got a list of in-out-combinations of intersections. That should be an even number of intersections, or
|
||||
// we're fucked.
|
||||
if( (intersections.size() & 1) != 0 )
|
||||
{
|
||||
IFCImporter::LogWarn("Odd number of intersections, can't work with that. Omitting half space boundary check.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if( intersections.size() > 1 )
|
||||
{
|
||||
// If we started outside, the first intersection is a out->in intersection. Cycle them so that it
|
||||
// starts with an intersection leaving the boundary
|
||||
if( !startedInside )
|
||||
for( size_t b = 0; b < intersections.size() - 1; ++b )
|
||||
std::swap(intersections[b], intersections[(b + intersections.size() - 1) % intersections.size()]);
|
||||
|
||||
// Filter pairs of out->in->out that lie too close to each other.
|
||||
for( size_t a = 0; intersections.size() > 0 && a < intersections.size() - 1; /**/ )
|
||||
{
|
||||
if( (std::get<1>(intersections[a]) - std::get<1>(intersections[(a + 1) % intersections.size()])).SquareLength() < 1e-10 )
|
||||
intersections.erase(intersections.begin() + a, intersections.begin() + a + 2);
|
||||
else
|
||||
a++;
|
||||
}
|
||||
if( intersections.size() > 1 && (std::get<1>(intersections.back()) - std::get<1>(intersections.front())).SquareLength() < 1e-10 )
|
||||
{
|
||||
intersections.pop_back(); intersections.erase(intersections.begin());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// no intersections at all: either completely inside the boundary, so everything gets discarded, or completely outside.
|
||||
// in the latter case we're implementional lost. I'm simply going to ignore this, so a large poly will not get any
|
||||
// holes if the boundary is smaller and does not touch it anywhere.
|
||||
if( intersections.empty() )
|
||||
{
|
||||
// starting point was outside -> everything is outside the boundary -> nothing is clipped -> add black side
|
||||
// to result mesh unchanged
|
||||
if( !startedInside )
|
||||
{
|
||||
outvertcnt.push_back(static_cast<unsigned int>(blackside.size()));
|
||||
outvert.insert(outvert.end(), blackside.begin(), blackside.end());
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// starting point was inside the boundary -> everything is inside the boundary -> nothing is spared from the
|
||||
// clipping -> nothing left to add to the result mesh
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// determine the direction in which we're marching along the boundary polygon. If the src poly is faced upwards
|
||||
// and the boundary is also winded this way, we need to march *backwards* on the boundary.
|
||||
const IfcVector3 polyNormal = IfcMatrix3(proj) * TempMesh::ComputePolygonNormal(blackside.data(), blackside.size());
|
||||
bool marchBackwardsOnBoundary = (profileNormal * polyNormal) >= 0.0;
|
||||
|
||||
// Build closed loops from these intersections. Starting from an intersection leaving the boundary we
|
||||
// walk along the polygon to the next intersection (which should be an IS entering the boundary poly).
|
||||
// From there we walk along the boundary until we hit another intersection leaving the boundary,
|
||||
// walk along the poly to the next IS and so on until we're back at the starting point.
|
||||
// We remove every intersection we "used up", so any remaining intersection is the start of a new loop.
|
||||
while( !intersections.empty() )
|
||||
{
|
||||
std::vector<IfcVector3> resultpoly;
|
||||
size_t currentIntersecIdx = 0;
|
||||
|
||||
while( true )
|
||||
{
|
||||
ai_assert(intersections.size() > currentIntersecIdx + 1);
|
||||
std::tuple<size_t, IfcVector3, size_t> currintsec = intersections[currentIntersecIdx + 0];
|
||||
std::tuple<size_t, IfcVector3, size_t> nextintsec = intersections[currentIntersecIdx + 1];
|
||||
intersections.erase(intersections.begin() + currentIntersecIdx, intersections.begin() + currentIntersecIdx + 2);
|
||||
|
||||
// we start with an in->out intersection
|
||||
resultpoly.push_back(std::get<1>(currintsec));
|
||||
// climb along the polygon to the next intersection, which should be an out->in
|
||||
size_t numPolyPoints = (std::get<0>(currintsec) > std::get<0>(nextintsec) ? blackside.size() : 0)
|
||||
+ std::get<0>(nextintsec) - std::get<0>(currintsec);
|
||||
for( size_t a = 1; a <= numPolyPoints; ++a )
|
||||
resultpoly.push_back(blackside[(std::get<0>(currintsec) + a) % blackside.size()]);
|
||||
// put the out->in intersection
|
||||
resultpoly.push_back(std::get<1>(nextintsec));
|
||||
|
||||
// generate segments along the boundary polygon that lie in the poly's plane until we hit another intersection
|
||||
IfcVector3 startingPoint = proj * std::get<1>(nextintsec);
|
||||
size_t currentBoundaryEdgeIdx = (std::get<2>(nextintsec) + (marchBackwardsOnBoundary ? 1 : 0)) % profile->mVerts.size();
|
||||
size_t nextIntsecIdx = SIZE_MAX;
|
||||
while( nextIntsecIdx == SIZE_MAX )
|
||||
{
|
||||
IfcFloat t = 1e10;
|
||||
|
||||
size_t nextBoundaryEdgeIdx = marchBackwardsOnBoundary ? (currentBoundaryEdgeIdx + profile->mVerts.size() - 1) : currentBoundaryEdgeIdx + 1;
|
||||
nextBoundaryEdgeIdx %= profile->mVerts.size();
|
||||
// vertices of the current boundary segments
|
||||
IfcVector3 currBoundaryPoint = profile->mVerts[currentBoundaryEdgeIdx];
|
||||
IfcVector3 nextBoundaryPoint = profile->mVerts[nextBoundaryEdgeIdx];
|
||||
// project the two onto the polygon
|
||||
if( std::abs(polyNormal.z) > 1e-5 )
|
||||
{
|
||||
currBoundaryPoint.z = startingPoint.z + (currBoundaryPoint.x - startingPoint.x) * polyNormal.x/polyNormal.z + (currBoundaryPoint.y - startingPoint.y) * polyNormal.y/polyNormal.z;
|
||||
nextBoundaryPoint.z = startingPoint.z + (nextBoundaryPoint.x - startingPoint.x) * polyNormal.x/polyNormal.z + (nextBoundaryPoint.y - startingPoint.y) * polyNormal.y/polyNormal.z;
|
||||
}
|
||||
|
||||
// build a direction that goes along the boundary border but lies in the poly plane
|
||||
IfcVector3 boundaryPlaneNormal = ((nextBoundaryPoint - currBoundaryPoint) ^ profileNormal).Normalize();
|
||||
IfcVector3 dirAtPolyPlane = (boundaryPlaneNormal ^ polyNormal).Normalize() * (marchBackwardsOnBoundary ? -1.0 : 1.0);
|
||||
// if we can project the direction to the plane, we can calculate a maximum marching distance along that dir
|
||||
// until we finish that boundary segment and continue on the next
|
||||
if( std::abs(polyNormal.z) > 1e-5 )
|
||||
{
|
||||
t = std::min(t, (nextBoundaryPoint - startingPoint).Length());
|
||||
}
|
||||
|
||||
// check if the direction hits the loop start - if yes, we got a poly to output
|
||||
IfcVector3 dirToThatPoint = proj * resultpoly.front() - startingPoint;
|
||||
IfcFloat tpt = dirToThatPoint * dirAtPolyPlane;
|
||||
if( tpt > -1e-6 && tpt <= t && (dirToThatPoint - tpt * dirAtPolyPlane).SquareLength() < 1e-10 )
|
||||
{
|
||||
nextIntsecIdx = intersections.size(); // dirty hack to end marching along the boundary and signal the end of the loop
|
||||
t = tpt;
|
||||
}
|
||||
|
||||
// also check if the direction hits any in->out intersections earlier. If we hit one, we can switch back
|
||||
// to marching along the poly border from that intersection point
|
||||
for( size_t a = 0; a < intersections.size(); a += 2 )
|
||||
{
|
||||
dirToThatPoint = proj * std::get<1>(intersections[a]) - startingPoint;
|
||||
tpt = dirToThatPoint * dirAtPolyPlane;
|
||||
if( tpt > -1e-6 && tpt <= t && (dirToThatPoint - tpt * dirAtPolyPlane).SquareLength() < 1e-10 )
|
||||
{
|
||||
nextIntsecIdx = a; // switch back to poly and march on from this in->out intersection
|
||||
t = tpt;
|
||||
}
|
||||
}
|
||||
|
||||
// if we keep marching on the boundary, put the segment end point to the result poly and well... keep marching
|
||||
if( nextIntsecIdx == SIZE_MAX )
|
||||
{
|
||||
resultpoly.push_back(proj_inv * nextBoundaryPoint);
|
||||
currentBoundaryEdgeIdx = nextBoundaryEdgeIdx;
|
||||
startingPoint = nextBoundaryPoint;
|
||||
}
|
||||
|
||||
// quick endless loop check
|
||||
if( resultpoly.size() > blackside.size() + profile->mVerts.size() )
|
||||
{
|
||||
IFCImporter::LogError("Encountered endless loop while clipping polygon against poly-bounded half space.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// we're back on the poly - if this is the intersection we started from, we got a closed loop.
|
||||
if( nextIntsecIdx >= intersections.size() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise it's another intersection. Continue marching from there.
|
||||
currentIntersecIdx = nextIntsecIdx;
|
||||
}
|
||||
|
||||
WritePolygon(resultpoly, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
IFCImporter::LogDebug("generating CSG geometry by plane clipping with polygonal bounding (IfcBooleanClippingResult)");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid* as, TempMesh& result,
|
||||
const TempMesh& first_operand,
|
||||
ConversionData& conv)
|
||||
{
|
||||
ai_assert(as != NULL);
|
||||
|
||||
// This case is handled by reduction to an instance of the quadrify() algorithm.
|
||||
// Obviously, this won't work for arbitrarily complex cases. In fact, the first
|
||||
// operand should be near-planar. Luckily, this is usually the case in Ifc
|
||||
// buildings.
|
||||
|
||||
std::shared_ptr<TempMesh> meshtmp = std::shared_ptr<TempMesh>(new TempMesh());
|
||||
ProcessExtrudedAreaSolid(*as,*meshtmp,conv,false);
|
||||
|
||||
std::vector<TempOpening> openings(1, TempOpening(as,IfcVector3(0,0,0),meshtmp,std::shared_ptr<TempMesh>()));
|
||||
|
||||
result = first_operand;
|
||||
|
||||
TempMesh temp;
|
||||
|
||||
std::vector<IfcVector3>::const_iterator vit = first_operand.mVerts.begin();
|
||||
for(unsigned int pcount : first_operand.mVertcnt) {
|
||||
temp.Clear();
|
||||
|
||||
temp.mVerts.insert(temp.mVerts.end(), vit, vit + pcount);
|
||||
temp.mVertcnt.push_back(pcount);
|
||||
|
||||
// The algorithms used to generate mesh geometry sometimes
|
||||
// spit out lines or other degenerates which must be
|
||||
// filtered to avoid running into assertions later on.
|
||||
|
||||
// ComputePolygonNormal returns the Newell normal, so the
|
||||
// length of the normal is the area of the polygon.
|
||||
const IfcVector3& normal = temp.ComputeLastPolygonNormal(false);
|
||||
if (normal.SquareLength() < static_cast<IfcFloat>(1e-5)) {
|
||||
IFCImporter::LogWarn("skipping degenerate polygon (ProcessBooleanExtrudedAreaSolidDifference)");
|
||||
continue;
|
||||
}
|
||||
|
||||
GenerateOpenings(openings, std::vector<IfcVector3>(1,IfcVector3(1,0,0)), temp, false, true);
|
||||
result.Append(temp);
|
||||
|
||||
vit += pcount;
|
||||
}
|
||||
|
||||
IFCImporter::LogDebug("generating CSG geometry by geometric difference to a solid (IfcExtrudedAreaSolid)");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessBoolean(const Schema_2x3::IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv)
|
||||
{
|
||||
// supported CSG operations:
|
||||
// DIFFERENCE
|
||||
if(const Schema_2x3::IfcBooleanResult* const clip = boolean.ToPtr<Schema_2x3::IfcBooleanResult>()) {
|
||||
if(clip->Operator != "DIFFERENCE") {
|
||||
IFCImporter::LogWarn("encountered unsupported boolean operator: " + (std::string)clip->Operator);
|
||||
return;
|
||||
}
|
||||
|
||||
// supported cases (1st operand):
|
||||
// IfcBooleanResult -- call ProcessBoolean recursively
|
||||
// IfcSweptAreaSolid -- obtain polygonal geometry first
|
||||
|
||||
// supported cases (2nd operand):
|
||||
// IfcHalfSpaceSolid -- easy, clip against plane
|
||||
// IfcExtrudedAreaSolid -- reduce to an instance of the quadrify() algorithm
|
||||
|
||||
|
||||
const Schema_2x3::IfcHalfSpaceSolid* const hs = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcHalfSpaceSolid>(conv.db);
|
||||
const Schema_2x3::IfcExtrudedAreaSolid* const as = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcExtrudedAreaSolid>(conv.db);
|
||||
if(!hs && !as) {
|
||||
IFCImporter::LogError("expected IfcHalfSpaceSolid or IfcExtrudedAreaSolid as second clipping operand");
|
||||
return;
|
||||
}
|
||||
|
||||
TempMesh first_operand;
|
||||
if(const Schema_2x3::IfcBooleanResult* const op0 = clip->FirstOperand->ResolveSelectPtr<Schema_2x3::IfcBooleanResult>(conv.db)) {
|
||||
ProcessBoolean(*op0,first_operand,conv);
|
||||
}
|
||||
else if (const Schema_2x3::IfcSweptAreaSolid* const swept = clip->FirstOperand->ResolveSelectPtr<Schema_2x3::IfcSweptAreaSolid>(conv.db)) {
|
||||
ProcessSweptAreaSolid(*swept,first_operand,conv);
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogError("expected IfcSweptAreaSolid or IfcBooleanResult as first clipping operand");
|
||||
return;
|
||||
}
|
||||
|
||||
if(hs) {
|
||||
|
||||
const Schema_2x3::IfcPolygonalBoundedHalfSpace* const hs_bounded = clip->SecondOperand->ResolveSelectPtr<Schema_2x3::IfcPolygonalBoundedHalfSpace>(conv.db);
|
||||
if (hs_bounded) {
|
||||
ProcessPolygonalBoundedBooleanHalfSpaceDifference(hs_bounded, result, first_operand, conv);
|
||||
}
|
||||
else {
|
||||
ProcessBooleanHalfSpaceDifference(hs, result, first_operand, conv);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ProcessBooleanExtrudedAreaSolidDifference(as, result, first_operand, conv);
|
||||
}
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogWarn("skipping unknown IfcBooleanResult entity, type is " + boolean.GetClassName());
|
||||
}
|
||||
}
|
||||
|
||||
} // ! IFC
|
||||
} // ! Assimp
|
||||
|
||||
#endif
|
||||
|
||||
616
thirdparty/assimp/code/Importer/IFC/IFCCurve.cpp
vendored
Normal file
616
thirdparty/assimp/code/Importer/IFC/IFCCurve.cpp
vendored
Normal file
@@ -0,0 +1,616 @@
|
||||
/*
|
||||
Open Asset Import Library (assimp)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2019, assimp 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 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
|
||||
*/
|
||||
|
||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||
#include "IFCUtil.h"
|
||||
|
||||
namespace Assimp {
|
||||
namespace IFC {
|
||||
namespace {
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// Conic is the base class for Circle and Ellipse
|
||||
// --------------------------------------------------------------------------------
|
||||
class Conic : public Curve {
|
||||
public:
|
||||
// --------------------------------------------------
|
||||
Conic(const Schema_2x3::IfcConic& entity, ConversionData& conv)
|
||||
: Curve(entity,conv) {
|
||||
IfcMatrix4 trafo;
|
||||
ConvertAxisPlacement(trafo,*entity.Position,conv);
|
||||
|
||||
// for convenience, extract the matrix rows
|
||||
location = IfcVector3(trafo.a4,trafo.b4,trafo.c4);
|
||||
p[0] = IfcVector3(trafo.a1,trafo.b1,trafo.c1);
|
||||
p[1] = IfcVector3(trafo.a2,trafo.b2,trafo.c2);
|
||||
p[2] = IfcVector3(trafo.a3,trafo.b3,trafo.c3);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
bool IsClosed() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
||||
ai_assert( InRange( a ) );
|
||||
ai_assert( InRange( b ) );
|
||||
|
||||
a *= conv.angle_scale;
|
||||
b *= conv.angle_scale;
|
||||
|
||||
a = std::fmod(a,static_cast<IfcFloat>( AI_MATH_TWO_PI ));
|
||||
b = std::fmod(b,static_cast<IfcFloat>( AI_MATH_TWO_PI ));
|
||||
const IfcFloat setting = static_cast<IfcFloat>( AI_MATH_PI * conv.settings.conicSamplingAngle / 180.0 );
|
||||
return static_cast<size_t>( std::ceil(std::abs( b-a)) / setting);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
ParamRange GetParametricRange() const {
|
||||
return std::make_pair(static_cast<IfcFloat>( 0. ), static_cast<IfcFloat>( AI_MATH_TWO_PI / conv.angle_scale ));
|
||||
}
|
||||
|
||||
protected:
|
||||
IfcVector3 location, p[3];
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// Circle
|
||||
// --------------------------------------------------------------------------------
|
||||
class Circle : public Conic {
|
||||
public:
|
||||
// --------------------------------------------------
|
||||
Circle(const Schema_2x3::IfcCircle& entity, ConversionData& conv)
|
||||
: Conic(entity,conv)
|
||||
, entity(entity)
|
||||
{
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
IfcVector3 Eval(IfcFloat u) const {
|
||||
u = -conv.angle_scale * u;
|
||||
return location + static_cast<IfcFloat>(entity.Radius)*(static_cast<IfcFloat>(std::cos(u))*p[0] +
|
||||
static_cast<IfcFloat>(std::sin(u))*p[1]);
|
||||
}
|
||||
|
||||
private:
|
||||
const Schema_2x3::IfcCircle& entity;
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// Ellipse
|
||||
// --------------------------------------------------------------------------------
|
||||
class Ellipse : public Conic {
|
||||
public:
|
||||
// --------------------------------------------------
|
||||
Ellipse(const Schema_2x3::IfcEllipse& entity, ConversionData& conv)
|
||||
: Conic(entity,conv)
|
||||
, entity(entity) {
|
||||
// empty
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
IfcVector3 Eval(IfcFloat u) const {
|
||||
u = -conv.angle_scale * u;
|
||||
return location + static_cast<IfcFloat>(entity.SemiAxis1)*static_cast<IfcFloat>(std::cos(u))*p[0] +
|
||||
static_cast<IfcFloat>(entity.SemiAxis2)*static_cast<IfcFloat>(std::sin(u))*p[1];
|
||||
}
|
||||
|
||||
private:
|
||||
const Schema_2x3::IfcEllipse& entity;
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// Line
|
||||
// --------------------------------------------------------------------------------
|
||||
class Line : public Curve {
|
||||
public:
|
||||
// --------------------------------------------------
|
||||
Line(const Schema_2x3::IfcLine& entity, ConversionData& conv)
|
||||
: Curve(entity,conv) {
|
||||
ConvertCartesianPoint(p,entity.Pnt);
|
||||
ConvertVector(v,entity.Dir);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
bool IsClosed() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
IfcVector3 Eval(IfcFloat u) const {
|
||||
return p + u*v;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
||||
ai_assert( InRange( a ) );
|
||||
ai_assert( InRange( b ) );
|
||||
// two points are always sufficient for a line segment
|
||||
return a==b ? 1 : 2;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------
|
||||
void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
|
||||
ai_assert( InRange( a ) );
|
||||
ai_assert( InRange( b ) );
|
||||
|
||||
if (a == b) {
|
||||
out.mVerts.push_back(Eval(a));
|
||||
return;
|
||||
}
|
||||
out.mVerts.reserve(out.mVerts.size()+2);
|
||||
out.mVerts.push_back(Eval(a));
|
||||
out.mVerts.push_back(Eval(b));
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
ParamRange GetParametricRange() const {
|
||||
const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity();
|
||||
|
||||
return std::make_pair(-inf,+inf);
|
||||
}
|
||||
|
||||
private:
|
||||
IfcVector3 p,v;
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// CompositeCurve joins multiple smaller, bounded curves
|
||||
// --------------------------------------------------------------------------------
|
||||
class CompositeCurve : public BoundedCurve {
|
||||
typedef std::pair< std::shared_ptr< BoundedCurve >, bool > CurveEntry;
|
||||
|
||||
public:
|
||||
// --------------------------------------------------
|
||||
CompositeCurve(const Schema_2x3::IfcCompositeCurve& entity, ConversionData& conv)
|
||||
: BoundedCurve(entity,conv)
|
||||
, total() {
|
||||
curves.reserve(entity.Segments.size());
|
||||
for(const Schema_2x3::IfcCompositeCurveSegment& curveSegment :entity.Segments) {
|
||||
// according to the specification, this must be a bounded curve
|
||||
std::shared_ptr< Curve > cv(Curve::Convert(curveSegment.ParentCurve,conv));
|
||||
std::shared_ptr< BoundedCurve > bc = std::dynamic_pointer_cast<BoundedCurve>(cv);
|
||||
|
||||
if (!bc) {
|
||||
IFCImporter::LogError("expected segment of composite curve to be a bounded curve");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( (std::string)curveSegment.Transition != "CONTINUOUS" ) {
|
||||
IFCImporter::LogDebug("ignoring transition code on composite curve segment, only continuous transitions are supported");
|
||||
}
|
||||
|
||||
curves.push_back( CurveEntry(bc,IsTrue(curveSegment.SameSense)) );
|
||||
total += bc->GetParametricRangeDelta();
|
||||
}
|
||||
|
||||
if (curves.empty()) {
|
||||
throw CurveError("empty composite curve");
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
IfcVector3 Eval(IfcFloat u) const {
|
||||
if (curves.empty()) {
|
||||
return IfcVector3();
|
||||
}
|
||||
|
||||
IfcFloat acc = 0;
|
||||
for(const CurveEntry& entry : curves) {
|
||||
const ParamRange& range = entry.first->GetParametricRange();
|
||||
const IfcFloat delta = std::abs(range.second-range.first);
|
||||
if (u < acc+delta) {
|
||||
return entry.first->Eval( entry.second ? (u-acc) + range.first : range.second-(u-acc));
|
||||
}
|
||||
|
||||
acc += delta;
|
||||
}
|
||||
// clamp to end
|
||||
return curves.back().first->Eval(curves.back().first->GetParametricRange().second);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
||||
ai_assert( InRange( a ) );
|
||||
ai_assert( InRange( b ) );
|
||||
size_t cnt = 0;
|
||||
|
||||
IfcFloat acc = 0;
|
||||
for(const CurveEntry& entry : curves) {
|
||||
const ParamRange& range = entry.first->GetParametricRange();
|
||||
const IfcFloat delta = std::abs(range.second-range.first);
|
||||
if (a <= acc+delta && b >= acc) {
|
||||
const IfcFloat at = std::max(static_cast<IfcFloat>( 0. ),a-acc), bt = std::min(delta,b-acc);
|
||||
cnt += entry.first->EstimateSampleCount( entry.second ? at + range.first : range.second - bt, entry.second ? bt + range.first : range.second - at );
|
||||
}
|
||||
|
||||
acc += delta;
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
|
||||
ai_assert( InRange( a ) );
|
||||
ai_assert( InRange( b ) );
|
||||
|
||||
const size_t cnt = EstimateSampleCount(a,b);
|
||||
out.mVerts.reserve(out.mVerts.size() + cnt);
|
||||
|
||||
for(const CurveEntry& entry : curves) {
|
||||
const size_t cnt = out.mVerts.size();
|
||||
entry.first->SampleDiscrete(out);
|
||||
|
||||
if (!entry.second && cnt != out.mVerts.size()) {
|
||||
std::reverse(out.mVerts.begin()+cnt,out.mVerts.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
ParamRange GetParametricRange() const {
|
||||
return std::make_pair(static_cast<IfcFloat>( 0. ),total);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector< CurveEntry > curves;
|
||||
IfcFloat total;
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// TrimmedCurve can be used to trim an unbounded curve to a bounded range
|
||||
// --------------------------------------------------------------------------------
|
||||
class TrimmedCurve : public BoundedCurve {
|
||||
public:
|
||||
// --------------------------------------------------
|
||||
TrimmedCurve(const Schema_2x3::IfcTrimmedCurve& entity, ConversionData& conv)
|
||||
: BoundedCurve(entity,conv),
|
||||
base(std::shared_ptr<const Curve>(Curve::Convert(entity.BasisCurve,conv)))
|
||||
{
|
||||
typedef std::shared_ptr<const STEP::EXPRESS::DataType> Entry;
|
||||
|
||||
// for some reason, trimmed curves can either specify a parametric value
|
||||
// or a point on the curve, or both. And they can even specify which of the
|
||||
// two representations they prefer, even though an information invariant
|
||||
// claims that they must be identical if both are present.
|
||||
// oh well.
|
||||
bool have_param = false, have_point = false;
|
||||
IfcVector3 point;
|
||||
for(const Entry sel :entity.Trim1) {
|
||||
if (const ::Assimp::STEP::EXPRESS::REAL* const r = sel->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
|
||||
range.first = *r;
|
||||
have_param = true;
|
||||
break;
|
||||
}
|
||||
else if (const Schema_2x3::IfcCartesianPoint* const r = sel->ResolveSelectPtr<Schema_2x3::IfcCartesianPoint>(conv.db)) {
|
||||
ConvertCartesianPoint(point,*r);
|
||||
have_point = true;
|
||||
}
|
||||
}
|
||||
if (!have_param) {
|
||||
if (!have_point || !base->ReverseEval(point,range.first)) {
|
||||
throw CurveError("IfcTrimmedCurve: failed to read first trim parameter, ignoring curve");
|
||||
}
|
||||
}
|
||||
have_param = false, have_point = false;
|
||||
for(const Entry sel :entity.Trim2) {
|
||||
if (const ::Assimp::STEP::EXPRESS::REAL* const r = sel->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
|
||||
range.second = *r;
|
||||
have_param = true;
|
||||
break;
|
||||
}
|
||||
else if (const Schema_2x3::IfcCartesianPoint* const r = sel->ResolveSelectPtr<Schema_2x3::IfcCartesianPoint>(conv.db)) {
|
||||
ConvertCartesianPoint(point,*r);
|
||||
have_point = true;
|
||||
}
|
||||
}
|
||||
if (!have_param) {
|
||||
if (!have_point || !base->ReverseEval(point,range.second)) {
|
||||
throw CurveError("IfcTrimmedCurve: failed to read second trim parameter, ignoring curve");
|
||||
}
|
||||
}
|
||||
|
||||
agree_sense = IsTrue(entity.SenseAgreement);
|
||||
if( !agree_sense ) {
|
||||
std::swap(range.first,range.second);
|
||||
}
|
||||
|
||||
// "NOTE In case of a closed curve, it may be necessary to increment t1 or t2
|
||||
// by the parametric length for consistency with the sense flag."
|
||||
if (base->IsClosed()) {
|
||||
if( range.first > range.second ) {
|
||||
range.second += base->GetParametricRangeDelta();
|
||||
}
|
||||
}
|
||||
|
||||
maxval = range.second-range.first;
|
||||
ai_assert(maxval >= 0);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
IfcVector3 Eval(IfcFloat p) const {
|
||||
ai_assert(InRange(p));
|
||||
return base->Eval( TrimParam(p) );
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
||||
ai_assert( InRange( a ) );
|
||||
ai_assert( InRange( b ) );
|
||||
return base->EstimateSampleCount(TrimParam(a),TrimParam(b));
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
void SampleDiscrete(TempMesh& out,IfcFloat a,IfcFloat b) const {
|
||||
ai_assert(InRange(a) && InRange(b));
|
||||
return base->SampleDiscrete(out,TrimParam(a),TrimParam(b));
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
ParamRange GetParametricRange() const {
|
||||
return std::make_pair(static_cast<IfcFloat>( 0. ),maxval);
|
||||
}
|
||||
|
||||
private:
|
||||
// --------------------------------------------------
|
||||
IfcFloat TrimParam(IfcFloat f) const {
|
||||
return agree_sense ? f + range.first : range.second - f;
|
||||
}
|
||||
|
||||
private:
|
||||
ParamRange range;
|
||||
IfcFloat maxval;
|
||||
bool agree_sense;
|
||||
|
||||
std::shared_ptr<const Curve> base;
|
||||
};
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// PolyLine is a 'curve' defined by linear interpolation over a set of discrete points
|
||||
// --------------------------------------------------------------------------------
|
||||
class PolyLine : public BoundedCurve {
|
||||
public:
|
||||
// --------------------------------------------------
|
||||
PolyLine(const Schema_2x3::IfcPolyline& entity, ConversionData& conv)
|
||||
: BoundedCurve(entity,conv)
|
||||
{
|
||||
points.reserve(entity.Points.size());
|
||||
|
||||
IfcVector3 t;
|
||||
for(const Schema_2x3::IfcCartesianPoint& cp : entity.Points) {
|
||||
ConvertCartesianPoint(t,cp);
|
||||
points.push_back(t);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
IfcVector3 Eval(IfcFloat p) const {
|
||||
ai_assert(InRange(p));
|
||||
|
||||
const size_t b = static_cast<size_t>(std::floor(p));
|
||||
if (b == points.size()-1) {
|
||||
return points.back();
|
||||
}
|
||||
|
||||
const IfcFloat d = p-static_cast<IfcFloat>(b);
|
||||
return points[b+1] * d + points[b] * (static_cast<IfcFloat>( 1. )-d);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
||||
ai_assert(InRange(a) && InRange(b));
|
||||
return static_cast<size_t>( std::ceil(b) - std::floor(a) );
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
ParamRange GetParametricRange() const {
|
||||
return std::make_pair(static_cast<IfcFloat>( 0. ),static_cast<IfcFloat>(points.size()-1));
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<IfcVector3> points;
|
||||
};
|
||||
|
||||
} // anon
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
Curve* Curve::Convert(const IFC::Schema_2x3::IfcCurve& curve,ConversionData& conv) {
|
||||
if(curve.ToPtr<Schema_2x3::IfcBoundedCurve>()) {
|
||||
if(const Schema_2x3::IfcPolyline* c = curve.ToPtr<Schema_2x3::IfcPolyline>()) {
|
||||
return new PolyLine(*c,conv);
|
||||
}
|
||||
if(const Schema_2x3::IfcTrimmedCurve* c = curve.ToPtr<Schema_2x3::IfcTrimmedCurve>()) {
|
||||
return new TrimmedCurve(*c,conv);
|
||||
}
|
||||
if(const Schema_2x3::IfcCompositeCurve* c = curve.ToPtr<Schema_2x3::IfcCompositeCurve>()) {
|
||||
return new CompositeCurve(*c,conv);
|
||||
}
|
||||
}
|
||||
|
||||
if(curve.ToPtr<Schema_2x3::IfcConic>()) {
|
||||
if(const Schema_2x3::IfcCircle* c = curve.ToPtr<Schema_2x3::IfcCircle>()) {
|
||||
return new Circle(*c,conv);
|
||||
}
|
||||
if(const Schema_2x3::IfcEllipse* c = curve.ToPtr<Schema_2x3::IfcEllipse>()) {
|
||||
return new Ellipse(*c,conv);
|
||||
}
|
||||
}
|
||||
|
||||
if(const Schema_2x3::IfcLine* c = curve.ToPtr<Schema_2x3::IfcLine>()) {
|
||||
return new Line(*c,conv);
|
||||
}
|
||||
|
||||
// XXX OffsetCurve2D, OffsetCurve3D not currently supported
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef ASSIMP_BUILD_DEBUG
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool Curve::InRange(IfcFloat u) const {
|
||||
const ParamRange range = GetParametricRange();
|
||||
if (IsClosed()) {
|
||||
return true;
|
||||
}
|
||||
const IfcFloat epsilon = Math::getEpsilon<float>();
|
||||
return u - range.first > -epsilon && range.second - u > -epsilon;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
IfcFloat Curve::GetParametricRangeDelta() const {
|
||||
const ParamRange& range = GetParametricRange();
|
||||
return std::abs(range.second - range.first);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
size_t Curve::EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
||||
(void)(a); (void)(b);
|
||||
ai_assert( InRange( a ) );
|
||||
ai_assert( InRange( b ) );
|
||||
|
||||
// arbitrary default value, deriving classes should supply better suited values
|
||||
return 16;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
IfcFloat RecursiveSearch(const Curve* cv, const IfcVector3& val, IfcFloat a, IfcFloat b,
|
||||
unsigned int samples, IfcFloat threshold, unsigned int recurse = 0, unsigned int max_recurse = 15) {
|
||||
ai_assert(samples>1);
|
||||
|
||||
const IfcFloat delta = (b-a)/samples, inf = std::numeric_limits<IfcFloat>::infinity();
|
||||
IfcFloat min_point[2] = {a,b}, min_diff[2] = {inf,inf};
|
||||
IfcFloat runner = a;
|
||||
|
||||
for (unsigned int i = 0; i < samples; ++i, runner += delta) {
|
||||
const IfcFloat diff = (cv->Eval(runner)-val).SquareLength();
|
||||
if (diff < min_diff[0]) {
|
||||
min_diff[1] = min_diff[0];
|
||||
min_point[1] = min_point[0];
|
||||
|
||||
min_diff[0] = diff;
|
||||
min_point[0] = runner;
|
||||
}
|
||||
else if (diff < min_diff[1]) {
|
||||
min_diff[1] = diff;
|
||||
min_point[1] = runner;
|
||||
}
|
||||
}
|
||||
|
||||
ai_assert( min_diff[ 0 ] != inf );
|
||||
ai_assert( min_diff[ 1 ] != inf );
|
||||
if ( std::fabs(a-min_point[0]) < threshold || recurse >= max_recurse) {
|
||||
return min_point[0];
|
||||
}
|
||||
|
||||
// fix for closed curves to take their wrap-over into account
|
||||
if (cv->IsClosed() && std::fabs(min_point[0]-min_point[1]) > cv->GetParametricRangeDelta()*0.5 ) {
|
||||
const Curve::ParamRange& range = cv->GetParametricRange();
|
||||
const IfcFloat wrapdiff = (cv->Eval(range.first)-val).SquareLength();
|
||||
|
||||
if (wrapdiff < min_diff[0]) {
|
||||
const IfcFloat t = min_point[0];
|
||||
min_point[0] = min_point[1] > min_point[0] ? range.first : range.second;
|
||||
min_point[1] = t;
|
||||
}
|
||||
}
|
||||
|
||||
return RecursiveSearch(cv,val,min_point[0],min_point[1],samples,threshold,recurse+1,max_recurse);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool Curve::ReverseEval(const IfcVector3& val, IfcFloat& paramOut) const
|
||||
{
|
||||
// note: the following algorithm is not guaranteed to find the 'right' parameter value
|
||||
// in all possible cases, but it will always return at least some value so this function
|
||||
// will never fail in the default implementation.
|
||||
|
||||
// XXX derive threshold from curve topology
|
||||
static const IfcFloat threshold = 1e-4f;
|
||||
static const unsigned int samples = 16;
|
||||
|
||||
const ParamRange& range = GetParametricRange();
|
||||
paramOut = RecursiveSearch(this,val,range.first,range.second,samples,threshold);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void Curve::SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
|
||||
ai_assert( InRange( a ) );
|
||||
ai_assert( InRange( b ) );
|
||||
|
||||
const size_t cnt = std::max(static_cast<size_t>(0),EstimateSampleCount(a,b));
|
||||
out.mVerts.reserve( out.mVerts.size() + cnt + 1);
|
||||
|
||||
IfcFloat p = a, delta = (b-a)/cnt;
|
||||
for(size_t i = 0; i <= cnt; ++i, p += delta) {
|
||||
out.mVerts.push_back(Eval(p));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool BoundedCurve::IsClosed() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void BoundedCurve::SampleDiscrete(TempMesh& out) const {
|
||||
const ParamRange& range = GetParametricRange();
|
||||
ai_assert( range.first != std::numeric_limits<IfcFloat>::infinity() );
|
||||
ai_assert( range.second != std::numeric_limits<IfcFloat>::infinity() );
|
||||
|
||||
return SampleDiscrete(out,range.first,range.second);
|
||||
}
|
||||
|
||||
} // IFC
|
||||
} // Assimp
|
||||
|
||||
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||
888
thirdparty/assimp/code/Importer/IFC/IFCGeometry.cpp
vendored
Normal file
888
thirdparty/assimp/code/Importer/IFC/IFCGeometry.cpp
vendored
Normal file
@@ -0,0 +1,888 @@
|
||||
/*
|
||||
Open Asset Import Library (assimp)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2010, assimp 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 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
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||
#include "IFCUtil.h"
|
||||
#include "Common/PolyTools.h"
|
||||
#include "PostProcessing/ProcessHelper.h"
|
||||
|
||||
#ifdef ASSIMP_USE_HUNTER
|
||||
# include <poly2tri/poly2tri.h>
|
||||
# include <polyclipping/clipper.hpp>
|
||||
#else
|
||||
# include "../contrib/poly2tri/poly2tri/poly2tri.h"
|
||||
# include "../contrib/clipper/clipper.hpp"
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <iterator>
|
||||
|
||||
namespace Assimp {
|
||||
namespace IFC {
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/)
|
||||
{
|
||||
size_t cnt = 0;
|
||||
for(const Schema_2x3::IfcCartesianPoint& c : loop.Polygon) {
|
||||
IfcVector3 tmp;
|
||||
ConvertCartesianPoint(tmp,c);
|
||||
|
||||
meshout.mVerts.push_back(tmp);
|
||||
++cnt;
|
||||
}
|
||||
|
||||
meshout.mVertcnt.push_back(static_cast<unsigned int>(cnt));
|
||||
|
||||
// zero- or one- vertex polyloops simply ignored
|
||||
if (meshout.mVertcnt.back() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (meshout.mVertcnt.back()==1) {
|
||||
meshout.mVertcnt.pop_back();
|
||||
meshout.mVerts.pop_back();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1)
|
||||
{
|
||||
// handle all trivial cases
|
||||
if(inmesh.mVertcnt.empty()) {
|
||||
return;
|
||||
}
|
||||
if(inmesh.mVertcnt.size() == 1) {
|
||||
result.Append(inmesh);
|
||||
return;
|
||||
}
|
||||
|
||||
ai_assert(std::count(inmesh.mVertcnt.begin(), inmesh.mVertcnt.end(), 0) == 0);
|
||||
|
||||
typedef std::vector<unsigned int>::const_iterator face_iter;
|
||||
|
||||
face_iter begin = inmesh.mVertcnt.begin(), end = inmesh.mVertcnt.end(), iit;
|
||||
std::vector<unsigned int>::const_iterator outer_polygon_it = end;
|
||||
|
||||
// major task here: given a list of nested polygon boundaries (one of which
|
||||
// is the outer contour), reduce the triangulation task arising here to
|
||||
// one that can be solved using the "quadrulation" algorithm which we use
|
||||
// for pouring windows out of walls. The algorithm does not handle all
|
||||
// cases but at least it is numerically stable and gives "nice" triangles.
|
||||
|
||||
// first compute normals for all polygons using Newell's algorithm
|
||||
// do not normalize 'normals', we need the original length for computing the polygon area
|
||||
std::vector<IfcVector3> normals;
|
||||
inmesh.ComputePolygonNormals(normals,false);
|
||||
|
||||
// One of the polygons might be 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'
|
||||
IfcFloat area_outer_polygon = 1e-10f;
|
||||
if (master_bounds != (size_t)-1) {
|
||||
ai_assert(master_bounds < inmesh.mVertcnt.size());
|
||||
outer_polygon_it = begin + master_bounds;
|
||||
}
|
||||
else {
|
||||
for(iit = begin; iit != end; ++iit) {
|
||||
// find the polygon with the largest area and take it as the outer bound.
|
||||
IfcVector3& n = normals[std::distance(begin,iit)];
|
||||
const IfcFloat area = n.SquareLength();
|
||||
if (area > area_outer_polygon) {
|
||||
area_outer_polygon = area;
|
||||
outer_polygon_it = iit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ai_assert(outer_polygon_it != end);
|
||||
|
||||
const size_t outer_polygon_size = *outer_polygon_it;
|
||||
const IfcVector3& master_normal = normals[std::distance(begin, outer_polygon_it)];
|
||||
|
||||
// Generate fake openings to meet the interface for the quadrulate
|
||||
// algorithm. It boils down to generating small boxes given the
|
||||
// inner polygon and the surface normal of the outer contour.
|
||||
// It is important that we use the outer contour's normal because
|
||||
// this is the plane onto which the quadrulate algorithm will
|
||||
// project the entire mesh.
|
||||
std::vector<TempOpening> fake_openings;
|
||||
fake_openings.reserve(inmesh.mVertcnt.size()-1);
|
||||
|
||||
std::vector<IfcVector3>::const_iterator vit = inmesh.mVerts.begin(), outer_vit;
|
||||
|
||||
for(iit = begin; iit != end; vit += *iit++) {
|
||||
if (iit == outer_polygon_it) {
|
||||
outer_vit = vit;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter degenerate polygons to keep them from causing trouble later on
|
||||
IfcVector3& n = normals[std::distance(begin,iit)];
|
||||
const IfcFloat area = n.SquareLength();
|
||||
if (area < 1e-5f) {
|
||||
IFCImporter::LogWarn("skipping degenerate polygon (ProcessPolygonBoundaries)");
|
||||
continue;
|
||||
}
|
||||
|
||||
fake_openings.push_back(TempOpening());
|
||||
TempOpening& opening = fake_openings.back();
|
||||
|
||||
opening.extrusionDir = master_normal;
|
||||
opening.solid = NULL;
|
||||
|
||||
opening.profileMesh = std::make_shared<TempMesh>();
|
||||
opening.profileMesh->mVerts.reserve(*iit);
|
||||
opening.profileMesh->mVertcnt.push_back(*iit);
|
||||
|
||||
std::copy(vit, vit + *iit, std::back_inserter(opening.profileMesh->mVerts));
|
||||
}
|
||||
|
||||
// fill a mesh with ONLY the main polygon
|
||||
TempMesh temp;
|
||||
temp.mVerts.reserve(outer_polygon_size);
|
||||
temp.mVertcnt.push_back(static_cast<unsigned int>(outer_polygon_size));
|
||||
std::copy(outer_vit, outer_vit+outer_polygon_size,
|
||||
std::back_inserter(temp.mVerts));
|
||||
|
||||
GenerateOpenings(fake_openings, normals, temp, false, false);
|
||||
result.Append(temp);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessConnectedFaceSet(const Schema_2x3::IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv)
|
||||
{
|
||||
for(const Schema_2x3::IfcFace& face : fset.CfsFaces) {
|
||||
// size_t ob = -1, cnt = 0;
|
||||
TempMesh meshout;
|
||||
for(const Schema_2x3::IfcFaceBound& bound : face.Bounds) {
|
||||
|
||||
if(const Schema_2x3::IfcPolyLoop* const polyloop = bound.Bound->ToPtr<Schema_2x3::IfcPolyLoop>()) {
|
||||
if(ProcessPolyloop(*polyloop, meshout,conv)) {
|
||||
|
||||
// The outer boundary is better determined by checking which
|
||||
// polygon covers the largest area.
|
||||
|
||||
//if(bound.ToPtr<IfcFaceOuterBound>()) {
|
||||
// ob = cnt;
|
||||
//}
|
||||
//++cnt;
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is " + bound.Bound->GetClassName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// And this, even though it is sometimes TRUE and sometimes FALSE,
|
||||
// does not really improve results.
|
||||
|
||||
/*if(!IsTrue(bound.Orientation)) {
|
||||
size_t c = 0;
|
||||
for(unsigned int& c : meshout.vertcnt) {
|
||||
std::reverse(result.verts.begin() + cnt,result.verts.begin() + cnt + c);
|
||||
cnt += c;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
ProcessPolygonBoundaries(result, meshout);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv)
|
||||
{
|
||||
TempMesh meshout;
|
||||
|
||||
// first read the profile description
|
||||
if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.mVerts.size()<=1) {
|
||||
return;
|
||||
}
|
||||
|
||||
IfcVector3 axis, pos;
|
||||
ConvertAxisPlacement(axis,pos,solid.Axis);
|
||||
|
||||
IfcMatrix4 tb0,tb1;
|
||||
IfcMatrix4::Translation(pos,tb0);
|
||||
IfcMatrix4::Translation(-pos,tb1);
|
||||
|
||||
const std::vector<IfcVector3>& in = meshout.mVerts;
|
||||
const size_t size=in.size();
|
||||
|
||||
bool has_area = solid.SweptArea->ProfileType == "AREA" && size>2;
|
||||
const IfcFloat max_angle = solid.Angle*conv.angle_scale;
|
||||
if(std::fabs(max_angle) < 1e-3) {
|
||||
if(has_area) {
|
||||
result = meshout;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned int cnt_segments = std::max(2u,static_cast<unsigned int>(conv.settings.cylindricalTessellation * std::fabs(max_angle)/AI_MATH_HALF_PI_F));
|
||||
const IfcFloat delta = max_angle/cnt_segments;
|
||||
|
||||
has_area = has_area && std::fabs(max_angle) < AI_MATH_TWO_PI_F*0.99;
|
||||
|
||||
result.mVerts.reserve(size*((cnt_segments+1)*4+(has_area?2:0)));
|
||||
result.mVertcnt.reserve(size*cnt_segments+2);
|
||||
|
||||
IfcMatrix4 rot;
|
||||
rot = tb0 * IfcMatrix4::Rotation(delta,axis,rot) * tb1;
|
||||
|
||||
size_t base = 0;
|
||||
std::vector<IfcVector3>& out = result.mVerts;
|
||||
|
||||
// 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.mVertcnt.push_back(4);
|
||||
const IfcVector3 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.mVertcnt.push_back(static_cast<unsigned int>(size));
|
||||
result.mVertcnt.push_back(static_cast<unsigned int>(size));
|
||||
}
|
||||
|
||||
IfcMatrix4 trafo;
|
||||
ConvertAxisPlacement(trafo, solid.Position);
|
||||
|
||||
result.Transform(trafo);
|
||||
IFCImporter::LogDebug("generate mesh procedurally by radial extrusion (IfcRevolvedAreaSolid)");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, TempMesh& result, ConversionData& conv)
|
||||
{
|
||||
const Curve* const curve = Curve::Convert(*solid.Directrix, conv);
|
||||
if(!curve) {
|
||||
IFCImporter::LogError("failed to convert Directrix curve (IfcSweptDiskSolid)");
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned int cnt_segments = conv.settings.cylindricalTessellation;
|
||||
const IfcFloat deltaAngle = AI_MATH_TWO_PI/cnt_segments;
|
||||
|
||||
TempMesh temp;
|
||||
curve->SampleDiscrete(temp, solid.StartParam, solid.EndParam);
|
||||
const std::vector<IfcVector3>& curve_points = temp.mVerts;
|
||||
|
||||
const size_t samples = curve_points.size();
|
||||
|
||||
result.mVerts.reserve(cnt_segments * samples * 4);
|
||||
result.mVertcnt.reserve((cnt_segments - 1) * samples);
|
||||
|
||||
std::vector<IfcVector3> points;
|
||||
points.reserve(cnt_segments * samples);
|
||||
|
||||
if(curve_points.empty()) {
|
||||
IFCImporter::LogWarn("curve evaluation yielded no points (IfcSweptDiskSolid)");
|
||||
return;
|
||||
}
|
||||
|
||||
IfcVector3 current = curve_points[0];
|
||||
IfcVector3 previous = current;
|
||||
IfcVector3 next;
|
||||
|
||||
IfcVector3 startvec;
|
||||
startvec.x = 1.0f;
|
||||
startvec.y = 1.0f;
|
||||
startvec.z = 1.0f;
|
||||
|
||||
unsigned int last_dir = 0;
|
||||
|
||||
// generate circles at the sweep positions
|
||||
for(size_t i = 0; i < samples; ++i) {
|
||||
|
||||
if(i != samples - 1) {
|
||||
next = curve_points[i + 1];
|
||||
}
|
||||
|
||||
// get a direction vector reflecting the approximate curvature (i.e. tangent)
|
||||
IfcVector3 d = (current-previous) + (next-previous);
|
||||
|
||||
d.Normalize();
|
||||
|
||||
// figure out an arbitrary point q so that (p-q) * d = 0,
|
||||
// try to maximize ||(p-q)|| * ||(p_last-q_last)||
|
||||
IfcVector3 q;
|
||||
bool take_any = false;
|
||||
|
||||
for (unsigned int i = 0; i < 2; ++i, take_any = true) {
|
||||
if ((last_dir == 0 || take_any) && std::abs(d.x) > 1e-6) {
|
||||
q.y = startvec.y;
|
||||
q.z = startvec.z;
|
||||
q.x = -(d.y * q.y + d.z * q.z) / d.x;
|
||||
last_dir = 0;
|
||||
break;
|
||||
}
|
||||
else if ((last_dir == 1 || take_any) && std::abs(d.y) > 1e-6) {
|
||||
q.x = startvec.x;
|
||||
q.z = startvec.z;
|
||||
q.y = -(d.x * q.x + d.z * q.z) / d.y;
|
||||
last_dir = 1;
|
||||
break;
|
||||
}
|
||||
else if ((last_dir == 2 && std::abs(d.z) > 1e-6) || take_any) {
|
||||
q.y = startvec.y;
|
||||
q.x = startvec.x;
|
||||
q.z = -(d.y * q.y + d.x * q.x) / d.z;
|
||||
last_dir = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
q *= solid.Radius / q.Length();
|
||||
startvec = q;
|
||||
|
||||
// generate a rotation matrix to rotate q around d
|
||||
IfcMatrix4 rot;
|
||||
IfcMatrix4::Rotation(deltaAngle,d,rot);
|
||||
|
||||
for (unsigned int seg = 0; seg < cnt_segments; ++seg, q *= rot ) {
|
||||
points.push_back(q + current);
|
||||
}
|
||||
|
||||
previous = current;
|
||||
current = next;
|
||||
}
|
||||
|
||||
// make quads
|
||||
for(size_t i = 0; i < samples - 1; ++i) {
|
||||
|
||||
const aiVector3D& this_start = points[ i * cnt_segments ];
|
||||
|
||||
// locate corresponding point on next sample ring
|
||||
unsigned int best_pair_offset = 0;
|
||||
float best_distance_squared = 1e10f;
|
||||
for (unsigned int seg = 0; seg < cnt_segments; ++seg) {
|
||||
const aiVector3D& p = points[ (i+1) * cnt_segments + seg];
|
||||
const float l = (p-this_start).SquareLength();
|
||||
|
||||
if(l < best_distance_squared) {
|
||||
best_pair_offset = seg;
|
||||
best_distance_squared = l;
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int seg = 0; seg < cnt_segments; ++seg) {
|
||||
|
||||
result.mVerts.push_back(points[ i * cnt_segments + (seg % cnt_segments)]);
|
||||
result.mVerts.push_back(points[ i * cnt_segments + (seg + 1) % cnt_segments]);
|
||||
result.mVerts.push_back(points[ (i+1) * cnt_segments + ((seg + 1 + best_pair_offset) % cnt_segments)]);
|
||||
result.mVerts.push_back(points[ (i+1) * cnt_segments + ((seg + best_pair_offset) % cnt_segments)]);
|
||||
|
||||
IfcVector3& v1 = *(result.mVerts.end()-1);
|
||||
IfcVector3& v2 = *(result.mVerts.end()-2);
|
||||
IfcVector3& v3 = *(result.mVerts.end()-3);
|
||||
IfcVector3& v4 = *(result.mVerts.end()-4);
|
||||
|
||||
if (((v4-v3) ^ (v4-v1)) * (v4 - curve_points[i]) < 0.0f) {
|
||||
std::swap(v4, v1);
|
||||
std::swap(v3, v2);
|
||||
}
|
||||
|
||||
result.mVertcnt.push_back(4);
|
||||
}
|
||||
}
|
||||
|
||||
IFCImporter::LogDebug("generate mesh procedurally by sweeping a disk along a curve (IfcSweptDiskSolid)");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut)
|
||||
{
|
||||
const std::vector<IfcVector3>& out = curmesh.mVerts;
|
||||
IfcMatrix3 m;
|
||||
|
||||
ok = true;
|
||||
|
||||
// The input "mesh" must be a single polygon
|
||||
const size_t s = out.size();
|
||||
ai_assert( curmesh.mVertcnt.size() == 1 );
|
||||
ai_assert( curmesh.mVertcnt.back() == s);
|
||||
|
||||
const IfcVector3 any_point = out[s-1];
|
||||
IfcVector3 nor;
|
||||
|
||||
// The input polygon is arbitrarily shaped, therefore we might need some tries
|
||||
// until we find a suitable normal. Note that Newell's algorithm would give
|
||||
// a more robust result, but this variant also gives us a suitable first
|
||||
// axis for the 2D coordinate space on the polygon plane, exploiting the
|
||||
// fact that the input polygon is nearly always a quad.
|
||||
bool done = false;
|
||||
size_t idx( 0 );
|
||||
for (size_t i = 0; !done && i < s-2; done || ++i) {
|
||||
idx = i;
|
||||
for (size_t j = i+1; j < s-1; ++j) {
|
||||
nor = -((out[i]-any_point)^(out[j]-any_point));
|
||||
if(std::fabs(nor.Length()) > 1e-8f) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!done) {
|
||||
ok = false;
|
||||
return m;
|
||||
}
|
||||
|
||||
nor.Normalize();
|
||||
norOut = nor;
|
||||
|
||||
IfcVector3 r = (out[idx]-any_point);
|
||||
r.Normalize();
|
||||
|
||||
//if(d) {
|
||||
// *d = -any_point * nor;
|
||||
//}
|
||||
|
||||
// Reconstruct orthonormal basis
|
||||
// XXX use Gram Schmidt for increased robustness
|
||||
IfcVector3 u = r ^ nor;
|
||||
u.Normalize();
|
||||
|
||||
m.a1 = r.x;
|
||||
m.a2 = r.y;
|
||||
m.a3 = r.z;
|
||||
|
||||
m.b1 = u.x;
|
||||
m.b2 = u.y;
|
||||
m.b3 = u.z;
|
||||
|
||||
m.c1 = -nor.x;
|
||||
m.c2 = -nor.y;
|
||||
m.c3 = -nor.z;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
// Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary.
|
||||
void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve,
|
||||
const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings)
|
||||
{
|
||||
// Outline: 'curve' is now a list of vertex points forming the underlying profile, extrude along the given axis,
|
||||
// forming new triangles.
|
||||
const bool has_area = solid.SweptArea->ProfileType == "AREA" && curve.mVerts.size() > 2;
|
||||
if( solid.Depth < 1e-6 ) {
|
||||
if( has_area ) {
|
||||
result.Append(curve);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
result.mVerts.reserve(curve.mVerts.size()*(has_area ? 4 : 2));
|
||||
result.mVertcnt.reserve(curve.mVerts.size() + 2);
|
||||
std::vector<IfcVector3> in = curve.mVerts;
|
||||
|
||||
// First step: transform all vertices into the target coordinate space
|
||||
IfcMatrix4 trafo;
|
||||
ConvertAxisPlacement(trafo, solid.Position);
|
||||
|
||||
IfcVector3 vmin, vmax;
|
||||
MinMaxChooser<IfcVector3>()(vmin, vmax);
|
||||
for(IfcVector3& v : in) {
|
||||
v *= trafo;
|
||||
|
||||
vmin = std::min(vmin, v);
|
||||
vmax = std::max(vmax, v);
|
||||
}
|
||||
|
||||
vmax -= vmin;
|
||||
const IfcFloat diag = vmax.Length();
|
||||
IfcVector3 dir = IfcMatrix3(trafo) * extrusionDir;
|
||||
|
||||
// reverse profile polygon if it's winded in the wrong direction in relation to the extrusion direction
|
||||
IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(in.data(), in.size());
|
||||
if( profileNormal * dir < 0.0 )
|
||||
std::reverse(in.begin(), in.end());
|
||||
|
||||
std::vector<IfcVector3> nors;
|
||||
const bool openings = !!conv.apply_openings && conv.apply_openings->size();
|
||||
|
||||
// Compute the normal vectors for all opening polygons as a prerequisite
|
||||
// to TryAddOpenings_Poly2Tri()
|
||||
// XXX this belongs into the aforementioned function
|
||||
if( 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(), TempOpening::DistanceSorter(in[0]));
|
||||
}
|
||||
|
||||
nors.reserve(conv.apply_openings->size());
|
||||
for(TempOpening& t : *conv.apply_openings) {
|
||||
TempMesh& bounds = *t.profileMesh.get();
|
||||
|
||||
if( bounds.mVerts.size() <= 2 ) {
|
||||
nors.push_back(IfcVector3());
|
||||
continue;
|
||||
}
|
||||
nors.push_back(((bounds.mVerts[2] - bounds.mVerts[0]) ^ (bounds.mVerts[1] - bounds.mVerts[0])).Normalize());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TempMesh temp;
|
||||
TempMesh& curmesh = openings ? temp : result;
|
||||
std::vector<IfcVector3>& out = curmesh.mVerts;
|
||||
|
||||
size_t sides_with_openings = 0;
|
||||
for( size_t i = 0; i < in.size(); ++i ) {
|
||||
const size_t next = (i + 1) % in.size();
|
||||
|
||||
curmesh.mVertcnt.push_back(4);
|
||||
|
||||
out.push_back(in[i]);
|
||||
out.push_back(in[next]);
|
||||
out.push_back(in[next] + dir);
|
||||
out.push_back(in[i] + dir);
|
||||
|
||||
if( openings ) {
|
||||
if( (in[i] - in[next]).Length() > diag * 0.1 && GenerateOpenings(*conv.apply_openings, nors, temp, true, true, dir) ) {
|
||||
++sides_with_openings;
|
||||
}
|
||||
|
||||
result.Append(temp);
|
||||
temp.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if( openings ) {
|
||||
for(TempOpening& opening : *conv.apply_openings) {
|
||||
if( !opening.wallPoints.empty() ) {
|
||||
IFCImporter::LogError("failed to generate all window caps");
|
||||
}
|
||||
opening.wallPoints.clear();
|
||||
}
|
||||
}
|
||||
|
||||
size_t sides_with_v_openings = 0;
|
||||
if( has_area ) {
|
||||
|
||||
for( size_t n = 0; n < 2; ++n ) {
|
||||
if( n > 0 ) {
|
||||
for( size_t i = 0; i < in.size(); ++i )
|
||||
out.push_back(in[i] + dir);
|
||||
}
|
||||
else {
|
||||
for( size_t i = in.size(); i--; )
|
||||
out.push_back(in[i]);
|
||||
}
|
||||
|
||||
curmesh.mVertcnt.push_back(static_cast<unsigned int>(in.size()));
|
||||
if( openings && in.size() > 2 ) {
|
||||
if( GenerateOpenings(*conv.apply_openings, nors, temp, true, true, dir) ) {
|
||||
++sides_with_v_openings;
|
||||
}
|
||||
|
||||
result.Append(temp);
|
||||
temp.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( openings && ((sides_with_openings == 1 && 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)");
|
||||
|
||||
// If this is an opening element, store both the extruded mesh and the 2D profile mesh
|
||||
// it was created from. Return an empty mesh to the caller.
|
||||
if( collect_openings && !result.IsEmpty() ) {
|
||||
ai_assert(conv.collect_openings);
|
||||
std::shared_ptr<TempMesh> profile = std::shared_ptr<TempMesh>(new TempMesh());
|
||||
profile->Swap(result);
|
||||
|
||||
std::shared_ptr<TempMesh> profile2D = std::shared_ptr<TempMesh>(new TempMesh());
|
||||
profile2D->mVerts.insert(profile2D->mVerts.end(), in.begin(), in.end());
|
||||
profile2D->mVertcnt.push_back(static_cast<unsigned int>(in.size()));
|
||||
conv.collect_openings->push_back(TempOpening(&solid, dir, profile, profile2D));
|
||||
|
||||
ai_assert(result.IsEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, TempMesh& result,
|
||||
ConversionData& conv, bool collect_openings)
|
||||
{
|
||||
TempMesh meshout;
|
||||
|
||||
// First read the profile description.
|
||||
if(!ProcessProfile(*solid.SweptArea,meshout,conv) || meshout.mVerts.size()<=1) {
|
||||
return;
|
||||
}
|
||||
|
||||
IfcVector3 dir;
|
||||
ConvertDirection(dir,solid.ExtrudedDirection);
|
||||
dir *= solid.Depth;
|
||||
|
||||
// Some profiles bring their own holes, for which we need to provide a container. This all is somewhat backwards,
|
||||
// and there's still so many corner cases uncovered - we really need a generic solution to all of this hole carving.
|
||||
std::vector<TempOpening> fisherPriceMyFirstOpenings;
|
||||
std::vector<TempOpening>* oldApplyOpenings = conv.apply_openings;
|
||||
if( const Schema_2x3::IfcArbitraryProfileDefWithVoids* const cprofile = solid.SweptArea->ToPtr<Schema_2x3::IfcArbitraryProfileDefWithVoids>() ) {
|
||||
if( !cprofile->InnerCurves.empty() ) {
|
||||
// read all inner curves and extrude them to form proper openings.
|
||||
std::vector<TempOpening>* oldCollectOpenings = conv.collect_openings;
|
||||
conv.collect_openings = &fisherPriceMyFirstOpenings;
|
||||
|
||||
for (const Schema_2x3::IfcCurve* curve : cprofile->InnerCurves) {
|
||||
TempMesh curveMesh, tempMesh;
|
||||
ProcessCurve(*curve, curveMesh, conv);
|
||||
ProcessExtrudedArea(solid, curveMesh, dir, tempMesh, conv, true);
|
||||
}
|
||||
// and then apply those to the geometry we're about to generate
|
||||
conv.apply_openings = conv.collect_openings;
|
||||
conv.collect_openings = oldCollectOpenings;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessExtrudedArea(solid, meshout, dir, result, conv, collect_openings);
|
||||
conv.apply_openings = oldApplyOpenings;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, TempMesh& meshout,
|
||||
ConversionData& conv)
|
||||
{
|
||||
if(const Schema_2x3::IfcExtrudedAreaSolid* const solid = swept.ToPtr<Schema_2x3::IfcExtrudedAreaSolid>()) {
|
||||
ProcessExtrudedAreaSolid(*solid,meshout,conv, !!conv.collect_openings);
|
||||
}
|
||||
else if(const Schema_2x3::IfcRevolvedAreaSolid* const rev = swept.ToPtr<Schema_2x3::IfcRevolvedAreaSolid>()) {
|
||||
ProcessRevolvedAreaSolid(*rev,meshout,conv);
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is " + swept.GetClassName());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned int matid, std::set<unsigned int>& mesh_indices,
|
||||
ConversionData& conv)
|
||||
{
|
||||
bool fix_orientation = false;
|
||||
std::shared_ptr< TempMesh > meshtmp = std::make_shared<TempMesh>();
|
||||
if(const Schema_2x3::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr<Schema_2x3::IfcShellBasedSurfaceModel>()) {
|
||||
for(std::shared_ptr<const Schema_2x3::IfcShell> shell :shellmod->SbsmBoundary) {
|
||||
try {
|
||||
const ::Assimp::STEP::EXPRESS::ENTITY& e = shell->To<::Assimp::STEP::EXPRESS::ENTITY>();
|
||||
const Schema_2x3::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To<Schema_2x3::IfcConnectedFaceSet>();
|
||||
|
||||
ProcessConnectedFaceSet(fs,*meshtmp.get(),conv);
|
||||
}
|
||||
catch(std::bad_cast&) {
|
||||
IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet");
|
||||
}
|
||||
}
|
||||
fix_orientation = true;
|
||||
}
|
||||
else if(const Schema_2x3::IfcConnectedFaceSet* fset = geo.ToPtr<Schema_2x3::IfcConnectedFaceSet>()) {
|
||||
ProcessConnectedFaceSet(*fset,*meshtmp.get(),conv);
|
||||
fix_orientation = true;
|
||||
}
|
||||
else if(const Schema_2x3::IfcSweptAreaSolid* swept = geo.ToPtr<Schema_2x3::IfcSweptAreaSolid>()) {
|
||||
ProcessSweptAreaSolid(*swept,*meshtmp.get(),conv);
|
||||
}
|
||||
else if(const Schema_2x3::IfcSweptDiskSolid* disk = geo.ToPtr<Schema_2x3::IfcSweptDiskSolid>()) {
|
||||
ProcessSweptDiskSolid(*disk,*meshtmp.get(),conv);
|
||||
}
|
||||
else if(const Schema_2x3::IfcManifoldSolidBrep* brep = geo.ToPtr<Schema_2x3::IfcManifoldSolidBrep>()) {
|
||||
ProcessConnectedFaceSet(brep->Outer,*meshtmp.get(),conv);
|
||||
fix_orientation = true;
|
||||
}
|
||||
else if(const Schema_2x3::IfcFaceBasedSurfaceModel* surf = geo.ToPtr<Schema_2x3::IfcFaceBasedSurfaceModel>()) {
|
||||
for(const Schema_2x3::IfcConnectedFaceSet& fc : surf->FbsmFaces) {
|
||||
ProcessConnectedFaceSet(fc,*meshtmp.get(),conv);
|
||||
}
|
||||
fix_orientation = true;
|
||||
}
|
||||
else if(const Schema_2x3::IfcBooleanResult* boolean = geo.ToPtr<Schema_2x3::IfcBooleanResult>()) {
|
||||
ProcessBoolean(*boolean,*meshtmp.get(),conv);
|
||||
}
|
||||
else if(geo.ToPtr<Schema_2x3::IfcBoundingBox>()) {
|
||||
// silently skip over bounding boxes
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogWarn("skipping unknown IfcGeometricRepresentationItem entity, type is " + geo.GetClassName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do we just collect openings for a parent element (i.e. a wall)?
|
||||
// In such a case, we generate the polygonal mesh as usual,
|
||||
// but attach it to a TempOpening instance which will later be applied
|
||||
// to the wall it pertains to.
|
||||
|
||||
// Note: swep area solids are added in ProcessExtrudedAreaSolid(),
|
||||
// which returns an empty mesh.
|
||||
if(conv.collect_openings) {
|
||||
if (!meshtmp->IsEmpty()) {
|
||||
conv.collect_openings->push_back(TempOpening(geo.ToPtr<Schema_2x3::IfcSolidModel>(),
|
||||
IfcVector3(0,0,0),
|
||||
meshtmp,
|
||||
std::shared_ptr<TempMesh>()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (meshtmp->IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
meshtmp->RemoveAdjacentDuplicates();
|
||||
meshtmp->RemoveDegenerates();
|
||||
|
||||
if(fix_orientation) {
|
||||
// meshtmp->FixupFaceOrientation();
|
||||
}
|
||||
|
||||
aiMesh* const mesh = meshtmp->ToMesh();
|
||||
if(mesh) {
|
||||
mesh->mMaterialIndex = matid;
|
||||
mesh_indices.insert(static_cast<unsigned int>(conv.meshes.size()));
|
||||
conv.meshes.push_back(mesh);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd,
|
||||
ConversionData& /*conv*/)
|
||||
{
|
||||
if (!mesh_indices.empty()) {
|
||||
std::set<unsigned int>::const_iterator it = mesh_indices.cbegin();
|
||||
std::set<unsigned int>::const_iterator end = mesh_indices.cend();
|
||||
|
||||
nd->mNumMeshes = static_cast<unsigned int>(mesh_indices.size());
|
||||
|
||||
nd->mMeshes = new unsigned int[nd->mNumMeshes];
|
||||
for(unsigned int i = 0; it != end && i < nd->mNumMeshes; ++i, ++it) {
|
||||
nd->mMeshes[i] = *it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item,
|
||||
std::set<unsigned int>& mesh_indices, unsigned int mat_index,
|
||||
ConversionData& conv)
|
||||
{
|
||||
ConversionData::MeshCacheIndex idx(&item, mat_index);
|
||||
ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(idx);
|
||||
if (it != conv.cached_meshes.end()) {
|
||||
std::copy((*it).second.begin(),(*it).second.end(),std::inserter(mesh_indices, mesh_indices.end()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void PopulateMeshCache(const Schema_2x3::IfcRepresentationItem& item,
|
||||
const std::set<unsigned int>& mesh_indices, unsigned int mat_index,
|
||||
ConversionData& conv)
|
||||
{
|
||||
ConversionData::MeshCacheIndex idx(&item, mat_index);
|
||||
conv.cached_meshes[idx] = mesh_indices;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, unsigned int matid,
|
||||
std::set<unsigned int>& mesh_indices,
|
||||
ConversionData& conv)
|
||||
{
|
||||
// determine material
|
||||
unsigned int localmatid = ProcessMaterials(item.GetID(), matid, conv, true);
|
||||
|
||||
if (!TryQueryMeshCache(item,mesh_indices,localmatid,conv)) {
|
||||
if(ProcessGeometricItem(item,localmatid,mesh_indices,conv)) {
|
||||
if(mesh_indices.size()) {
|
||||
PopulateMeshCache(item,mesh_indices,localmatid,conv);
|
||||
}
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // ! IFC
|
||||
} // ! Assimp
|
||||
|
||||
#endif
|
||||
988
thirdparty/assimp/code/Importer/IFC/IFCLoader.cpp
vendored
Normal file
988
thirdparty/assimp/code/Importer/IFC/IFCLoader.cpp
vendored
Normal file
@@ -0,0 +1,988 @@
|
||||
/*
|
||||
Open Asset Import Library (assimp)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2019, assimp 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 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 IFCLoad.cpp
|
||||
* @brief Implementation of the Industry Foundation Classes loader.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <tuple>
|
||||
|
||||
#ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC
|
||||
# ifdef ASSIMP_USE_HUNTER
|
||||
# include <minizip/unzip.h>
|
||||
# else
|
||||
# include <unzip.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "IFCLoader.h"
|
||||
#include "../STEPParser/STEPFileReader.h"
|
||||
|
||||
#include "IFCUtil.h"
|
||||
|
||||
#include <assimp/MemoryIOWrapper.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <assimp/Importer.hpp>
|
||||
#include <assimp/importerdesc.h>
|
||||
|
||||
|
||||
namespace Assimp {
|
||||
template<> const char* LogFunctions<IFCImporter>::Prefix()
|
||||
{
|
||||
static auto prefix = "IFC: ";
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace Assimp;
|
||||
using namespace Assimp::Formatter;
|
||||
using namespace Assimp::IFC;
|
||||
|
||||
/* DO NOT REMOVE this comment block. The genentitylist.sh script
|
||||
* 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.
|
||||
|
||||
IfcRepresentationMap
|
||||
IfcProductRepresentation
|
||||
IfcUnitAssignment
|
||||
IfcClosedShell
|
||||
IfcDoor
|
||||
|
||||
*/
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
// forward declarations
|
||||
void SetUnits(ConversionData& conv);
|
||||
void SetCoordinateSpace(ConversionData& conv);
|
||||
void ProcessSpatialStructures(ConversionData& conv);
|
||||
void MakeTreeRelative(ConversionData& conv);
|
||||
void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType& dt,ConversionData& conv);
|
||||
|
||||
} // anon
|
||||
|
||||
static const aiImporterDesc desc = {
|
||||
"Industry Foundation Classes (IFC) Importer",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
aiImporterFlags_SupportBinaryFlavour,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"ifc ifczip stp"
|
||||
};
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Constructor to be privately used by Importer
|
||||
IFCImporter::IFCImporter()
|
||||
{}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Destructor, private as well
|
||||
IFCImporter::~IFCImporter()
|
||||
{
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Returns whether the class can handle the format of the given file.
|
||||
bool IFCImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
|
||||
{
|
||||
const std::string& extension = GetExtension(pFile);
|
||||
if (extension == "ifc" || extension == "ifczip" ) {
|
||||
return true;
|
||||
} else if ((!extension.length() || checkSig) && pIOHandler) {
|
||||
// note: this is the common identification for STEP-encoded files, so
|
||||
// it is only unambiguous as long as we don't support any further
|
||||
// file formats with STEP as their encoding.
|
||||
const char* tokens[] = {"ISO-10303-21"};
|
||||
const bool found( SearchFileHeaderForToken( pIOHandler, pFile, tokens, 1 ) );
|
||||
return found;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// List all extensions handled by this loader
|
||||
const aiImporterDesc* IFCImporter::GetInfo () const
|
||||
{
|
||||
return &desc;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Setup configuration properties for the loader
|
||||
void IFCImporter::SetupProperties(const Importer* pImp)
|
||||
{
|
||||
settings.skipSpaceRepresentations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS,true);
|
||||
settings.useCustomTriangulation = pImp->GetPropertyBool(AI_CONFIG_IMPORT_IFC_CUSTOM_TRIANGULATION,true);
|
||||
settings.conicSamplingAngle = std::min(std::max((float) pImp->GetPropertyFloat(AI_CONFIG_IMPORT_IFC_SMOOTHING_ANGLE, AI_IMPORT_IFC_DEFAULT_SMOOTHING_ANGLE), 5.0f), 120.0f);
|
||||
settings.cylindricalTessellation = std::min(std::max(pImp->GetPropertyInteger(AI_CONFIG_IMPORT_IFC_CYLINDRICAL_TESSELLATION, AI_IMPORT_IFC_DEFAULT_CYLINDRICAL_TESSELLATION), 3), 180);
|
||||
settings.skipAnnotations = true;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Imports the given file into the given scene structure.
|
||||
void IFCImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
|
||||
{
|
||||
std::shared_ptr<IOStream> stream(pIOHandler->Open(pFile));
|
||||
if (!stream) {
|
||||
ThrowException("Could not open file for reading");
|
||||
}
|
||||
|
||||
|
||||
// if this is a ifczip file, decompress its contents first
|
||||
if(GetExtension(pFile) == "ifczip") {
|
||||
#ifndef ASSIMP_BUILD_NO_COMPRESSED_IFC
|
||||
unzFile zip = unzOpen( pFile.c_str() );
|
||||
if(zip == NULL) {
|
||||
ThrowException("Could not open ifczip file for reading, unzip failed");
|
||||
}
|
||||
|
||||
// chop 'zip' postfix
|
||||
std::string fileName = pFile.substr(0,pFile.length() - 3);
|
||||
|
||||
std::string::size_type s = pFile.find_last_of('\\');
|
||||
if(s == std::string::npos) {
|
||||
s = pFile.find_last_of('/');
|
||||
}
|
||||
if(s != std::string::npos) {
|
||||
fileName = fileName.substr(s+1);
|
||||
}
|
||||
|
||||
// search file (same name as the IFCZIP except for the file extension) and place file pointer there
|
||||
if(UNZ_OK == unzGoToFirstFile(zip)) {
|
||||
do {
|
||||
// get file size, etc.
|
||||
unz_file_info fileInfo;
|
||||
char filename[256];
|
||||
unzGetCurrentFileInfo( zip , &fileInfo, filename, sizeof(filename), 0, 0, 0, 0 );
|
||||
if (GetExtension(filename) != "ifc") {
|
||||
continue;
|
||||
}
|
||||
uint8_t* buff = new uint8_t[fileInfo.uncompressed_size];
|
||||
LogInfo("Decompressing IFCZIP file");
|
||||
unzOpenCurrentFile(zip);
|
||||
size_t total = 0;
|
||||
int read = 0;
|
||||
do {
|
||||
int bufferSize = fileInfo.uncompressed_size < INT16_MAX ? fileInfo.uncompressed_size : INT16_MAX;
|
||||
void* buffer = malloc(bufferSize);
|
||||
read = unzReadCurrentFile(zip, buffer, bufferSize);
|
||||
if (read > 0) {
|
||||
memcpy((char*)buff + total, buffer, read);
|
||||
total += read;
|
||||
}
|
||||
free(buffer);
|
||||
} while (read > 0);
|
||||
size_t filesize = fileInfo.uncompressed_size;
|
||||
if (total == 0 || size_t(total) != filesize)
|
||||
{
|
||||
delete[] buff;
|
||||
ThrowException("Failed to decompress IFC ZIP file");
|
||||
}
|
||||
unzCloseCurrentFile( zip );
|
||||
stream.reset(new MemoryIOStream(buff,fileInfo.uncompressed_size,true));
|
||||
break;
|
||||
|
||||
if (unzGoToNextFile(zip) == UNZ_END_OF_LIST_OF_FILE) {
|
||||
ThrowException("Found no IFC file member in IFCZIP file (1)");
|
||||
}
|
||||
|
||||
} while(true);
|
||||
}
|
||||
else {
|
||||
ThrowException("Found no IFC file member in IFCZIP file (2)");
|
||||
}
|
||||
|
||||
unzClose(zip);
|
||||
#else
|
||||
ThrowException("Could not open ifczip file for reading, assimp was built without ifczip support");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<STEP::DB> db(STEP::ReadFileHeader(stream));
|
||||
const STEP::HeaderInfo& head = static_cast<const STEP::DB&>(*db).GetHeader();
|
||||
|
||||
if(!head.fileSchema.size() || head.fileSchema.substr(0,3) != "IFC") {
|
||||
ThrowException("Unrecognized file schema: " + head.fileSchema);
|
||||
}
|
||||
|
||||
if (!DefaultLogger::isNullLogger()) {
|
||||
LogDebug("File schema is \'" + head.fileSchema + '\'');
|
||||
if (head.timestamp.length()) {
|
||||
LogDebug("Timestamp \'" + head.timestamp + '\'');
|
||||
}
|
||||
if (head.app.length()) {
|
||||
LogDebug("Application/Exporter identline is \'" + head.app + '\'');
|
||||
}
|
||||
}
|
||||
|
||||
// obtain a copy of the machine-generated IFC scheme
|
||||
::Assimp::STEP::EXPRESS::ConversionSchema schema;
|
||||
Schema_2x3::GetSchema(schema);
|
||||
|
||||
// tell the reader which entity types to track with special care
|
||||
static const char* const types_to_track[] = {
|
||||
"ifcsite", "ifcbuilding", "ifcproject"
|
||||
};
|
||||
|
||||
// tell the reader for which types we need to simulate STEPs reverse indices
|
||||
static const char* const inverse_indices_to_track[] = {
|
||||
"ifcrelcontainedinspatialstructure", "ifcrelaggregates", "ifcrelvoidselement", "ifcreldefinesbyproperties", "ifcpropertyset", "ifcstyleditem"
|
||||
};
|
||||
|
||||
// feed the IFC schema into the reader and pre-parse all lines
|
||||
STEP::ReadFile(*db, schema, types_to_track, inverse_indices_to_track);
|
||||
const STEP::LazyObject* proj = db->GetObject("ifcproject");
|
||||
if (!proj) {
|
||||
ThrowException("missing IfcProject entity");
|
||||
}
|
||||
|
||||
ConversionData conv(*db,proj->To<Schema_2x3::IfcProject>(),pScene,settings);
|
||||
SetUnits(conv);
|
||||
SetCoordinateSpace(conv);
|
||||
ProcessSpatialStructures(conv);
|
||||
MakeTreeRelative(conv);
|
||||
|
||||
// NOTE - this is a stress test for the importer, but it works only
|
||||
// in a build with no entities disabled. See
|
||||
// scripts/IFCImporter/CPPGenerator.py
|
||||
// for more information.
|
||||
#ifdef ASSIMP_IFC_TEST
|
||||
db->EvaluateAll();
|
||||
#endif
|
||||
|
||||
// do final data copying
|
||||
if (conv.meshes.size()) {
|
||||
pScene->mNumMeshes = static_cast<unsigned int>(conv.meshes.size());
|
||||
pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]();
|
||||
std::copy(conv.meshes.begin(),conv.meshes.end(),pScene->mMeshes);
|
||||
|
||||
// needed to keep the d'tor from burning us
|
||||
conv.meshes.clear();
|
||||
}
|
||||
|
||||
if (conv.materials.size()) {
|
||||
pScene->mNumMaterials = static_cast<unsigned int>(conv.materials.size());
|
||||
pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]();
|
||||
std::copy(conv.materials.begin(),conv.materials.end(),pScene->mMaterials);
|
||||
|
||||
// needed to keep the d'tor from burning us
|
||||
conv.materials.clear();
|
||||
}
|
||||
|
||||
// apply world coordinate system (which includes the scaling to convert to meters and a -90 degrees rotation around x)
|
||||
aiMatrix4x4 scale, rot;
|
||||
aiMatrix4x4::Scaling(static_cast<aiVector3D>(IfcVector3(conv.len_scale)),scale);
|
||||
aiMatrix4x4::RotationX(-AI_MATH_HALF_PI_F,rot);
|
||||
|
||||
pScene->mRootNode->mTransformation = rot * scale * conv.wcs * pScene->mRootNode->mTransformation;
|
||||
|
||||
// this must be last because objects are evaluated lazily as we process them
|
||||
if ( !DefaultLogger::isNullLogger() ){
|
||||
LogDebug((Formatter::format(),"STEP: evaluated ",db->GetEvaluatedObjectCount()," object records"));
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertUnit(const Schema_2x3::IfcNamedUnit& unit,ConversionData& conv)
|
||||
{
|
||||
if(const Schema_2x3::IfcSIUnit* const si = unit.ToPtr<Schema_2x3::IfcSIUnit>()) {
|
||||
if(si->UnitType == "LENGTHUNIT") {
|
||||
conv.len_scale = si->Prefix ? ConvertSIPrefix(si->Prefix) : 1.f;
|
||||
IFCImporter::LogDebug("got units used for lengths");
|
||||
}
|
||||
if(si->UnitType == "PLANEANGLEUNIT") {
|
||||
if (si->Name != "RADIAN") {
|
||||
IFCImporter::LogWarn("expected base unit for angles to be radian");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(const Schema_2x3::IfcConversionBasedUnit* const convu = unit.ToPtr<Schema_2x3::IfcConversionBasedUnit>()) {
|
||||
if(convu->UnitType == "PLANEANGLEUNIT") {
|
||||
try {
|
||||
conv.angle_scale = convu->ConversionFactor->ValueComponent->To<::Assimp::STEP::EXPRESS::REAL>();
|
||||
ConvertUnit(*convu->ConversionFactor->UnitComponent,conv);
|
||||
IFCImporter::LogDebug("got units used for angles");
|
||||
}
|
||||
catch(std::bad_cast&) {
|
||||
IFCImporter::LogError("skipping unknown IfcConversionBasedUnit.ValueComponent entry - expected REAL");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertUnit(const ::Assimp::STEP::EXPRESS::DataType& dt,ConversionData& conv)
|
||||
{
|
||||
try {
|
||||
const ::Assimp::STEP::EXPRESS::ENTITY& e = dt.To<::Assimp::STEP::EXPRESS::ENTITY>();
|
||||
|
||||
const Schema_2x3::IfcNamedUnit& unit = e.ResolveSelect<Schema_2x3::IfcNamedUnit>(conv.db);
|
||||
if(unit.UnitType != "LENGTHUNIT" && unit.UnitType != "PLANEANGLEUNIT") {
|
||||
return;
|
||||
}
|
||||
|
||||
ConvertUnit(unit,conv);
|
||||
}
|
||||
catch(std::bad_cast&) {
|
||||
// not entity, somehow
|
||||
IFCImporter::LogError("skipping unknown IfcUnit entry - expected entity");
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void SetUnits(ConversionData& conv)
|
||||
{
|
||||
// see if we can determine the coordinate space used to express.
|
||||
for(size_t i = 0; i < conv.proj.UnitsInContext->Units.size(); ++i ) {
|
||||
ConvertUnit(*conv.proj.UnitsInContext->Units[i],conv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void SetCoordinateSpace(ConversionData& conv)
|
||||
{
|
||||
const Schema_2x3::IfcRepresentationContext* fav = NULL;
|
||||
for(const Schema_2x3::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") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fav) {
|
||||
if(const Schema_2x3::IfcGeometricRepresentationContext* const geo = fav->ToPtr<Schema_2x3::IfcGeometricRepresentationContext>()) {
|
||||
ConvertAxisPlacement(conv.wcs, *geo->WorldCoordinateSystem, conv);
|
||||
IFCImporter::LogDebug("got world coordinate system");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ResolveObjectPlacement(aiMatrix4x4& m, const Schema_2x3::IfcObjectPlacement& place, ConversionData& conv)
|
||||
{
|
||||
if (const Schema_2x3::IfcLocalPlacement* const local = place.ToPtr<Schema_2x3::IfcLocalPlacement>()){
|
||||
IfcMatrix4 tmp;
|
||||
ConvertAxisPlacement(tmp, *local->RelativePlacement, conv);
|
||||
|
||||
m = static_cast<aiMatrix4x4>(tmp);
|
||||
|
||||
if (local->PlacementRelTo) {
|
||||
aiMatrix4x4 tmp;
|
||||
ResolveObjectPlacement(tmp,local->PlacementRelTo.Get(),conv);
|
||||
m = tmp * m;
|
||||
}
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogWarn("skipping unknown IfcObjectPlacement entity, type is " + place.GetClassName());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool ProcessMappedItem(const Schema_2x3::IfcMappedItem& mapped, aiNode* nd_src, std::vector< aiNode* >& subnodes_src, unsigned int matid, ConversionData& conv)
|
||||
{
|
||||
// insert a custom node here, the carthesian transform operator is simply a conventional transformation matrix
|
||||
std::unique_ptr<aiNode> nd(new aiNode());
|
||||
nd->mName.Set("IfcMappedItem");
|
||||
|
||||
// handle the Cartesian operator
|
||||
IfcMatrix4 m;
|
||||
ConvertTransformOperator(m, *mapped.MappingTarget);
|
||||
|
||||
IfcMatrix4 msrc;
|
||||
ConvertAxisPlacement(msrc,*mapped.MappingSource->MappingOrigin,conv);
|
||||
|
||||
msrc = m*msrc;
|
||||
|
||||
std::set<unsigned int> meshes;
|
||||
const size_t old_openings = conv.collect_openings ? conv.collect_openings->size() : 0;
|
||||
if (conv.apply_openings) {
|
||||
IfcMatrix4 minv = msrc;
|
||||
minv.Inverse();
|
||||
for(TempOpening& open :*conv.apply_openings){
|
||||
open.Transform(minv);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int localmatid = ProcessMaterials(mapped.GetID(),matid,conv,false);
|
||||
const Schema_2x3::IfcRepresentation& repr = mapped.MappingSource->MappedRepresentation;
|
||||
|
||||
bool got = false;
|
||||
for(const Schema_2x3::IfcRepresentationItem& item : repr.Items) {
|
||||
if(!ProcessRepresentationItem(item,localmatid,meshes,conv)) {
|
||||
IFCImporter::LogWarn("skipping mapped entity of type " + item.GetClassName() + ", no representations could be generated");
|
||||
}
|
||||
else got = true;
|
||||
}
|
||||
|
||||
if (!got) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AssignAddedMeshes(meshes,nd.get(),conv);
|
||||
if (conv.collect_openings) {
|
||||
|
||||
// if this pass serves us only to collect opening geometry,
|
||||
// make sure we transform the TempMesh's which we need to
|
||||
// preserve as well.
|
||||
if(const size_t diff = conv.collect_openings->size() - old_openings) {
|
||||
for(size_t i = 0; i < diff; ++i) {
|
||||
(*conv.collect_openings)[old_openings+i].Transform(msrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nd->mTransformation = nd_src->mTransformation * static_cast<aiMatrix4x4>( msrc );
|
||||
subnodes_src.push_back(nd.release());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
struct RateRepresentationPredicate {
|
||||
int Rate(const Schema_2x3::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 Schema_2x3::IfcMappedItem* const m = r->Items.front()->ToPtr<Schema_2x3::IfcMappedItem>();
|
||||
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 Schema_2x3::IfcRepresentation* a, const Schema_2x3::IfcRepresentation* b) const {
|
||||
return Rate(a) < Rate(b);
|
||||
}
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessProductRepresentation(const Schema_2x3::IfcProduct& el, aiNode* nd, std::vector< aiNode* >& subnodes, ConversionData& conv)
|
||||
{
|
||||
if(!el.Representation) {
|
||||
return;
|
||||
}
|
||||
|
||||
// extract Color from metadata, if present
|
||||
unsigned int matid = ProcessMaterials( el.GetID(), std::numeric_limits<uint32_t>::max(), conv, false);
|
||||
std::set<unsigned int> meshes;
|
||||
|
||||
// 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< Schema_2x3::IfcRepresentation >, 1, 0 >& src = el.Representation.Get()->Representations;
|
||||
std::vector<const Schema_2x3::IfcRepresentation*> repr_ordered(src.size());
|
||||
std::copy(src.begin(),src.end(),repr_ordered.begin());
|
||||
std::sort(repr_ordered.begin(),repr_ordered.end(),RateRepresentationPredicate());
|
||||
for(const Schema_2x3::IfcRepresentation* repr : repr_ordered) {
|
||||
bool res = false;
|
||||
for(const Schema_2x3::IfcRepresentationItem& item : repr->Items) {
|
||||
if(const Schema_2x3::IfcMappedItem* const geo = item.ToPtr<Schema_2x3::IfcMappedItem>()) {
|
||||
res = ProcessMappedItem(*geo,nd,subnodes,matid,conv) || res;
|
||||
}
|
||||
else {
|
||||
res = ProcessRepresentationItem(item,matid,meshes,conv) || res;
|
||||
}
|
||||
}
|
||||
// if we got something meaningful at this point, skip any further representations
|
||||
if(res) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
AssignAddedMeshes(meshes,nd,conv);
|
||||
}
|
||||
|
||||
typedef std::map<std::string, std::string> Metadata;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessMetadata(const Schema_2x3::ListOf< Schema_2x3::Lazy< Schema_2x3::IfcProperty >, 1, 0 >& set, ConversionData& conv, Metadata& properties,
|
||||
const std::string& prefix = "",
|
||||
unsigned int nest = 0) {
|
||||
for(const Schema_2x3::IfcProperty& property : set) {
|
||||
const std::string& key = prefix.length() > 0 ? (prefix + "." + property.Name) : property.Name;
|
||||
if (const Schema_2x3::IfcPropertySingleValue* const singleValue = property.ToPtr<Schema_2x3::IfcPropertySingleValue>()) {
|
||||
if (singleValue->NominalValue) {
|
||||
if (const ::Assimp::STEP::EXPRESS::STRING* str = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::STRING>()) {
|
||||
std::string value = static_cast<std::string>(*str);
|
||||
properties[key]=value;
|
||||
}
|
||||
else if (const ::Assimp::STEP::EXPRESS::REAL* val = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
|
||||
float value = static_cast<float>(*val);
|
||||
std::stringstream s;
|
||||
s << value;
|
||||
properties[key]=s.str();
|
||||
}
|
||||
else if (const ::Assimp::STEP::EXPRESS::INTEGER* val = singleValue->NominalValue.Get()->ToPtr<::Assimp::STEP::EXPRESS::INTEGER>()) {
|
||||
int64_t value = static_cast<int64_t>(*val);
|
||||
std::stringstream s;
|
||||
s << value;
|
||||
properties[key]=s.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (const Schema_2x3::IfcPropertyListValue* const listValue = property.ToPtr<Schema_2x3::IfcPropertyListValue>()) {
|
||||
std::stringstream ss;
|
||||
ss << "[";
|
||||
unsigned index=0;
|
||||
for(const Schema_2x3::IfcValue::Out& v : listValue->ListValues) {
|
||||
if (!v) continue;
|
||||
if (const ::Assimp::STEP::EXPRESS::STRING* str = v->ToPtr<::Assimp::STEP::EXPRESS::STRING>()) {
|
||||
std::string value = static_cast<std::string>(*str);
|
||||
ss << "'" << value << "'";
|
||||
}
|
||||
else if (const ::Assimp::STEP::EXPRESS::REAL* val = v->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
|
||||
float value = static_cast<float>(*val);
|
||||
ss << value;
|
||||
}
|
||||
else if (const ::Assimp::STEP::EXPRESS::INTEGER* val = v->ToPtr<::Assimp::STEP::EXPRESS::INTEGER>()) {
|
||||
int64_t value = static_cast<int64_t>(*val);
|
||||
ss << value;
|
||||
}
|
||||
if (index+1<listValue->ListValues.size()) {
|
||||
ss << ",";
|
||||
}
|
||||
index++;
|
||||
}
|
||||
ss << "]";
|
||||
properties[key]=ss.str();
|
||||
}
|
||||
else if (const Schema_2x3::IfcComplexProperty* const complexProp = property.ToPtr<Schema_2x3::IfcComplexProperty>()) {
|
||||
if(nest > 2) { // mostly arbitrary limit to prevent stack overflow vulnerabilities
|
||||
IFCImporter::LogError("maximum nesting level for IfcComplexProperty reached, skipping this property.");
|
||||
}
|
||||
else {
|
||||
ProcessMetadata(complexProp->HasProperties, conv, properties, key, nest + 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
properties[key]="";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessMetadata(uint64_t relDefinesByPropertiesID, ConversionData& conv, Metadata& properties)
|
||||
{
|
||||
if (const Schema_2x3::IfcRelDefinesByProperties* const pset = conv.db.GetObject(relDefinesByPropertiesID)->ToPtr<Schema_2x3::IfcRelDefinesByProperties>()) {
|
||||
if (const Schema_2x3::IfcPropertySet* const set = conv.db.GetObject(pset->RelatingPropertyDefinition->GetID())->ToPtr<Schema_2x3::IfcPropertySet>()) {
|
||||
ProcessMetadata(set->HasProperties, conv, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
aiNode* ProcessSpatialStructure(aiNode* parent, const Schema_2x3::IfcProduct& el, ConversionData& conv,
|
||||
std::vector<TempOpening>* collect_openings = nullptr ) {
|
||||
const STEP::DB::RefMap& refs = conv.db.GetRefs();
|
||||
|
||||
// skip over space and annotation nodes - usually, these have no meaning in Assimp's context
|
||||
bool skipGeometry = false;
|
||||
if(conv.settings.skipSpaceRepresentations) {
|
||||
if(el.ToPtr<Schema_2x3::IfcSpace>()) {
|
||||
IFCImporter::LogDebug("skipping IfcSpace entity due to importer settings");
|
||||
skipGeometry = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(conv.settings.skipAnnotations) {
|
||||
if(el.ToPtr<Schema_2x3::IfcAnnotation>()) {
|
||||
IFCImporter::LogDebug("skipping IfcAnnotation entity due to importer settings");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// add an output node for this spatial structure
|
||||
aiNode *nd(new aiNode );
|
||||
nd->mName.Set(el.GetClassName()+"_"+(el.Name?el.Name.Get():"Unnamed")+"_"+el.GlobalId);
|
||||
nd->mParent = parent;
|
||||
|
||||
conv.already_processed.insert(el.GetID());
|
||||
|
||||
// check for node metadata
|
||||
STEP::DB::RefMapRange children = refs.equal_range(el.GetID());
|
||||
if (children.first!=refs.end()) {
|
||||
Metadata properties;
|
||||
if (children.first==children.second) {
|
||||
// handles single property set
|
||||
ProcessMetadata((*children.first).second, conv, properties);
|
||||
} else {
|
||||
// handles multiple property sets (currently all property sets are merged,
|
||||
// which may not be the best solution in the long run)
|
||||
for (STEP::DB::RefMap::const_iterator it=children.first; it!=children.second; ++it) {
|
||||
ProcessMetadata((*it).second, conv, properties);
|
||||
}
|
||||
}
|
||||
|
||||
if (!properties.empty()) {
|
||||
aiMetadata* data = aiMetadata::Alloc( static_cast<unsigned int>(properties.size()) );
|
||||
unsigned int index( 0 );
|
||||
for ( const Metadata::value_type& kv : properties ) {
|
||||
data->Set( index++, kv.first, aiString( kv.second ) );
|
||||
}
|
||||
nd->mMetaData = data;
|
||||
}
|
||||
}
|
||||
|
||||
if(el.ObjectPlacement) {
|
||||
ResolveObjectPlacement(nd->mTransformation,el.ObjectPlacement.Get(),conv);
|
||||
}
|
||||
|
||||
std::vector<TempOpening> openings;
|
||||
|
||||
IfcMatrix4 myInv;
|
||||
bool didinv = false;
|
||||
|
||||
// convert everything contained directly within this structure,
|
||||
// this may result in more nodes.
|
||||
std::vector< aiNode* > subnodes;
|
||||
try {
|
||||
// locate aggregates and 'contained-in-here'-elements of this spatial structure and add them in recursively
|
||||
// on our way, collect openings in *this* element
|
||||
STEP::DB::RefMapRange range = refs.equal_range(el.GetID());
|
||||
|
||||
for(STEP::DB::RefMapRange range2 = range; range2.first != range.second; ++range2.first) {
|
||||
// skip over meshes that have already been processed before. This is strictly necessary
|
||||
// because the reverse indices also include references contained in argument lists and
|
||||
// therefore every element has a back-reference hold by its parent.
|
||||
if (conv.already_processed.find((*range2.first).second) != conv.already_processed.end()) {
|
||||
continue;
|
||||
}
|
||||
const STEP::LazyObject& obj = conv.db.MustGetObject((*range2.first).second);
|
||||
|
||||
// handle regularly-contained elements
|
||||
if(const Schema_2x3::IfcRelContainedInSpatialStructure* const cont = obj->ToPtr<Schema_2x3::IfcRelContainedInSpatialStructure>()) {
|
||||
if(cont->RelatingStructure->GetID() != el.GetID()) {
|
||||
continue;
|
||||
}
|
||||
for(const Schema_2x3::IfcProduct& pro : cont->RelatedElements) {
|
||||
if(pro.ToPtr<Schema_2x3::IfcOpeningElement>()) {
|
||||
// IfcOpeningElement is handled below. Sadly we can't use it here as is:
|
||||
// The docs say that opening elements are USUALLY attached to building storey,
|
||||
// but we want them for the building elements to which they belong.
|
||||
continue;
|
||||
}
|
||||
|
||||
aiNode* const ndnew = ProcessSpatialStructure(nd,pro,conv,nullptr);
|
||||
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 Schema_2x3::IfcRelVoidsElement* const fills = obj->ToPtr<Schema_2x3::IfcRelVoidsElement>()) {
|
||||
if(fills->RelatingBuildingElement->GetID() == el.GetID()) {
|
||||
const Schema_2x3::IfcFeatureElementSubtraction& open = fills->RelatedOpeningElement;
|
||||
|
||||
// move opening elements to a separate node since they are semantically different than elements that are just 'contained'
|
||||
std::unique_ptr<aiNode> nd_aggr(new aiNode());
|
||||
nd_aggr->mName.Set("$RelVoidsElement");
|
||||
nd_aggr->mParent = nd;
|
||||
|
||||
nd_aggr->mTransformation = nd->mTransformation;
|
||||
|
||||
std::vector<TempOpening> openings_local;
|
||||
aiNode* const ndnew = ProcessSpatialStructure( nd_aggr.get(),open, conv,&openings_local);
|
||||
if (ndnew) {
|
||||
|
||||
nd_aggr->mNumChildren = 1;
|
||||
nd_aggr->mChildren = new aiNode*[1]();
|
||||
|
||||
|
||||
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
|
||||
for(TempOpening& op :openings_local) {
|
||||
op.Transform( myInv*nd_aggr->mChildren[0]->mTransformation);
|
||||
openings.push_back(op);
|
||||
}
|
||||
}
|
||||
subnodes.push_back( nd_aggr.release() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(;range.first != range.second; ++range.first) {
|
||||
// see note in loop above
|
||||
if (conv.already_processed.find((*range.first).second) != conv.already_processed.end()) {
|
||||
continue;
|
||||
}
|
||||
if(const Schema_2x3::IfcRelAggregates* const aggr = conv.db.GetObject((*range.first).second)->ToPtr<Schema_2x3::IfcRelAggregates>()) {
|
||||
if(aggr->RelatingObject->GetID() != el.GetID()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// move aggregate elements to a separate node since they are semantically different than elements that are just 'contained'
|
||||
std::unique_ptr<aiNode> nd_aggr(new aiNode());
|
||||
nd_aggr->mName.Set("$RelAggregates");
|
||||
nd_aggr->mParent = nd;
|
||||
|
||||
nd_aggr->mTransformation = nd->mTransformation;
|
||||
|
||||
nd_aggr->mChildren = new aiNode*[aggr->RelatedObjects.size()]();
|
||||
for(const Schema_2x3::IfcObjectDefinition& def : aggr->RelatedObjects) {
|
||||
if(const Schema_2x3::IfcProduct* const prod = def.ToPtr<Schema_2x3::IfcProduct>()) {
|
||||
|
||||
aiNode* const ndnew = ProcessSpatialStructure(nd_aggr.get(),*prod,conv,NULL);
|
||||
if(ndnew) {
|
||||
nd_aggr->mChildren[nd_aggr->mNumChildren++] = ndnew;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subnodes.push_back( nd_aggr.release() );
|
||||
}
|
||||
}
|
||||
|
||||
conv.collect_openings = collect_openings;
|
||||
if(!conv.collect_openings) {
|
||||
conv.apply_openings = &openings;
|
||||
}
|
||||
|
||||
if (!skipGeometry) {
|
||||
ProcessProductRepresentation(el, nd, subnodes, conv);
|
||||
conv.apply_openings = conv.collect_openings = nullptr;
|
||||
}
|
||||
|
||||
if (subnodes.size()) {
|
||||
nd->mChildren = new aiNode*[subnodes.size()]();
|
||||
for(aiNode* nd2 : subnodes) {
|
||||
nd->mChildren[nd->mNumChildren++] = nd2;
|
||||
nd2->mParent = nd;
|
||||
}
|
||||
}
|
||||
} catch(...) {
|
||||
// it hurts, but I don't want to pull boost::ptr_vector into -noboost only for these few spots here
|
||||
std::for_each(subnodes.begin(),subnodes.end(),delete_fun<aiNode>());
|
||||
throw;
|
||||
}
|
||||
|
||||
ai_assert(conv.already_processed.find(el.GetID()) != conv.already_processed.end());
|
||||
conv.already_processed.erase(conv.already_processed.find(el.GetID()));
|
||||
return nd;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessSpatialStructures(ConversionData& conv)
|
||||
{
|
||||
// XXX add support for multiple sites (i.e. IfcSpatialStructureElements with composition == COMPLEX)
|
||||
|
||||
|
||||
// process all products in the file. it is reasonable to assume that a
|
||||
// file that is relevant for us contains at least a site or a building.
|
||||
const STEP::DB::ObjectMapByType& map = conv.db.GetObjectsByType();
|
||||
|
||||
ai_assert(map.find("ifcsite") != map.end());
|
||||
const STEP::DB::ObjectSet* range = &map.find("ifcsite")->second;
|
||||
|
||||
if (range->empty()) {
|
||||
ai_assert(map.find("ifcbuilding") != map.end());
|
||||
range = &map.find("ifcbuilding")->second;
|
||||
if (range->empty()) {
|
||||
// no site, no building - fail;
|
||||
IFCImporter::ThrowException("no root element found (expected IfcBuilding or preferably IfcSite)");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<aiNode*> nodes;
|
||||
|
||||
for(const STEP::LazyObject* lz : *range) {
|
||||
const Schema_2x3::IfcSpatialStructureElement* const prod = lz->ToPtr<Schema_2x3::IfcSpatialStructureElement>();
|
||||
if(!prod) {
|
||||
continue;
|
||||
}
|
||||
IFCImporter::LogDebug("looking at spatial structure `" + (prod->Name ? prod->Name.Get() : "unnamed") + "`" + (prod->ObjectType? " which is of type " + prod->ObjectType.Get():""));
|
||||
|
||||
// the primary sites are referenced by an IFCRELAGGREGATES element which assigns them to the IFCPRODUCT
|
||||
const STEP::DB::RefMap& refs = conv.db.GetRefs();
|
||||
STEP::DB::RefMapRange ref_range = refs.equal_range(conv.proj.GetID());
|
||||
for(; ref_range.first != ref_range.second; ++ref_range.first) {
|
||||
if(const Schema_2x3::IfcRelAggregates* const aggr = conv.db.GetObject((*ref_range.first).second)->ToPtr<Schema_2x3::IfcRelAggregates>()) {
|
||||
|
||||
for(const Schema_2x3::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()) {
|
||||
IFCImporter::LogDebug("selecting this spatial structure as root structure");
|
||||
// got it, this is one primary site.
|
||||
nodes.push_back(ProcessSpatialStructure(NULL, *prod, conv, NULL));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t nb_nodes = nodes.size();
|
||||
|
||||
if (nb_nodes == 0) {
|
||||
IFCImporter::LogWarn("failed to determine primary site element, taking all the IfcSite");
|
||||
for (const STEP::LazyObject* lz : *range) {
|
||||
const Schema_2x3::IfcSpatialStructureElement* const prod = lz->ToPtr<Schema_2x3::IfcSpatialStructureElement>();
|
||||
if (!prod) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nodes.push_back(ProcessSpatialStructure(NULL, *prod, conv, NULL));
|
||||
}
|
||||
|
||||
nb_nodes = nodes.size();
|
||||
}
|
||||
|
||||
if (nb_nodes == 1) {
|
||||
conv.out->mRootNode = nodes[0];
|
||||
}
|
||||
else if (nb_nodes > 1) {
|
||||
conv.out->mRootNode = new aiNode("Root");
|
||||
conv.out->mRootNode->mParent = NULL;
|
||||
conv.out->mRootNode->mNumChildren = static_cast<unsigned int>(nb_nodes);
|
||||
conv.out->mRootNode->mChildren = new aiNode*[conv.out->mRootNode->mNumChildren];
|
||||
|
||||
for (size_t i = 0; i < nb_nodes; ++i) {
|
||||
aiNode* node = nodes[i];
|
||||
|
||||
node->mParent = conv.out->mRootNode;
|
||||
|
||||
conv.out->mRootNode->mChildren[i] = node;
|
||||
}
|
||||
}
|
||||
else {
|
||||
IFCImporter::ThrowException("failed to determine primary site element");
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void MakeTreeRelative(aiNode* start, const aiMatrix4x4& combined)
|
||||
{
|
||||
// combined is the parent's absolute transformation matrix
|
||||
const aiMatrix4x4 old = start->mTransformation;
|
||||
|
||||
if (!combined.IsIdentity()) {
|
||||
start->mTransformation = aiMatrix4x4(combined).Inverse() * start->mTransformation;
|
||||
}
|
||||
|
||||
// All nodes store absolute transformations right now, so we need to make them relative
|
||||
for (unsigned int i = 0; i < start->mNumChildren; ++i) {
|
||||
MakeTreeRelative(start->mChildren[i],old);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void MakeTreeRelative(ConversionData& conv)
|
||||
{
|
||||
MakeTreeRelative(conv.out->mRootNode,IfcMatrix4());
|
||||
}
|
||||
|
||||
} // !anon
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
134
thirdparty/assimp/code/Importer/IFC/IFCLoader.h
vendored
Normal file
134
thirdparty/assimp/code/Importer/IFC/IFCLoader.h
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
Open Asset Import Library (assimp)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2019, assimp 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 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.h
|
||||
* @brief Declaration of the Industry Foundation Classes (IFC) loader main class
|
||||
*/
|
||||
#ifndef INCLUDED_AI_IFC_LOADER_H
|
||||
#define INCLUDED_AI_IFC_LOADER_H
|
||||
|
||||
#include <assimp/BaseImporter.h>
|
||||
#include <assimp/LogAux.h>
|
||||
|
||||
namespace Assimp {
|
||||
|
||||
// TinyFormatter.h
|
||||
namespace Formatter {
|
||||
template <typename T,typename TR, typename A> class basic_formatter;
|
||||
typedef class basic_formatter< char, std::char_traits<char>, std::allocator<char> > format;
|
||||
}
|
||||
|
||||
namespace STEP {
|
||||
class DB;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
/** Load the IFC format, which is an open specification to describe building and construction
|
||||
industry data.
|
||||
|
||||
See http://en.wikipedia.org/wiki/Industry_Foundation_Classes
|
||||
*/
|
||||
// -------------------------------------------------------------------------------------------
|
||||
class IFCImporter : public BaseImporter, public LogFunctions<IFCImporter>
|
||||
{
|
||||
public:
|
||||
IFCImporter();
|
||||
~IFCImporter();
|
||||
|
||||
|
||||
public:
|
||||
|
||||
// --------------------
|
||||
bool CanRead( const std::string& pFile,
|
||||
IOSystem* pIOHandler,
|
||||
bool checkSig
|
||||
) const;
|
||||
|
||||
protected:
|
||||
|
||||
// --------------------
|
||||
const aiImporterDesc* GetInfo () const;
|
||||
|
||||
// --------------------
|
||||
void SetupProperties(const Importer* pImp);
|
||||
|
||||
// --------------------
|
||||
void InternReadFile( const std::string& pFile,
|
||||
aiScene* pScene,
|
||||
IOSystem* pIOHandler
|
||||
);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
public:
|
||||
|
||||
|
||||
// loader settings, publicly accessible via their corresponding AI_CONFIG constants
|
||||
struct Settings
|
||||
{
|
||||
Settings()
|
||||
: skipSpaceRepresentations()
|
||||
, useCustomTriangulation()
|
||||
, skipAnnotations()
|
||||
, conicSamplingAngle(10.f)
|
||||
, cylindricalTessellation(32)
|
||||
{}
|
||||
|
||||
|
||||
bool skipSpaceRepresentations;
|
||||
bool useCustomTriangulation;
|
||||
bool skipAnnotations;
|
||||
float conicSamplingAngle;
|
||||
int cylindricalTessellation;
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
|
||||
Settings settings;
|
||||
|
||||
}; // !class IFCImporter
|
||||
|
||||
} // end of namespace Assimp
|
||||
#endif // !INCLUDED_AI_IFC_LOADER_H
|
||||
202
thirdparty/assimp/code/Importer/IFC/IFCMaterial.cpp
vendored
Normal file
202
thirdparty/assimp/code/Importer/IFC/IFCMaterial.cpp
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
Open Asset Import Library (assimp)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2019, assimp 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 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
|
||||
*/
|
||||
|
||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||
|
||||
#include "IFCUtil.h"
|
||||
#include <limits>
|
||||
#include <assimp/material.h>
|
||||
|
||||
namespace Assimp {
|
||||
namespace IFC {
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
static 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;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
static void FillMaterial(aiMaterial* mat,const IFC::Schema_2x3::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
|
||||
for(std::shared_ptr< const IFC::Schema_2x3::IfcSurfaceStyleElementSelect > sel2 : surf->Styles) {
|
||||
if (const IFC::Schema_2x3::IfcSurfaceStyleShading* shade = sel2->ResolveSelectPtr<IFC::Schema_2x3::IfcSurfaceStyleShading>(conv.db)) {
|
||||
aiColor4D col_base,col;
|
||||
|
||||
ConvertColor(col_base, shade->SurfaceColour);
|
||||
mat->AddProperty(&col_base,1, AI_MATKEY_COLOR_DIFFUSE);
|
||||
|
||||
if (const IFC::Schema_2x3::IfcSurfaceStyleRendering* ren = shade->ToPtr<IFC::Schema_2x3::IfcSurfaceStyleRendering>()) {
|
||||
|
||||
if (ren->Transparency) {
|
||||
const float t = 1.f-static_cast<float>(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<int>(aiShadingMode_Gouraud);
|
||||
mat->AddProperty(&shading,1, AI_MATKEY_SHADING_MODEL);
|
||||
|
||||
if (ren->SpecularHighlight) {
|
||||
if(const ::Assimp::STEP::EXPRESS::REAL* rt = ren->SpecularHighlight.Get()->ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
|
||||
// at this point we don't distinguish between the two distinct ways of
|
||||
// specifying highlight intensities. leave this to the user.
|
||||
const float e = static_cast<float>(*rt);
|
||||
mat->AddProperty(&e,1,AI_MATKEY_SHININESS);
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogWarn("unexpected type error, SpecularHighlight should be a REAL");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionData& conv, bool forceDefaultMat) {
|
||||
STEP::DB::RefMapRange range = conv.db.GetRefs().equal_range(id);
|
||||
for(;range.first != range.second; ++range.first) {
|
||||
if(const IFC::Schema_2x3::IfcStyledItem* const styled = conv.db.GetObject((*range.first).second)->ToPtr<IFC::Schema_2x3::IfcStyledItem>()) {
|
||||
for(const IFC::Schema_2x3::IfcPresentationStyleAssignment& as : styled->Styles) {
|
||||
for(std::shared_ptr<const IFC::Schema_2x3::IfcPresentationStyleSelect> sel : as.Styles) {
|
||||
|
||||
if( const IFC::Schema_2x3::IfcSurfaceStyle* const surf = sel->ResolveSelectPtr<IFC::Schema_2x3::IfcSurfaceStyle>(conv.db) ) {
|
||||
// try to satisfy from cache
|
||||
ConversionData::MaterialCache::iterator mit = conv.cached_materials.find(surf);
|
||||
if( mit != conv.cached_materials.end() )
|
||||
return mit->second;
|
||||
|
||||
// not found, create new material
|
||||
const std::string side = static_cast<std::string>(surf->Side);
|
||||
if( side != "BOTH" ) {
|
||||
IFCImporter::LogWarn("ignoring surface side marker on IFC::IfcSurfaceStyle: " + side);
|
||||
}
|
||||
|
||||
std::unique_ptr<aiMaterial> mat(new aiMaterial());
|
||||
|
||||
FillMaterial(mat.get(), surf, conv);
|
||||
|
||||
conv.materials.push_back(mat.release());
|
||||
unsigned int matindex = static_cast<unsigned int>(conv.materials.size() - 1);
|
||||
conv.cached_materials[surf] = matindex;
|
||||
return matindex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no local material defined. If there's global one, use that instead
|
||||
if ( prevMatId != std::numeric_limits<uint32_t>::max() ) {
|
||||
return prevMatId;
|
||||
}
|
||||
|
||||
// we're still here - create an default material if required, or simply fail otherwise
|
||||
if ( !forceDefaultMat ) {
|
||||
return std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
|
||||
aiString name;
|
||||
name.Set("<IFCDefault>");
|
||||
// ConvertColorToString( color, name);
|
||||
|
||||
// look if there's already a default material with this base color
|
||||
for( size_t a = 0; a < conv.materials.size(); ++a ) {
|
||||
aiString mname;
|
||||
conv.materials[a]->Get(AI_MATKEY_NAME, mname);
|
||||
if ( name == mname ) {
|
||||
return ( unsigned int )a;
|
||||
}
|
||||
}
|
||||
|
||||
// we're here, yet - no default material with suitable color available. Generate one
|
||||
std::unique_ptr<aiMaterial> mat(new aiMaterial());
|
||||
mat->AddProperty(&name,AI_MATKEY_NAME);
|
||||
|
||||
const aiColor4D col = aiColor4D( 0.6f, 0.6f, 0.6f, 1.0f); // aiColor4D( color.r, color.g, color.b, 1.0f);
|
||||
mat->AddProperty(&col,1, AI_MATKEY_COLOR_DIFFUSE);
|
||||
|
||||
conv.materials.push_back(mat.release());
|
||||
return (unsigned int) conv.materials.size() - 1;
|
||||
}
|
||||
|
||||
} // ! IFC
|
||||
} // ! Assimp
|
||||
|
||||
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||
1704
thirdparty/assimp/code/Importer/IFC/IFCOpenings.cpp
vendored
Normal file
1704
thirdparty/assimp/code/Importer/IFC/IFCOpenings.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
190
thirdparty/assimp/code/Importer/IFC/IFCProfile.cpp
vendored
Normal file
190
thirdparty/assimp/code/Importer/IFC/IFCProfile.cpp
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
Open Asset Import Library (assimp)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2019, assimp 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 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
|
||||
*/
|
||||
|
||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||
|
||||
#include "IFCUtil.h"
|
||||
|
||||
namespace Assimp {
|
||||
namespace IFC {
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, TempMesh& meshout, ConversionData& /*conv*/)
|
||||
{
|
||||
// this won't produce a valid mesh, it just spits out a list of vertices
|
||||
IfcVector3 t;
|
||||
for(const Schema_2x3::IfcCartesianPoint& cp : def.Points) {
|
||||
ConvertCartesianPoint(t,cp);
|
||||
meshout.mVerts.push_back(t);
|
||||
}
|
||||
meshout.mVertcnt.push_back(static_cast<unsigned int>(meshout.mVerts.size()));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv)
|
||||
{
|
||||
std::unique_ptr<const Curve> cv(Curve::Convert(curve,conv));
|
||||
if (!cv) {
|
||||
IFCImporter::LogWarn("skipping unknown IfcCurve entity, type is " + curve.GetClassName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// we must have a bounded curve at this point
|
||||
if (const BoundedCurve* bc = dynamic_cast<const BoundedCurve*>(cv.get())) {
|
||||
try {
|
||||
bc->SampleDiscrete(meshout);
|
||||
}
|
||||
catch(const CurveError& cv) {
|
||||
IFCImporter::LogError(cv.mStr + " (error occurred while processing curve)");
|
||||
return false;
|
||||
}
|
||||
meshout.mVertcnt.push_back(static_cast<unsigned int>(meshout.mVerts.size()));
|
||||
return true;
|
||||
}
|
||||
|
||||
IFCImporter::LogError("cannot use unbounded curve as profile");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessClosedProfile(const Schema_2x3::IfcArbitraryClosedProfileDef& def, TempMesh& meshout, ConversionData& conv)
|
||||
{
|
||||
ProcessCurve(def.OuterCurve,meshout,conv);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessOpenProfile(const Schema_2x3::IfcArbitraryOpenProfileDef& def, TempMesh& meshout, ConversionData& conv)
|
||||
{
|
||||
ProcessCurve(def.Curve,meshout,conv);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& def, TempMesh& meshout, ConversionData& conv)
|
||||
{
|
||||
if(const Schema_2x3::IfcRectangleProfileDef* const cprofile = def.ToPtr<Schema_2x3::IfcRectangleProfileDef>()) {
|
||||
const IfcFloat x = cprofile->XDim*0.5f, y = cprofile->YDim*0.5f;
|
||||
|
||||
meshout.mVerts.reserve(meshout.mVerts.size()+4);
|
||||
meshout.mVerts.push_back( IfcVector3( x, y, 0.f ));
|
||||
meshout.mVerts.push_back( IfcVector3(-x, y, 0.f ));
|
||||
meshout.mVerts.push_back( IfcVector3(-x,-y, 0.f ));
|
||||
meshout.mVerts.push_back( IfcVector3( x,-y, 0.f ));
|
||||
meshout.mVertcnt.push_back(4);
|
||||
}
|
||||
else if( const Schema_2x3::IfcCircleProfileDef* const circle = def.ToPtr<Schema_2x3::IfcCircleProfileDef>()) {
|
||||
if(def.ToPtr<Schema_2x3::IfcCircleHollowProfileDef>()) {
|
||||
// TODO
|
||||
}
|
||||
const size_t segments = conv.settings.cylindricalTessellation;
|
||||
const IfcFloat delta = AI_MATH_TWO_PI_F/segments, radius = circle->Radius;
|
||||
|
||||
meshout.mVerts.reserve(segments);
|
||||
|
||||
IfcFloat angle = 0.f;
|
||||
for(size_t i = 0; i < segments; ++i, angle += delta) {
|
||||
meshout.mVerts.push_back( IfcVector3( std::cos(angle)*radius, std::sin(angle)*radius, 0.f ));
|
||||
}
|
||||
|
||||
meshout.mVertcnt.push_back(static_cast<unsigned int>(segments));
|
||||
}
|
||||
else if( const Schema_2x3::IfcIShapeProfileDef* const ishape = def.ToPtr<Schema_2x3::IfcIShapeProfileDef>()) {
|
||||
// construct simplified IBeam shape
|
||||
const IfcFloat offset = (ishape->OverallWidth - ishape->WebThickness) / 2;
|
||||
const IfcFloat inner_height = ishape->OverallDepth - ishape->FlangeThickness * 2;
|
||||
|
||||
meshout.mVerts.reserve(12);
|
||||
meshout.mVerts.push_back(IfcVector3(0,0,0));
|
||||
meshout.mVerts.push_back(IfcVector3(0,ishape->FlangeThickness,0));
|
||||
meshout.mVerts.push_back(IfcVector3(offset,ishape->FlangeThickness,0));
|
||||
meshout.mVerts.push_back(IfcVector3(offset,ishape->FlangeThickness + inner_height,0));
|
||||
meshout.mVerts.push_back(IfcVector3(0,ishape->FlangeThickness + inner_height,0));
|
||||
meshout.mVerts.push_back(IfcVector3(0,ishape->OverallDepth,0));
|
||||
meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,ishape->OverallDepth,0));
|
||||
meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,ishape->FlangeThickness + inner_height,0));
|
||||
meshout.mVerts.push_back(IfcVector3(offset+ishape->WebThickness,ishape->FlangeThickness + inner_height,0));
|
||||
meshout.mVerts.push_back(IfcVector3(offset+ishape->WebThickness,ishape->FlangeThickness,0));
|
||||
meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,ishape->FlangeThickness,0));
|
||||
meshout.mVerts.push_back(IfcVector3(ishape->OverallWidth,0,0));
|
||||
|
||||
meshout.mVertcnt.push_back(12);
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is " + def.GetClassName());
|
||||
return;
|
||||
}
|
||||
|
||||
IfcMatrix4 trafo;
|
||||
ConvertAxisPlacement(trafo, *def.Position);
|
||||
meshout.Transform(trafo);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv)
|
||||
{
|
||||
if(const Schema_2x3::IfcArbitraryClosedProfileDef* const cprofile = prof.ToPtr<Schema_2x3::IfcArbitraryClosedProfileDef>()) {
|
||||
ProcessClosedProfile(*cprofile,meshout,conv);
|
||||
}
|
||||
else if(const Schema_2x3::IfcArbitraryOpenProfileDef* const copen = prof.ToPtr<Schema_2x3::IfcArbitraryOpenProfileDef>()) {
|
||||
ProcessOpenProfile(*copen,meshout,conv);
|
||||
}
|
||||
else if(const Schema_2x3::IfcParameterizedProfileDef* const cparam = prof.ToPtr<Schema_2x3::IfcParameterizedProfileDef>()) {
|
||||
ProcessParametrizedProfile(*cparam,meshout,conv);
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is " + prof.GetClassName());
|
||||
return false;
|
||||
}
|
||||
meshout.RemoveAdjacentDuplicates();
|
||||
if (!meshout.mVertcnt.size() || meshout.mVertcnt.front() <= 1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // ! IFC
|
||||
} // ! Assimp
|
||||
|
||||
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||
3168
thirdparty/assimp/code/Importer/IFC/IFCReaderGen1_2x3.cpp
vendored
Normal file
3168
thirdparty/assimp/code/Importer/IFC/IFCReaderGen1_2x3.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1918
thirdparty/assimp/code/Importer/IFC/IFCReaderGen2_2x3.cpp
vendored
Normal file
1918
thirdparty/assimp/code/Importer/IFC/IFCReaderGen2_2x3.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4371
thirdparty/assimp/code/Importer/IFC/IFCReaderGen_2x3.h
vendored
Normal file
4371
thirdparty/assimp/code/Importer/IFC/IFCReaderGen_2x3.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6207
thirdparty/assimp/code/Importer/IFC/IFCReaderGen_4.cpp
vendored
Normal file
6207
thirdparty/assimp/code/Importer/IFC/IFCReaderGen_4.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5452
thirdparty/assimp/code/Importer/IFC/IFCReaderGen_4.h
vendored
Normal file
5452
thirdparty/assimp/code/Importer/IFC/IFCReaderGen_4.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
703
thirdparty/assimp/code/Importer/IFC/IFCUtil.cpp
vendored
Normal file
703
thirdparty/assimp/code/Importer/IFC/IFCUtil.cpp
vendored
Normal file
@@ -0,0 +1,703 @@
|
||||
/*
|
||||
Open Asset Import Library (assimp)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2019, assimp 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 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.
|
||||
*/
|
||||
|
||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||
|
||||
#include "Importer/IFC/IFCUtil.h"
|
||||
#include "Common/PolyTools.h"
|
||||
#include "PostProcessing/ProcessHelper.h"
|
||||
#include <assimp/Defines.h>
|
||||
|
||||
namespace Assimp {
|
||||
namespace IFC {
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void TempOpening::Transform(const IfcMatrix4& mat) {
|
||||
if(profileMesh) {
|
||||
profileMesh->Transform(mat);
|
||||
}
|
||||
if(profileMesh2D) {
|
||||
profileMesh2D->Transform(mat);
|
||||
}
|
||||
extrusionDir *= IfcMatrix3(mat);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
aiMesh* TempMesh::ToMesh()
|
||||
{
|
||||
ai_assert(mVerts.size() == std::accumulate(mVertcnt.begin(),mVertcnt.end(),size_t(0)));
|
||||
|
||||
if (mVerts.empty()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::unique_ptr<aiMesh> mesh(new aiMesh());
|
||||
|
||||
// copy vertices
|
||||
mesh->mNumVertices = static_cast<unsigned int>(mVerts.size());
|
||||
mesh->mVertices = new aiVector3D[mesh->mNumVertices];
|
||||
std::copy(mVerts.begin(),mVerts.end(),mesh->mVertices);
|
||||
|
||||
// and build up faces
|
||||
mesh->mNumFaces = static_cast<unsigned int>(mVertcnt.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 (!mVertcnt[n]) {
|
||||
--mesh->mNumFaces;
|
||||
continue;
|
||||
}
|
||||
|
||||
f.mNumIndices = mVertcnt[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()
|
||||
{
|
||||
mVerts.clear();
|
||||
mVertcnt.clear();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void TempMesh::Transform(const IfcMatrix4& mat)
|
||||
{
|
||||
for(IfcVector3& v : mVerts) {
|
||||
v *= mat;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
IfcVector3 TempMesh::Center() const
|
||||
{
|
||||
return (mVerts.size() == 0) ? IfcVector3(0.0f, 0.0f, 0.0f) : (std::accumulate(mVerts.begin(),mVerts.end(),IfcVector3()) / static_cast<IfcFloat>(mVerts.size()));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void TempMesh::Append(const TempMesh& other)
|
||||
{
|
||||
mVerts.insert(mVerts.end(),other.mVerts.begin(),other.mVerts.end());
|
||||
mVertcnt.insert(mVertcnt.end(),other.mVertcnt.begin(),other.mVertcnt.end());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void TempMesh::RemoveDegenerates()
|
||||
{
|
||||
// The strategy is simple: walk the mesh and compute normals using
|
||||
// Newell's algorithm. The length of the normals gives the area
|
||||
// of the polygons, which is close to zero for lines.
|
||||
|
||||
std::vector<IfcVector3> normals;
|
||||
ComputePolygonNormals(normals, false);
|
||||
|
||||
bool drop = false;
|
||||
size_t inor = 0;
|
||||
|
||||
std::vector<IfcVector3>::iterator vit = mVerts.begin();
|
||||
for (std::vector<unsigned int>::iterator it = mVertcnt.begin(); it != mVertcnt.end(); ++inor) {
|
||||
const unsigned int pcount = *it;
|
||||
|
||||
if (normals[inor].SquareLength() < 1e-10f) {
|
||||
it = mVertcnt.erase(it);
|
||||
vit = mVerts.erase(vit, vit + pcount);
|
||||
|
||||
drop = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
vit += pcount;
|
||||
++it;
|
||||
}
|
||||
|
||||
if(drop) {
|
||||
IFCImporter::LogDebug("removing degenerate faces");
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize)
|
||||
{
|
||||
std::vector<IfcFloat> temp((cnt+2)*3);
|
||||
for( size_t vofs = 0, i = 0; vofs < cnt; ++vofs )
|
||||
{
|
||||
const IfcVector3& v = vtcs[vofs];
|
||||
temp[i++] = v.x;
|
||||
temp[i++] = v.y;
|
||||
temp[i++] = v.z;
|
||||
}
|
||||
|
||||
IfcVector3 nor;
|
||||
NewellNormal<3, 3, 3>(nor, static_cast<int>(cnt), &temp[0], &temp[1], &temp[2]);
|
||||
return normalize ? nor.Normalize() : nor;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void TempMesh::ComputePolygonNormals(std::vector<IfcVector3>& normals,
|
||||
bool normalize,
|
||||
size_t ofs) const
|
||||
{
|
||||
size_t max_vcount = 0;
|
||||
std::vector<unsigned int>::const_iterator begin = mVertcnt.begin()+ofs, end = mVertcnt.end(), iit;
|
||||
for(iit = begin; iit != end; ++iit) {
|
||||
max_vcount = std::max(max_vcount,static_cast<size_t>(*iit));
|
||||
}
|
||||
|
||||
std::vector<IfcFloat> temp((max_vcount+2)*4);
|
||||
normals.reserve( normals.size() + mVertcnt.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(mVertcnt.begin(),begin,0);
|
||||
for(iit = begin; iit != end; vidx += *iit++) {
|
||||
if (!*iit) {
|
||||
normals.push_back(IfcVector3());
|
||||
continue;
|
||||
}
|
||||
for(size_t vofs = 0, cnt = 0; vofs < *iit; ++vofs) {
|
||||
const IfcVector3& v = mVerts[vidx+vofs];
|
||||
temp[cnt++] = v.x;
|
||||
temp[cnt++] = v.y;
|
||||
temp[cnt++] = v.z;
|
||||
#ifdef ASSIMP_BUILD_DEBUG
|
||||
temp[cnt] = std::numeric_limits<IfcFloat>::quiet_NaN();
|
||||
#endif
|
||||
++cnt;
|
||||
}
|
||||
|
||||
normals.push_back(IfcVector3());
|
||||
NewellNormal<4,4,4>(normals.back(),*iit,&temp[0],&temp[1],&temp[2]);
|
||||
}
|
||||
|
||||
if(normalize) {
|
||||
for(IfcVector3& n : normals) {
|
||||
n.Normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Compute the normal of the last polygon in the given mesh
|
||||
IfcVector3 TempMesh::ComputeLastPolygonNormal(bool normalize) const
|
||||
{
|
||||
return ComputePolygonNormal(&mVerts[mVerts.size() - mVertcnt.back()], mVertcnt.back(), normalize);
|
||||
}
|
||||
|
||||
struct CompareVector
|
||||
{
|
||||
bool operator () (const IfcVector3& a, const IfcVector3& b) const
|
||||
{
|
||||
IfcVector3 d = a - b;
|
||||
IfcFloat eps = 1e-6;
|
||||
return d.x < -eps || (std::abs(d.x) < eps && d.y < -eps) || (std::abs(d.x) < eps && std::abs(d.y) < eps && d.z < -eps);
|
||||
}
|
||||
};
|
||||
struct FindVector
|
||||
{
|
||||
IfcVector3 v;
|
||||
FindVector(const IfcVector3& p) : v(p) { }
|
||||
bool operator () (const IfcVector3& p) { return FuzzyVectorCompare(1e-6)(p, v); }
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void TempMesh::FixupFaceOrientation()
|
||||
{
|
||||
const IfcVector3 vavg = Center();
|
||||
|
||||
// create a list of start indices for all faces to allow random access to faces
|
||||
std::vector<size_t> faceStartIndices(mVertcnt.size());
|
||||
for( size_t i = 0, a = 0; a < mVertcnt.size(); i += mVertcnt[a], ++a )
|
||||
faceStartIndices[a] = i;
|
||||
|
||||
// list all faces on a vertex
|
||||
std::map<IfcVector3, std::vector<size_t>, CompareVector> facesByVertex;
|
||||
for( size_t a = 0; a < mVertcnt.size(); ++a )
|
||||
{
|
||||
for( size_t b = 0; b < mVertcnt[a]; ++b )
|
||||
facesByVertex[mVerts[faceStartIndices[a] + b]].push_back(a);
|
||||
}
|
||||
// determine neighbourhood for all polys
|
||||
std::vector<size_t> neighbour(mVerts.size(), SIZE_MAX);
|
||||
std::vector<size_t> tempIntersect(10);
|
||||
for( size_t a = 0; a < mVertcnt.size(); ++a )
|
||||
{
|
||||
for( size_t b = 0; b < mVertcnt[a]; ++b )
|
||||
{
|
||||
size_t ib = faceStartIndices[a] + b, nib = faceStartIndices[a] + (b + 1) % mVertcnt[a];
|
||||
const std::vector<size_t>& facesOnB = facesByVertex[mVerts[ib]];
|
||||
const std::vector<size_t>& facesOnNB = facesByVertex[mVerts[nib]];
|
||||
// there should be exactly one or two faces which appear in both lists. Our face and the other side
|
||||
std::vector<size_t>::iterator sectstart = tempIntersect.begin();
|
||||
std::vector<size_t>::iterator sectend = std::set_intersection(
|
||||
facesOnB.begin(), facesOnB.end(), facesOnNB.begin(), facesOnNB.end(), sectstart);
|
||||
|
||||
if( std::distance(sectstart, sectend) != 2 )
|
||||
continue;
|
||||
if( *sectstart == a )
|
||||
++sectstart;
|
||||
neighbour[ib] = *sectstart;
|
||||
}
|
||||
}
|
||||
|
||||
// now we're getting started. We take the face which is the farthest away from the center. This face is most probably
|
||||
// facing outwards. So we reverse this face to point outwards in relation to the center. Then we adapt neighbouring
|
||||
// faces to have the same winding until all faces have been tested.
|
||||
std::vector<bool> faceDone(mVertcnt.size(), false);
|
||||
while( std::count(faceDone.begin(), faceDone.end(), false) != 0 )
|
||||
{
|
||||
// find the farthest of the remaining faces
|
||||
size_t farthestIndex = SIZE_MAX;
|
||||
IfcFloat farthestDistance = -1.0;
|
||||
for( size_t a = 0; a < mVertcnt.size(); ++a )
|
||||
{
|
||||
if( faceDone[a] )
|
||||
continue;
|
||||
IfcVector3 faceCenter = std::accumulate(mVerts.begin() + faceStartIndices[a],
|
||||
mVerts.begin() + faceStartIndices[a] + mVertcnt[a], IfcVector3(0.0)) / IfcFloat(mVertcnt[a]);
|
||||
IfcFloat dst = (faceCenter - vavg).SquareLength();
|
||||
if( dst > farthestDistance ) { farthestDistance = dst; farthestIndex = a; }
|
||||
}
|
||||
|
||||
// calculate its normal and reverse the poly if its facing towards the mesh center
|
||||
IfcVector3 farthestNormal = ComputePolygonNormal(mVerts.data() + faceStartIndices[farthestIndex], mVertcnt[farthestIndex]);
|
||||
IfcVector3 farthestCenter = std::accumulate(mVerts.begin() + faceStartIndices[farthestIndex],
|
||||
mVerts.begin() + faceStartIndices[farthestIndex] + mVertcnt[farthestIndex], IfcVector3(0.0))
|
||||
/ IfcFloat(mVertcnt[farthestIndex]);
|
||||
// We accept a bit of negative orientation without reversing. In case of doubt, prefer the orientation given in
|
||||
// the file.
|
||||
if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 )
|
||||
{
|
||||
size_t fsi = faceStartIndices[farthestIndex], fvc = mVertcnt[farthestIndex];
|
||||
std::reverse(mVerts.begin() + fsi, mVerts.begin() + fsi + fvc);
|
||||
std::reverse(neighbour.begin() + fsi, neighbour.begin() + fsi + fvc);
|
||||
// because of the neighbour index belonging to the edge starting with the point at the same index, we need to
|
||||
// cycle the neighbours through to match the edges again.
|
||||
// Before: points A - B - C - D with edge neighbour p - q - r - s
|
||||
// After: points D - C - B - A, reversed neighbours are s - r - q - p, but the should be
|
||||
// r q p s
|
||||
for( size_t a = 0; a < fvc - 1; ++a )
|
||||
std::swap(neighbour[fsi + a], neighbour[fsi + a + 1]);
|
||||
}
|
||||
faceDone[farthestIndex] = true;
|
||||
std::vector<size_t> todo;
|
||||
todo.push_back(farthestIndex);
|
||||
|
||||
// go over its neighbour faces recursively and adapt their winding order to match the farthest face
|
||||
while( !todo.empty() )
|
||||
{
|
||||
size_t tdf = todo.back();
|
||||
size_t vsi = faceStartIndices[tdf], vc = mVertcnt[tdf];
|
||||
todo.pop_back();
|
||||
|
||||
// check its neighbours
|
||||
for( size_t a = 0; a < vc; ++a )
|
||||
{
|
||||
// ignore neighbours if we already checked them
|
||||
size_t nbi = neighbour[vsi + a];
|
||||
if( nbi == SIZE_MAX || faceDone[nbi] )
|
||||
continue;
|
||||
|
||||
const IfcVector3& vp = mVerts[vsi + a];
|
||||
size_t nbvsi = faceStartIndices[nbi], nbvc = mVertcnt[nbi];
|
||||
std::vector<IfcVector3>::iterator it = std::find_if(mVerts.begin() + nbvsi, mVerts.begin() + nbvsi + nbvc, FindVector(vp));
|
||||
ai_assert(it != mVerts.begin() + nbvsi + nbvc);
|
||||
size_t nb_vidx = std::distance(mVerts.begin() + nbvsi, it);
|
||||
// two faces winded in the same direction should have a crossed edge, where one face has p0->p1 and the other
|
||||
// has p1'->p0'. If the next point on the neighbouring face is also the next on the current face, we need
|
||||
// to reverse the neighbour
|
||||
nb_vidx = (nb_vidx + 1) % nbvc;
|
||||
size_t oursideidx = (a + 1) % vc;
|
||||
if( FuzzyVectorCompare(1e-6)(mVerts[vsi + oursideidx], mVerts[nbvsi + nb_vidx]) )
|
||||
{
|
||||
std::reverse(mVerts.begin() + nbvsi, mVerts.begin() + nbvsi + nbvc);
|
||||
std::reverse(neighbour.begin() + nbvsi, neighbour.begin() + nbvsi + nbvc);
|
||||
for( size_t a = 0; a < nbvc - 1; ++a )
|
||||
std::swap(neighbour[nbvsi + a], neighbour[nbvsi + a + 1]);
|
||||
}
|
||||
|
||||
// either way we're done with the neighbour. Mark it as done and continue checking from there recursively
|
||||
faceDone[nbi] = true;
|
||||
todo.push_back(nbi);
|
||||
}
|
||||
}
|
||||
|
||||
// no more faces reachable from this part of the surface, start over with a disjunct part and its farthest face
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void TempMesh::RemoveAdjacentDuplicates() {
|
||||
bool drop = false;
|
||||
std::vector<IfcVector3>::iterator base = mVerts.begin();
|
||||
for(unsigned int& cnt : mVertcnt) {
|
||||
if (cnt < 2){
|
||||
base += cnt;
|
||||
continue;
|
||||
}
|
||||
|
||||
IfcVector3 vmin,vmax;
|
||||
ArrayBounds(&*base, cnt ,vmin,vmax);
|
||||
|
||||
|
||||
const IfcFloat epsilon = (vmax-vmin).SquareLength() / static_cast<IfcFloat>(1e9);
|
||||
//const IfcFloat 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) {
|
||||
// IfcVector3& v1 = *(base+i), &v0 = *(base+(i?i-1:cnt-1)), &v2 = *(base+(i+1)%cnt);
|
||||
// const IfcVector3& d0 = (v1-v0), &d1 = (v2-v1);
|
||||
// const IfcFloat l0 = d0.SquareLength(), l1 = d1.SquareLength();
|
||||
// if (!l0 || !l1) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// const IfcFloat d = (d0/std::sqrt(l0))*(d1/std::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<IfcVector3>::iterator end = base+cnt, e = std::unique( base, end, fz );
|
||||
if (e != end) {
|
||||
cnt -= static_cast<unsigned int>(std::distance(e, end));
|
||||
mVerts.erase(e,end);
|
||||
drop = true;
|
||||
}
|
||||
|
||||
// check front and back vertices for this polygon
|
||||
if (cnt > 1 && fz(*base,*(base+cnt-1))) {
|
||||
mVerts.erase(base+ --cnt);
|
||||
drop = true;
|
||||
}
|
||||
|
||||
// removing adjacent duplicates shouldn't erase everything :-)
|
||||
ai_assert(cnt>0);
|
||||
base += cnt;
|
||||
}
|
||||
if(drop) {
|
||||
IFCImporter::LogDebug("removing duplicate vertices");
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void TempMesh::Swap(TempMesh& other)
|
||||
{
|
||||
mVertcnt.swap(other.mVertcnt);
|
||||
mVerts.swap(other.mVerts);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool IsTrue(const ::Assimp::STEP::EXPRESS::BOOLEAN& in)
|
||||
{
|
||||
return (std::string)in == "TRUE" || (std::string)in == "T";
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
IfcFloat 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 Schema_2x3::IfcColourRgb& in)
|
||||
{
|
||||
out.r = static_cast<float>( in.Red );
|
||||
out.g = static_cast<float>( in.Green );
|
||||
out.b = static_cast<float>( in.Blue );
|
||||
out.a = static_cast<float>( 1.f );
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base)
|
||||
{
|
||||
if (const ::Assimp::STEP::EXPRESS::REAL* const r = in.ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
|
||||
out.r = out.g = out.b = static_cast<float>(*r);
|
||||
if(base) {
|
||||
out.r *= static_cast<float>( base->r );
|
||||
out.g *= static_cast<float>( base->g );
|
||||
out.b *= static_cast<float>( base->b );
|
||||
out.a = static_cast<float>( base->a );
|
||||
}
|
||||
else out.a = 1.0;
|
||||
}
|
||||
else if (const Schema_2x3::IfcColourRgb* const rgb = in.ResolveSelectPtr<Schema_2x3::IfcColourRgb>(conv.db)) {
|
||||
ConvertColor(out,*rgb);
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogWarn("skipping unknown IfcColourOrFactor entity");
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in)
|
||||
{
|
||||
out = IfcVector3();
|
||||
for(size_t i = 0; i < in.Coordinates.size(); ++i) {
|
||||
out[static_cast<unsigned int>(i)] = in.Coordinates[i];
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in)
|
||||
{
|
||||
ConvertDirection(out,in.Orientation);
|
||||
out *= in.Magnitude;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in)
|
||||
{
|
||||
out = IfcVector3();
|
||||
for(size_t i = 0; i < in.DirectionRatios.size(); ++i) {
|
||||
out[static_cast<unsigned int>(i)] = in.DirectionRatios[i];
|
||||
}
|
||||
const IfcFloat len = out.Length();
|
||||
if (len<1e-6) {
|
||||
IFCImporter::LogWarn("direction vector magnitude too small, normalization would result in a division by zero");
|
||||
return;
|
||||
}
|
||||
out /= len;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& 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(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in)
|
||||
{
|
||||
IfcVector3 loc;
|
||||
ConvertCartesianPoint(loc,in.Location);
|
||||
|
||||
IfcVector3 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());
|
||||
}
|
||||
|
||||
IfcVector3 v = r.Normalize();
|
||||
IfcVector3 tmpx = z * (v*z);
|
||||
|
||||
x = (v-tmpx).Normalize();
|
||||
IfcVector3 y = (z^x);
|
||||
|
||||
IfcMatrix4::Translation(loc,out);
|
||||
AssignMatrixAxes(out,x,y,z);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in)
|
||||
{
|
||||
IfcVector3 loc;
|
||||
ConvertCartesianPoint(loc,in.Location);
|
||||
|
||||
IfcVector3 x(1.f,0.f,0.f);
|
||||
if (in.RefDirection) {
|
||||
ConvertDirection(x,*in.RefDirection.Get());
|
||||
}
|
||||
|
||||
const IfcVector3 y = IfcVector3(x.y,-x.x,0.f);
|
||||
|
||||
IfcMatrix4::Translation(loc,out);
|
||||
AssignMatrixAxes(out,x,y,IfcVector3(0.f,0.f,1.f));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const Schema_2x3::IfcAxis1Placement& in)
|
||||
{
|
||||
ConvertCartesianPoint(pos,in.Location);
|
||||
if (in.Axis) {
|
||||
ConvertDirection(axis,in.Axis.Get());
|
||||
}
|
||||
else {
|
||||
axis = IfcVector3(0.f,0.f,1.f);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv)
|
||||
{
|
||||
if(const Schema_2x3::IfcAxis2Placement3D* pl3 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement3D>(conv.db)) {
|
||||
ConvertAxisPlacement(out,*pl3);
|
||||
}
|
||||
else if(const Schema_2x3::IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement2D>(conv.db)) {
|
||||
ConvertAxisPlacement(out,*pl2);
|
||||
}
|
||||
else {
|
||||
IFCImporter::LogWarn("skipping unknown IfcAxis2Placement entity");
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op)
|
||||
{
|
||||
IfcVector3 loc;
|
||||
ConvertCartesianPoint(loc,op.LocalOrigin);
|
||||
|
||||
IfcVector3 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 Schema_2x3::IfcCartesianTransformationOperator3D* op2 = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3D>()) {
|
||||
if(op2->Axis3) {
|
||||
ConvertDirection(z,*op2->Axis3.Get());
|
||||
}
|
||||
}
|
||||
|
||||
IfcMatrix4 locm;
|
||||
IfcMatrix4::Translation(loc,locm);
|
||||
AssignMatrixAxes(out,x,y,z);
|
||||
|
||||
|
||||
IfcVector3 vscale;
|
||||
if (const Schema_2x3::IfcCartesianTransformationOperator3DnonUniform* nuni = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3DnonUniform>()) {
|
||||
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 IfcFloat sc = op.Scale?op.Scale.Get():1.f;
|
||||
vscale = IfcVector3(sc,sc,sc);
|
||||
}
|
||||
|
||||
IfcMatrix4 s;
|
||||
IfcMatrix4::Scaling(vscale,s);
|
||||
|
||||
out = locm * out * s;
|
||||
}
|
||||
|
||||
|
||||
} // ! IFC
|
||||
} // ! Assimp
|
||||
|
||||
#endif
|
||||
413
thirdparty/assimp/code/Importer/IFC/IFCUtil.h
vendored
Normal file
413
thirdparty/assimp/code/Importer/IFC/IFCUtil.h
vendored
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
Open Asset Import Library (assimp)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2019, assimp 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 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_2x3.h"
|
||||
#include "IFCLoader.h"
|
||||
#include "code/Step/STEPFile.h"
|
||||
#include <assimp/mesh.h>
|
||||
#include <assimp/material.h>
|
||||
|
||||
struct aiNode;
|
||||
|
||||
namespace Assimp {
|
||||
namespace IFC {
|
||||
|
||||
typedef double IfcFloat;
|
||||
|
||||
// IfcFloat-precision math data types
|
||||
typedef aiVector2t<IfcFloat> IfcVector2;
|
||||
typedef aiVector3t<IfcFloat> IfcVector3;
|
||||
typedef aiMatrix4x4t<IfcFloat> IfcMatrix4;
|
||||
typedef aiMatrix3x3t<IfcFloat> IfcMatrix3;
|
||||
typedef aiColor4t<IfcFloat> IfcColor4;
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Helper for std::for_each to delete all heap-allocated items in a container
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
template<typename T>
|
||||
struct delete_fun {
|
||||
void operator()(T* del) {
|
||||
delete del;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Helper used during mesh construction. Aids at creating aiMesh'es out of relatively few polygons.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
struct TempMesh {
|
||||
std::vector<IfcVector3> mVerts;
|
||||
std::vector<unsigned int> mVertcnt;
|
||||
|
||||
// utilities
|
||||
aiMesh* ToMesh();
|
||||
void Clear();
|
||||
void Transform(const IfcMatrix4& mat);
|
||||
IfcVector3 Center() const;
|
||||
void Append(const TempMesh& other);
|
||||
bool IsEmpty() const;
|
||||
void RemoveAdjacentDuplicates();
|
||||
void RemoveDegenerates();
|
||||
void FixupFaceOrientation();
|
||||
static IfcVector3 ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize = true);
|
||||
IfcVector3 ComputeLastPolygonNormal(bool normalize = true) const;
|
||||
void ComputePolygonNormals(std::vector<IfcVector3>& normals, bool normalize = true, size_t ofs = 0) const;
|
||||
void Swap(TempMesh& other);
|
||||
};
|
||||
|
||||
inline
|
||||
bool TempMesh::IsEmpty() const {
|
||||
return mVerts.empty() && mVertcnt.empty();
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Temporary representation of an opening in a wall or a floor
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
struct TempOpening
|
||||
{
|
||||
const IFC::Schema_2x3::IfcSolidModel *solid;
|
||||
IfcVector3 extrusionDir;
|
||||
|
||||
std::shared_ptr<TempMesh> profileMesh;
|
||||
std::shared_ptr<TempMesh> profileMesh2D;
|
||||
|
||||
// list of points generated for this opening. This is used to
|
||||
// create connections between two opposing holes created
|
||||
// from a single opening instance (two because walls tend to
|
||||
// have two sides). If !empty(), the other side of the wall
|
||||
// has already been processed.
|
||||
std::vector<IfcVector3> wallPoints;
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
TempOpening()
|
||||
: solid()
|
||||
, extrusionDir()
|
||||
, profileMesh()
|
||||
{
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
TempOpening(const IFC::Schema_2x3::IfcSolidModel* solid,IfcVector3 extrusionDir,
|
||||
std::shared_ptr<TempMesh> profileMesh,
|
||||
std::shared_ptr<TempMesh> profileMesh2D)
|
||||
: solid(solid)
|
||||
, extrusionDir(extrusionDir)
|
||||
, profileMesh(profileMesh)
|
||||
, profileMesh2D(profileMesh2D)
|
||||
{
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
void Transform(const IfcMatrix4& mat); // defined later since TempMesh is not complete yet
|
||||
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Helper to sort openings by distance from a given base point
|
||||
struct DistanceSorter {
|
||||
|
||||
DistanceSorter(const IfcVector3& base) : base(base) {}
|
||||
|
||||
bool operator () (const TempOpening& a, const TempOpening& b) const {
|
||||
return (a.profileMesh->Center()-base).SquareLength() < (b.profileMesh->Center()-base).SquareLength();
|
||||
}
|
||||
|
||||
IfcVector3 base;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Intermediate data storage during conversion. Keeps everything and a bit more.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
struct ConversionData
|
||||
{
|
||||
ConversionData(const STEP::DB& db, const IFC::Schema_2x3::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<aiMesh>());
|
||||
std::for_each(materials.begin(),materials.end(),delete_fun<aiMaterial>());
|
||||
}
|
||||
|
||||
IfcFloat len_scale, angle_scale;
|
||||
bool plane_angle_in_radians;
|
||||
|
||||
const STEP::DB& db;
|
||||
const IFC::Schema_2x3::IfcProject& proj;
|
||||
aiScene* out;
|
||||
|
||||
IfcMatrix4 wcs;
|
||||
std::vector<aiMesh*> meshes;
|
||||
std::vector<aiMaterial*> materials;
|
||||
|
||||
struct MeshCacheIndex {
|
||||
const IFC::Schema_2x3::IfcRepresentationItem* item; unsigned int matindex;
|
||||
MeshCacheIndex() : item(NULL), matindex(0) { }
|
||||
MeshCacheIndex(const IFC::Schema_2x3::IfcRepresentationItem* i, unsigned int mi) : item(i), matindex(mi) { }
|
||||
bool operator == (const MeshCacheIndex& o) const { return item == o.item && matindex == o.matindex; }
|
||||
bool operator < (const MeshCacheIndex& o) const { return item < o.item || (item == o.item && matindex < o.matindex); }
|
||||
};
|
||||
typedef std::map<MeshCacheIndex, std::set<unsigned int> > MeshCache;
|
||||
MeshCache cached_meshes;
|
||||
|
||||
typedef std::map<const IFC::Schema_2x3::IfcSurfaceStyle*, unsigned int> MaterialCache;
|
||||
MaterialCache cached_materials;
|
||||
|
||||
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<TempOpening>* apply_openings;
|
||||
std::vector<TempOpening>* collect_openings;
|
||||
|
||||
std::set<uint64_t> already_processed;
|
||||
};
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Binary predicate to compare vectors with a given, quadratic epsilon.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
struct FuzzyVectorCompare {
|
||||
|
||||
FuzzyVectorCompare(IfcFloat epsilon) : epsilon(epsilon) {}
|
||||
bool operator()(const IfcVector3& a, const IfcVector3& b) {
|
||||
return std::abs((a-b).SquareLength()) < epsilon;
|
||||
}
|
||||
|
||||
const IfcFloat epsilon;
|
||||
};
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Ordering predicate to totally order R^2 vectors first by x and then by y
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
struct XYSorter {
|
||||
|
||||
// sort first by X coordinates, then by Y coordinates
|
||||
bool operator () (const IfcVector2&a, const IfcVector2& b) const {
|
||||
if (a.x == b.x) {
|
||||
return a.y < b.y;
|
||||
}
|
||||
return a.x < b.x;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// conversion routines for common IFC entities, implemented in IFCUtil.cpp
|
||||
void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in);
|
||||
void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base);
|
||||
void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in);
|
||||
void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in);
|
||||
void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in);
|
||||
void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z);
|
||||
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in);
|
||||
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in);
|
||||
void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const IFC::Schema_2x3::IfcAxis1Placement& in);
|
||||
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv);
|
||||
void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op);
|
||||
bool IsTrue(const Assimp::STEP::EXPRESS::BOOLEAN& in);
|
||||
IfcFloat ConvertSIPrefix(const std::string& prefix);
|
||||
|
||||
|
||||
// IFCProfile.cpp
|
||||
bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv);
|
||||
bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv);
|
||||
|
||||
// IFCMaterial.cpp
|
||||
unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionData& conv, bool forceDefaultMat);
|
||||
|
||||
// IFCGeometry.cpp
|
||||
IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut);
|
||||
bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, unsigned int matid, std::set<unsigned int>& mesh_indices, ConversionData& conv);
|
||||
void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd,ConversionData& /*conv*/);
|
||||
|
||||
void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, TempMesh& meshout,
|
||||
ConversionData& conv);
|
||||
|
||||
void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, TempMesh& result,
|
||||
ConversionData& conv, bool collect_openings);
|
||||
|
||||
// IFCBoolean.cpp
|
||||
|
||||
void ProcessBoolean(const Schema_2x3::IfcBooleanResult& boolean, TempMesh& result, ConversionData& conv);
|
||||
void ProcessBooleanHalfSpaceDifference(const Schema_2x3::IfcHalfSpaceSolid* hs, TempMesh& result,
|
||||
const TempMesh& first_operand,
|
||||
ConversionData& conv);
|
||||
|
||||
void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPolygonalBoundedHalfSpace* hs, TempMesh& result,
|
||||
const TempMesh& first_operand,
|
||||
ConversionData& conv);
|
||||
void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid* as, TempMesh& result,
|
||||
const TempMesh& first_operand,
|
||||
ConversionData& conv);
|
||||
|
||||
|
||||
// IFCOpenings.cpp
|
||||
|
||||
bool GenerateOpenings(std::vector<TempOpening>& openings,
|
||||
const std::vector<IfcVector3>& nors,
|
||||
TempMesh& curmesh,
|
||||
bool check_intersection,
|
||||
bool generate_connection_geometry,
|
||||
const IfcVector3& wall_extrusion_axis = IfcVector3(0,1,0));
|
||||
|
||||
|
||||
|
||||
// IFCCurve.cpp
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Custom exception for use by members of the Curve class
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
class CurveError {
|
||||
public:
|
||||
CurveError(const std::string& s)
|
||||
: mStr(s) {
|
||||
// empty
|
||||
}
|
||||
|
||||
std::string mStr;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Temporary representation for an arbitrary sub-class of IfcCurve. Used to sample the curves
|
||||
// to obtain a list of line segments.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
class Curve {
|
||||
protected:
|
||||
Curve(const Schema_2x3::IfcCurve& base_entity, ConversionData& conv)
|
||||
: base_entity(base_entity)
|
||||
, conv(conv) {
|
||||
// empty
|
||||
}
|
||||
|
||||
public:
|
||||
typedef std::pair<IfcFloat, IfcFloat> ParamRange;
|
||||
|
||||
virtual ~Curve() {}
|
||||
|
||||
|
||||
// check if a curve is closed
|
||||
virtual bool IsClosed() const = 0;
|
||||
|
||||
// evaluate the curve at the given parametric position
|
||||
virtual IfcVector3 Eval(IfcFloat p) const = 0;
|
||||
|
||||
// try to match a point on the curve to a given parameter
|
||||
// for self-intersecting curves, the result is not ambiguous and
|
||||
// it is undefined which parameter is returned.
|
||||
virtual bool ReverseEval(const IfcVector3& val, IfcFloat& paramOut) const;
|
||||
|
||||
// get the range of the curve (both inclusive).
|
||||
// +inf and -inf are valid return values, the curve is not bounded in such a case.
|
||||
virtual std::pair<IfcFloat,IfcFloat> GetParametricRange() const = 0;
|
||||
IfcFloat GetParametricRangeDelta() const;
|
||||
|
||||
// estimate the number of sample points that this curve will require
|
||||
virtual size_t EstimateSampleCount(IfcFloat start,IfcFloat end) const;
|
||||
|
||||
// intelligently sample the curve based on the current settings
|
||||
// and append the result to the mesh
|
||||
virtual void SampleDiscrete(TempMesh& out,IfcFloat start,IfcFloat end) const;
|
||||
|
||||
#ifdef ASSIMP_BUILD_DEBUG
|
||||
// check if a particular parameter value lies within the well-defined range
|
||||
bool InRange(IfcFloat) const;
|
||||
#endif
|
||||
static Curve* Convert(const IFC::Schema_2x3::IfcCurve&,ConversionData& conv);
|
||||
|
||||
protected:
|
||||
const Schema_2x3::IfcCurve& base_entity;
|
||||
ConversionData& conv;
|
||||
};
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// A BoundedCurve always holds the invariant that GetParametricRange()
|
||||
// never returns infinite values.
|
||||
// --------------------------------------------------------------------------------
|
||||
class BoundedCurve : public Curve {
|
||||
public:
|
||||
BoundedCurve(const Schema_2x3::IfcBoundedCurve& entity, ConversionData& conv)
|
||||
: Curve(entity,conv)
|
||||
{}
|
||||
|
||||
public:
|
||||
|
||||
bool IsClosed() const;
|
||||
|
||||
public:
|
||||
|
||||
// sample the entire curve
|
||||
void SampleDiscrete(TempMesh& out) const;
|
||||
using Curve::SampleDiscrete;
|
||||
};
|
||||
|
||||
// IfcProfile.cpp
|
||||
bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user