1 // Copyright (C) 2009-2012 Gaz Davidson
\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 "IrrCompileConfig.h"
\r
6 #ifdef _IRR_COMPILE_WITH_PLY_LOADER_
\r
8 #include "CPLYMeshFileLoader.h"
\r
9 #include "IMeshManipulator.h"
\r
11 #include "CDynamicMeshBuffer.h"
\r
12 #include "SAnimatedMesh.h"
\r
13 #include "IReadFile.h"
\r
14 #include "fast_atof.h"
\r
22 // input buffer must be at least twice as long as the longest line in the file
\r
23 #define PLY_INPUT_BUFFER_SIZE 51200 // file is loaded in 50k chunks
\r
26 CPLYMeshFileLoader::CPLYMeshFileLoader(scene::ISceneManager* smgr)
\r
27 : SceneManager(smgr), File(0), Buffer(0)
\r
32 CPLYMeshFileLoader::~CPLYMeshFileLoader()
\r
34 // delete the buffer in case we didn't earlier
\r
35 // (we do, but this could be disabled to increase the speed of loading hundreds of meshes)
\r
42 // Destroy the element list if it exists
\r
43 for (u32 i=0; i<ElementList.size(); ++i)
\r
44 delete ElementList[i];
\r
45 ElementList.clear();
\r
49 //! returns true if the file maybe is able to be loaded by this class
\r
50 bool CPLYMeshFileLoader::isALoadableFileExtension(const io::path& filename) const
\r
52 return core::hasFileExtension(filename, "ply");
\r
56 //! creates/loads an animated mesh from the file.
\r
57 IAnimatedMesh* CPLYMeshFileLoader::createMesh(io::IReadFile* file)
\r
65 // attempt to allocate the buffer and fill with data
\r
66 if (!allocateBuffer())
\r
73 // start with empty mesh
\r
74 SAnimatedMesh* animMesh = 0;
\r
77 // Currently only supports ASCII meshes
\r
78 if (strcmp(getNextLine(), "ply"))
\r
80 os::Printer::log("Not a valid PLY file", file->getFileName().c_str(), ELL_ERROR);
\r
84 // cut the next line out
\r
86 // grab the word from this line
\r
87 c8 *word = getNextWord();
\r
90 while (strcmp(word, "comment") == 0)
\r
93 word = getNextWord();
\r
96 bool readingHeader = true;
\r
97 bool continueReading = true;
\r
98 IsBinaryFile = false;
\r
99 IsWrongEndian= false;
\r
103 if (strcmp(word, "format") == 0)
\r
105 word = getNextWord();
\r
107 if (strcmp(word, "binary_little_endian") == 0)
\r
109 IsBinaryFile = true;
\r
110 #ifdef __BIG_ENDIAN__
\r
111 IsWrongEndian = true;
\r
115 else if (strcmp(word, "binary_big_endian") == 0)
\r
117 IsBinaryFile = true;
\r
118 #ifndef __BIG_ENDIAN__
\r
119 IsWrongEndian = true;
\r
122 else if (strcmp(word, "ascii"))
\r
124 // abort if this isn't an ascii or a binary mesh
\r
125 os::Printer::log("Unsupported PLY mesh format", word, ELL_ERROR);
\r
126 continueReading = false;
\r
129 if (continueReading)
\r
131 word = getNextWord();
\r
132 if (strcmp(word, "1.0"))
\r
134 os::Printer::log("Unsupported PLY mesh version", word, ELL_WARNING);
\r
138 else if (strcmp(word, "property") == 0)
\r
140 word = getNextWord();
\r
142 if (!ElementList.size())
\r
144 os::Printer::log("PLY property found before element", word, ELL_WARNING);
\r
149 SPLYElement* el = ElementList[ElementList.size()-1];
\r
151 // fill property struct
\r
153 prop.Type = getPropertyType(word);
\r
154 el->KnownSize += prop.size();
\r
156 if (prop.Type == EPLYPT_LIST)
\r
158 el->IsFixedWidth = false;
\r
160 word = getNextWord();
\r
162 prop.Data.List.CountType = getPropertyType(word);
\r
163 if (IsBinaryFile && prop.Data.List.CountType == EPLYPT_UNKNOWN)
\r
165 os::Printer::log("Cannot read binary PLY file containing data types of unknown length", word, ELL_ERROR);
\r
166 continueReading = false;
\r
170 word = getNextWord();
\r
171 prop.Data.List.ItemType = getPropertyType(word);
\r
172 if (IsBinaryFile && prop.Data.List.ItemType == EPLYPT_UNKNOWN)
\r
174 os::Printer::log("Cannot read binary PLY file containing data types of unknown length", word, ELL_ERROR);
\r
175 continueReading = false;
\r
179 else if (IsBinaryFile && prop.Type == EPLYPT_UNKNOWN)
\r
181 os::Printer::log("Cannot read binary PLY file containing data types of unknown length", word, ELL_ERROR);
\r
182 continueReading = false;
\r
185 prop.Name = getNextWord();
\r
187 // add property to element
\r
188 el->Properties.push_back(prop);
\r
191 else if (strcmp(word, "element") == 0)
\r
193 SPLYElement* el = new SPLYElement;
\r
194 el->Name = getNextWord();
\r
195 el->Count = atoi(getNextWord());
\r
196 el->IsFixedWidth = true;
\r
198 ElementList.push_back(el);
\r
200 if (el->Name == "vertex")
\r
201 vertCount = el->Count;
\r
204 else if (strcmp(word, "end_header") == 0)
\r
206 readingHeader = false;
\r
209 StartPointer = LineEndPointer + 1;
\r
212 else if (strcmp(word, "comment") == 0)
\r
218 os::Printer::log("Unknown item in PLY file", word, ELL_WARNING);
\r
221 if (readingHeader && continueReading)
\r
224 word = getNextWord();
\r
227 while (readingHeader && continueReading);
\r
229 // now to read the actual data from the file
\r
230 if (continueReading)
\r
232 // create a mesh buffer
\r
233 CDynamicMeshBuffer *mb = new CDynamicMeshBuffer(video::EVT_STANDARD, vertCount > 65565 ? video::EIT_32BIT : video::EIT_16BIT);
\r
234 mb->getVertexBuffer().reallocate(vertCount);
\r
235 mb->getIndexBuffer().reallocate(vertCount);
\r
236 mb->setHardwareMappingHint(EHM_STATIC);
\r
238 bool hasNormals=true;
\r
239 // loop through each of the elements
\r
240 for (u32 i=0; i<ElementList.size(); ++i)
\r
242 // do we want this element type?
\r
243 if (ElementList[i]->Name == "vertex")
\r
245 // loop through vertex properties
\r
246 for (u32 j=0; j < ElementList[i]->Count; ++j)
\r
247 hasNormals &= readVertex(*ElementList[i], mb);
\r
249 else if (ElementList[i]->Name == "face")
\r
252 for (u32 j=0; j < ElementList[i]->Count; ++j)
\r
253 readFace(*ElementList[i], mb);
\r
257 // skip these elements
\r
258 for (u32 j=0; j < ElementList[i]->Count; ++j)
\r
259 skipElement(*ElementList[i]);
\r
262 mb->recalculateBoundingBox();
\r
264 SceneManager->getMeshManipulator()->recalculateNormals(mb);
\r
265 SMesh* m = new SMesh();
\r
266 m->addMeshBuffer(mb);
\r
267 m->recalculateBoundingBox();
\r
269 animMesh = new SAnimatedMesh();
\r
270 animMesh->addMesh(m);
\r
271 animMesh->recalculateBoundingBox();
\r
283 // if we managed to create a mesh, return it
\r
288 bool CPLYMeshFileLoader::readVertex(const SPLYElement &Element, scene::CDynamicMeshBuffer* mb)
\r
293 video::S3DVertex vert;
\r
294 vert.Color.set(255,255,255,255);
\r
295 vert.TCoords.X = 0.0f;
\r
296 vert.TCoords.Y = 0.0f;
\r
297 vert.Normal.X = 0.0f;
\r
298 vert.Normal.Y = 1.0f;
\r
299 vert.Normal.Z = 0.0f;
\r
302 for (u32 i=0; i < Element.Properties.size(); ++i)
\r
304 E_PLY_PROPERTY_TYPE t = Element.Properties[i].Type;
\r
306 if (Element.Properties[i].Name == "x")
\r
307 vert.Pos.X = getFloat(t);
\r
308 else if (Element.Properties[i].Name == "y")
\r
309 vert.Pos.Z = getFloat(t);
\r
310 else if (Element.Properties[i].Name == "z")
\r
311 vert.Pos.Y = getFloat(t);
\r
312 else if (Element.Properties[i].Name == "nx")
\r
314 vert.Normal.X = getFloat(t);
\r
317 else if (Element.Properties[i].Name == "ny")
\r
319 vert.Normal.Z = getFloat(t);
\r
322 else if (Element.Properties[i].Name == "nz")
\r
324 vert.Normal.Y = getFloat(t);
\r
327 // there isn't a single convention for the UV, some software like Blender or Assimp uses "st" instead of "uv"
\r
328 else if (Element.Properties[i].Name == "u" || Element.Properties[i].Name == "s")
\r
329 vert.TCoords.X = getFloat(t);
\r
330 else if (Element.Properties[i].Name == "v" || Element.Properties[i].Name == "t")
\r
331 vert.TCoords.Y = getFloat(t);
\r
332 else if (Element.Properties[i].Name == "red")
\r
334 u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
\r
335 vert.Color.setRed(value);
\r
337 else if (Element.Properties[i].Name == "green")
\r
339 u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
\r
340 vert.Color.setGreen(value);
\r
342 else if (Element.Properties[i].Name == "blue")
\r
344 u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
\r
345 vert.Color.setBlue(value);
\r
347 else if (Element.Properties[i].Name == "alpha")
\r
349 u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
\r
350 vert.Color.setAlpha(value);
\r
353 skipProperty(Element.Properties[i]);
\r
356 mb->getVertexBuffer().push_back(vert);
\r
362 bool CPLYMeshFileLoader::readFace(const SPLYElement &Element, scene::CDynamicMeshBuffer* mb)
\r
367 for (u32 i=0; i < Element.Properties.size(); ++i)
\r
369 if ( (Element.Properties[i].Name == "vertex_indices" ||
\r
370 Element.Properties[i].Name == "vertex_index") && Element.Properties[i].Type == EPLYPT_LIST)
\r
373 s32 count = getInt(Element.Properties[i].Data.List.CountType);
\r
374 u32 a = getInt(Element.Properties[i].Data.List.ItemType),
\r
375 b = getInt(Element.Properties[i].Data.List.ItemType),
\r
376 c = getInt(Element.Properties[i].Data.List.ItemType);
\r
379 mb->getIndexBuffer().push_back(a);
\r
380 mb->getIndexBuffer().push_back(c);
\r
381 mb->getIndexBuffer().push_back(b);
\r
383 for (; j < count; ++j)
\r
386 c = getInt(Element.Properties[i].Data.List.ItemType);
\r
387 mb->getIndexBuffer().push_back(a);
\r
388 mb->getIndexBuffer().push_back(c);
\r
389 mb->getIndexBuffer().push_back(b);
\r
392 else if (Element.Properties[i].Name == "intensity")
\r
394 // todo: face intensity
\r
395 skipProperty(Element.Properties[i]);
\r
398 skipProperty(Element.Properties[i]);
\r
404 // skips an element and all properties. return false on EOF
\r
405 void CPLYMeshFileLoader::skipElement(const SPLYElement &Element)
\r
408 if (Element.IsFixedWidth)
\r
409 moveForward(Element.KnownSize);
\r
411 for (u32 i=0; i < Element.Properties.size(); ++i)
\r
412 skipProperty(Element.Properties[i]);
\r
418 void CPLYMeshFileLoader::skipProperty(const SPLYProperty &Property)
\r
420 if (Property.Type == EPLYPT_LIST)
\r
422 s32 count = getInt(Property.Data.List.CountType);
\r
424 for (s32 i=0; i < count; ++i)
\r
425 getInt(Property.Data.List.CountType);
\r
430 moveForward(Property.size());
\r
437 bool CPLYMeshFileLoader::allocateBuffer()
\r
439 // Destroy the element list if it exists
\r
440 for (u32 i=0; i<ElementList.size(); ++i)
\r
441 delete ElementList[i];
\r
442 ElementList.clear();
\r
445 Buffer = new c8[PLY_INPUT_BUFFER_SIZE];
\r
447 // not enough memory?
\r
452 memset(Buffer, 0, PLY_INPUT_BUFFER_SIZE);
\r
454 StartPointer = Buffer;
\r
455 EndPointer = Buffer;
\r
456 LineEndPointer = Buffer-1;
\r
460 // get data from the file
\r
467 // gets more data from the file. returns false on EOF
\r
468 void CPLYMeshFileLoader::fillBuffer()
\r
473 size_t length = (size_t)(EndPointer - StartPointer);
\r
474 if (length && StartPointer != Buffer)
\r
476 // copy the remaining data to the start of the buffer
\r
477 memcpy(Buffer, StartPointer, length);
\r
479 // reset start position
\r
480 StartPointer = Buffer;
\r
481 EndPointer = StartPointer + length;
\r
483 if (File->getPos() == File->getSize())
\r
489 // read data from the file
\r
490 size_t count = File->read(EndPointer, PLY_INPUT_BUFFER_SIZE - length);
\r
492 // increment the end pointer by the number of bytes read
\r
493 EndPointer = EndPointer + count;
\r
495 // if we didn't completely fill the buffer
\r
496 if (count != PLY_INPUT_BUFFER_SIZE - length)
\r
498 // blank the rest of the memory
\r
499 memset(EndPointer, 0, Buffer + PLY_INPUT_BUFFER_SIZE - EndPointer);
\r
508 // skips x bytes in the file, getting more data if required
\r
509 void CPLYMeshFileLoader::moveForward(u32 bytes)
\r
511 if (StartPointer + bytes >= EndPointer)
\r
513 if (StartPointer + bytes < EndPointer)
\r
514 StartPointer += bytes;
\r
516 StartPointer = EndPointer;
\r
520 E_PLY_PROPERTY_TYPE CPLYMeshFileLoader::getPropertyType(const c8* typeString) const
\r
522 if (strcmp(typeString, "char") == 0 ||
\r
523 strcmp(typeString, "uchar") == 0 ||
\r
524 strcmp(typeString, "int8") == 0 ||
\r
525 strcmp(typeString, "uint8") == 0)
\r
527 return EPLYPT_INT8;
\r
529 else if (strcmp(typeString, "uint") == 0 ||
\r
530 strcmp(typeString, "int16") == 0 ||
\r
531 strcmp(typeString, "uint16") == 0 ||
\r
532 strcmp(typeString, "short") == 0 ||
\r
533 strcmp(typeString, "ushort") == 0)
\r
535 return EPLYPT_INT16;
\r
537 else if (strcmp(typeString, "int") == 0 ||
\r
538 strcmp(typeString, "long") == 0 ||
\r
539 strcmp(typeString, "ulong") == 0 ||
\r
540 strcmp(typeString, "int32") == 0 ||
\r
541 strcmp(typeString, "uint32") == 0)
\r
543 return EPLYPT_INT32;
\r
545 else if (strcmp(typeString, "float") == 0 ||
\r
546 strcmp(typeString, "float32") == 0)
\r
548 return EPLYPT_FLOAT32;
\r
550 else if (strcmp(typeString, "float64") == 0 ||
\r
551 strcmp(typeString, "double") == 0)
\r
553 return EPLYPT_FLOAT64;
\r
555 else if ( strcmp(typeString, "list") == 0 )
\r
557 return EPLYPT_LIST;
\r
561 // unsupported type.
\r
562 // cannot be loaded in binary mode
\r
563 return EPLYPT_UNKNOWN;
\r
568 // Split the string data into a line in place by terminating it instead of copying.
\r
569 c8* CPLYMeshFileLoader::getNextLine()
\r
571 // move the start pointer along
\r
572 StartPointer = LineEndPointer + 1;
\r
574 // crlf split across buffer move
\r
575 if (*StartPointer == '\n')
\r
577 *StartPointer = '\0';
\r
581 // begin at the start of the next line
\r
582 c8* pos = StartPointer;
\r
583 while (pos < EndPointer && *pos && *pos != '\r' && *pos != '\n')
\r
586 if ( pos < EndPointer && ( *(pos+1) == '\r' || *(pos+1) == '\n') )
\r
592 // we have reached the end of the buffer
\r
593 if (pos >= EndPointer)
\r
595 // get data from the file
\r
599 // reset line end pointer
\r
600 LineEndPointer = StartPointer - 1;
\r
602 if (StartPointer != EndPointer)
\r
603 return getNextLine();
\r
610 StartPointer = EndPointer-1;
\r
611 *StartPointer = '\0';
\r
612 return StartPointer;
\r
617 // null terminate the string in place
\r
619 LineEndPointer = pos;
\r
621 // return pointer to the start of the line
\r
622 return StartPointer;
\r
627 // null terminate the next word on the previous line and move the next word pointer along
\r
628 // since we already have a full line in the buffer, we never need to retrieve more data
\r
629 c8* CPLYMeshFileLoader::getNextWord()
\r
631 // move the start pointer along
\r
632 StartPointer += WordLength + 1;
\r
634 if (StartPointer == LineEndPointer)
\r
636 WordLength = -1; //
\r
637 return LineEndPointer;
\r
639 // begin at the start of the next word
\r
640 c8* pos = StartPointer;
\r
641 while (*pos && pos < LineEndPointer && pos < EndPointer && *pos != ' ' && *pos != '\t')
\r
644 while(*pos && pos < LineEndPointer && pos < EndPointer && (*pos == ' ' || *pos == '\t') )
\r
646 // null terminate the string in place
\r
651 WordLength = (s32)(pos-StartPointer);
\r
652 // return pointer to the start of the word
\r
653 return StartPointer;
\r
657 // read the next float from the file and move the start pointer along
\r
658 f32 CPLYMeshFileLoader::getFloat(E_PLY_PROPERTY_TYPE t)
\r
664 if (EndPointer - StartPointer < 8)
\r
667 if (EndPointer - StartPointer > 0)
\r
672 retVal = *StartPointer;
\r
677 retVal = os::Byteswap::byteswap(*(reinterpret_cast<s16*>(StartPointer)));
\r
679 retVal = *(reinterpret_cast<s16*>(StartPointer));
\r
684 retVal = f32(os::Byteswap::byteswap(*(reinterpret_cast<s32*>(StartPointer))));
\r
686 retVal = f32(*(reinterpret_cast<s32*>(StartPointer)));
\r
689 case EPLYPT_FLOAT32:
\r
691 retVal = os::Byteswap::byteswap(*(reinterpret_cast<f32*>(StartPointer)));
\r
693 retVal = *(reinterpret_cast<f32*>(StartPointer));
\r
696 case EPLYPT_FLOAT64:
\r
697 // todo: byteswap 64-bit
\r
698 retVal = f32(*(reinterpret_cast<f64*>(StartPointer)));
\r
702 case EPLYPT_UNKNOWN:
\r
705 StartPointer++; // ouch!
\r
713 c8* word = getNextWord();
\r
719 retVal = f32(atoi(word));
\r
721 case EPLYPT_FLOAT32:
\r
722 case EPLYPT_FLOAT64:
\r
723 retVal = f32(atof(word));
\r
726 case EPLYPT_UNKNOWN:
\r
735 // read the next int from the file and move the start pointer along
\r
736 u32 CPLYMeshFileLoader::getInt(E_PLY_PROPERTY_TYPE t)
\r
742 if (!EndOfFile && EndPointer - StartPointer < 8)
\r
745 if (EndPointer - StartPointer)
\r
750 retVal = *StartPointer;
\r
755 retVal = os::Byteswap::byteswap(*(reinterpret_cast<u16*>(StartPointer)));
\r
757 retVal = *(reinterpret_cast<u16*>(StartPointer));
\r
762 retVal = os::Byteswap::byteswap(*(reinterpret_cast<s32*>(StartPointer)));
\r
764 retVal = *(reinterpret_cast<s32*>(StartPointer));
\r
767 case EPLYPT_FLOAT32:
\r
769 retVal = (u32)os::Byteswap::byteswap(*(reinterpret_cast<f32*>(StartPointer)));
\r
771 retVal = (u32)(*(reinterpret_cast<f32*>(StartPointer)));
\r
774 case EPLYPT_FLOAT64:
\r
775 // todo: byteswap 64-bit
\r
776 retVal = (u32)(*(reinterpret_cast<f64*>(StartPointer)));
\r
780 case EPLYPT_UNKNOWN:
\r
783 StartPointer++; // ouch!
\r
791 c8* word = getNextWord();
\r
797 retVal = atoi(word);
\r
799 case EPLYPT_FLOAT32:
\r
800 case EPLYPT_FLOAT64:
\r
801 retVal = u32(atof(word));
\r
804 case EPLYPT_UNKNOWN:
\r
814 } // end namespace scene
\r
815 } // end namespace irr
\r
817 #endif // _IRR_COMPILE_WITH_PLY_LOADER_
\r