1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2017, assimp team
7 
8 
9 All rights reserved.
10 
11 Redistribution and use of this software in source and binary forms,
12 with or without modification, are permitted provided that the following
13 conditions are met:
14 
15 * Redistributions of source code must retain the above
16   copyright notice, this list of conditions and the
17   following disclaimer.
18 
19 * Redistributions in binary form must reproduce the above
20   copyright notice, this list of conditions and the
21   following disclaimer in the documentation and/or other
22   materials provided with the distribution.
23 
24 * Neither the name of the assimp team, nor the names of its
25   contributors may be used to endorse or promote products
26   derived from this software without specific prior
27   written permission of the assimp team.
28 
29 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 ---------------------------------------------------------------------------
41 */
42 
43 /** @file  UnrealLoader.cpp
44  *  @brief Implementation of the UNREAL (*.3D) importer class
45  *
46  *  Sources:
47  *    http://local.wasp.uwa.edu.au/~pbourke/dataformats/unreal/
48  */
49 
50 
51 
52 #ifndef ASSIMP_BUILD_NO_3D_IMPORTER
53 
54 #include "UnrealLoader.h"
55 #include "StreamReader.h"
56 #include "ParsingUtils.h"
57 #include "fast_atof.h"
58 #include "ConvertToLHProcess.h"
59 
60 #include <assimp/Importer.hpp>
61 #include <assimp/DefaultLogger.hpp>
62 #include <assimp/IOSystem.hpp>
63 #include <assimp/scene.h>
64 #include <assimp/importerdesc.h>
65 
66 #include <memory>
67 
68 using namespace Assimp;
69 
70 static const aiImporterDesc desc = {
71     "Unreal Mesh Importer",
72     "",
73     "",
74     "",
75     aiImporterFlags_SupportTextFlavour,
76     0,
77     0,
78     0,
79     0,
80     "3d uc"
81 };
82 
83 
84 // ------------------------------------------------------------------------------------------------
85 // Constructor to be privately used by Importer
UnrealImporter()86 UnrealImporter::UnrealImporter()
87 :   configFrameID   (0)
88 ,   configHandleFlags (true)
89 {}
90 
91 // ------------------------------------------------------------------------------------------------
92 // Destructor, private as well
~UnrealImporter()93 UnrealImporter::~UnrealImporter()
94 {}
95 
96 // ------------------------------------------------------------------------------------------------
97 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem *,bool) const98 bool UnrealImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const
99 {
100     return  SimpleExtensionCheck(pFile,"3d","uc");
101 }
102 
103 // ------------------------------------------------------------------------------------------------
104 // Build a string of all file extensions supported
GetInfo() const105 const aiImporterDesc* UnrealImporter::GetInfo () const
106 {
107     return &desc;
108 }
109 
110 // ------------------------------------------------------------------------------------------------
111 // Setup configuration properties for the loader
SetupProperties(const Importer * pImp)112 void UnrealImporter::SetupProperties(const Importer* pImp)
113 {
114     // The
115     // AI_CONFIG_IMPORT_UNREAL_KEYFRAME option overrides the
116     // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
117     configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_UNREAL_KEYFRAME,-1);
118     if(static_cast<unsigned int>(-1) == configFrameID)  {
119         configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
120     }
121 
122     // AI_CONFIG_IMPORT_UNREAL_HANDLE_FLAGS, default is true
123     configHandleFlags = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_UNREAL_HANDLE_FLAGS,1));
124 }
125 
126 // ------------------------------------------------------------------------------------------------
127 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)128 void UnrealImporter::InternReadFile( const std::string& pFile,
129     aiScene* pScene, IOSystem* pIOHandler)
130 {
131     // For any of the 3 files being passed get the three correct paths
132     // First of all, determine file extension
133     std::string::size_type pos = pFile.find_last_of('.');
134     std::string extension = GetExtension(pFile);
135 
136     std::string d_path,a_path,uc_path;
137     if (extension == "3d")      {
138         // jjjj_d.3d
139         // jjjj_a.3d
140         pos = pFile.find_last_of('_');
141         if (std::string::npos == pos) {
142             throw DeadlyImportError("UNREAL: Unexpected naming scheme");
143         }
144         extension = pFile.substr(0,pos);
145     }
146     else {
147         extension = pFile.substr(0,pos);
148     }
149 
150     // build proper paths
151     d_path  = extension+"_d.3d";
152     a_path  = extension+"_a.3d";
153     uc_path = extension+".uc";
154 
155     DefaultLogger::get()->debug("UNREAL: data file is " + d_path);
156     DefaultLogger::get()->debug("UNREAL: aniv file is " + a_path);
157     DefaultLogger::get()->debug("UNREAL: uc file is "   + uc_path);
158 
159     // and open the files ... we can't live without them
160     std::unique_ptr<IOStream> p(pIOHandler->Open(d_path));
161     if (!p)
162         throw DeadlyImportError("UNREAL: Unable to open _d file");
163     StreamReaderLE d_reader(pIOHandler->Open(d_path));
164 
165     const uint16_t numTris = d_reader.GetI2();
166     const uint16_t numVert = d_reader.GetI2();
167     d_reader.IncPtr(44);
168     if (!numTris || numVert < 3)
169         throw DeadlyImportError("UNREAL: Invalid number of vertices/triangles");
170 
171     // maximum texture index
172     unsigned int maxTexIdx = 0;
173 
174     // collect triangles
175     std::vector<Unreal::Triangle> triangles(numTris);
176     for (auto & tri : triangles) {
177         for (unsigned int i = 0; i < 3;++i) {
178 
179             tri.mVertex[i] = d_reader.GetI2();
180             if (tri.mVertex[i] >= numTris)  {
181                 DefaultLogger::get()->warn("UNREAL: vertex index out of range");
182                 tri.mVertex[i] = 0;
183             }
184         }
185         tri.mType = d_reader.GetI1();
186 
187         // handle mesh flagss?
188         if (configHandleFlags)
189             tri.mType = Unreal::MF_NORMAL_OS;
190         else {
191             // ignore MOD and MASKED for the moment, treat them as two-sided
192             if (tri.mType == Unreal::MF_NORMAL_MOD_TS || tri.mType == Unreal::MF_NORMAL_MASKED_TS)
193                 tri.mType = Unreal::MF_NORMAL_TS;
194         }
195         d_reader.IncPtr(1);
196 
197         for (unsigned int i = 0; i < 3;++i)
198             for (unsigned int i2 = 0; i2 < 2;++i2)
199                 tri.mTex[i][i2] = d_reader.GetI1();
200 
201         tri.mTextureNum = d_reader.GetI1();
202         maxTexIdx = std::max(maxTexIdx,(unsigned int)tri.mTextureNum);
203         d_reader.IncPtr(1);
204     }
205 
206     p.reset(pIOHandler->Open(a_path));
207     if (!p)
208         throw DeadlyImportError("UNREAL: Unable to open _a file");
209     StreamReaderLE a_reader(pIOHandler->Open(a_path));
210 
211     // read number of frames
212     const uint32_t numFrames = a_reader.GetI2();
213     if (configFrameID >= numFrames)
214         throw DeadlyImportError("UNREAL: The requested frame does not exist");
215 
216     uint32_t st = a_reader.GetI2();
217     if (st != numVert*4)
218         throw DeadlyImportError("UNREAL: Unexpected aniv file length");
219 
220     // skip to our frame
221     a_reader.IncPtr(configFrameID *numVert*4);
222 
223     // collect vertices
224     std::vector<aiVector3D> vertices(numVert);
225     for (auto &vertex : vertices)    {
226         int32_t val = a_reader.GetI4();
227         Unreal::DecompressVertex(vertex ,val);
228     }
229 
230     // list of textures.
231     std::vector< std::pair<unsigned int, std::string> > textures;
232 
233     // allocate the output scene
234     aiNode* nd = pScene->mRootNode = new aiNode();
235     nd->mName.Set("<UnrealRoot>");
236 
237     // we can live without the uc file if necessary
238     std::unique_ptr<IOStream> pb (pIOHandler->Open(uc_path));
239     if (pb.get())   {
240 
241         std::vector<char> _data;
242         TextFileToBuffer(pb.get(),_data);
243         const char* data = &_data[0];
244 
245         std::vector< std::pair< std::string,std::string > > tempTextures;
246 
247         // do a quick search in the UC file for some known, usually texture-related, tags
248         for (;*data;++data) {
249             if (TokenMatchI(data,"#exec",5))    {
250                 SkipSpacesAndLineEnd(&data);
251 
252                 // #exec TEXTURE IMPORT [...] NAME=jjjjj [...] FILE=jjjj.pcx [...]
253                 if (TokenMatchI(data,"TEXTURE",7))  {
254                     SkipSpacesAndLineEnd(&data);
255 
256                     if (TokenMatchI(data,"IMPORT",6))   {
257                         tempTextures.push_back(std::pair< std::string,std::string >());
258                         std::pair< std::string,std::string >& me = tempTextures.back();
259                         for (;!IsLineEnd(*data);++data) {
260                             if (!::ASSIMP_strincmp(data,"NAME=",5)) {
261                                 const char *d = data+=5;
262                                 for (;!IsSpaceOrNewLine(*data);++data);
263                                 me.first = std::string(d,(size_t)(data-d));
264                             }
265                             else if (!::ASSIMP_strincmp(data,"FILE=",5))    {
266                                 const char *d = data+=5;
267                                 for (;!IsSpaceOrNewLine(*data);++data);
268                                 me.second = std::string(d,(size_t)(data-d));
269                             }
270                         }
271                         if (!me.first.length() || !me.second.length())
272                             tempTextures.pop_back();
273                     }
274                 }
275                 // #exec MESHMAP SETTEXTURE MESHMAP=box NUM=1 TEXTURE=Jtex1
276                 // #exec MESHMAP SCALE MESHMAP=box X=0.1 Y=0.1 Z=0.2
277                 else if (TokenMatchI(data,"MESHMAP",7)) {
278                     SkipSpacesAndLineEnd(&data);
279 
280                     if (TokenMatchI(data,"SETTEXTURE",10)) {
281 
282                         textures.push_back(std::pair<unsigned int, std::string>());
283                         std::pair<unsigned int, std::string>& me = textures.back();
284 
285                         for (;!IsLineEnd(*data);++data) {
286                             if (!::ASSIMP_strincmp(data,"NUM=",4))  {
287                                 data += 4;
288                                 me.first = strtoul10(data,&data);
289                             }
290                             else if (!::ASSIMP_strincmp(data,"TEXTURE=",8)) {
291                                 data += 8;
292                                 const char *d = data;
293                                 for (;!IsSpaceOrNewLine(*data);++data);
294                                 me.second = std::string(d,(size_t)(data-d));
295 
296                                 // try to find matching path names, doesn't care if we don't find them
297                                 for (std::vector< std::pair< std::string,std::string > >::const_iterator it = tempTextures.begin();
298                                      it != tempTextures.end(); ++it)    {
299                                     if ((*it).first == me.second)   {
300                                         me.second = (*it).second;
301                                         break;
302                                     }
303                                 }
304                             }
305                         }
306                     }
307                     else if (TokenMatchI(data,"SCALE",5)) {
308 
309                         for (;!IsLineEnd(*data);++data) {
310                             if (data[0] == 'X' && data[1] == '=')   {
311                                 data = fast_atoreal_move<float>(data+2,(float&)nd->mTransformation.a1);
312                             }
313                             else if (data[0] == 'Y' && data[1] == '=')  {
314                                 data = fast_atoreal_move<float>(data+2,(float&)nd->mTransformation.b2);
315                             }
316                             else if (data[0] == 'Z' && data[1] == '=')  {
317                                 data = fast_atoreal_move<float>(data+2,(float&)nd->mTransformation.c3);
318                             }
319                         }
320                     }
321                 }
322             }
323         }
324     }
325     else    {
326         DefaultLogger::get()->error("Unable to open .uc file");
327     }
328 
329     std::vector<Unreal::TempMat> materials;
330     materials.reserve(textures.size()*2+5);
331 
332     // find out how many output meshes and materials we'll have and build material indices
333 	for (Unreal::Triangle &tri : triangles) {
334         Unreal::TempMat mat(tri);
335         std::vector<Unreal::TempMat>::iterator nt = std::find(materials.begin(),materials.end(),mat);
336         if (nt == materials.end()) {
337             // add material
338             tri.matIndex = static_cast<unsigned int>(materials.size());
339             mat.numFaces = 1;
340             materials.push_back(mat);
341 
342             ++pScene->mNumMeshes;
343         }
344         else {
345             tri.matIndex = static_cast<unsigned int>(nt-materials.begin());
346             ++nt->numFaces;
347         }
348     }
349 
350     if (!pScene->mNumMeshes) {
351         throw DeadlyImportError("UNREAL: Unable to find valid mesh data");
352     }
353 
354     // allocate meshes and bind them to the node graph
355     pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
356     pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials = pScene->mNumMeshes];
357 
358     nd->mNumMeshes  = pScene->mNumMeshes;
359     nd->mMeshes = new unsigned int[nd->mNumMeshes];
360     for (unsigned int i = 0; i < pScene->mNumMeshes;++i) {
361         aiMesh* m = pScene->mMeshes[i] =  new aiMesh();
362         m->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
363 
364         const unsigned int num = materials[i].numFaces;
365         m->mFaces            = new aiFace     [num];
366         m->mVertices         = new aiVector3D [num*3];
367         m->mTextureCoords[0] = new aiVector3D [num*3];
368 
369         nd->mMeshes[i] = i;
370 
371         // create materials, too
372         aiMaterial* mat = new aiMaterial();
373         pScene->mMaterials[i] = mat;
374 
375         // all white by default - texture rulez
376         aiColor3D color(1.f,1.f,1.f);
377 
378         aiString s;
379         ::ai_snprintf( s.data, MAXLEN, "mat%u_tx%u_",i,materials[i].tex );
380 
381         // set the two-sided flag
382         if (materials[i].type == Unreal::MF_NORMAL_TS) {
383             const int twosided = 1;
384             mat->AddProperty(&twosided,1,AI_MATKEY_TWOSIDED);
385             ::strcat(s.data,"ts_");
386         }
387         else ::strcat(s.data,"os_");
388 
389         // make TRANS faces 90% opaque that RemRedundantMaterials won't catch us
390         if (materials[i].type == Unreal::MF_NORMAL_TRANS_TS)    {
391             const float opac = 0.9f;
392             mat->AddProperty(&opac,1,AI_MATKEY_OPACITY);
393             ::strcat(s.data,"tran_");
394         }
395         else ::strcat(s.data,"opaq_");
396 
397         // a special name for the weapon attachment point
398         if (materials[i].type == Unreal::MF_WEAPON_PLACEHOLDER) {
399             s.length = ::ai_snprintf( s.data, MAXLEN, "$WeaponTag$" );
400             color = aiColor3D(0.f,0.f,0.f);
401         }
402 
403         // set color and name
404         mat->AddProperty(&color,1,AI_MATKEY_COLOR_DIFFUSE);
405         s.length = ::strlen(s.data);
406         mat->AddProperty(&s,AI_MATKEY_NAME);
407 
408         // set texture, if any
409         const unsigned int tex = materials[i].tex;
410         for (std::vector< std::pair< unsigned int, std::string > >::const_iterator it = textures.begin();it != textures.end();++it) {
411             if ((*it).first == tex) {
412                 s.Set((*it).second);
413                 mat->AddProperty(&s,AI_MATKEY_TEXTURE_DIFFUSE(0));
414                 break;
415             }
416         }
417     }
418 
419     // fill them.
420     for (const Unreal::Triangle &tri : triangles) {
421         Unreal::TempMat mat(tri);
422         std::vector<Unreal::TempMat>::iterator nt = std::find(materials.begin(),materials.end(),mat);
423 
424         aiMesh* mesh = pScene->mMeshes[nt-materials.begin()];
425         aiFace& f    = mesh->mFaces[mesh->mNumFaces++];
426         f.mIndices   = new unsigned int[f.mNumIndices = 3];
427 
428         for (unsigned int i = 0; i < 3;++i,mesh->mNumVertices++) {
429             f.mIndices[i] = mesh->mNumVertices;
430 
431             mesh->mVertices[mesh->mNumVertices] = vertices[ tri.mVertex[i] ];
432             mesh->mTextureCoords[0][mesh->mNumVertices] = aiVector3D( tri.mTex[i][0] / 255.f, 1.f - tri.mTex[i][1] / 255.f, 0.f);
433         }
434     }
435 
436     // convert to RH
437     MakeLeftHandedProcess hero;
438     hero.Execute(pScene);
439 
440     FlipWindingOrderProcess flipper;
441     flipper.Execute(pScene);
442 }
443 
444 #endif // !! ASSIMP_BUILD_NO_3D_IMPORTER
445