// polygon_funcs.h (Polygon<> intersection functions) // // The WorldForge Project // Copyright (C) 2002 The WorldForge Project // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // // For information about WorldForge and its authors, please contact // the Worldforge Web Site at http://www.worldforge.org. // // Author: Ron Steinke // Created: 2002-2-20 #ifndef WFMATH_POLYGON_INTERSECT_H #define WFMATH_POLYGON_INTERSECT_H #include #include #include #include #include #include #include // FIXME Work is needed on this code. At very least the following notes // from the original author apply: // "The Intersect() and Contains() functions involving WFMath::Polygon<>" // "are still under development, and probably shouldn't be used yet." namespace WFMath { template inline Vector _Poly2Orient::offset(const Point& pd, Point<2>& p2) const { assert(m_origin.isValid()); // Check for empty polygon before calling this Vector out = pd - m_origin; for(int j = 0; j < 2; ++j) { p2[j] = Dot(out, m_axes[j]); out -= p2[j] * m_axes[j]; } return out; } template inline bool _Poly2Orient::checkContained(const Point& pd, Point<2> & p2) const { Vector off = offset(pd, p2); CoordType sqrsum = 0; for(int i = 0; i < dim; ++i) sqrsum += pd[i] * pd[i]; return off.sqrMag() < numeric_constants::epsilon() * sqrsum; } template<> bool _Poly2Orient<3>::checkIntersectPlane(const AxisBox<3>& b, Point<2>& p2, bool proper) const; template bool _Poly2Orient::checkIntersect(const AxisBox& b, Point<2>& p2, bool proper) const { assert(m_origin.isValid()); if(!m_axes[0].isValid()) { // Single point p2[0] = p2[1] = 0; return Intersect(b, convert(p2), proper); } if(m_axes[1].isValid()) { // A plane // I only know how to do this in 3D, so write a function which will // specialize to different dimensions return checkIntersectPlane(b, p2, proper); } // A line // This is a modified version of AxisBox<>/Segment<> intersection CoordType min = 0, max = 0; // Initialize to avoid compiler warnings bool got_bounds = false; for(int i = 0; i < dim; ++i) { const CoordType dist = (m_axes[0])[i]; // const may optimize away better if(dist == 0) { if(_Less(m_origin[i], b.lowCorner()[i], proper) || _Greater(m_origin[i], b.highCorner()[i], proper)) return false; } else { CoordType low = (b.lowCorner()[i] - m_origin[i]) / dist; CoordType high = (b.highCorner()[i] - m_origin[i]) / dist; if(low > high) { CoordType tmp = high; high = low; low = tmp; } if(got_bounds) { if(low > min) min = low; if(high < max) max = high; } else { min = low; max = high; got_bounds = true; } } } assert(got_bounds); // We can't be parallel in _all_ dimensions if(_LessEq(min, max, proper)) { p2[0] = (max - min) / 2; p2[1] = 0; return true; } else return false; } template int _Intersect(const _Poly2Orient &o1, const _Poly2Orient &o2, _Poly2OrientIntersectData &data) { if(!o1.m_origin.isValid() || !o2.m_origin.isValid()) { // No points return -1; } // Check for single point basis if(!o1.m_axes[0].isValid()) { if(!o2.checkContained(o1.m_origin, data.p2)) return -1; // no intersect _Poly2OrientIntersectData data; data.p1[0] = data.p1[1] = 0; return 0; // point intersect } if(!o2.m_axes[0].isValid()) { if(!o1.checkContained(o2.m_origin, data.p1)) return -1; // no intersect data.p2[0] = data.p2[1] = 0; return 0; // point intersect } // Find a common basis for the plane's orientations // by projecting out the part of o1's basis that lies // in o2's basis Vector basis1, basis2; CoordType sqrmag1, sqrmag2; int basis_size = 0; basis1 = o2.m_axes[0] * Dot(o2.m_axes[0], o1.m_axes[0]); if(o2.m_axes[1].isValid()) basis1 += o2.m_axes[1] * Dot(o2.m_axes[1], o1.m_axes[0]); // Don't need to scale, the m_axes are unit vectors sqrmag1 = basis1.sqrMag(); if(sqrmag1 > numeric_constants::epsilon() * numeric_constants::epsilon()) basis_size = 1; if(o1.m_axes[1].isValid()) { basis2 = o2.m_axes[0] * Dot(o2.m_axes[0], o1.m_axes[1]); if(o2.m_axes[1].isValid()) basis2 += o2.m_axes[1] * Dot(o2.m_axes[1], o1.m_axes[1]); // Project out part parallel to basis1 if(basis_size == 1) basis2 -= basis1 * (Dot(basis1, basis2) / sqrmag1); sqrmag2 = basis2.sqrMag(); if(sqrmag2 > numeric_constants::epsilon() * numeric_constants::epsilon()) { if(basis_size++ == 0) { basis1 = basis2; sqrmag1 = sqrmag2; } } } Vector off = o2.m_origin - o1.m_origin; switch(basis_size) { case 0: { // All vectors are orthogonal, check for a common point in the plane // This can happen even in 3d for degenerate bases data.p1[0] = Dot(o1.m_axes[0], off); Vector off1 = o1.m_axes[0] * data.p1[0]; if(o1.m_axes[1].isValid()) { data.p1[1] = Dot(o1.m_axes[1], off); off1 += o1.m_axes[1] * data.p1[1]; } else data.p1[1] = 0; data.p2[0] = -Dot(o2.m_axes[0], off); Vector off2 = o2.m_axes[0] * data.p2[0]; if(o1.m_axes[1].isValid()) { data.p2[1] = -Dot(o2.m_axes[1], off); off2 += o1.m_axes[1] * data.p2[1]; } else data.p2[1] = 0; if(off1 - off2 != off) // No common point return -1; else // Got a point return 1; } case 1: { // Check for an intersection line data.o1_is_line = !o1.m_axes[1].isValid(); data.o2_is_line = !o2.m_axes[1].isValid(); if(!o1.m_axes[1].isValid() && !o2.m_axes[1].isValid()) { CoordType proj = Dot(off, o2.m_axes[0]); if(off != o2.m_axes[0] * proj) return -1; data.v1[0] = 1; data.v1[1] = 0; data.p1[0] = data.p1[1] = 0; data.v2[0] = (Dot(o1.m_axes[0], o2.m_axes[0]) > 0) ? 1 : -1; data.v2[1] = 0; data.p2[0] = -proj; data.p2[1] = 0; return 1; } if(!o1.m_axes[1].isValid()) { data.p2[0] = -Dot(off, o2.m_axes[0]); data.p2[1] = -Dot(off, o2.m_axes[1]); if(off != - data.p2[0] * o2.m_axes[0] - data.p2[1] * o2.m_axes[1]) return -1; data.v1[0] = 1; data.v1[1] = 0; data.p1[0] = data.p1[1] = 0; data.v2[0] = Dot(o1.m_axes[0], o2.m_axes[0]); data.v2[1] = Dot(o1.m_axes[0], o2.m_axes[1]); return 1; } if(!o2.m_axes[1].isValid()) { data.p1[0] = Dot(off, o1.m_axes[0]); data.p1[1] = Dot(off, o1.m_axes[1]); if(off != data.p1[0] * o1.m_axes[0] + data.p1[1] * o1.m_axes[1]) return -1; data.v2[0] = 1; data.v2[1] = 0; data.p2[0] = data.p2[1] = 0; data.v1[0] = Dot(o1.m_axes[0], o2.m_axes[0]); data.v1[1] = Dot(o1.m_axes[1], o2.m_axes[0]); return 1; } data.p1[0] = Dot(off, o1.m_axes[0]); data.p1[1] = Dot(off, o1.m_axes[1]); data.p2[0] = -Dot(off, o2.m_axes[0]); data.p2[1] = -Dot(off, o2.m_axes[1]); if(off != data.p1[0] * o1.m_axes[0] + data.p1[1] * o1.m_axes[1] - data.p2[0] * o2.m_axes[0] - data.p2[1] * o2.m_axes[1]) return -1; basis1 /= std::sqrt(sqrmag1); data.v1[0] = Dot(o1.m_axes[0], basis1); data.v1[1] = Dot(o1.m_axes[1], basis1); data.v2[0] = Dot(o2.m_axes[0], basis1); data.v2[1] = Dot(o2.m_axes[1], basis1); return 1; } case 2: { assert(o1.m_axes[1].isValid() && o2.m_axes[1].isValid()); // The planes are parallel, check if they are the same plane CoordType off_sqr_mag = data.off.sqrMag(); // Find the offset between the origins in o2's coordnates if(off_sqr_mag != 0) { // The offsets aren't identical Vector off_copy = off; data.off[0] = Dot(o2.m_axes[0], off); off_copy -= o1.m_axes[0] * data.off[0]; data.off[1] = Dot(o2.m_axes[1], off); off_copy -= o1.m_axes[1] * data.off[1]; if(off_copy.sqrMag() > off_sqr_mag * numeric_constants::epsilon()) return -1; // The planes are different } else data.off[0] = data.off[1] = 0; // Define o2's basis vectors in o1's coordinates data.v1[0] = Dot(o2.m_axes[0], o1.m_axes[0]); data.v1[1] = Dot(o2.m_axes[0], o1.m_axes[1]); data.v2[0] = Dot(o2.m_axes[1], o1.m_axes[0]); data.v2[1] = Dot(o2.m_axes[1], o1.m_axes[1]); return 2; } default: assert(false); return -1; } } template inline bool Intersect(const Polygon& r, const Point& p, bool proper) { Point<2> p2; return r.m_poly.numCorners() > 0 && r.m_orient.checkContained(p, p2) && Intersect(r.m_poly, p2, proper); } template inline bool Contains(const Point& p, const Polygon& r, bool proper) { if(r.m_poly.numCorners() == 0) return true; if(proper) return false; for(size_t i = 1; i < r.m_poly.numCorners(); ++i) if(r.m_poly[i] != r.m_poly[0]) return false; Point<2> p2; return r.m_orient.checkContained(p, p2) && p2 == r.m_poly[0]; } template bool Intersect(const Polygon& p, const AxisBox& b, bool proper) { size_t corners = p.m_poly.numCorners(); if(corners == 0) return false; Point<2> p2; if(!p.m_orient.checkIntersect(b, p2, proper)) return false; Segment s; s.endpoint(0) = p.m_orient.convert(p.m_poly.getCorner(corners-1)); int next_end = 1; for(size_t i = 0; i < corners; ++i) { s.endpoint(next_end) = p.m_orient.convert(p.m_poly.getCorner(i)); if(Intersect(b, s, proper)) return true; next_end = next_end ? 0 : 1; } return Contains(p, p2, proper); } template bool _PolyContainsBox(const _Poly2Orient &orient, const Polygon<2> &poly, const Point &corner, const Vector &size, bool proper) { int num_dim = 0, nonzero_dim = -1; for(int i = 0; i < dim; ++i) { if(size[i] == 0) continue; if(num_dim == 2) return false; if(nonzero_dim == -1 || std::fabs(size[nonzero_dim]) < std::fabs(size[i])) nonzero_dim = i; ++num_dim; } Point<2> corner1; if(!orient.checkContained(corner, corner1)) return false; if(num_dim == 0) return Contains(poly, corner1, proper); Point<2> corner2; if(!orient.checkContained(corner + size, corner2)) return false; if(num_dim == 1) return Contains(poly, Segment<2>(corner1, corner2), proper); Point other_corner = corner; other_corner[nonzero_dim] += size[nonzero_dim]; Point<2> corner3; if(!orient.checkContained(other_corner, corner3)) return false; // Create a RotBox<2> Vector<2> vec1(corner2 - corner1), vec2(corner3 - corner1); RotMatrix<2> m; // A matrix which gives the rotation from the x-axis to vec1 try { m.rotation(Vector<2>(1, 0), vec1); } catch(ColinearVectors<2>) { // vec1 is parallel to (-1, 0), so we're fine m.identity(); } RotBox<2> box(corner1, ProdInv(vec2, m), m); return Contains(poly, box, proper); } template inline bool Contains(const Polygon& p, const AxisBox& b, bool proper) { return _PolyContainsBox(p.m_orient, p.m_poly, b.m_low, b.m_high - b.m_low, proper); } template inline bool Contains(const AxisBox& b, const Polygon& p, bool proper) { for(size_t i = 0; i < p.m_poly.numCorners(); ++i) if(!Contains(b, p.getCorner(i), proper)) return false; return true; } template inline bool Intersect(const Polygon& p, const Ball& b, bool proper) { if(p.m_poly.numCorners() == 0) return false; Point<2> c2; CoordType dist; dist = b.m_radius * b.m_radius - p.m_orient.offset(b.m_center, c2).sqrMag(); if(_Less(dist, 0, proper)) return false; return Intersect(p.m_poly, Ball<2>(c2, std::sqrt(dist)), proper); } template inline bool Contains(const Polygon& p, const Ball& b, bool proper) { if(p.m_poly.numCorners() == 0) return false; if(b.m_radius > 0) return false; Point<2> c2; if(!p.m_orient.checkContained(b.m_center, c2)) return false; return Contains(p.m_poly, c2, proper); } template inline bool Contains(const Ball& b, const Polygon& p, bool proper) { if(p.m_poly.numCorners() == 0) return true; Point<2> c2; CoordType dist; dist = b.m_radius * b.m_radius - p.m_orient.offset(b.m_center, c2).sqrMag(); if(_Less(dist, 0, proper)) return false; for(size_t i = 0; i != p.m_poly.numCorners(); ++i) if(_Less(dist, SquaredDistance(c2, p.m_poly[i]), proper)) return false; return true; } template bool Intersect(const Polygon& p, const Segment& s, bool proper) { if(p.m_poly.numCorners() == 0) return false; Point<2> p1, p2; CoordType d1, d2; Vector v1, v2; v1 = p.m_orient.offset(s.m_p1, p1); v2 = p.m_orient.offset(s.m_p2, p2); if(Dot(v1, v2) > 0) // Both points on same side of sheet return false; d1 = v1.mag(); d2 = v2.mag(); Point<2> p_intersect; if(d1 + d2 == 0) // Avoid divide by zero later return Intersect(p.m_poly, Segment<2>(p1, p2), proper); for(int i = 0; i < 2; ++i) p_intersect[i] = (p1[i] * d2 + p2[i] * d1) / (d1 + d2); return Intersect(p.m_poly, p_intersect, proper); } template inline bool Contains(const Polygon& p, const Segment& s, bool proper) { if(p.m_poly.numCorners() == 0) return false; Segment<2> s2; if(!p.m_orient.checkContained(s.m_p1, s2.endpoint(0))) return false; if(!p.m_orient.checkContained(s.m_p2, s2.endpoint(1))) return false; return Contains(p.m_poly, s2, proper); } template inline bool Contains(const Segment& s, const Polygon& p, bool proper) { if(p.m_poly.numCorners() == 0) return true; // Expand the basis to include the segment, this deals well with // degenerate polygons Segment<2> s2; _Poly2Orient orient(p.m_orient); for(int i = 0; i < 2; ++i) if(!orient.expand(s.endpoint(i), s2.endpoint(i))) return false; return Contains(s2, p.m_poly, proper); } template bool Intersect(const Polygon& p, const RotBox& r, bool proper) { size_t corners = p.m_poly.numCorners(); if(corners == 0) return false; _Poly2Orient orient(p.m_orient); // FIXME rotateInverse() orient.rotate(r.m_orient.inverse(), r.m_corner0); AxisBox b(r.m_corner0, r.m_corner0 + r.m_size); Point<2> p2; if(!orient.checkIntersect(b, p2, proper)) return false; Segment s; s.endpoint(0) = orient.convert(p.m_poly.getCorner(corners-1)); int next_end = 1; for(size_t i = 0; i < corners; ++i) { s.endpoint(next_end) = orient.convert(p.m_poly.getCorner(i)); if(Intersect(b, s, proper)) return true; next_end = next_end ? 0 : 1; } return Contains(p, p2, proper); } template inline bool Contains(const Polygon& p, const RotBox& r, bool proper) { _Poly2Orient orient(p.m_orient); orient.rotate(r.m_orient.inverse(), r.m_corner0); return _PolyContainsBox(orient, p.m_poly, r.m_corner0, r.m_size, proper); } template inline bool Contains(const RotBox& r, const Polygon& p, bool proper) { if(p.m_poly.numCorners() == 0) return true; AxisBox b(r.m_corner0, r.m_corner0 + r.m_size); _Poly2Orient orient(p.m_orient); orient.rotate(r.m_orient.inverse(), r.m_corner0); for(size_t i = 0; i < p.m_poly.numCorners(); ++i) if(!Contains(b, orient.convert(p.m_poly[i]), proper)) return false; return true; } bool _PolyPolyIntersect(const Polygon<2> &poly1, const Polygon<2> &poly2, const int intersect_dim, const _Poly2OrientIntersectData &data, bool proper); template inline bool Intersect(const Polygon& p1, const Polygon& p2, bool proper) { _Poly2OrientIntersectData data; int intersect_dim = _Intersect(p1.m_orient, p2.m_orient, data); return _PolyPolyIntersect(p1.m_poly, p2.m_poly, intersect_dim, data, proper); } bool _PolyPolyContains(const Polygon<2> &outer, const Polygon<2> &inner, const int intersect_dim, const _Poly2OrientIntersectData &data, bool proper); template inline bool Contains(const Polygon& outer, const Polygon& inner, bool proper) { if(outer.m_poly.numCorners() == 0) return !proper && inner.m_poly.numCorners() == 0; if(inner.m_poly.numCorners() == 0) return true; _Poly2OrientIntersectData data; int intersect_dim = _Intersect(outer.m_orient, inner.m_orient, data); return _PolyPolyContains(outer.m_poly, inner.m_poly, intersect_dim, data, proper); } template<> bool Intersect(const Polygon<2>& r, const Point<2>& p, bool proper); template<> bool Contains(const Point<2>& p, const Polygon<2>& r, bool proper); template<> bool Intersect(const Polygon<2>& p, const AxisBox<2>& b, bool proper); template<> bool Contains(const Polygon<2>& p, const AxisBox<2>& b, bool proper); template<> bool Contains(const AxisBox<2>& b, const Polygon<2>& p, bool proper); template<> bool Intersect(const Polygon<2>& p, const Ball<2>& b, bool proper); template<> bool Contains(const Polygon<2>& p, const Ball<2>& b, bool proper); template<> bool Contains(const Ball<2>& b, const Polygon<2>& p, bool proper); template<> bool Intersect(const Polygon<2>& r, const Segment<2>& s, bool proper); template<> bool Contains(const Polygon<2>& p, const Segment<2>& s, bool proper); template<> bool Contains(const Segment<2>& s, const Polygon<2>& p, bool proper); template<> bool Intersect(const Polygon<2>& p, const RotBox<2>& r, bool proper); template<> bool Contains(const Polygon<2>& p, const RotBox<2>& r, bool proper); template<> bool Contains(const RotBox<2>& r, const Polygon<2>& p, bool proper); template<> bool Intersect(const Polygon<2>& p1, const Polygon<2>& p2, bool proper); template<> bool Contains(const Polygon<2>& outer, const Polygon<2>& inner, bool proper); } // namespace WFMath #endif // WFMATH_POLYGON_INTERSECT_H