1 /*---------------------------------------------------------------------------*\
5 * Copyright (C) 2000-2002 by the OpenSG Forum *
9 * contact: dirk@opensg.org, gerrit.voss@vossg.org, jbehr@zgdv.de *
11 \*---------------------------------------------------------------------------*/
12 /*---------------------------------------------------------------------------*\
15 * This library is free software; you can redistribute it and/or modify it *
16 * under the terms of the GNU Library General Public License as published *
17 * by the Free Software Foundation, version 2. *
19 * This library is distributed in the hope that it will be useful, but *
20 * WITHOUT ANY WARRANTY; without even the implied warranty of *
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
22 * Library General Public License for more details. *
24 * You should have received a copy of the GNU Library General Public *
25 * License along with this library; if not, write to the Free Software *
26 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
28 \*---------------------------------------------------------------------------*/
29 /*---------------------------------------------------------------------------*\
37 \*---------------------------------------------------------------------------*/
39 // System declarations
48 // Application declarations
52 #include "OSGStriperHalfEdgeGraph.h"
59 #if !defined(OSG_DO_DOC) || defined(OSG_DOC_DEV)
61 // Static Class Varible implementations:
63 //----------------------------------------------------------------------
64 // Method: HalfEdgeGraph
66 // Date: Tue Feb 15 18:16:59 2000
68 // Default Constructor
69 //----------------------------------------------------------------------
70 StriperHalfEdgeGraph::StriperHalfEdgeGraph (void) :
75 _invalidTriangleBag(),
83 //----------------------------------------------------------------------
84 // Method: HalfEdgeGraph
86 // Date: Tue Feb 15 18:16:59 2000
89 //----------------------------------------------------------------------
91 StriperHalfEdgeGraph::StriperHalfEdgeGraph(
92 const StriperHalfEdgeGraph
&OSG_CHECK_ARG(obj
))
94 SWARNING
<< "Run HalfEdgeGraph copy constructor; not impl.\n" << endl
;
98 //----------------------------------------------------------------------
99 // Method: ~HalfEdgeGraph
101 // Date: Tue Feb 15 18:16:59 2000
104 //----------------------------------------------------------------------
105 StriperHalfEdgeGraph::~StriperHalfEdgeGraph (void )
110 //----------------------------------------------------------------------
111 // Method: ~HalfEdgeGraph
113 // Date: Tue Feb 15 18:16:59 2000
116 //----------------------------------------------------------------------
117 bool StriperHalfEdgeGraph::Triangle::verify (void)
120 Triangle
*neighbor
[3];
122 neighbor
[0] = halfEdgeVec
[0].twin
? halfEdgeVec
[0].twin
->triangle
: 0;
123 neighbor
[1] = halfEdgeVec
[1].twin
? halfEdgeVec
[1].twin
->triangle
: 0;
124 neighbor
[2] = halfEdgeVec
[2].twin
? halfEdgeVec
[2].twin
->triangle
: 0;
126 if ( ( neighbor
[0] &&
127 ( (neighbor
[0] == neighbor
[1] ) ||
128 (neighbor
[0] == neighbor
[2] )
132 ( (neighbor
[1] == neighbor
[0] ) ||
133 (neighbor
[1] == neighbor
[2] ) )
136 ( (neighbor
[2] == neighbor
[0] ) ||
137 (neighbor
[2] == neighbor
[1] ) )
141 FINFO(("StriperHalfEdgeGraph::Triangle::verify: Neighbor linked more "
142 "than once: %p/%p/%p\n", static_cast<void *>(neighbor
[0]),
143 static_cast<void *>(neighbor
[1]),
144 static_cast<void *>(neighbor
[2])));
148 if((halfEdgeVec
[0].vertexStart() == halfEdgeVec
[1].vertexStart()) ||
149 (halfEdgeVec
[0].vertexStart() == halfEdgeVec
[2].vertexStart()) ||
150 (halfEdgeVec
[1].vertexStart() == halfEdgeVec
[2].vertexStart()))
152 SINFO
<< "StriperHalfEdgeGraph::Triangle::verify: "
153 << "Invalid collapsed Triangle"
158 if((halfEdgeVec
[0].triangle
!= this) ||
159 (halfEdgeVec
[1].triangle
!= this) ||
160 (halfEdgeVec
[2].triangle
!= this))
162 SINFO
<< "StriperHalfEdgeGraph::Triangle::verify: Invalid halfEdge->"
163 << "triangle pointer" << endl
;
167 if((halfEdgeVec
[0].next
!= &halfEdgeVec
[1]) ||
168 (halfEdgeVec
[1].next
!= &halfEdgeVec
[2]) ||
169 (halfEdgeVec
[2].next
!= &halfEdgeVec
[0]))
171 SINFO
<< "StriperHalfEdgeGraph::Triangle::verify: Edge next link error"
179 //----------------------------------------------------------------------
182 // Date: Tue Feb 15 18:16:59 2000
185 //----------------------------------------------------------------------
186 void StriperHalfEdgeGraph::reserve(UInt32 vertexNum
, UInt32 triangleNum
,
187 UInt32 reserveEdges
)
191 _trianglePool
.setChunkSize(triangleNum
);
192 _edgeLinkVec
.resize(vertexNum
);
196 for(i
= 0; i
< vertexNum
; ++i
)
197 _edgeLinkVec
[i
].reserve(reserveEdges
);
201 //----------------------------------------------------------------------
204 // Date: Tue Feb 15 18:16:59 2000
207 //----------------------------------------------------------------------
208 bool StriperHalfEdgeGraph::verify (bool verbose
)
212 Triangle
*triangle
, *nt0
, *nt1
, *nt2
;
213 Int32 triangleState
[4];
214 Int32 invalidTriangles
= 0;
215 Int32 halfEdgeCount
= 0;
216 map
< Int32
, Int32
> connectionMap
;
217 map
< Int32
, Int32
>::iterator connectionI
;
218 Int32 connectionCount
;
219 bool validTri
= false;
221 for(i
= 0; i
< 4; ++i
)
222 triangleState
[i
] = 0;
224 for( i
= 0, triangle
= _validTriangleBag
.first
;
226 i
++, triangle
= triangle
->next
)
228 // Looks strange (GV)
229 if((triangle
->verify() && (triangle
->state
>= 0)) ||
230 (triangle
->state
<= 3))
232 triangleState
[triangle
->state
]++;
243 nt0
= triangle
->halfEdgeVec
[0].twin
?
244 triangle
->halfEdgeVec
[0].twin
->triangle
: 0;
245 nt1
= triangle
->halfEdgeVec
[1].twin
?
246 triangle
->halfEdgeVec
[1].twin
->triangle
: 0;
247 nt2
= triangle
->halfEdgeVec
[2].twin
?
248 triangle
->halfEdgeVec
[2].twin
->triangle
: 0;
250 FINFO ( ( "HEG: Triangle %p: %d %d %d, %p %p %p: %s\n",
251 static_cast<void *>(triangle
),
252 triangle
->halfEdgeVec
[0].vertexStart(),
253 triangle
->halfEdgeVec
[1].vertexStart(),
254 triangle
->halfEdgeVec
[2].vertexStart(),
255 static_cast<void *>(nt0
),
256 static_cast<void *>(nt1
),
257 static_cast<void *>(nt2
),
258 (validTri
? "VALID" : "INVALID" ) ) );
262 SINFO
<< "HEG: linked triangle count " << _validTriangleBag
.countElem()
264 SINFO
<< "HEG: invalid triangle " << invalidTriangles
266 SINFO
<< "HEG: nonmanifold split: " << _invalidTriangleBag
.countElem()
269 SINFO
<< "HEG: triangle state: "
279 n
= UInt32(_edgeLinkVec
.size());
281 for (i
= 0; i
< n
; ++i
)
283 connectionCount
= UInt32(_edgeLinkVec
[i
].size());
285 halfEdgeCount
+= connectionCount
;
286 if (connectionMap
.find(connectionCount
) == connectionMap
.end())
287 connectionMap
[connectionCount
] = 1;
289 connectionMap
[connectionCount
]++;
293 HalfEdgeLink::iterator lI
;
294 for ( lI
= _edgeLinkVec
[i
].begin();
295 lI
!= _edgeLinkVec
[i
].end(); ++lI
)
297 FINFO (( "HEG: HalfEdge %p: %d to %d, twin: %p\n",
298 static_cast<void *>(lI
->second
),
299 lI
->second
->vertexStart(),
300 lI
->second
->vertexEnd(),
301 static_cast<void *>(lI
->second
->twin
)));
305 for(connectionI
= connectionMap
.begin();
306 connectionI
!= connectionMap
.end(); ++connectionI
)
308 SINFO
<< "HEG: Connection: " << connectionI
->first
<< '/'
309 << connectionI
->second
<< ' ' << std::endl
;
312 SINFO
<< "HEG: HalfEdgeCount: " << halfEdgeCount
<< endl
;
317 //----------------------------------------------------------------------
318 // Method: calcOptPrim
320 // Date: Tue Feb 15 18:16:59 2000
323 //----------------------------------------------------------------------
324 UInt32
StriperHalfEdgeGraph::calcOptPrim(UInt32 extIteration
,
325 bool doStrip
, bool doFan
,
326 UInt32 minFanTriangles
)
328 Int32 iteration
= extIteration
;
329 bool sample
= iteration
> 1 ? true : false;
330 bool checkRevOrder
= sample
;
331 TriangleList degreeBag
[4];
332 TriangleList
*fList
= 0;
333 Int32 cost
= 0, sampleCost
= 0;
334 Int32 stripCost
= 0, revCost
= 0, fanCost
= 0, triCost
= 0;
335 Int32 bestCost
= 0, worstCost
= 0, lowDegree
;
337 WalkCase walkCase
= START
;
338 Triangle
*triangle
, *next
;
339 HalfEdge
*twin
= 0, *gateEdge
= 0, *halfEdge
= 0;
340 bool doMainLoop
= true;
341 UInt32 seed
= 1, bestSeed
= 1;
342 Int32 mostDegree
= 3;
343 UInt32 triangleLeft
= _trianglePool
.countElem();
348 n
= UInt32(_edgeLinkVec
.size());
352 for(i
= 0; i
< n
; ++i
)
354 if((_edgeLinkVec
[i
].size() >= minFanTriangles
) &&
355 (gateEdge
= _edgeLinkVec
[i
][0].second
) &&
356 (gateEdge
->triangle
->valid()))
358 for(halfEdge
= gateEdge
->next
->next
->twin
;
359 (halfEdge
&& halfEdge
->triangle
->valid() &&
360 (halfEdge
!= gateEdge
));
361 halfEdge
= halfEdge
->next
->next
->twin
)
363 if(halfEdge
== gateEdge
)
365 // fan is closed; mark every triangle
367 fList
= new TriangleList
;
368 for(halfEdge
= gateEdge
;
369 !triangle
|| (halfEdge
!= gateEdge
);
370 halfEdge
= halfEdge
->next
->next
->twin
)
372 triangle
= halfEdge
->triangle
;
373 _validTriangleBag
.release(*triangle
);
375 triangle
->state
= FAN_PART
;
376 fList
->add(*triangle
);
378 _fanBag
.push_back(Primitive(i
,fList
));
379 fanCost
+= (UInt32(_edgeLinkVec
[i
].size()) + 2);
380 triangleLeft
-= UInt32(_edgeLinkVec
[i
].size());
386 if(doStrip
&& iteration
)
388 // push every triangle into the according degree bag
389 degreeBag
[mostDegree
].paste(_validTriangleBag
);
390 for(triangle
= degreeBag
[mostDegree
].first
; triangle
; triangle
= next
)
392 next
= triangle
->next
;
393 if(triangle
->valid())
395 if(triangle
->state
!= mostDegree
)
397 degreeBag
[mostDegree
].release(*triangle
);
398 _validTriangleBag
.release(*triangle
);
399 degreeBag
[triangle
->state
].add( *triangle
);
404 SWARNING
<< "INVALID TRIANGLE IN VALID TRIANGLE BAG\n" << endl
;
408 for(iteration
--; iteration
>= 0; iteration
--)
410 seed
= iteration
? rand() : bestSeed
;
427 for(lowDegree
= 1; lowDegree
< 4; ++lowDegree
)
429 if((degreeBag
[lowDegree
].empty() == false))
433 // pick a random triangle
434 n
= degreeBag
[lowDegree
].countElem() - 1;
435 i
= Int32(float(n
)*rand()/float(RAND_MAX
));
436 triangle
= degreeBag
[lowDegree
].first
;
438 triangle
= triangle
->next
;
442 // pick the first triangle
443 triangle
= degreeBag
[lowDegree
].first
;
451 // create the new list
452 fList
= new TriangleList
;
454 // find the best neighbour
456 for (i
= 0; i
< 3; ++i
)
458 if((twin
= triangle
->halfEdgeVec
[i
].twin
) &&
459 (twin
->triangle
->state
> 0))
461 if(twin
->next
->next
->twin
&&
462 (twin
->next
->next
->twin
->triangle
->state
> 0))
464 gateEdge
= &triangle
->halfEdgeVec
[i
];
469 if(twin
->next
->twin
&&
470 (twin
->next
->twin
->triangle
->state
> 0))
472 gateEdge
= &triangle
->halfEdgeVec
[i
];
476 if((twin
->triangle
->state
> 0))
477 gateEdge
= &triangle
->halfEdgeVec
[i
];
483 // release and store the first triangle
484 dropOutTriangle (*triangle
,degreeBag
);
485 fList
->add(*triangle
);
506 gateEdge
= gateEdge
->twin
;
507 triangle
= gateEdge
->triangle
;
509 // find the next gate
510 if(triangle
->state
== DEGREE_0
)
517 if((twin
= gateEdge
->next
->next
->twin
) &&
518 (twin
->triangle
->state
> 0))
520 gateEdge
= gateEdge
->next
->next
;
526 gateEdge
= gateEdge
->next
;
531 // store the current triangle
532 dropOutTriangle (*triangle
,degreeBag
);
533 fList
->add(*triangle
);
537 gateEdge
= gateEdge
->twin
;
538 triangle
= gateEdge
->triangle
;
540 // find the next gate
541 if(triangle
->state
== DEGREE_0
)
548 if((twin
= gateEdge
->next
->twin
) &&
549 (twin
->triangle
->state
> 0))
551 gateEdge
= gateEdge
->next
;
557 gateEdge
= gateEdge
->next
->next
;
562 // store the current triangle
563 dropOutTriangle (*triangle
,degreeBag
);
564 fList
->add(*triangle
);
568 // try to reverse the strip
570 (revCost
= calcStripCost(*fList
,true)) &&
571 (revCost
< stripCost
))
573 _stripBag
.push_back(Primitive(1,fList
));
578 _stripBag
.push_back(Primitive(0,fList
));
589 sampleCost
= cost
+ (degreeBag
[0].countElem() * 3) + fanCost
;
590 if(!bestCost
|| (sampleCost
< bestCost
))
592 bestCost
= sampleCost
;
595 if(sampleCost
> worstCost
)
596 worstCost
= sampleCost
;
598 SINFO
<< " cost/best/worst: "
599 << sampleCost
<< '/' << bestCost
<< '/' << worstCost
605 // reinit the four degree bags
606 degreeBag
[mostDegree
].paste(degreeBag
[0]);
607 n
= UInt32(_stripBag
.size());
608 for(i
= 0; i
< n
; ++i
)
610 degreeBag
[mostDegree
].paste(*_stripBag
[i
].second
);
611 delete _stripBag
[i
].second
;
614 for(triangle
= degreeBag
[mostDegree
].first
; triangle
;
617 next
= triangle
->next
;
618 triangle
->resetDegreeState(STRIP_PART
);
619 if (triangle
->valid())
621 if(triangle
->state
!= mostDegree
)
623 degreeBag
[mostDegree
].release(*triangle
);
624 degreeBag
[triangle
->state
].add(*triangle
);
629 SWARNING
<< "INVALID TRIANGLE IN REINIT\n" << endl
;
630 SWARNING
<< triangle
->state
<< endl
;
638 // push every valid triangle in degree 0; we don't strip anything
639 degreeBag
[0].paste(_validTriangleBag
);
644 SWARNING
<< "range: "
645 << bestCost
<< '/' << worstCost
<< ' '
646 << float(100 * (worstCost
-bestCost
))/float(bestCost
) << '%'
650 // collect isolated triangles
651 degreeBag
[0].paste(_invalidTriangleBag
);
652 triCost
= degreeBag
[0].countElem() * 3;
655 fList
= new TriangleList
;
656 fList
->paste(degreeBag
[0]);
657 _triBag
.push_back(Primitive(0,fList
));
660 return (cost
+ fanCost
+ triCost
);
663 //----------------------------------------------------------------------
664 // Method: calcStripCost
666 // Date: Tue Feb 15 18:16:59 2000
669 //----------------------------------------------------------------------
670 Int32
StriperHalfEdgeGraph::calcStripCost(TriangleList
&strip
, bool rev
)
672 Triangle
*triangle
= rev
? strip
.last
: strip
.first
, *nextTriangle
;
673 HalfEdge
/* *firstEdge, */ *halfEdge
, *gate
;
680 if ((nextTriangle
= rev
? triangle
->prev
: triangle
->next
))
682 gate
= findGateEdge(triangle
,nextTriangle
);
683 //firstEdge = gate->next->next;
686 for(triangle
= nextTriangle
;
687 (nextTriangle
= (rev
? triangle
->prev
: triangle
->next
));
688 triangle
= nextTriangle
)
690 halfEdge
= gate
->twin
;
691 gate
= findGateEdge(triangle
,nextTriangle
);
692 if (walkCase
== RIGHT
)
695 if(halfEdge
->next
== gate
)
702 // swap; walkCase stays RIGHT;
709 if(halfEdge
->next
->next
== gate
)
716 // swap; walkCase stays LEFT;
727 //----------------------------------------------------------------------
728 // Method: fillPrimFromStrip
730 // Date: Tue Feb 15 18:16:59 2000
733 //----------------------------------------------------------------------
734 Int32
StriperHalfEdgeGraph::fillIndexFromStrip(
735 std::vector
<StriperHalfEdgeGraph::IndexT
> &indexVec
,
736 TriangleList
&strip
, bool rev
)
738 Triangle
*triangle
= rev
? strip
.last
: strip
.first
, *nextTriangle
;
739 HalfEdge
*firstEdge
, *halfEdge
, *gate
;
741 StriperHalfEdgeGraph::IndexT vertex
;
747 indexVec
.reserve(32); // find better value
749 if((nextTriangle
= (rev
? triangle
->prev
: triangle
->next
)))
752 gate
= findGateEdge(triangle
,nextTriangle
);
753 firstEdge
= gate
->next
->next
;
754 indexVec
.push_back(vertex
= gate
->twin
->next
->vertexEnd());
757 for(triangle
= nextTriangle
;
758 (nextTriangle
= (rev
? triangle
->prev
: triangle
->next
));
759 triangle
= nextTriangle
)
761 halfEdge
= gate
->twin
;
762 gate
= findGateEdge(triangle
,nextTriangle
);
763 if(walkCase
== RIGHT
)
766 if(halfEdge
->next
== gate
)
768 indexVec
.push_back(vertex
= gate
->twin
->next
->vertexEnd());
774 // swap; walkCase stays RIGHT;
775 indexVec
.back() = gate
->vertexEnd();
776 indexVec
.push_back(gate
->vertexStart());
777 indexVec
.push_back(vertex
= gate
->twin
->next
->vertexEnd());
784 if (halfEdge
->next
->next
== gate
)
786 indexVec
.push_back(vertex
= gate
->twin
->next
->vertexEnd());
792 // swap; walkCase stays LEFT;
793 indexVec
.back() = gate
->vertexStart();
794 indexVec
.push_back(gate
->vertexEnd());
795 indexVec
.push_back(vertex
= gate
->twin
->next
->vertexEnd());
803 firstEdge
= &triangle
->halfEdgeVec
[0];
806 indexVec
[0] = vertex
= firstEdge
->vertexStart();
807 indexVec
[1] = vertex
= firstEdge
->next
->vertexStart();
808 indexVec
[2] = vertex
= firstEdge
->next
->next
->vertexStart();
814 //----------------------------------------------------------------------
815 // Method: fillPrimFromFan
817 // Date: Tue Feb 15 18:16:59 2000
820 //----------------------------------------------------------------------
821 Int32
StriperHalfEdgeGraph::fillIndexFromFan(
822 std::vector
<StriperHalfEdgeGraph::IndexT
> &indexVec
,
823 HalfEdge
&firstEdge
)
826 HalfEdge
*halfEdge(&firstEdge
);
827 HalfEdge
*gateEdge
= 0;
833 indexVec
[0] = halfEdge
->vertexStart();
834 indexVec
[1] = halfEdge
->vertexEnd();
835 for(gateEdge
= halfEdge
->next
->next
->twin
;
836 gateEdge
!= halfEdge
;
837 gateEdge
= gateEdge
->next
->next
->twin
)
839 indexVec
.push_back(gateEdge
->vertexEnd());
842 indexVec
.push_back(halfEdge
->vertexEnd());
846 SWARNING
<< "Invalid fac in fillIndexFromFan()" << endl
;
851 //----------------------------------------------------------------------
852 // Method: calcEdgeLines
854 // Date: Tue Feb 15 18:16:59 2000
857 //----------------------------------------------------------------------
858 Int32
StriperHalfEdgeGraph::getPrimitive(
859 vector
<StriperHalfEdgeGraph::IndexT
> &indexVec
,
864 std::vector
<Primitive
> *bag
= 0;
869 if(!bag
&& (n
= UInt32(_fanBag
.size())) &&
870 ((type
== GL_TRIANGLE_FAN
) || !type
))
874 fillIndexFromFan(indexVec
,
875 *_edgeLinkVec
[_fanBag
[i
].first
][0].second
);
876 type
= GL_TRIANGLE_FAN
;
880 if(!bag
&& (n
= UInt32(_stripBag
.size())) &&
881 ((type
== GL_TRIANGLE_STRIP
) || !type
))
885 fillIndexFromStrip(indexVec
, *_stripBag
[i
].second
,
886 _stripBag
[i
].first
? true : false );
887 type
= GL_TRIANGLE_STRIP
;
891 if(!bag
&& (n
= UInt32(_triBag
.size())) &&
892 ((type
== GL_TRIANGLES
) || !type
))
895 if (_triBag
[0].second
->empty() == false)
897 n
= _triBag
[0].second
->countElem() * 3;
900 for(triangle
= _triBag
[0].second
->first
; triangle
;
901 triangle
= triangle
->next
)
903 indexVec
[i
++] = triangle
->halfEdgeVec
[0].vertexStart();
904 indexVec
[i
++] = triangle
->halfEdgeVec
[1].vertexStart();
905 indexVec
[i
++] = triangle
->halfEdgeVec
[2].vertexStart();
914 _invalidTriangleBag
.paste(*((*bag
)[i
].second
));
915 delete (*bag
)[i
].second
;
928 //----------------------------------------------------------------------
929 // Method: calcEdgeLines
931 // Date: Tue Feb 15 18:16:59 2000
934 //----------------------------------------------------------------------
935 Int32
StriperHalfEdgeGraph::calcEgdeLines(
936 vector
<StriperHalfEdgeGraph::IndexT
> & indexVec
,
939 UInt32 i
, nN
, j
, nE
, halfEdgeCount
= 0;
940 StriperHalfEdgeGraph::IndexT startVertexIndex
, endVertexIndex
;
945 nN
= UInt32(_edgeLinkVec
.size());
946 for (i
= 0; i
< nN
; ++i
)
948 nE
= UInt32(_edgeLinkVec
[i
].size());
949 for ( j
= 0; j
< nE
; ++j
)
951 halfEdge
= _edgeLinkVec
[i
][j
].second
;
952 startVertexIndex
= halfEdge
->vertexStart();
953 endVertexIndex
= halfEdge
->vertexEnd();
955 if ((isBorder
= (halfEdge
->twin
== 0)) || (startVertexIndex
<
958 indexVec
.push_back(startVertexIndex
);
959 indexVec
.push_back(endVertexIndex
);
961 indexVec
.push_back(isBorder
? 0 : 1);
966 return halfEdgeCount
;
969 //----------------------------------------------------------------------
972 // Date: Tue Feb 15 18:16:59 2000
975 //----------------------------------------------------------------------
976 void StriperHalfEdgeGraph::clear(void)
980 _edgeLinkVec
.clear();
981 _trianglePool
.clear();
983 n
= UInt32(_stripBag
.size());
984 for(i
= 0; i
< n
; ++i
)
985 delete _stripBag
[i
].second
;
988 n
= UInt32(_fanBag
.size());
989 for(i
= 0; i
< n
; ++i
)
990 delete _fanBag
[i
].second
;
993 n
= UInt32(_triBag
.size());
994 for(i
= 0; i
< n
; ++i
)
995 delete _triBag
[i
].second
;
999 #endif // remove from all but dev docs