1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5
6 Copyright (c) 2006-2021, 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 UnrealLoader.cpp
43 * @brief Implementation of the UNREAL (*.3D) importer class
44 *
45 * Sources:
46 * http://local.wasp.uwa.edu.au/~pbourke/dataformats/unreal/
47 */
48
49 #ifndef ASSIMP_BUILD_NO_3D_IMPORTER
50
51 #include "AssetLib/Unreal/UnrealLoader.h"
52 #include "PostProcessing/ConvertToLHProcess.h"
53
54 #include <assimp/ParsingUtils.h>
55 #include <assimp/StreamReader.h>
56 #include <assimp/fast_atof.h>
57 #include <assimp/importerdesc.h>
58 #include <assimp/scene.h>
59 #include <assimp/DefaultLogger.hpp>
60 #include <assimp/IOSystem.hpp>
61 #include <assimp/Importer.hpp>
62
63 #include <cstdint>
64 #include <memory>
65
66 using namespace Assimp;
67
68 namespace Unreal {
69
70 /*
71 0 = Normal one-sided
72 1 = Normal two-sided
73 2 = Translucent two-sided
74 3 = Masked two-sided
75 4 = Modulation blended two-sided
76 8 = Placeholder triangle for weapon positioning (invisible)
77 */
78 enum MeshFlags {
79 MF_NORMAL_OS = 0,
80 MF_NORMAL_TS = 1,
81 MF_NORMAL_TRANS_TS = 2,
82 MF_NORMAL_MASKED_TS = 3,
83 MF_NORMAL_MOD_TS = 4,
84 MF_WEAPON_PLACEHOLDER = 8
85 };
86
87 // a single triangle
88 struct Triangle {
89 uint16_t mVertex[3]; // Vertex indices
90 char mType; // James' Mesh Type
91 char mColor; // Color for flat and Gourand Shaded
92 unsigned char mTex[3][2]; // Texture UV coordinates
93 unsigned char mTextureNum; // Source texture offset
94 char mFlags; // Unreal Mesh Flags (unused)
95 unsigned int matIndex;
96 };
97
98 // temporary representation for a material
99 struct TempMat {
TempMatUnreal::TempMat100 TempMat() :
101 type(MF_NORMAL_OS), tex(), numFaces(0) {}
102
TempMatUnreal::TempMat103 explicit TempMat(const Triangle &in) :
104 type((Unreal::MeshFlags)in.mType), tex(in.mTextureNum), numFaces(0) {}
105
106 // type of mesh
107 Unreal::MeshFlags type;
108
109 // index of texture
110 unsigned int tex;
111
112 // number of faces using us
113 unsigned int numFaces;
114
115 // for std::find
operator ==Unreal::TempMat116 bool operator==(const TempMat &o) {
117 return (tex == o.tex && type == o.type);
118 }
119 };
120
121 struct Vertex {
122 int32_t X : 11;
123 int32_t Y : 11;
124 int32_t Z : 10;
125 };
126
127 // UNREAL vertex compression
CompressVertex(const aiVector3D & v,uint32_t & out)128 inline void CompressVertex(const aiVector3D &v, uint32_t &out) {
129 union {
130 Vertex n;
131 int32_t t;
132 };
133 t = 0;
134 n.X = (int32_t)v.x;
135 n.Y = (int32_t)v.y;
136 n.Z = (int32_t)v.z;
137 ::memcpy(&out, &t, sizeof(int32_t));
138 }
139
140 // UNREAL vertex decompression
DecompressVertex(aiVector3D & v,int32_t in)141 inline void DecompressVertex(aiVector3D &v, int32_t in) {
142 union {
143 Vertex n;
144 int32_t i;
145 };
146 i = in;
147
148 v.x = (float)n.X;
149 v.y = (float)n.Y;
150 v.z = (float)n.Z;
151 }
152
153 } // end namespace Unreal
154
155 static const aiImporterDesc desc = {
156 "Unreal Mesh Importer",
157 "",
158 "",
159 "",
160 aiImporterFlags_SupportTextFlavour,
161 0,
162 0,
163 0,
164 0,
165 "3d uc"
166 };
167
168 // ------------------------------------------------------------------------------------------------
169 // Constructor to be privately used by Importer
UnrealImporter()170 UnrealImporter::UnrealImporter() :
171 mConfigFrameID(0), mConfigHandleFlags(true) {}
172
173 // ------------------------------------------------------------------------------------------------
174 // Destructor, private as well
~UnrealImporter()175 UnrealImporter::~UnrealImporter() {}
176
177 // ------------------------------------------------------------------------------------------------
178 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem *,bool) const179 bool UnrealImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
180 return SimpleExtensionCheck(pFile, "3d", "uc");
181 }
182
183 // ------------------------------------------------------------------------------------------------
184 // Build a string of all file extensions supported
GetInfo() const185 const aiImporterDesc *UnrealImporter::GetInfo() const {
186 return &desc;
187 }
188
189 // ------------------------------------------------------------------------------------------------
190 // Setup configuration properties for the loader
SetupProperties(const Importer * pImp)191 void UnrealImporter::SetupProperties(const Importer *pImp) {
192 // The
193 // AI_CONFIG_IMPORT_UNREAL_KEYFRAME option overrides the
194 // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
195 mConfigFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_UNREAL_KEYFRAME, -1);
196 if (static_cast<unsigned int>(-1) == mConfigFrameID) {
197 mConfigFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME, 0);
198 }
199
200 // AI_CONFIG_IMPORT_UNREAL_HANDLE_FLAGS, default is true
201 mConfigHandleFlags = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_UNREAL_HANDLE_FLAGS, 1));
202 }
203
204 // ------------------------------------------------------------------------------------------------
205 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)206 void UnrealImporter::InternReadFile(const std::string &pFile,
207 aiScene *pScene, IOSystem *pIOHandler) {
208 // For any of the 3 files being passed get the three correct paths
209 // First of all, determine file extension
210 std::string::size_type pos = pFile.find_last_of('.');
211 std::string extension = GetExtension(pFile);
212
213 std::string d_path, a_path, uc_path;
214 if (extension == "3d") {
215 // jjjj_d.3d
216 // jjjj_a.3d
217 pos = pFile.find_last_of('_');
218 if (std::string::npos == pos) {
219 throw DeadlyImportError("UNREAL: Unexpected naming scheme");
220 }
221 extension = pFile.substr(0, pos);
222 } else {
223 extension = pFile.substr(0, pos);
224 }
225
226 // build proper paths
227 d_path = extension + "_d.3d";
228 a_path = extension + "_a.3d";
229 uc_path = extension + ".uc";
230
231 ASSIMP_LOG_DEBUG("UNREAL: data file is ", d_path);
232 ASSIMP_LOG_DEBUG("UNREAL: aniv file is ", a_path);
233 ASSIMP_LOG_DEBUG("UNREAL: uc file is ", uc_path);
234
235 // and open the files ... we can't live without them
236 std::unique_ptr<IOStream> p(pIOHandler->Open(d_path));
237 if (!p)
238 throw DeadlyImportError("UNREAL: Unable to open _d file");
239 StreamReaderLE d_reader(pIOHandler->Open(d_path));
240
241 const uint16_t numTris = d_reader.GetI2();
242 const uint16_t numVert = d_reader.GetI2();
243 d_reader.IncPtr(44);
244 if (!numTris || numVert < 3)
245 throw DeadlyImportError("UNREAL: Invalid number of vertices/triangles");
246
247 // maximum texture index
248 unsigned int maxTexIdx = 0;
249
250 // collect triangles
251 std::vector<Unreal::Triangle> triangles(numTris);
252 for (auto &tri : triangles) {
253 for (unsigned int i = 0; i < 3; ++i) {
254
255 tri.mVertex[i] = d_reader.GetI2();
256 if (tri.mVertex[i] >= numTris) {
257 ASSIMP_LOG_WARN("UNREAL: vertex index out of range");
258 tri.mVertex[i] = 0;
259 }
260 }
261 tri.mType = d_reader.GetI1();
262
263 // handle mesh flagss?
264 if (mConfigHandleFlags)
265 tri.mType = Unreal::MF_NORMAL_OS;
266 else {
267 // ignore MOD and MASKED for the moment, treat them as two-sided
268 if (tri.mType == Unreal::MF_NORMAL_MOD_TS || tri.mType == Unreal::MF_NORMAL_MASKED_TS)
269 tri.mType = Unreal::MF_NORMAL_TS;
270 }
271 d_reader.IncPtr(1);
272
273 for (unsigned int i = 0; i < 3; ++i)
274 for (unsigned int i2 = 0; i2 < 2; ++i2)
275 tri.mTex[i][i2] = d_reader.GetI1();
276
277 tri.mTextureNum = d_reader.GetI1();
278 maxTexIdx = std::max(maxTexIdx, (unsigned int)tri.mTextureNum);
279 d_reader.IncPtr(1);
280 }
281
282 p.reset(pIOHandler->Open(a_path));
283 if (!p)
284 throw DeadlyImportError("UNREAL: Unable to open _a file");
285 StreamReaderLE a_reader(pIOHandler->Open(a_path));
286
287 // read number of frames
288 const uint32_t numFrames = a_reader.GetI2();
289 if (mConfigFrameID >= numFrames) {
290 throw DeadlyImportError("UNREAL: The requested frame does not exist");
291 }
292
293 uint32_t st = a_reader.GetI2();
294 if (st != numVert * 4u)
295 throw DeadlyImportError("UNREAL: Unexpected aniv file length");
296
297 // skip to our frame
298 a_reader.IncPtr(mConfigFrameID * numVert * 4);
299
300 // collect vertices
301 std::vector<aiVector3D> vertices(numVert);
302 for (auto &vertex : vertices) {
303 int32_t val = a_reader.GetI4();
304 Unreal::DecompressVertex(vertex, val);
305 }
306
307 // list of textures.
308 std::vector<std::pair<unsigned int, std::string>> textures;
309
310 // allocate the output scene
311 aiNode *nd = pScene->mRootNode = new aiNode();
312 nd->mName.Set("<UnrealRoot>");
313
314 // we can live without the uc file if necessary
315 std::unique_ptr<IOStream> pb(pIOHandler->Open(uc_path));
316 if (pb.get()) {
317
318 std::vector<char> _data;
319 TextFileToBuffer(pb.get(), _data);
320 const char *data = &_data[0];
321
322 std::vector<std::pair<std::string, std::string>> tempTextures;
323
324 // do a quick search in the UC file for some known, usually texture-related, tags
325 for (; *data; ++data) {
326 if (TokenMatchI(data, "#exec", 5)) {
327 SkipSpacesAndLineEnd(&data);
328
329 // #exec TEXTURE IMPORT [...] NAME=jjjjj [...] FILE=jjjj.pcx [...]
330 if (TokenMatchI(data, "TEXTURE", 7)) {
331 SkipSpacesAndLineEnd(&data);
332
333 if (TokenMatchI(data, "IMPORT", 6)) {
334 tempTextures.push_back(std::pair<std::string, std::string>());
335 std::pair<std::string, std::string> &me = tempTextures.back();
336 for (; !IsLineEnd(*data); ++data) {
337 if (!::ASSIMP_strincmp(data, "NAME=", 5)) {
338 const char *d = data += 5;
339 for (; !IsSpaceOrNewLine(*data); ++data)
340 ;
341 me.first = std::string(d, (size_t)(data - d));
342 } else if (!::ASSIMP_strincmp(data, "FILE=", 5)) {
343 const char *d = data += 5;
344 for (; !IsSpaceOrNewLine(*data); ++data)
345 ;
346 me.second = std::string(d, (size_t)(data - d));
347 }
348 }
349 if (!me.first.length() || !me.second.length())
350 tempTextures.pop_back();
351 }
352 }
353 // #exec MESHMAP SETTEXTURE MESHMAP=box NUM=1 TEXTURE=Jtex1
354 // #exec MESHMAP SCALE MESHMAP=box X=0.1 Y=0.1 Z=0.2
355 else if (TokenMatchI(data, "MESHMAP", 7)) {
356 SkipSpacesAndLineEnd(&data);
357
358 if (TokenMatchI(data, "SETTEXTURE", 10)) {
359
360 textures.push_back(std::pair<unsigned int, std::string>());
361 std::pair<unsigned int, std::string> &me = textures.back();
362
363 for (; !IsLineEnd(*data); ++data) {
364 if (!::ASSIMP_strincmp(data, "NUM=", 4)) {
365 data += 4;
366 me.first = strtoul10(data, &data);
367 } else if (!::ASSIMP_strincmp(data, "TEXTURE=", 8)) {
368 data += 8;
369 const char *d = data;
370 for (; !IsSpaceOrNewLine(*data); ++data)
371 ;
372 me.second = std::string(d, (size_t)(data - d));
373
374 // try to find matching path names, doesn't care if we don't find them
375 for (std::vector<std::pair<std::string, std::string>>::const_iterator it = tempTextures.begin();
376 it != tempTextures.end(); ++it) {
377 if ((*it).first == me.second) {
378 me.second = (*it).second;
379 break;
380 }
381 }
382 }
383 }
384 } else if (TokenMatchI(data, "SCALE", 5)) {
385
386 for (; !IsLineEnd(*data); ++data) {
387 if (data[0] == 'X' && data[1] == '=') {
388 data = fast_atoreal_move<float>(data + 2, (float &)nd->mTransformation.a1);
389 } else if (data[0] == 'Y' && data[1] == '=') {
390 data = fast_atoreal_move<float>(data + 2, (float &)nd->mTransformation.b2);
391 } else if (data[0] == 'Z' && data[1] == '=') {
392 data = fast_atoreal_move<float>(data + 2, (float &)nd->mTransformation.c3);
393 }
394 }
395 }
396 }
397 }
398 }
399 } else {
400 ASSIMP_LOG_ERROR("Unable to open .uc file");
401 }
402
403 std::vector<Unreal::TempMat> materials;
404 materials.reserve(textures.size() * 2 + 5);
405
406 // find out how many output meshes and materials we'll have and build material indices
407 for (Unreal::Triangle &tri : triangles) {
408 Unreal::TempMat mat(tri);
409 std::vector<Unreal::TempMat>::iterator nt = std::find(materials.begin(), materials.end(), mat);
410 if (nt == materials.end()) {
411 // add material
412 tri.matIndex = static_cast<unsigned int>(materials.size());
413 mat.numFaces = 1;
414 materials.push_back(mat);
415
416 ++pScene->mNumMeshes;
417 } else {
418 tri.matIndex = static_cast<unsigned int>(nt - materials.begin());
419 ++nt->numFaces;
420 }
421 }
422
423 if (!pScene->mNumMeshes) {
424 throw DeadlyImportError("UNREAL: Unable to find valid mesh data");
425 }
426
427 // allocate meshes and bind them to the node graph
428 pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
429 pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials = pScene->mNumMeshes];
430
431 nd->mNumMeshes = pScene->mNumMeshes;
432 nd->mMeshes = new unsigned int[nd->mNumMeshes];
433 for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
434 aiMesh *m = pScene->mMeshes[i] = new aiMesh();
435 m->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
436
437 const unsigned int num = materials[i].numFaces;
438 m->mFaces = new aiFace[num];
439 m->mVertices = new aiVector3D[num * 3];
440 m->mTextureCoords[0] = new aiVector3D[num * 3];
441
442 nd->mMeshes[i] = i;
443
444 // create materials, too
445 aiMaterial *mat = new aiMaterial();
446 pScene->mMaterials[i] = mat;
447
448 // all white by default - texture rulez
449 aiColor3D color(1.f, 1.f, 1.f);
450
451 aiString s;
452 ::ai_snprintf(s.data, MAXLEN, "mat%u_tx%u_", i, materials[i].tex);
453
454 // set the two-sided flag
455 if (materials[i].type == Unreal::MF_NORMAL_TS) {
456 const int twosided = 1;
457 mat->AddProperty(&twosided, 1, AI_MATKEY_TWOSIDED);
458 ::strcat(s.data, "ts_");
459 } else
460 ::strcat(s.data, "os_");
461
462 // make TRANS faces 90% opaque that RemRedundantMaterials won't catch us
463 if (materials[i].type == Unreal::MF_NORMAL_TRANS_TS) {
464 const float opac = 0.9f;
465 mat->AddProperty(&opac, 1, AI_MATKEY_OPACITY);
466 ::strcat(s.data, "tran_");
467 } else
468 ::strcat(s.data, "opaq_");
469
470 // a special name for the weapon attachment point
471 if (materials[i].type == Unreal::MF_WEAPON_PLACEHOLDER) {
472 s.length = ::ai_snprintf(s.data, MAXLEN, "$WeaponTag$");
473 color = aiColor3D(0.f, 0.f, 0.f);
474 }
475
476 // set color and name
477 mat->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE);
478 s.length = static_cast<ai_uint32>(::strlen(s.data));
479 mat->AddProperty(&s, AI_MATKEY_NAME);
480
481 // set texture, if any
482 const unsigned int tex = materials[i].tex;
483 for (std::vector<std::pair<unsigned int, std::string>>::const_iterator it = textures.begin(); it != textures.end(); ++it) {
484 if ((*it).first == tex) {
485 s.Set((*it).second);
486 mat->AddProperty(&s, AI_MATKEY_TEXTURE_DIFFUSE(0));
487 break;
488 }
489 }
490 }
491
492 // fill them.
493 for (const Unreal::Triangle &tri : triangles) {
494 Unreal::TempMat mat(tri);
495 std::vector<Unreal::TempMat>::iterator nt = std::find(materials.begin(), materials.end(), mat);
496
497 aiMesh *mesh = pScene->mMeshes[nt - materials.begin()];
498 aiFace &f = mesh->mFaces[mesh->mNumFaces++];
499 f.mIndices = new unsigned int[f.mNumIndices = 3];
500
501 for (unsigned int i = 0; i < 3; ++i, mesh->mNumVertices++) {
502 f.mIndices[i] = mesh->mNumVertices;
503
504 mesh->mVertices[mesh->mNumVertices] = vertices[tri.mVertex[i]];
505 mesh->mTextureCoords[0][mesh->mNumVertices] = aiVector3D(tri.mTex[i][0] / 255.f, 1.f - tri.mTex[i][1] / 255.f, 0.f);
506 }
507 }
508
509 // convert to RH
510 MakeLeftHandedProcess hero;
511 hero.Execute(pScene);
512
513 FlipWindingOrderProcess flipper;
514 flipper.Execute(pScene);
515 }
516
517 #endif // !! ASSIMP_BUILD_NO_3D_IMPORTER
518