changeset 21:1404dfcee7b8

More work on scenefile and model loading support. Can now load PLY models and simple scene definition files. Converted dragon mesh to binary PLY format.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 22 Nov 2019 03:03:52 +0200
parents 294c4c7943b5
children 03b86b9c2f29
files Makefile dmmodel.cpp dmmodel.h dmutil.cpp dmutil.h dragon.frag dragon.info dragon.mesh dragon.ply dragon.scene dragon.vert glxdragon.cpp shader.frag shader.vert
diffstat 14 files changed, 325 insertions(+), 316 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Fri Nov 22 00:14:16 2019 +0200
+++ b/Makefile	Fri Nov 22 03:03:52 2019 +0200
@@ -18,7 +18,8 @@
 %.o: %.cpp
 	$(CXX) $(CFLAGS) -c -o $@ $<
 
-glxdragon$(BINEXT): glxdragon.o
+
+glxdragon$(BINEXT): glxdragon.o dmmodel.o dmutil.o
 	$(CXX) -o $@ $+ $(LDFLAGS)
 
 clean:
--- a/dmmodel.cpp	Fri Nov 22 00:14:16 2019 +0200
+++ b/dmmodel.cpp	Fri Nov 22 03:03:52 2019 +0200
@@ -2,6 +2,7 @@
 //
 //
 #include "dmmodel.h"
+#include <SDL_endian.h>
 
 
 static bool dmError(DMTextFileInfo &info, const std::string &msg)
@@ -139,7 +140,10 @@
 
     // Read one line
     if (!dmReadLine(info))
-        return false;
+    {
+        return dmError(info,
+            "Unexpected end of file");
+    }
 
     // Skip empty lines
     if (info.line.empty())
@@ -166,71 +170,53 @@
 
 bool dmPLYReadPropertyValueBIN(DMPLYFileInfo &info, const DMPLYPropType ptype, DMPLYPropValue &pval)
 {
-    uint8_t tmp[8];
+    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 *>(&tmp), 1);
-            pval.v_int = (int8_t) tmp[0];
+            info.file.read(reinterpret_cast<char *>(&tmpS8), 1);
+            pval.v_int = tmpS8;
             break;
 
         case PLY_TYPE_UINT8:
-            info.file.read(reinterpret_cast<char *>(&tmp), 1);
-            pval.v_uint = (uint8_t) tmp[0];
+            info.file.read(reinterpret_cast<char *>(&tmpU8), 1);
+            pval.v_uint = tmpU8;
             break;
 
         case PLY_TYPE_INT16:
-            info.file.read(reinterpret_cast<char *>(&tmp), 2);
-            tmpS16 =
-                (tmp[0] << 8) |
-                (tmp[1]);
+            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 *>(&tmp), 2);
-            tmpU16 =
-                (tmp[0] << 8) |
-                (tmp[1]);
+            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 *>(&tmp), 4);
-            tmpS32 =
-                (tmp[0] << 24) |
-                (tmp[1] << 16) |
-                (tmp[2] << 8) |
-                (tmp[3]);
-
+            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 *>(&tmp), 4);
-            tmpU32 =
-                (tmp[3] << 24) |
-                (tmp[2] << 16) |
-                (tmp[1] << 8) |
-                (tmp[0]);
-
+            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 *>(&tmp), 4);
-            tmpU32 =
-                (tmp[0] << 24) |
-                (tmp[1] << 16) |
-                (tmp[2] << 8) |
-                (tmp[3]);
-
-            pval.v_uint = tmpU32;
+            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:
@@ -548,7 +534,7 @@
         else
         if (element->name == PLY_ELEM_VERTEX)
         {
-            DMVertex vert;
+            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;
@@ -559,7 +545,7 @@
                 element->checkProp("ny") &&
                 element->checkProp("nz"))
             {
-                DMVertex normal;
+                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;
@@ -578,9 +564,91 @@
 }
 
 
