Browse Source

Merge with upstream 0bdb20fd8e8614f74bd5bd8585ec3685ee8511be

# Conflicts:
#	.appveyor.yml
#	.bash_helpers.sh
#	.travis.yml
#	CMake/Modules/CheckCompilerToolchain.cmake
#	CMake/Modules/FindDirectX.cmake
#	CMake/Modules/FindUrho3D.cmake
#	CMake/Modules/UrhoCommon.cmake
#	CMakeLists.txt
#	Docs/AngelScriptAPI.h
#	Docs/Doxyfile.in
#	Docs/GettingStarted.dox
#	Docs/LuaScriptAPI.dox
#	Docs/Reference.dox
#	Docs/ScriptAPI.dox
#	Docs/Urho3D.dox
#	README.md
#	Rakefile
#	Resources/EditorData/AtomicEditor/eulas/atomic_thirdparty_eula.txt
#	Source/Atomic/Atomic2D/ParticleEmitter2D.cpp
#	Source/Atomic/Atomic2D/PhysicsWorld2D.cpp
#	Source/Atomic/Atomic2D/SpriteSheet2D.cpp
#	Source/Atomic/Atomic2D/TmxFile2D.cpp
#	Source/Atomic/Audio/Sound.h
#	Source/Atomic/Core/Context.cpp
#	Source/Atomic/Core/Main.h
#	Source/Atomic/Core/ProcessUtils.cpp
#	Source/Atomic/Core/ProcessUtils.h
#	Source/Atomic/Engine/Application.cpp
#	Source/Atomic/Engine/Application.h
#	Source/Atomic/Engine/Engine.cpp
#	Source/Atomic/Graphics/Animation.h
#	Source/Atomic/Graphics/Model.cpp
#	Source/Atomic/Graphics/Model.h
#	Source/Atomic/Graphics/Text3D/Text3D.cpp
#	Source/Atomic/Graphics/Text3D/Text3D.h
#	Source/Atomic/Graphics/Text3D/Text3DBatch.cpp
#	Source/Atomic/Graphics/Text3D/Text3DBatch.h
#	Source/Atomic/Graphics/Text3D/Text3DBitmap.cpp
#	Source/Atomic/Graphics/Text3D/Text3DFont.cpp
#	Source/Atomic/Graphics/Text3D/Text3DFont.h
#	Source/Atomic/Graphics/Text3D/Text3DFontFace.cpp
#	Source/Atomic/Graphics/Text3D/Text3DFreeType.cpp
#	Source/Atomic/Graphics/Text3D/Text3DText.cpp
#	Source/Atomic/Graphics/Text3D/Text3DText.h
#	Source/Atomic/Graphics/Texture.h
#	Source/Atomic/Graphics/VertexBuffer.cpp
#	Source/Atomic/IO/Log.cpp
#	Source/Atomic/Physics/PhysicsWorld.cpp
#	Source/Atomic/Resource/JSONFile.cpp
#	Source/Atomic/Scene/Node.h
#	Source/CMakeLists.txt
#	Source/Samples/40_Localization/L10n.cpp
#	Source/Samples/42_PBRMaterials/PBRMaterials.cpp
#	Source/Samples/45_InverseKinematics/CMakeLists.txt
#	Source/ThirdParty/Civetweb/CMakeLists.txt
#	Source/ThirdParty/Lua/CMakeLists.txt
#	Source/ThirdParty/Lua/src/loslib.c
#	Source/ThirdParty/LuaJIT/CMakeLists.txt
#	Source/ThirdParty/LuaJIT/src/lj_arch.h
#	Source/ThirdParty/SQLite/CMakeLists.txt
#	Source/ThirdParty/boost/preprocessor/cat.hpp
#	Source/ThirdParty/boost/preprocessor/config/config.hpp
#	Source/ThirdParty/boost/preprocessor/seq/detail/binary_transform.hpp
#	Source/ThirdParty/boost/preprocessor/seq/to_list.hpp
#	Source/ThirdParty/rapidjson/include/rapidjson/internal/dtoa.h
#	Source/ThirdParty/rapidjson/include/rapidjson/internal/ieee754.h
#	Source/ThirdParty/rapidjson/include/rapidjson/internal/regex.h
#	Source/ThirdParty/rapidjson/include/rapidjson/internal/strtod.h
#	Source/ThirdParty/rapidjson/include/rapidjson/istreamwrapper.h
#	Source/ThirdParty/rapidjson/include/rapidjson/pointer.h
#	Source/ThirdParty/rapidjson/include/rapidjson/schema.h
#	Source/Tools/AssetImporter/AssetImporter.cpp
#	Source/Tools/CMakeLists.txt
#	Source/Tools/PackageTool/PackageTool.cpp
#	Source/Tools/Urho3DPlayer/CMakeLists.txt
#	Source/Tools/Urho3DPlayer/Urho3DPlayer.cpp
#	Source/Urho3D/.soversion
#	Source/Urho3D/AngelScript/APITemplates.h
#	Source/Urho3D/AngelScript/AudioAPI.cpp
#	Source/Urho3D/AngelScript/CoreAPI.cpp
#	Source/Urho3D/AngelScript/GraphicsAPI.cpp
#	Source/Urho3D/AngelScript/IKAPI.cpp
#	Source/Urho3D/AngelScript/IOAPI.cpp
#	Source/Urho3D/AngelScript/InputAPI.cpp
#	Source/Urho3D/AngelScript/MathAPI.cpp
#	Source/Urho3D/AngelScript/PhysicsAPI.cpp
#	Source/Urho3D/AngelScript/ResourceAPI.cpp
#	Source/Urho3D/AngelScript/ScriptAPI.h
#	Source/Urho3D/AngelScript/ScriptAPIDump.cpp
#	Source/Urho3D/AngelScript/ScriptFile.cpp
#	Source/Urho3D/AngelScript/UIAPI.cpp
#	Source/Urho3D/AngelScript/Urho2DAPI.cpp
#	Source/Urho3D/CMakeLists.txt
#	Source/Urho3D/LuaScript/pkgs/Audio/Sound.pkg
#	Source/Urho3D/LuaScript/pkgs/Audio/SoundSource.pkg
#	Source/Urho3D/LuaScript/pkgs/Core/ProcessUtils.pkg
#	Source/Urho3D/LuaScript/pkgs/Graphics/Animation.pkg
#	Source/Urho3D/LuaScript/pkgs/Graphics/Model.pkg
#	Source/Urho3D/LuaScript/pkgs/Graphics/Texture.pkg
#	Source/Urho3D/LuaScript/pkgs/Input/Input.pkg
#	Source/Urho3D/LuaScript/pkgs/Math/Quaternion.pkg
#	Source/Urho3D/LuaScript/pkgs/Math/Vector3.pkg
#	Source/Urho3D/LuaScript/pkgs/PhysicsLuaAPI.pkg
#	Source/Urho3D/LuaScript/pkgs/Resource/JSONValue.pkg
#	Source/Urho3D/LuaScript/pkgs/Resource/Resource.pkg
#	Source/Urho3D/LuaScript/pkgs/Scene/Node.pkg
#	Source/Urho3D/LuaScript/pkgs/UI/Font.pkg
#	Source/Urho3D/LuaScript/pkgs/UI/Text.pkg
#	Source/Urho3D/LuaScript/pkgs/UI/Text3D.pkg
#	Source/Urho3D/LuaScript/pkgs/UI/UI.pkg
#	Source/Urho3D/LuaScript/pkgs/Urho2D/ParticleEmitter2D.pkg
#	Source/Urho3D/UI/Cursor.cpp
#	Source/Urho3D/UI/UI.cpp
#	Source/Urho3D/UI/UI.h
#	Source/Urho3D/Urho2D/PhysicsEvents2D.h
#	bin/Autoload/LargeData/Materials/PBR/Check.xml
#	bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic0.xml
#	bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic10.xml
#	bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic3.xml
#	bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic5.xml
#	bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic7.xml
#	bin/Autoload/LargeData/Materials/PBR/Metallic0.xml
#	bin/Autoload/LargeData/Materials/PBR/Metallic10.xml
#	bin/Autoload/LargeData/Materials/PBR/Metallic3.xml
#	bin/Autoload/LargeData/Materials/PBR/Metallic5.xml
#	bin/Autoload/LargeData/Materials/PBR/Metallic7.xml
#	bin/Autoload/LargeData/Materials/PBR/MetallicRough0.xml
#	bin/Autoload/LargeData/Materials/PBR/MetallicRough10.xml
#	bin/Autoload/LargeData/Materials/PBR/MetallicRough3.xml
#	bin/Autoload/LargeData/Materials/PBR/MetallicRough5.xml
#	bin/Autoload/LargeData/Materials/PBR/MetallicRough7.xml
#	bin/Autoload/LargeData/Materials/PBR/Mud.xml
#	bin/Autoload/LargeData/Materials/PBR/Roughness0.xml
#	bin/Autoload/LargeData/Materials/PBR/Roughness10.xml
#	bin/Autoload/LargeData/Materials/PBR/Roughness3.xml
#	bin/Autoload/LargeData/Materials/PBR/Roughness5.xml
#	bin/Autoload/LargeData/Materials/PBR/Roughness7.xml
#	bin/Autoload/LargeData/Materials/PBR/Tile.xml
#	bin/Data/LuaScripts/40_Localization.lua
#	bin/Data/LuaScripts/42_PBRMaterials.lua
#	bin/Data/Scenes/PBRExample.xml
#	bin/Data/Scripts/40_Localization.as
#	bin/Data/Scripts/42_PBRMaterials.as
#	bin/Data/Scripts/Editor.as
#	bin/Data/Scripts/Editor/EditorUI.as
#	bin/Data/Scripts/Editor/EditorView.as
#	cmake_generic.sh
Rokas Kupstys 8 years ago
parent
commit
d91f7ab817
100 changed files with 2211 additions and 515 deletions
  1. 1 0
      .gitignore
  2. 1 1
      Build/CMake/Modules/AtomicMac.cmake
  3. 35 0
      CMake/Modules/PostProcessForWebModule.cmake
  4. 36 7
      Resources/CoreData/Shaders/GLSL/BRDF.glsl
  5. 1 1
      Resources/CoreData/Shaders/GLSL/Constants.glsl
  6. 8 8
      Resources/CoreData/Shaders/GLSL/IBL.glsl
  7. 73 37
      Resources/CoreData/Shaders/GLSL/PBR.glsl
  8. 36 7
      Resources/CoreData/Shaders/HLSL/BRDF.hlsl
  9. 1 1
      Resources/CoreData/Shaders/HLSL/Constants.hlsl
  10. 8 8
      Resources/CoreData/Shaders/HLSL/IBL.hlsl
  11. 66 32
      Resources/CoreData/Shaders/HLSL/PBR.hlsl
  12. 1 1
      Script/Packages/Atomic/Resource.json
  13. 1 0
      Source/Atomic/Atomic2D/CollisionChain2D.cpp
  14. 2 1
      Source/Atomic/Atomic2D/CollisionShape2D.cpp
  15. 11 14
      Source/Atomic/Atomic2D/ParticleEmitter2D.cpp
  16. 6 17
      Source/Atomic/Atomic2D/ParticleEmitter2D.h
  17. 6 12
      Source/Atomic/Atomic2D/PhysicsEvents2D.h
  18. 26 45
      Source/Atomic/Atomic2D/PhysicsWorld2D.cpp
  19. 10 4
      Source/Atomic/Atomic2D/PhysicsWorld2D.h
  20. 9 11
      Source/Atomic/Atomic2D/RigidBody2D.cpp
  21. 2 2
      Source/Atomic/Atomic2D/SpriteSheet2D.cpp
  22. 11 0
      Source/Atomic/Atomic2D/TmxFile2D.cpp
  23. 10 0
      Source/Atomic/Audio/OggVorbisSoundStream.cpp
  24. 3 0
      Source/Atomic/Audio/OggVorbisSoundStream.h
  25. 3 5
      Source/Atomic/Audio/Sound.cpp
  26. 2 2
      Source/Atomic/Audio/Sound.h
  27. 24 0
      Source/Atomic/Audio/SoundSource.cpp
  28. 2 0
      Source/Atomic/Audio/SoundSource.h
  29. 5 0
      Source/Atomic/Audio/SoundStream.cpp
  30. 3 0
      Source/Atomic/Audio/SoundStream.h
  31. 2 2
      Source/Atomic/Core/Context.cpp
  32. 2 2
      Source/Atomic/Core/Main.h
  33. 232 14
      Source/Atomic/Core/ProcessUtils.cpp
  34. 9 2
      Source/Atomic/Core/ProcessUtils.h
  35. 6 6
      Source/Atomic/Engine/Application.cpp
  36. 2 2
      Source/Atomic/Engine/Application.h
  37. 9 6
      Source/Atomic/Engine/Engine.cpp
  38. 1 1
      Source/Atomic/Engine/Engine.h
  39. 2 4
      Source/Atomic/Graphics/AnimatedModel.cpp
  40. 23 19
      Source/Atomic/Graphics/Animation.cpp
  41. 4 4
      Source/Atomic/Graphics/Animation.h
  42. 9 4
      Source/Atomic/Graphics/Direct3D11/D3D11Graphics.cpp
  43. 5 4
      Source/Atomic/Graphics/Direct3D9/D3D9Graphics.cpp
  44. 1 1
      Source/Atomic/Graphics/Graphics.cpp
  45. 1 1
      Source/Atomic/Graphics/GraphicsDefs.h
  46. 40 15
      Source/Atomic/Graphics/Model.cpp
  47. 2 2
      Source/Atomic/Graphics/Model.h
  48. 17 13
      Source/Atomic/Graphics/OpenGL/OGLGraphics.cpp
  49. 2 2
      Source/Atomic/Graphics/OpenGL/OGLGraphicsImpl.h
  50. 2 2
      Source/Atomic/Graphics/OpenGL/OGLTexture2D.cpp
  51. 2 2
      Source/Atomic/Graphics/OpenGL/OGLTextureCube.cpp
  52. 7 7
      Source/Atomic/Graphics/Text3D/Text3D.cpp
  53. 6 6
      Source/Atomic/Graphics/Text3D/Text3D.h
  54. 28 30
      Source/Atomic/Graphics/Text3D/Text3DBatch.cpp
  55. 2 2
      Source/Atomic/Graphics/Text3D/Text3DBatch.h
  56. 3 3
      Source/Atomic/Graphics/Text3D/Text3DBitmap.cpp
  57. 1 1
      Source/Atomic/Graphics/Text3D/Text3DBitmap.h
  58. 24 11
      Source/Atomic/Graphics/Text3D/Text3DFont.cpp
  59. 4 4
      Source/Atomic/Graphics/Text3D/Text3DFont.h
  60. 2 2
      Source/Atomic/Graphics/Text3D/Text3DFontFace.cpp
  61. 8 8
      Source/Atomic/Graphics/Text3D/Text3DFontFace.h
  62. 43 17
      Source/Atomic/Graphics/Text3D/Text3DFreeType.cpp
  63. 28 2
      Source/Atomic/Graphics/Text3D/Text3DFreeType.h
  64. 24 24
      Source/Atomic/Graphics/Text3D/Text3DText.cpp
  65. 18 18
      Source/Atomic/Graphics/Text3D/Text3DText.h
  66. 3 5
      Source/Atomic/Graphics/Texture.cpp
  67. 3 3
      Source/Atomic/Graphics/Texture.h
  68. 12 1
      Source/Atomic/Graphics/VertexBuffer.cpp
  69. 3 0
      Source/Atomic/Graphics/VertexBuffer.h
  70. 8 2
      Source/Atomic/Graphics/View.cpp
  71. 2 0
      Source/Atomic/Graphics/View.h
  72. 2 0
      Source/Atomic/IK/IKSolver.cpp
  73. 2 2
      Source/Atomic/IO/Compression.cpp
  74. 11 3
      Source/Atomic/IO/FileSystem.cpp
  75. 6 6
      Source/Atomic/IO/FileWatcher.cpp
  76. 1 1
      Source/Atomic/IO/FileWatcher.h
  77. 5 5
      Source/Atomic/IO/Log.cpp
  78. 4 4
      Source/Atomic/Input/Input.cpp
  79. 2 0
      Source/Atomic/Input/Input.h
  80. 9 2
      Source/Atomic/Math/MathDefs.h
  81. 20 1
      Source/Atomic/Math/Matrix3.h
  82. 20 0
      Source/Atomic/Math/Matrix3x4.h
  83. 25 5
      Source/Atomic/Math/Matrix4.h
  84. 10 0
      Source/Atomic/Math/Quaternion.cpp
  85. 4 0
      Source/Atomic/Math/Quaternion.h
  86. 15 0
      Source/Atomic/Math/Vector3.h
  87. 1 1
      Source/Atomic/Navigation/DynamicNavigationMesh.cpp
  88. 6 3
      Source/Atomic/Physics/PhysicsWorld.cpp
  89. 744 0
      Source/Atomic/Physics/RaycastVehicle.cpp
  90. 187 0
      Source/Atomic/Physics/RaycastVehicle.h
  91. 2 2
      Source/Atomic/Resource/JSONFile.cpp
  92. 3 2
      Source/Atomic/Resource/JSONValue.cpp
  93. 63 0
      Source/Atomic/Resource/Resource.cpp
  94. 39 0
      Source/Atomic/Resource/Resource.h
  95. 1 1
      Source/Atomic/Resource/ResourceCache.cpp
  96. 2 2
      Source/Atomic/Resource/ResourceCache.h
  97. 15 4
      Source/Atomic/Scene/Node.cpp
  98. 3 0
      Source/Atomic/Scene/Node.h
  99. 3 0
      Source/Atomic/Scene/ValueAnimationInfo.h
  100. 1 1
      Source/AtomicNET/NETNative/CMakeLists.txt

+ 1 - 0
.gitignore

@@ -22,3 +22,4 @@ Script/TypeScript/**/*.d.ts
 !Script/TypeScript/**/*Work.d.ts
 !Script/TypeScript/**/*Work.d.ts
 Script/Haxe/*
 Script/Haxe/*
 **/.vscode
 **/.vscode
+.idea

+ 1 - 1
Build/CMake/Modules/AtomicMac.cmake

@@ -20,4 +20,4 @@ else ()
 endif ()
 endif ()
 
 
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -stdlib=libc++")
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof -stdlib=libc++")
-set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -framework AudioToolbox -framework Carbon -framework Cocoa -framework CoreAudio -framework CoreVideo -framework ForceFeedback -framework IOKit -framework OpenGL -framework CoreServices -framework Security")
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -framework AudioToolbox -framework Carbon -framework Cocoa -framework CoreAudio -framework CoreVideo -framework ForceFeedback -framework IOKit -framework OpenGL -framework CoreServices -framework Security -framework SystemConfiguration")

+ 35 - 0
CMake/Modules/PostProcessForWebModule.cmake

@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2008-2017 the Urho3D project.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+# Post process to glue the main module and side module(s) together
+
+string (REPLACE " " .js',' SIDE_MODULES "'${SIDE_MODULES}.js'")              # Stringify for string replacement
+if (HAS_SHELL_FILE)
+    file (READ ${TARGET_FILE} CONTENT)
+    string (REPLACE ${TARGET_NAME}.js libUrho3D.js CONTENT "${CONTENT}")     # Stringify to preserve semicolons
+    # Assume HTML shell-file has Module object without the 'dynamicLibraries' prop defined yet
+    string (REGEX REPLACE "(var Module *= *{)" \\1dynamicLibraries:[${SIDE_MODULES}], CONTENT "${CONTENT}")
+    file (WRITE ${TARGET_FILE} "${CONTENT}")
+else ()
+    file (READ ${TARGET_DIR}/libUrho3D.js CONTENT)
+    file (WRITE ${TARGET_DIR}/${TARGET_NAME}.main.js "var Module={dynamicLibraries:[${SIDE_MODULES}]};${CONTENT}")
+endif ()

+ 36 - 7
Resources/CoreData/Shaders/GLSL/BRDF.glsl

@@ -21,12 +21,23 @@
         return specular + (vec3(1.0, 1.0, 1.0) - specular) * sphericalGaussian;
         return specular + (vec3(1.0, 1.0, 1.0) - specular) * sphericalGaussian;
     }
     }
 
 
+    vec3 SchlickFresnelCustom(vec3 specular, float LdotH)
+    {
+        float ior = 0.25;
+        float airIor = 1.000277;
+        float f0 = (ior - airIor) / (ior + airIor);
+        float max_ior = 2.5;
+        f0 = clamp(f0 * f0, 0.0, (max_ior - airIor) / (max_ior + airIor));
+        return specular * (f0   + (1 - f0) * pow(2, (-5.55473 * LdotH - 6.98316) * LdotH));
+    }
+
     //Get Fresnel
     //Get Fresnel
     //specular  = the rgb specular color value of the pixel
     //specular  = the rgb specular color value of the pixel
     //VdotH     = the dot product of the camera view direction and the half vector 
     //VdotH     = the dot product of the camera view direction and the half vector 
-    vec3 Fresnel(vec3 specular, float VdotH)
+    vec3 Fresnel(vec3 specular, float VdotH, float LdotH)
     {
     {
-        return SchlickFresnel(specular, VdotH);
+        return SchlickFresnelCustom(specular, LdotH);
+        //return SchlickFresnel(specular, VdotH);
     }
     }
 
 
     // Smith GGX corrected Visibility
     // Smith GGX corrected Visibility
@@ -42,13 +53,19 @@
         return 0.5 / (lambdaV + lambdaL);
         return 0.5 / (lambdaV + lambdaL);
     }
     }
 
 
+    float NeumannVisibility(float NdotV, float NdotL) 
+    {
+        return NdotL * NdotV / max(1e-7, max(NdotL, NdotV));
+    }
+
     // Get Visibility
     // Get Visibility
     // NdotL        = the dot product of the normal and direction to the light
     // NdotL        = the dot product of the normal and direction to the light
     // NdotV        = the dot product of the normal and the camera view direction
     // NdotV        = the dot product of the normal and the camera view direction
     // roughness    = the roughness of the pixel
     // roughness    = the roughness of the pixel
     float Visibility(float NdotL, float NdotV, float roughness)
     float Visibility(float NdotL, float NdotV, float roughness)
     {
     {
-        return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
+        return NeumannVisibility(NdotV, NdotL);
+        //return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
     }
     }
 
 
     // Blinn Distribution
     // Blinn Distribution
@@ -96,9 +113,20 @@
     // NdotV        = the normal dot with the camera view direction
     // NdotV        = the normal dot with the camera view direction
     // NdotL        = the normal dot with the light direction
     // NdotL        = the normal dot with the light direction
     // VdotH        = the camera view direction dot with the half vector
     // VdotH        = the camera view direction dot with the half vector
-    vec3 LambertianDiffuse(vec3 diffuseColor, float NdotL)
+    vec3 LambertianDiffuse(vec3 diffuseColor)
+    {
+        return diffuseColor * (1.0 / M_PI) ;
+    }
+
+    // Custom Lambertian Diffuse
+    // diffuseColor = the rgb color value of the pixel
+    // roughness    = the roughness of the pixel
+    // NdotV        = the normal dot with the camera view direction
+    // NdotL        = the normal dot with the light direction
+    // VdotH        = the camera view direction dot with the half vector
+    vec3 CustomLambertianDiffuse(vec3 diffuseColor, float NdotV, float roughness)
     {
     {
-        return diffuseColor * NdotL;
+        return diffuseColor * (1.0 / M_PI) * pow(NdotV, 0.5 + 0.3 * roughness);
     }
     }
 
 
     // Burley Diffuse
     // Burley Diffuse
@@ -127,8 +155,9 @@
     // VdotH        = the camera view direction dot with the half vector
     // VdotH        = the camera view direction dot with the half vector
     vec3 Diffuse(vec3 diffuseColor, float roughness, float NdotV, float NdotL, float VdotH)
     vec3 Diffuse(vec3 diffuseColor, float roughness, float NdotV, float NdotL, float VdotH)
     {
     {
-        //return LambertianDiffuse(diffuseColor, NdotL);
-        return BurleyDiffuse(diffuseColor, roughness, NdotV, NdotL, VdotH);
+        //return LambertianDiffuse(diffuseColor);
+        return CustomLambertianDiffuse(diffuseColor, NdotV, roughness);
+        //return BurleyDiffuse(diffuseColor, roughness, NdotV, NdotL, VdotH);
     }
     }
 
 
   #endif
   #endif

+ 1 - 1
Resources/CoreData/Shaders/GLSL/Constants.glsl

@@ -2,6 +2,6 @@
 #define M_EPSILON 0.0001
 #define M_EPSILON 0.0001
 
 
 #ifdef PBR
 #ifdef PBR
-#define ROUGHNESS_FLOOR 0.003
+#define ROUGHNESS_FLOOR 0.004
 #define METALNESS_FLOOR 0.03
 #define METALNESS_FLOOR 0.03
 #endif
 #endif

+ 8 - 8
Resources/CoreData/Shaders/GLSL/IBL.glsl

@@ -229,17 +229,16 @@
     ///     normal: surface normal
     ///     normal: surface normal
     ///     reflection: vector of reflection off of the surface
     ///     reflection: vector of reflection off of the surface
     ///     roughness: surface roughness
     ///     roughness: surface roughness
-    vec3 GetSpecularDominantDir(vec3 normal, vec3 reflection, float roughness)
-    {
-        float smoothness = 1.0 - roughness;
-        float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
-        return mix(normal, reflection, lerpFactor);
-    }
+    // vec3 GetSpecularDominantDir(vec3 normal, vec3 reflection, float roughness)
+    // {
+    //     float smoothness = 1.0 - roughness;
+    //     float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
+    //     return mix(normal, reflection, lerpFactor);
+    // }
 
 
     float GetMipFromRoughness(float roughness)
     float GetMipFromRoughness(float roughness)
     {
     {
-        float Level = 3 - 1.15 * log2( roughness );
-        return 9.0 - 1 - Level;
+        return (roughness * 12.0 - pow(roughness, 6.0) * 1.5);
     }
     }
 
 
 
 
@@ -273,6 +272,7 @@
     ///     ambientOcclusion: ambient occlusion
     ///     ambientOcclusion: ambient occlusion
     vec3 ImageBasedLighting(vec3 reflectVec, vec3 tangent, vec3 bitangent, vec3 wsNormal, vec3 toCamera, vec3 diffColor, vec3 specColor, float roughness, inout vec3 reflectionCubeColor)
     vec3 ImageBasedLighting(vec3 reflectVec, vec3 tangent, vec3 bitangent, vec3 wsNormal, vec3 toCamera, vec3 diffColor, vec3 specColor, float roughness, inout vec3 reflectionCubeColor)
     {
     {
+        roughness = max(roughness, 0.08);
         reflectVec = GetSpecularDominantDir(wsNormal, reflectVec, roughness);
         reflectVec = GetSpecularDominantDir(wsNormal, reflectVec, roughness);
         float ndv = clamp(dot(-toCamera, wsNormal), 0.0, 1.0);
         float ndv = clamp(dot(-toCamera, wsNormal), 0.0, 1.0);
 
 

+ 73 - 37
Resources/CoreData/Shaders/GLSL/PBR.glsl

@@ -1,38 +1,70 @@
 #include "BRDF.glsl"
 #include "BRDF.glsl"
 #ifdef COMPILEPS
 #ifdef COMPILEPS
+#line 100
+    vec3 GetSpecularDominantDir(vec3 normal, vec3 reflection, float roughness)
+    {
+        float smoothness = 1.0 - roughness;
+        float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
+        return mix(normal, reflection, lerpFactor);
+    }
 
 
-    vec3 SphereLight(vec3 worldPos, vec3 lightVec, vec3 normal, vec3 toCamera, float roughness, vec3 specColor, out float ndl)
+    vec3 SphereLight(vec3 worldPos, vec3 lightVec, vec3 normal, vec3 toCamera, float roughness, vec3 specColor, vec3 diffColor, out float ndl)
     {
     {
-        vec3 pos   = (cLightPosPS.xyz - worldPos);
-        float radius = cLightRad;
+        float specEnergy = 1.0f;
 
 
-        vec3 reflectVec   = reflect(-toCamera, normal);
-        vec3 centreToRay  = dot(pos, reflectVec) * reflectVec - pos;
-        vec3 closestPoint = pos + centreToRay * clamp(radius / length(centreToRay), 0.0, 1.0);
+        float radius = cLightRad / 100;
+        float rough2 = max(roughness, 0.08);
+        rough2 *= rough2;
 
 
-        vec3 l = normalize(closestPoint);
-        vec3 h = normalize(toCamera + l);
+        float radius2           = radius * radius;
+        float distToLightSqrd   = dot(lightVec,lightVec);
+        float invDistToLight    = inversesqrt(distToLightSqrd);
+        float sinAlphaSqr       = clamp(radius2 / distToLightSqrd, 0.0, 1.0);
+        float sinAlpha          = sqrt(sinAlphaSqr);
 
 
-        ndl       = clamp(dot(normal, l), 0.0, 1.0);
-        float hdn = clamp(dot(h, normal), 0.0, 1.0);
-        float hdv = dot(h, toCamera);
-        float ndv = clamp(dot(normal, toCamera), 0.0, 1.0);
+        ndl       = dot(normal, (lightVec * invDistToLight));
 
 
-        float distL      = length(pos);
-        float alpha      = roughness * roughness;
-        float alphaPrime = clamp(radius / (distL * 2.0) + alpha, 0.0, 1.0);
+        if(ndl < sinAlpha)
+        {
+            ndl = max(ndl, -sinAlpha);
+            ndl = ((sinAlpha + ndl) * (sinAlpha + ndl)) / (4 * sinAlpha);
+        }
+
+        float sphereAngle = clamp(radius * invDistToLight, 0.0, 1.0);
+                            
+        specEnergy = rough2 / (rough2 + 0.5f * sphereAngle);
+        specEnergy *= specEnergy;                           
 
 
-        vec3 fresnelTerm = Fresnel(specColor, hdv) ;
-        float distTerm     = Distribution(hdn, alphaPrime);
-        float visTerm      = Visibility(ndl, ndv, roughness);
+        vec3 R = 2 * dot(toCamera, normal) * normal - toCamera;
+        R = GetSpecularDominantDir(normal, R, roughness);
 
 
-        return distTerm * visTerm * fresnelTerm ;
+        // Find closest point on sphere to ray
+        vec3 closestPointOnRay = dot(lightVec, R) * R;
+        vec3 centerToRay = closestPointOnRay - lightVec;
+        float invDistToRay = inversesqrt(dot(centerToRay, centerToRay));
+        vec3 closestPointOnSphere = lightVec + centerToRay * clamp(radius * invDistToRay, 0.0, 1.0);
+
+        lightVec = closestPointOnSphere;
+        vec3 L = normalize(lightVec);
+
+        vec3 h = normalize(toCamera + L);
+        float hdn = clamp(dot(h, normal), 0.0, 1.0);
+        float hdv = dot(h, toCamera);
+        float ndv = clamp(dot(normal, toCamera),0.0, 1.0);
+        float hdl = clamp(dot(h, lightVec), 0.0, 1.0);
+
+        vec3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, hdv)  * ndl;
+        vec3 fresnelTerm = Fresnel(specColor, hdv, hdl) ;
+        float distTerm = Distribution(hdn, roughness);
+        float visTerm = Visibility(ndl, ndv, roughness);
+        vec3 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+        return diffuseFactor + specularFactor;
     }
     }
 
 
-    vec3 TubeLight(vec3 worldPos, vec3 lightVec, vec3 normal, vec3 toCamera, float roughness, vec3 specColor, out float ndl)
+    vec3 TubeLight(vec3 worldPos, vec3 lightVec, vec3 normal, vec3 toCamera, float roughness, vec3 specColor, vec3 diffColor, out float ndl)
     {
     {
-        float radius      = cLightRad;
-        float len         = cLightLength; 
+        float radius      = cLightRad / 100;
+        float len         = cLightLength / 10; 
         vec3 pos         = (cLightPosPS.xyz - worldPos);
         vec3 pos         = (cLightPosPS.xyz - worldPos);
         vec3 reflectVec  = reflect(-toCamera, normal);
         vec3 reflectVec  = reflect(-toCamera, normal);
         
         
@@ -60,20 +92,22 @@
         vec3 l = normalize(closestPoint);
         vec3 l = normalize(closestPoint);
         vec3 h = normalize(toCamera + l);
         vec3 h = normalize(toCamera + l);
 
 
-        ndl       =  clamp(dot(normal, lightVec), 0.0, 1.0);
+        ndl       = clamp(dot(normal, lightVec), 0.0, 1.0);
         float hdn = clamp(dot(h, normal), 0.0, 1.0);
         float hdn = clamp(dot(h, normal), 0.0, 1.0);
         float hdv = dot(h, toCamera);
         float hdv = dot(h, toCamera);
-        float ndv = clamp(dot(normal, toCamera), 0.0 ,1.0);
+        float ndv = clamp(dot(normal, toCamera), 0.0, 1.0);
+        float hdl = clamp(dot(h, lightVec), 0.0, 1.0);
 
 
         float distL      = length(closestPoint);
         float distL      = length(closestPoint);
-        float alpha      = roughness * roughness;
+        float alpha      = max(roughness, 0.08) * max(roughness, 0.08);
         float alphaPrime = clamp(radius / (distL * 2.0) + alpha, 0.0, 1.0);
         float alphaPrime = clamp(radius / (distL * 2.0) + alpha, 0.0, 1.0);
 
 
-        vec3 fresnelTerm = Fresnel(specColor, hdv) ;
-        float distTerm     = Distribution(hdn, alphaPrime);
-        float visTerm      = Visibility(ndl, ndv, roughness);
-
-        return distTerm * visTerm * fresnelTerm ;
+        vec3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, hdv)  * ndl;
+        vec3 fresnelTerm = Fresnel(specColor, hdv, hdl) ;
+        float distTerm = Distribution(hdn, roughness);
+        float visTerm = Visibility(ndl, ndv, roughness);
+        vec3 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+        return diffuseFactor + specularFactor;
     }
     }
 
 
 	//Return the PBR BRDF value
 	//Return the PBR BRDF value
@@ -87,10 +121,11 @@
 	vec3 GetBRDF(vec3 worldPos, vec3 lightDir, vec3 lightVec, vec3 toCamera, vec3 normal, float roughness, vec3 diffColor, vec3 specColor)
 	vec3 GetBRDF(vec3 worldPos, vec3 lightDir, vec3 lightVec, vec3 toCamera, vec3 normal, float roughness, vec3 diffColor, vec3 specColor)
 	{
 	{
         vec3 Hn = normalize(toCamera + lightDir);
         vec3 Hn = normalize(toCamera + lightDir);
-        float vdh = clamp((dot(toCamera, Hn)), M_EPSILON, 1.0);
-        float ndh = clamp((dot(normal, Hn)), M_EPSILON, 1.0);
-        float ndl = clamp((dot(normal, lightVec)), M_EPSILON, 1.0);
-        float ndv = clamp((dot(normal, toCamera)), M_EPSILON, 1.0);
+        float vdh = clamp(dot(toCamera, Hn), M_EPSILON, 1.0);
+        float ndh = clamp(dot(normal, Hn), M_EPSILON, 1.0);
+        float ndl = clamp(dot(normal, lightVec), M_EPSILON, 1.0);
+        float ldh = clamp(dot(lightVec, Hn), M_EPSILON, 1.0);
+        float ndv = abs(dot(normal, toCamera)) + 1e-5;
 
 
         vec3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, vdh);
         vec3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, vdh);
         vec3 specularFactor = vec3(0.0, 0.0, 0.0);
         vec3 specularFactor = vec3(0.0, 0.0, 0.0);
