rev |
line source |
nuclear@0
|
1 /*
|
nuclear@0
|
2 ---------------------------------------------------------------------------
|
nuclear@0
|
3 Open Asset Import Library (assimp)
|
nuclear@0
|
4 ---------------------------------------------------------------------------
|
nuclear@0
|
5
|
nuclear@0
|
6 Copyright (c) 2006-2012, assimp team
|
nuclear@0
|
7
|
nuclear@0
|
8 All rights reserved.
|
nuclear@0
|
9
|
nuclear@0
|
10 Redistribution and use of this software in source and binary forms,
|
nuclear@0
|
11 with or without modification, are permitted provided that the following
|
nuclear@0
|
12 conditions are met:
|
nuclear@0
|
13
|
nuclear@0
|
14 * Redistributions of source code must retain the above
|
nuclear@0
|
15 copyright notice, this list of conditions and the
|
nuclear@0
|
16 following disclaimer.
|
nuclear@0
|
17
|
nuclear@0
|
18 * Redistributions in binary form must reproduce the above
|
nuclear@0
|
19 copyright notice, this list of conditions and the
|
nuclear@0
|
20 following disclaimer in the documentation and/or other
|
nuclear@0
|
21 materials provided with the distribution.
|
nuclear@0
|
22
|
nuclear@0
|
23 * Neither the name of the assimp team, nor the names of its
|
nuclear@0
|
24 contributors may be used to endorse or promote products
|
nuclear@0
|
25 derived from this software without specific prior
|
nuclear@0
|
26 written permission of the assimp team.
|
nuclear@0
|
27
|
nuclear@0
|
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
nuclear@0
|
29 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
nuclear@0
|
30 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
nuclear@0
|
31 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
nuclear@0
|
32 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
nuclear@0
|
33 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
nuclear@0
|
34 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
nuclear@0
|
35 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
nuclear@0
|
36 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
nuclear@0
|
37 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
nuclear@0
|
38 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
nuclear@0
|
39 ---------------------------------------------------------------------------
|
nuclear@0
|
40 */
|
nuclear@0
|
41
|
nuclear@0
|
42 /** @file MD3Loader.cpp
|
nuclear@0
|
43 * @brief Implementation of the MD3 importer class
|
nuclear@0
|
44 *
|
nuclear@0
|
45 * Sources:
|
nuclear@0
|
46 * http://www.gamers.org/dEngine/quake3/UQ3S
|
nuclear@0
|
47 * http://linux.ucla.edu/~phaethon/q3/formats/md3format.html
|
nuclear@0
|
48 * http://www.heppler.com/shader/shader/
|
nuclear@0
|
49 */
|
nuclear@0
|
50
|
nuclear@0
|
51 #include "AssimpPCH.h"
|
nuclear@0
|
52 #ifndef ASSIMP_BUILD_NO_MD3_IMPORTER
|
nuclear@0
|
53
|
nuclear@0
|
54 #include "MD3Loader.h"
|
nuclear@0
|
55 #include "ByteSwap.h"
|
nuclear@0
|
56 #include "SceneCombiner.h"
|
nuclear@0
|
57 #include "GenericProperty.h"
|
nuclear@0
|
58 #include "RemoveComments.h"
|
nuclear@0
|
59 #include "ParsingUtils.h"
|
nuclear@0
|
60 #include "Importer.h"
|
nuclear@0
|
61
|
nuclear@0
|
62 using namespace Assimp;
|
nuclear@0
|
63
|
nuclear@0
|
64 static const aiImporterDesc desc = {
|
nuclear@0
|
65 "Quake III Mesh Importer",
|
nuclear@0
|
66 "",
|
nuclear@0
|
67 "",
|
nuclear@0
|
68 "",
|
nuclear@0
|
69 aiImporterFlags_SupportBinaryFlavour,
|
nuclear@0
|
70 0,
|
nuclear@0
|
71 0,
|
nuclear@0
|
72 0,
|
nuclear@0
|
73 0,
|
nuclear@0
|
74 "md3"
|
nuclear@0
|
75 };
|
nuclear@0
|
76
|
nuclear@0
|
77 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
78 // Convert a Q3 shader blend function to the appropriate enum value
|
nuclear@0
|
79 Q3Shader::BlendFunc StringToBlendFunc(const std::string& m)
|
nuclear@0
|
80 {
|
nuclear@0
|
81 if (m == "GL_ONE") {
|
nuclear@0
|
82 return Q3Shader::BLEND_GL_ONE;
|
nuclear@0
|
83 }
|
nuclear@0
|
84 if (m == "GL_ZERO") {
|
nuclear@0
|
85 return Q3Shader::BLEND_GL_ZERO;
|
nuclear@0
|
86 }
|
nuclear@0
|
87 if (m == "GL_SRC_ALPHA") {
|
nuclear@0
|
88 return Q3Shader::BLEND_GL_SRC_ALPHA;
|
nuclear@0
|
89 }
|
nuclear@0
|
90 if (m == "GL_ONE_MINUS_SRC_ALPHA") {
|
nuclear@0
|
91 return Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA;
|
nuclear@0
|
92 }
|
nuclear@0
|
93 if (m == "GL_ONE_MINUS_DST_COLOR") {
|
nuclear@0
|
94 return Q3Shader::BLEND_GL_ONE_MINUS_DST_COLOR;
|
nuclear@0
|
95 }
|
nuclear@0
|
96 DefaultLogger::get()->error("Q3Shader: Unknown blend function: " + m);
|
nuclear@0
|
97 return Q3Shader::BLEND_NONE;
|
nuclear@0
|
98 }
|
nuclear@0
|
99
|
nuclear@0
|
100 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
101 // Load a Quake 3 shader
|
nuclear@0
|
102 bool Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* io)
|
nuclear@0
|
103 {
|
nuclear@0
|
104 boost::scoped_ptr<IOStream> file( io->Open( pFile, "rt"));
|
nuclear@0
|
105 if (!file.get())
|
nuclear@0
|
106 return false; // if we can't access the file, don't worry and return
|
nuclear@0
|
107
|
nuclear@0
|
108 DefaultLogger::get()->info("Loading Quake3 shader file " + pFile);
|
nuclear@0
|
109
|
nuclear@0
|
110 // read file in memory
|
nuclear@0
|
111 const size_t s = file->FileSize();
|
nuclear@0
|
112 std::vector<char> _buff(s+1);
|
nuclear@0
|
113 file->Read(&_buff[0],s,1);
|
nuclear@0
|
114 _buff[s] = 0;
|
nuclear@0
|
115
|
nuclear@0
|
116 // remove comments from it (C++ style)
|
nuclear@0
|
117 CommentRemover::RemoveLineComments("//",&_buff[0]);
|
nuclear@0
|
118 const char* buff = &_buff[0];
|
nuclear@0
|
119
|
nuclear@0
|
120 Q3Shader::ShaderDataBlock* curData = NULL;
|
nuclear@0
|
121 Q3Shader::ShaderMapBlock* curMap = NULL;
|
nuclear@0
|
122
|
nuclear@0
|
123 // read line per line
|
nuclear@0
|
124 for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
|
nuclear@0
|
125
|
nuclear@0
|
126 if (*buff == '{') {
|
nuclear@0
|
127 ++buff;
|
nuclear@0
|
128
|
nuclear@0
|
129 // append to last section, if any
|
nuclear@0
|
130 if (!curData) {
|
nuclear@0
|
131 DefaultLogger::get()->error("Q3Shader: Unexpected shader section token \'{\'");
|
nuclear@0
|
132 return true; // still no failure, the file is there
|
nuclear@0
|
133 }
|
nuclear@0
|
134
|
nuclear@0
|
135 // read this data section
|
nuclear@0
|
136 for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
|
nuclear@0
|
137 if (*buff == '{') {
|
nuclear@0
|
138 ++buff;
|
nuclear@0
|
139 // add new map section
|
nuclear@0
|
140 curData->maps.push_back(Q3Shader::ShaderMapBlock());
|
nuclear@0
|
141 curMap = &curData->maps.back();
|
nuclear@0
|
142
|
nuclear@0
|
143 for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
|
nuclear@0
|
144 // 'map' - Specifies texture file name
|
nuclear@0
|
145 if (TokenMatchI(buff,"map",3) || TokenMatchI(buff,"clampmap",8)) {
|
nuclear@0
|
146 curMap->name = GetNextToken(buff);
|
nuclear@0
|
147 }
|
nuclear@0
|
148 // 'blendfunc' - Alpha blending mode
|
nuclear@0
|
149 else if (TokenMatchI(buff,"blendfunc",9)) {
|
nuclear@0
|
150 const std::string blend_src = GetNextToken(buff);
|
nuclear@0
|
151 if (blend_src == "add") {
|
nuclear@0
|
152 curMap->blend_src = Q3Shader::BLEND_GL_ONE;
|
nuclear@0
|
153 curMap->blend_dest = Q3Shader::BLEND_GL_ONE;
|
nuclear@0
|
154 }
|
nuclear@0
|
155 else if (blend_src == "filter") {
|
nuclear@0
|
156 curMap->blend_src = Q3Shader::BLEND_GL_DST_COLOR;
|
nuclear@0
|
157 curMap->blend_dest = Q3Shader::BLEND_GL_ZERO;
|
nuclear@0
|
158 }
|
nuclear@0
|
159 else if (blend_src == "blend") {
|
nuclear@0
|
160 curMap->blend_src = Q3Shader::BLEND_GL_SRC_ALPHA;
|
nuclear@0
|
161 curMap->blend_dest = Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA;
|
nuclear@0
|
162 }
|
nuclear@0
|
163 else {
|
nuclear@0
|
164 curMap->blend_src = StringToBlendFunc(blend_src);
|
nuclear@0
|
165 curMap->blend_dest = StringToBlendFunc(GetNextToken(buff));
|
nuclear@0
|
166 }
|
nuclear@0
|
167 }
|
nuclear@0
|
168 // 'alphafunc' - Alpha testing mode
|
nuclear@0
|
169 else if (TokenMatchI(buff,"alphafunc",9)) {
|
nuclear@0
|
170 const std::string at = GetNextToken(buff);
|
nuclear@0
|
171 if (at == "GT0") {
|
nuclear@0
|
172 curMap->alpha_test = Q3Shader::AT_GT0;
|
nuclear@0
|
173 }
|
nuclear@0
|
174 else if (at == "LT128") {
|
nuclear@0
|
175 curMap->alpha_test = Q3Shader::AT_LT128;
|
nuclear@0
|
176 }
|
nuclear@0
|
177 else if (at == "GE128") {
|
nuclear@0
|
178 curMap->alpha_test = Q3Shader::AT_GE128;
|
nuclear@0
|
179 }
|
nuclear@0
|
180 }
|
nuclear@0
|
181 else if (*buff == '}') {
|
nuclear@0
|
182 ++buff;
|
nuclear@0
|
183 // close this map section
|
nuclear@0
|
184 curMap = NULL;
|
nuclear@0
|
185 break;
|
nuclear@0
|
186 }
|
nuclear@0
|
187 }
|
nuclear@0
|
188
|
nuclear@0
|
189 }
|
nuclear@0
|
190 else if (*buff == '}') {
|
nuclear@0
|
191 ++buff;
|
nuclear@0
|
192 curData = NULL;
|
nuclear@0
|
193 break;
|
nuclear@0
|
194 }
|
nuclear@0
|
195
|
nuclear@0
|
196 // 'cull' specifies culling behaviour for the model
|
nuclear@0
|
197 else if (TokenMatchI(buff,"cull",4)) {
|
nuclear@0
|
198 SkipSpaces(&buff);
|
nuclear@0
|
199 if (!ASSIMP_strincmp(buff,"back",4)) {
|
nuclear@0
|
200 curData->cull = Q3Shader::CULL_CCW;
|
nuclear@0
|
201 }
|
nuclear@0
|
202 else if (!ASSIMP_strincmp(buff,"front",5)) {
|
nuclear@0
|
203 curData->cull = Q3Shader::CULL_CW;
|
nuclear@0
|
204 }
|
nuclear@0
|
205 else if (!ASSIMP_strincmp(buff,"none",4) || !ASSIMP_strincmp(buff,"disable",7)) {
|
nuclear@0
|
206 curData->cull = Q3Shader::CULL_NONE;
|
nuclear@0
|
207 }
|
nuclear@0
|
208 else DefaultLogger::get()->error("Q3Shader: Unrecognized cull mode");
|
nuclear@0
|
209 }
|
nuclear@0
|
210 }
|
nuclear@0
|
211 }
|
nuclear@0
|
212
|
nuclear@0
|
213 else {
|
nuclear@0
|
214 // add new section
|
nuclear@0
|
215 fill.blocks.push_back(Q3Shader::ShaderDataBlock());
|
nuclear@0
|
216 curData = &fill.blocks.back();
|
nuclear@0
|
217
|
nuclear@0
|
218 // get the name of this section
|
nuclear@0
|
219 curData->name = GetNextToken(buff);
|
nuclear@0
|
220 }
|
nuclear@0
|
221 }
|
nuclear@0
|
222 return true;
|
nuclear@0
|
223 }
|
nuclear@0
|
224
|
nuclear@0
|
225 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
226 // Load a Quake 3 skin
|
nuclear@0
|
227 bool Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
|
nuclear@0
|
228 {
|
nuclear@0
|
229 boost::scoped_ptr<IOStream> file( io->Open( pFile, "rt"));
|
nuclear@0
|
230 if (!file.get())
|
nuclear@0
|
231 return false; // if we can't access the file, don't worry and return
|
nuclear@0
|
232
|
nuclear@0
|
233 DefaultLogger::get()->info("Loading Quake3 skin file " + pFile);
|
nuclear@0
|
234
|
nuclear@0
|
235 // read file in memory
|
nuclear@0
|
236 const size_t s = file->FileSize();
|
nuclear@0
|
237 std::vector<char> _buff(s+1);const char* buff = &_buff[0];
|
nuclear@0
|
238 file->Read(&_buff[0],s,1);
|
nuclear@0
|
239 _buff[s] = 0;
|
nuclear@0
|
240
|
nuclear@0
|
241 // remove commas
|
nuclear@0
|
242 std::replace(_buff.begin(),_buff.end(),',',' ');
|
nuclear@0
|
243
|
nuclear@0
|
244 // read token by token and fill output table
|
nuclear@0
|
245 for (;*buff;) {
|
nuclear@0
|
246 SkipSpacesAndLineEnd(&buff);
|
nuclear@0
|
247
|
nuclear@0
|
248 // get first identifier
|
nuclear@0
|
249 std::string ss = GetNextToken(buff);
|
nuclear@0
|
250
|
nuclear@0
|
251 // ignore tokens starting with tag_
|
nuclear@0
|
252 if (!::strncmp(&ss[0],"tag_",std::min((size_t)4, ss.length())))
|
nuclear@0
|
253 continue;
|
nuclear@0
|
254
|
nuclear@0
|
255 fill.textures.push_back(SkinData::TextureEntry());
|
nuclear@0
|
256 SkinData::TextureEntry& s = fill.textures.back();
|
nuclear@0
|
257
|
nuclear@0
|
258 s.first = ss;
|
nuclear@0
|
259 s.second = GetNextToken(buff);
|
nuclear@0
|
260 }
|
nuclear@0
|
261 return true;
|
nuclear@0
|
262 }
|
nuclear@0
|
263
|
nuclear@0
|
264 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
265 // Convert Q3Shader to material
|
nuclear@0
|
266 void Q3Shader::ConvertShaderToMaterial(aiMaterial* out, const ShaderDataBlock& shader)
|
nuclear@0
|
267 {
|
nuclear@0
|
268 ai_assert(NULL != out);
|
nuclear@0
|
269
|
nuclear@0
|
270 /* IMPORTANT: This is not a real conversion. Actually we're just guessing and
|
nuclear@0
|
271 * hacking around to build an aiMaterial that looks nearly equal to the
|
nuclear@0
|
272 * original Quake 3 shader. We're missing some important features like
|
nuclear@0
|
273 * animatable material properties in our material system, but at least
|
nuclear@0
|
274 * multiple textures should be handled correctly.
|
nuclear@0
|
275 */
|
nuclear@0
|
276
|
nuclear@0
|
277 // Two-sided material?
|
nuclear@0
|
278 if (shader.cull == Q3Shader::CULL_NONE) {
|
nuclear@0
|
279 const int twosided = 1;
|
nuclear@0
|
280 out->AddProperty(&twosided,1,AI_MATKEY_TWOSIDED);
|
nuclear@0
|
281 }
|
nuclear@0
|
282
|
nuclear@0
|
283 unsigned int cur_emissive = 0, cur_diffuse = 0, cur_lm =0;
|
nuclear@0
|
284
|
nuclear@0
|
285 // Iterate through all textures
|
nuclear@0
|
286 for (std::list< Q3Shader::ShaderMapBlock >::const_iterator it = shader.maps.begin(); it != shader.maps.end();++it) {
|
nuclear@0
|
287
|
nuclear@0
|
288 // CONVERSION BEHAVIOUR:
|
nuclear@0
|
289 //
|
nuclear@0
|
290 //
|
nuclear@0
|
291 // If the texture is additive
|
nuclear@0
|
292 // - if it is the first texture, assume additive blending for the whole material
|
nuclear@0
|
293 // - otherwise register it as emissive texture.
|
nuclear@0
|
294 //
|
nuclear@0
|
295 // If the texture is using standard blend (or if the blend mode is unknown)
|
nuclear@0
|
296 // - if first texture: assume default blending for material
|
nuclear@0
|
297 // - in any case: set it as diffuse texture
|
nuclear@0
|
298 //
|
nuclear@0
|
299 // If the texture is using 'filter' blending
|
nuclear@0
|
300 // - take as lightmap
|
nuclear@0
|
301 //
|
nuclear@0
|
302 // Textures with alpha funcs
|
nuclear@0
|
303 // - aiTextureFlags_UseAlpha is set (otherwise aiTextureFlags_NoAlpha is explicitly set)
|
nuclear@0
|
304 aiString s((*it).name);
|
nuclear@0
|
305 aiTextureType type; unsigned int index;
|
nuclear@0
|
306
|
nuclear@0
|
307 if ((*it).blend_src == Q3Shader::BLEND_GL_ONE && (*it).blend_dest == Q3Shader::BLEND_GL_ONE) {
|
nuclear@0
|
308 if (it == shader.maps.begin()) {
|
nuclear@0
|
309 const int additive = aiBlendMode_Additive;
|
nuclear@0
|
310 out->AddProperty(&additive,1,AI_MATKEY_BLEND_FUNC);
|
nuclear@0
|
311
|
nuclear@0
|
312 index = cur_diffuse++;
|
nuclear@0
|
313 type = aiTextureType_DIFFUSE;
|
nuclear@0
|
314 }
|
nuclear@0
|
315 else {
|
nuclear@0
|
316 index = cur_emissive++;
|
nuclear@0
|
317 type = aiTextureType_EMISSIVE;
|
nuclear@0
|
318 }
|
nuclear@0
|
319 }
|
nuclear@0
|
320 else if ((*it).blend_src == Q3Shader::BLEND_GL_DST_COLOR && (*it).blend_dest == Q3Shader::BLEND_GL_ZERO) {
|
nuclear@0
|
321 index = cur_lm++;
|
nuclear@0
|
322 type = aiTextureType_LIGHTMAP;
|
nuclear@0
|
323 }
|
nuclear@0
|
324 else {
|
nuclear@0
|
325 const int blend = aiBlendMode_Default;
|
nuclear@0
|
326 out->AddProperty(&blend,1,AI_MATKEY_BLEND_FUNC);
|
nuclear@0
|
327
|
nuclear@0
|
328 index = cur_diffuse++;
|
nuclear@0
|
329 type = aiTextureType_DIFFUSE;
|
nuclear@0
|
330 }
|
nuclear@0
|
331
|
nuclear@0
|
332 // setup texture
|
nuclear@0
|
333 out->AddProperty(&s,AI_MATKEY_TEXTURE(type,index));
|
nuclear@0
|
334
|
nuclear@0
|
335 // setup texture flags
|
nuclear@0
|
336 const int use_alpha = ((*it).alpha_test != Q3Shader::AT_NONE ? aiTextureFlags_UseAlpha : aiTextureFlags_IgnoreAlpha);
|
nuclear@0
|
337 out->AddProperty(&use_alpha,1,AI_MATKEY_TEXFLAGS(type,index));
|
nuclear@0
|
338 }
|
nuclear@0
|
339 // If at least one emissive texture was set, set the emissive base color to 1 to ensure
|
nuclear@0
|
340 // the texture is actually displayed.
|
nuclear@0
|
341 if (0 != cur_emissive) {
|
nuclear@0
|
342 aiColor3D one(1.f,1.f,1.f);
|
nuclear@0
|
343 out->AddProperty(&one,1,AI_MATKEY_COLOR_EMISSIVE);
|
nuclear@0
|
344 }
|
nuclear@0
|
345 }
|
nuclear@0
|
346
|
nuclear@0
|
347 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
348 // Constructor to be privately used by Importer
|
nuclear@0
|
349 MD3Importer::MD3Importer()
|
nuclear@0
|
350 : configFrameID (0)
|
nuclear@0
|
351 , configHandleMP (true)
|
nuclear@0
|
352 {}
|
nuclear@0
|
353
|
nuclear@0
|
354 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
355 // Destructor, private as well
|
nuclear@0
|
356 MD3Importer::~MD3Importer()
|
nuclear@0
|
357 {}
|
nuclear@0
|
358
|
nuclear@0
|
359 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
360 // Returns whether the class can handle the format of the given file.
|
nuclear@0
|
361 bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
|
nuclear@0
|
362 {
|
nuclear@0
|
363 const std::string extension = GetExtension(pFile);
|
nuclear@0
|
364 if (extension == "md3")
|
nuclear@0
|
365 return true;
|
nuclear@0
|
366
|
nuclear@0
|
367 // if check for extension is not enough, check for the magic tokens
|
nuclear@0
|
368 if (!extension.length() || checkSig) {
|
nuclear@0
|
369 uint32_t tokens[1];
|
nuclear@0
|
370 tokens[0] = AI_MD3_MAGIC_NUMBER_LE;
|
nuclear@0
|
371 return CheckMagicToken(pIOHandler,pFile,tokens,1);
|
nuclear@0
|
372 }
|
nuclear@0
|
373 return false;
|
nuclear@0
|
374 }
|
nuclear@0
|
375
|
nuclear@0
|
376 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
377 void MD3Importer::ValidateHeaderOffsets()
|
nuclear@0
|
378 {
|
nuclear@0
|
379 // Check magic number
|
nuclear@0
|
380 if (pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE &&
|
nuclear@0
|
381 pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE)
|
nuclear@0
|
382 throw DeadlyImportError( "Invalid MD3 file: Magic bytes not found");
|
nuclear@0
|
383
|
nuclear@0
|
384 // Check file format version
|
nuclear@0
|
385 if (pcHeader->VERSION > 15)
|
nuclear@0
|
386 DefaultLogger::get()->warn( "Unsupported MD3 file version. Continuing happily ...");
|
nuclear@0
|
387
|
nuclear@0
|
388 // Check some offset values whether they are valid
|
nuclear@0
|
389 if (!pcHeader->NUM_SURFACES)
|
nuclear@0
|
390 throw DeadlyImportError( "Invalid md3 file: NUM_SURFACES is 0");
|
nuclear@0
|
391
|
nuclear@0
|
392 if (pcHeader->OFS_FRAMES >= fileSize || pcHeader->OFS_SURFACES >= fileSize ||
|
nuclear@0
|
393 pcHeader->OFS_EOF > fileSize) {
|
nuclear@0
|
394 throw DeadlyImportError("Invalid MD3 header: some offsets are outside the file");
|
nuclear@0
|
395 }
|
nuclear@0
|
396
|
nuclear@0
|
397 if (pcHeader->NUM_FRAMES <= configFrameID )
|
nuclear@0
|
398 throw DeadlyImportError("The requested frame is not existing the file");
|
nuclear@0
|
399 }
|
nuclear@0
|
400
|
nuclear@0
|
401 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
402 void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf)
|
nuclear@0
|
403 {
|
nuclear@0
|
404 // Calculate the relative offset of the surface
|
nuclear@0
|
405 const int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer);
|
nuclear@0
|
406
|
nuclear@0
|
407 // Check whether all data chunks are inside the valid range
|
nuclear@0
|
408 if (pcSurf->OFS_TRIANGLES + ofs + pcSurf->NUM_TRIANGLES * sizeof(MD3::Triangle) > fileSize ||
|
nuclear@0
|
409 pcSurf->OFS_SHADERS + ofs + pcSurf->NUM_SHADER * sizeof(MD3::Shader) > fileSize ||
|
nuclear@0
|
410 pcSurf->OFS_ST + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::TexCoord) > fileSize ||
|
nuclear@0
|
411 pcSurf->OFS_XYZNORMAL + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::Vertex) > fileSize) {
|
nuclear@0
|
412
|
nuclear@0
|
413 throw DeadlyImportError("Invalid MD3 surface header: some offsets are outside the file");
|
nuclear@0
|
414 }
|
nuclear@0
|
415
|
nuclear@0
|
416 // Check whether all requirements for Q3 files are met. We don't
|
nuclear@0
|
417 // care, but probably someone does.
|
nuclear@0
|
418 if (pcSurf->NUM_TRIANGLES > AI_MD3_MAX_TRIANGLES) {
|
nuclear@0
|
419 DefaultLogger::get()->warn("MD3: Quake III triangle limit exceeded");
|
nuclear@0
|
420 }
|
nuclear@0
|
421
|
nuclear@0
|
422 if (pcSurf->NUM_SHADER > AI_MD3_MAX_SHADERS) {
|
nuclear@0
|
423 DefaultLogger::get()->warn("MD3: Quake III shader limit exceeded");
|
nuclear@0
|
424 }
|
nuclear@0
|
425
|
nuclear@0
|
426 if (pcSurf->NUM_VERTICES > AI_MD3_MAX_VERTS) {
|
nuclear@0
|
427 DefaultLogger::get()->warn("MD3: Quake III vertex limit exceeded");
|
nuclear@0
|
428 }
|
nuclear@0
|
429
|
nuclear@0
|
430 if (pcSurf->NUM_FRAMES > AI_MD3_MAX_FRAMES) {
|
nuclear@0
|
431 DefaultLogger::get()->warn("MD3: Quake III frame limit exceeded");
|
nuclear@0
|
432 }
|
nuclear@0
|
433 }
|
nuclear@0
|
434
|
nuclear@0
|
435 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
436 const aiImporterDesc* MD3Importer::GetInfo () const
|
nuclear@0
|
437 {
|
nuclear@0
|
438 return &desc;
|
nuclear@0
|
439 }
|
nuclear@0
|
440
|
nuclear@0
|
441 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
442 // Setup configuration properties
|
nuclear@0
|
443 void MD3Importer::SetupProperties(const Importer* pImp)
|
nuclear@0
|
444 {
|
nuclear@0
|
445 // The
|
nuclear@0
|
446 // AI_CONFIG_IMPORT_MD3_KEYFRAME option overrides the
|
nuclear@0
|
447 // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
|
nuclear@0
|
448 configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_KEYFRAME,-1);
|
nuclear@0
|
449 if(static_cast<unsigned int>(-1) == configFrameID) {
|
nuclear@0
|
450 configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
|
nuclear@0
|
451 }
|
nuclear@0
|
452
|
nuclear@0
|
453 // AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART
|
nuclear@0
|
454 configHandleMP = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART,1));
|
nuclear@0
|
455
|
nuclear@0
|
456 // AI_CONFIG_IMPORT_MD3_SKIN_NAME
|
nuclear@0
|
457 configSkinFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SKIN_NAME,"default"));
|
nuclear@0
|
458
|
nuclear@0
|
459 // AI_CONFIG_IMPORT_MD3_SHADER_SRC
|
nuclear@0
|
460 configShaderFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SHADER_SRC,""));
|
nuclear@0
|
461
|
nuclear@0
|
462 // AI_CONFIG_FAVOUR_SPEED
|
nuclear@0
|
463 configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0));
|
nuclear@0
|
464 }
|
nuclear@0
|
465
|
nuclear@0
|
466 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
467 // Try to read the skin for a MD3 file
|
nuclear@0
|
468 void MD3Importer::ReadSkin(Q3Shader::SkinData& fill) const
|
nuclear@0
|
469 {
|
nuclear@0
|
470 // skip any postfixes (e.g. lower_1.md3)
|
nuclear@0
|
471 std::string::size_type s = filename.find_last_of('_');
|
nuclear@0
|
472 if (s == std::string::npos) {
|
nuclear@0
|
473 s = filename.find_last_of('.');
|
nuclear@0
|
474 }
|
nuclear@0
|
475 ai_assert(s != std::string::npos);
|
nuclear@0
|
476
|
nuclear@0
|
477 const std::string skin_file = path + filename.substr(0,s) + "_" + configSkinFile + ".skin";
|
nuclear@0
|
478 Q3Shader::LoadSkin(fill,skin_file,mIOHandler);
|
nuclear@0
|
479 }
|
nuclear@0
|
480
|
nuclear@0
|
481 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
482 // Try to read the shader for a MD3 file
|
nuclear@0
|
483 void MD3Importer::ReadShader(Q3Shader::ShaderData& fill) const
|
nuclear@0
|
484 {
|
nuclear@0
|
485 // Determine Q3 model name from given path
|
nuclear@0
|
486 const std::string::size_type s = path.find_last_of("\\/",path.length()-2);
|
nuclear@0
|
487 const std::string model_file = path.substr(s+1,path.length()-(s+2));
|
nuclear@0
|
488
|
nuclear@0
|
489 // If no specific dir or file is given, use our default search behaviour
|
nuclear@0
|
490 if (!configShaderFile.length()) {
|
nuclear@0
|
491 if(!Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + model_file + ".shader",mIOHandler)) {
|
nuclear@0
|
492 Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + filename + ".shader",mIOHandler);
|
nuclear@0
|
493 }
|
nuclear@0
|
494 }
|
nuclear@0
|
495 else {
|
nuclear@0
|
496 // If the given string specifies a file, load this file.
|
nuclear@0
|
497 // Otherwise it's a directory.
|
nuclear@0
|
498 const std::string::size_type st = configShaderFile.find_last_of('.');
|
nuclear@0
|
499 if (st == std::string::npos) {
|
nuclear@0
|
500
|
nuclear@0
|
501 if(!Q3Shader::LoadShader(fill,configShaderFile + model_file + ".shader",mIOHandler)) {
|
nuclear@0
|
502 Q3Shader::LoadShader(fill,configShaderFile + filename + ".shader",mIOHandler);
|
nuclear@0
|
503 }
|
nuclear@0
|
504 }
|
nuclear@0
|
505 else {
|
nuclear@0
|
506 Q3Shader::LoadShader(fill,configShaderFile,mIOHandler);
|
nuclear@0
|
507 }
|
nuclear@0
|
508 }
|
nuclear@0
|
509 }
|
nuclear@0
|
510
|
nuclear@0
|
511 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
512 // Tiny helper to remove a single node from its parent' list
|
nuclear@0
|
513 void RemoveSingleNodeFromList(aiNode* nd)
|
nuclear@0
|
514 {
|
nuclear@0
|
515 if (!nd || nd->mNumChildren || !nd->mParent)return;
|
nuclear@0
|
516 aiNode* par = nd->mParent;
|
nuclear@0
|
517 for (unsigned int i = 0; i < par->mNumChildren;++i) {
|
nuclear@0
|
518 if (par->mChildren[i] == nd) {
|
nuclear@0
|
519 --par->mNumChildren;
|
nuclear@0
|
520 for (;i < par->mNumChildren;++i) {
|
nuclear@0
|
521 par->mChildren[i] = par->mChildren[i+1];
|
nuclear@0
|
522 }
|
nuclear@0
|
523 delete nd;
|
nuclear@0
|
524 break;
|
nuclear@0
|
525 }
|
nuclear@0
|
526 }
|
nuclear@0
|
527 }
|
nuclear@0
|
528
|
nuclear@0
|
529 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
530 // Read a multi-part Q3 player model
|
nuclear@0
|
531 bool MD3Importer::ReadMultipartFile()
|
nuclear@0
|
532 {
|
nuclear@0
|
533 // check whether the file name contains a common postfix, e.g lower_2.md3
|
nuclear@0
|
534 std::string::size_type s = filename.find_last_of('_'), t = filename.find_last_of('.');
|
nuclear@0
|
535 ai_assert(t != std::string::npos);
|
nuclear@0
|
536 if (s == std::string::npos)
|
nuclear@0
|
537 s = t;
|
nuclear@0
|
538
|
nuclear@0
|
539 const std::string mod_filename = filename.substr(0,s);
|
nuclear@0
|
540 const std::string suffix = filename.substr(s,t-s);
|
nuclear@0
|
541
|
nuclear@0
|
542 if (mod_filename == "lower" || mod_filename == "upper" || mod_filename == "head"){
|
nuclear@0
|
543 const std::string lower = path + "lower" + suffix + ".md3";
|
nuclear@0
|
544 const std::string upper = path + "upper" + suffix + ".md3";
|
nuclear@0
|
545 const std::string head = path + "head" + suffix + ".md3";
|
nuclear@0
|
546
|
nuclear@0
|
547 aiScene* scene_upper = NULL;
|
nuclear@0
|
548 aiScene* scene_lower = NULL;
|
nuclear@0
|
549 aiScene* scene_head = NULL;
|
nuclear@0
|
550 std::string failure;
|
nuclear@0
|
551
|
nuclear@0
|
552 aiNode* tag_torso, *tag_head;
|
nuclear@0
|
553 std::vector<AttachmentInfo> attach;
|
nuclear@0
|
554
|
nuclear@0
|
555 DefaultLogger::get()->info("Multi part MD3 player model: lower, upper and head parts are joined");
|
nuclear@0
|
556
|
nuclear@0
|
557 // ensure we won't try to load ourselves recursively
|
nuclear@0
|
558 BatchLoader::PropertyMap props;
|
nuclear@0
|
559 SetGenericProperty( props.ints, AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, 0, NULL);
|
nuclear@0
|
560
|
nuclear@0
|
561 // now read these three files
|
nuclear@0
|
562 BatchLoader batch(mIOHandler);
|
nuclear@0
|
563 const unsigned int _lower = batch.AddLoadRequest(lower,0,&props);
|
nuclear@0
|
564 const unsigned int _upper = batch.AddLoadRequest(upper,0,&props);
|
nuclear@0
|
565 const unsigned int _head = batch.AddLoadRequest(head,0,&props);
|
nuclear@0
|
566 batch.LoadAll();
|
nuclear@0
|
567
|
nuclear@0
|
568 // now construct a dummy scene to place these three parts in
|
nuclear@0
|
569 aiScene* master = new aiScene();
|
nuclear@0
|
570 aiNode* nd = master->mRootNode = new aiNode();
|
nuclear@0
|
571 nd->mName.Set("<MD3_Player>");
|
nuclear@0
|
572
|
nuclear@0
|
573 // ... and get them. We need all of them.
|
nuclear@0
|
574 scene_lower = batch.GetImport(_lower);
|
nuclear@0
|
575 if (!scene_lower) {
|
nuclear@0
|
576 DefaultLogger::get()->error("M3D: Failed to read multi part model, lower.md3 fails to load");
|
nuclear@0
|
577 failure = "lower";
|
nuclear@0
|
578 goto error_cleanup;
|
nuclear@0
|
579 }
|
nuclear@0
|
580
|
nuclear@0
|
581 scene_upper = batch.GetImport(_upper);
|
nuclear@0
|
582 if (!scene_upper) {
|
nuclear@0
|
583 DefaultLogger::get()->error("M3D: Failed to read multi part model, upper.md3 fails to load");
|
nuclear@0
|
584 failure = "upper";
|
nuclear@0
|
585 goto error_cleanup;
|
nuclear@0
|
586 }
|
nuclear@0
|
587
|
nuclear@0
|
588 scene_head = batch.GetImport(_head);
|
nuclear@0
|
589 if (!scene_head) {
|
nuclear@0
|
590 DefaultLogger::get()->error("M3D: Failed to read multi part model, head.md3 fails to load");
|
nuclear@0
|
591 failure = "head";
|
nuclear@0
|
592 goto error_cleanup;
|
nuclear@0
|
593 }
|
nuclear@0
|
594
|
nuclear@0
|
595 // build attachment infos. search for typical Q3 tags
|
nuclear@0
|
596
|
nuclear@0
|
597 // original root
|
nuclear@0
|
598 scene_lower->mRootNode->mName.Set("lower");
|
nuclear@0
|
599 attach.push_back(AttachmentInfo(scene_lower, nd));
|
nuclear@0
|
600
|
nuclear@0
|
601 // tag_torso
|
nuclear@0
|
602 tag_torso = scene_lower->mRootNode->FindNode("tag_torso");
|
nuclear@0
|
603 if (!tag_torso) {
|
nuclear@0
|
604 DefaultLogger::get()->error("M3D: Failed to find attachment tag for multi part model: tag_torso expected");
|
nuclear@0
|
605 goto error_cleanup;
|
nuclear@0
|
606 }
|
nuclear@0
|
607 scene_upper->mRootNode->mName.Set("upper");
|
nuclear@0
|
608 attach.push_back(AttachmentInfo(scene_upper,tag_torso));
|
nuclear@0
|
609
|
nuclear@0
|
610 // tag_head
|
nuclear@0
|
611 tag_head = scene_upper->mRootNode->FindNode("tag_head");
|
nuclear@0
|
612 if (!tag_head) {
|
nuclear@0
|
613 DefaultLogger::get()->error("M3D: Failed to find attachment tag for multi part model: tag_head expected");
|
nuclear@0
|
614 goto error_cleanup;
|
nuclear@0
|
615 }
|
nuclear@0
|
616 scene_head->mRootNode->mName.Set("head");
|
nuclear@0
|
617 attach.push_back(AttachmentInfo(scene_head,tag_head));
|
nuclear@0
|
618
|
nuclear@0
|
619 // Remove tag_head and tag_torso from all other model parts ...
|
nuclear@0
|
620 // this ensures (together with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY)
|
nuclear@0
|
621 // that tag_torso/tag_head is also the name of the (unique) output node
|
nuclear@0
|
622 RemoveSingleNodeFromList (scene_upper->mRootNode->FindNode("tag_torso"));
|
nuclear@0
|
623 RemoveSingleNodeFromList (scene_head-> mRootNode->FindNode("tag_head" ));
|
nuclear@0
|
624
|
nuclear@0
|
625 // Undo the rotations which we applied to the coordinate systems. We're
|
nuclear@0
|
626 // working in global Quake space here
|
nuclear@0
|
627 scene_head->mRootNode->mTransformation = aiMatrix4x4();
|
nuclear@0
|
628 scene_lower->mRootNode->mTransformation = aiMatrix4x4();
|
nuclear@0
|
629 scene_upper->mRootNode->mTransformation = aiMatrix4x4();
|
nuclear@0
|
630
|
nuclear@0
|
631 // and merge the scenes
|
nuclear@0
|
632 SceneCombiner::MergeScenes(&mScene,master, attach,
|
nuclear@0
|
633 AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES |
|
nuclear@0
|
634 AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES |
|
nuclear@0
|
635 AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS |
|
nuclear@0
|
636 (!configSpeedFlag ? AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY : 0));
|
nuclear@0
|
637
|
nuclear@0
|
638 // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
|
nuclear@0
|
639 mScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f,
|
nuclear@0
|
640 0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f);
|
nuclear@0
|
641
|
nuclear@0
|
642 return true;
|
nuclear@0
|
643
|
nuclear@0
|
644 error_cleanup:
|
nuclear@0
|
645 delete scene_upper;
|
nuclear@0
|
646 delete scene_lower;
|
nuclear@0
|
647 delete scene_head;
|
nuclear@0
|
648 delete master;
|
nuclear@0
|
649
|
nuclear@0
|
650 if (failure == mod_filename) {
|
nuclear@0
|
651 throw DeadlyImportError("MD3: failure to read multipart host file");
|
nuclear@0
|
652 }
|
nuclear@0
|
653 }
|
nuclear@0
|
654 return false;
|
nuclear@0
|
655 }
|
nuclear@0
|
656
|
nuclear@0
|
657 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
658 // Convert a MD3 path to a proper value
|
nuclear@0
|
659 void MD3Importer::ConvertPath(const char* texture_name, const char* header_name, std::string& out) const
|
nuclear@0
|
660 {
|
nuclear@0
|
661 // If the MD3's internal path itself and the given path are using
|
nuclear@0
|
662 // the same directory, remove it completely to get right output paths.
|
nuclear@0
|
663 const char* end1 = ::strrchr(header_name,'\\');
|
nuclear@0
|
664 if (!end1)end1 = ::strrchr(header_name,'/');
|
nuclear@0
|
665
|
nuclear@0
|
666 const char* end2 = ::strrchr(texture_name,'\\');
|
nuclear@0
|
667 if (!end2)end2 = ::strrchr(texture_name,'/');
|
nuclear@0
|
668
|
nuclear@0
|
669 // HACK: If the paths starts with "models", ignore the
|
nuclear@0
|
670 // next two hierarchy levels, it specifies just the model name.
|
nuclear@0
|
671 // Ignored by Q3, it might be not equal to the real model location.
|
nuclear@0
|
672 if (end2) {
|
nuclear@0
|
673
|
nuclear@0
|
674 size_t len2;
|
nuclear@0
|
675 const size_t len1 = (size_t)(end1 - header_name);
|
nuclear@0
|
676 if (!ASSIMP_strincmp(texture_name,"models",6) && (texture_name[6] == '/' || texture_name[6] == '\\')) {
|
nuclear@0
|
677 len2 = 6; // ignore the seventh - could be slash or backslash
|
nuclear@0
|
678
|
nuclear@0
|
679 if (!header_name[0]) {
|
nuclear@0
|
680 // Use the file name only
|
nuclear@0
|
681 out = end2+1;
|
nuclear@0
|
682 return;
|
nuclear@0
|
683 }
|
nuclear@0
|
684 }
|
nuclear@0
|
685 else len2 = std::min (len1, (size_t)(end2 - texture_name ));
|
nuclear@0
|
686 if (!ASSIMP_strincmp(texture_name,header_name,len2)) {
|
nuclear@0
|
687 // Use the file name only
|
nuclear@0
|
688 out = end2+1;
|
nuclear@0
|
689 return;
|
nuclear@0
|
690 }
|
nuclear@0
|
691 }
|
nuclear@0
|
692 // Use the full path
|
nuclear@0
|
693 out = texture_name;
|
nuclear@0
|
694 }
|
nuclear@0
|
695
|
nuclear@0
|
696 // ------------------------------------------------------------------------------------------------
|
nuclear@0
|
697 // Imports the given file into the given scene structure.
|
nuclear@0
|
698 void MD3Importer::InternReadFile( const std::string& pFile,
|
nuclear@0
|
699 aiScene* pScene, IOSystem* pIOHandler)
|
nuclear@0
|
700 {
|
nuclear@0
|
701 mFile = pFile;
|
nuclear@0
|
702 mScene = pScene;
|
nuclear@0
|
703 mIOHandler = pIOHandler;
|
nuclear@0
|
704
|
nuclear@0
|
705 // get base path and file name
|
nuclear@0
|
706 // todo ... move to PathConverter
|
nuclear@0
|
707 std::string::size_type s = mFile.find_last_of("/\\");
|
nuclear@0
|
708 if (s == std::string::npos) {
|
nuclear@0
|
709 s = 0;
|
nuclear@0
|
710 }
|
nuclear@0
|
711 else ++s;
|
nuclear@0
|
712 filename = mFile.substr(s), path = mFile.substr(0,s);
|
nuclear@0
|
713 for( std::string::iterator it = filename .begin(); it != filename.end(); ++it)
|
nuclear@0
|
714 *it = tolower( *it);
|
nuclear@0
|
715
|
nuclear@0
|
716 // Load multi-part model file, if necessary
|
nuclear@0
|
717 if (configHandleMP) {
|
nuclear@0
|
718 if (ReadMultipartFile())
|
nuclear@0
|
719 return;
|
nuclear@0
|
720 }
|
nuclear@0
|
721
|
nuclear@0
|
722 boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile));
|
nuclear@0
|
723
|
nuclear@0
|
724 // Check whether we can read from the file
|
nuclear@0
|
725 if( file.get() == NULL)
|
nuclear@0
|
726 throw DeadlyImportError( "Failed to open MD3 file " + pFile + ".");
|
nuclear@0
|
727
|
nuclear@0
|
728 // Check whether the md3 file is large enough to contain the header
|
nuclear@0
|
729 fileSize = (unsigned int)file->FileSize();
|
nuclear@0
|
730 if( fileSize < sizeof(MD3::Header))
|
nuclear@0
|
731 throw DeadlyImportError( "MD3 File is too small.");
|
nuclear@0
|
732
|
nuclear@0
|
733 // Allocate storage and copy the contents of the file to a memory buffer
|
nuclear@0
|
734 std::vector<unsigned char> mBuffer2 (fileSize);
|
nuclear@0
|
735 file->Read( &mBuffer2[0], 1, fileSize);
|
nuclear@0
|
736 mBuffer = &mBuffer2[0];
|
nuclear@0
|
737
|
nuclear@0
|
738 pcHeader = (BE_NCONST MD3::Header*)mBuffer;
|
nuclear@0
|
739
|
nuclear@0
|
740 // Ensure correct endianess
|
nuclear@0
|
741 #ifdef AI_BUILD_BIG_ENDIAN
|
nuclear@0
|
742
|
nuclear@0
|
743 AI_SWAP4(pcHeader->VERSION);
|
nuclear@0
|
744 AI_SWAP4(pcHeader->FLAGS);
|
nuclear@0
|
745 AI_SWAP4(pcHeader->IDENT);
|
nuclear@0
|
746 AI_SWAP4(pcHeader->NUM_FRAMES);
|
nuclear@0
|
747 AI_SWAP4(pcHeader->NUM_SKINS);
|
nuclear@0
|
748 AI_SWAP4(pcHeader->NUM_SURFACES);
|
nuclear@0
|
749 AI_SWAP4(pcHeader->NUM_TAGS);
|
nuclear@0
|
750 AI_SWAP4(pcHeader->OFS_EOF);
|
nuclear@0
|
751 AI_SWAP4(pcHeader->OFS_FRAMES);
|
nuclear@0
|
752 AI_SWAP4(pcHeader->OFS_SURFACES);
|
nuclear@0
|
753 AI_SWAP4(pcHeader->OFS_TAGS);
|
nuclear@0
|
754
|
nuclear@0
|
755 #endif
|
nuclear@0
|
756
|
nuclear@0
|
757 // Validate the file header
|
nuclear@0
|
758 ValidateHeaderOffsets();
|
nuclear@0
|
759
|
nuclear@0
|
760 // Navigate to the list of surfaces
|
nuclear@0
|
761 BE_NCONST MD3::Surface* pcSurfaces = (BE_NCONST MD3::Surface*)(mBuffer + pcHeader->OFS_SURFACES);
|
nuclear@0
|
762
|
nuclear@0
|
763 // Navigate to the list of tags
|
nuclear@0
|
764 BE_NCONST MD3::Tag* pcTags = (BE_NCONST MD3::Tag*)(mBuffer + pcHeader->OFS_TAGS);
|
nuclear@0
|
765
|
nuclear@0
|
766 // Allocate output storage
|
nuclear@0
|
767 pScene->mNumMeshes = pcHeader->NUM_SURFACES;
|
nuclear@0
|
768 pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
|
nuclear@0
|
769
|
nuclear@0
|
770 pScene->mNumMaterials = pcHeader->NUM_SURFACES;
|
nuclear@0
|
771 pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes];
|
nuclear@0
|
772
|
nuclear@0
|
773 // Set arrays to zero to ensue proper destruction if an exception is raised
|
nuclear@0
|
774 ::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*));
|
nuclear@0
|
775 ::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*));
|
nuclear@0
|
776
|
nuclear@0
|
777 // Now read possible skins from .skin file
|
nuclear@0
|
778 Q3Shader::SkinData skins;
|
nuclear@0
|
779 ReadSkin(skins);
|
nuclear@0
|
780
|
nuclear@0
|
781 // And check whether we can locate a shader file for this model
|
nuclear@0
|
782 Q3Shader::ShaderData shaders;
|
nuclear@0
|
783 ReadShader(shaders);
|
nuclear@0
|
784
|
nuclear@0
|
785 // Adjust all texture paths in the shader
|
nuclear@0
|
786 const char* header_name = pcHeader->NAME;
|
nuclear@0
|
787 if (shaders.blocks.size()) {
|
nuclear@0
|
788 for (std::list< Q3Shader::ShaderDataBlock >::iterator dit = shaders.blocks.begin(); dit != shaders.blocks.end(); ++dit) {
|
nuclear@0
|
789 ConvertPath((*dit).name.c_str(),header_name,(*dit).name);
|
nuclear@0
|
790
|
nuclear@0
|
791 for (std::list< Q3Shader::ShaderMapBlock >::iterator mit = (*dit).maps.begin(); mit != (*dit).maps.end(); ++mit) {
|
nuclear@0
|
792 ConvertPath((*mit).name.c_str(),header_name,(*mit).name);
|
nuclear@0
|
793 }
|
nuclear@0
|
794 }
|
nuclear@0
|
795 }
|
nuclear@0
|
796
|
nuclear@0
|
797 // Read all surfaces from the file
|
nuclear@0
|
798 unsigned int iNum = pcHeader->NUM_SURFACES;
|
nuclear@0
|
799 unsigned int iNumMaterials = 0;
|
nuclear@0
|
800 while (iNum-- > 0) {
|
nuclear@0
|
801
|
nuclear@0
|
802 // Ensure correct endianess
|
nuclear@0
|
803 #ifdef AI_BUILD_BIG_ENDIAN
|
nuclear@0
|
804
|
nuclear@0
|
805 AI_SWAP4(pcSurfaces->FLAGS);
|
nuclear@0
|
806 AI_SWAP4(pcSurfaces->IDENT);
|
nuclear@0
|
807 AI_SWAP4(pcSurfaces->NUM_FRAMES);
|
nuclear@0
|
808 AI_SWAP4(pcSurfaces->NUM_SHADER);
|
nuclear@0
|
809 AI_SWAP4(pcSurfaces->NUM_TRIANGLES);
|
nuclear@0
|
810 AI_SWAP4(pcSurfaces->NUM_VERTICES);
|
nuclear@0
|
811 AI_SWAP4(pcSurfaces->OFS_END);
|
nuclear@0
|
812 AI_SWAP4(pcSurfaces->OFS_SHADERS);
|
nuclear@0
|
813 AI_SWAP4(pcSurfaces->OFS_ST);
|
nuclear@0
|
814 AI_SWAP4(pcSurfaces->OFS_TRIANGLES);
|
nuclear@0
|
815 AI_SWAP4(pcSurfaces->OFS_XYZNORMAL);
|
nuclear@0
|
816
|
nuclear@0
|
817 #endif
|
nuclear@0
|
818
|
nuclear@0
|
819 // Validate the surface header
|
nuclear@0
|
820 ValidateSurfaceHeaderOffsets(pcSurfaces);
|
nuclear@0
|
821
|
nuclear@0
|
822 // Navigate to the vertex list of the surface
|
nuclear@0
|
823 BE_NCONST MD3::Vertex* pcVertices = (BE_NCONST MD3::Vertex*)
|
nuclear@0
|
824 (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL);
|
nuclear@0
|
825
|
nuclear@0
|
826 // Navigate to the triangle list of the surface
|
nuclear@0
|
827 BE_NCONST MD3::Triangle* pcTriangles = (BE_NCONST MD3::Triangle*)
|
nuclear@0
|
828 (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES);
|
nuclear@0
|
829
|
nuclear@0
|
830 // Navigate to the texture coordinate list of the surface
|
nuclear@0
|
831 BE_NCONST MD3::TexCoord* pcUVs = (BE_NCONST MD3::TexCoord*)
|
nuclear@0
|
832 (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_ST);
|
nuclear@0
|
833
|
nuclear@0
|
834 // Navigate to the shader list of the surface
|
nuclear@0
|
835 BE_NCONST MD3::Shader* pcShaders = (BE_NCONST MD3::Shader*)
|
nuclear@0
|
836 (((uint8_t*)pcSurfaces) + pcSurfaces->OFS_SHADERS);
|
nuclear@0
|
837
|
nuclear@0
|
838 // If the submesh is empty ignore it
|
nuclear@0
|
839 if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES)
|
nuclear@0
|
840 {
|
nuclear@0
|
841 pcSurfaces = (BE_NCONST MD3::Surface*)(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_END);
|
nuclear@0
|
842 pScene->mNumMeshes--;
|
nuclear@0
|
843 continue;
|
nuclear@0
|
844 }
|
nuclear@0
|
845
|
nuclear@0
|
846 // Allocate output mesh
|
nuclear@0
|
847 pScene->mMeshes[iNum] = new aiMesh();
|
nuclear@0
|
848 aiMesh* pcMesh = pScene->mMeshes[iNum];
|
nuclear@0
|
849
|
nuclear@0
|
850 std::string _texture_name;
|
nuclear@0
|
851 const char* texture_name = NULL;
|
nuclear@0
|
852
|
nuclear@0
|
853 // Check whether we have a texture record for this surface in the .skin file
|
nuclear@0
|
854 std::list< Q3Shader::SkinData::TextureEntry >::iterator it = std::find(
|
nuclear@0
|
855 skins.textures.begin(), skins.textures.end(), pcSurfaces->NAME );
|
nuclear@0
|
856
|
nuclear@0
|
857 if (it != skins.textures.end()) {
|
nuclear@0
|
858 texture_name = &*( _texture_name = (*it).second).begin();
|
nuclear@0
|
859 DefaultLogger::get()->debug("MD3: Assigning skin texture " + (*it).second + " to surface " + pcSurfaces->NAME);
|
nuclear@0
|
860 (*it).resolved = true; // mark entry as resolved
|
nuclear@0
|
861 }
|
nuclear@0
|
862
|
nuclear@0
|
863 // Get the first shader (= texture?) assigned to the surface
|
nuclear@0
|
864 if (!texture_name && pcSurfaces->NUM_SHADER) {
|
nuclear@0
|
865 texture_name = pcShaders->NAME;
|
nuclear@0
|
866 }
|
nuclear@0
|
867
|
nuclear@0
|
868 std::string convertedPath;
|
nuclear@0
|
869 if (texture_name) {
|
nuclear@0
|
870 ConvertPath(texture_name,header_name,convertedPath);
|
nuclear@0
|
871 }
|
nuclear@0
|
872
|
nuclear@0
|
873 const Q3Shader::ShaderDataBlock* shader = NULL;
|
nuclear@0
|
874
|
nuclear@0
|
875 // Now search the current shader for a record with this name (
|
nuclear@0
|
876 // excluding texture file extension)
|
nuclear@0
|
877 if (shaders.blocks.size()) {
|
nuclear@0
|
878
|
nuclear@0
|
879 std::string::size_type s = convertedPath.find_last_of('.');
|
nuclear@0
|
880 if (s == std::string::npos)
|
nuclear@0
|
881 s = convertedPath.length();
|
nuclear@0
|
882
|
nuclear@0
|
883 const std::string without_ext = convertedPath.substr(0,s);
|
nuclear@0
|
884 std::list< Q3Shader::ShaderDataBlock >::const_iterator dit = std::find(shaders.blocks.begin(),shaders.blocks.end(),without_ext);
|
nuclear@0
|
885 if (dit != shaders.blocks.end()) {
|
nuclear@0
|
886 // Hurra, wir haben einen. Tolle Sache.
|
nuclear@0
|
887 shader = &*dit;
|
nuclear@0
|
888 DefaultLogger::get()->info("Found shader record for " +without_ext );
|
nuclear@0
|
889 }
|
nuclear@0
|
890 else DefaultLogger::get()->warn("Unable to find shader record for " +without_ext );
|
nuclear@0
|
891 }
|
nuclear@0
|
892
|
nuclear@0
|
893 aiMaterial* pcHelper = new aiMaterial();
|
nuclear@0
|
894
|
nuclear@0
|
895 const int iMode = (int)aiShadingMode_Gouraud;
|
nuclear@0
|
896 pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
|
nuclear@0
|
897
|
nuclear@0
|
898 // Add a small ambient color value - Quake 3 seems to have one
|
nuclear@0
|
899 aiColor3D clr;
|
nuclear@0
|
900 clr.b = clr.g = clr.r = 0.05f;
|
nuclear@0
|
901 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
|
nuclear@0
|
902
|
nuclear@0
|
903 clr.b = clr.g = clr.r = 1.0f;
|
nuclear@0
|
904 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
|
nuclear@0
|
905 pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
|
nuclear@0
|
906
|
nuclear@0
|
907 // use surface name + skin_name as material name
|
nuclear@0
|
908 aiString name;
|
nuclear@0
|
909 name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]");
|
nuclear@0
|
910 pcHelper->AddProperty(&name,AI_MATKEY_NAME);
|
nuclear@0
|
911
|
nuclear@0
|
912 if (!shader) {
|
nuclear@0
|
913 // Setup dummy texture file name to ensure UV coordinates are kept during postprocessing
|
nuclear@0
|
914 aiString szString;
|
nuclear@0
|
915 if (convertedPath.length()) {
|
nuclear@0
|
916 szString.Set(convertedPath);
|
nuclear@0
|
917 }
|
nuclear@0
|
918 else {
|
nuclear@0
|
919 DefaultLogger::get()->warn("Texture file name has zero length. Using default name");
|
nuclear@0
|
920 szString.Set("dummy_texture.bmp");
|
nuclear@0
|
921 }
|
nuclear@0
|
922 pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0));
|
nuclear@0
|
923
|
nuclear@0
|
924 // prevent transparency by default
|
nuclear@0
|
925 int no_alpha = aiTextureFlags_IgnoreAlpha;
|
nuclear@0
|
926 pcHelper->AddProperty(&no_alpha,1,AI_MATKEY_TEXFLAGS_DIFFUSE(0));
|
nuclear@0
|
927 }
|
nuclear@0
|
928 else {
|
nuclear@0
|
929 Q3Shader::ConvertShaderToMaterial(pcHelper,*shader);
|
nuclear@0
|
930 }
|
nuclear@0
|
931
|
nuclear@0
|
932 pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
|
nuclear@0
|
933 pcMesh->mMaterialIndex = iNumMaterials++;
|
nuclear@0
|
934
|
nuclear@0
|
935 // Ensure correct endianess
|
nuclear@0
|
936 #ifdef AI_BUILD_BIG_ENDIAN
|
nuclear@0
|
937
|
nuclear@0
|
938 for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i) {
|
nuclear@0
|
939 AI_SWAP2( pcVertices[i].NORMAL );
|
nuclear@0
|
940 AI_SWAP2( pcVertices[i].X );
|
nuclear@0
|
941 AI_SWAP2( pcVertices[i].Y );
|
nuclear@0
|
942 AI_SWAP2( pcVertices[i].Z );
|
nuclear@0
|
943
|
nuclear@0
|
944 AI_SWAP4( pcUVs[i].U );
|
nuclear@0
|
945 AI_SWAP4( pcUVs[i].U );
|
nuclear@0
|
946 }
|
nuclear@0
|
947 for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES;++i) {
|
nuclear@0
|
948 AI_SWAP4(pcTriangles[i].INDEXES[0]);
|
nuclear@0
|
949 AI_SWAP4(pcTriangles[i].INDEXES[1]);
|
nuclear@0
|
950 AI_SWAP4(pcTriangles[i].INDEXES[2]);
|
nuclear@0
|
951 }
|
nuclear@0
|
952
|
nuclear@0
|
953 #endif
|
nuclear@0
|
954
|
nuclear@0
|
955 // Fill mesh information
|
nuclear@0
|
956 pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
nuclear@0
|
957
|
nuclear@0
|
958 pcMesh->mNumVertices = pcSurfaces->NUM_TRIANGLES*3;
|
nuclear@0
|
959 pcMesh->mNumFaces = pcSurfaces->NUM_TRIANGLES;
|
nuclear@0
|
960 pcMesh->mFaces = new aiFace[pcSurfaces->NUM_TRIANGLES];
|
nuclear@0
|
961 pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices];
|
nuclear@0
|
962 pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices];
|
nuclear@0
|
963 pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices];
|
nuclear@0
|
964 pcMesh->mNumUVComponents[0] = 2;
|
nuclear@0
|
965
|
nuclear@0
|
966 // Fill in all triangles
|
nuclear@0
|
967 unsigned int iCurrent = 0;
|
nuclear@0
|
968 for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES;++i) {
|
nuclear@0
|
969 pcMesh->mFaces[i].mIndices = new unsigned int[3];
|
nuclear@0
|
970 pcMesh->mFaces[i].mNumIndices = 3;
|
nuclear@0
|
971
|
nuclear@0
|
972 //unsigned int iTemp = iCurrent;
|
nuclear@0
|
973 for (unsigned int c = 0; c < 3;++c,++iCurrent) {
|
nuclear@0
|
974 pcMesh->mFaces[i].mIndices[c] = iCurrent;
|
nuclear@0
|
975
|
nuclear@0
|
976 // Read vertices
|
nuclear@0
|
977 aiVector3D& vec = pcMesh->mVertices[iCurrent];
|
nuclear@0
|
978 vec.x = pcVertices[ pcTriangles->INDEXES[c]].X*AI_MD3_XYZ_SCALE;
|
nuclear@0
|
979 vec.y = pcVertices[ pcTriangles->INDEXES[c]].Y*AI_MD3_XYZ_SCALE;
|
nuclear@0
|
980 vec.z = pcVertices[ pcTriangles->INDEXES[c]].Z*AI_MD3_XYZ_SCALE;
|
nuclear@0
|
981
|
nuclear@0
|
982 // Convert the normal vector to uncompressed float3 format
|
nuclear@0
|
983 aiVector3D& nor = pcMesh->mNormals[iCurrent];
|
nuclear@0
|
984 LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL,(float*)&nor);
|
nuclear@0
|
985
|
nuclear@0
|
986 // Read texture coordinates
|
nuclear@0
|
987 pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U;
|
nuclear@0
|
988 pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V;
|
nuclear@0
|
989 }
|
nuclear@0
|
990 // Flip face order if necessary
|
nuclear@0
|
991 if (!shader || shader->cull == Q3Shader::CULL_CW) {
|
nuclear@0
|
992 std::swap(pcMesh->mFaces[i].mIndices[2],pcMesh->mFaces[i].mIndices[1]);
|
nuclear@0
|
993 }
|
nuclear@0
|
994 pcTriangles++;
|
nuclear@0
|
995 }
|
nuclear@0
|
996
|
nuclear@0
|
997 // Go to the next surface
|
nuclear@0
|
998 pcSurfaces = (BE_NCONST MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END);
|
nuclear@0
|
999 }
|
nuclear@0
|
1000
|
nuclear@0
|
1001 // For debugging purposes: check whether we found matches for all entries in the skins file
|
nuclear@0
|
1002 if (!DefaultLogger::isNullLogger()) {
|
nuclear@0
|
1003 for (std::list< Q3Shader::SkinData::TextureEntry>::const_iterator it = skins.textures.begin();it != skins.textures.end(); ++it) {
|
nuclear@0
|
1004 if (!(*it).resolved) {
|
nuclear@0
|
1005 DefaultLogger::get()->error("MD3: Failed to match skin " + (*it).first + " to surface " + (*it).second);
|
nuclear@0
|
1006 }
|
nuclear@0
|
1007 }
|
nuclear@0
|
1008 }
|
nuclear@0
|
1009
|
nuclear@0
|
1010 if (!pScene->mNumMeshes)
|
nuclear@0
|
1011 throw DeadlyImportError( "MD3: File contains no valid mesh");
|
nuclear@0
|
1012 pScene->mNumMaterials = iNumMaterials;
|
nuclear@0
|
1013
|
nuclear@0
|
1014 // Now we need to generate an empty node graph
|
nuclear@0
|
1015 pScene->mRootNode = new aiNode("<MD3Root>");
|
nuclear@0
|
1016 pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
|
nuclear@0
|
1017 pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
|
nuclear@0
|
1018
|
nuclear@0
|
1019 // Attach tiny children for all tags
|
nuclear@0
|
1020 if (pcHeader->NUM_TAGS) {
|
nuclear@0
|
1021 pScene->mRootNode->mNumChildren = pcHeader->NUM_TAGS;
|
nuclear@0
|
1022 pScene->mRootNode->mChildren = new aiNode*[pcHeader->NUM_TAGS];
|
nuclear@0
|
1023
|
nuclear@0
|
1024 for (unsigned int i = 0; i < pcHeader->NUM_TAGS; ++i, ++pcTags) {
|
nuclear@0
|
1025
|
nuclear@0
|
1026 aiNode* nd = pScene->mRootNode->mChildren[i] = new aiNode();
|
nuclear@0
|
1027 nd->mName.Set((const char*)pcTags->NAME);
|
nuclear@0
|
1028 nd->mParent = pScene->mRootNode;
|
nuclear@0
|
1029
|
nuclear@0
|
1030 AI_SWAP4(pcTags->origin.x);
|
nuclear@0
|
1031 AI_SWAP4(pcTags->origin.y);
|
nuclear@0
|
1032 AI_SWAP4(pcTags->origin.z);
|
nuclear@0
|
1033
|
nuclear@0
|
1034 // Copy local origin, again flip z,y
|
nuclear@0
|
1035 nd->mTransformation.a4 = pcTags->origin.x;
|
nuclear@0
|
1036 nd->mTransformation.b4 = pcTags->origin.y;
|
nuclear@0
|
1037 nd->mTransformation.c4 = pcTags->origin.z;
|
nuclear@0
|
1038
|
nuclear@0
|
1039 // Copy rest of transformation (need to transpose to match row-order matrix)
|
nuclear@0
|
1040 for (unsigned int a = 0; a < 3;++a) {
|
nuclear@0
|
1041 for (unsigned int m = 0; m < 3;++m) {
|
nuclear@0
|
1042 nd->mTransformation[m][a] = pcTags->orientation[a][m];
|
nuclear@0
|
1043 AI_SWAP4(nd->mTransformation[m][a]);
|
nuclear@0
|
1044 }
|
nuclear@0
|
1045 }
|
nuclear@0
|
1046 }
|
nuclear@0
|
1047 }
|
nuclear@0
|
1048
|
nuclear@0
|
1049 for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
|
nuclear@0
|
1050 pScene->mRootNode->mMeshes[i] = i;
|
nuclear@0
|
1051
|
nuclear@0
|
1052 // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
|
nuclear@0
|
1053 pScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f,
|
nuclear@0
|
1054 0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f);
|
nuclear@0
|
1055 }
|
nuclear@0
|
1056
|
nuclear@0
|
1057 #endif // !! ASSIMP_BUILD_NO_MD3_IMPORTER
|