nvtristrip-ruby 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,50 @@
1
+
2
+
3
+ = NvTriStrip: Triangle stripification library
4
+
5
+ NvTriStrip provides a Ruby interface for Nvidia's Triangle stripification
6
+ library.
7
+
8
+ Original NvTriStrip is available from http://developer.nvidia.com ,
9
+ I've slightly modified the lib to fix compiling on various platforms.
10
+
11
+ == Features
12
+
13
+ All features and shortcomings of Nvidia's library are (hopefully) preserved.
14
+
15
+ Namely, features
16
+ * Makes trianglestrips out of triangle lists
17
+ * Optimizes trianglestrips for good cache use
18
+ * Optionally stitches separate strips with degenerates
19
+ * Can throw small strips into a list instead
20
+ * Remaps indices to improve spatial locality in your vertex buffer
21
+
22
+ Some shortcomings
23
+ * Limited geometry to 65k vertices (But that also has some practical value)
24
+ * Uses global state to control some options (but should be otherwise reentrant)
25
+ * Ruby version has to do some back and forth copying of the indices
26
+
27
+ == Installation
28
+
29
+ With rubygems:
30
+ gem install nvtristrip-ruby
31
+
32
+ Otherwise:
33
+ ruby setup.rb
34
+
35
+ == Usage
36
+
37
+ require 'nvtristrip'
38
+
39
+ my_triangle_indices=[0,1,2,1,2,3] # Two triangles
40
+ validate=true
41
+
42
+ stripified_indices=NvTriStrip::generate_strips my_triangle_indices, validate
43
+
44
+ # And now stripifiedIndices is an Array of NvTriStrip::PrimitiveGroup objects, which are Enumerable.
45
+
46
+ remapped_indices=NvTriStrip::remap_indices stripified_indices, 3
47
+
48
+ # And now remappedIndices is an array of PrimitiveGroups objects with indices
49
+ # remapped for better spatial locality. With this information you should remap your vertex buffer.
50
+
data/README.nvtristrip ADDED
@@ -0,0 +1,32 @@
1
+ README for NvTriStrip, library version
2
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3
+
4
+ To use:
5
+ -#include "NvTriStrip.h"
6
+ -put nvtristrip.lib in your library path (the pragma in nvtristrip.h will automatically look for the library).
7
+
8
+ Check out NvTriStrip.h for the interface.
9
+
10
+ See the StripTest source code (in function LoadXFileStripped) for an example of using the library.
11
+
12
+ Features:
13
+ -generates strips from arbitrary geometry.
14
+ -flexibly optimizes for post TnL vertex caches (16 on GeForce1/2, 24 on GeForce3).
15
+ -can stitch together strips using degenerate triangles, or not.
16
+ -can output lists instead of strips.
17
+ -can optionally throw excessively small strips into a list instead.
18
+ -can remap indices to improve spatial locality in your vertex buffers.
19
+
20
+ On cache sizes:
21
+ Note that it's better to UNDERESTIMATE the cache size instead of OVERESTIMATING.
22
+ So, if you're targetting GeForce1, 2, and 3, be conservative and use the GeForce1_2 cache
23
+ size, NOT the GeForce3 cache size.
24
+ This will make sure you don't "blow" the cache of the GeForce1 and 2.
25
+ Also note that the cache size you specify is the "actual" cache size, not the "effective"
26
+ cache size you may have heard about. This is 16 for GeForce1 and 2, and 24 for GeForce3.
27
+
28
+ Credit goes to Curtis Beeson and Joe Demers for the basis for this stripifier and to Jason Regier and
29
+ Jon Stone at Blizzard for providing a much cleaner version of CreateStrips().
30
+
31
+ Questions/comments email cem@nvidia.com
32
+
data/Rakefile ADDED
@@ -0,0 +1,76 @@
1
+
2
+ require 'rubygems'
3
+ require 'rake/gempackagetask'
4
+
5
+ require 'rake/clean'
6
+ require 'rake/testtask'
7
+ require 'rake/rdoctask'
8
+
9
+ PKG_VERSION="0.5.0"
10
+
11
+ file "ext/Makefile" => [ "ext/extconf.rb"] do
12
+ Dir.chdir "ext" do
13
+ ruby "extconf.rb"
14
+ end
15
+ end
16
+
17
+ EXT_SO="ext/nvtristrip.#{Config::CONFIG['DLEXT']}"
18
+
19
+ file EXT_SO => FileList[
20
+ "ext/*.cpp",
21
+ "ext/*.h",
22
+ "ext/Makefile",
23
+ ] do
24
+
25
+ Dir.chdir "ext" do
26
+ sh "make"
27
+ end
28
+ cp EXT_SO, "lib"
29
+
30
+ end
31
+
32
+ task :default => :build
33
+
34
+ desc "Build the extension"
35
+ task :build => [EXT_SO]
36
+
37
+
38
+ spec=Gem::Specification.new do |s|
39
+ s.name="nvtristrip-ruby"
40
+ s.author="Mikko Lehtonen"
41
+ s.version=PKG_VERSION
42
+ s.summary="Interface for NvTriStrip triangle stripification library."
43
+ s.platform=Gem::Platform::RUBY
44
+ s.has_rdoc=true
45
+ s.extra_rdoc_files = ["README","TODO","ext/RbTriStrip.cpp"]
46
+ s.files=Dir.glob("{test,lib}/**/*")+
47
+ Dir.glob("ext/**/*.{cpp,h,rb}")+
48
+ ["Rakefile","setup.rb","README.nvtristrip", "README"]
49
+
50
+ s.require_path="lib"
51
+ s.test_files=Dir.glob("test/test_*.rb")
52
+ s.extensions=FileList["ext/extconf.rb"].to_a
53
+ s.homepage="http://nvtristrip.rubyforge.org"
54
+ s.rubyforge_project="nvtristrip"
55
+ end
56
+
57
+ Rake::GemPackageTask.new(spec) do |pkg|
58
+
59
+ end
60
+
61
+
62
+ Rake::TestTask.new do |t|
63
+ t.libs << "test"
64
+ t.test_files=FileList['test/test*.rb']
65
+ t.verbose=true
66
+ end
67
+
68
+ Rake::RDocTask.new do |r|
69
+ r.rdoc_dir='doc/rdoc'
70
+ # r.options
71
+ r.rdoc_files.add ['README','TODO', 'ext/RbTriStrip.cpp']
72
+ end
73
+
74
+
75
+ CLEAN.include ['**/*.o', '**/*.log', "lib/"+File.basename(EXT_SO), EXT_SO]
76
+
data/TODO ADDED
@@ -0,0 +1,7 @@
1
+
2
+ * Improve test-suite
3
+ * Convenience function for remapping vertexbuffers?
4
+ * Maybe try to modify nvtristrip lib to use the ruby arrays directly, so I wouldn't have to
5
+ copy the indices back and forth.
6
+ * Clean up some of the code and improve documentation
7
+ * Win32 precompiled gem
@@ -0,0 +1,501 @@
1
+
2
+ #include "NvTriStripObjects.h"
3
+ #include "NvTriStrip.h"
4
+
5
+ ////////////////////////////////////////////////////////////////////////////////////////
6
+ //private data
7
+ static unsigned int cacheSize = CACHESIZE_GEFORCE1_2;
8
+ static bool bStitchStrips = true;
9
+ static unsigned int minStripSize = 0;
10
+ static bool bListsOnly = false;
11
+ static unsigned int restartVal = 0;
12
+ static bool bRestart = false;
13
+
14
+ void EnableRestart(const unsigned int _restartVal)
15
+ {
16
+ bRestart = true;
17
+ restartVal = _restartVal;
18
+ }
19
+
20
+ void DisableRestart()
21
+ {
22
+ bRestart = false;
23
+ }
24
+
25
+ ////////////////////////////////////////////////////////////////////////////////////////
26
+ // SetListsOnly()
27
+ //
28
+ // If set to true, will return an optimized list, with no strips at all.
29
+ //
30
+ // Default value: false
31
+ //
32
+ void SetListsOnly(const bool _bListsOnly)
33
+ {
34
+ bListsOnly = _bListsOnly;
35
+ }
36
+
37
+ ////////////////////////////////////////////////////////////////////////////////////////
38
+ // SetCacheSize()
39
+ //
40
+ // Sets the cache size which the stripfier uses to optimize the data.
41
+ // Controls the length of the generated individual strips.
42
+ // This is the "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2
43
+ // You may want to play around with this number to tweak performance.
44
+ //
45
+ // Default value: 16
46
+ //
47
+ void SetCacheSize(const unsigned int _cacheSize)
48
+ {
49
+ cacheSize = _cacheSize;
50
+ }
51
+
52
+
53
+ ////////////////////////////////////////////////////////////////////////////////////////
54
+ // SetStitchStrips()
55
+ //
56
+ // bool to indicate whether to stitch together strips into one huge strip or not.
57
+ // If set to true, you'll get back one huge strip stitched together using degenerate
58
+ // triangles.
59
+ // If set to false, you'll get back a large number of separate strips.
60
+ //
61
+ // Default value: true
62
+ //
63
+ void SetStitchStrips(const bool _bStitchStrips)
64
+ {
65
+ bStitchStrips = _bStitchStrips;
66
+ }
67
+
68
+
69
+ ////////////////////////////////////////////////////////////////////////////////////////
70
+ // SetMinStripSize()
71
+ //
72
+ // Sets the minimum acceptable size for a strip, in triangles.
73
+ // All strips generated which are shorter than this will be thrown into one big, separate list.
74
+ //
75
+ // Default value: 0
76
+ //
77
+ void SetMinStripSize(const unsigned int _minStripSize)
78
+ {
79
+ minStripSize = _minStripSize;
80
+ }
81
+
82
+
83
+ ////////////////////////////////////////////////////////////////////////////////////////
84
+ //Cleanup strips / faces, used by generatestrips
85
+ void Cleanup(NvStripInfoVec& tempStrips, NvFaceInfoVec& tempFaces)
86
+ {
87
+ //delete strips
88
+ int i;
89
+ for(i = 0; i < tempStrips.size(); i++)
90
+ {
91
+ for(int j = 0; j < tempStrips[i]->m_faces.size(); j++)
92
+ {
93
+ delete tempStrips[i]->m_faces[j];
94
+ tempStrips[i]->m_faces[j] = NULL;
95
+ }
96
+ tempStrips[i]->m_faces.resize(0);
97
+ delete tempStrips[i];
98
+ tempStrips[i] = NULL;
99
+ }
100
+
101
+ //delete faces
102
+ for(i = 0; i < tempFaces.size(); i++)
103
+ {
104
+ delete tempFaces[i];
105
+ tempFaces[i] = NULL;
106
+ }
107
+ }
108
+
109
+
110
+ ////////////////////////////////////////////////////////////////////////////////////////
111
+ //SameTriangle()
112
+ //
113
+ //Returns true if the two triangles defined by firstTri and secondTri are the same
114
+ // The "same" is defined in this case as having the same indices with the same winding order
115
+ //
116
+ bool SameTriangle(unsigned short firstTri0, unsigned short firstTri1, unsigned short firstTri2,
117
+ unsigned short secondTri0, unsigned short secondTri1, unsigned short secondTri2)
118
+ {
119
+ bool isSame = false;
120
+
121
+ if (firstTri0 == secondTri0)
122
+ {
123
+ if (firstTri1 == secondTri1)
124
+ {
125
+ if (firstTri2 == secondTri2)
126
+ isSame = true;
127
+ }
128
+ }
129
+ else if (firstTri0 == secondTri1)
130
+ {
131
+ if (firstTri1 == secondTri2)
132
+ {
133
+ if (firstTri2 == secondTri0)
134
+ isSame = true;
135
+ }
136
+ }
137
+ else if (firstTri0 == secondTri2)
138
+ {
139
+ if (firstTri1 == secondTri0)
140
+ {
141
+ if (firstTri2 == secondTri1)
142
+ isSame = true;
143
+ }
144
+ }
145
+
146
+ return isSame;
147
+ }
148
+
149
+
150
+ bool TestTriangle(const unsigned short v0, const unsigned short v1, const unsigned short v2, const std::vector<NvFaceInfo>* in_bins, const int NUMBINS)
151
+ {
152
+ //hash this triangle
153
+ bool isLegit = false;
154
+ int ctr = v0 % NUMBINS;
155
+ for (int k = 0; k < in_bins[ctr].size(); ++k)
156
+ {
157
+ //check triangles in this bin
158
+ if (SameTriangle(in_bins[ctr][k].m_v0, in_bins[ctr][k].m_v1, in_bins[ctr][k].m_v2,
159
+ v0, v1, v2))
160
+ {
161
+ isLegit = true;
162
+ break;
163
+ }
164
+ }
165
+ if (!isLegit)
166
+ {
167
+ ctr = v1 % NUMBINS;
168
+ for (int k = 0; k < in_bins[ctr].size(); ++k)
169
+ {
170
+ //check triangles in this bin
171
+ if (SameTriangle(in_bins[ctr][k].m_v0, in_bins[ctr][k].m_v1, in_bins[ctr][k].m_v2,
172
+ v0, v1, v2))
173
+ {
174
+ isLegit = true;
175
+ break;
176
+ }
177
+ }
178
+
179
+ if (!isLegit)
180
+ {
181
+ ctr = v2 % NUMBINS;
182
+ for (int k = 0; k < in_bins[ctr].size(); ++k)
183
+ {
184
+ //check triangles in this bin
185
+ if (SameTriangle(in_bins[ctr][k].m_v0, in_bins[ctr][k].m_v1, in_bins[ctr][k].m_v2,
186
+ v0, v1, v2))
187
+ {
188
+ isLegit = true;
189
+ break;
190
+ }
191
+ }
192
+
193
+ }
194
+ }
195
+
196
+ return isLegit;
197
+ }
198
+
199
+
200
+ ////////////////////////////////////////////////////////////////////////////////////////
201
+ // GenerateStrips()
202
+ //
203
+ // in_indices: input index list, the indices you would use to render
204
+ // in_numIndices: number of entries in in_indices
205
+ // primGroups: array of optimized/stripified PrimitiveGroups
206
+ // numGroups: number of groups returned
207
+ //
208
+ // Be sure to call delete[] on the returned primGroups to avoid leaking mem
209
+ //
210
+ bool GenerateStrips(const unsigned short* in_indices, const unsigned int in_numIndices,
211
+ PrimitiveGroup** primGroups, unsigned short* numGroups, bool validateEnabled)
212
+ {
213
+ //put data in format that the stripifier likes
214
+ WordVec tempIndices;
215
+ tempIndices.resize(in_numIndices);
216
+ unsigned short maxIndex = 0;
217
+ unsigned short minIndex = 0xFFFF;
218
+ for(int i = 0; i < in_numIndices; i++)
219
+ {
220
+ tempIndices[i] = in_indices[i];
221
+ if (in_indices[i] > maxIndex)
222
+ maxIndex = in_indices[i];
223
+ if (in_indices[i] < minIndex)
224
+ minIndex = in_indices[i];
225
+ }
226
+ NvStripInfoVec tempStrips;
227
+ NvFaceInfoVec tempFaces;
228
+
229
+ NvStripifier stripifier;
230
+
231
+ //do actual stripification
232
+ stripifier.Stripify(tempIndices, cacheSize, minStripSize, maxIndex, tempStrips, tempFaces);
233
+
234
+ //stitch strips together
235
+ IntVec stripIndices;
236
+ unsigned int numSeparateStrips = 0;
237
+
238
+ if(bListsOnly)
239
+ {
240
+ //if we're outputting only lists, we're done
241
+ *numGroups = 1;
242
+ (*primGroups) = new PrimitiveGroup[*numGroups];
243
+ PrimitiveGroup* primGroupArray = *primGroups;
244
+
245
+ //count the total number of indices
246
+ unsigned int numIndices = 0;
247
+ int i;
248
+ for(i = 0; i < tempStrips.size(); i++)
249
+ {
250
+ numIndices += tempStrips[i]->m_faces.size() * 3;
251
+ }
252
+
253
+ //add in the list
254
+ numIndices += tempFaces.size() * 3;
255
+
256
+ primGroupArray[0].type = PT_LIST;
257
+ primGroupArray[0].numIndices = numIndices;
258
+ primGroupArray[0].indices = new unsigned short[numIndices];
259
+
260
+ //do strips
261
+ unsigned int indexCtr = 0;
262
+ for(i = 0; i < tempStrips.size(); i++)
263
+ {
264
+ for(int j = 0; j < tempStrips[i]->m_faces.size(); j++)
265
+ {
266
+ //degenerates are of no use with lists
267
+ if(!NvStripifier::IsDegenerate(tempStrips[i]->m_faces[j]))
268
+ {
269
+ primGroupArray[0].indices[indexCtr++] = tempStrips[i]->m_faces[j]->m_v0;
270
+ primGroupArray[0].indices[indexCtr++] = tempStrips[i]->m_faces[j]->m_v1;
271
+ primGroupArray[0].indices[indexCtr++] = tempStrips[i]->m_faces[j]->m_v2;
272
+ }
273
+ else
274
+ {
275
+ //we've removed a tri, reduce the number of indices
276
+ primGroupArray[0].numIndices -= 3;
277
+ }
278
+ }
279
+ }
280
+
281
+ //do lists
282
+ for(i = 0; i < tempFaces.size(); i++)
283
+ {
284
+ primGroupArray[0].indices[indexCtr++] = tempFaces[i]->m_v0;
285
+ primGroupArray[0].indices[indexCtr++] = tempFaces[i]->m_v1;
286
+ primGroupArray[0].indices[indexCtr++] = tempFaces[i]->m_v2;
287
+ }
288
+ }
289
+ else
290
+ {
291
+ stripifier.CreateStrips(tempStrips, stripIndices, bStitchStrips, numSeparateStrips, bRestart, restartVal);
292
+
293
+ //if we're stitching strips together, we better get back only one strip from CreateStrips()
294
+ assert( (bStitchStrips && (numSeparateStrips == 1)) || !bStitchStrips);
295
+
296
+ //convert to output format
297
+ *numGroups = numSeparateStrips; //for the strips
298
+ if(tempFaces.size() != 0)
299
+ (*numGroups)++; //we've got a list as well, increment
300
+ (*primGroups) = new PrimitiveGroup[*numGroups];
301
+
302
+ PrimitiveGroup* primGroupArray = *primGroups;
303
+
304
+ //first, the strips
305
+ int startingLoc = 0;
306
+ for(int stripCtr = 0; stripCtr < numSeparateStrips; stripCtr++)
307
+ {
308
+ int stripLength = 0;
309
+
310
+ if(!bStitchStrips)
311
+ {
312
+ //if we've got multiple strips, we need to figure out the correct length
313
+ int i;
314
+ for(i = startingLoc; i < stripIndices.size(); i++)
315
+ {
316
+ if(stripIndices[i] == -1)
317
+ break;
318
+ }
319
+
320
+ stripLength = i - startingLoc;
321
+ }
322
+ else
323
+ stripLength = stripIndices.size();
324
+
325
+ primGroupArray[stripCtr].type = PT_STRIP;
326
+ primGroupArray[stripCtr].indices = new unsigned short[stripLength];
327
+ primGroupArray[stripCtr].numIndices = stripLength;
328
+
329
+ int indexCtr = 0;
330
+ for(int i = startingLoc; i < stripLength + startingLoc; i++)
331
+ primGroupArray[stripCtr].indices[indexCtr++] = stripIndices[i];
332
+
333
+ //we add 1 to account for the -1 separating strips
334
+ //this doesn't break the stitched case since we'll exit the loop
335
+ startingLoc += stripLength + 1;
336
+ }
337
+
338
+ //next, the list
339
+ if(tempFaces.size() != 0)
340
+ {
341
+ int faceGroupLoc = (*numGroups) - 1; //the face group is the last one
342
+ primGroupArray[faceGroupLoc].type = PT_LIST;
343
+ primGroupArray[faceGroupLoc].indices = new unsigned short[tempFaces.size() * 3];
344
+ primGroupArray[faceGroupLoc].numIndices = tempFaces.size() * 3;
345
+ int indexCtr = 0;
346
+ for(int i = 0; i < tempFaces.size(); i++)
347
+ {
348
+ primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces[i]->m_v0;
349
+ primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces[i]->m_v1;
350
+ primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces[i]->m_v2;
351
+ }
352
+ }
353
+ }
354
+
355
+ //validate generated data against input
356
+ if (validateEnabled)
357
+ {
358
+ const int NUMBINS = 100;
359
+
360
+ std::vector<NvFaceInfo> in_bins[NUMBINS];
361
+
362
+ int i;
363
+ //hash input indices on first index
364
+ for (i = 0; i < in_numIndices; i += 3)
365
+ {
366
+ NvFaceInfo faceInfo(in_indices[i], in_indices[i + 1], in_indices[i + 2]);
367
+ in_bins[in_indices[i] % NUMBINS].push_back(faceInfo);
368
+ }
369
+
370
+ for (i = 0; i < *numGroups; ++i)
371
+ {
372
+ switch ((*primGroups)[i].type)
373
+ {
374
+ case PT_LIST:
375
+ {
376
+ for (int j = 0; j < (*primGroups)[i].numIndices; j += 3)
377
+ {
378
+ unsigned short v0 = (*primGroups)[i].indices[j];
379
+ unsigned short v1 = (*primGroups)[i].indices[j + 1];
380
+ unsigned short v2 = (*primGroups)[i].indices[j + 2];
381
+
382
+ //ignore degenerates
383
+ if (NvStripifier::IsDegenerate(v0, v1, v2))
384
+ continue;
385
+
386
+ if (!TestTriangle(v0, v1, v2, in_bins, NUMBINS))
387
+ {
388
+ Cleanup(tempStrips, tempFaces);
389
+ return false;
390
+ }
391
+ }
392
+ break;
393
+ }
394
+
395
+ case PT_STRIP:
396
+ {
397
+ int brokenCtr = 0;
398
+ bool flip = false;
399
+ for (int j = 2; j < (*primGroups)[i].numIndices; ++j)
400
+ {
401
+ unsigned short v0 = (*primGroups)[i].indices[j - 2];
402
+ unsigned short v1 = (*primGroups)[i].indices[j - 1];
403
+ unsigned short v2 = (*primGroups)[i].indices[j];
404
+
405
+ if (flip)
406
+ {
407
+ //swap v1 and v2
408
+ unsigned short swap = v1;
409
+ v1 = v2;
410
+ v2 = swap;
411
+ }
412
+
413
+ //ignore degenerates
414
+ if (NvStripifier::IsDegenerate(v0, v1, v2))
415
+ {
416
+ flip = !flip;
417
+ continue;
418
+ }
419
+
420
+ if (!TestTriangle(v0, v1, v2, in_bins, NUMBINS))
421
+ {
422
+ Cleanup(tempStrips, tempFaces);
423
+ return false;
424
+ }
425
+
426
+ flip = !flip;
427
+ }
428
+ break;
429
+ }
430
+
431
+ case PT_FAN:
432
+ default:
433
+ break;
434
+ }
435
+ }
436
+
437
+ }
438
+
439
+ //clean up everything
440
+ Cleanup(tempStrips, tempFaces);
441
+
442
+ return true;
443
+ }
444
+
445
+
446
+ ////////////////////////////////////////////////////////////////////////////////////////
447
+ // RemapIndices()
448
+ //
449
+ // Function to remap your indices to improve spatial locality in your vertex buffer.
450
+ //
451
+ // in_primGroups: array of PrimitiveGroups you want remapped
452
+ // numGroups: number of entries in in_primGroups
453
+ // numVerts: number of vertices in your vertex buffer, also can be thought of as the range
454
+ // of acceptable values for indices in your primitive groups.
455
+ // remappedGroups: array of remapped PrimitiveGroups
456
+ //
457
+ // Note that, according to the remapping handed back to you, you must reorder your
458
+ // vertex buffer.
459
+ //
460
+ void RemapIndices(const PrimitiveGroup* in_primGroups, const unsigned short numGroups,
461
+ const unsigned short numVerts, PrimitiveGroup** remappedGroups)
462
+ {
463
+ (*remappedGroups) = new PrimitiveGroup[numGroups];
464
+
465
+ //caches oldIndex --> newIndex conversion
466
+ int *indexCache;
467
+ indexCache = new int[numVerts];
468
+ memset(indexCache, -1, sizeof(int)*numVerts);
469
+
470
+ //loop over primitive groups
471
+ unsigned int indexCtr = 0;
472
+ for(int i = 0; i < numGroups; i++)
473
+ {
474
+ unsigned int numIndices = in_primGroups[i].numIndices;
475
+
476
+ //init remapped group
477
+ (*remappedGroups)[i].type = in_primGroups[i].type;
478
+ (*remappedGroups)[i].numIndices = numIndices;
479
+ (*remappedGroups)[i].indices = new unsigned short[numIndices];
480
+
481
+ for(int j = 0; j < numIndices; j++)
482
+ {
483
+ int cachedIndex = indexCache[in_primGroups[i].indices[j]];
484
+ if(cachedIndex == -1) //we haven't seen this index before
485
+ {
486
+ //point to "last" vertex in VB
487
+ (*remappedGroups)[i].indices[j] = indexCtr;
488
+
489
+ //add to index cache, increment
490
+ indexCache[in_primGroups[i].indices[j]] = indexCtr++;
491
+ }
492
+ else
493
+ {
494
+ //we've seen this index before
495
+ (*remappedGroups)[i].indices[j] = cachedIndex;
496
+ }
497
+ }
498
+ }
499
+
500
+ delete[] indexCache;
501
+ }