Sfoglia il codice sorgente

Added support for generating heightmaps from mesh data gameplay-encoder via a new "-heightmaps <node_ids>" argument.

Steve Grenier 14 anni fa
parent
commit
9ef4599e59

+ 6 - 6
gameplay-encoder/gameplay-encoder.vcxproj

@@ -146,15 +146,15 @@
       <WarningLevel>Level4</WarningLevel>
       <Optimization>Disabled</Optimization>
       <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;NO_BOOST;NO_ZAE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <AdditionalIncludeDirectories>../external-deps/freetype2/include;../external-deps/collada-dom/include;../external-deps/collada-dom/include/1.4</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>../external-deps/freetype2/include;../external-deps/collada-dom/include;../external-deps/collada-dom/include/1.4;../external-deps/libpng/include;../external-deps/zlib/include</AdditionalIncludeDirectories>
       <DisableLanguageExtensions>
       </DisableLanguageExtensions>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>
       <GenerateDebugInformation>true</GenerateDebugInformation>
-      <AdditionalLibraryDirectories>;../external-deps/freetype2/lib/win32;../external-deps/collada-dom/lib/win32</AdditionalLibraryDirectories>
-      <AdditionalDependencies>freetype245.lib;libcollada14dom22-d.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>../external-deps/freetype2/lib/win32;../external-deps/collada-dom/lib/win32;../external-deps/libpng/lib/win32;../external-deps/zlib/lib/win32</AdditionalLibraryDirectories>
+      <AdditionalDependencies>freetype245.lib;libcollada14dom22-d.lib;libpng14.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
       <IgnoreSpecificDefaultLibraries>MSVCRT</IgnoreSpecificDefaultLibraries>
     </Link>
     <PostBuildEvent>
@@ -170,15 +170,15 @@
       <FunctionLevelLinking>true</FunctionLevelLinking>
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;NO_BOOST;NO_ZAE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
-      <AdditionalIncludeDirectories>../external-deps/freetype2/include;../external-deps/collada-dom/include;../external-deps/collada-dom/include/1.4</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>../external-deps/freetype2/include;../external-deps/collada-dom/include;../external-deps/collada-dom/include/1.4;../external-deps/libpng/include;../external-deps/zlib/include</AdditionalIncludeDirectories>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>
       <GenerateDebugInformation>true</GenerateDebugInformation>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
-      <AdditionalDependencies>freetype245.lib;libcollada14dom22-d.lib;%(AdditionalDependencies)</AdditionalDependencies>
-      <AdditionalLibraryDirectories>;../external-deps/freetype2/lib/win32;../external-deps/collada-dom/lib/win32</AdditionalLibraryDirectories>
+      <AdditionalDependencies>freetype245.lib;libcollada14dom22-d.lib;libpng14.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>../external-deps/freetype2/lib/win32;../external-deps/collada-dom/lib/win32;../external-deps/libpng/lib/win32;../external-deps/zlib/lib/win32</AdditionalLibraryDirectories>
       <IgnoreSpecificDefaultLibraries>
       </IgnoreSpecificDefaultLibraries>
     </Link>

+ 7 - 1
gameplay-encoder/gameplay-encoder.vcxproj.user

@@ -1,7 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <LocalDebuggerCommandArguments>-groupAnimations group1 movements C:\Git\gaming\GamePlay\gameplay-encoder\res\seymour.dae</LocalDebuggerCommandArguments>
+    <LocalDebuggerCommandArguments>
+    </LocalDebuggerCommandArguments>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+    <LocalDebuggerEnvironment>PATH=%PATH%;../external-deps/zlib/lib/win32;../external-deps/libpng/lib/win32</LocalDebuggerEnvironment>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LocalDebuggerEnvironment>PATH=%PATH%;../external-deps/zlib/lib/win32;../external-deps/libpng/lib/win32</LocalDebuggerEnvironment>
     <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
   </PropertyGroup>
 </Project>

+ 3 - 0
gameplay-encoder/src/Base.h

@@ -19,6 +19,9 @@
 #include <algorithm>
 #include <sys/stat.h>
 
+// PNG
+#include <png.h>
+
 // Collada includes
 #include <dae.h>
 #include <dae/daeSIDResolver.h>

