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