changeset 61:7b138613e2fc

Rename dmmodel.* to dmscene.*
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 14 Dec 2019 14:08:51 +0200
parents f645e38e3157
children baccf2044289
files Makefile.gen dmmodel.cpp dmmodel.h dmscene.cpp dmscene.h gldragon.cpp ply2bin.cpp
diffstat 7 files changed, 1035 insertions(+), 1035 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.gen	Thu Dec 12 17:51:53 2019 +0200
+++ b/Makefile.gen	Sat Dec 14 14:08:51 2019 +0200
@@ -44,10 +44,10 @@
 	$(COMPILE_CXX_OBJ)
 
 
-$(BINPATH)gldragon$(BINEXT): $(addprefix $(OBJPATH), gldragon.o dmmodel.o dmutil.o)
+$(BINPATH)gldragon$(BINEXT): $(addprefix $(OBJPATH), gldragon.o dmscene.o dmutil.o)
 	$(LINK_CXX_BIN) $(LDFLAGS) $(SDL_LDFLAGS) $(GL_LDFLAGS)
 
-$(BINPATH)ply2bin$(BINEXT): $(addprefix $(OBJPATH), ply2bin.o dmmodel.o dmutil.o)
+$(BINPATH)ply2bin$(BINEXT): $(addprefix $(OBJPATH), ply2bin.o dmscene.o dmutil.o)
 	$(LINK_CXX_BIN) $(LDFLAGS) $(TOOL_LDFLAGS)
 
 
