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