@@ -100,22 +135,23 @@
             {
             {
                 if(cLightLength > 0.0)
                 if(cLightLength > 0.0)
                 {
                 {
-                    specularFactor = TubeLight(worldPos, lightVec, normal, toCamera, roughness, specColor, ndl);
+                    specularFactor = TubeLight(worldPos, lightVec, normal, toCamera, roughness, specColor, diffColor, ndl);
                     specularFactor *= ndl;
                     specularFactor *= ndl;
                 }
                 }
                 else
                 else
                 {
                 {
-                    specularFactor = SphereLight(worldPos, lightVec, normal, toCamera, roughness, specColor, ndl);
+                    specularFactor = SphereLight(worldPos, lightVec, normal, toCamera, roughness, specColor, diffColor, ndl);
                     specularFactor *= ndl;
                     specularFactor *= ndl;
                 }
                 }
             }
             }
             else
             else
             {
             {
-                vec3 fresnelTerm = Fresnel(specColor, vdh) ;
+                vec3 fresnelTerm = Fresnel(specColor, vdh, ldh) ;
                 float distTerm = Distribution(ndh, roughness);
                 float distTerm = Distribution(ndh, roughness);
                 float visTerm = Visibility(ndl, ndv, roughness);
                 float visTerm = Visibility(ndl, ndv, roughness);
 
 
                 specularFactor = fresnelTerm * distTerm * visTerm  / M_PI;
                 specularFactor = fresnelTerm * distTerm * visTerm  / M_PI;
+                return diffuseFactor + specularFactor;
             }
             }
         #endif
         #endif
 
 

+ 36 - 7
Resources/CoreData/Shaders/HLSL/BRDF.hlsl

@@ -21,12 +21,23 @@
         return specular + (float3(1.0, 1.0, 1.0) - specular) * sphericalGaussian;
         return specular + (float3(1.0, 1.0, 1.0) - specular) * sphericalGaussian;
     }
     }
 
 
+    float3 SchlickFresnelCustom(float3 specular, float LdotH)
+    {
+        float ior = 0.25;
+        float airIor = 1.000277;
+        float f0 = (ior - airIor) / (ior + airIor);
+        const float max_ior = 2.5;
+        f0 = clamp(f0 * f0, 0.0, (max_ior - airIor) / (max_ior + airIor));
+        return specular * (f0   + (1 - f0) * pow(2, (-5.55473 * LdotH - 6.98316) * LdotH));
+    }
+
     //Get Fresnel
     //Get Fresnel
     //specular  = the rgb specular color value of the pixel
     //specular  = the rgb specular color value of the pixel
     //VdotH     = the dot product of the camera view direction and the half vector 
     //VdotH     = the dot product of the camera view direction and the half vector 
-    float3 Fresnel(float3 specular, float VdotH)
+    float3 Fresnel(float3 specular, float VdotH, float LdotH)
     {
     {
-        return SchlickFresnel(specular, VdotH);
+        return SchlickFresnelCustom(specular, LdotH);
+        //return SchlickFresnel(specular, VdotH);
     }
     }
 
 
     // Smith GGX corrected Visibility
     // Smith GGX corrected Visibility
@@ -42,13 +53,19 @@
         return 0.5 / (lambdaV + lambdaL);
         return 0.5 / (lambdaV + lambdaL);
     }
     }
 
 
+    float NeumannVisibility(float NdotV, float NdotL) 
+    {
+        return NdotL * NdotV / max(1e-7, max(NdotL, NdotV));
+    }
+
     // Get Visibility
     // Get Visibility
     // NdotL        = the dot product of the normal and direction to the light
     // NdotL        = the dot product of the normal and direction to the light
     // NdotV        = the dot product of the normal and the camera view direction
     // NdotV        = the dot product of the normal and the camera view direction
     // roughness    = the roughness of the pixel
     // roughness    = the roughness of the pixel
     float Visibility(float NdotL, float NdotV, float roughness)
     float Visibility(float NdotL, float NdotV, float roughness)
     {
     {
-        return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
+        return NeumannVisibility(NdotV, NdotL);
+        //return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
     }
     }
 
 
     // GGX Distribution
     // GGX Distribution
@@ -96,9 +113,20 @@
     // NdotV        = the normal dot with the camera view direction
     // NdotV        = the normal dot with the camera view direction
     // NdotL        = the normal dot with the light direction
     // NdotL        = the normal dot with the light direction
     // VdotH        = the camera view direction dot with the half vector
     // VdotH        = the camera view direction dot with the half vector
-    float3 LambertianDiffuse(float3 diffuseColor, float NdotL)
+    float3 LambertianDiffuse(float3 diffuseColor)
+    {
+        return diffuseColor * (1.0 / M_PI) ;
+    }
+
+    // Custom Lambertian Diffuse
+    // diffuseColor = the rgb color value of the pixel
+    // roughness    = the roughness of the pixel
+    // NdotV        = the normal dot with the camera view direction
+    // NdotL        = the normal dot with the light direction
+    // VdotH        = the camera view direction dot with the half vector
+    float3 CustomLambertianDiffuse(float3 diffuseColor, float NdotV, float roughness)
     {
     {
-        return diffuseColor * NdotL;
+        return diffuseColor * (1.0 / M_PI) * pow(NdotV, 0.5 + 0.3 * roughness);
     }
     }
 
 
     // Burley Diffuse
     // Burley Diffuse
@@ -127,8 +155,9 @@
     // VdotH        = the camera view direction dot with the half vector
     // VdotH        = the camera view direction dot with the half vector
     float3 Diffuse(float3 diffuseColor, float roughness, float NdotV, float NdotL, float VdotH)
     float3 Diffuse(float3 diffuseColor, float roughness, float NdotV, float NdotL, float VdotH)
     {
     {
-        //return LambertianDiffuse(diffuseColor, NdotL);
-        return BurleyDiffuse(diffuseColor, roughness, NdotV, NdotL, VdotH);
+        //return LambertianDiffuse(diffuseColor);
+        return CustomLambertianDiffuse(diffuseColor, NdotV, roughness);
+        //return BurleyDiffuse(diffuseColor, roughness, NdotV, NdotL, VdotH);
     }
     }
 
 
   #endif
   #endif

+ 1 - 1
Resources/CoreData/Shaders/HLSL/Constants.hlsl

@@ -2,6 +2,6 @@
 #define M_EPSILON 0.0001
 #define M_EPSILON 0.0001
 
 
 #ifdef PBR
 #ifdef PBR
-#define ROUGHNESS_FLOOR 0.003
+#define ROUGHNESS_FLOOR 0.004
 #define METALNESS_FLOOR 0.03
 #define METALNESS_FLOOR 0.03
 #endif
 #endif

+ 8 - 8
Resources/CoreData/Shaders/HLSL/IBL.hlsl

@@ -220,17 +220,16 @@
     ///     normal: surface normal
     ///     normal: surface normal
     ///     reflection: vector of reflection off of the surface
     ///     reflection: vector of reflection off of the surface
     ///     roughness: surface roughness
     ///     roughness: surface roughness
-    float3 GetSpecularDominantDir(float3 normal, float3 reflection, float roughness)
-    {
-        const float smoothness = 1.0 - roughness;
-        const float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
-        return lerp(normal, reflection, lerpFactor);
-    }
+    // float3 GetSpecularDominantDir(float3 normal, float3 reflection, float roughness)
+    // {
+    //     const float smoothness = 1.0 - roughness;
+    //     const float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
+    //     return lerp(normal, reflection, lerpFactor);
+    // }
 
 
     float GetMipFromRoughness(float roughness)
     float GetMipFromRoughness(float roughness)
     {
     {
-        float Level = 3 - 1.15 * log2( roughness );
-        return 9.0 - 1 - Level;
+        return (roughness * 12.0 - pow(roughness, 6.0) * 1.5);
     }
     }
 
 
 
 
@@ -264,6 +263,7 @@
     ///     ambientOcclusion: ambient occlusion
     ///     ambientOcclusion: ambient occlusion
     float3 ImageBasedLighting(in float3 reflectVec, in float3 tangent, in float3 bitangent, in float3 wsNormal, in float3 toCamera, in float3 diffColor, in float3 specColor, in float roughness, inout float3 reflectionCubeColor)
     float3 ImageBasedLighting(in float3 reflectVec, in float3 tangent, in float3 bitangent, in float3 wsNormal, in float3 toCamera, in float3 diffColor, in float3 specColor, in float roughness, inout float3 reflectionCubeColor)
     { 
     { 
+        roughness = max(roughness, 0.08);
         reflectVec = GetSpecularDominantDir(wsNormal, reflectVec, roughness);
         reflectVec = GetSpecularDominantDir(wsNormal, reflectVec, roughness);
         const float ndv = saturate(dot(-toCamera, wsNormal));
         const float ndv = saturate(dot(-toCamera, wsNormal));
 
 

+ 66 - 32
Resources/CoreData/Shaders/HLSL/PBR.hlsl

@@ -1,40 +1,71 @@
 #include "BRDF.hlsl"
 #include "BRDF.hlsl"
 #ifdef COMPILEPS
 #ifdef COMPILEPS
 
 
-    
+    float3 GetSpecularDominantDir(float3 normal, float3 reflection, float roughness)
+    {
+        const float smoothness = 1.0 - roughness;
+        const float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
+        return lerp(normal, reflection, lerpFactor);
+    }
 
 
-    float3 SphereLight(float3 worldPos, float3 lightVec, float3 normal, float3 toCamera, float roughness, float3 specColor, out float ndl)
+    float3 SphereLight(float3 worldPos, float3 lightVec, float3 normal, float3 toCamera, float roughness, float3 specColor, float3 diffColor, out float ndl)
     {
     {
-        float3 pos   = (cLightPosPS.xyz - worldPos);
-        float radius = cLightRad;
+        float specEnergy = 1.0f;
 
 
-        float3 reflectVec   = reflect(-toCamera, normal);
-        float3 centreToRay  = dot(pos, reflectVec) * reflectVec - pos;
-        float3 closestPoint = pos + centreToRay * saturate(radius / length(centreToRay));
+        float radius = cLightRad / 100;
+        float rough2 = max(roughness, 0.08);
+        rough2 *= rough2;
 
 
-        float3 l = normalize(closestPoint);
-        float3 h = normalize(toCamera + l);
+        float radius2           = radius * radius;
+        float distToLightSqrd   = dot(lightVec,lightVec);
+        float invDistToLight    = rsqrt(distToLightSqrd);
+        float sinAlphaSqr       = saturate(radius2 / distToLightSqrd);
+        float sinAlpha          = sqrt(sinAlphaSqr);
+
+        ndl       = dot(normal, (lightVec * invDistToLight));
+
+        if(ndl < sinAlpha)
+        {
+            ndl = max(ndl, -sinAlpha);
+            ndl = ((sinAlpha + ndl) * (sinAlpha + ndl)) / (4 * sinAlpha);
+        }
+
+        float sphereAngle = saturate(radius * invDistToLight);
+                            
+        specEnergy = rough2 / (rough2 + 0.5f * sphereAngle);
+        specEnergy *= specEnergy;                           
 
 
-        ndl       = saturate(dot(normal, l));
+        float3 R = 2 * dot(toCamera, normal) * normal - toCamera;
+        R = GetSpecularDominantDir(normal, R, roughness);
+
+        // Find closest point on sphere to ray
+        float3 closestPointOnRay = dot(lightVec, R) * R;
+        float3 centerToRay = closestPointOnRay - lightVec;
+        float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
+        float3 closestPointOnSphere = lightVec + centerToRay * saturate(radius * invDistToRay);
+
+        lightVec = closestPointOnSphere;
+        float3 L = normalize(lightVec);
+
+        float3 h = normalize(toCamera + L);
         float hdn = saturate(dot(h, normal));
         float hdn = saturate(dot(h, normal));
         float hdv = dot(h, toCamera);
         float hdv = dot(h, toCamera);
         float ndv = saturate(dot(normal, toCamera));
         float ndv = saturate(dot(normal, toCamera));
+        float hdl = saturate(dot(h, lightVec));
 
 
-        float distL      = length(pos);
-        float alpha      = roughness * roughness;
-        float alphaPrime = saturate(radius / (distL * 2.0) + alpha);
-
-        const float3 fresnelTerm = Fresnel(specColor, hdv) ;
-        const float distTerm     = Distribution(hdn, alphaPrime);
-        const float visTerm      = Visibility(ndl, ndv, roughness);
+        const float3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, hdv)  * ndl;
+        const float3 fresnelTerm = Fresnel(specColor, hdv, hdl) ;
+        const float distTerm = Distribution(hdn, roughness);
+        const float visTerm = Visibility(ndl, ndv, roughness);
+        float3 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+        return diffuseFactor + specularFactor;
 
 
-        return distTerm * visTerm * fresnelTerm ;
     }
     }
 
 
-    float3 TubeLight(float3 worldPos, float3 lightVec, float3 normal, float3 toCamera, float roughness, float3 specColor, out float ndl)
+    float3 TubeLight(float3 worldPos, float3 lightVec, float3 normal, float3 toCamera, float roughness, float3 specColor, float3 diffColor, out float ndl)
     {
     {
-         float radius      = cLightRad;
-         float len         = cLightLength; 
+        float radius      = cLightRad / 100;
+        float len         = cLightLength / 10; 
         float3 pos         = (cLightPosPS.xyz - worldPos);
         float3 pos         = (cLightPosPS.xyz - worldPos);
         float3 reflectVec  = reflect(-toCamera, normal);
         float3 reflectVec  = reflect(-toCamera, normal);
         
         
@@ -66,16 +97,18 @@
         float hdn = saturate(dot(h, normal));
         float hdn = saturate(dot(h, normal));
         float hdv = dot(h, toCamera);
         float hdv = dot(h, toCamera);
         float ndv = saturate(dot(normal, toCamera));
         float ndv = saturate(dot(normal, toCamera));
+        float hdl = saturate(dot(h, lightVec));
 
 
         float distL      = length(closestPoint);
         float distL      = length(closestPoint);
-        float alpha      = roughness * roughness;
+        float alpha      = max(roughness, 0.08) * max(roughness, 0.08);
         float alphaPrime = saturate(radius / (distL * 2.0) + alpha);
         float alphaPrime = saturate(radius / (distL * 2.0) + alpha);
 
 
-        const float3 fresnelTerm = Fresnel(specColor, hdv) ;
-        const float distTerm     = Distribution(hdn, alphaPrime);
-        const float visTerm      = Visibility(ndl, ndv, roughness);
-
-        return distTerm * visTerm * fresnelTerm ;
+       const float3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, hdv)  * ndl;
+       const float3 fresnelTerm = Fresnel(specColor, hdv, hdl) ;
+       const float distTerm = Distribution(hdn, roughness);
+       const float visTerm = Visibility(ndl, ndv, roughness);
+       float3 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+       return diffuseFactor + specularFactor;
     }
     }
 
 
 	//Return the PBR BRDF value
 	//Return the PBR BRDF value
@@ -94,6 +127,7 @@
         const float ndh = clamp((dot(normal, Hn)), M_EPSILON, 1.0);
         const float ndh = clamp((dot(normal, Hn)), M_EPSILON, 1.0);
         float ndl = clamp((dot(normal, lightVec)), M_EPSILON, 1.0);
         float ndl = clamp((dot(normal, lightVec)), M_EPSILON, 1.0);
         const float ndv = clamp((dot(normal, toCamera)), M_EPSILON, 1.0);
         const float ndv = clamp((dot(normal, toCamera)), M_EPSILON, 1.0);
+        const float ldh = clamp((dot(lightVec, Hn)), M_EPSILON, 1.0);
 
 
         const float3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, vdh)  * ndl;
         const float3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, vdh)  * ndl;
         float3 specularFactor = 0;
         float3 specularFactor = 0;
@@ -103,21 +137,21 @@
             {
             {
                 if(cLightLength > 0.0)
                 if(cLightLength > 0.0)
                 {
                 {
-                    specularFactor = TubeLight(worldPos, lightVec, normal, toCamera, roughness, specColor, ndl);
-                    specularFactor *= ndl;
+                    return TubeLight(worldPos, lightVec, normal, toCamera, roughness, specColor, diffColor, ndl);
+                    
                 }
                 }
                 else
                 else
                 {
                 {
-                    specularFactor = SphereLight(worldPos, lightVec, normal, toCamera, roughness, specColor, ndl);
-                    specularFactor *= ndl;
+                    return SphereLight(worldPos, lightVec, normal, toCamera, roughness, specColor, diffColor, ndl);
                 }
                 }
             }
             }
             else
             else
             {
             {
-                const float3 fresnelTerm = Fresnel(specColor, vdh) ;
+                const float3 fresnelTerm = Fresnel(specColor, vdh, ldh) ;
                 const float distTerm = Distribution(ndh, roughness);
                 const float distTerm = Distribution(ndh, roughness);
                 const float visTerm = Visibility(ndl, ndv, roughness);
                 const float visTerm = Visibility(ndl, ndv, roughness);
                 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
                 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+                return diffuseFactor + specularFactor;
             }
             }
 
 
         #endif
         #endif

+ 1 - 1
Script/Packages/Atomic/Resource.json

@@ -2,7 +2,7 @@
 	"name" : "Resource",
 	"name" : "Resource",
 	"sources" : ["Source/Atomic/Resource"],
 	"sources" : ["Source/Atomic/Resource"],
 	"includes" : ["<Atomic/IO/PackageFile.h>"],
 	"includes" : ["<Atomic/IO/PackageFile.h>"],
-	"classes" : ["Resource", "ResourceCache", "XMLFile", "PListFile", "JSONFile", "Image", "ResourceNameIterator"],
+	"classes" : ["Resource", "ResourceWithMetadata", "ResourceCache", "XMLFile", "PListFile", "JSONFile", "Image", "ResourceNameIterator"],
 	"overloads": {
 	"overloads": {
 		"Image": {
 		"Image": {
 			"GetPixel": ["int", "int"],
 			"GetPixel": ["int", "int"],

+ 1 - 0
Source/Atomic/Atomic2D/CollisionChain2D.cpp

@@ -135,6 +135,7 @@ void CollisionChain2D::RecreateFixture()
     for (unsigned i = 0; i < count; ++i)
     for (unsigned i = 0; i < count; ++i)
         b2Vertices[i] = ToB2Vec2(vertices_[i] * worldScale);
         b2Vertices[i] = ToB2Vec2(vertices_[i] * worldScale);
 
 
+    chainShape_.Clear();
     if (loop_)
     if (loop_)
         chainShape_.CreateLoop(&b2Vertices[0], count);
         chainShape_.CreateLoop(&b2Vertices[0], count);
     else
     else

+ 2 - 1
Source/Atomic/Atomic2D/CollisionShape2D.cpp

@@ -303,7 +303,8 @@ void CollisionShape2D::OnNodeSet(Node* node)
 
 
 void CollisionShape2D::OnMarkedDirty(Node* node)
 void CollisionShape2D::OnMarkedDirty(Node* node)
 {
 {
-    Vector3 newWorldScale = node_->GetWorldScale();
+    // Use signed world scale to allow flipping of sprites by negative scale to work properly in regard to the collision shape
+    Vector3 newWorldScale = node_->GetSignedWorldScale();
 
 
     Vector3 delta = newWorldScale - cachedWorldScale_;
     Vector3 delta = newWorldScale - cachedWorldScale_;
     if (delta.DotProduct(delta) < 0.01f)
     if (delta.DotProduct(delta) < 0.01f)

+ 11 - 14
Source/Atomic/Atomic2D/ParticleEmitter2D.cpp

@@ -73,6 +73,7 @@ void ParticleEmitter2D::RegisterObject(Context* context)
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Sprite ", GetSpriteAttr, SetSpriteAttr, ResourceRef, ResourceRef(Sprite2D::GetTypeStatic()),
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Sprite ", GetSpriteAttr, SetSpriteAttr, ResourceRef, ResourceRef(Sprite2D::GetTypeStatic()),
         AM_DEFAULT);
         AM_DEFAULT);
     ATOMIC_ENUM_ACCESSOR_ATTRIBUTE("Blend Mode", GetBlendMode, SetBlendMode, BlendMode, blendModeNames, BLEND_ALPHA, AM_DEFAULT);
     ATOMIC_ENUM_ACCESSOR_ATTRIBUTE("Blend Mode", GetBlendMode, SetBlendMode, BlendMode, blendModeNames, BLEND_ALPHA, AM_DEFAULT);
+    ATOMIC_ACCESSOR_ATTRIBUTE("Is Emitting", IsEmitting, SetEmitting, bool, true, AM_DEFAULT);
 }
 }
 
 
 void ParticleEmitter2D::OnSetEnabled()
 void ParticleEmitter2D::OnSetEnabled()
@@ -168,6 +169,15 @@ void ParticleEmitter2D::SetSpriteAttr(const ResourceRef& value)
         SetSprite(sprite);
         SetSprite(sprite);
 }
 }
 
 
+void ParticleEmitter2D::SetEmitting(bool enable)
+{
+    if (enable != emitting_)
+    {
+        emitting_ = enable;
+        emitParticleTime_ = 0.0f;
+    }
+}
+
 ResourceRef ParticleEmitter2D::GetSpriteAttr() const
 ResourceRef ParticleEmitter2D::GetSpriteAttr() const
 {
 {
     return Sprite2D::SaveToResourceRef(sprite_);
     return Sprite2D::SaveToResourceRef(sprite_);
@@ -328,7 +338,7 @@ void ParticleEmitter2D::Update(float timeStep)
         }
         }
     }
     }
 
 
-    if (emissionTime_ > 0.0f)
+    if (emitting_ && emissionTime_ > 0.0f)
     {
     {
         float worldAngle = GetNode()->GetWorldRotation().RollAngle();
         float worldAngle = GetNode()->GetWorldRotation().RollAngle();
 
 
@@ -464,17 +474,4 @@ void ParticleEmitter2D::UpdateParticle(Particle2D& particle, float timeStep, con
     boundingBoxMaxPoint_.z_ = Max(boundingBoxMaxPoint_.z_, particle.position_.z_);
     boundingBoxMaxPoint_.z_ = Max(boundingBoxMaxPoint_.z_, particle.position_.z_);
 }
 }
 
 
-// ATOMIC BEGIN
-
-void ParticleEmitter2D::SetEmitting(bool enable)
-{
-    emitting_ = enable;
-}
-bool ParticleEmitter2D::IsEmitting() const
-{
-    return emitting_;
-}
-
-// ATOMIC END
-
 }
 }

+ 6 - 17
Source/Atomic/Atomic2D/ParticleEmitter2D.h

@@ -96,6 +96,8 @@ public:
     void SetBlendMode(BlendMode blendMode);
     void SetBlendMode(BlendMode blendMode);
     /// Set max particles.
     /// Set max particles.
     void SetMaxParticles(unsigned maxParticles);
     void SetMaxParticles(unsigned maxParticles);
+    /// Set whether should be emitting. If the state was changed, also resets the emission period timer.
+    void SetEmitting(bool enable);
 
 
     /// Return particle effect.
     /// Return particle effect.
     ParticleEffect2D* GetEffect() const;
     ParticleEffect2D* GetEffect() const;
@@ -116,15 +118,8 @@ public:
     void SetSpriteAttr(const ResourceRef& value);
     void SetSpriteAttr(const ResourceRef& value);
     /// Return sprite attribute.
     /// Return sprite attribute.
     ResourceRef GetSpriteAttr() const;
     ResourceRef GetSpriteAttr() const;
-
-    // ATOMIC BEGIN
-
-    /// Set emission state.
-    void SetEmitting(bool enable);
-    /// Return emission state.
-    bool IsEmitting() const;
-
-    // ATOMIC END
+    /// Return whether is currently emitting.
+    bool IsEmitting() const { return emitting_; }
 
 
 private:
 private:
     /// Handle scene being assigned.
     /// Handle scene being assigned.
@@ -158,20 +153,14 @@ private:
     float emissionTime_;
     float emissionTime_;
     /// Emit particle time
     /// Emit particle time
     float emitParticleTime_;
     float emitParticleTime_;
+    /// Currently emitting flag.
+    bool emitting_;
     /// Particles.
     /// Particles.
     Vector<Particle2D> particles_;
     Vector<Particle2D> particles_;
     /// Bounding box min point.
     /// Bounding box min point.
     Vector3 boundingBoxMinPoint_;
     Vector3 boundingBoxMinPoint_;
     /// Bounding box max point.
     /// Bounding box max point.
     Vector3 boundingBoxMaxPoint_;
     Vector3 boundingBoxMaxPoint_;
-
-    // ATOMIC BEGIN
-
-    /// Emission enabled.
-    bool emitting_;
-
-    // ATOMIC END
-
 };
 };
 
 
 }
 }

+ 6 - 12
Source/Atomic/Atomic2D/PhysicsEvents2D.h

@@ -39,8 +39,7 @@ ATOMIC_EVENT(E_PHYSICSUPDATECONTACT2D, PhysicsUpdateContact2D)
     ATOMIC_PARAM(P_BODYB, BodyB);                  // RigidBody2D pointer
     ATOMIC_PARAM(P_BODYB, BodyB);                  // RigidBody2D pointer
     ATOMIC_PARAM(P_NODEA, NodeA);                  // Node pointer
     ATOMIC_PARAM(P_NODEA, NodeA);                  // Node pointer
     ATOMIC_PARAM(P_NODEB, NodeB);                  // Node pointer
     ATOMIC_PARAM(P_NODEB, NodeB);                  // Node pointer
-    ATOMIC_PARAM(P_CONTACT, Contact);              // b2Contact pointer
-    ATOMIC_PARAM(P_CONTACTPOINTS, ContactPoints);  // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
+    ATOMIC_PARAM(P_CONTACTS, Contacts);            // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
     ATOMIC_PARAM(P_SHAPEA, ShapeA);                // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPEA, ShapeA);                // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPEB, ShapeB);                // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPEB, ShapeB);                // CollisionShape2D pointer
     ATOMIC_PARAM(P_ENABLED, Enabled);              // bool [in/out]
     ATOMIC_PARAM(P_ENABLED, Enabled);              // bool [in/out]
@@ -54,8 +53,7 @@ ATOMIC_EVENT(E_PHYSICSBEGINCONTACT2D, PhysicsBeginContact2D)
     ATOMIC_PARAM(P_BODYB, BodyB);                  // RigidBody2D pointer
     ATOMIC_PARAM(P_BODYB, BodyB);                  // RigidBody2D pointer
     ATOMIC_PARAM(P_NODEA, NodeA);                  // Node pointer
     ATOMIC_PARAM(P_NODEA, NodeA);                  // Node pointer
     ATOMIC_PARAM(P_NODEB, NodeB);                  // Node pointer
     ATOMIC_PARAM(P_NODEB, NodeB);                  // Node pointer
-    ATOMIC_PARAM(P_CONTACT, Contact);              // b2Contact pointer
-    ATOMIC_PARAM(P_CONTACTPOINTS, ContactPoints);  // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
+    ATOMIC_PARAM(P_CONTACTS, Contacts);            // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
     ATOMIC_PARAM(P_SHAPEA, ShapeA);                // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPEA, ShapeA);                // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPEB, ShapeB);                // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPEB, ShapeB);                // CollisionShape2D pointer
 }
 }
@@ -68,8 +66,7 @@ ATOMIC_EVENT(E_PHYSICSENDCONTACT2D, PhysicsEndContact2D)
     ATOMIC_PARAM(P_BODYB, BodyB);                  // RigidBody2D pointer
     ATOMIC_PARAM(P_BODYB, BodyB);                  // RigidBody2D pointer
     ATOMIC_PARAM(P_NODEA, NodeA);                  // Node pointer
     ATOMIC_PARAM(P_NODEA, NodeA);                  // Node pointer
     ATOMIC_PARAM(P_NODEB, NodeB);                  // Node pointer
     ATOMIC_PARAM(P_NODEB, NodeB);                  // Node pointer
-    ATOMIC_PARAM(P_CONTACT, Contact);              // b2Contact pointer
-    ATOMIC_PARAM(P_CONTACTPOINTS, ContactPoints);  // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
+    ATOMIC_PARAM(P_CONTACTS, Contacts);            // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
     ATOMIC_PARAM(P_SHAPEA, ShapeA);                // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPEA, ShapeA);                // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPEB, ShapeB);                // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPEB, ShapeB);                // CollisionShape2D pointer
 }
 }
@@ -80,8 +77,7 @@ ATOMIC_EVENT(E_NODEUPDATECONTACT2D, NodeUpdateContact2D)
     ATOMIC_PARAM(P_BODY, Body);                    // RigidBody2D pointer
     ATOMIC_PARAM(P_BODY, Body);                    // RigidBody2D pointer
     ATOMIC_PARAM(P_OTHERNODE, OtherNode);          // Node pointer
     ATOMIC_PARAM(P_OTHERNODE, OtherNode);          // Node pointer
     ATOMIC_PARAM(P_OTHERBODY, OtherBody);          // RigidBody2D pointer
     ATOMIC_PARAM(P_OTHERBODY, OtherBody);          // RigidBody2D pointer
-    ATOMIC_PARAM(P_CONTACT, Contact);              // b2Contact pointer
-    ATOMIC_PARAM(P_CONTACTPOINTS, ContactPoints);  // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
+    ATOMIC_PARAM(P_CONTACTS, Contacts);            // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
     ATOMIC_PARAM(P_SHAPE, Shape);                  // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPE, Shape);                  // CollisionShape2D pointer
     ATOMIC_PARAM(P_OTHERSHAPE, OtherShape);        // CollisionShape2D pointer
     ATOMIC_PARAM(P_OTHERSHAPE, OtherShape);        // CollisionShape2D pointer
     ATOMIC_PARAM(P_ENABLED, Enabled);              // bool [in/out]
     ATOMIC_PARAM(P_ENABLED, Enabled);              // bool [in/out]
@@ -93,8 +89,7 @@ ATOMIC_EVENT(E_NODEBEGINCONTACT2D, NodeBeginContact2D)
     ATOMIC_PARAM(P_BODY, Body);                    // RigidBody2D pointer
     ATOMIC_PARAM(P_BODY, Body);                    // RigidBody2D pointer
     ATOMIC_PARAM(P_OTHERNODE, OtherNode);          // Node pointer
     ATOMIC_PARAM(P_OTHERNODE, OtherNode);          // Node pointer
     ATOMIC_PARAM(P_OTHERBODY, OtherBody);          // RigidBody2D pointer
     ATOMIC_PARAM(P_OTHERBODY, OtherBody);          // RigidBody2D pointer
-    ATOMIC_PARAM(P_CONTACT, Contact);              // b2Contact pointer
-    ATOMIC_PARAM(P_CONTACTPOINTS, ContactPoints);  // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
+    ATOMIC_PARAM(P_CONTACTS, Contacts);            // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
     ATOMIC_PARAM(P_SHAPE, Shape);                  // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPE, Shape);                  // CollisionShape2D pointer
     ATOMIC_PARAM(P_OTHERSHAPE, OtherShape);        // CollisionShape2D pointer
     ATOMIC_PARAM(P_OTHERSHAPE, OtherShape);        // CollisionShape2D pointer
 }
 }
@@ -105,8 +100,7 @@ ATOMIC_EVENT(E_NODEENDCONTACT2D, NodeEndContact2D)
     ATOMIC_PARAM(P_BODY, Body);                    // RigidBody2D pointer
     ATOMIC_PARAM(P_BODY, Body);                    // RigidBody2D pointer
     ATOMIC_PARAM(P_OTHERNODE, OtherNode);          // Node pointer
     ATOMIC_PARAM(P_OTHERNODE, OtherNode);          // Node pointer
     ATOMIC_PARAM(P_OTHERBODY, OtherBody);          // RigidBody2D pointer
     ATOMIC_PARAM(P_OTHERBODY, OtherBody);          // RigidBody2D pointer
-    ATOMIC_PARAM(P_CONTACT, Contact);              // b2Contact pointer
-    ATOMIC_PARAM(P_CONTACTPOINTS, ContactPoints);  // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
+    ATOMIC_PARAM(P_CONTACTS, Contacts);            // Buffer containing position (Vector2), normal (Vector2), negative overlap distance (float). Normal is the same for all points.
     ATOMIC_PARAM(P_SHAPE, Shape);                  // CollisionShape2D pointer
     ATOMIC_PARAM(P_SHAPE, Shape);                  // CollisionShape2D pointer
     ATOMIC_PARAM(P_OTHERSHAPE, OtherShape);        // CollisionShape2D pointer
     ATOMIC_PARAM(P_OTHERSHAPE, OtherShape);        // CollisionShape2D pointer
 }
 }

+ 26 - 45
Source/Atomic/Atomic2D/PhysicsWorld2D.cpp

@@ -48,30 +48,6 @@ static const Vector2 DEFAULT_GRAVITY(0.0f, -9.81f);
 static const int DEFAULT_VELOCITY_ITERATIONS = 8;
 static const int DEFAULT_VELOCITY_ITERATIONS = 8;
 static const int DEFAULT_POSITION_ITERATIONS = 3;
 static const int DEFAULT_POSITION_ITERATIONS = 3;
 
 
-// Helper function to write contact info into buffer.
-const PODVector<unsigned char>& WriteContactInfo(VectorBuffer& buffer, b2Contact* contact)
-{
-    buffer.Clear();
-
-    // ATOMIC BEGIN
-    // Handle case when the body has been deleted during the event
-    if (!contact->GetFixtureA()->GetBody() || !contact->GetFixtureB()->GetBody() )
-    {
-        return buffer.GetBuffer();
-    }
-    // ATOMIC END
-
-    b2WorldManifold worldManifold;
-    contact->GetWorldManifold(&worldManifold);
-    for (int i = 0; i < contact->GetManifold()->pointCount; ++i)
-    {
-        buffer.WriteVector2(Vector2(worldManifold.points[i].x, worldManifold.points[i].y));
-        buffer.WriteVector2(Vector2(worldManifold.normal.x, worldManifold.normal.y));
-        buffer.WriteFloat(worldManifold.separations[i]);
-    }
-    return buffer.GetBuffer();
-}
-
 PhysicsWorld2D::PhysicsWorld2D(Context* context) :
 PhysicsWorld2D::PhysicsWorld2D(Context* context) :
     Component(context),
     Component(context),
     gravity_(DEFAULT_GRAVITY),
     gravity_(DEFAULT_GRAVITY),
@@ -191,8 +167,7 @@ void PhysicsWorld2D::PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
     eventData[PhysicsUpdateContact2D::P_BODYB] = contactInfo.bodyB_.Get();
     eventData[PhysicsUpdateContact2D::P_BODYB] = contactInfo.bodyB_.Get();
     eventData[PhysicsUpdateContact2D::P_NODEA] = contactInfo.nodeA_.Get();
     eventData[PhysicsUpdateContact2D::P_NODEA] = contactInfo.nodeA_.Get();
     eventData[PhysicsUpdateContact2D::P_NODEB] = contactInfo.nodeB_.Get();
     eventData[PhysicsUpdateContact2D::P_NODEB] = contactInfo.nodeB_.Get();
