1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
\r
2 // This file is part of the "Irrlicht Engine".
\r
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
\r
5 #include "COBJMeshFileLoader.h"
\r
6 #include "IMeshManipulator.h"
\r
7 #include "IVideoDriver.h"
\r
9 #include "SMeshBuffer.h"
\r
10 #include "SAnimatedMesh.h"
\r
11 #include "IReadFile.h"
\r
12 #include "IAttributes.h"
\r
13 #include "fast_atof.h"
\r
14 #include "coreutil.h"
\r
23 #define _IRR_DEBUG_OBJ_LOADER_
\r
27 COBJMeshFileLoader::COBJMeshFileLoader(scene::ISceneManager* smgr, io::IFileSystem* fs)
\r
28 : SceneManager(smgr), FileSystem(fs)
\r
31 setDebugName("COBJMeshFileLoader");
\r
40 COBJMeshFileLoader::~COBJMeshFileLoader()
\r
47 //! returns true if the file maybe is able to be loaded by this class
\r
48 //! based on the file extension (e.g. ".bsp")
\r
49 bool COBJMeshFileLoader::isALoadableFileExtension(const io::path& filename) const
\r
51 return core::hasFileExtension ( filename, "obj" );
\r
55 //! creates/loads an animated mesh from the file.
\r
56 //! \return Pointer to the created mesh. Returns 0 if loading failed.
\r
57 //! If you no longer need the mesh, you should call IAnimatedMesh::drop().
\r
58 //! See IReferenceCounted::drop() for more information.
\r
59 IAnimatedMesh* COBJMeshFileLoader::createMesh(io::IReadFile* file)
\r
64 const long filesize = file->getSize();
\r
68 const u32 WORD_BUFFER_LENGTH = 512;
\r
70 core::array<core::vector3df> vertexBuffer(1000);
\r
71 core::array<core::vector3df> normalsBuffer(1000);
\r
72 core::array<core::vector2df> textureCoordBuffer(1000);
\r
74 SObjMtl * currMtl = new SObjMtl();
\r
75 Materials.push_back(currMtl);
\r
76 u32 smoothingGroup=0;
\r
78 const io::path fullName = file->getFileName();
\r
79 const io::path relPath = FileSystem->getFileDir(fullName)+"/";
\r
81 c8* buf = new c8[filesize];
\r
82 memset(buf, 0, filesize);
\r
83 file->read((void*)buf, filesize);
\r
84 const c8* const bufEnd = buf+filesize;
\r
86 // Process obj information
\r
87 const c8* bufPtr = buf;
\r
88 core::stringc grpName, mtlName;
\r
89 bool mtlChanged=false;
\r
90 bool useGroups = !SceneManager->getParameters()->getAttributeAsBool(OBJ_LOADER_IGNORE_GROUPS);
\r
91 bool useMaterials = !SceneManager->getParameters()->getAttributeAsBool(OBJ_LOADER_IGNORE_MATERIAL_FILES);
\r
92 irr::u32 lineNr = 1; // only counts non-empty lines, still useful in debugging to locate errors
\r
93 core::array<int> faceCorners;
\r
94 faceCorners.reallocate(32); // should be large enough
\r
95 const core::stringc TAG_OFF = "off";
\r
96 irr::u32 degeneratedFaces = 0;
\r
98 while(bufPtr != bufEnd)
\r
102 case 'm': // mtllib (material)
\r
106 c8 name[WORD_BUFFER_LENGTH];
\r
107 bufPtr = goAndCopyNextWord(name, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
108 #ifdef _IRR_DEBUG_OBJ_LOADER_
\r
109 os::Printer::log("Reading material file",name);
\r
115 case 'v': // v, vn, vt
\r
118 case ' ': // vertex
\r
120 core::vector3df vec;
\r
121 bufPtr = readVec3(bufPtr, vec, bufEnd);
\r
122 vertexBuffer.push_back(vec);
\r
126 case 'n': // normal
\r
128 core::vector3df vec;
\r
129 bufPtr = readVec3(bufPtr, vec, bufEnd);
\r
130 normalsBuffer.push_back(vec);
\r
134 case 't': // texcoord
\r
136 core::vector2df vec;
\r
137 bufPtr = readUV(bufPtr, vec, bufEnd);
\r
138 textureCoordBuffer.push_back(vec);
\r
144 case 'g': // group name
\r
146 c8 grp[WORD_BUFFER_LENGTH];
\r
147 bufPtr = goAndCopyNextWord(grp, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
148 #ifdef _IRR_DEBUG_OBJ_LOADER_
\r
149 os::Printer::log("Loaded group start",grp, ELL_DEBUG);
\r
156 grpName = "default";
\r
162 case 's': // smoothing can be a group or off (equiv. to 0)
\r
164 c8 smooth[WORD_BUFFER_LENGTH];
\r
165 bufPtr = goAndCopyNextWord(smooth, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
166 #ifdef _IRR_DEBUG_OBJ_LOADER_
\r
167 os::Printer::log("Loaded smoothing group start",smooth, ELL_DEBUG);
\r
169 if (TAG_OFF==smooth)
\r
172 smoothingGroup=core::strtoul10(smooth);
\r
174 (void)smoothingGroup; // disable unused variable warnings
\r
178 case 'u': // usemtl
\r
179 // get name of material
\r
181 c8 matName[WORD_BUFFER_LENGTH];
\r
182 bufPtr = goAndCopyNextWord(matName, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
183 #ifdef _IRR_DEBUG_OBJ_LOADER_
\r
184 os::Printer::log("Loaded material start",matName, ELL_DEBUG);
\r
193 c8 vertexWord[WORD_BUFFER_LENGTH]; // for retrieving vertex data
\r
194 video::S3DVertex v;
\r
195 // Assign vertex color from currently active material's diffuse color
\r
198 // retrieve the material
\r
199 SObjMtl *useMtl = findMtl(mtlName, grpName);
\r
200 // only change material if we found it
\r
206 v.Color = currMtl->Meshbuffer->Material.DiffuseColor;
\r
208 // get all vertices data in this face (current line of obj file)
\r
209 const core::stringc wordBuffer = copyLine(bufPtr, bufEnd);
\r
210 const c8* linePtr = wordBuffer.c_str();
\r
211 const c8* const endPtr = linePtr+wordBuffer.size();
\r
213 faceCorners.set_used(0); // fast clear
\r
215 // read in all vertices
\r
216 linePtr = goNextWord(linePtr, endPtr);
\r
217 while (0 != linePtr[0])
\r
219 // Array to communicate with retrieveVertexIndices()
\r
220 // sends the buffer sizes and gets the actual indices
\r
221 // if index not set returns -1
\r
223 Idx[0] = Idx[1] = Idx[2] = -1;
\r
225 // read in next vertex's data
\r
226 u32 wlength = copyWord(vertexWord, linePtr, WORD_BUFFER_LENGTH, endPtr);
\r
227 // this function will also convert obj's 1-based index to c++'s 0-based index
\r
228 retrieveVertexIndices(vertexWord, Idx, vertexWord+wlength+1, vertexBuffer.size(), textureCoordBuffer.size(), normalsBuffer.size());
\r
229 if ( -1 != Idx[0] && Idx[0] < (irr::s32)vertexBuffer.size() )
\r
230 v.Pos = vertexBuffer[Idx[0]];
\r
233 os::Printer::log("Invalid vertex index in this line:", wordBuffer.c_str(), ELL_ERROR);
\r
237 if ( -1 != Idx[1] && Idx[1] < (irr::s32)textureCoordBuffer.size() )
\r
238 v.TCoords = textureCoordBuffer[Idx[1]];
\r
240 v.TCoords.set(0.0f,0.0f);
\r
241 if ( -1 != Idx[2] && Idx[2] < (irr::s32)normalsBuffer.size() )
\r
242 v.Normal = normalsBuffer[Idx[2]];
\r
245 v.Normal.set(0.0f,0.0f,0.0f);
\r
246 currMtl->RecalculateNormals=true;
\r
250 auto n = currMtl->VertMap.find(v);
\r
251 if (n != currMtl->VertMap.end())
\r
253 vertLocation = n->second;
\r
257 currMtl->Meshbuffer->Vertices.push_back(v);
\r
258 vertLocation = currMtl->Meshbuffer->Vertices.size() -1;
\r
259 currMtl->VertMap.emplace(v, vertLocation);
\r
262 faceCorners.push_back(vertLocation);
\r
264 // go to next vertex
\r
265 linePtr = goNextWord(linePtr, endPtr);
\r
268 // triangulate the face
\r
269 const int c = faceCorners[0];
\r
270 for ( u32 i = 1; i < faceCorners.size() - 1; ++i )
\r
273 const int a = faceCorners[i + 1];
\r
274 const int b = faceCorners[i];
\r
275 if (a != b && a != c && b != c) // ignore degenerated faces. We can get them when we merge vertices above in the VertMap.
\r
277 currMtl->Meshbuffer->Indices.push_back(a);
\r
278 currMtl->Meshbuffer->Indices.push_back(b);
\r
279 currMtl->Meshbuffer->Indices.push_back(c);
\r
283 ++degeneratedFaces;
\r
289 case '#': // comment
\r
292 } // end switch(bufPtr[0])
\r
293 // eat up rest of line
\r
294 bufPtr = goNextLine(bufPtr, bufEnd);
\r
296 } // end while(bufPtr && (bufPtr-buf<filesize))
\r
298 if ( degeneratedFaces > 0 )
\r
300 irr::core::stringc log(degeneratedFaces);
\r
301 log += " degenerated faces removed in ";
\r
302 log += irr::core::stringc(fullName);
\r
303 os::Printer::log(log.c_str(), ELL_INFORMATION);
\r
306 SMesh* mesh = new SMesh();
\r
308 // Combine all the groups (meshbuffers) into the mesh
\r
309 for ( u32 m = 0; m < Materials.size(); ++m )
\r
311 if ( Materials[m]->Meshbuffer->getIndexCount() > 0 )
\r
313 Materials[m]->Meshbuffer->recalculateBoundingBox();
\r
314 if (Materials[m]->RecalculateNormals)
\r
315 SceneManager->getMeshManipulator()->recalculateNormals(Materials[m]->Meshbuffer);
\r
316 mesh->addMeshBuffer( Materials[m]->Meshbuffer );
\r
320 // Create the Animated mesh if there's anything in the mesh
\r
321 SAnimatedMesh* animMesh = 0;
\r
322 if ( 0 != mesh->getMeshBufferCount() )
\r
324 mesh->recalculateBoundingBox();
\r
325 animMesh = new SAnimatedMesh();
\r
326 animMesh->Type = EAMT_OBJ;
\r
327 animMesh->addMesh(mesh);
\r
328 animMesh->recalculateBoundingBox();
\r
331 // Clean up the allocate obj file contents
\r
333 // more cleaning up
\r
341 const c8* COBJMeshFileLoader::readColor(const c8* bufPtr, video::SColor& color, const c8* const bufEnd)
\r
343 const u32 COLOR_BUFFER_LENGTH = 16;
\r
344 c8 colStr[COLOR_BUFFER_LENGTH];
\r
346 bufPtr = goAndCopyNextWord(colStr, bufPtr, COLOR_BUFFER_LENGTH, bufEnd);
\r
347 color.setRed((u32)core::round32(core::fast_atof(colStr)*255.f));
\r
348 bufPtr = goAndCopyNextWord(colStr, bufPtr, COLOR_BUFFER_LENGTH, bufEnd);
\r
349 color.setGreen((u32)core::round32(core::fast_atof(colStr)*255.f));
\r
350 bufPtr = goAndCopyNextWord(colStr, bufPtr, COLOR_BUFFER_LENGTH, bufEnd);
\r
351 color.setBlue((u32)core::round32(core::fast_atof(colStr)*255.f));
\r
356 //! Read 3d vector of floats
\r
357 const c8* COBJMeshFileLoader::readVec3(const c8* bufPtr, core::vector3df& vec, const c8* const bufEnd)
\r
359 const u32 WORD_BUFFER_LENGTH = 256;
\r
360 c8 wordBuffer[WORD_BUFFER_LENGTH];
\r
362 bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
363 vec.X=-core::fast_atof(wordBuffer); // change handedness
\r
364 bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
365 vec.Y=core::fast_atof(wordBuffer);
\r
366 bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
367 vec.Z=core::fast_atof(wordBuffer);
\r
372 //! Read 2d vector of floats
\r
373 const c8* COBJMeshFileLoader::readUV(const c8* bufPtr, core::vector2df& vec, const c8* const bufEnd)
\r
375 const u32 WORD_BUFFER_LENGTH = 256;
\r
376 c8 wordBuffer[WORD_BUFFER_LENGTH];
\r
378 bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
379 vec.X=core::fast_atof(wordBuffer);
\r
380 bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
381 vec.Y=1-core::fast_atof(wordBuffer); // change handedness
\r
386 //! Read boolean value represented as 'on' or 'off'
\r
387 const c8* COBJMeshFileLoader::readBool(const c8* bufPtr, bool& tf, const c8* const bufEnd)
\r
389 const u32 BUFFER_LENGTH = 8;
\r
390 c8 tfStr[BUFFER_LENGTH];
\r
392 bufPtr = goAndCopyNextWord(tfStr, bufPtr, BUFFER_LENGTH, bufEnd);
\r
393 tf = strcmp(tfStr, "off") != 0;
\r
398 COBJMeshFileLoader::SObjMtl* COBJMeshFileLoader::findMtl(const core::stringc& mtlName, const core::stringc& grpName)
\r
400 COBJMeshFileLoader::SObjMtl* defMaterial = 0;
\r
401 // search existing Materials for best match
\r
402 // exact match does return immediately, only name match means a new group
\r
403 for (u32 i = 0; i < Materials.size(); ++i)
\r
405 if ( Materials[i]->Name == mtlName )
\r
407 if ( Materials[i]->Group == grpName )
\r
408 return Materials[i];
\r
410 defMaterial = Materials[i];
\r
413 // we found a partial match
\r
416 Materials.push_back(new SObjMtl(*defMaterial));
\r
417 Materials.getLast()->Group = grpName;
\r
418 return Materials.getLast();
\r
420 // we found a new group for a non-existant material
\r
421 else if (grpName.size())
\r
423 Materials.push_back(new SObjMtl(*Materials[0]));
\r
424 Materials.getLast()->Group = grpName;
\r
425 return Materials.getLast();
\r
431 //! skip space characters and stop on first non-space
\r
432 const c8* COBJMeshFileLoader::goFirstWord(const c8* buf, const c8* const bufEnd, bool acrossNewlines)
\r
434 // skip space characters
\r
435 if (acrossNewlines)
\r
436 while((buf != bufEnd) && core::isspace(*buf))
\r
439 while((buf != bufEnd) && core::isspace(*buf) && (*buf != '\n'))
\r
446 //! skip current word and stop at beginning of next one
\r
447 const c8* COBJMeshFileLoader::goNextWord(const c8* buf, const c8* const bufEnd, bool acrossNewlines)
\r
449 // skip current word
\r
450 while(( buf != bufEnd ) && !core::isspace(*buf))
\r
453 return goFirstWord(buf, bufEnd, acrossNewlines);
\r
457 //! Read until line break is reached and stop at the next non-space character
\r
458 const c8* COBJMeshFileLoader::goNextLine(const c8* buf, const c8* const bufEnd)
\r
460 // look for newline characters
\r
461 while(buf != bufEnd)
\r
463 // found it, so leave
\r
464 if (*buf=='\n' || *buf=='\r')
\r
468 return goFirstWord(buf, bufEnd);
\r
472 u32 COBJMeshFileLoader::copyWord(c8* outBuf, const c8* const inBuf, u32 outBufLength, const c8* const bufEnd)
\r
485 if (core::isspace(inBuf[i]) || &(inBuf[i]) == bufEnd)
\r
490 u32 length = core::min_(i, outBufLength-1);
\r
491 for (u32 j=0; j<length; ++j)
\r
492 outBuf[j] = inBuf[j];
\r
494 outBuf[length] = 0;
\r
499 core::stringc COBJMeshFileLoader::copyLine(const c8* inBuf, const c8* bufEnd)
\r
502 return core::stringc();
\r
504 const c8* ptr = inBuf;
\r
507 if (*ptr=='\n' || *ptr=='\r')
\r
511 // we must avoid the +1 in case the array is used up
\r
512 return core::stringc(inBuf, (u32)(ptr-inBuf+((ptr < bufEnd) ? 1 : 0)));
\r
516 const c8* COBJMeshFileLoader::goAndCopyNextWord(c8* outBuf, const c8* inBuf, u32 outBufLength, const c8* bufEnd)
\r
518 inBuf = goNextWord(inBuf, bufEnd, false);
\r
519 copyWord(outBuf, inBuf, outBufLength, bufEnd);
\r
524 bool COBJMeshFileLoader::retrieveVertexIndices(c8* vertexData, s32* idx, const c8* bufEnd, u32 vbsize, u32 vtsize, u32 vnsize)
\r
526 const u32 BUFFER_LENGTH = 16;
\r
527 c8 word[BUFFER_LENGTH];
\r
528 const c8* p = goFirstWord(vertexData, bufEnd);
\r
529 u32 idxType = 0; // 0 = posIdx, 1 = texcoordIdx, 2 = normalIdx
\r
532 while ( p != bufEnd )
\r
534 if ( i >= BUFFER_LENGTH )
\r
538 if ( ( core::isdigit(*p)) || (*p == '-') )
\r
540 // build up the number
\r
543 else if ( *p == '/' || *p == ' ' || *p == '\0' )
\r
545 // number is completed. Convert and store it
\r
547 // if no number was found index will become 0 and later on -1 by decrement
\r
548 idx[idxType] = core::strtol10(word);
\r
549 if (idx[idxType]<0)
\r
554 idx[idxType] += vbsize;
\r
557 idx[idxType] += vtsize;
\r
560 idx[idxType] += vnsize;
\r
571 // go to the next kind of index type
\r
574 if ( ++idxType > 2 )
\r
576 // error checking, shouldn't reach here unless file is wrong
\r
582 // set all missing values to disable (=-1)
\r
583 while (++idxType < 3)
\r
590 // go to the next char
\r
598 void COBJMeshFileLoader::cleanUp()
\r
600 for (u32 i=0; i < Materials.size(); ++i )
\r
602 Materials[i]->Meshbuffer->drop();
\r
603 delete Materials[i];
\r
610 } // end namespace scene
\r
611 } // end namespace irr
\r