view dmply.cpp @ 107:2b30217a3c39 default tip

Fix verbose build echos.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 29 Feb 2024 21:48:47 +0200
parents 03aa729a9e90
children
line wrap: on
line source

//
// GLDragon - OpenGL PLY model viewer / simple benchmark
// -- PLY file parsing
// Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org>
// (C) Copyright 2019 Tecnic Software productions (TNSP)
//
// See file "COPYING" for license information.
//
#include "dmply.h"
#include <SDL_endian.h>


static DMPLYPropType dmPLYParsePropType(const std::string &name)
{
    if (name == "list")
        return PLY_TYPE_LIST;
    else
    if (name == "float" || name == "float32")
        return PLY_TYPE_FLOAT;
    else
    if (name == "double" || name == "float64")
        return PLY_TYPE_DOUBLE;
    else
    if (name == "uchar" || name == "uint8")
        return PLY_TYPE_UINT8;
    else
    if (name == "int16")
        return PLY_TYPE_INT16;
    else
    if (name == "uint16")
        return PLY_TYPE_UINT16;
    else
    if (name == "int" || name == "int32")
        return PLY_TYPE_INT32;
    else
    if (name == "uint" || name == "uint32")
        return PLY_TYPE_UINT32;
    else
        return PLY_TYPE_NONE;
}


static bool dmPLYParsePropertyValueASCII(DMPLYFileInfo &info, const DMPLYPropType ptype, DMPLYPropValue &pval, size_t &pos)
{
    size_t len = 0;
    std::string val = info.line.substr(pos);

    switch (ptype)
    {
        case PLY_TYPE_INT8:
        case PLY_TYPE_INT16:
        case PLY_TYPE_INT32:
            pval.v_int = std::stoi(val, &len);
            break;

        case PLY_TYPE_UINT8:
        case PLY_TYPE_UINT16:
        case PLY_TYPE_UINT32:
            pval.v_uint = std::stoi(val, &len);
            break;

        case PLY_TYPE_FLOAT:
            pval.v_float = std::stof(val, &len);
            break;

        case PLY_TYPE_DOUBLE:
            pval.v_double = std::stod(val, &len);
            break;

        default:
            return info.textError(
                "Internal error, unimplemented PLY property type");
    }

    pos += len;

    return true;
}


static bool dmPLYParsePropertyASCII(DMPLYFileInfo &info, DMPLYFileProperty &prop, size_t &pos)
{
    switch (prop.type)
    {
        case PLY_TYPE_LIST:
            prop.list_values.clear();

            if (!dmPLYParsePropertyValueASCII(info, prop.list_num_type, prop.list_num_value, pos))
                return false;

            for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++)
            {
                DMPLYPropValue pval;
                if (!dmPLYParsePropertyValueASCII(info, prop.list_values_type, pval, pos))
                    return false;

                prop.list_values.push_back(pval);
            }

            if (prop.list_values.size() != prop.list_num_value.v_uint)
            {
                return info.syntaxError(
                    "Number of property list '"+ prop.name +" values not equal to number specified");
            }
            return true;

        default:
            return dmPLYParsePropertyValueASCII(info, prop.type, prop.value, pos);
    }
}


static bool dmPLYParseElementASCII(DMPLYFileInfo &info, DMPLYFileElement &element)
{
    size_t nprop = 0, pos = 0;

    // Read one line
    if (!info.readLine())
    {
        return info.textError(
            "Unexpected end of file");
    }

    // Skip empty lines
    if (info.line.empty())
        return true;

    // Parse the properties
    for (auto const prop : element.properties)
    {
        if (!dmPLYParsePropertyASCII(info, *prop, pos))
            return false;

        nprop++;
    }

    if (nprop != element.properties.size())
    {
        return info.syntaxError(
            "Expected N properties, got different number");
    }

    return true;
}


bool dmPLYReadPropertyValueBIN(DMPLYFileInfo &info, const DMPLYPropType ptype, DMPLYPropValue &pval)
{
    uint8_t tmpU8;
    int8_t tmpS8;
    uint16_t tmpU16;
    int16_t tmpS16;
    uint32_t tmpU32;
    int32_t tmpS32;
    float tmpFloat;

    switch (ptype)
    {
        case PLY_TYPE_INT8:
            info.file.read(reinterpret_cast<char *>(&tmpS8), 1);
            pval.v_int = tmpS8;
            break;

        case PLY_TYPE_UINT8:
            info.file.read(reinterpret_cast<char *>(&tmpU8), 1);
            pval.v_uint = tmpU8;
            break;

        case PLY_TYPE_INT16:
            info.file.read(reinterpret_cast<char *>(&tmpS16), 2);
            tmpS16 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE16(tmpS16) : SDL_SwapBE16(tmpS16);
            pval.v_int = tmpS16;
            break;

        case PLY_TYPE_UINT16:
            info.file.read(reinterpret_cast<char *>(&tmpU16), 2);
            tmpU16 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE16(tmpU16) : SDL_SwapBE16(tmpU16);
            pval.v_uint = tmpU16;
            break;

        case PLY_TYPE_INT32:
            info.file.read(reinterpret_cast<char *>(&tmpS32), 4);
            tmpS32 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE32(tmpS32) : SDL_SwapBE32(tmpS32);
            pval.v_int = tmpS32;
            break;

        case PLY_TYPE_UINT32:
            info.file.read(reinterpret_cast<char *>(&tmpU32), 4);
            tmpU32 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE32(tmpU32) : SDL_SwapBE32(tmpU32);
            pval.v_uint = tmpU32;
            break;

        case PLY_TYPE_FLOAT:
            info.file.read(reinterpret_cast<char *>(&tmpFloat), 4);
            pval.v_float = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapFloatLE(tmpFloat) : SDL_SwapFloatBE(tmpFloat);
            break;

        default:
            return info.textError(
                "Internal error, unimplemented PLY property type");
    }

    return true;
}


