/* * This file is part of libpmregion * * File: PMRegion\_operations.cpp * Author: Florian Heinz 1 Operations Implementation of the operations on PMRegions */ #include "PMRegion_internal.h" #include #include #include #include using namespace std; namespace pmr { /* * O P E R A T I O N S */ /* Operation "union" of two pmregions */ PMRegion PMRegion::operator+ (PMRegion ®) { // Convert polyhedra to Nef polyhedra Nef_polyhedron p1(polyhedron), p2(reg.polyhedron); // Compute the union Nef_polyhedron p12 = p1 + p2; if (!p12.is_simple()) { Polyhedron tmp = reg.polyhedron; translatedelta(0.000001, 1, 1, 1, tmp); p2 = Nef_polyhedron(tmp); p12 = p1 + p2; if (!p12.is_simple()) { p12 = p1; std::cerr << "Error: Union failed" << std::endl; } else { std::cerr << "Warning: Fixed union" << std::endl; } } PMRegion ret; // Convert it back to normal polyhedra try { p12.convert_to_polyhedron(ret.polyhedron); } catch (...) { ret = PMRegion(); cerr << "Error: converting union polyhedron" << endl; } return ret; } /* Operation "intersection" of two pmregions */ PMRegion PMRegion::operator* (PMRegion ®) { // Convert polyhedra to Nef polyhedra Nef_polyhedron p1(polyhedron), p2(reg.polyhedron); // Compute the intersection Nef_polyhedron p12 = p1 * p2; if (!p12.is_simple()) { Polyhedron tmp = reg.polyhedron; translatedelta(0.000001, 1, 1, 1, tmp); p2 = Nef_polyhedron(tmp); p12 = p1 * p2; if (!p12.is_simple()) { p12 = p1; std::cerr << "Error: Intersection failed" << std::endl; } else { std::cerr << "Warning: Fixed intersection" << std::endl; } } PMRegion ret; // Convert it back to normal polyhedra try { Polyhedron np; p12.convert_to_polyhedron(np); ret.polyhedron = np; } catch (...) { ret.polyhedron = polyhedron; cerr << "Error: converting intersection polyhedron" << endl; } return ret; } /* Operation "difference" of two pmregions */ PMRegion PMRegion::operator- (PMRegion ®) { // Convert polyhedra to Nef polyhedra Nef_polyhedron p1(polyhedron), p2(reg.polyhedron); // Compute the difference Nef_polyhedron p12 = p1 - p2; if (!p12.is_simple()) { Polyhedron tmp = reg.polyhedron; translatedelta(0.000001, 1, 1, 1, tmp); p2 = Nef_polyhedron(tmp); p12 = p1 - p2; if (!p12.is_simple()) { p12 = p1; std::cerr << "Error: Difference failed" << std::endl; } else { std::cerr << "Warning: Fixed difference" << std::endl; } } PMRegion ret; // Convert it back to normal polyhedra try { p12.convert_to_polyhedron(ret.polyhedron); } catch (...) { ret = PMRegion(); cerr << "Error: converting difference polyhedron" << endl; } return ret; } /* Helper function, which calculates the intersection of * a polyhedron with a cutting plane at a given z coordinate */ static Arrangement zplanecut (Polyhedron ph, Kernel::FT z) { // Build an AABB tree from the polyhedron Tree tree(faces(ph).first, faces(ph).second, ph); tree.accelerate_distance_queries(); // Create the cutting plane Plane pl(Point3d(0, 0, z), Vector(0, 0, 1)); std::list pis; // Calculate all intersections between plane and polyhedron tree.all_intersections(pl, std::back_inserter(pis)); // Insert all intersections (segments) into a 2D-Arrangement Arrangement arr; for (std::list::iterator it = pis.begin(); it != pis.end(); ++it) { Segment *s = boost::get(&((*it)->first)); if (s != NULL && !s->is_degenerate()) CGAL::insert(arr, Segment_2(Point_2(s->source().x(), s->source().y()), Point_2(s->target().x(), s->target().y()))); } return arr; } /* Operation "atinstant" for a pmregion */ RList PMRegion::atinstant (Kernel::FT instant) { // Calculate the intersections with a cutting plane Arrangement cut = zplanecut(polyhedron, instant); RList region; // Convert the faces from the arrangement to a region Arrangement2Region(cut.unbounded_face(), region); return region.obj("region", "region"); } /* Operation "mpointinside" for a pmregion */ MBool PMRegion::mpointinside(RList& mpoint) { // Convert the mpoint units to 3D segments vector segs = mpoint2segments(mpoint); // Build an AABB-Tree Tree tree(faces(polyhedron).first, faces(polyhedron).second, polyhedron); // Create a "point inside polyhedron" tester Point_inside inside_tester(tree); MBool mbool; for (unsigned int i = 0; i < segs.size(); i++) { std::list sis; // Calculate all intersections (points) between segments and polyhedra tree.all_intersections(segs[i], std::back_inserter(sis)); std::set events; // Insert the z coordinates of these points into an ordered set for (std::list::iterator it = sis.begin(); it != sis.end(); ++it) { Segment_intersection si = *it; if (Point3d *p = boost::get(&(si->first))) events.insert(p->z()); } // Also add the final z coordinate of the segment events.insert(segs[i].target().z()); // Begin with the start z coordinate of the segment Kernel::FT prev = segs[i].source().z(); // Check, if we start inside or outside the polyhedron bool inside = inside_tester(segs[i].source()) == CGAL::ON_BOUNDED_SIDE; for (set::iterator it = events.begin(); it != events.end(); it++) { // Append a moving boolean unit with the current "inside" value mbool.append(prev, *it, inside); // Next segment is outside, if current is inside and vice-versa inside = !inside; prev = *it; } } return mbool; } /* Operation "perimeter" for a pmregion */ Kernel::FT getPerimeterFromArrangement (Arrangement& arr); MReal PMRegion::perimeter () { MReal mreal; // Get all z coordinates of the polyhedron in an ordered set std::set zevents = getZEvents(polyhedron); Kernel::FT prev; bool first = true; for (std::set::iterator it = zevents.begin(); it != zevents.end(); it++) { // For each consecutive pair of z coordinates ... if (!first) { Kernel::FT cur = *it; // ... calculate the intersections with a cutting plane Arrangement a1 = zplanecut(polyhedron, prev+EPSILON); Arrangement a2 = zplanecut(polyhedron, cur-EPSILON); Kernel::FT deltat = (cur - prev) / 86400000; // ... calculate the perimeter lengths Kernel::FT a1perimeter = getPerimeterFromArrangement(a1); Kernel::FT a2perimeter = getPerimeterFromArrangement(a2); Kernel::FT diff = a2perimeter - a1perimeter; // create a mreal representing the perimeter development mreal.append(prev, cur, 0, diff/deltat, a1perimeter); } else first = false; prev = *it; } return mreal; } // Helper function to calculate the perimeter length of an arrangement Kernel::FT getPerimeterFromArrangement (Arrangement& arr) { Kernel::FT ret = 0; // Iterate over all segments in this arrangement for (Arrangement::Edge_iterator ei = arr.edges_begin(); ei != arr.edges_end(); ei++) { Arrangement::Halfedge he = *ei; // Add up the lengths of the segments ret = ret + sqrt(CGAL::to_double(Kernel::Segment_2(he.source()->point(), he.target()->point()).squared_length())); } return ret; } /* Operation "area" for a pmregion */ static Kernel::FT getAreaFromArrangement (Arrangement& arr); MReal PMRegion::area () { MReal mreal; // Get all z coordinates of the polyhedron in an ordered set std::set zevents = getZEvents(polyhedron); Kernel::FT prev; bool first = true; for (std::set::iterator it = zevents.begin(); it != zevents.end(); it++) { // For each consecutive pair of z coordinates ... if (!first) { Kernel::FT cur = *it; // ... calculate the intersections with a cutting plane Arrangement a1 = zplanecut(polyhedron, prev+EPSILON); Arrangement a2 = zplanecut(polyhedron, cur-EPSILON); Kernel::FT deltat = (cur - prev) / 86400000; // ... calculate the areas Kernel::FT a1area = getAreaFromArrangement(a1); Kernel::FT a2area = getAreaFromArrangement(a2); Kernel::FT diff = a2area - a1area; // create a mreal representing the area development mreal.append(prev, cur, diff/deltat/deltat, 0, a1area); } else first = false; prev = *it; } return mreal; } // Helper functions to calculate the area from an arrangement static Kernel::FT getAreaFromFace (Arrangement& arr, Arrangement::Face_handle f) { Kernel::FT area = 0; // Calculate the area of this face ... if (f->has_outer_ccb()) { Polygon p; Arrangement::Ccb_halfedge_circulator c = f->outer_ccb(), ec(c); do { p.push_back(c->source()->point()); } while (++c != ec); area = p.area(); } // ... but subtract the area of the holes for (Arrangement::Hole_iterator hi = f->holes_begin(); hi != f->holes_end(); hi++) { area -= getAreaFromFace(arr, (*hi)->twin()->face()); } return area; } static Kernel::FT getAreaFromArrangement (Arrangement& arr) { return -getAreaFromFace(arr, arr.unbounded_face()); } /* Operation "intersects" for a pmregion */ MBool PMRegion::intersects (PMRegion& pmr) { MBool mbool; // Convert the polyhedra to Nef polyhedra Nef_polyhedron np1(polyhedron), np2(pmr.polyhedron); // Calculate the intersection of the polyhedra Nef_polyhedron np3 = np1 * np2; // Iterate over all segments and calculate the unified ranges of // the z coordinates spanned by the segments Range is; for (Halfedge_const_iterator e = np3.halfedges_begin(); e != np3.halfedges_end(); ++e) { Kernel::FT z1 = e->source()->point().z(); Kernel::FT z2 = e->target()->point().z(); is.addrange(z1, z2); } // Append an "mbool" unit for each subrange std::map::iterator it = is.range.begin(); while (it != is.range.end()) { mbool.append(it->first, it->second, true); it++; } return mbool; } vector PMRegion::projectxy () { vector ii; vector oi; for (Facet_iterator s = polyhedron.facets_begin(); s != polyhedron.facets_end(); ++s) { Halfedge_facet_circulator h = s->facet_begin(), he(h); Polygon_2 polygon; do { Point3d p1 = h->vertex()->point(); polygon.insert(polygon.vertices_end(), Point_2(p1.x(), p1.y())); } while (++h != he); if (!polygon.is_simple()) { // Empty polygon after projection continue; } if (polygon.orientation() == CGAL::NEGATIVE) polygon.reverse_orientation(); ii.push_back(polygon); } CGAL::join(ii.begin(), ii.end(), std::back_inserter(oi)); return oi; } /* Operation "traversedarea" for a pmregion */ RList PMRegion::traversedarea () { return Polygons2Region(projectxy()); } void PMRegion::translate(Kernel::FT x, Kernel::FT y, Kernel::FT z) { Kernel::Vector_3 translate(x, y, z); for (Polyhedron::Vertex_iterator v = polyhedron.vertices_begin(); v != polyhedron.vertices_end(); v++) { v->point() = v->point() + translate; } } /*************************************************************************/ /* Reimplementation of atinstant more optimized for large moving regions */ /*************************************************************************/ /* represents a single 2d face */ class Face { public: // Enclosing parent face Face* parent; // Enclosed child faces vector children; Face () : parent(NULL) {}; // Convert this face to a nested list RList toRList() { RList ret; for (std::vector::iterator si = segs.begin(); si != segs.end(); si++) { RList p; p.append(si->s.x()); p.append(si->s.y()); ret.append(p); } return ret; } // Append another segment void append (Seg s) { segs.push_back(s); } // Determines if point p is inside this face. // Uses the winding number algorithm bool inside (Point_2 p) { int wn = 0; // Check if the point is outside the bounding box of this face if (p.x() < llx || p.x() > urx || p.y() < lly || p.y() > ury) return false; for (std::vector::iterator si = segs.begin(); si != segs.end(); si++) { if (si->s.y() <= p.y()) { if (si->e.y() > p.y()) { Kernel::FT sign = si->sign(p); if (sign > 0) { wn++; } else if (sign == 0) { return 0; } } } else { if (si->e.y() <= p.y()) { Kernel::FT sign = si->sign(p); if (sign < 0) { wn--; } else if (sign == 0) { return 0; } } } } return wn != 0; } // Returns true if Face f is inside this face bool inside (Face& f) { // Call inside for an arbitrary point of the face return inside(f.segs.begin()->s); } // Sort this face. After that, the face // - starts with the most lower left point // - is in counter clockwise order void sort() { std::vector::iterator si, best = segs.begin(); llx = urx = best->s.x(); lly = ury = best->s.y(); for (si = segs.begin()+1; si != segs.end(); si++) { if (si->s.x() < llx) llx = si->s.x(); else if (si->s.x() > urx) urx = si->s.x(); if (si->s.y() < lly) lly = si->s.y(); else if (si->s.y() > ury) ury = si->s.y(); if (si->s.y() < best->s.y() || ((si->s.y() == best->s.y()) && (si->s.x() < best->s.x()))) best = si; } vector n; n.insert(n.end(), best, segs.end()); if (best != segs.begin()) n.insert(n.end(), segs.begin(), best); segs = n; if (0 && !ccw()) { std::reverse(segs.begin(), segs.end()); for (si = segs.begin(); si != segs.end(); si++) { si->reverse(); } } } // Checks, if the face is already in counter clockwise order bool ccw () { // The first and the last segments both contain the most // lower left point, so these are part of the convex hull // of this face. Seg first = segs.front(); Seg last = segs.back(); // Now if the start point of the last segment is left // of the first segment, the face is in ccw order. return first.sign(last.s) > 0; } Kernel::FT area () { Kernel::FT a = 0; for (unsigned int i = 0; i < segs.size(); i++) { a += segs[i].s.x() * segs[i].e.y() - segs[i].e.x() * segs[i].s.y(); } a /= 2; if (a < 0) a = -a; for (unsigned int i = 0; i < children.size(); i++) { a -= children[i]->area(); } return a; } private: // ordered list of face segments std::vector segs; // bounding coordinates Kernel::FT llx, lly, urx, ury; }; /* fixTopology establishes the topological "inside" relationship between * a given vector of faces. After this function, all faces point to their * parent (if appropriate) and have their direct children in a "children" * vector. Indirect children are not inside this vector. After this, the * given vector only contains the top-level faces. */ static void fixTopology (vector& fcs) { // Compare each face with each other face. This might be accelerated with // some kind of sweepline algorithm. for (vector::iterator fi = fcs.begin(); fi != fcs.end(); fi++) { for (vector::iterator fj = fcs.begin(); fj != fcs.end(); fj++) { if (fi == fj) continue; if ((*fj)->inside(**fi)) { // fi is inside fj Face *parent = (*fi)->parent; if (parent) { // fi already has another parent if (parent->inside(**fj)) { // fj is inside the current parent, so this is our // direct parent (or at least more direct than the // current parent. If there is a direct parent, this // will be fixed in future iterations). Now we remove // the existing relationship and build a new one to // the new parent fj. parent->children.erase(std::remove( parent->children.begin(), parent->children.end(), *fj), parent->children.end()); (*fi)->parent = *fj; (*fj)->children.push_back(*fi); } } else { // Establish the newly-found relationship (*fi)->parent = *fj; (*fj)->children.push_back(*fi); } } } } // Now remove all faces, that are not on the top-level std::vector::iterator fi = fcs.begin(); while (fi != fcs.end()) { // If a face has a parent, it is not on top-level if ((*fi)->parent) fi = fcs.erase(fi); else fi++; } } // SegSet manages a set of 2d line segments and aids the reconstruction // of faces. class SegSet { public: // Add another segment to the segment set void add (Seg3d seg) { if (seg.s == seg.e) { // cerr << "Not adding degenerated segment "<< s.ToString() << endl; } else { Point_2 s(seg.s.x(), seg.s.y()); Point_2 e(seg.e.x(), seg.e.y()); m[s].insert(e); m[e].insert(s); } } // Add another segment by specifying start and end point void add (Point_2 s, Point_2 e) { m[s].insert(e); m[e].insert(s); } // Returns true if this SegSet is empty bool isEmpty() { return m.empty(); } // Get a vector of segments from the set that start at the given point vector get (Point_2 s) { vector ret; std::map >::iterator mi = m.find(s); if (mi != m.end()) { for (std::set::iterator i = mi->second.begin(); i != mi->second.end(); i++) { ret.push_back(Seg(s, *i)); } } return ret; } // Get a successor of the current segment. Remove the returned segment // from the segment set. Seg getSuccessor (Seg s) { Seg ret; std::map >::iterator mi = m.find(s.e); if (mi != m.end()) { for (std::set::iterator i = mi->second.begin(); i != mi->second.end(); i++) { if (!(*i == s.s)) { // Do not return the reversed segment ret = Seg(s.e, *i); break; } } } if (ret.valid) remove(ret); return ret; } // Get an arbitrary segment from this segment set. The segment // is removed from this set. Seg getSomeSeg () { Seg ret; if (!isEmpty()) { Point_2 s = *m.begin()->second.begin(); Point_2 e = *m[s].begin(); ret = Seg(s, e); remove(ret); } return ret; } // Remove the given segment from this segment set. void remove (Seg s) { std::map >::iterator mi = m.find(s.s); if (mi != m.end()) { mi->second.erase(s.e); if (mi->second.size() == 0) { m.erase(mi); } } mi = m.find(s.e); if (mi != m.end()) { mi->second.erase(s.s); if (mi->second.size() == 0) { m.erase(mi); } } } // Construct faces from this segment set vector getFaces() { vector fcs; while (!isEmpty()) { Seg prev = getSomeSeg(); Point_2 start = prev.s, last; Face *f = new Face(); f->append(prev); while (!isEmpty()) { Seg cur = getSuccessor(prev); if (!cur.valid) { cerr << "No successor found for " << prev.ToString() << " !" << endl; break; } f->append(cur); if (start == cur.e) { f->sort(); fcs.push_back(f); break; } prev = cur; } } fixTopology(fcs); return fcs; } private: // A map indexed by points and providing a // set of successor points. std::map > m; }; /* zplanecut2 calculates the intersection of * a polyhedron with a cutting plane at a given z coordinate */ SegSet zplanecut2 (Polyhedron polyhedron, Kernel::FT z) { SegSet ss; for (Facet_iterator f = polyhedron.facets_begin(); f != polyhedron.facets_end(); f++) { Halfedge_facet_circulator hc = f->facet_begin(); Point3d p1 = hc++->vertex()->point(); Point3d p2 = hc++->vertex()->point(); Point3d p3 = hc++->vertex()->point(); assert(hc == f->facet_begin()); if (p1.z() == p2.z() && p1.z() == p3.z()) continue; // p1,p2,p3 are coplanar to a plane parallel to xy plane Triangle t(p1, p2, p3); if (t.between(z)) // is this triangle relevant for the projection? ss.add(t.project(z)); } return ss; } // Append the face to the given nested list static void addToRList (Face* fcs, RList& rl) { RList fc; // Add the main cycle fc.append(fcs->toRList()); // Add all child cycles as holes for (std::vector::iterator fi = fcs->children.begin(); fi != fcs->children.end(); fi++) { fc.append((*fi)->toRList()); // If a child cycle has children, add these as new faces for (std::vector::iterator fj = (*fi)->children.begin(); fj != (*fi)->children.end(); fj++) { addToRList(*fj, rl); } } rl.append(fc); } // Convert a segment set to a region in nested list format static RList SegSet2Region(SegSet& ss) { vector fcs = ss.getFaces(); RList reg; for (std::vector::iterator fi = fcs.begin(); fi != fcs.end(); fi++) { addToRList(*fi, reg); free(*fi); } return reg; } RList PMRegion::atinstant2 (Kernel::FT instant) { SegSet cut = zplanecut2(polyhedron, instant); // Convert the faces from the segment set to a region RList region = SegSet2Region(cut); return region.obj("region", "region"); } Kernel::FT getAreaFromSegset(SegSet& ss) { Kernel::FT a = 0; vector fcs = ss.getFaces(); for (std::vector::iterator fi = fcs.begin(); fi != fcs.end(); fi++) { Kernel::FT _a = (*fi)->area(); a += _a; } return a; } /* Operation "area" for a pmregion */ MReal PMRegion::area2 () { MReal mreal; // Get all z coordinates of the polyhedron in an ordered set std::set zevents = getZEvents(polyhedron, 60000*60); Kernel::FT zprev, aprev; bool first = true; for (std::set::iterator it = zevents.begin(); it != zevents.end(); it++) { // ... calculate the intersections with a cutting plane Kernel::FT zcur = *it, acur; SegSet ss = zplanecut2(polyhedron, zcur+EPSILON); // SegSet ss = zplanecut2(polyhedron, zcur); acur = getAreaFromSegset(ss); // For each consecutive pair of z coordinates ... if (!first) { Kernel::FT deltat = (zcur - zprev) / 86400000; Kernel::FT diff = acur - aprev; mreal.append(zprev, zcur, diff/deltat/deltat, 0, aprev); } else first = false; zprev = zcur; aprev = acur; } return mreal; } } /* vim: ts=4:sw=4:et */