+ 55 - 2
gameplay-encoder/src/EncoderArguments.cpp

@@ -11,6 +11,8 @@
 namespace gameplay
 {
 
+static EncoderArguments* __instance;
+
 EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
     _fontSize(0),
     _parseError(false),
@@ -18,6 +20,8 @@ EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
     _textOutput(false),
     _daeOutput(false)
 {
+    __instance = this;
+
     if (argc > 1)
     {
         size_t filePathIndex = argc - 1;
@@ -51,6 +55,11 @@ EncoderArguments::~EncoderArguments(void)
 {
 }
 
+EncoderArguments* EncoderArguments::getInstance()
+{
+    return __instance;
+}
+
 const std::string& EncoderArguments::getFilePath() const
 {
     return _filePath;
@@ -61,6 +70,12 @@ const char* EncoderArguments::getFilePathPointer() const
     return _filePath.c_str();
 }
 
+std::string EncoderArguments::getOutputPath() const
+{
+    int pos = _filePath.find_last_of('/');
+    return (pos == -1 ? _filePath : _filePath.substr(0, pos));
+}
+
 const std::string& EncoderArguments::getDAEOutputPath() const
 {
     return _daeOutputPath;
@@ -94,6 +109,11 @@ const std::string EncoderArguments::getAnimationId(const std::string& nodeId) co
     return "";
 }
 
+const std::vector<std::string>& EncoderArguments::getHeightmapNodeIds() const
+{
+    return _heightmapNodeIds;
+}
+
 bool EncoderArguments::parseErrorOccured() const
 {
     return _parseError;
@@ -123,7 +143,12 @@ void EncoderArguments::printUsage() const
     fprintf(stderr,"COLLADA and FBX file options:\n");
     fprintf(stderr,"  -i<id>\t\tFilter by node ID.\n");
     fprintf(stderr,"  -t\t\t\tWrite text/xml.\n");
-    fprintf(stderr,"  -groupAnimations <node id> <animation id>\n\t\t\tGroup all animation channels targetting the nodes into a new animation.\n");
+    fprintf(stderr,"  -groupAnimations <node id> <animation id>\n" \
+        "\t\t\tGroup all animation channels targetting the nodes into a new animation.\n");
+    fprintf(stderr,"  -heightmaps \"<node ids>\"\n" \
+        "\t\t\tList of nodes to generate heightmaps for.\n" \
+        "\t\t\tNode id list should be in quotes with a space between each id.\n" \
+        "\t\t\tHeightmaps will be saved in files named <nodeid>.png.\n");
     fprintf(stderr,"\n");
     fprintf(stderr,"COLLADA file options:\n");
     fprintf(stderr,"  -dae <filepath>\tOutput optimized DAE.\n");
@@ -198,7 +223,7 @@ EncoderArguments::FileFormat EncoderArguments::getFileFormat() const
     return FILEFORMAT_UNKNOWN;
 }
 
-void EncoderArguments::readOption(const std::vector<std::string>& options, size_t *index)
+void EncoderArguments::readOption(const std::vector<std::string>& options, size_t* index)
 {
     const std::string& str = options[*index];
     if (str.length() == 0 && str[0] != '-')
@@ -252,6 +277,34 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
             _parseError = true;
             return;
         }
+        break;
+    case 'h':
+        {
+            if (str.compare("-heightmaps") == 0)
+            {
+                (*index)++;
+                if (*index < options.size())
+                {
+                    // Split node id list into tokens
+                    unsigned int length = options[*index].size() + 1;
+                    char* nodeIds = new char[length];
+                    strcpy(nodeIds, options[*index].c_str());
+                    nodeIds[length-1] = 0;
+                    char* id = strtok(nodeIds, " ");
+                    while (id)
+                    {
+                        _heightmapNodeIds.push_back(id);
+                        id = strtok(NULL, " ");
+                    }
+                    delete[] nodeIds;
+                }
+                else
+                {
+                    fprintf(stderr, "Error: missing argument for -heightmaps.\n");
+                }
+            }
+        }
+        break;
     case 'p':
         _fontPreview = true;
         break;

+ 13 - 0
gameplay-encoder/src/EncoderArguments.h

@@ -30,6 +30,11 @@ public:
      */
     ~EncoderArguments(void);
 
+    /**
+     * Gets the EncoderArguments instance.
+     */
+    static EncoderArguments* getInstance();
+
     /**
      * Gets the file format from the file path based on the extension.
      */
@@ -50,6 +55,10 @@ public:
      */
     const std::string& getDAEOutputPath() const;
 
+    /**
+     * Returns the output path/folder.
+     */
+    std::string getOutputPath() const;
 
     const std::vector<std::string>& getGroupAnimationNodeId() const;
     const std::vector<std::string>& getGroupAnimationAnimationId() const;
@@ -57,6 +66,8 @@ public:
     bool containsGroupNodeId(const std::string& nodeId) const;
     const std::string getAnimationId(const std::string& nodeId) const;
 
+    const std::vector<std::string>& getHeightmapNodeIds() const;
+
     /**
      * Returns true if an error occured while parsing the command line arguments.
      */
@@ -114,6 +125,8 @@ private:
 
     std::vector<std::string> _groupAnimationNodeId;
     std::vector<std::string> _groupAnimationAnimationId;
+    std::vector<std::string> _heightmapNodeIds;
+
 };
 
 }

+ 211 - 0
gameplay-encoder/src/Mesh.cpp

@@ -38,6 +38,217 @@ void Mesh::writeBinary(FILE* file)
     writeBinaryObjects(parts, file);
 }
 