bool dmPLYReadPropertyBIN(DMPLYFileInfo &info, DMPLYFileProperty &prop)
{
    switch (prop.type)
    {
        case PLY_TYPE_LIST:
            prop.list_values.clear();

            if (!dmPLYReadPropertyValueBIN(info, prop.list_num_type, prop.list_num_value))
                return false;

            for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++)
            {
                DMPLYPropValue pval;
                if (!dmPLYReadPropertyValueBIN(info, prop.list_values_type, pval))
                    return false;

                prop.list_values.push_back(pval);
            }

            if (prop.list_values.size() != prop.list_num_value.v_uint)
            {
                return info.syntaxError(
                    "Number of property list '"+ prop.name +" values not equal to number specified");
            }
            return true;

        default:
            return dmPLYReadPropertyValueBIN(info, prop.type, prop.value);
    }
}


bool dmPLYReadElementBIN(DMPLYFileInfo &info, DMPLYFileElement &element)
{
    size_t nprop = 0;

    for (auto const prop : element.properties)
    {
        if (!dmPLYReadPropertyBIN(info, *prop))
            return false;

        nprop++;
    }

    if (nprop != element.properties.size())
    {
        return info.syntaxError(
            "Expected N properties, got different number");
    }

    return true;
}


bool dmLoadFromPLY(DMModel &model, const std::string &filename)
{
    DMPLYFileInfo info;

    return dmLoadFromPLY(model, filename, info);
}