+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)
+{
+    vec.w = 1.0f;
+
+    if (tokens.size() == offs + 1)
+    {
+        vec.x = vec.y = vec.z = std::stof(tokens[offs]);
+    }
+    else
+    if (tokens.size() == offs + 3 || tokens.size() == offs + 4)
+    {
+        vec.x = std::stof(tokens[offs]);
+        vec.y = std::stof(tokens[offs + 1]);
+        vec.z = std::stof(tokens[offs + 2]);
+
+        if (tokens.size() == offs + 4)
+            vec.w = std::stof(tokens[offs + 3]);
+    }
+    else
+    {
+        return dmSyntaxError(info,
+            "Expected 1/3/4 value vector for '"+ *info.key +"'");
+    }
+
+    return true;
+}
+
+
+bool dmParseColor(DMTextFileInfo &info, const std::vector<std::string> tokens, const size_t offs, DMColor &color)
+{
+    color.alpha = 0xff;
+
+    if (tokens.size() == offs + 1)
+    {
+        color.r = color.g = color.b = std::stoi(tokens[offs], 0, 0);
+    }
+    else
+    if (tokens.size() == offs + 3 || tokens.size() == offs + 4)
+    {
+        color.r = std::stoi(tokens[offs], 0, 0);
+        color.g = std::stoi(tokens[offs + 1], 0, 0);
+        color.b = std::stoi(tokens[offs + 2], 0, 0);
+
+        if (tokens.size() == offs + 4)
+            color.alpha = std::stoi(tokens[offs + 3], 0, 0);
+    }
+    else
+    {
+        return dmSyntaxError(info,
+            "Expected color values <value> or <red> <green> <blue> [<alpha>] for '"+ *info.key +"'");
+    }
+
+    return true;
+}
+
+
 bool DMSimpleScene::loadInfo(const std::string &filename)
 {
     DMTextFileInfo info;
+    DMModel *model = 0;
+    DMLight *light = 0;
+    DMVector3 *ppos = 0, *ppointAt = 0;
 
     info.filename = filename;
     info.nline = info.state = 0;
@@ -613,88 +681,103 @@
 
         // Split key and values
         std::vector<std::string> tokens = dmStrSplit(info.line);
-        std::string &key = tokens[0];
+        std::string key = tokens[0];
+        info.key = &key;
 
-        if (key == "color")
+        if (key == "model")
         {
-            DMColor color;
-            color.alpha = 0xff;
-
-            if (tokens.size() == 2)
+            DMModel newmodel;
+            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>]");
+                return dmError(info,
+                    "Keyword model expects a filename argument");
             }
 
-            model.color = color;
+            models.push_back(newmodel);
+            model = &models.back();
+            model->modelFile = tokens[1];
+            info.state = 1;
         }
         else
