1/*
2---------------------------------------------------------------------------
3Open Asset Import Library (assimp)
4---------------------------------------------------------------------------
5
6Copyright (c) 2006-2017, assimp team
7
8
9All rights reserved.
10
11Redistribution and use of this software in source and binary forms,
12with or without modification, are permitted provided that the following
13conditions 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
29THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39OF 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
68using namespace Assimp;
69
70static 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
86UnrealImporter::UnrealImporter()
87: configFrameID (0)
88, configHandleFlags (true)
89{}
90
91// ------------------------------------------------------------------------------------------------
92// Destructor, private as well
93UnrealImporter::~UnrealImporter()
94{}
95
96// ------------------------------------------------------------------------------------------------
97// Returns whether the class can handle the format of the given file.
98bool 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
105const aiImporterDesc* UnrealImporter::GetInfo () const
106{
107 return &desc;
108}
109
110// ------------------------------------------------------------------------------------------------
111// Setup configuration properties for the loader
112void 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.
128void 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