1 # SPDX-FileCopyrightText: 2011-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
9 from math
import sqrt
, hypot
11 # Points are 3-tuples or 2-tuples of reals: (x,y,z) or (x,y)
12 # Faces are lists of integers (vertex indices into coord lists)
13 # After triangulation/quadrangulation, the tris and quads will
14 # be tuples instead of lists.
15 # Vmaps are lists taking vertex index -> Point
17 TOL
= 1e-7 # a tolerance for fuzzy equality
18 GTHRESH
= 75 # threshold above which use greedy to _Quandrangulate
19 ANGFAC
= 1.0 # weighting for angles in quad goodness measure
20 DEGFAC
= 10.0 # weighting for degree in quad goodness measure
22 # Angle kind constants
30 def TriangulateFace(face
, points
):
31 """Triangulate the given face.
33 Uses an easy triangulation first, followed by a constrained delauney
34 triangulation to get better shaped triangles.
37 face: list of int - indices in points, assumed CCW-oriented
38 points: geom.Points - holds coordinates for vertices
40 list of (int, int, int) - 3-tuples are CCW-oriented vertices of
41 triangles making up the triangulation
46 tris
= EarChopTriFace(face
, points
)
47 bord
= _BorderEdges([face
])
48 triscdt
= _CDT(tris
, bord
, points
)
52 def TriangulateFaceWithHoles(face
, holes
, points
):
53 """Like TriangulateFace, but with holes inside the face.
55 Works by making one complex polygon that has segments to
56 and from the holes ("islands"), and then using the same method
60 face: list of int - indices in points, assumed CCW-oriented
61 holes: list of list of int - each sublist is like face
62 but CW-oriented and assumed to be inside face
63 points: geom.Points - holds coordinates for vertices
65 list of (int, int, int) - 3-tuples are CCW-oriented vertices of
66 triangles making up the triangulation
70 return TriangulateFace(face
, points
)
71 allfaces
= [face
] + holes
72 sholes
= [_SortFace(h
, points
) for h
in holes
]
73 joinedface
= _JoinIslands(face
, sholes
, points
)
74 tris
= EarChopTriFace(joinedface
, points
)
75 bord
= _BorderEdges(allfaces
)
76 triscdt
= _CDT(tris
, bord
, points
)
80 def QuadrangulateFace(face
, points
):
81 """Quadrangulate the face (subdivide into convex quads and tris).
83 Like TriangulateFace, but after triangulating, join as many pairs
84 of triangles as possible into convex quadrilaterals.
87 face: list of int - indices in points, assumed CCW-oriented
88 points: geom.Points - holds coordinates for vertices
90 list of 3-tuples or 4-tuples of ints - CCW-oriented vertices of
91 quadrilaterals and triangles making up the quadrangulation.
96 tris
= EarChopTriFace(face
, points
)
97 bord
= _BorderEdges([face
])
98 triscdt
= _CDT(tris
, bord
, points
)
99 qs
= _Quandrangulate(triscdt
, bord
, points
)
103 def QuadrangulateFaceWithHoles(face
, holes
, points
):
104 """Like QuadrangulateFace, but with holes inside the faces.
107 face: list of int - indices in points, assumed CCW-oriented
108 holes: list of list of int - each sublist is like face
109 but CW-oriented and assumed to be inside face
110 points: geom.Points - holds coordinates for vertices
112 list of 3-tuples or 4-tuples of ints - CCW-oriented vertices of
113 quadrilaterals and triangles making up the quadrangulation.
117 return QuadrangulateFace(face
, points
)
118 allfaces
= [face
] + holes
119 sholes
= [_SortFace(h
, points
) for h
in holes
]
120 joinedface
= _JoinIslands(face
, sholes
, points
)
121 tris
= EarChopTriFace(joinedface
, points
)
122 bord
= _BorderEdges(allfaces
)
123 triscdt
= _CDT(tris
, bord
, points
)
124 qs
= _Quandrangulate(triscdt
, bord
, points
)
128 def _SortFace(face
, points
):
129 """Rotate face so leftmost vertex is first, where face is
130 list of indices in points."""
137 for i
in range(1, n
):
138 # following comparison is lexicographic on n-tuple
139 # so sorts on x first, using lower y as tie breaker.
140 if points
.pos
[face
[i
]] < points
.pos
[leftv
]:
143 return face
[lefti
:] + face
[0:lefti
]
146 def EarChopTriFace(face
, points
):
147 """Triangulate given face, with coords given by indexing into points.
148 Return list of faces, each of which will be a triangle.
149 Use the ear-chopping method."""
151 # start with lowest coord in 2d space to try
152 # to get a pleasing uniform triangulation if starting with
153 # a regular structure (like a grid)
154 start
= _GetLeastIndex(face
, points
)
159 i
= _FindEar(face
, n
, start
, incr
, points
)
160 vm1
= face
[(i
- 1) % n
]
162 v1
= face
[(i
+ 1) % n
]
163 face
= _ChopEar(face
, i
)
170 ans
.append((vm1
, v0
, v1
))
171 ans
.append(tuple(face
))
175 def _GetLeastIndex(face
, points
):
176 """Return index of coordinate that is leftmost, lowest in face."""
179 bestpos
= points
.pos
[face
[0]]
180 for i
in range(1, len(face
)):
181 pos
= points
.pos
[face
[i
]]
182 if pos
[0] < bestpos
[0] or \
183 (pos
[0] == bestpos
[0] and pos
[1] < bestpos
[1]):
189 def _FindEar(face
, n
, start
, incr
, points
):
190 """An ear of a polygon consists of three consecutive vertices
191 v(-1), v0, v1 such that v(-1) can connect to v1 without intersecting
193 Finds an ear, starting at index 'start' and moving
194 in direction incr. (We attempt to alternate directions, to find
195 'nice' triangulations for simple convex polygons.)
196 Returns index into faces of v0 (will always find one, because
197 uses a desperation mode if fails to find one with above rule)."""
199 angk
= _ClassifyAngles(face
, n
, points
)
200 for mode
in range(0, 5):
203 if _IsEar(face
, i
, n
, angk
, points
, mode
):
207 break # try next higher desperation mode
210 def _IsEar(face
, i
, n
, angk
, points
, mode
):
211 """Return true, false depending on ear status of vertices
212 with indices i-1, i, i+1.
213 mode is amount of desperation: 0 is Normal mode,
214 mode 1 allows degenerate triangles (with repeated vertices)
215 mode 2 allows local self crossing (folded) ears
216 mode 3 allows any convex vertex (should always be one)
217 mode 4 allows anything (just to be sure loop terminates!)"""
220 vm2
= face
[(i
- 2) % n
]
221 vm1
= face
[(i
- 1) % n
]
223 v1
= face
[(i
+ 1) % n
]
224 v2
= face
[(i
+ 2) % n
]
225 if vm1
== v0
or v0
== v1
:
227 b
= (k
== Angconvex
or k
== Angtangential
or k
== Ang0
)
228 c
= _InCone(vm1
, v0
, v1
, v2
, angk
[(i
+ 1) % n
], points
) and \
229 _InCone(v1
, vm2
, vm1
, v0
, angk
[(i
- 1) % n
], points
)
231 return _EarCheck(face
, n
, angk
, vm1
, v0
, v1
, points
)
235 return SegsIntersect(vm2
, vm1
, v0
, v1
, points
)
241 def _EarCheck(face
, n
, angk
, vm1
, v0
, v1
, points
):
242 """Return True if the successive vertices vm1, v0, v1
243 forms an ear. We already know that it is not a reflex
244 Angle, and that the local cone containment is ok.
245 What remains to check is that the edge vm1-v1 doesn't
246 intersect any other edge of the face (besides vm1-v0
247 and v0-v1). Equivalently, there can't be a reflex Angle
248 inside the triangle vm1-v0-v1. (Well, there are
249 messy cases when other points of the face coincide with
250 v0 or touch various lines involved in the ear.)"""
251 for j
in range(0, n
):
254 b
= (k
== Angreflex
or k
== Ang360
) \
255 and not(fv
== vm1
or fv
== v0
or fv
== v1
)
257 # Is fv inside closure of triangle (vm1,v0,v1)?
258 c
= not(Ccw(v0
, vm1
, fv
, points
) \
259 or Ccw(vm1
, v1
, fv
, points
) \
260 or Ccw(v1
, v0
, fv
, points
))
261 fvm1
= face
[(j
- 1) % n
]
262 fv1
= face
[(j
+ 1) % n
]
263 # To try to deal with some degenerate cases,
264 # also check to see if either segment attached to fv
265 # intersects either segment of potential ear.
266 d
= SegsIntersect(fvm1
, fv
, vm1
, v0
, points
) or \
267 SegsIntersect(fvm1
, fv
, v0
, v1
, points
) or \
268 SegsIntersect(fv
, fv1
, vm1
, v0
, points
) or \
269 SegsIntersect(fv
, fv1
, v0
, v1
, points
)
275 def _ChopEar(face
, i
):
276 """Return a copy of face (of length n), omitting element i."""
278 return face
[0:i
] + face
[i
+ 1:]
281 def _InCone(vtest
, a
, b
, c
, bkind
, points
):
282 """Return true if point with index vtest is in Cone of points with
283 indices a, b, c, where Angle ABC has AngleKind Bkind.
284 The Cone is the set of points inside the left face defined by
285 segments ab and bc, disregarding all other segments of polygon for
286 purposes of inside test."""
288 if bkind
== Angreflex
or bkind
== Ang360
:
289 if _InCone(vtest
, c
, b
, a
, Angconvex
, points
):
291 return not((not(Ccw(b
, a
, vtest
, points
)) \
292 and not(Ccw(b
, vtest
, a
, points
)) \
293 and Ccw(b
, a
, vtest
, points
))
295 (not(Ccw(b
, c
, vtest
, points
)) \
296 and not(Ccw(b
, vtest
, c
, points
)) \
297 and Ccw(b
, a
, vtest
, points
)))
299 return Ccw(a
, b
, vtest
, points
) and Ccw(b
, c
, vtest
, points
)
302 def _JoinIslands(face
, holes
, points
):
303 """face is a CCW face containing the CW faces in the holes list,
304 where each hole is sorted so the leftmost-lowest vertex is first.
305 faces and holes are given as lists of indices into points.
306 The holes should be sorted by softface.
307 Add edges to make a new face that includes the holes (a Ccw traversal
308 of the new face will have the inside always on the left),
309 and return the new face."""
311 while len(holes
) > 0:
312 (hole
, holeindex
) = _LeftMostFace(holes
, points
)
313 holes
= holes
[0:holeindex
] + holes
[holeindex
+ 1:]
314 face
= _JoinIsland(face
, hole
, points
)
318 def _JoinIsland(face
, hole
, points
):
319 """Return a modified version of face that splices in the
320 vertices of hole (which should be sorted)."""
325 d
= _FindDiag(face
, hv0
, points
)
326 newface
= face
[0:d
+ 1] + hole
+ [hv0
] + face
[d
:]
330 def _LeftMostFace(holes
, points
):
331 """Return (hole,index of hole in holes) where hole has
332 the leftmost first vertex. To be able to handle empty
333 holes gracefully, call an empty hole 'leftmost'.
334 Assumes holes are sorted by softface."""
336 assert(len(holes
) > 0)
339 if len(lefthole
) == 0:
340 return (lefthole
, lefti
)
342 for i
in range(1, len(holes
)):
347 if points
.pos
[iv
] < points
.pos
[leftv
]:
348 (lefti
, lefthole
, leftv
) = (i
, ihole
, iv
)
349 return (lefthole
, lefti
)
352 def _FindDiag(face
, hv
, points
):
353 """Find a vertex in face that can see vertex hv, if possible,
354 and return the index into face of that vertex.
355 Should be able to find a diagonal that connects a vertex of face
356 left of v to hv without crossing face, but try two
357 more desperation passes after that to get SOME diagonal, even if
358 it might cross some edge somewhere.
359 First desperation pass (mode == 1): allow points right of hv.
360 Second desperation pass (mode == 2): allow crossing boundary poly"""
364 for mode
in range(0, 3):
365 for i
in range(0, len(face
)):
367 if mode
== 0 and points
.pos
[v
] > points
.pos
[hv
]:
368 continue # in mode 0, only want points left of hv
369 dist
= _DistSq(v
, hv
, points
)
371 if _IsDiag(i
, v
, hv
, face
, points
) or mode
== 2:
372 (besti
, bestdist
) = (i
, dist
)
374 break # found one, so don't need other modes
379 def _IsDiag(i
, v
, hv
, face
, points
):
380 """Return True if vertex v (at index i in face) can see vertex hv.
381 v and hv are indices into points.
382 (v, hv) is a diagonal if hv is in the cone of the Angle at index i on face
383 and no segment in face intersects (h, hv).
387 vm1
= face
[(i
- 1) % n
]
388 v1
= face
[(i
+ 1) % n
]
389 k
= _AngleKind(vm1
, v
, v1
, points
)
390 if not _InCone(hv
, vm1
, v
, v1
, k
, points
):
392 for j
in range(0, n
):
394 vj1
= face
[(j
+ 1) % n
]
395 if SegsIntersect(v
, hv
, vj
, vj1
, points
):
400 def _DistSq(a
, b
, points
):
401 """Return distance squared between coords with indices a and b in points.
404 diff
= Sub2(points
.pos
[a
], points
.pos
[b
])
405 return Dot2(diff
, diff
)
408 def _BorderEdges(facelist
):
409 """Return a set of (u,v) where u and v are successive vertex indices
410 in some face in the list in facelist."""
413 for i
in range(0, len(facelist
)):
415 for j
in range(1, len(f
)):
416 ans
.add((f
[j
- 1], f
[j
]))
417 ans
.add((f
[-1], f
[0]))
421 def _CDT(tris
, bord
, points
):
422 """Tris is a list of triangles ((a,b,c), CCW-oriented indices into points)
423 Bord is a set of border edges (u,v), oriented so that tris
424 is a triangulation of the left face of the border(s).
425 Make the triangulation "Constrained Delaunay" by flipping "reversed"
426 quadrangulaterals until can flip no more.
427 Return list of triangles in new triangulation."""
430 re
= _ReveresedEdges(tris
, td
, bord
, points
)
432 # reverse the reversed edges until done.
433 # reversing and edge adds new edges, which may or
434 # may not be reversed or border edges, to re for
435 # consideration, but the process will stop eventually.
437 (a
, b
) = e
= re
.pop()
438 if e
in bord
or not _IsReversed(e
, td
, points
):
440 # rotate e in quad adbc to get other diagonal
445 continue # shouldn't happen
446 c
= _OtherVert(tl
, a
, b
)
447 d
= _OtherVert(tr
, a
, b
)
448 if c
is None or d
is None:
449 continue # shouldn't happen
466 re
.extend([(d
, b
), (b
, c
), (c
, a
), (a
, d
)])
471 """tris is a list of triangles (a,b,c), CCW-oriented indices.
472 Return dict mapping all edges in the triangles to the containing
476 for i
in range(0, len(tris
)):
477 (a
, b
, c
) = t
= tris
[i
]
484 def _ReveresedEdges(tris
, td
, bord
, points
):
485 """Return list of reversed edges in tris.
486 Only want edges not in bord, and only need one representative
487 of (u,v)/(v,u), so choose the one with u < v.
488 td is dictionary from _TriDict, and is used to find left and right
489 triangles of edges."""
492 for i
in range(0, len(tris
)):
494 for e
in [(a
, b
), (b
, c
), (c
, a
)]:
499 if _IsReversed(e
, td
, points
):
504 def _IsReversed(e
, td
, points
):
505 """If e=(a,b) is a non-border edge, with left-face triangle tl and
506 right-face triangle tr, then it is 'reversed' if the circle through
507 a, b, and (say) the other vertex of tl contains the other vertex of tr.
508 td is a _TriDict, for finding triangles containing edges, and points
509 gives the coordinates for vertex indices used in edges."""
518 c
= _OtherVert(tl
, a
, b
)
519 d
= _OtherVert(tr
, a
, b
)
520 if c
is None or d
is None:
522 return InCircle(a
, b
, c
, d
, points
)
525 def _OtherVert(tri
, a
, b
):
526 """tri should be a tuple of 3 vertex indices, two of which are a and b.
527 Return the third index, or None if all vertices are a or b"""
530 if v
!= a
and v
!= b
:
535 def _ClassifyAngles(face
, n
, points
):
536 """Return vector of anglekinds of the Angle around each point in face."""
538 return [_AngleKind(face
[(i
- 1) % n
], face
[i
], face
[(i
+ 1) % n
], points
) \
539 for i
in list(range(0, n
))]
542 def _AngleKind(a
, b
, c
, points
):
543 """Return one of the Ang... constants to classify Angle formed by ABC,
544 in a counterclockwise traversal of a face,
545 where a, b, c are indices into points."""
547 if Ccw(a
, b
, c
, points
):
549 elif Ccw(a
, c
, b
, points
):
553 udotv
= Dot2(Sub2(vb
, points
.pos
[a
]), Sub2(points
.pos
[c
], vb
))
557 return Ang0
# to fix: return Ang360 if "inside" spur
560 def _Quandrangulate(tris
, bord
, points
):
561 """Tris is list of triangles, forming a triangulation of region whose
562 border edges are in set bord.
563 Combine adjacent triangles to make quads, trying for "good" quads where
564 possible. Some triangles will probably remain uncombined"""
566 (er
, td
) = _ERGraph(tris
, bord
, points
)
569 if len(er
) > GTHRESH
:
570 match
= _GreedyMatch(er
)
572 match
= _MaxMatch(er
)
573 return _RemoveEdges(tris
, match
)
576 def _RemoveEdges(tris
, match
):
577 """tris is list of triangles.
578 er is as returned from _MaxMatch or _GreedyMatch.
580 Return list of (A,D,B,C) resulting from deleting edge (A,B) causing a merge
581 of two triangles; append to that list the remaining unmatched triangles."""
585 while len(match
) > 0:
586 (_
, e
, tl
, tr
) = match
.pop()
592 c
= _OtherVert(tl
, a
, b
)
593 d
= _OtherVert(tr
, a
, b
)
594 if c
is None or d
is None:
596 ans
.append((a
, d
, b
, c
))
597 return ans
+ list(triset
)
600 def _ERGraph(tris
, bord
, points
):
601 """Make an 'Edge Removal Graph'.
603 Given a list of triangles, the 'Edge Removal Graph' is a graph whose
604 nodes are the triangles (think of a point in the center of them),
605 and whose edges go between adjacent triangles (they share a non-border
606 edge), such that it would be possible to remove the shared edge
607 and form a convex quadrilateral. Forming a quadrilateralization
608 is then a matter of finding a matching (set of edges that don't
609 share a vertex - remember, these are the 'face' vertices).
610 For better quadrilaterlization, we'll make the Edge Removal Graph
611 edges have weights, with higher weights going to the edges that
612 are more desirable to remove. Then we want a maximum weight matching
615 We'll return the graph in a kind of implicit form, using edges of
616 the original triangles as a proxy for the edges between the faces
617 (i.e., the edge of the triangle is the shared edge). We'll arbitrarily
618 pick the triangle graph edge with lower-index start vertex.
619 Also, to aid in traversing the implicit graph, we'll keep the left
620 and right triangle triples with edge 'ER edge'.
621 Finally, since we calculate it anyway, we'll return a dictionary
622 mapping edges of the triangles to the triangle triples they're in.
625 tris: list of (int, int, int) giving a triple of vertex indices for
626 triangles, assumed CCW oriented
627 bord: set of (int, int) giving vertex indices for border edges
628 points: geom.Points - for mapping vertex indices to coords
630 (list of (weight,e,tl,tr), dict)
631 where edge e=(a,b) is non-border edge
632 with left face tl and right face tr (each a triple (i,j,k)),
633 where removing the edge would form an "OK" quad (no concave angles),
634 with weight representing the desirability of removing the edge
635 The dict maps int pairs (a,b) to int triples (i,j,k), that is,
636 mapping edges to their containing triangles.
640 dd
= _DegreeDict(tris
)
642 ctris
= tris
[:] # copy, so argument not affected
643 while len(ctris
) > 0:
644 (i
, j
, k
) = tl
= ctris
.pop()
645 for e
in [(i
, j
), (j
, k
), (k
, i
)]:
649 # just consider one of (a,b) and (b,a), to avoid dups
656 c
= _OtherVert(tl
, a
, b
)
657 d
= _OtherVert(tr
, a
, b
)
658 if c
is None or d
is None:
660 # calculate amax, the max of the new angles that would
661 # be formed at a and b if tl and tr were combined
662 amax
= max(Angle(c
, a
, b
, points
) + Angle(d
, a
, b
, points
),
663 Angle(c
, b
, a
, points
) + Angle(d
, b
, a
, points
))
666 weight
= ANGFAC
* (180.0 - amax
) + DEGFAC
* (dd
[a
] + dd
[b
])
667 ans
.append((weight
, e
, tl
, tr
))
671 def _GreedyMatch(er
):
672 """er is list of (weight,e,tl,tr).
674 Find maximal set so that each triangle appears in at most
677 # sort in order of decreasing weight
678 er
.sort(key
=lambda v
: v
[0], reverse
=True)
682 (_
, _
, tl
, tr
) = q
= er
.pop()
683 if tl
not in match
and tr
not in match
:
691 """Like _GreedyMatch, but use divide and conquer to find best possible set.
694 er: list of (weight,e,tl,tr) - see _ERGraph
696 list that is a subset of er giving a maximum weight match
699 (ans
, _
) = _DCMatch(er
)
704 """Recursive helper for _MaxMatch.
706 Divide and Conquer approach to finding max weight matching.
707 If we're lucky, there's an edge in er that separates the edge removal
708 graph into (at least) two separate components. Then the max weight
709 is either one that includes that edge or excludes it - and we can
710 use a recursive call to _DCMatch to handle each component separately
711 on what remains of the graph after including/excluding the separating edge.
712 If we're not lucky, we fall back on _EMatch (see below).
715 er: list of (weight, e, tl, tr) (see _ERGraph)
717 (list of (weight, e, tl, tr), float) - the subset forming a maximum
718 matching, and the total weight of the match.
724 return (er
, er
[0][0])
727 for i
in range(0, len(er
)):
728 (nc
, comp
) = _FindComponents(er
, i
)
730 # er[i] doesn't separate er
732 (wi
, _
, tl
, tr
) = er
[i
]
733 if comp
[tl
] != comp
[tr
]:
734 # case 1: er separates graph
735 # compare the matches that include er[i] versus
736 # those that exclude it
737 (a
, b
) = _PartitionComps(er
, comp
, i
, comp
[tl
], comp
[tr
])
738 ax
= _CopyExcluding(a
, tl
, tr
)
739 bx
= _CopyExcluding(b
, tl
, tr
)
740 (axmatch
, wax
) = _DCMatch(ax
)
741 (bxmatch
, wbx
) = _DCMatch(bx
)
742 if len(ax
) == len(a
):
746 (amatch
, wa
) = _DCMatch(a
)
747 if len(bx
) == len(b
):
751 (bmatch
, wb
) = _DCMatch(b
)
755 match
= amatch
+ bmatch
758 match
= [er
[i
]] + axmatch
+ bxmatch
761 # case 2: er not needed to separate graph
762 (a
, b
) = _PartitionComps(er
, comp
, -1, 0, 0)
763 (amatch
, wa
) = _DCMatch(a
)
764 (bmatch
, wb
) = _DCMatch(b
)
765 match
= amatch
+ bmatch
771 return (match
, matchw
)
775 """Exhaustive match helper for _MaxMatch.
777 This is the case when we were unable to find a single edge
778 separating the edge removal graph into two components.
779 So pick a single edge and try _DCMatch on the two cases of
780 including or excluding that edge. We may be lucky in these
781 subcases (say, if the graph is currently a simple cycle, so
782 only needs one more edge after the one we pick here to separate
783 it into components). Otherwise, we'll end up back in _EMatch
784 again, and the worse case will be exponential.
786 Pick a random edge rather than say, the first, to hopefully
787 avoid some pathological cases.
790 er: list of (weight, el, tl, tr) (see _ERGraph)
792 (list of (weight, e, tl, tr), float) - the subset forming a maximum
793 matching, and the total weight of the match.
799 return (er
, er
[1][1])
800 i
= random
.randint(0, len(er
) - 1)
801 eri
= (wi
, _
, tl
, tr
) = er
[i
]
802 # case a: include eri. exclude other edges that touch tl or tr
803 a
= _CopyExcluding(er
, tl
, tr
)
805 (amatch
, wa
) = _DCMatch(a
)
807 if len(a
) == len(er
) - 1:
808 # if a excludes only eri, then er didn't touch anything else
809 # in the graph, and the best match will always include er
810 # and we can skip the call for case b
814 b
= er
[:i
] + er
[i
+ 1:]
815 (bmatch
, wb
) = _DCMatch(b
)
823 return (match
, matchw
)
826 def _FindComponents(er
, excepti
):
827 """Find connected components induced by edges, excluding one edge.
830 er: list of (weight, el, tl, tr) (see _ERGraph)
831 excepti: index in er of edge to be excluded
833 (int, dict): int is number of connected components found,
834 dict maps triangle triple ->
835 connected component index (starting at 1)
840 for i
in range(0, len(er
)):
841 (_
, _
, tl
, tr
) = er
[i
]
845 _FCVisit(er
, excepti
, comp
, t
, ncomps
)
846 return (ncomps
, comp
)
849 def _FCVisit(er
, excepti
, comp
, t
, compnum
):
850 """Helper for _FindComponents depth-first-search."""
853 for i
in range(0, len(er
)):
856 (_
, _
, tl
, tr
) = er
[i
]
857 if tl
== t
or tr
== t
:
862 _FCVisit(er
, excepti
, comp
, s
, compnum
)
865 def _PartitionComps(er
, comp
, excepti
, compa
, compb
):
866 """Partition the edges of er by component number, into two lists.
868 Generally, put odd components into first list and even into second,
869 except that component compa goes in the first and compb goes in the second,
870 and we ignore edge er[excepti].
873 er: list of (weight, el, tl, tr) (see _ERGraph)
874 comp: dict - mapping triangle triple -> connected component index
875 excepti: int - index in er to ignore (unless excepti==-1)
876 compa: int - component to go in first list of answer (unless 0)
877 compb: int - component to go in second list of answer (unless 0)
879 (list, list) - a partition of er according to above rules
884 for i
in range(0, len(er
)):
890 if c
== compa
or (c
!= compb
and (c
& 1) == 1):
894 return (parta
, partb
)
897 def _CopyExcluding(er
, s
, t
):
898 """Return a copy of er, excluding all those involving triangles s and t.
901 er: list of (weight, e, tl, tr) - see _ERGraph
902 s: 3-tuple of int - a triangle
903 t: 3-tuple of int - a triangle
905 Copy of er excluding those with tl or tr == s or t
911 if tl
== s
or tr
== s
or tl
== t
or tr
== t
:
917 def _DegreeDict(tris
):
918 """Return a dictionary mapping vertices in tris to the number of triangles
919 that they are touch."""
931 def PolygonPlane(face
, points
):
932 """Return a Normal vector for the face with 3d coords given by indexing
936 return (0.0, 0.0, 1.0) # arbitrary, we really have no idea
938 coords
= [points
.pos
[i
] for i
in face
]
939 return Normal(coords
)
942 # This Normal appears to be on the CCW-traversing side of a polygon
944 """Return an average Normal vector for the point list, 3d coords."""
947 return (0.0, 0.0, 1.0) # arbitrary
949 (ax
, ay
, az
) = coords
[0]
950 (bx
, by
, bz
) = coords
[1]
951 (cx
, cy
, cz
) = coords
[2]
954 sx
= (ay
- by
) * (az
+ bz
) + \
955 (by
- cy
) * (bz
+ cz
) + \
956 (cy
- ay
) * (cz
+ az
)
957 sy
= (az
- bz
) * (ax
+ bx
) + \
958 (bz
- cz
) * (bx
+ cx
) + \
959 (cz
- az
) * (cx
+ ax
)
960 sz
= (ax
- bx
) * (by
+ by
) + \
961 (bx
- cx
) * (by
+ cy
) + \
962 (cx
- ax
) * (cy
+ ay
)
963 return Norm3(sx
, sy
, sz
)
965 sx
= (ay
- by
) * (az
+ bz
) + (by
- cy
) * (bz
+ cz
)
966 sy
= (az
- bz
) * (ax
+ bx
) + (bz
- cz
) * (bx
+ cx
)
967 sz
= (ax
- bx
) * (ay
+ by
) + (bx
- cx
) * (by
+ cy
)
968 return _NormalAux(coords
[3:], coords
[0], sx
, sy
, sz
)
971 def _NormalAux(rest
, first
, sx
, sy
, sz
):
972 (ax
, ay
, az
) = rest
[0]
976 (bx
, by
, bz
) = rest
[1]
977 nx
= sx
+ (ay
- by
) * (az
+ bz
)
978 ny
= sy
+ (az
- bz
) * (ax
+ bx
)
979 nz
= sz
+ (ax
- bx
) * (ay
+ by
)
981 return Norm3(nx
, ny
, nz
)
983 return _NormalAux(rest
[1:], first
, nx
, ny
, nz
)
987 """Return vector (x,y,z) normalized by dividing by squared length.
988 Return (0.0, 0.0, 1.0) if the result is undefined."""
989 sqrlen
= x
* x
+ y
* y
+ z
* z
991 return (0.0, 0.0, 1.0)
995 return (x
/ d
, y
/ d
, z
/ d
)
997 return (0.0, 0.0, 1.0)
1000 # We're using right-hand coord system, where
1001 # forefinger=x, middle=y, thumb=z on right hand.
1002 # Then, e.g., (1,0,0) x (0,1,0) = (0,0,1)
1004 """Return the cross product of two vectors, a x b."""
1008 return (ay
* bz
- az
* by
, az
* bx
- ax
* bz
, ax
* by
- ay
* bx
)
1012 """Return the dot product of two 2d vectors, a . b."""
1014 return a
[0] * b
[0] + a
[1] * b
[1]
1018 """Return a sort of 2d cross product."""
1020 return a
[0] * b
[1] - a
[1] * b
[0]
1024 """Return difference of 2d vectors, a-b."""
1026 return (a
[0] - b
[0], a
[1] - b
[1])
1030 """Return the sum of 2d vectors, a+b."""
1032 return (a
[0] + b
[0], a
[1] + b
[1])
1036 """Return length of vector v=(x,y)."""
1038 return hypot(v
[0], v
[1])
1041 def LinInterp2(a
, b
, alpha
):
1042 """Return the point alpha of the way from a to b."""
1045 return (beta
* a
[0] + alpha
* b
[0], beta
* a
[1] + alpha
* b
[1])
1049 """Return vector p normlized by dividing by its squared length.
1050 Return (0.0, 1.0) if the result is undefined."""
1053 sqrlen
= x
* x
+ y
* y
1059 return (x
/ d
, y
/ d
)
1064 def Angle(a
, b
, c
, points
):
1065 """Return Angle abc in degrees, in range [0,180),
1066 where a,b,c are indices into points."""
1068 u
= Sub2(points
.pos
[c
], points
.pos
[b
])
1069 v
= Sub2(points
.pos
[a
], points
.pos
[b
])
1072 if n1
== 0.0 or n2
== 0.0:
1075 costheta
= Dot2(u
, v
) / (n1
* n2
)
1078 if costheta
< - 1.0:
1080 return math
.acos(costheta
) * 180.0 / math
.pi
1083 def SegsIntersect(ixa
, ixb
, ixc
, ixd
, points
):
1084 """Return true if segment AB intersects CD,
1085 false if they just touch. ixa, ixb, ixc, ixd are indices
1097 si
= Perp2(v
, w
) / pp
1098 ti
= Perp2(u
, w
) / pp
1099 return 0.0 < si
< 1.0 and 0.0 < ti
< 1.0
1101 # parallel or overlapping
1102 if Dot2(u
, u
) == 0.0 or Dot2(v
, v
) == 0.0:
1107 return False # parallel, not collinear
1113 (t0
, t1
) = (wy
/ vy
, zy
/ vy
)
1115 (t0
, t1
) = (wx
/ vx
, zx
/ vx
)
1116 return 0.0 < t0
< 1.0 and 0.0 < t1
< 1.0
1119 def Ccw(a
, b
, c
, points
):
1120 """Return true if ABC is a counterclockwise-oriented triangle,
1121 where a, b, and c are indices into points.
1122 Returns false if not, or if colinear within TOL."""
1124 (ax
, ay
) = (points
.pos
[a
][0], points
.pos
[a
][1])
1125 (bx
, by
) = (points
.pos
[b
][0], points
.pos
[b
][1])
1126 (cx
, cy
) = (points
.pos
[c
][0], points
.pos
[c
][1])
1127 d
= ax
* by
- bx
* ay
- ax
* cy
+ cx
* ay
+ bx
* cy
- cx
* by
1131 def InCircle(a
, b
, c
, d
, points
):
1132 """Return true if circle through points with indices a, b, c
1133 contains point with index d (indices into points).
1134 Except: if ABC forms a counterclockwise oriented triangle
1135 then the test is reversed: return true if d is outside the circle.
1136 Will get false, no matter what orientation, if d is cocircular, with TOL^2.
1137 | xa ya xa^2+ya^2 1 |
1138 | xb yb xb^2+yb^2 1 | > 0
1139 | xc yc xc^2+yc^2 1 |
1140 | xd yd xd^2+yd^2 1 |
1143 (xa
, ya
, za
) = _Icc(points
.pos
[a
])
1144 (xb
, yb
, zb
) = _Icc(points
.pos
[b
])
1145 (xc
, yc
, zc
) = _Icc(points
.pos
[c
])
1146 (xd
, yd
, zd
) = _Icc(points
.pos
[d
])
1147 det
= xa
* (yb
* zc
- yc
* zb
- yb
* zd
+ yd
* zb
+ yc
* zd
- yd
* zc
) \
1148 - xb
* (ya
* zc
- yc
* za
- ya
* zd
+ yd
* za
+ yc
* zd
- yd
* zc
) \
1149 + xc
* (ya
* zb
- yb
* za
- ya
* zd
+ yd
* za
+ yb
* zd
- yd
* zb
) \
1150 - xd
* (ya
* zb
- yb
* za
- ya
* zc
+ yc
* za
+ yb
* zc
- yc
* zb
)
1151 return det
> TOL
* TOL
1155 (x
, y
) = (p
[0], p
[1])
1156 return (x
, y
, x
* x
+ y
* y
)