-        if (key == "translate" || key == "rotate" || key == "scale" ||
-            key == "camera_pos" || key == "camera_at")
+        if (info.state == 1 && key == "color")
         {
-            DMVertex vec;
+            if (!dmParseColor(info, tokens, 1, model->color))
+                return false;
+        }
+        else
+        if (info.state == 1 && (key == "translate" || key == "rotate" || key == "scale"))
+        {
+            DMVector3 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 (!dmParseVector(info, tokens, 1, vec))
+                return false;
+
+            if (!model)
+                return false;
 
             if (key == "translate")
-                model.translate = vec;
+                model->translate = vec;
             else
             if (key == "rotate")
-                model.rotate = vec;
+                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;
+                model->scale = vec;
         }
         else
         if (key == "light")
         {
-            if (tokens.size() == 4)
+            DMLight newlight;
+
+            if (lights.size() >= 4)
             {
-                DMVertex pos;
-                pos.x = std::stof(tokens[1]);
-                pos.y = std::stof(tokens[2]);
-                pos.z = std::stof(tokens[3]);
+                printf("ERROR: Too many lights defined.\n");
+                return false;
             }
+            lights.push_back(newlight);
+            light = &lights.back();
+            ppos = &light->pos;
+            ppointAt = &light->pointAt;
+            info.state = 2;
+        }
+        else
+        if (info.state == 2 && (key == "ambient" || key == "diffuse" || key == "specular"))
+        {
+            DMVector4 val;
+
+            if (!dmParseVector(info, tokens, 1, val))
+                return false;
+
+            if (key == "ambient")
+                light->ambient = val;
             else
-            {
-                return dmSyntaxError(info,
-                    "Expected light definition as <x> <y> <z> ");
-            }
+            if (key == "diffuse")
+                light->diffuse = val;
+            else
+            if (key == "specular")
+                light->specular = val;
+        }
+        else
+        if (key == "camera")
+        {
+            info.state = 3;
+
+            ppos = &camera.pos;
+            ppointAt = &camera.pointAt;
+        }
+        else
+        if ((info.state == 3 || info.state == 2) && (key == "pos" || key == "point_at"))
+        {
+            DMVector3 vec;
+
+            if (!dmParseVector(info, tokens, 1, vec))
+                return false;
+
+            if (key == "pos")
+                *ppos = vec;
+            else
+            if (key == "point_at")
+                *ppointAt = vec;
         }
         else
         {
@@ -703,7 +786,5 @@
         }
     }
 
-    model.printInfo();
-
     return true;
 }
--- a/dmmodel.h	Fri Nov 22 00:14:16 2019 +0200
+++ b/dmmodel.h	Fri Nov 22 03:03:52 2019 +0200
@@ -88,6 +88,7 @@
     std::string filename;
     std::string line;
     std::ifstream file;
+    std::string *key;
 };
 
 
@@ -121,23 +122,34 @@
 };
 
 
-struct DMVertex
+struct DMVector3
 {
     float x, y, z;
 };
 
 
+struct DMVector4
+{
+    float x, y, z, w;
+};
+
+
 struct DMModel
 {
     int nvertices, nfaces;
-    std::vector<DMVertex> vertices, normals;
+    std::vector<DMVector3> vertices, normals;
     std::vector<unsigned int> faces;
 
     DMColor color;
-    DMVertex translate, scale, rotate;
+    DMVector3 translate, scale, rotate;
 
     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);
 
@@ -155,6 +167,8 @@
 
     DMModel()
     {
+        nfaces = nvertices = 0;
+
         translate.x = translate.y = translate.z = 0;
         rotate.x = rotate.y = rotate.z = 0;
         scale.x = scale.y = scale.z = 1;
@@ -164,22 +178,22 @@
 
 struct DMLight
 {
-    DMVertex pos;
-    float ambient[4], diffuse[4], specular[4];
+    DMVector3 pos, pointAt;
+    DMVector4 ambient, diffuse, specular;
 };
 
 
 struct DMCamera
 {
-    DMVertex pos, lookAt;
+    DMVector3 pos, pointAt;
 };
 
 
 struct DMSimpleScene
 {
-    DMModel model;
     DMCamera camera;
     std::vector<DMLight> lights;
+    std::vector<DMModel> models;
 
     bool loadInfo(const std::string &filename);
 };
--- a/dmutil.cpp	Fri Nov 22 00:14:16 2019 +0200
+++ b/dmutil.cpp	Fri Nov 22 03:03:52 2019 +0200
@@ -73,6 +73,13 @@
 }
 
 
