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 #####
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
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.
53 face: list of int - indices in points, assumed CCW-oriented
54 points: geom.Points - holds coordinates for vertices
56 list of (int, int, int) - 3-tuples are CCW-oriented vertices of
57 triangles making up the triangulation
62 tris
= EarChopTriFace(face
, points
)
63 bord
= _BorderEdges([face
])
64 triscdt
= _CDT(tris
, bord
, points
)
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
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
81 list of (int, int, int) - 3-tuples are CCW-oriented vertices of
82 triangles making up the triangulation
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
)
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.
103 face: list of int - indices in points, assumed CCW-oriented
104 points: geom.Points - holds coordinates for vertices
106 list of 3-tuples or 4-tuples of ints - CCW-oriented vertices of
107 quadrilaterals and triangles making up the quadrangulation.
112 tris
= EarChopTriFace(face
, points
)
113 bord
= _BorderEdges([face
])
114 triscdt
= _CDT(tris
, bord
, points
)
115 qs
= _Quandrangulate(triscdt
, bord
, points
)
119 def QuadrangulateFaceWithHoles(face
, holes
, points
):
120 """Like QuadrangulateFace, but with holes inside the faces.
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
128 list of 3-tuples or 4-tuples of ints - CCW-oriented vertices of
129 quadrilaterals and triangles making up the quadrangulation.
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
)
144 def _SortFace(face
, points
):
145 """Rotate face so leftmost vertex is first, where face is
146 list of indices in points."""
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
]:
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
)
175 i
= _FindEar(face
, n
, start
, incr
, points
)
176 vm1
= face
[(i
- 1) % n
]
178 v1
= face
[(i
+ 1) % n
]
179 face
= _ChopEar(face
, i
)
186 ans
.append((vm1
, v0
, v1
))
187 ans
.append(tuple(face
))
191 def _GetLeastIndex(face
, points
):
192 """Return index of coordinate that is leftmost, lowest in face."""
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]):
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
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):
219 if _IsEar(face
, i
, n
, angk
, points
, mode
):
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!)"""
236 vm2
= face
[(i
- 2) % n
]
237 vm1
= face
[(i
- 1) % n
]
239 v1
= face
[(i
+ 1) % n
]
240 v2
= face
[(i
+ 2) % n
]
241 if vm1
== v0
or v0
== v1
:
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
)
247 return _EarCheck(face
, n
, angk
, vm1
, v0
, v1
, points
)
251 return SegsIntersect(vm2
, vm1
, v0
, v1
, points
)
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
):
270 b
= (k
== Angreflex
or k
== Ang360
) \
271 and not(fv
== vm1
or fv
== v0
or fv
== v1
)
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
)
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
):
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
)))
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
)
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)."""
341 d
= _FindDiag(face
, hv0
, points
)
342 newface
= face
[0:d
+ 1] + hole
+ [hv0
] + face
[d
:]
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)
355 if len(lefthole
) == 0:
356 return (lefthole
, lefti
)
358 for i
in range(1, len(holes
)):
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"""
380 for mode
in range(0, 3):
381 for i
in range(0, len(face
)):
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
)
387 if _IsDiag(i
, v
, hv
, face
, points
) or mode
== 2:
388 (besti
, bestdist
) = (i
, dist
)
390 break # found one, so don't need other modes
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).
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
):
408 for j
in range(0, n
):
410 vj1
= face
[(j
+ 1) % n
]
411 if SegsIntersect(v
, hv
, vj
, vj1
, points
):
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."""
429 for i
in range(0, len(facelist
)):
431 for j
in range(1, len(f
)):
432 ans
.add((f
[j
- 1], f
[j
]))
433 ans
.add((f
[-1], f
[0]))
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."""
446 re
= _ReveresedEdges(tris
, td
, bord
, points
)
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.
453 (a
, b
) = e
= re
.pop()
454 if e
in bord
or not _IsReversed(e
, td
, points
):
456 # rotate e in quad adbc to get other diagonal
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
482 re
.extend([(d
, b
), (b
, c
), (c
, a
), (a
, d
)])
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
492 for i
in range(0, len(tris
)):
493 (a
, b
, c
) = t
= tris
[i
]
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."""
508 for i
in range(0, len(tris
)):
510 for e
in [(a
, b
), (b
, c
), (c
, a
)]:
515 if _IsReversed(e
, td
, points
):
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 containts 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."""
534 c
= _OtherVert(tl
, a
, b
)
535 d
= _OtherVert(tr
, a
, b
)
536 if c
is None or d
is None:
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"""
546 if v
!= a
and v
!= b
:
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
):
565 elif Ccw(a
, c
, b
, points
):
569 udotv
= Dot2(Sub2(vb
, points
.pos
[a
]), Sub2(points
.pos
[c
], vb
))
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
)
585 if len(er
) > GTHRESH
:
586 match
= _GreedyMatch(er
)
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."""
601 while len(match
) > 0:
602 (_
, e
, tl
, tr
) = match
.pop()
608 c
= _OtherVert(tl
, a
, b
)
609 d
= _OtherVert(tr
, a
, b
)
610 if c
is None or d
is None:
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
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.
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
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.
656 dd
= _DegreeDict(tris
)
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
)]:
665 # just consider one of (a,b) and (b,a), to avoid dups
672 c
= _OtherVert(tl
, a
, b
)
673 d
= _OtherVert(tr
, a
, b
)
674 if c
is None or d
is None:
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
))
682 weight
= ANGFAC
* (180.0 - amax
) + DEGFAC
* (dd
[a
] + dd
[b
])
683 ans
.append((weight
, e
, tl
, tr
))
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
693 # sort in order of decreasing weight
694 er
.sort(key
=lambda v
: v
[0], reverse
=True)
698 (_
, _
, tl
, tr
) = q
= er
.pop()
699 if tl
not in match
and tr
not in match
:
707 """Like _GreedyMatch, but use divide and conquer to find best possible set.
710 er: list of (weight,e,tl,tr) - see _ERGraph
712 list that is a subset of er giving a maximum weight match
715 (ans
, _
) = _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).
731 er: list of (weight, e, tl, tr) (see _ERGraph)
733 (list of (weight, e, tl, tr), float) - the subset forming a maximum
734 matching, and the total weight of the match.
740 return (er
, er
[0][0])
743 for i
in range(0, len(er
)):
744 (nc
, comp
) = _FindComponents(er
, i
)
746 # er[i] doesn't separate er
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
):
762 (amatch
, wa
) = _DCMatch(a
)
763 if len(bx
) == len(b
):
767 (bmatch
, wb
) = _DCMatch(b
)
771 match
= amatch
+ bmatch
774 match
= [er
[i
]] + axmatch
+ bxmatch
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
787 return (match
, matchw
)
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.
806 er: list of (weight, el, tl, tr) (see _ERGraph)
808 (list of (weight, e, tl, tr), float) - the subset forming a maximum
809 matching, and the total weight of the match.
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. exlude other edges that touch tl or tr
819 a
= _CopyExcluding(er
, tl
, tr
)
821 (amatch
, wa
) = _DCMatch(a
)
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
830 b
= er
[:i
] + er
[i
+ 1:]
831 (bmatch
, wb
) = _DCMatch(b
)
839 return (match
, matchw
)
842 def _FindComponents(er
, excepti
):
843 """Find connected components induced by edges, excluding one edge.
846 er: list of (weight, el, tl, tr) (see _ERGraph)
847 excepti: index in er of edge to be excluded
849 (int, dict): int is number of connected components found,
850 dict maps triangle triple ->
851 connected component index (starting at 1)
856 for i
in range(0, len(er
)):
857 (_
, _
, tl
, tr
) = er
[i
]
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."""
869 for i
in range(0, len(er
)):
872 (_
, _
, tl
, tr
) = er
[i
]
873 if tl
== t
or tr
== t
:
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].
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)
895 (list, list) - a partition of er according to above rules
900 for i
in range(0, len(er
)):
906 if c
== compa
or (c
!= compb
and (c
& 1) == 1):
910 return (parta
, partb
)
913 def _CopyExcluding(er
, s
, t
):
914 """Return a copy of er, excluding all those involving triangles s and t.
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
921 Copy of er excluding those with tl or tr == s or t
927 if tl
== s
or tr
== s
or tl
== t
or tr
== t
:
933 def _DegreeDict(tris
):
934 """Return a dictionary mapping vertices in tris to the number of triangles
935 that they are touch."""
947 def PolygonPlane(face
, points
):
948 """Return a Normal vector for the face with 3d coords given by indexing
952 return (0.0, 0.0, 1.0) # arbitrary, we really have no idea
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
960 """Return an average Normal vector for the point list, 3d coords."""
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]
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
)
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]
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
)
997 return Norm3(nx
, ny
, nz
)
999 return _NormalAux(rest
[1:], first
, nx
, ny
, nz
)
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
1007 return (0.0, 0.0, 1.0)
1011 return (x
/ d
, y
/ d
, z
/ d
)
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)
1020 """Return the cross product of two vectors, a x b."""
1024 return (ay
* bz
- az
* by
, az
* bx
- ax
* bz
, ax
* by
- ay
* bx
)
1028 """Return the dot product of two 2d vectors, a . b."""
1030 return a
[0] * b
[0] + a
[1] * b
[1]
1034 """Return a sort of 2d cross product."""
1036 return a
[0] * b
[1] - a
[1] * b
[0]
1040 """Return difference of 2d vectors, a-b."""
1042 return (a
[0] - b
[0], a
[1] - b
[1])
1046 """Return the sum of 2d vectors, a+b."""
1048 return (a
[0] + b
[0], a
[1] + b
[1])
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."""
1061 return (beta
* a
[0] + alpha
* b
[0], beta
* a
[1] + alpha
* b
[1])
1065 """Return vector p normlized by dividing by its squared length.
1066 Return (0.0, 1.0) if the result is undefined."""
1069 sqrlen
= x
* x
+ y
* y
1075 return (x
/ d
, y
/ d
)
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
])
1088 if n1
== 0.0 or n2
== 0.0:
1091 costheta
= Dot2(u
, v
) / (n1
* n2
)
1094 if 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
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
1117 # parallel or overlapping
1118 if Dot2(u
, u
) == 0.0 or Dot2(v
, v
) == 0.0:
1123 return False # parallel, not collinear
1129 (t0
, t1
) = (wy
/ vy
, zy
/ vy
)
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
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
1171 (x
, y
) = (p
[0], p
[1])
1172 return (x
, y
, x
* x
+ y
* y
)