#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
{
#define PLY_INPUT_BUFFER_SIZE 51200
CPLYMeshFileLoader::CPLYMeshFileLoader()
: File(0), Buffer(0)
{
}
CPLYMeshFileLoader::~CPLYMeshFileLoader()
{
if (Buffer)
{
delete [] Buffer;
Buffer = 0;
}
for (u32 i=0; i<ElementList.size(); ++i)
delete ElementList[i];
ElementList.clear();
}
bool CPLYMeshFileLoader::isALoadableFileExtension(const io::path& filename) const
{
return core::hasFileExtension(filename, "ply");
}
IAnimatedMesh* CPLYMeshFileLoader::createMesh(io::IReadFile* file)
{
if (!file)
return 0;
File = file;
File->grab();
if (!allocateBuffer())
{
File->drop();
File = 0;
return 0;
}
SAnimatedMesh* animMesh = 0;
u32 vertCount=0;
if (strcmp(getNextLine(), "ply"))
{
os::Printer::log("Not a valid PLY file", file->getFileName().c_str(), ELL_ERROR);
}
else
{
c8 *line = getNextLine();
c8 *word = getNextWord();
while (strcmp(word, "comment") == 0)
{
line = 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"))
{
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
{
SPLYElement* el = ElementList[ElementList.size()-1];
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();
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)
{
}
else
{
os::Printer::log("Unknown item in PLY file", word, ELL_WARNING);
}
if (readingHeader && continueReading)
{
line = getNextLine();
word = getNextWord();
}
}
while (readingHeader && continueReading);
if (continueReading)
{
CDynamicMeshBuffer *mb = new CDynamicMeshBuffer(video::EVT_STANDARD, vertCount > 65565 ? video::EIT_32BIT : video::EIT_16BIT);
mb->getVertexBuffer().reallocate(vertCount);
mb->getIndexBuffer().reallocate(vertCount);
mb->setHardwareMappingHint(EHM_STATIC);
for (u32 i=0; i<ElementList.size(); ++i)
{
if (ElementList[i]->Name == "vertex")
{
for (u32 j=0; j < ElementList[i]->Count; ++j)
readVertex(*ElementList[i], mb);
}
else if (ElementList[i]->Name == "face")
{
for (u32 j=0; j < ElementList[i]->Count; ++j)
readFace(*ElementList[i], mb);
}
else
{
for (u32 j=0; j < ElementList[i]->Count; ++j)
skipElement(*ElementList[i]);
}
}
mb->recalculateBoundingBox();
SMesh* m = new SMesh();
m->addMeshBuffer(mb);
m->recalculateBoundingBox();
mb->drop();
animMesh = new SAnimatedMesh();
animMesh->addMesh(m);
animMesh->recalculateBoundingBox();
m->drop();
}
}
delete [] Buffer;
Buffer = 0;
File->drop();
File = 0;
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;
for (u32 i=0; i < Element.Properties.size(); ++i)
{
E_PLY_PROPERTY_TYPE t = Element.Properties[i].Type;
if (Element.Properties[i].Name == "x")
vert.Pos.X = getFloat(t);
else if (Element.Properties[i].Name == "y")
vert.Pos.Z = getFloat(t);
else if (Element.Properties[i].Name == "z")
vert.Pos.Y = getFloat(t);
else if (Element.Properties[i].Name == "nx")
vert.Normal.X = getFloat(t);
else if (Element.Properties[i].Name == "ny")
vert.Normal.Z = getFloat(t);
else if (Element.Properties[i].Name == "nz")
vert.Normal.Y = getFloat(t);
else if (Element.Properties[i].Name == "u")
vert.TCoords.X = getFloat(t);
else if (Element.Properties[i].Name == "v")
vert.TCoords.Y = getFloat(t);
else if (Element.Properties[i].Name == "red")
{
u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
vert.Color.setRed(value);
}
else if (Element.Properties[i].Name == "green")
{
u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
vert.Color.setGreen(value);
}
else if (Element.Properties[i].Name == "blue")
{
u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
vert.Color.setBlue(value);
}
else if (Element.Properties[i].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 true;
}
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)
{
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")
{
skipProperty(Element.Properties[i]);
}
else
skipProperty(Element.Properties[i]);
}
return true;
}
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()
{
for (u32 i=0; i<ElementList.size(); ++i)
delete ElementList[i];
ElementList.clear();
if (!Buffer)
Buffer = new c8[PLY_INPUT_BUFFER_SIZE];
if (!Buffer)
return false;
memset(Buffer, 0, PLY_INPUT_BUFFER_SIZE);
StartPointer = Buffer;
EndPointer = Buffer;
LineEndPointer = Buffer-1;
WordLength = -1;
EndOfFile = false;
fillBuffer();
return true;
}
void CPLYMeshFileLoader::fillBuffer()
{
if (EndOfFile)
return;
u32 length = EndPointer - StartPointer;
if (length && StartPointer != Buffer)
{
memcpy(Buffer, StartPointer, length);
}
StartPointer = Buffer;
EndPointer = StartPointer + length;
if (File->getPos() == File->getSize())
{
EndOfFile = true;
}
else
{
u32 count = File->read(EndPointer, PLY_INPUT_BUFFER_SIZE - length);
EndPointer = EndPointer + count;
if (count != PLY_INPUT_BUFFER_SIZE - length)
{
memset(EndPointer, 0, Buffer + PLY_INPUT_BUFFER_SIZE - EndPointer);
EndOfFile = true;
}
}
}
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
{
return EPLYPT_UNKNOWN;
}
}
c8* CPLYMeshFileLoader::getNextLine()
{
StartPointer = LineEndPointer + 1;
if (*StartPointer == '\n')
{
*StartPointer = '\0';
++StartPointer;
}
c8* pos = StartPointer;
while (pos < EndPointer && *pos && *pos != '\r' && *pos != '\n')
++pos;
if ( pos < EndPointer && ( *(pos+1) == '\r' || *(pos+1) == '\n') )
{
*pos = '\0';
++pos;
}
if (pos >= EndPointer)
{
if (!EndOfFile)
{
fillBuffer();
LineEndPointer = StartPointer - 1;
if (StartPointer != EndPointer)
return getNextLine();
else
return Buffer;
}
else
{
StartPointer = EndPointer-1;
*StartPointer = '\0';
return StartPointer;
}
}
else
{
*pos = '\0';
LineEndPointer = pos;
WordLength = -1;
return StartPointer;
}
}
c8* CPLYMeshFileLoader::getNextWord()
{
StartPointer += WordLength + 1;
if (StartPointer == LineEndPointer)
{
WordLength = -1;
return LineEndPointer;
}
c8* pos = StartPointer;
while (*pos && pos < LineEndPointer && pos < EndPointer && *pos != ' ' && *pos != '\t')
++pos;
while(*pos && pos < LineEndPointer && pos < EndPointer && (*pos == ' ' || *pos == '\t') )
{
*pos = '\0';
++pos;
}
--pos;
WordLength = pos-StartPointer;
return StartPointer;
}
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<s16*>(StartPointer)));
else
retVal = *(reinterpret_cast<s16*>(StartPointer));
StartPointer += 2;
break;
case EPLYPT_INT32:
if (IsWrongEndian)
retVal = f32(os::Byteswap::byteswap(*(reinterpret_cast<s32*>(StartPointer))));
else
retVal = f32(*(reinterpret_cast<s32*>(StartPointer)));
StartPointer += 4;
break;
case EPLYPT_FLOAT32:
if (IsWrongEndian)
retVal = os::Byteswap::byteswap(*(reinterpret_cast<f32*>(StartPointer)));
else
retVal = *(reinterpret_cast<f32*>(StartPointer));
StartPointer += 4;
break;
case EPLYPT_FLOAT64:
retVal = f32(*(reinterpret_cast<f64*>(StartPointer)));
StartPointer += 8;
break;
case EPLYPT_LIST:
case EPLYPT_UNKNOWN:
default:
retVal = 0.0f;
StartPointer++;
}
}
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;
}
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<u16*>(StartPointer)));
else
retVal = *(reinterpret_cast<u16*>(StartPointer));
StartPointer += 2;
break;
case EPLYPT_INT32:
if (IsWrongEndian)
retVal = os::Byteswap::byteswap(*(reinterpret_cast<s32*>(StartPointer)));
else
retVal = *(reinterpret_cast<s32*>(StartPointer));
StartPointer += 4;
break;
case EPLYPT_FLOAT32:
if (IsWrongEndian)
retVal = (u32)os::Byteswap::byteswap(*(reinterpret_cast<f32*>(StartPointer)));
else
retVal = (u32)(*(reinterpret_cast<f32*>(StartPointer)));
StartPointer += 4;
break;
case EPLYPT_FLOAT64:
retVal = (u32)(*(reinterpret_cast<f64*>(StartPointer)));
StartPointer += 8;
break;
case EPLYPT_LIST:
case EPLYPT_UNKNOWN:
default:
retVal = 0;
StartPointer++;
}
}
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;
}
}
}
#endif