Code

c4d8df3385bf77d6036550d6878fd081b91e33ec
[inkscape.git] / src / 2geom / convex-cover.cpp
1 /*
2  * convex-cover.cpp
3  *
4  * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
5  * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it either under the terms of the GNU Lesser General Public
9  * License version 2.1 as published by the Free Software Foundation
10  * (the "LGPL") or, at your option, under the terms of the Mozilla
11  * Public License Version 1.1 (the "MPL"). If you do not alter this
12  * notice, a recipient may use your version of this file under either
13  * the MPL or the LGPL.
14  *
15  * You should have received a copy of the LGPL along with this library
16  * in the file COPYING-LGPL-2.1; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  * You should have received a copy of the MPL along with this library
19  * in the file COPYING-MPL-1.1
20  *
21  * The contents of this file are subject to the Mozilla Public License
22  * Version 1.1 (the "License"); you may not use this file except in
23  * compliance with the License. You may obtain a copy of the License at
24  * http://www.mozilla.org/MPL/
25  *
26  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
27  * OF ANY KIND, either express or implied. See the LGPL or the MPL for
28  * the specific language governing rights and limitations.
29  *
30  */
32 #include <2geom/convex-cover.h>
33 #include <2geom/exception.h>
34 #include <algorithm>
35 #include <map>
37 /** Todo:
38     + modify graham scan to work top to bottom, rather than around angles
39     + intersection
40     + minimum distance between convex hulls
41     + maximum distance between convex hulls
42     + hausdorf metric?
43     + check all degenerate cases carefully
44     + check all algorithms meet all invariants
45     + generalise rotating caliper algorithm (iterator/circulator?)
46 */
48 using std::vector;
49 using std::map;
50 using std::pair;
52 namespace Geom{
54 /*** SignedTriangleArea
55  * returns the area of the triangle defined by p0, p1, p2.  A clockwise triangle has positive area.
56  */
57 double
58 SignedTriangleArea(Point p0, Point p1, Point p2) {
59     return cross((p1 - p0), (p2 - p0));
60 }
62 class angle_cmp{
63 public:
64     Point o;
65     angle_cmp(Point o) : o(o) {}
67 #if 0
68     bool
69     operator()(Point a, Point b) {
70         // not remove this check or std::sort could crash
71         if (a == b) return false;
72         Point da = a - o;
73         Point db = b - o;
74         if (da == -db) return false;
76 #if 1
77         double aa = da[0];
78         double ab = db[0];
79         if((da[1] == 0) && (db[1] == 0))
80             return da[0] < db[0];
81         if(da[1] == 0)
82             return true; // infinite tangent
83         if(db[1] == 0)
84             return false; // infinite tangent
85         aa = da[0] / da[1];
86         ab = db[0] / db[1];
87         if(aa > ab)
88             return true;
89 #else
90         //assert((ata > atb) == (aa < ab));
91         double aa = atan2(da);
92         double ab = atan2(db);
93         if(aa < ab)
94             return true;
95 #endif
96         if(aa == ab)
97             return L2sq(da) < L2sq(db);
98         return false;
99     }
100 #else
101     bool operator() (Point const& a, Point const&  b)
102     {
103         // not remove this check or std::sort could generate
104         // a segmentation fault because it needs a strict '<'
105         // but due to round errors a == b doesn't mean dxy == dyx
106         if (a == b) return false;
107         Point da = a - o;
108         Point db = b - o;
109         if (da == -db) return false;
110         double dxy = da[X] * db[Y];
111         double dyx = da[Y] * db[X];
112         if (dxy > dyx) return true;
113         else if (dxy < dyx) return false;
114         return L2sq(da) < L2sq(db);
115     }
116 #endif
117 };
119 void
120 ConvexHull::find_pivot() {
121     // Find pivot P;
122     unsigned pivot = 0;
123     for (unsigned i = 1; i < boundary.size(); i++)
124         if(boundary[i] <= boundary[pivot])
125             pivot = i;
127     std::swap(boundary[0], boundary[pivot]);
130 void
131 ConvexHull::angle_sort() {
132 // sort points by angle (resolve ties in favor of point farther from P);
133 // we leave the first one in place as our pivot
134     std::sort(boundary.begin()+1, boundary.end(), angle_cmp(boundary[0]));
138 void
139 ConvexHull::graham_scan() {
140     if (boundary.size() < 4) return;
141     unsigned stac = 2;
142     for(unsigned int i = 2; i < boundary.size(); i++) {
143         double o = SignedTriangleArea(boundary[stac-2],
144                                       boundary[stac-1],
145                                       boundary[i]);
146         if(o == 0) { // colinear - dangerous...
147             stac--;
148         } else if(o < 0) { // anticlockwise
149         } else { // remove concavity
150             while(o >= 0 && stac > 2) {
151                 stac--;
152                 o = SignedTriangleArea(boundary[stac-2],
153                                        boundary[stac-1],
154                                        boundary[i]);
155             }
156         }
157         boundary[stac++] = boundary[i];
158     }
159     boundary.resize(stac);
162 void
163 ConvexHull::graham() {
164     if(is_degenerate()) // nothing to do
165         return;
166     find_pivot();
167     angle_sort();
168     graham_scan();
171 //Mathematically incorrect mod, but more useful.
172 int mod(int i, int l) {
173     return i >= 0 ?
174            i % l : (i % l) + l;
176 //OPT: usages can often be replaced by conditions
178 /*** ConvexHull::left
179  * Tests if a point is left (outside) of a particular segment, n. */
180 bool
181 ConvexHull::is_left(Point p, int n) {
182     return SignedTriangleArea((*this)[n], (*this)[n+1], p) > 0;
185 /*** ConvexHull::find_positive
186  * May return any number n where the segment n -> n + 1 (possibly looped around) in the hull such
187  * that the point is on the wrong side to be within the hull.  Returns -1 if it is within the hull.*/
188 int
189 ConvexHull::find_left(Point p) {
190     int l = boundary.size(); //Who knows if C++ is smart enough to optimize this?
191     for(int i = 0; i < l; i++) {
192         if(is_left(p, i)) return i;
193     }
194     return -1;
196 //OPT: do a spread iteration - quasi-random with no repeats and full coverage.
198 /*** ConvexHull::contains_point
199  * In order to test whether a point is inside a convex hull we can travel once around the outside making
200  * sure that each triangle made from an edge and the point has positive area. */
201 bool
202 ConvexHull::contains_point(Point p) {
203     return find_left(p) == -1;
206 /*** ConvexHull::add_point
207  * to add a point we need to find whether the new point extends the boundary, and if so, what it
208  * obscures.  Tarjan?  Jarvis?*/
209 void
210 ConvexHull::merge(Point p) {
211     std::vector<Point> out;
213     int l = boundary.size();
215     if(l < 2) {
216         boundary.push_back(p);
217         return;
218     }
220     bool pushed = false;
222     bool pre = is_left(p, -1);
223     for(int i = 0; i < l; i++) {
224         bool cur = is_left(p, i);
225         if(pre) {
226             if(cur) {
227                 if(!pushed) {
228                     out.push_back(p);
229                     pushed = true;
230                 }
231                 continue;
232             }
233             else if(!pushed) {
234                 out.push_back(p);
235                 pushed = true;
236             }
237         }
238         out.push_back(boundary[i]);
239         pre = cur;
240     }
242     boundary = out;
244 //OPT: quickly find an obscured point and find the bounds by extending from there.  then push all points not within the bounds in order.
245   //OPT: use binary searches to find the actual starts/ends, use known rights as boundaries.  may require cooperation of find_left algo.
247 /*** ConvexHull::is_clockwise
248  * We require that successive pairs of edges always turn right.
249  * proposed algorithm: walk successive edges and require triangle area is positive.
250  */
251 bool
252 ConvexHull::is_clockwise() const {
253     if(is_degenerate())
254         return true;
255     Point first = boundary[0];
256     Point second = boundary[1];
257     for(std::vector<Point>::const_iterator it(boundary.begin()+2), e(boundary.end());
258         it != e;) {
259         if(SignedTriangleArea(first, second, *it) > 0)
260             return false;
261         first = second;
262         second = *it;
263         ++it;
264     }
265     return true;
268 /*** ConvexHull::top_point_first
269  * We require that the first point in the convex hull has the least y coord, and that off all such points on the hull, it has the least x coord.
270  * proposed algorithm: track lexicographic minimum while walking the list.
271  */
272 bool
273 ConvexHull::top_point_first() const {
274     std::vector<Point>::const_iterator pivot = boundary.begin();
275     for(std::vector<Point>::const_iterator it(boundary.begin()+1),
276             e(boundary.end());
277         it != e; it++) {
278         if((*it)[1] < (*pivot)[1])
279             pivot = it;
280         else if(((*it)[1] == (*pivot)[1]) &&
281                 ((*it)[0] < (*pivot)[0]))
282             pivot = it;
283     }
284     return pivot == boundary.begin();
286 //OPT: since the Y values are orderly there should be something like a binary search to do this.
288 /*** ConvexHull::no_colinear_points
289  * We require that no three vertices are colinear.
290 proposed algorithm:  We must be very careful about rounding here.
291 */
292 bool
293 ConvexHull::no_colinear_points() const {
294     // XXX: implement me!
295     THROW_NOTIMPLEMENTED();
298 bool
299 ConvexHull::meets_invariants() const {
300     return is_clockwise() && top_point_first() && no_colinear_points();
303 /*** ConvexHull::is_degenerate
304  * We allow three degenerate cases: empty, 1 point and 2 points.  In many cases these should be handled explicitly.
305  */
306 bool
307 ConvexHull::is_degenerate() const {
308     return boundary.size() < 3;
312 /* Here we really need a rotating calipers implementation.  This implementation is slow and incorrect.
313    This incorrectness is a problem because it throws off the algorithms.  Perhaps I will come up with
314    something better tomorrow.  The incorrectness is in the order of the bridges - they must be in the
315    order of traversal around.  Since the a->b and b->a bridges are seperated, they don't need to be merge
316    order, just the order of the traversal of the host hull.  Currently some situations make a n->0 bridge
317    first.*/
318 pair< map<int, int>, map<int, int> >
319 bridges(ConvexHull a, ConvexHull b) {
320     map<int, int> abridges;
321     map<int, int> bbridges;
323     for(unsigned ia = 0; ia < a.boundary.size(); ia++) {
324         for(unsigned ib = 0; ib < b.boundary.size(); ib++) {
325             Point d = b[ib] - a[ia];
326             Geom::Coord e = cross(d, a[ia - 1] - a[ia]), f = cross(d, a[ia + 1] - a[ia]);
327             Geom::Coord g = cross(d, b[ib - 1] - a[ia]), h = cross(d, b[ib + 1] - a[ia]);
328             if     (e > 0 && f > 0 && g > 0 && h > 0) abridges[ia] = ib;
329             else if(e < 0 && f < 0 && g < 0 && h < 0) bbridges[ib] = ia;
330         }
331     }
333     return make_pair(abridges, bbridges);
336 std::vector<Point> bridge_points(ConvexHull a, ConvexHull b) {
337     vector<Point> ret;
338     pair< map<int, int>, map<int, int> > indices = bridges(a, b);
339     for(map<int, int>::iterator it = indices.first.begin(); it != indices.first.end(); it++) {
340       ret.push_back(a[it->first]);
341       ret.push_back(b[it->second]);
342     }
343     for(map<int, int>::iterator it = indices.second.begin(); it != indices.second.end(); it++) {
344       ret.push_back(b[it->first]);
345       ret.push_back(a[it->second]);
346     }
347     return ret;
350 unsigned find_bottom_right(ConvexHull const &a) {
351     unsigned it = 1;
352     while(it < a.boundary.size() &&
353           a.boundary[it][Y] > a.boundary[it-1][Y])
354         it++;
355     return it-1;
358 /*** ConvexHull sweepline_intersection(ConvexHull a, ConvexHull b);
359  * find the intersection between two convex hulls.  The intersection is also a convex hull.
360  * (Proof: take any two points both in a and in b.  Any point between them is in a by convexity,
361  * and in b by convexity, thus in both.  Need to prove still finite bounds.)
362  * This algorithm works by sweeping a line down both convex hulls in parallel, working out the left and right edges of the new hull.
363  */
364 ConvexHull sweepline_intersection(ConvexHull const &a, ConvexHull const &b) {
365     ConvexHull ret;
367     unsigned al = 0;
368     unsigned bl = 0;
370     while(al+1 < a.boundary.size() &&
371           (a.boundary[al+1][Y] > b.boundary[bl][Y])) {
372         al++;
373     }
374     while(bl+1 < b.boundary.size() &&
375           (b.boundary[bl+1][Y] > a.boundary[al][Y])) {
376         bl++;
377     }
378     // al and bl now point to the top of the first pair of edges that overlap in y value
379     //double sweep_y = std::min(a.boundary[al][Y],
380     //                          b.boundary[bl][Y]);
381     return ret;
384 /*** ConvexHull intersection(ConvexHull a, ConvexHull b);
385  * find the intersection between two convex hulls.  The intersection is also a convex hull.
386  * (Proof: take any two points both in a and in b.  Any point between them is in a by convexity,
387  * and in b by convexity, thus in both.  Need to prove still finite bounds.)
388  */
389 ConvexHull intersection(ConvexHull /*a*/, ConvexHull /*b*/) {
390     ConvexHull ret;
391     /*
392     int ai = 0, bi = 0;
393     int aj = a.boundary.size() - 1;
394     int bj = b.boundary.size() - 1;
395     */
396     /*while (true) {
397         if(a[ai]
398     }*/
399     return ret;
402 /*** ConvexHull merge(ConvexHull a, ConvexHull b);
403  * find the smallest convex hull that surrounds a and b.
404  */
405 ConvexHull merge(ConvexHull a, ConvexHull b) {
406     ConvexHull ret;
408     pair< map<int, int>, map<int, int> > bpair = bridges(a, b);
409     map<int, int> ab = bpair.first;
410     map<int, int> bb = bpair.second;
412     ab[-1] = 0;
413     bb[-1] = 0;
415     int i = -1; // XXX: i is int but refers to vector indices
417     if(a.boundary[0][1] > b.boundary[0][1]) goto start_b;
418     while(true) {
419         for(; ab.count(i) == 0; i++) {
420             ret.boundary.push_back(a[i]);
421             if(i >= (int)a.boundary.size()) return ret;
422         }
423         if(ab[i] == 0 && i != -1) break;
424         i = ab[i];
425         start_b:
427         for(; bb.count(i) == 0; i++) {
428             ret.boundary.push_back(b[i]);
429             if(i >= (int)b.boundary.size()) return ret;
430         }
431         if(bb[i] == 0 && i != -1) break;
432         i = bb[i];
433     }
434     return ret;
437 ConvexHull graham_merge(ConvexHull a, ConvexHull b) {
438     ConvexHull result;
440     // we can avoid the find pivot step because of top_point_first
441     if(b.boundary[0] <= a.boundary[0])
442         std::swap(a, b);
444     result.boundary = a.boundary;
445     result.boundary.insert(result.boundary.end(),
446                            b.boundary.begin(), b.boundary.end());
448 /** if we modified graham scan to work top to bottom as proposed in lect754.pdf we could replace the
449  angle sort with a simple merge sort type algorithm. furthermore, we could do the graham scan
450  online, avoiding a bunch of memory copies.  That would probably be linear. -- njh*/
451     result.angle_sort();
452     result.graham_scan();
454     return result;
456 //TODO: reinstate
457 /*ConvexCover::ConvexCover(Path const &sp) : path(&sp) {
458     cc.reserve(sp.size());
459     for(Geom::Path::const_iterator it(sp.begin()), end(sp.end()); it != end; ++it) {
460         cc.push_back(ConvexHull((*it).begin(), (*it).end()));
461     }
462 }*/
464 double ConvexHull::centroid_and_area(Geom::Point& centroid) const {
465     const unsigned n = boundary.size();
466     if (n < 2)
467         return 0;
468     if(n < 3) {
469         centroid = (boundary[0] + boundary[1])/2;
470         return 0;
471     }
472     Geom::Point centroid_tmp(0,0);
473     double atmp = 0;
474     for (unsigned i = n-1, j = 0; j < n; i = j, j++) {
475         const double ai = -cross(boundary[j], boundary[i]);
476         atmp += ai;
477         centroid_tmp += (boundary[j] + boundary[i])*ai; // first moment.
478     }
479     if (atmp != 0) {
480         centroid = centroid_tmp / (3 * atmp);
481     }
482     return atmp / 2;
485 // TODO: This can be made lg(n) using golden section/fibonacci search three starting points, say 0,
486 // n/2, n-1 construct a new point, say (n/2 + n)/2 throw away the furthest boundary point iterate
487 // until interval is a single value
488 Point const * ConvexHull::furthest(Point direction) const {
489     Point const * p = &boundary[0];
490     double d = dot(*p, direction);
491     for(unsigned i = 1; i < boundary.size(); i++) {
492         double dd = dot(boundary[i], direction);
493         if(d < dd) {
494             p = &boundary[i];
495             d = dd;
496         }
497     }
498     return p;
502 // returns (a, (b,c)), three points which define the narrowest diameter of the hull as the pair of
503 // lines going through b,c, and through a, parallel to b,c TODO: This can be made linear time by
504 // moving point tc incrementally from the previous value (it can only move in one direction).  It
505 // is currently n*O(furthest)
506 double ConvexHull::narrowest_diameter(Point &a, Point &b, Point &c) {
507     Point tb = boundary.back();
508     double d = INFINITY;
509     for(unsigned i = 0; i < boundary.size(); i++) {
510         Point tc = boundary[i];
511         Point n = -rot90(tb-tc);
512         Point ta = *furthest(n);
513         double td = dot(n, ta-tb)/dot(n,n);
514         if(td < d) {
515             a = ta;
516             b = tb;
517             c = tc;
518             d = td;
519         }
520         tb = tc;
521     }
522     return d;
525 };
527 /*
528   Local Variables:
529   mode:c++
530   c-file-style:"stroustrup"
531   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
532   indent-tabs-mode:nil
533   fill-column:99
534   End:
535 */
536 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :