- Ifc: replace old algorithm to merge nested polygons with a version that reduces the problem to an instance of the quadrulate algorithm. This great reduces artifacts in walls.
git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@1312 67173fc5-114c-0410-ac8e-9d2fd5bffc1fpull/6/merge
parent
0696d97399
commit
a3d5b2e0d7
|
@ -66,6 +66,12 @@ namespace Assimp {
|
||||||
#define from_int64(p) (static_cast<IfcFloat>((p)) / max_ulong64)
|
#define from_int64(p) (static_cast<IfcFloat>((p)) / max_ulong64)
|
||||||
#define one_vec (IfcVector2(static_cast<IfcFloat>(1.0),static_cast<IfcFloat>(1.0)))
|
#define one_vec (IfcVector2(static_cast<IfcFloat>(1.0),static_cast<IfcFloat>(1.0)))
|
||||||
|
|
||||||
|
|
||||||
|
bool TryAddOpenings_Quadrulate(const std::vector<TempOpening>& openings,
|
||||||
|
const std::vector<IfcVector3>& nors,
|
||||||
|
TempMesh& curmesh);
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool ProcessPolyloop(const IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/)
|
bool ProcessPolyloop(const IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/)
|
||||||
{
|
{
|
||||||
|
@ -173,250 +179,105 @@ void FixupFaceOrientation(TempMesh& result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void RecursiveMergeBoundaries(TempMesh& final_result, const TempMesh& in, const TempMesh& boundary, std::vector<IfcVector3>& normals, const IfcVector3& nor_boundary)
|
void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1)
|
||||||
{
|
{
|
||||||
ai_assert(in.vertcnt.size() >= 1);
|
// handle all trivial cases
|
||||||
ai_assert(boundary.vertcnt.size() == 1);
|
if(inmesh.vertcnt.empty()) {
|
||||||
std::vector<unsigned int>::const_iterator end = in.vertcnt.end(), begin=in.vertcnt.begin(), iit, best_iit;
|
return;
|
||||||
|
|
||||||
TempMesh out;
|
|
||||||
|
|
||||||
// iterate through all other bounds and find the one for which the shortest connection
|
|
||||||
// to the outer boundary is actually the shortest possible.
|
|
||||||
size_t vidx = 0, best_vidx_start = 0;
|
|
||||||
size_t best_ofs, best_outer = boundary.verts.size();
|
|
||||||
IfcFloat best_dist = 1e10;
|
|
||||||
for(std::vector<unsigned int>::const_iterator iit = begin; iit != end; vidx += *iit++) {
|
|
||||||
|
|
||||||
for(size_t vofs = 0; vofs < *iit; ++vofs) {
|
|
||||||
const IfcVector3& v = in.verts[vidx+vofs];
|
|
||||||
|
|
||||||
for(size_t outer = 0; outer < boundary.verts.size(); ++outer) {
|
|
||||||
const IfcVector3& o = boundary.verts[outer];
|
|
||||||
const IfcFloat d = (o-v).SquareLength();
|
|
||||||
|
|
||||||
if (d < best_dist) {
|
|
||||||
best_dist = d;
|
|
||||||
best_ofs = vofs;
|
|
||||||
best_outer = outer;
|
|
||||||
best_iit = iit;
|
|
||||||
best_vidx_start = vidx;
|
|
||||||
}
|
}
|
||||||
}
|
if(inmesh.vertcnt.size() == 1) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ai_assert(best_outer != boundary.verts.size());
|
|
||||||
|
|
||||||
|
|
||||||
// now that we collected all vertex connections to be added, build the output polygon
|
|
||||||
const size_t cnt = boundary.verts.size() + *best_iit+2;
|
|
||||||
out.verts.reserve(cnt);
|
|
||||||
|
|
||||||
for(size_t outer = 0; outer < boundary.verts.size(); ++outer) {
|
|
||||||
const IfcVector3& o = boundary.verts[outer];
|
|
||||||
out.verts.push_back(o);
|
|
||||||
|
|
||||||
if (outer == best_outer) {
|
|
||||||
for(size_t i = best_ofs; i < *best_iit; ++i) {
|
|
||||||
out.verts.push_back(in.verts[best_vidx_start + i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need the first vertex of the inner polygon twice as we return to the
|
|
||||||
// outer loop through the very same connection through which we got there.
|
|
||||||
for(size_t i = 0; i <= best_ofs; ++i) {
|
|
||||||
out.verts.push_back(in.verts[best_vidx_start + i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reverse face winding if the normal of the sub-polygon points in the
|
|
||||||
// same direction as the normal of the outer polygonal boundary
|
|
||||||
if (normals[std::distance(begin,best_iit)] * nor_boundary > 0) {
|
|
||||||
std::reverse(out.verts.rbegin(),out.verts.rbegin()+*best_iit+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// also append a copy of the initial insertion point to be able to continue the outer polygon
|
|
||||||
out.verts.push_back(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.vertcnt.push_back(cnt);
|
|
||||||
ai_assert(out.verts.size() == cnt);
|
|
||||||
|
|
||||||
if (in.vertcnt.size()-std::count(begin,end,0) > 1) {
|
|
||||||
// Recursively apply the same algorithm if there are more boundaries to merge. The
|
|
||||||
// current implementation is relatively inefficient, though.
|
|
||||||
|
|
||||||
TempMesh temp;
|
|
||||||
|
|
||||||
// drop the boundary that we just processed
|
|
||||||
const size_t dist = std::distance(begin, best_iit);
|
|
||||||
TempMesh remaining = in;
|
|
||||||
remaining.vertcnt.erase(remaining.vertcnt.begin() + dist);
|
|
||||||
remaining.verts.erase(remaining.verts.begin()+best_vidx_start,remaining.verts.begin()+best_vidx_start+*best_iit);
|
|
||||||
|
|
||||||
normals.erase(normals.begin() + dist);
|
|
||||||
RecursiveMergeBoundaries(temp,remaining,out,normals,nor_boundary);
|
|
||||||
|
|
||||||
final_result.Append(temp);
|
|
||||||
}
|
|
||||||
else final_result.Append(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
|
||||||
void MergePolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = -1)
|
|
||||||
{
|
|
||||||
// standard case - only one boundary, just copy it to the result vector
|
|
||||||
if (inmesh.vertcnt.size() <= 1) {
|
|
||||||
result.Append(inmesh);
|
result.Append(inmesh);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.vertcnt.reserve(inmesh.vertcnt.size()+result.vertcnt.size());
|
ai_assert(std::count(inmesh.vertcnt.begin(), inmesh.vertcnt.end(), 0) == 0);
|
||||||
|
|
||||||
// XXX get rid of the extra copy if possible
|
typedef std::vector<unsigned int>::const_iterator face_iter;
|
||||||
TempMesh meshout = inmesh;
|
|
||||||
|
|
||||||
// handle polygons with holes. Our built in triangulation won't handle them as is, but
|
face_iter begin = inmesh.vertcnt.begin(), end = inmesh.vertcnt.end(), iit;
|
||||||
// the ear cutting algorithm is solid enough to deal with them if we join the inner
|
std::vector<unsigned int>::const_iterator outer_polygon_it = end;
|
||||||
// holes with the outer boundaries by dummy connections.
|
|
||||||
IFCImporter::LogDebug("fixing polygon with holes for triangulation via ear-cutting");
|
|
||||||
std::vector<unsigned int>::iterator outer_polygon = meshout.vertcnt.end(), begin=meshout.vertcnt.begin(), end=outer_polygon, iit;
|
|
||||||
|
|
||||||
// each hole results in two extra vertices
|
// major task here: given a list of nested polygon boundaries (one of which
|
||||||
result.verts.reserve(meshout.verts.size()+meshout.vertcnt.size()*2+result.verts.size());
|
// is the outer contour), reduce the triangulation task arising here to
|
||||||
size_t outer_polygon_start = 0;
|
// 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 newell normals for all polygons
|
||||||
// do not normalize 'normals', we need the original length for computing the polygon area
|
// do not normalize 'normals', we need the original length for computing the polygon area
|
||||||
std::vector<IfcVector3> normals;
|
std::vector<IfcVector3> normals;
|
||||||
ComputePolygonNormals(meshout,normals,false);
|
ComputePolygonNormals(inmesh,normals,false);
|
||||||
|
|
||||||
// see if one of the polygons is a IfcFaceOuterBound (in which case `master_bounds` is its index).
|
// One of the polygons might be a IfcFaceOuterBound (in which case `master_bounds`
|
||||||
// sadly we can't rely on it, the docs say 'At most one of the bounds shall be of the type IfcFaceOuterBound'
|
// 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;
|
IfcFloat area_outer_polygon = 1e-10f;
|
||||||
if (master_bounds != (size_t)-1) {
|
if (master_bounds != (size_t)-1) {
|
||||||
outer_polygon = begin + master_bounds;
|
ai_assert(master_bounds < inmesh.vertcnt.size());
|
||||||
outer_polygon_start = std::accumulate(begin,outer_polygon,0);
|
outer_polygon_it = begin + master_bounds;
|
||||||
area_outer_polygon = normals[master_bounds].SquareLength();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
size_t vidx = 0;
|
for(iit = begin; iit != end; iit++) {
|
||||||
for(iit = begin; iit != meshout.vertcnt.end(); vidx += *iit++) {
|
// find the polygon with the largest area and take it as the outer bound.
|
||||||
// find the polygon with the largest area, it must be the outer bound.
|
|
||||||
IfcVector3& n = normals[std::distance(begin,iit)];
|
IfcVector3& n = normals[std::distance(begin,iit)];
|
||||||
const IfcFloat area = n.SquareLength();
|
const IfcFloat area = n.SquareLength();
|
||||||
if (area > area_outer_polygon) {
|
if (area > area_outer_polygon) {
|
||||||
area_outer_polygon = area;
|
area_outer_polygon = area;
|
||||||
outer_polygon = iit;
|
outer_polygon_it = iit;
|
||||||
outer_polygon_start = vidx;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ai_assert(outer_polygon != meshout.vertcnt.end());
|
ai_assert(outer_polygon_it != end);
|
||||||
std::vector<IfcVector3>& in = meshout.verts;
|
|
||||||
|
|
||||||
// skip over extremely small boundaries - this is a workaround to fix cases
|
const size_t outer_polygon_size = *outer_polygon_it;
|
||||||
// in which the number of holes is so extremely large that the
|
const IfcVector3& master_normal = normals[std::distance(begin, outer_polygon_it)];
|
||||||
// triangulation code fails.
|
|
||||||
#define IFC_VERTICAL_HOLE_SIZE_THRESHOLD 0.000001f
|
|
||||||
size_t vidx = 0, removed = 0, index = 0;
|
|
||||||
const IfcFloat threshold = area_outer_polygon * IFC_VERTICAL_HOLE_SIZE_THRESHOLD;
|
|
||||||
for(iit = begin; iit != end ;++index) {
|
|
||||||
const IfcFloat sqlen = normals[index].SquareLength();
|
|
||||||
if (sqlen < threshold) {
|
|
||||||
std::vector<IfcVector3>::iterator inbase = in.begin()+vidx;
|
|
||||||
in.erase(inbase,inbase+*iit);
|
|
||||||
|
|
||||||
outer_polygon_start -= outer_polygon_start>vidx ? *iit : 0;
|
// generate fake openings to meet the interface for the quadrulate
|
||||||
*iit++ = 0;
|
// algorithm. It boils down to generating small boxes given the
|
||||||
++removed;
|
// 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.vertcnt.size()-1);
|
||||||
|
|
||||||
IFCImporter::LogDebug("skip small hole below threshold");
|
std::vector<IfcVector3>::const_iterator vit = inmesh.verts.begin(), outer_vit;
|
||||||
}
|
|
||||||
else {
|
|
||||||
normals[index] /= sqrt(sqlen);
|
|
||||||
vidx += *iit++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// see if one or more of the hole has a face that lies directly on an outer bound.
|
for(iit = begin; iit != end; vit += *iit++) {
|
||||||
// this happens for doors, for example.
|
if (iit == outer_polygon_it) {
|
||||||
vidx = 0;
|
outer_vit = vit;
|
||||||
for(iit = begin; ; vidx += *iit++) {
|
|
||||||
next_loop:
|
|
||||||
if (iit == end) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (iit == outer_polygon) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(size_t vofs = 0; vofs < *iit; ++vofs) {
|
fake_openings.push_back(TempOpening());
|
||||||
if (!*iit) {
|
TempOpening& opening = fake_openings.back();
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const size_t next = (vofs+1)%*iit;
|
|
||||||
const IfcVector3& v = in[vidx+vofs], &vnext = in[vidx+next],&vd = (vnext-v).Normalize();
|
|
||||||
|
|
||||||
for(size_t outer = 0; outer < *outer_polygon; ++outer) {
|
opening.extrusionDir = master_normal;
|
||||||
const IfcVector3& o = in[outer_polygon_start+outer], &onext = in[outer_polygon_start+(outer+1)%*outer_polygon], &od = (onext-o).Normalize();
|
opening.solid = NULL;
|
||||||
|
|
||||||
if (fabs(vd * od) > 1.f-1e-6f && (onext-v).Normalize() * vd > 1.f-1e-6f && (onext-v)*(o-v) < 0) {
|
opening.profileMesh = boost::make_shared<TempMesh>();
|
||||||
IFCImporter::LogDebug("got an inner hole that lies partly on the outer polygonal boundary, merging them to a single contour");
|
opening.profileMesh->verts.reserve(*iit);
|
||||||
|
opening.profileMesh->vertcnt.push_back(*iit);
|
||||||
|
|
||||||
// in between outer and outer+1 insert all vertices of this loop, then drop the original altogether.
|
std::copy(vit, vit + *iit, std::back_inserter(opening.profileMesh->verts));
|
||||||
std::vector<IfcVector3> tmp(*iit);
|
|
||||||
|
|
||||||
const size_t start = (v-o).SquareLength() > (vnext-o).SquareLength() ? vofs : next;
|
|
||||||
std::vector<IfcVector3>::iterator inbase = in.begin()+vidx, it = std::copy(inbase+start, inbase+*iit,tmp.begin());
|
|
||||||
std::copy(inbase, inbase+start,it);
|
|
||||||
std::reverse(tmp.begin(),tmp.end());
|
|
||||||
|
|
||||||
in.insert(in.begin()+outer_polygon_start+(outer+1)%*outer_polygon,tmp.begin(),tmp.end());
|
|
||||||
vidx += outer_polygon_start<vidx ? *iit : 0;
|
|
||||||
|
|
||||||
inbase = in.begin()+vidx;
|
|
||||||
in.erase(inbase,inbase+*iit);
|
|
||||||
|
|
||||||
outer_polygon_start -= outer_polygon_start>vidx ? *iit : 0;
|
|
||||||
|
|
||||||
*outer_polygon += tmp.size();
|
|
||||||
*iit++ = 0;
|
|
||||||
++removed;
|
|
||||||
goto next_loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( meshout.vertcnt.size() - removed <= 1) {
|
// fill a mesh with ONLY the main polygon
|
||||||
result.Append(meshout);
|
TempMesh temp;
|
||||||
return;
|
temp.verts.reserve(outer_polygon_size);
|
||||||
}
|
temp.vertcnt.push_back(outer_polygon_size);
|
||||||
|
std::copy(outer_vit, outer_vit+outer_polygon_size,
|
||||||
|
std::back_inserter(temp.verts));
|
||||||
|
|
||||||
// extract the outer boundary and move it to a separate mesh
|
TryAddOpenings_Quadrulate(fake_openings, normals, temp);
|
||||||
TempMesh boundary;
|
result.Append(temp);
|
||||||
boundary.vertcnt.resize(1,*outer_polygon);
|
|
||||||
boundary.verts.resize(*outer_polygon);
|
|
||||||
|
|
||||||
std::vector<IfcVector3>::iterator b = in.begin()+outer_polygon_start;
|
|
||||||
std::copy(b,b+*outer_polygon,boundary.verts.begin());
|
|
||||||
in.erase(b,b+*outer_polygon);
|
|
||||||
|
|
||||||
std::vector<IfcVector3>::iterator norit = normals.begin()+std::distance(meshout.vertcnt.begin(),outer_polygon);
|
|
||||||
const IfcVector3 nor_boundary = *norit;
|
|
||||||
normals.erase(norit);
|
|
||||||
meshout.vertcnt.erase(outer_polygon);
|
|
||||||
|
|
||||||
// keep merging the closest inner boundary with the outer boundary until no more boundaries are left
|
|
||||||
RecursiveMergeBoundaries(result,meshout,boundary,normals,nor_boundary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessConnectedFaceSet(const IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv)
|
void ProcessConnectedFaceSet(const IfcConnectedFaceSet& fset, TempMesh& result, ConversionData& conv)
|
||||||
{
|
{
|
||||||
BOOST_FOREACH(const IfcFace& face, fset.CfsFaces) {
|
BOOST_FOREACH(const IfcFace& face, fset.CfsFaces) {
|
||||||
|
|
||||||
// size_t ob = -1, cnt = 0;
|
// size_t ob = -1, cnt = 0;
|
||||||
TempMesh meshout;
|
TempMesh meshout;
|
||||||
BOOST_FOREACH(const IfcFaceBound& bound, face.Bounds) {
|
BOOST_FOREACH(const IfcFaceBound& bound, face.Bounds) {
|
||||||
|
@ -446,12 +307,10 @@ void ProcessConnectedFaceSet(const IfcConnectedFaceSet& fset, TempMesh& result,
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
}
|
}
|
||||||
MergePolygonBoundaries(result,meshout);
|
ProcessPolygonBoundaries(result, meshout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessRevolvedAreaSolid(const IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv)
|
void ProcessRevolvedAreaSolid(const IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv)
|
||||||
{
|
{
|
||||||
|
@ -1430,7 +1289,7 @@ void CleanupOuterContour(const std::vector<IfcVector2>& contour_flat, TempMesh&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// undo the projection, generate output quads
|
// swap data arrays
|
||||||
std::swap(vold,curmesh.verts);
|
std::swap(vold,curmesh.verts);
|
||||||
std::swap(iold,curmesh.vertcnt);
|
std::swap(iold,curmesh.vertcnt);
|
||||||
}
|
}
|
||||||
|
@ -1550,8 +1409,8 @@ bool TryAddOpenings_Quadrulate(const std::vector<TempOpening>& openings,
|
||||||
for (std::vector<BoundingBox>::iterator it = bbs.begin(); it != bbs.end();) {
|
for (std::vector<BoundingBox>::iterator it = bbs.begin(); it != bbs.end();) {
|
||||||
const BoundingBox& ibb = *it;
|
const BoundingBox& ibb = *it;
|
||||||
|
|
||||||
if (ibb.first.x < bb.second.x && ibb.second.x > bb.first.x &&
|
if (ibb.first.x <= bb.second.x && ibb.second.x >= bb.first.x &&
|
||||||
ibb.first.y < bb.second.y && ibb.second.y > bb.second.x) {
|
ibb.first.y <= bb.second.y && ibb.second.y >= bb.second.x) {
|
||||||
|
|
||||||
// take these two contours and try to merge them. If they overlap (which
|
// take these two contours and try to merge them. If they overlap (which
|
||||||
// should not happen, but in fact happens-in-the-real-world [tm] ),
|
// should not happen, but in fact happens-in-the-real-world [tm] ),
|
||||||
|
@ -1669,6 +1528,7 @@ void ProcessExtrudedAreaSolid(const IfcExtrudedAreaSolid& solid, TempMesh& resul
|
||||||
IfcVector3 min = in[0];
|
IfcVector3 min = in[0];
|
||||||
dir *= IfcMatrix3(trafo);
|
dir *= IfcMatrix3(trafo);
|
||||||
|
|
||||||
|
|
||||||
std::vector<IfcVector3> nors;
|
std::vector<IfcVector3> nors;
|
||||||
const bool openings = !!conv.apply_openings && conv.apply_openings->size();
|
const bool openings = !!conv.apply_openings && conv.apply_openings->size();
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,14 @@ struct TempOpening
|
||||||
IfcVector3 extrusionDir;
|
IfcVector3 extrusionDir;
|
||||||
boost::shared_ptr<TempMesh> profileMesh;
|
boost::shared_ptr<TempMesh> profileMesh;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
TempOpening()
|
||||||
|
: solid()
|
||||||
|
, extrusionDir()
|
||||||
|
, profileMesh()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
TempOpening(const IFC::IfcExtrudedAreaSolid* solid,IfcVector3 extrusionDir,boost::shared_ptr<TempMesh> profileMesh)
|
TempOpening(const IFC::IfcExtrudedAreaSolid* solid,IfcVector3 extrusionDir,boost::shared_ptr<TempMesh> profileMesh)
|
||||||
: solid(solid)
|
: solid(solid)
|
||||||
|
|
Loading…
Reference in New Issue