소스 검색

Merge branch 'next' of https://github.com/blackberry/GamePlay into next

Dimitry Tomilovskiy 13 년 전
부모
커밋
12145806ac
100개의 변경된 파일6105개의 추가작업 그리고 1949개의 파일을 삭제
  1. 4 0
      gameplay-encoder/gameplay-encoder.vcxproj
  2. 12 0
      gameplay-encoder/gameplay-encoder.vcxproj.filters
  3. 12 0
      gameplay-encoder/gameplay-encoder.xcodeproj/project.pbxproj
  4. 193 48
      gameplay-encoder/src/EncoderArguments.cpp
  5. 42 1
      gameplay-encoder/src/EncoderArguments.h
  6. 1 1
      gameplay-encoder/src/GPBFile.cpp
  7. 41 29
      gameplay-encoder/src/Heightmap.cpp
  8. 3 2
      gameplay-encoder/src/Heightmap.h
  9. 264 0
      gameplay-encoder/src/Image.cpp
  10. 123 0
      gameplay-encoder/src/Image.h
  11. 268 0
      gameplay-encoder/src/NormalMapGenerator.cpp
  12. 35 0
      gameplay-encoder/src/NormalMapGenerator.h
  13. 18 0
      gameplay-encoder/src/main.cpp
  14. 5 0
      gameplay-luagen/src/Base.h
  15. 6 4
      gameplay-luagen/src/ClassBinding.cpp
  16. 74 60
      gameplay-luagen/src/FunctionBinding.cpp
  17. 2 0
      gameplay-luagen/src/FunctionBinding.h
  18. 41 26
      gameplay-luagen/src/Generator.cpp
  19. 37 0
      gameplay-luagen/src/main.cpp
  20. 16 0
      gameplay/CMakeLists.txt
  21. 7 0
      gameplay/android/jni/Android.mk
  22. 15 1
      gameplay/gameplay.cbp
  23. 16 0
      gameplay/gameplay.vcxproj
  24. 48 0
      gameplay/gameplay.vcxproj.filters
  25. 32 946
      gameplay/gameplay.xcodeproj/project.pbxproj
  26. 78 0
      gameplay/res/shaders/terrain.frag
  27. 60 0
      gameplay/res/shaders/terrain.vert
  28. 3 1
      gameplay/src/AudioListener.cpp
  29. 1 0
      gameplay/src/Base.h
  30. 1 2
      gameplay/src/Bundle.cpp
  31. 68 0
      gameplay/src/Camera.cpp
  32. 18 0
      gameplay/src/Camera.h
  33. 33 3
      gameplay/src/Effect.cpp
  34. 11 0
      gameplay/src/Effect.h
  35. 14 0
      gameplay/src/FileSystem.cpp
  36. 14 0
      gameplay/src/FileSystem.h
  37. 42 32
      gameplay/src/Game.cpp
  38. 6 1
      gameplay/src/Game.h
  39. 226 0
      gameplay/src/HeightField.cpp
  40. 142 0
      gameplay/src/HeightField.h
  41. 95 78
      gameplay/src/MaterialParameter.cpp
  42. 22 1
      gameplay/src/MaterialParameter.h
  43. 11 8
      gameplay/src/Model.h
  44. 50 10
      gameplay/src/Node.cpp
  45. 25 3
      gameplay/src/Node.h
  46. 3 4
      gameplay/src/PhysicsCharacter.cpp
  47. 40 3
      gameplay/src/PhysicsCollisionObject.cpp
  48. 59 3
      gameplay/src/PhysicsCollisionObject.h
  49. 131 104
      gameplay/src/PhysicsCollisionShape.cpp
  50. 39 11
      gameplay/src/PhysicsCollisionShape.h
  51. 80 226
      gameplay/src/PhysicsController.cpp
  52. 3 7
      gameplay/src/PhysicsController.h
  53. 4 5
      gameplay/src/PhysicsGhostObject.cpp
  54. 57 26
      gameplay/src/PhysicsRigidBody.cpp
  55. 5 6
      gameplay/src/PhysicsRigidBody.h
  56. 89 1
      gameplay/src/RenderState.cpp
  57. 54 1
      gameplay/src/RenderState.h
  58. 88 22
      gameplay/src/Scene.cpp
  59. 74 4
      gameplay/src/Scene.h
  60. 80 19
      gameplay/src/SceneLoader.cpp
  61. 7 5
      gameplay/src/SceneLoader.h
  62. 28 2
      gameplay/src/ScriptController.cpp
  63. 574 0
      gameplay/src/Terrain.cpp
  64. 375 0
      gameplay/src/Terrain.h
  65. 670 0
      gameplay/src/TerrainPatch.cpp
  66. 163 0
      gameplay/src/TerrainPatch.h
  67. 151 33
      gameplay/src/Texture.cpp
  68. 7 0
      gameplay/src/Texture.h
  69. 3 0
      gameplay/src/gameplay.h
  70. 1 1
      gameplay/src/lua/lua_AIController.cpp
  71. 3 3
      gameplay/src/lua/lua_AIMessage.cpp
  72. 1 1
      gameplay/src/lua/lua_AIState.cpp
  73. 3 3
      gameplay/src/lua/lua_AIStateMachine.cpp
  74. 6 6
      gameplay/src/lua/lua_Animation.cpp
  75. 3 3
      gameplay/src/lua/lua_AnimationClip.cpp
  76. 9 9
      gameplay/src/lua/lua_AnimationTarget.cpp
  77. 1 1
      gameplay/src/lua/lua_AudioSource.cpp
  78. 6 6
      gameplay/src/lua/lua_Bundle.cpp
  79. 18 18
      gameplay/src/lua/lua_Button.cpp
  80. 53 0
      gameplay/src/lua/lua_Camera.cpp
  81. 1 0
      gameplay/src/lua/lua_Camera.h
  82. 18 18
      gameplay/src/lua/lua_CheckBox.cpp
  83. 20 20
      gameplay/src/lua/lua_Container.cpp
  84. 16 16
      gameplay/src/lua/lua_Control.cpp
  85. 2 2
      gameplay/src/lua/lua_DepthStencilTarget.cpp
  86. 12 12
      gameplay/src/lua/lua_Effect.cpp
  87. 46 8
      gameplay/src/lua/lua_FileSystem.cpp
  88. 1 0
      gameplay/src/lua/lua_FileSystem.h
  89. 35 35
      gameplay/src/lua/lua_Font.cpp
  90. 1 1
      gameplay/src/lua/lua_FontText.cpp
  91. 22 22
      gameplay/src/lua/lua_Form.cpp
  92. 3 3
      gameplay/src/lua/lua_FrameBuffer.cpp
  93. 2 2
      gameplay/src/lua/lua_Game.cpp
  94. 34 0
      gameplay/src/lua/lua_Global.cpp
  95. 2 0
      gameplay/src/lua/lua_Global.h
  96. 632 0
      gameplay/src/lua/lua_HeightField.cpp
  97. 24 0
      gameplay/src/lua/lua_HeightField.h
  98. 1 1
      gameplay/src/lua/lua_Image.cpp
  99. 108 19
      gameplay/src/lua/lua_Joint.cpp
  100. 2 0
      gameplay/src/lua/lua_Joint.h

+ 4 - 0
gameplay-encoder/gameplay-encoder.vcxproj

@@ -31,6 +31,7 @@
     <ClCompile Include="src\GPBDecoder.cpp" />
     <ClCompile Include="src\Animations.cpp" />
     <ClCompile Include="src\Heightmap.cpp" />
+    <ClCompile Include="src\Image.cpp" />
     <ClCompile Include="src\Light.cpp" />
     <ClCompile Include="src\main.cpp" />
     <ClCompile Include="src\Material.cpp" />
@@ -41,6 +42,7 @@
     <ClCompile Include="src\MeshPart.cpp" />
     <ClCompile Include="src\MeshSkin.cpp" />
     <ClCompile Include="src\Node.cpp" />
+    <ClCompile Include="src\NormalMapGenerator.cpp" />
     <ClCompile Include="src\Object.cpp" />
     <ClCompile Include="src\Quaternion.cpp" />
     <ClCompile Include="src\Reference.cpp" />
@@ -76,6 +78,7 @@
     <ClInclude Include="src\GPBDecoder.h" />
     <ClInclude Include="src\Animations.h" />
     <ClInclude Include="src\Heightmap.h" />
+    <ClInclude Include="src\Image.h" />
     <ClInclude Include="src\Light.h" />
     <ClInclude Include="src\Material.h" />
     <ClInclude Include="src\MaterialParameter.h" />
@@ -85,6 +88,7 @@
     <ClInclude Include="src\MeshPart.h" />
     <ClInclude Include="src\MeshSkin.h" />
     <ClInclude Include="src\Node.h" />
+    <ClInclude Include="src\NormalMapGenerator.h" />
     <ClInclude Include="src\Object.h" />
     <ClInclude Include="src\Quaternion.h" />
     <ClInclude Include="src\Reference.h" />

+ 12 - 0
gameplay-encoder/gameplay-encoder.vcxproj.filters

@@ -130,6 +130,12 @@
     <ClCompile Include="src\Heightmap.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\Image.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\NormalMapGenerator.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\VertexElement.h">
@@ -261,6 +267,12 @@
     <ClInclude Include="src\Heightmap.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\Image.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\NormalMapGenerator.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\Vector2.inl">

+ 12 - 0
gameplay-encoder/gameplay-encoder.xcodeproj/project.pbxproj

@@ -61,6 +61,8 @@
 		42D277591472EFA700D867A4 /* libpcre.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 42D277571472EFA700D867A4 /* libpcre.a */; };
 		42D2775A1472EFA700D867A4 /* libpcrecpp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 42D277581472EFA700D867A4 /* libpcrecpp.a */; };
 		5BCD0643152CFC3C0071FAB5 /* libpng.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BCD0642152CFC3C0071FAB5 /* libpng.a */; };
+		B661733F16A61CE40083A307 /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B661733D16A61CE40083A307 /* Image.cpp */; };
+		B661734316A61CFA0083A307 /* NormalMapGenerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B661734116A61CFA0083A307 /* NormalMapGenerator.cpp */; };
 		F18DCD0615D554B800DB35DB /* Heightmap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F18DCD0315D554B800DB35DB /* Heightmap.cpp */; };
 /* End PBXBuildFile section */
 
@@ -179,6 +181,10 @@
 		42D277571472EFA700D867A4 /* libpcre.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpcre.a; path = "../external-deps/pcre/lib/macosx/libpcre.a"; sourceTree = "<group>"; };
 		42D277581472EFA700D867A4 /* libpcrecpp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpcrecpp.a; path = "../external-deps/pcre/lib/macosx/libpcrecpp.a"; sourceTree = "<group>"; };
 		5BCD0642152CFC3C0071FAB5 /* libpng.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpng.a; path = "../external-deps/libpng/lib/macosx/libpng.a"; sourceTree = "<group>"; };
+		B661733D16A61CE40083A307 /* Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Image.cpp; path = src/Image.cpp; sourceTree = SOURCE_ROOT; };
+		B661733E16A61CE40083A307 /* Image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Image.h; path = src/Image.h; sourceTree = SOURCE_ROOT; };
+		B661734116A61CFA0083A307 /* NormalMapGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = NormalMapGenerator.cpp; path = src/NormalMapGenerator.cpp; sourceTree = SOURCE_ROOT; };
+		B661734216A61CFA0083A307 /* NormalMapGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NormalMapGenerator.h; path = src/NormalMapGenerator.h; sourceTree = SOURCE_ROOT; };
 		F18DCD0315D554B800DB35DB /* Heightmap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Heightmap.cpp; path = src/Heightmap.cpp; sourceTree = SOURCE_ROOT; };
 		F18DCD0415D554B800DB35DB /* Heightmap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Heightmap.h; path = src/Heightmap.h; sourceTree = SOURCE_ROOT; };
 		F18DCD0515D554B800DB35DB /* Thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Thread.h; path = src/Thread.h; sourceTree = SOURCE_ROOT; };
@@ -278,6 +284,8 @@
 				42C8EDD614724CD700E43619 /* GPBDecoder.h */,
 				42C8EDD714724CD700E43619 /* GPBFile.cpp */,
 				42C8EDD814724CD700E43619 /* GPBFile.h */,
+				B661733D16A61CE40083A307 /* Image.cpp */,
+				B661733E16A61CE40083A307 /* Image.h */,
 				42C8EDD914724CD700E43619 /* Light.cpp */,
 				42C8EDDA14724CD700E43619 /* Light.h */,
 				42C8EDDD14724CD700E43619 /* main.cpp */,
@@ -299,6 +307,8 @@
 				42C8EDED14724CD700E43619 /* Model.h */,
 				42C8EDEE14724CD700E43619 /* Node.cpp */,
 				42C8EDEF14724CD700E43619 /* Node.h */,
+				B661734116A61CFA0083A307 /* NormalMapGenerator.cpp */,
+				B661734216A61CFA0083A307 /* NormalMapGenerator.h */,
 				42C8EDF014724CD700E43619 /* Object.cpp */,
 				42C8EDF114724CD700E43619 /* Object.h */,
 				42C8EDF214724CD700E43619 /* Quaternion.cpp */,
@@ -444,6 +454,8 @@
 				42783423148D6F7500A6E27F /* FBXSceneEncoder.cpp in Sources */,
 				4251B12C152D044B002F6199 /* Curve.cpp in Sources */,
 				F18DCD0615D554B800DB35DB /* Heightmap.cpp in Sources */,
+				B661733F16A61CE40083A307 /* Image.cpp in Sources */,
+				B661734316A61CFA0083A307 /* NormalMapGenerator.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 193 - 48
gameplay-encoder/src/EncoderArguments.cpp

@@ -8,6 +8,8 @@
     #define realpath(A,B)    _fullpath(B,A,PATH_MAX)
 #endif
 
+#define MAX_HEIGHTMAP_SIZE 2049
+
 namespace gameplay
 {
 
@@ -17,6 +19,7 @@ extern int __logVerbosity = 1;
 
 EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
     _fontSize(0),
+    _normalMap(false),
     _parseError(false),
     _fontPreview(false),
     _textOutput(false),
@@ -25,6 +28,8 @@ EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
 {
     __instance = this;
 
+    memset(_heightmapResolution, 0, sizeof(int) * 2);
+
     if (argc > 1)
     {
         // read the options
@@ -91,27 +96,41 @@ std::string EncoderArguments::getOutputDirPath() const
     }
 }
 
+std::string EncoderArguments::getOutputFileExtension() const
+{
+    switch (getFileFormat())
+    {
+    case FILEFORMAT_PNG:
+    case FILEFORMAT_RAW:
+        if (_normalMap)
+            return ".png";
+
+    default:
+        return ".gpb";
+    }
+}
+
 std::string EncoderArguments::getOutputFilePath() const
 {
     if (_fileOutputPath.size() > 0)
     {
+        // Output file explicitly set
         return _fileOutputPath;
     }
     else
     {
+        // Generate an output file path
         int pos = _filePath.find_last_of('.');
-        if (pos > 0)
-        {
-            std::string outputFilePath(_filePath.substr(0, pos));
-            outputFilePath.append(".gpb");
-            return outputFilePath;
-        }
-        else
+        std::string outputFilePath(pos > 0 ? _filePath.substr(0, pos) : _filePath);
+
+        // Modify the original file name if the output extension can be the same as the input
+        if (_normalMap)
         {
-            std::string outputFilePath(_filePath);
-            outputFilePath.append(".gpb");
-            return outputFilePath;
+            outputFilePath.append("_normalmap");
         }
+
+        outputFilePath.append(getOutputFileExtension());
+        return outputFilePath;
     }
 }
 
@@ -152,6 +171,22 @@ const std::vector<EncoderArguments::HeightmapOption>& EncoderArguments::getHeigh
     return _heightmaps;
 }
 
+bool EncoderArguments::normalMapGeneration() const
+{
+    return _normalMap;
+}
+
+void EncoderArguments::getHeightmapResolution(int* x, int* y) const
+{
+    *x = _heightmapResolution[0];
+    *y = _heightmapResolution[1];
+}
+
+const Vector3& EncoderArguments::getHeightmapWorldSize() const
+{
+    return _heightmapWorldSize;
+}
+
 bool EncoderArguments::parseErrorOccured() const
 {
     return _parseError;
@@ -170,6 +205,21 @@ bool EncoderArguments::fileExists() const
     return false;
 }
 
+void splitString(const char* str, std::vector<std::string>* tokens)
+{
+    // Split node id list into tokens
+    unsigned int length = strlen(str);
+    char* temp = new char[length + 1];
+    strcpy(temp, str);
+    char* tok = strtok(temp, ",");
+    while (tok)
+    {
+        tokens->push_back(tok);
+        tok = strtok(NULL, ",");
+    }
+    delete[] temp;
+}
+
 void EncoderArguments::printUsage() const
 {
     LOG(1, "Usage: gameplay-encoder [options] <input filepath> <output filepath>\n\n");
@@ -191,13 +241,28 @@ void EncoderArguments::printUsage() const
         "\t\tremoving any channels that contain default/identity values\n" \
         "\t\tand removing any duplicate contiguous keyframes, which are common\n" \
         "\t\twhen exporting baked animation data.\n");
-    LOG(1, "  -h \"<node ids>\" <filename>\n" \
+    LOG(1, "  -h <size> \"<node ids>\" <filename>\n" \
         "\t\tGenerates a single heightmap image using meshes from the specified\n" \
-        "\t\tnodes. Node id list should be in quotes with a space between each id.\n" \
+        "\t\tnodes. <size> should be two comma-separated numbers in the format\n" \
+        "\t\t\"X,Y\", indicating the dimensions of the produced heightmap image.\n" \
+        "\t\t<node ids> should be in quotes with a space between each id.\n" \
         "\t\tFilename is the name of the image (PNG) to be saved.\n" \
         "\t\tMultiple -h arguments can be supplied to generate more than one heightmap.\n" \
         "\t\tFor 24-bit packed height data use -hp instead of -h.\n");
     LOG(1, "\n");
+    LOG(1, "Normal map generation options:\n" \
+        "  -n\t\tGenerate normal map (requires input file of type PNG or RAW)\n" \
+        "  -s\t\tSize/resolution of the input heightmap image (requried for RAW files)\n" \
+        "  -w <size>\tSpecifies the size of an input terran heightmap file in world\n" \
+        "\t\tunits, along the X, Y and Z axes. <size> should be three comma-separated\n" \
+        "\t\tnumbers in the format \"X,Y,Z\". The Y value represents the maximum\n" \
+        "\t\tpossible height value of a full intensity heightmap pixel.\n" \
+        "\n" \
+        "  Normal map generation can be used to create object-space normal maps from heightmap\n" \
+        "  images. Heightmaps must be in either PNG format (where the intensity of each pixel\n" \
+        "  represents a height value), or in RAW format (8 or 16-bit), which is a common\n" \
+        "  headerless format supported by most terran generation tools.\n");
+    LOG(1, "\n");
     LOG(1, "TTF file options:\n");
     LOG(1, "  -s <size>\tSize of the font.\n");
     LOG(1, "  -p\t\tOutput font preview.\n");
@@ -252,24 +317,34 @@ EncoderArguments::FileFormat EncoderArguments::getFileFormat() const
     {
         ext = _filePath.substr(pos + 1);
     }
+    for (size_t i = 0; i < ext.size(); ++i)
+        ext[i] = (char)tolower(ext[i]);
     
     // Match every supported extension with its format constant
-    if (ext.compare("dae") == 0 || ext.compare("DAE") == 0)
+    if (ext.compare("dae") == 0)
     {
         return FILEFORMAT_DAE;
     }
-    if (ext.compare("fbx") == 0 || ext.compare("FBX") == 0)
+    if (ext.compare("fbx") == 0)
     {
         return FILEFORMAT_FBX;
     }
-    if (ext.compare("ttf") == 0 || ext.compare("TTF") == 0)
+    if (ext.compare("ttf") == 0)
     {
         return FILEFORMAT_TTF;
     }
-    if (ext.compare("gpb") == 0 || ext.compare("GPB") == 0)
+    if (ext.compare("gpb") == 0)
     {
         return FILEFORMAT_GPB;
     }
+    if (ext.compare("png") == 0)
+    {
+        return FILEFORMAT_PNG;
+    }
+    if (ext.compare("raw") == 0)
+    {
+        return FILEFORMAT_RAW;
+    }
 
     return FILEFORMAT_UNKNOWN;
 }
@@ -342,25 +417,36 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
             if (str.compare("-heightmap") == 0 || str.compare("-h") == 0 || isHighPrecision)
             {
                 (*index)++;
-                if (*index < (options.size() + 1))
+                if (*index < (options.size() + 2))
                 {
                     _heightmaps.resize(_heightmaps.size() + 1);
                     HeightmapOption& heightmap = _heightmaps.back();
                     
                     heightmap.isHighPrecision = isHighPrecision;
 
-                    // 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)
+                    // Read heightmap size
+                    std::vector<std::string> parts;
+                    splitString(options[*index].c_str(), &parts);
+                    if (parts.size() != 2)
                     {
-                        heightmap.nodeIds.push_back(id);
-                        id = strtok(NULL, " ");
+                        LOG(1, "Error: invalid size argument for -h|-heightmap.\n");
+                        _parseError = true;
+                        return;
+                    }
+                    heightmap.width = atoi(parts[0].c_str());
+                    heightmap.height = atoi(parts[1].c_str());
+
+                    // Put some artificial bounds on heightmap dimensions
+                    if (heightmap.width <= 0 || heightmap.height <= 0 || heightmap.width > MAX_HEIGHTMAP_SIZE || heightmap.height > MAX_HEIGHTMAP_SIZE)
+                    {
+                        LOG(1, "Error: size argument for -h|-heightmap must be between (1,1) and (%d,%d).\n", (int)MAX_HEIGHTMAP_SIZE, (int)MAX_HEIGHTMAP_SIZE);
+                        _parseError = true;
+                        return;
                     }
-                    delete[] nodeIds;
+
+                    // Split node id list into tokens
+                    (*index)++;
+                    splitString(options[*index].c_str(), &heightmap.nodeIds);
 
                     // Store output filename
                     (*index)++;
@@ -391,34 +477,90 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
             }
         }
         break;
+    case 'n':
+        _normalMap = true;
+        break;
+    case 'w':
+        {
+            // Read world size
+            (*index)++;
+            if (*index >= options.size())
+            {
+                LOG(1, "Error: missing world size argument for -w.\n");
+                _parseError = true;
+                return;
+            }
+            std::vector<std::string> parts;
+            splitString(options[*index].c_str(), &parts);
+            if (parts.size() != 3)
+            {
+                LOG(1, "Error: invalid world size argument for -w.\n");
+                _parseError = true;
+                return;
+            }
+            _heightmapWorldSize.x = (float)atof(parts[0].c_str());
+            _heightmapWorldSize.y = (float)atof(parts[1].c_str());
+            _heightmapWorldSize.z = (float)atof(parts[2].c_str());
+            if (_heightmapWorldSize.x == 0 || _heightmapWorldSize.y == 0 || _heightmapWorldSize.z == 0)
+            {
+                LOG(1, "Error: invalid world size argument for -w.\n");
+                _parseError = true;
+                return;
+            }
+        }
+        break;
     case 'p':
         _fontPreview = true;
         break;
     case 's':
-        // Font Size
-
-        // old format was -s##
-        if (str.length() > 2)
+        if (_normalMap)
         {
-            char n = str[2];
-            if (n > '0' && n <= '9')
+            (*index)++;
+            if (*index >= options.size())
             {
-                const char* number = str.c_str() + 2;
-                _fontSize = atoi(number);
-                break;
+                LOG(1, "Error: missing argument for -s.\n");
+                _parseError = true;
+                return;
+            }
+            // Heightmap size
+            std::vector<std::string> parts;
+            splitString(options[*index].c_str(), &parts);
+            if (parts.size() != 2 ||
+                (_heightmapResolution[0] = atoi(parts[0].c_str())) <= 0 ||
+                (_heightmapResolution[1] = atoi(parts[1].c_str())) <= 0)
+            {
+                LOG(1, "Error: invalid argument for -s.\n");
+                _parseError = true;
+                return;
             }
-        }
-
-        (*index)++;
-        if (*index < options.size())
-        {
-            _fontSize = atoi(options[*index].c_str());
         }
         else
         {
-            LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
-            _parseError = true;
-            return;
+            // Font Size
+
+            // old format was -s##
+            if (str.length() > 2)
+            {
+                char n = str[2];
+                if (n > '0' && n <= '9')
+                {
+                    const char* number = str.c_str() + 2;
+                    _fontSize = atoi(number);
+                    break;
+                }
+            }
+
+            (*index)++;
+            if (*index < options.size())
+            {
+                _fontSize = atoi(options[*index].c_str());
+            }
+            else
+            {
+                LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
+                _parseError = true;
+                return;
+            }
         }
         break;
     case 't':
@@ -434,6 +576,7 @@ void EncoderArguments::readOption(const std::vector<std::string>& options, size_
             else if (__logVerbosity > 4)
                 __logVerbosity = 4;
         }
+        break;
     default:
         break;
     }
@@ -446,10 +589,12 @@ void EncoderArguments::setInputfilePath(const std::string& inputPath)
 
 void EncoderArguments::setOutputfilePath(const std::string& outputPath)
 {
+    std::string ext = getOutputFileExtension();
+
     if (outputPath.size() > 0 && outputPath[0] != '\0')
     {
         std::string realPath = getRealPath(outputPath);
-        if (endsWith(realPath.c_str(), ".gpb"))
+        if (endsWith(realPath.c_str(), ext.c_str()))
         {
             _fileOutputPath.assign(realPath);
         }
@@ -459,7 +604,7 @@ void EncoderArguments::setOutputfilePath(const std::string& outputPath)
 
             _fileOutputPath.assign(outputPath);
             _fileOutputPath.append(filenameNoExt);
-            _fileOutputPath.append(".gpb");
+            _fileOutputPath.append(ext);
         }
         else
         {
@@ -470,7 +615,7 @@ void EncoderArguments::setOutputfilePath(const std::string& outputPath)
                 _fileOutputPath = realPath.substr(0, pos);
                 _fileOutputPath.append("/");
                 _fileOutputPath.append(filenameNoExt);
-                _fileOutputPath.append(".gpb");
+                _fileOutputPath.append(ext);
             }
         }
     }

+ 42 - 1
gameplay-encoder/src/EncoderArguments.h

@@ -1,6 +1,8 @@
 #ifndef ENCODERARGUMENTS_H_
 #define ENCODERARGUMENTS_H_
 
+#include "Vector3.h"
+
 namespace gameplay
 {
 
@@ -17,7 +19,9 @@ public:
         FILEFORMAT_DAE,
         FILEFORMAT_FBX,
         FILEFORMAT_TTF,
-        FILEFORMAT_GPB
+        FILEFORMAT_GPB,
+        FILEFORMAT_PNG,
+        FILEFORMAT_RAW
     };
 
     struct HeightmapOption
@@ -25,6 +29,15 @@ public:
         std::vector<std::string> nodeIds;
         std::string filename;
         bool isHighPrecision;
+        int width;
+        int height;
+    };
+
+    struct NormalMapOption
+    {
+        std::string inputFile;
+        std::string outputFile;
+        Vector3 worldSize;
     };
 
     /**
@@ -74,6 +87,11 @@ public:
      */
     std::string getOutputFilePath() const;
 
+    /**
+     * Returns the output file extension.
+     */
+    std::string getOutputFileExtension() const;
+
     const std::vector<std::string>& getGroupAnimationNodeId() const;
     const std::vector<std::string>& getGroupAnimationAnimationId() const;
 
@@ -82,6 +100,25 @@ public:
 
     const std::vector<HeightmapOption>& getHeightmapOptions() const;
 
+    /**
+     * Returns true if normal map generation is turned on.
+     */
+    bool normalMapGeneration() const;
+    
+    /**
+     * Returns the supplied intput heightmap resolution.
+     *
+     * This option is only applicable for normal map generation.
+     */
+    void getHeightmapResolution(int* x, int* y) const;
+
+    /**
+     * Returns world size option.
+     *
+     * This option is only applicable for normal map generation.
+     */
+    const Vector3& getHeightmapWorldSize() const;
+    
     /**
      * Returns true if an error occurred while parsing the command line arguments.
      */
@@ -143,6 +180,10 @@ private:
 
     unsigned int _fontSize;
 
+    bool _normalMap;
+    Vector3 _heightmapWorldSize;
+    int _heightmapResolution[2];
+
     bool _parseError;
     bool _fontPreview;
     bool _textOutput;

+ 1 - 1
gameplay-encoder/src/GPBFile.cpp

@@ -338,7 +338,7 @@ void GPBFile::adjust()
     const std::vector<EncoderArguments::HeightmapOption>& heightmaps = EncoderArguments::getInstance()->getHeightmapOptions();
     for (unsigned int i = 0, count = heightmaps.size(); i < count; ++i)
     {
-        Heightmap::generate(heightmaps[i].nodeIds, heightmaps[i].filename.c_str(), heightmaps[i].isHighPrecision);
+        Heightmap::generate(heightmaps[i].nodeIds, heightmaps[i].width, heightmaps[i].height, heightmaps[i].filename.c_str(), heightmaps[i].isHighPrecision);
     }
 }
 

+ 41 - 29
gameplay-encoder/src/Heightmap.cpp

@@ -15,13 +15,17 @@ struct HeightmapThreadData
     const Vector3* rayDirection;        // [in]
     const std::vector<Mesh*>* meshes;   // [in]
     const BoundingVolume* bounds;       // [in]
-    int minX;                           // [in]
-    int maxX;                           // [in]
-    int minZ;                           // [in]
-    int maxZ;                           // [in]
+    float minX;                         // [in]
+    float maxX;                         // [in]
+    float minZ;                         // [in]
+    float maxZ;                         // [in]
+    float stepX;                        // [in]
+    float stepZ;                        // [in]
     float minHeight;                    // [out]
     float maxHeight;                    // [out]
     float* heights;                     // [in][out]
+    int width;                          // [in]
+    int height;                         // [in]
     int heightIndex;                    // [in]
 };
 
@@ -36,7 +40,7 @@ bool intersect(const Vector3& rayOrigin, const Vector3& rayDirection, const Vect
 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);
 bool intersect(const Vector3& rayOrigin, const Vector3& rayDirection, const std::vector<Vertex>& vertices, const std::vector<MeshPart*>& parts, Vector3* point);
 
-void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* filename, bool highP)
+void Heightmap::generate(const std::vector<std::string>& nodeIds, int width, int height, const char* filename, bool highP)
 {
     LOG(1, "Generating heightmap: %s...\n", filename);
 
@@ -92,12 +96,10 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
     Vector3 rayOrigin(0, bounds.max.y + 10, 0);
     Vector3 rayDirection(0, -1, 0);
 
-    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 minX = bounds.min.x;
+    float maxX = bounds.max.x;
+    float minZ = bounds.min.z;
+    float maxZ = bounds.max.z;
     int size = width * height;
     float* heights = new float[size];
     float minHeight = FLT_MAX;
@@ -105,11 +107,14 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
 
     __totalHeightmapScanlines = height;
 
+    // Determine # of threads to spawn
+    int threadCount = std::min(THREAD_COUNT, height);
+
     // Split the work into separate threads to make max use of available cpu cores and speed up computation.
-    HeightmapThreadData threadData[THREAD_COUNT];
-    THREAD_HANDLE threads[THREAD_COUNT];
-    int stepSize = height / THREAD_COUNT;
-    for (int i = 0; i < THREAD_COUNT; ++i)
+    HeightmapThreadData* threadData = new HeightmapThreadData[threadCount];
+    THREAD_HANDLE* threads = new THREAD_HANDLE[threadCount];
+    int stepSize = height / threadCount;
+    for (int i = 0, remaining = height; i < threadCount; ++i, remaining -= stepSize)
     {
         HeightmapThreadData& data = threadData[i];
         data.rayHeight = rayOrigin.y;
@@ -120,9 +125,13 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
         data.maxX = maxX;
         data.minZ = minZ + (stepSize * i);
         data.maxZ = data.minZ + stepSize - 1;
-        if (i == THREAD_COUNT - 1)
+        if (i == threadCount - 1)
             data.maxZ = maxZ;
+        data.stepX = (maxX - minX) / width;
+        data.stepZ = (maxZ - minZ) / height;
         data.heights = heights;
+        data.width = width;
+        data.height = remaining > stepSize ? stepSize : remaining;
         data.heightIndex = width * (stepSize * i);
 
         // Start the processing thread
@@ -134,14 +143,14 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
     }
 
     // Wait for all threads to terminate
-    waitForThreads(THREAD_COUNT, threads);
+    waitForThreads(threadCount, threads);
 
     // Close all thread handles and free memory allocations.
-    for (int i = 0; i < THREAD_COUNT; ++i)
+    for (int i = 0; i < threadCount; ++i)
         closeThread(threads[i]);
 
     // Update min/max height from all completed threads
-    for (int i = 0; i < THREAD_COUNT; ++i)
+    for (int i = 0; i < threadCount; ++i)
     {
         if (threadData[i].minHeight < minHeight)
             minHeight = threadData[i].minHeight;
@@ -153,7 +162,7 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
 
     if (__failedRayCasts)
     {
-        LOG(1, "Warning: %d triangle intersections failed for heightmap: %s\n", __failedRayCasts, filename);
+        LOG(2, "Warning: %d triangle intersections failed for heightmap: %s\n", __failedRayCasts, filename);
 
         // Go through and clamp any height values that are set to -FLT_MAX to the min recorded height value
         // (otherwise the range of height values will be far too large).
@@ -234,6 +243,10 @@ void Heightmap::generate(const std::vector<std::string>& nodeIds, const char* fi
     LOG(1, "Saved heightmap: %s\n", filename);
 
 error:
+    if (threadData)
+        delete[] threadData;
+    if (threads)
+        delete[] threads;
     if (heights)
         delete[] heights;
     if (fp)
@@ -252,10 +265,6 @@ int generateHeightmapChunk(void* threadData)
 
     Vector3 rayOrigin(0, data->rayHeight, 0);
     const Vector3& rayDirection = *data->rayDirection;
-    int minX = data->minX;
-    int maxX = data->maxX;
-    int minZ = data->minZ;
-    int maxZ = data->maxZ;
     const std::vector<Mesh*>& meshes = *data->meshes;
     float* heights = data->heights;
 
@@ -264,15 +273,18 @@ int generateHeightmapChunk(void* threadData)
     float maxHeight = -FLT_MAX;
     int index = data->heightIndex;
 
-    for (int z = minZ; z <= maxZ; ++z)
+    int zi = 0;
+    for (float z = data->minZ; zi < data->height; z += data->stepZ, ++zi)
     {
         LOG(1, "\r\t%d%%", (int)(((float)__processedHeightmapScanLines / __totalHeightmapScanlines) * 100.0f));
 
-        rayOrigin.z = (float)z;
-        for (int x = minX; x <= maxX; ++x)
+        rayOrigin.z = z;
+
+        int xi = 0;
+        for (float x = data->minX; xi < data->width; x += data->stepX, ++xi)
         {
             float h = -FLT_MAX;
-            rayOrigin.x = (float)x;
+            rayOrigin.x = x;
 
             for (unsigned int i = 0, count = meshes.size(); i < count; ++i)
             {
@@ -283,7 +295,7 @@ int generateHeightmapChunk(void* threadData)
                 if (!intersect(rayOrigin, rayDirection, mesh->bounds.min, mesh->bounds.max))
                     continue;
 
-                // Computer intersection point of ray with mesh
+                // Compute the intersection point of ray with mesh
                 if (intersect(rayOrigin, rayDirection, mesh->vertices, mesh->parts, &intersectionPoint))
                 {
                     if (intersectionPoint.y > h)

+ 3 - 2
gameplay-encoder/src/Heightmap.h

@@ -14,11 +14,12 @@ public:
      * Generates heightmap data and saves the result to the specified filename (PNG file).
      *
      * @param nodeIds List of node ids to include in the heightmap generation.
+     * @param width Width of the produced heightmap image.
+     * @param height Height of the produced  heightmap image.
      * @param filename Output PNG file to write the heightmap image to.
      * @param highP Use packed 24-bit (RGB) instead of standard 8-bit grayscale.
      */
-    static void generate(const std::vector<std::string>& nodeIds, const char* filename, bool highP = false);
-
+    static void generate(const std::vector<std::string>& nodeIds, int width, int height, const char* filename, bool highP = false);
 
 };
 

+ 264 - 0
gameplay-encoder/src/Image.cpp

@@ -0,0 +1,264 @@
+#include "Image.h"
+#include "Base.h"
+
+namespace gameplay
+{
+
+Image::Image() :
+    _data(NULL), _format(RGBA), _width(0), _height(0), _bpp(0)
+{
+}
+
+Image::~Image()
+{
+    delete[] _data;
+}
+
+Image* Image::create(const char* path)
+{
+    // Open the file.
+    FILE* fp = fopen(path, "rb");
+    if (fp == NULL)
+    {
+        LOG(1, "Failed to open image file '%s'.\n", path);
+        return NULL;
+    }
+
+    // Verify PNG signature.
+    unsigned char sig[8];
+    if (fread(sig, 1, 8, fp) != 8 || png_sig_cmp(sig, 0, 8) != 0)
+    {
+        LOG(1, "Failed to load file '%s'; not a valid PNG.\n", path);
+        if (fclose(fp) != 0)
+        {
+            LOG(1, "Failed to close image file '%s'.\n", path);
+        }
+        return NULL;
+    }
+
+    // Initialize png read struct (last three parameters use stderr+longjump if NULL).
+    png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (png == NULL)
+    {
+        LOG(1, "Failed to create PNG structure for reading PNG file '%s'.\n", path);
+        if (fclose(fp) != 0)
+        {
+            LOG(1, "Failed to close image file '%s'.\n", path);
+        }
+        return NULL;
+    }
+
+    // Initialize info struct.
+    png_infop info = png_create_info_struct(png);
+    if (info == NULL)
+    {
+        LOG(1, "Failed to create PNG info structure for PNG file '%s'.\n", path);
+        if (fclose(fp) != 0)
+        {
+            LOG(1, "Failed to close image file '%s'.\n", path);
+        }
+        png_destroy_read_struct(&png, NULL, NULL);
+        return NULL;
+    }
+
+    // Initialize file io.
+    png_init_io(png, fp);
+
+    // Indicate that we already read the first 8 bytes (signature).
+    png_set_sig_bytes(png, 8);
+
+    // Read the entire image into memory.
+    png_read_png(png, info, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL);
+
+    Image* image = new Image();
+    image->_width = png_get_image_width(png, info);
+    image->_height = png_get_image_height(png, info);
+
+    png_byte colorType = png_get_color_type(png, info);
+    switch (colorType)
+    {
+    case PNG_COLOR_TYPE_GRAY:
+        image->_bpp = 1;
+        image->_format = Image::LUMINANCE;
+        break;
+
+    case PNG_COLOR_TYPE_RGBA:
+        image->_bpp = 4;
+        image->_format = Image::RGBA;
+        break;
+
+    case PNG_COLOR_TYPE_RGB:
+        image->_bpp = 3;
+        image->_format = Image::RGB;
+        break;
+
+    default:
+        LOG(1, "Unsupported PNG color type (%d) for image file '%s'.\n", (int)colorType, path);
+        if (fclose(fp) != 0)
+        {
+            LOG(1, "Failed to close image file '%s'.\n", path);
+        }
+        png_destroy_read_struct(&png, &info, NULL);
+        return NULL;
+    }
+
+    size_t stride = png_get_rowbytes(png, info);
+
+    // Allocate image data.
+    image->_data = new unsigned char[stride * image->_height];
+
+    // Read rows into image data.
+    png_bytepp rows = png_get_rows(png, info);
+    for (unsigned int i = 0; i < image->_height; ++i)
+    {
+        memcpy(image->_data+(stride * i), rows[i], stride);
+    }
+
+    // Clean up.
+    png_destroy_read_struct(&png, &info, NULL);
+    if (fclose(fp) != 0)
+    {
+        LOG(1, "Failed to close image file '%s'.\n", path);
+    }
+
+    return image;
+}
+
+Image* Image::create(Format format, unsigned int width, unsigned int height)
+{
+    unsigned int bpp;
+    switch (format)
+    {
+    case LUMINANCE:
+        bpp = 1;
+        break;
+    case RGB:
+        bpp = 3;
+        break;
+    case RGBA:
+        bpp = 4;
+        break;
+    default:
+        LOG(1, "Invalid image format passed to create.\n");
+        return NULL;
+    }
+
+    Image* image = new Image();
+    image->_format = format;
+    image->_width = width;
+    image->_height = height;
+    image->_bpp = bpp;
+    image->_data = new unsigned char[width * height * bpp];
+    memset(image->_data, 0, width * height * bpp);
+    return image;
+}
+
+void* Image::getData() const
+{
+    return _data;
+}
+
+void Image::setData(void* data)
+{
+    memcpy(_data, data, _width * _height * _bpp);
+}
+
+Image::Format Image::getFormat() const
+{
+    return _format;
+}
+
+unsigned int Image::getHeight() const
+{
+    return _height;
+}
+        
+unsigned int Image::getWidth() const
+{
+    return _width;
+}
+
+unsigned int Image::getBpp() const
+{
+    return _bpp;
+}
+
+int getPNGColorType(Image::Format format)
+{
+    switch (format)
+    {
+    case Image::LUMINANCE:
+        return PNG_COLOR_TYPE_GRAY;
+    case Image::RGB:
+        return PNG_COLOR_TYPE_RGB;
+    case Image::RGBA:
+        return PNG_COLOR_TYPE_RGBA;
+    }
+
+    return PNG_COLOR_TYPE_RGBA;
+}
+
+void Image::save(const char* path)
+{
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_bytep row = NULL;
+    unsigned int stride;
+    int index;
+
+    FILE* fp = fopen(path, "wb");
+    if (fp == NULL)
+    {
+        LOG(1, "Error: Failed to open image for writing: %s\n", path);
+        goto error;
+    }
+
+    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (png_ptr == NULL)
+    {
+        LOG(1, "Error: Write struct creation failed: %s\n", path);
+        goto error;
+    }
+
+    info_ptr = png_create_info_struct(png_ptr);
+    if (info_ptr == NULL)
+    {
+        LOG(1, "Error: Info struct creation failed: %s\n", path);
+        goto error;
+    }
+
+    png_init_io(png_ptr, fp);
+
+    png_set_IHDR(png_ptr, info_ptr, _width, _height, 8, getPNGColorType(_format), 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
+    stride = _bpp * _width * sizeof(png_byte);
+    row = (png_bytep)malloc(stride);
+
+    index = 0;
+    for (unsigned int y = 0; y < _height; ++y)
+    {
+        for (unsigned int x = 0; x < stride; ++x)
+        {
+            // Write data
+            row[x] = (png_byte)_data[index++];
+        }
+        png_write_row(png_ptr, row);
+    }
+
+    png_write_end(png_ptr, NULL);
+
+error:
+    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);
+}
+
+}

+ 123 - 0
gameplay-encoder/src/Image.h

@@ -0,0 +1,123 @@
+#ifndef IMAGE_H__
+#define IMAGE_H__
+
+namespace gameplay
+{
+
+/**
+ * Represents an image (currently only supports PNG files).
+ */
+class Image
+{
+public:
+
+    /**
+     * Defines the set of supported image formats.
+     */
+    enum Format
+    {
+        LUMINANCE,
+        RGB,
+        RGBA
+    };
+
+    /**
+     * Destructor.
+     */
+    ~Image();
+
+    /**
+     * Creates an image from the image file at the given path.
+     * 
+     * @param path The path to the image file.
+     * @return The newly created image.
+     */
+    static Image* create(const char* path);
+
+    /**
+     * Creates a new empty image of the given format and size.
+     *
+     * @param format Image format.
+     * @param width Image width.
+     * @param height Image height.
+     * @return The newly created image.
+     */
+    static Image* create(Format format, unsigned int width, unsigned int height);
+
+    /**
+     * Gets the image's raw pixel data.
+     * 
+     * @return The image's pixel data.
+     */
+    void* getData() const;
+
+    /** 
+     * Sets the image's raw pixel data.
+     *
+     * The passed in data MUST be at least width*height*bpp
+     * bytes of data.
+     */
+    void setData(void* data);
+
+    /**
+     * Gets the image's format.
+     * 
+     * @return The image's format.
+     */
+    Format getFormat() const;
+
+    /**
+     * Gets the height of the image.
+     * 
+     * @return The height of the image.
+     */
+    unsigned int getHeight() const;
+        
+    /**
+     * Gets the width of the image.
+     * 
+     * @return The width of the image.
+     */
+    unsigned int getWidth() const;
+
+    /**
+     * Returns the number of bytes per pixel for this image.
+     *
+     * @return The number of bytes per pixel.
+     */
+    unsigned int getBpp() const;
+
+    /**
+     * Saves the contents of the image as a PNG to the specified location.
+     *
+     * @param path Path to save to.
+     */
+    void save(const char* path);
+
+private:
+
+    /**
+     * Hidden constructor.
+     */
+    Image();
+
+    /**
+     * Hidden copy constructor.
+     */
+    Image(const Image&);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    Image& operator=(const Image&);
+
+    unsigned char* _data;
+    Format _format;
+    unsigned int _height;
+    unsigned int _width;
+    unsigned int _bpp;
+};
+
+}
+
+#endif

+ 268 - 0
gameplay-encoder/src/NormalMapGenerator.cpp

@@ -0,0 +1,268 @@
+#include "NormalMapGenerator.h"
+#include "Image.h"
+#include "Base.h"
+
+namespace gameplay
+{
+
+NormalMapGenerator::NormalMapGenerator(const char* inputFile, const char* outputFile, int resolutionX, int resolutionY, const Vector3& worldSize)
+    : _inputFile(inputFile), _outputFile(outputFile), _resolutionX(resolutionX), _resolutionY(resolutionY), _worldSize(worldSize)
+{
+}
+
+NormalMapGenerator::~NormalMapGenerator()
+{
+}
+
+bool equalsIgnoreCase(const std::string& s1, const std::string& s2)
+{
+    size_t l1 = s1.size();
+    size_t l2 = s2.size();
+    if (l1 != l2)
+        return false;
+
+    for (size_t i = 0; i < l1; ++i)
+    {
+        if (tolower(s1[i]) != tolower(s2[i]))
+            return false;
+    }
+
+    return true;
+}
+
+float getHeight(float* heights, int width, int height, int x, int y)
+{
+    if (x < 0)
+        x = 0;
+    else if (x >= width)
+        x = width-1;
+    if (y < 0)
+        y = 0;
+    else if (y >= height)
+        y = height-1;
+    return heights[y*width+x];
+}
+
+void calculateNormal(
+    float x1, float y1, float z1,
+    float x2, float y2, float z2,
+    float x3, float y3, float z3,
+    Vector3* normal)
+{
+    Vector3 E(x1, y1, z1);
+    Vector3 F(x2, y2, z2);
+    Vector3 G(x3, y3, z3);
+
+    Vector3 P, Q;
+    Vector3::subtract(F, E, &P);
+    Vector3::subtract(G, E, &Q);
+
+    Vector3::cross(Q, P, normal);
+}
+
+void NormalMapGenerator::generate()
+{
+    // Load the input heightmap
+    float* heights = NULL;
+    size_t pos = _inputFile.find_last_of('.');
+    std::string ext = pos == std::string::npos ? "" : _inputFile.substr(pos, _inputFile.size()-pos);
+    if (equalsIgnoreCase(ext, ".png"))
+    {
+        // Load heights from PNG image
+        Image* image = Image::create(_inputFile.c_str());
+        if (image == NULL)
+        {
+            LOG(1, "Failed to load input heightmap PNG: %s.\n", _inputFile.c_str());
+            return;
+        }
+
+        // TODO: Add command-line argument for high precision heightmaps
+
+        _resolutionX = image->getWidth();
+        _resolutionY = image->getHeight();
+        int size = _resolutionX * _resolutionY;
+        heights = new float[size];
+        unsigned char* data = (unsigned char*)image->getData();
+        for (int i = 0; i < size; ++i)
+        {
+            switch (image->getFormat())
+            {
+            case Image::LUMINANCE:
+                heights[i] = data[i];
+                break;
+            case Image::RGB:
+            case Image::RGBA:
+                {
+                    int pos = i * image->getBpp();
+                    heights[i] = (data[pos] + data[pos+1] + data[pos+2]) / 3.0f;
+                }
+                break;
+            default:
+                heights[i] = 0.0f;
+                break;
+            }
+            heights[i] = (heights[i] / 255.0f) * _worldSize.y;
+        }
+        SAFE_DELETE(image);
+    }
+    else if (equalsIgnoreCase(ext, ".raw"))
+    {
+        // Load heights from RAW 8 or 16-bit file
+        if (_resolutionX <= 0 || _resolutionY <= 0)
+        {
+            LOG(1, "Missing resolution argument - must be explicitly specified for RAW heightmap files: %s\n.", _inputFile.c_str());
+            return;
+        }
+
+        // TODO
+        heights = new float[_resolutionX * _resolutionY];
+        // TODO
+
+        LOG(1, "RAW files not yet implemented...");
+        return;
+    }
+    else
+    {
+        LOG(1, "Unsupported input heightmap file (must be a valid PNG or RAW file: %s.\n", _inputFile.c_str());
+        return;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //
+    // NOTE: This method assumes the heightmap geometry is generated as follows.
+    //
+    //   -----------
+    //  | / | / | / |
+    //  |-----------|
+    //  | / | / | / |
+    //  |-----------|
+    //  | / | / | / |
+    //   -----------
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    struct NormalPixel
+    {
+        unsigned char r, g, b;
+    };
+    NormalPixel* normalPixels = new NormalPixel[_resolutionX * _resolutionY];
+
+    struct Face
+    {
+        Vector3 normal1;
+        Vector3 normal2;
+    };
+
+    int progressMax = (_resolutionX-1) * (_resolutionY-1) + _resolutionX * _resolutionY;
+    int progress = 0;
+
+    Vector2 scale(_worldSize.x / (_resolutionX-1), _worldSize.z / (_resolutionY-1));
+
+    // First calculate all face normals for the heightmap
+    LOG(1, "Calculating normals... 0%%");
+    Face* faceNormals = new Face[(_resolutionX - 1) * (_resolutionY - 1)];
+    Vector3 v1, v2;
+    for (int z = 0; z < _resolutionY-1; z++)
+    {
+        for (int x = 0; x < _resolutionX-1; x++)
+        {
+            float topLeftHeight = getHeight(heights, _resolutionX, _resolutionY, x, z);
+            float bottomLeftHeight = getHeight(heights, _resolutionX, _resolutionY, x, z + 1);
+            float bottomRightHeight = getHeight(heights, _resolutionX, _resolutionY, x + 1, z + 1);
+            float topRightHeight = getHeight(heights, _resolutionX, _resolutionY, x + 1, z);
+
+            // Triangle 1
+            calculateNormal(
+                (float)x*scale.x, bottomLeftHeight, (float)(z + 1)*scale.y,
+                (float)x*scale.x, topLeftHeight, (float)z*scale.y,
+                (float)(x + 1)*scale.x, topRightHeight, (float)z*scale.y,
+                &faceNormals[z*(_resolutionX-1)+x].normal1);
+
+            // Triangle 2
+            calculateNormal(
+                (float)x*scale.x, bottomLeftHeight, (float)(z + 1)*scale.y,
+                (float)(x + 1)*scale.x, topRightHeight, (float)z*scale.y,
+                (float)(x + 1)*scale.x, bottomRightHeight, (float)(z + 1)*scale.y,
+                &faceNormals[z*(_resolutionX-1)+x].normal2);
+
+            ++progress;
+            LOG(1, "\rCalculating normals... %d%%", (int)(((float)progress / progressMax) * 100));
+        }
+    }
+
+    // Free height array
+    delete[] heights;
+    heights = NULL;
+
+    // Smooth normals by taking an average for each vertex
+    Vector3 normal;
+    for (int z = 0; z < _resolutionY; z++)
+    {
+        for (int x = 0; x < _resolutionX; x++)
+        {
+            // Reset normal sum
+            normal.set(0, 0, 0);
+
+            if (x > 0)
+            {
+                if (z > 0)
+                {
+                    // Top left
+                    normal.add(faceNormals[(z-1)*(_resolutionX-1) + (x-1)].normal2);
+                }
+
+                if (z < (_resolutionY - 1))
+                {
+                    // Bottom left
+                    normal.add(faceNormals[z*(_resolutionX-1) + (x - 1)].normal1);
+                    normal.add(faceNormals[z*(_resolutionX-1) + (x - 1)].normal2);
+                }
+            }
+
+            if (x < (_resolutionX - 1))
+            {
+                if (z > 0)
+                {
+                    // Top right
+                    normal.add(faceNormals[(z-1)*(_resolutionX-1) + x].normal1);
+                    normal.add(faceNormals[(z-1)*(_resolutionX-1) + x].normal2);
+                }
+
+                if (z < (_resolutionY - 1))
+                {
+                    // Bottom right
+                    normal.add(faceNormals[z*(_resolutionX-1) + x].normal1);
+                }
+            }
+
+            // We don't have to worry about weighting the normals by
+            // the surface area of the triangles since a heightmap 
+            // guarantees that all triangles have the same surface area.
+            normal.normalize();
+
+            // Store this vertex normal
+            NormalPixel& pixel = normalPixels[z*_resolutionX + x];
+            pixel.r = (unsigned char)((normal.x + 1.0f) * 0.5f * 255.0f);
+            pixel.g = (unsigned char)((normal.y + 1.0f) * 0.5f * 255.0f);
+            pixel.b = (unsigned char)((normal.z + 1.0f) * 0.5f * 255.0f);
+
+            ++progress;
+            LOG(1, "\rCalculating normals... %d%%", (int)(((float)progress / progressMax) * 100));
+        }
+    }
+
+    LOG(1, "\rCalculating normals... Done.\n");
+
+    // Create and save an image for the normal map
+    Image* normalMap = Image::create(Image::RGB, _resolutionX, _resolutionY);
+    normalMap->setData(normalPixels);
+    normalMap->save(_outputFile.c_str());
+
+    LOG(1, "Normal map saved to '%s'.\n", _outputFile.c_str());
+
+    // Free temp data
+    delete[] normalPixels;
+    normalPixels = NULL;
+}
+
+}

+ 35 - 0
gameplay-encoder/src/NormalMapGenerator.h

@@ -0,0 +1,35 @@
+#ifndef NORMALMAPGENERATOR_H_
+#define NORMALMAPGENERATOR_H_
+
+#include "Vector3.h"
+
+namespace gameplay
+{
+
+class NormalMapGenerator
+{
+
+public:
+
+    NormalMapGenerator(const char* inputFile, const char* outputFile, int resolutionX, int resolutionY, const Vector3& worldSize);
+    ~NormalMapGenerator();
+
+    void generate();
+
+private:
+
+    // Hidden copy/assignment
+    NormalMapGenerator(const NormalMapGenerator&);
+    NormalMapGenerator& operator=(const NormalMapGenerator&);
+
+    std::string _inputFile;
+    std::string _outputFile;
+    int _resolutionX;
+    int _resolutionY;
+    Vector3 _worldSize;
+
+};
+
+}
+
+#endif

+ 18 - 0
gameplay-encoder/src/main.cpp

@@ -4,6 +4,7 @@
 #include "TTFFontEncoder.h"
 #include "GPBDecoder.h"
 #include "EncoderArguments.h"
+#include "NormalMapGenerator.h"
 
 using namespace gameplay;
 
@@ -102,6 +103,23 @@ int main(int argc, const char** argv)
             decoder.readBinary(realpath);
             break;
         }
+    case EncoderArguments::FILEFORMAT_PNG:
+    case EncoderArguments::FILEFORMAT_RAW:
+        {
+            if (arguments.normalMapGeneration())
+            {
+                int x, y;
+                arguments.getHeightmapResolution(&x, &y);
+                NormalMapGenerator generator(arguments.getFilePath().c_str(), arguments.getOutputFilePath().c_str(), x, y, arguments.getHeightmapWorldSize());
+                generator.generate();
+            }
+            else
+            {
+                LOG(1, "Error: Nothing to do for specified file format. Did you forget an option?\n");
+                return -1;
+            }
+            break;
+        }
    default:
         {
             LOG(1, "Error: Unsupported file format: %s\n", arguments.getFilePathPointer());

+ 5 - 0
gameplay-luagen/src/Base.h

@@ -2,6 +2,7 @@
 #include <cassert>
 #include <fstream>
 #include <iostream>
+#include <sstream>
 #include <map>
 #include <set>
 #include <string>
@@ -42,6 +43,10 @@ using namespace std;
 
 extern void printError(const char* format, ...);
 
+extern void writeFile(const string& path, const string& text);
+
+extern std::vector<string> generatedFiles;
+
 // Current function macro.
 #ifdef WIN32
 #define __current__func__ __FUNCTION__

+ 6 - 4
gameplay-luagen/src/ClassBinding.cpp

@@ -88,7 +88,7 @@ void ClassBinding::write(string dir, const set<string>& includes, string* bindin
     // Write out the header.
     {
         string path = dir + string("lua_") + uniquename + string(".h");
-        ofstream o(path.c_str());
+        ostringstream o;
         if (!o)
         {
             GP_ERROR("Failed to open file '%s' for generating Lua bindings.", path.c_str());
@@ -122,13 +122,14 @@ void ClassBinding::write(string dir, const set<string>& includes, string* bindin
         if (bindingNS)
             o << "}\n\n";
         o << "#endif\n";
-        o.close();
+
+        writeFile(path, o.str());
     }
 
     // Write out the implementation.
     {
         string path = dir + string("lua_") + uniquename + string(".cpp");
-        ofstream o(path.c_str());
+        ostringstream o;
         if (!o)
         {
             GP_ERROR("Failed to open file '%s' for generating Lua bindings.", path.c_str());
@@ -288,7 +289,8 @@ void ClassBinding::write(string dir, const set<string>& includes, string* bindin
 
         if (bindingNS)
             o << "}\n";
-        o.close();
+
+        writeFile(path, o.str());
     }
 
     SAFE_DELETE(constructorUniqueName);

+ 74 - 60
gameplay-luagen/src/FunctionBinding.cpp

@@ -14,7 +14,7 @@ static inline void outputReturnValue(ostream& o, const FunctionBinding& b, int i
 static inline std::string getTypeName(const FunctionBinding::Param& param);
 
 FunctionBinding::Param::Param(FunctionBinding::Param::Type type, Kind kind, const string& info) : 
-    type(type), kind(kind), info(info), hasDefaultValue(false)
+    type(type), kind(kind), info(info), hasDefaultValue(false), levelsOfIndirection(0)
 {
 }
 
@@ -496,7 +496,10 @@ ostream& operator<<(ostream& o, const FunctionBinding::Param& param)
     o << getTypeName(param);
 
     if (param.kind == FunctionBinding::Param::KIND_POINTER)
-        o << "*";
+    {
+        for (int i = 0; i < param.levelsOfIndirection; ++i)
+            o << "*";
+    }
 
     return o;
 }
@@ -695,129 +698,140 @@ static inline void outputBindingInvocation(ostream& o, const FunctionBinding& b,
     outputReturnValue(o, b, indentLevel);
 }
 
+void writeObjectTemplateType(ostream& o, const FunctionBinding::Param& p)
+{
+    o << getTypeName(p);
+    for (int i = 0; i < p.levelsOfIndirection-1; ++i)
+        o << "*";
+}
+
+void writePointerParameter(ostream& o, const char* primitiveType, const FunctionBinding::Param& p, int paramNum, int luaParamIndex, int indentLevel)
+{
+    o << "ScriptUtil::LuaArray<";
+    writeObjectTemplateType(o, p);
+    //o << "> param" << paramNum << "Pointer = ScriptUtil::get" << primitiveType << "Pointer(" << luaParamIndex << ");\n";
+    o << "> param" << paramNum << " = ScriptUtil::get" << primitiveType << "Pointer(" << luaParamIndex << ");\n";
+    //indent(o, indentLevel);
+    //o << p << " param" << paramNum << " = (" << p << ")param" << paramNum << "Pointer;\n";
+}
+
 static inline void outputGetParam(ostream& o, const FunctionBinding::Param& p, int i, int indentLevel, bool offsetIndex, int numBindings)
 {
     indent(o, indentLevel);
     o << "// Get parameter " << i + 1 << " off the stack.\n";
 
-    switch (p.type)
-    {
-    case FunctionBinding::Param::TYPE_UNRECOGNIZED:
-        indent(o, indentLevel);
-        o << "GP_WARN(\"Attempting to get parameter " << i + 1 << " with unrecognized type " << p.info << " as an unsigned integer.\");\n";
-    case FunctionBinding::Param::TYPE_BOOL:
-    case FunctionBinding::Param::TYPE_CHAR:
-    case FunctionBinding::Param::TYPE_SHORT:
-    case FunctionBinding::Param::TYPE_INT:
-    case FunctionBinding::Param::TYPE_LONG:
-    case FunctionBinding::Param::TYPE_UCHAR:
-    case FunctionBinding::Param::TYPE_USHORT:
-    case FunctionBinding::Param::TYPE_UINT:
-    case FunctionBinding::Param::TYPE_ULONG:
-    case FunctionBinding::Param::TYPE_FLOAT:
-    case FunctionBinding::Param::TYPE_DOUBLE:
-    case FunctionBinding::Param::TYPE_STRING:
-    case FunctionBinding::Param::TYPE_ENUM:
-        indent(o, indentLevel);
-        if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::LuaArray<" << getTypeName(p) << ">";
-        else
-            o << p;
-        o << " param" << i + 1 << " = ";
-        break;
-    default:
-        // Ignore these cases.
-        break;
-    }
-
     int paramIndex = (offsetIndex) ? i + 2 : i + 1;
+
     switch (p.type)
     {
     case FunctionBinding::Param::TYPE_BOOL:
+        indent(o, indentLevel);
         if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::getBoolPointer(" << paramIndex << ");\n";
+            writePointerParameter(o, "Bool", p, i+1, paramIndex, indentLevel);
         else
-            o << "ScriptUtil::luaCheckBool(state, " << paramIndex << ");\n";
+            o << p << " param" << i+1 << " = ScriptUtil::luaCheckBool(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_CHAR:
-        o << "(char)luaL_checkint(state, " << paramIndex << ");\n";
+        indent(o, indentLevel);
+        o << p << " param" << i+1 << " = (char)luaL_checkint(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_SHORT:
+        indent(o, indentLevel);
         if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::getShortPointer(" << paramIndex << ");\n";
+            writePointerParameter(o, "Short", p, i+1, paramIndex, indentLevel);
         else
-            o << "(short)luaL_checkint(state, " << paramIndex << ");\n";
+            o << p << " param" << i+1 << " = (short)luaL_checkint(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_INT:
+        indent(o, indentLevel);
         if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::getIntPointer(" << paramIndex << ");\n";
+            writePointerParameter(o, "Int", p, i+1, paramIndex, indentLevel);
         else
-            o << "(int)luaL_checkint(state, " << paramIndex << ");\n";
+            o << p << " param" << i+1 << " = (int)luaL_checkint(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_LONG:
+        indent(o, indentLevel);
         if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::getLongPointer(" << paramIndex << ");\n";
+            writePointerParameter(o, "Long", p, i+1, paramIndex, indentLevel);
         else
-            o << "(long)luaL_checklong(state, " << paramIndex << ");\n";
+            o << p << " param" << i+1 << " = (long)luaL_checklong(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_UCHAR:
+        indent(o, indentLevel);
         if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::getUnsignedCharPointer(" << paramIndex << ");\n";
+            writePointerParameter(o, "UnsignedChar", p, i+1, paramIndex, indentLevel);
         else
-            o << "(unsigned char)luaL_checkunsigned(state, " << paramIndex << ");\n";
+            o << p << " param" << i+1 << " = (unsigned char)luaL_checkunsigned(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_USHORT:
+        indent(o, indentLevel);
         if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::getUnsignedShortPointer(" << paramIndex << ");\n";
+            writePointerParameter(o, "UnsignedShort", p, i+1, paramIndex, indentLevel);
         else
-            o << "(unsigned short)luaL_checkunsigned(state, " << paramIndex << ");\n";
+            o << p << " param" << i+1 << " = (unsigned short)luaL_checkunsigned(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_UINT:
+        indent(o, indentLevel);
         if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::getUnsignedIntPointer(" << paramIndex << ");\n";
+            writePointerParameter(o, "UnsignedInt", p, i+1, paramIndex, indentLevel);
         else
-            o << "(unsigned int)luaL_checkunsigned(state, " << paramIndex << ");\n";
+            o << p << " param" << i+1 << " = (unsigned int)luaL_checkunsigned(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_ULONG:
+        indent(o, indentLevel);
         if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::getUnsignedLongPointer(" << paramIndex << ");\n";
+            writePointerParameter(o, "UnsignedLong", p, i+1, paramIndex, indentLevel);
         else
-            o << "(unsigned long)luaL_checkunsigned(state, " << paramIndex << ");\n";
+            o << p << " param" << i+1 << " = (unsigned long)luaL_checkunsigned(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_FLOAT:
+        indent(o, indentLevel);
         if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::getFloatPointer(" << paramIndex << ");\n";
+            writePointerParameter(o, "Float", p, i+1, paramIndex, indentLevel);
         else
-            o << "(float)luaL_checknumber(state, " << paramIndex << ");\n";
+            o << p << " param" << i+1 << " = (float)luaL_checknumber(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_DOUBLE:
+        indent(o, indentLevel);
         if (p.kind == FunctionBinding::Param::KIND_POINTER)
-            o << "ScriptUtil::getDoublePointer(" << paramIndex << ");\n";
+            writePointerParameter(o, "Double", p, i+1, paramIndex, indentLevel);
         else
-            o << "(double)luaL_checknumber(state, " << paramIndex << ");\n";
+            o << p << " param" << i+1 << " = (double)luaL_checknumber(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_STRING:
-        o << "ScriptUtil::getString(" << paramIndex << ", " << ((p.info == "string") ? "true" : "false") << ");\n";
+        indent(o, indentLevel);
+        o << p << " param" << i+1 << " = ScriptUtil::getString(" << paramIndex << ", " << ((p.info == "string") ? "true" : "false") << ");\n";
         break;
     case FunctionBinding::Param::TYPE_ENUM:
-        o << "(" << p << ")lua_enumFromString_" << Generator::getInstance()->getUniqueNameFromRef(p.info) << "(luaL_checkstring(state, " << paramIndex << "));\n";
+        indent(o, indentLevel);
+        o << p << " param" << i+1 << " = (" << p << ")lua_enumFromString_" << Generator::getInstance()->getUniqueNameFromRef(p.info) << "(luaL_checkstring(state, " << paramIndex << "));\n";
         break;
     case FunctionBinding::Param::TYPE_UNRECOGNIZED:
         // Attempt to retrieve the unrecognized type as an unsigned integer.
-        o << "(" << p.info << ")luaL_checkunsigned(state, " << paramIndex << ");\n";
+        indent(o, indentLevel);
+        o << "GP_WARN(\"Attempting to get parameter " << i + 1 << " with unrecognized type " << p.info << " as an unsigned integer.\");\n";
+        indent(o, indentLevel);
+        o << p << " param" << i+1 << " = (" << p.info << ")luaL_checkunsigned(state, " << paramIndex << ");\n";
         break;
     case FunctionBinding::Param::TYPE_OBJECT:
         {
             indent(o, indentLevel);
             o << "bool param" << i + 1 << "Valid;\n";
             indent(o, indentLevel);
-            o << "ScriptUtil::LuaArray<" << getTypeName(p) << ">";
-            o << " param" << i + 1 << " = ";
-            o << "ScriptUtil::getObjectPointer<";
-            o << Generator::getInstance()->getIdentifier(p.info) << ">(" << paramIndex;
+            o << "ScriptUtil::LuaArray<";
+            writeObjectTemplateType(o, p);
+            //o << "> param" << i+1 << "Pointer = ScriptUtil::getObjectPointer<";
+            o << "> param" << i+1 << " = ScriptUtil::getObjectPointer<";
+            writeObjectTemplateType(o, p);
+            o << ">(" << paramIndex;
             o << ", \"" << Generator::getInstance()->getUniqueNameFromRef(p.info) << "\", ";
             o << ((p.kind != FunctionBinding::Param::KIND_POINTER) ? "true" : "false") << ", &param" << i + 1 << "Valid);\n";
             indent(o, indentLevel);
+            //writeObjectTemplateType(o, p);
+            //o << "* param" << i+1 << " = (";
+            //writeObjectTemplateType(o, p);
+            //o << "*)param" << i+1 << "Pointer;\n";
+            //indent(o, indentLevel);
             o << "if (!param" << i + 1 << "Valid)\n";
             if (numBindings > 1)
             {

+ 2 - 0
gameplay-luagen/src/FunctionBinding.h

@@ -99,6 +99,8 @@ struct FunctionBinding
         string info;
         /** Whether the parameter has a default value. */
         bool hasDefaultValue;
+        /** For pointer types, the number of levels of indirection */
+        int levelsOfIndirection;
     };
 
     /**

+ 41 - 26
gameplay-luagen/src/Generator.cpp

@@ -9,7 +9,7 @@ static bool __printOperatorWarning = false;
 
 // Utility functions (local to this file).
 static string trim(const string& str);
-static string stripTypeQualifiers(const string& typeStr, FunctionBinding::Param::Kind& kind);
+static string stripTypeQualifiers(const string& typeStr, FunctionBinding::Param::Kind& kind, int& levelsOfIndirection);
 static inline bool isWantedFileNormal(const string& s);
 static inline bool isNamespaceFile(const string& s);
 static inline bool isGeneratedBindingFile(const string& s);
@@ -171,13 +171,6 @@ void Generator::run(string inDir, string outDir, string* bindingNS)
     vector<string> oldBindingsFiles;
     getFileList(outDir, oldBindingsFiles, isGeneratedBindingFile);
 
-    // Delete the old bindings.
-    for (unsigned int i = 0; i < oldBindingsFiles.size(); i++)
-    {
-        remove(oldBindingsFiles[i].c_str());
-    }
-
-
     // Get a list of the Doxygen XML files that specify a namespace.
     // Note: we must do this before adding the normal files so that
     // when we process the files sequentially, we process the namespaces
@@ -278,6 +271,16 @@ void Generator::run(string inDir, string outDir, string* bindingNS)
         GP_WARN("Detected the use of variable argument lists; this feature of C++ is not supported.");
     if (__printOperatorWarning)
         GP_WARN("Detected the use of operator overloading; this feature of C++ is not supported.");
+
+    // Delete files that are no longer needed and print warnings for them
+    for (unsigned int i = 0; i < oldBindingsFiles.size(); i++)
+    {
+        if (std::find(generatedFiles.begin(), generatedFiles.end(), oldBindingsFiles[i]) == generatedFiles.end())
+        {
+            GP_WARN("Deleting unused file: %s", oldBindingsFiles[i].c_str());
+            remove(oldBindingsFiles[i].c_str());
+        }
+    }
 }
 
 Generator::Generator()
@@ -975,6 +978,7 @@ FunctionBinding::Param Generator::getParam(XMLElement* e, bool isVariable, strin
         // Get the type string without const or reference qualifiers (and trim whitespace).
         string refId = "";
         string typeStr = "";
+        int levelsOfIndirection = 0;
         FunctionBinding::Param::Kind kind;
         {
             // Attempt to process the type as reference (i.e. class, struct, enum, typedef, etc.) type.
@@ -994,7 +998,7 @@ FunctionBinding::Param Generator::getParam(XMLElement* e, bool isVariable, strin
                 node = node->NextSibling();
             }
 
-            typeStr = stripTypeQualifiers(typeStr, kind);
+            typeStr = stripTypeQualifiers(typeStr, kind, levelsOfIndirection);
         }
 
         if (typeStr == "void")
@@ -1067,6 +1071,7 @@ FunctionBinding::Param Generator::getParam(XMLElement* e, bool isVariable, strin
         {
             p = FunctionBinding::Param(FunctionBinding::Param::TYPE_UNRECOGNIZED, kind, (refId.size() > 0) ? refId : typeStr);
         }
+        p.levelsOfIndirection = levelsOfIndirection;
 
         // Check if the type is a pointer declared with square brackets (i.e. float x[4]).
         XMLElement* arrayElement = NULL;
@@ -1080,6 +1085,7 @@ FunctionBinding::Param Generator::getParam(XMLElement* e, bool isVariable, strin
             if (i != arrayString.npos && k != arrayString.npos)
             {
                 p.kind = FunctionBinding::Param::KIND_POINTER;
+                p.levelsOfIndirection = 1;
                 if (i != k - 1)
                     p.info = arrayString.substr(i + 1, k - (i + 1));
             }
@@ -1375,14 +1381,14 @@ void Generator::generateBindings(string* bindingNS)
         generatingGameplay = true;
 
     string luaAllHStr = _outDir + string(LUA_ALL_BINDINGS_FILENAME) + string(".h");
-    ofstream luaAllH(luaAllHStr.c_str());
+    ostringstream luaAllH;
     string includeGuard = string(LUA_ALL_BINDINGS_FILENAME) + string("_H_");
     transform(includeGuard.begin(), includeGuard.end(), includeGuard.begin(), ::toupper);
     luaAllH << "#ifndef " << includeGuard << "\n";
     luaAllH << "#define " << includeGuard << "\n\n";
     
     string luaAllCppStr = _outDir + string(LUA_ALL_BINDINGS_FILENAME) + string(".cpp");
-    ofstream luaAllCpp(luaAllCppStr.c_str());
+    ostringstream luaAllCpp;
     luaAllCpp << "#include \"Base.h\"\n";
     luaAllCpp << "#include \"" << string(LUA_ALL_BINDINGS_FILENAME) << ".h\"\n\n";
     if (bindingNS)
@@ -1430,7 +1436,7 @@ void Generator::generateBindings(string* bindingNS)
 
                 // Header.
                 string enumHStr = _outDir + string("lua_") + getUniqueName(iter->first) + string(".h");
-                ofstream enumH(enumHStr.c_str());
+                ostringstream enumH;
                 includeGuard = string("lua_") + getUniqueName(iter->first) + string("_H_");
                 transform(includeGuard.begin(), includeGuard.end(), includeGuard.begin(), ::toupper);
                 enumH << "#ifndef " << includeGuard << "\n";
@@ -1453,11 +1459,12 @@ void Generator::generateBindings(string* bindingNS)
                 }
 
                 enumH << "#endif\n";
-                enumH.close();
+
+                writeFile(enumHStr, enumH.str());
 
                 // Implementation.
                 string enumCppStr = _outDir + string("lua_") + getUniqueName(iter->first) + string(".cpp"); 
-                ofstream enumCpp(enumCppStr.c_str());
+                ostringstream enumCpp(enumCppStr.c_str());
                 enumCpp << "#include \"Base.h\"\n";
                 enumCpp << "#include \"lua_" << getUniqueName(iter->first) << ".h\"\n\n";
 
@@ -1539,7 +1546,8 @@ void Generator::generateBindings(string* bindingNS)
                 {
                     enumCpp << "}\n\n";
                 }
-                enumCpp.close();
+
+                writeFile(enumCppStr, enumCpp.str());
             }
         }
     }
@@ -1579,7 +1587,7 @@ void Generator::generateBindings(string* bindingNS)
         // Write out the header file.
         {
             string path = _outDir + string(LUA_GLOBAL_FILENAME) + string(".h");
-            ofstream global(path.c_str());
+            ostringstream global;
             includeGuard = string(LUA_GLOBAL_FILENAME) + string("_H_");
             transform(includeGuard.begin(), includeGuard.end(), includeGuard.begin(), ::toupper);
             global << "#ifndef " << includeGuard << "\n";
@@ -1626,13 +1634,14 @@ void Generator::generateBindings(string* bindingNS)
             if (bindingNS)
                 global << "}\n\n";
             global << "#endif\n";
-            global.close();
+
+            writeFile(path, global.str());
         }
 
         // Write out the implementation.
         {
             string path = _outDir + string(LUA_GLOBAL_FILENAME) + string(".cpp");
-            ofstream global(path.c_str());            
+            ostringstream global;
             global << "#include \"ScriptController.h\"\n";
             global << "#include \"" << LUA_GLOBAL_FILENAME << ".h\"\n";
             map<string, set<string> >::iterator iter = _includes.find(string(LUA_GLOBAL_FILENAME) + string(".h"));
@@ -1747,14 +1756,16 @@ void Generator::generateBindings(string* bindingNS)
 
             if (bindingNS)
                 global << "}\n";
-            global.close();
+
+            writeFile(path, global.str());
         }
     }
 
     luaAllCpp << "}\n\n";
     if (bindingNS)
         luaAllCpp << "}\n\n";
-    luaAllCpp.close();
+
+    writeFile(luaAllCppStr, luaAllCpp.str());
 
     if (bindingNS)
     {
@@ -1765,7 +1776,8 @@ void Generator::generateBindings(string* bindingNS)
     if (bindingNS)
         luaAllH<< "}\n\n";
     luaAllH << "#endif\n";
-    luaAllH.close();
+
+    writeFile(luaAllHStr, luaAllH.str());
 }
 
 void Generator::getAllDerived(set<string>& derived, string classname)
@@ -1810,8 +1822,10 @@ static string trim(const string& str)
     return s;
 }
 
-static string stripTypeQualifiers(const string& typeStr, FunctionBinding::Param::Kind& kind)
+static string stripTypeQualifiers(const string& typeStr, FunctionBinding::Param::Kind& kind, int& levelsOfIndirection)
 {
+    levelsOfIndirection = 0;
+
     string type = typeStr;
     kind = FunctionBinding::Param::KIND_VALUE;
 
@@ -1824,11 +1838,12 @@ static string stripTypeQualifiers(const string& typeStr, FunctionBinding::Param:
     }
 
     // Check if the type is a pointer.
-    i = type.find("*");
-    if (i != type.npos)
+    while ((i = type.find("*")) != std::string::npos)
     {
         kind = FunctionBinding::Param::KIND_POINTER;
         type.erase(type.begin() + i);
+        ++levelsOfIndirection;
+        int j = 0;
     }
 
     // Ignore const qualifiers.
@@ -1894,7 +1909,7 @@ static bool getFileList(string directory, vector<string>& files, bool (*isWanted
 
             if (isWantedFile(filename))
             {
-                filename = string(directory) + string("/") + filename;
+                filename = string(directory) + filename;
                 files.push_back(filename);
             }
         }
@@ -1925,7 +1940,7 @@ static bool getFileList(string directory, vector<string>& files, bool (*isWanted
                 string filename = dp->d_name;
                 if (isWantedFile(filename))
                 {
-                    filename = string(directory) + string("/") + filename;
+                    filename = string(directory) + filename;
                     files.push_back(filename);
                 }
             }

+ 37 - 0
gameplay-luagen/src/main.cpp

@@ -3,6 +3,9 @@
 
 //TRACK_MEMORY();
 
+// Generated file list (extern from Base.h)
+std::vector<string> generatedFiles;
+
 void printError(const char* format, ...)
 {
     va_list argptr;
@@ -23,6 +26,40 @@ void printError(const char* format, ...)
     va_end(argptr);
 }
 
+void writeFile(const std::string& path, const std::string& text)
+{
+    generatedFiles.push_back(path);
+
+    // Read in content of path to compare before writing
+    bool changed = true;
+    ifstream in(path.c_str());
+    if (in.is_open())
+    {
+        changed = false;
+        istringstream textStream(text, istringstream::in);
+        string line1, line2;
+        while (in.good() && textStream.good())
+        {
+            getline(in, line1);
+            getline(textStream, line2);
+            if (line1 != line2 || in.good() != textStream.good())
+            {
+                // Files differ
+                changed = true;
+                break;
+            }
+        }
+        in.close();
+    }
+
+    if (changed)
+    {
+        ofstream o(path.c_str());
+        o << text;
+        o.close();
+    }
+}
+
 int main(int argc, char** argv)
 {
     // Ensure the user is calling the program correctly.

+ 16 - 0
gameplay/CMakeLists.txt

@@ -78,6 +78,8 @@ set(GAMEPLAY_SRC
     src/gameplay-main-linux.cpp
     src/gameplay-main-windows.cpp
     src/Gesture.h
+    src/HeightField.cpp
+    src/HeightField.h
     src/Image.cpp
     src/Image.h
     src/Image.inl
@@ -193,6 +195,10 @@ set(GAMEPLAY_SRC
     src/SpriteBatch.h
     src/Technique.cpp
     src/Technique.h
+    src/Terrain.cpp
+    src/Terrain.h
+    src/TerrainPatch.cpp
+    src/TerrainPatch.h
     src/TextBox.cpp
     src/TextBox.h
     src/Texture.cpp
@@ -339,6 +345,8 @@ set(GAMEPLAY_LUA
     src/lua/lua_GestureGestureEvent.h
     src/lua/lua_Global.cpp
     src/lua/lua_Global.h
+    src/lua/lua_HeightField.cpp
+    src/lua/lua_HeightField.h
     src/lua/lua_Image.cpp
     src/lua/lua_Image.h
     src/lua/lua_ImageFormat.cpp
@@ -479,6 +487,8 @@ set(GAMEPLAY_LUA
     src/lua/lua_RenderStateAutoBinding.h
     src/lua/lua_RenderStateBlend.cpp
     src/lua/lua_RenderStateBlend.h
+    src/lua/lua_RenderStateDepthFunction.cpp
+    src/lua/lua_RenderStateDepthFunction.h
     src/lua/lua_RenderStateStateBlock.cpp
     src/lua/lua_RenderStateStateBlock.h
     src/lua/lua_RenderTarget.cpp
@@ -499,6 +509,10 @@ set(GAMEPLAY_LUA
     src/lua/lua_SpriteBatch.h
     src/lua/lua_Technique.cpp
     src/lua/lua_Technique.h
+    src/lua/lua_Terrain.cpp
+    src/lua/lua_Terrain.h
+    src/lua/lua_TerrainFlags.cpp
+    src/lua/lua_TerrainFlags.h
     src/lua/lua_TextBox.cpp
     src/lua/lua_TextBox.h
     src/lua/lua_Texture.cpp
@@ -567,6 +581,8 @@ set(GAMEPLAY_RES_SHADERS
     res/shaders/textured-unlit.vert
     res/shaders/textured.frag
     res/shaders/textured.vert
+    res/shaders/terrain.frag
+    res/shaders/terrain.vert
 )
 
 set(GAMEPLAY_RES_SHADERS_LIB

+ 7 - 0
gameplay/android/jni/Android.mk

@@ -52,6 +52,7 @@ LOCAL_SRC_FILES := \
     Frustum.cpp \
     Game.cpp \
     Gamepad.cpp \
+    HeightField.cpp \
     Image.cpp \
     Joint.cpp \
     Joystick.cpp \
@@ -103,6 +104,8 @@ LOCAL_SRC_FILES := \
     Slider.cpp \
     SpriteBatch.cpp \
     Technique.cpp \
+    Terrain.cpp \
+    TerrainPatch.cpp \
     TextBox.cpp \
     Texture.cpp \
     Theme.cpp \
@@ -173,6 +176,7 @@ LOCAL_SRC_FILES := \
     lua/lua_Gesture.cpp \
     lua/lua_GestureGestureEvent.cpp \
     lua/lua_Global.cpp \
+    lua/lua_HeightField.cpp \
     lua/lua_Image.cpp \
     lua/lua_ImageFormat.cpp \
     lua/lua_Joint.cpp \
@@ -243,6 +247,7 @@ LOCAL_SRC_FILES := \
     lua/lua_RenderState.cpp \
     lua/lua_RenderStateAutoBinding.cpp \
     lua/lua_RenderStateBlend.cpp \
+    lua/lua_RenderStateDepthFunction.cpp \
     lua/lua_RenderStateStateBlock.cpp \
     lua/lua_RenderTarget.cpp \
     lua/lua_Scene.cpp \
@@ -253,6 +258,8 @@ LOCAL_SRC_FILES := \
     lua/lua_Slider.cpp \
     lua/lua_SpriteBatch.cpp \
     lua/lua_Technique.cpp \
+    lua/lua_Terrain.cpp \
+    lua/lua_TerrainFlags.cpp \
     lua/lua_TextBox.cpp \
     lua/lua_Texture.cpp \
     lua/lua_TextureFilter.cpp \

+ 15 - 1
gameplay/gameplay.cbp

@@ -121,11 +121,13 @@
 		<Unit filename="src/Gamepad.cpp" />
 		<Unit filename="src/Gamepad.h" />
 		<Unit filename="src/gameplay.h" />
-        <Unit filename="src/gameplay-main-android.cpp" />
+		<Unit filename="src/gameplay-main-android.cpp" />
 		<Unit filename="src/gameplay-main-blackberry.cpp" />
 		<Unit filename="src/gameplay-main-linux.cpp" />
 		<Unit filename="src/gameplay-main-windows.cpp" />
 		<Unit filename="src/Gesture.h" />
+		<Unit filename="src/HeightField.cpp" />
+		<Unit filename="src/HeightField.h" />
 		<Unit filename="src/Image.cpp" />
 		<Unit filename="src/Image.h" />
 		<Unit filename="src/Joint.cpp" />
@@ -233,6 +235,10 @@
 		<Unit filename="src/SpriteBatch.h" />
 		<Unit filename="src/Technique.cpp" />
 		<Unit filename="src/Technique.h" />
+		<Unit filename="src/Terrain.cpp" />
+		<Unit filename="src/Terrain.h" />
+		<Unit filename="src/TerrainPatch.cpp" />
+		<Unit filename="src/TerrainPatch.h" />
 		<Unit filename="src/TextBox.cpp" />
 		<Unit filename="src/TextBox.h" />
 		<Unit filename="src/Texture.cpp" />
@@ -373,6 +379,8 @@
 		<Unit filename="src/lua/lua_GestureGestureEvent.h" />
 		<Unit filename="src/lua/lua_Global.cpp" />
 		<Unit filename="src/lua/lua_Global.h" />
+		<Unit filename="src/lua/lua_HeightField.cpp" />
+		<Unit filename="src/lua/lua_HeightField.h" />
 		<Unit filename="src/lua/lua_Image.cpp" />
 		<Unit filename="src/lua/lua_Image.h" />
 		<Unit filename="src/lua/lua_ImageFormat.cpp" />
@@ -513,6 +521,8 @@
 		<Unit filename="src/lua/lua_RenderStateAutoBinding.h" />
 		<Unit filename="src/lua/lua_RenderStateBlend.cpp" />
 		<Unit filename="src/lua/lua_RenderStateBlend.h" />
+		<Unit filename="src/lua/lua_RenderStateDepthFunction.cpp" />
+		<Unit filename="src/lua/lua_RenderStateDepthFunction.h" />
 		<Unit filename="src/lua/lua_RenderStateStateBlock.cpp" />
 		<Unit filename="src/lua/lua_RenderStateStateBlock.h" />
 		<Unit filename="src/lua/lua_RenderTarget.cpp" />
@@ -533,6 +543,10 @@
 		<Unit filename="src/lua/lua_SpriteBatch.h" />
 		<Unit filename="src/lua/lua_Technique.cpp" />
 		<Unit filename="src/lua/lua_Technique.h" />
+		<Unit filename="src/lua/lua_Terrain.cpp" />
+		<Unit filename="src/lua/lua_Terrain.h" />
+		<Unit filename="src/lua/lua_TerrainFlags.cpp" />
+		<Unit filename="src/lua/lua_TerrainFlags.h" />
 		<Unit filename="src/lua/lua_TextBox.cpp" />
 		<Unit filename="src/lua/lua_TextBox.h" />
 		<Unit filename="src/lua/lua_Texture.cpp" />

+ 16 - 0
gameplay/gameplay.vcxproj

@@ -89,6 +89,7 @@
     <ClCompile Include="src\gameplay-main-blackberry.cpp" />
     <ClCompile Include="src\gameplay-main-linux.cpp" />
     <ClCompile Include="src\gameplay-main-windows.cpp" />
+    <ClCompile Include="src\HeightField.cpp" />
     <ClCompile Include="src\Image.cpp" />
     <ClCompile Include="src\Joint.cpp" />
     <ClCompile Include="src\Joystick.cpp" />
@@ -155,6 +156,7 @@
     <ClCompile Include="src\lua\lua_Gesture.cpp" />
     <ClCompile Include="src\lua\lua_GestureGestureEvent.cpp" />
     <ClCompile Include="src\lua\lua_Global.cpp" />
+    <ClCompile Include="src\lua\lua_HeightField.cpp" />
     <ClCompile Include="src\lua\lua_Image.cpp" />
     <ClCompile Include="src\lua\lua_ImageFormat.cpp" />
     <ClCompile Include="src\lua\lua_Joint.cpp" />
@@ -225,6 +227,7 @@
     <ClCompile Include="src\lua\lua_RenderState.cpp" />
     <ClCompile Include="src\lua\lua_RenderStateAutoBinding.cpp" />
     <ClCompile Include="src\lua\lua_RenderStateBlend.cpp" />
+    <ClCompile Include="src\lua\lua_RenderStateDepthFunction.cpp" />
     <ClCompile Include="src\lua\lua_RenderStateStateBlock.cpp" />
     <ClCompile Include="src\lua\lua_RenderTarget.cpp" />
     <ClCompile Include="src\lua\lua_Scene.cpp" />
@@ -235,6 +238,8 @@
     <ClCompile Include="src\lua\lua_Slider.cpp" />
     <ClCompile Include="src\lua\lua_SpriteBatch.cpp" />
     <ClCompile Include="src\lua\lua_Technique.cpp" />
+    <ClCompile Include="src\lua\lua_Terrain.cpp" />
+    <ClCompile Include="src\lua\lua_TerrainFlags.cpp" />
     <ClCompile Include="src\lua\lua_TextBox.cpp" />
     <ClCompile Include="src\lua\lua_Texture.cpp" />
     <ClCompile Include="src\lua\lua_TextureFilter.cpp" />
@@ -307,6 +312,8 @@
     <ClCompile Include="src\Slider.cpp" />
     <ClCompile Include="src\SpriteBatch.cpp" />
     <ClCompile Include="src\Technique.cpp" />
+    <ClCompile Include="src\Terrain.cpp" />
+    <ClCompile Include="src\TerrainPatch.cpp" />
     <ClCompile Include="src\TextBox.cpp" />
     <ClCompile Include="src\Texture.cpp" />
     <ClCompile Include="src\Theme.cpp" />
@@ -357,6 +364,7 @@
     <ClInclude Include="src\Gamepad.h" />
     <ClInclude Include="src\gameplay.h" />
     <ClInclude Include="src\Gesture.h" />
+    <ClInclude Include="src\HeightField.h" />
     <ClInclude Include="src\Image.h" />
     <ClInclude Include="src\Joint.h" />
     <ClInclude Include="src\Joystick.h" />
@@ -424,6 +432,7 @@
     <ClInclude Include="src\lua\lua_Gesture.h" />
     <ClInclude Include="src\lua\lua_GestureGestureEvent.h" />
     <ClInclude Include="src\lua\lua_Global.h" />
+    <ClInclude Include="src\lua\lua_HeightField.h" />
     <ClInclude Include="src\lua\lua_Image.h" />
     <ClInclude Include="src\lua\lua_ImageFormat.h" />
     <ClInclude Include="src\lua\lua_Joint.h" />
@@ -494,6 +503,7 @@
     <ClInclude Include="src\lua\lua_RenderState.h" />
     <ClInclude Include="src\lua\lua_RenderStateAutoBinding.h" />
     <ClInclude Include="src\lua\lua_RenderStateBlend.h" />
+    <ClInclude Include="src\lua\lua_RenderStateDepthFunction.h" />
     <ClInclude Include="src\lua\lua_RenderStateStateBlock.h" />
     <ClInclude Include="src\lua\lua_RenderTarget.h" />
     <ClInclude Include="src\lua\lua_Scene.h" />
@@ -504,6 +514,8 @@
     <ClInclude Include="src\lua\lua_Slider.h" />
     <ClInclude Include="src\lua\lua_SpriteBatch.h" />
     <ClInclude Include="src\lua\lua_Technique.h" />
+    <ClInclude Include="src\lua\lua_Terrain.h" />
+    <ClInclude Include="src\lua\lua_TerrainFlags.h" />
     <ClInclude Include="src\lua\lua_TextBox.h" />
     <ClInclude Include="src\lua\lua_Texture.h" />
     <ClInclude Include="src\lua\lua_TextureFilter.h" />
@@ -575,6 +587,8 @@
     <ClInclude Include="src\SpriteBatch.h" />
     <ClInclude Include="src\Stream.h" />
     <ClInclude Include="src\Technique.h" />
+    <ClInclude Include="src\Terrain.h" />
+    <ClInclude Include="src\TerrainPatch.h" />
     <ClInclude Include="src\TextBox.h" />
     <ClInclude Include="src\Texture.h" />
     <ClInclude Include="src\Theme.h" />
@@ -607,6 +621,8 @@
     <None Include="res\shaders\lib\lighting-spot.frag" />
     <None Include="res\shaders\lib\lighting-spot.vert" />
     <None Include="res\shaders\lib\lighting.frag" />
+    <None Include="res\shaders\terrain.frag" />
+    <None Include="res\shaders\terrain.vert" />
     <None Include="res\shaders\textured-bumped.frag" />
     <None Include="res\shaders\textured-bumped.vert" />
     <None Include="res\shaders\textured-unlit.frag" />

+ 48 - 0
gameplay/gameplay.vcxproj.filters

@@ -819,6 +819,27 @@
     <ClCompile Include="src\lua\lua_GamepadButtonMapping.cpp">
       <Filter>lua</Filter>
     </ClCompile>
+    <ClCompile Include="src\Terrain.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\TerrainPatch.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\lua\lua_TerrainFlags.cpp">
+      <Filter>lua</Filter>
+    </ClCompile>
+    <ClCompile Include="src\lua\lua_Terrain.cpp">
+      <Filter>lua</Filter>
+    </ClCompile>
+    <ClCompile Include="src\lua\lua_RenderStateDepthFunction.cpp">
+      <Filter>lua</Filter>
+    </ClCompile>
+    <ClCompile Include="src\HeightField.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\lua\lua_HeightField.cpp">
+      <Filter>lua</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -1625,6 +1646,27 @@
     <ClInclude Include="src\lua\lua_GamepadButtonMapping.h">
       <Filter>lua</Filter>
     </ClInclude>
+    <ClInclude Include="src\Terrain.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\TerrainPatch.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\lua\lua_TerrainFlags.h">
+      <Filter>lua</Filter>
+    </ClInclude>
+    <ClInclude Include="src\lua\lua_Terrain.h">
+      <Filter>lua</Filter>
+    </ClInclude>
+    <ClInclude Include="src\lua\lua_RenderStateDepthFunction.h">
+      <Filter>lua</Filter>
+    </ClInclude>
+    <ClInclude Include="src\HeightField.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\lua\lua_HeightField.h">
+      <Filter>lua</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\Game.inl">
@@ -1717,6 +1759,12 @@
     <None Include="res\shaders\lib\lighting-spot.vert">
       <Filter>res\shaders\lib</Filter>
     </None>
+    <None Include="res\shaders\terrain.vert">
+      <Filter>res\shaders</Filter>
+    </None>
+    <None Include="res\shaders\terrain.frag">
+      <Filter>res\shaders</Filter>
+    </None>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\PhysicsFixedConstraint.inl">

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 32 - 946
gameplay/gameplay.xcodeproj/project.pbxproj


+ 78 - 0
gameplay/res/shaders/terrain.frag

@@ -0,0 +1,78 @@
+#ifdef OPENGL_ES
+precision highp float;
+#endif
+
+// Inputs
+#if defined(NORMAL_MAP)
+vec3 v_normalVector;                            // Normal vector variable (from normal map)
+#else
+varying vec3 v_normalVector;					// Normal vector from vertex shader
+#endif
+varying vec2 v_texCoord0;
+
+// Uniforms
+uniform vec3 u_ambientColor;                    // Ambient color
+uniform vec3 u_lightColor;                      // Light color
+uniform vec3 u_lightDirection;					// Light direction
+
+#if defined(DEBUG_PATCHES)
+uniform float u_row;
+uniform float u_column;
+#endif
+
+#if (LAYER_COUNT > 0)
+uniform sampler2D u_samplers[SAMPLER_COUNT];
+#endif
+
+#if defined (NORMAL_MAP)
+uniform sampler2D u_normalMap;
+#endif
+
+#include "lib/lighting.frag"
+#include "lib/lighting-directional.frag"
+
+#if (LAYER_COUNT > 1)
+void blendLayer(sampler2D textureMap, vec2 textureRepeat, float alphaBlend)
+{
+    vec2 uv = mod(v_texCoord0 * textureRepeat, vec2(1,1));
+
+    // Sample full intensity diffuse color
+    vec3 diffuse = texture2D(textureMap, uv).rgb;
+
+    _baseColor.rgb = _baseColor.rgb * (1.0 - alphaBlend) + diffuse * alphaBlend;
+}
+#endif
+
+void main()
+{
+#if (LAYER_COUNT > 0)
+    // Set base diffuse color
+    vec2 uvCoord = mod(v_texCoord0 * TEXTURE_REPEAT_0, vec2(1,1));
+	_baseColor = texture2D(u_samplers[TEXTURE_INDEX_0], uvCoord);
+#else
+    // If no layers are defined, simple use a white color
+    _baseColor = vec4(1,1,1,1);
+#endif
+
+#if (LAYER_COUNT > 1)
+    blendLayer(u_samplers[TEXTURE_INDEX_1], TEXTURE_REPEAT_1, texture2D(u_samplers[BLEND_INDEX_1], v_texCoord0)[BLEND_CHANNEL_1]);
+#endif
+
+#if (LAYER_COUNT > 2)
+    blendLayer(u_samplers[TEXTURE_INDEX_2], TEXTURE_REPEAT_2, texture2D(u_samplers[BLEND_INDEX_2], v_texCoord0)[BLEND_CHANNEL_2]);
+#endif
+
+#if defined(DEBUG_PATCHES)
+    // If patch debug drawing is enabled, tint patches alternate colors
+    float tint = mod(u_row + mod(u_column, 2.0), 2.0);
+    _baseColor.rgb = _baseColor.rgb * 0.75 + vec3(1.0-tint, tint, 0) * 0.25;
+#endif
+
+    // Light the pixel
+#if defined(NORMAL_MAP)
+    v_normalVector = normalize(texture2D(u_normalMap, v_texCoord0).xyz * 2.0 - 1.0);
+#endif
+
+    gl_FragColor.a = _baseColor.a;
+    gl_FragColor.rgb = getLitPixel();
+}

+ 60 - 0
gameplay/res/shaders/terrain.vert

@@ -0,0 +1,60 @@
+// Inputs
+attribute vec4 a_position;									// Vertex Position							(x, y, z, w)
+#ifndef NORMAL_MAP
+attribute vec3 a_normal;									// Vertex Normal							(x, y, z)
+#endif
+attribute vec2 a_texCoord0;
+#if LAYER_COUNT > 1
+attribute vec2 a_texCoord1;
+#endif
+#if LAYER_COUNT > 2
+attribute vec2 a_texCoord2;
+#endif
+#if LAYER_COUNT > 3
+attribute vec2 a_texCoord3;
+#endif
+
+// Uniforms
+uniform mat4 u_worldViewProjectionMatrix;					// World view projection matrix
+#ifndef NORMAL_MAP
+uniform mat4 u_normalMatrix;					            // Matrix used for normal vector transformation
+#endif
+uniform vec3 u_lightDirection;								// Direction of light
+
+// Outputs
+#ifndef NORMAL_MAP
+varying vec3 v_normalVector;								// Normal vector out
+#endif
+varying vec2 v_texCoord0;
+#if LAYER_COUNT > 1
+varying vec2 v_texCoord1;
+#endif
+#if LAYER_COUNT > 2
+varying vec2 v_texCoord2;
+#endif
+#if LAYER_COUNT > 3
+varying vec2 v_texCoord3;
+#endif
+
+void main()
+{
+    // Transform position to clip space.
+    gl_Position = u_worldViewProjectionMatrix * a_position;
+
+#ifndef NORMAL_MAP
+    // Pass normal to fragment shader
+    v_normalVector = (u_normalMatrix * vec4(a_normal.x, a_normal.y, a_normal.z, 0)).xyz;
+#endif
+
+    v_texCoord0 = a_texCoord0;
+
+#if LAYER_COUNT > 1
+    v_texCoord1 = a_texCoord1;
+#endif
+#if LAYER_COUNT > 2
+    v_texCoord2 = a_texCoord2;
+#endif
+#if LAYER_COUNT > 3
+    v_texCoord3 = a_texCoord3;
+#endif
+}

+ 3 - 1
gameplay/src/AudioListener.cpp

@@ -13,7 +13,9 @@ AudioListener::AudioListener()
 
 AudioListener::~AudioListener()
 {
-    SAFE_RELEASE(_camera);
+	// Call setCamera() to release camera and cause transform listener
+	// to be removed.
+	setCamera(NULL);
 }
 
 AudioListener* AudioListener::getInstance()

+ 1 - 0
gameplay/src/Base.h

@@ -14,6 +14,7 @@
 #include <cstdarg>
 #include <ctime>
 #include <iostream>
+#include <sstream>
 #include <string>
 #include <cstring>
 #include <vector>

+ 1 - 2
gameplay/src/Bundle.cpp

@@ -378,8 +378,7 @@ Scene* Bundle::loadScene(const char* id)
         }
     }
 
-    Scene* scene = Scene::create();
-    scene->setId(getIdFromOffset());
+    Scene* scene = Scene::create(getIdFromOffset());
 
     // Read the number of children.
     unsigned int childrenCount;

+ 68 - 0
gameplay/src/Camera.cpp

@@ -46,6 +46,74 @@ Camera* Camera::createOrthographic(float zoomX, float zoomY, float aspectRatio,
     return new Camera(zoomX, zoomY, aspectRatio, nearPlane, farPlane);
 }
 
+Camera* Camera::create(Properties* properties)
+{
+    GP_ASSERT(properties);
+
+    // Read camera type
+    std::string typeStr;
+    if (properties->exists("type"))
+        typeStr = properties->getString("type");
+    Camera::Type type;
+    if (typeStr == "PERSPECTIVE")
+    {
+        type = Camera::PERSPECTIVE;
+    }
+    else if (typeStr == "ORTHOGRAPHIC")
+    {
+        type = Camera::ORTHOGRAPHIC;
+    }
+    else
+    {
+        GP_ERROR("Invalid 'type' parameter for camera definition.");
+        return NULL;
+    }
+
+    // Read common parameters
+    float aspectRatio, nearPlane, farPlane;
+    if (properties->exists("aspectRatio"))
+    {
+        aspectRatio = properties->getFloat("aspectRatio");
+    }
+    else
+    {
+        // Use default aspect ratio
+        aspectRatio = (float)Game::getInstance()->getWidth() / Game::getInstance()->getHeight();
+    }
+
+    if (properties->exists("nearPlane"))
+        nearPlane = properties->getFloat("nearPlane");
+    else
+        nearPlane = 0.2f; // use some reasonable default value
+
+    if (properties->exists("farPlane"))
+        farPlane = properties->getFloat("farPlane");
+    else
+        farPlane = 100; // use some reasonable default value
+
+    Camera* camera = NULL;
+
+    switch (type)
+    {
+    case Camera::PERSPECTIVE:
+        // If field of view is not specified, use a default of 60 degrees
+        camera = createPerspective(
+            properties->exists("fieldOfView") ? properties->getFloat("fieldOfView") : 60.0f,
+            aspectRatio, nearPlane, farPlane);
+        break;
+
+    case Camera::ORTHOGRAPHIC:
+        // If zoomX and zoomY are not specified, use screen width/height
+        camera = createOrthographic(
+            properties->exists("zoomX") ? properties->getFloat("zoomX") : Game::getInstance()->getWidth(),
+            properties->exists("zoomY") ? properties->getFloat("zoomY") : Game::getInstance()->getHeight(),
+            aspectRatio, nearPlane, farPlane);
+        break;
+    }
+
+    return camera;
+}
+
 Camera::Type Camera::getCameraType() const
 {
     return _type;

+ 18 - 0
gameplay/src/Camera.h

@@ -5,6 +5,7 @@
 #include "Transform.h"
 #include "Frustum.h"
 #include "Rectangle.h"
+#include "Properties.h"
 
 namespace gameplay
 {
@@ -37,6 +38,8 @@ public:
      * @param aspectRatio The aspect ratio of the camera (normally the width of the viewport divided by the height of the viewport).
      * @param nearPlane The near plane distance.
      * @param farPlane The far plane distance.
+     *
+     * @return The new Camera.
      */
     static Camera* createPerspective(float fieldOfView, float aspectRatio, float nearPlane, float farPlane);
 
@@ -48,9 +51,24 @@ public:
      * @param aspectRatio The aspect ratio of the orthographic projection.
      * @param nearPlane The near plane distance.
      * @param farPlane The far plane distance.
+     *
+     * @return The new Camera.
      */
     static Camera* createOrthographic(float zoomX, float zoomY, float aspectRatio, float nearPlane, float farPlane);
 
+    /**
+     * Creates a camera from a properties definition.
+     *
+     * The properties object must contain a "type" parameter, specifying either PERSPECTIVE or ORTHOGRAPHIC,
+     * as well as values for all required parameters in the Camera::createPerspective and Camera::createOrthographic
+     * methods.
+     *
+     * @param properties The properties definition of the Camera.
+     *
+     * @return The new Camera.
+     */
+    static Camera* create(Properties* properties);
+
     /**
      * Gets the type of camera.
      *

+ 33 - 3
gameplay/src/Effect.cpp

@@ -402,9 +402,9 @@ Effect* Effect::createFromSource(const char* vshPath, const char* vshSource, con
                 // Query uniform info.
                 GL_ASSERT( glGetActiveUniform(program, i, length, NULL, &uniformSize, &uniformType, uniformName) );
                 uniformName[length] = '\0';  // null terminate
-                if (uniformSize > 1 && length > 3)
+                if (length > 3)
                 {
-                    // This is an array uniform. I'm stripping array indexers off it since GL does not
+                    // If this is an array uniform, strip array indexers off it since GL does not
                     // seem to be consistent across different drivers/implementations in how it returns
                     // array uniforms. On some systems it will return "u_matrixArray", while on others
                     // it will return "u_matrixArray[0]".
@@ -423,7 +423,15 @@ Effect* Effect::createFromSource(const char* vshPath, const char* vshSource, con
                 uniform->_name = uniformName;
                 uniform->_location = uniformLocation;
                 uniform->_type = uniformType;
-                uniform->_index = uniformType == GL_SAMPLER_2D ? (samplerIndex++) : 0;
+                if (uniformType == GL_SAMPLER_2D)
+                {
+                    uniform->_index = samplerIndex;
+                    samplerIndex += uniformSize;
+                }
+                else
+                {
+                    uniform->_index = 0;
+                }
 
                 effect->_uniforms[uniformName] = uniform;
             }
@@ -561,6 +569,28 @@ void Effect::setValue(Uniform* uniform, const Texture::Sampler* sampler)
     GL_ASSERT( glUniform1i(uniform->_location, uniform->_index) );
 }
 
+void Effect::setValue(Uniform* uniform, const Texture::Sampler** values, unsigned int count)
+{
+    GP_ASSERT(uniform);
+    GP_ASSERT(uniform->_type == GL_SAMPLER_2D);
+    GP_ASSERT(values);
+
+    // Set samplers as active and load texture unit array
+    GLint units[32];
+    for (unsigned int i = 0; i < count; ++i)
+    {
+        GL_ASSERT( glActiveTexture(GL_TEXTURE0 + uniform->_index + i) );
+
+        // Bind the sampler - this binds the texture and applies sampler state
+        const_cast<Texture::Sampler*>(values[i])->bind();
+
+        units[i] = uniform->_index + i;
+    }
+
+    // Pass texture unit array to GL
+    GL_ASSERT( glUniform1iv(uniform->_location, count, units) );
+}
+
 void Effect::bind()
 {
    GL_ASSERT( glUseProgram(_program) );

+ 11 - 0
gameplay/src/Effect.h

@@ -199,6 +199,17 @@ public:
      */
     void setValue(Uniform* uniform, const Texture::Sampler* sampler);
 
+    /**
+     * Sets a sampler array uniform value.
+     *
+     * @param uniform The uniform to set.
+     * @param values The sampler array to set.
+     * @param count The number of elements in the array.
+     *
+     * @script{ignore}
+     */
+    void setValue(Uniform* uniform, const Texture::Sampler** values, unsigned int count);
+
     /**
      * Binds this effect to make it the currently active effect for the rendering system.
      */

+ 14 - 0
gameplay/src/FileSystem.cpp

@@ -516,6 +516,20 @@ void FileSystem::createFileFromAsset(const char* path)
 #endif
 }
 
+std::string FileSystem::getExtension(const char* path)
+{
+    const char* str = strrchr(path, '.');
+    if (str == NULL)
+        return "";
+
+    std::string ext;
+    size_t len = strlen(str);
+    for (size_t i = 0; i < len; ++i)
+        ext += std::toupper(str[i]);
+
+    return ext;
+}
+
 //////////////////
 
 FileStream::FileStream(FILE* file)

+ 14 - 0
gameplay/src/FileSystem.h

@@ -181,6 +181,20 @@ public:
      */
     static void createFileFromAsset(const char* path);
 
+    /**
+     * Returns the extension of the given file path.
+     *
+     * The extension returned includes all character after and including the last '.'
+     * in the file path. The extension is returned as all uppercase.
+     *
+     * If the path does not contain an extension, an empty string is returned.
+     * 
+     * @param path File path.
+     *
+     * @return The file extension, all uppercase, including the '.'.
+     */
+    static std::string getExtension(const char* path);
+
 private:
 
     /**

+ 42 - 32
gameplay/src/Game.cpp

@@ -33,20 +33,7 @@ Game::Game()
 
 Game::~Game()
 {
-    if (_scriptListeners)
-    {
-        for (unsigned int i = 0; i < _scriptListeners->size(); i++)
-        {
-            SAFE_DELETE((*_scriptListeners)[i]);
-        }
-        SAFE_DELETE(_scriptListeners);
-    }
-
-    if (_scriptController)
-    {
-        _scriptController->finalize();
-        SAFE_DELETE(_scriptController);
-    }
+	SAFE_DELETE(_scriptController);
 
     // Do not call any virtual functions from the destructor.
     // Finalization is done from outside this class.
@@ -180,20 +167,32 @@ void Game::shutdown()
         GP_ASSERT(_aiController);
 
         Platform::signalShutdown();
+
+		// Call user finalize
         finalize();
 
-        std::vector<Gamepad*>* gamepads = Gamepad::getGamepads(); 
-        if (gamepads) 
-        { 
-            for (size_t i = 0, count = gamepads->size(); i < count; ++i) 
-            { 
-                SAFE_DELETE((*gamepads)[i]); 
-            } 
-            gamepads->clear(); 
+		// Shutdown scripting system first so that any objects allocated in script are released before our subsystems are released
+		_scriptController->finalizeGame();
+		if (_scriptListeners)
+		{
+			for (size_t i = 0; i < _scriptListeners->size(); i++)
+			{
+				SAFE_DELETE((*_scriptListeners)[i]);
+			}
+			SAFE_DELETE(_scriptListeners);
+		}
+		_scriptController->finalize();
+
+        std::vector<Gamepad*>* gamepads = Gamepad::getGamepads();
+        if (gamepads)
+        {
+            for (size_t i = 0, count = gamepads->size(); i < count; ++i)
+            {
+                SAFE_DELETE((*gamepads)[i]);
+            }
+            gamepads->clear();
         }
 
-        _scriptController->finalizeGame();
-
         _animationController->finalize();
         SAFE_DELETE(_animationController);
 
@@ -214,7 +213,7 @@ void Game::shutdown()
 
         SAFE_DELETE(_properties);
 
-        _state = UNINITIALIZED;
+		_state = UNINITIALIZED;
     }
 }
 
@@ -261,7 +260,11 @@ void Game::resume()
 
 void Game::exit()
 {
-    shutdown();
+	// Schedule a call to shutdown rather than calling it right away.
+	// This handles the case of shutting down the script system from
+	// within a script function (which can cause errors).
+	static ShutdownListener listener;
+	schedule(0, &listener);
 }
 
 void Game::frame()
@@ -273,6 +276,12 @@ void Game::frame()
         _initialized = true;
     }
 
+	static double lastFrameTime = Game::getGameTime();
+	double frameTime = getGameTime();
+
+    // Fire time events to scheduled TimeListeners
+    fireTimeEvents(frameTime);
+
     if (_state == Game::RUNNING)
     {
         GP_ASSERT(_animationController);
@@ -281,17 +290,12 @@ void Game::frame()
         GP_ASSERT(_aiController);
 
         // Update Time.
-        static double lastFrameTime = Game::getGameTime();
-        double frameTime = getGameTime();
         float elapsedTime = (frameTime - lastFrameTime);
         lastFrameTime = frameTime;
 
         // Update the scheduled and running animations.
         _animationController->update(elapsedTime);
 
-        // Fire time events to scheduled TimeListeners
-        fireTimeEvents(frameTime);
-
         // Update the physics.
         _physicsController->update(elapsedTime);
 
@@ -322,7 +326,7 @@ void Game::frame()
             _frameLastFPS = getGameTime();
         }
     }
-    else
+	else if (_state == Game::PAUSED)
     {
         // Application Update.
         update(0);
@@ -586,4 +590,10 @@ void Game::loadGamepads()
     }
 }
 
+void Game::ShutdownListener::timeEvent(long timeDiff, void* cookie)
+{
+	Game::getInstance()->shutdown();
+}
+
 }
+

+ 6 - 1
gameplay/src/Game.h

@@ -25,8 +25,8 @@ class ScriptController;
  */
 class Game
 {
-
     friend class Platform;
+	friend class ShutdownListener;
 
 public:
     
@@ -584,6 +584,11 @@ private:
         std::string function;
     };
 
+	struct ShutdownListener : public TimeListener
+	{
+		void timeEvent(long timeDiff, void* cookie);
+	};
+
     /**
      * TimeEvent represents the event that is sent to TimeListeners as a result of calling Game::schedule().
      */

+ 226 - 0
gameplay/src/HeightField.cpp

@@ -0,0 +1,226 @@
+#include "Base.h"
+#include "HeightField.h"
+#include "Image.h"
+#include "FileSystem.h"
+
+namespace gameplay
+{
+
+HeightField::HeightField(unsigned int columns, unsigned int rows)
+    : _array(NULL), _cols(columns), _rows(rows)
+{
+    _array = new float[columns * rows];
+}
+
+HeightField::~HeightField()
+{
+    SAFE_DELETE_ARRAY(_array);
+}
+
+HeightField* HeightField::create(unsigned int columns, unsigned int rows)
+{
+    return new HeightField(columns, rows);
+}
+
+/**
+ * @script{ignore}
+ */
+float normalizedHeightPacked(float r, float g, float b)
+{
+    // This formula is intended for 24-bit packed heightmap images (that are generated
+    // with gameplay-encoder. However, it is also compatible with normal grayscale 
+    // heightmap images, with an error of approximately 0.4%. This can be seen by
+    // setting r=g=b=x and comparing the grayscale height expression to the packed
+    // height expression: the error is 2^-8 + 2^-16 which is just under 0.4%.
+    return (256.0f*r + g + 0.00390625f*b) / 65536.0f;
+}
+
+HeightField* HeightField::createFromImage(const char* path, float minHeight, float maxHeight)
+{
+    return create(path, 0, 0, minHeight, maxHeight);
+}
+
+HeightField* HeightField::createFromRAW(const char* path, unsigned int width, unsigned int height, float minHeight, float maxHeight)
+{
+    return create(path, width, height, minHeight, maxHeight);
+}
+
+HeightField* HeightField::create(const char* path, unsigned int width, unsigned int height, float minHeight, float maxHeight)
+{
+    GP_ASSERT(path);
+    GP_ASSERT(maxHeight >= minHeight);
+
+    // Validate input parameters
+    size_t pathLength = strlen(path);
+    if (pathLength <= 4)
+    {
+        GP_WARN("Unrecognized file extension for heightfield image: %s.", path);
+        return NULL;
+    }
+
+    float heightScale = maxHeight - minHeight;
+
+    HeightField* heightfield = NULL;
+
+    // Load height data from image
+    const char* ext = path + (pathLength - 4);
+    if (ext[0] == '.' && toupper(ext[1]) == 'P' && toupper(ext[2]) == 'N' && toupper(ext[3]) == 'G')
+    {
+        // Normal image
+        Image* image = Image::create(path);
+        if (!image)
+            return NULL;
+
+        unsigned int pixelSize = 0;
+        switch (image->getFormat())
+        {
+            case Image::RGB:
+                pixelSize = 3;
+                break;
+            case Image::RGBA:
+                pixelSize = 4;
+                break;
+            default:
+                SAFE_RELEASE(image);
+                GP_WARN("Unsupported pixel format for heightfield image: %s.", path);
+                return NULL;
+        }
+
+        // Calculate the heights for each pixel.
+        heightfield = HeightField::create(image->getWidth(), image->getHeight());
+        float* heights = heightfield->getArray();
+        unsigned char* data = image->getData();
+        int idx;
+        for (int y = image->getHeight()-1, i = 0; y >= 0; --y)
+        {
+            for (unsigned int x = 0, w = image->getWidth(); x < w; ++x)
+            {
+                idx = (y*w + x) * pixelSize;
+                heights[i++] = minHeight + normalizedHeightPacked(data[idx], data[idx + 1], data[idx + 2]) * heightScale;
+            }
+        }
+
+        SAFE_RELEASE(image);
+    }
+    else if (ext[0] == '.' && toupper(ext[1]) == 'R' && toupper(ext[2]) == 'A' && toupper(ext[3]) == 'W')
+    {
+        // RAW image (headerless)
+        if (width < 2 || height < 2 || maxHeight < 0)
+        {
+            GP_WARN("Invalid 'width', 'height' or 'maxHeight' parameter for RAW heightfield image: %s.", path);
+            return NULL;
+        }
+
+        // Load raw bytes
+        int fileSize = 0;
+        unsigned char* bytes = (unsigned char*)FileSystem::readAll(path, &fileSize);
+        if (bytes == NULL)
+        {
+            GP_WARN("Falied to read bytes from RAW heightfield image: %s.", path);
+            return NULL;
+        }
+
+        // Determine if the RAW file is 8-bit or 16-bit based on file size.
+        int bits = (fileSize / (width * height)) * 8;
+        if (bits != 8 && bits != 16)
+        {
+            GP_WARN("Invalid RAW file - must be 8-bit or 16-bit, but found neither: %s.", path);
+            SAFE_DELETE_ARRAY(bytes);
+            return NULL;
+        }
+
+        heightfield = HeightField::create(width, height);
+        float* heights = heightfield->getArray();
+
+        // RAW files have an origin of bottom left, whereas our height array needs an origin of
+        // top left, so we need to flip the Y as we write height values out.
+        if (bits == 16)
+        {
+            // 16-bit (0-65535)
+            int idx;
+            for (int y = height-1, i = 0; y >= 0; --y)
+            {
+                for (unsigned int x = 0; x < width; ++x, ++i)
+                {
+                    idx = (y * width + x) << 1;
+                    heights[i] = minHeight + ((bytes[idx] | (int)bytes[idx+1] << 8) / 65535.0f) * heightScale;
+                }
+            }
+        }
+        else
+        {
+            // 8-bit (0-255)
+            for (int y = height-1, i = 0; y >= 0; --y)
+            {
+                for (unsigned int x = 0; x < width; ++x, ++i)
+                {
+                    heights[i] = minHeight + (bytes[y * width + x] / 255.0f) * heightScale;
+                }
+            }
+        }
+
+        SAFE_DELETE_ARRAY(bytes);
+    }
+    else
+    {
+        GP_WARN("Unsupported heightfield image format: %s.", path);
+    }
+
+    return heightfield;
+}
+
+float* HeightField::getArray() const
+{
+    return _array;
+}
+
+float HeightField::getHeight(float column, float row) const
+{
+    // Clamp to heightfield boundaries
+    column = column < 0 ? 0 : (column > (_cols-1) ? (_cols-1) : column);
+    row = row < 0 ? 0 : (row > (_rows-1) ? (_rows-1) : row);
+
+    unsigned int x1 = column;
+    unsigned int y1 = row;
+    unsigned int x2 = x1 + 1;
+    unsigned int y2 = y1 + 1;
+    float tmp;
+    float xFactor = modf(column, &tmp);
+    float yFactor = modf(row, &tmp);
+    float xFactorI = 1.0f - xFactor;
+    float yFactorI = 1.0f - yFactor;
+
+    if (x2 >= _cols && y2 >= _rows)
+    {
+        return _array[x1 + y1 * _cols];
+    }
+    else if (x2 >= _cols)
+    {
+        return _array[x1 + y1 * _cols] * yFactorI + _array[x1 + y2 * _cols] * yFactor;
+    }
+    else if (y2 >= _rows)
+    {
+        return _array[x1 + y1 * _cols] * xFactorI + _array[x2 + y1 * _cols] * xFactor;
+    }
+    else
+    {
+        float a = xFactorI * yFactorI;
+        float b = xFactorI * yFactor;
+        float c = xFactor * yFactor;
+        float d = xFactor * yFactorI;
+        return _array[x1 + y1 * _cols] * a + _array[x1 + y2 * _cols] * b +
+            _array[x2 + y2 * _cols] * c + _array[x2 + y1 * _cols] * d;
+    }
+}
+
+unsigned int HeightField::getColumnCount() const
+{
+    return _cols;
+}
+
+unsigned int HeightField::getRowCount() const
+{
+    return _rows;
+}
+
+}

+ 142 - 0
gameplay/src/HeightField.h

@@ -0,0 +1,142 @@
+#ifndef HEIGHTFIELD_H_
+#define HEIGHTFIELD_H_
+
+#include "Ref.h"
+
+namespace gameplay
+{
+
+    /**
+     * Defines a reference counted class that holds heightfeild data.
+     *
+     * Heightfields can be used to construct both Terrain objects as well as PhysicsCollisionShape
+     * heightfield defintions, which are used in heightfield rigid body creation. Heightfields can
+     * be populated manually, or loaded from images and RAW files.
+     */
+    class HeightField : public Ref
+    {
+    public:
+
+        /**
+         * Creates a new HeightField of the given dimensions, with uninitialized height data.
+         *
+         * @param rows Number of rows in the height field.
+         * @param columns Number of columns in the height field.
+         *
+         * @return The new HeightField.
+         */
+        static HeightField* create(unsigned int rows, unsigned int columns);
+
+        /**
+         * Creates a HeightField from the specified heightfield image.
+         *
+         * The specified image path must refer to a valid heightfield image. Supported images are
+         * the same as those supported by the Image class (i.e. PNG). 
+         *
+         * The minHeight and maxHeight parameters provides a mapping from heightfield pixel
+         * intensity to height values. The minHeight parameter is mapped to zero intensity
+         * pixel, while maxHeight maxHeight is mapped to full intensity pixels.
+         *
+         * @param path Path to a heightfield image.
+         * @param width Width of the image (required for headerless/RAW files, can be zero for other image formats).
+         * @param height Height of the image (required for headerless/RAW files, can be zero for other image formats).
+         * @param minHeight Minimum height value for a zero intensity pixel.
+         * @param maxHeight Maximum height value for a full intensity heightfield pixel (must be >= minHeight).
+         * 
+         * @return The new HeightField.
+         */
+        static HeightField* createFromImage(const char* path, float minHeight = 0, float maxHeight = 1);
+
+        /**
+         * Creates a HeightField from the specified RAW8 or RAW16 file.
+         *
+         * RAW files are header-less files containing intensity values, either in 8-bit (RAW8)
+         * or 16-bit (RAW16) format. RAW16 files must have little endian (PC) byte ordering. Since
+         * RAW files have no header, you must specify the dimensions of the data in the file.
+         * This method automatically determines (based on file size) whether the input file
+         * is RAW8 or RAW16.
+         *
+         * RAW files are commonly used in software that produces heightmap images. Using RAW16 is 
+         * preferred or any 8-bit heightfield source since it allows greater precision, resulting in
+         * smoother height transitions.
+         *
+         * The minHeight and maxHeight parameters provides a mapping from heightfield pixel
+         * intensity to height values. The minHeight parameter is mapped to zero intensity
+         * pixel, while maxHeight maxHeight is mapped to full intensity pixels.
+         *
+         * @param path Path to the RAW file.
+         * @param width Width of the RAW data.
+         * @param height Height of the RAW data.
+         * @param minHeight Minimum height value for a zero intensity pixel.
+         * @param maxHeight Maximum height value for a full intensity heightfield pixel (must be >= minHeight).
+         * 
+         * @return The new HeightField.
+         */
+        static HeightField* createFromRAW(const char* path, unsigned int width, unsigned int height, float minHeight = 0, float maxHeight = 1);
+
+        /**
+         * Returns a pointer to the underying height array.
+         *
+         * The array is packed in row major order, meaning that the data is aligned in rows,
+         * from top left to bottom right.
+         *
+         * @return The underlying height array.
+         */
+        float* getArray() const;
+
+        /**
+         * Returns the height at the specified row and column.
+         *
+         * The specified row and column are specified as floating point numbers so that values
+         * between points can be specified. In this case, a height value is calculated that is
+         * interpolated between neighboring height values. 
+         *
+         * If the specified point lies outside the heightfield, it is clamped to the boundary
+         * of the heightfield.
+         *
+         * @param column The column of the height value to query.
+         * @param row The row of the height value to query.
+         *
+         * @return The height value.
+         */
+        float getHeight(float column, float row) const;
+
+        /**
+         * Returns the number of rows in the heightfield.
+         *
+         * @return The number of rows.
+         */
+        unsigned int getRowCount() const;
+
+        /**
+         * Returns the number of columns in the heightfield.
+         * 
+         * @return The column count.
+         */
+        unsigned int getColumnCount() const;
+
+    private:
+
+        /**
+         * Hidden constructor.
+         */
+        HeightField(unsigned int columns, unsigned int rows);
+
+        /**
+         * Hidden destructor (use Ref::release()).
+         */
+        ~HeightField();
+
+        /**
+         * Internal method for creating a HeightField.
+         */
+        static HeightField* create(const char* path, unsigned int width, unsigned int height, float minHeight, float maxHeight);
+
+        float* _array;
+        unsigned int _cols;
+        unsigned int _rows;
+    };
+
+}
+
+#endif

+ 95 - 78
gameplay/src/MaterialParameter.cpp

@@ -18,11 +18,34 @@ MaterialParameter::~MaterialParameter()
 
 void MaterialParameter::clearValue()
 {
+    // Release parameters
+    switch (_type)
+    {
+    case MaterialParameter::SAMPLER:
+        if (_value.samplerValue)
+            const_cast<Texture::Sampler*>(_value.samplerValue)->release();
+        break;
+    case MaterialParameter::SAMPLER_ARRAY:
+        if (_value.samplerArrayValue)
+        {
+            for (unsigned int i = 0; i < _count; ++i)
+            {
+                const_cast<Texture::Sampler*>(_value.samplerArrayValue[i])->release();
+            }
+        }
+        break;
+    default:
+        // Ignore all other cases.
+        break;
+    }
+
+    // Free dynamic data
     if (_dynamic)
     {
         switch (_type)
         {
         case MaterialParameter::FLOAT:
+        case MaterialParameter::FLOAT_ARRAY:
         case MaterialParameter::VECTOR2:
         case MaterialParameter::VECTOR3:
         case MaterialParameter::VECTOR4:
@@ -30,11 +53,15 @@ void MaterialParameter::clearValue()
             SAFE_DELETE_ARRAY(_value.floatPtrValue);
             break;
         case MaterialParameter::INT:
+        case MaterialParameter::INT_ARRAY:
             SAFE_DELETE_ARRAY(_value.intPtrValue);
             break;
         case MaterialParameter::METHOD:
             SAFE_RELEASE(_value.method);
             break;
+        case MaterialParameter::SAMPLER_ARRAY:
+            SAFE_DELETE_ARRAY(_value.samplerArrayValue);
+            break;
         default:
             // Ignore all other cases.
             break;
@@ -43,21 +70,6 @@ void MaterialParameter::clearValue()
         _dynamic = false;
         _count = 1;
     }
-    else
-    {
-        switch (_type)
-        {
-        case MaterialParameter::SAMPLER:
-            if (_value.samplerValue)
-            {
-                const_cast<Texture::Sampler*>(_value.samplerValue)->release();
-            }
-            break;
-        default:
-            // Ignore all other cases.
-            break;
-        }
-    }
 
     memset(&_value, 0, sizeof(_value));
     _type = MaterialParameter::NONE;
@@ -68,10 +80,12 @@ const char* MaterialParameter::getName() const
     return _name.c_str();
 }
 
-Texture::Sampler* MaterialParameter::getSampler() const
+Texture::Sampler* MaterialParameter::getSampler(unsigned int index) const
 {
     if (_type == MaterialParameter::SAMPLER)
         return const_cast<Texture::Sampler*>(_value.samplerValue);
+    if (_type == MaterialParameter::SAMPLER_ARRAY && index < _count)
+        return const_cast<Texture::Sampler*>(_value.samplerArrayValue[index]);
     return NULL;
 }
 
@@ -97,7 +111,7 @@ void MaterialParameter::setValue(const float* values, unsigned int count)
 
     _value.floatPtrValue = const_cast<float*> (values);
     _count = count;
-    _type = MaterialParameter::FLOAT;
+    _type = MaterialParameter::FLOAT_ARRAY;
 }
 
 void MaterialParameter::setValue(const int* values, unsigned int count)
@@ -106,7 +120,7 @@ void MaterialParameter::setValue(const int* values, unsigned int count)
 
     _value.intPtrValue = const_cast<int*> (values);
     _count = count;
-    _type = MaterialParameter::INT;
+    _type = MaterialParameter::INT_ARRAY;
 }
 
 void MaterialParameter::setValue(const Vector2& value)
@@ -221,6 +235,22 @@ void MaterialParameter::setValue(const Texture::Sampler* sampler)
     }
 }
 
+void MaterialParameter::setValue(const Texture::Sampler** samplers, unsigned int count)
+{
+    clearValue();
+
+    if (samplers)
+    {
+        for (unsigned int i = 0; i < count; ++i)
+        {
+            const_cast<Texture::Sampler*>(samplers[i])->addRef();
+        }
+        _value.samplerArrayValue = samplers;
+        _count = count;
+        _type = MaterialParameter::SAMPLER_ARRAY;
+    }
+}
+
 Texture::Sampler* MaterialParameter::setValue(const char* texturePath, bool generateMipmaps)
 {
     if (texturePath)
@@ -259,24 +289,16 @@ void MaterialParameter::bind(Effect* effect)
     switch (_type)
     {
     case MaterialParameter::FLOAT:
-        if (_count == 1)
-        {
-            effect->setValue(_uniform, _value.floatValue);
-        }
-        else
-        {
-            effect->setValue(_uniform, _value.floatPtrValue, _count);
-        }
+        effect->setValue(_uniform, _value.floatValue);
+        break;
+    case MaterialParameter::FLOAT_ARRAY:
+        effect->setValue(_uniform, _value.floatPtrValue, _count);
         break;
     case MaterialParameter::INT:
-        if (_count == 1)
-        {
-            effect->setValue(_uniform, _value.intValue);
-        }
-        else
-        {
-            effect->setValue(_uniform, _value.intPtrValue, _count);
-        }
+        effect->setValue(_uniform, _value.intValue);
+        break;
+    case MaterialParameter::INT_ARRAY:
+        effect->setValue(_uniform, _value.intPtrValue, _count);
         break;
     case MaterialParameter::VECTOR2:
         effect->setValue(_uniform, reinterpret_cast<Vector2*>(_value.floatPtrValue), _count);
@@ -293,6 +315,9 @@ void MaterialParameter::bind(Effect* effect)
     case MaterialParameter::SAMPLER:
         effect->setValue(_uniform, _value.samplerValue);
         break;
+    case MaterialParameter::SAMPLER_ARRAY:
+        effect->setValue(_uniform, _value.samplerArrayValue, _count);
+        break;
     case MaterialParameter::METHOD:
         GP_ASSERT(_value.method);
         _value.method->setValue(effect);
@@ -405,10 +430,13 @@ unsigned int MaterialParameter::getAnimationPropertyComponentCount(int propertyI
                 case NONE:
                 case MATRIX:
                 case SAMPLER:
+                case SAMPLER_ARRAY:
                 case METHOD:
                     return 0;
                 case FLOAT:
+                case FLOAT_ARRAY:
                 case INT:
+                case INT_ARRAY:
                     return _count;
                 case VECTOR2:
                     return 2 * _count;
@@ -437,31 +465,23 @@ void MaterialParameter::getAnimationPropertyValue(int propertyId, AnimationValue
             switch (_type)
             {
                 case FLOAT:
-                    if (_count == 1)
-                    {
-                        value->setFloat(0, _value.floatValue);
-                    }
-                    else
+                    value->setFloat(0, _value.floatValue);
+                    break;
+                case FLOAT_ARRAY:
+                    GP_ASSERT(_value.floatPtrValue);
+                    for (unsigned int i = 0; i < _count; i++)
                     {
-                        GP_ASSERT(_value.floatPtrValue);
-                        for (unsigned int i = 0; i < _count; i++)
-                        {
-                            value->setFloat(i, _value.floatPtrValue[i]);
-                        }
+                        value->setFloat(i, _value.floatPtrValue[i]);
                     }
                     break;
                 case INT:
-                    if (_count == 1)
-                    {
-                        value->setFloat(0, _value.intValue);
-                    }
-                    else
+                    value->setFloat(0, _value.intValue);
+                    break;
+                case INT_ARRAY:
+                    GP_ASSERT(_value.intPtrValue);
+                    for (unsigned int i = 0; i < _count; i++)
                     {
-                        GP_ASSERT(_value.intPtrValue);
-                        for (unsigned int i = 0; i < _count; i++)
-                        {
-                            value->setFloat(i, _value.intPtrValue[i]);
-                        }
+                        value->setFloat(i, _value.intPtrValue[i]);
                     }
                     break;
                 case VECTOR2:
@@ -477,6 +497,7 @@ void MaterialParameter::getAnimationPropertyValue(int propertyId, AnimationValue
                 case MATRIX:
                 case METHOD:
                 case SAMPLER:
+                case SAMPLER_ARRAY:
                     // Unsupported material parameter types for animation.
                     break;
                 default:
@@ -500,46 +521,33 @@ void MaterialParameter::setAnimationPropertyValue(int propertyId, AnimationValue
             switch (_type)
             {
                 case FLOAT:
-                {
-                    if (_count == 1)
-                        _value.floatValue = Curve::lerp(blendWeight, _value.floatValue, value->getFloat(0));
-                    else
-                        applyAnimationValue(value, blendWeight, 1);
+                    _value.floatValue = Curve::lerp(blendWeight, _value.floatValue, value->getFloat(0));
+                    break;
+                case FLOAT_ARRAY:
+                    applyAnimationValue(value, blendWeight, 1);
                     break;
-                }
                 case INT:
-                {
-                    if (_count == 1)
-                    {
-                        _value.intValue = Curve::lerp(blendWeight, _value.intValue, value->getFloat(0));
-                    }
-                    else
-                    {
-                        GP_ASSERT(_value.intPtrValue);
-                        for (unsigned int i = 0; i < _count; i++)
-                            _value.intPtrValue[i] = Curve::lerp(blendWeight, _value.intPtrValue[i], value->getFloat(i));
-                    }
+                    _value.intValue = Curve::lerp(blendWeight, _value.intValue, value->getFloat(0));
+                    break;
+                case INT_ARRAY:
+                    GP_ASSERT(_value.intPtrValue);
+                    for (unsigned int i = 0; i < _count; i++)
+                        _value.intPtrValue[i] = Curve::lerp(blendWeight, _value.intPtrValue[i], value->getFloat(i));
                     break;
-                }
                 case VECTOR2:
-                {
                     applyAnimationValue(value, blendWeight, 2);
                     break;
-                }
                 case VECTOR3:
-                {
                     applyAnimationValue(value, blendWeight, 3);
                     break;
-                }
                 case VECTOR4:
-                {
                     applyAnimationValue(value, blendWeight, 4);
                     break;
-                }
                 case NONE:
                 case MATRIX:
                 case METHOD:
                 case SAMPLER:
+                case SAMPLER_ARRAY:
                     // Unsupported material parameter types for animation.
                     break;
                 default:
@@ -575,9 +583,15 @@ void MaterialParameter::cloneInto(MaterialParameter* materialParameter) const
     case FLOAT:
         materialParameter->setValue(_value.floatValue);
         break;
+    case FLOAT_ARRAY:
+        materialParameter->setValue(_value.floatPtrValue, _count);
+        break;
     case INT:
         materialParameter->setValue(_value.intValue);
         break;
+    case INT_ARRAY:
+        materialParameter->setValue(_value.intPtrValue, _count);
+        break;
     case VECTOR2:
     {
         Vector2* value = reinterpret_cast<Vector2*>(_value.floatPtrValue);
@@ -637,6 +651,9 @@ void MaterialParameter::cloneInto(MaterialParameter* materialParameter) const
     case SAMPLER:
         materialParameter->setValue(_value.samplerValue);
         break;
+    case SAMPLER_ARRAY:
+        materialParameter->setValue(_value.samplerArrayValue, _count);
+        break;
     case METHOD:
         materialParameter->_value.method = _value.method;
         GP_ASSERT(materialParameter->_value.method);

+ 22 - 1
gameplay/src/MaterialParameter.h

@@ -27,6 +27,12 @@ namespace gameplay
  * setting the parameter value to a pointer to a Matrix, any changes
  * to the Matrix will automatically be reflected in the technique the
  * next time the parameter is applied to the render state.
+ *
+ * Note that for parameter values to arrays or pointers, the 
+ * MaterialParameter will keep a long-lived reference to the passed
+ * in array/pointer. Therefore, you must ensure that the pointers
+ * you pass in are valid for the lifetime of the MaterialParameter
+ * object.
  */
 class MaterialParameter : public AnimationTarget, public Ref
 {
@@ -47,9 +53,12 @@ public:
     /**
      * Returns the texture sampler or NULL if this MaterialParameter is not a sampler type.
      * 
+     * @param index Index of the sampler (if the parameter is a sampler array),
+     *      or zero if it is a single sampler value.
+     *
      * @return The texture sampler or NULL if this MaterialParameter is not a sampler type.
      */
-    Texture::Sampler* getSampler() const;
+    Texture::Sampler* getSampler(unsigned int index = 0) const;
 
     /**
      * Sets the value of this parameter to a float value.
@@ -116,6 +125,13 @@ public:
      */
     void setValue(const Texture::Sampler* sampler);
 
+    /**
+     * Sets the value of this parameter to the specified texture sampler array.
+     *
+     * @script{ignore}
+     */
+    void setValue(const Texture::Sampler** samplers, unsigned int count);
+
     /**
      * Loads a texture sampler from the specified path and sets it as the value of this parameter.
      *
@@ -298,6 +314,8 @@ private:
         /** @script{ignore} */
         const Texture::Sampler* samplerValue;
         /** @script{ignore} */
+        const Texture::Sampler** samplerArrayValue;
+        /** @script{ignore} */
         MethodBinding* method;
     } _value;
     
@@ -305,12 +323,15 @@ private:
     {
         NONE,
         FLOAT,
+        FLOAT_ARRAY,
         INT,
+        INT_ARRAY,
         VECTOR2,
         VECTOR3,
         VECTOR4,
         MATRIX,
         SAMPLER,
+        SAMPLER_ARRAY,
         METHOD
     } _type;
     

+ 11 - 8
gameplay/src/Model.h

@@ -134,6 +134,16 @@ public:
      */
     Node* getNode() const;
 
+    /**
+     * Sets the node that is associated with this model.
+     *
+     * This method is automatically called when a model is attached to a node
+     * and therefore should not normally be called explicitly.
+     * 
+     * @param node The node that is associated with this model.
+     */
+    void setNode(Node* node);
+
     /**
      * Draws this mesh instance.
      *
@@ -171,14 +181,7 @@ private:
     void setSkin(MeshSkin* skin);
 
     /**
-     * Sets the node that is associated with this model.
-     * 
-     * @param node The node that is associated with this model.
-     */
-    void setNode(Node* node);
-
-    /**
-     * Sets the specified materia's node binding to this model's node.
+     * Sets the specified material's node binding to this model's node.
      */
     void setMaterialNodeBinding(Material *m);
 

+ 50 - 10
gameplay/src/Node.cpp

@@ -1,6 +1,6 @@
 #include "Base.h"
-#include "AudioSource.h"
 #include "Node.h"
+#include "AudioSource.h"
 #include "Scene.h"
 #include "Joint.h"
 #include "PhysicsRigidBody.h"
@@ -9,6 +9,7 @@
 #include "PhysicsGhostObject.h"
 #include "PhysicsCharacter.h"
 #include "Game.h"
+#include "Terrain.h"
 
 // Node dirty flags
 #define NODE_DIRTY_WORLD 1
@@ -20,7 +21,7 @@ namespace gameplay
 
 Node::Node(const char* id)
     : _scene(NULL), _firstChild(NULL), _nextSibling(NULL), _prevSibling(NULL), _parent(NULL), _childCount(0),
-    _tags(NULL), _camera(NULL), _light(NULL), _model(NULL), _form(NULL), _audioSource(NULL), _particleEmitter(NULL),
+    _tags(NULL), _camera(NULL), _light(NULL), _model(NULL), _terrain(NULL), _form(NULL), _audioSource(NULL), _particleEmitter(NULL),
     _collisionObject(NULL), _agent(NULL), _dirtyBits(NODE_DIRTY_ALL), _notifyHierarchyChanged(true), _userData(NULL)
 {
     if (id)
@@ -45,6 +46,7 @@ Node::~Node()
     SAFE_RELEASE(_camera);
     SAFE_RELEASE(_light);
     SAFE_RELEASE(_model);
+    SAFE_RELEASE(_terrain);
     SAFE_RELEASE(_audioSource);
     SAFE_RELEASE(_particleEmitter);
     SAFE_RELEASE(_form);
@@ -767,6 +769,11 @@ void Node::setLight(Light* light)
     }
 }
 
+Model* Node::getModel() const
+{
+    return _model;
+}
+
 void Node::setModel(Model* model)
 {
     if (_model != model)
@@ -787,9 +794,34 @@ void Node::setModel(Model* model)
     }
 }
 
-Model* Node::getModel() const
+Terrain* Node::getTerrain() const
 {
-    return _model;
+    return _terrain;
+}
+
+void Node::setTerrain(Terrain* terrain)
+{
+    if (_terrain != terrain)
+    {
+        if (_terrain)
+        {
+            _terrain->setNode(NULL);
+            SAFE_RELEASE(_terrain);
+        }
+
+        _terrain = terrain;
+
+        if (_terrain)
+        {
+            _terrain->addRef();
+            _terrain->setNode(this);
+        }
+    }
+}
+
+Form* Node::getForm() const
+{
+    return _form;
 }
 
 void Node::setForm(Form* form)
@@ -812,10 +844,6 @@ void Node::setForm(Form* form)
     }
 }
 
-Form* Node::getForm() const
-{
-    return _form;
-}
 
 const BoundingSphere& Node::getBoundingSphere() const
 {
@@ -828,11 +856,23 @@ const BoundingSphere& Node::getBoundingSphere() const
         // Start with our local bounding sphere
         // TODO: Incorporate bounds from entities other than mesh (i.e. emitters, audiosource, etc)
         bool empty = true;
-        if (_model && _model->getMesh())
+        if (_terrain)
         {
-            _bounds.set(_model->getMesh()->getBoundingSphere());
+            _bounds.set(_terrain->getBoundingBox());
             empty = false;
         }
+        if (_model && _model->getMesh())
+        {
+            if (empty)
+            {
+                _bounds.set(_model->getMesh()->getBoundingSphere());
+                empty = false;
+            }
+            else
+            {
+                _bounds.merge(_model->getMesh()->getBoundingSphere());
+            }
+        }
         else
         {
             // Empty bounding sphere, set the world translation with zero radius

+ 25 - 3
gameplay/src/Node.h

@@ -9,7 +9,6 @@
 #include "ParticleEmitter.h"
 #include "PhysicsRigidBody.h"
 #include "PhysicsCollisionObject.h"
-#include "PhysicsCollisionShape.h"
 #include "BoundingBox.h"
 #include "AIAgent.h"
 
@@ -20,6 +19,7 @@ class AudioSource;
 class Bundle;
 class Scene;
 class Form;
+class Terrain;
 
 /**
  * Defines a basic hierarchical structure of transformation spaces.
@@ -38,7 +38,7 @@ public:
     enum Type
     {
         NODE = 1,
-        JOINT = 2
+        JOINT
     };
 
     /**
@@ -425,6 +425,23 @@ public:
      */
     void setModel(Model* model);
 
+    /**
+     * Returns the pointer to this node's terrain.
+     *
+     * @return The pointer to this node's terrain.
+     */
+    Terrain* getTerrain() const;
+
+    /**
+     * Assigns a terrain to this node.
+     *
+     * This will increase the reference count of the new terrain and decrease
+     * the reference count of the old terrain.
+     *
+     * @param terrain The new terrain. May be NULL.
+     */
+    void setTerrain(Terrain* terrain);
+
     /**
      * Returns the pointer to this node's form.
      * 
@@ -768,7 +785,12 @@ protected:
      * Pointer to the Model attached to the Node.
      */
     Model* _model;
-    
+
+    /**
+     * Pointer to the Terrain attached to the Node.
+     */
+    Terrain* _terrain;
+
     /**
      * Pointer to the Form attached to the Node.
      */

+ 3 - 4
gameplay/src/PhysicsCharacter.cpp

@@ -99,8 +99,8 @@ PhysicsCharacter* PhysicsCharacter::create(Node* node, Properties* properties)
     }
 
     // Load the physics collision shape definition.
-    PhysicsCollisionShape::Definition* shape = PhysicsCollisionShape::Definition::create(node, properties);
-    if (shape == NULL)
+    PhysicsCollisionShape::Definition shape = PhysicsCollisionShape::Definition::create(node, properties);
+    if (shape.isEmpty())
     {
         GP_ERROR("Failed to create collision shape during physics character creation.");
         return NULL;
@@ -133,10 +133,9 @@ PhysicsCharacter* PhysicsCharacter::create(Node* node, Properties* properties)
     }
 
     // Create the physics character.
-    PhysicsCharacter* character = new PhysicsCharacter(node, *shape, mass);
+    PhysicsCharacter* character = new PhysicsCharacter(node, shape, mass);
     character->setMaxStepHeight(maxStepHeight);
     character->setMaxSlopeAngle(maxSlopeAngle);
-    SAFE_DELETE(shape);
 
     return character;
 }

+ 40 - 3
gameplay/src/PhysicsCollisionObject.cpp

@@ -4,6 +4,11 @@
 #include "Game.h"
 #include "Node.h"
 #include "ScriptController.h"
+#include "PhysicsRigidBody.h"
+#include "PhysicsCharacter.h"
+#include "PhysicsGhostObject.h"
+#include "PhysicsVehicle.h"
+#include "PhysicsVehicleWheel.h"
 
 namespace gameplay
 {
@@ -165,6 +170,31 @@ bool PhysicsCollisionObject::collidesWith(PhysicsCollisionObject* object) const
     return callback.result;
 }
 
+PhysicsRigidBody* PhysicsCollisionObject::asRigidBody()
+{
+    return getType() == RIGID_BODY ? static_cast<PhysicsRigidBody*>(this) : NULL;
+}
+
+PhysicsCharacter* PhysicsCollisionObject::asCharacter()
+{
+    return getType() == CHARACTER ? static_cast<PhysicsCharacter*>(this) : NULL;
+}
+
+PhysicsGhostObject* PhysicsCollisionObject::asGhostObject()
+{
+    return getType() == GHOST_OBJECT ? static_cast<PhysicsGhostObject*>(this) : NULL;
+}
+
+PhysicsVehicle* PhysicsCollisionObject::asVehicle()
+{
+    return getType() == VEHICLE ? static_cast<PhysicsVehicle*>(this) : NULL;
+}
+
+PhysicsVehicleWheel* PhysicsCollisionObject::asVehicleWheel()
+{
+    return getType() == VEHICLE_WHEEL ? static_cast<PhysicsVehicleWheel*>(this) : NULL;
+}
+
 PhysicsCollisionObject::CollisionPair::CollisionPair(PhysicsCollisionObject* objectA, PhysicsCollisionObject* objectB)
     : objectA(objectA), objectB(objectB)
 {
@@ -187,8 +217,8 @@ bool PhysicsCollisionObject::CollisionPair::operator < (const CollisionPair& col
     return false;
 }
 
-PhysicsCollisionObject::PhysicsMotionState::PhysicsMotionState(Node* node, const Vector3* centerOfMassOffset) : _node(node),
-    _centerOfMassOffset(btTransform::getIdentity())
+PhysicsCollisionObject::PhysicsMotionState::PhysicsMotionState(Node* node, PhysicsCollisionObject* collisionObject, const Vector3* centerOfMassOffset) :
+    _node(node), _collisionObject(collisionObject), _centerOfMassOffset(btTransform::getIdentity())
 {
     if (centerOfMassOffset)
     {
@@ -206,7 +236,9 @@ PhysicsCollisionObject::PhysicsMotionState::~PhysicsMotionState()
 void PhysicsCollisionObject::PhysicsMotionState::getWorldTransform(btTransform &transform) const
 {
     GP_ASSERT(_node);
-    if (_node->getCollisionObject() && _node->getCollisionObject()->isKinematic())
+    GP_ASSERT(_collisionObject);
+
+    if (_collisionObject->isKinematic())
         updateTransformFromNode();
 
     transform = _centerOfMassOffset.inverse() * _worldTransform;
@@ -251,6 +283,11 @@ void PhysicsCollisionObject::PhysicsMotionState::updateTransformFromNode() const
     }
 }
 
+void PhysicsCollisionObject::PhysicsMotionState::setCenterOfMassOffset(const Vector3& centerOfMassOffset)
+{
+    _centerOfMassOffset.setOrigin(BV(centerOfMassOffset));
+}
+
 PhysicsCollisionObject::ScriptListener::ScriptListener(const char* url)
     : url(url)
 {

+ 59 - 3
gameplay/src/PhysicsCollisionObject.h

@@ -8,6 +8,11 @@ namespace gameplay
 {
 
 class Node;
+class PhysicsRigidBody;
+class PhysicsCharacter;
+class PhysicsGhostObject;
+class PhysicsVehicle;
+class PhysicsVehicleWheel;
 
 /**
  * Base class for all gameplay physics objects that support collision events.
@@ -238,6 +243,50 @@ public:
      */
     bool collidesWith(PhysicsCollisionObject* object) const;
 
+    /**
+     * Returns this collision object as a physics rigid body.
+     *
+     * If this collision object is not of type RIGID_BODY, this method returns NULL.
+     *
+     * @return This collision object cast to a PhysicsRigidBody.
+     */
+    PhysicsRigidBody* asRigidBody();
+
+    /**
+     * Returns this collision object as a physics character.
+     *
+     * If this collision object is not of type CHARACTER, this method returns NULL.
+     *
+     * @return This collision object cast to a PhysicsCharacter.
+     */
+    PhysicsCharacter* asCharacter();
+
+    /**
+     * Returns this collision object as a physics ghost object.
+     *
+     * If this collision object is not of type GHOST_OBJECT, this method returns NULL.
+     *
+     * @return This collision object cast to a PhysicsGhostObject.
+     */
+    PhysicsGhostObject* asGhostObject();
+
+    /**
+     * Returns this collision object as a physics vehicle.
+     *
+     * If this collision object is not of type VEHICLE, this method returns NULL.
+     *
+     * @return This collision object cast to a PhysicsVehicle.
+     */
+    PhysicsVehicle* asVehicle();
+
+    /**
+     * Returns this collision object as a physics vehicle wheel.
+     *
+     * If this collision object is not of type VEHICLE_WHEEL, this method returns NULL.
+     *
+     * @return This collision object cast to a PhysicsVehicleWheel.
+     */
+    PhysicsVehicleWheel* asVehicleWheel();
 
 protected:
     /**
@@ -270,16 +319,17 @@ protected:
     class PhysicsMotionState : public btMotionState
     {
         friend class PhysicsConstraint;
-
+        
     public:
 
         /**
          * Creates a physics motion state for a rigid body.
          * 
-         * @param node The node that owns the rigid body that the motion state is being created for.
+         * @param node The node that contains the transformation to be associated with the motion state.
+         * @param collisionObject The collision object that owns the motion state.
          * @param centerOfMassOffset The translation offset to the center of mass of the rigid body.
          */
-        PhysicsMotionState(Node* node, const Vector3* centerOfMassOffset = NULL);
+        PhysicsMotionState(Node* node, PhysicsCollisionObject* collisionObject, const Vector3* centerOfMassOffset = NULL);
 
         /**
          * Destructor.
@@ -301,9 +351,15 @@ protected:
          */
         void updateTransformFromNode() const;
 
+        /**
+         * Sets the center of mass offset for the associated collision shape.
+         */
+        void setCenterOfMassOffset(const Vector3& centerOfMassOffset);
+
     private:
 
         Node* _node;
+        PhysicsCollisionObject* _collisionObject;
         btTransform _centerOfMassOffset;
         mutable btTransform _worldTransform;
     };

+ 131 - 104
gameplay/src/PhysicsCollisionShape.cpp

@@ -1,7 +1,10 @@
 #include "Base.h"
 #include "PhysicsCollisionShape.h"
 #include "Node.h"
+#include "Image.h"
 #include "Properties.h"
+#include "FileSystem.h"
+#include "HeightField.h"
 
 namespace gameplay
 {
@@ -32,13 +35,12 @@ PhysicsCollisionShape::~PhysicsCollisionShape()
 
             // Also need to delete the btTriangleIndexVertexArray, if it exists.
             SAFE_DELETE(_meshInterface);
-
             break;
+
         case SHAPE_HEIGHTFIELD:
             if (_shapeData.heightfieldData)
             {
-                SAFE_DELETE_ARRAY(_shapeData.heightfieldData->heightData);
-                SAFE_DELETE_ARRAY(_shapeData.heightfieldData->normalData);
+                SAFE_RELEASE(_shapeData.heightfieldData->heightfield);
                 SAFE_DELETE(_shapeData.heightfieldData);
             }
             break;
@@ -55,7 +57,7 @@ PhysicsCollisionShape::Type PhysicsCollisionShape::getType() const
 }
 
 PhysicsCollisionShape::Definition::Definition()
-    : isExplicit(false), centerAbsolute(false)
+    : type(SHAPE_NONE), isExplicit(false), centerAbsolute(false)
 {
     memset(&data, 0, sizeof(data));
 }
@@ -69,8 +71,8 @@ PhysicsCollisionShape::Definition::Definition(const Definition& definition)
     switch (type)
     {
     case PhysicsCollisionShape::SHAPE_HEIGHTFIELD:
-        GP_ASSERT(data.heightfield);
-        data.heightfield->addRef();
+        if (data.heightfield)
+            data.heightfield->addRef();
         break;
 
     case PhysicsCollisionShape::SHAPE_MESH:
@@ -105,8 +107,8 @@ PhysicsCollisionShape::Definition& PhysicsCollisionShape::Definition::operator=(
         switch (type)
         {
         case PhysicsCollisionShape::SHAPE_HEIGHTFIELD:
-            GP_ASSERT(data.heightfield);
-            data.heightfield->addRef();
+            if (data.heightfield)
+                data.heightfield->addRef();
             break;
 
         case PhysicsCollisionShape::SHAPE_MESH:
@@ -119,7 +121,12 @@ PhysicsCollisionShape::Definition& PhysicsCollisionShape::Definition::operator=(
     return *this;
 }
 
-PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Node* node, Properties* properties)
+bool PhysicsCollisionShape::Definition::isEmpty() const
+{
+    return type == SHAPE_NONE;
+}
+
+PhysicsCollisionShape::Definition PhysicsCollisionShape::Definition::create(Node* node, Properties* properties)
 {
     GP_ASSERT(node);
 
@@ -127,17 +134,21 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
     if (!properties || !(strcmp(properties->getNamespace(), "collisionObject") == 0))
     {
         GP_ERROR("Failed to load physics collision shape from properties object: must be non-null object and have namespace equal to 'collisionObject'.");
-        return NULL;
+        return Definition();
     }
 
     // Set values to their defaults.
     PhysicsCollisionShape::Type type = PhysicsCollisionShape::SHAPE_BOX;
-    Vector3* extents = NULL;
-    Vector3* center = NULL;
+    Vector3 extents, center;
+    bool extentsSpecified = false;
+    bool centerSpecified = false;
     float radius = -1.0f;
+    float width = -1.0f;
     float height = -1.0f;
     bool centerIsAbsolute = false;
     const char* imagePath = NULL;
+    float maxHeight = 0;
+    float minHeight = 0;
     bool shapeSpecified = false;
 
     // Load the defined properties.
@@ -161,7 +172,7 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
             else
             {
                 GP_ERROR("Could not create physics collision shape; unsupported value for collision shape type: '%s'.", shapeStr.c_str());
-                return NULL;
+                return Definition();
             }
 
             shapeSpecified = true;
@@ -170,23 +181,35 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
         {
             imagePath = properties->getString();
         }
+        else if (strcmp(name, "maxHeight") == 0)
+        {
+            maxHeight = properties->getFloat();
+        }
+        else if (strcmp(name, "minHeight") == 0)
+        {
+            minHeight = properties->getFloat();
+        }
         else if (strcmp(name, "radius") == 0)
         {
             radius = properties->getFloat();
         }
+        else if (strcmp(name, "width") == 0)
+        {
+            width = properties->getFloat();
+        }
         else if (strcmp(name, "height") == 0)
         {
             height = properties->getFloat();
         }
         else if (strcmp(name, "extents") == 0)
         {
-            extents = new Vector3();
-            properties->getVector3("extents", extents);
+            properties->getVector3("extents", &extents);
+            extentsSpecified = true;
         }
         else if (strcmp(name, "center") == 0)
         {
-            center = new Vector3();
-            properties->getVector3("center", center);
+            properties->getVector3("center", &center);
+            centerSpecified = true;
         }
         else if (strcmp(name, "centerAbsolute") == 0)
         {
@@ -201,136 +224,130 @@ PhysicsCollisionShape::Definition* PhysicsCollisionShape::Definition::create(Nod
     if (!shapeSpecified)
     {
         GP_ERROR("Missing 'shape' specifier for collision shape definition.");
-        return NULL;
+        return Definition();
     }
 
     // Create the collision shape.
-    PhysicsCollisionShape::Definition* shape = new PhysicsCollisionShape::Definition();
+    Definition shape;
     switch (type)
     {
-        case SHAPE_BOX:
-            if (extents)
+    case SHAPE_BOX:
+        if (extentsSpecified)
+        {
+            if (centerSpecified)
             {
-                if (center)
-                {
-                    *shape = box(*extents, *center, centerIsAbsolute);
-                }
-                else
-                {
-                    *shape = box(*extents);
-                }
+                shape = box(extents, center, centerIsAbsolute);
             }
             else
             {
-                *shape = box();
+                shape = box(extents);
             }
-            break;
-        case SHAPE_SPHERE:
-            if (radius != -1.0f)
+        }
+        else
+        {
+            shape = box();
+        }
+        break;
+
+    case SHAPE_SPHERE:
+        if (radius != -1.0f)
+        {
+            if (centerSpecified)
             {
-                if (center)
-                {
-                    *shape = sphere(radius, *center, centerIsAbsolute);
-                }
-                else
-                {
-                    *shape = sphere(radius);
-                }
+                shape = sphere(radius, center, centerIsAbsolute);
             }
             else
             {
-                *shape = sphere();
+                shape = sphere(radius);
             }
-            break;
-        case SHAPE_CAPSULE:
-            if (radius != -1.0f && height != -1.0f)
+        }
+        else
+        {
+            shape = sphere();
+        }
+        break;
+
+    case SHAPE_CAPSULE:
+        if (radius != -1.0f && height != -1.0f)
+        {
+            if (centerSpecified)
             {
-                if (center)
-                {
-                    *shape = capsule(radius, height, *center, centerIsAbsolute);
-                }
-                else
-                {
-                    *shape = capsule(radius, height);
-                }
+                shape = capsule(radius, height, center, centerIsAbsolute);
             }
             else
             {
-                *shape = capsule();
+                shape = capsule(radius, height);
             }
-            break;
-        case SHAPE_MESH:
+        }
+        else
+        {
+            shape = capsule();
+        }
+        break;
+
+    case SHAPE_MESH:
         {
             // Mesh is required on node.
             Mesh* nodeMesh = node->getModel() ? node->getModel()->getMesh() : NULL;
             if (nodeMesh == NULL)
             {
                 GP_ERROR("Cannot create mesh collision object for node without model/mesh.");
-                return NULL;
             }
-
-            // Check that the node's mesh's primitive type is supported.
-            switch (nodeMesh->getPrimitiveType())
+            else
             {
-                case Mesh::TRIANGLES:
+                // Check that the node's mesh's primitive type is supported.
+                switch (nodeMesh->getPrimitiveType())
                 {
-                    *shape = mesh(nodeMesh);
+                case Mesh::TRIANGLES:
+                    shape = mesh(nodeMesh);
                     break;
-                }
                 case Mesh::LINES:
                 case Mesh::LINE_STRIP:
                 case Mesh::POINTS:
                 case Mesh::TRIANGLE_STRIP:
                     GP_ERROR("Mesh collision objects are currently only supported on meshes with primitive type equal to TRIANGLES.");
-                    SAFE_DELETE(shape);
                     break;
+                }
             }
-
-            break;
         }
-        case SHAPE_HEIGHTFIELD:
+        break;
+
+    case SHAPE_HEIGHTFIELD:
+        {
             if (imagePath == NULL)
             {
-                GP_ERROR("Heightfield collision objects require an image path.");
-                SAFE_DELETE(shape);
-                return NULL;
+                // Node requires a valid terrain
+                if (node->getTerrain() == NULL)
+                {
+                    GP_ERROR("Heightfield collision objects can only be specified on nodes that have a valid terrain, or that specify an image path.");
+                }
+                else
+                {
+                    shape = PhysicsCollisionShape::heightfield();
+                }
             }
             else
             {
-                // Load the image data from the given file path.
-                Image* image = Image::create(imagePath);
-                if (!image)
-                {
-                    GP_ERROR("Failed create image for heightfield collision object from file '%s'.", imagePath);
-                    SAFE_DELETE(shape);
-                    return NULL;
-                }
-
-                // Ensure that the image's pixel format is supported.
-                switch (image->getFormat())
+                std::string ext = FileSystem::getExtension(imagePath);
+                HeightField* heightfield = NULL;
+                if (ext == ".PNG")
+                    heightfield = HeightField::createFromImage(imagePath, minHeight, maxHeight);
+                else if (ext == ".RAW")
+                    heightfield = HeightField::createFromRAW(imagePath, (unsigned int)width, (unsigned int)height, minHeight, maxHeight);
+
+                if (heightfield)
                 {
-                    case Image::RGB:
-                    case Image::RGBA:
-                        break;
-                    default:
-                        GP_ERROR("Heightmap: pixel format is not supported: %d.", image->getFormat());
-                        SAFE_RELEASE(image);
-                        SAFE_DELETE(shape);
-                        return NULL;
+                    shape = PhysicsCollisionShape::heightfield(heightfield);
+                    SAFE_RELEASE(heightfield);
                 }
-
-                *shape = PhysicsCollisionShape::heightfield(image);
-                SAFE_RELEASE(image);
             }
-            break;
-        default:
-            GP_ERROR("Unsupported physics collision shape type (%d).", type);
-            SAFE_DELETE(shape);
-            return NULL;
-    }
+        }
+        break;
 
-    SAFE_DELETE(extents);
-    SAFE_DELETE(center);
+    default:
+        GP_ERROR("Unsupported physics collision shape type (%d).", type);
+        break;
+    }
 
     return shape;
 }
@@ -396,14 +413,24 @@ PhysicsCollisionShape::Definition PhysicsCollisionShape::capsule(float radius, f
     return d;
 }
 
-PhysicsCollisionShape::Definition PhysicsCollisionShape::heightfield(Image* image)
+PhysicsCollisionShape::Definition PhysicsCollisionShape::heightfield()
 {
-    GP_ASSERT(image);
-    image->addRef();
+    Definition d;
+    d.type = SHAPE_HEIGHTFIELD;
+    d.isExplicit = false;
+    d.centerAbsolute = false;
+    return d;
+}
+
+PhysicsCollisionShape::Definition PhysicsCollisionShape::heightfield(HeightField* heightfield)
+{
+    GP_ASSERT(heightfield);
+
+    heightfield->addRef();
 
     Definition d;
     d.type = SHAPE_HEIGHTFIELD;
-    d.data.heightfield = image;
+    d.data.heightfield = heightfield;
     d.isExplicit = true;
     d.centerAbsolute = false;
     return d;

+ 39 - 11
gameplay/src/PhysicsCollisionShape.h

@@ -2,8 +2,8 @@
 #define PHYSICSCOLLISIONSHAPE_H_
 
 #include "Vector3.h"
-#include "Image.h"
 #include "Mesh.h"
+#include "HeightField.h"
 
 namespace gameplay
 {
@@ -25,6 +25,7 @@ public:
      */
     enum Type
     {
+        SHAPE_NONE,
         SHAPE_BOX,
         SHAPE_SPHERE,
         SHAPE_CAPSULE,
@@ -73,6 +74,11 @@ public:
          */
         ~Definition();
 
+        /**
+         * Determines if this is an empty/undefined collision shape definition.
+         */
+        bool isEmpty() const;
+
     private:
 
         /**
@@ -82,7 +88,7 @@ public:
          * @param properties The properties object to create the PhysicsCollisionShape::Definition object from.
          * @return A PhysicsCollisionShape::Definition object.
          */
-        static Definition* create(Node* node, Properties* properties);
+        static Definition create(Node* node, Properties* properties);
 
         // Shape type.
         PhysicsCollisionShape::Type type;
@@ -101,7 +107,7 @@ public:
             /** @script{ignore} */
             CapsuleData capsule;
             /** @script{ignore} */
-            Image* heightfield;
+            HeightField* heightfield;
             /** @script{ignore} */
             Mesh* mesh;
         } data;
@@ -193,11 +199,34 @@ public:
     static PhysicsCollisionShape::Definition capsule(float radius, float height, const Vector3& center = Vector3::zero(), bool absolute = false);
 
     /**
-     * Defines a heightfield shape using the specified heightfield image.
+     * Defines a heightfield shape, using the height data of a terrain on the node that is attached to.
+     *
+     * This method only results in a valid heightfield collision object when the shape is used
+     * to create a collision object on a node that has a Terrain attached to it. If there is no
+     * Terrain attached to the node, the collision object creation will fail.
+     *
+     * @return Definition of a heightfield shape.
+     */
+    static PhysicsCollisionShape::Definition heightfield();
+
+    /**
+     * Defines a heightfield shape using the specified array of height values.
+     *
+     * The dimensions of the heightfield will be (width, maxHeight, height), where width and
+     * height are the dimensions of the passed in height array and maxHeight is the maximum
+     * height value in the height array.
+     *
+     * Heightfield rigid bodies are always assumed be Y-up (height value on the Y axis) and 
+     * be centered around the X and Z axes.
+     *
+     * The heightfield can be scaled once a PhysicsRigidBody has been created for it, using the
+     * PhysicsRigidBody::setLocalScaling method.
+     *
+     * @param heightfield HeightField object containing the array of height values representing the heightfield.
      *
      * @return Definition of a heightfield shape.
      */
-    static PhysicsCollisionShape::Definition heightfield(Image* image);
+    static PhysicsCollisionShape::Definition heightfield(HeightField* heightfield);
 
     /**
      * Defines a mesh shape using the specified mesh.
@@ -216,12 +245,11 @@ private:
 
     struct HeightfieldData
     {
-        float* heightData;
-        Vector3* normalData;
-        unsigned int width;
-        unsigned int height;
-        mutable Matrix inverse;
-        mutable bool inverseIsDirty;
+        HeightField* heightfield;
+        bool inverseIsDirty;
+        Matrix inverse;
+        float minHeight;
+        float maxHeight;
     };
 
     /**

+ 80 - 226
gameplay/src/PhysicsController.cpp

@@ -5,6 +5,7 @@
 #include "Game.h"
 #include "MeshPart.h"
 #include "Bundle.h"
+#include "Terrain.h"
 
 #ifdef GAMEPLAY_MEM_LEAK_DETECTION
 #undef new
@@ -865,8 +866,19 @@ PhysicsCollisionShape* PhysicsController::createShape(Node* node, const PhysicsC
 
     case PhysicsCollisionShape::SHAPE_HEIGHTFIELD:
         {
-            // Build heightfield rigid body from the passed in shape.
-            collisionShape = createHeightfield(node, shape.data.heightfield, centerOfMassOffset);
+            if (shape.isExplicit)
+            {
+                // Build heightfield rigid body from the passed in shape.
+                collisionShape = createHeightfield(node, shape.data.heightfield, centerOfMassOffset);
+            }
+            else
+            {
+                // Build the heightfield from an attached terrain's height array
+                if (node->getTerrain() == NULL)
+                    GP_ERROR("Empty heightfield collision shapes can only be used on nodes that have an attached Terrain.");
+                else
+                    collisionShape = createHeightfield(node, node->getTerrain()->_heightfield, centerOfMassOffset);
+            }
         }
         break;
 
@@ -876,6 +888,7 @@ PhysicsCollisionShape* PhysicsController::createShape(Node* node, const PhysicsC
             collisionShape = createMesh(shape.data.mesh, scale);
         }
         break;
+
     default:
         GP_ERROR("Unsupported collision shape type (%d).", shape.type);
         break;
@@ -983,147 +996,53 @@ PhysicsCollisionShape* PhysicsController::createCapsule(float radius, float heig
     return shape;
 }
 
-PhysicsCollisionShape* PhysicsController::createHeightfield(Node* node, Image* image, Vector3* centerOfMassOffset)
+PhysicsCollisionShape* PhysicsController::createHeightfield(Node* node, HeightField* heightfield, Vector3* centerOfMassOffset)
 {
     GP_ASSERT(node);
-    GP_ASSERT(image);
+    GP_ASSERT(heightfield);
     GP_ASSERT(centerOfMassOffset);
 
-    // Get the dimensions of the heightfield.
-    // If the node has a mesh defined, use the dimensions of the bounding box for the mesh.
-    // Otherwise simply use the image dimensions (with a max height of 255).
-    float width, length, minHeight, maxHeight;
-    if (node->getModel() && node->getModel()->getMesh())
+    // Inspect the height array for the min and max values
+    float* heights = heightfield->getArray();
+    float minHeight = FLT_MAX, maxHeight = -FLT_MAX;
+    for (unsigned int i = 0, count = heightfield->getColumnCount()*heightfield->getRowCount(); i < count; ++i)
     {
-        const BoundingBox& box = node->getModel()->getMesh()->getBoundingBox();
-        width = box.max.x - box.min.x;
-        length = box.max.z - box.min.z;
-        minHeight = box.min.y;
-        maxHeight = box.max.y;
-    }
-    else
-    {
-        width = image->getWidth();
-        length = image->getHeight();
-        minHeight = 0.0f;
-        maxHeight = 255.0f;
+        float h = heights[i];
+        if (h < minHeight)
+            minHeight = h;
+        if (h > maxHeight)
+            maxHeight = h;
     }
 
-    // Get the size in bytes of a pixel (we ensure that the image's
-    // pixel format is actually supported before calling this constructor).
-    unsigned int pixelSize = 0;
-    switch (image->getFormat())
-    {
-        case Image::RGB:
-            pixelSize = 3;
-            break;
-        case Image::RGBA:
-            pixelSize = 4;
-            break;
-        default:
-            GP_ERROR("Unsupported pixel format for heightmap image (%d).", image->getFormat());
-            return NULL;
-    }
+    // Compute initial heightfield scale by pulling the current world scale out of the node
+    Vector3 scale;
+    node->getWorldMatrix().getScale(&scale);
 
-    // Calculate the heights for each pixel.
-    float* heights = new float[image->getWidth() * image->getHeight()];
-    unsigned char* data = image->getData();
-    for (unsigned int x = 0, w = image->getWidth(); x < w; ++x)
+    // If the node has a terrain, apply the terrain's local scale to the world scale
+    if (node->getTerrain())
     {
-        for (unsigned int y = 0, h = image->getHeight(); y < h; ++y)
-        {
-            //
-            // Originally in GamePlay this was normalizedHeightGrayscale which generally yielded
-            // only 8-bit precision. This has been replaced by normalizedHeightPacked (with a
-            // corresponding change in gameplay-encoder).
-            //
-            // BACKWARD COMPATIBILITY
-            // In grayscale images where r=g=b this will maintain some degree of compatibility,
-            // to within 0.4%. This can be seen by setting r=g=b=x and comparing the grayscale
-            // height expression to the packed height expression: the error is 2^-8 + 2^-16
-            // which is just under 0.4%.
-            //
-            heights[x + y * w] = normalizedHeightPacked(
-                data[(x + y * w) * pixelSize + 0],
-                data[(x + y * w) * pixelSize + 1],
-                data[(x + y * w) * pixelSize + 2]) * (maxHeight - minHeight) + minHeight;
-        }
+        Vector3& tScale = node->getTerrain()->_localScale;
+        scale.set(scale.x * tScale.x, scale.y * tScale.y, scale.z * tScale.z);
     }
 
+    // Compute initial center of mass offset necessary to move the height from its position in bullet
+    // physics (always centered around origin) to its intended location.
+    centerOfMassOffset->set(0, -(minHeight + (maxHeight-minHeight)*0.5f) * scale.y, 0);
+
+    // Create our heightfield data to be stored in the collision shape
     PhysicsCollisionShape::HeightfieldData* heightfieldData = new PhysicsCollisionShape::HeightfieldData();
-    heightfieldData->heightData = NULL;
-    heightfieldData->normalData = NULL;
+    heightfieldData->heightfield = heightfield;
+    heightfieldData->heightfield->addRef();
     heightfieldData->inverseIsDirty = true;
+    heightfieldData->minHeight = minHeight;
+    heightfieldData->maxHeight = maxHeight;
 
-    unsigned int sizeWidth = width;
-    unsigned int sizeHeight = length;
-    GP_ASSERT(sizeWidth);
-    GP_ASSERT(sizeHeight);
-    
-    // Generate the heightmap data needed for physics (one height per world unit).
-    heightfieldData->width = sizeWidth + 1;
-    heightfieldData->height = sizeHeight + 1;
-    heightfieldData->heightData = new float[heightfieldData->width * heightfieldData->height];
-    heightfieldData->normalData = new Vector3[heightfieldData->width * heightfieldData->height];
-    unsigned int heightIndex = 0, prevRowIndex = 0, prevColIndex = 0;
-    float widthImageFactor = (float)(image->getWidth() - 1) / sizeWidth;
-    float heightImageFactor = (float)(image->getHeight() - 1) / sizeHeight;
-    float x = 0.0f;
-    float z = 0.0f;
-    const float horizStepsize = 1.0f;
-    for (unsigned int row = 0, z = 0.0f; row <= sizeHeight; row++, z += horizStepsize)
-    {
-        for (unsigned int col = 0, x = 0.0f; col <= sizeWidth; col++, x += horizStepsize)
-        {
-            heightIndex = row * heightfieldData->width + col;
-            prevRowIndex = heightIndex - heightfieldData->width; // ignored if row<1
-            prevColIndex = heightIndex - 1; // ignored if col<1
-
-            heightfieldData->heightData[heightIndex] = calculateHeight(heights, image->getWidth(), image->getHeight(), x * widthImageFactor, (sizeHeight - z) * heightImageFactor);
-
-            //
-            // Normal calculation based on height data using a backward difference.
-            //
-            if (row == 0 || col == 0)
-            {
-                // This is just a safe default value.
-                heightfieldData->normalData[heightIndex].set(Vector3::unitY());
-            }
-            else
-            {
-                heightfieldData->normalData[heightIndex].set(
-                    heightfieldData->heightData[prevColIndex] - heightfieldData->heightData[heightIndex],
-                    horizStepsize,
-                    heightfieldData->heightData[prevRowIndex] - heightfieldData->heightData[heightIndex]);
-                heightfieldData->normalData[heightIndex].normalize();
-            }
-
-            // For the starting row, just copy from the second row (i.e., a forward difference).
-            if (row == 1)
-            {
-                heightfieldData->normalData[prevRowIndex].set(heightfieldData->normalData[heightIndex]);
-            }
-
-            // For the starting column, just copy from the second column (i.e., a forward difference).
-            // (We don't care which of the 2 valid sources heightfieldData->normalData[0] ultimately comes from).
-            if (col == 1)
-            {
-                heightfieldData->normalData[prevColIndex].set(heightfieldData->normalData[heightIndex]);
-            }
-        }
-    }
-    SAFE_DELETE_ARRAY(heights);
-
-    // Offset the heightmap's center of mass according to the way that Bullet calculates the origin 
-    // of its heightfield collision shape; see documentation for the btHeightfieldTerrainShape for more info.
-    Vector3 s;
-    node->getWorldMatrix().getScale(&s);
-    GP_ASSERT(s.y);
-    centerOfMassOffset->set(0.0f, -(maxHeight - (0.5f * (maxHeight - minHeight))) / s.y, 0.0f);
-
-    // Create the bullet terrain shape.
+    // Create the bullet terrain shape
     btHeightfieldTerrainShape* terrainShape = bullet_new<btHeightfieldTerrainShape>(
-        heightfieldData->width, heightfieldData->height, heightfieldData->heightData, 1.0f, minHeight, maxHeight, 1, PHY_FLOAT, false);
+        heightfield->getColumnCount(), heightfield->getRowCount(), heightfield->getArray(), 1.0f, minHeight, maxHeight, 1, PHY_FLOAT, false);
+
+    // Set initial bullet local scaling for the heightfield
+    terrainShape->setLocalScaling(BV(scale));
 
     // Create our collision shape object and store heightfieldData in it.
     PhysicsCollisionShape* shape = new PhysicsCollisionShape(PhysicsCollisionShape::SHAPE_HEIGHTFIELD, terrainShape);
@@ -1308,78 +1227,6 @@ void PhysicsController::destroyShape(PhysicsCollisionShape* shape)
     }
 }
 
-float PhysicsController::calculateHeight(float* data, unsigned int width, unsigned int height, float x, float y,
-    const Matrix* worldMatrix, Vector3* normalData, Vector3* normalResult)
-{
-    GP_ASSERT(data);
-
-    unsigned int x1 = x;
-    unsigned int y1 = y;
-    unsigned int x2 = x1 + 1;
-    unsigned int y2 = y1 + 1;
-    float tmp;
-    float xFactor = modf(x, &tmp);
-    float yFactor = modf(y, &tmp);
-    float xFactorI = 1.0f - xFactor;
-    float yFactorI = 1.0f - yFactor;
-
-    if (x2 >= width && y2 >= height)
-    {
-        if (normalResult)
-        {
-            normalResult->set(normalData[x1 + y1 * width]);
-            worldMatrix->transformVector(normalResult);
-        }
-        return data[x1 + y1 * width];
-    }
-    else if (x2 >= width)
-    {
-        if (normalResult)
-        {
-            normalResult->set(normalData[x1 + y1 * width] * yFactorI + normalData[x1 + y2 * width] * yFactor);
-            normalResult->normalize();
-            worldMatrix->transformVector(normalResult);
-        }
-        return data[x1 + y1 * width] * yFactorI + data[x1 + y2 * width] * yFactor;
-    }
-    else if (y2 >= height)
-    {
-        if (normalResult)
-        {
-            normalResult->set(normalData[x1 + y1 * width] * xFactorI + normalData[x2 + y1 * width] * xFactor);
-            normalResult->normalize();
-            worldMatrix->transformVector(normalResult);
-        }
-        return data[x1 + y1 * width] * xFactorI + data[x2 + y1 * width] * xFactor;
-    }
-    else
-    {
-        float a = xFactorI * yFactorI;
-        float b = xFactorI * yFactor;
-        float c = xFactor * yFactor;
-        float d = xFactor * yFactorI;
-        if (normalResult)
-        {
-            normalResult->set(normalData[x1 + y1 * width] * a + normalData[x1 + y2 * width] * b +
-                normalData[x2 + y2 * width] * c + normalData[x2 + y1 * width] * d);
-            normalResult->normalize();
-            worldMatrix->transformVector(normalResult);
-        }
-        return data[x1 + y1 * width] * a + data[x1 + y2 * width] * b +
-            data[x2 + y2 * width] * c + data[x2 + y1 * width] * d;
-    }
-}
-
-float PhysicsController::normalizedHeightGrayscale(float r, float g, float b)
-{
-    return (r + g + b) / 768.0f;
-}
-
-float PhysicsController::normalizedHeightPacked(float r, float g, float b)
-{
-    return (256.0f*r + g + 0.00390625f*b) / 65536.0f;
-}
-
 void PhysicsController::addConstraint(PhysicsRigidBody* a, PhysicsRigidBody* b, PhysicsConstraint* constraint)
 {
     GP_ASSERT(a);
@@ -1435,7 +1282,7 @@ void PhysicsController::removeConstraint(PhysicsConstraint* constraint)
 
 PhysicsController::DebugDrawer::DebugDrawer()
     : _mode(btIDebugDraw::DBG_DrawAabb | btIDebugDraw::DBG_DrawConstraintLimits | btIDebugDraw::DBG_DrawConstraints | 
-       btIDebugDraw::DBG_DrawContactPoints | btIDebugDraw::DBG_DrawWireframe), _viewProjection(NULL), _meshBatch(NULL)
+       btIDebugDraw::DBG_DrawContactPoints | btIDebugDraw::DBG_DrawWireframe), _meshBatch(NULL), _lineCount(0)
 {
     // Vertex shader for drawing colored lines.
     const char* vs_str = 
@@ -1466,14 +1313,14 @@ PhysicsController::DebugDrawer::DebugDrawer()
     Material* material = Material::create(effect);
     GP_ASSERT(material && material->getStateBlock());
     material->getStateBlock()->setDepthTest(true);
+    material->getStateBlock()->setDepthFunction(RenderState::DEPTH_LEQUAL);
 
     VertexFormat::Element elements[] =
     {
         VertexFormat::Element(VertexFormat::POSITION, 3),
         VertexFormat::Element(VertexFormat::COLOR, 4),
     };
-    _meshBatch = MeshBatch::create(VertexFormat(elements, 2), Mesh::LINES, material, false);
-
+    _meshBatch = MeshBatch::create(VertexFormat(elements, 2), Mesh::LINES, material, false, 4096, 4096);
     SAFE_RELEASE(material);
     SAFE_RELEASE(effect);
 }
@@ -1486,42 +1333,49 @@ PhysicsController::DebugDrawer::~DebugDrawer()
 void PhysicsController::DebugDrawer::begin(const Matrix& viewProjection)
 {
     GP_ASSERT(_meshBatch);
-    _viewProjection = &viewProjection;
     _meshBatch->start();
+    _meshBatch->getMaterial()->getParameter("u_viewProjectionMatrix")->setValue(viewProjection);
 }
 
 void PhysicsController::DebugDrawer::end()
 {
-    GP_ASSERT(_meshBatch && _meshBatch->getMaterial() && _meshBatch->getMaterial()->getParameter("u_viewProjectionMatrix"));
+    GP_ASSERT(_meshBatch && _meshBatch->getMaterial());
     _meshBatch->finish();
-    _meshBatch->getMaterial()->getParameter("u_viewProjectionMatrix")->setValue(_viewProjection);
     _meshBatch->draw();
+    _lineCount = 0;
 }
 
 void PhysicsController::DebugDrawer::drawLine(const btVector3& from, const btVector3& to, const btVector3& fromColor, const btVector3& toColor)
 {
     GP_ASSERT(_meshBatch);
 
-    static DebugDrawer::DebugVertex fromVertex, toVertex;
-
-    fromVertex.x = from.getX();
-    fromVertex.y = from.getY();
-    fromVertex.z = from.getZ();
-    fromVertex.r = fromColor.getX();
-    fromVertex.g = fromColor.getY();
-    fromVertex.b = fromColor.getZ();
-    fromVertex.a = 1.0f;
-
-    toVertex.x = to.getX();
-    toVertex.y = to.getY();
-    toVertex.z = to.getZ();
-    toVertex.r = toColor.getX();
-    toVertex.g = toColor.getY();
-    toVertex.b = toColor.getZ();
-    toVertex.a = 1.0f;
-
-    _meshBatch->add(&fromVertex, 1);
-    _meshBatch->add(&toVertex, 1);
+    static DebugDrawer::DebugVertex vertices[2];
+
+    vertices[0].x = from.getX();
+    vertices[0].y = from.getY();
+    vertices[0].z = from.getZ();
+    vertices[0].r = fromColor.getX();
+    vertices[0].g = fromColor.getY();
+    vertices[0].b = fromColor.getZ();
+    vertices[0].a = 1.0f;
+
+    vertices[1].x = to.getX();
+    vertices[1].y = to.getY();
+    vertices[1].z = to.getZ();
+    vertices[1].r = toColor.getX();
+    vertices[1].g = toColor.getY();
+    vertices[1].b = toColor.getZ();
+    vertices[1].a = 1.0f;
+
+    _meshBatch->add(vertices, 2);
+
+    ++_lineCount;
+    if (_lineCount >= 4096)
+    {
+        // Flush the batch when it gets full (don't want to to grow infinitely)
+        end();
+        _meshBatch->start();
+    }
 }
 
 void PhysicsController::DebugDrawer::drawLine(const btVector3& from, const btVector3& to, const btVector3& color)

+ 3 - 7
gameplay/src/PhysicsController.h

@@ -9,6 +9,7 @@
 #include "PhysicsSpringConstraint.h"
 #include "PhysicsCollisionObject.h"
 #include "MeshBatch.h"
+#include "HeightField.h"
 #include "ScriptTarget.h"
 
 namespace gameplay
@@ -430,7 +431,7 @@ private:
     PhysicsCollisionShape* createCapsule(float radius, float height, const Vector3& scale);
 
     // Creates a heightfield collision shape.
-    PhysicsCollisionShape* createHeightfield(Node* node, Image* image, Vector3* centerOfMassOffset);
+    PhysicsCollisionShape* createHeightfield(Node* node, HeightField* heightfield, Vector3* centerOfMassOffset);
 
     // Creates a triangle mesh collision shape.
     PhysicsCollisionShape* createMesh(Mesh* mesh, const Vector3& scale);
@@ -438,11 +439,6 @@ private:
     // Destroys a collision shape created through PhysicsController
     void destroyShape(PhysicsCollisionShape* shape);
 
-    // Helper function for calculating heights from heightmap (image) or heightfield data.
-    // The worldMatrix and normalData arguments are ignored if normalResult is NULL.
-    static float calculateHeight(float* data, unsigned int width, unsigned int height, float x, float y,
-        const Matrix* worldMatrix = NULL, Vector3* normalData = NULL, Vector3* normalResult = NULL);
-
     // Legacy method for grayscale heightmaps: r + g + b, normalized.
     static float normalizedHeightGrayscale(float r, float g, float b);
 
@@ -533,8 +529,8 @@ private:
     private:
         
         int _mode;
-        const Matrix* _viewProjection;
         MeshBatch* _meshBatch;
+        int _lineCount;
     };
 
     bool _isUpdating;

+ 4 - 5
gameplay/src/PhysicsGhostObject.cpp

@@ -23,7 +23,7 @@ PhysicsGhostObject::PhysicsGhostObject(Node* node, const PhysicsCollisionShape::
     _ghostObject->setCollisionFlags(_ghostObject->getCollisionFlags() | btCollisionObject::CF_NO_CONTACT_RESPONSE);
 
     // Initialize a physics motion state object for syncing the transform.
-    _motionState = new PhysicsMotionState(_node, &centerOfMassOffset);
+    _motionState = new PhysicsMotionState(_node, this, &centerOfMassOffset);
     _motionState->getWorldTransform(_ghostObject->getWorldTransform());
 
     // Add the ghost object to the physics world.
@@ -67,16 +67,15 @@ PhysicsGhostObject* PhysicsGhostObject::create(Node* node, Properties* propertie
     }
 
     // Load the physics collision shape definition.
-    PhysicsCollisionShape::Definition* shape = PhysicsCollisionShape::Definition::create(node, properties);
-    if (shape == NULL)
+    PhysicsCollisionShape::Definition shape = PhysicsCollisionShape::Definition::create(node, properties);
+    if (shape.isEmpty())
     {
         GP_ERROR("Failed to create collision shape during ghost object creation.");
         return NULL;
     }
 
     // Create the ghost object.
-    PhysicsGhostObject* ghost = new PhysicsGhostObject(node, *shape);
-    SAFE_DELETE(shape);
+    PhysicsGhostObject* ghost = new PhysicsGhostObject(node, shape);
 
     return ghost;
 }

+ 57 - 26
gameplay/src/PhysicsRigidBody.cpp

@@ -5,6 +5,7 @@
 #include "Image.h"
 #include "MeshPart.h"
 #include "Node.h"
+#include "Terrain.h"
 
 namespace gameplay
 {
@@ -21,7 +22,7 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, const PhysicsCollisionShape::Defi
     GP_ASSERT(_collisionShape && _collisionShape->getShape());
 
     // Create motion state object.
-    _motionState = new PhysicsMotionState(node, (centerOfMassOffset.lengthSquared() > MATH_EPSILON) ? &centerOfMassOffset : NULL);
+    _motionState = new PhysicsMotionState(node, this, (centerOfMassOffset.lengthSquared() > MATH_EPSILON) ? &centerOfMassOffset : NULL);
 
     // If the mass is non-zero, then the object is dynamic so we calculate the local 
     // inertia. However, if the collision shape is a triangle mesh, we don't calculate 
@@ -31,7 +32,7 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, const PhysicsCollisionShape::Defi
         _collisionShape->getShape()->calculateLocalInertia(parameters.mass, localInertia);
 
     // Create the Bullet physics rigid body object.
-    btRigidBody::btRigidBodyConstructionInfo rbInfo(parameters.mass, _motionState, _collisionShape->getShape(), localInertia);
+    btRigidBody::btRigidBodyConstructionInfo rbInfo(parameters.mass, NULL, _collisionShape->getShape(), localInertia);
     rbInfo.m_friction = parameters.friction;
     rbInfo.m_restitution = parameters.restitution;
     rbInfo.m_linearDamping = parameters.linearDamping;
@@ -40,6 +41,10 @@ PhysicsRigidBody::PhysicsRigidBody(Node* node, const PhysicsCollisionShape::Defi
     // Create + assign the new bullet rigid body object.
     _body = bullet_new<btRigidBody>(rbInfo);
 
+    // Set motion state after rigid body assignment, since bullet will callback on the motion state interface to query
+    // the initial transform and it will need to access to rigid body (_body).
+    _body->setMotionState(_motionState);
+
     // Set other initially defined properties.
     setKinematic(parameters.kinematic);
     setAnisotropicFriction(parameters.anisotropicFriction);
@@ -176,8 +181,8 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties, c
     }
 
     // Load the physics collision shape definition.
-    PhysicsCollisionShape::Definition* shape = PhysicsCollisionShape::Definition::create(node, properties);
-    if (shape == NULL)
+    PhysicsCollisionShape::Definition shape = PhysicsCollisionShape::Definition::create(node, properties);
+    if (shape.isEmpty())
     {
         GP_ERROR("Failed to create collision shape during rigid body creation.");
         return NULL;
@@ -240,8 +245,7 @@ PhysicsRigidBody* PhysicsRigidBody::create(Node* node, Properties* properties, c
     }
 
     // Create the rigid body.
-    PhysicsRigidBody* body = new PhysicsRigidBody(node, *shape, parameters);
-    SAFE_DELETE(shape);
+    PhysicsRigidBody* body = new PhysicsRigidBody(node, shape, parameters);
 
     if (gravity)
     {
@@ -275,46 +279,53 @@ void PhysicsRigidBody::setEnabled(bool enable)
         _body->setMotionState(_motionState);
 }
 
-float PhysicsRigidBody::getHeight(float x, float y, Vector3* normal) const
+float PhysicsRigidBody::getHeight(float x, float z) const
 {
     GP_ASSERT(_collisionShape);
+    GP_ASSERT(_node);
+
+    // If our node has a terrain, call getHeight() on it since we need to factor in local
+    // scaling on the terrain into the height calculation.
+    if (_node->getTerrain())
+        return _node->getTerrain()->getHeight(x, z);
 
     // This function is only supported for heightfield rigid bodies.
     if (_collisionShape->getType() != PhysicsCollisionShape::SHAPE_HEIGHTFIELD)
     {
-        GP_ERROR("Attempting to get the height of a non-heightfield rigid body.");
+        GP_WARN("Attempting to get the height of a non-heightfield rigid body.");
         return 0.0f;
     }
 
     GP_ASSERT(_collisionShape->_shapeData.heightfieldData);
-    GP_ASSERT(_node);
 
-    // Calculate the correct x, y position relative to the heightfield data.
+    // Ensure inverse matrix is updated so we can transform from world back into local heightfield coordinates for indexing
     if (_collisionShape->_shapeData.heightfieldData->inverseIsDirty)
     {
-        _node->getWorldMatrix().invert(&_collisionShape->_shapeData.heightfieldData->inverse);
         _collisionShape->_shapeData.heightfieldData->inverseIsDirty = false;
+
+        _node->getWorldMatrix().invert(&_collisionShape->_shapeData.heightfieldData->inverse);
     }
 
-    float w = _collisionShape->_shapeData.heightfieldData->width;
-    float h = _collisionShape->_shapeData.heightfieldData->height;
+    // Calculate the correct x, z position relative to the heightfield data.
+    float cols = _collisionShape->_shapeData.heightfieldData->heightfield->getColumnCount();
+    float rows = _collisionShape->_shapeData.heightfieldData->heightfield->getRowCount();
 
-    GP_ASSERT(w - 1);
-    GP_ASSERT(h - 1);
+    GP_ASSERT(cols > 0);
+    GP_ASSERT(rows > 0);
 
-    Vector3 v = _collisionShape->_shapeData.heightfieldData->inverse * Vector3(x, 0.0f, y);
-    x = (v.x + (0.5f * (w - 1))) * w / (w - 1);
-    y = (v.z + (0.5f * (h - 1))) * h / (h - 1);
+    Vector3 v = _collisionShape->_shapeData.heightfieldData->inverse * Vector3(x, 0.0f, z);
+    x = v.x + (cols - 1) * 0.5f;
+    z = v.z + (rows - 1) * 0.5f;
 
-    // Check that the x, y position is within the bounds.
-    if (x < 0.0f || x > w || y < 0.0f || y > h)
-    {
-        GP_ERROR("Attempting to get height at point '%f, %f', which is outside the range of the heightfield with width %d and height %d.", x, y, w, h);
-        return 0.0f;
-    }
+    // Get the unscaled height value from the HeightField
+    float height = _collisionShape->_shapeData.heightfieldData->heightfield->getHeight(x, z);
+
+    // Apply scale back to height
+    Vector3 worldScale;
+    _node->getWorldMatrix().getScale(&worldScale);
+    height *= worldScale.y;
 
-    return PhysicsController::calculateHeight(_collisionShape->_shapeData.heightfieldData->heightData, w, h, x, y,
-        &_node->getWorldMatrix(), _collisionShape->_shapeData.heightfieldData->normalData, normal);
+    return height;
 }
 
 void PhysicsRigidBody::addConstraint(PhysicsConstraint* constraint)
@@ -354,7 +365,27 @@ void PhysicsRigidBody::transformChanged(Transform* transform, long cookie)
     if (getShapeType() == PhysicsCollisionShape::SHAPE_HEIGHTFIELD)
     {
         GP_ASSERT(_collisionShape && _collisionShape->_shapeData.heightfieldData);
+
+        // Dirty the heightfield's inverse matrix (used to compute height values from world-space coordinates)
         _collisionShape->_shapeData.heightfieldData->inverseIsDirty = true;
+
+        // Update local scaling for the heightfield.
+        Vector3 scale;
+        _node->getWorldMatrix().getScale(&scale);
+
+        // If the node has a terrain attached, factor in the terrain local scaling as well for the collision shape
+        if (_node->getTerrain())
+        {
+            Vector3& tScale = _node->getTerrain()->_localScale;
+            scale.set(scale.x * tScale.x, scale.y * tScale.y, scale.z * tScale.z);
+        }
+
+        _collisionShape->_shape->setLocalScaling(BV(scale));
+
+        // Update center of mass offset
+        float minHeight = _collisionShape->_shapeData.heightfieldData->minHeight;
+        float maxHeight = _collisionShape->_shapeData.heightfieldData->maxHeight;
+        _motionState->setCenterOfMassOffset(Vector3(0, -(minHeight + (maxHeight-minHeight)*0.5f) * scale.y, 0));
     }
 }
 

+ 5 - 6
gameplay/src/PhysicsRigidBody.h

@@ -99,7 +99,7 @@ public:
         /**
          * Constructor.
          */
-        Parameters(float mass, float friction = 0.5f, float resititution = 0.0f,
+        Parameters(float mass, float friction = 0.5f, float restitution = 0.0f,
             float linearDamping = 0.0f, float angularDamping = 0.0f, bool kinematic = false,
             const Vector3& anisotropicFriction = Vector3::one(), const Vector3& linearFactor = Vector3::one(), 
             const Vector3& angularFactor = Vector3::one())
@@ -333,14 +333,13 @@ public:
     void setEnabled(bool enable);
 
     /**
-     * Gets the height and normal at the given point (only for rigid bodies of type HEIGHTFIELD).
+     * Gets the height at the given point (only for rigid bodies of type HEIGHTFIELD).
      * 
-     * @param x The x position.
-     * @param y The y position.
-     * @param normal If non-null, the surface normal at the given point.
+     * @param x The x position, in world space.
+     * @param z The z position, in world space.
      * @return The height at the given point, or zero if this is not a heightfield rigid body.
      */
-    float getHeight(float x, float y, Vector3* normal = NULL) const;
+    float getHeight(float x, float z) const;
 
     /**
      * Gets whether the rigid body is a static rigid body or not.

+ 89 - 1
gameplay/src/RenderState.cpp

@@ -4,6 +4,7 @@
 #include "Pass.h"
 #include "Technique.h"
 #include "Node.h"
+#include "Scene.h"
 
 // Render state override bits
 #define RS_BLEND 1
@@ -11,6 +12,7 @@
 #define RS_CULL_FACE 4
 #define RS_DEPTH_TEST 8
 #define RS_DEPTH_WRITE 16
+#define RS_DEPTH_FUNC 32
 
 namespace gameplay
 {
@@ -116,6 +118,15 @@ const char* autoBindingToString(RenderState::AutoBinding autoBinding)
     case RenderState::MATRIX_PALETTE:
         return "MATRIX_PALETTE";
 
+    case RenderState::SCENE_AMBIENT_COLOR:
+        return "SCENE_AMBIENT_COLOR";
+
+    case RenderState::SCENE_LIGHT_COLOR:
+        return "SCENE_LIGHT_COLOR";
+
+    case RenderState::SCENE_LIGHT_DIRECTION:
+        return "SCENE_LIGHT_DIRECTION";
+
     default:
         return "";
     }
@@ -261,6 +272,24 @@ void RenderState::applyAutoBinding(const char* uniformName, const char* autoBind
             param->bindValue(skin, &MeshSkin::getMatrixPalette, &MeshSkin::getMatrixPaletteSize);
         }
     }
+    else if (strcmp(autoBinding, "SCENE_AMBIENT_COLOR") == 0)
+    {
+        Scene* scene = _nodeBinding->getScene();
+        if (scene)
+            param->bindValue(scene, &Scene::getAmbientColor);
+    }
+    else if (strcmp(autoBinding, "SCENE_LIGHT_COLOR") == 0)
+    {
+        Scene* scene = _nodeBinding->getScene();
+        if (scene)
+            param->bindValue(scene, &Scene::getLightColor);
+    }
+    else if (strcmp(autoBinding, "SCENE_LIGHT_DIRECTION") == 0)
+    {
+        Scene* scene = _nodeBinding->getScene();
+        if (scene)
+            param->bindValue(scene, &Scene::getLightDirection);
+    }
     else
     {
         GP_WARN("Unsupported auto binding type (%d).", autoBinding);
@@ -354,7 +383,7 @@ void RenderState::cloneInto(RenderState* renderState, NodeCloneContext& context)
 }
 
 RenderState::StateBlock::StateBlock()
-    : _cullFaceEnabled(false), _depthTestEnabled(false), _depthWriteEnabled(false),
+    : _cullFaceEnabled(false), _depthTestEnabled(false), _depthWriteEnabled(false), _depthFunction(RenderState::DEPTH_LESS),
       _blendEnabled(false), _blendSrc(RenderState::BLEND_ONE), _blendDst(RenderState::BLEND_ZERO),
       _bits(0L)
 {
@@ -426,6 +455,11 @@ void RenderState::StateBlock::bindNoRestore()
         GL_ASSERT( glDepthMask(_depthWriteEnabled ? GL_TRUE : GL_FALSE) );
         _defaultState->_depthWriteEnabled = _depthWriteEnabled;
     }
+    if ((_bits & RS_DEPTH_FUNC) && (_depthFunction != _defaultState->_depthFunction))
+    {
+        GL_ASSERT( glDepthFunc((GLenum)_depthFunction) );
+        _defaultState->_depthFunction = _depthFunction;
+    }
 
     _defaultState->_bits |= _bits;
 }
@@ -472,6 +506,12 @@ void RenderState::StateBlock::restore(long stateOverrideBits)
         _defaultState->_bits &= ~RS_DEPTH_WRITE;
         _defaultState->_depthWriteEnabled = true;
     }
+    if (!(stateOverrideBits & RS_DEPTH_FUNC) && (_defaultState->_bits & RS_DEPTH_FUNC))
+    {
+        GL_ASSERT( glDepthFunc((GLenum)GL_LESS) );
+        _defaultState->_bits &= ~RS_DEPTH_FUNC;
+        _defaultState->_depthFunction = RenderState::DEPTH_LESS;
+    }
 }
 
 void RenderState::StateBlock::enableDepthWrite()
@@ -537,6 +577,36 @@ static RenderState::Blend parseBlend(const char* value)
     }
 }
 
+static RenderState::DepthFunction parseDepthFunc(const char* value)
+{
+    GP_ASSERT(value);
+
+    // Convert string to uppercase for comparison
+    std::string upper(value);
+    std::transform(upper.begin(), upper.end(), upper.begin(), (int(*)(int))toupper);
+    if (upper == "NEVER")
+        return RenderState::DEPTH_NEVER;
+    else if (upper == "LESS")
+        return RenderState::DEPTH_LESS;
+    else if (upper == "EQUAL")
+        return RenderState::DEPTH_EQUAL;
+    else if (upper == "LEQUAL")
+        return RenderState::DEPTH_LEQUAL;
+    else if (upper == "GREATER")
+        return RenderState::DEPTH_GREATER;
+    else if (upper == "NOTEQUAL")
+        return RenderState::DEPTH_NOTEQUAL;
+    else if (upper == "GEQUAL")
+        return RenderState::DEPTH_GEQUAL;
+    else if (upper == "ALWAYS")
+        return RenderState::DEPTH_ALWAYS;
+    else
+    {
+        GP_ERROR("Unsupported depth function value (%s). Will default to DEPTH_LESS if errors are treated as warnings)", value);
+        return RenderState::DEPTH_LESS;
+    }
+}
+
 void RenderState::StateBlock::setState(const char* name, const char* value)
 {
     GP_ASSERT(name);
@@ -565,6 +635,10 @@ void RenderState::StateBlock::setState(const char* name, const char* value)
     {
         setDepthWrite(parseBoolean(value));
     }
+    else if (strcmp(name, "depthFunc") == 0)
+    {
+        setDepthFunction(parseDepthFunc(value));
+    }
     else
     {
         GP_ERROR("Unsupported render state string '%s'.", name);
@@ -651,4 +725,18 @@ void RenderState::StateBlock::setDepthWrite(bool enabled)
     }
 }
 
+void RenderState::StateBlock::setDepthFunction(DepthFunction func)
+{
+    _depthFunction = func;
+    if (_depthFunction == DEPTH_LESS)
+    {
+        // Default depth function
+        _bits &= ~RS_DEPTH_FUNC;
+    }
+    else
+    {
+        _bits |= RS_DEPTH_FUNC;
+    }
+}
+
 }

+ 54 - 1
gameplay/src/RenderState.h

@@ -84,7 +84,26 @@ public:
         /**
          * Binds the matrix palette of MeshSkin attached to a node's model.
          */
-        MATRIX_PALETTE
+        MATRIX_PALETTE,
+
+        /**
+         * Binds the current scene's ambient color (Vector3).
+         */
+        SCENE_AMBIENT_COLOR,
+
+        /**
+         * Binds the current scene's light color (Vector3).
+         *
+         * This is typically used for the main directional light in a scene, such as the Sun.
+         */
+        SCENE_LIGHT_COLOR,
+
+        /**
+         * Binds the current scene's light direction (Vector3).
+         *
+         * This is typically used for the main directional light in a scene, such as the Sun.
+         */
+        SCENE_LIGHT_DIRECTION
     };
 
     /**
@@ -127,6 +146,27 @@ public:
         BLEND_SRC_ALPHA_SATURATE = GL_SRC_ALPHA_SATURATE
     };
 
+    /**
+     * Defines the supported depth compare functions.
+     *
+     * Depth compare functions specify the comparison that takes place between the
+     * incoming pixel's depth value and the depth value already in the depth buffer.
+     * If the compare function passes, the new pixel will be drawn.
+     *
+     * The intial depth compare function is DEPTH_LESS.
+     */
+    enum DepthFunction
+    {
+        DEPTH_NEVER = GL_NEVER,
+        DEPTH_LESS = GL_LESS,
+        DEPTH_EQUAL = GL_EQUAL,
+        DEPTH_LEQUAL = GL_LEQUAL,
+        DEPTH_GREATER = GL_GREATER,
+        DEPTH_NOTEQUAL = GL_NOTEQUAL,
+        DEPTH_GEQUAL = GL_GEQUAL,
+        DEPTH_ALWAYS = GL_ALWAYS
+    };
+
     /**
      * Defines a block of fixed-function render states that can be applied to a
      * RenderState object.
@@ -187,6 +227,8 @@ public:
         /**
          * Toggles depth testing.
          *
+         * By default, depth testing is disabled.
+         *
          * @param enabled true to enable, false to disable.
          */
         void setDepthTest(bool enabled);
@@ -198,6 +240,16 @@ public:
          */
         void setDepthWrite(bool enabled);
 
+        /**
+         * Sets the depth function to use when depth testing is enabled.
+         *
+         * When not explicitly set and when depth testing is enabled, the default
+         * depth function is DEPTH_LESS.
+         *
+         * @param func The depth function.
+         */
+        void setDepthFunction(DepthFunction func);
+
         /**
          * Sets a render state from the given name and value strings.
          *
@@ -237,6 +289,7 @@ public:
         bool _cullFaceEnabled;
         bool _depthTestEnabled;
         bool _depthWriteEnabled;
+        DepthFunction _depthFunction;
         bool _blendEnabled;
         Blend _blendSrc;
         Blend _blendDst;

+ 88 - 22
gameplay/src/Scene.cpp

@@ -4,12 +4,19 @@
 #include "SceneLoader.h"
 #include "MeshSkin.h"
 #include "Joint.h"
+#include "Terrain.h"
 
 namespace gameplay
 {
 
-Scene::Scene() : _activeCamera(NULL), _firstNode(NULL), _lastNode(NULL), _nodeCount(0), _bindAudioListenerToCamera(true), _debugBatch(NULL)
+// Global list of active scenes
+static std::vector<Scene*> __sceneList;
+
+Scene::Scene(const char* id)
+    : _id(id ? id : ""), _activeCamera(NULL), _firstNode(NULL), _lastNode(NULL), _nodeCount(0), 
+    _lightColor(1,1,1), _lightDirection(0,-1,0), _bindAudioListenerToCamera(true), _debugBatch(NULL)
 {
+    __sceneList.push_back(this);
 }
 
 Scene::~Scene()
@@ -29,11 +36,16 @@ Scene::~Scene()
     // Remove all nodes from the scene
     removeAllNodes();
     SAFE_DELETE(_debugBatch);
+
+    // Remove the scene from global list
+    std::vector<Scene*>::iterator itr = std::find(__sceneList.begin(), __sceneList.end(), this);
+    if (itr != __sceneList.end())
+        __sceneList.erase(itr);
 }
 
-Scene* Scene::create()
+Scene* Scene::create(const char* id)
 {
-    return new Scene();
+    return new Scene(id);
 }
 
 Scene* Scene::load(const char* filePath)
@@ -41,6 +53,20 @@ Scene* Scene::load(const char* filePath)
     return SceneLoader::load(filePath);
 }
 
+Scene* Scene::getScene(const char* id)
+{
+    if (id == NULL)
+        return __sceneList.size() ? __sceneList[0] : NULL;
+
+    for (size_t i = 0, count = __sceneList.size(); i < count; ++i)
+    {
+        if (__sceneList[i]->_id == id)
+            return __sceneList[i];
+    }
+
+    return NULL;
+}
+
 const char* Scene::getId() const
 {
     return _id.c_str();
@@ -48,10 +74,7 @@ const char* Scene::getId() const
 
 void Scene::setId(const char* id)
 {
-    if (id)
-    {
-        _id = id;
-    }
+    _id = id ? id : "";
 }
 
 Node* Scene::findNode(const char* id, bool recursive, bool exactMatch) const
@@ -278,6 +301,26 @@ void Scene::setAmbientColor(float red, float green, float blue)
     _ambientColor.set(red, green, blue);
 }
 
+const Vector3& Scene::getLightColor() const
+{
+    return _lightColor;
+}
+
+void Scene::setLightColor(float red, float green, float blue)
+{
+    _lightColor.set(red, green, blue);
+}
+
+const Vector3& Scene::getLightDirection() const
+{
+    return _lightDirection;
+}
+
+void Scene::setLightDirection(const Vector3& direction)
+{
+    _lightDirection = direction;
+}
+
 static Material* createDebugMaterial()
 {
     // Vertex shader for drawing colored lines.
@@ -387,6 +430,9 @@ static void drawDebugLine(MeshBatch* batch, const Vector3& point1, const Vector3
 
 static void drawDebugBox(MeshBatch* batch, const BoundingBox& box, const Matrix& matrix)
 {
+    if (box.isEmpty())
+        return;
+
     // Transform box into world space (since we only store local boxes on mesh)
     BoundingBox worldSpaceBox(box);
     worldSpaceBox.transform(matrix);
@@ -412,6 +458,9 @@ static void drawDebugBox(MeshBatch* batch, const BoundingBox& box, const Matrix&
 
 static void drawDebugSphere(MeshBatch* batch, const BoundingSphere& sphere)
 {
+    if (sphere.isEmpty())
+        return;
+
     // Draw three rings for the sphere (one for the x, y and z axes)
     Vector3 pos1, pos2;
     float step = MATH_PI * 0.2f;
@@ -457,37 +506,54 @@ static void drawDebugSphere(MeshBatch* batch, const BoundingSphere& sphere)
     }
 }
 
-static void drawDebugNode(MeshBatch* batch, Node* node, unsigned int debugFlags)
+static void drawDebugNode(Scene* scene, MeshBatch* batch, Node* node, unsigned int debugFlags)
 {
     GP_ASSERT(node);
-    Model* model = node->getModel();
 
-    if ((debugFlags & Scene::DEBUG_BOXES) && model)
+    // If the node isn't visible, don't draw its bounds
+    Camera* camera = scene->getActiveCamera();
+    if (camera)
     {
-        GP_ASSERT(model->getMesh());
+        const BoundingSphere& sphere = node->getBoundingSphere();
+        if (!sphere.isEmpty() && !camera->getFrustum().intersects(sphere))
+            return;
+    }
 
-        MeshSkin* skin = model->getSkin();
-        if (skin && skin->getRootJoint() && skin->getRootJoint()->getParent())
+    if (debugFlags & Scene::DEBUG_BOXES)
+    {
+        if (node->getModel())
         {
-            // For skinned meshes that have a parent node to the skin's root joint,
-            // we need to transform the bounding volume by that parent node's transform
-            // as well to get the full skinned bounding volume.
-            drawDebugBox(batch, model->getMesh()->getBoundingBox(), node->getWorldMatrix() * skin->getRootJoint()->getParent()->getWorldMatrix());
+            Model* model = node->getModel();
+            GP_ASSERT(model->getMesh());
+
+            MeshSkin* skin = model->getSkin();
+            if (skin && skin->getRootJoint() && skin->getRootJoint()->getParent())
+            {
+                // For skinned meshes that have a parent node to the skin's root joint,
+                // we need to transform the bounding volume by that parent node's transform
+                // as well to get the full skinned bounding volume.
+                drawDebugBox(batch, model->getMesh()->getBoundingBox(), node->getWorldMatrix() * skin->getRootJoint()->getParent()->getWorldMatrix());
+            }
+            else
+            {
+                drawDebugBox(batch, model->getMesh()->getBoundingBox(), node->getWorldMatrix());
+            }
         }
-        else
+
+        if (node->getTerrain())
         {
-            drawDebugBox(batch, model->getMesh()->getBoundingBox(), node->getWorldMatrix());
+            drawDebugBox(batch, node->getTerrain()->getBoundingBox(), node->getWorldMatrix());
         }
     }
 
-    if ((debugFlags & Scene::DEBUG_SPHERES) && model)
+    if (debugFlags & Scene::DEBUG_SPHERES)
     {
         drawDebugSphere(batch, node->getBoundingSphere());
     }
 
     for (Node* child = node->getFirstChild(); child != NULL; child = child->getNextSibling())
     {
-        drawDebugNode(batch, child, debugFlags);
+        drawDebugNode(scene, batch, child, debugFlags);
     }
 }
 
@@ -512,7 +578,7 @@ void Scene::drawDebug(unsigned int debugFlags)
 
     for (Node* node = _firstNode; node != NULL; node = node->_nextSibling)
     {
-        drawDebugNode(_debugBatch, node, debugFlags);
+        drawDebugNode(this, _debugBatch, node, debugFlags);
     }
 
     _debugBatch->finish();

+ 74 - 4
gameplay/src/Scene.h

@@ -4,6 +4,7 @@
 #include "Node.h"
 #include "MeshBatch.h"
 #include "ScriptController.h"
+#include "Light.h"
 
 namespace gameplay
 {
@@ -27,10 +28,12 @@ public:
     /**
      * Creates a new empty scene.
      * 
+     * @param id ID of the new scene, or NULL to use an empty string for the ID (default).
+     *
      * @return The newly created empty scene.
      * @script{create}
      */
-    static Scene* create();
+    static Scene* create(const char* id = NULL);
 
     /**
      * Loads a scene from the given '.scene' file.
@@ -42,6 +45,17 @@ public:
      */
     static Scene* load(const char* filePath);
 
+    /**
+     * Gets a currently active scene.
+     *
+     * If id is an NULL, the first active scene is returned.
+     *
+     * @param id ID of the scene to retrieve, or NULL to retrieve the first active scene.
+     *
+     * @return The scene that matches the specified ID, or NULL if no matching scene could be found.
+     */
+    static Scene* getScene(const char* id = NULL);
+
     /**
      * Gets the identifier for the scene.
      *
@@ -147,9 +161,13 @@ public:
     void bindAudioListenerToCamera(bool bind);
 
     /**
-     * Returns the ambient color of the scene. Black is the default color.
+     * Returns the ambient color of the scene.
+     *
+     * The default ambient light color is black (0,0,0).
+     *
+     * This value can be bound to materials using the SCENE_LIGHT_AMBIENT_COLOR auto binding.
      * 
-     * @return The ambient color of the scene.
+     * @return The scene's ambient color.
      */
     const Vector3& getAmbientColor() const;
 
@@ -159,9 +177,59 @@ public:
      * @param red The red channel between 0.0 and 1.0.
      * @param green The green channel between 0.0 and 1.0.
      * @param blue The blue channel between 0.0 and 1.0.
+     *
+     * @see getAmbientColor()
      */
     void setAmbientColor(float red, float green, float blue);
 
+    /**
+     * Returns the light color of the scene.
+     *
+     * The default light color is white (1,1,1).
+     *
+     * This color is typically used to represent the color of the main directional light (i.e. the Sun) in a scene.
+     * The corresponding direction can be queried using Scene::getLightDirection().
+     *
+     * This value can be bound to materials using the SCENE_LIGHT_COLOR auto binding.
+     *
+     * @return The scene's light color.
+     */
+    const Vector3& getLightColor() const;
+
+    /**
+     * Sets the scene's light color.
+     *
+     * @param red The red channel between 0.0 and 1.0.
+     * @param green The green channel between 0.0 and 1.0.
+     * @param blue The blue channel between 0.0 and 1.0.
+     *
+     * @see getLightColor()
+     */
+    void setLightColor(float red, float green, float blue);
+
+    /**
+     * Returns the current light direction for the scene.
+     *
+     * The default value is (0,-1,0), which is a pointing directly down the Y-axis.
+     *
+     * This value is typically used to represent the the main directional lihgt (i.e. the Sun) in a scene.
+     * The corresponding light color can be queried using Scene::getLightColor().
+     *
+     * This value can be bound to materials using the SCENE_LIGHT_DIRECTION auto binding.
+     *
+     * @return The scene's light direction.
+     */
+    const Vector3& getLightDirection() const;
+
+    /**
+     * Sets the scene's light direction.
+     *
+     * @param direction The new light direction.
+     *
+     * @see getLightDirection()
+     */
+    void setLightDirection(const Vector3& direction);
+
     /**
      * Visits each node in the scene and calls the specified method pointer.
      *
@@ -222,7 +290,7 @@ private:
     /**
      * Constructor.
      */
-    Scene();
+    Scene(const char* id);
 
     /**
      * Hidden copy constructor.
@@ -262,6 +330,8 @@ private:
     Node* _lastNode;
     unsigned int _nodeCount;
     Vector3 _ambientColor;
+    Vector3 _lightColor;
+    Vector3 _lightDirection;
     bool _bindAudioListenerToCamera;
     MeshBatch* _debugBatch;
 };

+ 80 - 19
gameplay/src/SceneLoader.cpp

@@ -3,6 +3,7 @@
 #include "Game.h"
 #include "Bundle.h"
 #include "SceneLoader.h"
+#include "Terrain.h"
 
 namespace gameplay
 {
@@ -42,19 +43,31 @@ Scene* SceneLoader::loadInternal(const char* url)
     }
 
     // Get the path to the main GPB.
-    _gpbPath = sceneProperties->getString("path");
+    const char* path = sceneProperties->getString("path");
+    if (path)
+        _gpbPath = path;
 
     // Build the node URL/property and animation reference tables and load the referenced files/store the inline properties objects.
     buildReferenceTables(sceneProperties);
     loadReferencedFiles();
 
     // Load the main scene data from GPB and apply the global scene properties.
-    Scene* scene = loadMainSceneData(sceneProperties);
-    if (!scene)
+    Scene* scene = NULL;
+    if (!_gpbPath.empty())
     {
-        GP_ERROR("Failed to load main scene from bundle.");
-        SAFE_DELETE(properties);
-        return NULL;
+        // Load scene from bundle
+        scene = loadMainSceneData(sceneProperties);
+        if (!scene)
+        {
+            GP_ERROR("Failed to load main scene from bundle.");
+            SAFE_DELETE(properties);
+            return NULL;
+        }
+    }
+    else
+    {
+        // Create a new empty scene
+        scene = Scene::create(sceneProperties->getId());
     }
 
     // First apply the node url properties. Following that,
@@ -67,6 +80,8 @@ Scene* SceneLoader::loadInternal(const char* url)
         SceneNodeProperty::AUDIO | 
         SceneNodeProperty::MATERIAL | 
         SceneNodeProperty::PARTICLE |
+        SceneNodeProperty::TERRAIN |
+        SceneNodeProperty::CAMERA |
         SceneNodeProperty::ROTATE |
         SceneNodeProperty::SCALE |
         SceneNodeProperty::TRANSLATE);
@@ -83,6 +98,24 @@ Scene* SceneLoader::loadInternal(const char* url)
         }
     }
 
+    // Set active camera
+    const char* activeCamera = sceneProperties->getString("activeCamera");
+    if (activeCamera)
+    {
+        Node* camera = scene->findNode(activeCamera);
+        if (camera && camera->getCamera())
+            scene->setActiveCamera(camera->getCamera());
+    }
+
+    // Set ambient and light properties
+    Vector3 vec3;
+    if (sceneProperties->getVector3("ambientColor", &vec3))
+        scene->setAmbientColor(vec3.x, vec3.y, vec3.z);
+    if (sceneProperties->getVector3("lightColor", &vec3))
+        scene->setLightColor(vec3.x, vec3.y, vec3.z);
+    if (sceneProperties->getVector3("lightDirection", &vec3))
+        scene->setLightDirection(vec3);
+
     // Create animations for scene
     createAnimations(scene);
 
@@ -177,6 +210,8 @@ void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Prop
     if (snp._type == SceneNodeProperty::AUDIO ||
         snp._type == SceneNodeProperty::MATERIAL ||
         snp._type == SceneNodeProperty::PARTICLE ||
+        snp._type == SceneNodeProperty::TERRAIN ||
+        snp._type == SceneNodeProperty::CAMERA ||
         snp._type == SceneNodeProperty::COLLISION_OBJECT)
     {
         // Check to make sure the referenced properties object was loaded properly.
@@ -219,6 +254,20 @@ void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Prop
             SAFE_RELEASE(particleEmitter);
             break;
         }
+        case SceneNodeProperty::TERRAIN:
+        {
+            Terrain* terrain = Terrain::create(p);
+            node->setTerrain(terrain);
+            SAFE_RELEASE(terrain);
+            break;
+        }
+        case SceneNodeProperty::CAMERA:
+        {
+            Camera* camera = Camera::create(p);
+            node->setCamera(camera);
+            SAFE_RELEASE(camera);
+            break;
+        }
         case SceneNodeProperty::COLLISION_OBJECT:
         {
             // Check to make sure the type of the namespace used to load the physics collision object is correct.
@@ -297,21 +346,21 @@ void SceneLoader::applyNodeProperty(SceneNode& sceneNode, Node* node, const Prop
         {
             Vector3 t;
             if (np && np->getVector3("translate", &t))
-                node->setTranslation(t);
+                node->translate(t);
             break;
         }
         case SceneNodeProperty::ROTATE:
         {
             Quaternion r;
             if (np && np->getQuaternionFromAxisAngle("rotate", &r))
-                node->setRotation(r);
+                node->rotate(r);
             break;
         }
         case SceneNodeProperty::SCALE:
         {
             Vector3 s;
             if (np && np->getVector3("scale", &s))
-                node->setScale(s);
+                node->scale(s);
             break;
         }
         default:
@@ -470,7 +519,8 @@ void SceneLoader::applyNodeUrls(Scene* scene)
             }
             else
             {
-                GP_ERROR("Failed to locate node with id '%s' in bundle '%s'.", sceneNode._nodeID, _gpbPath.c_str());
+                // There is no node in the scene with this ID, so create a new empty node
+                sceneNode._nodes.push_back(scene->addNode(sceneNode._nodeID));
             }
         }
     }
@@ -519,6 +569,18 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
                     addSceneNodeProperty(sceneNode, SceneNodeProperty::PARTICLE, propertyUrl.c_str());
                     _properties[propertyUrl] = subns;
                 }
+                else if (strcmp(subns->getNamespace(), "terrain") == 0)
+                {
+                    propertyUrl += "terrain/" + std::string(subns->getId());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::TERRAIN, propertyUrl.c_str());
+                    _properties[propertyUrl] = subns;
+                }
+                else if (strcmp(subns->getNamespace(), "camera") == 0)
+                {
+                    propertyUrl += "camera/" + std::string(subns->getId());
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::CAMERA, propertyUrl.c_str());
+                    _properties[propertyUrl] = subns;
+                }
                 else if (strcmp(subns->getNamespace(), "collisionObject") == 0)
                 {
                     propertyUrl += "collisionObject/" + std::string(subns->getId());
@@ -565,6 +627,14 @@ void SceneLoader::buildReferenceTables(Properties* sceneProperties)
                 {
                     addSceneNodeProperty(sceneNode, SceneNodeProperty::PARTICLE, ns->getString());
                 }
+                else if (strcmp(name, "terrain") == 0)
+                {
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::TERRAIN, ns->getString());
+                }
+                else if (strcmp(name, "camera") == 0)
+                {
+                    addSceneNodeProperty(sceneNode, SceneNodeProperty::CAMERA, ns->getString());
+                }
                 else if (strcmp(name, "collisionObject") == 0)
                 {
                     addSceneNodeProperty(sceneNode, SceneNodeProperty::COLLISION_OBJECT, ns->getString());
@@ -795,15 +865,6 @@ Scene* SceneLoader::loadMainSceneData(const Properties* sceneProperties)
         return NULL;
     }
 
-    // Go through the supported scene properties and apply them to the scene.
-    const char* name = sceneProperties->getString("activeCamera");
-    if (name)
-    {
-        Node* camera = scene->findNode(name);
-        if (camera && camera->getCamera())
-            scene->setActiveCamera(camera->getCamera());
-    }
-
     SAFE_RELEASE(bundle);
     return scene;
 }

+ 7 - 5
gameplay/src/SceneLoader.h

@@ -49,11 +49,13 @@ private:
             AUDIO = 1,
             MATERIAL = 2,
             PARTICLE = 4,
-            COLLISION_OBJECT = 8,
-            TRANSLATE = 16,
-            ROTATE = 32,
-            SCALE = 64,
-            URL = 128
+            TERRAIN = 8,
+            CAMERA = 16,
+            COLLISION_OBJECT = 32,
+            TRANSLATE = 64,
+            ROTATE = 128,
+            SCALE = 256,
+            URL = 512
         };
 
         SceneNodeProperty(Type type, const std::string& url, int index);

+ 28 - 2
gameplay/src/ScriptController.cpp

@@ -1,7 +1,10 @@
 #include "Base.h"
 #include "FileSystem.h"
 #include "ScriptController.h"
+
+#ifndef NO_LUA_BINDINGS
 #include "lua/lua_all_bindings.h"
+#endif
 
 #define GENERATE_LUA_GET_POINTER(type, checkFunc) \
     ScriptController* sc = Game::getInstance()->getScriptController(); \
@@ -600,7 +603,10 @@ void ScriptController::initialize()
     if (!_lua)
         GP_ERROR("Failed to initialize Lua scripting engine.");
     luaL_openlibs(_lua);
+
+#ifndef NO_LUA_BINDINGS
     lua_RegisterAllBindings();
+#endif
 
     // Create our own print() function that uses gameplay::print.
     if (luaL_dostring(_lua, lua_print_function))
@@ -626,17 +632,34 @@ void ScriptController::initializeGame()
 void ScriptController::finalize()
 {
     if (_lua)
+	{
         lua_close(_lua);
+		_lua = NULL;
+	}
 }
 
 void ScriptController::finalizeGame()
 {
-    if (_callbacks[FINALIZE])
+	std::string finalizeCallback;
+	if (_callbacks[FINALIZE])
+		finalizeCallback = _callbacks[FINALIZE]->c_str();
+
+	// Remove any registered callbacks so they don't get called after shutdown
+	for (unsigned int i = 0; i < CALLBACK_COUNT; i++)
     {
-        executeFunction<void>(_callbacks[FINALIZE]->c_str());
+        SAFE_DELETE(_callbacks[i]);
+    }
+
+	// Fire script finalize callback
+    if (!finalizeCallback.empty())
+	{
+        executeFunction<void>(finalizeCallback.c_str());
     }
 
     // Perform a full garbage collection cycle.
+	// Note that this does NOT free any global variables declared in scripts, since 
+	// they are stored in the global state and are still referenced. Only after 
+	// closing the state (lua_close) will those variables be released.
     lua_gc(_lua, LUA_GCCOLLECT, 0);
 }
 
@@ -691,6 +714,9 @@ void ScriptController::gamepadEvent(Gamepad::GamepadEvent evt, Gamepad* gamepad)
 
 void ScriptController::executeFunctionHelper(int resultCount, const char* func, const char* args, va_list* list)
 {
+	if (!_lua)
+		return; // handles calling this method after script is finalized
+
     if (func == NULL)
     {
         GP_ERROR("Lua function name must be non-null.");

+ 574 - 0
gameplay/src/Terrain.cpp

@@ -0,0 +1,574 @@
+#include "Base.h"
+#include "Terrain.h"
+#include "Node.h"
+#include "FileSystem.h"
+
+namespace gameplay
+{
+
+// The default square size of terrain patches for a terrain that
+// does not have an explicitly specified patch size.
+//
+#define DEFAULT_TERRAIN_PATCH_SIZE 32
+
+// The default height of a terrain that does not have an explicitly
+// specified terrain size, expressed as a ratio of the average
+// of the dimensions of the terrain heightfield:
+//
+//   maxHeight = (image.width + image.height) / 2 * DEFAULT_TERRAIN_HEIGHT_RATIO
+//
+#define DEFAULT_TERRAIN_HEIGHT_RATIO 0.3f
+
+// Terrain dirty flag bits
+#define TERRAIN_DIRTY_WORLD_MATRIX 1
+#define TERRAIN_DIRTY_INV_WORLD_MATRIX 2
+#define TERRAIN_DIRTY_NORMAL_MATRIX 4
+
+/**
+ * @script{ignore}
+ */
+float getDefaultHeight(unsigned int width, unsigned int height);
+
+Terrain::Terrain() :
+    _heightfield(NULL), _node(NULL), _normalMap(NULL), _flags(ENABLE_FRUSTUM_CULLING | ENABLE_LEVEL_OF_DETAIL),
+    _dirtyFlags(TERRAIN_DIRTY_WORLD_MATRIX | TERRAIN_DIRTY_INV_WORLD_MATRIX | TERRAIN_DIRTY_NORMAL_MATRIX)
+{
+}
+
+Terrain::~Terrain()
+{
+    if (_node)
+        _node->removeListener(this);
+
+    SAFE_RELEASE(_normalMap);
+    SAFE_RELEASE(_heightfield);
+}
+
+Terrain* Terrain::create(const char* path)
+{
+    return create(path, NULL);
+}
+
+Terrain* Terrain::create(Properties* properties)
+{
+    return create(properties->getNamespace(), properties);
+}
+
+Terrain* Terrain::create(const char* path, Properties* properties)
+{
+    // Terrain properties
+    Properties* p = properties;
+    Properties* pTerrain = NULL;
+    bool externalProperties = (p != NULL);
+    HeightField* heightfield = NULL;
+    Vector3 terrainSize;
+    int patchSize = 0;
+    int detailLevels = 1;
+    float skirtScale = 0;
+    const char* normalMap = NULL;
+
+    if (!p && path)
+    {
+        p = Properties::create(path);
+    }
+
+    if (p)
+    {
+        pTerrain = strlen(p->getNamespace()) > 0 ? p : p->getNextNamespace();
+        if (pTerrain == NULL)
+        {
+            GP_WARN("Invalid terrain definition.");
+            if (!externalProperties)
+                SAFE_DELETE(p);
+            return NULL;
+        }
+
+        // Read heightmap info
+        Properties* pHeightmap = pTerrain->getNamespace("heightmap", true);
+        if (pHeightmap)
+        {
+            // Read heightmap path
+            const char* heightmap = pHeightmap->getString("path");
+            if (strlen(heightmap) == 0)
+            {
+                GP_WARN("No 'path' property supplied in heightmap section of terrain definition: %s", path);
+                if (!externalProperties)
+                    SAFE_DELETE(p);
+                return NULL;
+            }
+
+            std::string ext = FileSystem::getExtension(heightmap);
+            if (ext == ".PNG")
+            {
+                // Read normalized height values from heightmap image
+                heightfield = HeightField::createFromImage(heightmap, 0, 1);
+            }
+            else if (ext == ".RAW")
+            {
+                // Require additional properties to be specified for RAW files
+                Vector2 imageSize;
+                if (!pHeightmap->getVector2("size", &imageSize))
+                {
+                    GP_WARN("Invalid or missing 'size' attribute in heightmap defintion of terrain definition: %s", path);
+                    if (!externalProperties)
+                        SAFE_DELETE(p);
+                    return NULL;
+                }
+
+                // Read normalized height values from RAW file
+                heightfield = HeightField::createFromRAW(heightmap, (unsigned int)imageSize.x, (unsigned int)imageSize.y, 0, 1);
+            }
+            else
+            {
+                // Unsupported heightmap format
+                GP_WARN("Unsupported heightmap format ('%s') in terrain definition: %s", heightmap, path);
+                if (!externalProperties)
+                    SAFE_DELETE(p);
+                return NULL;
+            }
+        }
+        else
+        {
+            // Try to read 'heightmap' as a simple string property
+            const char* heightmap = pTerrain->getString("heightmap");
+            if (heightmap == NULL || strlen(heightmap) == 0)
+            {
+                GP_WARN("No 'heightmap' property supplied in terrain definition: %s", path);
+                if (!externalProperties)
+                    SAFE_DELETE(p);
+                return NULL;
+            }
+
+            std::string ext = FileSystem::getExtension(heightmap);
+            if (ext == ".PNG")
+            {
+                // Read normalized height values from heightmap image
+                heightfield = HeightField::createFromImage(heightmap, 0, 1);
+            }
+            else if (ext == ".RAW")
+            {
+                GP_WARN("RAW heightmaps must be specified inside a heightmap block with width and height properties.");
+                if (!externalProperties)
+                    SAFE_DELETE(p);
+                return NULL;
+            }
+            else
+            {
+                GP_WARN("Unsupported 'heightmap' format ('%s') in terrain definition: %s.", heightmap, path);
+                if (!externalProperties)
+                    SAFE_DELETE(p);
+                return NULL;
+            }
+        }
+
+        // Read terrain 'size'
+        if (pTerrain->exists("size"))
+        {
+            if (!pTerrain->getVector3("size", &terrainSize))
+            {
+                GP_WARN("Invalid 'size' value ('%s') in terrain definition: %s", pTerrain->getString("size"), path);
+            }
+        }
+
+        // Read terrain 'patch size'
+        if (pTerrain->exists("patchSize"))
+        {
+            patchSize = pTerrain->getInt("patchSize");
+        }
+
+        // Read terrain 'detailLevels'
+        if (pTerrain->exists("detailLevels"))
+        {
+            detailLevels = pTerrain->getInt("detailLevels");
+        }
+
+        // Read 'skirtScale'
+        if (pTerrain->exists("skirtScale"))
+        {
+            skirtScale = pTerrain->getFloat("skirtScale");
+        }
+
+        // Read 'normalMap'
+        normalMap = pTerrain->getString("normalMap");
+    }
+
+    if (heightfield == NULL)
+    {
+        GP_WARN("Failed to read heightfield heights for terrain definition: %s", path);
+        if (!externalProperties)
+            SAFE_DELETE(p);
+        return NULL;
+    }
+
+    if (terrainSize.isZero())
+    {
+        terrainSize.set(heightfield->getColumnCount(), getDefaultHeight(heightfield->getColumnCount(), heightfield->getRowCount()), heightfield->getRowCount());
+    }
+
+    if (patchSize <= 0 || patchSize > (int)heightfield->getColumnCount() || patchSize > (int)heightfield->getRowCount())
+    {
+        patchSize = std::min(heightfield->getRowCount(), std::min(heightfield->getColumnCount(), (unsigned int)DEFAULT_TERRAIN_PATCH_SIZE));
+    }
+
+    if (detailLevels <= 0)
+        detailLevels = 1;
+
+    if (skirtScale < 0)
+        skirtScale = 0;
+
+    // Compute terrain scale
+    Vector3 scale(terrainSize.x / (heightfield->getColumnCount()-1), terrainSize.y, terrainSize.z / (heightfield->getRowCount()-1));
+
+    // Create terrain
+    Terrain* terrain = create(heightfield, scale, (unsigned int)patchSize, (unsigned int)detailLevels, skirtScale, normalMap, pTerrain);
+
+    if (!externalProperties)
+        SAFE_DELETE(p);
+
+    return terrain;
+}
+
+Terrain* Terrain::create(HeightField* heightfield, const Vector3& scale, unsigned int patchSize, unsigned int detailLevels, float skirtScale, const char* normalMapPath)
+{
+    return create(heightfield, scale, patchSize, detailLevels, skirtScale, normalMapPath, NULL);
+}
+
+Terrain* Terrain::create(HeightField* heightfield, const Vector3& scale, unsigned int patchSize, unsigned int detailLevels, float skirtScale, const char* normalMapPath, Properties* properties)
+{
+    GP_ASSERT(heightfield);
+
+    unsigned int width = heightfield->getColumnCount();
+    unsigned int height = heightfield->getRowCount();
+
+    // Create the terrain object
+    Terrain* terrain = new Terrain();
+    terrain->_heightfield = heightfield;
+    terrain->_localScale = scale;
+
+    // Store reference to bounding box (it is calculated and updated from TerrainPatch)
+    BoundingBox& bounds = terrain->_boundingBox;
+
+    if (normalMapPath)
+        terrain->_normalMap = Texture::Sampler::create(normalMapPath, true);
+
+    float halfWidth = (width - 1) * 0.5f;
+    float halfHeight = (height - 1) * 0.5f;
+    unsigned int maxStep = (unsigned int)std::pow(2.0, (double)(detailLevels-1));
+
+    // Create terrain patches
+    unsigned int x1, x2, z1, z2;
+    unsigned int row = 0, column = 0;
+    for (unsigned int z = 0; z < height-1; z = z2, ++row)
+    {
+        z1 = z;
+        z2 = std::min(z1 + patchSize, height-1);
+
+        for (unsigned int x = 0; x < width-1; x = x2, ++column)
+        {
+            x1 = x;
+            x2 = std::min(x1 + patchSize, width-1);
+
+            // Create this patch
+            TerrainPatch* patch = TerrainPatch::create(terrain, row, column, heightfield->getArray(), width, height, x1, z1, x2, z2, -halfWidth, -halfHeight, maxStep, skirtScale);
+            terrain->_patches.push_back(patch);
+
+            // Append the new patch's local bounds to the terrain local bounds
+            bounds.merge(patch->getBoundingBox(false));
+        }
+    }
+
+    // Read additional layer information from properties (if specified)
+    if (properties)
+    {
+        // Parse terrain layers
+        Properties* lp;
+        int index = -1;
+        while ((lp = properties->getNextNamespace()) != NULL)
+        {
+            if (strcmp(lp->getNamespace(), "layer") == 0)
+            {
+                // If there is no explicitly specified index for this layer, assume it's the 'next' layer
+                if (lp->exists("index"))
+                    index = lp->getInt("index");
+                else
+                    ++index;
+
+                const char* textureMap = NULL;
+                const char* blendMap = NULL;
+                Vector2 textureRepeat;
+                int blendChannel = 0;
+                int row = -1, column = -1;
+                Vector4 temp;
+
+                // Read layer textures
+                Properties* t = lp->getNamespace("texture", true);
+                if (t)
+                {
+                    textureMap = t->getString("path");
+                    if (!t->getVector2("repeat", &textureRepeat))
+                        textureRepeat.set(1,1);
+                }
+
+                Properties* b = lp->getNamespace("blend", true);
+                if (b)
+                {
+                    blendMap = b->getString("path");
+                    const char* channel = b->getString("channel");
+                    if (channel && strlen(channel) > 0)
+                    {
+                        char c = std::toupper(channel[0]);
+                        if (c == 'R' || c == '0')
+                            blendChannel = 0;
+                        else if (c == 'G' || c == '1')
+                            blendChannel = 1;
+                        else if (c == 'B' || c == '2')
+                            blendChannel = 2;
+                        else if (c == 'A' || c == '3')
+                            blendChannel = 3;
+                    }
+                }
+
+                // Get patch row/columns that this layer applies to.
+                if (lp->exists("row"))
+                    row = lp->getInt("row");
+                if (lp->exists("column"))
+                    column = lp->getInt("column");
+
+                if (!terrain->setLayer(index, textureMap, textureRepeat, blendMap, blendChannel, row, column))
+                {
+                    GP_WARN("Failed to load terrain layer: %s", textureMap);
+                }
+            }
+        }
+    }
+
+    // Load materials for all patches
+    for (size_t i = 0, count = terrain->_patches.size(); i < count; ++i)
+        terrain->_patches[i]->updateMaterial();
+
+    return terrain;
+}
+
+void Terrain::setNode(Node* node)
+{
+    if (_node != node)
+    {
+        if (_node)
+            _node->removeListener(this);
+
+        _node = node;
+
+        if (_node)
+            _node->addListener(this);
+
+        _dirtyFlags |= TERRAIN_DIRTY_WORLD_MATRIX | TERRAIN_DIRTY_INV_WORLD_MATRIX | TERRAIN_DIRTY_NORMAL_MATRIX;
+    }
+}
+
+bool Terrain::setLayer(int index, const char* texturePath, const Vector2& textureRepeat, const char* blendPath, int blendChannel, int row, int column)
+{
+    if (!texturePath)
+        return false;
+
+    // Set layer on applicable patches
+    bool result = true;
+    for (size_t i = 0, count = _patches.size(); i < count; ++i)
+    {
+        TerrainPatch* patch = _patches[i];
+
+        if ((row == -1 || (int)patch->_row == row) && (column == -1 || (int)patch->_column == column))
+        {
+            if (!patch->setLayer(index, texturePath, textureRepeat, blendPath, blendChannel))
+                result = false;
+        }
+    }
+
+    return result;
+}
+
+Node* Terrain::getNode() const
+{
+    return _node;
+}
+
+bool Terrain::isFlagSet(Flags flag) const
+{
+    return (_flags & flag) == flag;
+}
+
+void Terrain::setFlag(Flags flag, bool on)
+{
+    bool changed = false;
+
+    if (on)
+    {
+        if ((_flags & flag) == 0)
+        {
+            _flags |= flag;
+            changed = true;
+        }
+    }
+    else
+    {
+        if ((_flags & flag) == flag)
+        {
+            _flags &= ~flag;
+            changed = true;
+        }
+    }
+
+    if (flag == DEBUG_PATCHES && changed)
+    {
+        // Dirty all materials since they need to be updated to support debug drawing
+        for (size_t i = 0, count = _patches.size(); i < count; ++i)
+            _patches[i]->_materialDirty = true;
+    }
+}
+
+unsigned int Terrain::getPatchCount() const
+{
+    return _patches.size();
+}
+
+unsigned int Terrain::getVisiblePatchCount() const
+{
+    unsigned int visibleCount = 0;
+    for (size_t i = 0, count = _patches.size(); i < count; ++i)
+    {
+        if (_patches[i]->isVisible())
+            ++visibleCount;
+    }
+    return visibleCount;
+}
+
+unsigned int Terrain::getTriangleCount() const
+{
+    unsigned int triangleCount = 0;
+    for (size_t i = 0, count = _patches.size(); i < count; ++i)
+    {
+        triangleCount += _patches[i]->getTriangleCount();
+    }
+    return triangleCount;
+}
+
+unsigned int Terrain::getVisibleTriangleCount() const
+{
+    unsigned int triangleCount = 0;
+    for (size_t i = 0, count = _patches.size(); i < count; ++i)
+    {
+        triangleCount += _patches[i]->getVisibleTriangleCount();
+    }
+    return triangleCount;
+}
+
+const BoundingBox& Terrain::getBoundingBox() const
+{
+    return _boundingBox;
+}
+
+float Terrain::getHeight(float x, float z) const
+{
+    // Calculate the correct x, z position relative to the heightfield data.
+    float cols = _heightfield->getColumnCount();
+    float rows = _heightfield->getRowCount();
+
+    GP_ASSERT(cols > 0);
+    GP_ASSERT(rows > 0);
+
+    // Since the specified coordinates are in world space, we need to use the 
+    // inverse of our world matrix to transform the world x,z coords back into
+    // local heightfield coordinates for indexing into the height array.
+    Vector3 v = getInverseWorldMatrix() * Vector3(x, 0.0f, z);
+    x = v.x + (cols - 1) * 0.5f;
+    z = v.z + (rows - 1) * 0.5f;
+
+    // Get the unscaled height value from the HeightField
+    float height = _heightfield->getHeight(x, z);
+
+    // Now apply world scale (this includes local terrain scale) to the heightfield value
+    Vector3 worldScale;
+    getWorldMatrix().getScale(&worldScale);
+    height *= worldScale.y;
+
+    return height;
+}
+
+void Terrain::draw(bool wireframe)
+{
+    for (size_t i = 0, count = _patches.size(); i < count; ++i)
+    {
+        _patches[i]->draw(wireframe);
+    }
+}
+
+void Terrain::transformChanged(Transform* transform, long cookie)
+{
+    _dirtyFlags |= TERRAIN_DIRTY_WORLD_MATRIX | TERRAIN_DIRTY_INV_WORLD_MATRIX | TERRAIN_DIRTY_NORMAL_MATRIX;
+}
+
+const Matrix& Terrain::getWorldMatrix() const
+{
+    if (_dirtyFlags & TERRAIN_DIRTY_WORLD_MATRIX)
+    {
+        _dirtyFlags &= ~TERRAIN_DIRTY_WORLD_MATRIX;
+
+        // Apply our attached node's world matrix
+        if (_node)
+        {
+            _worldMatrix = _node->getWorldMatrix();
+        }
+        else
+        {
+            _worldMatrix.setIdentity();
+        }
+
+        // Factor in our local scaling
+        _worldMatrix.scale(_localScale);
+    }
+
+    return _worldMatrix;
+}
+
+const Matrix& Terrain::getInverseWorldMatrix() const
+{
+    if (_dirtyFlags & TERRAIN_DIRTY_INV_WORLD_MATRIX)
+    {
+        _dirtyFlags &= ~TERRAIN_DIRTY_INV_WORLD_MATRIX;
+
+        getWorldMatrix().invert(&_inverseWorldMatrix);
+    }
+
+    return _inverseWorldMatrix;
+}
+
+const Matrix& Terrain::getNormalMatrix() const
+{
+    if (_dirtyFlags & TERRAIN_DIRTY_NORMAL_MATRIX)
+    {
+        _dirtyFlags &= ~TERRAIN_DIRTY_NORMAL_MATRIX;
+
+        getInverseWorldMatrix().transpose(&_normalMatrix);
+    }
+
+    return _normalMatrix;
+}
+
+const Matrix& Terrain::getWorldViewProjectionMatrix() const
+{
+    static Matrix worldViewProj;
+
+    if (_node)
+        Matrix::multiply(_node->getViewProjectionMatrix(), getWorldMatrix(), &worldViewProj);
+    else
+        worldViewProj = getWorldMatrix(); // no node, so nothing to get viewProjection from
+
+    return worldViewProj;
+}
+
+float getDefaultHeight(unsigned int width, unsigned int height)
+{
+    // When terrain height is not specified, we'll use a default height of ~ 0.3 of the image dimensions
+    return ((width + height) * 0.5f) * DEFAULT_TERRAIN_HEIGHT_RATIO;
+}
+
+}

+ 375 - 0
gameplay/src/Terrain.h

@@ -0,0 +1,375 @@
+#ifndef TERRAIN_H_
+#define TERRAIN_H_
+
+#include "TerrainPatch.h"
+#include "Transform.h"
+#include "Properties.h"
+#include "HeightField.h"
+
+namespace gameplay
+{
+
+class Node;
+
+/**
+ * Defines a Terrain that is capable of rendering large landscapes from 2D heightmap images.
+ *
+ * Terrains can be constructed from several different heightmap sources:
+ *
+ * 1. Basic intensity image (PNG), where the intensity of pixel represents the height of the
+ *    terrain.
+ * 2. 24-bit high precision heightmap image (PNG), which can be generated from a mesh using
+ *    gameplay-encoder.
+ * 3. 8-bit or 16-bit RAW heightmap image using PC byte ordering (little endian), which is
+ *    compatible with many external tools such as World Machine, Unity and more.
+ *
+ * Physics/collision is supported by setting a rigid body collision object on the Node that
+ * the terrain is attached to. The collision shape should be specified using
+ * PhysicsCollisionShape::heightfield(), which will utilize the internal height array of the
+ * terrain to define the collision shape. Define a collision object in this way will allow
+ * the terrain to automatically interact with other rigid bodies, characters and vehicles in
+ * the scene.
+ *
+ * Surface detail is provided via texture splatting, where multiple texture layers can be added
+ * along with blend maps to define how different layers blend with each other. These layers
+ * can be defined in terrain properties files, as well as with the setLayer method. The number
+ * of supported layers depends on the target hardware, although typically 2-3 levels is
+ * sufficient. Multiple blend maps for different layers can be packed into different channels
+ * of a single texture for more efficient texture utilization. Levels can be applied across
+ * the entire terrain, or in more complex cases, for individual patches only.
+ *
+ * Surface lighting is achieved with either vertex normals or with a normal map. If a
+ * normal map is used, it should be an object-space normal map containing normal vectors for
+ * the entire terrain, encoded into the red, green and blue channels of the texture. This is
+ * useful as a replacement for vertex normals, especially when using level-of-detail, since
+ * it allows you to provide high quality normal vectors regardless of the tessellation or 
+ * LOD of the terrain. This also eliminates lighting artifacts/popping from LOD changes,
+ * which commonly occurs when vertex normals are used. A terrain normal map can only be
+ * specified at creation time, since it requires omission of vertex normal information in
+ * the generated terrain geometry data.
+ *
+ * Internally, Terrain is broken into smaller, more managable patches, which can be culled
+ * separately for more efficient rendering. The size of the terrain patches can be controlled
+ * via the patchSize property. Patches can be previewed by enabling the DEBUG_PATCHES flag
+ * via the setFlag method. Other terrain behavior can also be enabled and disabled using terrain
+ * flags.
+ * 
+ * Level of detail (LOD) is supported using a technique that is similar to texture mipmapping.
+ * A distance-to-camera based test, using a simple screen-space error metric is used to decide
+ * the appropriate LOD for a terrain patch. The number of LOD levels is 1 by default (which
+ * means only the base level is used), but can be specified via the detailLevels property.
+ * Using too large a number for detailLevels can result in excessive popping in the distance
+ * for very hilly terrains, so a smaller number (2-3) often works best in these cases.
+ *
+ * Finally, when LOD is enabled, cracks can begin to appear between terrain patches of
+ * different LOD levels. If the cracks are only minor (depends on your terrain topology
+ * and tetures used), an acceptable appraoch might be to simply use a background clear 
+ * color that closely matches your terrain to make the cracks much less visible. However,
+ * often that is not acceptable, so the Terrain class also supports a simple solution called
+ * "vertical skirts". When enabled (via the skirtScale parameter in the terrain file), a vertical
+ * edge will extend down along the sides of all terrain patches, which fills in the crack.
+ * This is a very fast approach as it adds only a small number of triangles per patch and requires
+ * zero extra CPU time or draw calls, which are often needed for more complex stitching 
+ * approaches. In practice, the skirts are often not noticable at all unless the LOD variation
+ * is very large and the terrain is excessively hilly on the edge of a LOD transition.
+ */
+class Terrain : public Ref, public Transform::Listener
+{
+    friend class Node;
+    friend class TerrainPatch;
+    friend class PhysicsController;
+    friend class PhysicsRigidBody;
+
+public:
+
+    /**
+     * Terrain flags.
+     */
+    enum Flags
+    {
+        /**
+         * Draw terrain patches different colors (off by default).
+         */
+        DEBUG_PATCHES = 1,
+
+        /**
+         * Enables view frustum culling (on by default).
+         *
+         * Frustum culling uses the scene's active camera. The terrain must be attached
+         * to a node that is within a scene for this to work.
+         */
+        ENABLE_FRUSTUM_CULLING = 2,
+
+         /**
+          * Enables level of detail (on by default).
+          *
+          * This flag enables or disables level of detail, however it does nothing if
+          * "detailLevels" was not set to a value greater than 1 in the terrain
+          * properties file at creation time.
+          */
+         ENABLE_LEVEL_OF_DETAIL = 8
+    };
+
+    /**
+     * Loads a Terrain from the given properties file.
+     *
+     * The specified properties file can contain a full terrain definition, including a 
+     * heightmap (PNG, RAW8, RAW16), level of detail information, patch size, layer texture
+     * details and vertical skirt size.
+     *
+     * @param path Path to a properties file describing the terrain.
+     *
+     * @return A new Terrain.
+     * @script{create}
+     */
+    static Terrain* create(const char* path);
+
+    /**
+     * Creates a new terrain definition from the configuration in the specified Properties object.
+     *
+     * @param properties Properties object containing the terrain definition.
+     *
+     * @return A new Terrain.
+     *
+     * @see create(const char*)
+     * @script{create}
+     */
+    static Terrain* create(Properties* properties);
+
+    /**
+     * Creates a terrain from the given heightfield.
+     *
+     * Terrain geometry is loaded from the given height array, using the specified parameters for
+     * size, patch size, detail levels and skirt scale.
+     *
+     * The newly created terrain increases the reference count of the HeightField.
+     *
+     * @param heightfield The heightfield object containing height data for the terrain.
+     * @param scale A scale to apply to the terrain along the X, Y and Z axes. The terrain and any associated
+     *      physics hegihtfield is scaled by this amount. Pass Vector3::one() to use the exact dimensions and heights
+     *      in the supplied height array.
+     * @param patchSize Size of terrain patches (number of quads).
+     * @param detailLevel Number of detail levels to generate for the terrain (a value of one generates only the base
+     *      level, resulting in no LOD at runtime.
+     * @param skirtScale A positive value indicates that vertical skirts should be generated at the specified
+     *      scale, which is relative to the height of the terrain. For example, a value of 0.5f indicates that
+     *      vertical skirts should extend down by half of the maximum height of the terrain. A value of zero
+     *      disables vertical skirts.
+     * @param normalMapPath Path to an object-space normal map to use for terrain lighting, instead of vertex normals.
+     *
+     * @return A new Terrain.
+     * @script{create}
+     */
+    static Terrain* create(HeightField* heightfield,
+        const Vector3& scale = Vector3::one(), unsigned int patchSize = 32,
+        unsigned int detailLevels = 1, float skirtScale = 0.0f,
+        const char* normalMapPath = NULL);
+
+    /**
+     * Sets the detail textures information for a terrain layer.
+     *
+     * A detail layer includes a color texture, a repeat count across the terrain for the texture and
+     * a region of the texture to use.
+     *
+     * Optionally, a layer can also include a blend texture, which is used to instruct the terrain how
+     * to blend the new layer with the layer underneath it. Blend maps use only a single channel of a 
+     * texture and are best supplied by packing the blend map for a layer into the alpha channel of
+     * the color texture. Blend maps are always stretched over the entire terrain 
+     *
+     * The lowest/base layer of the terrain should not include a blend map, since there is no lower
+     * level to blend with. All other layers should normally include a blend map. However, since no
+     * blend map will result in the texture completely masking the layer underneath it.
+     *
+     * Detail layers can be applied globally (to the entire terrain), or to one or more specific
+     * patches in the terrain. Patches are specified by row and column number, which is dependent
+     * on the patch size configuration of your terrain. For layers that span the entire terrain, 
+     * the repeat count is relative to the entire terrain. For layers that span only specific
+     * patches, the repeat count is relative to those patches only.
+     *
+     * @param index Layer index number. Layer indexes do not neccessarily need to be sequential and
+     *      are used simply to uinquely identify layers, where higher numbers specificy higher-level
+     *      layers.
+     * @param texturePath Path to the color texture for this layer.
+     * @param textureRepeat Repeat count for the color texture across the terrain or patches.
+     * @param blendPath Path to the blend texture for this layer (optional).
+     * @param blendChannel Channel of the blend texture to sample for the blend map (0 == R, 1 == G, 2 == B, 3 == A).
+     * @param row Specifies the row index of patches to use this layer (optional, -1 means all rows).
+     * @param column Specifies the column index of patches to use this layer (optional, -1 means all columns).
+     *
+     * @return True if the layer was successfully set, false otherwise. The most common reason for failure is an
+     *      invalid texture path.
+     *
+     * @script{ignore}
+     */
+    bool setLayer(int index,
+        const char* texturePath, const Vector2& textureRepeat = Vector2::one(),
+        const char* blendPath = NULL, int blendChannel = 0,
+        int row = -1, int column = -1);
+
+    /**
+     * Returns the node that this terrain is bound to.
+     *
+     * @return The node this terrain is bound to, or NULL if the terrain is not bound to a node.
+     */
+    Node* getNode() const;
+
+    /**
+     * Determines if the specified terrain flag is currently set.
+     */
+    bool isFlagSet(Flags flag) const;
+
+    /**
+     * Enables or disables the specified terrain flag.
+     *
+     * @param flag The terrain flag to set.
+     * @param on True to turn the flag on, false to turn it off.
+     */
+    void setFlag(Flags flag, bool on);
+
+    /**
+     * Returns the total number of terrain patches.
+     *
+     * @return The number of terrain patches.
+     */
+    unsigned int getPatchCount() const;
+
+    /**
+     * Returns the number of patches that are currently visible from the scene camera's point of view.
+     *
+     * If the terrain is not attached to a scene, or if there is no active scene camera, this method
+     * returns zero.
+     *
+     * This method is not exact - it may return false positives since it only determines if the
+     * bounding box of terrain patches intersect the view frustum. Should be used for debug 
+     * purposes only.
+     *
+     * @return The number of currently visible patches.
+     */
+    unsigned int getVisiblePatchCount() const;
+
+    /**
+     * Returns the total number of triangles for this terrain at the base LOD.
+     *
+     * @return The total triangle count for the terrain at the base LOD.
+     */
+    unsigned int getTriangleCount() const;
+
+    /**
+     * Returns the number of currently visible triangles, taking LOD and view frustum
+     * (if enabled) into consideration.
+     *
+     * @return The current visible triangle count.
+     */
+    unsigned int getVisibleTriangleCount() const;
+
+    /**
+     * Returns the local bounding box for this terrain.
+     *
+     * @return The local bounding box for the terrain.
+     */
+    const BoundingBox& getBoundingBox() const;
+
+    /**
+     * Returns the world-space height of the terrain at the specified position on the X,Z plane.
+     *
+     * The specified X and Z coordinates should be in world units and may fall between height values.
+     * In this case, an interpolated value will be returned between neighboring heightfield heights.
+     * If the specified point lies outside of the terrain, it is clamped to the terrain boundaries.
+     *
+     * @param x The X coordinate, in world space.
+     * @param z The Z coordinate, in world space.
+     *
+     * @return The height at the specified point, clamped to the boundaries of the terrain.
+     */
+    float getHeight(float x, float z) const;
+
+    /**
+     * Draws the terrain.
+     *
+     * @param wireframe True to draw the terrain as wireframe, false to draw it solid (default).
+     */
+    void draw(bool wireframe = false);
+
+    /**
+     * @see Transform::Listener::transformChanged.
+     *
+     * Internal use only.
+     *
+     * @script{ignore}
+     */
+    void transformChanged(Transform* transform, long cookie);
+
+private:
+
+    /**
+     * Constructor.
+     */
+    Terrain();
+
+    /**
+     * Hidden copy constructor.
+     */
+    Terrain(const Terrain&);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    Terrain& operator=(const Terrain&);
+
+    /**
+     * Destructor.
+     */
+    ~Terrain();
+
+    /**
+     * Internal method for creating terrain.
+     */
+    static Terrain* create(HeightField* heightfield, const Vector3& scale, unsigned int patchSize, unsigned int detailLevels, float skirtScale, const char* normalMapPath, Properties* properties);
+
+    /**
+     * Internal method for creating terrain.
+     */
+    static Terrain* create(const char* path, Properties* properties);
+
+    /**
+     * Sets the node that the terrain is attached to.
+     */
+    void setNode(Node* node);
+
+    /**
+     * Returns the world matrix of the terrain, factoring in terrain local scaling.
+     */
+    const Matrix& getWorldMatrix() const;
+
+    /**
+     * Returns the terrain's inverse world matrix.
+     */
+    const Matrix& getInverseWorldMatrix() const;
+
+    /**
+     * Returns a matrix to be used for transforming normal vectors for the terrain.
+     */
+    const Matrix& getNormalMatrix() const;
+
+    /**
+     * Returns the world view projection matrix for the terrain, factoring in terrain local scaling.
+     */
+    const Matrix& getWorldViewProjectionMatrix() const;
+
+    HeightField* _heightfield;
+    Node* _node;
+    std::vector<TerrainPatch*> _patches;
+    Vector3 _localScale;
+    Texture::Sampler* _normalMap;
+    unsigned int _flags;
+    mutable Matrix _worldMatrix;
+    mutable Matrix _inverseWorldMatrix;
+    mutable Matrix _normalMatrix;
+    mutable unsigned int _dirtyFlags;
+    BoundingBox _boundingBox;
+
+};
+
+}
+
+#endif

+ 670 - 0
gameplay/src/TerrainPatch.cpp

@@ -0,0 +1,670 @@
+#include "Base.h"
+#include "TerrainPatch.h"
+#include "Terrain.h"
+#include "MeshPart.h"
+#include "Scene.h"
+#include "Game.h"
+
+namespace gameplay
+{
+
+/**
+ * @script{ignore}
+ */
+float calculateHeight(float* heights, unsigned int width, unsigned int height, unsigned int x, unsigned int z);
+
+/**
+ * @script{ignore}
+ */
+template <class T> T clamp(T value, T min, T max) { return value < min ? min : (value > max ? max : value); }
+
+TerrainPatch::TerrainPatch() :
+    _terrain(NULL), _row(0), _column(0), _materialDirty(true)
+{
+}
+
+TerrainPatch::~TerrainPatch()
+{
+    for (size_t i = 0, count = _levels.size(); i < count; ++i)
+    {
+        Level* level = _levels[i];
+
+        SAFE_RELEASE(level->model);
+        SAFE_DELETE(level);
+    }
+
+    while (_layers.size() > 0)
+    {
+        deleteLayer(*_layers.begin());
+    }
+}
+
+TerrainPatch* TerrainPatch::create(Terrain* terrain,
+    unsigned int row, unsigned int column,
+    float* heights, unsigned int width, unsigned int height,
+    unsigned int x1, unsigned int z1, unsigned int x2, unsigned int z2,
+    float xOffset, float zOffset,
+    unsigned int maxStep, float verticalSkirtSize)
+{
+    // Create patch
+    TerrainPatch* patch = new TerrainPatch();
+    patch->_terrain = terrain;
+    patch->_row = row;
+    patch->_column = column;
+
+    // Add patch lods
+    for (unsigned int step = 1; step <= maxStep; step *= 2)
+    {
+        patch->addLOD(heights, width, height, x1, z1, x2, z2, xOffset, zOffset, step, verticalSkirtSize);
+    }
+
+    // Set our bounding box using the base LOD mesh
+    BoundingBox& bounds = patch->_boundingBox;
+    bounds.set(patch->_levels[0]->model->getMesh()->getBoundingBox());
+
+    // Apply the terrain's local scale to our bounds
+    const Vector3& localScale = terrain->_localScale;
+    if (!localScale.isOne())
+    {
+        bounds.min.set(bounds.min.x * localScale.x, bounds.min.y * localScale.y, bounds.min.z * localScale.z);
+        bounds.max.set(bounds.max.x * localScale.x, bounds.max.y * localScale.y, bounds.max.z * localScale.z);
+    }
+
+    return patch;
+}
+
+void TerrainPatch::addLOD(float* heights, unsigned int width, unsigned int height,
+    unsigned int x1, unsigned int z1, unsigned int x2, unsigned int z2,
+    float xOffset, float zOffset,
+    unsigned int step, float verticalSkirtSize)
+{
+    // Allocate vertex data for this patch
+    unsigned int patchWidth;
+    unsigned int patchHeight;
+    if (step == 1)
+    {
+        patchWidth = (x2 - x1) + 1;
+        patchHeight = (z2 - z1) + 1;
+    }
+    else
+    {
+        patchWidth = (x2 - x1) / step + ((x2 - x1) %step == 0 ? 0 : 1) + 1;
+        patchHeight = (z2 - z1) / step + ((z2 - z1) % step == 0 ? 0 : 1) + 1;
+    }
+
+    if (patchWidth < 2 || patchHeight < 2)
+        return; // ignore this level, not enough geometry
+
+    if (verticalSkirtSize > 0.0f)
+    {
+        patchWidth += 2;
+        patchHeight += 2;
+    }
+
+    unsigned int vertexCount = patchHeight * patchWidth;
+    unsigned int vertexElements = _terrain->_normalMap ? 5 : 8; //<x,y,z>[i,j,k]<u,v>
+    float* vertices = new float[vertexCount * vertexElements];
+    unsigned int index = 0;
+    Vector3 min(FLT_MAX, FLT_MAX, FLT_MAX);
+    Vector3 max(-FLT_MAX, -FLT_MAX, -FLT_MAX);
+    bool zskirt = verticalSkirtSize > 0 ? true : false;
+    for (unsigned int z = z1; ; )
+    {
+        bool xskirt = verticalSkirtSize > 0 ? true : false;
+        for (unsigned int x = x1; ; )
+        {
+            GP_ASSERT(index < vertexCount);
+
+            float* v = vertices + (index * vertexElements);
+            index++;
+
+            // Compute position
+            v[0] = x + xOffset;
+            v[1] = calculateHeight(heights, width, height, x, z);
+            if (xskirt || zskirt)
+                v[1] -= verticalSkirtSize;
+            v[2] = z + zOffset;
+
+            // Update bounding box min/max (don't include vertical skirt vertices in bounding box)
+            if (!(xskirt || zskirt))
+            {
+                if (v[0] < min.x)
+                    min.x = v[0];
+                if (v[1] < min.y)
+                    min.y = v[1];
+                if (v[2] < min.z)
+                    min.z = v[2];
+                if (v[0] > max.x)
+                    max.x = v[0];
+                if (v[1] > max.y)
+                    max.y = v[1];
+                if (v[2] > max.z)
+                    max.z = v[2];
+            }
+            v += 3;
+
+            // Compute normal
+            if (!_terrain->_normalMap)
+            {
+                Vector3 p(x, calculateHeight(heights, width, height, x, z), z);
+                Vector3 w(Vector3(x>=step ? x-step : x, calculateHeight(heights, width, height, x>=step ? x-step : x, z), z), p);
+                Vector3 e(Vector3(x<width-step ? x+step : x, calculateHeight(heights, width, height, x<width-step ? x+step : x, z), z), p);
+                Vector3 s(Vector3(x, calculateHeight(heights, width, height, x, z>=step ? z-step : z), z>=step ? z-step : z), p);
+                Vector3 n(Vector3(x, calculateHeight(heights, width, height, x, z<height-step ? z+step : z), z<height-step ? z+step : z), p);
+                Vector3 normals[4];
+                Vector3::cross(n, w, &normals[0]);
+                Vector3::cross(w, s, &normals[1]);
+                Vector3::cross(e, n, &normals[2]);
+                Vector3::cross(s, e, &normals[3]);
+                Vector3 normal = -(normals[0] + normals[1] + normals[2] + normals[3]);
+                normal.normalize();
+                v[0] = normal.x;
+                v[1] = normal.y;
+                v[2] = normal.z;
+                v += 3;
+            }
+
+            // Compute texture coord
+            v[0] = (float)x / width;
+            v[1] = 1.0f - (float)z / height;
+            if (xskirt)
+            {
+                float offset = verticalSkirtSize / width;
+                v[0] = x == x1 ? v[0]-offset : v[0]+offset;
+            }
+            else if (zskirt)
+            {
+                float offset = verticalSkirtSize / height;
+                v[1] = z == z1 ? v[1]-offset : v[1]+offset;
+            }
+
+            if (x == x2)
+            {
+                if ((verticalSkirtSize == 0) || xskirt)
+                    break;
+                else
+                    xskirt = true;
+            }
+            else if (xskirt)
+            {
+                xskirt = false;
+            }
+            else
+            {
+                x = std::min(x + step, x2);
+            }
+        }
+
+        if (z == z2)
+        {
+            if ((verticalSkirtSize == 0) || zskirt)
+                break;
+            else
+                zskirt = true;
+        }
+        else if (zskirt)
+        {
+            zskirt = false;
+        }
+        else
+        {
+            z = std::min(z + step, z2);
+        }
+    }
+    GP_ASSERT(index == vertexCount);
+
+    Vector3 center(min + ((max - min) * 0.5f));
+
+    // Create mesh
+    VertexFormat::Element elements[3];
+    elements[0] = VertexFormat::Element(VertexFormat::POSITION, 3);
+    if (_terrain->_normalMap)
+    {
+        elements[1] = VertexFormat::Element(VertexFormat::TEXCOORD0, 2);
+    }
+    else
+    {
+        elements[1] = VertexFormat::Element(VertexFormat::NORMAL, 3);
+        elements[2] = VertexFormat::Element(VertexFormat::TEXCOORD0, 2);
+    }
+    VertexFormat format(elements, _terrain->_normalMap ? 2 : 3);
+    Mesh* mesh = Mesh::createMesh(format, vertexCount);
+    mesh->setVertexData(vertices);
+    mesh->setBoundingBox(BoundingBox(min, max));
+    mesh->setBoundingSphere(BoundingSphere(center, center.distance(max)));
+
+    // Add mesh part for indices
+    unsigned int indexCount =
+        (patchWidth * 2) *      // # indices per row of tris
+        (patchHeight - 1) +     // # rows of tris
+        (patchHeight-2) * 2;    // # degenerate tris
+
+    // Support a maximum number of indices of USHRT_MAX. Any more indices we will require breaking up the
+    // terrain into smaller patches.
+    if (indexCount > USHRT_MAX)
+    {
+        GP_WARN("Index count of %d for terrain patch exceeds the limit of 65535. Please specifiy a smaller patch size.", indexCount);
+        GP_ASSERT(indexCount <= USHRT_MAX);
+    }
+
+    MeshPart* part = mesh->addPart(Mesh::TRIANGLE_STRIP, Mesh::INDEX16, indexCount);
+    unsigned short* indices = new unsigned short[indexCount];
+    index = 0;
+    for (unsigned int z = 0; z < patchHeight-1; ++z)
+    {
+        unsigned int i1 = z * patchWidth;
+        unsigned int i2 = (z+1) * patchWidth;
+
+        // Move left to right for even rows and right to left for odd rows.
+        // Note that this results in two degenerate triangles between rows
+        // for stitching purposes, but actually does not require any extra
+        // indices to achieve this.
+        if (z % 2 == 0)
+        {
+            if (z > 0)
+            {
+                // Add degenerate indices to connect strips
+                indices[index] = indices[index-1];
+                ++index;
+                indices[index++] = i1;
+            }
+
+            // Add row strip
+            for (unsigned int x = 0; x < patchWidth; ++x)
+            {
+                indices[index++] = i1 + x;
+                indices[index++] = i2 + x;
+            }
+        }
+        else
+        {
+            // Add degenerate indices to connect strips
+            if (z > 0)
+            {
+                indices[index] = indices[index-1];
+                ++index;
+                indices[index++] = i2 + ((int)patchWidth-1);
+            }
+
+            // Add row strip
+            for (int x = (int)patchWidth-1; x >= 0; --x)
+            {
+                indices[index++] = i2 + x;
+                indices[index++] = i1 + x;
+            }
+        }
+    }
+    GP_ASSERT(index == indexCount);
+    part->setIndexData(indices, 0, indexCount);
+
+    SAFE_DELETE_ARRAY(vertices);
+    SAFE_DELETE_ARRAY(indices);
+
+    // Create model
+    Model* model = Model::create(mesh);
+    mesh->release();
+
+    // Add this level
+    Level* level = new Level();
+    level->model = model;
+    _levels.push_back(level);
+}
+
+void TerrainPatch::deleteLayer(Layer* layer)
+{
+    // Release layer samplers
+    if (layer->textureIndex != -1)
+    {
+        if (_samplers[layer->textureIndex]->getRefCount() == 1)
+        {
+            SAFE_RELEASE(_samplers[layer->textureIndex]);
+        }
+        else
+        {
+            _samplers[layer->textureIndex]->release();
+        }
+    }
+
+    if (layer->blendIndex != -1)
+    {
+        if (_samplers[layer->blendIndex]->getRefCount() == 1)
+        {
+            SAFE_RELEASE(_samplers[layer->blendIndex]);
+        }
+        else
+        {
+            _samplers[layer->blendIndex]->release();
+        }
+    }
+
+    _layers.erase(layer);
+    SAFE_DELETE(layer);
+}
+
+int TerrainPatch::addSampler(const char* path)
+{
+    // TODO: Support shared samplers stored in Terrain class for layers that span all patches
+    // on the terrain (row == col == -1).
+
+    // Load the texture. If this texture is already loaded, it will return
+    // a pointer to the same one, with its ref count incremented.
+    Texture* texture = Texture::create(path, true);
+    if (!texture)
+        return -1;
+
+    int firstAvailableIndex = -1;
+    for (size_t i = 0, count = _samplers.size(); i < count; ++i)
+    {
+        Texture::Sampler* sampler = _samplers[i];
+
+        if (sampler == NULL && firstAvailableIndex == -1)
+        {
+            firstAvailableIndex = (int)i;
+        }
+        else if (sampler->getTexture() == texture)
+        {
+            // A sampler was already added for this texture.
+            // Increase the ref count for the sampler to indicate that a new
+            // layer will be referencing it.
+            texture->release();
+            sampler->addRef();
+            return (int)i;
+        }
+    }
+
+    // Add a new sampler to the list
+    Texture::Sampler* sampler = Texture::Sampler::create(texture);
+    texture->release();
+    sampler->setWrapMode(Texture::REPEAT, Texture::REPEAT);
+    sampler->setFilterMode(Texture::LINEAR_MIPMAP_LINEAR, Texture::LINEAR);
+    if (firstAvailableIndex != -1)
+    {
+        _samplers[firstAvailableIndex] = sampler;
+        return firstAvailableIndex;
+    }
+
+    _samplers.push_back(sampler);
+    return (int)(_samplers.size()-1);
+}
+
+bool TerrainPatch::setLayer(int index, const char* texturePath, const Vector2& textureRepeat, const char* blendPath, int blendChannel)
+{
+    // If there is an existing layer at this index, delete it
+    for (std::set<Layer*, LayerCompare>::iterator itr = _layers.begin(); itr != _layers.end(); ++itr)
+    {
+        Layer* layer = *itr;
+        if (layer->index == index)
+        {
+            deleteLayer(layer);
+            break;
+        }
+    }
+
+    // Load texture sampler
+    int textureIndex = addSampler(texturePath);
+    if (textureIndex == -1)
+        return false;
+
+    // Load blend sampler
+    int blendIndex = -1;
+    if (blendPath)
+    {
+        blendIndex = addSampler(blendPath);
+    }
+
+    // Create the layer
+    Layer* layer = new Layer();
+    layer->index = index;
+    layer->textureIndex = textureIndex;
+    layer->textureRepeat = textureRepeat;
+    layer->blendIndex = blendIndex;
+    layer->blendChannel = blendChannel;
+
+    _layers.insert(layer);
+
+    _materialDirty = true;
+
+    return true;
+}
+
+bool TerrainPatch::updateMaterial()
+{
+    if (!_materialDirty)
+        return true;
+
+    _materialDirty = false;
+
+    for (size_t i = 0, count = _levels.size(); i < count; ++i)
+    {
+        // Build preprocessor string to pass to shader.
+        // NOTE: I make heavy use of preprocessor definitions, rather than passing in arrays and doing
+        // non-constant array access in the shader. This is due to the fact that non-constant array access
+        // in GLES is very slow on some GLES 2.x hardware.
+        std::ostringstream defines;
+        defines << "LAYER_COUNT " << _layers.size();
+        defines << ";SAMPLER_COUNT " << _samplers.size();
+        if (_terrain->isFlagSet(Terrain::DEBUG_PATCHES))
+            defines << ";DEBUG_PATCHES";
+        if (_terrain->_normalMap)
+            defines << ";NORMAL_MAP";
+
+        // Append texture and blend index constants to preprocessor definition.
+        // We need to do this since older versions of GLSL only allow sampler arrays
+        // to be indexed using constant expressions (otherwise we could simply pass an
+        // array of indices to use for sampler lookup).
+        //
+        // Rebuild layer lists while we're at it.
+        //
+        int layerIndex = 0;
+        for (std::set<Layer*, LayerCompare>::iterator itr = _layers.begin(); itr != _layers.end(); ++itr, ++layerIndex)
+        {
+            Layer* layer = *itr;
+
+            defines << ";TEXTURE_INDEX_" << layerIndex << " " << layer->textureIndex;
+            defines << ";TEXTURE_REPEAT_" << layerIndex << " vec2(" << layer->textureRepeat.x << "," << layer->textureRepeat.y << ")";
+
+            if (layerIndex > 0)
+            {
+                defines << ";BLEND_INDEX_" << layerIndex << " " << layer->blendIndex;
+                defines << ";BLEND_CHANNEL_" << layerIndex << " " << layer->blendChannel;
+            }
+        }
+
+        Material* material = Material::create("res/shaders/terrain.vert", "res/shaders/terrain.frag", defines.str().c_str());
+        if (!material)
+            return false;
+        material->getStateBlock()->setCullFace(true);
+        material->getStateBlock()->setDepthTest(true);
+
+        // Set material parameter bindings
+        material->getParameter("u_worldViewProjectionMatrix")->bindValue(_terrain, &Terrain::getWorldViewProjectionMatrix);
+        if (!_terrain->_normalMap)
+            material->getParameter("u_normalMatrix")->bindValue(_terrain, &Terrain::getNormalMatrix);
+        material->getParameter("u_ambientColor")->bindValue(this, &TerrainPatch::getAmbientColor);
+        material->getParameter("u_lightColor")->bindValue(this, &TerrainPatch::getLightColor);
+        material->getParameter("u_lightDirection")->bindValue(this, &TerrainPatch::getLightDirection);
+        if (_layers.size() > 0)
+        {
+            material->getParameter("u_samplers")->setValue((const Texture::Sampler**)&_samplers[0], (unsigned int)_samplers.size());
+            if (_terrain->_normalMap)
+                material->getParameter("u_normalMap")->setValue(_terrain->_normalMap);
+        }
+
+        if (_terrain->isFlagSet(Terrain::DEBUG_PATCHES))
+        {
+            material->getParameter("u_row")->setValue((float)_row);
+            material->getParameter("u_column")->setValue((float)_column);
+        }
+
+        // Set material on this lod level
+        _levels[i]->model->setMaterial(material);
+
+        material->release();
+    }
+
+    return true;
+}
+
+void TerrainPatch::draw(bool wireframe)
+{
+    Scene* scene = _terrain->_node ? _terrain->_node->getScene() : NULL;
+    Camera* camera = scene ? scene->getActiveCamera() : NULL;
+    if (!camera)
+        return;
+
+    // Get our world-space bounding box
+    BoundingBox bounds = getBoundingBox(true);
+
+    // If the box does not intersect the view frustum, cull it
+    if (_terrain->isFlagSet(Terrain::ENABLE_FRUSTUM_CULLING) && !camera->getFrustum().intersects(bounds))
+        return;
+
+    if (!updateMaterial())
+        return;
+
+    // Compute the LOD level from the camera's perspective
+    size_t lod = computeLOD(camera, bounds);
+
+    // Draw the model for the current LOD
+    _levels[lod]->model->draw(wireframe);
+}
+
+bool TerrainPatch::isVisible() const
+{
+    // If frustum culling is disabled, assume the patch is always visible
+    if ((_terrain->_flags & Terrain::ENABLE_FRUSTUM_CULLING) == 0)
+        return true;
+
+    Scene* scene = _terrain->_node ? _terrain->_node->getScene() : NULL;
+    Camera* camera = scene ? scene->getActiveCamera() : NULL;
+    if (!camera)
+        return false;
+
+    // Does the current camera view frustum intersect our world bounds?
+    return camera->getFrustum().intersects(getBoundingBox(true));
+}
+
+unsigned int TerrainPatch::getTriangleCount() const
+{
+    // Patches are made up of a single mesh part using triangle strips
+    return _levels[0]->model->getMesh()->getPart(0)->getIndexCount() - 2;
+}
+
+unsigned int TerrainPatch::getVisibleTriangleCount() const
+{
+    Scene* scene = _terrain->_node ? _terrain->_node->getScene() : NULL;
+    Camera* camera = scene ? scene->getActiveCamera() : NULL;
+    if (!camera)
+        return 0;
+
+    // Does the current camera intersect this patch at all?
+    BoundingBox bounds = getBoundingBox(true);
+    if (!camera->getFrustum().intersects(bounds))
+        return 0;
+
+    // Return the triangle count of the LOD level depending on the camera
+    size_t lod = computeLOD(camera, bounds);
+    return _levels[lod]->model->getMesh()->getPart(0)->getIndexCount() - 2;
+}
+
+BoundingBox TerrainPatch::getBoundingBox(bool worldSpace) const
+{
+    if (!worldSpace)
+        return _boundingBox;
+
+    // Apply a world-space transformation to our bounding box
+    BoundingBox bounds(_boundingBox);
+
+    // Transform the bounding box by the terrain node's world transform.
+    // We don't use Terrain::getWorldMatrix because that returns a matrix
+    // that has terrain->_localScale factored in - and our patche's bounding
+    // box already has local scale factored in.
+    if (_terrain->_node)
+        bounds.transform(_terrain->_node->getWorldMatrix());
+
+    return bounds;
+}
+
+const Vector3& TerrainPatch::getAmbientColor() const
+{
+    Scene* scene = _terrain->_node ? _terrain->_node->getScene() : NULL;
+    return scene ? scene->getAmbientColor() : Vector3::zero();
+}
+
+const Vector3& TerrainPatch::getLightColor() const
+{
+    Scene* scene = _terrain->_node ? _terrain->_node->getScene() : NULL;
+    return scene ? scene->getLightColor() : Vector3::one();
+}
+
+const Vector3& TerrainPatch::getLightDirection() const
+{
+    Scene* scene = _terrain->_node ? _terrain->_node->getScene() : NULL;
+    if (!scene)
+    {
+        static Vector3 down(0, -1, 0);
+        return down;
+    }
+
+    return scene->getLightDirection();
+}
+
+size_t TerrainPatch::computeLOD(Camera* camera, const BoundingBox& worldBounds) const
+{
+    if (!_terrain->isFlagSet(Terrain::ENABLE_LEVEL_OF_DETAIL) || _levels.size() == 0)
+        return 0; // base level
+
+    // Compute LOD to use based on very simple distance metric.
+    // TODO: Optimize this.
+    Game* game = Game::getInstance();
+    Rectangle vp(0, 0, game->getWidth(), game->getHeight());
+    Vector3 corners[8];
+    Vector2 min(FLT_MAX, FLT_MAX);
+    Vector2 max(-FLT_MAX, -FLT_MAX);
+    worldBounds.getCorners(corners);
+    for (unsigned int i = 0; i < 8; ++i)
+    {
+        const Vector3& corner = corners[i];
+        float x, y;
+        camera->project(vp, corners[i], &x, &y);
+        if (x < min.x)
+            min.x = x;
+        if (y < min.y)
+            min.y = y;
+        if (x > max.x)
+            max.x = x;
+        if (y > max.y)
+            max.y = y;
+    }
+    float area = (max.x - min.x) * (max.y - min.y);
+    float screenArea = game->getWidth() * game->getHeight() / 10.0f;
+    float error = screenArea / area;
+
+    // Level LOD based on distance from camera
+    size_t maxLod = _levels.size()-1;
+    size_t lod = (size_t)error;
+    lod = std::max(lod, (size_t)0);
+    lod = std::min(lod, maxLod);
+    return lod;
+}
+
+float calculateHeight(float* heights, unsigned int width, unsigned int height, unsigned int x, unsigned int z)
+{
+    return heights[z * width + x];
+}
+
+TerrainPatch::Layer::Layer() :
+    index(0), row(-1), column(-1), textureIndex(-1), blendIndex(-1)
+{
+}
+
+TerrainPatch::Layer::~Layer()
+{
+}
+
+bool TerrainPatch::LayerCompare::operator() (const Layer* lhs, const Layer* rhs) const
+{
+    return (lhs->index < rhs->index);
+}
+
+}

+ 163 - 0
gameplay/src/TerrainPatch.h

@@ -0,0 +1,163 @@
+#ifndef TERRAINPATCH_H_
+#define TERRAINPATCH_H_
+
+#include "Model.h"
+#include "Camera.h"
+
+namespace gameplay
+{
+
+class Terrain;
+
+/**
+ * Represents a single patch for a Terrain.
+ *
+ * This is an internal class used exclusively by Terrain.
+ *
+ * @script{ignore}
+ */
+class TerrainPatch
+{
+    friend class Terrain;
+
+private:
+
+    struct Layer
+    {
+        Layer();
+        Layer(const Layer&);
+        ~Layer();
+        Layer& operator=(const Layer&);
+
+        int index;
+        int row;
+        int column;
+        int textureIndex;
+        Vector2 textureRepeat;
+        int blendIndex;
+        int blendChannel;
+    };
+
+    struct Level
+    {
+        Model* model;
+    };
+
+    struct LayerCompare
+    {
+        bool operator() (const Layer* lhs, const Layer* rhs) const;
+    };
+
+    /**
+     * Constructor.
+     */
+    TerrainPatch();
+
+    /**
+     * Hidden copy constructor.
+     */
+    TerrainPatch(const TerrainPatch&);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    TerrainPatch& operator=(const TerrainPatch&);
+
+    /**
+     * Destructor.
+     */
+    ~TerrainPatch();
+
+    /**
+     * Internal method to create new terrain patch.
+     */
+    static TerrainPatch* create(Terrain* terrain,
+        unsigned int row, unsigned int column,
+        float* heights, unsigned int width, unsigned int height,
+        unsigned int x1, unsigned int z1, unsigned int x2, unsigned int z2,
+        float xOffset, float zOffset,
+        unsigned int maxStep, float verticalSkirtSize);
+
+    /**
+     * Adds a single LOD level to the terrain patch.
+     */
+    void addLOD(float* heights, unsigned int width, unsigned int height,
+        unsigned int x1, unsigned int z1, unsigned int x2, unsigned int z2,
+        float xOffset, float zOffset,
+        unsigned int step, float verticalSkirtSize);
+
+    /**
+     * Sets details for a layer of this patch.
+     */
+    bool setLayer(int index, const char* texturePath, const Vector2& textureRepeat, const char* blendPath, int blendChannel);
+
+    /**
+     * Sets the level of detail for the terrain patch.
+     */
+    void setLod(unsigned int lod);
+
+    /**
+     * Adds a sampler to the patch.
+     */
+    int addSampler(const char* path);
+
+    /**
+     * Deletes the specified layer.
+     */
+    void deleteLayer(Layer* layer);
+
+    /**
+     * Determines whether this patch is current visible by the scene's active camera.
+     */
+    bool isVisible() const;
+
+    /**
+     * Returns the triangle count of the base LOD level of this terrain patch.
+     */
+    unsigned int getTriangleCount() const;
+
+    /**
+     * Returns the currently visible triangle count, taking the current LOD into account.
+     */
+    unsigned int getVisibleTriangleCount() const;
+
+    /**
+     * Draws the terrain patch.
+     */
+    void draw(bool wireframe);
+
+    /**
+     * Updates the material for the patch.
+     */
+    bool updateMaterial();
+
+    /**
+     * Computes the current LOD for this patch, from the viewpoint of the specified camera.
+     */
+    size_t computeLOD(Camera* camera, const BoundingBox& worldBounds) const;
+
+    /**
+     * Returns the local bounding box for this patch, at the base LOD level.
+     */
+    BoundingBox getBoundingBox(bool worldSpace) const;
+
+    const Vector3& getAmbientColor() const;
+
+    const Vector3& getLightColor() const;
+
+    const Vector3& getLightDirection() const;
+
+    Terrain* _terrain;
+    std::vector<Level*> _levels;
+    unsigned int _row;
+    unsigned int _column;
+    std::set<Layer*, LayerCompare> _layers;
+    std::vector<Texture::Sampler*> _samplers;
+    bool _materialDirty;
+    BoundingBox _boundingBox;
+
+};
+
+}
+
+#endif

+ 151 - 33
gameplay/src/Texture.cpp

@@ -273,7 +273,7 @@ Texture* Texture::createCompressedPVRTC(const char* path)
     GLuint textureId;
     GL_ASSERT( glGenTextures(1, &textureId) );
     GL_ASSERT( glBindTexture(GL_TEXTURE_2D, textureId) );
-    GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipMapCount > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR) );
+    GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipMapCount > 1 ? GL_NEAREST_MIPMAP_LINEAR : GL_LINEAR) );
 
     Texture* texture = new Texture();
     texture->_handle = textureId;
@@ -477,6 +477,23 @@ GLubyte* Texture::readCompressedPVRTCLegacy(const char* path, Stream* stream, GL
     return data;
 }
 
+int getMaskByteIndex(unsigned int mask)
+{
+    switch (mask)
+    {
+    case 0xff000000:
+        return 3;
+    case 0x00ff0000:
+        return 2;
+    case 0x0000ff00:
+        return 1;
+    case 0x000000ff:
+        return 0;
+    default:
+        return -1; // no or invalid mask
+    }
+}
+
 Texture* Texture::createCompressedDDS(const char* path)
 {
     GP_ASSERT(path);
@@ -556,15 +573,17 @@ Texture* Texture::createCompressedDDS(const char* path)
     dds_mip_level* mipLevels = new dds_mip_level[header.dwMipMapCount];
     memset(mipLevels, 0, sizeof(dds_mip_level) * header.dwMipMapCount);
 
-    GLenum format, internalFormat;
+    GLenum format = 0;
+    GLenum internalFormat = 0;
     bool compressed = false;
     GLsizei width = header.dwWidth;
     GLsizei height = header.dwHeight;
-    int bytesPerBlock;
+    Texture::Format textureFormat = Texture::UNKNOWN;
 
     if (header.ddspf.dwFlags & 0x4/*DDPF_FOURCC*/)
     {
         compressed = true;
+        int bytesPerBlock;
 
         // Compressed.
         switch (header.ddspf.dwFourCC)
@@ -601,18 +620,20 @@ Texture* Texture::createCompressedDDS(const char* path)
 
         for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
         {
-            mipLevels[i].width = width;
-            mipLevels[i].height = height;
-            mipLevels[i].size =  std::max(1, (width+3) >> 2) * std::max(1, (height+3) >> 2) * bytesPerBlock;
-            mipLevels[i].data = new GLubyte[mipLevels[i].size];
+            dds_mip_level& level = mipLevels[i];
+
+            level.width = width;
+            level.height = height;
+            level.size =  std::max(1, (width+3) >> 2) * std::max(1, (height+3) >> 2) * bytesPerBlock;
+            level.data = new GLubyte[level.size];
 
-            if (stream->read(mipLevels[i].data, 1, mipLevels[i].size) != (unsigned int)mipLevels[i].size)
+            if (stream->read(level.data, 1, level.size) != (unsigned int)level.size)
             {
                 GP_ERROR("Failed to load dds compressed texture bytes for texture: %s", path);
                 
                 // Cleanup mip data.
                 for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
-                    SAFE_DELETE_ARRAY(mipLevels[i].data);
+                    SAFE_DELETE_ARRAY(level.data);
                 SAFE_DELETE_ARRAY(mipLevels);
                 return texture;
             }
@@ -621,21 +642,114 @@ Texture* Texture::createCompressedDDS(const char* path)
             height = std::max(1, height >> 1);
         }
     }
-    else if (header.ddspf.dwFlags == 0x40/*DDPF_RGB*/)
+    else if (header.ddspf.dwFlags & 0x40/*DDPF_RGB*/)
     {
-        // RGB (uncompressed)
-        // Note: Use GL_BGR as internal format to flip bytes.
-        GP_ERROR("Failed to create texture from DDS file '%s': uncompressed RGB format is not supported.", path);
-        SAFE_DELETE_ARRAY(mipLevels);
-        return NULL;
-    }
-    else if (header.ddspf.dwFlags == 0x41/*DDPF_RGB|DDPF_ALPHAPIXELS*/)
-    {
-        // RGBA (uncompressed)
-        // Note: Use GL_BGRA as internal format to flip bytes.
-        GP_ERROR("Failed to create texture from DDS file '%s': uncompressed RGBA format is not supported.", path);
-        SAFE_DELETE_ARRAY(mipLevels);
-        return NULL;
+        // RGB/RGBA (uncompressed)
+        bool colorConvert = false;
+        unsigned int rmask = header.ddspf.dwRBitMask;
+        unsigned int gmask = header.ddspf.dwGBitMask;
+        unsigned int bmask = header.ddspf.dwBBitMask;
+        unsigned int amask = header.ddspf.dwABitMask;
+        int ridx = getMaskByteIndex(rmask);
+        int gidx = getMaskByteIndex(gmask);
+        int bidx = getMaskByteIndex(bmask);
+        int aidx = getMaskByteIndex(amask);
+
+        if (header.ddspf.dwRGBBitCount == 24)
+        {
+            format = internalFormat = GL_RGB;
+            textureFormat = Texture::RGB;
+            colorConvert = (ridx != 0) || (gidx != 1) || (bidx != 2);
+        }
+        else if (header.ddspf.dwRGBBitCount == 32)
+        {
+            format = internalFormat = GL_RGBA;
+            textureFormat = Texture::RGBA;
+            if (ridx == 0 && gidx == 1 && bidx == 2)
+            {
+                aidx = 3; // XBGR or ABGR
+                colorConvert = false;
+            }
+            else if (ridx == 2 && gidx == 1 && bidx == 0)
+            {
+                aidx = 3; // XRGB or ARGB
+                colorConvert = true;
+            }
+            else
+            {
+                format = 0; // invalid format
+            }
+        }
+
+        if (format == 0)
+        {
+            GP_ERROR("Failed to create texture from uncompressed DDS file '%s': Unsupported color format (must be one of R8G8B8, A8R8G8B8, A8B8G8R8, X8R8G8B8, X8B8G8R8.", path);
+            SAFE_DELETE_ARRAY(mipLevels);
+            return NULL;
+        }
+
+        // Read data.
+        for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
+        {
+            dds_mip_level& level = mipLevels[i];
+
+            level.width = width;
+            level.height = height;
+            level.size =  width * height * (header.ddspf.dwRGBBitCount >> 3);
+            level.data = new GLubyte[level.size];
+
+            if (stream->read(level.data, 1, level.size) != (unsigned int)level.size)
+            {
+                GP_ERROR("Failed to load bytes for RGB dds texture: %s", path);
+
+                // Cleanup mip data.
+                for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
+                    SAFE_DELETE_ARRAY(level.data);
+                SAFE_DELETE_ARRAY(mipLevels);
+                return texture;
+            }
+
+            width  = std::max(1, width >> 1);
+            height = std::max(1, height >> 1);
+        }
+
+        // Perform color conversion.
+        if (colorConvert)
+        {
+            // Note: While it's possible to use BGRA_EXT texture formats here and avoid CPU color conversion below,
+            // there seems to be different flavors of the BGRA extension, with some vendors requiring an internal
+            // format of RGBA and others requiring an internal format of BGRA.
+            // We could be smarter here later and skip color conversion in favor of GL_BGRA_EXT (for format
+            // and/or internal format) based on which GL extensions are available.
+            // Tip: Using A8B8G8R8 and X8B8G8R8 DDS format maps directly to GL RGBA and requires on no color conversion.
+            GLubyte *pixel, r, g, b, a;
+            if (format == GL_RGB)
+            {
+                for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
+                {
+                    dds_mip_level& level = mipLevels[i];
+                    for (int j = 0; j < level.size; j += 3)
+                    {
+                        pixel = &level.data[j];
+                        r = pixel[ridx]; g = pixel[gidx]; b = pixel[bidx];
+                        pixel[0] = r; pixel[1] = g; pixel[2] = b;
+                    }
+                }
+            }
+            else if (format == GL_RGBA)
+            {
+                for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
+                {
+                    dds_mip_level& level = mipLevels[i];
+                    for (int j = 0; j < level.size; j += 4)
+                    {
+                        pixel = &level.data[j];
+                        r = pixel[ridx]; g = pixel[gidx]; b = pixel[bidx]; a = pixel[aidx];
+                        pixel[0] = r; pixel[1] = g; pixel[2] = b; pixel[3] = a;
+                    }
+                }
+            }
+        }
     }
     else
     {
@@ -644,7 +758,7 @@ Texture* Texture::createCompressedDDS(const char* path)
         SAFE_DELETE_ARRAY(mipLevels);
         return NULL;
     }
-    
+
     // Close file.
     stream->close();
 
@@ -652,7 +766,7 @@ Texture* Texture::createCompressedDDS(const char* path)
     GLuint textureId;
     GL_ASSERT( glGenTextures(1, &textureId) );
     GL_ASSERT( glBindTexture(GL_TEXTURE_2D, textureId) );
-    GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, header.dwMipMapCount > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ) );
+    GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, header.dwMipMapCount > 1 ? GL_NEAREST_MIPMAP_LINEAR : GL_LINEAR ) );
 
     // Create gameplay texture.
     texture = new Texture();
@@ -665,18 +779,18 @@ Texture* Texture::createCompressedDDS(const char* path)
     // Load texture data.
     for (unsigned int i = 0; i < header.dwMipMapCount; ++i)
     {
+        dds_mip_level& level = mipLevels[i];
         if (compressed)
         {
-            GL_ASSERT( glCompressedTexImage2D(GL_TEXTURE_2D, i, format, mipLevels[i].width, mipLevels[i].height, 0, mipLevels[i].size, mipLevels[i].data) );
+            GL_ASSERT( glCompressedTexImage2D(GL_TEXTURE_2D, i, format, level.width, level.height, 0, level.size, level.data) );
         }
         else
         {
-            // TODO: For uncompressed formats, set GL_UNPACK_ALIGNMENT based on stride
-            GL_ASSERT( glTexImage2D(GL_TEXTURE_2D, i, internalFormat, mipLevels[i].width, mipLevels[i].height, 0, format, GL_UNSIGNED_INT, mipLevels[i].data) );
+            GL_ASSERT( glTexImage2D(GL_TEXTURE_2D, i, internalFormat, level.width, level.height, 0, format, GL_UNSIGNED_BYTE, level.data) );
         }
 
         // Clean up the texture data.
-        SAFE_DELETE_ARRAY(mipLevels[i].data);
+        SAFE_DELETE_ARRAY(level.data);
     }
 
     // Clean up mip levels structure.
@@ -690,6 +804,11 @@ Texture::Format Texture::getFormat() const
     return _format;
 }
 
+const char* Texture::getPath() const
+{
+    return _path.c_str();
+}
+
 unsigned int Texture::getWidth() const
 {
     return _width;
@@ -724,6 +843,7 @@ void Texture::generateMipmaps()
     if (!_mipmapped)
     {
         GL_ASSERT( glBindTexture(GL_TEXTURE_2D, _handle) );
+        GL_ASSERT( glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST) );
         GL_ASSERT( glGenerateMipmap(GL_TEXTURE_2D) );
 
         _mipmapped = true;
@@ -769,12 +889,14 @@ void Texture::Sampler::setWrapMode(Wrap wrapS, Wrap wrapT)
 {
     _wrapS = wrapS;
     _wrapT = wrapT;
+    _texture->setWrapMode(wrapS, wrapT);
 }
 
 void Texture::Sampler::setFilterMode(Filter minificationFilter, Filter magnificationFilter)
 {
     _minFilter = minificationFilter;
     _magFilter = magnificationFilter;
+    _texture->setFilterMode(minificationFilter, magnificationFilter);
 }
 
 Texture* Texture::Sampler::getTexture() const
@@ -787,10 +909,6 @@ void Texture::Sampler::bind()
     GP_ASSERT(_texture);
 
     GL_ASSERT( glBindTexture(GL_TEXTURE_2D, _texture->_handle) );
-    GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, (GLenum)_wrapS) );
-    GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, (GLenum)_wrapT) );
-    GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLenum)_minFilter) );
-    GL_ASSERT( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLenum)_magFilter) );
 }
 
 }

+ 7 - 0
gameplay/src/Texture.h

@@ -199,6 +199,13 @@ public:
      */
     static Texture* create(TextureHandle handle, int width, int height, Format format = UNKNOWN);
 
+    /**
+     * Returns the path that the texture was originally loaded from (if applicable).
+     *
+     * @return The texture path, or an empty string if the texture was not loaded from file.
+     */
+    const char* getPath() const;
+
     /**
      * Gets the format of the texture.
      *

+ 3 - 0
gameplay/src/gameplay.h

@@ -29,6 +29,7 @@
 
 // Graphics
 #include "Texture.h"
+#include "Image.h"
 #include "Mesh.h"
 #include "MeshPart.h"
 #include "Effect.h"
@@ -37,6 +38,8 @@
 #include "VertexFormat.h"
 #include "VertexAttributeBinding.h"
 #include "Model.h"
+#include "HeightField.h"
+#include "Terrain.h"
 #include "Camera.h"
 #include "Light.h"
 #include "Scene.h"

+ 1 - 1
gameplay/src/lua/lua_AIController.cpp

@@ -43,7 +43,7 @@ int lua_AIController_findAgent(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 AIController* instance = getInstance(state);
                 void* returnPtr = (void*)instance->findAgent(param1);

+ 3 - 3
gameplay/src/lua/lua_AIMessage.cpp

@@ -680,7 +680,7 @@ int lua_AIMessage_setString(lua_State* state)
                 unsigned int param1 = (unsigned int)luaL_checkunsigned(state, 2);
 
                 // Get parameter 2 off the stack.
-                ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(3, false);
+                const char* param2 = ScriptUtil::getString(3, false);
 
                 AIMessage* instance = getInstance(state);
                 instance->setString(param1, param2);
@@ -721,10 +721,10 @@ int lua_AIMessage_static_create(lua_State* state)
                 unsigned int param1 = (unsigned int)luaL_checkunsigned(state, 1);
 
                 // Get parameter 2 off the stack.
-                ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(2, false);
+                const char* param2 = ScriptUtil::getString(2, false);
 
                 // Get parameter 3 off the stack.
-                ScriptUtil::LuaArray<const char> param3 = ScriptUtil::getString(3, false);
+                const char* param3 = ScriptUtil::getString(3, false);
 
                 // Get parameter 4 off the stack.
                 unsigned int param4 = (unsigned int)luaL_checkunsigned(state, 4);

+ 1 - 1
gameplay/src/lua/lua_AIState.cpp

@@ -349,7 +349,7 @@ int lua_AIState_static_create(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 void* returnPtr = (void*)AIState::create(param1);
                 if (returnPtr)

+ 3 - 3
gameplay/src/lua/lua_AIStateMachine.cpp

@@ -51,7 +51,7 @@ int lua_AIStateMachine_addState(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     AIStateMachine* instance = getInstance(state);
                     void* returnPtr = (void*)instance->addState(param1);
@@ -206,7 +206,7 @@ int lua_AIStateMachine_getState(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 AIStateMachine* instance = getInstance(state);
                 void* returnPtr = (void*)instance->getState(param1);
@@ -298,7 +298,7 @@ int lua_AIStateMachine_setState(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     AIStateMachine* instance = getInstance(state);
                     void* returnPtr = (void*)instance->setState(param1);

+ 6 - 6
gameplay/src/lua/lua_Animation.cpp

@@ -132,7 +132,7 @@ int lua_Animation_createClip(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 unsigned long param2 = (unsigned long)luaL_checkunsigned(state, 3);
@@ -186,7 +186,7 @@ int lua_Animation_createClips(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Animation* instance = getInstance(state);
                 instance->createClips(param1);
@@ -253,7 +253,7 @@ int lua_Animation_getClip(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     Animation* instance = getInstance(state);
                     void* returnPtr = (void*)instance->getClip(param1);
@@ -483,7 +483,7 @@ int lua_Animation_pause(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Animation* instance = getInstance(state);
                 instance->pause(param1);
@@ -533,7 +533,7 @@ int lua_Animation_play(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Animation* instance = getInstance(state);
                 instance->play(param1);
@@ -615,7 +615,7 @@ int lua_Animation_stop(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Animation* instance = getInstance(state);
                 instance->stop(param1);

+ 3 - 3
gameplay/src/lua/lua_AnimationClip.cpp

@@ -134,7 +134,7 @@ int lua_AnimationClip_addBeginListener(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     AnimationClip* instance = getInstance(state);
                     instance->addBeginListener(param1);
@@ -191,7 +191,7 @@ int lua_AnimationClip_addEndListener(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     AnimationClip* instance = getInstance(state);
                     instance->addEndListener(param1);
@@ -253,7 +253,7 @@ int lua_AnimationClip_addListener(lua_State* state)
                     lua_type(state, 3) == LUA_TNUMBER)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     unsigned long param2 = (unsigned long)luaL_checkunsigned(state, 3);

+ 9 - 9
gameplay/src/lua/lua_AnimationTarget.cpp

@@ -55,10 +55,10 @@ int lua_AnimationTarget_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
-                    ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(3, false);
+                    const char* param2 = ScriptUtil::getString(3, false);
 
                     AnimationTarget* instance = getInstance(state);
                     void* returnPtr = (void*)instance->createAnimation(param1, param2);
@@ -86,7 +86,7 @@ int lua_AnimationTarget_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -130,7 +130,7 @@ int lua_AnimationTarget_createAnimation(lua_State* state)
                     (lua_type(state, 7) == LUA_TSTRING || lua_type(state, 7) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -185,7 +185,7 @@ int lua_AnimationTarget_createAnimation(lua_State* state)
                     (lua_type(state, 9) == LUA_TSTRING || lua_type(state, 9) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -260,7 +260,7 @@ int lua_AnimationTarget_createAnimationFromBy(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -328,7 +328,7 @@ int lua_AnimationTarget_createAnimationFromTo(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -405,7 +405,7 @@ int lua_AnimationTarget_destroyAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 AnimationTarget* instance = getInstance(state);
                 instance->destroyAnimation(param1);
@@ -467,7 +467,7 @@ int lua_AnimationTarget_getAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 AnimationTarget* instance = getInstance(state);
                 void* returnPtr = (void*)instance->getAnimation(param1);

+ 1 - 1
gameplay/src/lua/lua_AudioSource.cpp

@@ -747,7 +747,7 @@ int lua_AudioSource_static_create(lua_State* state)
                 if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                    const char* param1 = ScriptUtil::getString(1, false);
 
                     void* returnPtr = (void*)AudioSource::create(param1);
                     if (returnPtr)

+ 6 - 6
gameplay/src/lua/lua_Bundle.cpp

@@ -130,7 +130,7 @@ int lua_Bundle_contains(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Bundle* instance = getInstance(state);
                 bool result = instance->contains(param1);
@@ -278,7 +278,7 @@ int lua_Bundle_loadFont(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Bundle* instance = getInstance(state);
                 void* returnPtr = (void*)instance->loadFont(param1);
@@ -326,7 +326,7 @@ int lua_Bundle_loadMesh(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Bundle* instance = getInstance(state);
                 void* returnPtr = (void*)instance->loadMesh(param1);
@@ -374,7 +374,7 @@ int lua_Bundle_loadNode(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Bundle* instance = getInstance(state);
                 void* returnPtr = (void*)instance->loadNode(param1);
@@ -448,7 +448,7 @@ int lua_Bundle_loadScene(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Bundle* instance = getInstance(state);
                 void* returnPtr = (void*)instance->loadScene(param1);
@@ -527,7 +527,7 @@ int lua_Bundle_static_create(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 void* returnPtr = (void*)Bundle::create(param1);
                 if (returnPtr)

+ 18 - 18
gameplay/src/lua/lua_Button.cpp

@@ -308,10 +308,10 @@ int lua_Button_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
-                    ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(3, false);
+                    const char* param2 = ScriptUtil::getString(3, false);
 
                     Button* instance = getInstance(state);
                     void* returnPtr = (void*)instance->createAnimation(param1, param2);
@@ -339,7 +339,7 @@ int lua_Button_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -383,7 +383,7 @@ int lua_Button_createAnimation(lua_State* state)
                     (lua_type(state, 7) == LUA_TSTRING || lua_type(state, 7) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -438,7 +438,7 @@ int lua_Button_createAnimation(lua_State* state)
                     (lua_type(state, 9) == LUA_TSTRING || lua_type(state, 9) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -513,7 +513,7 @@ int lua_Button_createAnimationFromBy(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -581,7 +581,7 @@ int lua_Button_createAnimationFromTo(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -658,7 +658,7 @@ int lua_Button_destroyAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Button* instance = getInstance(state);
                 instance->destroyAnimation(param1);
@@ -755,7 +755,7 @@ int lua_Button_getAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Button* instance = getInstance(state);
                 void* returnPtr = (void*)instance->getAnimation(param1);
@@ -1579,7 +1579,7 @@ int lua_Button_getImageColor(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -1631,7 +1631,7 @@ int lua_Button_getImageRegion(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -1683,7 +1683,7 @@ int lua_Button_getImageUVs(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -3395,7 +3395,7 @@ int lua_Button_setImageColor(lua_State* state)
                 (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3424,7 +3424,7 @@ int lua_Button_setImageColor(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3473,7 +3473,7 @@ int lua_Button_setImageRegion(lua_State* state)
                 (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3502,7 +3502,7 @@ int lua_Button_setImageRegion(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -4002,7 +4002,7 @@ int lua_Button_setText(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Button* instance = getInstance(state);
                 instance->setText(param1);
@@ -4451,7 +4451,7 @@ int lua_Button_static_create(lua_State* state)
                 (lua_type(state, 2) == LUA_TUSERDATA || lua_type(state, 2) == LUA_TTABLE || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;

+ 53 - 0
gameplay/src/lua/lua_Camera.cpp

@@ -52,6 +52,7 @@ void luaRegister_Camera()
     };
     const luaL_Reg lua_statics[] = 
     {
+        {"create", lua_Camera_static_create},
         {"createOrthographic", lua_Camera_static_createOrthographic},
         {"createPerspective", lua_Camera_static_createPerspective},
         {NULL, NULL}
@@ -1140,6 +1141,58 @@ int lua_Camera_setZoomY(lua_State* state)
     return 0;
 }
 
+int lua_Camera_static_create(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA || lua_type(state, 1) == LUA_TTABLE || lua_type(state, 1) == LUA_TNIL))
+            {
+                // Get parameter 1 off the stack.
+                bool param1Valid;
+                ScriptUtil::LuaArray<Properties> param1 = ScriptUtil::getObjectPointer<Properties>(1, "Properties", false, &param1Valid);
+                if (!param1Valid)
+                {
+                    lua_pushstring(state, "Failed to convert parameter 1 to type 'Properties'.");
+                    lua_error(state);
+                }
+
+                void* returnPtr = (void*)Camera::create(param1);
+                if (returnPtr)
+                {
+                    ScriptUtil::LuaObject* object = (ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Camera");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Camera_static_create - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Camera_static_createOrthographic(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_Camera.h

@@ -31,6 +31,7 @@ int lua_Camera_setFieldOfView(lua_State* state);
 int lua_Camera_setNearPlane(lua_State* state);
 int lua_Camera_setZoomX(lua_State* state);
 int lua_Camera_setZoomY(lua_State* state);
+int lua_Camera_static_create(lua_State* state);
 int lua_Camera_static_createOrthographic(lua_State* state);
 int lua_Camera_static_createPerspective(lua_State* state);
 int lua_Camera_unproject(lua_State* state);

+ 18 - 18
gameplay/src/lua/lua_CheckBox.cpp

@@ -314,10 +314,10 @@ int lua_CheckBox_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
-                    ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(3, false);
+                    const char* param2 = ScriptUtil::getString(3, false);
 
                     CheckBox* instance = getInstance(state);
                     void* returnPtr = (void*)instance->createAnimation(param1, param2);
@@ -345,7 +345,7 @@ int lua_CheckBox_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -389,7 +389,7 @@ int lua_CheckBox_createAnimation(lua_State* state)
                     (lua_type(state, 7) == LUA_TSTRING || lua_type(state, 7) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -444,7 +444,7 @@ int lua_CheckBox_createAnimation(lua_State* state)
                     (lua_type(state, 9) == LUA_TSTRING || lua_type(state, 9) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -519,7 +519,7 @@ int lua_CheckBox_createAnimationFromBy(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -587,7 +587,7 @@ int lua_CheckBox_createAnimationFromTo(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -664,7 +664,7 @@ int lua_CheckBox_destroyAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 CheckBox* instance = getInstance(state);
                 instance->destroyAnimation(param1);
@@ -761,7 +761,7 @@ int lua_CheckBox_getAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 CheckBox* instance = getInstance(state);
                 void* returnPtr = (void*)instance->getAnimation(param1);
@@ -1585,7 +1585,7 @@ int lua_CheckBox_getImageColor(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -1637,7 +1637,7 @@ int lua_CheckBox_getImageRegion(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -1733,7 +1733,7 @@ int lua_CheckBox_getImageUVs(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -3551,7 +3551,7 @@ int lua_CheckBox_setImageColor(lua_State* state)
                 (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3580,7 +3580,7 @@ int lua_CheckBox_setImageColor(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3629,7 +3629,7 @@ int lua_CheckBox_setImageRegion(lua_State* state)
                 (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3658,7 +3658,7 @@ int lua_CheckBox_setImageRegion(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -4198,7 +4198,7 @@ int lua_CheckBox_setText(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 CheckBox* instance = getInstance(state);
                 instance->setText(param1);
@@ -4647,7 +4647,7 @@ int lua_CheckBox_static_create(lua_State* state)
                 (lua_type(state, 2) == LUA_TUSERDATA || lua_type(state, 2) == LUA_TTABLE || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;

+ 20 - 20
gameplay/src/lua/lua_Container.cpp

@@ -374,10 +374,10 @@ int lua_Container_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
-                    ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(3, false);
+                    const char* param2 = ScriptUtil::getString(3, false);
 
                     Container* instance = getInstance(state);
                     void* returnPtr = (void*)instance->createAnimation(param1, param2);
@@ -405,7 +405,7 @@ int lua_Container_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -449,7 +449,7 @@ int lua_Container_createAnimation(lua_State* state)
                     (lua_type(state, 7) == LUA_TSTRING || lua_type(state, 7) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -504,7 +504,7 @@ int lua_Container_createAnimation(lua_State* state)
                     (lua_type(state, 9) == LUA_TSTRING || lua_type(state, 9) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -579,7 +579,7 @@ int lua_Container_createAnimationFromBy(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -647,7 +647,7 @@ int lua_Container_createAnimationFromTo(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -724,7 +724,7 @@ int lua_Container_destroyAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Container* instance = getInstance(state);
                 instance->destroyAnimation(param1);
@@ -821,7 +821,7 @@ int lua_Container_getAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Container* instance = getInstance(state);
                 void* returnPtr = (void*)instance->getAnimation(param1);
@@ -1294,7 +1294,7 @@ int lua_Container_getControl(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     Container* instance = getInstance(state);
                     void* returnPtr = (void*)instance->getControl(param1);
@@ -1723,7 +1723,7 @@ int lua_Container_getImageColor(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -1775,7 +1775,7 @@ int lua_Container_getImageRegion(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -1827,7 +1827,7 @@ int lua_Container_getImageUVs(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -2994,7 +2994,7 @@ int lua_Container_removeControl(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     Container* instance = getInstance(state);
                     instance->removeControl(param1);
@@ -3806,7 +3806,7 @@ int lua_Container_setImageColor(lua_State* state)
                 (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3835,7 +3835,7 @@ int lua_Container_setImageColor(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3884,7 +3884,7 @@ int lua_Container_setImageRegion(lua_State* state)
                 (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3913,7 +3913,7 @@ int lua_Container_setImageRegion(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -4915,7 +4915,7 @@ int lua_Container_static_create(lua_State* state)
                 (lua_type(state, 2) == LUA_TUSERDATA || lua_type(state, 2) == LUA_TTABLE || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -4954,7 +4954,7 @@ int lua_Container_static_create(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;

+ 16 - 16
gameplay/src/lua/lua_Control.cpp

@@ -303,10 +303,10 @@ int lua_Control_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
-                    ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(3, false);
+                    const char* param2 = ScriptUtil::getString(3, false);
 
                     Control* instance = getInstance(state);
                     void* returnPtr = (void*)instance->createAnimation(param1, param2);
@@ -334,7 +334,7 @@ int lua_Control_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -378,7 +378,7 @@ int lua_Control_createAnimation(lua_State* state)
                     (lua_type(state, 7) == LUA_TSTRING || lua_type(state, 7) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -433,7 +433,7 @@ int lua_Control_createAnimation(lua_State* state)
                     (lua_type(state, 9) == LUA_TSTRING || lua_type(state, 9) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -508,7 +508,7 @@ int lua_Control_createAnimationFromBy(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -576,7 +576,7 @@ int lua_Control_createAnimationFromTo(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -653,7 +653,7 @@ int lua_Control_destroyAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Control* instance = getInstance(state);
                 instance->destroyAnimation(param1);
@@ -750,7 +750,7 @@ int lua_Control_getAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Control* instance = getInstance(state);
                 void* returnPtr = (void*)instance->getAnimation(param1);
@@ -1574,7 +1574,7 @@ int lua_Control_getImageColor(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -1626,7 +1626,7 @@ int lua_Control_getImageRegion(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -1678,7 +1678,7 @@ int lua_Control_getImageUVs(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -3390,7 +3390,7 @@ int lua_Control_setImageColor(lua_State* state)
                 (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3419,7 +3419,7 @@ int lua_Control_setImageColor(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3468,7 +3468,7 @@ int lua_Control_setImageRegion(lua_State* state)
                 (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3497,7 +3497,7 @@ int lua_Control_setImageRegion(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;

+ 2 - 2
gameplay/src/lua/lua_DepthStencilTarget.cpp

@@ -334,7 +334,7 @@ int lua_DepthStencilTarget_static_create(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
                 DepthStencilTarget::Format param2 = (DepthStencilTarget::Format)lua_enumFromString_DepthStencilTargetFormat(luaL_checkstring(state, 2));
@@ -389,7 +389,7 @@ int lua_DepthStencilTarget_static_getDepthStencilTarget(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 void* returnPtr = (void*)DepthStencilTarget::getDepthStencilTarget(param1);
                 if (returnPtr)

+ 12 - 12
gameplay/src/lua/lua_Effect.cpp

@@ -232,7 +232,7 @@ int lua_Effect_getUniform(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     Effect* instance = getInstance(state);
                     void* returnPtr = (void*)instance->getUniform(param1);
@@ -343,7 +343,7 @@ int lua_Effect_getVertexAttribute(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Effect* instance = getInstance(state);
                 void* returnPtr = (void*)new GLint(instance->getVertexAttribute(param1));
@@ -934,10 +934,10 @@ int lua_Effect_static_createFromFile(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
-                ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(2, false);
+                const char* param2 = ScriptUtil::getString(2, false);
 
                 void* returnPtr = (void*)Effect::createFromFile(param1, param2);
                 if (returnPtr)
@@ -967,13 +967,13 @@ int lua_Effect_static_createFromFile(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
-                ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(2, false);
+                const char* param2 = ScriptUtil::getString(2, false);
 
                 // Get parameter 3 off the stack.
-                ScriptUtil::LuaArray<const char> param3 = ScriptUtil::getString(3, false);
+                const char* param3 = ScriptUtil::getString(3, false);
 
                 void* returnPtr = (void*)Effect::createFromFile(param1, param2, param3);
                 if (returnPtr)
@@ -1020,10 +1020,10 @@ int lua_Effect_static_createFromSource(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
-                ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(2, false);
+                const char* param2 = ScriptUtil::getString(2, false);
 
                 void* returnPtr = (void*)Effect::createFromSource(param1, param2);
                 if (returnPtr)
@@ -1053,13 +1053,13 @@ int lua_Effect_static_createFromSource(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
-                ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(2, false);
+                const char* param2 = ScriptUtil::getString(2, false);
 
                 // Get parameter 3 off the stack.
-                ScriptUtil::LuaArray<const char> param3 = ScriptUtil::getString(3, false);
+                const char* param3 = ScriptUtil::getString(3, false);
 
                 void* returnPtr = (void*)Effect::createFromSource(param1, param2, param3);
                 if (returnPtr)

+ 46 - 8
gameplay/src/lua/lua_FileSystem.cpp

@@ -19,6 +19,7 @@ void luaRegister_FileSystem()
     {
         {"createFileFromAsset", lua_FileSystem_static_createFileFromAsset},
         {"fileExists", lua_FileSystem_static_fileExists},
+        {"getExtension", lua_FileSystem_static_getExtension},
         {"getResourcePath", lua_FileSystem_static_getResourcePath},
         {"isAbsolutePath", lua_FileSystem_static_isAbsolutePath},
         {"loadResourceAliases", lua_FileSystem_static_loadResourceAliases},
@@ -90,7 +91,7 @@ int lua_FileSystem_static_createFileFromAsset(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 FileSystem::createFileFromAsset(param1);
                 
@@ -124,7 +125,7 @@ int lua_FileSystem_static_fileExists(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 bool result = FileSystem::fileExists(param1);
 
@@ -148,6 +149,43 @@ int lua_FileSystem_static_fileExists(lua_State* state)
     return 0;
 }
 
+int lua_FileSystem_static_getExtension(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
+            {
+                // Get parameter 1 off the stack.
+                const char* param1 = ScriptUtil::getString(1, false);
+
+                std::string result = FileSystem::getExtension(param1);
+
+                // Push the return value onto the stack.
+                lua_pushstring(state, result.c_str());
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_FileSystem_static_getExtension - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_FileSystem_static_getResourcePath(lua_State* state)
 {
     // Get the number of parameters.
@@ -189,7 +227,7 @@ int lua_FileSystem_static_isAbsolutePath(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 bool result = FileSystem::isAbsolutePath(param1);
 
@@ -228,7 +266,7 @@ int lua_FileSystem_static_loadResourceAliases(lua_State* state)
                 if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                    const char* param1 = ScriptUtil::getString(1, false);
 
                     FileSystem::loadResourceAliases(param1);
                     
@@ -279,7 +317,7 @@ int lua_FileSystem_static_readAll(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 const char* result = FileSystem::readAll(param1);
 
@@ -299,7 +337,7 @@ int lua_FileSystem_static_readAll(lua_State* state)
                 (lua_type(state, 2) == LUA_TTABLE || lua_type(state, 2) == LUA_TLIGHTUSERDATA))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
                 ScriptUtil::LuaArray<int> param2 = ScriptUtil::getIntPointer(2);
@@ -339,7 +377,7 @@ int lua_FileSystem_static_resolvePath(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 const char* result = FileSystem::resolvePath(param1);
 
@@ -376,7 +414,7 @@ int lua_FileSystem_static_setResourcePath(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 FileSystem::setResourcePath(param1);
                 

+ 1 - 0
gameplay/src/lua/lua_FileSystem.h

@@ -8,6 +8,7 @@ namespace gameplay
 int lua_FileSystem__gc(lua_State* state);
 int lua_FileSystem_static_createFileFromAsset(lua_State* state);
 int lua_FileSystem_static_fileExists(lua_State* state);
+int lua_FileSystem_static_getExtension(lua_State* state);
 int lua_FileSystem_static_getResourcePath(lua_State* state);
 int lua_FileSystem_static_isAbsolutePath(lua_State* state);
 int lua_FileSystem_static_loadResourceAliases(lua_State* state);

+ 35 - 35
gameplay/src/lua/lua_Font.cpp

@@ -134,7 +134,7 @@ int lua_Font_createText(lua_State* state)
                 (lua_type(state, 4) == LUA_TUSERDATA || lua_type(state, 4) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -185,7 +185,7 @@ int lua_Font_createText(lua_State* state)
                 lua_type(state, 5) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -240,7 +240,7 @@ int lua_Font_createText(lua_State* state)
                 (lua_type(state, 6) == LUA_TSTRING || lua_type(state, 6) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -299,7 +299,7 @@ int lua_Font_createText(lua_State* state)
                 lua_type(state, 7) == LUA_TBOOLEAN)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -362,7 +362,7 @@ int lua_Font_createText(lua_State* state)
                 lua_type(state, 8) == LUA_TBOOLEAN)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -429,7 +429,7 @@ int lua_Font_createText(lua_State* state)
                 (lua_type(state, 9) == LUA_TUSERDATA || lua_type(state, 9) == LUA_TTABLE || lua_type(state, 9) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -544,7 +544,7 @@ int lua_Font_drawText(lua_State* state)
                     (lua_type(state, 4) == LUA_TUSERDATA || lua_type(state, 4) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -580,7 +580,7 @@ int lua_Font_drawText(lua_State* state)
                     (lua_type(state, 5) == LUA_TUSERDATA || lua_type(state, 5) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -610,7 +610,7 @@ int lua_Font_drawText(lua_State* state)
                     lua_type(state, 5) == LUA_TNUMBER)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -650,7 +650,7 @@ int lua_Font_drawText(lua_State* state)
                     lua_type(state, 6) == LUA_TNUMBER)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -684,7 +684,7 @@ int lua_Font_drawText(lua_State* state)
                     (lua_type(state, 6) == LUA_TSTRING || lua_type(state, 6) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -728,7 +728,7 @@ int lua_Font_drawText(lua_State* state)
                     lua_type(state, 7) == LUA_TBOOLEAN)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -766,7 +766,7 @@ int lua_Font_drawText(lua_State* state)
                     lua_type(state, 7) == LUA_TBOOLEAN)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -814,7 +814,7 @@ int lua_Font_drawText(lua_State* state)
                     lua_type(state, 8) == LUA_TNUMBER)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -853,7 +853,7 @@ int lua_Font_drawText(lua_State* state)
                     lua_type(state, 8) == LUA_TBOOLEAN)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -905,7 +905,7 @@ int lua_Font_drawText(lua_State* state)
                     lua_type(state, 9) == LUA_TNUMBER)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -948,7 +948,7 @@ int lua_Font_drawText(lua_State* state)
                     (lua_type(state, 9) == LUA_TUSERDATA || lua_type(state, 9) == LUA_TTABLE || lua_type(state, 9) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -1007,7 +1007,7 @@ int lua_Font_drawText(lua_State* state)
                     lua_type(state, 10) == LUA_TBOOLEAN)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -1104,7 +1104,7 @@ int lua_Font_getIndexAtLocation(lua_State* state)
                 (lua_type(state, 6) == LUA_TUSERDATA || lua_type(state, 6) == LUA_TTABLE || lua_type(state, 6) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -1160,7 +1160,7 @@ int lua_Font_getIndexAtLocation(lua_State* state)
                 (lua_type(state, 7) == LUA_TSTRING || lua_type(state, 7) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -1220,7 +1220,7 @@ int lua_Font_getIndexAtLocation(lua_State* state)
                 lua_type(state, 8) == LUA_TBOOLEAN)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -1284,7 +1284,7 @@ int lua_Font_getIndexAtLocation(lua_State* state)
                 lua_type(state, 9) == LUA_TBOOLEAN)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -1366,7 +1366,7 @@ int lua_Font_getLocationAtIndex(lua_State* state)
                 lua_type(state, 6) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -1413,7 +1413,7 @@ int lua_Font_getLocationAtIndex(lua_State* state)
                 (lua_type(state, 7) == LUA_TSTRING || lua_type(state, 7) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -1464,7 +1464,7 @@ int lua_Font_getLocationAtIndex(lua_State* state)
                 lua_type(state, 8) == LUA_TBOOLEAN)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -1519,7 +1519,7 @@ int lua_Font_getLocationAtIndex(lua_State* state)
                 lua_type(state, 9) == LUA_TBOOLEAN)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -1707,7 +1707,7 @@ int lua_Font_measureText(lua_State* state)
                     (lua_type(state, 5) == LUA_TTABLE || lua_type(state, 5) == LUA_TLIGHTUSERDATA))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     unsigned int param2 = (unsigned int)luaL_checkunsigned(state, 3);
@@ -1734,7 +1734,7 @@ int lua_Font_measureText(lua_State* state)
                     (lua_type(state, 5) == LUA_TUSERDATA || lua_type(state, 5) == LUA_TTABLE || lua_type(state, 5) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -1774,7 +1774,7 @@ int lua_Font_measureText(lua_State* state)
                     (lua_type(state, 6) == LUA_TSTRING || lua_type(state, 6) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -1818,7 +1818,7 @@ int lua_Font_measureText(lua_State* state)
                     lua_type(state, 7) == LUA_TBOOLEAN)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -1866,7 +1866,7 @@ int lua_Font_measureText(lua_State* state)
                     lua_type(state, 8) == LUA_TBOOLEAN)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -1990,7 +1990,7 @@ int lua_Font_static_create(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 void* returnPtr = (void*)Font::create(param1);
                 if (returnPtr)
@@ -2019,10 +2019,10 @@ int lua_Font_static_create(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 // Get parameter 2 off the stack.
-                ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(2, false);
+                const char* param2 = ScriptUtil::getString(2, false);
 
                 void* returnPtr = (void*)Font::create(param1, param2);
                 if (returnPtr)
@@ -2068,7 +2068,7 @@ int lua_Font_static_getJustify(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 Font::Justify result = Font::getJustify(param1);
 

+ 1 - 1
gameplay/src/lua/lua_FontText.cpp

@@ -84,7 +84,7 @@ int lua_FontText__init(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 void* returnPtr = (void*)new Font::Text(param1);
                 if (returnPtr)

+ 22 - 22
gameplay/src/lua/lua_Form.cpp

@@ -382,10 +382,10 @@ int lua_Form_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
-                    ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(3, false);
+                    const char* param2 = ScriptUtil::getString(3, false);
 
                     Form* instance = getInstance(state);
                     void* returnPtr = (void*)instance->createAnimation(param1, param2);
@@ -413,7 +413,7 @@ int lua_Form_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -457,7 +457,7 @@ int lua_Form_createAnimation(lua_State* state)
                     (lua_type(state, 7) == LUA_TSTRING || lua_type(state, 7) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -512,7 +512,7 @@ int lua_Form_createAnimation(lua_State* state)
                     (lua_type(state, 9) == LUA_TSTRING || lua_type(state, 9) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -587,7 +587,7 @@ int lua_Form_createAnimationFromBy(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -655,7 +655,7 @@ int lua_Form_createAnimationFromTo(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -732,7 +732,7 @@ int lua_Form_destroyAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Form* instance = getInstance(state);
                 instance->destroyAnimation(param1);
@@ -861,7 +861,7 @@ int lua_Form_getAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Form* instance = getInstance(state);
                 void* returnPtr = (void*)instance->getAnimation(param1);
@@ -1334,7 +1334,7 @@ int lua_Form_getControl(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     Form* instance = getInstance(state);
                     void* returnPtr = (void*)instance->getControl(param1);
@@ -1763,7 +1763,7 @@ int lua_Form_getImageColor(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -1815,7 +1815,7 @@ int lua_Form_getImageRegion(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -1867,7 +1867,7 @@ int lua_Form_getImageUVs(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 Control::State param2 = (Control::State)lua_enumFromString_ControlState(luaL_checkstring(state, 3));
@@ -3078,7 +3078,7 @@ int lua_Form_removeControl(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     Form* instance = getInstance(state);
                     instance->removeControl(param1);
@@ -3890,7 +3890,7 @@ int lua_Form_setImageColor(lua_State* state)
                 (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3919,7 +3919,7 @@ int lua_Form_setImageColor(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3968,7 +3968,7 @@ int lua_Form_setImageRegion(lua_State* state)
                 (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -3997,7 +3997,7 @@ int lua_Form_setImageRegion(lua_State* state)
                 lua_type(state, 4) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2Valid;
@@ -5042,7 +5042,7 @@ int lua_Form_static_create(lua_State* state)
                 if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                    const char* param1 = ScriptUtil::getString(1, false);
 
                     void* returnPtr = (void*)Form::create(param1);
                     if (returnPtr)
@@ -5074,7 +5074,7 @@ int lua_Form_static_create(lua_State* state)
                     (lua_type(state, 2) == LUA_TUSERDATA || lua_type(state, 2) == LUA_TTABLE || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                    const char* param1 = ScriptUtil::getString(1, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -5113,7 +5113,7 @@ int lua_Form_static_create(lua_State* state)
                     (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                    const char* param1 = ScriptUtil::getString(1, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -5169,7 +5169,7 @@ int lua_Form_static_getForm(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 void* returnPtr = (void*)Form::getForm(param1);
                 if (returnPtr)

+ 3 - 3
gameplay/src/lua/lua_FrameBuffer.cpp

@@ -590,7 +590,7 @@ int lua_FrameBuffer_static_create(lua_State* state)
                 if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                    const char* param1 = ScriptUtil::getString(1, false);
 
                     void* returnPtr = (void*)FrameBuffer::create(param1);
                     if (returnPtr)
@@ -623,7 +623,7 @@ int lua_FrameBuffer_static_create(lua_State* state)
                     lua_type(state, 3) == LUA_TNUMBER)
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                    const char* param1 = ScriptUtil::getString(1, false);
 
                     // Get parameter 2 off the stack.
                     unsigned int param2 = (unsigned int)luaL_checkunsigned(state, 2);
@@ -676,7 +676,7 @@ int lua_FrameBuffer_static_getFrameBuffer(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 void* returnPtr = (void*)FrameBuffer::getFrameBuffer(param1);
                 if (returnPtr)

+ 2 - 2
gameplay/src/lua/lua_Game.cpp

@@ -1411,7 +1411,7 @@ int lua_Game_launchURL(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Game* instance = getInstance(state);
                 bool result = instance->launchURL(param1);
@@ -1672,7 +1672,7 @@ int lua_Game_schedule(lua_State* state)
                 float param1 = (float)luaL_checknumber(state, 2);
 
                 // Get parameter 2 off the stack.
-                ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(3, false);
+                const char* param2 = ScriptUtil::getString(3, false);
 
                 Game* instance = getInstance(state);
                 instance->schedule(param1, param2);

+ 34 - 0
gameplay/src/lua/lua_Global.cpp

@@ -74,6 +74,7 @@ void luaRegister_lua_Global()
     ScriptUtil::setGlobalHierarchyPair("Ref", "Font");
     ScriptUtil::setGlobalHierarchyPair("Ref", "Form");
     ScriptUtil::setGlobalHierarchyPair("Ref", "FrameBuffer");
+    ScriptUtil::setGlobalHierarchyPair("Ref", "HeightField");
     ScriptUtil::setGlobalHierarchyPair("Ref", "Image");
     ScriptUtil::setGlobalHierarchyPair("Ref", "Joint");
     ScriptUtil::setGlobalHierarchyPair("Ref", "Joystick");
@@ -95,6 +96,7 @@ void luaRegister_lua_Global()
     ScriptUtil::setGlobalHierarchyPair("Ref", "Scene");
     ScriptUtil::setGlobalHierarchyPair("Ref", "Slider");
     ScriptUtil::setGlobalHierarchyPair("Ref", "Technique");
+    ScriptUtil::setGlobalHierarchyPair("Ref", "Terrain");
     ScriptUtil::setGlobalHierarchyPair("Ref", "TextBox");
     ScriptUtil::setGlobalHierarchyPair("Ref", "Texture");
     ScriptUtil::setGlobalHierarchyPair("Ref", "Texture::Sampler");
@@ -130,6 +132,7 @@ void luaRegister_lua_Global()
     ScriptUtil::setGlobalHierarchyPair("Transform::Listener", "PhysicsCharacter");
     ScriptUtil::setGlobalHierarchyPair("Transform::Listener", "PhysicsGhostObject");
     ScriptUtil::setGlobalHierarchyPair("Transform::Listener", "PhysicsRigidBody");
+    ScriptUtil::setGlobalHierarchyPair("Transform::Listener", "Terrain");
     ScriptUtil::addStringFromEnumConversionFunction(&gameplay::lua_stringFromEnumGlobal);
 
     // Register enumeration AIMessage::ParameterType.
@@ -669,6 +672,7 @@ void luaRegister_lua_Global()
     {
         std::vector<std::string> scopePath;
         scopePath.push_back("PhysicsCollisionShape");
+        ScriptUtil::registerConstantString("SHAPE_NONE", "SHAPE_NONE", scopePath);
         ScriptUtil::registerConstantString("SHAPE_BOX", "SHAPE_BOX", scopePath);
         ScriptUtil::registerConstantString("SHAPE_SPHERE", "SHAPE_SPHERE", scopePath);
         ScriptUtil::registerConstantString("SHAPE_CAPSULE", "SHAPE_CAPSULE", scopePath);
@@ -714,6 +718,9 @@ void luaRegister_lua_Global()
         ScriptUtil::registerConstantString("CAMERA_WORLD_POSITION", "CAMERA_WORLD_POSITION", scopePath);
         ScriptUtil::registerConstantString("CAMERA_VIEW_POSITION", "CAMERA_VIEW_POSITION", scopePath);
         ScriptUtil::registerConstantString("MATRIX_PALETTE", "MATRIX_PALETTE", scopePath);
+        ScriptUtil::registerConstantString("SCENE_AMBIENT_COLOR", "SCENE_AMBIENT_COLOR", scopePath);
+        ScriptUtil::registerConstantString("SCENE_LIGHT_COLOR", "SCENE_LIGHT_COLOR", scopePath);
+        ScriptUtil::registerConstantString("SCENE_LIGHT_DIRECTION", "SCENE_LIGHT_DIRECTION", scopePath);
     }
 
     // Register enumeration RenderState::Blend.
@@ -735,6 +742,20 @@ void luaRegister_lua_Global()
         ScriptUtil::registerConstantString("BLEND_SRC_ALPHA_SATURATE", "BLEND_SRC_ALPHA_SATURATE", scopePath);
     }
 
+    // Register enumeration RenderState::DepthFunction.
+    {
+        std::vector<std::string> scopePath;
+        scopePath.push_back("RenderState");
+        ScriptUtil::registerConstantString("DEPTH_NEVER", "DEPTH_NEVER", scopePath);
+        ScriptUtil::registerConstantString("DEPTH_LESS", "DEPTH_LESS", scopePath);
+        ScriptUtil::registerConstantString("DEPTH_EQUAL", "DEPTH_EQUAL", scopePath);
+        ScriptUtil::registerConstantString("DEPTH_LEQUAL", "DEPTH_LEQUAL", scopePath);
+        ScriptUtil::registerConstantString("DEPTH_GREATER", "DEPTH_GREATER", scopePath);
+        ScriptUtil::registerConstantString("DEPTH_NOTEQUAL", "DEPTH_NOTEQUAL", scopePath);
+        ScriptUtil::registerConstantString("DEPTH_GEQUAL", "DEPTH_GEQUAL", scopePath);
+        ScriptUtil::registerConstantString("DEPTH_ALWAYS", "DEPTH_ALWAYS", scopePath);
+    }
+
     // Register enumeration Scene::DebugFlags.
     {
         std::vector<std::string> scopePath;
@@ -743,6 +764,15 @@ void luaRegister_lua_Global()
         ScriptUtil::registerConstantString("DEBUG_SPHERES", "DEBUG_SPHERES", scopePath);
     }
 
+    // Register enumeration Terrain::Flags.
+    {
+        std::vector<std::string> scopePath;
+        scopePath.push_back("Terrain");
+        ScriptUtil::registerConstantString("DEBUG_PATCHES", "DEBUG_PATCHES", scopePath);
+        ScriptUtil::registerConstantString("ENABLE_FRUSTUM_CULLING", "ENABLE_FRUSTUM_CULLING", scopePath);
+        ScriptUtil::registerConstantString("ENABLE_LEVEL_OF_DETAIL", "ENABLE_LEVEL_OF_DETAIL", scopePath);
+    }
+
     // Register enumeration Texture::Filter.
     {
         std::vector<std::string> scopePath;
@@ -878,8 +908,12 @@ const char* lua_stringFromEnumGlobal(std::string& enumname, unsigned int value)
         return lua_stringFromEnum_RenderStateAutoBinding((RenderState::AutoBinding)value);
     if (enumname == "RenderState::Blend")
         return lua_stringFromEnum_RenderStateBlend((RenderState::Blend)value);
+    if (enumname == "RenderState::DepthFunction")
+        return lua_stringFromEnum_RenderStateDepthFunction((RenderState::DepthFunction)value);
     if (enumname == "Scene::DebugFlags")
         return lua_stringFromEnum_SceneDebugFlags((Scene::DebugFlags)value);
+    if (enumname == "Terrain::Flags")
+        return lua_stringFromEnum_TerrainFlags((Terrain::Flags)value);
     if (enumname == "Texture::Filter")
         return lua_stringFromEnum_TextureFilter((Texture::Filter)value);
     if (enumname == "Texture::Format")

+ 2 - 0
gameplay/src/lua/lua_Global.h

@@ -36,7 +36,9 @@
 #include "lua_PropertiesType.h"
 #include "lua_RenderStateAutoBinding.h"
 #include "lua_RenderStateBlend.h"
+#include "lua_RenderStateDepthFunction.h"
 #include "lua_SceneDebugFlags.h"
+#include "lua_TerrainFlags.h"
 #include "lua_TextureFilter.h"
 #include "lua_TextureFormat.h"
 #include "lua_TextureWrap.h"

+ 632 - 0
gameplay/src/lua/lua_HeightField.cpp

@@ -0,0 +1,632 @@
+#include "Base.h"
+#include "ScriptController.h"
+#include "lua_HeightField.h"
+#include "Base.h"
+#include "FileSystem.h"
+#include "Game.h"
+#include "HeightField.h"
+#include "Image.h"
+#include "Ref.h"
+
+namespace gameplay
+{
+
+void luaRegister_HeightField()
+{
+    const luaL_Reg lua_members[] = 
+    {
+        {"addRef", lua_HeightField_addRef},
+        {"getArray", lua_HeightField_getArray},
+        {"getColumnCount", lua_HeightField_getColumnCount},
+        {"getHeight", lua_HeightField_getHeight},
+        {"getRefCount", lua_HeightField_getRefCount},
+        {"getRowCount", lua_HeightField_getRowCount},
+        {"release", lua_HeightField_release},
+        {NULL, NULL}
+    };
+    const luaL_Reg lua_statics[] = 
+    {
+        {"create", lua_HeightField_static_create},
+        {"createFromImage", lua_HeightField_static_createFromImage},
+        {"createFromRAW", lua_HeightField_static_createFromRAW},
+        {NULL, NULL}
+    };
+    std::vector<std::string> scopePath;
+
+    ScriptUtil::registerClass("HeightField", lua_members, NULL, lua_HeightField__gc, lua_statics, scopePath);
+}
+
+static HeightField* getInstance(lua_State* state)
+{
+    void* userdata = luaL_checkudata(state, 1, "HeightField");
+    luaL_argcheck(state, userdata != NULL, 1, "'HeightField' expected.");
+    return (HeightField*)((ScriptUtil::LuaObject*)userdata)->instance;
+}
+
+int lua_HeightField__gc(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                void* userdata = luaL_checkudata(state, 1, "HeightField");
+                luaL_argcheck(state, userdata != NULL, 1, "'HeightField' expected.");
+                ScriptUtil::LuaObject* object = (ScriptUtil::LuaObject*)userdata;
+                if (object->owns)
+                {
+                    HeightField* instance = (HeightField*)object->instance;
+                    SAFE_RELEASE(instance);
+                }
+                
+                return 0;
+            }
+
+            lua_pushstring(state, "lua_HeightField__gc - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_HeightField_addRef(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                HeightField* instance = getInstance(state);
+                instance->addRef();
+                
+                return 0;
+            }
+
+            lua_pushstring(state, "lua_HeightField_addRef - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_HeightField_getArray(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                HeightField* instance = getInstance(state);
+                float* result = instance->getArray();
+
+                // Push the return value onto the stack.
+                lua_pushlightuserdata(state, result);
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_getArray - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_HeightField_getColumnCount(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                HeightField* instance = getInstance(state);
+                unsigned int result = instance->getColumnCount();
+
+                // Push the return value onto the stack.
+                lua_pushunsigned(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_getColumnCount - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_HeightField_getHeight(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 3:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                lua_type(state, 2) == LUA_TNUMBER &&
+                lua_type(state, 3) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                float param1 = (float)luaL_checknumber(state, 2);
+
+                // Get parameter 2 off the stack.
+                float param2 = (float)luaL_checknumber(state, 3);
+
+                HeightField* instance = getInstance(state);
+                float result = instance->getHeight(param1, param2);
+
+                // Push the return value onto the stack.
+                lua_pushnumber(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_getHeight - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 3).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_HeightField_getRefCount(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                HeightField* instance = getInstance(state);
+                unsigned int result = instance->getRefCount();
+
+                // Push the return value onto the stack.
+                lua_pushunsigned(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_getRefCount - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_HeightField_getRowCount(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                HeightField* instance = getInstance(state);
+                unsigned int result = instance->getRowCount();
+
+                // Push the return value onto the stack.
+                lua_pushunsigned(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_getRowCount - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_HeightField_release(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                HeightField* instance = getInstance(state);
+                instance->release();
+                
+                return 0;
+            }
+
+            lua_pushstring(state, "lua_HeightField_release - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_HeightField_static_create(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 2:
+        {
+            if (lua_type(state, 1) == LUA_TNUMBER &&
+                lua_type(state, 2) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                unsigned int param1 = (unsigned int)luaL_checkunsigned(state, 1);
+
+                // Get parameter 2 off the stack.
+                unsigned int param2 = (unsigned int)luaL_checkunsigned(state, 2);
+
+                void* returnPtr = (void*)HeightField::create(param1, param2);
+                if (returnPtr)
+                {
+                    ScriptUtil::LuaObject* object = (ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "HeightField");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_static_create - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 2).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_HeightField_static_createFromImage(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
+            {
+                // Get parameter 1 off the stack.
+                const char* param1 = ScriptUtil::getString(1, false);
+
+                void* returnPtr = (void*)HeightField::createFromImage(param1);
+                if (returnPtr)
+                {
+                    ScriptUtil::LuaObject* object = (ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "HeightField");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_static_createFromImage - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        case 2:
+        {
+            if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL) &&
+                lua_type(state, 2) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                const char* param1 = ScriptUtil::getString(1, false);
+
+                // Get parameter 2 off the stack.
+                float param2 = (float)luaL_checknumber(state, 2);
+
+                void* returnPtr = (void*)HeightField::createFromImage(param1, param2);
+                if (returnPtr)
+                {
+                    ScriptUtil::LuaObject* object = (ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "HeightField");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_static_createFromImage - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        case 3:
+        {
+            if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL) &&
+                lua_type(state, 2) == LUA_TNUMBER &&
+                lua_type(state, 3) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                const char* param1 = ScriptUtil::getString(1, false);
+
+                // Get parameter 2 off the stack.
+                float param2 = (float)luaL_checknumber(state, 2);
+
+                // Get parameter 3 off the stack.
+                float param3 = (float)luaL_checknumber(state, 3);
+
+                void* returnPtr = (void*)HeightField::createFromImage(param1, param2, param3);
+                if (returnPtr)
+                {
+                    ScriptUtil::LuaObject* object = (ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "HeightField");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_static_createFromImage - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1, 2 or 3).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+int lua_HeightField_static_createFromRAW(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 3:
+        {
+            if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL) &&
+                lua_type(state, 2) == LUA_TNUMBER &&
+                lua_type(state, 3) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                const char* param1 = ScriptUtil::getString(1, false);
+
+                // Get parameter 2 off the stack.
+                unsigned int param2 = (unsigned int)luaL_checkunsigned(state, 2);
+
+                // Get parameter 3 off the stack.
+                unsigned int param3 = (unsigned int)luaL_checkunsigned(state, 3);
+
+                void* returnPtr = (void*)HeightField::createFromRAW(param1, param2, param3);
+                if (returnPtr)
+                {
+                    ScriptUtil::LuaObject* object = (ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "HeightField");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_static_createFromRAW - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        case 4:
+        {
+            if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL) &&
+                lua_type(state, 2) == LUA_TNUMBER &&
+                lua_type(state, 3) == LUA_TNUMBER &&
+                lua_type(state, 4) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                const char* param1 = ScriptUtil::getString(1, false);
+
+                // Get parameter 2 off the stack.
+                unsigned int param2 = (unsigned int)luaL_checkunsigned(state, 2);
+
+                // Get parameter 3 off the stack.
+                unsigned int param3 = (unsigned int)luaL_checkunsigned(state, 3);
+
+                // Get parameter 4 off the stack.
+                float param4 = (float)luaL_checknumber(state, 4);
+
+                void* returnPtr = (void*)HeightField::createFromRAW(param1, param2, param3, param4);
+                if (returnPtr)
+                {
+                    ScriptUtil::LuaObject* object = (ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "HeightField");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_static_createFromRAW - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        case 5:
+        {
+            if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL) &&
+                lua_type(state, 2) == LUA_TNUMBER &&
+                lua_type(state, 3) == LUA_TNUMBER &&
+                lua_type(state, 4) == LUA_TNUMBER &&
+                lua_type(state, 5) == LUA_TNUMBER)
+            {
+                // Get parameter 1 off the stack.
+                const char* param1 = ScriptUtil::getString(1, false);
+
+                // Get parameter 2 off the stack.
+                unsigned int param2 = (unsigned int)luaL_checkunsigned(state, 2);
+
+                // Get parameter 3 off the stack.
+                unsigned int param3 = (unsigned int)luaL_checkunsigned(state, 3);
+
+                // Get parameter 4 off the stack.
+                float param4 = (float)luaL_checknumber(state, 4);
+
+                // Get parameter 5 off the stack.
+                float param5 = (float)luaL_checknumber(state, 5);
+
+                void* returnPtr = (void*)HeightField::createFromRAW(param1, param2, param3, param4, param5);
+                if (returnPtr)
+                {
+                    ScriptUtil::LuaObject* object = (ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "HeightField");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_HeightField_static_createFromRAW - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 3, 4 or 5).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
+}

+ 24 - 0
gameplay/src/lua/lua_HeightField.h

@@ -0,0 +1,24 @@
+#ifndef LUA_HEIGHTFIELD_H_
+#define LUA_HEIGHTFIELD_H_
+
+namespace gameplay
+{
+
+// Lua bindings for HeightField.
+int lua_HeightField__gc(lua_State* state);
+int lua_HeightField_addRef(lua_State* state);
+int lua_HeightField_getArray(lua_State* state);
+int lua_HeightField_getColumnCount(lua_State* state);
+int lua_HeightField_getHeight(lua_State* state);
+int lua_HeightField_getRefCount(lua_State* state);
+int lua_HeightField_getRowCount(lua_State* state);
+int lua_HeightField_release(lua_State* state);
+int lua_HeightField_static_create(lua_State* state);
+int lua_HeightField_static_createFromImage(lua_State* state);
+int lua_HeightField_static_createFromRAW(lua_State* state);
+
+void luaRegister_HeightField();
+
+}
+
+#endif

+ 1 - 1
gameplay/src/lua/lua_Image.cpp

@@ -295,7 +295,7 @@ int lua_Image_static_create(lua_State* state)
             if ((lua_type(state, 1) == LUA_TSTRING || lua_type(state, 1) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(1, false);
+                const char* param1 = ScriptUtil::getString(1, false);
 
                 void* returnPtr = (void*)Image::create(param1);
                 if (returnPtr)

+ 108 - 19
gameplay/src/lua/lua_Joint.cpp

@@ -18,6 +18,7 @@
 #include "Scene.h"
 #include "ScriptController.h"
 #include "ScriptTarget.h"
+#include "Terrain.h"
 #include "Transform.h"
 #include "lua_CurveInterpolationType.h"
 #include "lua_NodeType.h"
@@ -87,6 +88,7 @@ void luaRegister_Joint()
         {"getScaleZ", lua_Joint_getScaleZ},
         {"getScene", lua_Joint_getScene},
         {"getTag", lua_Joint_getTag},
+        {"getTerrain", lua_Joint_getTerrain},
         {"getTranslation", lua_Joint_getTranslation},
         {"getTranslationView", lua_Joint_getTranslationView},
         {"getTranslationWorld", lua_Joint_getTranslationWorld},
@@ -133,6 +135,7 @@ void luaRegister_Joint()
         {"setScaleY", lua_Joint_setScaleY},
         {"setScaleZ", lua_Joint_setScaleZ},
         {"setTag", lua_Joint_setTag},
+        {"setTerrain", lua_Joint_setTerrain},
         {"setTranslation", lua_Joint_setTranslation},
         {"setTranslationX", lua_Joint_setTranslationX},
         {"setTranslationY", lua_Joint_setTranslationY},
@@ -507,10 +510,10 @@ int lua_Joint_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
-                    ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(3, false);
+                    const char* param2 = ScriptUtil::getString(3, false);
 
                     Joint* instance = getInstance(state);
                     void* returnPtr = (void*)instance->createAnimation(param1, param2);
@@ -538,7 +541,7 @@ int lua_Joint_createAnimation(lua_State* state)
                     (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     bool param2Valid;
@@ -582,7 +585,7 @@ int lua_Joint_createAnimation(lua_State* state)
                     (lua_type(state, 7) == LUA_TSTRING || lua_type(state, 7) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -637,7 +640,7 @@ int lua_Joint_createAnimation(lua_State* state)
                     (lua_type(state, 9) == LUA_TSTRING || lua_type(state, 9) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     // Get parameter 2 off the stack.
                     int param2 = (int)luaL_checkint(state, 3);
@@ -712,7 +715,7 @@ int lua_Joint_createAnimationFromBy(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -780,7 +783,7 @@ int lua_Joint_createAnimationFromTo(lua_State* state)
                 lua_type(state, 7) == LUA_TNUMBER)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 int param2 = (int)luaL_checkint(state, 3);
@@ -857,7 +860,7 @@ int lua_Joint_destroyAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Joint* instance = getInstance(state);
                 instance->destroyAnimation(param1);
@@ -893,7 +896,7 @@ int lua_Joint_findNode(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Joint* instance = getInstance(state);
                 void* returnPtr = (void*)instance->findNode(param1);
@@ -924,7 +927,7 @@ int lua_Joint_findNode(lua_State* state)
                 lua_type(state, 3) == LUA_TBOOLEAN)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2 = ScriptUtil::luaCheckBool(state, 3);
@@ -959,7 +962,7 @@ int lua_Joint_findNode(lua_State* state)
                 lua_type(state, 4) == LUA_TBOOLEAN)
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
                 bool param2 = ScriptUtil::luaCheckBool(state, 3);
@@ -1219,7 +1222,7 @@ int lua_Joint_getAnimation(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Joint* instance = getInstance(state);
                 void* returnPtr = (void*)instance->getAnimation(param1);
@@ -3189,7 +3192,7 @@ int lua_Joint_getTag(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Joint* instance = getInstance(state);
                 const char* result = instance->getTag(param1);
@@ -3214,6 +3217,50 @@ int lua_Joint_getTag(lua_State* state)
     return 0;
 }
 
+int lua_Joint_getTerrain(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Joint* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getTerrain();
+                if (returnPtr)
+                {
+                    ScriptUtil::LuaObject* object = (ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "Terrain");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Joint_getTerrain - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Joint_getTranslation(lua_State* state)
 {
     // Get the number of parameters.
@@ -3862,7 +3909,7 @@ int lua_Joint_hasTag(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Joint* instance = getInstance(state);
                 bool result = instance->hasTag(param1);
@@ -4899,7 +4946,7 @@ int lua_Joint_setCollisionObject(lua_State* state)
                     (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
                 {
                     // Get parameter 1 off the stack.
-                    ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                    const char* param1 = ScriptUtil::getString(2, false);
 
                     Joint* instance = getInstance(state);
                     void* returnPtr = (void*)instance->setCollisionObject(param1);
@@ -5107,7 +5154,7 @@ int lua_Joint_setId(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Joint* instance = getInstance(state);
                 instance->setId(param1);
@@ -5616,7 +5663,7 @@ int lua_Joint_setTag(lua_State* state)
                 (lua_type(state, 2) == LUA_TSTRING || lua_type(state, 2) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 Joint* instance = getInstance(state);
                 instance->setTag(param1);
@@ -5635,10 +5682,10 @@ int lua_Joint_setTag(lua_State* state)
                 (lua_type(state, 3) == LUA_TSTRING || lua_type(state, 3) == LUA_TNIL))
             {
                 // Get parameter 1 off the stack.
-                ScriptUtil::LuaArray<const char> param1 = ScriptUtil::getString(2, false);
+                const char* param1 = ScriptUtil::getString(2, false);
 
                 // Get parameter 2 off the stack.
-                ScriptUtil::LuaArray<const char> param2 = ScriptUtil::getString(3, false);
+                const char* param2 = ScriptUtil::getString(3, false);
 
                 Joint* instance = getInstance(state);
                 instance->setTag(param1, param2);
@@ -5660,6 +5707,48 @@ int lua_Joint_setTag(lua_State* state)
     return 0;
 }
 
+int lua_Joint_setTerrain(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 2:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                (lua_type(state, 2) == LUA_TUSERDATA || lua_type(state, 2) == LUA_TTABLE || lua_type(state, 2) == LUA_TNIL))
+            {
+                // Get parameter 1 off the stack.
+                bool param1Valid;
+                ScriptUtil::LuaArray<Terrain> param1 = ScriptUtil::getObjectPointer<Terrain>(2, "Terrain", false, &param1Valid);
+                if (!param1Valid)
+                {
+                    lua_pushstring(state, "Failed to convert parameter 1 to type 'Terrain'.");
+                    lua_error(state);
+                }
+
+                Joint* instance = getInstance(state);
+                instance->setTerrain(param1);
+                
+                return 0;
+            }
+
+            lua_pushstring(state, "lua_Joint_setTerrain - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 2).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Joint_setTranslation(lua_State* state)
 {
     // Get the number of parameters.

+ 2 - 0
gameplay/src/lua/lua_Joint.h

@@ -63,6 +63,7 @@ int lua_Joint_getScaleY(lua_State* state);
 int lua_Joint_getScaleZ(lua_State* state);
 int lua_Joint_getScene(lua_State* state);
 int lua_Joint_getTag(lua_State* state);
+int lua_Joint_getTerrain(lua_State* state);
 int lua_Joint_getTranslation(lua_State* state);
 int lua_Joint_getTranslationView(lua_State* state);
 int lua_Joint_getTranslationWorld(lua_State* state);
@@ -109,6 +110,7 @@ int lua_Joint_setScaleX(lua_State* state);
 int lua_Joint_setScaleY(lua_State* state);
 int lua_Joint_setScaleZ(lua_State* state);
 int lua_Joint_setTag(lua_State* state);
+int lua_Joint_setTerrain(lua_State* state);
 int lua_Joint_setTranslation(lua_State* state);
 int lua_Joint_setTranslationX(lua_State* state);
 int lua_Joint_setTranslationY(lua_State* state);

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.