From dfa85075e8e347ca33e26f96fc90cd311b8c4c32 Mon Sep 17 00:00:00 2001 From: Werner Almesberger Date: Fri, 4 May 2012 21:40:55 -0300 Subject: [PATCH] poly2d/: Yet another 2D polygon library (WIP) --- poly2d/Makefile | 105 +++++++++++++++++++++++++++ poly2d/README | 54 ++++++++++++++ poly2d/cgal_helper.h | 65 +++++++++++++++++ poly2d/p2d_area.c | 44 ++++++++++++ poly2d/p2d_area_holes.cpp | 101 ++++++++++++++++++++++++++ poly2d/p2d_attrib.c | 122 +++++++++++++++++++++++++++++++ poly2d/p2d_contains_point.c | 56 +++++++++++++++ poly2d/p2d_contains_poly.c | 38 ++++++++++ poly2d/p2d_copy.c | 95 ++++++++++++++++++++++++ poly2d/p2d_free.c | 44 ++++++++++++ poly2d/p2d_gnuplot.c | 97 +++++++++++++++++++++++++ poly2d/p2d_hsort.c | 107 +++++++++++++++++++++++++++ poly2d/p2d_hsort.h | 32 +++++++++ poly2d/p2d_make.c | 73 +++++++++++++++++++ poly2d/p2d_offset.cpp | 78 ++++++++++++++++++++ poly2d/poly2d.h | 140 ++++++++++++++++++++++++++++++++++++ poly2d/test/Common | 99 +++++++++++++++++++++++++ poly2d/test/area | 103 ++++++++++++++++++++++++++ poly2d/test/hsort | 96 +++++++++++++++++++++++++ poly2d/test/make | 61 ++++++++++++++++ poly2d/test/offset | 52 ++++++++++++++ poly2d/util.h | 28 ++++++++ poly2d/v2d_intersect.c | 69 ++++++++++++++++++ poly2d/v2d_line_distance.c | 34 +++++++++ 24 files changed, 1793 insertions(+) create mode 100644 poly2d/Makefile create mode 100644 poly2d/README create mode 100644 poly2d/cgal_helper.h create mode 100644 poly2d/p2d_area.c create mode 100644 poly2d/p2d_area_holes.cpp create mode 100644 poly2d/p2d_attrib.c create mode 100644 poly2d/p2d_contains_point.c create mode 100644 poly2d/p2d_contains_poly.c create mode 100644 poly2d/p2d_copy.c create mode 100644 poly2d/p2d_free.c create mode 100644 poly2d/p2d_gnuplot.c create mode 100644 poly2d/p2d_hsort.c create mode 100644 poly2d/p2d_hsort.h create mode 100644 poly2d/p2d_make.c create mode 100644 poly2d/p2d_offset.cpp create mode 100644 poly2d/poly2d.h create mode 100755 poly2d/test/Common create mode 100755 poly2d/test/area create mode 100755 poly2d/test/hsort create mode 100755 poly2d/test/make create mode 100755 poly2d/test/offset create mode 100644 poly2d/util.h create mode 100644 poly2d/v2d_intersect.c create mode 100644 poly2d/v2d_line_distance.c diff --git a/poly2d/Makefile b/poly2d/Makefile new file mode 100644 index 0000000..d3b90ea --- /dev/null +++ b/poly2d/Makefile @@ -0,0 +1,105 @@ +# +# Makefile - Makefile of libpoly2d +# +# Written 2012 by Werner Almesberger +# Copyright 2012 by Werner Almesberger +# +# 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. +# + +PREFIX ?= /usr/local + +SHELL = /bin/bash + +LIB = libpoly2d.a +OBJS = v2d_intersect.o v2d_line_distance.o \ + p2d_area.o p2d_area_holes.o \ + p2d_attrib.o p2d_contains_point.o p2d_contains_poly.o \ + p2d_copy.o p2d_free.o p2d_gnuplot.o p2d_make.o p2d_offset.o p2d_hsort.o + +CFLAGS_WARN = -Wall -Wshadow -Wmissing-prototypes \ + -Wmissing-declarations -Wno-format-zero-length + +CFLAGS = $(CFLAGS_WARN) -g +CXXFLAGS = -Wall -frounding-math +LDFLAGS = +LDLIBS = -lm + +# ----- Verbosity control ----------------------------------------------------- + +CC_normal := $(CC) +CXX_normal := $(CXX) +AR_normal := $(AR) +DEPEND_normal := $(CPP) $(CFLAGS) -MM -MG + +CC_quiet = @echo " CC " $@ && $(CC_normal) +CXX_quiet = @echo " CXX " $@ && $(CXX_normal) +AR_quiet = @echo " AR " $@ && $(AR_normal) +DEPEND_quiet = @$(DEPEND_normal) + +ifeq ($(V),1) + CC = $(CC_normal) + CXX = $(CXX_normal) + AR = $(AR_normal) + DEPEND = $(DEPEND_normal) +else + CC = $(CC_quiet) + CXX = $(CXX_quiet) + AR = $(AR_quiet) + DEPEND = $(DEPEND_quiet) +endif + +# ----- Rules ----------------------------------------------------------------- + +.PHONY: all clean spotless +.PHONY: test tests valgrind + +all: $(LIB) + +$(LIB): $(OBJS) + $(AR) cr $@ $^ + +clean: + rm -f $(OBJS) $(OBJS:.o=.d) + +spotless: clean + rm -f $(LIB) + +# ----- Install / uninstall --------------------------------------------------- + +install: all + mkdir -p $(DESTDIR)/$(PREFIX)/bin/ + install -m 755 $(MAIN) $(DESTDIR)/$(PREFIX)/bin/ + +uninstall: + rm -f $(DESTDIR)/$(PREFIX)/bin/$(MAIN) + +# ----- Dependencies ---------------------------------------------------------- + +# compile and generate dependencies, from fped, based on +# http://scottmcpeak.com/autodepend/autodepend.html + +%.o: %.c + $(CC) -c $(CFLAGS) $*.c -o $*.o + $(DEPEND) $*.c | \ + sed -e \ + '/^\(.*:\)\? */{p;s///;s/ *\\\?$$/ /;s/ */:\n/g;H;}' \ + -e '$${g;p;}' -e d >$*.d; \ + [ "$${PIPESTATUS[*]}" = "0 0" ] || { rm -f $*.d; exit 1; } + +-include $(OBJS:.o=.d) + +# ----- Tests ----------------------------------------------------------------- + +test tests: all + LANG= sh -c \ + 'passed=0 && cd test && \ + for n in [a-z]*; do \ + [ $$n != core ] && SCRIPT=$$n . ./$$n; done; \ + echo "Passed all $$passed tests"' + +valgrind: + VALGRIND="valgrind -q" $(MAKE) tests diff --git a/poly2d/README b/poly2d/README new file mode 100644 index 0000000..a6bf8dc --- /dev/null +++ b/poly2d/README @@ -0,0 +1,54 @@ +poly2d - Yet another 2D polygon library +======================================= + +Why do we need another 2D polygon library, if there are already CGAL, +Boost, Clipper, GPC, ... ? + +All the above are either written in a weird language, are under a +non-Free license, or simply don't provide the feature set we need +here. poly2d is written in C, doesn't depend on non-standard +libraries, and is licensed under the GPL (will change to LGPL). + +poly2d serves itself liberally from code already in cameo but +provides a simpler and cleaner interface. The first objective is +to provide the tools to replace the (badly broken) area filling +operation in cameo. Later, poly2d could replace more parts of +cameo. + +poly2d puts more emphasis on simplicity than on performance. Some +functions expect clockwise closed polygons that don't self-intersect. +The table below shows the capabilities + +Open Counter-clockwise +| Concave Min. vertices +| | Self-intersect +| | | | | +Y Y Y Y 0 p2d_contains_poly(b), p2d_free, p2d_free_all, + p2d_is_closed, p2d_copy, p2d_reverse, p2d_vertices, + p2d_write_gnuplog, p2d_write_gnuplot_all +Y Y Y Y 1 p2d_read_gnuplot +- Y Y Y 0 p2d_simplify +- Y - Y 3 p2d_is_cw +- Y - - 3 p2d_contains_point, p2d_contains_poly (a), +- Y - # 3 p2d_area*, p2d_offset* + +# CGAL uses ccw, poly2d uses cw. Need to switch. + +Not yet implemented: +- p2d_simplify (low priority - the offsetting from CGAL already covers + the main use case) + +Not yet specified: +- subtraction (do we actually need it ?) + +Other: +- change the license from GPL to LGPL +- change ccw to cw +- make sure CGAL is only fed ccw ploygons and cw holes +- transform CGAL's idea of outer polygons into something we can use +- use more meaningful offset/overlap model for area fill +- check for memory leaks +- try to generate dependencies also for *.cpp + +Prerequisite: +libcgal-dev diff --git a/poly2d/cgal_helper.h b/poly2d/cgal_helper.h new file mode 100644 index 0000000..dff6a5f --- /dev/null +++ b/poly2d/cgal_helper.h @@ -0,0 +1,65 @@ +/* + * cgal_helper.h - Conversions between poly2d and CGAL + * + * Written 2012 by Werner Almesberger + * Copyright 2012 by Werner Almesberger + * + * 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. + */ + +#ifndef CGAL_HELPER_H +#define CGAL_HELPER_H + +/* + * References: + * http://www.cgal.org/Manual/latest/examples/Straight_skeleton_2/ + * Create_saop_from_polygon_with_holes_2.cpp + * http://www.cgal.org/Manual/latest/examples/Straight_skeleton_2/print.h + */ + +extern "C" { + #include "poly2d.h" +} + +#include + +#include +#include + + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; + +typedef CGAL::Polygon_2 Polygon_2; + + +static inline Polygon_2 p2d_to_P2(const struct p2d *p) +{ + const struct v2d *v; + Polygon_2 np; + + v = p->v; + do { + np.push_back(K::Point_2(v->x, v->y)); + v = v->next; + } + while (v != p->v); + return np; +} + + +static inline struct p2d *P2_to_p2d(Polygon_2 p) +{ + struct p2d *np; + + np = p2d_new(); + for (Polygon_2::Vertex_iterator vit = p.vertices_begin(); + vit != p.vertices_end(); ++vit) + p2d_append(np, v2d_new(vit->x(), vit->y())); + p2d_close(np); + return np; +} + +#endif /* !CGAL_HELPER_H */ diff --git a/poly2d/p2d_area.c b/poly2d/p2d_area.c new file mode 100644 index 0000000..36695e8 --- /dev/null +++ b/poly2d/p2d_area.c @@ -0,0 +1,44 @@ +/* + * p2d_area.c - Fill a set of nested polygons + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#include "poly2d.h" +#include "p2d_hsort.h" + + +static void recurse_area(struct p2d_hier *t, double offset, double overlap, + struct p2d ***last) +{ + const struct p2d *p, *h; + + for (p = &t->p; p; p = p->next) { + h = &p2d_to_hier(p)->holes->p; + p2d_area_holes_append(p, h, offset, overlap, last); + while (h) { + recurse_area(p2d_to_hier(h)->holes, offset, overlap, + last); + h = h->next; + } + } +} + + +struct p2d *p2d_area(const struct p2d *p, double offset, double overlap) +{ + struct p2d_hier *t; + struct p2d *res = NULL, **last = &res; + + t = p2d_hsort(p); + recurse_area(t, offset, overlap, &last); + p2d_hier_free(t); + return res; +} diff --git a/poly2d/p2d_area_holes.cpp b/poly2d/p2d_area_holes.cpp new file mode 100644 index 0000000..8153db7 --- /dev/null +++ b/poly2d/p2d_area_holes.cpp @@ -0,0 +1,101 @@ +/* + * p2d_area_holes.cpp - Fill an area with holes + * + * Written 2012 by Werner Almesberger + * Copyright 2012 by Werner Almesberger + * + * 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. + */ + +/* + * References: + * http://www.cgal.org/Manual/latest/examples/Straight_skeleton_2/ + * Create_skeleton_and_offset_polygons_with_holes_2.cpp + * http://www.cgal.org/Manual/latest/examples/Straight_skeleton_2/print.h + */ + +extern "C" { + #include + + #include "poly2d.h" +} + +#include "cgal_helper.h" + +#include +#include + +#include +#include +#include + + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; + +typedef CGAL::Polygon_2 Polygon_2; +typedef CGAL::Polygon_with_holes_2 Polygon_with_holes; + +typedef boost::shared_ptr PolygonPtr; + +typedef std::vector PolygonPtrVector; + + struct p2d *res = NULL, **last = &res, *np; + + +static void append_poly(Polygon_2 poly, struct p2d ***last) +{ + **last = P2_to_p2d(poly); + *last = &(**last)->next; +} + + +static void recurse_area(Polygon_with_holes poly, double curr_off, + double next_off, struct p2d ***last) +{ + PolygonPtrVector tmp = + CGAL::create_interior_skeleton_and_offset_polygons_with_holes_2( + curr_off, poly); + + for (PolygonPtrVector::const_iterator pit = tmp.begin(); + pit != tmp.end(); ++pit) { + append_poly((*pit)->outer_boundary(), last); + recurse_area(**pit, next_off, next_off, last); + + for (Polygon_with_holes::Hole_const_iterator + hit = (*pit)->holes_begin(); + hit != (*pit)->holes_end(); ++hit) { + append_poly(*hit, last); + } + } +} + + +extern "C" void p2d_area_holes_append(const struct p2d *p, + const struct p2d *holes, double offset, double overlap, + struct p2d ***last) +{ + const struct p2d *h; + + assert(p2d_is_closed(p)); + Polygon_with_holes poly(p2d_to_P2(p)); + + for (h = holes; h; h = h->next) { + assert(p2d_is_closed(h)); + poly.add_hole(p2d_to_P2(h)); + } + + recurse_area(poly, offset, offset-overlap, last); +} + + +extern "C" struct p2d *p2d_area_holes(const struct p2d *p, + const struct p2d *holes, double offset, double overlap) +{ + struct p2d *res = NULL, **last = &res; + + p2d_area_holes_append(p, holes, offset, overlap, &last); + return res; +} diff --git a/poly2d/p2d_attrib.c b/poly2d/p2d_attrib.c new file mode 100644 index 0000000..7cde546 --- /dev/null +++ b/poly2d/p2d_attrib.c @@ -0,0 +1,122 @@ +/* + * p2d_attrib.c - Determine various polygon attributes + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#include +#include + +#include "poly2d.h" + + +/* + * Angle in counter-clockwise direction to turn at point B when coming from A + * in order to face towards C. + */ + +static double angle_3(const struct v2d *a, const struct v2d *b, + const struct v2d *c) +{ + double ax, ay, bx, by; + double aa, bb; + double angle; + + ax = b->x-a->x; + ay = b->y-a->y; + bx = c->x-b->x; + by = c->y-b->y; + + aa = hypot(ax, ay); + bb = hypot(bx, by); + + angle = acos((ax*bx+ay*by)/aa/bb)/M_PI*180.0; + + return (ax*by-ay*bx) >= 0 ? angle : -angle; +} + +/* + * If we predominantly turn to the right, then the path must be clockwise. + */ + +int p2d_is_cw(const struct p2d *p) +{ + const struct v2d *v; + double a = 0; + + assert(p2d_vertices(p) >= 3); + assert(p2d_is_closed(p)); + assert(p2d_no_intersect(p)); + + v = p->v; + do { + a += angle_3(v, v->next, v->next->next); + v = v->next; + } + while (v != p->v); + return a < 0; +} + + +int p2d_is_closed(const struct p2d *p) +{ + return p->v == p->last || p->last->next; +} + + +/* + * Known bug: if the polygon intersects on a vertex, the intersection may + * go unnoticed. + */ + +int p2d_no_intersect(const struct p2d *p) +{ + const struct v2d *v, *u; + + v = p->v; + while (v) { + u = v->next; + if (!u || u == p->v) + return 1; + u = u->next; + if (!u || u == p->v) + return 1; + while (u && u->next) { + if (v2d_intersect(v, v->next, u, u->next, + NULL, NULL) > 0) + return 0; + u = u->next; + if (u == p->v) + break; + if (u->next == v) + break; + } + v = v->next; + if (v == p->v) + break; + } + return 1; +} + + +int p2d_vertices(const struct p2d *p) +{ + const struct v2d *v; + int n = 0; + + v = p->v; + while (v) { + n++; + v = v->next; + if (v == p->v) + break; + } + return n; +} diff --git a/poly2d/p2d_contains_point.c b/poly2d/p2d_contains_point.c new file mode 100644 index 0000000..d6bd8a3 --- /dev/null +++ b/poly2d/p2d_contains_point.c @@ -0,0 +1,56 @@ +/* + * p2d_contains_point.c - Determine whether polygon contains point/polygon + * + * Based on the algorithm by W. Randolph Franklin + * http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + * which is distributed under the following license, similar to the 3-clause + * BSD license: + * + * Copyright (c) 1970-2003, Wm. Randolph Franklin + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimers. + * 2. Redistributions in binary form must reproduce the above copyright + * notice in the documentation and/or other materials provided with the + * distribution. + * 3. The name of W. Randolph Franklin may not be used to endorse or promote + * products derived from this Software without specific prior written + * permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + + +#include "poly2d.h" + + +int p2d_contains_point(const struct p2d *p, const struct v2d *v) +{ + const struct v2d *j, *i; + int in = 0; + + j = p->v; + do { + i = j->next; + if (((i->y > v->y) != (j->y > v->y)) && + (v->x < (j->x-i->x)*(v->y-i->y)/ + (j->y-i->y)+i->x)) + in = !in; + j = i; + } + while (j != p->v); + return in; +} diff --git a/poly2d/p2d_contains_poly.c b/poly2d/p2d_contains_poly.c new file mode 100644 index 0000000..0cf2f82 --- /dev/null +++ b/poly2d/p2d_contains_poly.c @@ -0,0 +1,38 @@ +/* + * p2d_contains_poly.c - Determine whether polygon contains other polygon + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#include + +#include "poly2d.h" + + +int p2d_contains_poly(const struct p2d *a, const struct p2d *b) +{ + const struct v2d *v; + int in = 0, out = 0; + + assert(p2d_is_closed(a)); + v = b->v; + while (v) { + if (p2d_contains_point(a, v)) + in++; + else + out++; + v = v->next; + if (v == b->v) + break; + } + if (in && out) + return -1; + return !out; +} diff --git a/poly2d/p2d_copy.c b/poly2d/p2d_copy.c new file mode 100644 index 0000000..875f24c --- /dev/null +++ b/poly2d/p2d_copy.c @@ -0,0 +1,95 @@ +/* + * p2d_copy.c - Copy a polygon, with or without reversing it + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#include + +#include "util.h" +#include "poly2d.h" + + +struct p2d *p2d_copy(const struct p2d *p) +{ + struct p2d *np; + const struct v2d *v; + struct v2d *nv, **last; + + np = alloc_type(struct p2d); + np->v = NULL; + np->last = NULL; + np->next = NULL; + + last = &np->v; + v = p->v; + while (v) { + nv = alloc_type(struct v2d); + nv->x = v->x; + nv->y = v->y; + nv->next = NULL; + *last = nv; + last = &nv->next; + + np->last = nv; + + v = v->next; + if (v == p->v) + break; + } + if (v) + *last = np->v; + return np; +} + + +struct p2d *p2d_copy_all(const struct p2d *p) +{ + struct p2d *res = NULL, **last = &res; + + while (p) { + *last = p2d_copy(p); + last = &(*last)->next; + p = p->next; + } + return res; +} + + +struct p2d *p2d_reverse(const struct p2d *p) +{ + struct p2d *np; + const struct v2d *v; + struct v2d *nv; + + np = alloc_type(struct p2d); + np->v = NULL; + np->last = NULL; + np->next = NULL; + + v = p->v; + while (v) { + nv = alloc_type(struct v2d); + nv->x = v->x; + nv->y = v->y; + nv->next = np->v; + np->v = nv; + + if (!np->last) + np->last= nv; + + v = v->next; + if (v == p->v) + break; + } + if (v && np->last) + np->last->next = np->v; + return np; +} diff --git a/poly2d/p2d_free.c b/poly2d/p2d_free.c new file mode 100644 index 0000000..b194919 --- /dev/null +++ b/poly2d/p2d_free.c @@ -0,0 +1,44 @@ +/* + * p2d_free.c - Deallocate polygons + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#include + +#include "poly2d.h" + + +void p2d_free(struct p2d *p) +{ + struct v2d *v, *next; + + v = p->v; + while (v) { + next = v->next; + free(v); + v = next; + if (v == p->v) + break; + } + free(p); +} + + +void p2d_free_all(struct p2d *p) +{ + struct p2d *next; + + while (p) { + next = p->next; + p2d_free(p); + p = next; + } +} diff --git a/poly2d/p2d_gnuplot.c b/poly2d/p2d_gnuplot.c new file mode 100644 index 0000000..d426621 --- /dev/null +++ b/poly2d/p2d_gnuplot.c @@ -0,0 +1,97 @@ +/* + * p2d_gnuplot.c - File I/O in gnuplot format + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#include +#include +#include +#include + +#include "poly2d.h" + + +#define EPSILON 1e-6 + + +static void check_closed(struct p2d *p) +{ + if (!p) + return; + if (hypot(p->v->x-p->last->x, p->v->y-p->last->y) > EPSILON) + return; + free(p->last); + p->last = p->v; +} + + +struct p2d *p2d_read_gnuplot(FILE *file) +{ + struct p2d *res = NULL, **last = &res, *p = NULL; + char buf[1024]; + double x, y; + int n; + + while (fgets(buf, sizeof(buf), file)) { + if (*buf == '#') + continue; + n = sscanf(buf, "%lf %lf\n", &x, &y); + switch (n) { + case -1: + check_closed(p); + p = NULL; + break; + case 2: + break; + default: + errno = EINVAL; + return NULL; + } + if (!p) { + p = p2d_new(); + *last = p; + last = &p->next; + } + p2d_append(p, v2d_new(x, y)); + } + check_closed(p); + return res; +} + + +int p2d_write_gnuplot(FILE *file, const struct p2d *p) +{ + const struct v2d *v; + + v = p->v; + while (v) { + if (fprintf(file, "%g %g\n", v->x, v->y) < 0) + return 0; + v = v->next; + if (v == p->v) { + if (fprintf(file, "%g %g\n", v->x, v->y) < 0) + return 0; + break; + } + } + return fprintf(file, "\n") >= 0; +} + + +int p2d_write_gnuplot_all(FILE *file, const struct p2d *p) +{ + while (p) { + if (!p2d_write_gnuplot(file, p)) + return 0; + p = p->next; + } + return 1; +} diff --git a/poly2d/p2d_hsort.c b/poly2d/p2d_hsort.c new file mode 100644 index 0000000..e4ee6e6 --- /dev/null +++ b/poly2d/p2d_hsort.c @@ -0,0 +1,107 @@ +/* + * p2d_hsort.c - Hierarchical polygon sort + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#include "util.h" +#include "poly2d.h" +#include "p2d_hsort.h" + + +static struct p2d_hier *recurse_hsort(struct p2d *p) +{ + struct p2d *sub = NULL, *sub2 = NULL, **last = ⊂ + struct p2d **a, *b, **next; + struct p2d_hier *res = NULL, *t; + struct p2d **res_last = (struct p2d **) &res; + + /* + * Move all polygons that are inside some other polygon to "sub". + */ + for (a = &p; *a; a = next) { + next = &(*a)->next; + for (b = p; b; b = b->next) + if (*a != b && p2d_contains_poly(b, *a)) { + *last = *a; + last = &(*last)->next; + *a = *next; + next = a; + *last = NULL; + break; + } + } + + while (p) { + /* + * Begin transplanting "p" into t->p. + */ + t = alloc_type(struct p2d_hier); + t->p = *p; + + /* + * Move all polygons inside the current one from "sub" to + * "sub2". (Direct and indirect subordinates.) + */ + sub2 = NULL; + last = &sub2; + for (a = ⊂ *a; a = next) { + next = &(*a)->next; + if (p2d_contains_poly(p, *a)) { + *last = *a; + last = &(*last)->next; + *a = *next; + next = a; + *last = NULL; + } + } + + /* + * Sort the subordinates. + */ + t->holes = recurse_hsort(sub2); + + /* + * End transplanting "p" into t->p. + */ + free(p); + p = t->p.next; + + /* + * Append "t" to "res". + */ + *res_last = &t->p; + res_last = &t->p.next; + t->p.next = NULL; + + } + return res; +} + + +struct p2d_hier *p2d_hsort(const struct p2d *p) +{ + return recurse_hsort(p2d_copy_all(p)); +} + + +void p2d_hier_free(struct p2d_hier *t) +{ + struct p2d_hier *next; + struct p2d *p; + + while (t) { + p2d_hier_free(t->holes); + p = &t->p; + next = p2d_to_hier(p->next); + p2d_free_all(p); + t = next; + } +} diff --git a/poly2d/p2d_hsort.h b/poly2d/p2d_hsort.h new file mode 100644 index 0000000..e789656 --- /dev/null +++ b/poly2d/p2d_hsort.h @@ -0,0 +1,32 @@ +/* + * p2d_hsort.h - Hierarchical polygon sort + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#ifndef P2D_HSORT_H +#define P2D_HSORT_H + +#include "poly2d.h" + + +#define p2d_to_hier(p) ((struct p2d_hier *) (p)) + + +struct p2d_hier { + struct p2d p; /* "next" link for siblings */ + struct p2d_hier *holes; /* children */ +}; + + +struct p2d_hier *p2d_hsort(const struct p2d *p); +void p2d_hier_free(struct p2d_hier *t); + +#endif /* !P2D_HSORT_H */ diff --git a/poly2d/p2d_make.c b/poly2d/p2d_make.c new file mode 100644 index 0000000..73a6256 --- /dev/null +++ b/poly2d/p2d_make.c @@ -0,0 +1,73 @@ +/* + * p2d_make.c - Polygon creation + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#include + +#include "util.h" +#include "poly2d.h" + + +struct p2d *p2d_new(void) +{ + struct p2d *np; + + np = alloc_type(struct p2d); + np->v = NULL; + np->last = NULL; + np->next = NULL; + return np; +} + + +struct v2d *v2d_new(double x, double y) +{ + struct v2d *nv; + + nv = alloc_type(struct v2d); + nv->x = x; + nv->y = y; + nv->next = NULL; + return nv; +} + + +void p2d_append(struct p2d *p, struct v2d *v) +{ + if (p->last && p->last->next) + v->next = p->v; + if (p->last) + p->last->next = v; + else + p->v = v; + p->last = v; +} + + +void p2d_prepend(struct p2d *p, struct v2d *v) +{ + v->next = p->v; + p->v = v; + if (p->last) { + if (p->last->next) + p->last->next = v; + } else { + p->last = v; + } +} + + +void p2d_close(struct p2d *p) +{ + assert(!p->v || !p->last->next); + p->last->next = p->v; +} diff --git a/poly2d/p2d_offset.cpp b/poly2d/p2d_offset.cpp new file mode 100644 index 0000000..349b0a8 --- /dev/null +++ b/poly2d/p2d_offset.cpp @@ -0,0 +1,78 @@ +/* + * p2d_offset.cpp - Simple offsetting (without dogbones) + * + * Written 2012 by Werner Almesberger + * Copyright 2012 by Werner Almesberger + * + * 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. + */ + +/* + * References: + * http://www.cgal.org/Manual/latest/examples/Straight_skeleton_2/ + * Create_saop_from_polygon_with_holes_2.cpp + * http://www.cgal.org/Manual/latest/examples/Straight_skeleton_2/print.h + */ + +extern "C" { + #include + + #include "poly2d.h" +} + +#include "cgal_helper.h" + +#include +#include + +#include +#include +#include + + +typedef CGAL::Exact_predicates_inexact_constructions_kernel K; + +typedef CGAL::Polygon_2 Polygon_2; +typedef CGAL::Polygon_with_holes_2 Polygon_with_holes; + +typedef boost::shared_ptr PolygonPtr; + +typedef std::vector PolygonPtrVector; + + +extern "C" struct p2d *p2d_offset_holes(const struct p2d *p, + const struct p2d *holes, double off) +{ + const struct p2d *h; + struct p2d *res = NULL, **last = &res; + + assert(p2d_is_closed(p)); + Polygon_with_holes poly(p2d_to_P2(p)); + + for (h = holes; h; h = h->next) { + assert(p2d_is_closed(h)); + poly.add_hole(p2d_to_P2(h)); + } + + PolygonPtrVector tmp = off > 0 ? + CGAL::create_exterior_skeleton_and_offset_polygons_2(off, + poly.outer_boundary()) : + CGAL::create_interior_skeleton_and_offset_polygons_2(-off, poly); + + for (PolygonPtrVector::const_iterator pit = tmp.begin(); + pit != tmp.end(); ++pit) { + *last = P2_to_p2d(**pit); + last = &(*last)->next; + } + + return res; +} + + +extern "C" struct p2d *p2d_offset(const struct p2d *p, double off) +{ + return p2d_offset_holes(p, NULL, off); +} diff --git a/poly2d/poly2d.h b/poly2d/poly2d.h new file mode 100644 index 0000000..72281ff --- /dev/null +++ b/poly2d/poly2d.h @@ -0,0 +1,140 @@ +/* + * poly2d.h - The public face of the 2D Polygon library + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#ifndef POLY2D_H +#define POLY2D_H + +#include + + +struct v2d { + double x, y; + struct v2d *next; /* may end in NULL or may be cyclic */ +}; + + +struct p2d { + struct v2d *v; /* vertices */ + struct v2d *last; /* last vertex or vertex preceding first */ + struct p2d *next; +}; + + +/* + * Polygon creation + */ + +struct p2d *p2d_new(void); +struct v2d *v2d_new(double x, double y); +void p2d_append(struct p2d *p, struct v2d *v); +void p2d_prepend(struct p2d *p, struct v2d *v); +void p2d_close(struct p2d *p); + +/* + * Intersect line from A0 to A1 with line from B0 to B1. + * + * Returns: + * 0 if the lines are parallel, + * 1 if the lines intersect between A0-A1 and B0-B1, + * -1 if the lines intersect outside A0-A1 or B0-B1. + * + * If v2d_intersect returns non-zero, the intersection P is at + * + * P = A0+(A1-A0)*na = B0+(B1-B0)*nb + */ + +int v2d_intersect(const struct v2d *a0, const struct v2d *a1, + const struct v2d *b0, const struct v2d *b1, + double *na, double *nb); + +/* + * Calculate the distance between point P and the line from A to B. + * The result is negative if P is on the "right" side of A->B. + */ + +double v2d_line_distance(const struct v2d *a, const struct v2d *b, + const struct v2d *p); + +/* + * Duplicate a polygon + */ + +struct p2d *p2d_copy(const struct p2d *p); +struct p2d *p2d_copy_all(const struct p2d *p); + +/* + * Change a polygon from clockwise to counter-clockwise and vice versa. + */ + +struct p2d *p2d_reverse(const struct p2d *p); + +/* + * p2d_is_cw determine whether a polygon is clockwise. + * p2d_is_closed determines whether a polygon is closed. + * p2d_no_intersect determines whether a polygon does't self-intersect. + * p2d_vertices counts the number of vertices in a polygon. + */ + +int p2d_is_cw(const struct p2d *p); +int p2d_is_closed(const struct p2d *p); +int p2d_no_intersect(const struct p2d *p); +int p2d_vertices(const struct p2d *p); + +/* + * Convert a possibly self-intersecting polygon into one or more simple + * polygons. [1] + * + * http://en.wikipedia.org/wiki/Simple_polygon + */ + +struct p2d *p2d_simplify(const struct p2d *p); + +/* + * p2d_free deallocates a single polygon and its vertices. + * p2d_free_all deallocates all polygons in a list. + */ + +void p2d_free(struct p2d *p); +void p2d_free_all(struct p2d *p); + +/* + * Returns non-zero if the point is inside or on the simple polygon. + */ + +int p2d_contains_point(const struct p2d *p, const struct v2d *v); + +/* + * Returns: + * 0 if polygon "b" is outside of polygon "a" + * 1 if polygon "b" is inside of polygon "a" + * -1 if the two polygons intersect + */ + +int p2d_contains_poly(const struct p2d *a, const struct p2d *b); + +struct p2d *p2d_offset_holes(const struct p2d *p, const struct p2d *holes, + double off); +struct p2d *p2d_offset(const struct p2d *p, double off); + +void p2d_area_holes_append(const struct p2d *p, + const struct p2d *holes, double offset, double overlap, + struct p2d ***last); +struct p2d *p2d_area_holes(const struct p2d *p, const struct p2d *holes, + double offset, double overlap); +struct p2d *p2d_area(const struct p2d *p, double offset, double overlap); + +struct p2d *p2d_read_gnuplot(FILE *file); +int p2d_write_gnuplot(FILE *file, const struct p2d *p); +int p2d_write_gnuplot_all(FILE *file, const struct p2d *p); + +#endif /* !POLY2D_H */ diff --git a/poly2d/test/Common b/poly2d/test/Common new file mode 100755 index 0000000..2787aaa --- /dev/null +++ b/poly2d/test/Common @@ -0,0 +1,99 @@ +#!/bin/sh +# +# Common - Elements shared by all regression tests for poly2d +# +# Written 2010, 2011 by Werner Almesberger +# Copyright 2010, 2011 Werner Almesberger +# +# 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. +# + + +compile_and_run() +{ + LIBS="-lpoly2d -lCGAL -lCGAL_Core -lboost_thread" + LIBS="$LIBS -lstdc++ -lmpfr -lgmp -lm" + + cat <_.c +#include +#include "p2d_hsort.h" + + +static void recurse_hier(const struct p2d_hier *h, int level) +{ + const struct v2d *v; + + while (h) { + printf("%*s", level*2, ""); + v = h->p.v; + while (v) { + printf("%s%g %g", v == h->p.v ? "" : " ", v->x, v->y); + v = v->next; + if (v == h->p.v) + break; + } + printf("\n"); + recurse_hier(h->holes, level+1); + h = p2d_to_hier(h->p.next); + } +} + + +static void __attribute__((unused)) print_hier(const struct p2d_hier *h) +{ + recurse_hier(h, 0); +} + + +int main(void) +{ +`cat _in` + return 0; +} +EOF + gcc -Wall -Werror -g -I.. _.c -L.. $LIBS || return + $VALGRIND ./a.out +} + + +tst() +{ + echo -n "$1: " 1>&2 + shift + cat >_in + compile_and_run "$@" >_out 2>&1 || { + echo FAILED "($SCRIPT)" 1>&2 + cat _out + exit 1 + } +} + + +tst_fail() +{ + echo -n "$1: " 1>&2 + shift + cat >_in + compile_and_run "$@" >_out 2>&1 && { + echo FAILED "($SCRIPT)" 1>&2 + cat _out + exit 1 + } + rm -f _in _.c a.out +} + + +expect() +{ + diff -u - "$@" _out >_diff || { + echo FAILED "($SCRIPT)" 1>&2 + cat _diff 1>&2 + exit 1 + } + echo PASSED 1>&2 + rm -f _in _out _diff _.c a.out + passed=`expr ${passed:-0} + 1` +} diff --git a/poly2d/test/area b/poly2d/test/area new file mode 100755 index 0000000..2517793 --- /dev/null +++ b/poly2d/test/area @@ -0,0 +1,103 @@ +#!/bin/sh +. ./Common + +############################################################################### + +tst "area without holes, constant offset" <next = p2d_new(); +p2d_append(p, v2d_new(2, 2)); +p2d_append(p, v2d_new(2, 8)); +p2d_append(p, v2d_new(18, 8)); +p2d_append(p, v2d_new(18, 2)); +p2d_close(p); + +q = p2d_area(pl, 0.7, 0); +p2d_write_gnuplot_all(stdout, q); +EOF + +expect <next = p2d_new(); +p2d_append(p, v2d_new(2, 2)); +p2d_append(p, v2d_new(2, 8)); +p2d_append(p, v2d_new(18, 8)); +p2d_append(p, v2d_new(18, 2)); +p2d_close(p); + +print_hier(p2d_hsort(pl)); +EOF + +expect <next = p2d_new(); +p2d_append(p, v2d_new(2, 2)); +p2d_append(p, v2d_new(4, 2)); +p2d_append(p, v2d_new(4, 4)); +p2d_close(p); + +p = p->next = p2d_new(); +p2d_append(p, v2d_new(6, 2)); +p2d_append(p, v2d_new(8, 2)); +p2d_append(p, v2d_new(8, 8)); +p2d_close(p); + +print_hier(p2d_hsort(pl)); +EOF + +expect <next = p2d_new(); +p2d_append(p, v2d_new(2, 2)); +p2d_append(p, v2d_new(8, 2)); +p2d_append(p, v2d_new(8, 8)); +p2d_close(p); + +p = p->next = p2d_new(); +p2d_append(p, v2d_new(3, 3)); +p2d_append(p, v2d_new(7, 3)); +p2d_append(p, v2d_new(7, 7)); +p2d_close(p); + +print_hier(p2d_hsort(pl)); +EOF + +expect <next = q; +p2d_append(q, v2d_new(3, 15)); +p2d_append(q, v2d_new(4, 16)); +p2d_write_gnuplot_all(stdout, p); +EOF + +expect <next)); +EOF + +expect < +#include + + +#define alloc_size(s) \ + ({ void *alloc_size_tmp = malloc(s); \ + if (!alloc_size_tmp) \ + abort(); \ + alloc_size_tmp; }) + +#define alloc_type(t) ((t *) alloc_size(sizeof(t))) + +#endif /* !UTIL_H */ diff --git a/poly2d/v2d_intersect.c b/poly2d/v2d_intersect.c new file mode 100644 index 0000000..776225d --- /dev/null +++ b/poly2d/v2d_intersect.c @@ -0,0 +1,69 @@ +/* + * v2d_intersect.c - Intersect two lines + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#include + +#include "poly2d.h" + + +#define EPSILON 1e-6 + + +/* + * Solve + * + * ax+by = e + * cx+dy = f + * + * with Cramer's rule: + * http://en.wikipedia.org/wiki/Cramer's_rule + */ + +static int cramer2(double a, double b, double c, double d, double e, double f, + double *x, double *y) +{ + double det; + + det = a*d-b*c; + if (fabs(det) < EPSILON) + return 0; + *x = (e*d-b*f)/det; + *y = (a*f-e*c)/det; + return 1; +} + + +int v2d_intersect(const struct v2d *a0, const struct v2d *a1, + const struct v2d *b0, const struct v2d *b1, + double *na, double *nb) +{ + double ax, ay, bx, by, dx, dy; + double a, b; + + ax = a1->x-a0->x; + ay = a1->y-a0->y; + + bx = b1->x-b0->x; + by = b1->y-b0->y; + + dx = b0->x-a0->x; + dy = b0->y-a0->y; + + if (!cramer2(ax, -bx, ay, -by, dx, dy, &a, &b)) + return 0; + if (na) + *na = a; + if (nb) + *nb = b; + return a >= 0 && a <= 1 && b >= 0 && b <= 1 ? 1 : -1; +} diff --git a/poly2d/v2d_line_distance.c b/poly2d/v2d_line_distance.c new file mode 100644 index 0000000..1aaf427 --- /dev/null +++ b/poly2d/v2d_line_distance.c @@ -0,0 +1,34 @@ +/* + * v2d_line_distance.c - Calculate the distance between a point and a line + * + * Written 2012 by Werner Almesberger + * Copyright 2012 Werner Almesberger + * + * 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. + */ + + +#include + +#include "poly2d.h" + + +/* + * We use formula (14) from + * http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html + * but keep the sign. + */ + +double v2d_line_distance(const struct v2d *a, const struct v2d *b, + const struct v2d *p) +{ + double ax, ay; + + ax = b->x-a->x; + ay = b->y-a->y; + + return (ax*(a->y-p->y)-ay*(a->x-p->x))/hypot(ax, ay); +}