--- a/dmmodel.cpp	Thu Dec 12 17:51:53 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,811 +0,0 @@
-//
-// GLDragon - OpenGL PLY model viewer / simple benchmark
-// -- Scene and model handling + 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 "dmmodel.h"
-#include <SDL_endian.h>
-
-
-static bool dmTextError(DMTextFileInfo &info, const std::string &msg)
-{
-    dmError("%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)
-{
-    dmError("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 dmTextError(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 dmTextError(info,
-            "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 dmSyntaxError(info,
-            "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 dmTextError(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);
-
-    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 (!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")
-            {
-                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 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
-        )
-    {
-        dmError("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)
-    {
-        dmError("Invalid nvertices (%d) and/or nfaces (%d).\n",
-            nvertices, nfaces);
-        return false;
-    }
-
-    dmMsg("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)
-        {
-            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;
-
-            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;
-
-                normals.push_back(normal);
-            }
-        }
-    }
-
-    dmMsg("Found %ld vertices, %ld normals, %ld faces\n",
-        vertices.size(),
-        normals.size(),
-        faces.size() / 3);
-
-    return true;
-}
-
-
-bool dmParseVector(DMTextFileInfo &info, const std::vector<std::string> tokens, const size_t offs, DMVector3 &vec)
-{
-    if (tokens.size() == offs + 1)
-    {
-        vec.x = vec.y = vec.z = std::stof(tokens[offs]);
-    }
-    else
-    if (tokens.size() == offs + 3)
-    {
-        vec.x = std::stof(tokens[offs]);
-        vec.y = std::stof(tokens[offs + 1]);
-        vec.z = std::stof(tokens[offs + 2]);
-    }
-    else
-    {
-        return dmSyntaxError(info,
-            "Expected 1/3 value vector for '"+ *info.key +"'");
-    }
-
-    return true;
-}
-
-
-bool dmParseVector(DMTextFileInfo &info, const std::vector<std::string> tokens, const size_t offs, DMVector4 &vec)
-{
-    if (tokens.size() == offs + 1)
-    {
-        vec.p.x = vec.p.y = vec.p.z = std::stof(tokens[offs]);
-    }
-    else
-    if (tokens.size() == offs + 3 || tokens.size() == offs + 4)
-    {
-        vec.p.x = std::stof(tokens[offs]);
-        vec.p.y = std::stof(tokens[offs + 1]);
-        vec.p.z = std::stof(tokens[offs + 2]);
-
-        if (tokens.size() == offs + 4)
-            vec.p.w = std::stof(tokens[offs + 3]);
-    }
-    else
-    {
-        return dmSyntaxError(info,
-            "Expected 1/3/4 value vector for '"+ *info.key +"'");
-    }
-
-    return true;
-}
-
-
-bool DMSimpleScene::loadInfo(const std::string &filename)
-{
-    DMTextFileInfo info;
-    DMModel *model = 0;
-    DMLight *light = 0;
-    DMVector4 *ppos = 0, *ppointAt = 0;
-    DMMaterial *pmat = 0;
-
-    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 scene data 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;
-    }
-
-    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];
-        info.key = &key;
-
-        if (key == "model")
-        {
-            DMModel newmodel;
-            if (tokens.size() != 2)
-            {
-                return dmSyntaxError(info,
-                    "Keyword model expects a filename argument");
-            }
-
-            models.push_back(newmodel);
-            model = &models.back();
-            model->modelFile = tokens[1];
-            pmat = &model->material;
-            info.state = 1;
-        }
-        else
-        if (info.state == 1 && (key == "shaderfile"))
-        {
-            if (tokens.size() != 3)
-            {
-                return dmSyntaxError(info,
-                    "Keyword shaderfile expects shader type (fs, vs) and filename arguments");
-            }
-
-            std::string
-                &shtype = tokens[1],
-                &shfile = tokens[2];
-
-            if (shtype == "fs")
-                model->fragShaderFile = shfile;
-            else
-            if (shtype == "vs")
-                model->vertShaderFile = shfile;
-            else
-            {
-                return dmSyntaxError(info,
-                    "Invalid shaderfile type '"+ shtype +"'");
-            }
-        }
-        else
-        if (info.state == 1 && (key == "translate" || key == "rotate" || key == "scale"))
-        {
-            DMVector3 vec;
-
-            if (!dmParseVector(info, tokens, 1, vec))
-                return false;
-
-            if (!model)
-                return false;
-
-            if (key == "translate")
-            {
-                model->translate = vec;
-                model->translateSet = true;
-            }
-            else
-            if (key == "rotate")
-            {
-                model->rotate = vec;
-                model->rotateSet = true;
-            }
-            else
-            if (key == "scale")
-            {
-                model->scale = vec;
-                model->scaleSet = true;
-            }
-        }
-        else
-        if (info.state == 1 && key == "shininess")
-        {
-            if (tokens.size() != 2)
-            {
-                return dmSyntaxError(info,
-                    "Expected argument for shininess");
-            }
-
-            model->material.shininess = std::stoi(tokens[1], 0, 0);
-        }
-        else
-        if (key == "light")
-        {
-            DMLight newlight;
-
-            if (lights.size() >= 4)
-            {
-                return dmTextError(info,
-                "Too many lights defined (max 4)");
-            }
-
-            lights.push_back(newlight);
-            light = &lights.back();
-            ppos = &light->position;
-            ppointAt = &light->pointAt;
-            pmat = &light->color;
-            info.state = 2;
-        }
-        else
-        if ((info.state == 1 || info.state == 2) &&
-            (key == "ambient" || key == "diffuse" || key == "specular"))
-        {
-            DMVector4 val;
-            val.c.a = 1.0f;
-
-            if (!dmParseVector(info, tokens, 1, val))
-                return false;
-
-            if (key == "ambient")
-                pmat->ambient = val;
-            else
-            if (key == "diffuse")
-                pmat->diffuse = val;
-            else
-            if (key == "specular")
-                pmat->specular = val;
-        }
-        else
-        if (key == "camera")
-        {
-            info.state = 3;
-
-            ppos = &camera.position;
-            ppointAt = &camera.pointAt;
-        }
-        else
-        if ((info.state == 3 || info.state == 2) &&
-            (key == "position" || key == "pos" || key == "point_at"))
-        {
-            DMVector4 vec;
-            vec.p.w = 0;
-
-            if (!dmParseVector(info, tokens, 1, vec))
-                return false;
-
-            if (key == "position" || key == "pos")
-                *ppos = vec;
-            else
-            if (key == "point_at")
-                *ppointAt = vec;
-        }
-        else
-        {
-            return dmSyntaxError(info,
-                "Unexpected key '"+ key +"'");
-        }
-    }
-
-    return true;
-}
--- a/dmmodel.h	Thu Dec 12 17:51:53 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,220 +0,0 @@
-//
-// GLDragon - OpenGL PLY model viewer / simple benchmark
-// -- Scene and model handling + 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.
-//
-#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;
-    std::string *key;
-};
-
-
-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 DMVector3
-{
-    float x, y, z;
-};
-
-
-union DMVector4
-{
-    struct { float x, y, z, w; } p;
-    struct { float r, g, b, a; } c;
-    float values[4];
-};
-
-
-struct DMMaterial
-{
-    DMVector4 ambient, diffuse, specular;
-    int shininess;
-};
-
-
-struct DMModel
-{
-    int nvertices, nfaces;
-    std::vector<DMVector3> vertices, normals;
-    std::vector<unsigned int> faces;
-
-    DMMaterial material;
-    DMVector3 translate, scale, rotate;
-    bool translateSet, scaleSet, rotateSet;
-
-    unsigned int id_prog, id_fs, id_vs;
-
-    std::string
-        modelFile,
-        fragShaderFile, vertShaderFile,
-        fragShaderStr, vertShaderStr;
-
-    bool loadFromPLY(const std::string &filename);
-    bool loadFromPLY(const std::string &filename, DMPLYFileInfo &info);
-
-    DMModel()
-    {
-        nfaces = nvertices = 0;
-
-        translate.x = translate.y = translate.z = 0;
-        rotate.x = rotate.y = rotate.z = 0;
-        scale.x = scale.y = scale.z = 0;
-        translateSet = rotateSet = scaleSet = false;
-
-        material.diffuse.c.r = material.diffuse.p.z = 0.56471f;
-        material.diffuse.c.g = 0.5f;
-        material.diffuse.c.a = 1.0f;
-
-        material.specular.c.r = material.specular.c.g = material.specular.c.b = 0.8f;
-        material.specular.c.a = 1.0f;
-
-        material.shininess = 96;
-    }
-};
-
-
-struct DMLight
-{
-    DMMaterial color;
-    DMVector4 position, pointAt;
-
-    DMLight()
-    {
-        color.ambient.c.r  = color.ambient.c.g  = color.ambient.p.z  = 0.2f; color.ambient.c.a  = 1.0f;
-        color.diffuse.c.r  = color.diffuse.c.g  = color.diffuse.p.z  = 0.8f; color.diffuse.c.a  = 1.0f;
-        color.specular.c.r = color.specular.c.g = color.specular.p.z = 0.5f; color.specular.c.a = 1.0f;
-
-        position.p.x = 10.0f;
-        position.p.y = 10.0f;
-        position.p.z =  0.0f;
-        position.p.w =  0.0f;
-    }
-};
-
-
-struct DMCamera
-{
-    DMVector4 position, pointAt;
-};
-
-
-struct DMSimpleScene
-{
-    DMCamera camera;
-    std::vector<DMLight> lights;
-    std::vector<DMModel> models;
-
-    bool loadInfo(const std::string &filename);
-};
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dmscene.cpp	Sat Dec 14 14:08:51 2019 +0200
@@ -0,0 +1,811 @@
+//
+// GLDragon - OpenGL PLY model viewer / simple benchmark
+// -- Scene and model handling + 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 "dmscene.h"
+#include <SDL_endian.h>
+
+
+static bool dmTextError(DMTextFileInfo &info, const std::string &msg)
+{
+    dmError("%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)
+{
+    dmError("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 dmTextError(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 dmTextError(info,
+            "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 dmSyntaxError(info,
+            "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 dmTextError(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);
+
+    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 (!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")
+            {
+                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 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
+        )
+    {
+        dmError("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)
+    {
+        dmError("Invalid nvertices (%d) and/or nfaces (%d).\n",
+            nvertices, nfaces);
+        return false;
+    }
+
+    dmMsg("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)
+        {
+            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;
+
+            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;
+
+                normals.push_back(normal);
+            }
+        }
+    }
+
+    dmMsg("Found %ld vertices, %ld normals, %ld faces\n",
+        vertices.size(),
+        normals.size(),
+        faces.size() / 3);
+
+    return true;
+}
+
+
+bool dmParseVector(DMTextFileInfo &info, const std::vector<std::string> tokens, const size_t offs, DMVector3 &vec)
+{
+    if (tokens.size() == offs + 1)
+    {
+        vec.x = vec.y = vec.z = std::stof(tokens[offs]);
+    }
+    else
+    if (tokens.size() == offs + 3)
+    {
+        vec.x = std::stof(tokens[offs]);
+        vec.y = std::stof(tokens[offs + 1]);
+        vec.z = std::stof(tokens[offs + 2]);
+    }
+    else
+    {
+        return dmSyntaxError(info,
+            "Expected 1/3 value vector for '"+ *info.key +"'");
+    }
+
+    return true;
+}
+
+
+bool dmParseVector(DMTextFileInfo &info, const std::vector<std::string> tokens, const size_t offs, DMVector4 &vec)
+{
+    if (tokens.size() == offs + 1)
+    {
+        vec.p.x = vec.p.y = vec.p.z = std::stof(tokens[offs]);
+    }
+    else
+    if (tokens.size() == offs + 3 || tokens.size() == offs + 4)
+    {
+        vec.p.x = std::stof(tokens[offs]);
+        vec.p.y = std::stof(tokens[offs + 1]);
+        vec.p.z = std::stof(tokens[offs + 2]);
+
+        if (tokens.size() == offs + 4)
+            vec.p.w = std::stof(tokens[offs + 3]);
+    }
+    else
+    {
+        return dmSyntaxError(info,
+            "Expected 1/3/4 value vector for '"+ *info.key +"'");
+    }
+
+    return true;
+}
+
+
+bool DMSimpleScene::loadInfo(const std::string &filename)
+{
+    DMTextFileInfo info;
+    DMModel *model = 0;
+    DMLight *light = 0;
+    DMVector4 *ppos = 0, *ppointAt = 0;
+    DMMaterial *pmat = 0;
+
+    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 scene data 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;
+    }
+
+    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];
+        info.key = &key;
+
+        if (key == "model")
+        {
+            DMModel newmodel;
+            if (tokens.size() != 2)
+            {
+                return dmSyntaxError(info,
+                    "Keyword model expects a filename argument");
+            }
+
+            models.push_back(newmodel);
+            model = &models.back();
+            model->modelFile = tokens[1];
+            pmat = &model->material;
+            info.state = 1;
+        }
+        else
+        if (info.state == 1 && (key == "shaderfile"))
+        {
+            if (tokens.size() != 3)
+            {
+                return dmSyntaxError(info,
+                    "Keyword shaderfile expects shader type (fs, vs) and filename arguments");
+            }
+
+            std::string
+                &shtype = tokens[1],
+                &shfile = tokens[2];
+
+            if (shtype == "fs")
+                model->fragShaderFile = shfile;
+            else
+            if (shtype == "vs")
+                model->vertShaderFile = shfile;
+            else
+            {
+                return dmSyntaxError(info,
+                    "Invalid shaderfile type '"+ shtype +"'");
+            }
+        }
+        else
+        if (info.state == 1 && (key == "translate" || key == "rotate" || key == "scale"))
+        {
+            DMVector3 vec;
+
+            if (!dmParseVector(info, tokens, 1, vec))
+                return false;
+
+            if (!model)
+                return false;
+
+            if (key == "translate")
+            {
+                model->translate = vec;
+                model->translateSet = true;
+            }
+            else
+            if (key == "rotate")
+            {
+                model->rotate = vec;
+                model->rotateSet = true;
+            }
+            else
+            if (key == "scale")
+            {
+                model->scale = vec;
+                model->scaleSet = true;
+            }
+        }
+        else
+        if (info.state == 1 && key == "shininess")
+        {
+            if (tokens.size() != 2)
+            {
+                return dmSyntaxError(info,
+                    "Expected argument for shininess");
+            }
+
+            model->material.shininess = std::stoi(tokens[1], 0, 0);
+        }
+        else
+        if (key == "light")
+        {
+            DMLight newlight;
+
+            if (lights.size() >= 4)
+            {
+                return dmTextError(info,
+                "Too many lights defined (max 4)");
+            }
+
+            lights.push_back(newlight);
+            light = &lights.back();
+            ppos = &light->position;
+            ppointAt = &light->pointAt;
+            pmat = &light->color;
+            info.state = 2;
+        }
+        else
+        if ((info.state == 1 || info.state == 2) &&
+            (key == "ambient" || key == "diffuse" || key == "specular"))
+        {
+            DMVector4 val;
+            val.c.a = 1.0f;
+
+            if (!dmParseVector(info, tokens, 1, val))
+                return false;
+
+            if (key == "ambient")
+                pmat->ambient = val;
+            else
+            if (key == "diffuse")
+                pmat->diffuse = val;
+            else
+            if (key == "specular")
+                pmat->specular = val;
+        }
+        else
+        if (key == "camera")
+        {
+            info.state = 3;
+
+            ppos = &camera.position;
+            ppointAt = &camera.pointAt;
+        }
+        else
+        if ((info.state == 3 || info.state == 2) &&
+            (key == "position" || key == "pos" || key == "point_at"))
+        {
+            DMVector4 vec;
+            vec.p.w = 0;
+
+            if (!dmParseVector(info, tokens, 1, vec))
+                return false;
+
+            if (key == "position" || key == "pos")
+                *ppos = vec;
+            else
+            if (key == "point_at")
+                *ppointAt = vec;
+        }
+        else
+        {
+            return dmSyntaxError(info,
+                "Unexpected key '"+ key +"'");
+        }
+    }
+
+    return true;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dmscene.h	Sat Dec 14 14:08:51 2019 +0200
@@ -0,0 +1,220 @@
+//
+// GLDragon - OpenGL PLY model viewer / simple benchmark
+// -- Scene and model handling + 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.
+//
+#ifndef DMSCENE_H
+#define DMSCENE_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;
+    std::string *key;
+};
+
+
+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 DMVector3
+{
+    float x, y, z;
+};
+
+
+union DMVector4
+{
+    struct { float x, y, z, w; } p;
+    struct { float r, g, b, a; } c;
+    float values[4];
+};
+
+
+struct DMMaterial
+{
+    DMVector4 ambient, diffuse, specular;
+    int shininess;
+};
+
+
+struct DMModel
+{
+    int nvertices, nfaces;
+    std::vector<DMVector3> vertices, normals;
+    std::vector<unsigned int> faces;
+
+    DMMaterial material;
+    DMVector3 translate, scale, rotate;
+    bool translateSet, scaleSet, rotateSet;
+
+    unsigned int id_prog, id_fs, id_vs;
+
+    std::string
+        modelFile,
+        fragShaderFile, vertShaderFile,
+        fragShaderStr, vertShaderStr;
+
+    bool loadFromPLY(const std::string &filename);
+    bool loadFromPLY(const std::string &filename, DMPLYFileInfo &info);
+
+    DMModel()
+    {
+        nfaces = nvertices = 0;
+
+        translate.x = translate.y = translate.z = 0;
+        rotate.x = rotate.y = rotate.z = 0;
+        scale.x = scale.y = scale.z = 0;
+        translateSet = rotateSet = scaleSet = false;
+
+        material.diffuse.c.r = material.diffuse.p.z = 0.56471f;
+        material.diffuse.c.g = 0.5f;
+        material.diffuse.c.a = 1.0f;
+
+        material.specular.c.r = material.specular.c.g = material.specular.c.b = 0.8f;
+        material.specular.c.a = 1.0f;
+
+        material.shininess = 96;
+    }
+};
+
+
+struct DMLight
+{
+    DMMaterial color;
+    DMVector4 position, pointAt;
+
+    DMLight()
+    {
+        color.ambient.c.r  = color.ambient.c.g  = color.ambient.p.z  = 0.2f; color.ambient.c.a  = 1.0f;
+        color.diffuse.c.r  = color.diffuse.c.g  = color.diffuse.p.z  = 0.8f; color.diffuse.c.a  = 1.0f;
+        color.specular.c.r = color.specular.c.g = color.specular.p.z = 0.5f; color.specular.c.a = 1.0f;
+
+        position.p.x = 10.0f;
+        position.p.y = 10.0f;
+        position.p.z =  0.0f;
+        position.p.w =  0.0f;
+    }
+};
+
+
+struct DMCamera
+{
+    DMVector4 position, pointAt;
+};
+
+
+struct DMSimpleScene
+{
+    DMCamera camera;
+    std::vector<DMLight> lights;
+    std::vector<DMModel> models;
+
+    bool loadInfo(const std::string &filename);
+};
+
+#endif
--- a/gldragon.cpp	Thu Dec 12 17:51:53 2019 +0200
+++ b/gldragon.cpp	Sat Dec 14 14:08:51 2019 +0200
@@ -12,7 +12,7 @@
 #include <GL/glu.h>
 
 #include "dmutil.h"
-#include "dmmodel.h"
+#include "dmscene.h"
 
 
 /* Default settings etc. constants
--- a/ply2bin.cpp	Thu Dec 12 17:51:53 2019 +0200
+++ b/ply2bin.cpp	Sat Dec 14 14:08:51 2019 +0200
@@ -12,7 +12,7 @@
 #define SDL_main main
 
 #include "dmutil.h"
-#include "dmmodel.h"
+#include "dmscene.h"
 
 
 bool dmFWriteFloatLE(FILE *fh, const float val)