-    eventData[PhysicsUpdateContact2D::P_CONTACT] = (void*)contactInfo.contact_;
-    eventData[PhysicsUpdateContact2D::P_CONTACTPOINTS] = WriteContactInfo(contacts_, contactInfo.contact_);
+    eventData[PhysicsUpdateContact2D::P_CONTACTS] = contactInfo.Serialize(contacts_);
     eventData[PhysicsUpdateContact2D::P_SHAPEA] = contactInfo.shapeA_.Get();
     eventData[PhysicsUpdateContact2D::P_SHAPEA] = contactInfo.shapeA_.Get();
     eventData[PhysicsUpdateContact2D::P_SHAPEB] = contactInfo.shapeB_.Get();
     eventData[PhysicsUpdateContact2D::P_SHAPEB] = contactInfo.shapeB_.Get();
 
 
@@ -202,8 +177,7 @@ void PhysicsWorld2D::PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
 
 
     // Send node event
     // Send node event
     eventData[NodeUpdateContact2D::P_ENABLED] = contact->IsEnabled();
     eventData[NodeUpdateContact2D::P_ENABLED] = contact->IsEnabled();
-    eventData[NodeUpdateContact2D::P_CONTACT] = (void*)contactInfo.contact_;
-    eventData[NodeUpdateContact2D::P_CONTACTPOINTS] = WriteContactInfo(contacts_, contactInfo.contact_);
+    eventData[NodeUpdateContact2D::P_CONTACTS] = contactInfo.Serialize(contacts_);
 
 
     if (contactInfo.nodeA_)
     if (contactInfo.nodeA_)
     {
     {
@@ -781,15 +755,13 @@ void PhysicsWorld2D::SendBeginContactEvents()
         eventData[P_BODYB] = contactInfo.bodyB_.Get();
         eventData[P_BODYB] = contactInfo.bodyB_.Get();
         eventData[P_NODEA] = contactInfo.nodeA_.Get();
         eventData[P_NODEA] = contactInfo.nodeA_.Get();
         eventData[P_NODEB] = contactInfo.nodeB_.Get();
         eventData[P_NODEB] = contactInfo.nodeB_.Get();
-        eventData[P_CONTACT] = (void*)contactInfo.contact_;
-        eventData[P_CONTACTPOINTS] = WriteContactInfo(contacts_, contactInfo.contact_);
+        eventData[P_CONTACTS] = contactInfo.Serialize(contacts_);
         eventData[P_SHAPEA] = contactInfo.shapeA_.Get();
         eventData[P_SHAPEA] = contactInfo.shapeA_.Get();
         eventData[P_SHAPEB] = contactInfo.shapeB_.Get();
         eventData[P_SHAPEB] = contactInfo.shapeB_.Get();
 
 
         SendEvent(E_PHYSICSBEGINCONTACT2D, eventData);
         SendEvent(E_PHYSICSBEGINCONTACT2D, eventData);
 
 
-        nodeEventData[NodeBeginContact2D::P_CONTACT] = (void*)contactInfo.contact_;
-        nodeEventData[NodeBeginContact2D::P_CONTACTPOINTS] = WriteContactInfo(contacts_, contactInfo.contact_);
+        nodeEventData[NodeBeginContact2D::P_CONTACTS] = contactInfo.Serialize(contacts_);
 
 
         if (contactInfo.nodeA_)
         if (contactInfo.nodeA_)
         {
         {
@@ -834,15 +806,13 @@ void PhysicsWorld2D::SendEndContactEvents()
         eventData[P_BODYB] = contactInfo.bodyB_.Get();
         eventData[P_BODYB] = contactInfo.bodyB_.Get();
         eventData[P_NODEA] = contactInfo.nodeA_.Get();
         eventData[P_NODEA] = contactInfo.nodeA_.Get();
         eventData[P_NODEB] = contactInfo.nodeB_.Get();
         eventData[P_NODEB] = contactInfo.nodeB_.Get();
-        eventData[P_CONTACT] = (void*)contactInfo.contact_;
-        eventData[P_CONTACTPOINTS] = WriteContactInfo(contacts_, contactInfo.contact_);
+        eventData[P_CONTACTS] = contactInfo.Serialize(contacts_);
         eventData[P_SHAPEA] = contactInfo.shapeA_.Get();
         eventData[P_SHAPEA] = contactInfo.shapeA_.Get();
         eventData[P_SHAPEB] = contactInfo.shapeB_.Get();
         eventData[P_SHAPEB] = contactInfo.shapeB_.Get();
 
 
         SendEvent(E_PHYSICSENDCONTACT2D, eventData);
         SendEvent(E_PHYSICSENDCONTACT2D, eventData);
 
 
-        nodeEventData[NodeEndContact2D::P_CONTACT] = (void*)contactInfo.contact_;
-        nodeEventData[NodeEndContact2D::P_CONTACTPOINTS] = WriteContactInfo(contacts_, contactInfo.contact_);
+        nodeEventData[NodeEndContact2D::P_CONTACTS] = contactInfo.Serialize(contacts_);
 
 
         if (contactInfo.nodeA_)
         if (contactInfo.nodeA_)
         {
         {
@@ -882,20 +852,31 @@ PhysicsWorld2D::ContactInfo::ContactInfo(b2Contact* contact)
     bodyB_ = (RigidBody2D*)(fixtureB->GetBody()->GetUserData());
     bodyB_ = (RigidBody2D*)(fixtureB->GetBody()->GetUserData());
     nodeA_ = bodyA_->GetNode();
     nodeA_ = bodyA_->GetNode();
     nodeB_ = bodyB_->GetNode();
     nodeB_ = bodyB_->GetNode();
-    contact_ = contact;
     shapeA_ = (CollisionShape2D*)fixtureA->GetUserData();
     shapeA_ = (CollisionShape2D*)fixtureA->GetUserData();
     shapeB_ = (CollisionShape2D*)fixtureB->GetUserData();
     shapeB_ = (CollisionShape2D*)fixtureB->GetUserData();
+
+    b2WorldManifold worldManifold;
+    contact->GetWorldManifold(&worldManifold);
+    numPoints_ = contact->GetManifold()->pointCount;
+    worldNormal_ = Vector2(worldManifold.normal.x, worldManifold.normal.y);
+    for (int i = 0; i < numPoints_; ++i)
+    {
+        worldPositions_[i] = Vector2(worldManifold.points[i].x, worldManifold.points[i].y);
+        separations_[i] = worldManifold.separations[i];
+    }
 }
 }
 
 
-PhysicsWorld2D::ContactInfo::ContactInfo(const ContactInfo& other) :
-    bodyA_(other.bodyA_),
-    bodyB_(other.bodyB_),
-    nodeA_(other.nodeA_),
-    nodeB_(other.nodeB_),
-    contact_(other.contact_),
-    shapeA_(other.shapeA_),
-    shapeB_(other.shapeB_)
+const PODVector<unsigned char>& PhysicsWorld2D::ContactInfo::Serialize(VectorBuffer& buffer) const
 {
 {
+    buffer.Clear();
+
+    for (int i = 0; i < numPoints_; ++i)
+    {
+        buffer.WriteVector2(worldPositions_[i]);
+        buffer.WriteVector2(worldNormal_);
+        buffer.WriteFloat(separations_[i]);
+    }
+    return buffer.GetBuffer();
 }
 }
 
 
 }
 }

+ 10 - 4
Source/Atomic/Atomic2D/PhysicsWorld2D.h

@@ -256,8 +256,8 @@ protected:
         ContactInfo();
         ContactInfo();
         /// Construct.
         /// Construct.
         ContactInfo(b2Contact* contract);
         ContactInfo(b2Contact* contract);
-        /// Copy construct.
-        ContactInfo(const ContactInfo& other);
+        /// Write contact info to buffer.
+        const PODVector<unsigned char>& Serialize(VectorBuffer& buffer) const;
 
 
         /// Rigid body A.
         /// Rigid body A.
         SharedPtr<RigidBody2D> bodyA_;
         SharedPtr<RigidBody2D> bodyA_;
@@ -267,12 +267,18 @@ protected:
         SharedPtr<Node> nodeA_;
         SharedPtr<Node> nodeA_;
         /// Node B.
         /// Node B.
         SharedPtr<Node> nodeB_;
         SharedPtr<Node> nodeB_;
-        /// Box2D contact.
-        b2Contact* contact_;
         /// Shape A.
         /// Shape A.
         SharedPtr<CollisionShape2D> shapeA_;
         SharedPtr<CollisionShape2D> shapeA_;
         /// Shape B.
         /// Shape B.
         SharedPtr<CollisionShape2D> shapeB_;
         SharedPtr<CollisionShape2D> shapeB_;
+        /// Number of contact points.
+        int numPoints_;
+        /// Contact normal in world space.
+        Vector2 worldNormal_;
+        /// Contact positions in world space.
+        Vector2 worldPositions_[b2_maxManifoldPoints];
+        /// Contact overlap values.
+        float separations_[b2_maxManifoldPoints];
     };
     };
     /// Begin contact infos.
     /// Begin contact infos.
     Vector<ContactInfo> beginContactInfos_;
     Vector<ContactInfo> beginContactInfos_;

+ 9 - 11
Source/Atomic/Atomic2D/RigidBody2D.cpp

@@ -111,7 +111,7 @@ void RigidBody2D::OnSetEnabled()
 
 
 void RigidBody2D::SetBodyType(BodyType2D type)
 void RigidBody2D::SetBodyType(BodyType2D type)
 {
 {
-    b2BodyType bodyType = (b2BodyType)type;    
+    b2BodyType bodyType = (b2BodyType)type;
     if (body_)
     if (body_)
     {
     {
         body_->SetType(bodyType);
         body_->SetType(bodyType);
@@ -321,7 +321,7 @@ void RigidBody2D::SetLinearVelocity(const Vector2& linearVelocity)
 void RigidBody2D::SetAngularVelocity(float angularVelocity)
 void RigidBody2D::SetAngularVelocity(float angularVelocity)
 {
 {
     if (body_)
     if (body_)
-        body_->SetAngularVelocity(angularVelocity); 
+        body_->SetAngularVelocity(angularVelocity);
     else
     else
     {
     {
         if (bodyDef_.angularVelocity == angularVelocity)
         if (bodyDef_.angularVelocity == angularVelocity)
@@ -377,7 +377,7 @@ void RigidBody2D::CreateBody()
     if (!physicsWorld_ || !physicsWorld_->GetWorld())
     if (!physicsWorld_ || !physicsWorld_->GetWorld())
         return;
         return;
 
 
-    bodyDef_.position = ToB2Vec2(node_->GetWorldPosition());;
+    bodyDef_.position = ToB2Vec2(node_->GetWorldPosition());
     bodyDef_.angle = node_->GetWorldRotation().RollAngle() * M_DEGTORAD;
     bodyDef_.angle = node_->GetWorldRotation().RollAngle() * M_DEGTORAD;
 
 
     body_ = physicsWorld_->GetWorld()->CreateBody(&bodyDef_);
     body_ = physicsWorld_->GetWorld()->CreateBody(&bodyDef_);
@@ -607,16 +607,14 @@ void RigidBody2D::OnMarkedDirty(Node* node)
     // Check if transform has changed from the last one set in ApplyWorldTransform()
     // Check if transform has changed from the last one set in ApplyWorldTransform()
     b2Vec2 newPosition = ToB2Vec2(node_->GetWorldPosition());
     b2Vec2 newPosition = ToB2Vec2(node_->GetWorldPosition());
     float newAngle = node_->GetWorldRotation().RollAngle() * M_DEGTORAD;
     float newAngle = node_->GetWorldRotation().RollAngle() * M_DEGTORAD;
-    if (newPosition != bodyDef_.position || newAngle != bodyDef_.angle)
+
+    if (!body_)
     {
     {
-        if (body_)
-            body_->SetTransform(newPosition, newAngle);
-        else
-        {
-            bodyDef_.position = newPosition;
-            bodyDef_.angle = newAngle;
-        }
+        bodyDef_.position = newPosition;
+        bodyDef_.angle = newAngle;
     }
     }
+    else if (newPosition != body_->GetPosition() || newAngle != body_->GetAngle())
+        body_->SetTransform(newPosition, newAngle);
 }
 }
 
 
 }
 }

+ 2 - 2
Source/Atomic/Atomic2D/SpriteSheet2D.cpp

@@ -155,7 +155,7 @@ bool SpriteSheet2D::EndLoadFromPListFile()
     if (!texture_)
     if (!texture_)
     {
     {
         ATOMIC_LOGERROR("Could not load texture " + loadTextureName_);
         ATOMIC_LOGERROR("Could not load texture " + loadTextureName_);
-        loadXMLFile_.Reset();
+        loadPListFile_.Reset();
         loadTextureName_.Clear();
         loadTextureName_.Clear();
         return false;
         return false;
     }
     }
@@ -191,7 +191,7 @@ bool SpriteSheet2D::EndLoadFromPListFile()
         DefineSprite(name, rectangle, hotSpot, offset);
         DefineSprite(name, rectangle, hotSpot, offset);
     }
     }
 
 
-    loadXMLFile_.Reset();
+    loadPListFile_.Reset();
     loadTextureName_.Clear();
     loadTextureName_.Clear();
     return true;
     return true;
 }
 }

+ 11 - 0
Source/Atomic/Atomic2D/TmxFile2D.cpp

@@ -666,6 +666,17 @@ bool TmxFile2D::LoadTileSet(const XMLElement& element)
             propertySet->Load(tileElem.GetChild("properties"));
             propertySet->Load(tileElem.GetChild("properties"));
             gidToPropertySetMapping_[firstgid + tileElem.GetInt("id")] = propertySet;
             gidToPropertySetMapping_[firstgid + tileElem.GetInt("id")] = propertySet;
         }
         }
+		else if (tileElem.HasChild("objectgroup"))
+        {
+            XMLElement objectGroup = tileElem.GetChild("objectgroup");
+
+            if (objectGroup.HasChild("properties"))
+            {
+                SharedPtr<PropertySet2D> propertySet(new PropertySet2D());
+                propertySet->Load(objectGroup.GetChild("properties"));
+                gidToPropertySetMapping_[firstgid + tileElem.GetInt("id")] = propertySet;
+            }
+        }
 
 
         // ATOMIC BEGIN
         // ATOMIC BEGIN
 
 

+ 10 - 0
Source/Atomic/Audio/OggVorbisSoundStream.cpp

@@ -59,6 +59,16 @@ OggVorbisSoundStream::~OggVorbisSoundStream()
     }
     }
 }
 }
 
 
+bool OggVorbisSoundStream::Seek(unsigned sample_number)
+{
+    if (!decoder_)
+        return false;
+    
+    stb_vorbis* vorbis = static_cast<stb_vorbis*>(decoder_);
+    
+    return stb_vorbis_seek(vorbis, sample_number) == 1;
+}
+
 unsigned OggVorbisSoundStream::GetData(signed char* dest, unsigned numBytes)
 unsigned OggVorbisSoundStream::GetData(signed char* dest, unsigned numBytes)
 {
 {
     if (!decoder_)
     if (!decoder_)

+ 3 - 0
Source/Atomic/Audio/OggVorbisSoundStream.h

@@ -39,6 +39,9 @@ public:
     /// Destruct.
     /// Destruct.
     ~OggVorbisSoundStream();
     ~OggVorbisSoundStream();
 
 
+    /// Seek to sample number. Return true on success.
+    virtual bool Seek(unsigned sample_number);
+
     /// Produce sound data into destination. Return number of bytes produced. Called by SoundSource from the mixing thread.
     /// Produce sound data into destination. Return number of bytes produced. Called by SoundSource from the mixing thread.
     virtual unsigned GetData(signed char* dest, unsigned numBytes);
     virtual unsigned GetData(signed char* dest, unsigned numBytes);
 
 

+ 3 - 5
Source/Atomic/Audio/Sound.cpp

@@ -59,7 +59,7 @@ struct WavHeader
 static const unsigned IP_SAFETY = 4;
 static const unsigned IP_SAFETY = 4;
 
 
 Sound::Sound(Context* context) :
 Sound::Sound(Context* context) :
-    Resource(context),
+    ResourceWithMetadata(context),
     repeat_(0),
     repeat_(0),
     end_(0),
     end_(0),
     dataSize_(0),
     dataSize_(0),
@@ -348,9 +348,9 @@ void Sound::LoadParameters()
         return;
         return;
 
 
     XMLElement rootElem = file->GetRoot();
     XMLElement rootElem = file->GetRoot();
-    XMLElement paramElem = rootElem.GetChild();
+    LoadMetadataFromXML(rootElem);
 
 
-    while (paramElem)
+    for (XMLElement paramElem = rootElem.GetChild(); paramElem; paramElem = paramElem.GetNext())
     {
     {
         String name = paramElem.GetName();
         String name = paramElem.GetName();
 
 
@@ -373,8 +373,6 @@ void Sound::LoadParameters()
             if (paramElem.HasAttribute("start") && paramElem.HasAttribute("end"))
             if (paramElem.HasAttribute("start") && paramElem.HasAttribute("end"))
                 SetLoop((unsigned)paramElem.GetInt("start"), (unsigned)paramElem.GetInt("end"));
                 SetLoop((unsigned)paramElem.GetInt("start"), (unsigned)paramElem.GetInt("end"));
         }
         }
-
-        paramElem = paramElem.GetNext();
     }
     }
 }
 }
 
 

+ 2 - 2
Source/Atomic/Audio/Sound.h

@@ -31,9 +31,9 @@ namespace Atomic
 class SoundStream;
 class SoundStream;
 
 
 /// %Sound resource.
 /// %Sound resource.
-class ATOMIC_API Sound : public Resource
+class ATOMIC_API Sound : public ResourceWithMetadata
 {
 {
-    ATOMIC_OBJECT(Sound, Resource);
+    ATOMIC_OBJECT(Sound, ResourceWithMetadata);
 
 
 public:
 public:
     /// Construct.
     /// Construct.

+ 24 - 0
Source/Atomic/Audio/SoundSource.cpp

@@ -144,6 +144,30 @@ void SoundSource::RegisterObject(Context* context)
     ATOMIC_ACCESSOR_ATTRIBUTE("Play Position", GetPositionAttr, SetPositionAttr, int, 0, AM_FILE);
     ATOMIC_ACCESSOR_ATTRIBUTE("Play Position", GetPositionAttr, SetPositionAttr, int, 0, AM_FILE);
 }
 }
 
 
+void SoundSource::Seek(float seekTime)
+{
+    // Ignore buffered sound stream
+    if (!audio_ || !sound_ || (soundStream_ && !sound_->IsCompressed()))
+        return;
+
+    // Set to valid range
+    seekTime = Clamp(seekTime, 0.0f, sound_->GetLength());
+
+    if (!soundStream_)
+    {
+        // Raw or wav format
+        SetPositionAttr((int)(seekTime * (sound_->GetSampleSize() * sound_->GetFrequency())));
+    }
+    else
+    {
+        // Ogg format
+        if (soundStream_->Seek((unsigned)(seekTime * soundStream_->GetFrequency())))
+        {
+            timePosition_ = seekTime;
+        }
+    }
+}
+
 void SoundSource::Play(Sound* sound)
 void SoundSource::Play(Sound* sound)
 {
 {
     if (!audio_)
     if (!audio_)

+ 2 - 0
Source/Atomic/Audio/SoundSource.h

@@ -48,6 +48,8 @@ public:
     /// Register object factory.
     /// Register object factory.
     static void RegisterObject(Context* context);
     static void RegisterObject(Context* context);
 
 
+    /// Seek to time.
+    void Seek(float seekTime);
     /// Play a sound.
     /// Play a sound.
     void Play(Sound* sound);
     void Play(Sound* sound);
     /// Play a sound with specified frequency.
     /// Play a sound with specified frequency.

+ 5 - 0
Source/Atomic/Audio/SoundStream.cpp

@@ -39,6 +39,11 @@ SoundStream::~SoundStream()
 {
 {
 }
 }
 
 
+bool SoundStream::Seek(unsigned int sample_number)
+{
+    return false;
+}
+    
 void SoundStream::SetFormat(unsigned frequency, bool sixteenBit, bool stereo)
 void SoundStream::SetFormat(unsigned frequency, bool sixteenBit, bool stereo)
 {
 {
     frequency_ = frequency;
     frequency_ = frequency;

+ 3 - 0
Source/Atomic/Audio/SoundStream.h

@@ -38,6 +38,9 @@ public:
     /// Destruct.
     /// Destruct.
     ~SoundStream();
     ~SoundStream();
 
 
+    /// Seek to sample number. Return true on success. Need not be implemented by all streams.
+    virtual bool Seek(unsigned sample_number);
+    
     /// Produce sound data into destination. Return number of bytes produced. Called by SoundSource from the mixing thread.
     /// Produce sound data into destination. Return number of bytes produced. Called by SoundSource from the mixing thread.
     virtual unsigned GetData(signed char* dest, unsigned numBytes) = 0;
     virtual unsigned GetData(signed char* dest, unsigned numBytes) = 0;
 
 

+ 2 - 2
Source/Atomic/Core/Context.cpp

@@ -317,8 +317,8 @@ void Context::ReleaseIK()
     if (ikInitCounter < 0)
     if (ikInitCounter < 0)
         ATOMIC_LOGERROR("Too many calls to Context::ReleaseIK()");
         ATOMIC_LOGERROR("Too many calls to Context::ReleaseIK()");
 }
 }
-#endif
-#endif
+#endif // ifdef URHO3D_IK
+#endif // ifndef MINI_URHO
 
 
 void Context::CopyBaseAttributes(StringHash baseType, StringHash derivedType)
 void Context::CopyBaseAttributes(StringHash baseType, StringHash derivedType)
 {
 {

+ 2 - 2
Source/Atomic/Core/Main.h

@@ -67,8 +67,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, in
     Atomic::ParseArguments(GetCommandLineW()); \
     Atomic::ParseArguments(GetCommandLineW()); \
     return function; \
     return function; \
 }
 }
-// Android or iOS: use SDL_main
-#elif defined(__ANDROID__) || defined(IOS)
+// Android or iOS or tvOS: use SDL_main
+#elif defined(__ANDROID__) || defined(IOS) || defined(TVOS)
 #define ATOMIC_DEFINE_MAIN(function) \
 #define ATOMIC_DEFINE_MAIN(function) \
 extern "C" int SDL_main(int argc, char** argv); \
 extern "C" int SDL_main(int argc, char** argv); \
 int SDL_main(int argc, char** argv) \
 int SDL_main(int argc, char** argv) \

+ 232 - 14
Source/Atomic/Core/ProcessUtils.cpp

@@ -23,6 +23,7 @@
 #include "../Precompiled.h"
 #include "../Precompiled.h"
 
 
 #include "../Core/ProcessUtils.h"
 #include "../Core/ProcessUtils.h"
+#include "../Core/StringUtils.h"
 #include "../IO/FileSystem.h"
 #include "../IO/FileSystem.h"
 
 
 #include <cstdio>
 #include <cstdio>
@@ -33,21 +34,32 @@
 #endif
 #endif
 
 
 #if defined(IOS)
 #if defined(IOS)
-#include "../Math/MathDefs.h"
 #include <mach/mach_host.h>
 #include <mach/mach_host.h>
+#elif defined(TVOS)
+extern "C" unsigned SDL_TVOS_GetActiveProcessorCount();
 #elif !defined(__linux__) && !defined(__EMSCRIPTEN__)
 #elif !defined(__linux__) && !defined(__EMSCRIPTEN__)
-// ATOMIC BEGIN
-#include <LibCpuId/src/libcpuid.h>
-// ATOMIC END
+#include <libcpuid.h>
 #endif
 #endif
 
 
-#ifdef _WIN32
+#if defined(_WIN32)
 #include <windows.h>
 #include <windows.h>
 #include <io.h>
 #include <io.h>
 #if defined(_MSC_VER)
 #if defined(_MSC_VER)
 #include <float.h>
 #include <float.h>
+#include <Lmcons.h> // For UNLEN. 
+#elif defined(__MINGW32__)
+#include <lmcons.h> // For UNLEN. Apparently MSVC defines "<Lmcons.h>" (with an upperscore 'L' but MinGW uses an underscore 'l'). 
+#include <ntdef.h> 
 #endif
 #endif
-#else
+#elif defined(__linux__) && !defined(__ANDROID__) 
+#include <pwd.h> 
+#include <sys/sysinfo.h>
+#include <sys/utsname.h>
+#elif defined(__APPLE__)
+#include <sys/sysctl.h>
+#include <SystemConfiguration/SystemConfiguration.h> // For the detection functions inside GetLoginName(). 
+#endif
+#ifndef _WIN32
 #include <unistd.h>
 #include <unistd.h>
 #endif
 #endif
 
 
@@ -149,7 +161,7 @@ static void GetCPUData(struct CpuCoreCount* data)
     }
     }
 }
 }
 
 
-#elif !defined(__EMSCRIPTEN__)
+#elif !defined(__EMSCRIPTEN__) && !defined(TVOS)
 static void GetCPUData(struct cpu_id_t* data)
 static void GetCPUData(struct cpu_id_t* data)
 {
 {
     if (cpu_identify(0, data) < 0)
     if (cpu_identify(0, data) < 0)
@@ -206,7 +218,7 @@ void OpenConsoleWindow()
 
 
 void PrintUnicode(const String& str, bool error)
 void PrintUnicode(const String& str, bool error)
 {
 {
-#if !defined(__ANDROID__) && !defined(IOS)
+#if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
 #ifdef _WIN32
 #ifdef _WIN32
     // If the output stream has been redirected, use fprintf instead of WriteConsoleW,
     // If the output stream has been redirected, use fprintf instead of WriteConsoleW,
     // though it means that proper Unicode output will not work
     // though it means that proper Unicode output will not work
@@ -235,7 +247,7 @@ void PrintUnicodeLine(const String& str, bool error)
 
 
 void PrintLine(const String& str, bool error)
 void PrintLine(const String& str, bool error)
 {
 {
-#if !defined(__ANDROID__) && !defined(IOS)
+#if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
     fprintf(error ? stderr : stdout, "%s\n", str.CString());
     fprintf(error ? stderr : stdout, "%s\n", str.CString());
 #endif
 #endif
 }
 }
@@ -373,7 +385,7 @@ String GetConsoleInput()
             }
             }
         }
         }
     }
     }
-#elif !defined(__ANDROID__) && !defined(IOS)
+#elif !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
     int flags = fcntl(STDIN_FILENO, F_GETFL);
     int flags = fcntl(STDIN_FILENO, F_GETFL);
     fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
     fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
     for (;;)
     for (;;)
@@ -396,10 +408,12 @@ String GetPlatform()
     return "Android";
     return "Android";
 #elif defined(IOS)
 #elif defined(IOS)
     return "iOS";
     return "iOS";
+#elif defined(TVOS)
+    return "tvOS";
+#elif defined(__APPLE__)
+    return "macOS";
 #elif defined(_WIN32)
 #elif defined(_WIN32)
     return "Windows";
     return "Windows";
-#elif defined(__APPLE__)
-    return "Mac OS X";
 #elif defined(RPI)
 #elif defined(RPI)
     return "Raspberry Pi";
     return "Raspberry Pi";
 #elif defined(__EMSCRIPTEN__)
 #elif defined(__EMSCRIPTEN__)
@@ -416,12 +430,18 @@ unsigned GetNumPhysicalCPUs()
 #if defined(IOS)
 #if defined(IOS)
     host_basic_info_data_t data;
     host_basic_info_data_t data;
     GetCPUData(&data);
     GetCPUData(&data);
-#if defined(TARGET_IPHONE_SIMULATOR)
+#if defined(TARGET_OS_SIMULATOR)
     // Hardcoded to dual-core on simulator mode even if the host has more
     // Hardcoded to dual-core on simulator mode even if the host has more
     return Min(2, data.physical_cpu);
     return Min(2, data.physical_cpu);
 #else
 #else
     return data.physical_cpu;
     return data.physical_cpu;
 #endif
 #endif
+#elif defined(TVOS)
+#if defined(TARGET_OS_SIMULATOR)
+    return Min(2, SDL_TVOS_GetActiveProcessorCount());
+#else
+    return SDL_TVOS_GetActiveProcessorCount();
+#endif
 #elif defined(__linux__)
 #elif defined(__linux__)
     struct CpuCoreCount data;
     struct CpuCoreCount data;
     GetCPUData(&data);
     GetCPUData(&data);
@@ -444,11 +464,17 @@ unsigned GetNumLogicalCPUs()
 #if defined(IOS)
 #if defined(IOS)
     host_basic_info_data_t data;
     host_basic_info_data_t data;
     GetCPUData(&data);
     GetCPUData(&data);
-#if defined(TARGET_IPHONE_SIMULATOR)
+#if defined(TARGET_OS_SIMULATOR)
     return Min(2, data.logical_cpu);
     return Min(2, data.logical_cpu);
 #else
 #else
     return data.logical_cpu;
     return data.logical_cpu;
 #endif
 #endif
+#elif defined(TVOS)
+#if defined(TARGET_OS_SIMULATOR)
+    return Min(2, SDL_TVOS_GetActiveProcessorCount());
+#else
+    return SDL_TVOS_GetActiveProcessorCount();
+#endif
 #elif defined(__linux__)
 #elif defined(__linux__)
     struct CpuCoreCount data;
     struct CpuCoreCount data;
     GetCPUData(&data);
     GetCPUData(&data);
@@ -504,4 +530,196 @@ void QuoteArguments(Vector<String>& args)
 
 
 // ATOMIC END
 // ATOMIC END
 
 
+unsigned long long GetTotalMemory()
+{
+#if defined(__linux__) && !defined(__ANDROID__)
+    struct sysinfo s;
+    if (sysinfo(&s) != -1)
+        return s.totalram; 
+#elif defined(_WIN32)
+    MEMORYSTATUSEX state;
+    state.dwLength = sizeof(state); 
+    if (GlobalMemoryStatusEx(&state)) 
+        return state.ullTotalPhys; 
+#elif defined(__APPLE__)
+    unsigned long long memSize;
+    size_t len = sizeof(memSize);
+    int mib[2]; 
+    mib[0] = CTL_HW;
+    mib[1] = HW_MEMSIZE;
+    sysctl(mib, 2, &memSize, &len, NULL, 0);
+    return memSize;
+#endif
+    return 0ull;
+}
+
+String GetLoginName() 
+{
+#if defined(__linux__) && !defined(__ANDROID__)
+    struct passwd *p = getpwuid(getuid());
+    if (p) 
+        return p->pw_name;
+#elif defined(_WIN32)
+    char name[UNLEN + 1];
+    DWORD len = UNLEN + 1;
+    if (GetUserName(name, &len))
+        return name;
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
+    SCDynamicStoreRef s = SCDynamicStoreCreate(NULL, CFSTR("GetConsoleUser"), NULL, NULL);
+    if (s != NULL)
+    {
+        uid_t u; 
+        CFStringRef n = SCDynamicStoreCopyConsoleUser(s, &u, NULL);
+        CFRelease(s); 
+        if (n != NULL)
+        {
+            char name[256]; 
+            Boolean b = CFStringGetCString(n, name, 256, kCFStringEncodingUTF8);
+            CFRelease(n); 
+
+            if (b == true)
+                return name; 
+        }
+    }
+#endif
+    return "(?)"; 
+}
+
+String GetHostName() 
+{
+#if (defined(__linux__) || defined(__APPLE__)) && !defined(__ANDROID__)
+    char buffer[256]; 
+    if (gethostname(buffer, 256) == 0) 
+        return buffer; 
+#elif defined(_WIN32)
+    char buffer[MAX_COMPUTERNAME_LENGTH + 1]; 
+    DWORD len = MAX_COMPUTERNAME_LENGTH + 1; 
+    if (GetComputerName(buffer, &len))
+        return buffer;
+#endif
+    return String::EMPTY; 
+}
+
+// Disable Windows OS version functionality when compiling mini version for Web, see https://github.com/urho3d/Urho3D/issues/1998
+#if defined(_WIN32) && !defined(MINI_URHO)
+typedef NTSTATUS (WINAPI *RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
+
+static void GetOS(RTL_OSVERSIONINFOW *r)
+{
+    HMODULE m = GetModuleHandle("ntdll.dll");
+    if (m)
+    {
+        RtlGetVersionPtr fPtr = (RtlGetVersionPtr) GetProcAddress(m, "RtlGetVersion");
+        if (r && fPtr && fPtr(r) == 0)
+            r->dwOSVersionInfoSize = sizeof *r; 
+    }
+}
+#endif 
+
+String GetOSVersion() 
+{
+#if defined(__linux__) && !defined(__ANDROID__)
+    struct utsname u;
+    if (uname(&u) == 0)
+        return String(u.sysname) + " " + u.release; 
+#elif defined(_WIN32) && !defined(MINI_URHO)
+    RTL_OSVERSIONINFOW r;
+    GetOS(&r); 
+    // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832(v=vs.85).aspx
+    if (r.dwMajorVersion == 5 && r.dwMinorVersion == 0) 
+        return "Windows 2000"; 
+    else if (r.dwMajorVersion == 5 && r.dwMinorVersion == 1) 
+        return "Windows XP"; 
+    else if (r.dwMajorVersion == 5 && r.dwMinorVersion == 2) 
+        return "Windows XP 64-Bit Edition/Windows Server 2003/Windows Server 2003 R2"; 
+    else if (r.dwMajorVersion == 6 && r.dwMinorVersion == 0) 
+        return "Windows Vista/Windows Server 2008"; 
+    else if (r.dwMajorVersion == 6 && r.dwMinorVersion == 1) 
+        return "Windows 7/Windows Server 2008 R2"; 
+    else if (r.dwMajorVersion == 6 && r.dwMinorVersion == 2) 
+        return "Windows 8/Windows Server 2012";
+    else if (r.dwMajorVersion == 6 && r.dwMinorVersion == 3) 
+        return "Windows 8.1/Windows Server 2012 R2"; 
+    else if (r.dwMajorVersion == 10 && r.dwMinorVersion == 0) 
+        return "Windows 10/Windows Server 2016"; 
+    else 
+        return "Windows Unidentified";
+#elif defined(__APPLE__)
+    char kernel_r[256]; 
+    size_t size = sizeof(kernel_r); 
+
+    if (sysctlbyname("kern.osrelease", &kernel_r, &size, NULL, 0) != -1)
+    {
+        Vector<String> kernel_version = String(kernel_r).Split('.'); 
+        String version = "macOS/Mac OS X "; 
+        int major = ToInt(kernel_version[0]);
+        int minor = ToInt(kernel_version[1]);
+
+        // https://en.wikipedia.org/wiki/Darwin_(operating_system)
+        if (major == 16) // macOS Sierra 
+        {
+            version += "Sierra "; 
+            switch(minor)
+            {
+                case 0: version += "10.12.0 "; break; 
+                case 1: version += "10.12.1 "; break; 
+                case 3: version += "10.12.2 "; break; 
+            }
+        }
+        else if (major == 15) // OS X El Capitan
+        {
+            version += "El Capitan ";
+            switch(minor)
+            {
+                case 0: version += "10.11.0 "; break; 
+                case 6: version += "10.11.6 "; break; 
+            }
+        }
+        else if (major == 14) // OS X Yosemite 
+        {
+            version += "Yosemite "; 
+            switch(minor) 
+            {
+                case 0: version += "10.10.0 "; break; 
+                case 5: version += "10.10.5 "; break; 
+            }
+        }
+        else if (major == 13) // OS X Mavericks
+        {
+            version += "Mavericks ";
+            switch(minor)
+            {
+                case 0: version += "10.9.0 "; break; 
+                case 4: version += "10.9.5 "; break; 
+            }
+        }
+        else if (major == 12) // OS X Mountain Lion
+        {
+            version += "Mountain Lion "; 
+            switch(minor) 
+            {
+                case 0: version += "10.8.0 "; break; 
+                case 6: version += "10.8.5 "; break; 
+            }
+        }
+        else if (major == 11) // Mac OS X Lion
+        {
+            version += "Lion ";
+            switch(minor)
+            {
+                case 0: version += "10.7.0 "; break; 
+                case 4: version += "10.7.5 "; break; 
+            }
+        }
+        else 
+        {
+            version += "Unknown ";
+        }
+
+        return version + " (Darwin kernel " + kernel_version[0] + "." + kernel_version[1] + "." + kernel_version[2] + ")"; 
+    }
+#endif
+    return String::EMPTY; 
+}
+
 }
 }