+/////////////////////////////////////////////////////////////
+// 
+// Fast, Minimum Storage Ray-Triangle Intersection
+// 
+// Authors: Tomas Möller, Ben Trumbore
+// http://jgt.akpeters.com/papers/MollerTrumbore97
+//
+// Implementation of algorithm from Real-Time Rendering (vol 1), pg. 305.
+//
+// Adapted slightly for use here.
+// 
+#define EPSILON 0.000001
+#define CROSS(dest,v1,v2) \
+          dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \
+          dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \
+          dest[2]=v1[0]*v2[1]-v1[1]*v2[0];
+#define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])
+#define SUB(dest,v1,v2) \
+          dest[0]=v1[0]-v2[0]; \
+          dest[1]=v1[1]-v2[1]; \
+          dest[2]=v1[2]-v2[2]; 
+
+int
+intersect_triangle(const float orig[3], const float dir[3],
+                   const float vert0[3], const float vert1[3], const float vert2[3],
+                   float *t, float *u, float *v)
+{
+   float edge1[3], edge2[3], tvec[3], pvec[3], qvec[3];
+   float det,inv_det;
+
+   /* find vectors for two edges sharing vert0 */
+   SUB(edge1, vert1, vert0);
+   SUB(edge2, vert2, vert0);
+
+   /* begin calculating determinant - also used to calculate U parameter */
+   CROSS(pvec, dir, edge2);
+
+   /* if determinant is near zero, ray lies in plane of triangle */
+   det = DOT(edge1, pvec);
+
+   if (det > -EPSILON && det < EPSILON)
+     return 0;
+   inv_det = 1.0f / det;
+
+   /* calculate distance from vert0 to ray origin */
+   SUB(tvec, orig, vert0);
+
+   /* calculate U parameter and test bounds */
+   *u = DOT(tvec, pvec) * inv_det;
+   if (*u < 0.0 || *u > 1.0)
+     return 0;
+
+   /* prepare to test V parameter */
+   CROSS(qvec, tvec, edge1);
+
+   /* calculate V parameter and test bounds */
+   *v = DOT(dir, qvec) * inv_det;
+   if (*v < 0.0 || *u + *v > 1.0)
+     return 0;
+
+   /* calculate t, ray intersects triangle */
+   *t = DOT(edge2, qvec) * inv_det;
+
+   return 1;
+}
+
+// Performs an intersection test between a ray and the given mesh part
+// and stores the result in "point".
+bool intersect(const Vector3& rayOrigin, const Vector3& rayDirection, const std::vector<Vertex>& vertices, const std::vector<MeshPart*>& parts, Vector3* point)
+{
+    const float* orig = &rayOrigin.x;
+    const float* dir = &rayDirection.x;
+
+    for (unsigned int i = 0, partCount = parts.size(); i < partCount; ++i)
+    {
+        MeshPart* part = parts[i];
+
+        for (unsigned int j = 0, indexCount = part->getIndicesCount(); j < indexCount; j += 3)
+        {
+            const float* v0 = &vertices[part->getIndex( j )].position.x;
+            const float* v1 = &vertices[part->getIndex(j+1)].position.x;
+            const float* v2 = &vertices[part->getIndex(j+2)].position.x;
+
+            float t, u, v;
+            if (intersect_triangle(orig, dir, v0, v1, v2, &t, &u, &v))
+            {
+                // Found an intersection!
+                if (point)
+                {
+                    Vector3 rd(rayDirection);
+                    rd.scale(t);
+                    Vector3::add(rayOrigin, rd, point);
+                }
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+void Mesh::generateHeightmap(const char* filename)
+{
+    // Shoot rays down from a point just above the max Y position of the mesh.
+    // Compute ray-triangle intersection tests against the ray and this mesh to 
+    // generate heightmap data.
+    Vector3 rayOrigin(0, bounds.max.y + 10, 0);
+    Vector3 rayDirection(0, -1, 0);
+    Vector3 intersectionPoint;
+    int minX = (int)ceil(bounds.min.x);
+    int maxX = (int)floor(bounds.max.x);
+    int minZ = (int)ceil(bounds.min.z);
+    int maxZ = (int)floor(bounds.max.z);
+    int width = maxX - minX + 1;
+    int height = maxZ - minZ + 1;
+    float* heights = new float[width * height];
+    int index = 0;
+    float minHeight = FLT_MAX;
+    float maxHeight = FLT_MIN;
+    for (int z = minZ; z <= maxZ; z++)
+    {
+        rayOrigin.z = (float)z;
+        for (int x = minX; x <= maxX; x++)
+        {
+            float h;
+            rayOrigin.x = (float)x;
+            if (intersect(rayOrigin, rayDirection, vertices, parts, &intersectionPoint))
+            {
+                h = intersectionPoint.y;
+            }
+            else
+            {
+                h = 0;
+                fprintf(stderr, "Warning: Heightmap triangle intersection failed for (%d, %d).\n", x, z);
+            }
+            if (h < minHeight)
+                minHeight = h;
+            if (h > maxHeight)
+                maxHeight = h;
+            heights[index++] = h;
+        }
+    }
+    
+    // Normalize the max height value
+    maxHeight = maxHeight - minHeight;
+
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_bytep row = NULL;
+
+    FILE* fp = fopen(filename, "wb");
+    if (fp == NULL)
+    {
+        fprintf(stderr, "Error: Failed to open file for writing: %s\n", filename);
+        goto error;
+    }
+
+    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (png_ptr == NULL)
+    {
+        fprintf(stderr, "Error: Write struct creation failed: %s\n", filename);
+        goto error;
+    }
+
+    info_ptr = png_create_info_struct(png_ptr);
+    if (info_ptr == NULL)
+    {
+        fprintf(stderr, "Error: Info struct creation failed: %s\n", filename);
+        goto error;
+    }
+
+    png_init_io(png_ptr, fp);
+
+    png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+    png_write_info(png_ptr, info_ptr);
+
+    // Allocate memory for a single row of image data
+    row = (png_bytep)malloc(3 * width * sizeof(png_byte));
+
+    for (int y = 0; y < height; y++)
+    {
+        for (int x = 0; x < width; x++)
+        {
+            // Write height value normalized between 0-255 (between min and max height)
+            float h = heights[y*width + x];
+            float nh = (h - minHeight) / maxHeight;
+            png_byte b = (png_byte)(nh * 255.0f);
+            
+            int pos = x*3;
+            row[pos] = row[pos+1] = row[pos+2] = b;
+        }
+        png_write_row(png_ptr, row);
+    }
+
+    DEBUGPRINT_VARG("> Saved heightmap: %s\n", filename);
+
+error:
+    if (heights)
+        delete[] heights;
+    if (fp)
+        fclose(fp);
+    if (row)
+        free(row);
+    if (info_ptr)
+        png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
+    if (png_ptr)
+        png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+
+}
+
 void Mesh::writeBinaryVertices(FILE* file)
 {
     if (vertices.size() > 0)

+ 5 - 0
gameplay-encoder/src/Mesh.h

@@ -60,6 +60,11 @@ public:
 
     unsigned int getVertexIndex(const Vertex& vertex);
 
+    /**
+     * Generates a heightmap with the given filename for this mesh.
+     */
+    void generateHeightmap(const char* filename);
+
     Model* model;
     std::vector<Vertex> vertices;
     std::vector<MeshPart*> parts;

+ 18 - 12
gameplay-encoder/src/MeshPart.cpp

@@ -6,7 +6,7 @@ namespace gameplay
 
 MeshPart::MeshPart(void) :
     _primitiveType(TRIANGLES),
-    _indexFormat(INDEX8)
+    _indexFormat(INDEX16)
 {
 }
 
@@ -18,16 +18,18 @@ unsigned int MeshPart::getTypeId(void) const
 {
     return MESHPART_ID;
 }
+
 const char* MeshPart::getElementName(void) const
 {
     return "MeshPart";
 }
+
 void MeshPart::writeBinary(FILE* file)
 {
     Object::writeBinary(file);
 
     write(_primitiveType, file);
-    write(_indexFormat, file);
+    write((unsigned int)_indexFormat, file);
 
     // write the number of bytes
     write(indicesByteSize(), file);
@@ -37,11 +39,12 @@ void MeshPart::writeBinary(FILE* file)
         writeBinaryIndex(*i, file);
     }
 }
+
 void MeshPart::writeText(FILE* file)
 {
     fprintElementStart(file);
     fprintfElement(file, "primitiveType", _primitiveType);
-    fprintfElement(file, "indexFormat", _indexFormat);
+    fprintfElement(file, "indexFormat", (unsigned int)_indexFormat);
     fprintfElement(file, "%d ", "indices", _indices);
     fprintElementEnd(file);
 }
@@ -68,14 +71,21 @@ unsigned int MeshPart::indexFormatSize() const
     {
     case INDEX32:
         return 4;
-    case INDEX16:
+    default: // INDEX16
         return 2;
-    case INDEX8:
-    default:
-        return 1;
     }
 }
 
+MeshPart::IndexFormat MeshPart::getIndexFormat() const
+{
+    return _indexFormat;
+}
+
+unsigned int MeshPart::getIndex(unsigned int i) const
+{
+    return _indices[i];
+}
+
 void MeshPart::writeBinaryIndex(unsigned int index, FILE* file)
 {
     switch (_indexFormat)
@@ -83,13 +93,9 @@ void MeshPart::writeBinaryIndex(unsigned int index, FILE* file)
     case INDEX32:
         write(index, file);
         break;
-    case INDEX16:
+    default: // INDEX16
         write((unsigned short)index, file);
         break;
-    case INDEX8:
-    default:
-        write((unsigned char)index, file);
-        break;
     }
 }
 

+ 12 - 3
gameplay-encoder/src/MeshPart.h

@@ -23,12 +23,10 @@ public:
 
     enum IndexFormat
     {
-        INDEX8  = 0x1401, // GL_UNSIGNED_BYTE
         INDEX16 = 0x1403, // GL_UNSIGNED_SHORT
         INDEX32 = 0x1405  // GL_UNSIGNED_INT
     };
 
-
     /**
      * Constructor.
      */
@@ -54,6 +52,16 @@ public:
      */
     size_t getIndicesCount() const;
 
+    /**
+     * Returns the index format.
+     */
+    IndexFormat getIndexFormat() const;
+
+    /**
+     * Gets the value of the index at the specificied location.
+     */
+    unsigned int getIndex(unsigned int i) const;
+
 private:
 
     /**
@@ -78,8 +86,9 @@ private:
     void writeBinaryIndex(unsigned int index, FILE* file);
 
 private:
+
     unsigned int _primitiveType;
-    unsigned int _indexFormat;
+    IndexFormat _indexFormat;
     std::vector<unsigned int> _indices;
 };
 

+ 14 - 9
gameplay-encoder/src/Model.cpp

@@ -5,7 +5,7 @@ namespace gameplay
 {
 
 Model::Model(void) :
-    _ref(NULL),
+    _mesh(NULL),
     _meshSkin(NULL)
 {
 }
@@ -27,9 +27,9 @@ void Model::writeBinary(FILE* file)
     Object::writeBinary(file);
 
     // xref:Mesh
-    if (_ref != NULL)
+    if (_mesh != NULL)
     {
-        _ref->writeBinaryXref(file);
+        _mesh->writeBinaryXref(file);
     }
     else
     {
@@ -56,9 +56,9 @@ void Model::writeText(FILE* file)
     // Compute mesh bounds before writing
 
     fprintElementStart(file);
-    if (_ref != NULL)
+    if (_mesh != NULL)
     {
-        fprintfElement(file, "ref", _ref->getId());
+        fprintfElement(file, "ref", _mesh->getId());
     }
     if (_meshSkin != NULL)
     {
@@ -72,18 +72,23 @@ MeshSkin* Model::getSkin()
     return _meshSkin;
 }
 
+Mesh* Model::getMesh()
+{
+    return _mesh;
+}
+
 void Model::setMesh(Mesh* mesh)
 {
-    _ref = mesh;
+    _mesh = mesh;
 
     if (mesh)
     {
         mesh->model = this;
     }
 
-    if (_ref && _meshSkin)
+    if (_mesh && _meshSkin)
     {
-        _meshSkin->_mesh = _ref;
+        _meshSkin->_mesh = _mesh;
     }
 }
 
@@ -93,7 +98,7 @@ void Model::setSkin(MeshSkin* skin)
 
     if (_meshSkin)
     {
-        _meshSkin->_mesh = _ref;
+        _meshSkin->_mesh = _mesh;
     }
 }
 

+ 2 - 1
gameplay-encoder/src/Model.h

@@ -28,13 +28,14 @@ public:
     virtual void writeBinary(FILE* file);
     virtual void writeText(FILE* file);
 
+    Mesh* getMesh();
     void setMesh(Mesh* mesh);
     MeshSkin* getSkin();
     void setSkin(MeshSkin* skin);
 
 private:
 
-    Mesh* _ref;
+    Mesh* _mesh;
     MeshSkin* _meshSkin;
     std::list<Material*> _materials;
 };

+ 27 - 0
gameplay-encoder/src/Node.cpp

@@ -1,6 +1,7 @@
 #include "Base.h"
 #include "Node.h"
 #include "Matrix.h"
+#include "EncoderArguments.h"
 
 #define NODE 1
 #define JOINT 2
@@ -73,7 +74,10 @@ void Node::writeBinary(FILE* file)
     {
         writeZero(file);
     }
+
+    generateHeightmap();
 }
+
 void Node::writeText(FILE* file)
 {
     if (isJoint())
@@ -109,6 +113,29 @@ void Node::writeText(FILE* file)
         _model->writeText(file);
     }
     fprintElementEnd(file);
+
+    generateHeightmap();
+}
+
+void Node::generateHeightmap()
+{
+    // Is this node flagged to have a heightmap generated for it?
+    const std::vector<std::string>& heightmapNodes = EncoderArguments::getInstance()->getHeightmapNodeIds();
+    if (std::find(heightmapNodes.begin(), heightmapNodes.end(), getId()) != heightmapNodes.end())
+    {
+        Mesh* mesh = _model ? _model->getMesh() : NULL;
+        if (mesh)
+        {
+            DEBUGPRINT_VARG("> Generating heightmap for node: %s\n", getId().c_str());
+
+            std::string heightmapFilename(EncoderArguments::getInstance()->getOutputPath());
+            heightmapFilename += "/heightmap_";
+            heightmapFilename += getId();
+            heightmapFilename += ".png";
+
+            mesh->generateHeightmap(heightmapFilename.c_str());
+        }
+    }
 }
 
 void Node::addChild(Node* child)

+ 6 - 0
gameplay-encoder/src/Node.h

@@ -171,6 +171,12 @@ public:
     
 private:
 
+    /**
+     * Internal method to generate heightmap for a node's mesh data
+     * if the node was flagged as a heightmap via encoder arguments.
+     */
+    void generateHeightmap();
+
     Matrix _transform;
     mutable Matrix _worldTransform;