xmsmesh  1.0
MePolyOffsetter.cpp
Go to the documentation of this file.
1 //------------------------------------------------------------------------------
6 //------------------------------------------------------------------------------
7 
8 //----- Included files ---------------------------------------------------------
9 
10 // 1. Precompiled header
11 
12 // 2. My own header
14 
15 // 3. Standard library headers
16 #include <sstream>
17 
18 // 4. External library headers
19 #include <boost/unordered_set.hpp>
20 #include <xmscore/math/math.h>
21 #include <xmscore/points/pt.h>
22 
23 // 5. Shared code headers
26 #include <xmscore/misc/XmError.h>
27 
28 // 6. Non-shared code headers
29 
30 //----- Forward declarations ---------------------------------------------------
31 
32 //----- External globals -------------------------------------------------------
33 
34 //----- Namespace declaration --------------------------------------------------
35 namespace xms
36 {
37 //----- Constants / Enumerations -----------------------------------------------
38 
39 //----- Classes / Structs ------------------------------------------------------
41 {
42 public:
44 
45  virtual bool Offset(const std::vector<Pt3d>& a_input,
47  MePolyOffsetterOutput& a_output,
48  double a_xyTol) override;
49 
50  bool Offset(const std::vector<Pt3d>& a_input,
52  std::vector<std::vector<Pt3d>>& a_output,
53  std::vector<MePolyOffsetter::polytype>& a_outPolyType);
54  bool DoOffset(const std::vector<Pt3d>& a_input);
55  void CheckToAddPoint(std::vector<Pt3d>& a_result, const Pt3d& a_pt);
56  void ProcessAngleSegmentEnd(int npt_end,
57  double ang_end,
58  int in1,
59  int in2,
60  int in3,
61  double dx1,
62  double dy1,
63  const Pt3d* pts,
64  std::vector<Pt3d>& a_result);
65  void SpecialRejection(const std::vector<Pt3d>& a_input, std::vector<Pt3d>& a_output);
66  void SelfIntersection(std::vector<Pt3d>& a_pLine);
67  void FindDuplicatesAndOrderLoops(const std::vector<Pt3d>& a_input);
68 
69  double m_xyTol;
71  BSHP<MePolyCleaner> m_intersector;
74 };
75 //----- Internal functions -----------------------------------------------------
76 
77 //----- Class / Function definitions -------------------------------------------
78 
79 namespace
80 {
81 #define TWOPI 6.28318530717958647692528
82 #define PIOVER6 0.523598775598299
83 #define SEVENPIOVER3 7.330382858376180
84 #define FOURPIOVER3 4.188790204786390
85 #define SIN60 0.866025403784
86 #define TWOPIOVER3 2.094395102393200
87 #define PIOVER3 1.047197551196600
88 
89 } // unnamed namespace
90 
91 //------------------------------------------------------------------------------
94 //------------------------------------------------------------------------------
95 BSHP<MePolyOffsetter> MePolyOffsetter::New()
96 {
97  BSHP<MePolyOffsetter> ptr(new MePolyOffsetterImpl());
98  return ptr;
99 } // PolyOffsetter::New
100 
105 //------------------------------------------------------------------------------
107 //------------------------------------------------------------------------------
109 : m_xyTol(1e-9)
110 , m_setOffsetToZero(false)
111 , m_intersector(MePolyCleaner::New())
112 , m_pType(OUTSIDE_POLY)
113 {
114 } // MePolyOffsetterImpl::MePolyOffsetterImpl
115 //------------------------------------------------------------------------------
124 //------------------------------------------------------------------------------
125 bool MePolyOffsetterImpl::Offset(const std::vector<Pt3d>& a_input,
127  MePolyOffsetterOutput& a_out,
128  double a_xyTol)
129 {
130  m_xyTol = a_xyTol;
131  m_pType = a_pType;
132  std::vector<Pt3d> input(a_input);
133  if (!DoOffset(input))
134  return false;
135  a_out = m_output;
136  return true;
137 } // MePolyOffsetterImpl::Offset
138 //------------------------------------------------------------------------------
148 //------------------------------------------------------------------------------
149 bool MePolyOffsetterImpl::Offset(const std::vector<Pt3d>& a_input,
151  std::vector<std::vector<Pt3d>>& a_output,
152  std::vector<MePolyOffsetter::polytype>& a_outPolyType)
153 {
154  m_pType = a_pType;
155  // check that input polygon is valid
156  // if (!InputValid(a_input)) return false;
157  std::vector<Pt3d> input(a_input);
158 
159  // buffer the polygon
160  if (!DoOffset(input))
161  return false;
162 
163  a_outPolyType.resize(m_output.m_loopTypes.size());
164  for (size_t i = 0; i < m_output.m_loopTypes.size(); ++i)
165  {
166  a_outPolyType[i] = (MePolyOffsetter::polytype)m_output.m_loopTypes[i];
167  }
168  a_output.assign(m_output.m_loops.size(), std::vector<Pt3d>());
169  for (size_t i = 0; i < m_output.m_loops.size(); ++i)
170  {
171  a_output[i].reserve(m_output.m_loops[i].size());
172  for (size_t j = 0; j < m_output.m_loops[i].size(); ++j)
173  {
174  a_output[i].push_back(m_output.m_pts[m_output.m_loops[i][j]]);
175  }
176  }
177 
178  return true;
179 } // MePolyOffsetterImpl::Buffer
180 //------------------------------------------------------------------------------
184 //------------------------------------------------------------------------------
185 bool MePolyOffsetterImpl::DoOffset(const std::vector<Pt3d>& a_input)
186 {
187  bool rval(true);
188  std::vector<Pt3d> result;
189 
190  const Pt3d* pts(&a_input[0]);
191  int numpts = (int)a_input.size();
192  // don't repeat last point
193  if (gmEqualPointsXY(a_input.front(), a_input.back(), m_xyTol))
194  --numpts;
195  // compute starting angle
196  int in1 = numpts - 3;
197  int in2 = numpts - 2;
198  int in3 = numpts - 1;
199  double dx1 = pts[in1].x - pts[in2].x;
200  double dy1 = pts[in1].y - pts[in2].y;
201  double dx2 = pts[in3].x - pts[in2].x;
202  double dy2 = pts[in3].y - pts[in2].y;
203  double ang_beg = gmAngleBetween2DVectors(dx1, dy1, dx2, dy2);
204  if (EQ_TOL(ang_beg, 0.0, 0.00001))
205  ang_beg = TWOPI;
206  // compute the number of points to add at the corner based on the angle
207  // 0- <90 degrees - -2 or -3 = block equilateral triangle creation
208  // 90- <150 degrees - -1 = usually block equilateral triangle
209  // 150- <210 degrees - 0 points around corner
210  // 210- <270 degrees - 1 points - bisect corner
211  // 270- <330 degrees - 2 points
212  // 330- 360 degrees - 3 points
213  // computed as the closest integer to (angle-120 degrees)/(60 degrees)
214  int npt_beg = int((ang_beg + PIOVER6) / SEVENPIOVER3 * 7.0) - 3;
215  in1 = in2;
216  in2 = in3;
217  // for each segments in the loop
218  int num;
219  for (in3 = 0, num = 0; in3 < numpts; in3++)
220  {
221  // find the angle at the end of the segment
222  dx1 = pts[in1].x - pts[in2].x;
223  dy1 = pts[in1].y - pts[in2].y;
224  dx2 = pts[in3].x - pts[in2].x;
225  dy2 = pts[in3].y - pts[in2].y;
226  double ang_end = gmAngleBetween2DVectors(dx1, dy1, dx2, dy2);
227  if (EQ_TOL(ang_end, 0.0, 0.00001))
228  ang_end = TWOPI;
229  int npt_end = int((ang_end + PIOVER6) / SEVENPIOVER3 * 7.0) - 3;
230  // process the segment - the equilateral point
231  // if (npt_beg > -2 && npt_end > -2 && (ang_beg+ang_end > THREEPIOVER2)) {
232  if (npt_beg > -2 && npt_end > -2 && (ang_beg + ang_end > FOURPIOVER3))
233  {
234  Pt3d newpt;
235  newpt.x = (pts[in2].x + pts[in1].x) / 2.0 + (pts[in2].y - pts[in1].y) * SIN60;
236  newpt.y = (pts[in2].y + pts[in1].y) / 2.0 + (pts[in1].x - pts[in2].x) * SIN60;
237  newpt.z = (pts[in2].z + pts[in1].z) / 2.0;
238  CheckToAddPoint(result, newpt);
239  }
240  // process the angle at the end of the segment
241  ProcessAngleSegmentEnd(npt_end, ang_end, in1, in2, in3, dx1, dy1, pts, result);
242 
243  // set up for next segment
244  in1 = in2;
245  in2 = in3;
246  ang_beg = ang_end;
247  npt_beg = npt_end;
248  }
249 
250  SpecialRejection(a_input, result);
251  if (result.size() < 3)
252  return false;
253  SelfIntersection(result);
255  return rval;
256 } // MePolyOffsetterImpl::DoOffset
257 //------------------------------------------------------------------------------
261 //------------------------------------------------------------------------------
262 void MePolyOffsetterImpl::CheckToAddPoint(std::vector<Pt3d>& a_result, const Pt3d& a_pt)
263 {
264  a_result.push_back(a_pt);
265 } // MePolyOffsetterImpl::CheckToAddPoint
266 //------------------------------------------------------------------------------
277 //------------------------------------------------------------------------------
279  double ang_end,
280  int in1,
281  int in2,
282  int in3,
283  double dx1,
284  double dy1,
285  const Pt3d* pts,
286  std::vector<Pt3d>& a_result)
287 {
288  Pt3d newpt;
289  double l1, l2, dl, offset, ptx, pty, ctheta, stheta, alpha;
290 
291  switch (npt_end)
292  {
293  case 1:
294  l1 = Mdist(pts[in1].x, pts[in1].y, pts[in2].x, pts[in2].y);
295  l2 = Mdist(pts[in3].x, pts[in3].y, pts[in2].x, pts[in2].y);
296  offset = (l1 + l2) * 0.4330127019 / l1;
297  ptx = pts[in2].x + offset * dx1;
298  pty = pts[in2].y + offset * dy1;
299  ctheta = cos(ang_end / 2);
300  stheta = sin(ang_end / 2);
301  newpt.x = ptx * ctheta - pty * stheta + (1.0 - ctheta) * pts[in2].x + stheta * pts[in2].y;
302  newpt.y = ptx * stheta + pty * ctheta + (1.0 - ctheta) * pts[in2].y - stheta * pts[in2].x;
303  newpt.z = (pts[in2].z + pts[in1].z) / 2.0;
304  CheckToAddPoint(a_result, newpt);
305  break;
306  case 2:
307  l1 = Mdist(pts[in1].x, pts[in1].y, pts[in2].x, pts[in2].y);
308  l2 = Mdist(pts[in3].x, pts[in3].y, pts[in2].x, pts[in2].y);
309  dl = l2 - l1;
310  // compute angle between points
311  alpha = (ang_end - TWOPIOVER3) / 3.0;
312  // place first point
313  offset = SIN60 * (l1 + dl * ((alpha - PIOVER6) / (3 * alpha - PIOVER3))) / l1;
314  ptx = pts[in2].x + offset * dx1;
315  pty = pts[in2].y + offset * dy1;
316  ctheta = cos(alpha + PIOVER3);
317  stheta = sin(alpha + PIOVER3);
318  newpt.x = ptx * ctheta - pty * stheta + (1.0 - ctheta) * pts[in2].x + stheta * pts[in2].y;
319  newpt.y = ptx * stheta + pty * ctheta + (1.0 - ctheta) * pts[in2].y - stheta * pts[in2].x;
320  newpt.z = (pts[in2].z + pts[in1].z) / 2.0;
321  CheckToAddPoint(a_result, newpt);
322  // place second point
323  offset = SIN60 * (l1 + dl * ((2 * alpha - PIOVER6) / (3 * alpha - PIOVER3))) / l1;
324  ptx = pts[in2].x + offset * dx1;
325  pty = pts[in2].y + offset * dy1;
326  ctheta = cos(2 * alpha + PIOVER3);
327  stheta = sin(2 * alpha + PIOVER3);
328  newpt.x = ptx * ctheta - pty * stheta + (1.0 - ctheta) * pts[in2].x + stheta * pts[in2].y;
329  newpt.y = ptx * stheta + pty * ctheta + (1.0 - ctheta) * pts[in2].y - stheta * pts[in2].x;
330  newpt.z = (pts[in2].z + pts[in1].z) / 2.0;
331  CheckToAddPoint(a_result, newpt);
332  break;
333  case 3:
334  l1 = Mdist(pts[in1].x, pts[in1].y, pts[in2].x, pts[in2].y);
335  l2 = Mdist(pts[in3].x, pts[in3].y, pts[in2].x, pts[in2].y);
336  dl = l2 - l1;
337  // compute angle between points
338  alpha = (ang_end - TWOPIOVER3) / 4.0;
339  // place first point
340  offset = SIN60 * (l1 + dl * ((alpha - PIOVER6) / (4 * alpha - PIOVER3))) / l1;
341  ptx = pts[in2].x + offset * dx1;
342  pty = pts[in2].y + offset * dy1;
343  ctheta = cos(alpha + PIOVER3);
344  stheta = sin(alpha + PIOVER3);
345  newpt.x = ptx * ctheta - pty * stheta + (1.0 - ctheta) * pts[in2].x + stheta * pts[in2].y;
346  newpt.y = ptx * stheta + pty * ctheta + (1.0 - ctheta) * pts[in2].y - stheta * pts[in2].x;
347  newpt.z = (pts[in2].z + pts[in1].z) / 2.0;
348  CheckToAddPoint(a_result, newpt);
349  // place second point
350  offset = SIN60 * (l1 + dl * ((2 * alpha - PIOVER6) / (4 * alpha - PIOVER3))) / l1;
351  ptx = pts[in2].x + offset * dx1;
352  pty = pts[in2].y + offset * dy1;
353  ctheta = cos(2 * alpha + PIOVER3);
354  stheta = sin(2 * alpha + PIOVER3);
355  newpt.x = ptx * ctheta - pty * stheta + (1.0 - ctheta) * pts[in2].x + stheta * pts[in2].y;
356  newpt.y = ptx * stheta + pty * ctheta + (1.0 - ctheta) * pts[in2].y - stheta * pts[in2].x;
357  newpt.z = (pts[in2].z + pts[in1].z) / 2.0;
358  CheckToAddPoint(a_result, newpt);
359  // place third point
360  offset = SIN60 * (l1 + dl * ((3 * alpha - PIOVER6) / (4 * alpha - PIOVER3))) / l1;
361  ptx = pts[in2].x + offset * dx1;
362  pty = pts[in2].y + offset * dy1;
363  ctheta = cos(3 * alpha + PIOVER3);
364  stheta = sin(3 * alpha + PIOVER3);
365  newpt.x = ptx * ctheta - pty * stheta + (1.0 - ctheta) * pts[in2].x + stheta * pts[in2].y;
366  newpt.y = ptx * stheta + pty * ctheta + (1.0 - ctheta) * pts[in2].y - stheta * pts[in2].x;
367  newpt.z = (pts[in2].z + pts[in1].z) / 2.0;
368  CheckToAddPoint(a_result, newpt);
369  break;
370  }
371 } // MePolyOffsetterImpl::ProcessAngleSegmentEnd
372 //------------------------------------------------------------------------------
376 //------------------------------------------------------------------------------
377 void MePolyOffsetterImpl::SpecialRejection(const std::vector<Pt3d>& a_input,
378  std::vector<Pt3d>& a_out)
379 {
380  // See if the beginning node needs to be deleted due to being a sharp angle
381  /****************************************************
382  * AKZ-FIX - look for special rejection case
383  * - a thin triangle can offset to a triangle almost
384  * as big as the original with two or three edges
385  * intersecting of the original.
386  * We may want to handle all cases of the offset
387  * intersecting the original, but for now just
388  * delete this special case
389  ****************************************************/
390  const Pt3d* pts(&a_input[0]);
391  int in(0);
392  if (a_input.size() == 3 && a_out.size() == 3)
393  {
394  in = (int)gmPointInTriangleWithTol(&pts[0], &pts[1], &pts[2], a_out[0].x, a_out[0].y, m_xyTol);
395  in += (int)gmPointInTriangleWithTol(&pts[0], &pts[1], &pts[2], a_out[1].x, a_out[1].y, m_xyTol);
396  in += (int)gmPointInTriangleWithTol(&pts[0], &pts[1], &pts[2], a_out[2].x, a_out[2].y, m_xyTol);
397  if (in < 2)
398  a_out.resize(0);
399  }
400 } // MePolyOffsetterImpl::SpecialRejection
401 //------------------------------------------------------------------------------
404 //------------------------------------------------------------------------------
405 void MePolyOffsetterImpl::SelfIntersection(std::vector<Pt3d>& a_pLine)
406 {
407  m_intersector->CleanPolyOffset(a_pLine, m_pType, m_xyTol, m_output);
408 } // MePolyOffsetterImpl::SelfIntersection
409 //------------------------------------------------------------------------------
412 //------------------------------------------------------------------------------
413 void MePolyOffsetterImpl::FindDuplicatesAndOrderLoops(const std::vector<Pt3d>& a_input)
414 {
415  // figure out which points are repeated in the loops (these will be nodes
416  // in a coverage)
417  std::vector<int> vCnt(m_output.m_pts.size(), 0);
418  for (size_t i = 0; i < m_output.m_loops.size(); ++i)
419  {
420  for (size_t j = 0; j < m_output.m_loops[i].size(); ++j)
421  {
422  vCnt[m_output.m_loops[i][j]]++;
423  }
424  }
425  boost::unordered_set<size_t> setIdx;
426  for (size_t i = 0; i < vCnt.size(); ++i)
427  {
428  if (vCnt[i] > 1)
429  {
430  setIdx.insert(i);
431  }
432  }
433  // add all of the intersection points
434  for (size_t i = a_input.size(); i < m_output.m_pts.size(); ++i)
435  {
436  if (setIdx.find(i) == setIdx.end() && vCnt[i] > 0)
437  {
438  setIdx.insert(i);
439  }
440  }
441 
442  for (size_t i = 0; i < m_output.m_loops.size(); ++i)
443  {
444  bool found = false;
445  for (size_t j = 0; !found && j < m_output.m_loops[i].size(); ++j)
446  {
447  if (setIdx.find(m_output.m_loops[i][j]) != setIdx.end())
448  found = true;
449  }
450  if (!found)
451  {
452  setIdx.insert(m_output.m_loops[i][0]);
453  }
454  }
455  // order the points in the loops so that they start with one of the points
456  // in setIdx
457  for (size_t i = 0; i < m_output.m_loops.size(); ++i)
458  {
459  if (setIdx.find(m_output.m_loops[i][0]) == setIdx.end())
460  {
461  size_t start = 1;
462  bool found = false;
463  for (size_t j = 1; !found && j < m_output.m_loops[i].size(); ++j)
464  {
465  if (setIdx.find(m_output.m_loops[i][j]) != setIdx.end())
466  {
467  found = true;
468  start = j;
469  }
470  }
471  std::vector<size_t> l = m_output.m_loops[i];
472  size_t cnt(0);
473  while (cnt < l.size())
474  {
475  l[cnt] = m_output.m_loops[i][start];
476  cnt++;
477  start++;
478  if (start >= l.size())
479  start = 0;
480  }
481  m_output.m_loops[i] = l;
482  }
483  }
484 } // MePolyOffsetterImpl::FindDuplicatesAndOrderLoops
485 
486 } // namespace xms
487 
488 #ifdef CXX_TEST
489 
492 
493 //#include <boost/assign.hpp>
494 
496 
497 // namespace xms {
498 using namespace xms;
499 
504 //------------------------------------------------------------------------------
506 //------------------------------------------------------------------------------
508 {
509  BSHP<MePolyOffsetter> b = MePolyOffsetter::New();
510  TS_ASSERT(b);
511 } // PolyOffsetterTest::testCreateClass
512 //------------------------------------------------------------------------------
514 //------------------------------------------------------------------------------
516 {
517  // clang-format off
518  // This is the input
519  //
520  // x= 0 4 8 12
521  //
522  // y=12 2--- 3--- 4--- 5
523  // | |
524  // | |
525  // y-8 1 6
526  // | |
527  // | |
528  // y=4 0 7
529  // | |
530  // | |
531  // y=0 11---10--- 9--- 8
532  //
533  // clang-format on
534  MePolyOffsetter::polytype ptype = MePolyOffsetter::OUTSIDE_POLY;
536  pl.m_setOffsetToZero = true;
537  std::vector<Pt3d> input = {{0, 4, 0}, {0, 8, 0}, {0, 12, 0}, {4, 12, 0},
538  {8, 12, 0}, {12, 12, 0}, {12, 8, 0}, {12, 4, 0},
539  {12, 0, 0}, {8, 0, 0}, {4, 0, 0}, {0, 0, 0}};
540  std::vector<std::vector<Pt3d>> output;
541  std::vector<MePolyOffsetter::polytype> outPolyTypes;
542  pl.Offset(input, ptype, output, outPolyTypes);
543  std::vector<Pt3d> base = {{3.4641, 3.4641, 0.0}, {3.4641, 6.0, 0.0}, {3.4641, 8.5359, 0.0},
544  {6.0, 8.5359, 0.0}, {8.5359, 8.5359, 0.0}, {8.5359, 6.0, 0.0},
545  {8.5359, 3.4641, 0.0}, {6.0, 3.4641, 0.0}};
546  TS_ASSERT_DELTA_VECPT3D(base, output[0], 1e-4);
547 } // PolyOffsetterTest::testBox0
548 //------------------------------------------------------------------------------
550 //------------------------------------------------------------------------------
552 {
553  // clang-format off
554  // This is the input
555  //
556  // x= 0 4 8 12
557  //
558  // y=12 2--- 3--- 4--- 5
559  // | |
560  // | |
561  // y-8 1 6
562  // | /
563  // | 7
564  // | \
565  //y=4 0 8
566  // | |
567  // | |
568  // y=0 12---11---10--- 9
569  //
570  // clang-format on
571  MePolyOffsetter::polytype ptype = MePolyOffsetter::OUTSIDE_POLY;
573  pl.m_setOffsetToZero = true;
574  std::vector<Pt3d> input = {{0, 4, 0}, {0, 8, 0}, {0, 12, 0}, {4, 12, 0}, {8, 12, 0},
575  {12, 12, 0}, {12, 8, 0}, {8, 6, 0}, {12, 4, 0}, {12, 0, 0},
576  {8, 0, 0}, {4, 0, 0}, {0, 0, 0}};
577  std::vector<std::vector<Pt3d>> output;
578  std::vector<MePolyOffsetter::polytype> outPolyTypes;
579  pl.Offset(input, ptype, output, outPolyTypes);
580  std::vector<Pt3d> base = {{3.4641, 3.4641, 0.0}, {3.4641, 6.0, 0.0}, {3.4641, 8.5359, 0.0},
581  {5.4609, 8.5359, 0.0}, {4.6853, 8.0031, 0.0}, {4.6853, 3.9969, 0.0},
582  {5.4609, 3.4641, 0.0}};
583  TS_ASSERT_DELTA_VECPT3D(base, output[0], 1e-4);
584 } // PolyOffsetterTest::testBox1
585 //------------------------------------------------------------------------------
587 //------------------------------------------------------------------------------
589 {
590  // clang-format off
591  // x= 0 4 8 12
592  //
593  // y=12 11---10--- 9--- 8
594  // | |
595  // | |
596  // y-8 12 7
597  // | /
598  // | 6
599  // | \
600  //y=4 0 5
601  // | |
602  // | |
603  // y=0 1--- 2--- 3--- 4
604  //
605  // clang-format on
606  MePolyOffsetter::polytype ptype = MePolyOffsetter::INSIDE_POLY;
608  pl.m_setOffsetToZero = true;
609  std::vector<Pt3d> input = {{0, 4, 0}, {0, 0, 0}, {4, 0, 0}, {8, 0, 0}, {12, 0, 0},
610  {12, 4, 0}, {8, 6, 0}, {12, 8, 0}, {12, 12, 0}, {8, 12, 0},
611  {4, 12, 0}, {0, 12, 0}, {0, 8, 0}};
612  std::vector<std::vector<Pt3d>> output;
613  std::vector<MePolyOffsetter::polytype> outPolyTypes;
614  pl.Offset(input, ptype, output, outPolyTypes);
615  std::vector<Pt3d> base = {{-3.4641, 10, 0}, {-3.4641, 6, 0}, {-3.4641, 2, 0},
616  {-3.2551, -1.1847, 0}, {-1.1847, -3.2551, 0}, {2, -3.4641, 0},
617  {6, -3.4641, 0}, {10, -3.4641, 0}, {13.1847, -3.2551, 0},
618  {15.2551, -1.1847, 0}, {15.4641, 2, 0}, {15.1206, 5.9286, 0},
619  {15.1206, 6.0713, 0}, {15.4641, 10, 0}, {15.2551, 13.1847, 0},
620  {13.1847, 15.2551, 0}, {10, 15.4641, 0}, {6, 15.4641, 0},
621  {2, 15.4641, 0}, {-1.18479, 15.2551, 0}, {-3.2551, 13.1847, 0}};
622  TS_ASSERT_DELTA_VECPT3D(base, output[0], 1e-4);
623 } // PolyOffsetterTest::testBox1
624 //------------------------------------------------------------------------------
626 //------------------------------------------------------------------------------
628 {
629  // clang-format off
630  // x= 0 4 8 12
631  //
632  // y=12 18-----17-----16----- 15
633  // | |
634  // | |
635  // | |
636  // | |
637  // y-8 19 11--12--13-14
638  // | |
639  // | 10
640  // | |
641  // | 9
642  // | |
643  // y=4 0 8---7--6--5
644  // | |
645  // | |
646  // | |
647  // | |
648  // y=0 1----- 2----- 3----- 4
649  //
650  // clang-format on
651  MePolyOffsetter::polytype ptype = MePolyOffsetter::INSIDE_POLY;
653  pl.m_setOffsetToZero = true;
654  std::vector<Pt3d> input{{0, 4, 0}, {0, 0, 0}, {4, 0, 0}, // 2
655  {8, 0, 0}, {12, 0, 0}, {12, 4, 0}, // 5
656  {10, 4, 0}, {8, 4, 0}, {6, 4, 0}, // 8
657  {6, 5.33, 0}, {6, 6.66, 0}, {6, 8, 0}, // 11
658  {8, 8, 0}, {10, 8, 0}, {12, 8, 0}, // 14
659  {12, 12, 0}, {8, 12, 0}, {4, 12, 0}, // 17
660  {0, 12, 0}, {0, 8, 0}};
661  std::vector<std::vector<Pt3d>> output;
662  std::vector<MePolyOffsetter::polytype> outPolyTypes;
663  pl.Offset(input, ptype, output, outPolyTypes);
664  std::vector<Pt3d> base{{7.1518, 5.7321, 0.0}, {7.1518, 5.9950, 0.0}, {7.1536, 6.2679, 0.0},
665  {9.0, 6.2679, 0.0}, {11.0, 6.2679, 0.0}, {12.7240, 6.0107, 0.0},
666  {14.8935, 6.9469, 0.0}, {15.4641, 10.0, 0.0}, {15.2552, 13.1848, 0.0},
667  {13.1848, 15.2552, 0.0}, {10.0, 15.4641, 0.0}, {6.0, 15.4641, 0.0},
668  {2.0, 15.4641, 0.0}, {-1.1848, 15.2552, 0.0}, {-3.2552, 13.1848, 0.0},
669  {-3.4641, 10.0, 0.0}, {-3.4641, 6.0, 0.0}, {-3.4641, 2.0, 0.0},
670  {-3.2552, -1.1848, 0.0}, {-1.1848, -3.2552, 0.0}, {2.0, -3.4641, 0.0},
671  {6.0, -3.4641, 0.0}, {10.0, -3.4641, 0.0}, {13.1848, -3.2552, 0.0},
672  {15.2552, -1.1848, 0.0}, {15.4641, 2.0, 0.0}, {14.8935, 5.0531, 0.0},
673  {12.7240, 5.9893, 0.0}, {11.0000, 5.7321, 0.0}, {9.0000, 5.7321, 0.0}};
674  TS_ASSERT_DELTA_VECPT3D(base, output[0], 1e-4);
675 } // PolyOffsetterTest::testCase1
676 //------------------------------------------------------------------------------
678 //------------------------------------------------------------------------------
680 {
681  // clang-format off
682  // x= 0 4 8 12
683  //
684  // y=12 18-----17-----16----- 15
685  // | |
686  // | |
687  // | |
688  // | |
689  // y-8 19 11--12--13 |
690  // | | \14
691  // | 10
692  // | |
693  // | 9
694  // | | 5
695  // y=4 0 8---7--6/ |
696  // | |
697  // | |
698  // | |
699  // | |
700  // y=0 1----- 2----- 3----- 4
701  //
702  // clang-format on
703  MePolyOffsetter::polytype ptype = MePolyOffsetter::INSIDE_POLY;
705  pl.m_setOffsetToZero = true;
706  std::vector<Pt3d> input{{0, 4, 0}, {0, 0, 0}, {4, 0, 0}, // 2
707  {8, 0, 0}, {12, 0, 0}, {12, 5, 0}, // 5
708  {10, 4, 0}, {8, 4, 0}, {6, 4, 0}, // 8
709  {6, 5.33, 0}, {6, 6.66, 0}, {6, 8, 0}, // 11
710  {8, 8, 0}, {10, 8, 0}, {12, 7, 0}, // 14
711  {12, 12, 0}, {8, 12, 0}, {4, 12, 0}, // 17
712  {0, 12, 0}, {0, 8, 0}};
713  std::vector<std::vector<Pt3d>> output;
714  std::vector<MePolyOffsetter::polytype> outPolyTypes;
715  pl.Offset(input, ptype, output, outPolyTypes);
716  TS_ASSERT_EQUALS(2, output.size());
717  if (2 != output.size())
718  return;
719  std::vector<Pt3d> base{{9.6076, 6.0, 0.0}, {9, 5.7320, 0.0}, {7.1518, 5.7320, 0.0},
720  {7.1518, 5.995, 0.0}, {7.1535, 6.2679, 0.0}, {9, 6.2679, 0.0}};
721  TS_ASSERT_DELTA_VECPT3D(base, output[0], 1e-4);
722  base = {{15.4709, 6.0, 0.0}, {16.3301, 9.5, 0.0}, {15.8881, 13.4151, 0.0},
723  {13.2506, 15.4360, 0.0}, {10.0, 15.4641, 0.0}, {6.0, 15.4641, 0.0},
724  {2.0, 15.4641, 0.0}, {-1.1848, 15.2551, 0.0}, {-3.2552, 13.1847, 0.0},
725  {-3.4641, 10.0, 0.0}, {-3.4641, 6.0, 0.0}, {-3.4641, 2.0, 0.0},
726  {-3.2552, -1.1847, 0.0}, {-1.1848, -3.2551, 0.0}, {2.0, -3.4641, 0.0},
727  {6.0, -3.4641, 0.0}, {10.0, -3.4641, 0.0}, {13.2506, -3.4360, 0.0},
728  {15.8881, -1.4151, 0.0}, {16.3301, 2.5, 0.0}};
729  TS_ASSERT_DELTA_VECPT3D(base, output[1], 1e-4);
730  std::vector<MePolyOffsetter::polytype> basePolyTypes = {MePolyOffsetter::NEWOUT_POLY,
731  MePolyOffsetter::INSIDE_POLY};
732  TS_ASSERT_EQUALS_VEC(basePolyTypes, outPolyTypes);
733 } // PolyOffsetterTest::testCase1a
734 //------------------------------------------------------------------------------
736 //------------------------------------------------------------------------------
738 {
739  // clang-format off
740  // x= 0 4 8 12
741  //
742  // y=12 18-----17-----16----- 15
743  // | |
744  // | |
745  // | |
746  // | |
747  // y-8 19 11--12--13 |
748  // | | \14
749  // | 10
750  // | |
751  // | 9
752  // | | 5
753  // y=4 0 8---7--6/ |
754  // | |
755  // | |
756  // | |
757  // | |
758  // y=0 1----- 2----- 3----- 4
759  //
760  // clang-format on
761  MePolyOffsetter::polytype ptype = MePolyOffsetter::INSIDE_POLY;
763  pl.m_setOffsetToZero = true;
764  std::vector<Pt3d> input{{0, 4, 0}, {0, 0, 0}, {4, 0, 0}, // 2
765  {8, 0, 0}, {12, 0, 0}, {12, 5, 0}, // 5
766  {10, 4, 0}, {8, 4, 0}, {6, 4, 0}, // 8
767  {6, 5.33, 0}, {6, 6.66, 0}, {6, 8, 0}, // 11
768  {8, 8, 0}, {10, 8, 0}, {12, 7, 0}, // 14
769  {12, 12, 0}, {8, 12, 0}, {4, 12, 0}, // 17
770  {0, 12, 0}, {0, 8, 0}};
772  pl.Offset(input, ptype, out, 1e-9);
773  TS_ASSERT_EQUALS(2, out.m_loops.size());
774  if (2 != out.m_loops.size())
775  return;
776  std::vector<Pt3d> base{{-3.4641, 10, 0.0}, {-3.4641, 6, 0.0}, {-3.4641, 2, 0.0},
777  {-3.2552, -1.1847, 0.0}, {-1.1848, -3.2551, 0.0}, {2.0, -3.4641, 0.0},
778  {6.0, -3.4641, 0.0}, {10.0, -3.4641, 0.0}, {13.2506, -3.4360, 0.0},
779  {15.8881, -1.4151, 0.0}, {16.3301, 2.5, 0.0}, {15.2735, 6.8037, 0.0},
780  {12.1011, 7.5270, 0.0}, {10.1340, 6.2320, 0.0}, {9.0, 5.7320, 0.0},
781  {7.0, 5.7320, 0.0}, {7.1518, 4.665, 0.0}, {7.1518, 5.995, 0.0},
782  {7.1605, 7.33, 0.0}, {7.0, 6.2679, 0.0}, {9.0, 6.2679, 0.0},
783  {10.1340, 5.7679, 0.0}, {12.1011, 4.4730, 0.0}, {15.2735, 5.1963, 0.0},
784  {16.3301, 9.5, 0.0}, {15.8881, 13.4152, 0.0}, {13.2506, 15.4360, 0.0},
785  {10.0, 15.4641, 0.0}, {6.0, 15.4641, 0.0}, {2.0, 15.4641, 0.0},
786  {-1.1847, 15.2552, 0.0}, {-3.2551, 13.1848, 0.0}, {15.4709, 6.0, 0.0},
787  {9.6077, 6.0, 0.0}, {7.1518, 5.7321, 0.0}, {7.1536, 6.2679, 0.0}};
788  TS_ASSERT_DELTA_VECPT3D(base, out.m_pts, 1e-4);
789 
790  std::vector<MePolyOffsetter::polytype> basePolyTypes = {MePolyOffsetter::NEWOUT_POLY,
791  MePolyOffsetter::INSIDE_POLY};
792  TS_ASSERT_EQUALS_VEC(basePolyTypes, out.m_loopTypes);
793 
794  std::vector<size_t> baseIdx = {33, 14, 34, 17, 35, 20};
795  TS_ASSERT_EQUALS_VEC(baseIdx, out.m_loops[0]);
796  baseIdx = {32, 24, 25, 26, 27, 28, 29, 30, 31, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
797  TS_ASSERT_EQUALS_VEC(baseIdx, out.m_loops[1]);
798 } // PolyOffsetterTest::testCase1b
799 
800 //} // namespace xms
801 #endif
static BSHP< MePolyOffsetter > New()
Creates a BufferPoly class.
void testCase1()
tests offseting from the shape shown below
bool DoOffset(const std::vector< Pt3d > &a_input)
buffers the polygon
void SpecialRejection(const std::vector< Pt3d > &a_input, std::vector< Pt3d > &a_output)
a special case for removing points.
void testCreateClass()
tests creating the class
#define SIN60
prior calculated constant
bool gmEqualPointsXY(double x1, double y1, double x2, double y2, double tolerance)
#define TS_ASSERT_DELTA_VECPT3D(a, b, delta)
bool m_setOffsetToZero
flag used in testing
#define PIOVER3
prior calculated constant
virtual bool Offset(const std::vector< Pt3d > &a_input, MePolyOffsetter::polytype a_pType, MePolyOffsetterOutput &a_output, double a_xyTol) override
Takes an input polyline and offsets it based on the distance of the segments that make up the polylin...
#define TWOPI
prior calculated constant
void testBox0()
test offsetting from a box polygon
bool gmPointInTriangleWithTol(const Pt3d *p1, const Pt3d *p2, const Pt3d *p3, double x, double y, double tol)
convenience class for holding output data from the MePolyOffsetter
std::vector< Pt3d > m_pts
locations used by polygons
#define SEVENPIOVER3
prior calculated constant
MePolyOffsetterOutput m_output
the new polygons created by this class
#define FOURPIOVER3
prior calculated constant
void testBox1()
tests offseting from the shape shown below
polytype
enum to identify types of polygons created by this class
double gmAngleBetween2DVectors(double dxp, double dyp, double dxn, double dyn)
double m_xyTol
tolerance for geometric comparisons
Offsets a polyline (in or out). The polyline forms a closed loop.
Does an internal offset from a polygon outer boundary (shrink) and does an external offset from a pol...
bool EQ_TOL(const _T &A, const _U &B, const _V &tolerance)
#define PIOVER6
prior calculated constant
#define TWOPIOVER3
prior calculated constant
cleans the output produced by MePolyOffsetter
Definition: MePolyCleaner.h:26
std::vector< int > m_loopTypes
type of loop
void CheckToAddPoint(std::vector< Pt3d > &a_result, const Pt3d &a_pt)
checks to see if a point can be added to the resulting line
void testCase1a()
tests offseting from the shape shown below
double Mdist(_T x1, _U y1, _V x2, _W y2)
MePolyOffsetter::polytype m_pType
the type of polygon being offset
std::vector< std::vector< size_t > > m_loops
indexes of points that define loops
void testBox1a()
tests offseting from the shape shown below
void SelfIntersection(std::vector< Pt3d > &a_pLine)
Self intersection of a polyline.
BSHP< MePolyCleaner > m_intersector
class to clean the offset from the polygon
void FindDuplicatesAndOrderLoops(const std::vector< Pt3d > &a_input)
Finds duplicate points and orders the loops.
#define TS_ASSERT_EQUALS_VEC(a, b)
void ProcessAngleSegmentEnd(int npt_end, double ang_end, int in1, int in2, int in3, double dx1, double dy1, const Pt3d *pts, std::vector< Pt3d > &a_result)
handles inserting points around the end of a segment.
void testCase1b()
tests offseting from the shape shown below