Cleanup: trailing space
[blender-addons.git] / mesh_inset / triquad.py
blob1a5c266f061921536af533a41d76b81fad160516
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8 compliant>
22 from . import geom
23 import math
24 import random
25 from math import sqrt, hypot
27 # Points are 3-tuples or 2-tuples of reals: (x,y,z) or (x,y)
28 # Faces are lists of integers (vertex indices into coord lists)
29 # After triangulation/quadrangulation, the tris and quads will
30 # be tuples instead of lists.
31 # Vmaps are lists taking vertex index -> Point
33 TOL = 1e-7 # a tolerance for fuzzy equality
34 GTHRESH = 75 # threshold above which use greedy to _Quandrangulate
35 ANGFAC = 1.0 # weighting for angles in quad goodness measure
36 DEGFAC = 10.0 # weighting for degree in quad goodness measure
38 # Angle kind constants
39 Ang0 = 1
40 Angconvex = 2
41 Angreflex = 3
42 Angtangential = 4
43 Ang360 = 5
46 def TriangulateFace(face, points):
47 """Triangulate the given face.
49 Uses an easy triangulation first, followed by a constrained delauney
50 triangulation to get better shaped triangles.
52 Args:
53 face: list of int - indices in points, assumed CCW-oriented
54 points: geom.Points - holds coordinates for vertices
55 Returns:
56 list of (int, int, int) - 3-tuples are CCW-oriented vertices of
57 triangles making up the triangulation
58 """
60 if len(face) <= 3:
61 return [tuple(face)]
62 tris = EarChopTriFace(face, points)
63 bord = _BorderEdges([face])
64 triscdt = _CDT(tris, bord, points)
65 return triscdt
68 def TriangulateFaceWithHoles(face, holes, points):
69 """Like TriangulateFace, but with holes inside the face.
71 Works by making one complex polygon that has segments to
72 and from the holes ("islands"), and then using the same method
73 as TriangulateFace.
75 Args:
76 face: list of int - indices in points, assumed CCW-oriented
77 holes: list of list of int - each sublist is like face
78 but CW-oriented and assumed to be inside face
79 points: geom.Points - holds coordinates for vertices
80 Returns:
81 list of (int, int, int) - 3-tuples are CCW-oriented vertices of
82 triangles making up the triangulation
83 """
85 if len(holes) == 0:
86 return TriangulateFace(face, points)
87 allfaces = [face] + holes
88 sholes = [_SortFace(h, points) for h in holes]
89 joinedface = _JoinIslands(face, sholes, points)
90 tris = EarChopTriFace(joinedface, points)
91 bord = _BorderEdges(allfaces)
92 triscdt = _CDT(tris, bord, points)
93 return triscdt
96 def QuadrangulateFace(face, points):
97 """Quadrangulate the face (subdivide into convex quads and tris).
99 Like TriangulateFace, but after triangulating, join as many pairs
100 of triangles as possible into convex quadrilaterals.
102 Args:
103 face: list of int - indices in points, assumed CCW-oriented
104 points: geom.Points - holds coordinates for vertices
105 Returns:
106 list of 3-tuples or 4-tuples of ints - CCW-oriented vertices of
107 quadrilaterals and triangles making up the quadrangulation.
110 if len(face) <= 3:
111 return [tuple(face)]
112 tris = EarChopTriFace(face, points)
113 bord = _BorderEdges([face])
114 triscdt = _CDT(tris, bord, points)
115 qs = _Quandrangulate(triscdt, bord, points)
116 return qs
119 def QuadrangulateFaceWithHoles(face, holes, points):
120 """Like QuadrangulateFace, but with holes inside the faces.
122 Args:
123 face: list of int - indices in points, assumed CCW-oriented
124 holes: list of list of int - each sublist is like face
125 but CW-oriented and assumed to be inside face
126 points: geom.Points - holds coordinates for vertices
127 Returns:
128 list of 3-tuples or 4-tuples of ints - CCW-oriented vertices of
129 quadrilaterals and triangles making up the quadrangulation.
132 if len(holes) == 0:
133 return QuadrangulateFace(face, points)
134 allfaces = [face] + holes
135 sholes = [_SortFace(h, points) for h in holes]
136 joinedface = _JoinIslands(face, sholes, points)
137 tris = EarChopTriFace(joinedface, points)
138 bord = _BorderEdges(allfaces)
139 triscdt = _CDT(tris, bord, points)
140 qs = _Quandrangulate(triscdt, bord, points)
141 return qs
144 def _SortFace(face, points):
145 """Rotate face so leftmost vertex is first, where face is
146 list of indices in points."""
148 n = len(face)
149 if n <= 1:
150 return face
151 lefti = 0
152 leftv = face[0]
153 for i in range(1, n):
154 # following comparison is lexicographic on n-tuple
155 # so sorts on x first, using lower y as tie breaker.
156 if points.pos[face[i]] < points.pos[leftv]:
157 lefti = i
158 leftv = face[i]
159 return face[lefti:] + face[0:lefti]
162 def EarChopTriFace(face, points):
163 """Triangulate given face, with coords given by indexing into points.
164 Return list of faces, each of which will be a triangle.
165 Use the ear-chopping method."""
167 # start with lowest coord in 2d space to try
168 # to get a pleasing uniform triangulation if starting with
169 # a regular structure (like a grid)
170 start = _GetLeastIndex(face, points)
171 ans = []
172 incr = 1
173 n = len(face)
174 while n > 3:
175 i = _FindEar(face, n, start, incr, points)
176 vm1 = face[(i - 1) % n]
177 v0 = face[i]
178 v1 = face[(i + 1) % n]
179 face = _ChopEar(face, i)
180 n = len(face)
181 incr = - incr
182 if incr == 1:
183 start = i % n
184 else:
185 start = (i - 1) % n
186 ans.append((vm1, v0, v1))
187 ans.append(tuple(face))
188 return ans
191 def _GetLeastIndex(face, points):
192 """Return index of coordinate that is leftmost, lowest in face."""
194 bestindex = 0
195 bestpos = points.pos[face[0]]
196 for i in range(1, len(face)):
197 pos = points.pos[face[i]]
198 if pos[0] < bestpos[0] or \
199 (pos[0] == bestpos[0] and pos[1] < bestpos[1]):
200 bestindex = i
201 bestpos = pos
202 return bestindex
205 def _FindEar(face, n, start, incr, points):
206 """An ear of a polygon consists of three consecutive vertices
207 v(-1), v0, v1 such that v(-1) can connect to v1 without intersecting
208 the polygon.
209 Finds an ear, starting at index 'start' and moving
210 in direction incr. (We attempt to alternate directions, to find
211 'nice' triangulations for simple convex polygons.)
212 Returns index into faces of v0 (will always find one, because
213 uses a desperation mode if fails to find one with above rule)."""
215 angk = _ClassifyAngles(face, n, points)
216 for mode in range(0, 5):
217 i = start
218 while True:
219 if _IsEar(face, i, n, angk, points, mode):
220 return i
221 i = (i + incr) % n
222 if i == start:
223 break # try next higher desperation mode
226 def _IsEar(face, i, n, angk, points, mode):
227 """Return true, false depending on ear status of vertices
228 with indices i-1, i, i+1.
229 mode is amount of desperation: 0 is Normal mode,
230 mode 1 allows degenerate triangles (with repeated vertices)
231 mode 2 allows local self crossing (folded) ears
232 mode 3 allows any convex vertex (should always be one)
233 mode 4 allows anything (just to be sure loop terminates!)"""
235 k = angk[i]
236 vm2 = face[(i - 2) % n]
237 vm1 = face[(i - 1) % n]
238 v0 = face[i]
239 v1 = face[(i + 1) % n]
240 v2 = face[(i + 2) % n]
241 if vm1 == v0 or v0 == v1:
242 return (mode > 0)
243 b = (k == Angconvex or k == Angtangential or k == Ang0)
244 c = _InCone(vm1, v0, v1, v2, angk[(i + 1) % n], points) and \
245 _InCone(v1, vm2, vm1, v0, angk[(i - 1) % n], points)
246 if b and c:
247 return _EarCheck(face, n, angk, vm1, v0, v1, points)
248 if mode < 2:
249 return False
250 if mode == 3:
251 return SegsIntersect(vm2, vm1, v0, v1, points)
252 if mode == 4:
253 return b
254 return True
257 def _EarCheck(face, n, angk, vm1, v0, v1, points):
258 """Return True if the successive vertices vm1, v0, v1
259 forms an ear. We already know that it is not a reflex
260 Angle, and that the local cone containment is ok.
261 What remains to check is that the edge vm1-v1 doesn't
262 intersect any other edge of the face (besides vm1-v0
263 and v0-v1). Equivalently, there can't be a reflex Angle
264 inside the triangle vm1-v0-v1. (Well, there are
265 messy cases when other points of the face coincide with
266 v0 or touch various lines involved in the ear.)"""
267 for j in range(0, n):
268 fv = face[j]
269 k = angk[j]
270 b = (k == Angreflex or k == Ang360) \
271 and not(fv == vm1 or fv == v0 or fv == v1)
272 if b:
273 # Is fv inside closure of triangle (vm1,v0,v1)?
274 c = not(Ccw(v0, vm1, fv, points) \
275 or Ccw(vm1, v1, fv, points) \
276 or Ccw(v1, v0, fv, points))
277 fvm1 = face[(j - 1) % n]
278 fv1 = face[(j + 1) % n]
279 # To try to deal with some degenerate cases,
280 # also check to see if either segment attached to fv
281 # intersects either segment of potential ear.
282 d = SegsIntersect(fvm1, fv, vm1, v0, points) or \
283 SegsIntersect(fvm1, fv, v0, v1, points) or \
284 SegsIntersect(fv, fv1, vm1, v0, points) or \
285 SegsIntersect(fv, fv1, v0, v1, points)
286 if c or d:
287 return False
288 return True
291 def _ChopEar(face, i):
292 """Return a copy of face (of length n), omitting element i."""
294 return face[0:i] + face[i + 1:]
297 def _InCone(vtest, a, b, c, bkind, points):
298 """Return true if point with index vtest is in Cone of points with
299 indices a, b, c, where Angle ABC has AngleKind Bkind.
300 The Cone is the set of points inside the left face defined by
301 segments ab and bc, disregarding all other segments of polygon for
302 purposes of inside test."""
304 if bkind == Angreflex or bkind == Ang360:
305 if _InCone(vtest, c, b, a, Angconvex, points):
306 return False
307 return not((not(Ccw(b, a, vtest, points)) \
308 and not(Ccw(b, vtest, a, points)) \
309 and Ccw(b, a, vtest, points))
311 (not(Ccw(b, c, vtest, points)) \
312 and not(Ccw(b, vtest, c, points)) \
313 and Ccw(b, a, vtest, points)))
314 else:
315 return Ccw(a, b, vtest, points) and Ccw(b, c, vtest, points)
318 def _JoinIslands(face, holes, points):
319 """face is a CCW face containing the CW faces in the holes list,
320 where each hole is sorted so the leftmost-lowest vertex is first.
321 faces and holes are given as lists of indices into points.
322 The holes should be sorted by softface.
323 Add edges to make a new face that includes the holes (a Ccw traversal
324 of the new face will have the inside always on the left),
325 and return the new face."""
327 while len(holes) > 0:
328 (hole, holeindex) = _LeftMostFace(holes, points)
329 holes = holes[0:holeindex] + holes[holeindex + 1:]
330 face = _JoinIsland(face, hole, points)
331 return face
334 def _JoinIsland(face, hole, points):
335 """Return a modified version of face that splices in the
336 vertices of hole (which should be sorted)."""
338 if len(hole) == 0:
339 return face
340 hv0 = hole[0]
341 d = _FindDiag(face, hv0, points)
342 newface = face[0:d + 1] + hole + [hv0] + face[d:]
343 return newface
346 def _LeftMostFace(holes, points):
347 """Return (hole,index of hole in holes) where hole has
348 the leftmost first vertex. To be able to handle empty
349 holes gracefully, call an empty hole 'leftmost'.
350 Assumes holes are sorted by softface."""
352 assert(len(holes) > 0)
353 lefti = 0
354 lefthole = holes[0]
355 if len(lefthole) == 0:
356 return (lefthole, lefti)
357 leftv = lefthole[0]
358 for i in range(1, len(holes)):
359 ihole = holes[i]
360 if len(ihole) == 0:
361 return (ihole, i)
362 iv = ihole[0]
363 if points.pos[iv] < points.pos[leftv]:
364 (lefti, lefthole, leftv) = (i, ihole, iv)
365 return (lefthole, lefti)
368 def _FindDiag(face, hv, points):
369 """Find a vertex in face that can see vertex hv, if possible,
370 and return the index into face of that vertex.
371 Should be able to find a diagonal that connects a vertex of face
372 left of v to hv without crossing face, but try two
373 more desperation passes after that to get SOME diagonal, even if
374 it might cross some edge somewhere.
375 First desperation pass (mode == 1): allow points right of hv.
376 Second desperation pass (mode == 2): allow crossing boundary poly"""
378 besti = - 1
379 bestdist = 1e30
380 for mode in range(0, 3):
381 for i in range(0, len(face)):
382 v = face[i]
383 if mode == 0 and points.pos[v] > points.pos[hv]:
384 continue # in mode 0, only want points left of hv
385 dist = _DistSq(v, hv, points)
386 if dist < bestdist:
387 if _IsDiag(i, v, hv, face, points) or mode == 2:
388 (besti, bestdist) = (i, dist)
389 if besti >= 0:
390 break # found one, so don't need other modes
391 assert(besti >= 0)
392 return besti
395 def _IsDiag(i, v, hv, face, points):
396 """Return True if vertex v (at index i in face) can see vertex hv.
397 v and hv are indices into points.
398 (v, hv) is a diagonal if hv is in the cone of the Angle at index i on face
399 and no segment in face intersects (h, hv).
402 n = len(face)
403 vm1 = face[(i - 1) % n]
404 v1 = face[(i + 1) % n]
405 k = _AngleKind(vm1, v, v1, points)
406 if not _InCone(hv, vm1, v, v1, k, points):
407 return False
408 for j in range(0, n):
409 vj = face[j]
410 vj1 = face[(j + 1) % n]
411 if SegsIntersect(v, hv, vj, vj1, points):
412 return False
413 return True
416 def _DistSq(a, b, points):
417 """Return distance squared between coords with indices a and b in points.
420 diff = Sub2(points.pos[a], points.pos[b])
421 return Dot2(diff, diff)
424 def _BorderEdges(facelist):
425 """Return a set of (u,v) where u and v are successive vertex indices
426 in some face in the list in facelist."""
428 ans = set()
429 for i in range(0, len(facelist)):
430 f = facelist[i]
431 for j in range(1, len(f)):
432 ans.add((f[j - 1], f[j]))
433 ans.add((f[-1], f[0]))
434 return ans
437 def _CDT(tris, bord, points):
438 """Tris is a list of triangles ((a,b,c), CCW-oriented indices into points)
439 Bord is a set of border edges (u,v), oriented so that tris
440 is a triangulation of the left face of the border(s).
441 Make the triangulation "Constrained Delaunay" by flipping "reversed"
442 quadrangulaterals until can flip no more.
443 Return list of triangles in new triangulation."""
445 td = _TriDict(tris)
446 re = _ReveresedEdges(tris, td, bord, points)
447 ts = set(tris)
448 # reverse the reversed edges until done.
449 # reversing and edge adds new edges, which may or
450 # may not be reversed or border edges, to re for
451 # consideration, but the process will stop eventually.
452 while len(re) > 0:
453 (a, b) = e = re.pop()
454 if e in bord or not _IsReversed(e, td, points):
455 continue
456 # rotate e in quad adbc to get other diagonal
457 erev = (b, a)
458 tl = td.get(e)
459 tr = td.get(erev)
460 if not tl or not tr:
461 continue # shouldn't happen
462 c = _OtherVert(tl, a, b)
463 d = _OtherVert(tr, a, b)
464 if c is None or d is None:
465 continue # shouldn't happen
466 newt1 = (c, d, b)
467 newt2 = (c, a, d)
468 del td[e]
469 del td[erev]
470 td[(c, d)] = newt1
471 td[(d, b)] = newt1
472 td[(b, c)] = newt1
473 td[(d, c)] = newt2
474 td[(c, a)] = newt2
475 td[(a, d)] = newt2
476 if tl in ts:
477 ts.remove(tl)
478 if tr in ts:
479 ts.remove(tr)
480 ts.add(newt1)
481 ts.add(newt2)
482 re.extend([(d, b), (b, c), (c, a), (a, d)])
483 return list(ts)
486 def _TriDict(tris):
487 """tris is a list of triangles (a,b,c), CCW-oriented indices.
488 Return dict mapping all edges in the triangles to the containing
489 triangle list."""
491 ans = dict()
492 for i in range(0, len(tris)):
493 (a, b, c) = t = tris[i]
494 ans[(a, b)] = t
495 ans[(b, c)] = t
496 ans[(c, a)] = t
497 return ans
500 def _ReveresedEdges(tris, td, bord, points):
501 """Return list of reversed edges in tris.
502 Only want edges not in bord, and only need one representative
503 of (u,v)/(v,u), so choose the one with u < v.
504 td is dictionary from _TriDict, and is used to find left and right
505 triangles of edges."""
507 ans = []
508 for i in range(0, len(tris)):
509 (a, b, c) = tris[i]
510 for e in [(a, b), (b, c), (c, a)]:
511 if e in bord:
512 continue
513 (u, v) = e
514 if u < v:
515 if _IsReversed(e, td, points):
516 ans.append(e)
517 return ans
520 def _IsReversed(e, td, points):
521 """If e=(a,b) is a non-border edge, with left-face triangle tl and
522 right-face triangle tr, then it is 'reversed' if the circle through
523 a, b, and (say) the other vertex of tl contains the other vertex of tr.
524 td is a _TriDict, for finding triangles containing edges, and points
525 gives the coordinates for vertex indices used in edges."""
527 tl = td.get(e)
528 if not tl:
529 return False
530 (a, b) = e
531 tr = td.get((b, a))
532 if not tr:
533 return False
534 c = _OtherVert(tl, a, b)
535 d = _OtherVert(tr, a, b)
536 if c is None or d is None:
537 return False
538 return InCircle(a, b, c, d, points)
541 def _OtherVert(tri, a, b):
542 """tri should be a tuple of 3 vertex indices, two of which are a and b.
543 Return the third index, or None if all vertices are a or b"""
545 for v in tri:
546 if v != a and v != b:
547 return v
548 return None
551 def _ClassifyAngles(face, n, points):
552 """Return vector of anglekinds of the Angle around each point in face."""
554 return [_AngleKind(face[(i - 1) % n], face[i], face[(i + 1) % n], points) \
555 for i in list(range(0, n))]
558 def _AngleKind(a, b, c, points):
559 """Return one of the Ang... constants to classify Angle formed by ABC,
560 in a counterclockwise traversal of a face,
561 where a, b, c are indices into points."""
563 if Ccw(a, b, c, points):
564 return Angconvex
565 elif Ccw(a, c, b, points):
566 return Angreflex
567 else:
568 vb = points.pos[b]
569 udotv = Dot2(Sub2(vb, points.pos[a]), Sub2(points.pos[c], vb))
570 if udotv > 0.0:
571 return Angtangential
572 else:
573 return Ang0 # to fix: return Ang360 if "inside" spur
576 def _Quandrangulate(tris, bord, points):
577 """Tris is list of triangles, forming a triangulation of region whose
578 border edges are in set bord.
579 Combine adjacent triangles to make quads, trying for "good" quads where
580 possible. Some triangles will probably remain uncombined"""
582 (er, td) = _ERGraph(tris, bord, points)
583 if len(er) == 0:
584 return tris
585 if len(er) > GTHRESH:
586 match = _GreedyMatch(er)
587 else:
588 match = _MaxMatch(er)
589 return _RemoveEdges(tris, match)
592 def _RemoveEdges(tris, match):
593 """tris is list of triangles.
594 er is as returned from _MaxMatch or _GreedyMatch.
596 Return list of (A,D,B,C) resulting from deleting edge (A,B) causing a merge
597 of two triangles; append to that list the remaining unmatched triangles."""
599 ans = []
600 triset = set(tris)
601 while len(match) > 0:
602 (_, e, tl, tr) = match.pop()
603 (a, b) = e
604 if tl in triset:
605 triset.remove(tl)
606 if tr in triset:
607 triset.remove(tr)
608 c = _OtherVert(tl, a, b)
609 d = _OtherVert(tr, a, b)
610 if c is None or d is None:
611 continue
612 ans.append((a, d, b, c))
613 return ans + list(triset)
616 def _ERGraph(tris, bord, points):
617 """Make an 'Edge Removal Graph'.
619 Given a list of triangles, the 'Edge Removal Graph' is a graph whose
620 nodes are the triangles (think of a point in the center of them),
621 and whose edges go between adjacent triangles (they share a non-border
622 edge), such that it would be possible to remove the shared edge
623 and form a convex quadrilateral. Forming a quadrilateralization
624 is then a matter of finding a matching (set of edges that don't
625 share a vertex - remember, these are the 'face' vertices).
626 For better quadrilaterlization, we'll make the Edge Removal Graph
627 edges have weights, with higher weights going to the edges that
628 are more desirable to remove. Then we want a maximum weight matching
629 in this graph.
631 We'll return the graph in a kind of implicit form, using edges of
632 the original triangles as a proxy for the edges between the faces
633 (i.e., the edge of the triangle is the shared edge). We'll arbitrarily
634 pick the triangle graph edge with lower-index start vertex.
635 Also, to aid in traversing the implicit graph, we'll keep the left
636 and right triangle triples with edge 'ER edge'.
637 Finally, since we calculate it anyway, we'll return a dictionary
638 mapping edges of the triangles to the triangle triples they're in.
640 Args:
641 tris: list of (int, int, int) giving a triple of vertex indices for
642 triangles, assumed CCW oriented
643 bord: set of (int, int) giving vertex indices for border edges
644 points: geom.Points - for mapping vertex indices to coords
645 Returns:
646 (list of (weight,e,tl,tr), dict)
647 where edge e=(a,b) is non-border edge
648 with left face tl and right face tr (each a triple (i,j,k)),
649 where removing the edge would form an "OK" quad (no concave angles),
650 with weight representing the desirability of removing the edge
651 The dict maps int pairs (a,b) to int triples (i,j,k), that is,
652 mapping edges to their containing triangles.
655 td = _TriDict(tris)
656 dd = _DegreeDict(tris)
657 ans = []
658 ctris = tris[:] # copy, so argument not affected
659 while len(ctris) > 0:
660 (i, j, k) = tl = ctris.pop()
661 for e in [(i, j), (j, k), (k, i)]:
662 if e in bord:
663 continue
664 (a, b) = e
665 # just consider one of (a,b) and (b,a), to avoid dups
666 if a > b:
667 continue
668 erev = (b, a)
669 tr = td.get(erev)
670 if not tr:
671 continue
672 c = _OtherVert(tl, a, b)
673 d = _OtherVert(tr, a, b)
674 if c is None or d is None:
675 continue
676 # calculate amax, the max of the new angles that would
677 # be formed at a and b if tl and tr were combined
678 amax = max(Angle(c, a, b, points) + Angle(d, a, b, points),
679 Angle(c, b, a, points) + Angle(d, b, a, points))
680 if amax > 180.0:
681 continue
682 weight = ANGFAC * (180.0 - amax) + DEGFAC * (dd[a] + dd[b])
683 ans.append((weight, e, tl, tr))
684 return (ans, td)
687 def _GreedyMatch(er):
688 """er is list of (weight,e,tl,tr).
690 Find maximal set so that each triangle appears in at most
691 one member of set"""
693 # sort in order of decreasing weight
694 er.sort(key=lambda v: v[0], reverse=True)
695 match = set()
696 ans = []
697 while len(er) > 0:
698 (_, _, tl, tr) = q = er.pop()
699 if tl not in match and tr not in match:
700 match.add(tl)
701 match.add(tr)
702 ans.append(q)
703 return ans
706 def _MaxMatch(er):
707 """Like _GreedyMatch, but use divide and conquer to find best possible set.
709 Args:
710 er: list of (weight,e,tl,tr) - see _ERGraph
711 Returns:
712 list that is a subset of er giving a maximum weight match
715 (ans, _) = _DCMatch(er)
716 return ans
719 def _DCMatch(er):
720 """Recursive helper for _MaxMatch.
722 Divide and Conquer approach to finding max weight matching.
723 If we're lucky, there's an edge in er that separates the edge removal
724 graph into (at least) two separate components. Then the max weight
725 is either one that includes that edge or excludes it - and we can
726 use a recursive call to _DCMatch to handle each component separately
727 on what remains of the graph after including/excluding the separating edge.
728 If we're not lucky, we fall back on _EMatch (see below).
730 Args:
731 er: list of (weight, e, tl, tr) (see _ERGraph)
732 Returns:
733 (list of (weight, e, tl, tr), float) - the subset forming a maximum
734 matching, and the total weight of the match.
737 if not er:
738 return ([], 0.0)
739 if len(er) == 1:
740 return (er, er[0][0])
741 match = []
742 matchw = 0.0
743 for i in range(0, len(er)):
744 (nc, comp) = _FindComponents(er, i)
745 if nc == 1:
746 # er[i] doesn't separate er
747 continue
748 (wi, _, tl, tr) = er[i]
749 if comp[tl] != comp[tr]:
750 # case 1: er separates graph
751 # compare the matches that include er[i] versus
752 # those that exclude it
753 (a, b) = _PartitionComps(er, comp, i, comp[tl], comp[tr])
754 ax = _CopyExcluding(a, tl, tr)
755 bx = _CopyExcluding(b, tl, tr)
756 (axmatch, wax) = _DCMatch(ax)
757 (bxmatch, wbx) = _DCMatch(bx)
758 if len(ax) == len(a):
759 wa = wax
760 amatch = axmatch
761 else:
762 (amatch, wa) = _DCMatch(a)
763 if len(bx) == len(b):
764 wb = wbx
765 bmatch = bxmatch
766 else:
767 (bmatch, wb) = _DCMatch(b)
768 w = wa + wb
769 wx = wax + wbx + wi
770 if w > wx:
771 match = amatch + bmatch
772 matchw = w
773 else:
774 match = [er[i]] + axmatch + bxmatch
775 matchw = wx
776 else:
777 # case 2: er not needed to separate graph
778 (a, b) = _PartitionComps(er, comp, -1, 0, 0)
779 (amatch, wa) = _DCMatch(a)
780 (bmatch, wb) = _DCMatch(b)
781 match = amatch + bmatch
782 matchw = wa + wb
783 if match:
784 break
785 if not match:
786 return _EMatch(er)
787 return (match, matchw)
790 def _EMatch(er):
791 """Exhaustive match helper for _MaxMatch.
793 This is the case when we were unable to find a single edge
794 separating the edge removal graph into two components.
795 So pick a single edge and try _DCMatch on the two cases of
796 including or excluding that edge. We may be lucky in these
797 subcases (say, if the graph is currently a simple cycle, so
798 only needs one more edge after the one we pick here to separate
799 it into components). Otherwise, we'll end up back in _EMatch
800 again, and the worse case will be exponential.
802 Pick a random edge rather than say, the first, to hopefully
803 avoid some pathological cases.
805 Args:
806 er: list of (weight, el, tl, tr) (see _ERGraph)
807 Returns:
808 (list of (weight, e, tl, tr), float) - the subset forming a maximum
809 matching, and the total weight of the match.
812 if not er:
813 return ([], 0.0)
814 if len(er) == 1:
815 return (er, er[1][1])
816 i = random.randint(0, len(er) - 1)
817 eri = (wi, _, tl, tr) = er[i]
818 # case a: include eri. exclude other edges that touch tl or tr
819 a = _CopyExcluding(er, tl, tr)
820 a.append(eri)
821 (amatch, wa) = _DCMatch(a)
822 wa += wi
823 if len(a) == len(er) - 1:
824 # if a excludes only eri, then er didn't touch anything else
825 # in the graph, and the best match will always include er
826 # and we can skip the call for case b
827 wb = -1.0
828 bmatch = []
829 else:
830 b = er[:i] + er[i + 1:]
831 (bmatch, wb) = _DCMatch(b)
832 if wa > wb:
833 match = amatch
834 match.append(eri)
835 matchw = wa
836 else:
837 match = bmatch
838 matchw = wb
839 return (match, matchw)
842 def _FindComponents(er, excepti):
843 """Find connected components induced by edges, excluding one edge.
845 Args:
846 er: list of (weight, el, tl, tr) (see _ERGraph)
847 excepti: index in er of edge to be excluded
848 Returns:
849 (int, dict): int is number of connected components found,
850 dict maps triangle triple ->
851 connected component index (starting at 1)
854 ncomps = 0
855 comp = dict()
856 for i in range(0, len(er)):
857 (_, _, tl, tr) = er[i]
858 for t in [tl, tr]:
859 if t not in comp:
860 ncomps += 1
861 _FCVisit(er, excepti, comp, t, ncomps)
862 return (ncomps, comp)
865 def _FCVisit(er, excepti, comp, t, compnum):
866 """Helper for _FindComponents depth-first-search."""
868 comp[t] = compnum
869 for i in range(0, len(er)):
870 if i == excepti:
871 continue
872 (_, _, tl, tr) = er[i]
873 if tl == t or tr == t:
874 s = tl
875 if s == t:
876 s = tr
877 if s not in comp:
878 _FCVisit(er, excepti, comp, s, compnum)
881 def _PartitionComps(er, comp, excepti, compa, compb):
882 """Partition the edges of er by component number, into two lists.
884 Generally, put odd components into first list and even into second,
885 except that component compa goes in the first and compb goes in the second,
886 and we ignore edge er[excepti].
888 Args:
889 er: list of (weight, el, tl, tr) (see _ERGraph)
890 comp: dict - mapping triangle triple -> connected component index
891 excepti: int - index in er to ignore (unless excepti==-1)
892 compa: int - component to go in first list of answer (unless 0)
893 compb: int - component to go in second list of answer (unless 0)
894 Returns:
895 (list, list) - a partition of er according to above rules
898 parta = []
899 partb = []
900 for i in range(0, len(er)):
902 if i == excepti:
903 continue
904 tl = er[i][2]
905 c = comp[tl]
906 if c == compa or (c != compb and (c & 1) == 1):
907 parta.append(er[i])
908 else:
909 partb.append(er[i])
910 return (parta, partb)
913 def _CopyExcluding(er, s, t):
914 """Return a copy of er, excluding all those involving triangles s and t.
916 Args:
917 er: list of (weight, e, tl, tr) - see _ERGraph
918 s: 3-tuple of int - a triangle
919 t: 3-tuple of int - a triangle
920 Returns:
921 Copy of er excluding those with tl or tr == s or t
924 ans = []
925 for e in er:
926 (_, _, tl, tr) = e
927 if tl == s or tr == s or tl == t or tr == t:
928 continue
929 ans.append(e)
930 return ans
933 def _DegreeDict(tris):
934 """Return a dictionary mapping vertices in tris to the number of triangles
935 that they are touch."""
937 ans = dict()
938 for t in tris:
939 for v in t:
940 if v in ans:
941 ans[v] = ans[v] + 1
942 else:
943 ans[v] = 1
944 return ans
947 def PolygonPlane(face, points):
948 """Return a Normal vector for the face with 3d coords given by indexing
949 into points."""
951 if len(face) < 3:
952 return (0.0, 0.0, 1.0) # arbitrary, we really have no idea
953 else:
954 coords = [points.pos[i] for i in face]
955 return Normal(coords)
958 # This Normal appears to be on the CCW-traversing side of a polygon
959 def Normal(coords):
960 """Return an average Normal vector for the point list, 3d coords."""
962 if len(coords) < 3:
963 return (0.0, 0.0, 1.0) # arbitrary
965 (ax, ay, az) = coords[0]
966 (bx, by, bz) = coords[1]
967 (cx, cy, cz) = coords[2]
969 if len(coords) == 3:
970 sx = (ay - by) * (az + bz) + \
971 (by - cy) * (bz + cz) + \
972 (cy - ay) * (cz + az)
973 sy = (az - bz) * (ax + bx) + \
974 (bz - cz) * (bx + cx) + \
975 (cz - az) * (cx + ax)
976 sz = (ax - bx) * (by + by) + \
977 (bx - cx) * (by + cy) + \
978 (cx - ax) * (cy + ay)
979 return Norm3(sx, sy, sz)
980 else:
981 sx = (ay - by) * (az + bz) + (by - cy) * (bz + cz)
982 sy = (az - bz) * (ax + bx) + (bz - cz) * (bx + cx)
983 sz = (ax - bx) * (ay + by) + (bx - cx) * (by + cy)
984 return _NormalAux(coords[3:], coords[0], sx, sy, sz)
987 def _NormalAux(rest, first, sx, sy, sz):
988 (ax, ay, az) = rest[0]
989 if len(rest) == 1:
990 (bx, by, bz) = first
991 else:
992 (bx, by, bz) = rest[1]
993 nx = sx + (ay - by) * (az + bz)
994 ny = sy + (az - bz) * (ax + bx)
995 nz = sz + (ax - bx) * (ay + by)
996 if len(rest) == 1:
997 return Norm3(nx, ny, nz)
998 else:
999 return _NormalAux(rest[1:], first, nx, ny, nz)
1002 def Norm3(x, y, z):
1003 """Return vector (x,y,z) normalized by dividing by squared length.
1004 Return (0.0, 0.0, 1.0) if the result is undefined."""
1005 sqrlen = x * x + y * y + z * z
1006 if sqrlen < 1e-100:
1007 return (0.0, 0.0, 1.0)
1008 else:
1009 try:
1010 d = sqrt(sqrlen)
1011 return (x / d, y / d, z / d)
1012 except:
1013 return (0.0, 0.0, 1.0)
1016 # We're using right-hand coord system, where
1017 # forefinger=x, middle=y, thumb=z on right hand.
1018 # Then, e.g., (1,0,0) x (0,1,0) = (0,0,1)
1019 def Cross3(a, b):
1020 """Return the cross product of two vectors, a x b."""
1022 (ax, ay, az) = a
1023 (bx, by, bz) = b
1024 return (ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx)
1027 def Dot2(a, b):
1028 """Return the dot product of two 2d vectors, a . b."""
1030 return a[0] * b[0] + a[1] * b[1]
1033 def Perp2(a, b):
1034 """Return a sort of 2d cross product."""
1036 return a[0] * b[1] - a[1] * b[0]
1039 def Sub2(a, b):
1040 """Return difference of 2d vectors, a-b."""
1042 return (a[0] - b[0], a[1] - b[1])
1045 def Add2(a, b):
1046 """Return the sum of 2d vectors, a+b."""
1048 return (a[0] + b[0], a[1] + b[1])
1051 def Length2(v):
1052 """Return length of vector v=(x,y)."""
1054 return hypot(v[0], v[1])
1057 def LinInterp2(a, b, alpha):
1058 """Return the point alpha of the way from a to b."""
1060 beta = 1 - alpha
1061 return (beta * a[0] + alpha * b[0], beta * a[1] + alpha * b[1])
1064 def Normalized2(p):
1065 """Return vector p normlized by dividing by its squared length.
1066 Return (0.0, 1.0) if the result is undefined."""
1068 (x, y) = p
1069 sqrlen = x * x + y * y
1070 if sqrlen < 1e-100:
1071 return (0.0, 1.0)
1072 else:
1073 try:
1074 d = sqrt(sqrlen)
1075 return (x / d, y / d)
1076 except:
1077 return (0.0, 1.0)
1080 def Angle(a, b, c, points):
1081 """Return Angle abc in degrees, in range [0,180),
1082 where a,b,c are indices into points."""
1084 u = Sub2(points.pos[c], points.pos[b])
1085 v = Sub2(points.pos[a], points.pos[b])
1086 n1 = Length2(u)
1087 n2 = Length2(v)
1088 if n1 == 0.0 or n2 == 0.0:
1089 return 0.0
1090 else:
1091 costheta = Dot2(u, v) / (n1 * n2)
1092 if costheta > 1.0:
1093 costheta = 1.0
1094 if costheta < - 1.0:
1095 costheta = - 1.0
1096 return math.acos(costheta) * 180.0 / math.pi
1099 def SegsIntersect(ixa, ixb, ixc, ixd, points):
1100 """Return true if segment AB intersects CD,
1101 false if they just touch. ixa, ixb, ixc, ixd are indices
1102 into points."""
1104 a = points.pos[ixa]
1105 b = points.pos[ixb]
1106 c = points.pos[ixc]
1107 d = points.pos[ixd]
1108 u = Sub2(b, a)
1109 v = Sub2(d, c)
1110 w = Sub2(a, c)
1111 pp = Perp2(u, v)
1112 if abs(pp) > TOL:
1113 si = Perp2(v, w) / pp
1114 ti = Perp2(u, w) / pp
1115 return 0.0 < si < 1.0 and 0.0 < ti < 1.0
1116 else:
1117 # parallel or overlapping
1118 if Dot2(u, u) == 0.0 or Dot2(v, v) == 0.0:
1119 return False
1120 else:
1121 pp2 = Perp2(w, v)
1122 if abs(pp2) > TOL:
1123 return False # parallel, not collinear
1124 z = Sub2(b, c)
1125 (vx, vy) = v
1126 (wx, wy) = w
1127 (zx, zy) = z
1128 if vx == 0.0:
1129 (t0, t1) = (wy / vy, zy / vy)
1130 else:
1131 (t0, t1) = (wx / vx, zx / vx)
1132 return 0.0 < t0 < 1.0 and 0.0 < t1 < 1.0
1135 def Ccw(a, b, c, points):
1136 """Return true if ABC is a counterclockwise-oriented triangle,
1137 where a, b, and c are indices into points.
1138 Returns false if not, or if colinear within TOL."""
1140 (ax, ay) = (points.pos[a][0], points.pos[a][1])
1141 (bx, by) = (points.pos[b][0], points.pos[b][1])
1142 (cx, cy) = (points.pos[c][0], points.pos[c][1])
1143 d = ax * by - bx * ay - ax * cy + cx * ay + bx * cy - cx * by
1144 return d > TOL
1147 def InCircle(a, b, c, d, points):
1148 """Return true if circle through points with indices a, b, c
1149 contains point with index d (indices into points).
1150 Except: if ABC forms a counterclockwise oriented triangle
1151 then the test is reversed: return true if d is outside the circle.
1152 Will get false, no matter what orientation, if d is cocircular, with TOL^2.
1153 | xa ya xa^2+ya^2 1 |
1154 | xb yb xb^2+yb^2 1 | > 0
1155 | xc yc xc^2+yc^2 1 |
1156 | xd yd xd^2+yd^2 1 |
1159 (xa, ya, za) = _Icc(points.pos[a])
1160 (xb, yb, zb) = _Icc(points.pos[b])
1161 (xc, yc, zc) = _Icc(points.pos[c])
1162 (xd, yd, zd) = _Icc(points.pos[d])
1163 det = xa * (yb * zc - yc * zb - yb * zd + yd * zb + yc * zd - yd * zc) \
1164 - xb * (ya * zc - yc * za - ya * zd + yd * za + yc * zd - yd * zc) \
1165 + xc * (ya * zb - yb * za - ya * zd + yd * za + yb * zd - yd * zb) \
1166 - xd * (ya * zb - yb * za - ya * zc + yc * za + yb * zc - yc * zb)
1167 return det > TOL * TOL
1170 def _Icc(p):
1171 (x, y) = (p[0], p[1])
1172 return (x, y, x * x + y * y)