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)
\r
28 : SceneManager(smgr)
\r
31 setDebugName("COBJMeshFileLoader");
\r
37 COBJMeshFileLoader::~COBJMeshFileLoader()
\r
42 //! returns true if the file maybe is able to be loaded by this class
\r
43 //! based on the file extension (e.g. ".bsp")
\r
44 bool COBJMeshFileLoader::isALoadableFileExtension(const io::path& filename) const
\r
46 return core::hasFileExtension ( filename, "obj" );
\r
50 //! creates/loads an animated mesh from the file.
\r
51 //! \return Pointer to the created mesh. Returns 0 if loading failed.
\r
52 //! If you no longer need the mesh, you should call IAnimatedMesh::drop().
\r
53 //! See IReferenceCounted::drop() for more information.
\r
54 IAnimatedMesh* COBJMeshFileLoader::createMesh(io::IReadFile* file)
\r
59 const long filesize = file->getSize();
\r
63 const u32 WORD_BUFFER_LENGTH = 512;
\r
65 core::array<core::vector3df> vertexBuffer(1000);
\r
66 core::array<core::vector3df> normalsBuffer(1000);
\r
67 core::array<core::vector2df> textureCoordBuffer(1000);
\r
69 SObjMtl * currMtl = new SObjMtl();
\r
70 Materials.push_back(currMtl);
\r
71 u32 smoothingGroup=0;
\r
73 const io::path fullName = file->getFileName();
\r
75 c8* buf = new c8[filesize];
\r
76 memset(buf, 0, filesize);
\r
77 file->read((void*)buf, filesize);
\r
78 const c8* const bufEnd = buf+filesize;
\r
80 // Process obj information
\r
81 const c8* bufPtr = buf;
\r
82 core::stringc grpName, mtlName;
\r
83 bool mtlChanged=false;
\r
84 bool useGroups = !SceneManager->getParameters()->getAttributeAsBool(OBJ_LOADER_IGNORE_GROUPS);
\r
85 bool useMaterials = !SceneManager->getParameters()->getAttributeAsBool(OBJ_LOADER_IGNORE_MATERIAL_FILES);
\r
86 irr::u32 lineNr = 1; // only counts non-empty lines, still useful in debugging to locate errors
\r
87 core::array<int> faceCorners;
\r
88 faceCorners.reallocate(32); // should be large enough
\r
89 const core::stringc TAG_OFF = "off";
\r
90 irr::u32 degeneratedFaces = 0;
\r
92 while(bufPtr != bufEnd)
\r
96 case 'm': // mtllib (material)
\r
100 c8 name[WORD_BUFFER_LENGTH];
\r
101 bufPtr = goAndCopyNextWord(name, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
102 #ifdef _IRR_DEBUG_OBJ_LOADER_
\r
103 os::Printer::log("Reading material file",name);
\r
109 case 'v': // v, vn, vt
\r
112 case ' ': // vertex
\r
114 core::vector3df vec;
\r
115 bufPtr = readVec3(bufPtr, vec, bufEnd);
\r
116 vertexBuffer.push_back(vec);
\r
120 case 'n': // normal
\r
122 core::vector3df vec;
\r
123 bufPtr = readVec3(bufPtr, vec, bufEnd);
\r
124 normalsBuffer.push_back(vec);
\r
128 case 't': // texcoord
\r
130 core::vector2df vec;
\r
131 bufPtr = readUV(bufPtr, vec, bufEnd);
\r
132 textureCoordBuffer.push_back(vec);
\r
138 case 'g': // group name
\r
140 c8 grp[WORD_BUFFER_LENGTH];
\r
141 bufPtr = goAndCopyNextWord(grp, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
142 #ifdef _IRR_DEBUG_OBJ_LOADER_
\r
143 os::Printer::log("Loaded group start",grp, ELL_DEBUG);
\r
150 grpName = "default";
\r
156 case 's': // smoothing can be a group or off (equiv. to 0)
\r
158 c8 smooth[WORD_BUFFER_LENGTH];
\r
159 bufPtr = goAndCopyNextWord(smooth, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
160 #ifdef _IRR_DEBUG_OBJ_LOADER_
\r
161 os::Printer::log("Loaded smoothing group start",smooth, ELL_DEBUG);
\r
163 if (TAG_OFF==smooth)
\r
166 smoothingGroup=core::strtoul10(smooth);
\r
168 (void)smoothingGroup; // disable unused variable warnings
\r
172 case 'u': // usemtl
\r
173 // get name of material
\r
175 c8 matName[WORD_BUFFER_LENGTH];
\r
176 bufPtr = goAndCopyNextWord(matName, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
177 #ifdef _IRR_DEBUG_OBJ_LOADER_
\r
178 os::Printer::log("Loaded material start",matName, ELL_DEBUG);
\r
187 c8 vertexWord[WORD_BUFFER_LENGTH]; // for retrieving vertex data
\r
188 video::S3DVertex v;
\r
189 // Assign vertex color from currently active material's diffuse color
\r
192 // retrieve the material
\r
193 SObjMtl *useMtl = findMtl(mtlName, grpName);
\r
194 // only change material if we found it
\r
200 v.Color = currMtl->Meshbuffer->Material.DiffuseColor;
\r
202 // get all vertices data in this face (current line of obj file)
\r
203 const core::stringc wordBuffer = copyLine(bufPtr, bufEnd);
\r
204 const c8* linePtr = wordBuffer.c_str();
\r
205 const c8* const endPtr = linePtr+wordBuffer.size();
\r
207 faceCorners.set_used(0); // fast clear
\r
209 // read in all vertices
\r
210 linePtr = goNextWord(linePtr, endPtr);
\r
211 while (0 != linePtr[0])
\r
213 // Array to communicate with retrieveVertexIndices()
\r
214 // sends the buffer sizes and gets the actual indices
\r
215 // if index not set returns -1
\r
217 Idx[0] = Idx[1] = Idx[2] = -1;
\r
219 // read in next vertex's data
\r
220 u32 wlength = copyWord(vertexWord, linePtr, WORD_BUFFER_LENGTH, endPtr);
\r
221 // this function will also convert obj's 1-based index to c++'s 0-based index
\r
222 retrieveVertexIndices(vertexWord, Idx, vertexWord+wlength+1, vertexBuffer.size(), textureCoordBuffer.size(), normalsBuffer.size());
\r
223 if ( -1 != Idx[0] && Idx[0] < (irr::s32)vertexBuffer.size() )
\r
224 v.Pos = vertexBuffer[Idx[0]];
\r
227 os::Printer::log("Invalid vertex index in this line:", wordBuffer.c_str(), ELL_ERROR);
\r
231 if ( -1 != Idx[1] && Idx[1] < (irr::s32)textureCoordBuffer.size() )
\r
232 v.TCoords = textureCoordBuffer[Idx[1]];
\r
234 v.TCoords.set(0.0f,0.0f);
\r
235 if ( -1 != Idx[2] && Idx[2] < (irr::s32)normalsBuffer.size() )
\r
236 v.Normal = normalsBuffer[Idx[2]];
\r
239 v.Normal.set(0.0f,0.0f,0.0f);
\r
240 currMtl->RecalculateNormals=true;
\r
244 auto n = currMtl->VertMap.find(v);
\r
245 if (n != currMtl->VertMap.end())
\r
247 vertLocation = n->second;
\r
251 currMtl->Meshbuffer->Vertices.push_back(v);
\r
252 vertLocation = currMtl->Meshbuffer->Vertices.size() -1;
\r
253 currMtl->VertMap.emplace(v, vertLocation);
\r
256 faceCorners.push_back(vertLocation);
\r
258 // go to next vertex
\r
259 linePtr = goNextWord(linePtr, endPtr);
\r
262 // triangulate the face
\r
263 const int c = faceCorners[0];
\r
264 for ( u32 i = 1; i < faceCorners.size() - 1; ++i )
\r
267 const int a = faceCorners[i + 1];
\r
268 const int b = faceCorners[i];
\r
269 if (a != b && a != c && b != c) // ignore degenerated faces. We can get them when we merge vertices above in the VertMap.
\r
271 currMtl->Meshbuffer->Indices.push_back(a);
\r
272 currMtl->Meshbuffer->Indices.push_back(b);
\r
273 currMtl->Meshbuffer->Indices.push_back(c);
\r
277 ++degeneratedFaces;
\r
283 case '#': // comment
\r
286 } // end switch(bufPtr[0])
\r
287 // eat up rest of line
\r
288 bufPtr = goNextLine(bufPtr, bufEnd);
\r
290 } // end while(bufPtr && (bufPtr-buf<filesize))
\r
292 if ( degeneratedFaces > 0 )
\r
294 irr::core::stringc log(degeneratedFaces);
\r
295 log += " degenerated faces removed in ";
\r
296 log += irr::core::stringc(fullName);
\r
297 os::Printer::log(log.c_str(), ELL_INFORMATION);
\r
300 SMesh* mesh = new SMesh();
\r
302 // Combine all the groups (meshbuffers) into the mesh
\r
303 for ( u32 m = 0; m < Materials.size(); ++m )
\r
305 if ( Materials[m]->Meshbuffer->getIndexCount() > 0 )
\r
307 Materials[m]->Meshbuffer->recalculateBoundingBox();
\r
308 if (Materials[m]->RecalculateNormals)
\r
309 SceneManager->getMeshManipulator()->recalculateNormals(Materials[m]->Meshbuffer);
\r
310 mesh->addMeshBuffer( Materials[m]->Meshbuffer );
\r
314 // Create the Animated mesh if there's anything in the mesh
\r
315 SAnimatedMesh* animMesh = 0;
\r
316 if ( 0 != mesh->getMeshBufferCount() )
\r
318 mesh->recalculateBoundingBox();
\r
319 animMesh = new SAnimatedMesh();
\r
320 animMesh->Type = EAMT_OBJ;
\r
321 animMesh->addMesh(mesh);
\r
322 animMesh->recalculateBoundingBox();
\r
325 // Clean up the allocate obj file contents
\r
327 // more cleaning up
\r
335 const c8* COBJMeshFileLoader::readColor(const c8* bufPtr, video::SColor& color, const c8* const bufEnd)
\r
337 const u32 COLOR_BUFFER_LENGTH = 16;
\r
338 c8 colStr[COLOR_BUFFER_LENGTH];
\r
340 bufPtr = goAndCopyNextWord(colStr, bufPtr, COLOR_BUFFER_LENGTH, bufEnd);
\r
341 color.setRed((u32)core::round32(core::fast_atof(colStr)*255.f));
\r
342 bufPtr = goAndCopyNextWord(colStr, bufPtr, COLOR_BUFFER_LENGTH, bufEnd);
\r
343 color.setGreen((u32)core::round32(core::fast_atof(colStr)*255.f));
\r
344 bufPtr = goAndCopyNextWord(colStr, bufPtr, COLOR_BUFFER_LENGTH, bufEnd);
\r
345 color.setBlue((u32)core::round32(core::fast_atof(colStr)*255.f));
\r
350 //! Read 3d vector of floats
\r
351 const c8* COBJMeshFileLoader::readVec3(const c8* bufPtr, core::vector3df& vec, const c8* const bufEnd)
\r
353 const u32 WORD_BUFFER_LENGTH = 256;
\r
354 c8 wordBuffer[WORD_BUFFER_LENGTH];
\r
356 bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
357 vec.X=-core::fast_atof(wordBuffer); // change handedness
\r
358 bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
359 vec.Y=core::fast_atof(wordBuffer);
\r
360 bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
361 vec.Z=core::fast_atof(wordBuffer);
\r
366 //! Read 2d vector of floats
\r
367 const c8* COBJMeshFileLoader::readUV(const c8* bufPtr, core::vector2df& vec, const c8* const bufEnd)
\r
369 const u32 WORD_BUFFER_LENGTH = 256;
\r
370 c8 wordBuffer[WORD_BUFFER_LENGTH];
\r
372 bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
373 vec.X=core::fast_atof(wordBuffer);
\r
374 bufPtr = goAndCopyNextWord(wordBuffer, bufPtr, WORD_BUFFER_LENGTH, bufEnd);
\r
375 vec.Y=1-core::fast_atof(wordBuffer); // change handedness
\r
380 //! Read boolean value represented as 'on' or 'off'
\r
381 const c8* COBJMeshFileLoader::readBool(const c8* bufPtr, bool& tf, const c8* const bufEnd)
\r
383 const u32 BUFFER_LENGTH = 8;
\r
384 c8 tfStr[BUFFER_LENGTH];
\r
386 bufPtr = goAndCopyNextWord(tfStr, bufPtr, BUFFER_LENGTH, bufEnd);
\r
387 tf = strcmp(tfStr, "off") != 0;
\r
392 COBJMeshFileLoader::SObjMtl* COBJMeshFileLoader::findMtl(const core::stringc& mtlName, const core::stringc& grpName)
\r
394 COBJMeshFileLoader::SObjMtl* defMaterial = 0;
\r
395 // search existing Materials for best match
\r
396 // exact match does return immediately, only name match means a new group
\r
397 for (u32 i = 0; i < Materials.size(); ++i)
\r
399 if ( Materials[i]->Name == mtlName )
\r
401 if ( Materials[i]->Group == grpName )
\r
402 return Materials[i];
\r
404 defMaterial = Materials[i];
\r
407 // we found a partial match
\r
410 Materials.push_back(new SObjMtl(*defMaterial));
\r
411 Materials.getLast()->Group = grpName;
\r
412 return Materials.getLast();
\r
414 // we found a new group for a non-existant material
\r
415 else if (grpName.size())
\r
417 Materials.push_back(new SObjMtl(*Materials[0]));
\r
418 Materials.getLast()->Group = grpName;
\r
419 return Materials.getLast();
\r
425 //! skip space characters and stop on first non-space
\r
426 const c8* COBJMeshFileLoader::goFirstWord(const c8* buf, const c8* const bufEnd, bool acrossNewlines)
\r
428 // skip space characters
\r
429 if (acrossNewlines)
\r
430 while((buf != bufEnd) && core::isspace(*buf))
\r
433 while((buf != bufEnd) && core::isspace(*buf) && (*buf != '\n'))
\r
440 //! skip current word and stop at beginning of next one
\r
441 const c8* COBJMeshFileLoader::goNextWord(const c8* buf, const c8* const bufEnd, bool acrossNewlines)
\r
443 // skip current word
\r
444 while(( buf != bufEnd ) && !core::isspace(*buf))
\r
447 return goFirstWord(buf, bufEnd, acrossNewlines);
\r
451 //! Read until line break is reached and stop at the next non-space character
\r
452 const c8* COBJMeshFileLoader::goNextLine(const c8* buf, const c8* const bufEnd)
\r
454 // look for newline characters
\r
455 while(buf != bufEnd)
\r
457 // found it, so leave
\r
458 if (*buf=='\n' || *buf=='\r')
\r
462 return goFirstWord(buf, bufEnd);
\r
466 u32 COBJMeshFileLoader::copyWord(c8* outBuf, const c8* const inBuf, u32 outBufLength, const c8* const bufEnd)
\r
479 if (core::isspace(inBuf[i]) || &(inBuf[i]) == bufEnd)
\r
484 u32 length = core::min_(i, outBufLength-1);
\r
485 for (u32 j=0; j<length; ++j)
\r
486 outBuf[j] = inBuf[j];
\r
488 outBuf[length] = 0;
\r
493 core::stringc COBJMeshFileLoader::copyLine(const c8* inBuf, const c8* bufEnd)
\r
496 return core::stringc();
\r
498 const c8* ptr = inBuf;
\r
501 if (*ptr=='\n' || *ptr=='\r')
\r
505 // we must avoid the +1 in case the array is used up
\r
506 return core::stringc(inBuf, (u32)(ptr-inBuf+((ptr < bufEnd) ? 1 : 0)));
\r
510 const c8* COBJMeshFileLoader::goAndCopyNextWord(c8* outBuf, const c8* inBuf, u32 outBufLength, const c8* bufEnd)
\r
512 inBuf = goNextWord(inBuf, bufEnd, false);
\r
513 copyWord(outBuf, inBuf, outBufLength, bufEnd);
\r
518 bool COBJMeshFileLoader::retrieveVertexIndices(c8* vertexData, s32* idx, const c8* bufEnd, u32 vbsize, u32 vtsize, u32 vnsize)
\r
520 const u32 BUFFER_LENGTH = 16;
\r
521 c8 word[BUFFER_LENGTH];
\r
522 const c8* p = goFirstWord(vertexData, bufEnd);
\r
523 u32 idxType = 0; // 0 = posIdx, 1 = texcoordIdx, 2 = normalIdx
\r
526 while ( p != bufEnd )
\r
528 if ( i >= BUFFER_LENGTH )
\r
532 if ( ( core::isdigit(*p)) || (*p == '-') )
\r
534 // build up the number
\r
537 else if ( *p == '/' || *p == ' ' || *p == '\0' )
\r
539 // number is completed. Convert and store it
\r
541 // if no number was found index will become 0 and later on -1 by decrement
\r
542 idx[idxType] = core::strtol10(word);
\r
543 if (idx[idxType]<0)
\r
548 idx[idxType] += vbsize;
\r
551 idx[idxType] += vtsize;
\r
554 idx[idxType] += vnsize;
\r
565 // go to the next kind of index type
\r
568 if ( ++idxType > 2 )
\r
570 // error checking, shouldn't reach here unless file is wrong
\r
576 // set all missing values to disable (=-1)
\r
577 while (++idxType < 3)
\r
584 // go to the next char
\r
592 void COBJMeshFileLoader::cleanUp()
\r
594 for (u32 i=0; i < Materials.size(); ++i )
\r
596 Materials[i]->Meshbuffer->drop();
\r
597 delete Materials[i];
\r
604 } // end namespace scene
\r
605 } // end namespace irr
\r