+ 9 - 2
Source/Atomic/Core/ProcessUtils.h

@@ -59,7 +59,7 @@ ATOMIC_API const Vector<String>& ParseArguments(int argc, char** argv);
 ATOMIC_API const Vector<String>& GetArguments();
 ATOMIC_API const Vector<String>& GetArguments();
 /// Read input from the console window. Return empty if no input.
 /// Read input from the console window. Return empty if no input.
 ATOMIC_API String GetConsoleInput();
 ATOMIC_API String GetConsoleInput();
-/// Return the runtime platform identifier, one of "Windows", "Linux", "Mac OS X", "Android", "iOS", "Web" or "Raspberry Pi".
+/// Return the runtime platform identifier.
 ATOMIC_API String GetPlatform();
 ATOMIC_API String GetPlatform();
 /// Return the number of physical CPU cores.
 /// Return the number of physical CPU cores.
 ATOMIC_API unsigned GetNumPhysicalCPUs();
 ATOMIC_API unsigned GetNumPhysicalCPUs();
@@ -69,6 +69,14 @@ ATOMIC_API unsigned GetNumLogicalCPUs();
 ATOMIC_API void SetMiniDumpDir(const String& pathName);
 ATOMIC_API void SetMiniDumpDir(const String& pathName);
 /// Return minidump write location.
 /// Return minidump write location.
 ATOMIC_API String GetMiniDumpDir();
 ATOMIC_API String GetMiniDumpDir();
+/// Return the total amount of useable memory. 
+ATOMIC_API unsigned long long GetTotalMemory(); 
+/// Return the name of the currently logged in user. 
+ATOMIC_API String GetLoginName(); 
+/// Return the name of the running machine. 
+ATOMIC_API String GetHostName();
+/// Return the version of the currently running OS. 
+ATOMIC_API String GetOSVersion(); 
 
 
 // ATOMIC BEGIN
 // ATOMIC BEGIN
 
 
@@ -76,5 +84,4 @@ ATOMIC_API String GetMiniDumpDir();
 ATOMIC_API void QuoteArguments(Vector<String>& args);
 ATOMIC_API void QuoteArguments(Vector<String>& args);
 
 
 // ATOMIC END
 // ATOMIC END
-
 }
 }

+ 6 - 6
Source/Atomic/Engine/Application.cpp

@@ -29,7 +29,7 @@
 #include "../Core/Profiler.h"
 #include "../Core/Profiler.h"
 // ATOMIC END
 // ATOMIC END
 
 
-#ifdef IOS
+#if defined(IOS) || defined(TVOS)
 #include "../Graphics/Graphics.h"
 #include "../Graphics/Graphics.h"
 // ATOMIC BEGIN
 // ATOMIC BEGIN
 #include <SDL/include/SDL.h>
 #include <SDL/include/SDL.h>
@@ -50,7 +50,7 @@ namespace Atomic
 bool Application::autoMetrics_ = false;
 bool Application::autoMetrics_ = false;
 // ATOMIC END
 // ATOMIC END
 
 
-#if defined(IOS) || defined(__EMSCRIPTEN__)
+#if defined(IOS) || defined(TVOS) || defined(__EMSCRIPTEN__)
 // Code for supporting SDL_iPhoneSetAnimationCallback() and emscripten_set_main_loop_arg()
 // Code for supporting SDL_iPhoneSetAnimationCallback() and emscripten_set_main_loop_arg()
 #if defined(__EMSCRIPTEN__)
 #if defined(__EMSCRIPTEN__)
 #include <emscripten/emscripten.h>
 #include <emscripten/emscripten.h>
@@ -112,16 +112,16 @@ int Application::Run()
         if (exitCode_)
         if (exitCode_)
             return exitCode_;
             return exitCode_;
 
 
-        // Platforms other than iOS and Emscripten run a blocking main loop
-#if !defined(IOS) && !defined(__EMSCRIPTEN__)
+        // Platforms other than iOS/tvOS and Emscripten run a blocking main loop
+#if !defined(IOS) && !defined(TVOS) && !defined(__EMSCRIPTEN__)
         while (!engine_->IsExiting())
         while (!engine_->IsExiting())
             engine_->RunFrame();
             engine_->RunFrame();
 
 
         Stop();
         Stop();
-        // iOS will setup a timer for running animation frames so eg. Game Center can run. In this case we do not
+        // iOS/tvOS will setup a timer for running animation frames so eg. Game Center can run. In this case we do not
         // support calling the Stop() function, as the application will never stop manually
         // support calling the Stop() function, as the application will never stop manually
 #else
 #else
-#if defined(IOS)
+#if defined(IOS) || defined(TVOS)
         SDL_iPhoneSetAnimationCallback(GetSubsystem<Graphics>()->GetWindow(), 1, &RunFrame, engine_);
         SDL_iPhoneSetAnimationCallback(GetSubsystem<Graphics>()->GetWindow(), 1, &RunFrame, engine_);
 #elif defined(__EMSCRIPTEN__)
 #elif defined(__EMSCRIPTEN__)
         emscripten_set_main_loop_arg(RunFrame, engine_, 0, 1);
         emscripten_set_main_loop_arg(RunFrame, engine_, 0, 1);

+ 2 - 2
Source/Atomic/Engine/Application.h

@@ -82,7 +82,7 @@ protected:
 };
 };
 
 
 // Macro for defining a main function which creates a Context and the application, then runs it
 // Macro for defining a main function which creates a Context and the application, then runs it
-#ifndef IOS
+#if !defined(IOS) && !defined(TVOS)
 #define ATOMIC_DEFINE_APPLICATION_MAIN(className) \
 #define ATOMIC_DEFINE_APPLICATION_MAIN(className) \
 int RunApplication() \
 int RunApplication() \
 { \
 { \
@@ -92,7 +92,7 @@ int RunApplication() \
 } \
 } \
 ATOMIC_DEFINE_MAIN(RunApplication());
 ATOMIC_DEFINE_MAIN(RunApplication());
 #else
 #else
-// On iOS we will let this function exit, so do not hold the context and application in SharedPtr's
+// On iOS/tvOS we will let this function exit, so do not hold the context and application in SharedPtr's
 #define ATOMIC_DEFINE_APPLICATION_MAIN(className) \
 #define ATOMIC_DEFINE_APPLICATION_MAIN(className) \
 int RunApplication() \
 int RunApplication() \
 { \
 { \

+ 9 - 6
Source/Atomic/Engine/Engine.cpp

@@ -66,6 +66,7 @@
 #endif
 #endif
 #ifdef ATOMIC_PHYSICS
 #ifdef ATOMIC_PHYSICS
 #include "../Physics/PhysicsWorld.h"
 #include "../Physics/PhysicsWorld.h"
+#include "../Physics/RaycastVehicle.h"
 #endif
 #endif
 #include "../Resource/ResourceCache.h"
 #include "../Resource/ResourceCache.h"
 #include "../Resource/Localization.h"
 #include "../Resource/Localization.h"
@@ -113,7 +114,7 @@ Engine::Engine(Context* context) :
     timeStep_(0.0f),
     timeStep_(0.0f),
     timeStepSmoothing_(2),
     timeStepSmoothing_(2),
     minFps_(10),
     minFps_(10),
-#if defined(IOS) || defined(__ANDROID__) || defined(__arm__) || defined(__aarch64__)
+#if defined(IOS) || defined(TVOS) || defined(__ANDROID__) || defined(__arm__) || defined(__aarch64__)
     maxFps_(60),
     maxFps_(60),
     maxInactiveFps_(10),
     maxInactiveFps_(10),
     pauseMinimized_(true),
     pauseMinimized_(true),
@@ -668,7 +669,7 @@ void Engine::SetPauseMinimized(bool enable)
 void Engine::SetAutoExit(bool enable)
 void Engine::SetAutoExit(bool enable)
 {
 {
     // On mobile platforms exit is mandatory if requested by the platform itself and should not be attempted to be disabled
     // On mobile platforms exit is mandatory if requested by the platform itself and should not be attempted to be disabled
-#if defined(__ANDROID__) || defined(IOS)
+#if defined(__ANDROID__) || defined(IOS) || defined(TVOS)
     enable = true;
     enable = true;
 #endif
 #endif
     autoExit_ = enable;
     autoExit_ = enable;
@@ -681,8 +682,8 @@ void Engine::SetNextTimeStep(float seconds)
 
 
 void Engine::Exit()
 void Engine::Exit()
 {
 {
-#if defined(IOS)
-    // On iOS it's not legal for the application to exit on its own, instead it will be minimized with the home key
+#if defined(IOS) || defined(TVOS)
+    // On iOS/tvOS it's not legal for the application to exit on its own, instead it will be minimized with the home key
 #else
 #else
     DoExit();
     DoExit();
 #endif
 #endif
@@ -806,10 +807,10 @@ void Engine::ApplyFrameLimit()
 
 
 #ifndef __EMSCRIPTEN__
 #ifndef __EMSCRIPTEN__
     // Perform waiting loop if maximum FPS set
     // Perform waiting loop if maximum FPS set
-#ifndef IOS
+#if !defined(IOS) && !defined(TVOS)
     if (maxFps)
     if (maxFps)
 #else
 #else
-    // If on iOS and target framerate is 60 or above, just let the animation callback handle frame timing
+    // If on iOS/tvOS and target framerate is 60 or above, just let the animation callback handle frame timing
     // instead of waiting ourselves
     // instead of waiting ourselves
     if (maxFps < 60)
     if (maxFps < 60)
 #endif
 #endif
@@ -923,6 +924,8 @@ VariantMap Engine::ParseParameters(const Vector<String>& arguments)
                 ret[EP_FULL_SCREEN] = false;
                 ret[EP_FULL_SCREEN] = false;
             else if (argument == "borderless")
             else if (argument == "borderless")
                 ret[EP_BORDERLESS] = true;
                 ret[EP_BORDERLESS] = true;
+            else if (argument == "lowdpi")
+                ret[EP_HIGH_DPI] = false;
             else if (argument == "s")
             else if (argument == "s")
                 ret[EP_WINDOW_RESIZABLE] = true;
                 ret[EP_WINDOW_RESIZABLE] = true;
             else if (argument == "hd")
             else if (argument == "hd")

+ 1 - 1
Source/Atomic/Engine/Engine.h

@@ -66,7 +66,7 @@ public:
     void SetAutoExit(bool enable);
     void SetAutoExit(bool enable);
     /// Override timestep of the next frame. Should be called in between RunFrame() calls.
     /// Override timestep of the next frame. Should be called in between RunFrame() calls.
     void SetNextTimeStep(float seconds);
     void SetNextTimeStep(float seconds);
-    /// Close the graphics window and set the exit flag. No-op on iOS, as an iOS application can not legally exit.
+    /// Close the graphics window and set the exit flag. No-op on iOS/tvOS, as an iOS/tvOS application can not legally exit.
     void Exit();
     void Exit();
     /// Dump information of all resources to the log.
     /// Dump information of all resources to the log.
     void DumpResources(bool dumpFileName = false);
     void DumpResources(bool dumpFileName = false);

+ 2 - 4
Source/Atomic/Graphics/AnimatedModel.cpp

@@ -611,11 +611,9 @@ void AnimatedModel::SetMorphWeight(unsigned index, float weight)
         return;
         return;
 
 
     // If morph vertex buffers have not been created yet, create now
     // If morph vertex buffers have not been created yet, create now
-    if (weight > 0.0f && morphVertexBuffers_.Empty())
+    if (weight != 0.0f && morphVertexBuffers_.Empty())
         CloneGeometries();
         CloneGeometries();
 
 
-    weight = Clamp(weight, 0.0f, 1.0f);
-
     if (weight != morphs_[index].weight_)
     if (weight != morphs_[index].weight_)
     {
     {
         morphs_[index].weight_ = weight;
         morphs_[index].weight_ = weight;
@@ -1463,7 +1461,7 @@ void AnimatedModel::UpdateMorphs()
 
 
                     for (unsigned j = 0; j < morphs_.Size(); ++j)
                     for (unsigned j = 0; j < morphs_.Size(); ++j)
                     {
                     {
-                        if (morphs_[j].weight_ > 0.0f)
+                        if (morphs_[j].weight_ != 0.0f)
                         {
                         {
                             HashMap<unsigned, VertexBufferMorph>::Iterator k = morphs_[j].buffers_.Find(i);
                             HashMap<unsigned, VertexBufferMorph>::Iterator k = morphs_[j].buffers_.Find(i);
                             if (k != morphs_[j].buffers_.End())
                             if (k != morphs_[j].buffers_.End())

+ 23 - 19
Source/Atomic/Graphics/Animation.cpp

@@ -107,7 +107,7 @@ void AnimationTrack::GetKeyFrameIndex(float time, unsigned& index) const
 }
 }
 
 
 Animation::Animation(Context* context) :
 Animation::Animation(Context* context) :
-    Resource(context),
+    ResourceWithMetadata(context),
     length_(0.f)
     length_(0.f)
 {
 {
 }
 }
@@ -173,17 +173,16 @@ bool Animation::BeginLoad(Deserializer& source)
     if (file)
     if (file)
     {
     {
         XMLElement rootElem = file->GetRoot();
         XMLElement rootElem = file->GetRoot();
-        XMLElement triggerElem = rootElem.GetChild("trigger");
-        while (triggerElem)
+        for (XMLElement triggerElem = rootElem.GetChild("trigger"); triggerElem; triggerElem = triggerElem.GetNext("trigger"))
         {
         {
             if (triggerElem.HasAttribute("normalizedtime"))
             if (triggerElem.HasAttribute("normalizedtime"))
                 AddTrigger(triggerElem.GetFloat("normalizedtime"), true, triggerElem.GetVariant());
                 AddTrigger(triggerElem.GetFloat("normalizedtime"), true, triggerElem.GetVariant());
             else if (triggerElem.HasAttribute("time"))
             else if (triggerElem.HasAttribute("time"))
                 AddTrigger(triggerElem.GetFloat("time"), false, triggerElem.GetVariant());
                 AddTrigger(triggerElem.GetFloat("time"), false, triggerElem.GetVariant());
-
-            triggerElem = triggerElem.GetNext("trigger");
         }
         }
 
 
+        LoadMetadataFromXML(rootElem);
+
         memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
         memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
         SetMemoryUse(memoryUse);
         SetMemoryUse(memoryUse);
         return true;
         return true;
@@ -196,7 +195,7 @@ bool Animation::BeginLoad(Deserializer& source)
     if (jsonFile)
     if (jsonFile)
     {
     {
         const JSONValue& rootVal = jsonFile->GetRoot();
         const JSONValue& rootVal = jsonFile->GetRoot();
-        JSONArray triggerArray = rootVal.Get("triggers").GetArray();
+        const JSONArray& triggerArray = rootVal.Get("triggers").GetArray();
 
 
         for (unsigned i = 0; i < triggerArray.Size(); i++)
         for (unsigned i = 0; i < triggerArray.Size(); i++)
         {
         {
@@ -212,6 +211,9 @@ bool Animation::BeginLoad(Deserializer& source)
             }
             }
         }
         }
 
 
+        const JSONArray& metadataArray = rootVal.Get("metadata").GetArray();
+        LoadMetadataFromJSON(metadataArray);
+
         memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
         memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
         SetMemoryUse(memoryUse);
         SetMemoryUse(memoryUse);
         return true;
         return true;
@@ -252,7 +254,7 @@ bool Animation::Save(Serializer& dest) const
     }
     }
 
 
     // If triggers have been defined, write an XML file for them
     // If triggers have been defined, write an XML file for them
-    if (triggers_.Size())
+    if (!triggers_.Empty() || HasMetadata())
     {
     {
         File* destFile = dynamic_cast<File*>(&dest);
         File* destFile = dynamic_cast<File*>(&dest);
         if (destFile)
         if (destFile)
@@ -269,6 +271,8 @@ bool Animation::Save(Serializer& dest) const
                 triggerElem.SetVariant(triggers_[i].data_);
                 triggerElem.SetVariant(triggers_[i].data_);
             }
             }
 
 
+            SaveMetadataToXML(rootElem);
+
             File xmlFile(context_, xmlName, FILE_WRITE);
             File xmlFile(context_, xmlName, FILE_WRITE);
             xml->Save(xmlFile);
             xml->Save(xmlFile);
         }
         }
@@ -373,29 +377,29 @@ SharedPtr<Animation> Animation::Clone(const String& cloneName) const
     ret->length_ = length_;
     ret->length_ = length_;
     ret->tracks_ = tracks_;
     ret->tracks_ = tracks_;
     ret->triggers_ = triggers_;
     ret->triggers_ = triggers_;
+    ret->CopyMetadata(*this);
     ret->SetMemoryUse(GetMemoryUse());
     ret->SetMemoryUse(GetMemoryUse());
-    
+
     return ret;
     return ret;
-} 
+}
 
 
-AnimationTrack* Animation::GetTrack(unsigned index) 
-{ 
-    if (index >= GetNumTracks()) 
+AnimationTrack* Animation::GetTrack(unsigned index)
+{
+    if (index >= GetNumTracks())
         return (AnimationTrack*) 0;
         return (AnimationTrack*) 0;
 
 
-    int j = 0; 
-    for(HashMap<StringHash, AnimationTrack>::Iterator i = tracks_.Begin(); i != tracks_.End(); ++i) 
+    int j = 0;
+    for(HashMap<StringHash, AnimationTrack>::Iterator i = tracks_.Begin(); i != tracks_.End(); ++i)
     {
     {
-        if (j == index) 
-            return &i->second_; 
-        
+        if (j == index)
+            return &i->second_;
+
         ++j;
         ++j;
     }
     }
-    
+
     return (AnimationTrack*) 0;
     return (AnimationTrack*) 0;
 }
 }
 
 
-
 AnimationTrack* Animation::GetTrack(const String& name)
 AnimationTrack* Animation::GetTrack(const String& name)
 {
 {
     HashMap<StringHash, AnimationTrack>::Iterator i = tracks_.Find(StringHash(name));
     HashMap<StringHash, AnimationTrack>::Iterator i = tracks_.Find(StringHash(name));

+ 4 - 4
Source/Atomic/Graphics/Animation.h

@@ -107,9 +107,9 @@ static const unsigned char CHANNEL_ROTATION = 0x2;
 static const unsigned char CHANNEL_SCALE = 0x4;
 static const unsigned char CHANNEL_SCALE = 0x4;
 
 
 /// Skeletal animation resource.
 /// Skeletal animation resource.
-class ATOMIC_API Animation : public Resource
+class ATOMIC_API Animation : public ResourceWithMetadata
 {
 {
-    ATOMIC_OBJECT(Animation, Resource);
+    ATOMIC_OBJECT(Animation, ResourceWithMetadata);
 
 
 public:
 public:
     /// Construct.
     /// Construct.
@@ -162,9 +162,9 @@ public:
     const HashMap<StringHash, AnimationTrack>& GetTracks() const { return tracks_; }
     const HashMap<StringHash, AnimationTrack>& GetTracks() const { return tracks_; }
 
 
     /// Return number of animation tracks.
     /// Return number of animation tracks.
-    unsigned GetNumTracks() const { return tracks_.Size(); } 
+    unsigned GetNumTracks() const { return tracks_.Size(); }
 
 
-    /// Return animation track by index. 
+    /// Return animation track by index.
     AnimationTrack *GetTrack(unsigned index);
     AnimationTrack *GetTrack(unsigned index);
 
 
     /// Return animation track by name.
     /// Return animation track by name.

+ 9 - 4
Source/Atomic/Graphics/Direct3D11/D3D11Graphics.cpp

@@ -212,6 +212,8 @@ Graphics::Graphics(Context* context) :
     resizable_(false),
     resizable_(false),
     highDPI_(false),
     highDPI_(false),
     vsync_(false),
     vsync_(false),
+    monitor_(0),
+    refreshRate_(0),
     tripleBuffer_(false),
     tripleBuffer_(false),
     flushGPU_(false),
     flushGPU_(false),
     forceGL2_(false),
     forceGL2_(false),
@@ -382,6 +384,7 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
 
 
     AdjustWindow(width, height, fullscreen, borderless, monitor);
     AdjustWindow(width, height, fullscreen, borderless, monitor);
     monitor_ = monitor;
     monitor_ = monitor;
+    refreshRate_ = refreshRate;
 
 
     if (maximize)
     if (maximize)
     {
     {
@@ -2110,13 +2113,15 @@ void Graphics::AdjustWindow(int& newWidth, int& newHeight, bool& newFullscreen,
         }
         }
         else 
         else 
         {
         {
-            if (newFullscreen || newBorderless) 
+            SDL_Rect display_rect;
+            SDL_GetDisplayBounds(monitor, &display_rect);
+
+            if (newFullscreen || (newBorderless && newWidth >= display_rect.w && newHeight >= display_rect.h))
             {
             {
-                // Reposition the window on the specified monitor
-                SDL_Rect display_rect;
-                SDL_GetDisplayBounds(monitor, &display_rect);
+                // Reposition the window on the specified monitor if it's supposed to cover the entire monitor
                 SDL_SetWindowPosition(window_, display_rect.x, display_rect.y);
                 SDL_SetWindowPosition(window_, display_rect.x, display_rect.y);
             }
             }
+
             SDL_SetWindowSize(window_, newWidth, newHeight);
             SDL_SetWindowSize(window_, newWidth, newHeight);
         }
         }
 
 

+ 5 - 4
Source/Atomic/Graphics/Direct3D9/D3D9Graphics.cpp

@@ -2393,11 +2393,12 @@ void Graphics::AdjustWindow(int& newWidth, int& newHeight, bool& newFullscreen,
             SDL_GetWindowSize(window_, &newWidth, &newHeight);
             SDL_GetWindowSize(window_, &newWidth, &newHeight);
         }
         }
         else {
         else {
-            if (newFullscreen || newBorderless) 
+            SDL_Rect display_rect;
+            SDL_GetDisplayBounds(monitor, &display_rect);
+
+            if (newFullscreen || (newBorderless && newWidth >= display_rect.w && newHeight >= display_rect.h))
             {
             {
-                // Reposition the window on the specified monitor
-                SDL_Rect display_rect;
-                SDL_GetDisplayBounds(monitor, &display_rect);
+                // Reposition the window on the specified monitor if it's supposed to cover the entire monitor
                 SDL_SetWindowPosition(window_, display_rect.x, display_rect.y);
                 SDL_SetWindowPosition(window_, display_rect.x, display_rect.y);
             }
             }
 
 

+ 1 - 1
Source/Atomic/Graphics/Graphics.cpp

@@ -220,7 +220,7 @@ PODVector<IntVector3> Graphics::GetResolutions(int monitor) const
 
 
 IntVector2 Graphics::GetDesktopResolution(int monitor) const
 IntVector2 Graphics::GetDesktopResolution(int monitor) const
 {
 {
-#if !defined(__ANDROID__) && !defined(IOS)
+#if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
     SDL_DisplayMode mode;
     SDL_DisplayMode mode;
     SDL_GetDesktopDisplayMode(monitor, &mode);
     SDL_GetDesktopDisplayMode(monitor, &mode);
     return IntVector2(mode.w, mode.h);
     return IntVector2(mode.w, mode.h);

+ 1 - 1
Source/Atomic/Graphics/GraphicsDefs.h

@@ -31,7 +31,7 @@ namespace Atomic
 class Vector3;
 class Vector3;
 
 
 /// Graphics capability support level. Web platform (Emscripten) also uses OpenGL ES, but is considered a desktop platform capability-wise
 /// Graphics capability support level. Web platform (Emscripten) also uses OpenGL ES, but is considered a desktop platform capability-wise
-#if defined(IOS) || defined(__ANDROID__) || defined(__arm__) || defined(__aarch64__)
+#if defined(IOS) || defined(TVOS) || defined(__ANDROID__) || defined(__arm__) || defined(__aarch64__)
 #define MOBILE_GRAPHICS
 #define MOBILE_GRAPHICS
 #else
 #else
 #define DESKTOP_GRAPHICS
 #define DESKTOP_GRAPHICS

+ 40 - 15
Source/Atomic/Graphics/Model.cpp

@@ -31,6 +31,9 @@
 #include "../Graphics/VertexBuffer.h"
 #include "../Graphics/VertexBuffer.h"
 #include "../IO/Log.h"
 #include "../IO/Log.h"
 #include "../IO/File.h"
 #include "../IO/File.h"
+#include "../IO/FileSystem.h"
+#include "../Resource/ResourceCache.h"
+#include "../Resource/XMLFile.h"
 
 
 #include "../DebugNew.h"
 #include "../DebugNew.h"
 
 
@@ -58,7 +61,7 @@ unsigned LookupIndexBuffer(IndexBuffer* buffer, const Vector<SharedPtr<IndexBuff
 }
 }
 
 
 Model::Model(Context* context) :
 Model::Model(Context* context) :
-    Resource(context)
+    ResourceWithMetadata(context)
 {
 {
 }
 }
 
 
@@ -318,25 +321,28 @@ bool Model::BeginLoad(Deserializer& source)
     memoryUse += sizeof(Vector3) * geometries_.Size();
     memoryUse += sizeof(Vector3) * geometries_.Size();
 
 
 // ATOMIC BEGIN
 // ATOMIC BEGIN
-    if (umdl)
+    if (!umdl)
     {
     {
-        SetMemoryUse(memoryUse);
-        return true;
-    }
-
-    // MODEL_VERSION
-    unsigned version = source.ReadUInt();
+        // MODEL_VERSION
+        unsigned version = source.ReadUInt();
 
 
-    // Read geometry names
-    geometryNames_.Resize(geometries_.Size());
-    for (unsigned i = 0; i < geometries_.Size(); ++i)
-    {
-        geometryNames_[i] = source.ReadString();
+        // Read geometry names
+        geometryNames_.Resize(geometries_.Size());
+        for (unsigned i = 0; i < geometries_.Size(); ++i)
+        {
+            geometryNames_[i] = source.ReadString();
+        }
     }
     }
+// ATOMIC END
 
 
-    SetMemoryUse(memoryUse);
+    // Read metadata
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    String xmlName = ReplaceExtension(GetName(), ".xml");
+    SharedPtr<XMLFile> file(cache->GetTempResource<XMLFile>(xmlName, false));
+    if (file)
+        LoadMetadataFromXML(file->GetRoot());
 
 
-// ATOMIC END
+    SetMemoryUse(memoryUse);
     return true;
     return true;
 }
 }
 
 
@@ -497,6 +503,25 @@ bool Model::Save(Serializer& dest) const
 
 
     // ATOMIC END
     // ATOMIC END
 
 
+    // Write metadata
+    if (HasMetadata())
+    {
+        File* destFile = dynamic_cast<File*>(&dest);
+        if (destFile)
+        {
+            String xmlName = ReplaceExtension(destFile->GetName(), ".xml");
+
+            SharedPtr<XMLFile> xml(new XMLFile(context_));
+            XMLElement rootElem = xml->CreateRoot("model");
+            SaveMetadataToXML(rootElem);
+
+            File xmlFile(context_, xmlName, FILE_WRITE);
+            xml->Save(xmlFile);
+        }
+        else
+            ATOMIC_LOGWARNING("Can not save model metadata when not saving into a file");
+    }
+
     return true;
     return true;
 }
 }
 
 

+ 2 - 2
Source/Atomic/Graphics/Model.h

@@ -111,9 +111,9 @@ static const unsigned MODEL_VERSION = 1;
 // ATOMIC END
 // ATOMIC END
 
 
 /// 3D model resource.
 /// 3D model resource.
-class ATOMIC_API Model : public Resource
+class ATOMIC_API Model : public ResourceWithMetadata
 {
 {
-    ATOMIC_OBJECT(Model, Resource);
+    ATOMIC_OBJECT(Model, ResourceWithMetadata);
 
 
 public:
 public:
     /// Construct.
     /// Construct.

+ 17 - 13
Source/Atomic/Graphics/OpenGL/OGLGraphics.cpp

@@ -236,6 +236,8 @@ Graphics::Graphics(Context* context_) :
     resizable_(false),
     resizable_(false),
     highDPI_(false),
     highDPI_(false),
     vsync_(false),
     vsync_(false),
+    monitor_(0),
+    refreshRate_(0),
     tripleBuffer_(false),
     tripleBuffer_(false),
     sRGB_(false),
     sRGB_(false),
     forceGL2_(false),
     forceGL2_(false),
@@ -429,9 +431,10 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
         SDL_Rect display_rect;
         SDL_Rect display_rect;
         SDL_GetDisplayBounds(monitor, &display_rect);
         SDL_GetDisplayBounds(monitor, &display_rect);
         SDL_SetWindowPosition(window_, display_rect.x, display_rect.y);
         SDL_SetWindowPosition(window_, display_rect.x, display_rect.y);
+        bool reposition = fullscreen || (borderless && width >= display_rect.w && height >= display_rect.h);
 
 
-        int x = fullscreen || borderless ? display_rect.x : position_.x_;
-        int y = fullscreen || borderless ? display_rect.y : position_.y_;
+        int x = reposition ? display_rect.x : position_.x_;
+        int y = reposition ? display_rect.y : position_.y_;
 
 
         unsigned flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
         unsigned flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
         if (fullscreen)
         if (fullscreen)
@@ -496,8 +499,8 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
     // Set vsync
     // Set vsync
     SDL_GL_SetSwapInterval(vsync ? 1 : 0);
     SDL_GL_SetSwapInterval(vsync ? 1 : 0);
 
 
-    // Store the system FBO on IOS now
-#ifdef IOS
+    // Store the system FBO on iOS/tvOS now
+#if defined(IOS) || defined(TVOS)
     glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&impl_->systemFBO_);
     glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&impl_->systemFBO_);
 #endif
 #endif
 
 
@@ -508,6 +511,8 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
     vsync_ = vsync;
     vsync_ = vsync;
     tripleBuffer_ = tripleBuffer;
     tripleBuffer_ = tripleBuffer;
     multiSample_ = multiSample;
     multiSample_ = multiSample;
+    monitor_ = monitor;
+    refreshRate_ = refreshRate;
 
 
     SDL_GL_GetDrawableSize(window_, &width_, &height_);
     SDL_GL_GetDrawableSize(window_, &width_, &height_);
     if (!fullscreen)
     if (!fullscreen)
@@ -2087,8 +2092,8 @@ bool Graphics::GetDither() const
 
 
 bool Graphics::IsDeviceLost() const
 bool Graphics::IsDeviceLost() const
 {
 {
-    // On iOS treat window minimization as device loss, as it is forbidden to access OpenGL when minimized
-#ifdef IOS
+    // On iOS and tvOS treat window minimization as device loss, as it is forbidden to access OpenGL when minimized
+#if defined(IOS) || defined(TVOS)
     if (window_ && (SDL_GetWindowFlags(window_) & SDL_WINDOW_MINIMIZED) != 0)
     if (window_ && (SDL_GetWindowFlags(window_) & SDL_WINDOW_MINIMIZED) != 0)
         return true;
         return true;
 #endif
 #endif
@@ -2418,7 +2423,7 @@ void Graphics::Release(bool clearGPUObjects, bool closeWindow)
     impl_->depthTextures_.Clear();
     impl_->depthTextures_.Clear();
 
 
     // End fullscreen mode first to counteract transition and getting stuck problems on OS X
     // End fullscreen mode first to counteract transition and getting stuck problems on OS X
-#if defined(__APPLE__) && !defined(IOS)
+#if defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
     if (closeWindow && fullscreen_ && !externalWindow_)
     if (closeWindow && fullscreen_ && !externalWindow_)
         SDL_SetWindowFullscreen(window_, 0);
         SDL_SetWindowFullscreen(window_, 0);
 #endif
 #endif
@@ -2511,7 +2516,7 @@ void Graphics::Restore()
         }
         }
 #endif
 #endif
 
 
-#ifdef IOS
+#if defined(IOS) || defined(TVOS)
         glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&impl_->systemFBO_);
         glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&impl_->systemFBO_);
 #endif
 #endif
 
 
@@ -2830,8 +2835,8 @@ void Graphics::CheckFeatureSupport()
     if (numSupportedRTs >= 4)
     if (numSupportedRTs >= 4)
         deferredSupport_ = true;
         deferredSupport_ = true;
 
 
-#if defined(__APPLE__) && !defined(IOS)
-    // On OS X check for an Intel driver and use shadow map RGBA dummy color textures, because mixing
+#if defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
+    // On macOS check for an Intel driver and use shadow map RGBA dummy color textures, because mixing
     // depth-only FBO rendering and backbuffer rendering will bug, resulting in a black screen in full
     // depth-only FBO rendering and backbuffer rendering will bug, resulting in a black screen in full
     // screen mode, and incomplete shadow maps in windowed mode
     // screen mode, and incomplete shadow maps in windowed mode
     String renderer((const char*)glGetString(GL_RENDERER));
     String renderer((const char*)glGetString(GL_RENDERER));
@@ -2870,9 +2875,8 @@ void Graphics::CheckFeatureSupport()
     }
     }
     else
     else
     {
     {
-        #ifdef IOS
-        // iOS hack: depth renderbuffer seems to fail, so use depth textures for everything
-        // if supported
+#if defined(IOS) || defined(TVOS)
+        // iOS hack: depth renderbuffer seems to fail, so use depth textures for everything if supported
         glesDepthStencilFormat = GL_DEPTH_COMPONENT;
         glesDepthStencilFormat = GL_DEPTH_COMPONENT;
 #endif
 #endif
         shadowMapFormat_ = GL_DEPTH_COMPONENT;
         shadowMapFormat_ = GL_DEPTH_COMPONENT;

+ 2 - 2
Source/Atomic/Graphics/OpenGL/OGLGraphicsImpl.h

@@ -29,7 +29,7 @@
 #include "../../Graphics/Texture2D.h"
 #include "../../Graphics/Texture2D.h"
 #include "../../Math/Color.h"
 #include "../../Math/Color.h"
 
 
-#if defined(IOS)
+#if defined(IOS) || defined(TVOS)
 #include <OpenGLES/ES2/gl.h>
 #include <OpenGLES/ES2/gl.h>
 #include <OpenGLES/ES2/glext.h>
 #include <OpenGLES/ES2/glext.h>
 #elif defined(__ANDROID__) || defined (__arm__) || defined(__aarch64__) || defined (__EMSCRIPTEN__)
 #elif defined(__ANDROID__) || defined (__arm__) || defined(__aarch64__) || defined (__EMSCRIPTEN__)
@@ -114,7 +114,7 @@ public:
 private:
 private:
     /// SDL OpenGL context.
     /// SDL OpenGL context.
     SDL_GLContext context_;
     SDL_GLContext context_;
-    /// IOS system framebuffer handle.
+    /// iOS/tvOS system framebuffer handle.
     unsigned systemFBO_;
     unsigned systemFBO_;
     /// Active texture unit.
     /// Active texture unit.
     unsigned activeTexture_;
     unsigned activeTexture_;

+ 2 - 2
Source/Atomic/Graphics/OpenGL/OGLTexture2D.cpp

@@ -460,8 +460,8 @@ bool Texture2D::Create()
         requestedLevels_ = 1;
         requestedLevels_ = 1;
     else if (usage_ == TEXTURE_RENDERTARGET)
     else if (usage_ == TEXTURE_RENDERTARGET)
     {
     {
-#if defined(__EMSCRIPTEN__) || defined(IOS)
-        // glGenerateMipmap appears to not be working on WebGL or iOS, disable rendertarget mipmaps for now
+#if defined(__EMSCRIPTEN__) || defined(IOS) || defined(TVOS)
+        // glGenerateMipmap appears to not be working on WebGL or iOS/tvOS, disable rendertarget mipmaps for now
         requestedLevels_ = 1;
         requestedLevels_ = 1;
 #else
 #else
         if (requestedLevels_ != 1)
         if (requestedLevels_ != 1)

+ 2 - 2
Source/Atomic/Graphics/OpenGL/OGLTextureCube.cpp

@@ -485,8 +485,8 @@ bool TextureCube::Create()
         requestedLevels_ = 1;
         requestedLevels_ = 1;
     else if (usage_ == TEXTURE_RENDERTARGET)
     else if (usage_ == TEXTURE_RENDERTARGET)
     {
     {
-#if defined(__EMSCRIPTEN__) || defined(IOS)
-        // glGenerateMipmap appears to not be working on WebGL or iOS, disable rendertarget mipmaps for now
+#if defined(__EMSCRIPTEN__) || defined(IOS) || defined(TVOS)
+        // glGenerateMipmap appears to not be working on WebGL or iOS/tvOS, disable rendertarget mipmaps for now
         requestedLevels_ = 1;
         requestedLevels_ = 1;
 #else
 #else
         if (requestedLevels_ != 1)
         if (requestedLevels_ != 1)

+ 7 - 7
Source/Atomic/Graphics/Text3D/Text3D.cpp

@@ -78,7 +78,7 @@ void Text3D::RegisterObject(Context* context)
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Font", GetFontAttr, SetFontAttr, ResourceRef, ResourceRef(Text3DFont::GetTypeStatic()), AM_DEFAULT);
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Font", GetFontAttr, SetFontAttr, ResourceRef, ResourceRef(Text3DFont::GetTypeStatic()), AM_DEFAULT);
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()),
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()),
         AM_DEFAULT);
         AM_DEFAULT);
-    ATOMIC_ATTRIBUTE("Font Size", int, text_.fontSize_, TEXT3D_DEFAULT_FONT_SIZE, AM_DEFAULT);
+    ATOMIC_ATTRIBUTE("Font Size", float, text_.fontSize_, TEXT3D_DEFAULT_FONT_SIZE, AM_DEFAULT);
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Text", GetTextAttr, SetTextAttr, String, String::EMPTY, AM_DEFAULT);
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Text", GetTextAttr, SetTextAttr, String, String::EMPTY, AM_DEFAULT);
     ATOMIC_ENUM_ATTRIBUTE("Text Alignment", text_.textAlignment_, horizontalAlignments, HA_LEFT, AM_DEFAULT);
     ATOMIC_ENUM_ATTRIBUTE("Text Alignment", text_.textAlignment_, horizontalAlignments, HA_LEFT, AM_DEFAULT);
     ATOMIC_ATTRIBUTE("Row Spacing", float, text_.rowSpacing_, 1.0f, AM_DEFAULT);
     ATOMIC_ATTRIBUTE("Row Spacing", float, text_.rowSpacing_, 1.0f, AM_DEFAULT);
@@ -185,7 +185,7 @@ void Text3D::SetMaterial(Material* material)
     UpdateTextMaterials(true);
     UpdateTextMaterials(true);
 }
 }
 
 
-bool Text3D::SetFont(const String& fontName, int size)
+bool Text3D::SetFont(const String& fontName, float size)
 {
 {
     bool success = text_.SetFont(fontName, size);
     bool success = text_.SetFont(fontName, size);
 
 
@@ -198,7 +198,7 @@ bool Text3D::SetFont(const String& fontName, int size)
     return success;
     return success;
 }
 }
 
 
-bool Text3D::SetFont(Text3DFont* font, int size)
+bool Text3D::SetFont(Text3DFont* font, float size)
 {
 {
     bool success = text_.SetFont(font, size);
     bool success = text_.SetFont(font, size);
 
 
@@ -209,7 +209,7 @@ bool Text3D::SetFont(Text3DFont* font, int size)
     return success;
     return success;
 }
 }
 
 
-bool Text3D::SetFontSize(int size)
+bool Text3D::SetFontSize(float size)
 {
 {
     bool success = text_.SetFontSize(size);
     bool success = text_.SetFontSize(size);
 
 
@@ -369,7 +369,7 @@ Text3DFont* Text3D::GetFont() const
     return text_.GetFont();
     return text_.GetFont();
 }
 }
 
 
-int Text3D::GetFontSize() const
+float Text3D::GetFontSize() const
 {
 {
     return text_.GetFontSize();
     return text_.GetFontSize();
 }
 }
@@ -454,12 +454,12 @@ int Text3D::GetRowWidth(unsigned index) const
     return text_.GetRowWidth(index);
     return text_.GetRowWidth(index);
 }
 }
 
 
-IntVector2 Text3D::GetCharPosition(unsigned index)
+Vector2 Text3D::GetCharPosition(unsigned index)
 {
 {
     return text_.GetCharPosition(index);
     return text_.GetCharPosition(index);
 }
 }
 
 
-IntVector2 Text3D::GetCharSize(unsigned index)
+Vector2 Text3D::GetCharSize(unsigned index)
 {
 {
     return text_.GetCharSize(index);
     return text_.GetCharSize(index);
 }
 }

+ 6 - 6
Source/Atomic/Graphics/Text3D/Text3D.h

@@ -55,11 +55,11 @@ public:
     virtual UpdateGeometryType GetUpdateGeometryType();
     virtual UpdateGeometryType GetUpdateGeometryType();
 
 
     /// Set font by looking from resource cache by name and font size. Return true if successful.
     /// Set font by looking from resource cache by name and font size. Return true if successful.
-    bool SetFont(const String& fontName, int size = TEXT3D_DEFAULT_FONT_SIZE);
+    bool SetFont(const String& fontName, float size = TEXT3D_DEFAULT_FONT_SIZE);
     /// Set font and font size. Return true if successful.
     /// Set font and font size. Return true if successful.
-    bool SetFont(Text3DFont* font, int size = TEXT3D_DEFAULT_FONT_SIZE);
+    bool SetFont(Text3DFont* font, float size = TEXT3D_DEFAULT_FONT_SIZE);
     /// Set font size only while retaining the existing font. Return true if successful.
     /// Set font size only while retaining the existing font. Return true if successful.
-    bool SetFontSize(int size);
+    bool SetFontSize(float size);
     /// Set material.
     /// Set material.
     void SetMaterial(Material* material);
     void SetMaterial(Material* material);
     /// Set text. Text is assumed to be either ASCII or UTF8-encoded.
     /// Set text. Text is assumed to be either ASCII or UTF8-encoded.
@@ -104,7 +104,7 @@ public:
     /// Return font.
     /// Return font.
     Text3DFont* GetFont() const;
     Text3DFont* GetFont() const;
     /// Return font size.
     /// Return font size.
-    int GetFontSize() const;
+    float GetFontSize() const;
     /// Return material.
     /// Return material.
     Material* GetMaterial() const;
     Material* GetMaterial() const;
     /// Return text.
     /// Return text.
@@ -144,9 +144,9 @@ public:
     /// Return width of row by index.
     /// Return width of row by index.
     int GetRowWidth(unsigned index) const;
     int GetRowWidth(unsigned index) const;
     /// Return position of character by index relative to the text element origin.
     /// Return position of character by index relative to the text element origin.
-    IntVector2 GetCharPosition(unsigned index);
+    Vector2 GetCharPosition(unsigned index);
     /// Return size of character by index.
     /// Return size of character by index.
-    IntVector2 GetCharSize(unsigned index);
+    Vector2 GetCharSize(unsigned index);
     /// Return corner color.
     /// Return corner color.
     const Color& GetColor(Text3DCorner corner) const;
     const Color& GetColor(Text3DCorner corner) const;
     /// Return opacity.
     /// Return opacity.

+ 28 - 30
Source/Atomic/Graphics/Text3D/Text3DBatch.cpp

@@ -79,32 +79,7 @@ void Text3DBatch::SetDefaultColor()
     }
     }
 }
 }
 
 
