changeset 19:a329f0216491

Implement PLY file format parsing and extremely simplistic scene setup file format. Not finished yet.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 07 Nov 2019 20:15:33 +0200
parents b1e75c65016d
children 294c4c7943b5
files Makefile dmmodel.cpp dmmodel.h dmutil.cpp dmutil.h
diffstat 5 files changed, 1046 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Tue Nov 05 11:46:31 2019 +0200
+++ b/Makefile	Thu Nov 07 20:15:33 2019 +0200
@@ -3,13 +3,23 @@
 SDLGL_CFLAGS  ?= $(shell pkg-config --cflags sdl2 gl glu)
 SDLGL_LIBS ?= $(shell pkg-config --libs sdl2 gl glu)
 
-CFLAGS ?= -O3 -W -Wall -DGL_GLEXT_PROTOTYPES
+CFLAGS ?= -O3 -W -Wall -DGL_GLEXT_PROTOTYPES -std=c++11
 LDFLAGS ?= 
 
+CFLAGS += $(SDLGL_CFLAGS)
+LDFLAGS += $(SDLGL_LIBS)
+
 TARGETS = glxdragon$(BINEXT)
 
-glxdragon$(BINEXT): glxdragon.cpp
-	$(CXX) $(CFLAGS) $(SDLGL_CFLAGS) -o $@ $< $(LDFLAGS) $(SDLGL_LIBS)
+
+%.o: %.cpp %.h
+	$(CXX) $(CFLAGS) -c -o $@ $<
+
+%.o: %.cpp
+	$(CXX) $(CFLAGS) -c -o $@ $<
+
+glxdragon$(BINEXT): glxdragon.o
+	$(CXX) -o $@ $+ $(LDFLAGS)
 
 clean:
