1
// Copyright (C) 2009-2011 Gaz Davidson
2
// This file is part of the "Irrlicht Engine".
3
// For conditions of distribution and use, see copyright notice in irrlicht.h
5
#include "IrrCompileConfig.h"
6
#ifdef _IRR_COMPILE_WITH_PLY_LOADER_
8
#include "CPLYMeshFileLoader.h"
9
#include "IMeshManipulator.h"
11
#include "CDynamicMeshBuffer.h"
12
#include "SAnimatedMesh.h"
13
#include "IReadFile.h"
14
#include "fast_atof.h"
22
// input buffer must be at least twice as long as the longest line in the file
23
#define PLY_INPUT_BUFFER_SIZE 51200 // file is loaded in 50k chunks
26
CPLYMeshFileLoader::CPLYMeshFileLoader()
31
CPLYMeshFileLoader::~CPLYMeshFileLoader()
33
// delete the buffer in case we didn't earlier
34
// (we do, but this could be disabled to increase the speed of loading hundreds of meshes)
41
// Destroy the element list if it exists
42
for (u32 i=0; i<ElementList.size(); ++i)
43
delete ElementList[i];
47
//! returns true if the file maybe is able to be loaded by this class
48
bool CPLYMeshFileLoader::isALoadableFileExtension(const io::path& filename) const
50
return core::hasFileExtension(filename, "ply");
54
//! creates/loads an animated mesh from the file.
55
IAnimatedMesh* CPLYMeshFileLoader::createMesh(io::IReadFile* file)
63
// attempt to allocate the buffer and fill with data
64
if (!allocateBuffer())
71
// start with empty mesh
72
SAnimatedMesh* animMesh = 0;
75
// Currently only supports ASCII meshes
76
if (strcmp(getNextLine(), "ply"))
78
os::Printer::log("Not a valid PLY file", file->getFileName().c_str(), ELL_ERROR);
82
// cut the next line out
83
c8 *line = getNextLine();
84
// grab the word from this line
85
c8 *word = getNextWord();
88
while (strcmp(word, "comment") == 0)
94
bool readingHeader = true;
95
bool continueReading = true;
101
if (strcmp(word, "format") == 0)
103
word = getNextWord();
105
if (strcmp(word, "binary_little_endian") == 0)
108
#ifdef __BIG_ENDIAN__
109
IsWrongEndian = true;
113
else if (strcmp(word, "binary_big_endian") == 0)
116
#ifndef __BIG_ENDIAN__
117
IsWrongEndian = true;
120
else if (strcmp(word, "ascii"))
122
// abort if this isn't an ascii or a binary mesh
123
os::Printer::log("Unsupported PLY mesh format", word, ELL_ERROR);
124
continueReading = false;
129
word = getNextWord();
130
if (strcmp(word, "1.0"))
132
os::Printer::log("Unsupported PLY mesh version", word, ELL_WARNING);
136
else if (strcmp(word, "property") == 0)
138
word = getNextWord();
140
if (!ElementList.size())
142
os::Printer::log("PLY property found before element", word, ELL_WARNING);
147
SPLYElement* el = ElementList[ElementList.size()-1];
149
// fill property struct
151
prop.Type = getPropertyType(word);
152
el->KnownSize += prop.size();
154
if (prop.Type == EPLYPT_LIST)
156
el->IsFixedWidth = false;
158
word = getNextWord();
160
prop.Data.List.CountType = getPropertyType(word);
161
if (IsBinaryFile && prop.Data.List.CountType == EPLYPT_UNKNOWN)
163
os::Printer::log("Cannot read binary PLY file containing data types of unknown length", word, ELL_ERROR);
164
continueReading = false;
168
word = getNextWord();
169
prop.Data.List.ItemType = getPropertyType(word);
170
if (IsBinaryFile && prop.Data.List.ItemType == EPLYPT_UNKNOWN)
172
os::Printer::log("Cannot read binary PLY file containing data types of unknown length", word, ELL_ERROR);
173
continueReading = false;
177
else if (IsBinaryFile && prop.Type == EPLYPT_UNKNOWN)
179
os::Printer::log("Cannot read binary PLY file containing data types of unknown length", word, ELL_ERROR);
180
continueReading = false;
183
prop.Name = getNextWord();
185
// add property to element
186
el->Properties.push_back(prop);
189
else if (strcmp(word, "element") == 0)
191
SPLYElement* el = new SPLYElement;
192
el->Name = getNextWord();
193
el->Count = atoi(getNextWord());
194
el->IsFixedWidth = true;
196
ElementList.push_back(el);
198
if (el->Name == "vertex")
199
vertCount = el->Count;
202
else if (strcmp(word, "end_header") == 0)
204
readingHeader = false;
207
StartPointer = LineEndPointer + 1;
210
else if (strcmp(word, "comment") == 0)
216
os::Printer::log("Unknown item in PLY file", word, ELL_WARNING);
219
if (readingHeader && continueReading)
221
line = getNextLine();
222
word = getNextWord();
225
while (readingHeader && continueReading);
227
// now to read the actual data from the file
230
// create a mesh buffer
231
CDynamicMeshBuffer *mb = new CDynamicMeshBuffer(video::EVT_STANDARD, vertCount > 65565 ? video::EIT_32BIT : video::EIT_16BIT);
232
mb->getVertexBuffer().reallocate(vertCount);
233
mb->getIndexBuffer().reallocate(vertCount);
234
mb->setHardwareMappingHint(EHM_STATIC);
236
// loop through each of the elements
237
for (u32 i=0; i<ElementList.size(); ++i)
239
// do we want this element type?
240
if (ElementList[i]->Name == "vertex")
242
// loop through vertex properties
243
for (u32 j=0; j < ElementList[i]->Count; ++j)
244
readVertex(*ElementList[i], mb);
246
else if (ElementList[i]->Name == "face")
249
for (u32 j=0; j < ElementList[i]->Count; ++j)
250
readFace(*ElementList[i], mb);
254
// skip these elements
255
for (u32 j=0; j < ElementList[i]->Count; ++j)
256
skipElement(*ElementList[i]);
259
mb->recalculateBoundingBox();
260
SMesh* m = new SMesh();
261
m->addMeshBuffer(mb);
262
m->recalculateBoundingBox();
264
animMesh = new SAnimatedMesh();
265
animMesh->addMesh(m);
266
animMesh->recalculateBoundingBox();
278
// if we managed to create a mesh, return it
282
bool CPLYMeshFileLoader::readVertex(const SPLYElement &Element, scene::CDynamicMeshBuffer* mb)
287
video::S3DVertex vert;
288
vert.Color.set(255,255,255,255);
289
vert.TCoords.X = 0.0f;
290
vert.TCoords.Y = 0.0f;
291
vert.Normal.X = 0.0f;
292
vert.Normal.Y = 1.0f;
293
vert.Normal.Z = 0.0f;
295
for (u32 i=0; i < Element.Properties.size(); ++i)
297
E_PLY_PROPERTY_TYPE t = Element.Properties[i].Type;
299
if (Element.Properties[i].Name == "x")
300
vert.Pos.X = getFloat(t);
301
else if (Element.Properties[i].Name == "y")
302
vert.Pos.Z = getFloat(t);
303
else if (Element.Properties[i].Name == "z")
304
vert.Pos.Y = getFloat(t);
305
else if (Element.Properties[i].Name == "nx")
306
vert.Normal.X = getFloat(t);
307
else if (Element.Properties[i].Name == "ny")
308
vert.Normal.Z = getFloat(t);
309
else if (Element.Properties[i].Name == "nz")
310
vert.Normal.Y = getFloat(t);
311
else if (Element.Properties[i].Name == "u")
312
vert.TCoords.X = getFloat(t);
313
else if (Element.Properties[i].Name == "v")
314
vert.TCoords.Y = getFloat(t);
315
else if (Element.Properties[i].Name == "red")
317
u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
318
vert.Color.setRed(value);
320
else if (Element.Properties[i].Name == "green")
322
u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
323
vert.Color.setGreen(value);
325
else if (Element.Properties[i].Name == "blue")
327
u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
328
vert.Color.setBlue(value);
330
else if (Element.Properties[i].Name == "alpha")
332
u32 value = Element.Properties[i].isFloat() ? (u32)(getFloat(t)*255.0f) : getInt(t);
333
vert.Color.setAlpha(value);
336
skipProperty(Element.Properties[i]);
339
mb->getVertexBuffer().push_back(vert);
344
bool CPLYMeshFileLoader::readFace(const SPLYElement &Element, scene::CDynamicMeshBuffer* mb)
349
for (u32 i=0; i < Element.Properties.size(); ++i)
351
if ( (Element.Properties[i].Name == "vertex_indices" ||
352
Element.Properties[i].Name == "vertex_index") && Element.Properties[i].Type == EPLYPT_LIST)
355
s32 count = getInt(Element.Properties[i].Data.List.CountType);
356
u32 a = getInt(Element.Properties[i].Data.List.ItemType),
357
b = getInt(Element.Properties[i].Data.List.ItemType),
358
c = getInt(Element.Properties[i].Data.List.ItemType);
361
mb->getIndexBuffer().push_back(a);
362
mb->getIndexBuffer().push_back(c);
363
mb->getIndexBuffer().push_back(b);
365
for (; j < count; ++j)
368
c = getInt(Element.Properties[i].Data.List.ItemType);
369
mb->getIndexBuffer().push_back(a);
370
mb->getIndexBuffer().push_back(c);
371
mb->getIndexBuffer().push_back(b);
374
else if (Element.Properties[i].Name == "intensity")
376
// todo: face intensity
377
skipProperty(Element.Properties[i]);
380
skipProperty(Element.Properties[i]);
385
// skips an element and all properties. return false on EOF
386
void CPLYMeshFileLoader::skipElement(const SPLYElement &Element)
389
if (Element.IsFixedWidth)
390
moveForward(Element.KnownSize);
392
for (u32 i=0; i < Element.Properties.size(); ++i)
393
skipProperty(Element.Properties[i]);
398
void CPLYMeshFileLoader::skipProperty(const SPLYProperty &Property)
400
if (Property.Type == EPLYPT_LIST)
402
s32 count = getInt(Property.Data.List.CountType);
404
for (s32 i=0; i < count; ++i)
405
getInt(Property.Data.List.CountType);
410
moveForward(Property.size());
417
bool CPLYMeshFileLoader::allocateBuffer()
419
// Destroy the element list if it exists
420
for (u32 i=0; i<ElementList.size(); ++i)
421
delete ElementList[i];
425
Buffer = new c8[PLY_INPUT_BUFFER_SIZE];
427
// not enough memory?
432
memset(Buffer, 0, PLY_INPUT_BUFFER_SIZE);
434
StartPointer = Buffer;
436
LineEndPointer = Buffer-1;
440
// get data from the file
446
// gets more data from the file. returns false on EOF
447
void CPLYMeshFileLoader::fillBuffer()
452
u32 length = EndPointer - StartPointer;
453
if (length && StartPointer != Buffer)
455
// copy the remaining data to the start of the buffer
456
memcpy(Buffer, StartPointer, length);
458
// reset start position
459
StartPointer = Buffer;
460
EndPointer = StartPointer + length;
462
if (File->getPos() == File->getSize())
468
// read data from the file
469
u32 count = File->read(EndPointer, PLY_INPUT_BUFFER_SIZE - length);
471
// increment the end pointer by the number of bytes read
472
EndPointer = EndPointer + count;
474
// if we didn't completely fill the buffer
475
if (count != PLY_INPUT_BUFFER_SIZE - length)
477
// blank the rest of the memory
478
memset(EndPointer, 0, Buffer + PLY_INPUT_BUFFER_SIZE - EndPointer);
486
// skips x bytes in the file, getting more data if required
487
void CPLYMeshFileLoader::moveForward(u32 bytes)
489
if (StartPointer + bytes >= EndPointer)
491
if (StartPointer + bytes < EndPointer)
492
StartPointer += bytes;
494
StartPointer = EndPointer;
497
E_PLY_PROPERTY_TYPE CPLYMeshFileLoader::getPropertyType(const c8* typeString) const
499
if (strcmp(typeString, "char") == 0 ||
500
strcmp(typeString, "uchar") == 0 ||
501
strcmp(typeString, "int8") == 0 ||
502
strcmp(typeString, "uint8") == 0)
506
else if (strcmp(typeString, "uint") == 0 ||
507
strcmp(typeString, "int16") == 0 ||
508
strcmp(typeString, "uint16") == 0 ||
509
strcmp(typeString, "short") == 0 ||
510
strcmp(typeString, "ushort") == 0)
514
else if (strcmp(typeString, "int") == 0 ||
515
strcmp(typeString, "long") == 0 ||
516
strcmp(typeString, "ulong") == 0 ||
517
strcmp(typeString, "int32") == 0 ||
518
strcmp(typeString, "uint32") == 0)
522
else if (strcmp(typeString, "float") == 0 ||
523
strcmp(typeString, "float32") == 0)
525
return EPLYPT_FLOAT32;
527
else if (strcmp(typeString, "float64") == 0 ||
528
strcmp(typeString, "double") == 0)
530
return EPLYPT_FLOAT64;
532
else if ( strcmp(typeString, "list") == 0 )
539
// cannot be loaded in binary mode
540
return EPLYPT_UNKNOWN;
545
// Split the string data into a line in place by terminating it instead of copying.
546
c8* CPLYMeshFileLoader::getNextLine()
548
// move the start pointer along
549
StartPointer = LineEndPointer + 1;
551
// crlf split across buffer move
552
if (*StartPointer == '\n')
554
*StartPointer = '\0';
558
// begin at the start of the next line
559
c8* pos = StartPointer;
560
while (pos < EndPointer && *pos && *pos != '\r' && *pos != '\n')
563
if ( pos < EndPointer && ( *(pos+1) == '\r' || *(pos+1) == '\n') )
569
// we have reached the end of the buffer
570
if (pos >= EndPointer)
572
// get data from the file
576
// reset line end pointer
577
LineEndPointer = StartPointer - 1;
579
if (StartPointer != EndPointer)
580
return getNextLine();
587
StartPointer = EndPointer-1;
588
*StartPointer = '\0';
594
// null terminate the string in place
596
LineEndPointer = pos;
598
// return pointer to the start of the line
602
// null terminate the next word on the previous line and move the next word pointer along
603
// since we already have a full line in the buffer, we never need to retrieve more data
604
c8* CPLYMeshFileLoader::getNextWord()
606
// move the start pointer along
607
StartPointer += WordLength + 1;
609
if (StartPointer == LineEndPointer)
612
return LineEndPointer;
614
// begin at the start of the next word
615
c8* pos = StartPointer;
616
while (*pos && pos < LineEndPointer && pos < EndPointer && *pos != ' ' && *pos != '\t')
619
while(*pos && pos < LineEndPointer && pos < EndPointer && (*pos == ' ' || *pos == '\t') )
621
// null terminate the string in place
626
WordLength = pos-StartPointer;
627
// return pointer to the start of the word
630
// read the next float from the file and move the start pointer along
631
f32 CPLYMeshFileLoader::getFloat(E_PLY_PROPERTY_TYPE t)
637
if (EndPointer - StartPointer < 8)
640
if (EndPointer - StartPointer > 0)
645
retVal = *StartPointer;
650
retVal = os::Byteswap::byteswap(*(reinterpret_cast<s16*>(StartPointer)));
652
retVal = *(reinterpret_cast<s16*>(StartPointer));
657
retVal = f32(os::Byteswap::byteswap(*(reinterpret_cast<s32*>(StartPointer))));
659
retVal = f32(*(reinterpret_cast<s32*>(StartPointer)));
664
retVal = os::Byteswap::byteswap(*(reinterpret_cast<f32*>(StartPointer)));
666
retVal = *(reinterpret_cast<f32*>(StartPointer));
670
// todo: byteswap 64-bit
671
retVal = f32(*(reinterpret_cast<f64*>(StartPointer)));
678
StartPointer++; // ouch!
686
c8* word = getNextWord();
692
retVal = f32(atoi(word));
696
retVal = f32(atof(word));
706
// read the next int from the file and move the start pointer along
707
u32 CPLYMeshFileLoader::getInt(E_PLY_PROPERTY_TYPE t)
713
if (!EndOfFile && EndPointer - StartPointer < 8)
716
if (EndPointer - StartPointer)
721
retVal = *StartPointer;
726
retVal = os::Byteswap::byteswap(*(reinterpret_cast<u16*>(StartPointer)));
728
retVal = *(reinterpret_cast<u16*>(StartPointer));
733
retVal = os::Byteswap::byteswap(*(reinterpret_cast<s32*>(StartPointer)));
735
retVal = *(reinterpret_cast<s32*>(StartPointer));
740
retVal = (u32)os::Byteswap::byteswap(*(reinterpret_cast<f32*>(StartPointer)));
742
retVal = (u32)(*(reinterpret_cast<f32*>(StartPointer)));
746
// todo: byteswap 64-bit
747
retVal = (u32)(*(reinterpret_cast<f64*>(StartPointer)));
754
StartPointer++; // ouch!
762
c8* word = getNextWord();
772
retVal = u32(atof(word));
785
} // end namespace scene
786
} // end namespace irr
788
#endif // _IRR_COMPILE_WITH_PLY_LOADER_