-
-unsigned Text3DBatch::GetInterpolatedColor(int x, int y)
-{
-    const IntVector2& size = element_->GetSize();
-
-    if (size.x_ && size.y_)
-    {
-        float cLerpX = Clamp((float)x / (float)size.x_, 0.0f, 1.0f);
-        float cLerpY = Clamp((float)y / (float)size.y_, 0.0f, 1.0f);
-
-        Color topColor = element_->GetColor(C_TOPLEFT).Lerp(element_->GetColor(C_TOPRIGHT), cLerpX);
-        Color bottomColor = element_->GetColor(C_BOTTOMLEFT).Lerp(element_->GetColor(C_BOTTOMRIGHT), cLerpX);
-        Color color = topColor.Lerp(bottomColor, cLerpY);
-        color.a_ *= element_->GetOpacity();
-        return color.ToUInt();
-    }
-    else
-    {
-        Color color = element_->GetColor(C_TOPLEFT);
-        color.a_ *= element_->GetOpacity();
-        return color.ToUInt();
-    }
-}
-
-
-void Text3DBatch::AddQuad(int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth, int texHeight)
+void Text3DBatch::AddQuad(float x, float y, float width, float height, int texOffsetX, int texOffsetY, int texWidth, int texHeight)
 {
 {
     unsigned topLeftColor, topRightColor, bottomLeftColor, bottomRightColor;
     unsigned topLeftColor, topRightColor, bottomLeftColor, bottomRightColor;
 
 
@@ -129,10 +104,10 @@ void Text3DBatch::AddQuad(int x, int y, int width, int height, int texOffsetX, i
 
 
     const IntVector2& screenPos = IntVector2::ZERO; //element_->GetScreenPosition();
     const IntVector2& screenPos = IntVector2::ZERO; //element_->GetScreenPosition();
 
 
-    float left = (float)(x + screenPos.x_) - posAdjust.x_;
-    float right = left + (float)width;
-    float top = (float)(y + screenPos.y_) - posAdjust.x_;
-    float bottom = top + (float)height;
+    float left = x + screenPos.x_ - posAdjust.x_;
+    float right = left + width;
+    float top = y + screenPos.y_ - posAdjust.x_;
+    float bottom = top + height;
 
 
     float leftUV = texOffsetX * invTextureSize_.x_;
     float leftUV = texOffsetX * invTextureSize_.x_;
     float topUV = texOffsetY * invTextureSize_.y_;
     float topUV = texOffsetY * invTextureSize_.y_;
@@ -444,6 +419,29 @@ bool Text3DBatch::Merge(const Text3DBatch& batch)
     return true;
     return true;
 }
 }
 
 
+unsigned Text3DBatch::GetInterpolatedColor(float x, float y)
+{
+    const IntVector2& size = element_->GetSize();
+
+    if (size.x_ && size.y_)
+    {
+        float cLerpX = Clamp(x / (float)size.x_, 0.0f, 1.0f);
+        float cLerpY = Clamp(y / (float)size.y_, 0.0f, 1.0f);
+
+        Color topColor = element_->GetColor(C_TOPLEFT).Lerp(element_->GetColor(C_TOPRIGHT), cLerpX);
+        Color bottomColor = element_->GetColor(C_BOTTOMLEFT).Lerp(element_->GetColor(C_BOTTOMRIGHT), cLerpX);
+        Color color = topColor.Lerp(bottomColor, cLerpY);
+        color.a_ *= element_->GetOpacity();
+        return color.ToUInt();
+    }
+    else
+    {
+        Color color = element_->GetColor(C_TOPLEFT);
+        color.a_ *= element_->GetOpacity();
+        return color.ToUInt();
+    }
+}
+
 void Text3DBatch::AddOrMerge(const Text3DBatch& batch, PODVector<Text3DBatch>& batches)
 void Text3DBatch::AddOrMerge(const Text3DBatch& batch, PODVector<Text3DBatch>& batches)
 {
 {
     if (batch.vertexEnd_ == batch.vertexStart_)
     if (batch.vertexEnd_ == batch.vertexStart_)

+ 2 - 2
Source/Atomic/Graphics/Text3D/Text3DBatch.h

@@ -51,7 +51,7 @@ public:
     /// Restore UI element's default color.
     /// Restore UI element's default color.
     void SetDefaultColor();
     void SetDefaultColor();
     /// Add a quad.
     /// Add a quad.
-    void AddQuad(int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth = 0, int texHeight = 0);
+    void AddQuad(float x, float y, float width, float height, int texOffsetX, int texOffsetY, int texWidth = 0, int texHeight = 0);
     /// Add a quad using a transform matrix.
     /// Add a quad using a transform matrix.
     void AddQuad(const Matrix3x4& transform, int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth = 0,
     void AddQuad(const Matrix3x4& transform, int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth = 0,
         int texHeight = 0);
         int texHeight = 0);
@@ -67,7 +67,7 @@ public:
     /// Merge with another batch.
     /// Merge with another batch.
     bool Merge(const Text3DBatch& batch);
     bool Merge(const Text3DBatch& batch);
     /// Return an interpolated color for the element.
     /// Return an interpolated color for the element.
-    unsigned GetInterpolatedColor(int x, int y);
+    unsigned GetInterpolatedColor(float x, float y);
 
 
 
 
     /// Add or merge a batch.
     /// Add or merge a batch.

+ 3 - 3
Source/Atomic/Graphics/Text3D/Text3DBitmap.cpp

@@ -48,7 +48,7 @@ Text3DBitmap::~Text3DBitmap()
 {
 {
 }
 }
 
 
-bool Text3DBitmap::Load(const unsigned char* fontData, unsigned fontDataSize, int pointSize)
+bool Text3DBitmap::Load(const unsigned char* fontData, unsigned fontDataSize, float pointSize)
 {
 {
     Context* context = font_->GetContext();
     Context* context = font_->GetContext();
 
 
@@ -253,7 +253,7 @@ bool Text3DBitmap::Load(Text3DFontFace* fontFace, bool usedGlyphs)
     for (unsigned i = 0; i < newImages.Size(); ++i)
     for (unsigned i = 0; i < newImages.Size(); ++i)
         textures_[i] = LoadFaceTexture(newImages[i]);
         textures_[i] = LoadFaceTexture(newImages[i]);
 
 
-    for (HashMap<unsigned, short>::ConstIterator i = fontFace->kerningMapping_.Begin(); i != fontFace->kerningMapping_.End(); ++i)
+    for (HashMap<unsigned, float>::ConstIterator i = fontFace->kerningMapping_.Begin(); i != fontFace->kerningMapping_.End(); ++i)
     {
     {
         unsigned first = (i->first_) >> 16;
         unsigned first = (i->first_) >> 16;
         unsigned second = (i->first_) & 0xffff;
         unsigned second = (i->first_) & 0xffff;
@@ -331,7 +331,7 @@ bool Text3DBitmap::Save(Serializer& dest, int pointSize, const String& indentati
     if (!kerningMapping_.Empty())
     if (!kerningMapping_.Empty())
     {
     {
         XMLElement kerningsElem = rootElem.CreateChild("kernings");
         XMLElement kerningsElem = rootElem.CreateChild("kernings");
-        for (HashMap<unsigned, short>::ConstIterator i = kerningMapping_.Begin(); i != kerningMapping_.End(); ++i)
+        for (HashMap<unsigned, float>::ConstIterator i = kerningMapping_.Begin(); i != kerningMapping_.End(); ++i)
         {
         {
             XMLElement kerningElem = kerningsElem.CreateChild("kerning");
             XMLElement kerningElem = kerningsElem.CreateChild("kerning");
             kerningElem.SetInt("first", i->first_ >> 16);
             kerningElem.SetInt("first", i->first_ >> 16);

+ 1 - 1
Source/Atomic/Graphics/Text3D/Text3DBitmap.h

@@ -42,7 +42,7 @@ public:
     ~Text3DBitmap();
     ~Text3DBitmap();
 
 
     /// Load font face.
     /// Load font face.
-    virtual bool Load(const unsigned char* fontData, unsigned fontDataSize, int pointSize);
+    virtual bool Load(const unsigned char* fontData, unsigned fontDataSize, float pointSize);
     /// Load from existed font face, pack used glyphs into smallest texture size and smallest number of texture.
     /// Load from existed font face, pack used glyphs into smallest texture size and smallest number of texture.
     bool Load(Text3DFontFace* fontFace, bool usedGlyphs);
     bool Load(Text3DFontFace* fontFace, bool usedGlyphs);
     /// Save as a new bitmap font type in XML format. Return true if successful.
     /// Save as a new bitmap font type in XML format. Return true if successful.

+ 24 - 11
Source/Atomic/Graphics/Text3D/Text3DFont.cpp

@@ -38,8 +38,17 @@
 namespace Atomic
 namespace Atomic
 {
 {
 
 
-static const int MIN_POINT_SIZE = 1;
-static const int MAX_POINT_SIZE = 96;
+namespace
+{
+    /// Convert float to 26.6 fixed-point (as used internally by FreeType)
+    inline int FloatToFixed(float value)
+    {
+        return (int)(value * 64);
+    }
+}
+
+static const float MIN_POINT_SIZE = 1;
+static const float MAX_POINT_SIZE = 96;
 
 
 Text3DFont::Text3DFont(Context* context) :
 Text3DFont::Text3DFont(Context* context) :
     Resource(context),
     Resource(context),
@@ -126,7 +135,7 @@ void Text3DFont::SetScaledGlyphOffset(const Vector2& offset)
     scaledOffset_ = offset;
     scaledOffset_ = offset;
 }
 }
 
 
-Text3DFontFace* Text3DFont::GetFace(int pointSize)
+Text3DFontFace* Text3DFont::GetFace(float pointSize)
 {
 {
     // In headless mode, always return null
     // In headless mode, always return null
     Graphics* graphics = GetSubsystem<Graphics>();
     Graphics* graphics = GetSubsystem<Graphics>();
@@ -139,7 +148,9 @@ Text3DFontFace* Text3DFont::GetFace(int pointSize)
     else
     else
         pointSize = Clamp(pointSize, MIN_POINT_SIZE, MAX_POINT_SIZE);
         pointSize = Clamp(pointSize, MIN_POINT_SIZE, MAX_POINT_SIZE);
 
 
-    HashMap<int, SharedPtr<Text3DFontFace> >::Iterator i = faces_.Find(pointSize);
+    // For outline fonts, we return the nearest size in 1/64th increments, as that's what FreeType supports.
+    int key = FloatToFixed(pointSize);
+    HashMap<int, SharedPtr<Text3DFontFace> >::Iterator i = faces_.Find(key);
     if (i != faces_.End())
     if (i != faces_.End())
     {
     {
         if (!i->second_->IsDataLost())
         if (!i->second_->IsDataLost())
@@ -166,10 +177,10 @@ Text3DFontFace* Text3DFont::GetFace(int pointSize)
     }
     }
 }
 }
 
 
-IntVector2 Text3DFont::GetTotalGlyphOffset(int pointSize) const
+IntVector2 Text3DFont::GetTotalGlyphOffset(float pointSize) const
 {
 {
-    Vector2 multipliedOffset = (float)pointSize * scaledOffset_;
-    return absoluteOffset_ + IntVector2((int)multipliedOffset.x_, (int)multipliedOffset.y_);
+    Vector2 multipliedOffset = pointSize * scaledOffset_;
+    return absoluteOffset_ + IntVector2((int)(multipliedOffset.x_ + 0.5f), (int)(multipliedOffset.y_ + 0.5f));
 }
 }
 
 
 void Text3DFont::ReleaseFaces()
 void Text3DFont::ReleaseFaces()
@@ -208,23 +219,25 @@ void Text3DFont::LoadParameters()
     }
     }
 }
 }
 
 
-Text3DFontFace* Text3DFont::GetFaceFreeType(int pointSize)
+Text3DFontFace* Text3DFont::GetFaceFreeType(float pointSize)
 {
 {
     SharedPtr<Text3DFontFace> newFace(new Text3DFreeType(this));
     SharedPtr<Text3DFontFace> newFace(new Text3DFreeType(this));
     if (!newFace->Load(&fontData_[0], fontDataSize_, pointSize))
     if (!newFace->Load(&fontData_[0], fontDataSize_, pointSize))
         return 0;
         return 0;
 
 
-    faces_[pointSize] = newFace;
+    int key = FloatToFixed(pointSize);
+    faces_[key] = newFace;
     return newFace;
     return newFace;
 }
 }
 
 
-Text3DFontFace* Text3DFont::GetFaceBitmap(int pointSize)
+Text3DFontFace* Text3DFont::GetFaceBitmap(float pointSize)
 {
 {
     SharedPtr<Text3DFontFace> newFace(new Text3DBitmap(this));
     SharedPtr<Text3DFontFace> newFace(new Text3DBitmap(this));
     if (!newFace->Load(&fontData_[0], fontDataSize_, pointSize))
     if (!newFace->Load(&fontData_[0], fontDataSize_, pointSize))
         return 0;
         return 0;
 
 
-    faces_[pointSize] = newFace;
+    int key = FloatToFixed(pointSize);
+    faces_[key] = newFace;
     return newFace;
     return newFace;
 }
 }
 
 

+ 4 - 4
Source/Atomic/Graphics/Text3D/Text3DFont.h

@@ -66,7 +66,7 @@ public:
     void SetScaledGlyphOffset(const Vector2& offset);
     void SetScaledGlyphOffset(const Vector2& offset);
 
 
     /// Return font face. Pack and render to a texture if not rendered yet. Return null on error.
     /// Return font face. Pack and render to a texture if not rendered yet. Return null on error.
-    Text3DFontFace* GetFace(int pointSize);
+    Text3DFontFace* GetFace(float pointSize);
 
 
     /// Return font type.
     /// Return font type.
     Text3DFontType GetFontType() const { return fontType_; }
     Text3DFontType GetFontType() const { return fontType_; }
@@ -81,7 +81,7 @@ public:
     const Vector2& GetScaledGlyphOffset() const { return scaledOffset_; }
     const Vector2& GetScaledGlyphOffset() const { return scaledOffset_; }
 
 
     /// Return the total effective offset for a point size.
     /// Return the total effective offset for a point size.
-    IntVector2 GetTotalGlyphOffset(int pointSize) const;
+    IntVector2 GetTotalGlyphOffset(float pointSize) const;
 
 
     /// Release font faces and recreate them next time when requested. Called when font textures lost or global font properties change.
     /// Release font faces and recreate them next time when requested. Called when font textures lost or global font properties change.
     void ReleaseFaces();
     void ReleaseFaces();
@@ -90,9 +90,9 @@ private:
     /// Load font glyph offset parameters from an optional XML file. Called internally when loading TrueType fonts.
     /// Load font glyph offset parameters from an optional XML file. Called internally when loading TrueType fonts.
     void LoadParameters();
     void LoadParameters();
     /// Return font face using FreeType. Called internally. Return null on error.
     /// Return font face using FreeType. Called internally. Return null on error.
-    Text3DFontFace* GetFaceFreeType(int pointSize);
+    Text3DFontFace* GetFaceFreeType(float pointSize);
     /// Return bitmap font face. Called internally. Return null on error.
     /// Return bitmap font face. Called internally. Return null on error.
-    Text3DFontFace* GetFaceBitmap(int pointSize);
+    Text3DFontFace* GetFaceBitmap(float pointSize);
 
 
     /// Created faces.
     /// Created faces.
     HashMap<int, SharedPtr<Text3DFontFace> > faces_;
     HashMap<int, SharedPtr<Text3DFontFace> > faces_;

+ 2 - 2
Source/Atomic/Graphics/Text3D/Text3DFontFace.cpp

@@ -70,7 +70,7 @@ const Text3DFontGlyph* Text3DFontFace::GetGlyph(unsigned c)
         return 0;
         return 0;
 }
 }
 
 
-short Text3DFontFace::GetKerning(unsigned c, unsigned d) const
+float Text3DFontFace::GetKerning(unsigned c, unsigned d) const
 {
 {
     if (kerningMapping_.Empty())
     if (kerningMapping_.Empty())
         return 0;
         return 0;
@@ -83,7 +83,7 @@ short Text3DFontFace::GetKerning(unsigned c, unsigned d) const
 
 
     unsigned value = (c << 16) + d;
     unsigned value = (c << 16) + d;
 
 
-    HashMap<unsigned, short>::ConstIterator i = kerningMapping_.Find(value);
+    HashMap<unsigned, float>::ConstIterator i = kerningMapping_.Find(value);
     if (i != kerningMapping_.End())
     if (i != kerningMapping_.End())
         return i->second_;
         return i->second_;
 
 

+ 8 - 8
Source/Atomic/Graphics/Text3D/Text3DFontFace.h

@@ -52,7 +52,7 @@ struct ATOMIC_API Text3DFontGlyph
     /// Glyph Y offset from origin.
     /// Glyph Y offset from origin.
     short offsetY_;
     short offsetY_;
     /// Horizontal advance.
     /// Horizontal advance.
-    short advanceX_;
+    float advanceX_;
     /// Texture page. M_MAX_UNSIGNED if not yet resident on any texture.
     /// Texture page. M_MAX_UNSIGNED if not yet resident on any texture.
     unsigned page_;
     unsigned page_;
     /// Used flag.
     /// Used flag.
@@ -73,7 +73,7 @@ public:
     ~Text3DFontFace();
     ~Text3DFontFace();
 
 
     /// Load font face.
     /// Load font face.
-    virtual bool Load(const unsigned char* fontData, unsigned fontDataSize, int pointSize) = 0;
+    virtual bool Load(const unsigned char* fontData, unsigned fontDataSize, float pointSize) = 0;
     /// Return pointer to the glyph structure corresponding to a character. Return null if glyph not found.
     /// Return pointer to the glyph structure corresponding to a character. Return null if glyph not found.
     virtual const Text3DFontGlyph* GetGlyph(unsigned c);
     virtual const Text3DFontGlyph* GetGlyph(unsigned c);
 
 
@@ -81,15 +81,15 @@ public:
     virtual bool HasMutableGlyphs() const { return false; }
     virtual bool HasMutableGlyphs() const { return false; }
 
 
     /// Return the kerning for a character and the next character.
     /// Return the kerning for a character and the next character.
-    short GetKerning(unsigned c, unsigned d) const;
+    float GetKerning(unsigned c, unsigned d) const;
     /// Return true when one of the texture has a data loss.
     /// Return true when one of the texture has a data loss.
     bool IsDataLost() const;
     bool IsDataLost() const;
 
 
     /// Return point size.
     /// Return point size.
-    int GetPointSize() const { return pointSize_; }
+    float GetPointSize() const { return pointSize_; }
 
 
     /// Return row height.
     /// Return row height.
-    int GetRowHeight() const { return rowHeight_; }
+    float GetRowHeight() const { return rowHeight_; }
 
 
     /// Return textures.
     /// Return textures.
     const Vector<SharedPtr<Texture2D> >& GetTextures() const { return textures_; }
     const Vector<SharedPtr<Texture2D> >& GetTextures() const { return textures_; }
@@ -106,13 +106,13 @@ protected:
     /// Glyph mapping.
     /// Glyph mapping.
     HashMap<unsigned, Text3DFontGlyph> glyphMapping_;
     HashMap<unsigned, Text3DFontGlyph> glyphMapping_;
     /// Kerning mapping.
     /// Kerning mapping.
-    HashMap<unsigned, short> kerningMapping_;
+    HashMap<unsigned, float> kerningMapping_;
     /// Glyph texture pages.
     /// Glyph texture pages.
     Vector<SharedPtr<Texture2D> > textures_;
     Vector<SharedPtr<Texture2D> > textures_;
     /// Point size.
     /// Point size.
-    int pointSize_;
+    float pointSize_;
     /// Row height.
     /// Row height.
-    int rowHeight_;
+    float rowHeight_;
 };
 };
 
 
 }
 }

+ 43 - 17
Source/Atomic/Graphics/Text3D/Text3DFreeType.cpp

@@ -40,9 +40,9 @@
 namespace Atomic
 namespace Atomic
 {
 {
 
 
-inline int RoundToPixels(FT_Pos value)
+inline float FixedToFloat(FT_Pos value)
 {
 {
-    return (int)(value >> 6) + (((value & 0x3f) >= 0x20) ? 1 : 0);
+    return value / 64.0f;
 }
 }
 
 
 /// FreeType library subsystem.
 /// FreeType library subsystem.
@@ -78,7 +78,9 @@ Text3DFreeType::Text3DFreeType(Text3DFont* font) :
     face_(0),
     face_(0),
     loadMode_(FT_LOAD_DEFAULT),
     loadMode_(FT_LOAD_DEFAULT),
     hasMutableGlyph_(false),
     hasMutableGlyph_(false),
-    forceAutoHint_(false)
+    forceAutoHint_(false),
+    subpixelGlyphPositions_(false),
+    fontHintLevel_(FONT_HINT_LEVEL_NORMAL)
 {
 {
 }
 }
 
 
@@ -91,7 +93,7 @@ Text3DFreeType::~Text3DFreeType()
     }
     }
 }
 }
 
 
-bool Text3DFreeType::Load(const unsigned char* fontData, unsigned fontDataSize, int pointSize)
+bool Text3DFreeType::Load(const unsigned char* fontData, unsigned fontDataSize, float pointSize)
 {
 {
     Context* context = font_->GetContext();
     Context* context = font_->GetContext();
 
 
@@ -138,7 +140,7 @@ bool Text3DFreeType::Load(const unsigned char* fontData, unsigned fontDataSize,
     face_ = face;
     face_ = face;
 
 
     unsigned numGlyphs = (unsigned)face->num_glyphs;
     unsigned numGlyphs = (unsigned)face->num_glyphs;
-    ATOMIC_LOGDEBUGF("Font face %s (%dpt) has %d glyphs", GetFileName(font_->GetName()).CString(), pointSize, numGlyphs);
+    ATOMIC_LOGDEBUGF("Font face %s (%fpt) has %d glyphs", GetFileName(font_->GetName()).CString(), pointSize, numGlyphs);
 
 
     PODVector<unsigned> charCodes(numGlyphs + 1, 0);
     PODVector<unsigned> charCodes(numGlyphs + 1, 0);
 
 
@@ -157,20 +159,34 @@ bool Text3DFreeType::Load(const unsigned char* fontData, unsigned fontDataSize,
     }
     }
 
 
     // Load each of the glyphs to see the sizes & store other information
     // Load each of the glyphs to see the sizes & store other information
-    loadMode_ = (int)(forceAutoHint_ ? FT_LOAD_FORCE_AUTOHINT : FT_LOAD_DEFAULT);
-    ascender_ = RoundToPixels(face->size->metrics.ascender);
-    rowHeight_ = RoundToPixels(face->size->metrics.height);
+    loadMode_ = FT_LOAD_DEFAULT;
+    if (forceAutoHint_)
+    {
+        loadMode_ |= FT_LOAD_FORCE_AUTOHINT;
+    }
+    if (GetFontHintLevel() == FONT_HINT_LEVEL_NONE)
+    {
+        loadMode_ |= FT_LOAD_NO_HINTING;
+    }
+    if (GetFontHintLevel() == FONT_HINT_LEVEL_LIGHT)
+    {
+        loadMode_ |= FT_LOAD_TARGET_LIGHT;
+    }
+
+    ascender_ = FixedToFloat(face->size->metrics.ascender);
+    rowHeight_ = FixedToFloat(face->size->metrics.height);
     pointSize_ = pointSize;
     pointSize_ = pointSize;
 
 
     // Check if the font's OS/2 info gives different (larger) values for ascender & descender
     // Check if the font's OS/2 info gives different (larger) values for ascender & descender
     TT_OS2* os2Info = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
     TT_OS2* os2Info = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
     if (os2Info)
     if (os2Info)
     {
     {
-        int descender = RoundToPixels(face->size->metrics.descender);
-        ascender_ = Max(ascender_, os2Info->usWinAscent * face->size->metrics.y_ppem / face->units_per_EM);
-        ascender_ = Max(ascender_, os2Info->sTypoAscender * face->size->metrics.y_ppem / face->units_per_EM);
-        descender = Max(descender, os2Info->usWinDescent * face->size->metrics.y_ppem / face->units_per_EM);
-        descender = Max(descender, os2Info->sTypoDescender * face->size->metrics.y_ppem / face->units_per_EM);
+        float descender = FixedToFloat(face->size->metrics.descender);
+        float unitsPerEm = face->units_per_EM;
+        ascender_ = Max(ascender_, os2Info->usWinAscent * face->size->metrics.y_ppem / unitsPerEm);
+        ascender_ = Max(ascender_, os2Info->sTypoAscender * face->size->metrics.y_ppem / unitsPerEm);
+        descender = Max(descender, os2Info->usWinDescent * face->size->metrics.y_ppem / unitsPerEm);
+        descender = Max(descender, os2Info->sTypoDescender * face->size->metrics.y_ppem / unitsPerEm);
         rowHeight_ = Max(rowHeight_, ascender_ + descender);
         rowHeight_ = Max(rowHeight_, ascender_ + descender);
     }
     }
 
 
@@ -251,7 +267,7 @@ bool Text3DFreeType::Load(const unsigned char* fontData, unsigned fontDataSize,
                     {
                     {
                         unsigned leftIndex = deserializer.ReadUShort();
                         unsigned leftIndex = deserializer.ReadUShort();
                         unsigned rightIndex = deserializer.ReadUShort();
                         unsigned rightIndex = deserializer.ReadUShort();
-                        short amount = RoundToPixels(deserializer.ReadShort());
+                        short amount = FixedToFloat(deserializer.ReadShort());
 
 
                         unsigned leftCharCode = leftIndex < numGlyphs ? charCodes[leftIndex] : 0;
                         unsigned leftCharCode = leftIndex < numGlyphs ? charCodes[leftIndex] : 0;
                         unsigned rightCharCode = rightIndex < numGlyphs ? charCodes[rightIndex] : 0;
                         unsigned rightCharCode = rightIndex < numGlyphs ? charCodes[rightIndex] : 0;
@@ -353,7 +369,19 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
         fontGlyph.height_ = slot->bitmap.rows;
         fontGlyph.height_ = slot->bitmap.rows;
         fontGlyph.offsetX_ = slot->bitmap_left;
         fontGlyph.offsetX_ = slot->bitmap_left;
         fontGlyph.offsetY_ = ascender_ - slot->bitmap_top;
         fontGlyph.offsetY_ = ascender_ - slot->bitmap_top;
-        fontGlyph.advanceX_ = (short)RoundToPixels(slot->metrics.horiAdvance);
+
+        FontHintLevel level = GetFontHintLevel();
+        bool subpixel = GetSubpixelGlyphPositions();
+        if (level <= FONT_HINT_LEVEL_LIGHT && subpixel && slot->linearHoriAdvance)
+        {
+            // linearHoriAdvance is stored in 16.16 fixed point, not the usual 26.6
+            fontGlyph.advanceX_ = slot->linearHoriAdvance / 65536.0;
+        }
+        else
+        {
+            // Round to nearest pixel (only necessary when hinting is disabled)
+            fontGlyph.advanceX_ = floor(FixedToFloat(slot->metrics.horiAdvance) + 0.5f);
+        }
     }
     }
 
 
     int x = 0, y = 0;
     int x = 0, y = 0;
@@ -371,13 +399,11 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
             int h = allocator_.GetHeight();
             int h = allocator_.GetHeight();
             if (!SetupNextTexture(w, h))
             if (!SetupNextTexture(w, h))
             {
             {
-                ATOMIC_LOGWARNINGF("Text3DFreeType::LoadCharGlyph: failed to allocate new %dx%d texture", w, h);
                 return false;
                 return false;
             }
             }
 
 
             if (!allocator_.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y))
             if (!allocator_.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y))
             {
             {
-                ATOMIC_LOGWARNINGF("Text3DFreeType::LoadCharGlyph: failed to position char code %u in blank page", charCode);
                 return false;
                 return false;
             }
             }
         }
         }

+ 28 - 2
Source/Atomic/Graphics/Text3D/Text3DFreeType.h

@@ -30,6 +30,18 @@ namespace Atomic
 class FreeTypeLibrary;
 class FreeTypeLibrary;
 class Texture2D;
 class Texture2D;
 
 
+enum FontHintLevel
+{
+    /// Completely disable font hinting. Output will be blurrier but more "correct".
+    FONT_HINT_LEVEL_NONE = 0,
+
+    /// Light hinting. FreeType will pixel-align fonts vertically, but not horizontally.
+    FONT_HINT_LEVEL_LIGHT,
+
+    /// Full hinting, using either the font's own hinting or FreeType's auto-hinter.
+    FONT_HINT_LEVEL_NORMAL
+};
+
 /// Free type font face description.
 /// Free type font face description.
 class ATOMIC_API Text3DFreeType : public Text3DFontFace
 class ATOMIC_API Text3DFreeType : public Text3DFontFace
 {
 {
@@ -42,7 +54,7 @@ public:
     ~Text3DFreeType();
     ~Text3DFreeType();
 
 
     /// Load font face.
     /// Load font face.
-    virtual bool Load(const unsigned char* fontData, unsigned fontDataSize, int pointSize);
+    virtual bool Load(const unsigned char* fontData, unsigned fontDataSize, float pointSize);
     /// Return pointer to the glyph structure corresponding to a character. Return null if glyph not found.
     /// Return pointer to the glyph structure corresponding to a character. Return null if glyph not found.
     virtual const Text3DFontGlyph* GetGlyph(unsigned c);
     virtual const Text3DFontGlyph* GetGlyph(unsigned c);
 
 
@@ -52,6 +64,18 @@ public:
     /// Set whether to force font autohinting instead of using FreeType's TTF bytecode interpreter.
     /// Set whether to force font autohinting instead of using FreeType's TTF bytecode interpreter.
     void SetForceAutoHint(bool enable) { forceAutoHint_ = enable; }
     void SetForceAutoHint(bool enable) { forceAutoHint_ = enable; }
 
 
+    /// Return the current FreeType font hinting level.
+    FontHintLevel GetFontHintLevel() const { return fontHintLevel_; }
+
+    /// Set the hinting level used by FreeType fonts.
+    void SetFontHintLevel(FontHintLevel level) { fontHintLevel_ = level; }
+
+    // Return whether text glyphs can have fractional positions.
+    bool GetSubpixelGlyphPositions() const { return subpixelGlyphPositions_; }
+
+    /// Set whether text glyphs can have fractional positions. Default is false (pixel-aligned).
+    void SetSubpixelGlyphPositions(bool enable) { subpixelGlyphPositions_ = enable; }
+
 private:
 private:
     /// Setup next texture.
     /// Setup next texture.
     bool SetupNextTexture(int textureWidth, int textureHeight);
     bool SetupNextTexture(int textureWidth, int textureHeight);
@@ -65,13 +89,15 @@ private:
     /// Load mode.
     /// Load mode.
     int loadMode_;
     int loadMode_;
     /// Ascender.
     /// Ascender.
-    int ascender_;
+    float ascender_;
     /// Has mutable glyph.
     /// Has mutable glyph.
     bool hasMutableGlyph_;
     bool hasMutableGlyph_;
     /// Glyph area allocator.
     /// Glyph area allocator.
     AreaAllocator allocator_;
     AreaAllocator allocator_;
 
 
     bool forceAutoHint_;
     bool forceAutoHint_;
+    bool subpixelGlyphPositions_;
+    FontHintLevel fontHintLevel_;
 };
 };
 
 
 }
 }

