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.

RPly
libply

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];
};

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;
}