1 """A general purpose stripifier, based on NvTriStrip (http://developer.nvidia.com/)
2
3 Credit for porting NvTriStrip to Python goes to the RuneBlade Foundation
4 library:
5 http://techgame.net/projects/Runeblade/browser/trunk/RBRapier/RBRapier/Tools/Geometry/Analysis/TriangleStripifier.py?rev=760
6
7 The algorithm of this stripifier is an improved version of the RuneBlade
8 Foundation / NVidia stripifier; it makes no assumptions about the
9 underlying geometry whatsoever and is intended to produce valid
10 output in all circumstances.
11 """
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 import itertools
51 import random
52
53 from pyffi.utils.trianglemesh import Face, Mesh
56 """A heavily specialized oriented strip of faces.
57
58 Heavily adapted from NvTriStrip and RuneBlade. Originals can be found at
59 http://developer.nvidia.com/view.asp?IO=nvtristrip_library
60 and
61 http://techgame.net/projects/Runeblade/browser/trunk/RBRapier/RBRapier/Tools/Geometry/Analysis/TriangleStripifier.py?rev=760
62 """
63
64 - def __init__(self, stripped_faces=None,
65 faces=None, vertices=None, reversed_=False):
66 """Initialise the triangle strip."""
67 self.faces = faces if faces is not None else []
68 self.vertices = vertices if vertices is not None else []
69 self.reversed_ = reversed_
70
71
72 self.stripped_faces = (stripped_faces
73 if stripped_faces is not None else set())
74
76 return ("TriangleStrip(stripped_faces=%s, faces=%s, vertices=%s, reversed_=%s)"
77 % (repr(self.stripped_faces), repr(self.faces),
78 repr(self.vertices), repr(self.reversed_)))
79
81 """Get adjacent face which is not yet stripped."""
82 for otherface in face.get_adjacent_faces(vi):
83 if otherface.index not in self.stripped_faces:
84 return otherface
85
125
126 - def build(self, start_vertex, start_face):
127 """Builds the face strip forwards, then backwards. Returns
128 index of start_face.
129
130 Check case of single triangle
131 -----------------------------
132
133 >>> m = Mesh()
134 >>> face = m.add_face(0, 1, 2)
135 >>> m.lock()
136 >>> t = TriangleStrip()
137 >>> t.build(0, face)
138 0
139 >>> t
140 TriangleStrip(stripped_faces=set([0]), faces=[Face(0, 1, 2)], vertices=[0, 1, 2], reversed_=False)
141 >>> t.get_strip()
142 [0, 1, 2]
143 >>> t = TriangleStrip()
144 >>> t.build(1, face)
145 0
146 >>> t
147 TriangleStrip(stripped_faces=set([0]), faces=[Face(0, 1, 2)], vertices=[1, 2, 0], reversed_=False)
148 >>> t.get_strip()
149 [1, 2, 0]
150 >>> t = TriangleStrip()
151 >>> t.build(2, face)
152 0
153 >>> t
154 TriangleStrip(stripped_faces=set([0]), faces=[Face(0, 1, 2)], vertices=[2, 0, 1], reversed_=False)
155 >>> t.get_strip()
156 [2, 0, 1]
157
158 Check case of two triangles, with special strip winding fix
159 -----------------------------------------------------------
160
161 >>> m = Mesh()
162 >>> face0 = m.add_face(0, 1, 2)
163 >>> face1 = m.add_face(2, 1, 3)
164 >>> m.lock()
165 >>> t = TriangleStrip()
166 >>> t.build(0, face0)
167 0
168 >>> t
169 TriangleStrip(stripped_faces=set([0, 1]), faces=[Face(0, 1, 2), Face(1, 3, 2)], vertices=[0, 1, 2, 3], reversed_=False)
170 >>> t.get_strip()
171 [0, 1, 2, 3]
172 >>> t = TriangleStrip()
173 >>> t.build(1, face0)
174 1
175 >>> t
176 TriangleStrip(stripped_faces=set([0, 1]), faces=[Face(1, 3, 2), Face(0, 1, 2)], vertices=[3, 1, 2, 0], reversed_=True)
177 >>> t.get_strip()
178 [3, 2, 1, 0]
179 >>> t = TriangleStrip()
180 >>> t.build(2, face1)
181 1
182 >>> t
183 TriangleStrip(stripped_faces=set([0, 1]), faces=[Face(0, 1, 2), Face(1, 3, 2)], vertices=[0, 2, 1, 3], reversed_=True)
184 >>> t.get_strip()
185 [0, 1, 2, 3]
186 >>> t = TriangleStrip()
187 >>> t.build(3, face1)
188 0
189 >>> t
190 TriangleStrip(stripped_faces=set([0, 1]), faces=[Face(1, 3, 2), Face(0, 1, 2)], vertices=[3, 2, 1, 0], reversed_=False)
191 >>> t.get_strip()
192 [3, 2, 1, 0]
193
194 Check that extra vertex is appended to fix winding
195 --------------------------------------------------
196
197 >>> m = Mesh()
198 >>> face0 = m.add_face(1, 3, 2)
199 >>> face1 = m.add_face(2, 3, 4)
200 >>> face2 = m.add_face(4, 3, 5)
201 >>> face3 = m.add_face(4, 5, 6)
202 >>> m.lock()
203 >>> t = TriangleStrip()
204 >>> t.build(2, face1)
205 1
206 >>> t
207 TriangleStrip(stripped_faces=set([0, 1, 2, 3]), faces=[Face(1, 3, 2), Face(2, 3, 4), Face(3, 5, 4), Face(4, 5, 6)], vertices=[1, 2, 3, 4, 5, 6], reversed_=True)
208 >>> t.get_strip()
209 [1, 1, 2, 3, 4, 5, 6]
210
211 Check that strip is reversed to fix winding
212 -------------------------------------------
213
214 >>> m = Mesh()
215 >>> face0 = m.add_face(1, 3, 2)
216 >>> face1 = m.add_face(2, 3, 4)
217 >>> face2 = m.add_face(4, 3, 5)
218 >>> m.lock()
219 >>> t = TriangleStrip()
220 >>> t.build(2, face1)
221 1
222 >>> t
223 TriangleStrip(stripped_faces=set([0, 1, 2]), faces=[Face(1, 3, 2), Face(2, 3, 4), Face(3, 5, 4)], vertices=[1, 2, 3, 4, 5], reversed_=True)
224 >>> t.get_strip()
225 [5, 4, 3, 2, 1]
226
227 More complicated mesh
228 ---------------------
229
230 >>> m = Mesh()
231 >>> face0 = m.add_face(0, 1, 2)
232 >>> face1 = m.add_face(2, 1, 7)
233 >>> face2 = m.add_face(2, 7, 4)
234 >>> face3 = m.add_face(5, 3, 2)
235 >>> face4 = m.add_face(2, 1, 9)
236 >>> face5 = m.add_face(4, 7, 10)
237 >>> face6 = m.add_face(4, 10, 11)
238 >>> face7 = m.add_face(11, 10, 12)
239 >>> face8 = m.add_face(1, 0, 13)
240 >>> m.lock()
241 >>> t = TriangleStrip()
242 >>> t.build(7, face1)
243 4
244 >>> t.faces[4] == face1 # check result from build
245 True
246 >>> t.stripped_faces
247 set([0, 1, 2, 5, 6, 7, 8])
248 >>> t.faces
249 [Face(10, 12, 11), Face(4, 10, 11), Face(4, 7, 10), Face(2, 7, 4), Face(1, 7, 2), Face(0, 1, 2), Face(0, 13, 1)]
250 >>> t.vertices
251 [12, 11, 10, 4, 7, 2, 1, 0, 13]
252 >>> t.reversed_
253 False
254 >>> t.get_strip()
255 [12, 11, 10, 4, 7, 2, 1, 0, 13]
256
257 Mesh which has more than a single strip
258 ---------------------------------------
259
260 >>> m = Mesh()
261 >>> tmp = m.add_face(2, 1, 7) # in strip
262 >>> start_face = m.add_face(0, 1, 2) # in strip
263 >>> tmp = m.add_face(2, 7, 4) # in strip
264 >>> tmp = m.add_face(4, 7, 11) # in strip
265 >>> tmp = m.add_face(5, 3, 2)
266 >>> tmp = m.add_face(1, 0, 8) # in strip
267 >>> tmp = m.add_face(0, 8, 9) # bad orientation!
268 >>> tmp = m.add_face(8, 0, 10) # in strip
269 >>> m.lock()
270 >>> t = TriangleStrip()
271 >>> t.build(0, start_face)
272 2
273 >>> t.vertices
274 [10, 8, 0, 1, 2, 7, 4, 11]
275 >>> t.get_strip()
276 [10, 8, 0, 1, 2, 7, 4, 11]
277 """
278 del self.faces[:]
279 del self.vertices[:]
280 self.reversed_ = False
281 v0 = start_vertex
282 v1 = start_face.get_next_vertex(v0)
283 v2 = start_face.get_next_vertex(v1)
284 self.stripped_faces.add(start_face.index)
285 self.faces.append(start_face)
286 self.vertices.append(v0)
287 self.vertices.append(v1)
288 self.vertices.append(v2)
289 self.traverse_faces(v0, start_face, True)
290 return self.traverse_faces(v2, start_face, False)
291
293 """Get strip in forward winding."""
294 strip = []
295 if self.reversed_:
296 if len(self.vertices) & 1:
297 strip = list(reversed(self.vertices))
298 elif len(self.vertices) == 4:
299 strip = list(self.vertices[i] for i in (0, 2, 1, 3))
300 else:
301 strip = list(self.vertices)
302 strip.insert(0, strip[0])
303 else:
304 strip = list(self.vertices)
305 return strip
306
308 """A stripification experiment, essentially consisting of a set of
309 adjacent strips.
310 """
311
312 - def __init__(self, start_vertex, start_face):
313 self.stripped_faces = set()
314 self.start_vertex = start_vertex
315 self.start_face = start_face
316 self.strips = []
317
319 """Build strips, starting from start_vertex and start_face.
320
321 >>> m = Mesh()
322 >>> tmp = m.add_face(2, 1, 7)
323 >>> s1_face = m.add_face(0, 1, 2)
324 >>> tmp = m.add_face(2, 7, 4) # in strip
325 >>> tmp = m.add_face(4, 7, 11) # in strip
326 >>> tmp = m.add_face(5, 3, 2)
327 >>> tmp = m.add_face(1, 0, 8) # in strip
328 >>> tmp = m.add_face(0, 8, 9) # bad orientation!
329 >>> tmp = m.add_face(8, 0, 10) # in strip
330 >>> tmp = m.add_face(10, 11, 8) # in strip
331 >>> # parallel strip
332 >>> s2_face = m.add_face(0, 2, 21) # in strip
333 >>> tmp = m.add_face(21, 2, 22) # in strip
334 >>> tmp = m.add_face(2, 4, 22) # in strip
335 >>> tmp = m.add_face(21, 24, 0) # in strip
336 >>> tmp = m.add_face(9, 0, 24) # in strip
337 >>> # parallel strip, further down
338 >>> s3_face = m.add_face(8, 11, 31) # in strip
339 >>> tmp = m.add_face(8, 31, 32) # in strip
340 >>> tmp = m.add_face(31, 11, 33) # in strip
341 >>> m.lock()
342 >>> # build experiment
343 >>> exp = Experiment(0, s1_face)
344 >>> exp.build()
345 >>> len(exp.strips)
346 2
347 >>> exp.strips[0].get_strip()
348 [11, 4, 7, 2, 1, 0, 8, 10, 11]
349 >>> exp.strips[1].get_strip()
350 [4, 22, 2, 21, 0, 24, 9]
351 >>> # note: with current algorithm [32, 8, 31, 11, 33] is not found
352 """
353
354 strip = TriangleStrip(stripped_faces=self.stripped_faces)
355 strip.build(self.start_vertex, self.start_face)
356 self.strips.append(strip)
357
358 num_faces = len(strip.faces)
359 if num_faces >= 4:
360 face_index = num_faces >> 1
361 self.build_adjacent(strip, face_index)
362 self.build_adjacent(strip, face_index + 1)
363 elif num_faces == 3:
364 if not self.build_adjacent(strip, 0):
365 self.build_adjacent(strip, 2)
366 self.build_adjacent(strip, 1)
367 elif num_faces == 2:
368 self.build_adjacent(strip, 0)
369 self.build_adjacent(strip, 1)
370 elif num_faces == 1:
371 self.build_adjacent(strip, 0)
372
374 """Build strips adjacent to given strip, and add them to the
375 experiment. This is a helper function used by build.
376 """
377 opposite_vertex = strip.vertices[face_index + 1]
378 face = strip.faces[face_index]
379 other_face = strip.get_unstripped_adjacent_face(face, opposite_vertex)
380 if other_face:
381 winding = strip.reversed_
382 if face_index & 1:
383 winding = not winding
384 other_strip = TriangleStrip(stripped_faces=self.stripped_faces)
385 if winding:
386 other_vertex = strip.vertices[face_index]
387 face_index = other_strip.build(other_vertex, other_face)
388 else:
389 other_vertex = strip.vertices[face_index + 2]
390 face_index = other_strip.build(other_vertex, other_face)
391 self.strips.append(other_strip)
392 if face_index > (len(other_strip.faces) >> 1):
393 self.build_adjacent(other_strip, face_index - 1)
394 elif face_index < len(other_strip.faces) - 1:
395 self.build_adjacent(other_strip, face_index + 1)
396 return True
397 return False
398
400
402 self.best_score = -1.0
403 self.best_experiment = None
404
405 - def update(self, experiment):
406 """Updates best experiment with given experiment, if given
407 experiment beats current experiment.
408 """
409 score = (sum((len(strip.faces) for strip in experiment.strips), 0.0)
410 / len(experiment.strips))
411 if score > self.best_score:
412 self.best_score = score
413 self.best_experiment = experiment
414
416 """Remove best experiment, to start a fresh sequence of
417 experiments.
418 """
419 self.best_score = -1.0
420 self.best_experiment = None
421
423 """Implementation of a triangle stripifier.
424
425 Heavily adapted from NvTriStrip.
426 Original can be found at http://developer.nvidia.com/view.asp?IO=nvtristrip_library.
427 """
428
430 self.num_samples = 10
431 self.mesh = mesh
432
433 @staticmethod
435 """Return a k length list of unique elements chosen from the
436 population sequence. Used for random sampling without
437 replacement. Deterministic version of random.sample (being
438 deterministic means that it is easier to test).
439
440 >>> TriangleStripifier.sample(range(10), 1)
441 [0]
442 >>> TriangleStripifier.sample(range(10), 2)
443 [0, 9]
444 >>> TriangleStripifier.sample(range(10), 3)
445 [0, 4, 9]
446 >>> TriangleStripifier.sample(range(10), 4)
447 [0, 3, 6, 9]
448 >>> TriangleStripifier.sample(range(10), 5)
449 [0, 2, 4, 6, 9]
450 >>> TriangleStripifier.sample(range(10), 6)
451 [0, 1, 3, 5, 7, 9]
452 >>> TriangleStripifier.sample(range(10), 7)
453 [0, 1, 3, 4, 6, 7, 9]
454 >>> TriangleStripifier.sample(range(10), 8)
455 [0, 1, 2, 3, 5, 6, 7, 9]
456 >>> TriangleStripifier.sample(range(10), 9)
457 [0, 1, 2, 3, 4, 5, 6, 7, 9]
458 >>> TriangleStripifier.sample(range(10), 10)
459 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
460 """
461 if k == 1:
462
463 return [population[0]]
464 else:
465
466 return [
467 population[int((i * (float(len(population)) - 1)) / (k - 1))]
468 for i in xrange(k)]
469
471 """Find all strips.
472
473 Empty mesh
474 ----------
475
476 >>> m = Mesh()
477 >>> m.lock()
478 >>> ts = TriangleStripifier(m)
479 >>> ts.find_all_strips()
480 []
481
482 Full mesh
483 ---------
484
485 >>> m = Mesh()
486 >>> tmp = m.add_face(2, 1, 7)
487 >>> tmp = m.add_face(0, 1, 2)
488 >>> tmp = m.add_face(2, 7, 4) # in strip
489 >>> tmp = m.add_face(4, 7, 11) # in strip
490 >>> tmp = m.add_face(5, 3, 2)
491 >>> tmp = m.add_face(1, 0, 8) # in strip
492 >>> tmp = m.add_face(0, 8, 9) # bad orientation!
493 >>> tmp = m.add_face(8, 0, 10) # in strip
494 >>> tmp = m.add_face(10, 11, 8) # in strip
495 >>> # parallel strip
496 >>> tmp = m.add_face(0, 2, 21) # in strip
497 >>> tmp = m.add_face(21, 2, 22) # in strip
498 >>> tmp = m.add_face(2, 4, 22) # in strip
499 >>> tmp = m.add_face(21, 24, 0) # in strip
500 >>> tmp = m.add_face(9, 0, 24) # in strip
501 >>> # parallel strip, further down
502 >>> tmp = m.add_face(8, 11, 31) # in strip
503 >>> tmp = m.add_face(8, 31, 32) # in strip
504 >>> tmp = m.add_face(31, 11, 33) # in strip
505 >>> m.lock()
506 >>> ts = TriangleStripifier(m)
507 >>> sorted(ts.find_all_strips())
508 [[3, 2, 5], [4, 22, 2, 21, 0, 24, 9], [9, 0, 8], [11, 4, 7, 2, 1, 0, 8, 10, 11], [32, 8, 31, 11, 33]]
509 """
510 all_strips = []
511 selector = ExperimentSelector()
512 unstripped_faces = set(xrange(len(self.mesh.faces)))
513 while True:
514 experiments = []
515
516
517
518 for sample in self.sample(list(unstripped_faces),
519 min(self.num_samples,
520 len(unstripped_faces))):
521 exp_face = self.mesh.faces[sample]
522 for exp_vertex in exp_face.verts:
523 experiments.append(
524 Experiment(start_vertex=exp_vertex,
525 start_face=exp_face))
526 if not experiments:
527
528 return all_strips
529
530
531 while experiments:
532 experiment = experiments.pop()
533 experiment.build()
534 selector.update(experiment)
535 unstripped_faces -= selector.best_experiment.stripped_faces
536
537 for strip in selector.best_experiment.strips:
538 for face in strip.faces:
539 self.mesh.discard_face(face)
540
541 all_strips.extend(
542 (strip.get_strip()
543 for strip in selector.best_experiment.strips))
544 selector.clear()
545
546 if __name__=='__main__':
547 import doctest
548 doctest.testmod()
549