Loading...
Searching...
No Matches
TriangularDecomposition.cpp
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2012, Rice University
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 * * Neither the name of the Rice University nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 *********************************************************************/
34
35/* Author: Matt Maly */
36
37#include "ompl/extensions/triangle/TriangularDecomposition.h"
38#include "ompl/base/State.h"
39#include "ompl/base/StateSampler.h"
40#include "ompl/base/spaces/RealVectorBounds.h"
41#include "ompl/control/planners/syclop/Decomposition.h"
42#include "ompl/control/planners/syclop/GridDecomposition.h"
43#include "ompl/util/RandomNumbers.h"
44#include "ompl/util/Hash.h"
45#include "ompl/util/String.h"
46#include <ostream>
47#include <utility>
48#include <vector>
49#include <set>
50#include <string>
51#include <unordered_map>
52#include <cstdlib>
53
54extern "C"
55{
56#define REAL double
57#define VOID void
58#define ANSI_DECLARATORS
59#include <triangle.h>
60}
61
62namespace std
63{
64 template <>
65 struct hash<ompl::control::TriangularDecomposition::Vertex>
66 {
67 size_t operator()(const ompl::control::TriangularDecomposition::Vertex &v) const
68 {
69 std::size_t hash = std::hash<double>()(v.x);
70 ompl::hash_combine(hash, v.y);
71 return hash;
72 }
73 };
74} // namespace std
75
77 std::vector<Polygon> holes,
78 std::vector<Polygon> intRegs)
79 : Decomposition(2, bounds)
80 , holes_(std::move(holes))
81 , intRegs_(std::move(intRegs))
82 , triAreaPct_(0.005)
83 , locator(64, this)
84{
85 // \todo: Ensure that no two holes overlap and no two regions of interest overlap.
86 // Report an error otherwise.
87}
88
89ompl::control::TriangularDecomposition::~TriangularDecomposition() = default;
90
91void ompl::control::TriangularDecomposition::setup()
92{
93 int numTriangles = createTriangles();
94 OMPL_INFORM("Created %u triangles", numTriangles);
95 buildLocatorGrid();
96}
97
98void ompl::control::TriangularDecomposition::addHole(const Polygon &hole)
99{
100 holes_.push_back(hole);
101}
102
103void ompl::control::TriangularDecomposition::addRegionOfInterest(const Polygon &region)
104{
105 intRegs_.push_back(region);
106}
107
108int ompl::control::TriangularDecomposition::getNumHoles() const
109{
110 return holes_.size();
111}
112
113int ompl::control::TriangularDecomposition::getNumRegionsOfInterest() const
114{
115 return intRegs_.size();
116}
117
118const std::vector<ompl::control::TriangularDecomposition::Polygon> &
119ompl::control::TriangularDecomposition::getHoles() const
120{
121 return holes_;
122}
123
124const std::vector<ompl::control::TriangularDecomposition::Polygon> &
125ompl::control::TriangularDecomposition::getAreasOfInterest() const
126{
127 return intRegs_;
128}
129
131{
132 return intRegInfo_[triID];
133}
134
136{
137 Triangle &tri = triangles_[triID];
138 if (tri.volume < 0)
139 {
140 /* This triangle area formula relies on the vertices being
141 * stored in counter-clockwise order. */
142 tri.volume = 0.5 * ((tri.pts[0].x - tri.pts[2].x) * (tri.pts[1].y - tri.pts[0].y) -
143 (tri.pts[0].x - tri.pts[1].x) * (tri.pts[2].y - tri.pts[0].y));
144 }
145 return tri.volume;
146}
147
148void ompl::control::TriangularDecomposition::getNeighbors(int triID, std::vector<int> &neighbors) const
149{
150 neighbors = triangles_[triID].neighbors;
151}
152
154{
155 std::vector<double> coord(2);
156 project(s, coord);
157 const std::vector<int> &gridTriangles = locator.locateTriangles(s);
158 int triangle = -1;
159 for (int triID : gridTriangles)
160 {
161 if (triContains(triangles_[triID], coord))
162 {
163 if (triangle >= 0)
164 OMPL_WARN("Decomposition space coordinate (%f,%f) is somehow contained by multiple triangles. \
165 This can happen if the coordinate is located exactly on a triangle segment.\n",
166 coord[0], coord[1]);
167 triangle = triID;
168 }
169 }
170 return triangle;
171}
172
173void ompl::control::TriangularDecomposition::sampleFromRegion(int triID, RNG &rng, std::vector<double> &coord) const
174{
175 /* Uniformly sample a point from within a triangle, using the approach discussed in
176 * http://math.stackexchange.com/questions/18686/uniform-random-point-in-triangle */
177 const Triangle &tri = triangles_[triID];
178 coord.resize(2);
179 const double r1 = sqrt(rng.uniform01());
180 const double r2 = rng.uniform01();
181 coord[0] = (1 - r1) * tri.pts[0].x + r1 * (1 - r2) * tri.pts[1].x + r1 * r2 * tri.pts[2].x;
182 coord[1] = (1 - r1) * tri.pts[0].y + r1 * (1 - r2) * tri.pts[1].y + r1 * r2 * tri.pts[2].y;
183}
184
185void ompl::control::TriangularDecomposition::print(std::ostream &out) const
186{
187 /* For each triangle, print a line of the form
188 N x1 y1 x2 y2 x3 y3 L1 L2 ... -1
189 N is the ID of the triangle
190 L1 L2 ... is the sequence of all regions of interest to which
191 this triangle belongs. */
192 for (unsigned int i = 0; i < triangles_.size(); ++i)
193 {
194 out << i << " ";
195 const Triangle &tri = triangles_[i];
196 for (int v = 0; v < 3; ++v)
197 out << tri.pts[v].x << " " << tri.pts[v].y << " ";
198 if (intRegInfo_[i] > -1)
199 out << intRegInfo_[i] << " ";
200 out << "-1" << std::endl;
201 }
202}
203
204ompl::control::TriangularDecomposition::Vertex::Vertex(double vx, double vy) : x(vx), y(vy)
205{
206}
207
208bool ompl::control::TriangularDecomposition::Vertex::operator==(const Vertex &v) const
209{
210 return x == v.x && y == v.y;
211}
212
214{
215 /* create a conforming Delaunay triangulation
216 where each triangle takes up no more than triAreaPct_ percentage of
217 the total area of the decomposition space */
218 const base::RealVectorBounds &bounds = getBounds();
219 const double maxTriangleArea = bounds.getVolume() * triAreaPct_;
220 std::string triswitches = "pDznQA -a" + ompl::toString(maxTriangleArea);
221 struct triangulateio in;
222
223 /* Some vertices may be duplicates, such as when an obstacle has a vertex equivalent
224 to one at the corner of the bounding box of the decomposition.
225 libtriangle does not perform correctly if points are duplicated in the pointlist;
226 so, to prevent duplicate vertices, we use a hashmap from Vertex to the index for
227 that Vertex in the pointlist. We'll fill the map with Vertex objects,
228 and then we'll actually add them to the pointlist. */
229 std::unordered_map<Vertex, int> pointIndex;
230
231 // First, add the points from the bounding box
232 pointIndex[Vertex(bounds.low[0], bounds.low[1])] = 0;
233 pointIndex[Vertex(bounds.high[0], bounds.low[1])] = 1;
234 pointIndex[Vertex(bounds.high[0], bounds.high[1])] = 2;
235 pointIndex[Vertex(bounds.low[0], bounds.high[1])] = 3;
236
237 /* in.numberofpoints equals the total number of unique vertices.
238 in.numberofsegments is slightly different: it equals the total number of given vertices.
239 They will both be at least 4, due to the bounding box. */
240 in.numberofpoints = 4;
241 in.numberofsegments = 4;
242
243 // Run through obstacle vertices in holes_, and tally point and segment counters
244 for (auto &p : holes_)
245 {
246 for (auto &pt : p.pts)
247 {
248 ++in.numberofsegments;
249 /* Only assign an index to this vertex (and tally the point counter)
250 if this is a newly discovered vertex. */
251 if (pointIndex.find(pt) == pointIndex.end())
252 pointIndex[pt] = in.numberofpoints++;
253 }
254 }
255
256 /* Run through region-of-interest vertices in intRegs_, and tally point and segment counters.
257 Here we're following the same logic as above with holes_. */
258 for (auto &p : intRegs_)
259 {
260 for (auto &pt : p.pts)
261 {
262 ++in.numberofsegments;
263 if (pointIndex.find(pt) == pointIndex.end())
264 pointIndex[pt] = in.numberofpoints++;
265 }
266 }
267
268 // in.pointlist is a sequence (x1 y1 x2 y2 ...) of ordered pairs of points
269 in.pointlist = (REAL *)malloc(2 * in.numberofpoints * sizeof(REAL));
270
271 // add unique vertices from our map, using their assigned indices
272 for (const auto &i : pointIndex)
273 {
274 const Vertex &v = i.first;
275 int index = i.second;
276 in.pointlist[2 * index] = v.x;
277 in.pointlist[2 * index + 1] = v.y;
278 }
279
280 /* in.segmentlist is a sequence (a1 b1 a2 b2 ...) of pairs of indices into
281 in.pointlist to designate a segment between the respective points. */
282 in.segmentlist = (int *)malloc(2 * in.numberofsegments * sizeof(int));
283
284 // First, add segments for the bounding box
285 for (int i = 0; i < 4; ++i)
286 {
287 in.segmentlist[2 * i] = i;
288 in.segmentlist[2 * i + 1] = (i + 1) % 4;
289 }
290
291 /* segIndex keeps track of where we are in in.segmentlist,
292 as we fill it from multiple sources of data. */
293 int segIndex = 4;
294
295 /* Now, add segments for each obstacle in holes_, using our index map
296 from before to get the pointlist index for each vertex */
297 for (auto &p : holes_)
298 {
299 for (unsigned int j = 0; j < p.pts.size(); ++j)
300 {
301 in.segmentlist[2 * segIndex] = pointIndex[p.pts[j]];
302 in.segmentlist[2 * segIndex + 1] = pointIndex[p.pts[(j + 1) % p.pts.size()]];
303 ++segIndex;
304 }
305 }
306
307 /* Now, add segments for each region-of-interest in intRegs_,
308 using the same logic as before. */
309 for (auto &p : intRegs_)
310 {
311 for (unsigned int j = 0; j < p.pts.size(); ++j)
312 {
313 in.segmentlist[2 * segIndex] = pointIndex[p.pts[j]];
314 in.segmentlist[2 * segIndex + 1] = pointIndex[p.pts[(j + 1) % p.pts.size()]];
315 ++segIndex;
316 }
317 }
318
319 /* libtriangle needs an interior point for each obstacle in holes_.
320 For now, we'll assume that each obstacle is convex, and we'll
321 generate the interior points ourselves using getPointInPoly. */
322 in.numberofholes = holes_.size();
323 in.holelist = nullptr;
324 if (in.numberofholes > 0)
325 {
326 /* holelist is a sequence (x1 y1 x2 y2 ...) of ordered pairs of interior points.
327 The i^th ordered pair is an interior point of the i^th obstacle in holes_. */
328 in.holelist = (REAL *)malloc(2 * in.numberofholes * sizeof(REAL));
329 for (int i = 0; i < in.numberofholes; ++i)
330 {
331 Vertex v = getPointInPoly(holes_[i]);
332 in.holelist[2 * i] = v.x;
333 in.holelist[2 * i + 1] = v.y;
334 }
335 }
336
337 /* Similar to above, libtriangle needs an interior point for each
338 region-of-interest in intRegs_. We follow the same assumption as before
339 that each region-of-interest is convex. */
340 in.numberofregions = intRegs_.size();
341 in.regionlist = nullptr;
342 if (in.numberofregions > 0)
343 {
344 /* regionlist is a sequence (x1 y1 L1 -1 x2 y2 L2 -1 ...) of ordered triples,
345 each ended with -1. The i^th ordered pair (xi,yi,Li) is an interior point
346 of the i^th region-of-interest in intRegs_, which is assigned the integer
347 label Li. */
348 in.regionlist = (REAL *)malloc(4 * in.numberofregions * sizeof(REAL));
349 for (unsigned int i = 0; i < intRegs_.size(); ++i)
350 {
351 Vertex v = getPointInPoly(intRegs_[i]);
352 in.regionlist[4 * i] = v.x;
353 in.regionlist[4 * i + 1] = v.y;
354 // triangles outside of interesting regions get assigned an attribute of zero by default
355 // so let's number our attributes from 1 to numProps, then shift it down by 1 when we're done
356 in.regionlist[4 * i + 2] = (REAL)(i + 1);
357 in.regionlist[4 * i + 3] = -1.;
358 }
359 }
360
361 // mark remaining input fields as unused
362 in.segmentmarkerlist = (int *)nullptr;
363 in.numberofpointattributes = 0;
364 in.pointattributelist = nullptr;
365 in.pointmarkerlist = nullptr;
366
367 // initialize output libtriangle structure, which will hold the results of the triangulation
368 struct triangulateio out;
369 out.pointlist = (REAL *)nullptr;
370 out.pointattributelist = (REAL *)nullptr;
371 out.pointmarkerlist = (int *)nullptr;
372 out.trianglelist = (int *)nullptr;
373 out.triangleattributelist = (REAL *)nullptr;
374 out.neighborlist = (int *)nullptr;
375 out.segmentlist = (int *)nullptr;
376 out.segmentmarkerlist = (int *)nullptr;
377 out.edgelist = (int *)nullptr;
378 out.edgemarkerlist = (int *)nullptr;
379 out.pointlist = (REAL *)nullptr;
380 out.pointattributelist = (REAL *)nullptr;
381 out.trianglelist = (int *)nullptr;
382 out.triangleattributelist = (REAL *)nullptr;
383
384 // call the triangulation routine
385 triangulate(const_cast<char *>(triswitches.c_str()), &in, &out, nullptr);
386
387 triangles_.resize(out.numberoftriangles);
388 intRegInfo_.resize(out.numberoftriangles);
389 for (int i = 0; i < out.numberoftriangles; ++i)
390 {
391 Triangle &t = triangles_[i];
392 for (int j = 0; j < 3; ++j)
393 {
394 t.pts[j].x = out.pointlist[2 * out.trianglelist[3 * i + j]];
395 t.pts[j].y = out.pointlist[2 * out.trianglelist[3 * i + j] + 1];
396 if (out.neighborlist[3 * i + j] >= 0)
397 t.neighbors.push_back(out.neighborlist[3 * i + j]);
398 }
399 t.volume = -1.;
400
401 if (in.numberofregions > 0)
402 {
403 auto attribute = (int)out.triangleattributelist[i];
404 /* Shift the region-of-interest ID's down to start from zero. */
405 intRegInfo_[i] = (attribute > 0 ? attribute - 1 : -1);
406 }
407 }
408
409 trifree(in.pointlist);
410 trifree(in.segmentlist);
411 if (in.numberofholes > 0)
412 trifree(in.holelist);
413 if (in.numberofregions > 0)
414 trifree(in.regionlist);
415 trifree(out.pointlist);
416 trifree(out.pointattributelist);
417 trifree(out.pointmarkerlist);
418 trifree(out.trianglelist);
419 trifree(out.triangleattributelist);
420 trifree(out.neighborlist);
421 trifree(out.edgelist);
422 trifree(out.edgemarkerlist);
423 trifree(out.segmentlist);
424 trifree(out.segmentmarkerlist);
425
426 return out.numberoftriangles;
427}
428
429void ompl::control::TriangularDecomposition::LocatorGrid::buildTriangleMap(const std::vector<Triangle> &triangles)
430{
431 regToTriangles_.resize(getNumRegions());
432 std::vector<double> bboxLow(2);
433 std::vector<double> bboxHigh(2);
434 std::vector<int> gridCoord[2];
435 for (unsigned int i = 0; i < triangles.size(); ++i)
436 {
437 /* for Triangle tri, compute the smallest rectangular
438 * bounding box that contains tri. */
439 const Triangle &tri = triangles[i];
440 bboxLow[0] = tri.pts[0].x;
441 bboxLow[1] = tri.pts[0].y;
442 bboxHigh[0] = bboxLow[0];
443 bboxHigh[1] = bboxLow[1];
444
445 for (int j = 1; j < 3; ++j)
446 {
447 if (tri.pts[j].x < bboxLow[0])
448 bboxLow[0] = tri.pts[j].x;
449 else if (tri.pts[j].x > bboxHigh[0])
450 bboxHigh[0] = tri.pts[j].x;
451 if (tri.pts[j].y < bboxLow[1])
452 bboxLow[1] = tri.pts[j].y;
453 else if (tri.pts[j].y > bboxHigh[1])
454 bboxHigh[1] = tri.pts[j].y;
455 }
456
457 /* Convert the bounding box into grid cell coordinates */
458
459 coordToGridCoord(bboxLow, gridCoord[0]);
460 coordToGridCoord(bboxHigh, gridCoord[1]);
461
462 /* Every grid cell within bounding box gets
463 tri added to its map entry */
464 std::vector<int> c(2);
465 for (int x = gridCoord[0][0]; x <= gridCoord[1][0]; ++x)
466 {
467 for (int y = gridCoord[0][1]; y <= gridCoord[1][1]; ++y)
468 {
469 c[0] = x;
470 c[1] = y;
471 int cellID = gridCoordToRegion(c);
472 regToTriangles_[cellID].push_back(i);
473 }
474 }
475 }
476}
477
478void ompl::control::TriangularDecomposition::buildLocatorGrid()
479{
480 locator.buildTriangleMap(triangles_);
481}
482
483bool ompl::control::TriangularDecomposition::triContains(const Triangle &tri, const std::vector<double> &coord)
484{
485 for (int i = 0; i < 3; ++i)
486 {
487 /* point (coord[0],coord[1]) needs to be to the left of
488 the vector from (ax,ay) to (bx,by) */
489 const double ax = tri.pts[i].x;
490 const double ay = tri.pts[i].y;
491 const double bx = tri.pts[(i + 1) % 3].x;
492 const double by = tri.pts[(i + 1) % 3].y;
493
494 // return false if the point is instead to the right of the vector
495 if ((coord[0] - ax) * (by - ay) - (bx - ax) * (coord[1] - ay) > 0.)
496 return false;
497 }
498 return true;
499}
500
501ompl::control::TriangularDecomposition::Vertex
502ompl::control::TriangularDecomposition::getPointInPoly(const Polygon &poly)
503{
504 Vertex p;
505 p.x = 0.;
506 p.y = 0.;
507 for (auto pt : poly.pts)
508 {
509 p.x += pt.x;
510 p.y += pt.y;
511 }
512 p.x /= poly.pts.size();
513 p.y /= poly.pts.size();
514 return p;
515}
Random number generation. An instance of this class cannot be used by multiple threads at once (membe...
double uniform01()
Generate a random real between 0 and 1.
The lower and upper bounds for an Rn space.
Definition of an abstract state.
Definition State.h:50
Decomposition(int dim, const base::RealVectorBounds &b)
Constructor. Creates a Decomposition with a given dimension and a given set of bounds....
virtual void project(const base::State *s, std::vector< double > &coord) const =0
Project a given State to a set of coordinates in R^k, where k is the dimension of this Decomposition.
virtual const base::RealVectorBounds & getBounds() const
Returns the bounds of this Decomposition.
TriangularDecomposition(const base::RealVectorBounds &bounds, std::vector< Polygon > holes=std::vector< Polygon >(), std::vector< Polygon > intRegs=std::vector< Polygon >())
Creates a TriangularDecomposition over the given bounds, which must be 2-dimensional....
int locateRegion(const base::State *s) const override
Returns the index of the region containing a given State. Most often, this is obtained by first calli...
virtual int createTriangles()
Helper method to triangulate the space and return the number of triangles.
std::vector< int > intRegInfo_
Maps from triangle ID to index of Polygon in intReg_ that contains the triangle ID....
int getRegionOfInterestAt(int triID) const
Returns the region of interest that contains the given triangle ID. Returns -1 if the triangle ID is ...
void sampleFromRegion(int triID, RNG &rng, std::vector< double > &coord) const override
Samples a projected coordinate from a given region.
void getNeighbors(int triID, std::vector< int > &neighbors) const override
Stores a given region's neighbors into a given vector.
double getRegionVolume(int triID) override
Returns the volume of a given region in this Decomposition.
#define OMPL_INFORM(fmt,...)
Log a formatted information string.
Definition Console.h:68
#define OMPL_WARN(fmt,...)
Log a formatted warning string.
Definition Console.h:66
std::string toString(float val)
convert float to string using classic "C" locale semantics
Definition String.cpp:82
STL namespace.