+std::string dmGetPath(const std::string &path)
+{
+    size_t dirsep = path.find_last_of("/\\");
+    return (dirsep != std::string::npos) ? path.substr(0, dirsep + 1) : "";
+}
+
+
 bool dmReadText(const std::string &filename, std::string &buf, const int maxSize)
 {
     std::ifstream in(filename.c_str(), std::fstream::in);
--- a/dmutil.h	Fri Nov 22 00:14:16 2019 +0200
+++ b/dmutil.h	Fri Nov 22 03:03:52 2019 +0200
@@ -10,16 +10,18 @@
 #include <iostream>
 
 
-#define SET_WHITESPACE           "\t\n\v\f\r "
+#define DMUTIL_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::string dmStrLTrim(const std::string& str, const std::string& delim = DMUTIL_WHITESPACE);
+std::string dmStrRTrim(const std::string& str, const std::string& delim = DMUTIL_WHITESPACE);
+std::string dmStrTrim(const std::string& str, const std::string& delim = DMUTIL_WHITESPACE);
 
-std::vector<std::string> dmStrSplit(const std::string& str, const std::string& delim = SET_WHITESPACE);
+std::vector<std::string> dmStrSplit(const std::string& str, const std::string& delim = DMUTIL_WHITESPACE);
 std::string dmStrJoin(const std::vector<std::string> &list, const std::string &delim);
 
+std::string dmGetPath(const std::string &path);
+
 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);
 
--- a/dragon.frag	Fri Nov 22 00:14:16 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-void main()
-{
-gl_FragColor = vec4(
-	gl_FragCoord.x * 0.1,
-	gl_FragCoord.y * 0.001,
-	0.0,
-	1.0);
-};
-
--- a/dragon.info	Fri Nov 22 00:14:16 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-
-
-
-v:100139
-
-f:200198
Binary file dragon.mesh has changed
Binary file dragon.ply has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dragon.scene	Fri Nov 22 03:03:52 2019 +0200
@@ -0,0 +1,1 @@
+model dragon.ply
--- a/dragon.vert	Fri Nov 22 00:14:16 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-void main()
-{
-gl_Position = ftransform();
-}
--- a/glxdragon.cpp	Fri Nov 22 00:14:16 2019 +0200
+++ b/glxdragon.cpp	Fri Nov 22 03:03:52 2019 +0200
@@ -33,11 +33,8 @@
 #include <GL/glu.h>
 #include <GL/glext.h>
 
-#include <iostream>
-#include <fstream>
-#include <string>
-#include <vector>
-#include <cstdio>
+#include "dmutil.h"
+#include "dmmodel.h"
 
 
 /* Default settings etc. constants
@@ -48,19 +45,6 @@
 #define SET_MAX_SHADER_SIZE  (128 * 1024)
 
 
-/* Structures
- */
-struct Mesh
-{
-    int nvertices, nfaces;
-
-    std::vector<float>    vertices;
-    std::vector<unsigned> faces;
-
-    GLuint id_prog, id_fs, id_vs;
-};
-
-
 /* Options
  */
 bool   optUseShaders = false;
@@ -68,7 +52,6 @@
        optHeight = SET_DEF_HEIGHT,
        optVSyncMode = 1;
 
-std::string optModelPrefix = "dragon";
 
 
 /* Globals
@@ -77,6 +60,8 @@
 SDL_GLContext dmGLContext = NULL;
 
 
+/* Helpers
+ */
 bool dmInitSDLGL(const int width, const int height, const char *title)
 {
     int ret;
@@ -169,7 +154,6 @@
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
 
-
     // Enable back face culling
     glEnable(GL_CULL_FACE);
 
@@ -179,6 +163,9 @@
     // Enable the depth buffer
     glEnable(GL_DEPTH_TEST);
 
+    // Enable normal rescaling
+    glEnable(GL_RESCALE_NORMAL);
+
     // Setup depth buffer
     glClearDepth(1.0f);
 
@@ -196,14 +183,14 @@
 }
 
 
