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 > 65536 ? 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
305 const core::stringc& name = Element.Properties[i].Name;
\r
308 vert.Pos.X = getFloat(t);
\r
309 else if (name == "y")
\r
310 vert.Pos.Z = getFloat(t);
\r
311 else if (name == "z")
\r
312 vert.Pos.Y = getFloat(t);
\r
313 else if (name == "nx")
\r
315 vert.Normal.X = getFloat(t);
\r
318 else if (name == "ny")
\r
320 vert.Normal.Z = getFloat(t);
\r
323 else if (name == "nz")
\r
325 vert.Normal.Y = getFloat(t);
\r
328 // There isn't a single convention for the UV, some software like Blender or Assimp uses "st" instead of "uv"
\r
329 // Not sure which tool creates texture_u/texture_v, but those exist as well.
\r
330 else if (name == "u" || name == "s" || name == "texture_u")
\r
331 vert.TCoords.X = getFloat(t);
\r
332 else if (name == "v" || name == "t" || name == "texture_v")
\r
333 vert.TCoords.Y = getFloat(t);
\r
334 else if (name == "red")
\r
336 u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
\r
337 vert.Color.setRed(value);
\r
339 else if (name == "green")
\r
341 u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
\r
342 vert.Color.setGreen(value);
\r
344 else if (name == "blue")
\r
346 u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
\r
347 vert.Color.setBlue(value);
\r
349 else if (name == "alpha")
\r
351 u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
\r
352 vert.Color.setAlpha(value);
\r
355 skipProperty(Element.Properties[i]);
\r
358 mb->getVertexBuffer().push_back(vert);
\r
364 bool CPLYMeshFileLoader::readFace(const SPLYElement &Element, scene::CDynamicMeshBuffer* mb)
\r
369 for (u32 i=0; i < Element.Properties.size(); ++i)
\r
371 const SPLYProperty& property = Element.Properties[i];
\r
372 if ( (property.Name == "vertex_indices" || property.Name == "vertex_index")
\r
373 && property.Type == EPLYPT_LIST)
\r
376 s32 count = getInt(property.Data.List.CountType);
\r
377 u32 a = getInt(property.Data.List.ItemType),
\r
378 b = getInt(property.Data.List.ItemType),
\r
379 c = getInt(property.Data.List.ItemType);
\r
382 mb->getIndexBuffer().push_back(a);
\r
383 mb->getIndexBuffer().push_back(c);
\r
384 mb->getIndexBuffer().push_back(b);
\r
386 for (; j < count; ++j)
\r
389 c = getInt(property.Data.List.ItemType);
\r
390 mb->getIndexBuffer().push_back(a);
\r
391 mb->getIndexBuffer().push_back(c);
\r
392 mb->getIndexBuffer().push_back(b);
\r
395 else if (property.Name == "intensity")
\r
397 // todo: face intensity
\r
398 skipProperty(property);
\r
401 skipProperty(property);
\r
407 // skips an element and all properties. return false on EOF
\r
408 void CPLYMeshFileLoader::skipElement(const SPLYElement &Element)
\r
411 if (Element.IsFixedWidth)
\r
412 moveForward(Element.KnownSize);
\r
414 for (u32 i=0; i < Element.Properties.size(); ++i)
\r
415 skipProperty(Element.Properties[i]);
\r
421 void CPLYMeshFileLoader::skipProperty(const SPLYProperty &Property)
\r
423 if (Property.Type == EPLYPT_LIST)
\r
425 s32 count = getInt(Property.Data.List.CountType);
\r
427 for (s32 i=0; i < count; ++i)
\r
428 getInt(Property.Data.List.CountType);
\r
433 moveForward(Property.size());
\r
440 bool CPLYMeshFileLoader::allocateBuffer()
\r
442 // Destroy the element list if it exists
\r
443 for (u32 i=0; i<ElementList.size(); ++i)
\r
444 delete ElementList[i];
\r
445 ElementList.clear();
\r
448 Buffer = new c8[PLY_INPUT_BUFFER_SIZE];
\r
450 // not enough memory?
\r
455 memset(Buffer, 0, PLY_INPUT_BUFFER_SIZE);
\r
457 StartPointer = Buffer;
\r
458 EndPointer = Buffer;
\r
459 LineEndPointer = Buffer-1;
\r
463 // get data from the file
\r
470 // gets more data from the file. returns false on EOF
\r
471 void CPLYMeshFileLoader::fillBuffer()
\r
476 size_t length = (size_t)(EndPointer - StartPointer);
\r
477 if (length && StartPointer != Buffer)
\r
479 // copy the remaining data to the start of the buffer
\r
480 memcpy(Buffer, StartPointer, length);
\r
482 // reset start position
\r
483 StartPointer = Buffer;
\r
484 EndPointer = StartPointer + length;
\r
486 if (File->getPos() == File->getSize())
\r
492 // read data from the file
\r
493 size_t count = File->read(EndPointer, PLY_INPUT_BUFFER_SIZE - length);
\r
495 // increment the end pointer by the number of bytes read
\r
496 EndPointer = EndPointer + count;
\r
498 // if we didn't completely fill the buffer
\r
499 if (count != PLY_INPUT_BUFFER_SIZE - length)
\r
501 // blank the rest of the memory
\r
502 memset(EndPointer, 0, Buffer + PLY_INPUT_BUFFER_SIZE - EndPointer);
\r
511 // skips x bytes in the file, getting more data if required
\r
512 void CPLYMeshFileLoader::moveForward(u32 bytes)
\r
514 if (StartPointer + bytes >= EndPointer)
\r
516 if (StartPointer + bytes < EndPointer)
\r
517 StartPointer += bytes;
\r
519 StartPointer = EndPointer;
\r
523 E_PLY_PROPERTY_TYPE CPLYMeshFileLoader::getPropertyType(const c8* typeString) const
\r
525 if (strcmp(typeString, "char") == 0 ||
\r
526 strcmp(typeString, "uchar") == 0 ||
\r
527 strcmp(typeString, "int8") == 0 ||
\r
528 strcmp(typeString, "uint8") == 0)
\r
530 return EPLYPT_INT8;
\r
532 else if (strcmp(typeString, "uint") == 0 ||
\r
533 strcmp(typeString, "int16") == 0 ||
\r
534 strcmp(typeString, "uint16") == 0 ||
\r
535 strcmp(typeString, "short") == 0 ||
\r
536 strcmp(typeString, "ushort") == 0)
\r
538 return EPLYPT_INT16;
\r
540 else if (strcmp(typeString, "int") == 0 ||
\r
541 strcmp(typeString, "long") == 0 ||
\r
542 strcmp(typeString, "ulong") == 0 ||
\r
543 strcmp(typeString, "int32") == 0 ||
\r
544 strcmp(typeString, "uint32") == 0)
\r
546 return EPLYPT_INT32;
\r
548 else if (strcmp(typeString, "float") == 0 ||
\r
549 strcmp(typeString, "float32") == 0)
\r
551 return EPLYPT_FLOAT32;
\r
553 else if (strcmp(typeString, "float64") == 0 ||
\r
554 strcmp(typeString, "double") == 0)
\r
556 return EPLYPT_FLOAT64;
\r
558 else if ( strcmp(typeString, "list") == 0 )
\r
560 return EPLYPT_LIST;
\r
564 // unsupported type.
\r
565 // cannot be loaded in binary mode
\r
566 return EPLYPT_UNKNOWN;
\r
571 // Split the string data into a line in place by terminating it instead of copying.
\r
572 c8* CPLYMeshFileLoader::getNextLine()
\r
574 // move the start pointer along
\r
575 StartPointer = LineEndPointer + 1;
\r
577 // crlf split across buffer move
\r
578 if (StartPointer<EndPointer && *StartPointer == '\n')
\r
580 *StartPointer = '\0';
\r
584 // begin at the start of the next line
\r
585 c8* pos = StartPointer;
\r
586 while (pos < EndPointer && *pos && *pos != '\r' && *pos != '\n')
\r
589 if ( (pos+1) < EndPointer && ( *(pos+1) == '\r' || *(pos+1) == '\n') )
\r
595 // we have reached the end of the buffer
\r
596 if (pos >= EndPointer)
\r
598 // get data from the file
\r
602 // reset line end pointer
\r
603 LineEndPointer = StartPointer - 1;
\r
605 if (StartPointer != EndPointer)
\r
606 return getNextLine();
\r
613 StartPointer = EndPointer-1;
\r
614 *StartPointer = '\0';
\r
615 return StartPointer;
\r
620 // null terminate the string in place
\r
622 LineEndPointer = pos;
\r
624 // return pointer to the start of the line
\r
625 return StartPointer;
\r
630 // null terminate the next word on the previous line and move the next word pointer along
\r
631 // since we already have a full line in the buffer, we never need to retrieve more data
\r
632 c8* CPLYMeshFileLoader::getNextWord()
\r
634 // move the start pointer along
\r
635 StartPointer += WordLength + 1;
\r
637 if (StartPointer == LineEndPointer)
\r
639 WordLength = -1; //
\r
640 return LineEndPointer;
\r
642 // begin at the start of the next word
\r
643 c8* pos = StartPointer;
\r
644 while (*pos && pos < LineEndPointer && pos < EndPointer && *pos != ' ' && *pos != '\t')
\r
647 while(*pos && pos < LineEndPointer && pos < EndPointer && (*pos == ' ' || *pos == '\t') )
\r
649 // null terminate the string in place
\r
654 WordLength = (s32)(pos-StartPointer);
\r
655 // return pointer to the start of the word
\r
656 return StartPointer;
\r
660 // read the next float from the file and move the start pointer along
\r
661 f32 CPLYMeshFileLoader::getFloat(E_PLY_PROPERTY_TYPE t)
\r
667 if (EndPointer - StartPointer < 8)
\r
670 if (EndPointer - StartPointer > 0)
\r
675 retVal = *StartPointer;
\r
680 retVal = os::Byteswap::byteswap(*(reinterpret_cast<s16*>(StartPointer)));
\r
682 retVal = *(reinterpret_cast<s16*>(StartPointer));
\r
687 retVal = f32(os::Byteswap::byteswap(*(reinterpret_cast<s32*>(StartPointer))));
\r
689 retVal = f32(*(reinterpret_cast<s32*>(StartPointer)));
\r
692 case EPLYPT_FLOAT32:
\r
694 retVal = os::Byteswap::byteswap(*(reinterpret_cast<f32*>(StartPointer)));
\r
696 retVal = *(reinterpret_cast<f32*>(StartPointer));
\r
699 case EPLYPT_FLOAT64:
\r
700 // todo: byteswap 64-bit
\r
701 retVal = f32(*(reinterpret_cast<f64*>(StartPointer)));
\r
705 case EPLYPT_UNKNOWN:
\r
708 StartPointer++; // ouch!
\r
716 c8* word = getNextWord();
\r
722 retVal = f32(atoi(word));
\r
724 case EPLYPT_FLOAT32:
\r
725 case EPLYPT_FLOAT64:
\r
726 retVal = f32(atof(word));
\r
729 case EPLYPT_UNKNOWN:
\r
738 // read the next int from the file and move the start pointer along
\r
739 u32 CPLYMeshFileLoader::getInt(E_PLY_PROPERTY_TYPE t)
\r
745 if (!EndOfFile && EndPointer - StartPointer < 8)
\r
748 if (EndPointer - StartPointer)
\r
753 retVal = *StartPointer;
\r
758 retVal = os::Byteswap::byteswap(*(reinterpret_cast<u16*>(StartPointer)));
\r
760 retVal = *(reinterpret_cast<u16*>(StartPointer));
\r
765 retVal = os::Byteswap::byteswap(*(reinterpret_cast<s32*>(StartPointer)));
\r
767 retVal = *(reinterpret_cast<s32*>(StartPointer));
\r
770 case EPLYPT_FLOAT32:
\r
772 retVal = (u32)os::Byteswap::byteswap(*(reinterpret_cast<f32*>(StartPointer)));
\r
774 retVal = (u32)(*(reinterpret_cast<f32*>(StartPointer)));
\r
777 case EPLYPT_FLOAT64:
\r
778 // todo: byteswap 64-bit
\r
779 retVal = (u32)(*(reinterpret_cast<f64*>(StartPointer)));
\r
783 case EPLYPT_UNKNOWN:
\r
786 StartPointer++; // ouch!
\r
794 c8* word = getNextWord();
\r
800 retVal = atoi(word);
\r
802 case EPLYPT_FLOAT32:
\r
803 case EPLYPT_FLOAT64:
\r
804 retVal = u32(atof(word));
\r
807 case EPLYPT_UNKNOWN:
\r
817 } // end namespace scene
\r
818 } // end namespace irr
\r
820 #endif // _IRR_COMPILE_WITH_PLY_LOADER_
\r