Miniply
A fast and easy-to-use PLY parsing library in a single c++11 header and cpp file
Install / Use
/learn @vilya/MiniplyREADME
miniply
<p align="center"> <img src="https://github.com/vilya/miniply/blob/master/miniPLY3d_logo-cropped.png" width="744"> </p>A fast and easy-to-use library for parsing PLY files, in a single c++11 header and cpp file with no external dependencies, ready to drop into your project.
Features
- Fast: loads all 8929 PLY files from the pbrt-v3-scenes repository in under 9 seconds total - an average parsing time of less than 1 millisecond per file!
- Small: just a single .h and .cpp file which you can copy into your project.
- Complete: parses ASCII, binary little-endian and binary big-endian versions of the file format (binary loading assumes you're running on a little-endian CPU).
- Cross-platform: works on Windows, macOS and Linux.
- Provides helper methods for getting standard vertex and face properties.
- Can triangulate polygons as they're loaded.
- Fast path for models where you know every face has the same fixed number of vertices
- MIT license
Note that miniply does not support writing PLY files, only reading them.
Getting started
- Copy
miniply.handminiply.cppinto your project. - Add
#include <miniply.h>wherever necessary.
The CMake file that you see in this repo is purely for building the miniply-info
and miniply-perf command line tools in the extra folder; it isn't required if
you're just using the library in your own project.
General use
The general usage model for this library is:
- Construct a
PLYReaderobject. This will open the PLY file and read the header. - Call
reader.valid()to check that the file was opened successfully. - Iterate over the elements using something like
for (; reader.has_element(); reader.next_element()) { ... }, callingreader.element_is()to check whether the current element is one that you want data from. - For any elements that you're interested in:
- Call
reader.load_element(). - Use some combination of the
extract_columns(),extract_list_column()andextract_triangles()methods to get the data you're interested in.
- Call
You can only iterate forwards over the elements in the file (at present). You cannot jump back to an earlier element.
You can skip forward to the next element simply by calling next_element() without
having called load_element() yet. This will be very efficient if the current element
is fixed-size. If the current element contains any list properties then we will have to
scan through all of the element data to find where it finishes and the next element
starts, which is not as efficient (although the library will do it's best!).
Loading header info from a PLY file
This function (taken directly from extra/miniply-info.cpp) shows how you can get the header from a .ply file:
bool print_ply_header(const char* filename)
{
miniply::PLYReader reader(filename);
if (!reader.valid()) {
fprintf(stderr, "Failed to open %s\n", filename);
return false;
}
printf("ply\n");
printf("format %s %d.%d\n", kFileTypes[int(reader.file_type())],
reader.version_major(), reader.version_minor());
for (uint32_t i = 0, endI = reader.num_elements(); i < endI; i++) {
const miniply::PLYElement* elem = reader.get_element(i);
printf("element %s %u\n", elem->name.c_str(), elem->count);
for (const miniply::PLYProperty& prop : elem->properties) {
if (prop.countType != miniply::PLYPropertyType::None) {
printf("property list %s %s %s\n", kPropertyTypes[uint32_t(prop.countType)],
kPropertyTypes[uint32_t(prop.type)], prop.name.c_str());
}
else {
printf("property %s %s\n", kPropertyTypes[uint32_t(prop.type)], prop.name.c_str());
}
}
}
printf("end_header\n");
return true;
}
Loading a triangle mesh
Polygons in PLY files are ordinarily stored in a list property, meaning that each
polygon is a variable-length list of vertex indices. If the mesh representation in
your program is triangles-only, you will need to triangulate the faces. miniply
has built-in support for this:
Note that if you know in advance that your PLY file only contains triangles, there is a much faster way to load it. See below for details.
// Very basic triangle mesh struct, for example purposes
struct TriMesh {
// Per-vertex data
float* pos = nullptr; // has 3 * numVerts elements.
float* uv = nullptr; // if non-null, has 2 * numVerts elements.
uint32_t numVerts = 0;
// Per-index data
int* indices = nullptr;
uint32_t numIndices = 0; // number of indices = 3 times the number of triangles.
};
TriMesh* load_trimesh_from_ply(const char* filename)
{
miniply::PLYReader reader(filename);
if (!reader.valid()) {
return nullptr;
}
uint32_t indexes[3];
bool gotVerts = false, gotFaces = false;
TriMesh* trimesh = new TriMesh();
while (reader.has_element() && (!gotVerts || !gotFaces)) {
if (reader.element_is(miniply::kPLYVertexElement) && reader.load_element() && reader.find_pos(indexes)) {
trimesh->numVerts = reader.num_rows();
trimesh->pos = new float[trimesh->numVerts * 3];
reader.extract_properties(indexes, 3, miniply::PLYPropertyType::Float, trimesh->pos);
if (reader.find_texcoord(indexes)) {
trimesh->uv = new float[trimesh->numVerts * 2];
reader.extract_properties(indexes, 2, miniply::PLYPropertyType::Float, trimesh->uv);
}
gotVerts = true;
}
else if (reader.element_is(miniply::kPLYFaceElement) && reader.load_element() && reader.find_indices(indexes)) {
bool polys = reader.requires_triangulation(indexes[0]);
if (polys && !gotVerts) {
fprintf(stderr, "Error: need vertex positions to triangulate faces.\n");
break;
}
if (polys) {
trimesh->numIndices = reader.num_triangles(indexes[0]) * 3;
trimesh->indices = new int[trimesh->numIndices];
reader.extract_triangles(indexes[0], trimesh->pos, trimesh->numVerts, miniply::PLYPropertyType::Int, trimesh->indices);
}
else {
trimesh->numIndices = reader.num_rows() * 3;
trimesh->indices = new int[trimesh->numIndices];
reader.extract_list_property(indexes[0], miniply::PLYPropertyType::Int, trimesh->indices);
}
gotFaces = true;
}
if (gotVerts && gotFaces) {
break;
}
reader.next_element();
}
if (!gotVerts || !gotFaces) {
delete trimesh;
return nullptr;
}
return trimesh;
}
For a more complete example, see extra/miniply-perf.cpp
Loading from a PLY file known to only contain triangles
Loading the vertex indices for each face from a variable length list is a bit
wasteful if you know ahread of time that your PLY file only contains triangles.
With miniply you can take advantage of this knowledge to get a massive
reduction in the loading time for the file.
The idea is to replace the single list property, which miniply has to treat as
variable-sized, with a set of fixed-size properties. There will be one
property corresponding to the item count for each list (which we will ignore
during loading, because we know it will always be three), followed by three
new properties (one for each list index). You do this by calling
convert_list_to_fixed_size() on the face element at some point prior to
loading its data.
Doing this allows miniply to use its far more efficient code path for loading
fixed-size elements instead. This can cut loading times by more than half!
// Note: using the same TriMesh class as the example above, omitting it here
// for the sake of brevity.
TriMesh* load_trimesh_from_triangles_only_ply(const char* filename)
{
miniply::PLYReader reader(filename);
if (!reader.valid()) {
return nullptr;
}
uint32_t faceIdxs[3];
miniply::PLYElement* faceElem = reader.get_element(reader.find_element(miniply::kPLYFaceElement));
if (faceElem == nullptr) {
return nullptr;
}
faceElem->convert_list_to_fixed_size(faceElem->find_property("vertex_indices"), 3, faceIdxs);
uint32_t indexes[3];
bool gotVerts = false, gotFaces = false;
TriMesh* trimesh = new TriMesh();
while (reader.has_element() && (!gotVerts || !gotFaces)) {
if (reader.element_is(miniply::kPLYVertexElement) && reader.load_element() && reader.find_pos(indexes)) {
// This section is the same as the example above, not repeating it here.
}
else if (!gotFaces && reader.element_is(miniply::kPLYFaceElement) && reader.load_element()) {
trimesh->numIndices = reader.num_rows() * 3;
trimesh->indices = new int[trimesh->numIndices];
reader.extract_properties(faceIdxs, 3, miniply::PLYPropertyType::Int, trimesh->indices);
gotFaces = true;
}
if (gotVerts && gotFaces) {
break;
}
reader.next_element();
}
if (!gotVerts || !gotFaces) {
delete trimesh;
return nullptr;
}
return trimesh;
}
To recap, the differences from the previous example are:
- We're calling
faceElem->convert_list_to_fixed_size()up front. - In the section which processes the face element, we're calling
reader.extract_properties()to get the index data instead ofreader.extract_triangles()orreader.extrat_list_property().
History
I originally wrote the code that became miniply as part of minipbrt. I looked at several existing PLY parsing libraries first, but (a) I really wanted to keep minipbrt dependency free; and (b) I already had a lot of parsin
Related Skills
node-connect
342.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
85.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
342.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
342.5kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