+ 24 - 24
Source/Atomic/Graphics/Text3D/Text3DText.cpp

@@ -111,7 +111,7 @@ void Text3DText::RegisterObject(Context* context)
     ATOMIC_COPY_BASE_ATTRIBUTES(Animatable);
     ATOMIC_COPY_BASE_ATTRIBUTES(Animatable);
     ATOMIC_UPDATE_ATTRIBUTE_DEFAULT_VALUE("Use Derived Opacity", false);
     ATOMIC_UPDATE_ATTRIBUTE_DEFAULT_VALUE("Use Derived Opacity", false);
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Font", GetFontAttr, SetFontAttr, ResourceRef, ResourceRef(Text3DFont::GetTypeStatic()), AM_FILE);
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Font", GetFontAttr, SetFontAttr, ResourceRef, ResourceRef(Text3DFont::GetTypeStatic()), AM_FILE);
-    ATOMIC_ATTRIBUTE("Font Size", int, fontSize_, TEXT3D_DEFAULT_FONT_SIZE, AM_FILE);
+    ATOMIC_ATTRIBUTE("Font Size", float, fontSize_, TEXT3D_DEFAULT_FONT_SIZE, AM_FILE);
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Text", GetTextAttr, SetTextAttr, String, String::EMPTY, AM_FILE);
     ATOMIC_MIXED_ACCESSOR_ATTRIBUTE("Text", GetTextAttr, SetTextAttr, String, String::EMPTY, AM_FILE);
     ATOMIC_ENUM_ATTRIBUTE("Text Alignment", textAlignment_, horizontalAlignments, HA_LEFT, AM_FILE);
     ATOMIC_ENUM_ATTRIBUTE("Text Alignment", textAlignment_, horizontalAlignments, HA_LEFT, AM_FILE);
     ATOMIC_ATTRIBUTE("Row Spacing", float, rowSpacing_, 1.0f, AM_FILE);
     ATOMIC_ATTRIBUTE("Row Spacing", float, rowSpacing_, 1.0f, AM_FILE);
@@ -233,12 +233,12 @@ void Text3DText::GetBatches(PODVector<Text3DBatch>& batches, PODVector<float>& v
         Text3DBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
         Text3DBatch batch(this, BLEND_ALPHA, currentScissor, 0, &vertexData);
         batch.SetColor(selectionColor_);
         batch.SetColor(selectionColor_);
 
 
-        IntVector2 currentStart = charLocations_[selectionStart_].position_;
-        IntVector2 currentEnd = currentStart;
+        Vector2 currentStart = charLocations_[selectionStart_].position_;
+        Vector2 currentEnd = currentStart;
         for (unsigned i = selectionStart_; i < selectionStart_ + selectionLength_; ++i)
         for (unsigned i = selectionStart_; i < selectionStart_ + selectionLength_; ++i)
         {
         {
             // Check if row changes, and start a new quad in that case
             // Check if row changes, and start a new quad in that case
-            if (charLocations_[i].size_ != IntVector2::ZERO)
+            if (charLocations_[i].size_ != Vector2::ZERO)
             {
             {
                 if (charLocations_[i].position_.y_ != currentStart.y_)
                 if (charLocations_[i].position_.y_ != currentStart.y_)
                 {
                 {
@@ -297,7 +297,7 @@ void Text3DText::GetBatches(PODVector<Text3DBatch>& batches, PODVector<float>& v
                 {
                 {
                     float x = Cos(angle * i) * floatThickness;
                     float x = Cos(angle * i) * floatThickness;
                     float y = Sin(angle * i) * floatThickness;
                     float y = Sin(angle * i) * floatThickness;
-                    ConstructBatch(pageBatch, pageGlyphLocation, (int)x, (int)y, &effectColor_, effectDepthBias_);
+                    ConstructBatch(pageBatch, pageGlyphLocation, x, y, &effectColor_, effectDepthBias_);
                 }
                 }
             }
             }
             else
             else
@@ -341,13 +341,13 @@ void Text3DText::OnIndentSet()
     charLocationsDirty_ = true;
     charLocationsDirty_ = true;
 }
 }
 
 
-bool Text3DText::SetFont(const String& fontName, int size)
+bool Text3DText::SetFont(const String& fontName, float size)
 {
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     return SetFont(cache->GetResource<Text3DFont>(fontName), size);
     return SetFont(cache->GetResource<Text3DFont>(fontName), size);
 }
 }
 
 
-bool Text3DText::SetFont(Text3DFont* font, int size)
+bool Text3DText::SetFont(Text3DFont* font, float size)
 {
 {
     if (!font)
     if (!font)
     {
     {
@@ -365,7 +365,7 @@ bool Text3DText::SetFont(Text3DFont* font, int size)
     return true;
     return true;
 }
 }
 
 
-bool Text3DText::SetFontSize(int size)
+bool Text3DText::SetFontSize(float size)
 {
 {
     // Initial font must be set
     // Initial font must be set
     if (!font_)
     if (!font_)
@@ -512,29 +512,29 @@ void Text3DText::SetEffectDepthBias(float bias)
     effectDepthBias_ = bias;
     effectDepthBias_ = bias;
 }
 }
 
 
-int Text3DText::GetRowWidth(unsigned index) const
+float Text3DText::GetRowWidth(unsigned index) const
 {
 {
     return index < rowWidths_.Size() ? rowWidths_[index] : 0;
     return index < rowWidths_.Size() ? rowWidths_[index] : 0;
 }
 }
 
 
-IntVector2 Text3DText::GetCharPosition(unsigned index)
+Vector2 Text3DText::GetCharPosition(unsigned index)
 {
 {
     if (charLocationsDirty_)
     if (charLocationsDirty_)
         UpdateCharLocations();
         UpdateCharLocations();
     if (charLocations_.Empty())
     if (charLocations_.Empty())
-        return IntVector2::ZERO;
+        return Vector2::ZERO;
     // For convenience, return the position of the text ending if index exceeded
     // For convenience, return the position of the text ending if index exceeded
     if (index > charLocations_.Size() - 1)
     if (index > charLocations_.Size() - 1)
         index = charLocations_.Size() - 1;
         index = charLocations_.Size() - 1;
     return charLocations_[index].position_;
     return charLocations_[index].position_;
 }
 }
 
 
-IntVector2 Text3DText::GetCharSize(unsigned index)
+Vector2 Text3DText::GetCharSize(unsigned index)
 {
 {
     if (charLocationsDirty_)
     if (charLocationsDirty_)
         UpdateCharLocations();
         UpdateCharLocations();
     if (charLocations_.Size() < 2)
     if (charLocations_.Size() < 2)
-        return IntVector2::ZERO;
+        return Vector2::ZERO;
     // For convenience, return the size of the last char if index exceeded (last size entry is zero)
     // For convenience, return the size of the last char if index exceeded (last size entry is zero)
     if (index > charLocations_.Size() - 2)
     if (index > charLocations_.Size() - 2)
         index = charLocations_.Size() - 2;
         index = charLocations_.Size() - 2;
@@ -583,7 +583,7 @@ void Text3DText::UpdateText(bool onResize)
         int width = 0;
         int width = 0;
         int height = 0;
         int height = 0;
         int rowWidth = 0;
         int rowWidth = 0;
-        int rowHeight = (int)(rowSpacing_ * rowHeight_);
+        int rowHeight = (int)(rowSpacing_ * rowHeight_ + 0.5f);
 
 
         // First see if the text must be split up
         // First see if the text must be split up
         if (!wordWrap_)
         if (!wordWrap_)
@@ -754,7 +754,7 @@ void Text3DText::UpdateCharLocations()
         return;
         return;
     fontFace_ = face;
     fontFace_ = face;
 
 
-    int rowHeight = (int)(rowSpacing_ * rowHeight_);
+    int rowHeight = (int)(rowSpacing_ * rowHeight_ + 0.5f);
 
 
     // Store position & size of each character, and locations per texture page
     // Store position & size of each character, and locations per texture page
     unsigned numChars = unicodeText_.Size();
     unsigned numChars = unicodeText_.Size();
@@ -767,19 +767,19 @@ void Text3DText::UpdateCharLocations()
 
 
     unsigned rowIndex = 0;
     unsigned rowIndex = 0;
     unsigned lastFilled = 0;
     unsigned lastFilled = 0;
-    int x = GetRowStartPosition(rowIndex) + offset.x_;
-    int y = offset.y_;
+    float x = floor(GetRowStartPosition(rowIndex) + offset.x_ + 0.5f);
+    float y = floor(offset.y_ + 0.5f);
 
 
     for (unsigned i = 0; i < printText_.Size(); ++i)
     for (unsigned i = 0; i < printText_.Size(); ++i)
     {
     {
         Text3DCharLocation loc;
         Text3DCharLocation loc;
-        loc.position_ = IntVector2(x, y);
+        loc.position_ = Vector2(x, y);
 
 
         unsigned c = printText_[i];
         unsigned c = printText_[i];
         if (c != '\n')
         if (c != '\n')
         {
         {
             const Text3DFontGlyph* glyph = face->GetGlyph(c);
             const Text3DFontGlyph* glyph = face->GetGlyph(c);
-            loc.size_ = IntVector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
+            loc.size_ = Vector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
             if (glyph)
             if (glyph)
             {
             {
                 // Store glyph's location for rendering. Verify that glyph page is valid
                 // Store glyph's location for rendering. Verify that glyph page is valid
@@ -792,7 +792,7 @@ void Text3DText::UpdateCharLocations()
         }
         }
         else
         else
         {
         {
-            loc.size_ = IntVector2::ZERO;
+            loc.size_ = Vector2::ZERO;
             x = GetRowStartPosition(++rowIndex);
             x = GetRowStartPosition(++rowIndex);
             y += rowHeight;
             y += rowHeight;
         }
         }
@@ -806,8 +806,8 @@ void Text3DText::UpdateCharLocations()
         lastFilled = printToText_[i] + 1;
         lastFilled = printToText_[i] + 1;
     }
     }
     // Store the ending position
     // Store the ending position
-    charLocations_[numChars].position_ = IntVector2(x, y);
-    charLocations_[numChars].size_ = IntVector2::ZERO;
+    charLocations_[numChars].position_ = Vector2(x, y);
+    charLocations_[numChars].size_ = Vector2::ZERO;
 
 
     charLocationsDirty_ = false;
     charLocationsDirty_ = false;
 }
 }
@@ -832,7 +832,7 @@ void Text3DText::ValidateSelection()
 
 
 int Text3DText::GetRowStartPosition(unsigned rowIndex) const
 int Text3DText::GetRowStartPosition(unsigned rowIndex) const
 {
 {
-    int rowWidth = 0;
+    float rowWidth = 0;
 
 
     if (rowIndex < rowWidths_.Size())
     if (rowIndex < rowWidths_.Size())
         rowWidth = rowWidths_[rowIndex];
         rowWidth = rowWidths_[rowIndex];
@@ -866,7 +866,7 @@ void Text3DText::SetIndentSpacing(int indentSpacing)
     OnIndentSet();
     OnIndentSet();
 }
 }
 
 
-void Text3DText::ConstructBatch(Text3DBatch& pageBatch, const PODVector<Text3DGlyphLocation>& pageGlyphLocation, int dx, int dy, Color* color,
+void Text3DText::ConstructBatch(Text3DBatch& pageBatch, const PODVector<Text3DGlyphLocation>& pageGlyphLocation, float dx, float dy, Color* color,
     float depthBias)
     float depthBias)
 {
 {
     unsigned startDataSize = pageBatch.vertexData_->Size();
     unsigned startDataSize = pageBatch.vertexData_->Size();

+ 18 - 18
Source/Atomic/Graphics/Text3D/Text3DText.h

@@ -28,7 +28,7 @@
 namespace Atomic
 namespace Atomic
 {
 {
 
 
-static const int TEXT3D_DEFAULT_FONT_SIZE = 12;
+static const float TEXT3D_DEFAULT_FONT_SIZE = 12;
 
 
 class Text3DFont;
 class Text3DFont;
 class Text3DFontFace;
 class Text3DFontFace;
@@ -46,16 +46,16 @@ enum Text3DTextEffect
 struct Text3DCharLocation
 struct Text3DCharLocation
 {
 {
     /// Position.
     /// Position.
-    IntVector2 position_;
+    Vector2 position_;
     /// Size.
     /// Size.
-    IntVector2 size_;
+    Vector2 size_;
 };
 };
 
 
 /// Glyph and its location within the text. Used when preparing text rendering.
 /// Glyph and its location within the text. Used when preparing text rendering.
 struct Text3DGlyphLocation
 struct Text3DGlyphLocation
 {
 {
     /// Construct.
     /// Construct.
-    Text3DGlyphLocation(int x, int y, const Text3DFontGlyph* glyph) :
+    Text3DGlyphLocation(float x, float y, const Text3DFontGlyph* glyph) :
         x_(x),
         x_(x),
         y_(y),
         y_(y),
         glyph_(glyph)
         glyph_(glyph)
@@ -63,9 +63,9 @@ struct Text3DGlyphLocation
     }
     }
 
 
     /// X coordinate.
     /// X coordinate.
-    int x_;
+    float x_;
     /// Y coordinate.
     /// Y coordinate.
-    int y_;
+    float y_;
     /// Glyph.
     /// Glyph.
     const Text3DFontGlyph* glyph_;
     const Text3DFontGlyph* glyph_;
 };
 };
@@ -121,11 +121,11 @@ public:
     virtual void OnIndentSet();
     virtual void OnIndentSet();
 
 
     /// Set font by looking from resource cache by name and font size. Return true if successful.
     /// Set font by looking from resource cache by name and font size. Return true if successful.
-    bool SetFont(const String& fontName, int size = TEXT3D_DEFAULT_FONT_SIZE);
+    bool SetFont(const String& fontName, float size = TEXT3D_DEFAULT_FONT_SIZE);
     /// Set font and font size. Return true if successful.
     /// Set font and font size. Return true if successful.
-    bool SetFont(Text3DFont* font, int size = TEXT3D_DEFAULT_FONT_SIZE);
+    bool SetFont(Text3DFont* font, float size = TEXT3D_DEFAULT_FONT_SIZE);
     /// Set font size only while retaining the existing font. Return true if successful.
     /// Set font size only while retaining the existing font. Return true if successful.
-    bool SetFontSize(int size);
+    bool SetFontSize(float size);
     /// Set text. Text is assumed to be either ASCII or UTF8-encoded.
     /// Set text. Text is assumed to be either ASCII or UTF8-encoded.
     void SetText(const String& text);
     void SetText(const String& text);
     /// Set row alignment.
     /// Set row alignment.
@@ -159,7 +159,7 @@ public:
     Text3DFont* GetFont() const { return font_; }
     Text3DFont* GetFont() const { return font_; }
 
 
     /// Return font size.
     /// Return font size.
-    int GetFontSize() const { return fontSize_; }
+    float GetFontSize() const { return fontSize_; }
 
 
     /// Return text.
     /// Return text.
     const String& GetText() const { return text_; }
     const String& GetText() const { return text_; }
@@ -204,7 +204,7 @@ public:
     const Color& GetEffectColor() const { return effectColor_; }
     const Color& GetEffectColor() const { return effectColor_; }
 
 
     /// Return row height.
     /// Return row height.
-    int GetRowHeight() const { return rowHeight_; }
+    float GetRowHeight() const { return rowHeight_; }
 
 
     /// Return number of rows.
     /// Return number of rows.
     unsigned GetNumRows() const { return rowWidths_.Size(); }
     unsigned GetNumRows() const { return rowWidths_.Size(); }
@@ -213,11 +213,11 @@ public:
     unsigned GetNumChars() const { return unicodeText_.Size(); }
     unsigned GetNumChars() const { return unicodeText_.Size(); }
 
 
     /// Return width of row by index.
     /// Return width of row by index.
-    int GetRowWidth(unsigned index) const;
+    float GetRowWidth(unsigned index) const;
     /// Return position of character by index relative to the text element origin.
     /// Return position of character by index relative to the text element origin.
-    IntVector2 GetCharPosition(unsigned index);
+    Vector2 GetCharPosition(unsigned index);
     /// Return size of character by index.
     /// Return size of character by index.
-    IntVector2 GetCharSize(unsigned index);
+    Vector2 GetCharSize(unsigned index);
 
 
     /// Set text effect Z bias. Zero by default, adjusted only in 3D mode.
     /// Set text effect Z bias. Zero by default, adjusted only in 3D mode.
     void SetEffectDepthBias(float bias);
     void SetEffectDepthBias(float bias);
@@ -370,7 +370,7 @@ protected:
     int GetRowStartPosition(unsigned rowIndex) const;
     int GetRowStartPosition(unsigned rowIndex) const;
     /// Contruct batch.
     /// Contruct batch.
     void ConstructBatch
     void ConstructBatch
-        (Text3DBatch& pageBatch, const PODVector<Text3DGlyphLocation>& pageGlyphLocation, int dx = 0, int dy = 0, Color* color = 0,
+        (Text3DBatch& pageBatch, const PODVector<Text3DGlyphLocation>& pageGlyphLocation, float dx = 0, float dy = 0, Color* color = 0,
             float depthBias = 0.0f);
             float depthBias = 0.0f);
 
 
     /// Font.
     /// Font.
@@ -378,7 +378,7 @@ protected:
     /// Current face.
     /// Current face.
     WeakPtr<Text3DFontFace> fontFace_;
     WeakPtr<Text3DFontFace> fontFace_;
     /// Font size.
     /// Font size.
-    int fontSize_;
+    float fontSize_;
     /// UTF-8 encoded text.
     /// UTF-8 encoded text.
     String text_;
     String text_;
     /// Row alignment.
     /// Row alignment.
@@ -410,7 +410,7 @@ protected:
     /// Text effect Z bias.
     /// Text effect Z bias.
     float effectDepthBias_;
     float effectDepthBias_;
     /// Row height.
     /// Row height.
-    int rowHeight_;
+    float rowHeight_;
     /// Text as Unicode characters.
     /// Text as Unicode characters.
     PODVector<unsigned> unicodeText_;
     PODVector<unsigned> unicodeText_;
     /// Text modified into printed form.
     /// Text modified into printed form.
@@ -418,7 +418,7 @@ protected:
     /// Mapping of printed form back to original char indices.
     /// Mapping of printed form back to original char indices.
     PODVector<unsigned> printToText_;
     PODVector<unsigned> printToText_;
     /// Row widths.
     /// Row widths.
-    PODVector<int> rowWidths_;
+    PODVector<float> rowWidths_;
     /// Glyph locations per each texture in the font.
     /// Glyph locations per each texture in the font.
     Vector<PODVector<Text3DGlyphLocation> > pageGlyphLocations_;
     Vector<PODVector<Text3DGlyphLocation> > pageGlyphLocations_;
     /// Cached locations of each character in the text.
     /// Cached locations of each character in the text.

+ 3 - 5
Source/Atomic/Graphics/Texture.cpp

@@ -56,7 +56,7 @@ static const char* filterModeNames[] =
 };
 };
 
 
 Texture::Texture(Context* context) :
 Texture::Texture(Context* context) :
-    Resource(context),
+    ResourceWithMetadata(context),
     GPUObject(GetSubsystem<Graphics>()),
     GPUObject(GetSubsystem<Graphics>()),
     shaderResourceView_(0),
     shaderResourceView_(0),
     sampler_(0),
     sampler_(0),
@@ -204,8 +204,8 @@ void Texture::SetParameters(XMLFile* file)
 
 
 void Texture::SetParameters(const XMLElement& element)
 void Texture::SetParameters(const XMLElement& element)
 {
 {
-    XMLElement paramElem = element.GetChild();
-    while (paramElem)
+    LoadMetadataFromXML(element);
+    for (XMLElement paramElem = element.GetChild(); paramElem; paramElem = paramElem.GetNext())
     {
     {
         String name = paramElem.GetName();
         String name = paramElem.GetName();
 
 
@@ -248,8 +248,6 @@ void Texture::SetParameters(const XMLElement& element)
 
 
         if (name == "srgb")
         if (name == "srgb")
             SetSRGB(paramElem.GetBool("enable"));
             SetSRGB(paramElem.GetBool("enable"));
-
-        paramElem = paramElem.GetNext();
     }
     }
 }
 }
 
 

+ 3 - 3
Source/Atomic/Graphics/Texture.h

@@ -36,9 +36,9 @@ class XMLElement;
 class XMLFile;
 class XMLFile;
 
 
 /// Base class for texture resources.
 /// Base class for texture resources.
-class ATOMIC_API Texture : public Resource, public GPUObject
+class ATOMIC_API Texture : public ResourceWithMetadata, public GPUObject
 {
 {
-    ATOMIC_OBJECT(Texture, Resource)
+    ATOMIC_OBJECT(Texture, ResourceWithMetadata)
 
 
 public:
 public:
     /// Construct.
     /// Construct.
@@ -116,7 +116,7 @@ public:
 
 
     /// Return whether rendertarget mipmap levels need regenration.
     /// Return whether rendertarget mipmap levels need regenration.
     bool GetLevelsDirty() const { return levelsDirty_; }
     bool GetLevelsDirty() const { return levelsDirty_; }
-    
+
     /// Return backup texture.
     /// Return backup texture.
     Texture* GetBackupTexture() const { return backupTexture_; }
     Texture* GetBackupTexture() const { return backupTexture_; }
 
 

+ 12 - 1
Source/Atomic/Graphics/VertexBuffer.cpp

@@ -202,4 +202,15 @@ unsigned VertexBuffer::GetVertexSize(unsigned elementMask)
     return size;
     return size;
 }
 }
 
 
-}
+void VertexBuffer::UpdateOffsets(PODVector<VertexElement>& elements)
+{
+    unsigned elementOffset = 0;
+
+    for (PODVector<VertexElement>::Iterator i = elements.Begin(); i != elements.End(); ++i)
+    {
+        i->offset_ = elementOffset;
+        elementOffset += ELEMENT_TYPESIZES[i->type_];
+    }
+}
+
+}

+ 3 - 0
Source/Atomic/Graphics/VertexBuffer.h

@@ -129,6 +129,9 @@ public:
     /// Return vertex size for a legacy vertex element bitmask.
     /// Return vertex size for a legacy vertex element bitmask.
     static unsigned GetVertexSize(unsigned elementMask);
     static unsigned GetVertexSize(unsigned elementMask);
 
 
+    /// Update offsets of vertex elements.
+    static void UpdateOffsets(PODVector<VertexElement>& elements);
+
 private:
 private:
     /// Update offsets of vertex elements.
     /// Update offsets of vertex elements.
     void UpdateOffsets();
     void UpdateOffsets();

+ 8 - 2
Source/Atomic/Graphics/View.cpp

@@ -655,12 +655,16 @@ void View::Render()
             {
             {
                 BlitFramebuffer(currentRenderTarget_->GetParentTexture(), renderTarget_, false);
                 BlitFramebuffer(currentRenderTarget_->GetParentTexture(), renderTarget_, false);
                 currentRenderTarget_ = renderTarget_;
                 currentRenderTarget_ = renderTarget_;
+                lastCustomDepthSurface_ = 0;
             }
             }
 
 
             graphics_->SetRenderTarget(0, currentRenderTarget_);
             graphics_->SetRenderTarget(0, currentRenderTarget_);
             for (unsigned i = 1; i < MAX_RENDERTARGETS; ++i)
             for (unsigned i = 1; i < MAX_RENDERTARGETS; ++i)
                 graphics_->SetRenderTarget(i, (RenderSurface*)0);
                 graphics_->SetRenderTarget(i, (RenderSurface*)0);
-            graphics_->SetDepthStencil(GetDepthStencil(currentRenderTarget_));
+
+            // If a custom depth surface was used, use it also for debug rendering
+            graphics_->SetDepthStencil(lastCustomDepthSurface_ ? lastCustomDepthSurface_ : GetDepthStencil(currentRenderTarget_));
+
             IntVector2 rtSizeNow = graphics_->GetRenderTargetDimensions();
             IntVector2 rtSizeNow = graphics_->GetRenderTargetDimensions();
             IntRect viewport = (currentRenderTarget_ == renderTarget_) ? viewRect_ : IntRect(0, 0, rtSizeNow.x_,
             IntRect viewport = (currentRenderTarget_ == renderTarget_) ? viewRect_ : IntRect(0, 0, rtSizeNow.x_,
                 rtSizeNow.y_);
                 rtSizeNow.y_);
@@ -1799,7 +1803,8 @@ void View::SetRenderTargets(RenderPathCommand& command)
         if (depthTexture)
         if (depthTexture)
         {
         {
             useCustomDepth = true;
             useCustomDepth = true;
-            graphics_->SetDepthStencil(GetRenderSurfaceFromTexture(depthTexture));
+            lastCustomDepthSurface_ = GetRenderSurfaceFromTexture(depthTexture);
+            graphics_->SetDepthStencil(lastCustomDepthSurface_);
         }
         }
     }
     }
 
 
@@ -1978,6 +1983,7 @@ void View::AllocateScreenBuffers()
     bool needSubstitute = false;
     bool needSubstitute = false;
     unsigned numViewportTextures = 0;
     unsigned numViewportTextures = 0;
     depthOnlyDummyTexture_ = 0;
     depthOnlyDummyTexture_ = 0;
+    lastCustomDepthSurface_ = 0;
 
 
     // Check for commands with special meaning: has custom depth, renders a scene pass to other than the destination viewport,
     // Check for commands with special meaning: has custom depth, renders a scene pass to other than the destination viewport,
     // read the viewport, or pingpong between viewport textures. These may trigger the need to substitute the destination RT
     // read the viewport, or pingpong between viewport textures. These may trigger the need to substitute the destination RT

+ 2 - 0
Source/Atomic/Graphics/View.h

@@ -336,6 +336,8 @@ private:
     Texture* viewportTextures_[MAX_VIEWPORT_TEXTURES];
     Texture* viewportTextures_[MAX_VIEWPORT_TEXTURES];
     /// Color rendertarget active for the current renderpath command.
     /// Color rendertarget active for the current renderpath command.
     RenderSurface* currentRenderTarget_;
     RenderSurface* currentRenderTarget_;
+    /// Last used custom depth render surface.
+    RenderSurface* lastCustomDepthSurface_;
     /// Texture containing the latest viewport texture.
     /// Texture containing the latest viewport texture.
     Texture* currentViewportTexture_;
     Texture* currentViewportTexture_;
     /// Dummy texture for D3D9 depth only rendering.
     /// Dummy texture for D3D9 depth only rendering.

+ 2 - 0
Source/Atomic/IK/IKSolver.cpp

@@ -54,6 +54,8 @@ static bool ChildrenHaveEffector(const Node* node)
         if (ChildrenHaveEffector(it->Get()))
         if (ChildrenHaveEffector(it->Get()))
             return true;
             return true;
     }
     }
+
+    return false;
 }
 }
 
 
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------

+ 2 - 2
Source/Atomic/IO/Compression.cpp

@@ -44,7 +44,7 @@ unsigned CompressData(void* dest, const void* src, unsigned srcSize)
     if (!dest || !src || !srcSize)
     if (!dest || !src || !srcSize)
         return 0;
         return 0;
     else
     else
-        return (unsigned)LZ4_compressHC((const char*)src, (char*)dest, srcSize);
+        return (unsigned)LZ4_compress_HC((const char*)src, (char*)dest, srcSize, LZ4_compressBound(srcSize), 0);
 }
 }
 
 
 unsigned DecompressData(void* dest, const void* src, unsigned destSize)
 unsigned DecompressData(void* dest, const void* src, unsigned destSize)
@@ -73,7 +73,7 @@ bool CompressStream(Serializer& dest, Deserializer& src)
     if (src.Read(srcBuffer, srcSize) != srcSize)
     if (src.Read(srcBuffer, srcSize) != srcSize)
         return false;
         return false;
 
 
-    unsigned destSize = (unsigned)LZ4_compressHC((const char*)srcBuffer.Get(), (char*)destBuffer.Get(), srcSize);
+    unsigned destSize = (unsigned)LZ4_compress_HC((const char*)srcBuffer.Get(), (char*)destBuffer.Get(), srcSize, LZ4_compressBound(srcSize), 0);
     bool success = true;
     bool success = true;
     success &= dest.WriteUInt(srcSize);
     success &= dest.WriteUInt(srcSize);
     success &= dest.WriteUInt(destSize);
     success &= dest.WriteUInt(destSize);

+ 11 - 3
Source/Atomic/IO/FileSystem.cpp

@@ -76,7 +76,7 @@ extern "C"
 const char* SDL_Android_GetFilesDir();
 const char* SDL_Android_GetFilesDir();
 char** SDL_Android_GetFileList(const char* path, int* count);
 char** SDL_Android_GetFileList(const char* path, int* count);
 void SDL_Android_FreeFileList(char*** array, int* count);
 void SDL_Android_FreeFileList(char*** array, int* count);
-#elif IOS
+#elif defined(IOS) || defined(TVOS)
 const char* SDL_IOS_GetResourceDir();
 const char* SDL_IOS_GetResourceDir();
 const char* SDL_IOS_GetDocumentsDir();
 const char* SDL_IOS_GetDocumentsDir();
 #endif
 #endif
@@ -89,6 +89,9 @@ namespace Atomic
 
 
 int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* context)
 int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* context)
 {
 {
+#ifdef TVOS
+    return -1;
+#else
 #if !defined(__EMSCRIPTEN__) && !defined(MINI_URHO)
 #if !defined(__EMSCRIPTEN__) && !defined(MINI_URHO)
     if (!redirectToLog)
     if (!redirectToLog)
 #endif
 #endif
@@ -139,10 +142,14 @@ int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* cont
 
 
     return exitCode;
     return exitCode;
 #endif
 #endif
+#endif
 }
 }
 
 
 int DoSystemRun(const String& fileName, const Vector<String>& arguments)
 int DoSystemRun(const String& fileName, const Vector<String>& arguments)
 {
 {
+#ifdef TVOS
+    return -1;
+#else
     String fixedFileName = GetNativePath(fileName);
     String fixedFileName = GetNativePath(fileName);
 
 
 #ifdef _WIN32
 #ifdef _WIN32
@@ -193,6 +200,7 @@ int DoSystemRun(const String& fileName, const Vector<String>& arguments)
     else
     else
         return -1;
         return -1;
 #endif
 #endif
+#endif
 }
 }
 
 
 /// Base class for async execution requests.
 /// Base class for async execution requests.
@@ -700,7 +708,7 @@ String FileSystem::GetProgramDir() const
     // This is an internal directory specifier pointing to the assets in the .apk
     // This is an internal directory specifier pointing to the assets in the .apk
     // Files from this directory will be opened using special handling
     // Files from this directory will be opened using special handling
     return APK;
     return APK;
-#elif defined(IOS)
+#elif defined(IOS) || defined(TVOS)
     return AddTrailingSlash(SDL_IOS_GetResourceDir());
     return AddTrailingSlash(SDL_IOS_GetResourceDir());
 #elif defined(_WIN32)
 #elif defined(_WIN32)
     wchar_t exeName[MAX_PATH];
     wchar_t exeName[MAX_PATH];
