PLY file read and write
The PLY file format was developed at Stanford University. Some well-known models in Computer Graphics, such as the models of The Stanford 3D Scanning Repository (including the Stanford Bunny, Happy Buddha and Dragon), the models of the Large Geometric Models Archive, and the Power Plant Model are in the PLY format.
Here is some C++ libraries for PLY reading and writing. I used RPly because the code is really well written and documented. My task is to read to and write from 2 data structure Point_3D
, storing the 3D position and other information for all 3D points, and Face
, storing the vertex index of each face of the triangulated surface.
Data structure
The declaration of data structure of Point_3D
and Face
.
struct Point_3D
{
int u, v;
float x, y, z;
vector<int> ind_vis_views;
};
struct Face
{
long inds[3];
};
Header
The header defines the format of the PLY file. The typical format is as follows
ply
format ascii 1.0
comment an example of a PLY header
element vertex 3
property float x
property float y
property float z
element face 1
property list uchar int vertex_indices
comment this is a comment
end_header
The format can be ascii
, binary_little_endian
or binary_big_endian
. The element
keyword introduces a description of how some particular data element is stored and how many of them there are. Each element consists of one or more properties, which can be either of type scalar or list. For instance, the vertex
consists of scalaer properties x
, y
, and z
, the face
element consists of one list property vertex_idx
.
This is how you define the file header in the code:
ply_add_element(ply, "vertex", num_vertices);
ply_add_scalar_property(ply, "x", PLY_FLOAT);
// define other properties for `vertex`
ply_add_element(ply, "face", num_faces);
ply_add_list_property(ply, "ind_vertex", PLY_UCHAR, PLY_INT);
PLY Read
Actual reading happens in a callback function the user develops, here is how it works: the method ply_read()
is called, which will further invoke ply_read_element()
, then ply_read_scalar_property()
or ply_read_list_property()
depending on the property type. The actually data is read in by handler
and stored in argument-value
, where argument
is the argument for the callback function. Finally, this callback function is invoked in read_cb(argument)
and the user can do further processing in the callback function.
int ply_read2file(const char* name)
{
// create a p_ply instance
p_ply ply = ply_open(name, NULL, 0, NULL);
if(!ply)
return 0;
// read the header/file format
if(!ply_read_header(ply))
return 0;
// link properties to the callback function `vertex_cb`
ply_set_read_cb(ply, "vertex", "ind_vis_views", vertex_cb, NULL, 1);
// link other properties to the callback function, omitted
// read file
if (!ply_read(ply))
return 0;
// close ply file
if (!ply_close(ply))
return 0;
return 1;
}
// callback function
int PLY_IO::vertex_cb(p_ply_argument argument)
{
// Further processing with the read data
}
Writing
Writing is even easier, there is no need to write a callback function. The only thing to keep in mind is that the order of writing should follow that defined in the header.
int ply_write2file(const char* name, vector<Point_3D> &points_3d, vector<Face> &faces)
{
long num_vertices = static_cast<long>(points_3d.size());
long num_faces = static_cast<long>(faces.size());
p_ply ply = ply_create(name, PLY_ASCII, NULL, 0, NULL);
if (!ply)
return 0;
ply_add_element(ply, "vertex", num_vertices);
ply_add_scalar_property(ply, "u", PLY_INT);
ply_add_scalar_property(ply, "v", PLY_INT);
ply_add_scalar_property(ply, "x", PLY_FLOAT);
ply_add_scalar_property(ply, "y", PLY_FLOAT);
ply_add_scalar_property(ply, "z", PLY_FLOAT);
ply_add_list_property(ply, "ind_vis_views", PLY_INT, PLY_INT);
ply_add_element(ply, "face", num_faces);
ply_add_list_property(ply, "ind_vertex", PLY_UCHAR, PLY_INT);
if (!ply_write_header(ply))
return 0;
// write vertices
std::cout << "#### Writing vertices..." << std::endl;
for (int i = 0; i < num_vertices; ++i)
{
ply_write(ply, points_3d[i].u);
ply_write(ply, points_3d[i].v);
ply_write(ply, points_3d[i].x);
ply_write(ply, points_3d[i].y);
ply_write(ply, points_3d[i].z);
int num_vis_views = static_cast<int>(points_3d[i].ind_vis_views.size());
ply_write(ply, num_vis_views);
for (int j = 0; j < num_vis_views; ++j)
::ply_write(ply, points_3d[i].ind_vis_views[j]);
}
// write faces
std::cout << "#### Writing polygons..." << std::endl;
for (int i = 0; i < num_faces; ++i)
{
int num_faces = 3;
ply_write(ply, num_faces);
for(int j = 0; j < num_faces; ++j)
{
ply_write(ply, faces[i].inds[j]);
}
}
if (!ply_close(ply))
return 0;
return 1;
}