-	$(RM) $(TARGETS)
+	$(RM) $(TARGETS) *.o
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dmmodel.cpp	Thu Nov 07 20:15:33 2019 +0200
@@ -0,0 +1,709 @@
+//
+//
+//
+#include "dmmodel.h"
+
+
+static bool dmError(DMTextFileInfo &info, const std::string &msg)
+{
+    printf("ERROR: %s on line #%d: %s\n",
+        msg.c_str(), info.nline, info.line.c_str());
+    return false;
+}
+
+
+static bool dmSyntaxError(DMTextFileInfo &info, const std::string &msg)
+{
+    printf("ERROR: Syntax error on line #%d: %s\n",
+        info.nline, msg.c_str());
+    return false;
+}
+
+
+static bool dmReadLine(DMTextFileInfo &info)
+{
+    if (!std::getline(info.file, info.line))
+        return false;
+
+    info.nline++;
+
+    info.line = dmStrTrim(info.line);
+
+    return true;
+}
+
+
+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 dmError(info,
+                "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 dmSyntaxError(info,
+                    "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 (!dmReadLine(info))
+        return false;
+
+    // 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 dmSyntaxError(info,
+            "Expected N properties, got different number");
+    }
+
+    return true;
+}
+
+
+bool dmPLYReadPropertyValueBIN(DMPLYFileInfo &info, const DMPLYPropType ptype, DMPLYPropValue &pval)
+{
+    uint8_t tmp[8];
+    uint16_t tmpU16;
+    int16_t tmpS16;
+    uint32_t tmpU32;
+    int32_t tmpS32;
+
+    switch (ptype)
+    {
+        case PLY_TYPE_INT8:
+            info.file.read(reinterpret_cast<char *>(&tmp), 1);
+            pval.v_int = (int8_t) tmp[0];
+            break;
+
+        case PLY_TYPE_UINT8:
+            info.file.read(reinterpret_cast<char *>(&tmp), 1);
+            pval.v_uint = (uint8_t) tmp[0];
+            break;
+
+        case PLY_TYPE_INT16:
+            info.file.read(reinterpret_cast<char *>(&tmp), 2);
+            tmpS16 =
+                (tmp[0] << 8) |
+                (tmp[1]);
+            pval.v_int = tmpS16;
+            break;
+
+        case PLY_TYPE_UINT16:
+            info.file.read(reinterpret_cast<char *>(&tmp), 2);
+            tmpU16 =
+                (tmp[0] << 8) |
+                (tmp[1]);
+            pval.v_uint = tmpU16;
+            break;
+
+        case PLY_TYPE_INT32:
+            info.file.read(reinterpret_cast<char *>(&tmp), 4);
+            tmpS32 =
+                (tmp[0] << 24) |
+                (tmp[1] << 16) |
+                (tmp[2] << 8) |
+                (tmp[3]);
+
+            pval.v_int = tmpS32;
+            break;
+
+        case PLY_TYPE_UINT32:
+            info.file.read(reinterpret_cast<char *>(&tmp), 4);
+            tmpU32 =
+                (tmp[3] << 24) |
+                (tmp[2] << 16) |
+                (tmp[1] << 8) |
+                (tmp[0]);
+
+            pval.v_uint = tmpU32;
+            break;
+
+        case PLY_TYPE_FLOAT:
+            info.file.read(reinterpret_cast<char *>(&tmp), 4);
+            tmpU32 =
+                (tmp[0] << 24) |
+                (tmp[1] << 16) |
+                (tmp[2] << 8) |
+                (tmp[3]);
+
+            pval.v_uint = tmpU32;
+            break;
+
+        default:
+            return dmError(info,
+                "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 dmSyntaxError(info,
+                    "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 dmSyntaxError(info,
+            "Expected N properties, got different number");
+    }
+
+    return true;
+}
+
+
+bool DMModel::loadFromPLY(const std::string &filename)
+{
+    DMPLYFileInfo info;
+
+    return loadFromPLY(filename, info);
+}
+
+
+bool DMModel::loadFromPLY(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);
+
+    printf("INFO: Trying to read mesh from '%s'.\n",
+        info.filename.c_str());
+
+    if (!info.file.is_open())
+    {
+        printf("ERROR: Unable to open file '%s'.\n",
+            info.filename.c_str());
+        return false;
+    }
+
+    // Parse the PLY header
+    while (info.state >= 0)
+    {
+        // Read one line
+        if (!dmReadLine(info))
+            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 dmSyntaxError(info,
+                    "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")
+            {
+                printf("ERROR: 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 dmSyntaxError(info,
+                    "Expected a value for element key");
+            }
+
+            std::string &el_name = tokens[1], &el_value = tokens[2];
+
+            if (info.elem_map.count(el_name))
+            {
+                return dmSyntaxError(info,
+                    "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 dmSyntaxError(info,
+                    "Expected value for property");
+            }
+
+            const std::string
+                &pr_name = tokens.back(),
+                &pr_typename = tokens.at(1);
+
+            if (!info.element)
+            {
+                // Should not happen
+                return dmSyntaxError(info,
+                    "No element defined for property '"+ pr_name +"'?");
+            }
+
+            // Check if this property has been already defined
+            if (info.element->prop_map.count(pr_name))
+            {
+                return dmSyntaxError(info,
+                    "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 dmSyntaxError(info,
+                    "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 dmSyntaxError(info,
+                        "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 dmSyntaxError(info,
+                        "Invalid or unsupported property type(s)");
+                }
+            }
+        }
+        else
+        if (info.state > 0 // && (key == "comment" || key == "obj_info")
+            )
+        {
+            // Ignore comments
+            // .. and unknown keys
+        }
+        else
+        {
+            return dmSyntaxError(info,
+                "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
+        )
+    {
+        printf("ERROR: PLY file did not contain expected information.\n");
+        return false;
+    }
+
+    nvertices = info.elem_map[PLY_ELEM_VERTEX].value;
+    nfaces    = info.elem_map[PLY_ELEM_FACE].value;
+
+
+    if (nvertices < 3 || nfaces < 1)
+    {
+        printf("ERROR: Invalid nvertices (%d) and/or nfaces (%d).\n",
+            nvertices, nfaces);
+        return false;
+    }
+
+    printf("INFO: Should have %d vertices, %d faces\n",
+        nvertices, nfaces);
+
+    // Pre-allocate space
+    vertices.reserve(nvertices);
+    normals.reserve(nvertices);
+    faces.reserve(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 dmSyntaxError(info,
+                    "Expected 3 vertices per face");
+            }
+
+            for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++)
+            {
+                faces.push_back(prop.list_values[n].v_uint);
+            }
+        }
+        else
+        if (element->name == PLY_ELEM_VERTEX)
+        {
+            DMVertex 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;
+
+            vertices.push_back(vert);
+
+            if (element->checkProp("nx") &&
+                element->checkProp("ny") &&
+                element->checkProp("nz"))
+            {
+                DMVertex 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;
+
+                normals.push_back(normal);
+            }
+        }
+    }
+
+    printf("INFO: Found %ld vertices, %ld normals, %ld faces\n",
+        vertices.size(),
+        normals.size(),
+        faces.size() / 3);
+
+    return true;
+}
+
+
+bool DMSimpleScene::loadInfo(const std::string &filename)
+{
+    DMTextFileInfo info;
+
+    info.filename = filename;
+    info.nline = info.state = 0;
+    info.file.open(info.filename.c_str(), std::fstream::in | std::fstream::binary);
+
+    printf("INFO: Trying to read scene data from '%s'.\n",
+        info.filename.c_str());
+
+    if (!info.file.is_open())
+    {
+        printf("ERROR: Unable to open file '%s'.\n",
+            info.filename.c_str());
+        return false;
+    }
+
+    while (info.state >= 0)
+    {
+        // Read one line
+        if (!dmReadLine(info))
+        {
+            info.state = -1;
+            break;
+        }
+
+        // Skip empty lines and comments
+        if (info.line.empty() ||
+            info.line[0] == '#' ||
+            info.line[0] == ';')
+            continue;
+
+        // Split key and values
+        std::vector<std::string> tokens = dmStrSplit(info.line);
+        std::string &key = tokens[0];
+
+        if (key == "color")
+        {
+            DMColor color;
+            color.alpha = 0xff;
+
+            if (tokens.size() == 2)
+            {
+                color.r = color.g = color.b = std::stoi(tokens[1], 0, 0);
+            }
+            else
+            if (tokens.size() == 4 || tokens.size() == 5)
+            {
+                color.r = std::stoi(tokens[1], 0, 0);
+                color.g = std::stoi(tokens[2], 0, 0);
+                color.b = std::stoi(tokens[3], 0, 0);
+
+                if (tokens.size() == 5)
+                    color.alpha = std::stoi(tokens[4], 0, 0);
+            }
+            else
+            {
+                return dmSyntaxError(info,
+                    "Expected color values <value> or <red> <green> <blue> [<alpha>]");
+            }
+
+            model.color = color;
+        }
+        else
+        if (key == "translate" || key == "rotate" || key == "scale" ||
+            key == "camera_pos" || key == "camera_at")
+        {
+            DMVertex vec;
+
+            if (tokens.size() == 2)
+            {
+                vec.x = vec.y = vec.z = std::stof(tokens[1]);
+            }
+            else
+            if (tokens.size() == 4)
+            {
+                vec.x = std::stof(tokens[1]);
+                vec.y = std::stof(tokens[2]);
+                vec.z = std::stof(tokens[3]);
+            }
+            else
+            {
+                return dmSyntaxError(info,
+                    "Expected vector value for '"+ key +"'");
+            }
+
+            if (key == "translate")
+                model.translate = vec;
+            else
+            if (key == "rotate")
+                model.rotate = vec;
+            else
+            if (key == "scale")
+                model.scale = vec;
+            else
+            if (key == "camera_pos")
+                camera.pos = vec;
+            else
+            if (key == "camera_at")
+                camera.lookAt = vec;
+        }
+        else
+        if (key == "light")
+        {
+            if (tokens.size() == 4)
+            {
+                DMVertex pos;
+                pos.x = std::stof(tokens[1]);
+                pos.y = std::stof(tokens[2]);
+                pos.z = std::stof(tokens[3]);
+            }
+            else
+            {
+                return dmSyntaxError(info,
+                    "Expected light definition as <x> <y> <z> ");
+            }
+        }
+        else
+        {
+            return dmSyntaxError(info,
+                "Unexpected key '"+ key +"'");
+        }
+    }
+
+    model.printInfo();
+
+    return true;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dmmodel.h	Thu Nov 07 20:15:33 2019 +0200
@@ -0,0 +1,187 @@
+//
+//
+//
+#ifndef DMMODEL_H
+#define DMMODEL_H 1
+
+#include "dmutil.h"
+#include <cstdint>
+#include <fstream>
+#include <unordered_map>
+
+
+#define PLY_PROP_VERTEX_INDICES  "vertex_indices"
+#define PLY_ELEM_FACE            "face"
+#define PLY_ELEM_VERTEX          "vertex"
+
+
+enum DMPLYFormat
+{
+    PLY_FMT_UNKNOWN,
+    PLY_FMT_ASCII,
+    PLY_FMT_BIN_LE,
+    PLY_FMT_BIN_BE
+};
+
+
+enum DMPLYPropType
+{
+    PLY_TYPE_NONE,
+    PLY_TYPE_LIST,
+
+    PLY_TYPE_UINT8,
+    PLY_TYPE_INT8,
+    PLY_TYPE_INT16,
+    PLY_TYPE_UINT16,
+    PLY_TYPE_INT32,
+    PLY_TYPE_UINT32,
+    PLY_TYPE_FLOAT,
+    PLY_TYPE_DOUBLE
+};
+
+
+/* Structures
+ */
+union DMPLYPropValue
+{
+    double v_double;
+    float v_float;
+    unsigned int v_uint;
+    int v_int;
+};
+
+
+struct DMPLYFileProperty
+{
+    std::string name;
+    DMPLYPropType
+        type,
+        list_num_type,
+        list_values_type;
+
+    DMPLYPropValue value, list_num_value;
+    std::vector<DMPLYPropValue> list_values;
+};
+
+
+struct DMPLYFileElement
+{
+    int value;
+    std::string name;
+
+    std::unordered_map<std::string, DMPLYFileProperty> prop_map;
+    std::vector<DMPLYFileProperty *> properties;
+
+    DMPLYFileProperty *checkProp(const std::string &prop)
+    {
+        if (prop_map.count(prop))
+            return &prop_map[prop];
+        else
+            return 0;
+    }
+};
+
+
+struct DMTextFileInfo
+{
+    int nline, state;
+    std::string filename;
+    std::string line;
+    std::ifstream file;
+};
+
+
+struct DMPLYFileInfo : DMTextFileInfo
+{
+    DMPLYFormat format;
+
+    std::unordered_map<std::string, DMPLYFileElement> elem_map;
+    std::vector<DMPLYFileElement *> elements;
+    DMPLYFileElement *element;
+
+    DMPLYFileInfo()
+    {
+        element = 0;
+        format = PLY_FMT_UNKNOWN;
+    }
+
+    DMPLYFileElement *checkElem(const std::string &elem)
+    {
+        if (elem_map.count(elem))
+            return &elem_map[elem];
+        else
+            return 0;
+    }
+};
+
+
+struct DMColor
+{
+    int r, g, b, alpha;
+};
+
+
+struct DMVertex
+{
+    float x, y, z;
+};
+
+
+struct DMModel
+{
+    int nvertices, nfaces;
+    std::vector<DMVertex> vertices, normals;
+    std::vector<unsigned int> faces;
+
+    DMColor color;
+    DMVertex translate, scale, rotate;
+
+    unsigned int id_prog, id_fs, id_vs;
+
+    bool loadFromPLY(const std::string &filename);
+    bool loadFromPLY(const std::string &filename, DMPLYFileInfo &info);
+
+    void printInfo()
+    {
+        printf(
+            "MODEL: scale <%1.5f, %1.5f, %1.5f>\n"
+            "MODEL: translate <%1.5f, %1.5f, %1.5f>\n"
+            "MODEL: rotate <%1.5f, %1.5f, %1.5f>\n"
+            ,
+            scale.x, scale.y, scale.z,
+            translate.x, translate.y, translate.z,
+            rotate.x, rotate.y, rotate.z);
+    }
+
+    DMModel()
+    {
+        translate.x = translate.y = translate.z = 0;
+        rotate.x = rotate.y = rotate.z = 0;
+        scale.x = scale.y = scale.z = 1;
+    }
+};
+
+
+struct DMLight
+{
+    DMVertex pos;
+    float ambient[4], diffuse[4], specular[4];
+};
+
+
+struct DMCamera
+{
+    DMVertex pos, lookAt;
+};
+
+
+struct DMSimpleScene
+{
+    DMModel model;
+    DMCamera camera;
+    std::vector<DMLight> lights;
+
+    bool loadInfo(const std::string &filename);
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dmutil.cpp	Thu Nov 07 20:15:33 2019 +0200
@@ -0,0 +1,109 @@
+//
+//
+//
+#include "dmutil.h"
+#include <fstream>
+
+
+std::string dmStrLTrim(const std::string& str, const std::string& delim)
+{
+    return str.substr(str.find_first_not_of(delim));
+}
+
+
+std::string dmStrRTrim(const std::string& str, const std::string& delim)
+{
+    return str.substr(0, str.find_last_not_of(delim));
+}
+
+
+std::string dmStrTrim(const std::string& str, const std::string& delim)
+{
+    if (str.empty())
+        return str;
+
+    size_t start = str.find_first_not_of(delim);
+    return str.substr(start, str.find_last_not_of(delim) - start + 1);
+}
+
+
+std::vector<std::string> dmStrSplit(const std::string& str, const std::string& delim)
+{
+    std::vector<std::string> result;
+    size_t oldpos = 0, newpos;
+
+    do
+    {
+        newpos = str.find_first_of(delim, oldpos);
+        std::string tmp = dmStrTrim(str.substr(oldpos, newpos - oldpos));
+
+        if (!tmp.empty())
+            result.push_back(tmp);
+
+        oldpos = newpos + 1;
+    } while (newpos != std::string::npos);
+
+    return result;
+}
+
+
+std::string dmStrJoin(const std::vector<std::string> &list, const std::string &delim)
+{
+    switch (list.size())
+    {
+        case 0:
+            return "";
+
+        case 1:
+            return list[0];
+
+        default:
+            std::string result;
+            bool first = true;
+            for (const auto &elem : list)
+            {
+                if (!first)
+                    result += delim;
+                else
+                    first = false;
+                result += elem;
+            }
+            return result;
+    }
+}
+
+
+bool dmReadText(const std::string &filename, std::string &buf, const int maxSize)
+{
+    std::ifstream in(filename.c_str(), std::fstream::in);
+
+    if (!in.is_open())
+    {
+        printf("ERROR: Unable to open file '%s'.\n",
+            filename.c_str());
+        return false;
+    }
+
+    in.seekg(0, std::ios::end);
+
+    if (in.tellg() > maxSize)
+    {
+        printf("ERROR: File '%s' is too large.\n",
+            filename.c_str());
+        return false;
+    }
+
+    buf.reserve(in.tellg());
+    in.seekg(0, std::ios::beg);
+
+    buf.assign((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
+
+    return true;
+}
+
+
+bool dmFileExists(const std::string &filename, std::ios_base::openmode mode)
+{
+    std::ifstream infile(filename.c_str(), mode);
+    return infile.good();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dmutil.h	Thu Nov 07 20:15:33 2019 +0200
@@ -0,0 +1,27 @@
+//
+//
+//
+#ifndef DMUTIL_H
+#define DMUTIL_H 1
+
+#include <string>
+#include <vector>
+#include <cstdio>
+#include <iostream>
+
+
+#define SET_WHITESPACE           "\t\n\v\f\r "
+
+
+std::string dmStrLTrim(const std::string& str, const std::string& delim = SET_WHITESPACE);
+std::string dmStrRTrim(const std::string& str, const std::string& delim = SET_WHITESPACE);
+std::string dmStrTrim(const std::string& str, const std::string& delim = SET_WHITESPACE);
+
+std::vector<std::string> dmStrSplit(const std::string& str, const std::string& delim = SET_WHITESPACE);
+std::string dmStrJoin(const std::vector<std::string> &list, const std::string &delim);
+
+bool dmReadText(const std::string &filename, std::string &buf, const int maxSize);
+bool dmFileExists(const std::string &filename, std::ios_base::openmode mode = std::ios_base::in);
+
+
+#endif