-void dmDrawModelVA(const Mesh &mesh)
+void dmDrawModel(const DMModel &model)
 {
     int maxIndices;
 
     if (optUseShaders)
     {
         // Enable shader program
-        glUseProgram(mesh.id_prog);
+        glUseProgram(model.id_prog);
     }
     else
     {
@@ -215,15 +202,26 @@
     // Render the model
     glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &maxIndices);
 
-    glVertexPointer(3, GL_FLOAT, 3 * 4 * 2, &mesh.vertices[0]);
-    glNormalPointer(   GL_FLOAT, 3 * 4 * 2, &mesh.vertices[3]);
+    glPushMatrix();
+
+    // Add transforms
+    glScalef(model.scale.x, model.scale.y, model.scale.z);
+    glTranslatef(model.translate.x, model.translate.y, model.translate. z);
+    glRotatef(model.rotate.x, 1.0f, 0.0f, 0.0f);
+    glRotatef(model.rotate.y, 0.0f, 1.0f, 0.0f);
+    glRotatef(model.rotate.z, 0.0f, 0.0f, 1.0f);
 
-    for (int n = 0; n < mesh.nfaces; n += maxIndices)
+    glVertexPointer(3, GL_FLOAT, 0, &model.vertices[0]);
+    glNormalPointer(   GL_FLOAT, 0, &model.normals[0]);
+
+    for (int n = 0; n < model.nfaces; n += maxIndices)
     {
-        const int count = std::min(maxIndices, mesh.nfaces - n);
-        glDrawElements(GL_TRIANGLES, count * 3, GL_UNSIGNED_INT, &mesh.faces[n * 3]);
+        const int count = std::min(maxIndices, model.nfaces - n);
+        glDrawElements(GL_TRIANGLES, count * 3, GL_UNSIGNED_INT, &model.faces[n * 3]);
     }
 
+    glPopMatrix();
+
     // Restore
     if (optUseShaders)
     {
@@ -232,7 +230,7 @@
 }
 
 
-void dmDrawBackground()
+void dmDrawScene(const DMSimpleScene &scene)
 {
     glClear(GL_DEPTH_BUFFER_BIT);
 
@@ -270,136 +268,10 @@
     glPopMatrix();
 
     glEnable(GL_DEPTH_TEST);
-}
 
-
-bool dmReadText(const std::string &filename, std::string &buf, const int maxSize)
-{
-    std::ifstream in(filename.c_str());
-
-    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 dmLoadMesh(const std::string &filename, const std::string &infofilename, Mesh &mesh)
-{
-    std::ifstream info(infofilename.c_str());
-    int required = 0, nline = 0;
-
-    printf("INFO: Trying to read mesh meta from '%s'.\n",
-        infofilename.c_str());
-
-    if (!info.is_open())
-    {
-        printf("ERROR: Unable to open file '%s'.\n",
-            infofilename.c_str());
-        return false;
-    }
-
-    while (required != 0x03)
-    {
-        std::string tmp;
-        int value;
-        char key;
-
-        // Read one line
-        if (!std::getline(info, tmp))
-        {
-            printf("ERROR: Could not read from file '%s'.\n",
-                infofilename.c_str());
-            return false;
-        }
-
-        nline++;
-
-        // Skip empty lines and comments
-        if (tmp.empty() || tmp.substr(0, 1) == "#")
-            continue;
-
-        if (sscanf(tmp.c_str(), "%c:%d", &key, &value) != 2)
-        {
-            printf("ERROR: Syntax error in '%s' line #%d\n'%s'\n",
-                infofilename.c_str(),
-                nline,
-                tmp.c_str());
-            return false;
-        }
-
-        switch (key)
-        {
-            case 'v':
-                mesh.nvertices = value;
-                required |= 0x01;
-                break;
-
-            case 'f':
-                mesh.nfaces = value;
-                required |= 0x02;
-                break;
-
-            default:
-                printf("ERROR: Syntax error in '%s' line #%d\nUnknown key value '%c'.\n",
-                    infofilename.c_str(),
-                    nline,
-                    key);
-                break;
-        }
-    }
-
-    if (mesh.nvertices < 3 || mesh.nfaces < 1)
-    {
-        printf("ERROR: Invalid nvertices (%d) and/or nfaces (%d) in '%s'.\n",
-            mesh.nvertices, mesh.nfaces,
-            infofilename.c_str());
-        return false;
-    }
-
-    printf("INFO: %d vertices, %d faces\n", mesh.nvertices, mesh.nfaces);
-    printf("INFO: Trying to read mesh data from '%s'.\n",
-        filename.c_str());
-
-    std::ifstream in(filename.c_str(), std::ios::binary);
-
-    if (!in.is_open())
-    {
-        printf("ERROR: Unable to open file '%s'.\n",
-            filename.c_str());
-        return false;
-    }
-
-    mesh.vertices.resize(mesh.nvertices * 6);
-    in.read(reinterpret_cast<char*>(&mesh.vertices[0]), mesh.nvertices * 6 * 4);
-
-    mesh.faces.resize(mesh.nfaces * 3);
-
-    for (int i = 0; i < mesh.nfaces; i++)
-    {
-        in.seekg(1, std::ios::cur);
-        in.read(reinterpret_cast<char*>(&mesh.faces[i * 3]), 3 * 4);
-    }
-
-    return true;
+    // Draw models
+    for (const DMModel &model : scene.models)
+        dmDrawModel(model);
 }
 
 
@@ -415,22 +287,25 @@
 }
 
 
-void dmLinkMeshShaders(Mesh &mesh)
+void dmLinkModelShaders(DMModel &model)
 {
-    mesh.id_prog = glCreateProgram();
-    glAttachShader(mesh.id_prog, mesh.id_fs);
-    glAttachShader(mesh.id_prog, mesh.id_vs);
-    glLinkProgram(mesh.id_prog);
+    model.id_prog = glCreateProgram();
+    glAttachShader(model.id_prog, model.id_fs);
+    glAttachShader(model.id_prog, model.id_vs);
+    glLinkProgram(model.id_prog);
 }
 
 
 int main(int argc, char *argv[])
 {
-    std::string modelVertStr, modelFragStr;
-    Mesh modelMesh;
-    bool exitFlag = false, optShowHelp = false;
     int startTime, cycleStart, cycleFrames = 0, totalFrames = 0;
     double totalTime;
+    bool
+        exitFlag = false,
+        optShowHelp = false,
+        optSetInputFilename = false;
+    std::string optInputFilename = "dragon.scene", basePath;
+    DMSimpleScene scene;
 
     // Check commandline argument for enabling shaders
     for (int narg = 1; narg < argc; narg++)
@@ -469,7 +344,6 @@
                     {
                         case 'w': optWidth = atoi(opt + 1); break;
                         case 'h': optHeight = atoi(opt + 1); break;
-                        case 'm': optModelPrefix = std::string(opt + 1); break;
                         case 'v': optVSyncMode = atoi(opt + 1); break;
                     }
                     break;
@@ -479,20 +353,36 @@
                     goto exit;
             }
         }
