1 | // Copyright (C) 2012-2014 ChaosForge Ltd
|
---|
2 | // http://chaosforge.org/
|
---|
3 | //
|
---|
4 | // This file is part of NV Libraries.
|
---|
5 | // For conditions of distribution and use, see copyright notice in nv.hh
|
---|
6 |
|
---|
7 | #include "nv/formats/md3_loader.hh"
|
---|
8 |
|
---|
9 | #include "nv/core/logging.hh"
|
---|
10 |
|
---|
11 | using namespace nv;
|
---|
12 |
|
---|
13 | // based on http://www.icculus.org/~phaethon/q3/formats/md3format.html#Surface
|
---|
14 |
|
---|
15 | // assuming low-endian
|
---|
16 | #define MD3_MAX_FRAMES 1024
|
---|
17 | #define MD3_MAX_TAGS 16
|
---|
18 | #define MD3_MAX_SURFACES 32
|
---|
19 | #define MD3_MAX_SHADERS 256
|
---|
20 | #define MD3_MAX_VERTS 4096
|
---|
21 | #define MD3_MAX_TRIANGLES 8192
|
---|
22 | #define MD3_XYZ_SCALE (1.0f/64.0f)
|
---|
23 |
|
---|
24 | struct md3_vec3_t
|
---|
25 | {
|
---|
26 | float xyz[3];
|
---|
27 | };
|
---|
28 |
|
---|
29 | struct md3_header_t
|
---|
30 | {
|
---|
31 | char ident[4]; // IDP3
|
---|
32 | sint32 version; // 15
|
---|
33 | uint8 name[64]; // path name
|
---|
34 | sint32 flags;
|
---|
35 | sint32 num_frames; // Number of Frame objects, with a maximum of MD3_MAX_FRAMES. Current value of MD3_MAX_FRAMES is 1024.
|
---|
36 | sint32 num_tags; // Number of Tag objects, with a maximum of MD3_MAX_TAGS. Current value of MD3_MAX_TAGS is 16.
|
---|
37 | sint32 num_surfaces; // Number of Surface objects, with a maximum of MD3_MAX_SURFACES. Current value of MD3_MAX_SURFACES is 32.
|
---|
38 | sint32 num_skins; // Number of Skin objects. I should note that I have not seen an MD3 using this particular field for anything; this appears to be an artifact from the Quake 2 MD2 format. Surface objects have their own Shader field.
|
---|
39 | sint32 ofs_frames; // Relative offset from start of MD3 object where Frame objects start. The Frame objects are written sequentially, that is, when you read one Frame object, you do not need to seek() for the next object.
|
---|
40 | sint32 ofs_tags; // Relative offset from start of MD3 where Tag objects start. Similarly written sequentially.
|
---|
41 | sint32 ofs_surfaces; // Relative offset from start of MD3 where Surface objects start. Again, written sequentially.
|
---|
42 | sint32 ofs_eof; // Relative offset from start of MD3 to the end of the MD3 object. Note there is no offset for Skin objects.
|
---|
43 | };
|
---|
44 |
|
---|
45 | struct md3_frame_t
|
---|
46 | {
|
---|
47 | md3_vec3_t min_bounds;
|
---|
48 | md3_vec3_t max_bounds;
|
---|
49 | md3_vec3_t local_origin;
|
---|
50 | float radius;
|
---|
51 | uint8 name[16];
|
---|
52 | };
|
---|
53 |
|
---|
54 | struct md3_tag_t
|
---|
55 | {
|
---|
56 | uint8 name[64];
|
---|
57 | md3_vec3_t origin;
|
---|
58 | md3_vec3_t axis[3];
|
---|
59 | };
|
---|
60 |
|
---|
61 | struct md3_surface_header_t
|
---|
62 | {
|
---|
63 | char ident[4]; // IDP3
|
---|
64 | uint8 name[64]; // path name
|
---|
65 | sint32 flags;
|
---|
66 | sint32 num_frames;
|
---|
67 | sint32 num_shaders;
|
---|
68 | sint32 num_verts;
|
---|
69 | sint32 num_triangles;
|
---|
70 | sint32 ofs_triangles;
|
---|
71 | sint32 ofs_shaders;
|
---|
72 | sint32 ofs_st;
|
---|
73 | sint32 ofs_xyznormal;
|
---|
74 | sint32 ofs_end;
|
---|
75 | };
|
---|
76 |
|
---|
77 | struct md3_shader_t
|
---|
78 | {
|
---|
79 | uint8 name[64];
|
---|
80 | sint32 shader_index;
|
---|
81 | };
|
---|
82 |
|
---|
83 | struct md3_triangle_t
|
---|
84 | {
|
---|
85 | sint32 indexes[3];
|
---|
86 | };
|
---|
87 |
|
---|
88 | struct md3_texcoord_t
|
---|
89 | {
|
---|
90 | float st[2];
|
---|
91 | };
|
---|
92 |
|
---|
93 | struct md3_vertex_t
|
---|
94 | {
|
---|
95 | sint16 x;
|
---|
96 | sint16 y;
|
---|
97 | sint16 z;
|
---|
98 | uint16 normal;
|
---|
99 | };
|
---|
100 |
|
---|
101 | struct md3_surface_t
|
---|
102 | {
|
---|
103 | md3_surface_header_t header;
|
---|
104 | md3_shader_t* shaders;
|
---|
105 | md3_triangle_t* triangles;
|
---|
106 | md3_texcoord_t* st;
|
---|
107 | md3_vertex_t* vertices;
|
---|
108 | };
|
---|
109 |
|
---|
110 | struct md3_t
|
---|
111 | {
|
---|
112 | md3_header_t header;
|
---|
113 | md3_frame_t* frames;
|
---|
114 | md3_tag_t* tags;
|
---|
115 | md3_surface_t* surfaces;
|
---|
116 | // extra information (not in md3 file)
|
---|
117 | sint32 vertices_per_frame;
|
---|
118 | };
|
---|
119 |
|
---|
120 | static bool check_md3_magic( char* magic )
|
---|
121 | {
|
---|
122 | return magic[0] == 'I' && magic[1] == 'D' && magic[2] == 'P' && magic[3] == '3';
|
---|
123 | }
|
---|
124 |
|
---|
125 | static void free_md3_surface( md3_surface_t * surface )
|
---|
126 | {
|
---|
127 | delete[] surface->shaders;
|
---|
128 | delete[] surface->triangles;
|
---|
129 | delete[] surface->st;
|
---|
130 | delete[] surface->vertices;
|
---|
131 | }
|
---|
132 |
|
---|
133 | static void free_md3( md3_t * md3 )
|
---|
134 | {
|
---|
135 | sint32 count = md3->header.num_surfaces;
|
---|
136 | for ( sint32 i = 0; i < count; ++i )
|
---|
137 | {
|
---|
138 | free_md3_surface( &md3->surfaces[i] );
|
---|
139 | }
|
---|
140 | delete[] md3->frames;
|
---|
141 | delete[] md3->tags;
|
---|
142 | delete[] md3->surfaces;
|
---|
143 | }
|
---|
144 |
|
---|
145 | static bool read_surface( md3_surface_t * surface, nv::stream& source )
|
---|
146 | {
|
---|
147 | sint32 pos = static_cast< sint32 >( source.tell() );
|
---|
148 | source.read( &surface->header, sizeof(md3_surface_header_t), 1 );
|
---|
149 |
|
---|
150 | if ( !check_md3_magic( surface->header.ident ) ) return false;
|
---|
151 | if ( surface->header.num_frames > MD3_MAX_FRAMES ) return false;
|
---|
152 | if ( surface->header.num_shaders > MD3_MAX_SHADERS ) return false;
|
---|
153 | if ( surface->header.num_verts > MD3_MAX_VERTS ) return false;
|
---|
154 | if ( surface->header.num_triangles > MD3_MAX_TRIANGLES ) return false;
|
---|
155 |
|
---|
156 | surface->shaders = new md3_shader_t [ surface->header.num_shaders ];
|
---|
157 | surface->vertices = new md3_vertex_t [ surface->header.num_verts * surface->header.num_frames ];
|
---|
158 | surface->st = new md3_texcoord_t[ surface->header.num_verts ];
|
---|
159 | surface->triangles = new md3_triangle_t[ surface->header.num_triangles ];
|
---|
160 |
|
---|
161 | source.seek( pos + surface->header.ofs_shaders, origin::SET );
|
---|
162 | source.read( surface->shaders, sizeof( md3_shader_t ), static_cast<nv::size_t>( surface->header.num_shaders ) );
|
---|
163 |
|
---|
164 | source.seek( pos + surface->header.ofs_triangles, origin::SET );
|
---|
165 | source.read( surface->triangles, sizeof( md3_triangle_t ), static_cast<nv::size_t>( surface->header.num_triangles ) );
|
---|
166 |
|
---|
167 | source.seek( pos + surface->header.ofs_st, origin::SET );
|
---|
168 | source.read( surface->st, sizeof( md3_texcoord_t ), static_cast<nv::size_t>( surface->header.num_verts ) );
|
---|
169 |
|
---|
170 | source.seek( pos + surface->header.ofs_xyznormal, origin::SET );
|
---|
171 | source.read( surface->vertices, sizeof( md3_vertex_t ), static_cast<nv::size_t>( surface->header.num_verts * surface->header.num_frames ) );
|
---|
172 |
|
---|
173 | if ( source.tell() != static_cast<nv::size_t>( pos + surface->header.ofs_end ) ) return false;
|
---|
174 |
|
---|
175 | return true;
|
---|
176 | }
|
---|
177 |
|
---|
178 | static bool read_md3( md3_t * md3, nv::stream& source )
|
---|
179 | {
|
---|
180 | md3->frames = nullptr;
|
---|
181 | md3->tags = nullptr;
|
---|
182 | md3->surfaces = nullptr;
|
---|
183 |
|
---|
184 | source.read( &md3->header, sizeof(md3_header_t), 1 );
|
---|
185 |
|
---|
186 | if ( !check_md3_magic( md3->header.ident ) ) return false;
|
---|
187 | if ( md3->header.num_frames > MD3_MAX_FRAMES ) return false;
|
---|
188 | if ( md3->header.num_tags > MD3_MAX_TAGS ) return false;
|
---|
189 | if ( md3->header.num_surfaces > MD3_MAX_SURFACES )
|
---|
190 | {
|
---|
191 | // to always have a safe free
|
---|
192 | md3->header.num_surfaces = 0;
|
---|
193 | return false;
|
---|
194 | }
|
---|
195 |
|
---|
196 | md3->frames = new md3_frame_t [ md3->header.num_frames ];
|
---|
197 | md3->tags = new md3_tag_t [ md3->header.num_tags * md3->header.num_frames ];
|
---|
198 | md3->surfaces = new md3_surface_t[ md3->header.num_surfaces ];
|
---|
199 | nv::raw_zero_n( md3->surfaces, static_cast< nv::size_t >( md3->header.num_surfaces ) );
|
---|
200 |
|
---|
201 | source.seek( md3->header.ofs_frames, origin::SET );
|
---|
202 | source.read( md3->frames, sizeof( md3_frame_t ), static_cast<nv::size_t>( md3->header.num_frames ) );
|
---|
203 |
|
---|
204 | if ( md3->header.num_tags > 0 )
|
---|
205 | {
|
---|
206 | source.seek( md3->header.ofs_tags, origin::SET );
|
---|
207 | source.read( md3->tags, sizeof( md3_tag_t ), static_cast<nv::size_t>( md3->header.num_tags * md3->header.num_frames ) );
|
---|
208 | }
|
---|
209 |
|
---|
210 | source.seek( md3->header.ofs_surfaces, origin::SET );
|
---|
211 | md3->vertices_per_frame = 0;
|
---|
212 |
|
---|
213 | for ( sint32 i = 0; i < md3->header.num_surfaces; ++i )
|
---|
214 | {
|
---|
215 | if ( !read_surface( md3->surfaces + i, source ) ) return false;
|
---|
216 | if ( md3->header.num_frames != md3->surfaces[i].header.num_frames ) return false;
|
---|
217 | md3->vertices_per_frame += md3->surfaces[i].header.num_verts;
|
---|
218 | }
|
---|
219 | return true;
|
---|
220 | }
|
---|
221 |
|
---|
222 | static inline vec3 md3_vec3( const md3_vec3_t& v )
|
---|
223 | {
|
---|
224 | // return vec3( v.xyz[0], v.xyz[1], v.xyz[2] );
|
---|
225 | return vec3( v.xyz[0], v.xyz[2], v.xyz[1] );
|
---|
226 | }
|
---|
227 |
|
---|
228 | static inline vec2 md3_texcoord( const md3_texcoord_t& v )
|
---|
229 | {
|
---|
230 | return vec2( v.st[0], v.st[1] );
|
---|
231 | }
|
---|
232 |
|
---|
233 | static vec3 s_normal_cache[256*256];
|
---|
234 | static bool s_normal_ready = false;
|
---|
235 |
|
---|
236 | md3_loader::md3_loader( bool merge_all )
|
---|
237 | : m_merge_all( merge_all ), m_md3( nullptr )
|
---|
238 | {
|
---|
239 | if ( !s_normal_ready )
|
---|
240 | {
|
---|
241 | float pi = glm::pi<float>();
|
---|
242 | float convert = (2 * pi) / 255.0f;
|
---|
243 | int n = 0;
|
---|
244 | for ( int lat = 0; lat < 256; ++lat )
|
---|
245 | {
|
---|
246 | float flat = lat * convert;
|
---|
247 | float sin_lat = glm::sin( flat );
|
---|
248 | float cos_lat = glm::cos( flat );
|
---|
249 | for ( int lng = 0; lng < 256; ++lng, ++n )
|
---|
250 | {
|
---|
251 | float flng = lng * convert;
|
---|
252 | float sin_lng = glm::sin( flng );
|
---|
253 | float cos_lng = glm::cos( flng );
|
---|
254 | s_normal_cache[n].x = cos_lat * sin_lng;
|
---|
255 | // s_normal_cache[n].y = sin_lat * sin_lng;
|
---|
256 | // s_normal_cache[n].z = cos_lng;
|
---|
257 | s_normal_cache[n].z = sin_lat * sin_lng;
|
---|
258 | s_normal_cache[n].y = cos_lng;
|
---|
259 | }
|
---|
260 | }
|
---|
261 |
|
---|
262 | s_normal_ready = true;
|
---|
263 | }
|
---|
264 | }
|
---|
265 |
|
---|
266 |
|
---|
267 | nv::md3_loader::~md3_loader()
|
---|
268 | {
|
---|
269 | if (m_md3 != nullptr)
|
---|
270 | {
|
---|
271 | free_md3( (md3_t*)(m_md3) );
|
---|
272 | delete (md3_t*)m_md3;
|
---|
273 | }
|
---|
274 | }
|
---|
275 |
|
---|
276 | bool nv::md3_loader::load( stream& source )
|
---|
277 | {
|
---|
278 | m_md3 = (void*)(new md3_t);
|
---|
279 | if ( !read_md3( (md3_t*)m_md3, source ) )
|
---|
280 | {
|
---|
281 | return false;
|
---|
282 | }
|
---|
283 | return true;
|
---|
284 | }
|
---|
285 |
|
---|
286 | nv::key_raw_channel* nv::md3_loader::load_tags( const string_ref& tag )
|
---|
287 | {
|
---|
288 | md3_t* md3 = (md3_t*)m_md3;
|
---|
289 | key_raw_channel* result = key_raw_channel::create<md3_key>( (uint32)md3->header.num_frames );
|
---|
290 | // TODO: is this brain damaged in efficiency (loop nest order) or what?
|
---|
291 | for ( sint32 f = 0; f < md3->header.num_frames; ++f )
|
---|
292 | {
|
---|
293 | for ( sint32 i = 0; i < md3->header.num_tags; ++i )
|
---|
294 | {
|
---|
295 | const md3_tag_t& rtag = md3->tags[i + md3->header.num_tags * f];
|
---|
296 | string_ref rname((char*)(rtag.name));
|
---|
297 | if (rname == tag)
|
---|
298 | {
|
---|
299 | vec3 axisx ( md3_vec3( rtag.axis[0] ) );
|
---|
300 | vec3 axisz ( md3_vec3( rtag.axis[1] ) );
|
---|
301 | vec3 axisy ( md3_vec3( rtag.axis[2] ) );
|
---|
302 | vec3 origin ( md3_vec3( rtag.origin ) );
|
---|
303 | ((md3_key*)(result->data))[f].tform = transform( origin, quat( mat3( axisx, axisy, axisz ) ) );
|
---|
304 | }
|
---|
305 | }
|
---|
306 |
|
---|
307 | }
|
---|
308 | return result;
|
---|
309 | }
|
---|
310 |
|
---|
311 | struct vtx_md3_pn
|
---|
312 | {
|
---|
313 | nv::vec3 position;
|
---|
314 | nv::vec3 normal;
|
---|
315 | };
|
---|
316 |
|
---|
317 | struct vtx_md3_t
|
---|
318 | {
|
---|
319 | nv::vec2 texcoord;
|
---|
320 | };
|
---|
321 |
|
---|
322 | mesh_data* nv::md3_loader::release_mesh_data( nv::size_t index )
|
---|
323 | {
|
---|
324 | mesh_data* data = new mesh_data;
|
---|
325 | release_mesh_frame( data, -1, (sint32)index );
|
---|
326 | return data;
|
---|
327 | }
|
---|
328 |
|
---|
329 | void nv::md3_loader::release_mesh_frame( mesh_data* data, sint32 frame, sint32 surface )
|
---|
330 | {
|
---|
331 | md3_t* md3 = (md3_t*)m_md3;
|
---|
332 | uint32 num_surfaces = (uint32)md3->header.num_surfaces;
|
---|
333 | uint32 num_verts = 0;
|
---|
334 | uint32 current_frame = ( frame == -1 ? 0 : (uint32)frame );
|
---|
335 | uint32 frame_count = ( frame == -1 ? (uint32)md3->header.num_frames : 1 );
|
---|
336 | uint32 current_surf = ( surface == -1 ? 0 : (uint32)surface );
|
---|
337 | uint32 surf_count = ( surface == -1 ? (uint32)md3->header.num_surfaces : 1 );
|
---|
338 | uint32 index_count = 0;
|
---|
339 |
|
---|
340 | if ( surface >= 0 )
|
---|
341 | {
|
---|
342 | index_count = (uint32)md3->surfaces[(uint32)surface].header.num_triangles * 3;
|
---|
343 | num_verts = (uint32)md3->surfaces[(uint32)surface].header.num_verts;
|
---|
344 | }
|
---|
345 | else
|
---|
346 | for ( uint32 i = 0; i < num_surfaces; ++i )
|
---|
347 | {
|
---|
348 | index_count += (uint32)md3->surfaces[i].header.num_triangles * 3;
|
---|
349 | num_verts += (uint32)md3->surfaces[i].header.num_verts;
|
---|
350 | }
|
---|
351 |
|
---|
352 | mesh_raw_channel* mc_pn = mesh_raw_channel::create< vtx_md3_pn >( num_verts * frame_count );
|
---|
353 | mesh_raw_channel* mc_t = mesh_raw_channel::create< vtx_md3_t >( num_verts );
|
---|
354 | mesh_raw_channel* ic = mesh_raw_channel::create_index< uint16 >( index_count );
|
---|
355 | vtx_md3_pn* vtx_pn = (vtx_md3_pn*)mc_pn->data;
|
---|
356 | vtx_md3_t* vtx_t = (vtx_md3_t*) mc_t->data;
|
---|
357 | uint16* icp = (uint16*)ic->data;
|
---|
358 |
|
---|
359 | uint32 index = 0;
|
---|
360 | uint32 iindex = 0;
|
---|
361 | sint32 index_base = 0;
|
---|
362 |
|
---|
363 | while ( surf_count > 0 )
|
---|
364 | {
|
---|
365 | const md3_surface_t& sface = md3->surfaces[ current_surf ];
|
---|
366 | const uint32 vcount = static_cast< uint32 >( sface.header.num_verts );
|
---|
367 | const uint32 tcount = static_cast< uint32 >( sface.header.num_triangles );
|
---|
368 |
|
---|
369 | for (uint32 j = 0; j < vcount; ++j )
|
---|
370 | {
|
---|
371 | vtx_t[index++].texcoord = md3_texcoord( sface.st[j] );
|
---|
372 | }
|
---|
373 |
|
---|
374 | for (size_t j = 0; j < tcount; ++j )
|
---|
375 | {
|
---|
376 | const md3_triangle_t& t = sface.triangles[j];
|
---|
377 | icp[iindex++] = static_cast< uint16 >( index_base + t.indexes[0] );
|
---|
378 | icp[iindex++] = static_cast< uint16 >( index_base + t.indexes[1] );
|
---|
379 | icp[iindex++] = static_cast< uint16 >( index_base + t.indexes[2] );
|
---|
380 | }
|
---|
381 | index_base += sface.header.num_verts;
|
---|
382 | ++current_surf;
|
---|
383 | --surf_count;
|
---|
384 | }
|
---|
385 |
|
---|
386 | index = 0;
|
---|
387 | while ( frame_count > 0 )
|
---|
388 | {
|
---|
389 | current_surf = ( surface == -1 ? 0 : (uint32)surface );
|
---|
390 | surf_count = ( surface == -1 ? (uint32)md3->header.num_surfaces : 1 );
|
---|
391 |
|
---|
392 | while ( surf_count > 0 )
|
---|
393 | {
|
---|
394 | md3_surface_t& sface = md3->surfaces[current_surf];
|
---|
395 | uint32 vcount = (uint32)sface.header.num_verts;
|
---|
396 | uint32 offset = vcount * current_frame;
|
---|
397 | uint32 limit = vcount + offset;
|
---|
398 | for (uint32 j = offset; j < limit; ++j )
|
---|
399 | {
|
---|
400 | md3_vertex_t& v = sface.vertices[j];
|
---|
401 | vtx_pn[index].position = vec3( v.x * MD3_XYZ_SCALE, v.z * MD3_XYZ_SCALE, v.y * MD3_XYZ_SCALE );
|
---|
402 | vtx_pn[index].normal = s_normal_cache[ v.normal ];
|
---|
403 | index++;
|
---|
404 | }
|
---|
405 | ++current_surf;
|
---|
406 | --surf_count;
|
---|
407 | }
|
---|
408 | ++current_frame;
|
---|
409 | --frame_count;
|
---|
410 | }
|
---|
411 |
|
---|
412 | data->set_name( (char*)md3->header.name );
|
---|
413 | data->add_channel( mc_pn );
|
---|
414 | data->add_channel( mc_t );
|
---|
415 | data->add_channel( ic );
|
---|
416 | }
|
---|
417 |
|
---|
418 | mesh_nodes_data* nv::md3_loader::release_mesh_nodes_data( nv::size_t )
|
---|
419 | {
|
---|
420 | md3_t* md3 = (md3_t*)m_md3;
|
---|
421 | uint32 node_count = (uint32)md3->header.num_tags;
|
---|
422 | if ( node_count == 0 ) return nullptr;
|
---|
423 | mesh_node_data* nodes = new mesh_node_data[ node_count ];
|
---|
424 | for ( uint32 i = 0; i < node_count; ++i )
|
---|
425 | {
|
---|
426 | const md3_tag_t& rtag = md3->tags[i];
|
---|
427 | string_ref name( (char*)(rtag.name) );
|
---|
428 |
|
---|
429 | nodes[i].transform = mat4();
|
---|
430 | nodes[i].name = name.to_string();
|
---|
431 | nodes[i].parent_id = -1;
|
---|
432 | nodes[i].target_id = -1;
|
---|
433 | nodes[i].data = new key_data;
|
---|
434 |
|
---|
435 | key_raw_channel* keys = load_tags( name );
|
---|
436 | nodes[i].data->add_channel( keys );
|
---|
437 | }
|
---|
438 | return new mesh_nodes_data( "tags", node_count, nodes );
|
---|
439 | }
|
---|
440 |
|
---|
441 | mesh_data_pack* nv::md3_loader::release_mesh_data_pack()
|
---|
442 | {
|
---|
443 | md3_t* md3 = (md3_t*)m_md3;
|
---|
444 | uint32 count = 1;
|
---|
445 | mesh_data* data = nullptr;
|
---|
446 | if ( m_merge_all )
|
---|
447 | {
|
---|
448 | data = new mesh_data[1];
|
---|
449 | release_mesh_frame( &data[0], -1, -1 );
|
---|
450 | data[0].set_name( (char*)md3->header.name );
|
---|
451 | }
|
---|
452 | else
|
---|
453 | {
|
---|
454 | count = (uint32)md3->header.num_surfaces;
|
---|
455 | data = new mesh_data[ count ];
|
---|
456 | for ( uint32 i = 0; i < count; ++i )
|
---|
457 | {
|
---|
458 | release_mesh_frame( &data[i], -1, (sint32)i );
|
---|
459 | data[i].set_name( (char*)md3->surfaces[i].header.name );
|
---|
460 | }
|
---|
461 | }
|
---|
462 | return new mesh_data_pack( count, data, release_mesh_nodes_data() );
|
---|
463 | }
|
---|
464 |
|
---|
465 | nv::size_t md3_loader::get_max_frames() const
|
---|
466 | {
|
---|
467 | return static_cast< size_t >( ((md3_t*)m_md3)->header.num_frames );
|
---|
468 | }
|
---|