bool dmLoadFromPLY(DMModel &model, const std::string &filename, DMPLYFileInfo &info)
{
    info.filename = filename;
    info.nline = info.state = 0;
    info.file.open(info.filename.c_str(), std::fstream::in | std::fstream::binary);

    dmMsg("Trying to read mesh from '%s'.\n",
        info.filename.c_str());

    if (!info.file.is_open())
    {
        dmError("Unable to open file '%s'.\n",
            info.filename.c_str());
        return false;
    }

    // Parse the PLY header
    while (info.state >= 0)
    {
        // Read one line
        if (!info.readLine())
            return false;

        // Skip empty lines
        if (info.line.empty())
            continue;

        // Split key and value
        std::vector<std::string> tokens = dmStrSplit(info.line);
        std::string &key = tokens[0];

        if (info.state == 0 && key == "ply")
        {
            info.state = 1;
        }
        else
        if (info.state > 0 && key == "end_header")
        {
            info.state = -1;
        }
        else
        if (info.state == 1 && key == "format")
        {
            if (tokens.size() < 3)
            {
                return info.syntaxError(
                    "Expected value for format");
            }

            info.format = PLY_FMT_UNKNOWN;

            if (tokens[1] == "ascii")
                info.format = PLY_FMT_ASCII;
            else
            if (tokens[1] == "binary_little_endian")
                info.format = PLY_FMT_BIN_LE;
            else
            if (tokens[1] == "binary_big_endian")
                info.format = PLY_FMT_BIN_BE;

            if (info.format == PLY_FMT_UNKNOWN ||
                tokens[2] != "1.0")
            {
                dmError("Unknown or unsupported PLY file format '%s'.\n",
                    (tokens[1] +" "+ tokens[2]).c_str());
                return false;
            }

            info.state = 2;
        }
        else
        if ((info.state == 2 || info.state == 3) && key == "element")
        {
            if (tokens.size() < 3)
            {
                return info.syntaxError(
                    "Expected a value for element key");
            }

            std::string &el_name = tokens[1], &el_value = tokens[2];

            if (info.elem_map.count(el_name))
            {
                return info.syntaxError(
                    "Element '"+ el_name +"' has already been defined");
            }

            DMPLYFileElement &elem = info.elem_map[el_name];
            info.element = &elem;
            info.elements.push_back(&elem);

            elem.name = el_name;
            elem.value = std::stoi(el_value);

            info.state = 3;
        }
        else
        if (info.state == 3 && key == "property")
        {
            if (tokens.size() < 3)
            {
                return info.syntaxError(
                    "Expected value for property");
            }

            const std::string
                &pr_name = tokens.back(),
                &pr_typename = tokens.at(1);

            if (!info.element)
            {
                // Should not happen
                return info.syntaxError(
                    "No element defined for property '"+ pr_name +"'?");
            }

            // Check if this property has been already defined
            if (info.element->prop_map.count(pr_name))
            {
                return info.syntaxError(
                    "Element '"+ info.element->name +
                    "' already has property '"+ pr_name +"' defined?");
            }

            // Parse property information
            DMPLYPropType pr_type = dmPLYParsePropType(pr_typename);
            if (pr_type == PLY_TYPE_NONE)
            {
                return info.syntaxError(
                    "Invalid or unsupported property type '"+ pr_typename +"'");
            }

            DMPLYFileProperty &prop = info.element->prop_map[pr_name];
            info.element->properties.push_back(&prop);

            prop.name = pr_name;
            prop.type = pr_type;

            if (pr_type == PLY_TYPE_LIST)
            {
                // List is a special case
                if (tokens.size() < 5)
                {
                    return info.syntaxError(
                        "Expected more values for a list property (num_type, val_type, name)");
                }

                prop.list_num_type = dmPLYParsePropType(tokens.at(2));
                prop.list_values_type = dmPLYParsePropType(tokens.at(3));

                if (prop.list_num_type == PLY_TYPE_NONE ||
                    prop.list_values_type == PLY_TYPE_NONE)
                {
                    return info.syntaxError(
                        "Invalid or unsupported property type(s)");
                }
            }
        }
        else
        if (info.state > 0 // && (key == "comment" || key == "obj_info")
            )
        {
            // Ignore comments
            // .. and unknown keys
        }
        else
        {
            return info.syntaxError(
                "Unexpected key '"+ key +"'");
        }
    }

    // Check header data
    DMPLYFileElement *elem;
    DMPLYFileProperty *prop;
    if (info.state != -1 ||
        (elem = info.checkElem(PLY_ELEM_FACE)) == 0 ||
        (prop = elem->checkProp(PLY_PROP_VERTEX_INDICES)) == 0 ||
        prop->type != PLY_TYPE_LIST ||
        (elem = info.checkElem(PLY_ELEM_VERTEX)) == 0 ||
        (prop = elem->checkProp("x")) == 0 || prop->type != PLY_TYPE_FLOAT ||
        (prop = elem->checkProp("y")) == 0 || prop->type != PLY_TYPE_FLOAT ||
        (prop = elem->checkProp("z")) == 0 || prop->type != PLY_TYPE_FLOAT
        )
    {
        dmError("PLY file did not contain expected information.\n");
        return false;
    }

    model.nvertices = info.elem_map[PLY_ELEM_VERTEX].value;
    model.nfaces    = info.elem_map[PLY_ELEM_FACE].value;


    if (model.nvertices < 3 || model.nfaces < 1)
    {
        dmError("Invalid nvertices (%d) and/or nfaces (%d).\n",
            model.nvertices, model.nfaces);
        return false;
    }

    dmMsg("Should have %d vertices, %d faces\n",
        model.nvertices, model.nfaces);

    // Pre-allocate space
    model.vertices.reserve(model.nvertices);
    model.normals.reserve(model.nvertices);
    model.faces.reserve(model.nfaces * 3);

    // Read the actual data (in order of the elements)
    for (auto *element : info.elements)
    for (int n = 0; n < element->value; n++)
    {
        switch (info.format)
        {
            case PLY_FMT_ASCII:
                if (!dmPLYParseElementASCII(info, *element))
                    return false;
                break;

            default:
                if (!dmPLYReadElementBIN(info, *element))
                    return false;
                break;
        }

        // Check for specific elements
        if (element->name == PLY_ELEM_FACE)
        {
            DMPLYFileProperty &prop = element->prop_map[PLY_PROP_VERTEX_INDICES];

            if (prop.list_num_value.v_uint != 3)
            {
                return info.syntaxError(
                    "Expected 3 vertices per face");
            }

            for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++)
            {
                model.faces.push_back(prop.list_values[n].v_uint);
            }
        }
        else
        if (element->name == PLY_ELEM_VERTEX)
        {
            DMVector3 vert;
            vert.x = element->prop_map["x"].value.v_float;
            vert.y = element->prop_map["y"].value.v_float;
            vert.z = element->prop_map["z"].value.v_float;

            model.vertices.push_back(vert);

            if (element->checkProp("nx") &&
                element->checkProp("ny") &&
                element->checkProp("nz"))
            {
                DMVector3 normal;
                normal.x = element->prop_map["nx"].value.v_float;
                normal.y = element->prop_map["ny"].value.v_float;
                normal.z = element->prop_map["nz"].value.v_float;

                model.normals.push_back(normal);
            }
        }
    }

    dmMsg("Found %ld vertices, %ld normals, %ld faces\n",
        model.vertices.size(),
        model.normals.size(),
        model.faces.size() / 3);

    return true;
}