+        else
+        {
+            if (optSetInputFilename)
+            {
+                printf("ERROR: Please specify only one scene file.\n");
+                goto exit;
+            }
+
+            optSetInputFilename = true;
+            optInputFilename = std::string(arg);
+            if (optInputFilename.empty())
+            {
+                printf("ERROR: Invalid input filename.\n");
+                goto exit;
+            }
+
+        }
     }
 
     if (optShowHelp)
     {
         printf(
-            "Usage: %s [options]\n"
+            "Usage: %s [options] [<scenefile.scene>]\n"
             "-?            Show this help\n"
             "-g            Use GLSL shader instead of basic OpenGL lighting\n"
             "-w<width>     Window width (default %d)\n"
             "-h<height>    Window height (default %d)\n"
-            "-m<modelfile> Set model filenames prefix. Using \"-mfoo\" will\n"
-            "              specify \"foo.mesh\", \"foo.frag\", \"foo.vert\".\n"
             "-v<1-3>       Set vsync mode: 1 = no vsync, 2 = vsync, 3 = adaptive\n"
-            "              Default is no vsync.\n"
+            "              Default is no vsync. Using vsync will result in FPS being\n"
+            "              approx whatever your monitor refresh rate is.\n"
             "\n",
             argv[0],
             SET_DEF_WIDTH, SET_DEF_HEIGHT
@@ -508,33 +398,53 @@
         goto exit;
     }
 
-    if (optModelPrefix.empty())
+    // Load the scene
+    if (!scene.loadInfo(optInputFilename))
+        goto exit;
+
+    if (scene.models.size() == 0)
     {
-        printf("Model file prefix empty.\n");
+        printf("ERROR: Scenefile '%s' contains no models.\n",
+            optInputFilename.c_str());
         goto exit;
     }
 
-    if (!dmLoadMesh(optModelPrefix + ".mesh", optModelPrefix + ".info", modelMesh))
-        goto exit;
+    printf("INFO: Loading %ld model(s) ..\n",
+        scene.models.size());
 
-    if (optUseShaders)
+    basePath = dmGetPath(optInputFilename);
+    printf("INFO: Model base path '%s'\n", basePath.c_str());
+
+    for (DMModel &model : scene.models)
     {
-        // Read shader files
-        if (!dmReadText(optModelPrefix + ".frag", modelFragStr, SET_MAX_SHADER_SIZE) ||
-            !dmReadText(optModelPrefix + ".vert", modelVertStr, SET_MAX_SHADER_SIZE))
+        if (!model.loadFromPLY(basePath + model.modelFile))
             goto exit;
+
+        if (optUseShaders)
+        {
+            std::string
+                fragFile = model.fragShaderFile.empty() ? "shader.frag" : basePath + model.fragShaderFile,
+                vertFile = model.vertShaderFile.empty() ? "shader.vert" : basePath + model.vertShaderFile;
+
+            if (!dmReadText(fragFile, model.fragShaderStr, SET_MAX_SHADER_SIZE) ||
+                !dmReadText(vertFile, model.vertShaderStr, SET_MAX_SHADER_SIZE))
+                goto exit;
+        }
     }
 
     // Initialize SDL + OpenGL
-    if (!dmInitSDLGL(optWidth, optHeight, "GLXDragon2"))
+    if (!dmInitSDLGL(optWidth, optHeight, "GLDragon"))
         goto exit;
 
     // According to our mode ..
     if (optUseShaders)
     {
-        modelMesh.id_fs = dmCompileShader(GL_FRAGMENT_SHADER, modelFragStr);
-        modelMesh.id_vs = dmCompileShader(GL_VERTEX_SHADER, modelVertStr);
-        dmLinkMeshShaders(modelMesh);
+        for (DMModel &model : scene.models)
+        {
+            model.id_fs = dmCompileShader(GL_FRAGMENT_SHADER, model.fragShaderStr);
+            model.id_vs = dmCompileShader(GL_VERTEX_SHADER, model.vertShaderStr);
+            dmLinkModelShaders(model);
+        }
     }
     else
     {
@@ -591,8 +501,7 @@
         }
 
         // Render the next frame
-        dmDrawBackground();
-        dmDrawModelVA(modelMesh);
+        dmDrawScene(scene);
 
         // Draw the current frame
         SDL_GL_SwapWindow(dmWindow);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/shader.frag	Fri Nov 22 03:03:52 2019 +0200
@@ -0,0 +1,9 @@
+void main()
+{
+gl_FragColor = vec4(
+	gl_FragCoord.x * 0.1,
+	gl_FragCoord.y * 0.001,
+	0.0,
+	1.0);
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/shader.vert	Fri Nov 22 03:03:52 2019 +0200
@@ -0,0 +1,4 @@
+void main()
+{
+gl_Position = ftransform();
+}