@@ -730,7 +738,7 @@ String FileSystem::GetUserDocumentsDir() const
 {
 {
 #if defined(__ANDROID__)
 #if defined(__ANDROID__)
     return AddTrailingSlash(SDL_Android_GetFilesDir());
     return AddTrailingSlash(SDL_Android_GetFilesDir());
-#elif defined(IOS)
+#elif defined(IOS) || defined(TVOS)
     return AddTrailingSlash(SDL_IOS_GetDocumentsDir());
     return AddTrailingSlash(SDL_IOS_GetDocumentsDir());
 #elif defined(_WIN32)
 #elif defined(_WIN32)
     wchar_t pathName[MAX_PATH];
     wchar_t pathName[MAX_PATH];

+ 6 - 6
Source/Atomic/IO/FileWatcher.cpp

@@ -36,7 +36,7 @@ extern "C"
 // Need read/close for inotify
 // Need read/close for inotify
 #include "unistd.h"
 #include "unistd.h"
 }
 }
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
 extern "C"
 extern "C"
 {
 {
 #include "../IO/MacFileWatcher.h"
 #include "../IO/MacFileWatcher.h"
@@ -58,7 +58,7 @@ FileWatcher::FileWatcher(Context* context) :
 #ifdef ATOMIC_FILEWATCHER
 #ifdef ATOMIC_FILEWATCHER
 #ifdef __linux__
 #ifdef __linux__
     watchHandle_ = inotify_init();
     watchHandle_ = inotify_init();
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
     supported_ = IsFileWatcherSupported();
     supported_ = IsFileWatcherSupported();
 #endif
 #endif
 #endif
 #endif
@@ -176,7 +176,7 @@ bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs)
         ATOMIC_LOGDEBUG("Started watching path " + pathName);
         ATOMIC_LOGDEBUG("Started watching path " + pathName);
         return true;
         return true;
     }
     }
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
     if (!supported_)
     if (!supported_)
     {
     {
         ATOMIC_LOGERROR("Individual file watching not supported by this OS version, can not start watching path " + pathName);
         ATOMIC_LOGERROR("Individual file watching not supported by this OS version, can not start watching path " + pathName);
@@ -225,7 +225,7 @@ void FileWatcher::StopWatching()
             fileSystem_->Delete(dummyFileName);
             fileSystem_->Delete(dummyFileName);
 #endif
 #endif
 
 
-#if defined(__APPLE__) && !defined(IOS)
+#if defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
         // Our implementation of file watcher requires the thread to be stopped first before closing the watcher
         // Our implementation of file watcher requires the thread to be stopped first before closing the watcher
         Stop();
         Stop();
 #endif
 #endif
@@ -236,7 +236,7 @@ void FileWatcher::StopWatching()
         for (HashMap<int, String>::Iterator i = dirHandle_.Begin(); i != dirHandle_.End(); ++i)
         for (HashMap<int, String>::Iterator i = dirHandle_.Begin(); i != dirHandle_.End(); ++i)
             inotify_rm_watch(watchHandle_, i->first_);
             inotify_rm_watch(watchHandle_, i->first_);
         dirHandle_.Clear();
         dirHandle_.Clear();
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
         CloseFileWatcher(watcher_);
         CloseFileWatcher(watcher_);
 #endif
 #endif
 
 
@@ -326,7 +326,7 @@ void FileWatcher::ThreadFunction()
             i += sizeof(inotify_event) + event->len;
             i += sizeof(inotify_event) + event->len;
         }
         }
     }
     }
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
     while (shouldRun_)
     while (shouldRun_)
     {
     {
         Time::Sleep(100);
         Time::Sleep(100);

+ 1 - 1
Source/Atomic/IO/FileWatcher.h

@@ -90,7 +90,7 @@ private:
     /// Linux inotify needs a handle.
     /// Linux inotify needs a handle.
     int watchHandle_;
     int watchHandle_;
 
 
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
     
     
     /// Flag indicating whether the running OS supports individual file watching.
     /// Flag indicating whether the running OS supports individual file watching.
     bool supported_;
     bool supported_;

+ 5 - 5
Source/Atomic/IO/Log.cpp

@@ -36,7 +36,7 @@
 #ifdef __ANDROID__
 #ifdef __ANDROID__
 #include <android/log.h>
 #include <android/log.h>
 #endif
 #endif
-#ifdef IOS
+#if defined(IOS) || defined(TVOS)
 extern "C" void SDL_IOS_LogMessage(const char* message);
 extern "C" void SDL_IOS_LogMessage(const char* message);
 #endif
 #endif
 
 
@@ -80,7 +80,7 @@ Log::~Log()
 
 
 void Log::Open(const String& fileName)
 void Log::Open(const String& fileName)
 {
 {
-#if !defined(__ANDROID__) && !defined(IOS)
+#if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
     if (fileName.Empty())
     if (fileName.Empty())
         return;
         return;
     if (logFile_ && logFile_->IsOpen())
     if (logFile_ && logFile_->IsOpen())
@@ -104,7 +104,7 @@ void Log::Open(const String& fileName)
 
 
 void Log::Close()
 void Log::Close()
 {
 {
-#if !defined(__ANDROID__) && !defined(IOS)
+#if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
     if (logFile_ && logFile_->IsOpen())
     if (logFile_ && logFile_->IsOpen())
     {
     {
         logFile_->Close();
         logFile_->Close();
@@ -173,7 +173,7 @@ void Log::Write(int level, const String& message)
 #if defined(__ANDROID__)
 #if defined(__ANDROID__)
     int androidLevel = ANDROID_LOG_DEBUG + level;
     int androidLevel = ANDROID_LOG_DEBUG + level;
     __android_log_print(androidLevel, "Atomic", "%s", message.CString());
     __android_log_print(androidLevel, "Atomic", "%s", message.CString());
-#elif defined(IOS)
+#elif defined(IOS) || defined(TVOS)
     SDL_IOS_LogMessage(message.CString());
     SDL_IOS_LogMessage(message.CString());
 #else
 #else
     if (logInstance->quiet_)
     if (logInstance->quiet_)
@@ -232,7 +232,7 @@ void Log::WriteRaw(const String& message, bool error)
     }
     }
     else
     else
         __android_log_print(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO, "Atomic", "%s", message.CString());
         __android_log_print(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO, "Atomic", "%s", message.CString());
-#elif defined(IOS)
+#elif defined(IOS) || defined(TVOS)
     SDL_IOS_LogMessage(message.CString());
     SDL_IOS_LogMessage(message.CString());
 #else
 #else
     if (logInstance->quiet_)
     if (logInstance->quiet_)

+ 4 - 4
Source/Atomic/Input/Input.cpp

@@ -53,8 +53,7 @@
 extern "C" int SDL_AddTouch(SDL_TouchID touchID, const char* name);
 extern "C" int SDL_AddTouch(SDL_TouchID touchID, const char* name);
 
 
 // Use a "click inside window to focus" mechanism on desktop platforms when the mouse cursor is hidden
 // Use a "click inside window to focus" mechanism on desktop platforms when the mouse cursor is hidden
-// TODO: For now, in this particular case only, treat all the ARM on Linux as "desktop" (e.g. RPI, odroid, etc), revisit this again when we support "mobile" ARM on Linux
-#if defined(_WIN32) || (defined(__APPLE__) && !defined(IOS)) || (defined(__linux__) && !defined(__ANDROID__))
+#if defined(_WIN32) || (defined(__APPLE__) && !defined(IOS) && !defined(TVOS)) || (defined(__linux__) && !defined(__ANDROID__))
 #define REQUIRE_CLICK_TO_FOCUS
 #define REQUIRE_CLICK_TO_FOCUS
 #endif
 #endif
 
 
@@ -413,6 +412,7 @@ Input::Input(Context* context) :
     mouseButtonPress_(0),
     mouseButtonPress_(0),
     lastVisibleMousePosition_(MOUSE_POSITION_OFFSCREEN),
     lastVisibleMousePosition_(MOUSE_POSITION_OFFSCREEN),
     mouseMoveWheel_(0),
     mouseMoveWheel_(0),
+    inputScale_(Vector2::ONE),
     windowID_(0),
     windowID_(0),
     toggleFullscreen_(true),
     toggleFullscreen_(true),
 // ATOMIC BEGIN
 // ATOMIC BEGIN
@@ -2522,8 +2522,8 @@ void Input::HandleSDLEvent(void* sdlEvent)
 
 
             case SDL_WINDOWEVENT_MAXIMIZED:
             case SDL_WINDOWEVENT_MAXIMIZED:
             case SDL_WINDOWEVENT_RESTORED:
             case SDL_WINDOWEVENT_RESTORED:
-#if defined(IOS) || defined (__ANDROID__)
-                // On iOS we never lose the GL context, but may have done GPU object changes that could not be applied yet. Apply them now
+#if defined(IOS) || defined(TVOS) || defined (__ANDROID__)
+                // On iOS/tvOS we never lose the GL context, but may have done GPU object changes that could not be applied yet. Apply them now
                 // On Android the old GL context may be lost already, restore GPU objects to the new GL context
                 // On Android the old GL context may be lost already, restore GPU objects to the new GL context
                 graphics_->Restore();
                 graphics_->Restore();
 #endif
 #endif

+ 2 - 0
Source/Atomic/Input/Input.h

@@ -288,6 +288,8 @@ public:
     int GetMouseMoveY() const;
     int GetMouseMoveY() const;
     /// Return mouse wheel movement since last frame.
     /// Return mouse wheel movement since last frame.
     int GetMouseMoveWheel() const { return mouseMoveWheel_; }
     int GetMouseMoveWheel() const { return mouseMoveWheel_; }
+    /// Return input coordinate scaling. Should return non-unity on High DPI display.
+    Vector2 GetInputScale() const { return inputScale_; }
 
 
     /// Return number of active finger touches.
     /// Return number of active finger touches.
     unsigned GetNumTouches() const { return touches_.Size(); }
     unsigned GetNumTouches() const { return touches_.Size(); }

+ 9 - 2
Source/Atomic/Math/MathDefs.h

@@ -91,6 +91,13 @@ inline T Abs(T value) { return value >= 0.0 ? value : -value; }
 template <class T>
 template <class T>
 inline T Sign(T value) { return value > 0.0 ? 1.0 : (value < 0.0 ? -1.0 : 0.0); }
 inline T Sign(T value) { return value > 0.0 ? 1.0 : (value < 0.0 ? -1.0 : 0.0); }
 
 
+/// Return a representation of the specified floating-point value as a single format bit layout.
+inline unsigned FloatToRawIntBits(float value)
+{
+    unsigned u = *((unsigned*)&value);
+    return u;
+}
+
 /// Check whether a floating point value is NaN.
 /// Check whether a floating point value is NaN.
 /// Use a workaround for GCC, see https://github.com/urho3d/Urho3D/issues/655
 /// Use a workaround for GCC, see https://github.com/urho3d/Urho3D/issues/655
 #ifndef __GNUC__
 #ifndef __GNUC__
@@ -99,7 +106,7 @@ inline bool IsNaN(float value) { return value != value; }
 
 
 inline bool IsNaN(float value)
 inline bool IsNaN(float value)
 {
 {
-    unsigned u = *(unsigned*)(&value);
+    unsigned u = FloatToRawIntBits(value);
     return (u & 0x7fffffff) > 0x7f800000;
     return (u & 0x7fffffff) > 0x7f800000;
 }
 }
 
 
@@ -232,7 +239,7 @@ inline float RandomNormal(float meanValue, float variance) { return RandStandard
 /// Convert float to half float. From https://gist.github.com/martinkallman/5049614
 /// Convert float to half float. From https://gist.github.com/martinkallman/5049614
 inline unsigned short FloatToHalf(float value)
 inline unsigned short FloatToHalf(float value)
 {
 {
-    unsigned inu = *((unsigned*)&value);
+    unsigned inu = FloatToRawIntBits(value);
     unsigned t1 = inu & 0x7fffffff;         // Non-sign bits
     unsigned t1 = inu & 0x7fffffff;         // Non-sign bits
     unsigned t2 = inu & 0x80000000;         // Sign bit
     unsigned t2 = inu & 0x80000000;         // Sign bit
     unsigned t3 = inu & 0x7f800000;         // Exponent
     unsigned t3 = inu & 0x7f800000;         // Exponent

+ 20 - 1
Source/Atomic/Math/Matrix3.h

@@ -222,7 +222,17 @@ public:
         );
         );
     }
     }
 
 
-    /// Return transpose.
+    /// Return the scaling part with the sign. Reference rotation matrix is required to avoid ambiguity.
+    Vector3 SignedScale(const Matrix3& rotation) const
+    {
+        return Vector3(
+            rotation.m00_ * m00_ + rotation.m10_ * m10_ + rotation.m20_ * m20_,
+            rotation.m01_ * m01_ + rotation.m11_ * m11_ + rotation.m21_ * m21_,
+            rotation.m02_ * m02_ + rotation.m12_ * m12_ + rotation.m22_ * m22_
+        );
+    }
+
+    /// Return transposed.
     Matrix3 Transpose() const
     Matrix3 Transpose() const
     {
     {
         return Matrix3(
         return Matrix3(
@@ -275,6 +285,15 @@ public:
     /// Return float data.
     /// Return float data.
     const float* Data() const { return &m00_; }
     const float* Data() const { return &m00_; }
 
 
+    /// Return matrix element.
+    float Element(unsigned i, unsigned j) const { return Data()[i * 3 + j]; }
+
+    /// Return matrix row.
+    Vector3 Row(unsigned i) const { return Vector3(Element(i, 0), Element(i, 1), Element(i, 2)); }
+
+    /// Return matrix column.
+    Vector3 Column(unsigned j) const { return Vector3(Element(0, j), Element(1, j), Element(2, j)); }
+
     /// Return as string.
     /// Return as string.
     String ToString() const;
     String ToString() const;
 
 

+ 20 - 0
Source/Atomic/Math/Matrix3x4.h

@@ -665,6 +665,16 @@ public:
         );
         );
     }
     }
 
 
+    /// Return the scaling part with the sign. Reference rotation matrix is required to avoid ambiguity.
+    Vector3 SignedScale(const Matrix3& rotation) const
+    {
+        return Vector3(
+            rotation.m00_ * m00_ + rotation.m10_ * m10_ + rotation.m20_ * m20_,
+            rotation.m01_ * m01_ + rotation.m11_ * m11_ + rotation.m21_ * m21_,
+            rotation.m02_ * m02_ + rotation.m12_ * m12_ + rotation.m22_ * m22_
+        );
+    }
+
     /// Test for equality with another matrix with epsilon.
     /// Test for equality with another matrix with epsilon.
     bool Equals(const Matrix3x4& rhs) const
     bool Equals(const Matrix3x4& rhs) const
     {
     {
@@ -682,12 +692,22 @@ public:
 
 
     /// Return decomposition to translation, rotation and scale.
     /// Return decomposition to translation, rotation and scale.
     void Decompose(Vector3& translation, Quaternion& rotation, Vector3& scale) const;
     void Decompose(Vector3& translation, Quaternion& rotation, Vector3& scale) const;
+
     /// Return inverse.
     /// Return inverse.
     Matrix3x4 Inverse() const;
     Matrix3x4 Inverse() const;
 
 
     /// Return float data.
     /// Return float data.
     const float* Data() const { return &m00_; }
     const float* Data() const { return &m00_; }
 
 
+    /// Return matrix element.
+    float Element(unsigned i, unsigned j) const { return Data()[i * 4 + j]; }
+
+    /// Return matrix row.
+    Vector4 Row(unsigned i) const { return Vector4(Element(i, 0), Element(i, 1), Element(i, 2), Element(i, 3)); }
+
+    /// Return matrix column.
+    Vector3 Column(unsigned j) const { return Vector3(Element(0, j), Element(1, j), Element(2, j)); }
+
     /// Return as string.
     /// Return as string.
     String ToString() const;
     String ToString() const;
 
 

+ 25 - 5
Source/Atomic/Math/Matrix4.h

@@ -96,7 +96,7 @@ public:
 #endif
 #endif
     }
     }
 
 
-    /// Copy-cnstruct from a 3x3 matrix and set the extra elements to identity.
+    /// Copy-construct from a 3x3 matrix and set the extra elements to identity.
     Matrix4(const Matrix3& matrix) :
     Matrix4(const Matrix3& matrix) :
         m00_(matrix.m00_),
         m00_(matrix.m00_),
         m01_(matrix.m01_),
         m01_(matrix.m01_),
@@ -557,7 +557,7 @@ public:
     /// Return the rotation part.
     /// Return the rotation part.
     Quaternion Rotation() const { return Quaternion(RotationMatrix()); }
     Quaternion Rotation() const { return Quaternion(RotationMatrix()); }
 
 
-    /// Return the scaling part
+    /// Return the scaling part.
     Vector3 Scale() const
     Vector3 Scale() const
     {
     {
         return Vector3(
         return Vector3(
@@ -567,7 +567,17 @@ public:
         );
         );
     }
     }
 
 
-    /// Return transpose
+    /// Return the scaling part with the sign. Reference rotation matrix is required to avoid ambiguity.
+    Vector3 SignedScale(const Matrix3& rotation) const
+    {
+        return Vector3(
+            rotation.m00_ * m00_ + rotation.m10_ * m10_ + rotation.m20_ * m20_,
+            rotation.m01_ * m01_ + rotation.m11_ * m11_ + rotation.m21_ * m21_,
+            rotation.m02_ * m02_ + rotation.m12_ * m12_ + rotation.m22_ * m22_
+        );
+    }
+
+    /// Return transposed.
     Matrix4 Transpose() const
     Matrix4 Transpose() const
     {
     {
 #ifdef ATOMIC_SSE
 #ifdef ATOMIC_SSE
@@ -621,12 +631,22 @@ public:
 
 
     /// Return decomposition to translation, rotation and scale.
     /// Return decomposition to translation, rotation and scale.
     void Decompose(Vector3& translation, Quaternion& rotation, Vector3& scale) const;
     void Decompose(Vector3& translation, Quaternion& rotation, Vector3& scale) const;
+
     /// Return inverse.
     /// Return inverse.
     Matrix4 Inverse() const;
     Matrix4 Inverse() const;
 
 
-    /// Return float data
+    /// Return float data.
     const float* Data() const { return &m00_; }
     const float* Data() const { return &m00_; }
 
 
+    /// Return matrix element.
+    float Element(unsigned i, unsigned j) const { return Data()[i * 4 + j]; }
+
+    /// Return matrix row.
+    Vector4 Row(unsigned i) const { return Vector4(Element(i, 0), Element(i, 1), Element(i, 2), Element(i, 3)); }
+
+    /// Return matrix column.
+    Vector4 Column(unsigned j) const { return Vector4(Element(0, j), Element(1, j), Element(2, j), Element(3, j)); }
+
     /// Return as string.
     /// Return as string.
     String ToString() const;
     String ToString() const;
 
 
@@ -691,7 +711,7 @@ public:
     static const Matrix4 IDENTITY;
     static const Matrix4 IDENTITY;
 };
 };
 
 
-/// Multiply a 4x4 matrix with a scalar
+/// Multiply a 4x4 matrix with a scalar.
 inline Matrix4 operator *(float lhs, const Matrix4& rhs) { return rhs * lhs; }
 inline Matrix4 operator *(float lhs, const Matrix4& rhs) { return rhs * lhs; }
 
 
 }
 }

+ 10 - 0
Source/Atomic/Math/Quaternion.cpp

@@ -221,6 +221,16 @@ float Quaternion::RollAngle() const
     return EulerAngles().z_;
     return EulerAngles().z_;
 }
 }
 
 
+Vector3 Quaternion::Axis() const
+{
+    return Vector3(x_, y_, z_) / sqrt(1 - w_ * w_);
+}
+
+float Quaternion::Angle() const
+{
+    return 2 * Acos(w_);
+}
+
 Matrix3 Quaternion::RotationMatrix() const
 Matrix3 Quaternion::RotationMatrix() const
 {
 {
     return Matrix3(
     return Matrix3(

+ 4 - 0
Source/Atomic/Math/Quaternion.h

@@ -431,6 +431,10 @@ public:
     float PitchAngle() const;
     float PitchAngle() const;
     /// Return roll angle in degrees.
     /// Return roll angle in degrees.
     float RollAngle() const;
     float RollAngle() const;
+    /// Return rotation axis.
+    Vector3 Axis() const;
+    /// Return rotation angle.
+    float Angle() const;
     /// Return the rotation matrix that corresponds to this quaternion.
     /// Return the rotation matrix that corresponds to this quaternion.
     Matrix3 RotationMatrix() const;
     Matrix3 RotationMatrix() const;
     /// Spherical interpolation with another quaternion.
     /// Spherical interpolation with another quaternion.

+ 15 - 0
Source/Atomic/Math/Vector3.h

@@ -23,6 +23,7 @@
 #pragma once
 #pragma once
 
 
 #include "../Math/Vector2.h"
 #include "../Math/Vector2.h"
+#include "../Math/MathDefs.h"
 
 
 namespace Atomic
 namespace Atomic
 {
 {
@@ -356,6 +357,9 @@ public:
     /// Project vector onto axis.
     /// Project vector onto axis.
     float ProjectOntoAxis(const Vector3& axis) const { return DotProduct(axis.Normalized()); }
     float ProjectOntoAxis(const Vector3& axis) const { return DotProduct(axis.Normalized()); }
 
 
+    /// Make vector orthogonal to the axis.
+    Vector3 Orthogonalize(const Vector3& axis) const { return axis.CrossProduct(*this).CrossProduct(axis).Normalized(); }
+
     /// Calculate cross product.
     /// Calculate cross product.
     Vector3 CrossProduct(const Vector3& rhs) const
     Vector3 CrossProduct(const Vector3& rhs) const
     {
     {
@@ -415,6 +419,17 @@ public:
     /// Return as string.
     /// Return as string.
     String ToString() const;
     String ToString() const;
 
 
+    /// Return hash value for HashSet & HashMap.
+    unsigned ToHash() const
+    {
+        unsigned hash = 37;
+        hash = 37 * hash + FloatToRawIntBits(x_);
+        hash = 37 * hash + FloatToRawIntBits(y_);
+        hash = 37 * hash + FloatToRawIntBits(z_);
+
+        return hash;
+    }
+
     /// X coordinate.
     /// X coordinate.
     float x_;
     float x_;
     /// Y coordinate.
     /// Y coordinate.

+ 1 - 1
Source/Atomic/Navigation/DynamicNavigationMesh.cpp

@@ -77,7 +77,7 @@ struct TileCompressor : public dtTileCacheCompressor
     virtual dtStatus compress(const unsigned char* buffer, const int bufferSize,
     virtual dtStatus compress(const unsigned char* buffer, const int bufferSize,
         unsigned char* compressed, const int /*maxCompressedSize*/, int* compressedSize)
         unsigned char* compressed, const int /*maxCompressedSize*/, int* compressedSize)
     {
     {
-        *compressedSize = LZ4_compress((const char*)buffer, (char*)compressed, bufferSize);
+        *compressedSize = LZ4_compress_default((const char*)buffer, (char*)compressed, bufferSize, LZ4_compressBound(bufferSize));
         return DT_SUCCESS;
         return DT_SUCCESS;
     }
     }
 
 

+ 6 - 3
Source/Atomic/Physics/PhysicsWorld.cpp

@@ -34,6 +34,7 @@
 #include "../Physics/PhysicsEvents.h"
 #include "../Physics/PhysicsEvents.h"
 #include "../Physics/PhysicsUtils.h"
 #include "../Physics/PhysicsUtils.h"
 #include "../Physics/PhysicsWorld.h"
 #include "../Physics/PhysicsWorld.h"
+#include "../Physics/RaycastVehicle.h"
 #include "../Physics/RigidBody.h"
 #include "../Physics/RigidBody.h"
 #include "../Scene/Scene.h"
 #include "../Scene/Scene.h"
 #include "../Scene/SceneEvents.h"
 #include "../Scene/SceneEvents.h"
@@ -151,6 +152,7 @@ PhysicsWorld::PhysicsWorld(Context* context) :
     world_->setDebugDrawer(this);
     world_->setDebugDrawer(this);
     world_->setInternalTickCallback(InternalPreTickCallback, static_cast<void*>(this), true);
     world_->setInternalTickCallback(InternalPreTickCallback, static_cast<void*>(this), true);
     world_->setInternalTickCallback(InternalTickCallback, static_cast<void*>(this), false);
     world_->setInternalTickCallback(InternalTickCallback, static_cast<void*>(this), false);
+    world_->setSynchronizeAllMotionStates(true);
 }
 }
 
 
 
 
@@ -646,15 +648,15 @@ void PhysicsWorld::GetRigidBodies(PODVector<RigidBody*>& result, const BoundingB
 void PhysicsWorld::GetRigidBodies(PODVector<RigidBody*>& result, const RigidBody* body)
 void PhysicsWorld::GetRigidBodies(PODVector<RigidBody*>& result, const RigidBody* body)
 {
 {
     ATOMIC_PROFILE(PhysicsBodyQuery);
     ATOMIC_PROFILE(PhysicsBodyQuery);
-    
+
     result.Clear();
     result.Clear();
-    
+
     if (!body || !body->GetBody())
     if (!body || !body->GetBody())
         return;
         return;
 
 
     PhysicsQueryCallback callback(result, body->GetCollisionMask());
     PhysicsQueryCallback callback(result, body->GetCollisionMask());
     world_->contactTest(body->GetBody(), callback);
     world_->contactTest(body->GetBody(), callback);
-    
+
     // Remove the body itself from the returned list
     // Remove the body itself from the returned list
     for (unsigned i = 0; i < result.Size(); i++)
     for (unsigned i = 0; i < result.Size(); i++)
     {
     {
@@ -1081,6 +1083,7 @@ void RegisterPhysicsLibrary(Context* context)
     RigidBody::RegisterObject(context);
     RigidBody::RegisterObject(context);
     Constraint::RegisterObject(context);
     Constraint::RegisterObject(context);
     PhysicsWorld::RegisterObject(context);
     PhysicsWorld::RegisterObject(context);
+    RaycastVehicle::RegisterObject(context);
 }
 }
 
 
 }
 }

+ 744 - 0
Source/Atomic/Physics/RaycastVehicle.cpp

@@ -0,0 +1,744 @@
+//
+// Copyright (c) 2008-2017 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "../Core/Context.h"
+#include "../Physics/PhysicsUtils.h"
+#include "../Physics/RigidBody.h"
+#include "../Physics/PhysicsWorld.h"
+#include "../Scene/Scene.h"
+#include "../IO/Log.h"
+#include "../Physics/RaycastVehicle.h"
+
+#include <BulletDynamics/Vehicle/btRaycastVehicle.h>
+#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
+
+namespace Atomic
+{
+
+struct RaycastVehicleData
+{
+    RaycastVehicleData()
+    {
+        vehicleRayCaster_ = 0;
+        vehicle_ = 0;
+        added_ = false;
+    }
+
+    ~RaycastVehicleData()
+    {
+        if (vehicleRayCaster_)
+        {
+            delete vehicleRayCaster_;
+        }
+        vehicleRayCaster_ = 0;
+        if (vehicle_)
+        {
+            if (physWorld_ && added_)
+            {
+                btDynamicsWorld* pbtDynWorld = physWorld_->GetWorld();
+                if (pbtDynWorld)
+                    pbtDynWorld->removeAction(vehicle_);
+                added_ = false;
+            }
+            delete vehicle_;
+        }
+        vehicle_ = 0;
+    }
+
+    btRaycastVehicle* Get()
+    {
+        return vehicle_;
+    }
+
+    void Init(Scene* scene, RigidBody* body, bool enabled)
+    {
+        int rightIndex = 0;
+        int upIndex = 1;
+        int forwardIndex = 2;
+        PhysicsWorld* pPhysWorld = scene->GetComponent<PhysicsWorld>();
+        btDynamicsWorld* pbtDynWorld = pPhysWorld->GetWorld();
+        if (!pbtDynWorld)
+            return;
+
+        // Delete old vehicle & action first
+        if (vehicleRayCaster_)
+            delete vehicleRayCaster_;
+        if (vehicle_)
+        {
+            if (added_)
+                pbtDynWorld->removeAction(vehicle_);
+            delete vehicle_;
+        }
+
+        vehicleRayCaster_ = new btDefaultVehicleRaycaster(pbtDynWorld);
+        btRigidBody* bthullBody = body->GetBody();
+        vehicle_ = new btRaycastVehicle(tuning_, bthullBody, vehicleRayCaster_);
+        if (enabled)
+        {
+            pbtDynWorld->addAction(vehicle_);
+            added_ = true;
+        }
+
+        vehicle_->setCoordinateSystem(rightIndex, upIndex, forwardIndex);
+        physWorld_ = pPhysWorld;
+    }
+
+    void SetEnabled(bool enabled)
+    {
+        if (!physWorld_ || !vehicle_)
+            return;
+        btDynamicsWorld* pbtDynWorld = physWorld_->GetWorld();
+        if (!pbtDynWorld)
+            return;
+
+        if (enabled && !added_)
+        {
+            pbtDynWorld->addAction(vehicle_);
+            added_ = true;
+        }
+        else if (!enabled && added_)
+        {
+            pbtDynWorld->removeAction(vehicle_);
+            added_ = false;
+        }
+    }
+
+    WeakPtr<PhysicsWorld> physWorld_;
+    btVehicleRaycaster* vehicleRayCaster_;
+    btRaycastVehicle* vehicle_;
+    btRaycastVehicle::btVehicleTuning tuning_;
+    bool added_;
+};
+
+RaycastVehicle::RaycastVehicle(Context* context) : 
+    LogicComponent(context)
+{
+    // fixed update() for inputs and post update() to sync wheels for rendering
+    SetUpdateEventMask(USE_FIXEDUPDATE | USE_FIXEDPOSTUPDATE | USE_POSTUPDATE);
+    vehicleData_ = new RaycastVehicleData();
+    wheelNodes_.Clear();
+    activate_ = false;
+    inAirRPM_ = 0.0f;
+    maxSideSlipSpeed_ = 4.0f;
+}
+
+RaycastVehicle::~RaycastVehicle()
+{
+    delete vehicleData_;
+    wheelNodes_.Clear();
+}
+
+const char* wheelElementNames[] =
+{
+    "Number of wheels",
+    "   Wheel node id",
+    "   Wheel direction",
+    "   Wheel axle",
+    "   Wheel rest length",
+    "   Wheel radius",
+    "   Wheel is front wheel",
+    "   Steering",
+    "   Connection point vector",
+    "   Original rotation",
+    "   Cumulative skid info",
+    "   Side skip speed",
+    "   Grounded",
+    "   Contact position",
+    "   Contact normal",
+    "   Suspension stiffness",
+    "   Damping relaxation",
+    "   Damping compression",
+    "   Friction slip",
+    "   Roll influence",
+    "   Engine force",
+    "   Brake",
+    0
+};
+
+void RaycastVehicle::RegisterObject(Context* context)
+{
+    context->RegisterFactory<RaycastVehicle>();
+    ATOMIC_MIXED_ACCESSOR_VARIANT_VECTOR_STRUCTURE_ATTRIBUTE("Wheel data", GetWheelDataAttr, SetWheelDataAttr,
+            VariantVector, Variant::emptyVariantVector,
+            wheelElementNames, AM_DEFAULT);
+    ATOMIC_ATTRIBUTE("Maximum side slip threshold", float, maxSideSlipSpeed_, 4.0f, AM_DEFAULT);
+    ATOMIC_ATTRIBUTE("RPM for wheel motors in air (0=calculate)", float, inAirRPM_, 0.0f, AM_DEFAULT);
+}
+
+void RaycastVehicle::OnSetEnabled()
+{
+    if (vehicleData_)
+        vehicleData_->SetEnabled(IsEnabledEffective());
+}
+
+void RaycastVehicle::ApplyAttributes()
+{
+    int index = 0;
+    hullBody_ = node_->GetOrCreateComponent<RigidBody>();
+    Scene* scene = GetScene();
+    vehicleData_->Init(scene, hullBody_, IsEnabledEffective());
+    VariantVector& value = loadedWheelData_;
+    int numObjects = value[index++].GetInt();
+    int wheelIndex = 0;
+    origRotation_.Clear();
+    skidInfoCumulative_.Clear();
+    wheelSideSlipSpeed_.Clear();
+
+    for (int i = 0; i < numObjects; i++)
+    {
+        int node_id = value[index++].GetInt();
+        Vector3 direction = value[index++].GetVector3();
+        Vector3 axle = value[index++].GetVector3();
+        float restLength = value[index++].GetFloat();
+        float radius = value[index++].GetFloat();
+        bool isFrontWheel = value[index++].GetBool();
+        float steering = value[index++].GetFloat();
+        Vector3 connectionPoint = value[index++].GetVector3();
+        Quaternion origRotation = value[index++].GetQuaternion();
+        float skidInfoC = value[index++].GetFloat();
+        float sideSlipSpeed = value[index++].GetFloat();
+
+        bool isContact = value[index++].GetBool();
+        Vector3 contactPosition = value[index++].GetVector3();
+        Vector3 contactNormal = value[index++].GetVector3();
+        float suspensionStiffness = value[index++].GetFloat();
+        float dampingRelaxation = value[index++].GetFloat();
+        float dampingCompression = value[index++].GetFloat();
+        float frictionSlip = value[index++].GetFloat();
+        float rollInfluence = value[index++].GetFloat();
+        float engineForce = value[index++].GetFloat();
+        float brake = value[index++].GetFloat();
+        float skidInfo = value[index++].GetFloat();
+        Node* wheelNode = GetScene()->GetNode(node_id);
+        if (!wheelNode)
+        {
+            ATOMIC_LOGERROR("RaycastVehicle: Incorrect node id = " + String(node_id) + " index: " + String(index));
+            continue;
+        }
+        btRaycastVehicle* vehicle = vehicleData_->Get();
+        int id = GetNumWheels();
+        btVector3 connectionPointCS0(connectionPoint.x_, connectionPoint.y_, connectionPoint.z_);
+        btVector3 wheelDirectionCS0(direction.x_, direction.y_, direction.z_);
+        btVector3 wheelAxleCS(axle.x_, axle.y_, axle.z_);
+        btWheelInfo& wheel = vehicle->addWheel(connectionPointCS0,
+                                wheelDirectionCS0,
+                                wheelAxleCS,
+                                restLength,
+                                radius,
+                                vehicleData_->tuning_,
+                                isFrontWheel);
+        wheelNodes_.Push(wheelNode);
+        origRotation_.Push(origRotation);
+        skidInfoCumulative_.Push(skidInfoC);
+        wheelSideSlipSpeed_.Push(sideSlipSpeed);
+        SetSteeringValue(wheelIndex, steering);
+        wheel.m_raycastInfo.m_isInContact = isContact;
+        wheel.m_raycastInfo.m_contactNormalWS = btVector3(contactNormal.x_, contactNormal.y_, contactNormal.z_);
+        wheel.m_raycastInfo.m_contactPointWS = btVector3(contactPosition.x_, contactPosition.y_, contactPosition.z_);
+        wheel.m_suspensionStiffness = suspensionStiffness;
+        wheel.m_wheelsDampingRelaxation = dampingRelaxation;
+        wheel.m_wheelsDampingCompression = dampingCompression;
+        wheel.m_frictionSlip = frictionSlip;
+        wheel.m_rollInfluence = rollInfluence;
+        wheel.m_engineForce = engineForce;
+        wheel.m_brake = brake;
+        wheel.m_skidInfo = skidInfo;
+        wheelIndex++;
+    }
+    ATOMIC_LOGDEBUG("maxSideSlipSpeed_ value: " + String(maxSideSlipSpeed_));
+    ATOMIC_LOGDEBUG("loaded items: " + String(index));
+    ATOMIC_LOGDEBUG("loaded wheels: " + String(GetNumWheels()));
+}
+
+void RaycastVehicle::Init()
+{
+    hullBody_ = node_->GetOrCreateComponent<RigidBody>();
+    Scene* scene = GetScene();
+    vehicleData_->Init(scene, hullBody_, IsEnabledEffective());
+}
+
+void RaycastVehicle::FixedUpdate(float timeStep)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    for (int i = 0; i < GetNumWheels(); i++)
+    {
+        btWheelInfo whInfo = vehicle->getWheelInfo(i);
+        if (whInfo.m_engineForce != 0.0f || whInfo.m_steering != 0.0f)
+        {
+            hullBody_->Activate();
+            break;
+        }
+    }
+}
+
+void RaycastVehicle::PostUpdate(float timeStep)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    for (int i = 0; i < GetNumWheels(); i++)
+    {
+        vehicle->updateWheelTransform(i, true);
+        btTransform transform = vehicle->getWheelTransformWS(i);
+        Vector3 origin = ToVector3(transform.getOrigin());
+        Quaternion qRot = ToQuaternion(transform.getRotation());
+        Node* pWheel = wheelNodes_[i];
+        pWheel->SetWorldPosition(origin);
+        pWheel->SetWorldRotation(qRot * origRotation_[i]);
+    }
+}
+
+void RaycastVehicle::FixedPostUpdate(float timeStep)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    Vector3 velocity = hullBody_->GetLinearVelocity();
+    for (int i = 0; i < GetNumWheels(); i++)
+    {
+        btWheelInfo& whInfo = vehicle->getWheelInfo(i);
+        if (!WheelIsGrounded(i) && GetEngineForce(i) != 0.0f)
+        {
+            float delta;
+            if (inAirRPM_ != 0.0f)
+            {
+                delta = inAirRPM_ * timeStep / 60.0f;
+            }
+            else
+            {
+                delta = 8.0f * GetEngineForce(i) * timeStep / (hullBody_->GetMass() * GetWheelRadius(i));
+            }
+            if (Abs(whInfo.m_deltaRotation) < Abs(delta))
+            {
+                whInfo.m_rotation += delta - whInfo.m_deltaRotation;
+                whInfo.m_deltaRotation = delta;
+            }
+            if (skidInfoCumulative_[i] > 0.05f)
+            {
+                skidInfoCumulative_[i] -= 0.002;
+            }
+        }
+        else
+        {
+            skidInfoCumulative_[i] = GetWheelSkidInfo(i);
+        }
+        wheelSideSlipSpeed_[i] = Abs(ToVector3(whInfo.m_raycastInfo.m_wheelAxleWS).DotProduct(velocity));
+        if (wheelSideSlipSpeed_[i] > maxSideSlipSpeed_)
+        {
+            skidInfoCumulative_[i] = Clamp(skidInfoCumulative_[i], 0.0f, 0.89f);
+        }
+    }
+}
+
+void RaycastVehicle::SetMaxSideSlipSpeed(float speed)
+{
+    maxSideSlipSpeed_ = speed;
+}
+
+float RaycastVehicle::GetMaxSideSlipSpeed() const
+{
+    return maxSideSlipSpeed_;
+}
+
+void RaycastVehicle::SetWheelSkidInfoCumulative(int wheel, float skid)
+{
+    skidInfoCumulative_[wheel] = skid;
+}
+
+float RaycastVehicle::GetWheelSkidInfoCumulative(int wheel) const
+{
+    return skidInfoCumulative_[wheel];
+}
+
+void RaycastVehicle::AddWheel(Node* wheelNode,
+                                Vector3 wheelDirection, Vector3 wheelAxle,
+                                float restLength, float wheelRadius,
+                                bool frontWheel)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    int id = GetNumWheels();
+    Vector3 connectionPoint = wheelNode->GetWorldPosition() - node_->GetWorldPosition();
+    btVector3 connectionPointCS0(connectionPoint.x_, connectionPoint.y_, connectionPoint.z_);
+    btVector3 wheelDirectionCS0(wheelDirection.x_, wheelDirection.y_, wheelDirection.z_);
+    btVector3 wheelAxleCS(wheelAxle.x_, wheelAxle.y_, wheelAxle.z_);
+    btWheelInfo& wheel = vehicle->addWheel(connectionPointCS0,
+                            wheelDirectionCS0,
+                            wheelAxleCS,
+                            restLength,
+                            wheelRadius,
+                            vehicleData_->tuning_,
+                            frontWheel);
+
+    wheelNodes_.Push(wheelNode);
+    origRotation_.Push(wheelNode->GetWorldRotation());
+    skidInfoCumulative_.Push(1.0f);
+    wheelSideSlipSpeed_.Push(0.0f);
+    wheel.m_raycastInfo.m_isInContact = false;
+}
+
+void RaycastVehicle::ResetSuspension()
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    vehicle->resetSuspension();
+}
+
+void RaycastVehicle::UpdateWheelTransform(int wheel, bool interpolated)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    vehicle->updateWheelTransform(wheel, interpolated);
+}
+
+Vector3 RaycastVehicle::GetWheelPosition(int wheel)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btTransform transform = vehicle->getWheelTransformWS(wheel);
+    Vector3 origin = ToVector3(transform.getOrigin());
+    return origin;
+}
+
+Quaternion RaycastVehicle::GetWheelRotation(int wheel)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btTransform transform = vehicle->getWheelTransformWS(wheel);
+    Quaternion rotation = ToQuaternion(transform.getRotation());
+    return rotation;
+}
+
+Vector3 RaycastVehicle::GetWheelConnectionPoint(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return ToVector3(whInfo.m_chassisConnectionPointCS);
+}
+
+void RaycastVehicle::SetSteeringValue(int wheel, float steeringValue)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    vehicle->setSteeringValue(steeringValue, wheel);
+}
+
+float RaycastVehicle::GetSteeringValue(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_steering;
+}
+
+void RaycastVehicle::SetWheelSuspensionStiffness(int wheel, float stiffness)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_suspensionStiffness = stiffness;
+}
+
+float RaycastVehicle::GetWheelSuspensionStiffness(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_suspensionStiffness;
+}
+
+void RaycastVehicle::SetWheelDampingRelaxation(int wheel, float damping)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_wheelsDampingRelaxation = damping;
+}
+
+float RaycastVehicle::GetWheelDampingRelaxation(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_wheelsDampingRelaxation;
+}
+
+void RaycastVehicle::SetWheelDampingCompression(int wheel, float compression)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_wheelsDampingCompression = compression;
+}
+
+float RaycastVehicle::GetWheelDampingCompression(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_wheelsDampingCompression;
+}
+
+void RaycastVehicle::SetWheelFrictionSlip(int wheel, float slip)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_frictionSlip = slip;
+}
+
+float RaycastVehicle::GetWheelFrictionSlip(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_frictionSlip;
+}
+
+void RaycastVehicle::SetWheelRollInfluence(int wheel, float rollInfluence)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_rollInfluence = rollInfluence;
+}
+
+Vector3 RaycastVehicle::GetContactPosition(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    return ToVector3(whInfo.m_raycastInfo.m_contactPointWS);
+}
+
+Vector3 RaycastVehicle::GetContactNormal(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    return ToVector3(whInfo.m_raycastInfo.m_contactNormalWS);
+}
+
+float RaycastVehicle::GetWheelSideSlipSpeed(int wheel) const
+{
+    return wheelSideSlipSpeed_[wheel];
+}
+
+float RaycastVehicle::GetWheelRollInfluence(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_rollInfluence;
+}
+
+void RaycastVehicle::SetWheelRadius(int wheel, float wheelRadius)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_wheelsRadius = wheelRadius;
+}
+
+float RaycastVehicle::GetWheelRadius(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_wheelsRadius;
+}
+
+void RaycastVehicle::SetEngineForce(int wheel, float force)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    vehicle->applyEngineForce(force, wheel);
+}
+
+float RaycastVehicle::GetEngineForce(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_engineForce;
+}
+
+void RaycastVehicle::SetBrake(int wheel, float force)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    vehicle->setBrake(force, wheel);
+}
+
+float RaycastVehicle::GetBrake(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_brake;
+}
+
+int RaycastVehicle::GetNumWheels() const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    return vehicle->getNumWheels();
+}
+
+Node* RaycastVehicle::GetWheelNode(int wheel) const
+{
+    return wheelNodes_[wheel];
+}
+
+void RaycastVehicle::SetMaxSuspensionTravel(int wheel, float maxSuspensionTravel)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_maxSuspensionTravelCm = maxSuspensionTravel;
+}
+
+float RaycastVehicle::GetMaxSuspensionTravel(int wheel)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_maxSuspensionTravelCm;
+}
+
+void RaycastVehicle::SetWheelDirection(int wheel, Vector3 direction)
+{
+    btVector3 dir(direction.x_, direction.y_, direction.z_);
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_wheelDirectionCS = dir;
+}
+
+Vector3 RaycastVehicle::GetWheelDirection(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    return ToVector3(whInfo.m_wheelDirectionCS);
+}
+
+void RaycastVehicle::SetWheelAxle(int wheel, Vector3 axle)
+{
+    btVector3 ax(axle.x_, axle.y_, axle.z_);
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_wheelAxleCS = ax;
+}
+
+Vector3 RaycastVehicle::GetWheelAxle(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    return ToVector3(whInfo.m_wheelAxleCS);
+}
+
+void RaycastVehicle::SetWheelRestLength(int wheel, float length)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_suspensionRestLength1 = length;
+}
+
+float RaycastVehicle::GetWheelRestLength(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_suspensionRestLength1;
+}
+
+void RaycastVehicle::SetWheelSkidInfo(int wheel, float factor)
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    whInfo.m_skidInfo = factor;
+}
+
+float RaycastVehicle::GetWheelSkidInfo(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_skidInfo;
+}
+
+bool RaycastVehicle::IsFrontWheel(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo& whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_bIsFrontWheel;
+}
+
+bool RaycastVehicle::WheelIsGrounded(int wheel) const
+{
+    btRaycastVehicle* vehicle = vehicleData_->Get();
+    btWheelInfo whInfo = vehicle->getWheelInfo(wheel);
+    return whInfo.m_raycastInfo.m_isInContact;
+}
+
+void RaycastVehicle::SetInAirRPM(float rpm)
+{
+    inAirRPM_ = rpm;
+}
+
+float RaycastVehicle::GetInAirRPM() const
+{
+    return inAirRPM_;
+}
+
+void RaycastVehicle::ResetWheels()
+{
+    ResetSuspension();
+    for (int i = 0; i < GetNumWheels(); i++)
+    {
+        UpdateWheelTransform(i, true);
+        Vector3 origin = GetWheelPosition(i);
+        Node* wheelNode = GetWheelNode(i);
+        wheelNode->SetWorldPosition(origin);
+    }
+}
+
+VariantVector RaycastVehicle::GetWheelDataAttr() const
+{
+    VariantVector ret;
+    ret.Reserve(GetNumWheels() * 22 + 1);
+    ret.Push(GetNumWheels());
+    for (int i = 0; i < GetNumWheels(); i++)
+    {
+        Node* wNode = GetWheelNode(i);
+        int node_id = wNode->GetID();
+        ATOMIC_LOGDEBUG("RaycastVehicle: Saving node id = " + String(node_id));
+        ret.Push(node_id);
+        ret.Push(GetWheelDirection(i));
+        ret.Push(GetWheelAxle(i));
+        ret.Push(GetWheelRestLength(i));
+        ret.Push(GetWheelRadius(i));
+        ret.Push(IsFrontWheel(i));
+        ret.Push(GetSteeringValue(i));
+        ret.Push(GetWheelConnectionPoint(i));
+        ret.Push(origRotation_[i]);
+        ret.Push(GetWheelSkidInfoCumulative(i));
+        ret.Push(GetWheelSideSlipSpeed(i));
+        ret.Push(WheelIsGrounded(i));
+        ret.Push(GetContactPosition(i));
+        ret.Push(GetContactNormal(i));       // 14
+        ret.Push(GetWheelSuspensionStiffness(i));
+        ret.Push(GetWheelDampingRelaxation(i));
+        ret.Push(GetWheelDampingCompression(i));
+        ret.Push(GetWheelFrictionSlip(i));
+        ret.Push(GetWheelRollInfluence(i));
+        ret.Push(GetEngineForce(i));
+        ret.Push(GetBrake(i));
+        ret.Push(GetWheelSkidInfo(i));
+    }
+    ATOMIC_LOGDEBUG("RaycastVehicle: saved items: " + String(ret.Size()));
+    ATOMIC_LOGDEBUG("maxSideSlipSpeed_ value save: " + String(maxSideSlipSpeed_));
+    return ret;
+}
+
+void RaycastVehicle::SetWheelDataAttr(const VariantVector& value)
+{
+    if (!vehicleData_)
+    {
+        ATOMIC_LOGERROR("RaycastVehicle: vehicleData doesn't exist");
+        return;
+    }
+    if (value.Size() < 2)
+    {
+        ATOMIC_LOGERROR("RaycastVehicle: Incorrect vehicleData");
+        return;
+    }
+
+    loadedWheelData_ = value;
+}
+
+} // namespace Urho3D

