2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // convex decomposition algorithm originally created by Mark Bayazit (http://mnbayazit.com/)
18 // for more information about this algorithm, see http://mnbayazit.com/406/bayazit
19 // modified by Yogesh (http://yogeshkulkarni.com)
20 // D port and further modifications by Ketmar // Invisible Vector
22 // decompose simple polygon (i.e. polygon without holes and self-intersections) into set of convex polygons
23 module iv
.vmath2d
.ybdecomposer
;
26 import iv
.vmath2d
.math2d
;
27 import iv
.vmath2d
.vxstore
;
30 // ////////////////////////////////////////////////////////////////////////// //
31 public struct YBDecomposer(VT
) if (IsVectorDim
!(VT
, 2)) {
34 static bool reflex() (in auto ref VT prev
, in auto ref VT on
, in auto ref VT next
) {
36 // YOGESH: Added following condition of collinearity
37 if (Math2D
.isCollinear(prev
, on
, next
)) return false;
38 return right(prev
, on
, next
);
41 // if i is on the left of i-1 and i+1 line in the polygon vertices; checks area -ve
42 static bool left() (in auto ref VT a
, in auto ref VT b
, in auto ref VT c
) { pragma(inline
, true); return (Math2D
.area(a
, b
, c
) > 0); }
44 // if i is on the left or ON of i-1 and i+1 line in the polygon vertices; checks area -ve and 0
45 static bool leftOn() (in auto ref VT a
, in auto ref VT b
, in auto ref VT c
) { pragma(inline
, true); return (Math2D
.area(a
, b
, c
) >= 0); }
47 // if i is on the right of i-1 and i+1 line in the polygon vertices; checks area +ve
48 static bool right() (in auto ref VT a
, in auto ref VT b
, in auto ref VT c
) { pragma(inline
, true); return (Math2D
.area(a
, b
, c
) < 0); }
50 // if i is on the right or ON of i-1 and i+1 line in the polygon vertices; checks area +ve and 0
51 static bool rightOn() (in auto ref VT a
, in auto ref VT b
, in auto ref VT c
) { pragma(inline
, true); return (Math2D
.area(a
, b
, c
) <= 0); }
54 // decompose the polygon into several smaller non-concave polygon
55 // if the polygon is already convex, it will return the original polygon
56 // returned polygons are CCW
57 static void convexPartition(VS
) (ref VS
[] accum
, ref VS poly
) if (IsGoodVertexStorage
!(VS
, VT
)) {
58 // can you see j from i in the polygon vertices without any obstructions?
59 bool canSee (int i
, int j
) {
64 if (reflex(prev
, on
, next
)) {
65 if (leftOn(poly
[i
], poly
[i
-1], poly
[j
]) && rightOn(poly
[i
], poly
[i
+1], poly
[j
])) return false;
67 if (rightOn(poly
[i
], poly
[i
+1], poly
[j
]) ||
leftOn(poly
[i
], poly
[i
-1], poly
[j
])) return false;
74 if (reflex(prevj
, onj
, nextj
)) {
75 if (leftOn(poly
[j
], poly
[j
-1], poly
[i
]) && rightOn(poly
[j
], poly
[j
+1], poly
[i
])) return false;
77 if (rightOn(poly
[j
], poly
[j
+1], poly
[i
]) ||
leftOn(poly
[j
], poly
[j
-1], poly
[i
])) return false;
80 foreach (immutable k
; 0..poly
.length
) {
81 //if ((k + 1) % vertices.Count == i || k == i || (k + 1) % vertices.Count == j || k == j) continue; // ignore incident edges
83 // YOGESH: changed from Line-Line intersection to Segment-Segment Intersection
89 // ignore incident edges
90 if (p1
== q1 || p1
== q2 || p2
== q1 || p2
== q2
) continue;
92 auto intPoint
= Math2D
.segIntersect(p1
, p2
, q1
, q2
);
93 //Debug.Print("Line from point {0} to {1} is tested against [{2},{3} to see if they intersect]", i, j, k, k+1);
95 // intPoint is not any of the j line then false, else continue. Intersection has to be interior to qualify s 'false' from here
96 //if (intPoint != poly.verts[k] || intPoint != poly.verts[k+1]) return false;
97 if (intPoint
.equals(poly
[k
]) || intPoint
.equals(poly
[k
+1])) return false;
104 void copyPolyPartTo (ref VS dest
, int i
, int j
) {
105 while (j
< i
) j
+= poly
.length
;
106 for (; i
<= j
; ++i
) dest
~= poly
[i
];
110 poly
.collinearSimplify();
111 if (poly
.length
< 3) return;
113 // we force it to CCW as it is a precondition in this algorithm
116 //VT.Float lowerDist = 0.0, upperDist = 0.0;
117 //int lowerIndex = 0, upperIndex = 0;
118 VT lowerInt
, upperInt
; // intersection points
120 // go thru all Verices until we find a reflex vertex i
121 // extend the edges incident at i until they hit an edge
122 // find BEST vertex within the range, that the partitioning chord
124 // a polygon can be broken into convex regions by eliminating all reflex vertices
125 // eliminating two reflex vertices with one diagonal is better than eliminating just one
126 // a reflex vertex can only be removed if the diagonal connecting to it is within the range given by extending its neighbouring edges;
127 // otherwise, its angle is only reduced
128 foreach (immutable i
; 0..poly
.length
) {
129 if (reflex(poly
[i
-1], poly
[i
], poly
[i
+1])) {
130 VT
.Float lowerDist
= VT
.Float
.infinity
;
131 VT
.Float upperDist
= VT
.Float
.infinity
;
132 int lowerIndex
= 0, upperIndex
= 0;
133 for (int j
= 0; j
< poly
.length
; ++j
) {
134 // YOGESH: if any of j line's endpoints matches with reflex i, skip
135 if (poly
.sameIndex(i
, j
) || poly
.sameIndex(i
, j
-1) || poly
.sameIndex(i
, j
+1)) continue; // no self and prev and next, for testing
137 // testing incoming edge:
138 // if line coming into i vertex (i-1 to i) has j vertex of the test-line on left
139 // AND have j-i on right, then they will be intersecting
141 VT iPrev
= poly
[i
-1];
144 VT jPrev
= poly
[j
-1];
146 bool leftOK
= left(iPrev
, iSelf
, jSelf
);
147 bool rightOK
= right(iPrev
, iSelf
, jPrev
);
149 bool leftOnOK
= Math2D
.isCollinear(iPrev
, iSelf
, jSelf
); // YOGESH: cached into variables for better debugging
150 bool rightOnOK
= Math2D
.isCollinear(iPrev
, iSelf
, jPrev
); // YOGESH: cached into variables for better debugging
152 if (leftOnOK || rightOnOK
) {
153 // YOGESH: Checked "ON" condition as well, collinearity
154 // lines are colinear, they can not be overlapping as polygon is simple
155 // find closest point which is not internal to incoming line i , i -1
156 VT
.Float d
= iSelf
.distanceSquared(jSelf
);
158 // this lower* is the point got from incoming edge into the i vertex,
159 // lowerInt incoming edge intersection point
160 // lowerIndex incoming edge intersection edge
162 // keep only the closest intersection
168 d
= iSelf
.distanceSquared(jPrev
);
170 // this lower* is the point got from incoming edge into the i vertex,
171 // lowerInt incoming edge intersection point
172 // lowerIndex incoming edge intersection edge
174 // keep only the closest intersection
179 } else if (leftOK
&& rightOK
) {
180 // YOGESH: Intersection in-between. Bayazit had ON condition in built here, which I have taken care above.
181 // find the point of intersection
182 VT p
= Math2D
.lineIntersect(poly
[i
-1], poly
[i
], poly
[j
], poly
[j
-1]);
183 // make sure it's inside the poly,
184 if (right(poly
[i
+1], poly
[i
], p
)) {
185 VT
.Float d
= poly
[i
].distanceSquared(p
);
186 // this lower* is the point got from incoming edge into the i vertex,
187 // lowerInt incoming edge intersection point
188 // lowerIndex incoming edge intersection edge
190 // keep only the closest intersection
198 // testing outgoing edge:
199 // if line outgoing from i vertex (i to i+1) has j vertex of the test-line on right
200 // AND has j+1 on left, they they will be intersecting
202 VT iNext
= poly
[i
+1];
203 VT jNext
= poly
[j
+1];
205 bool leftOKn
= left(iNext
, iSelf
, jNext
);
206 bool rightOKn
= right(iNext
, iSelf
, jSelf
);
208 bool leftOnOKn
= Math2D
.isCollinear(iNext
, iSelf
, jNext
); // YOGESH: cached into variables for better debugging
209 bool rightOnOKn
= Math2D
.isCollinear(iNext
, iSelf
, jSelf
);
211 if (leftOnOKn || rightOnOKn
) {
212 // YOGESH: Checked "ON" condition as well, collinearity
213 // lines are colinear, they can not be overlapping as polygon is simple
214 // find closest point which is not internal to incoming line i , i -1
215 VT
.Float d
= iSelf
.distanceSquared(jNext
);
217 // this upper* is the point got from outgoing edge into the i vertex,
218 // upperInt outgoing edge intersection point
219 // upperIndex outgoing edge intersection edge
221 // keep only the closest intersection
227 d
= poly
[i
].distanceSquared(poly
[j
]);
229 // this upper* is the point got from outgoing edge into the i vertex,
230 // upperInt outgoing edge intersection point
231 // upperIndex outgoing edge intersection edge
233 // keep only the closest intersection
238 } else if (leftOKn
&& rightOKn
) {
239 // YOGESH: Intersection in-between. Bayazit had ON condition in built here, which I have taken care above.
240 VT p
= Math2D
.lineIntersect(poly
[i
+1], poly
[i
], poly
[j
], poly
[j
+1]);
241 if (left(poly
[i
-1], poly
[i
], p
)) {
242 VT
.Float d
= poly
[i
].distanceSquared(p
);
243 // this upper* is the point got from outgoing edge from the i vertex,
244 // upperInt outgoing edge intersection point
245 // upperIndex outgoing edge intersection edge
255 VS lowerPoly
, upperPoly
;
256 static if (is(VS
== class)) {
257 lowerPoly
= new VS();
258 upperPoly
= new VS();
261 // YOGESH: If no vertices in the range, lets not choose midpoint but closet point of that segment
262 // if there are no vertices to connect to, choose a point in the middle
263 if (lowerIndex
== (upperIndex
+1)%poly
.length
) {
264 VT sp
= ((lowerInt
+upperInt
)/cast(VT
.Float
)2);
265 copyPolyPartTo(lowerPoly
, i
, upperIndex
);
267 copyPolyPartTo(upperPoly
, lowerIndex
, i
);
270 // find vertex to connect to
271 VT
.Float highestScore
= 0, bestIndex
= lowerIndex
;
272 while (upperIndex
< lowerIndex
) upperIndex
+= poly
.length
;
274 // go through all the vertices between the range of lower and upper
275 for (int j
= lowerIndex
; j
<= upperIndex
; ++j
) {
277 VT
.Float score
= cast(VT
.Float
)1/(poly
[i
].distanceSquared(poly
[j
])+1);
279 // if another vertex is reflex, choosing it has highest score
280 VT prevj
= poly
[j
-1];
282 VT nextj
= poly
[j
+1];
284 if (reflex(prevj
, onj
, nextj
)) {
285 if (rightOn(poly
[j
-1], poly
[j
], poly
[i
]) && leftOn(poly
[j
+1], poly
[j
], poly
[i
])) {
293 if (score
> highestScore
) {
295 highestScore
= score
;
300 // YOGESH : Pending: if there are 2 vertices as 'bestIndex', its better to disregard both and put midpoint (M case)
301 copyPolyPartTo(lowerPoly
, i
, cast(int)bestIndex
);
302 copyPolyPartTo(upperPoly
, cast(int)bestIndex
, i
);
305 // solve smallest poly first (SAW in Bayazit's C++ code)
306 if (lowerPoly
.length
< upperPoly
.length
) {
307 convexPartition(accum
, lowerPoly
);
308 convexPartition(accum
, upperPoly
);
310 convexPartition(accum
, upperPoly
);
311 convexPartition(accum
, lowerPoly
);
317 // polygon is already convex