// Copyright (C) 2009-2012 Gaz Davidson // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "IrrCompileConfig.h" #ifdef _IRR_COMPILE_WITH_PLY_LOADER_ #include "CPLYMeshFileLoader.h" #include "IMeshManipulator.h" #include "SMesh.h" #include "CDynamicMeshBuffer.h" #include "SAnimatedMesh.h" #include "IReadFile.h" #include "fast_atof.h" #include "os.h" namespace irr { namespace scene { // input buffer must be at least twice as long as the longest line in the file #define PLY_INPUT_BUFFER_SIZE 51200 // file is loaded in 50k chunks // constructor CPLYMeshFileLoader::CPLYMeshFileLoader(scene::ISceneManager* smgr) : SceneManager(smgr), File(0), Buffer(0) { } CPLYMeshFileLoader::~CPLYMeshFileLoader() { // delete the buffer in case we didn't earlier // (we do, but this could be disabled to increase the speed of loading hundreds of meshes) if (Buffer) { delete [] Buffer; Buffer = 0; } // Destroy the element list if it exists for (u32 i=0; igrab(); // attempt to allocate the buffer and fill with data if (!allocateBuffer()) { File->drop(); File = 0; return 0; } // start with empty mesh SAnimatedMesh* animMesh = 0; u32 vertCount=0; // Currently only supports ASCII meshes if (strcmp(getNextLine(), "ply")) { os::Printer::log("Not a valid PLY file", file->getFileName().c_str(), ELL_ERROR); } else { // cut the next line out getNextLine(); // grab the word from this line c8 *word = getNextWord(); // ignore comments while (strcmp(word, "comment") == 0) { getNextLine(); word = getNextWord(); } bool readingHeader = true; bool continueReading = true; IsBinaryFile = false; IsWrongEndian= false; do { if (strcmp(word, "format") == 0) { word = getNextWord(); if (strcmp(word, "binary_little_endian") == 0) { IsBinaryFile = true; #ifdef __BIG_ENDIAN__ IsWrongEndian = true; #endif } else if (strcmp(word, "binary_big_endian") == 0) { IsBinaryFile = true; #ifndef __BIG_ENDIAN__ IsWrongEndian = true; #endif } else if (strcmp(word, "ascii")) { // abort if this isn't an ascii or a binary mesh os::Printer::log("Unsupported PLY mesh format", word, ELL_ERROR); continueReading = false; } if (continueReading) { word = getNextWord(); if (strcmp(word, "1.0")) { os::Printer::log("Unsupported PLY mesh version", word, ELL_WARNING); } } } else if (strcmp(word, "property") == 0) { word = getNextWord(); if (!ElementList.size()) { os::Printer::log("PLY property found before element", word, ELL_WARNING); } else { // get element SPLYElement* el = ElementList[ElementList.size()-1]; // fill property struct SPLYProperty prop; prop.Type = getPropertyType(word); el->KnownSize += prop.size(); if (prop.Type == EPLYPT_LIST) { el->IsFixedWidth = false; word = getNextWord(); prop.Data.List.CountType = getPropertyType(word); if (IsBinaryFile && prop.Data.List.CountType == EPLYPT_UNKNOWN) { os::Printer::log("Cannot read binary PLY file containing data types of unknown length", word, ELL_ERROR); continueReading = false; } else { word = getNextWord(); prop.Data.List.ItemType = getPropertyType(word); if (IsBinaryFile && prop.Data.List.ItemType == EPLYPT_UNKNOWN) { os::Printer::log("Cannot read binary PLY file containing data types of unknown length", word, ELL_ERROR); continueReading = false; } } } else if (IsBinaryFile && prop.Type == EPLYPT_UNKNOWN) { os::Printer::log("Cannot read binary PLY file containing data types of unknown length", word, ELL_ERROR); continueReading = false; } prop.Name = getNextWord(); // add property to element el->Properties.push_back(prop); } } else if (strcmp(word, "element") == 0) { SPLYElement* el = new SPLYElement; el->Name = getNextWord(); el->Count = atoi(getNextWord()); el->IsFixedWidth = true; el->KnownSize = 0; ElementList.push_back(el); if (el->Name == "vertex") vertCount = el->Count; } else if (strcmp(word, "end_header") == 0) { readingHeader = false; if (IsBinaryFile) { StartPointer = LineEndPointer + 1; } } else if (strcmp(word, "comment") == 0) { // ignore line } else { os::Printer::log("Unknown item in PLY file", word, ELL_WARNING); } if (readingHeader && continueReading) { getNextLine(); word = getNextWord(); } } while (readingHeader && continueReading); // now to read the actual data from the file if (continueReading) { // create a mesh buffer CDynamicMeshBuffer *mb = new CDynamicMeshBuffer(video::EVT_STANDARD, vertCount > 65536 ? video::EIT_32BIT : video::EIT_16BIT); mb->getVertexBuffer().reallocate(vertCount); mb->getIndexBuffer().reallocate(vertCount); mb->setHardwareMappingHint(EHM_STATIC); bool hasNormals=true; // loop through each of the elements for (u32 i=0; iName == "vertex") { // loop through vertex properties for (u32 j=0; j < ElementList[i]->Count; ++j) hasNormals &= readVertex(*ElementList[i], mb); } else if (ElementList[i]->Name == "face") { // read faces for (u32 j=0; j < ElementList[i]->Count; ++j) readFace(*ElementList[i], mb); } else { // skip these elements for (u32 j=0; j < ElementList[i]->Count; ++j) skipElement(*ElementList[i]); } } mb->recalculateBoundingBox(); if (!hasNormals) SceneManager->getMeshManipulator()->recalculateNormals(mb); SMesh* m = new SMesh(); m->addMeshBuffer(mb); m->recalculateBoundingBox(); mb->drop(); animMesh = new SAnimatedMesh(); animMesh->addMesh(m); animMesh->recalculateBoundingBox(); m->drop(); } } // free the buffer delete [] Buffer; Buffer = 0; File->drop(); File = 0; // if we managed to create a mesh, return it return animMesh; } bool CPLYMeshFileLoader::readVertex(const SPLYElement &Element, scene::CDynamicMeshBuffer* mb) { if (!IsBinaryFile) getNextLine(); video::S3DVertex vert; vert.Color.set(255,255,255,255); vert.TCoords.X = 0.0f; vert.TCoords.Y = 0.0f; vert.Normal.X = 0.0f; vert.Normal.Y = 1.0f; vert.Normal.Z = 0.0f; bool result=false; for (u32 i=0; i < Element.Properties.size(); ++i) { E_PLY_PROPERTY_TYPE t = Element.Properties[i].Type; const core::stringc& name = Element.Properties[i].Name; if (name == "x") vert.Pos.X = getFloat(t); else if (name == "y") vert.Pos.Z = getFloat(t); else if (name == "z") vert.Pos.Y = getFloat(t); else if (name == "nx") { vert.Normal.X = getFloat(t); result=true; } else if (name == "ny") { vert.Normal.Z = getFloat(t); result=true; } else if (name == "nz") { vert.Normal.Y = getFloat(t); result=true; } // There isn't a single convention for the UV, some software like Blender or Assimp uses "st" instead of "uv" // Not sure which tool creates texture_u/texture_v, but those exist as well. else if (name == "u" || name == "s" || name == "texture_u") vert.TCoords.X = getFloat(t); else if (name == "v" || name == "t" || name == "texture_v") vert.TCoords.Y = getFloat(t); else if (name == "red") { u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t); vert.Color.setRed(value); } else if (name == "green") { u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t); vert.Color.setGreen(value); } else if (name == "blue") { u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t); vert.Color.setBlue(value); } else if (name == "alpha") { u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t); vert.Color.setAlpha(value); } else skipProperty(Element.Properties[i]); } mb->getVertexBuffer().push_back(vert); return result; } bool CPLYMeshFileLoader::readFace(const SPLYElement &Element, scene::CDynamicMeshBuffer* mb) { if (!IsBinaryFile) getNextLine(); for (u32 i=0; i < Element.Properties.size(); ++i) { if ( (Element.Properties[i].Name == "vertex_indices" || Element.Properties[i].Name == "vertex_index") && Element.Properties[i].Type == EPLYPT_LIST) { // get count s32 count = getInt(Element.Properties[i].Data.List.CountType); u32 a = getInt(Element.Properties[i].Data.List.ItemType), b = getInt(Element.Properties[i].Data.List.ItemType), c = getInt(Element.Properties[i].Data.List.ItemType); s32 j = 3; mb->getIndexBuffer().push_back(a); mb->getIndexBuffer().push_back(c); mb->getIndexBuffer().push_back(b); for (; j < count; ++j) { b = c; c = getInt(Element.Properties[i].Data.List.ItemType); mb->getIndexBuffer().push_back(a); mb->getIndexBuffer().push_back(c); mb->getIndexBuffer().push_back(b); } } else if (Element.Properties[i].Name == "intensity") { // todo: face intensity skipProperty(Element.Properties[i]); } else skipProperty(Element.Properties[i]); } return true; } // skips an element and all properties. return false on EOF void CPLYMeshFileLoader::skipElement(const SPLYElement &Element) { if (IsBinaryFile) if (Element.IsFixedWidth) moveForward(Element.KnownSize); else for (u32 i=0; i < Element.Properties.size(); ++i) skipProperty(Element.Properties[i]); else getNextLine(); } void CPLYMeshFileLoader::skipProperty(const SPLYProperty &Property) { if (Property.Type == EPLYPT_LIST) { s32 count = getInt(Property.Data.List.CountType); for (s32 i=0; i < count; ++i) getInt(Property.Data.List.CountType); } else { if (IsBinaryFile) moveForward(Property.size()); else getNextWord(); } } bool CPLYMeshFileLoader::allocateBuffer() { // Destroy the element list if it exists for (u32 i=0; igetPos() == File->getSize()) { EndOfFile = true; } else { // read data from the file size_t count = File->read(EndPointer, PLY_INPUT_BUFFER_SIZE - length); // increment the end pointer by the number of bytes read EndPointer = EndPointer + count; // if we didn't completely fill the buffer if (count != PLY_INPUT_BUFFER_SIZE - length) { // blank the rest of the memory memset(EndPointer, 0, Buffer + PLY_INPUT_BUFFER_SIZE - EndPointer); // end of file EndOfFile = true; } } } // skips x bytes in the file, getting more data if required void CPLYMeshFileLoader::moveForward(u32 bytes) { if (StartPointer + bytes >= EndPointer) fillBuffer(); if (StartPointer + bytes < EndPointer) StartPointer += bytes; else StartPointer = EndPointer; } E_PLY_PROPERTY_TYPE CPLYMeshFileLoader::getPropertyType(const c8* typeString) const { if (strcmp(typeString, "char") == 0 || strcmp(typeString, "uchar") == 0 || strcmp(typeString, "int8") == 0 || strcmp(typeString, "uint8") == 0) { return EPLYPT_INT8; } else if (strcmp(typeString, "uint") == 0 || strcmp(typeString, "int16") == 0 || strcmp(typeString, "uint16") == 0 || strcmp(typeString, "short") == 0 || strcmp(typeString, "ushort") == 0) { return EPLYPT_INT16; } else if (strcmp(typeString, "int") == 0 || strcmp(typeString, "long") == 0 || strcmp(typeString, "ulong") == 0 || strcmp(typeString, "int32") == 0 || strcmp(typeString, "uint32") == 0) { return EPLYPT_INT32; } else if (strcmp(typeString, "float") == 0 || strcmp(typeString, "float32") == 0) { return EPLYPT_FLOAT32; } else if (strcmp(typeString, "float64") == 0 || strcmp(typeString, "double") == 0) { return EPLYPT_FLOAT64; } else if ( strcmp(typeString, "list") == 0 ) { return EPLYPT_LIST; } else { // unsupported type. // cannot be loaded in binary mode return EPLYPT_UNKNOWN; } } // Split the string data into a line in place by terminating it instead of copying. c8* CPLYMeshFileLoader::getNextLine() { // move the start pointer along StartPointer = LineEndPointer + 1; // crlf split across buffer move if (StartPointer= EndPointer) { // get data from the file if (!EndOfFile) { fillBuffer(); // reset line end pointer LineEndPointer = StartPointer - 1; if (StartPointer != EndPointer) return getNextLine(); else return Buffer; } else { // EOF StartPointer = EndPointer-1; *StartPointer = '\0'; return StartPointer; } } else { // null terminate the string in place *pos = '\0'; LineEndPointer = pos; WordLength = -1; // return pointer to the start of the line return StartPointer; } } // null terminate the next word on the previous line and move the next word pointer along // since we already have a full line in the buffer, we never need to retrieve more data c8* CPLYMeshFileLoader::getNextWord() { // move the start pointer along StartPointer += WordLength + 1; if (StartPointer == LineEndPointer) { WordLength = -1; // return LineEndPointer; } // begin at the start of the next word c8* pos = StartPointer; while (*pos && pos < LineEndPointer && pos < EndPointer && *pos != ' ' && *pos != '\t') ++pos; while(*pos && pos < LineEndPointer && pos < EndPointer && (*pos == ' ' || *pos == '\t') ) { // null terminate the string in place *pos = '\0'; ++pos; } --pos; WordLength = (s32)(pos-StartPointer); // return pointer to the start of the word return StartPointer; } // read the next float from the file and move the start pointer along f32 CPLYMeshFileLoader::getFloat(E_PLY_PROPERTY_TYPE t) { f32 retVal = 0.0f; if (IsBinaryFile) { if (EndPointer - StartPointer < 8) fillBuffer(); if (EndPointer - StartPointer > 0) { switch (t) { case EPLYPT_INT8: retVal = *StartPointer; StartPointer++; break; case EPLYPT_INT16: if (IsWrongEndian) retVal = os::Byteswap::byteswap(*(reinterpret_cast(StartPointer))); else retVal = *(reinterpret_cast(StartPointer)); StartPointer += 2; break; case EPLYPT_INT32: if (IsWrongEndian) retVal = f32(os::Byteswap::byteswap(*(reinterpret_cast(StartPointer)))); else retVal = f32(*(reinterpret_cast(StartPointer))); StartPointer += 4; break; case EPLYPT_FLOAT32: if (IsWrongEndian) retVal = os::Byteswap::byteswap(*(reinterpret_cast(StartPointer))); else retVal = *(reinterpret_cast(StartPointer)); StartPointer += 4; break; case EPLYPT_FLOAT64: // todo: byteswap 64-bit retVal = f32(*(reinterpret_cast(StartPointer))); StartPointer += 8; break; case EPLYPT_LIST: case EPLYPT_UNKNOWN: default: retVal = 0.0f; StartPointer++; // ouch! } } else retVal = 0.0f; } else { c8* word = getNextWord(); switch (t) { case EPLYPT_INT8: case EPLYPT_INT16: case EPLYPT_INT32: retVal = f32(atoi(word)); break; case EPLYPT_FLOAT32: case EPLYPT_FLOAT64: retVal = f32(atof(word)); break; case EPLYPT_LIST: case EPLYPT_UNKNOWN: default: retVal = 0.0f; } } return retVal; } // read the next int from the file and move the start pointer along u32 CPLYMeshFileLoader::getInt(E_PLY_PROPERTY_TYPE t) { u32 retVal = 0; if (IsBinaryFile) { if (!EndOfFile && EndPointer - StartPointer < 8) fillBuffer(); if (EndPointer - StartPointer) { switch (t) { case EPLYPT_INT8: retVal = *StartPointer; StartPointer++; break; case EPLYPT_INT16: if (IsWrongEndian) retVal = os::Byteswap::byteswap(*(reinterpret_cast(StartPointer))); else retVal = *(reinterpret_cast(StartPointer)); StartPointer += 2; break; case EPLYPT_INT32: if (IsWrongEndian) retVal = os::Byteswap::byteswap(*(reinterpret_cast(StartPointer))); else retVal = *(reinterpret_cast(StartPointer)); StartPointer += 4; break; case EPLYPT_FLOAT32: if (IsWrongEndian) retVal = (u32)os::Byteswap::byteswap(*(reinterpret_cast(StartPointer))); else retVal = (u32)(*(reinterpret_cast(StartPointer))); StartPointer += 4; break; case EPLYPT_FLOAT64: // todo: byteswap 64-bit retVal = (u32)(*(reinterpret_cast(StartPointer))); StartPointer += 8; break; case EPLYPT_LIST: case EPLYPT_UNKNOWN: default: retVal = 0; StartPointer++; // ouch! } } else retVal = 0; } else { c8* word = getNextWord(); switch (t) { case EPLYPT_INT8: case EPLYPT_INT16: case EPLYPT_INT32: retVal = atoi(word); break; case EPLYPT_FLOAT32: case EPLYPT_FLOAT64: retVal = u32(atof(word)); break; case EPLYPT_LIST: case EPLYPT_UNKNOWN: default: retVal = 0; } } return retVal; } } // end namespace scene } // end namespace irr #endif // _IRR_COMPILE_WITH_PLY_LOADER_