+ 187 - 0
Source/Atomic/Physics/RaycastVehicle.h

@@ -0,0 +1,187 @@
+//
+// Copyright (c) 2008-2017 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Scene/LogicComponent.h"
+#include "../Physics/PhysicsUtils.h"
+#include "../Physics/RigidBody.h"
+
+namespace Atomic
+{
+
+struct RaycastVehicleData;
+
+class ATOMIC_API RaycastVehicle : public LogicComponent
+{
+    ATOMIC_OBJECT(RaycastVehicle, LogicComponent)
+
+public:
+    /// Construct.
+    RaycastVehicle(Atomic::Context* context);
+    /// Destruct.
+    ~RaycastVehicle();
+
+    /// Register object factory and attributes.
+    static void RegisterObject(Context* context);
+    
+    /// Handle enabled/disabled state change.
+    virtual void OnSetEnabled();
+
+    /// Perform post-load after deserialization. Acquire the components from the scene nodes.
+    virtual void ApplyAttributes();
+
+    /// Add a wheel. All parameters are relative to RigidBody / node.
+    void AddWheel(Node* wheelNode, Vector3 wheelDirection, Vector3 wheelAxle, float restLength, float wheelRadius, bool frontWheel);
+    /// Reset all suspension.
+    void ResetSuspension(void);
+    /// Update transform for particular wheel.
+    void UpdateWheelTransform(int wheel, bool interpolated);
+    /// Set steering value of particular wheel.
+    void SetSteeringValue(int wheel, float steeringValue);
+    /// Set suspension stiffness for particular wheel.
+    void SetWheelSuspensionStiffness(int wheel, float stiffness);
+    /// Set wheel damping relaxation.
+    void SetWheelDampingRelaxation(int wheel, float damping);
+    /// Set wheel damping compression.
+    void SetWheelDampingCompression(int wheel, float compression);
+    /// Set wheel friction slip.
+    void SetWheelFrictionSlip(int wheel, float slip);
+    /// Set wheel roll influence.
+    void SetWheelRollInfluence(int wheel, float rollInfluence);
+    /// Set engine force for the wheel.
+    void SetEngineForce(int wheel, float force);
+    /// Set hand brake (wheel rotation blocking force.)
+    void SetBrake(int wheel, float force);
+    /// Set wheel radius.
+    void SetWheelRadius(int wheel, float wheelRadius);
+    /// Sets node initial positions.
+    void ResetWheels();
+    /// Set sliding factor 0 <= x <= 1. The less the value, more sliding.
+    void SetWheelSkidInfo(int wheel, float factor);
+    /// True if wheel touches ground (raycast hits something.)
+    bool WheelIsGrounded(int wheel) const;
+    /// Set maximum suspension travel value.
+    void SetMaxSuspensionTravel(int wheel, float maxSuspensionTravel);
+    /// Set wheel direction vector.
+    void SetWheelDirection(int wheel, Vector3 direction);
+    /// Set wheel axle vector.
+    void SetWheelAxle(int wheel, Vector3 axle);
+    /// Set side speed which is considered sliding.
+    void SetMaxSideSlipSpeed(float speed);
+    /// Set cumulative skid info.
+    void SetWheelSkidInfoCumulative(int wheel, float skid);
+    /// Set revolution per minute value for when wheel doesn't touch ground. If set to 0 (or not set), calculated from engine force (probably not what you want).
+    void SetInAirRPM(float rpm);
+    /// Init the vehicle component after creation
+    void Init();
+    /// Perform fixed step pre-update.
+    void FixedUpdate(float timeStep);
+    /// Perform fixed step post-update.
+    void FixedPostUpdate(float timeStep);
+    /// Perform variable step post-update.
+    void PostUpdate(float timeStep);
+
+    /// Get wheel position relative to RigidBody.
+    Vector3 GetWheelPosition(int wheel);
+    /// Get wheel rotation relative to RigidBody.
+    Quaternion GetWheelRotation(int wheel);
+    /// Get wheel connection point relative to RigidBody.
+    Vector3 GetWheelConnectionPoint(int wheel) const;
+    /// Get number of attached wheels.
+    int GetNumWheels() const;
+    /// Get node of the wheel.
+    Node* GetWheelNode(int wheel) const;
+    /// Get steering value of particular wheel.
+    float GetSteeringValue(int wheel) const;
+    /// Get suspension stiffness for particular wheel.
+    float GetWheelSuspensionStiffness(int wheel) const;
+    /// Get wheel damping relaxation.
+    float GetWheelDampingRelaxation(int wheel) const;
+    /// Get wheel damping compression.
+    float GetWheelDampingCompression(int wheel) const;
+    /// Get wheel friction slip.
+    float GetWheelFrictionSlip(int wheel) const;
+    /// Get wheel roll influence.
+    float GetWheelRollInfluence(int wheel) const;
+    /// Get engine force for the wheel.
+    float GetEngineForce(int wheel) const;
+    /// Get hand brake value.
+    float GetBrake(int wheel) const;
+    /// Get wheel radius.
+    float GetWheelRadius(int wheel) const;
+    /// Get wheel rest length.
+    void SetWheelRestLength(int wheel, float length);
+    /// Get wheel rest length.
+    float GetWheelRestLength(int wheel) const;
+    /// Get maximum suspension travel value.
+    float GetMaxSuspensionTravel(int wheel);
+    /// Get wheel axle vector.
+    Vector3 GetWheelAxle(int wheel) const;
+    /// Get wheel slide speed.
+    float GetWheelSideSlipSpeed(int wheel) const;
+    /// Get side speed which is considered sliding.
+    float GetMaxSideSlipSpeed() const;
+    /// Sliding factor 0 <= x <= 1.
+    float GetWheelSkidInfo(int wheel) const;
+    /// Get wheel direction vector.
+    Vector3 GetWheelDirection(int wheel) const;
+    /// Get cumulative skid info.
+    float GetWheelSkidInfoCumulative(int wheel) const;
+    /// True if front wheel, otherwise false.
+    bool IsFrontWheel(int wheel) const;
+    /// Get wheel contact position.
+    Vector3 GetContactPosition(int wheel) const;
+    /// Get contact normal.
+    Vector3 GetContactNormal(int wheel) const;
+    /// Get revolution per minute value for when wheel doesn't touch ground.
+    float GetInAirRPM() const;
+
+    /// Get wheel data attribute for serialization.
+    VariantVector GetWheelDataAttr() const;
+    /// Set wheel data attribute during loading.
+    void SetWheelDataAttr(const VariantVector& value);
+
+private:
+    /// If the RigidBody should be activated.
+    bool activate_;
+    /// Hull RigidBody
+    WeakPtr<RigidBody> hullBody_;
+    /// Opaque Bullet data hidden from public
+    RaycastVehicleData* vehicleData_;
+    /// Nodes of all wheels
+    Vector<Node*> wheelNodes_;
+    /// All wheels original rotations. These are applied in addition to wheel rotations by btRaycastVehicle
+    Vector<Quaternion> origRotation_;
+    /// Revolutions per minute value for in-air motor wheels. FIXME: set this one per wheel
+    float inAirRPM_;
+    /// Per-wheel extra settings.
+    Vector<float> skidInfoCumulative_;
+    /// Wheel side movement speed.
+    Vector<float> wheelSideSlipSpeed_;
+    /// Side slip speed threshold.
+    float maxSideSlipSpeed_;
+    /// Loaded data temporarily wait here for ApplyAttributes to come pick them up.
+    VariantVector loadedWheelData_;
+};
+
+}

+ 2 - 2
Source/Atomic/Resource/JSONFile.cpp

@@ -128,7 +128,7 @@ bool JSONFile::BeginLoad(Deserializer& source)
     buffer[dataSize] = '\0';
     buffer[dataSize] = '\0';
 
 
     rapidjson::Document document;
     rapidjson::Document document;
-    if (document.Parse<0>(buffer).HasParseError())
+    if (document.Parse<kParseCommentsFlag | kParseTrailingCommasFlag>(buffer).HasParseError())
     {
     {
         ATOMIC_LOGERROR("Could not parse JSON data from " + source.GetName());
         ATOMIC_LOGERROR("Could not parse JSON data from " + source.GetName());
         return false;
         return false;
@@ -202,7 +202,7 @@ static void ToRapidjsonValue(rapidjson::Value& rapidjsonValue, const JSONValue&
                 const char* name = i->first_.CString();
                 const char* name = i->first_.CString();
                 rapidjson::Value value;
                 rapidjson::Value value;
                 ToRapidjsonValue(value, i->second_, allocator);
                 ToRapidjsonValue(value, i->second_, allocator);
-                rapidjsonValue.AddMember(name, value, allocator);
+                rapidjsonValue.AddMember(StringRef(name), value, allocator);
             }
             }
         }
         }
         break;
         break;

+ 3 - 2
Source/Atomic/Resource/JSONValue.cpp

@@ -322,7 +322,7 @@ JSONObjectIterator JSONValue::End()
     // Convert to object type.
     // Convert to object type.
     SetType(JSON_OBJECT);
     SetType(JSON_OBJECT);
 
 
-    return objectValue_->Begin();
+    return objectValue_->End();
 }
 }
 
 
 ConstJSONObjectIterator JSONValue::End() const
 ConstJSONObjectIterator JSONValue::End() const
@@ -581,7 +581,8 @@ VariantMap JSONValue::GetVariantMap() const
 
 
     for (ConstJSONObjectIterator i = Begin(); i != End(); ++i)
     for (ConstJSONObjectIterator i = Begin(); i != End(); ++i)
     {
     {
-        StringHash key(ToUInt(i->first_));
+        /// \todo Ideally this should allow any strings, but for now the convention is that the keys need to be hexadecimal StringHashes
+        StringHash key(ToUInt(i->first_, 16));
         Variant variant = i->second_.GetVariant();
         Variant variant = i->second_.GetVariant();
         variantMap[key] = variant;
         variantMap[key] = variant;
     }
     }

+ 63 - 0
Source/Atomic/Resource/Resource.cpp

@@ -26,6 +26,7 @@
 #include "../IO/File.h"
 #include "../IO/File.h"
 #include "../IO/Log.h"
 #include "../IO/Log.h"
 #include "../Resource/Resource.h"
 #include "../Resource/Resource.h"
+#include "../Resource/XMLElement.h"
 
 
 namespace Atomic
 namespace Atomic
 {
 {
@@ -120,4 +121,66 @@ unsigned Resource::GetUseTimer()
         return useTimer_.GetMSec(false);
         return useTimer_.GetMSec(false);
 }
 }
 
 
+void ResourceWithMetadata::AddMetadata(const String& name, const Variant& value)
+{
+    bool exists;
+    metadata_.Insert(MakePair(StringHash(name), value), exists);
+    if (!exists)
+        metadataKeys_.Push(name);
+}
+
+void ResourceWithMetadata::RemoveMetadata(const String& name)
+{
+    metadata_.Erase(name);
+    metadataKeys_.Remove(name);
+}
+
+void ResourceWithMetadata::RemoveAllMetadata()
+{
+    metadata_.Clear();
+    metadataKeys_.Clear();
+}
+
+const Variant& ResourceWithMetadata::GetMetadata(const String& name) const
+{
+    const Variant* value = metadata_[name];
+    return value ? *value : Variant::EMPTY;
+}
+
+bool ResourceWithMetadata::HasMetadata() const
+{
+    return !metadata_.Empty();
+}
+
+void ResourceWithMetadata::LoadMetadataFromXML(const XMLElement& source)
+{
+    for (XMLElement elem = source.GetChild("metadata"); elem; elem = elem.GetNext("metadata"))
+        AddMetadata(elem.GetAttribute("name"), elem.GetVariant());
+}
+
+void ResourceWithMetadata::LoadMetadataFromJSON(const JSONArray& array)
+{
+    for (unsigned i = 0; i < array.Size(); i++)
+    {
+        const JSONValue& value = array.At(i);
+        AddMetadata(value.Get("name").GetString(), value.GetVariant());
+    }
+}
+
+void ResourceWithMetadata::SaveMetadataToXML(XMLElement& destination) const
+{
+    for (unsigned i = 0; i < metadataKeys_.Size(); ++i)
+    {
+        XMLElement elem = destination.CreateChild("metadata");
+        elem.SetString("name", metadataKeys_[i]);
+        elem.SetVariant(GetMetadata(metadataKeys_[i]));
+    }
+}
+
+void ResourceWithMetadata::CopyMetadata(const ResourceWithMetadata& source)
+{
+    metadata_ = source.metadata_;
+    metadataKeys_ = source.metadataKeys_;
+}
+
 }
 }

+ 39 - 0
Source/Atomic/Resource/Resource.h

@@ -24,12 +24,14 @@
 
 
 #include "../Core/Object.h"
 #include "../Core/Object.h"
 #include "../Core/Timer.h"
 #include "../Core/Timer.h"
+#include "../Resource/JSONValue.h"
 
 
 namespace Atomic
 namespace Atomic
 {
 {
 
 
 class Deserializer;
 class Deserializer;
 class Serializer;
 class Serializer;
+class XMLElement;
 
 
 /// Asynchronous loading state of a resource.
 /// Asynchronous loading state of a resource.
 enum AsyncLoadState
 enum AsyncLoadState
@@ -106,6 +108,43 @@ private:
     AsyncLoadState asyncLoadState_;
     AsyncLoadState asyncLoadState_;
 };
 };
 
 
+/// Base class for resources that support arbitrary metadata stored. Metadata serialization shall be implemented in derived classes.
+class ATOMIC_API ResourceWithMetadata : public Resource
+{
+    ATOMIC_OBJECT(ResourceWithMetadata, Resource);
+
+public:
+    /// Construct.
+    ResourceWithMetadata(Context* context) : Resource(context) {}
+
+    /// Add new metadata variable or overwrite old value.
+    void AddMetadata(const String& name, const Variant& value);
+    /// Remove metadata variable.
+    void RemoveMetadata(const String& name);
+    /// Remove all metadata variables.
+    void RemoveAllMetadata();
+    /// Return metadata variable.
+    const Variant& GetMetadata(const String& name) const;
+    /// Return whether the resource has metadata.
+    bool HasMetadata() const;
+
+protected:
+    /// Load metadata from <metadata> children of XML element.
+    void LoadMetadataFromXML(const XMLElement& source);
+    /// Load metadata from JSON array.float
+    void LoadMetadataFromJSON(const JSONArray& array);
+    /// Save as <metadata> children of XML element.
+    void SaveMetadataToXML(XMLElement& destination) const;
+    /// Copy metadata from another resource.
+    void CopyMetadata(const ResourceWithMetadata& source);
+
+private:
+    /// Animation metadata variables.
+    VariantMap metadata_;
+    /// Animation metadata keys.
+    StringVector metadataKeys_;
+};
+
 inline const String& GetResourceName(Resource* resource)
 inline const String& GetResourceName(Resource* resource)
 {
 {
     return resource ? resource->GetName() : String::EMPTY;
     return resource ? resource->GetName() : String::EMPTY;

+ 1 - 1
Source/Atomic/Resource/ResourceCache.cpp

@@ -907,7 +907,7 @@ String ResourceCache::SanitateResourceName(const String& nameIn) const
     if (resourceDirs_.Size())
     if (resourceDirs_.Size())
     {
     {
         String namePath = GetPath(name);
         String namePath = GetPath(name);
-        String exePath = fileSystem->GetProgramDir();
+        String exePath = fileSystem->GetProgramDir().Replaced("/./", "/");
         for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
         for (unsigned i = 0; i < resourceDirs_.Size(); ++i)
         {
         {
             String relativeResourcePath = resourceDirs_[i];
             String relativeResourcePath = resourceDirs_[i];

+ 2 - 2
Source/Atomic/Resource/ResourceCache.h

@@ -115,7 +115,7 @@ public:
     bool AddPackageFile(PackageFile* package, unsigned priority = PRIORITY_LAST);
     bool AddPackageFile(PackageFile* package, unsigned priority = PRIORITY_LAST);
     /// Add a package file for loading resources from by name. Optional priority parameter which will control search order.
     /// Add a package file for loading resources from by name. Optional priority parameter which will control search order.
     bool AddPackageFile(const String& fileName, unsigned priority = PRIORITY_LAST);
     bool AddPackageFile(const String& fileName, unsigned priority = PRIORITY_LAST);
-    /// Add a manually created resource. Must be uniquely named.
+    /// Add a manually created resource. Must be uniquely named within its type.
     bool AddManualResource(Resource* resource);
     bool AddManualResource(Resource* resource);
     /// Remove a resource load directory.
     /// Remove a resource load directory.
     void RemoveResourceDir(const String& pathName);
     void RemoveResourceDir(const String& pathName);
@@ -193,7 +193,7 @@ public:
     template <class T> bool BackgroundLoadResource(const String& name, bool sendEventOnFailure = true, Resource* caller = 0);
     template <class T> bool BackgroundLoadResource(const String& name, bool sendEventOnFailure = true, Resource* caller = 0);
     /// Template version of returning loaded resources of a specific type.
     /// Template version of returning loaded resources of a specific type.
     template <class T> void GetResources(PODVector<T*>& result) const;
     template <class T> void GetResources(PODVector<T*>& result) const;
-    /// Return whether a file exists by name.
+    /// Return whether a file exists in the resource directories or package files. Does not check manually added in-memory resources.
     bool Exists(const String& name) const;
     bool Exists(const String& name) const;
     /// Return memory budget for a resource type.
     /// Return memory budget for a resource type.
     unsigned long long GetMemoryBudget(StringHash type) const;
     unsigned long long GetMemoryBudget(StringHash type) const;

+ 15 - 4
Source/Atomic/Scene/Node.cpp

@@ -1188,6 +1188,14 @@ void Node::RemoveListener(Component* component)
     }
     }
 }
 }
 
 
+Vector3 Node::GetSignedWorldScale() const
+{
+    if (dirty_)
+        UpdateWorldTransform();
+
+    return worldTransform_.SignedScale(worldRotation_.RotationMatrix());
+}
+
 Vector3 Node::LocalToWorld(const Vector3& position) const
 Vector3 Node::LocalToWorld(const Vector3& position) const
 {
 {
     return GetWorldTransform() * position;
     return GetWorldTransform() * position;
@@ -1933,7 +1941,7 @@ Animatable* Node::FindAttributeAnimationTarget(const String& name, String& outNa
         {
         {
             if (names[i].Front() != '#')
             if (names[i].Front() != '#')
                 break;
                 break;
-            
+
             String name = names[i].Substring(1, names[i].Length() - 1);
             String name = names[i].Substring(1, names[i].Length() - 1);
             char s = name.Front();
             char s = name.Front();
             if (s >= '0' && s <= '9')
             if (s >= '0' && s <= '9')
@@ -1945,7 +1953,7 @@ Animatable* Node::FindAttributeAnimationTarget(const String& name, String& outNa
             {
             {
                 node = node->GetChild(name, true);
                 node = node->GetChild(name, true);
             }
             }
-            
+
             if (!node)
             if (!node)
             {
             {
                 ATOMIC_LOGERROR("Could not find node by name " + name);
                 ATOMIC_LOGERROR("Could not find node by name " + name);
@@ -2114,9 +2122,12 @@ void Node::UpdateWorldTransform() const
 
 
 void Node::RemoveChild(Vector<SharedPtr<Node> >::Iterator i)
 void Node::RemoveChild(Vector<SharedPtr<Node> >::Iterator i)
 {
 {
-    // Send change event. Do not send when already being destroyed
-    Node* child = *i;
+    // Keep a shared pointer to the child about to be removed, to make sure the erase from container completes first. Otherwise
+    // it would be possible that other child nodes get removed as part of the node's components' cleanup, causing a re-entrant
+    // erase and a crash
+    SharedPtr<Node> child(*i);
 
 
+    // Send change event. Do not send when this node is already being destroyed
     if (Refs() > 0 && scene_)
     if (Refs() > 0 && scene_)
     {
     {
         using namespace NodeRemoved;
         using namespace NodeRemoved;

+ 3 - 0
Source/Atomic/Scene/Node.h

@@ -464,6 +464,9 @@ public:
         return worldTransform_.Scale();
         return worldTransform_.Scale();
     }
     }
 
 
+    /// Return signed scale in world space. Utilized for Urho2D physics.
+    Vector3 GetSignedWorldScale() const;
+
     /// Return scale in world space (for Atomic2D).
     /// Return scale in world space (for Atomic2D).
     Vector2 GetWorldScale2D() const
     Vector2 GetWorldScale2D() const
     {
     {

+ 3 - 0
Source/Atomic/Scene/ValueAnimationInfo.h

@@ -22,7 +22,9 @@
 
 
 #pragma once
 #pragma once
 
 
+#include "../Container/Ptr.h"
 #include "../Container/RefCounted.h"
 #include "../Container/RefCounted.h"
+#include "../Container/Vector.h"
 #include "../Scene/AnimationDefs.h"
 #include "../Scene/AnimationDefs.h"
 
 
 namespace Atomic
 namespace Atomic
@@ -30,6 +32,7 @@ namespace Atomic
 
 
 class Object;
 class Object;
 class ValueAnimation;
 class ValueAnimation;
+class Variant;
 struct VAnimEventFrame;
 struct VAnimEventFrame;
 
 
 /// Base class for a value animation instance, which includes animation runtime information and updates the target object's value automatically.
 /// Base class for a value animation instance, which includes animation runtime information and updates the target object's value automatically.

+ 1 - 1
Source/AtomicNET/NETNative/CMakeLists.txt

@@ -32,7 +32,7 @@ target_include_directories(AtomicNETNative PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
 
 
 if (APPLE)
 if (APPLE)
     if (NOT IOS)
     if (NOT IOS)
-        target_link_libraries( AtomicNETNative "-stdlib=libc++ -framework AudioToolbox -framework Carbon -framework Cocoa -framework CoreAudio -framework CoreVideo -framework ForceFeedback -framework IOKit -framework OpenGL -framework CoreServices -framework Security")
+        target_link_libraries( AtomicNETNative "-stdlib=libc++ -framework AudioToolbox -framework Carbon -framework Cocoa -framework CoreAudio -framework CoreVideo -framework ForceFeedback -framework IOKit -framework OpenGL -framework CoreServices -framework Security -framework SystemConfiguration")
     else()
     else()
         set_target_properties(AtomicNETNative PROPERTIES
         set_target_properties(AtomicNETNative PROPERTIES
             FRAMEWORK TRUE
             FRAMEWORK TRUE

Some files were not shown because too many files changed in this diff