<main()>= int main(int argc, char * argv[]) { cl_options opts; opts['e'] = "0.005"; get_cl_options(argc, argv, opts); epsilon = atof(opts['e']); points pts; <read the input> <find and output the polygons> }
Definesmain
(links are to index).Used below.
<read the input>= std::copy(std::istream_iterator<point>(std::cin), std::istream_iterator<point>(), std::inserter(pts, pts.end())); if (not std::cin.eof()) { std::cerr << "! Input error while reading pts.\n"; return EXIT_FAILURE; }
Used above.
<find and output the polygons>= for (unsigned i = 3; i <= pts.size(); i++) { polygons rpolys; find_rpolys(pts, rpolys, i); if (not rpolys.empty()) { std::cout << i << "\n"; std::copy(rpolys.begin(), rpolys.end(), std::ostream_iterator<polygon>(std::cout, "\n")); } }
Used above.
<point extraction>= static std::istream & operator >> (std::istream & is, point & pt) { // Read from the given input stream a point and store it in the given // reference. is >> pt.x; if (is.good()) { is >> pt.y; if (not is.good()) is.clear((is.rdstate() & ~std::ios::eofbit) | std::ios::failbit); } return is; }
Used below.
return in >> pt.x >> pt.y;
would seem to do the trick, but this code has a problem: it doesn't distinguish between and end-of-stream that occurred while reading the x coordinate - which is legal and indicates the end of input - from an end-of-stream that occurs when reading the y coordinate, in which the missing coordinate causes a format error. To make such a distinction, the code has to read the coordinates in two parts, with an error check between.
The second source of complexity comes about from making sure the stream returns
the proper error status. As shown by <read the input>
, the expected
input behavior is to fail at the eof when all the points have been read; that
is, the eof failure is normal and indicates that nothing is wrong.
The eof that occurs when trying to read the y coordinate should not be taken as a normal eof, but as an error; this is why the eof bit is cleared. Although it's most likely true that some other status bit was also set at the premature end-of-stream (probably the fail bit to indicate a format error), explicitly setting the fail bit will make sure this is true.
Testing between coordinate reads and clearing the eof bit lets the point
extractor indicate that input with an odd number of coordinates is illegal.
The cascaded input is >> pt.x >> pt.y
can't make the same indication given
the ambiguity over when the eof occurred.
<main.cc
>=
#include <algorithm>
#include <iterator>
#include <iostream>
#include <cstdlib>
#include "cl-options.h"
#include "rpoly.h"
#include "find-rpolys.h"
<main()>
<point extraction>
Definesmain.cc
(links are to index).This code is written to a file (or else not used).
Given a set of n > 2 points, pick any two points from the set. These two points could form one side of an i-sided regular polygon. To determine if they do, we could find the other i-2 points in the polygon and see if those points are in the set too. If they are, then we've found an i-sided regular polygon.
The previous paragraph provides the basis for implementing the
search_poly()
procedure that looks for an i-sided regular polygon
given a side. The point set is specified as an iterator range that does not
contain the two points forming the side; this simplifies the serach for the
candidate polygon in the point set.
<searching for a polygon>= static void search_poly( const point & p1, const point & p2, unsigned sides, const pts_citer & begin, const pts_citer & end, polygons & polys) { // Look in the given point set [begin, end) for a polygon of the given size // having the given line segment as a side; store any such polygon found // in the given reference. polygon poly; gen_poly(poly, p1, p2, sides); polygon sorted_poly(poly); std::sort(sorted_poly.begin(), sorted_poly.end()); polygon match; std::set_intersection(sorted_poly.begin(), sorted_poly.end(), begin, end, std::back_inserter(match)); if (match.size() + 2 == sides) polys.push_back(poly); }
Definessearch_poly
(links are to index).Used below.
There's a couple of ways to find a i-sided regular polygon given a side. Probably the simplest way is to pick one of the two given points as a pivot point and then rotate the other point about the pivot until it co-incides with another vertex in the polygon. The new vertex becomes the pivot point and the rotations are repeated until all the vertices are placed.
The pivoting-side method for generating a candidate polygon is not the best approach because the positional inaccuracies accumulate in each successive vertex and are magnified by each rotation. A more numerically stable approach finds the center of the candidate polygon and rotates one (or both) of the given points once into each new vertex position, which minimizes both the initaial and accumulated errors.
However, the pivoting-side approach works well enough and is relatively simpler than finding the polygon's center.
Because an edge can belong to two polygons (one on either side of the edge), the two points defining the edge are oriented from the first to the second.
<create a candidate polygon>= static void gen_poly(polygon & poly, const point & p1, const point p2, unsigned sides) { // Generate a regular polygon having the given number of sides and given line // segment as one of the sides; store the polygon in the given point-set // reference. The polygon's inside is to your right as you walk from p1 to // p2. assert(sides > 2); const double angle = (static_cast<double>(sides - 2)/sides)*pi; poly.push_back(p1); poly.push_back(p2); for (unsigned i = 2; i < sides; i++) poly.push_back(rotate(poly[i - 2], poly[i - 1], angle)); }
Definesgen_poly
(links are to index).Used below.
<finding polygons>= void find_rpolys(const points & pts, polygons & polys, unsigned sides) { polys.clear(); for (pts_citer i = pts.begin(); i != pts.end(); ++i) { pts_citer j = i; for (++j; j != pts.end(); ++j) { pts_citer k = j; search_poly(*i, *j, sides, ++k, pts.end(), polys); search_poly(*j, *i, sides, k, pts.end(), polys); } } }
Definesfind_rpolys
(links are to index).Used below.
search_poly()
takes O(n) time on each call, for a total of
O(n3). There's lots of geometric information that isn't being
exploited in this solution, so it's quite possible that there's an
asymptotically better solution than this one; but for now, this will do..
<find-rpolys.cc
>=
#include <algorithm>
#include <cmath>
#include <iostream>
#include <cassert>
#include "find-rpolys.h"
#include "rpoly.h"
// what pi is.
const double
pi = M_PI,
two_pi = 2*pi;
// Deja vu c++ style.
static void search_poly(const point &, const point &, unsigned,
const pts_citer &, const pts_citer &, polygons &);
static point rotate(point, const point &, double);
<finding polygons>
<create a candidate polygon>
static point
rotate(point p1, const point & p2, double angle) {
// Rotate the point p1 about the point p2 through the given angle; return the
// rotated point.
point p;
const double
cos_a = cos(angle),
sin_a = sin(angle);
p.x = p1.x - p2.x;
p.y = p1.y - p2.y;
p1.x = cos_a*p.x - sin_a*p.y;
p1.y = sin_a*p.x + cos_a*p.y;
p1.x += p2.x;
p1.y += p2.y;
return p1;
}
<searching for a polygon>
#ifdef FRTESTING
// g++ -o frtesting -DFRTESTING -ansi -pedantic -Wall -g find-rpolys.cc rpoly.cc && ./frtesting
int main() {
point p1, p2, p3;
p1.x = 1;
p1.y = 1;
p2.x = 2;
p2.y = 1;
p3 = rotate(p1, p2, pi/2);
assert(equal(p3.x, 2));
assert(equal(p3.y, 0));
p3 = rotate(p2, p1, pi/2);
assert(equal(p3.x, 1));
assert(equal(p3.y, 2));
p1.x = 1;
p1.y = 1;
p2.x = 2;
p2.y = 2;
for (unsigned i = 3; i < 20; i++) {
polygon poly;
gen_poly(poly, p1, p2, i);
p3 = rotate(poly[i - 2], poly[i - 1], (static_cast<double>(i - 2)/i)*pi);
assert(equal(p3.x, poly[0].x));
assert(equal(p3.y, poly[0].y));
}
}
#endif
Definesfind-rpolys.cc
(links are to index).This code is written to a file (or else not used).
<rpoly.h
>=
#ifndef _rpoly_h_defined_
#define _rpoly_h_defined_
#include <set>
#include <vector>
#include <ostream>
// What a point is. You can't use std::pair because istream iterators freak
// out when extracting std::pairs in g++.
struct point { double x, y; };
// A set of points.
typedef std::set<point> points;
typedef points::const_iterator pts_citer;
// A polygon.
typedef std::vector<point> polygon;
// A set of polygons.
typedef std::vector<polygon> polygons;
// Return true iff the given values are (close enough to being) equal.
extern bool equal(double, double);
// The less-than operator for points.
extern bool operator < (const point &, const point &);
// Write the given point to the given output stream.
extern std::ostream & operator << (std::ostream &, const point &);
// Write the given polygon to the given output stream.
extern std::ostream & operator << (std::ostream &, const polygon &);
// What "close enough" means.
extern double epsilon;
#endif
Definesrpoly.h
(links are to index).This code is written to a file (or else not used).
<rpoly.cc
>=
#include "rpoly.h"
#include <cmath>
#include <ostream>
#include <iterator>
#include <iomanip>
// How close is "close enough"?
double epsilon = 0.0001;
// Deja vu c++ style.
static std::ostream & outv(std::ostream &, double);
bool
equal(double d1, double d2) {
return fabs(d1 - d2) < epsilon;
}
static bool
less(double d1, double d2) {
// Return true iff d1 is close enough to being less than d2.
return (d1 - d2) < -epsilon;
}
bool
operator < (const point & p1, const point & p2) {
return less(p1.x, p2.x) or (equal(p1.x, p2.x) and less(p1.y, p2.y));
}
std::ostream &
operator << (std::ostream & os, const point & pt) {
return outv(outv(os, pt.x) << " ", pt.y);
}
std::ostream &
operator << (std::ostream & os, const polygon & pg) {
std::copy(pg.begin(), pg.end(),
std::ostream_iterator<point>(os, " "));
return os;
}
static std::ostream &
outv(std::ostream & os, double v) {
// Output the given value to the given otput stream; return the stream.
return os << std::fixed << std::setprecision(3) << v;
}
#ifdef RPTESTING
// g++ -o rptesting -DRPTESTING -g -ansi -pedantic -Wall rpoly.cc && ./rptesting
int main() {
point p1, p2;
p1.x = 1;
p1.y = 1;
p2.x = 1;
p2.y = 2;
assert(p1 < p2);
assert(not (p2 < p1));
}
#endif
Definesrpoly.cc
(links are to index).This code is written to a file (or else not used).
find-rpolys.cc
>: D1
main.cc
>: D1
rpoly.cc
>: D1
rpoly.h
>: D1
This page last modified on 25 March 2004.