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/Haxe/*
 **/.vscode
+.idea

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

@@ -20,4 +20,4 @@ else ()
 endif ()
 
 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;
     }
 
+    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
     //specular  = the rgb specular color value of the pixel
     //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
@@ -42,13 +53,19 @@
         return 0.5 / (lambdaV + lambdaL);
     }
 
+    float NeumannVisibility(float NdotV, float NdotL) 
+    {
+        return NdotL * NdotV / max(1e-7, max(NdotL, NdotV));
+    }
+
     // Get Visibility
     // NdotL        = the dot product of the normal and direction to the light
     // NdotV        = the dot product of the normal and the camera view direction
     // roughness    = the roughness of the pixel
     float Visibility(float NdotL, float NdotV, float roughness)
     {
-        return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
+        return NeumannVisibility(NdotV, NdotL);
+        //return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
     }
 
     // Blinn Distribution
@@ -96,9 +113,20 @@
     // 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 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
@@ -127,8 +155,9 @@
     // VdotH        = the camera view direction dot with the half vector
     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

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

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

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

@@ -229,17 +229,16 @@
     ///     normal: surface normal
     ///     reflection: vector of reflection off of the surface
     ///     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 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
     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);
         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"
 #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 reflectVec  = reflect(-toCamera, normal);
         
@@ -60,20 +92,22 @@
         vec3 l = normalize(closestPoint);
         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 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 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);
 
-        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
@@ -87,10 +121,11 @@
 	vec3 GetBRDF(vec3 worldPos, vec3 lightDir, vec3 lightVec, vec3 toCamera, vec3 normal, float roughness, vec3 diffColor, vec3 specColor)
 	{
         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 specularFactor = vec3(0.0, 0.0, 0.0);
@@ -100,22 +135,23 @@
             {
                 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;
                 }
                 else
                 {
-                    specularFactor = SphereLight(worldPos, lightVec, normal, toCamera, roughness, specColor, ndl);
+                    specularFactor = SphereLight(worldPos, lightVec, normal, toCamera, roughness, specColor, diffColor, ndl);
                     specularFactor *= ndl;
                 }
             }
             else
             {
-                vec3 fresnelTerm = Fresnel(specColor, vdh) ;
+                vec3 fresnelTerm = Fresnel(specColor, vdh, ldh) ;
                 float distTerm = Distribution(ndh, roughness);
                 float visTerm = Visibility(ndl, ndv, roughness);
 
                 specularFactor = fresnelTerm * distTerm * visTerm  / M_PI;
+                return diffuseFactor + specularFactor;
             }
         #endif
 

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

@@ -21,12 +21,23 @@
         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
     //specular  = the rgb specular color value of the pixel
     //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
@@ -42,13 +53,19 @@
         return 0.5 / (lambdaV + lambdaL);
     }
 
+    float NeumannVisibility(float NdotV, float NdotL) 
+    {
+        return NdotL * NdotV / max(1e-7, max(NdotL, NdotV));
+    }
+
     // Get Visibility
     // NdotL        = the dot product of the normal and direction to the light
     // NdotV        = the dot product of the normal and the camera view direction
     // roughness    = the roughness of the pixel
     float Visibility(float NdotL, float NdotV, float roughness)
     {
-        return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
+        return NeumannVisibility(NdotV, NdotL);
+        //return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
     }
 
     // GGX Distribution
@@ -96,9 +113,20 @@
     // 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 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
@@ -127,8 +155,9 @@
     // VdotH        = the camera view direction dot with the half vector
     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

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

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

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

@@ -220,17 +220,16 @@
     ///     normal: surface normal
     ///     reflection: vector of reflection off of the surface
     ///     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 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
     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);
         const float ndv = saturate(dot(-toCamera, wsNormal));
 

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

@@ -1,40 +1,71 @@
 #include "BRDF.hlsl"
 #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 hdv = dot(h, 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 reflectVec  = reflect(-toCamera, normal);
         
@@ -66,16 +97,18 @@
         float hdn = saturate(dot(h, normal));
         float hdv = dot(h, toCamera);
         float ndv = saturate(dot(normal, toCamera));
+        float hdl = saturate(dot(h, lightVec));
 
         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);
 
-        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
@@ -94,6 +127,7 @@
         const float ndh = clamp((dot(normal, Hn)), 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 ldh = clamp((dot(lightVec, Hn)), M_EPSILON, 1.0);
 
         const float3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, vdh)  * ndl;
         float3 specularFactor = 0;
@@ -103,21 +137,21 @@
             {
                 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
                 {
-                    specularFactor = SphereLight(worldPos, lightVec, normal, toCamera, roughness, specColor, ndl);
-                    specularFactor *= ndl;
+                    return SphereLight(worldPos, lightVec, normal, toCamera, roughness, specColor, diffColor, ndl);
                 }
             }
             else
             {
-                const float3 fresnelTerm = Fresnel(specColor, vdh) ;
+                const float3 fresnelTerm = Fresnel(specColor, vdh, ldh) ;
                 const float distTerm = Distribution(ndh, roughness);
                 const float visTerm = Visibility(ndl, ndv, roughness);
                 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+                return diffuseFactor + specularFactor;
             }
 
         #endif

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

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

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

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

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

@@ -303,7 +303,8 @@ void CollisionShape2D::OnNodeSet(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_;
     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()),
         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()
@@ -168,6 +169,15 @@ void ParticleEmitter2D::SetSpriteAttr(const ResourceRef& value)
         SetSprite(sprite);
 }
 
+void ParticleEmitter2D::SetEmitting(bool enable)
+{
+    if (enable != emitting_)
+    {
+        emitting_ = enable;
+        emitParticleTime_ = 0.0f;
+    }
+}
+
 ResourceRef ParticleEmitter2D::GetSpriteAttr() const
 {
     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();
 
@@ -464,17 +474,4 @@ void ParticleEmitter2D::UpdateParticle(Particle2D& particle, float timeStep, con
     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);
     /// Set max particles.
     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.
     ParticleEffect2D* GetEffect() const;
@@ -116,15 +118,8 @@ public:
     void SetSpriteAttr(const ResourceRef& value);
     /// Return sprite attribute.
     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:
     /// Handle scene being assigned.
@@ -158,20 +153,14 @@ private:
     float emissionTime_;
     /// Emit particle time
     float emitParticleTime_;
+    /// Currently emitting flag.
+    bool emitting_;
     /// Particles.
     Vector<Particle2D> particles_;
     /// Bounding box min point.
     Vector3 boundingBoxMinPoint_;
     /// Bounding box max point.
     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_NODEA, NodeA);                  // 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_SHAPEB, ShapeB);                // CollisionShape2D pointer
     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_NODEA, NodeA);                  // 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_SHAPEB, ShapeB);                // CollisionShape2D pointer
 }
@@ -68,8 +66,7 @@ ATOMIC_EVENT(E_PHYSICSENDCONTACT2D, PhysicsEndContact2D)
     ATOMIC_PARAM(P_BODYB, BodyB);                  // RigidBody2D pointer
     ATOMIC_PARAM(P_NODEA, NodeA);                  // 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_SHAPEB, ShapeB);                // CollisionShape2D pointer
 }
@@ -80,8 +77,7 @@ ATOMIC_EVENT(E_NODEUPDATECONTACT2D, NodeUpdateContact2D)
     ATOMIC_PARAM(P_BODY, Body);                    // RigidBody2D pointer
     ATOMIC_PARAM(P_OTHERNODE, OtherNode);          // Node 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_OTHERSHAPE, OtherShape);        // CollisionShape2D pointer
     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_OTHERNODE, OtherNode);          // Node 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_OTHERSHAPE, OtherShape);        // CollisionShape2D pointer
 }
@@ -105,8 +100,7 @@ ATOMIC_EVENT(E_NODEENDCONTACT2D, NodeEndContact2D)
     ATOMIC_PARAM(P_BODY, Body);                    // RigidBody2D pointer
     ATOMIC_PARAM(P_OTHERNODE, OtherNode);          // Node 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_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_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) :
     Component(context),
     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_NODEA] = contactInfo.nodeA_.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_SHAPEB] = contactInfo.shapeB_.Get();
 
@@ -202,8 +177,7 @@ void PhysicsWorld2D::PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
 
     // Send node event
     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_)
     {
@@ -781,15 +755,13 @@ void PhysicsWorld2D::SendBeginContactEvents()
         eventData[P_BODYB] = contactInfo.bodyB_.Get();
         eventData[P_NODEA] = contactInfo.nodeA_.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_SHAPEB] = contactInfo.shapeB_.Get();
 
         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_)
         {
@@ -834,15 +806,13 @@ void PhysicsWorld2D::SendEndContactEvents()
         eventData[P_BODYB] = contactInfo.bodyB_.Get();
         eventData[P_NODEA] = contactInfo.nodeA_.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_SHAPEB] = contactInfo.shapeB_.Get();
 
         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_)
         {
@@ -882,20 +852,31 @@ PhysicsWorld2D::ContactInfo::ContactInfo(b2Contact* contact)
     bodyB_ = (RigidBody2D*)(fixtureB->GetBody()->GetUserData());
     nodeA_ = bodyA_->GetNode();
     nodeB_ = bodyB_->GetNode();
-    contact_ = contact;
     shapeA_ = (CollisionShape2D*)fixtureA->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();
         /// Construct.
         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.
         SharedPtr<RigidBody2D> bodyA_;
@@ -267,12 +267,18 @@ protected:
         SharedPtr<Node> nodeA_;
         /// Node B.
         SharedPtr<Node> nodeB_;
-        /// Box2D contact.
-        b2Contact* contact_;
         /// Shape A.
         SharedPtr<CollisionShape2D> shapeA_;
         /// Shape B.
         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.
     Vector<ContactInfo> beginContactInfos_;

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

@@ -111,7 +111,7 @@ void RigidBody2D::OnSetEnabled()
 
 void RigidBody2D::SetBodyType(BodyType2D type)
 {
-    b2BodyType bodyType = (b2BodyType)type;    
+    b2BodyType bodyType = (b2BodyType)type;
     if (body_)
     {
         body_->SetType(bodyType);
@@ -321,7 +321,7 @@ void RigidBody2D::SetLinearVelocity(const Vector2& linearVelocity)
 void RigidBody2D::SetAngularVelocity(float angularVelocity)
 {
     if (body_)
-        body_->SetAngularVelocity(angularVelocity); 
+        body_->SetAngularVelocity(angularVelocity);
     else
     {
         if (bodyDef_.angularVelocity == angularVelocity)
@@ -377,7 +377,7 @@ void RigidBody2D::CreateBody()
     if (!physicsWorld_ || !physicsWorld_->GetWorld())
         return;
 
-    bodyDef_.position = ToB2Vec2(node_->GetWorldPosition());;
+    bodyDef_.position = ToB2Vec2(node_->GetWorldPosition());
     bodyDef_.angle = node_->GetWorldRotation().RollAngle() * M_DEGTORAD;
 
     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()
     b2Vec2 newPosition = ToB2Vec2(node_->GetWorldPosition());
     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_)
     {
         ATOMIC_LOGERROR("Could not load texture " + loadTextureName_);
-        loadXMLFile_.Reset();
+        loadPListFile_.Reset();
         loadTextureName_.Clear();
         return false;
     }
@@ -191,7 +191,7 @@ bool SpriteSheet2D::EndLoadFromPListFile()
         DefineSprite(name, rectangle, hotSpot, offset);
     }
 
-    loadXMLFile_.Reset();
+    loadPListFile_.Reset();
     loadTextureName_.Clear();
     return true;
 }

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

@@ -666,6 +666,17 @@ bool TmxFile2D::LoadTileSet(const XMLElement& element)
             propertySet->Load(tileElem.GetChild("properties"));
             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
 

+ 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)
 {
     if (!decoder_)

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

@@ -39,6 +39,9 @@ public:
     /// Destruct.
     ~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.
     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;
 
 Sound::Sound(Context* context) :
-    Resource(context),
+    ResourceWithMetadata(context),
     repeat_(0),
     end_(0),
     dataSize_(0),
@@ -348,9 +348,9 @@ void Sound::LoadParameters()
         return;
 
     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();
 
@@ -373,8 +373,6 @@ void Sound::LoadParameters()
             if (paramElem.HasAttribute("start") && paramElem.HasAttribute("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;
 
 /// %Sound resource.
-class ATOMIC_API Sound : public Resource
+class ATOMIC_API Sound : public ResourceWithMetadata
 {
-    ATOMIC_OBJECT(Sound, Resource);
+    ATOMIC_OBJECT(Sound, ResourceWithMetadata);
 
 public:
     /// 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);
 }
 
+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)
 {
     if (!audio_)

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

@@ -48,6 +48,8 @@ public:
     /// Register object factory.
     static void RegisterObject(Context* context);
 
+    /// Seek to time.
+    void Seek(float seekTime);
     /// Play a sound.
     void Play(Sound* sound);
     /// 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)
 {
     frequency_ = frequency;

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

@@ -38,6 +38,9 @@ public:
     /// Destruct.
     ~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.
     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)
         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)
 {

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

@@ -67,8 +67,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, in
     Atomic::ParseArguments(GetCommandLineW()); \
     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) \
 extern "C" 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 "../Core/ProcessUtils.h"
+#include "../Core/StringUtils.h"
 #include "../IO/FileSystem.h"
 
 #include <cstdio>
@@ -33,21 +34,32 @@
 #endif
 
 #if defined(IOS)
-#include "../Math/MathDefs.h"
 #include <mach/mach_host.h>
+#elif defined(TVOS)
+extern "C" unsigned SDL_TVOS_GetActiveProcessorCount();
 #elif !defined(__linux__) && !defined(__EMSCRIPTEN__)
-// ATOMIC BEGIN
-#include <LibCpuId/src/libcpuid.h>
-// ATOMIC END
+#include <libcpuid.h>
 #endif
 
-#ifdef _WIN32
+#if defined(_WIN32)
 #include <windows.h>
 #include <io.h>
 #if defined(_MSC_VER)
 #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
-#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>
 #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)
 {
     if (cpu_identify(0, data) < 0)
@@ -206,7 +218,7 @@ void OpenConsoleWindow()
 
 void PrintUnicode(const String& str, bool error)
 {
-#if !defined(__ANDROID__) && !defined(IOS)
+#if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
 #ifdef _WIN32
     // If the output stream has been redirected, use fprintf instead of WriteConsoleW,
     // 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)
 {
-#if !defined(__ANDROID__) && !defined(IOS)
+#if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
     fprintf(error ? stderr : stdout, "%s\n", str.CString());
 #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);
     fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
     for (;;)
@@ -396,10 +408,12 @@ String GetPlatform()
     return "Android";
 #elif defined(IOS)
     return "iOS";
+#elif defined(TVOS)
+    return "tvOS";
+#elif defined(__APPLE__)
+    return "macOS";
 #elif defined(_WIN32)
     return "Windows";
-#elif defined(__APPLE__)
-    return "Mac OS X";
 #elif defined(RPI)
     return "Raspberry Pi";
 #elif defined(__EMSCRIPTEN__)
@@ -416,12 +430,18 @@ unsigned GetNumPhysicalCPUs()
 #if defined(IOS)
     host_basic_info_data_t 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
     return Min(2, data.physical_cpu);
 #else
     return data.physical_cpu;
 #endif
+#elif defined(TVOS)
+#if defined(TARGET_OS_SIMULATOR)
+    return Min(2, SDL_TVOS_GetActiveProcessorCount());
+#else
+    return SDL_TVOS_GetActiveProcessorCount();
+#endif
 #elif defined(__linux__)
     struct CpuCoreCount data;
     GetCPUData(&data);
@@ -444,11 +464,17 @@ unsigned GetNumLogicalCPUs()
 #if defined(IOS)
     host_basic_info_data_t data;
     GetCPUData(&data);
-#if defined(TARGET_IPHONE_SIMULATOR)
+#if defined(TARGET_OS_SIMULATOR)
     return Min(2, data.logical_cpu);
 #else
     return data.logical_cpu;
 #endif
+#elif defined(TVOS)
+#if defined(TARGET_OS_SIMULATOR)
+    return Min(2, SDL_TVOS_GetActiveProcessorCount());
+#else
+    return SDL_TVOS_GetActiveProcessorCount();
+#endif
 #elif defined(__linux__)
     struct CpuCoreCount data;
     GetCPUData(&data);
@@ -504,4 +530,196 @@ void QuoteArguments(Vector<String>& args)
 
 // 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();
 /// Read input from the console window. Return empty if no input.
 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();
 /// Return the number of physical CPU cores.
 ATOMIC_API unsigned GetNumPhysicalCPUs();
@@ -69,6 +69,14 @@ ATOMIC_API unsigned GetNumLogicalCPUs();
 ATOMIC_API void SetMiniDumpDir(const String& pathName);
 /// Return minidump write location.
 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
 
@@ -76,5 +84,4 @@ ATOMIC_API String GetMiniDumpDir();
 ATOMIC_API void QuoteArguments(Vector<String>& args);
 
 // ATOMIC END
-
 }

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

@@ -29,7 +29,7 @@
 #include "../Core/Profiler.h"
 // ATOMIC END
 
-#ifdef IOS
+#if defined(IOS) || defined(TVOS)
 #include "../Graphics/Graphics.h"
 // ATOMIC BEGIN
 #include <SDL/include/SDL.h>
@@ -50,7 +50,7 @@ namespace Atomic
 bool Application::autoMetrics_ = false;
 // 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()
 #if defined(__EMSCRIPTEN__)
 #include <emscripten/emscripten.h>
@@ -112,16 +112,16 @@ int Application::Run()
         if (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())
             engine_->RunFrame();
 
         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
 #else
-#if defined(IOS)
+#if defined(IOS) || defined(TVOS)
         SDL_iPhoneSetAnimationCallback(GetSubsystem<Graphics>()->GetWindow(), 1, &RunFrame, engine_);
 #elif defined(__EMSCRIPTEN__)
         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
-#ifndef IOS
+#if !defined(IOS) && !defined(TVOS)
 #define ATOMIC_DEFINE_APPLICATION_MAIN(className) \
 int RunApplication() \
 { \
@@ -92,7 +92,7 @@ int RunApplication() \
 } \
 ATOMIC_DEFINE_MAIN(RunApplication());
 #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) \
 int RunApplication() \
 { \

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

@@ -66,6 +66,7 @@
 #endif
 #ifdef ATOMIC_PHYSICS
 #include "../Physics/PhysicsWorld.h"
+#include "../Physics/RaycastVehicle.h"
 #endif
 #include "../Resource/ResourceCache.h"
 #include "../Resource/Localization.h"
@@ -113,7 +114,7 @@ Engine::Engine(Context* context) :
     timeStep_(0.0f),
     timeStepSmoothing_(2),
     minFps_(10),
-#if defined(IOS) || defined(__ANDROID__) || defined(__arm__) || defined(__aarch64__)
+#if defined(IOS) || defined(TVOS) || defined(__ANDROID__) || defined(__arm__) || defined(__aarch64__)
     maxFps_(60),
     maxInactiveFps_(10),
     pauseMinimized_(true),
@@ -668,7 +669,7 @@ void Engine::SetPauseMinimized(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
-#if defined(__ANDROID__) || defined(IOS)
+#if defined(__ANDROID__) || defined(IOS) || defined(TVOS)
     enable = true;
 #endif
     autoExit_ = enable;
@@ -681,8 +682,8 @@ void Engine::SetNextTimeStep(float seconds)
 
 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
     DoExit();
 #endif
@@ -806,10 +807,10 @@ void Engine::ApplyFrameLimit()
 
 #ifndef __EMSCRIPTEN__
     // Perform waiting loop if maximum FPS set
-#ifndef IOS
+#if !defined(IOS) && !defined(TVOS)
     if (maxFps)
 #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
     if (maxFps < 60)
 #endif
@@ -923,6 +924,8 @@ VariantMap Engine::ParseParameters(const Vector<String>& arguments)
                 ret[EP_FULL_SCREEN] = false;
             else if (argument == "borderless")
                 ret[EP_BORDERLESS] = true;
+            else if (argument == "lowdpi")
+                ret[EP_HIGH_DPI] = false;
             else if (argument == "s")
                 ret[EP_WINDOW_RESIZABLE] = true;
             else if (argument == "hd")

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

@@ -66,7 +66,7 @@ public:
     void SetAutoExit(bool enable);
     /// Override timestep of the next frame. Should be called in between RunFrame() calls.
     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();
     /// Dump information of all resources to the log.
     void DumpResources(bool dumpFileName = false);

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

@@ -611,11 +611,9 @@ void AnimatedModel::SetMorphWeight(unsigned index, float weight)
         return;
 
     // If morph vertex buffers have not been created yet, create now
-    if (weight > 0.0f && morphVertexBuffers_.Empty())
+    if (weight != 0.0f && morphVertexBuffers_.Empty())
         CloneGeometries();
 
-    weight = Clamp(weight, 0.0f, 1.0f);
-
     if (weight != morphs_[index].weight_)
     {
         morphs_[index].weight_ = weight;
@@ -1463,7 +1461,7 @@ void AnimatedModel::UpdateMorphs()
 
                     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);
                             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) :
-    Resource(context),
+    ResourceWithMetadata(context),
     length_(0.f)
 {
 }
@@ -173,17 +173,16 @@ bool Animation::BeginLoad(Deserializer& source)
     if (file)
     {
         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"))
                 AddTrigger(triggerElem.GetFloat("normalizedtime"), true, triggerElem.GetVariant());
             else if (triggerElem.HasAttribute("time"))
                 AddTrigger(triggerElem.GetFloat("time"), false, triggerElem.GetVariant());
-
-            triggerElem = triggerElem.GetNext("trigger");
         }
 
+        LoadMetadataFromXML(rootElem);
+
         memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
         SetMemoryUse(memoryUse);
         return true;
@@ -196,7 +195,7 @@ bool Animation::BeginLoad(Deserializer& source)
     if (jsonFile)
     {
         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++)
         {
@@ -212,6 +211,9 @@ bool Animation::BeginLoad(Deserializer& source)
             }
         }
 
+        const JSONArray& metadataArray = rootVal.Get("metadata").GetArray();
+        LoadMetadataFromJSON(metadataArray);
+
         memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
         SetMemoryUse(memoryUse);
         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_.Size())
+    if (!triggers_.Empty() || HasMetadata())
     {
         File* destFile = dynamic_cast<File*>(&dest);
         if (destFile)
@@ -269,6 +271,8 @@ bool Animation::Save(Serializer& dest) const
                 triggerElem.SetVariant(triggers_[i].data_);
             }
 
+            SaveMetadataToXML(rootElem);
+
             File xmlFile(context_, xmlName, FILE_WRITE);
             xml->Save(xmlFile);
         }
@@ -373,29 +377,29 @@ SharedPtr<Animation> Animation::Clone(const String& cloneName) const
     ret->length_ = length_;
     ret->tracks_ = tracks_;
     ret->triggers_ = triggers_;
+    ret->CopyMetadata(*this);
     ret->SetMemoryUse(GetMemoryUse());
-    
+
     return ret;
-} 
+}
 
-AnimationTrack* Animation::GetTrack(unsigned index) 
-{ 
-    if (index >= GetNumTracks()) 
+AnimationTrack* Animation::GetTrack(unsigned index)
+{
+    if (index >= GetNumTracks())
         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;
     }
-    
+
     return (AnimationTrack*) 0;
 }
 
-
 AnimationTrack* Animation::GetTrack(const String& 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;
 
 /// Skeletal animation resource.
-class ATOMIC_API Animation : public Resource
+class ATOMIC_API Animation : public ResourceWithMetadata
 {
-    ATOMIC_OBJECT(Animation, Resource);
+    ATOMIC_OBJECT(Animation, ResourceWithMetadata);
 
 public:
     /// Construct.
@@ -162,9 +162,9 @@ public:
     const HashMap<StringHash, AnimationTrack>& GetTracks() const { return 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);
 
     /// Return animation track by name.

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

@@ -212,6 +212,8 @@ Graphics::Graphics(Context* context) :
     resizable_(false),
     highDPI_(false),
     vsync_(false),
+    monitor_(0),
+    refreshRate_(0),
     tripleBuffer_(false),
     flushGPU_(false),
     forceGL2_(false),
@@ -382,6 +384,7 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
 
     AdjustWindow(width, height, fullscreen, borderless, monitor);
     monitor_ = monitor;
+    refreshRate_ = refreshRate;
 
     if (maximize)
     {
@@ -2110,13 +2113,15 @@ void Graphics::AdjustWindow(int& newWidth, int& newHeight, bool& newFullscreen,
         }
         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_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);
         }
         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);
             }
 

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

@@ -220,7 +220,7 @@ PODVector<IntVector3> Graphics::GetResolutions(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_GetDesktopDisplayMode(monitor, &mode);
     return IntVector2(mode.w, mode.h);

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

@@ -31,7 +31,7 @@ namespace Atomic
 class Vector3;
 
 /// 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
 #else
 #define DESKTOP_GRAPHICS

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

@@ -31,6 +31,9 @@
 #include "../Graphics/VertexBuffer.h"
 #include "../IO/Log.h"
 #include "../IO/File.h"
+#include "../IO/FileSystem.h"
+#include "../Resource/ResourceCache.h"
+#include "../Resource/XMLFile.h"
 
 #include "../DebugNew.h"
 
@@ -58,7 +61,7 @@ unsigned LookupIndexBuffer(IndexBuffer* buffer, const Vector<SharedPtr<IndexBuff
 }
 
 Model::Model(Context* context) :
-    Resource(context)
+    ResourceWithMetadata(context)
 {
 }
 
@@ -318,25 +321,28 @@ bool Model::BeginLoad(Deserializer& source)
     memoryUse += sizeof(Vector3) * geometries_.Size();
 
 // 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;
 }
 
@@ -497,6 +503,25 @@ bool Model::Save(Serializer& dest) const
 
     // 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;
 }
 

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

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

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

@@ -236,6 +236,8 @@ Graphics::Graphics(Context* context_) :
     resizable_(false),
     highDPI_(false),
     vsync_(false),
+    monitor_(0),
+    refreshRate_(0),
     tripleBuffer_(false),
     sRGB_(false),
     forceGL2_(false),
@@ -429,9 +431,10 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
         SDL_Rect display_rect;
         SDL_GetDisplayBounds(monitor, &display_rect);
         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;
         if (fullscreen)
@@ -496,8 +499,8 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
     // Set vsync
     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_);
 #endif
 
@@ -508,6 +511,8 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
     vsync_ = vsync;
     tripleBuffer_ = tripleBuffer;
     multiSample_ = multiSample;
+    monitor_ = monitor;
+    refreshRate_ = refreshRate;
 
     SDL_GL_GetDrawableSize(window_, &width_, &height_);
     if (!fullscreen)
@@ -2087,8 +2092,8 @@ bool Graphics::GetDither() 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)
         return true;
 #endif
@@ -2418,7 +2423,7 @@ void Graphics::Release(bool clearGPUObjects, bool closeWindow)
     impl_->depthTextures_.Clear();
 
     // 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_)
         SDL_SetWindowFullscreen(window_, 0);
 #endif
@@ -2511,7 +2516,7 @@ void Graphics::Restore()
         }
 #endif
 
-#ifdef IOS
+#if defined(IOS) || defined(TVOS)
         glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&impl_->systemFBO_);
 #endif
 
@@ -2830,8 +2835,8 @@ void Graphics::CheckFeatureSupport()
     if (numSupportedRTs >= 4)
         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
     // screen mode, and incomplete shadow maps in windowed mode
     String renderer((const char*)glGetString(GL_RENDERER));
@@ -2870,9 +2875,8 @@ void Graphics::CheckFeatureSupport()
     }
     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;
 #endif
         shadowMapFormat_ = GL_DEPTH_COMPONENT;

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

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

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

@@ -460,8 +460,8 @@ bool Texture2D::Create()
         requestedLevels_ = 1;
     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;
 #else
         if (requestedLevels_ != 1)

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

@@ -485,8 +485,8 @@ bool TextureCube::Create()
         requestedLevels_ = 1;
     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;
 #else
         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("Material", GetMaterialAttr, SetMaterialAttr, ResourceRef, ResourceRef(Material::GetTypeStatic()),
         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_ENUM_ATTRIBUTE("Text Alignment", text_.textAlignment_, horizontalAlignments, HA_LEFT, AM_DEFAULT);
     ATOMIC_ATTRIBUTE("Row Spacing", float, text_.rowSpacing_, 1.0f, AM_DEFAULT);
@@ -185,7 +185,7 @@ void Text3D::SetMaterial(Material* material)
     UpdateTextMaterials(true);
 }
 
-bool Text3D::SetFont(const String& fontName, int size)
+bool Text3D::SetFont(const String& fontName, float size)
 {
     bool success = text_.SetFont(fontName, size);
 
@@ -198,7 +198,7 @@ bool Text3D::SetFont(const String& fontName, int size)
     return success;
 }
 
-bool Text3D::SetFont(Text3DFont* font, int size)
+bool Text3D::SetFont(Text3DFont* font, float size)
 {
     bool success = text_.SetFont(font, size);
 
@@ -209,7 +209,7 @@ bool Text3D::SetFont(Text3DFont* font, int size)
     return success;
 }
 
-bool Text3D::SetFontSize(int size)
+bool Text3D::SetFontSize(float size)
 {
     bool success = text_.SetFontSize(size);
 
@@ -369,7 +369,7 @@ Text3DFont* Text3D::GetFont() const
     return text_.GetFont();
 }
 
-int Text3D::GetFontSize() const
+float Text3D::GetFontSize() const
 {
     return text_.GetFontSize();
 }
@@ -454,12 +454,12 @@ int Text3D::GetRowWidth(unsigned index) const
     return text_.GetRowWidth(index);
 }
 
-IntVector2 Text3D::GetCharPosition(unsigned index)
+Vector2 Text3D::GetCharPosition(unsigned index)
 {
     return text_.GetCharPosition(index);
 }
 
-IntVector2 Text3D::GetCharSize(unsigned index)
+Vector2 Text3D::GetCharSize(unsigned index)
 {
     return text_.GetCharSize(index);
 }

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

@@ -55,11 +55,11 @@ public:
     virtual UpdateGeometryType GetUpdateGeometryType();
 
     /// 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.
-    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.
-    bool SetFontSize(int size);
+    bool SetFontSize(float size);
     /// Set material.
     void SetMaterial(Material* material);
     /// Set text. Text is assumed to be either ASCII or UTF8-encoded.
@@ -104,7 +104,7 @@ public:
     /// Return font.
     Text3DFont* GetFont() const;
     /// Return font size.
-    int GetFontSize() const;
+    float GetFontSize() const;
     /// Return material.
     Material* GetMaterial() const;
     /// Return text.
@@ -144,9 +144,9 @@ public:
     /// Return width of row by index.
     int GetRowWidth(unsigned index) const;
     /// 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.
-    IntVector2 GetCharSize(unsigned index);
+    Vector2 GetCharSize(unsigned index);
     /// Return corner color.
     const Color& GetColor(Text3DCorner corner) const;
     /// 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;
 
@@ -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();
 
-    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 topUV = texOffsetY * invTextureSize_.y_;
@@ -444,6 +419,29 @@ bool Text3DBatch::Merge(const Text3DBatch& batch)
     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)
 {
     if (batch.vertexEnd_ == batch.vertexStart_)

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

@@ -51,7 +51,7 @@ public:
     /// Restore UI element's default color.
     void SetDefaultColor();
     /// 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.
     void AddQuad(const Matrix3x4& transform, int x, int y, int width, int height, int texOffsetX, int texOffsetY, int texWidth = 0,
         int texHeight = 0);
@@ -67,7 +67,7 @@ public:
     /// Merge with another batch.
     bool Merge(const Text3DBatch& batch);
     /// Return an interpolated color for the element.
-    unsigned GetInterpolatedColor(int x, int y);
+    unsigned GetInterpolatedColor(float x, float y);
 
 
     /// 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();
 
@@ -253,7 +253,7 @@ bool Text3DBitmap::Load(Text3DFontFace* fontFace, bool usedGlyphs)
     for (unsigned i = 0; i < newImages.Size(); ++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 second = (i->first_) & 0xffff;
@@ -331,7 +331,7 @@ bool Text3DBitmap::Save(Serializer& dest, int pointSize, const String& indentati
     if (!kerningMapping_.Empty())
     {
         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");
             kerningElem.SetInt("first", i->first_ >> 16);

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

@@ -42,7 +42,7 @@ public:
     ~Text3DBitmap();
 
     /// 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.
     bool Load(Text3DFontFace* fontFace, bool usedGlyphs);
     /// 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
 {
 
-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) :
     Resource(context),
@@ -126,7 +135,7 @@ void Text3DFont::SetScaledGlyphOffset(const Vector2& offset)
     scaledOffset_ = offset;
 }
 
-Text3DFontFace* Text3DFont::GetFace(int pointSize)
+Text3DFontFace* Text3DFont::GetFace(float pointSize)
 {
     // In headless mode, always return null
     Graphics* graphics = GetSubsystem<Graphics>();
@@ -139,7 +148,9 @@ Text3DFontFace* Text3DFont::GetFace(int pointSize)
     else
         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->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()
@@ -208,23 +219,25 @@ void Text3DFont::LoadParameters()
     }
 }
 
-Text3DFontFace* Text3DFont::GetFaceFreeType(int pointSize)
+Text3DFontFace* Text3DFont::GetFaceFreeType(float pointSize)
 {
     SharedPtr<Text3DFontFace> newFace(new Text3DFreeType(this));
     if (!newFace->Load(&fontData_[0], fontDataSize_, pointSize))
         return 0;
 
-    faces_[pointSize] = newFace;
+    int key = FloatToFixed(pointSize);
+    faces_[key] = newFace;
     return newFace;
 }
 
-Text3DFontFace* Text3DFont::GetFaceBitmap(int pointSize)
+Text3DFontFace* Text3DFont::GetFaceBitmap(float pointSize)
 {
     SharedPtr<Text3DFontFace> newFace(new Text3DBitmap(this));
     if (!newFace->Load(&fontData_[0], fontDataSize_, pointSize))
         return 0;
 
-    faces_[pointSize] = newFace;
+    int key = FloatToFixed(pointSize);
+    faces_[key] = newFace;
     return newFace;
 }
 

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

@@ -66,7 +66,7 @@ public:
     void SetScaledGlyphOffset(const Vector2& offset);
 
     /// 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.
     Text3DFontType GetFontType() const { return fontType_; }
@@ -81,7 +81,7 @@ public:
     const Vector2& GetScaledGlyphOffset() const { return scaledOffset_; }
 
     /// 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.
     void ReleaseFaces();
@@ -90,9 +90,9 @@ private:
     /// Load font glyph offset parameters from an optional XML file. Called internally when loading TrueType fonts.
     void LoadParameters();
     /// 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.
-    Text3DFontFace* GetFaceBitmap(int pointSize);
+    Text3DFontFace* GetFaceBitmap(float pointSize);
 
     /// Created 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;
 }
 
-short Text3DFontFace::GetKerning(unsigned c, unsigned d) const
+float Text3DFontFace::GetKerning(unsigned c, unsigned d) const
 {
     if (kerningMapping_.Empty())
         return 0;
@@ -83,7 +83,7 @@ short Text3DFontFace::GetKerning(unsigned c, unsigned d) const
 
     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())
         return i->second_;
 

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

@@ -52,7 +52,7 @@ struct ATOMIC_API Text3DFontGlyph
     /// Glyph Y offset from origin.
     short offsetY_;
     /// Horizontal advance.
-    short advanceX_;
+    float advanceX_;
     /// Texture page. M_MAX_UNSIGNED if not yet resident on any texture.
     unsigned page_;
     /// Used flag.
@@ -73,7 +73,7 @@ public:
     ~Text3DFontFace();
 
     /// 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.
     virtual const Text3DFontGlyph* GetGlyph(unsigned c);
 
@@ -81,15 +81,15 @@ public:
     virtual bool HasMutableGlyphs() const { return false; }
 
     /// 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.
     bool IsDataLost() const;
 
     /// Return point size.
-    int GetPointSize() const { return pointSize_; }
+    float GetPointSize() const { return pointSize_; }
 
     /// Return row height.
-    int GetRowHeight() const { return rowHeight_; }
+    float GetRowHeight() const { return rowHeight_; }
 
     /// Return textures.
     const Vector<SharedPtr<Texture2D> >& GetTextures() const { return textures_; }
@@ -106,13 +106,13 @@ protected:
     /// Glyph mapping.
     HashMap<unsigned, Text3DFontGlyph> glyphMapping_;
     /// Kerning mapping.
-    HashMap<unsigned, short> kerningMapping_;
+    HashMap<unsigned, float> kerningMapping_;
     /// Glyph texture pages.
     Vector<SharedPtr<Texture2D> > textures_;
     /// Point size.
-    int pointSize_;
+    float pointSize_;
     /// Row height.
-    int rowHeight_;
+    float rowHeight_;
 };
 
 }

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

@@ -40,9 +40,9 @@
 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.
@@ -78,7 +78,9 @@ Text3DFreeType::Text3DFreeType(Text3DFont* font) :
     face_(0),
     loadMode_(FT_LOAD_DEFAULT),
     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();
 
@@ -138,7 +140,7 @@ bool Text3DFreeType::Load(const unsigned char* fontData, unsigned fontDataSize,
     face_ = face;
 
     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);
 
@@ -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
-    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;
 
     // 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);
     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);
     }
 
@@ -251,7 +267,7 @@ bool Text3DFreeType::Load(const unsigned char* fontData, unsigned fontDataSize,
                     {
                         unsigned leftIndex = deserializer.ReadUShort();
                         unsigned rightIndex = deserializer.ReadUShort();
-                        short amount = RoundToPixels(deserializer.ReadShort());
+                        short amount = FixedToFloat(deserializer.ReadShort());
 
                         unsigned leftCharCode = leftIndex < numGlyphs ? charCodes[leftIndex] : 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.offsetX_ = slot->bitmap_left;
         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;
@@ -371,13 +399,11 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
             int h = allocator_.GetHeight();
             if (!SetupNextTexture(w, h))
             {
-                ATOMIC_LOGWARNINGF("Text3DFreeType::LoadCharGlyph: failed to allocate new %dx%d texture", w, h);
                 return false;
             }
 
             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;
             }
         }

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

@@ -30,6 +30,18 @@ namespace Atomic
 class FreeTypeLibrary;
 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.
 class ATOMIC_API Text3DFreeType : public Text3DFontFace
 {
@@ -42,7 +54,7 @@ public:
     ~Text3DFreeType();
 
     /// 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.
     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.
     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:
     /// Setup next texture.
     bool SetupNextTexture(int textureWidth, int textureHeight);
@@ -65,13 +89,15 @@ private:
     /// Load mode.
     int loadMode_;
     /// Ascender.
-    int ascender_;
+    float ascender_;
     /// Has mutable glyph.
     bool hasMutableGlyph_;
     /// Glyph area allocator.
     AreaAllocator allocator_;
 
     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_UPDATE_ATTRIBUTE_DEFAULT_VALUE("Use Derived Opacity", false);
     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_ENUM_ATTRIBUTE("Text Alignment", textAlignment_, horizontalAlignments, HA_LEFT, 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);
         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)
         {
             // 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_)
                 {
@@ -297,7 +297,7 @@ void Text3DText::GetBatches(PODVector<Text3DBatch>& batches, PODVector<float>& v
                 {
                     float x = Cos(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
@@ -341,13 +341,13 @@ void Text3DText::OnIndentSet()
     charLocationsDirty_ = true;
 }
 
-bool Text3DText::SetFont(const String& fontName, int size)
+bool Text3DText::SetFont(const String& fontName, float size)
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     return SetFont(cache->GetResource<Text3DFont>(fontName), size);
 }
 
-bool Text3DText::SetFont(Text3DFont* font, int size)
+bool Text3DText::SetFont(Text3DFont* font, float size)
 {
     if (!font)
     {
@@ -365,7 +365,7 @@ bool Text3DText::SetFont(Text3DFont* font, int size)
     return true;
 }
 
-bool Text3DText::SetFontSize(int size)
+bool Text3DText::SetFontSize(float size)
 {
     // Initial font must be set
     if (!font_)
@@ -512,29 +512,29 @@ void Text3DText::SetEffectDepthBias(float bias)
     effectDepthBias_ = bias;
 }
 
-int Text3DText::GetRowWidth(unsigned index) const
+float Text3DText::GetRowWidth(unsigned index) const
 {
     return index < rowWidths_.Size() ? rowWidths_[index] : 0;
 }
 
-IntVector2 Text3DText::GetCharPosition(unsigned index)
+Vector2 Text3DText::GetCharPosition(unsigned index)
 {
     if (charLocationsDirty_)
         UpdateCharLocations();
     if (charLocations_.Empty())
-        return IntVector2::ZERO;
+        return Vector2::ZERO;
     // For convenience, return the position of the text ending if index exceeded
     if (index > charLocations_.Size() - 1)
         index = charLocations_.Size() - 1;
     return charLocations_[index].position_;
 }
 
-IntVector2 Text3DText::GetCharSize(unsigned index)
+Vector2 Text3DText::GetCharSize(unsigned index)
 {
     if (charLocationsDirty_)
         UpdateCharLocations();
     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)
     if (index > charLocations_.Size() - 2)
         index = charLocations_.Size() - 2;
@@ -583,7 +583,7 @@ void Text3DText::UpdateText(bool onResize)
         int width = 0;
         int height = 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
         if (!wordWrap_)
@@ -754,7 +754,7 @@ void Text3DText::UpdateCharLocations()
         return;
     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
     unsigned numChars = unicodeText_.Size();
@@ -767,19 +767,19 @@ void Text3DText::UpdateCharLocations()
 
     unsigned rowIndex = 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)
     {
         Text3DCharLocation loc;
-        loc.position_ = IntVector2(x, y);
+        loc.position_ = Vector2(x, y);
 
         unsigned c = printText_[i];
         if (c != '\n')
         {
             const Text3DFontGlyph* glyph = face->GetGlyph(c);
-            loc.size_ = IntVector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
+            loc.size_ = Vector2(glyph ? glyph->advanceX_ : 0, rowHeight_);
             if (glyph)
             {
                 // Store glyph's location for rendering. Verify that glyph page is valid
@@ -792,7 +792,7 @@ void Text3DText::UpdateCharLocations()
         }
         else
         {
-            loc.size_ = IntVector2::ZERO;
+            loc.size_ = Vector2::ZERO;
             x = GetRowStartPosition(++rowIndex);
             y += rowHeight;
         }
@@ -806,8 +806,8 @@ void Text3DText::UpdateCharLocations()
         lastFilled = printToText_[i] + 1;
     }
     // 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;
 }
@@ -832,7 +832,7 @@ void Text3DText::ValidateSelection()
 
 int Text3DText::GetRowStartPosition(unsigned rowIndex) const
 {
-    int rowWidth = 0;
+    float rowWidth = 0;
 
     if (rowIndex < rowWidths_.Size())
         rowWidth = rowWidths_[rowIndex];
@@ -866,7 +866,7 @@ void Text3DText::SetIndentSpacing(int indentSpacing)
     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)
 {
     unsigned startDataSize = pageBatch.vertexData_->Size();

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

@@ -28,7 +28,7 @@
 namespace Atomic
 {
 
-static const int TEXT3D_DEFAULT_FONT_SIZE = 12;
+static const float TEXT3D_DEFAULT_FONT_SIZE = 12;
 
 class Text3DFont;
 class Text3DFontFace;
@@ -46,16 +46,16 @@ enum Text3DTextEffect
 struct Text3DCharLocation
 {
     /// Position.
-    IntVector2 position_;
+    Vector2 position_;
     /// Size.
-    IntVector2 size_;
+    Vector2 size_;
 };
 
 /// Glyph and its location within the text. Used when preparing text rendering.
 struct Text3DGlyphLocation
 {
     /// Construct.
-    Text3DGlyphLocation(int x, int y, const Text3DFontGlyph* glyph) :
+    Text3DGlyphLocation(float x, float y, const Text3DFontGlyph* glyph) :
         x_(x),
         y_(y),
         glyph_(glyph)
@@ -63,9 +63,9 @@ struct Text3DGlyphLocation
     }
 
     /// X coordinate.
-    int x_;
+    float x_;
     /// Y coordinate.
-    int y_;
+    float y_;
     /// Glyph.
     const Text3DFontGlyph* glyph_;
 };
@@ -121,11 +121,11 @@ public:
     virtual void OnIndentSet();
 
     /// 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.
-    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.
-    bool SetFontSize(int size);
+    bool SetFontSize(float size);
     /// Set text. Text is assumed to be either ASCII or UTF8-encoded.
     void SetText(const String& text);
     /// Set row alignment.
@@ -159,7 +159,7 @@ public:
     Text3DFont* GetFont() const { return font_; }
 
     /// Return font size.
-    int GetFontSize() const { return fontSize_; }
+    float GetFontSize() const { return fontSize_; }
 
     /// Return text.
     const String& GetText() const { return text_; }
@@ -204,7 +204,7 @@ public:
     const Color& GetEffectColor() const { return effectColor_; }
 
     /// Return row height.
-    int GetRowHeight() const { return rowHeight_; }
+    float GetRowHeight() const { return rowHeight_; }
 
     /// Return number of rows.
     unsigned GetNumRows() const { return rowWidths_.Size(); }
@@ -213,11 +213,11 @@ public:
     unsigned GetNumChars() const { return unicodeText_.Size(); }
 
     /// 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.
-    IntVector2 GetCharPosition(unsigned index);
+    Vector2 GetCharPosition(unsigned 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.
     void SetEffectDepthBias(float bias);
@@ -370,7 +370,7 @@ protected:
     int GetRowStartPosition(unsigned rowIndex) const;
     /// Contruct batch.
     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);
 
     /// Font.
@@ -378,7 +378,7 @@ protected:
     /// Current face.
     WeakPtr<Text3DFontFace> fontFace_;
     /// Font size.
-    int fontSize_;
+    float fontSize_;
     /// UTF-8 encoded text.
     String text_;
     /// Row alignment.
@@ -410,7 +410,7 @@ protected:
     /// Text effect Z bias.
     float effectDepthBias_;
     /// Row height.
-    int rowHeight_;
+    float rowHeight_;
     /// Text as Unicode characters.
     PODVector<unsigned> unicodeText_;
     /// Text modified into printed form.
@@ -418,7 +418,7 @@ protected:
     /// Mapping of printed form back to original char indices.
     PODVector<unsigned> printToText_;
     /// Row widths.
-    PODVector<int> rowWidths_;
+    PODVector<float> rowWidths_;
     /// Glyph locations per each texture in the font.
     Vector<PODVector<Text3DGlyphLocation> > pageGlyphLocations_;
     /// 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) :
-    Resource(context),
+    ResourceWithMetadata(context),
     GPUObject(GetSubsystem<Graphics>()),
     shaderResourceView_(0),
     sampler_(0),
@@ -204,8 +204,8 @@ void Texture::SetParameters(XMLFile* file)
 
 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();
 
@@ -248,8 +248,6 @@ void Texture::SetParameters(const XMLElement& element)
 
         if (name == "srgb")
             SetSRGB(paramElem.GetBool("enable"));
-
-        paramElem = paramElem.GetNext();
     }
 }
 

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

@@ -36,9 +36,9 @@ class XMLElement;
 class XMLFile;
 
 /// 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:
     /// Construct.
@@ -116,7 +116,7 @@ public:
 
     /// Return whether rendertarget mipmap levels need regenration.
     bool GetLevelsDirty() const { return levelsDirty_; }
-    
+
     /// Return backup texture.
     Texture* GetBackupTexture() const { return backupTexture_; }
 

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

@@ -202,4 +202,15 @@ unsigned VertexBuffer::GetVertexSize(unsigned elementMask)
     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.
     static unsigned GetVertexSize(unsigned elementMask);
 
+    /// Update offsets of vertex elements.
+    static void UpdateOffsets(PODVector<VertexElement>& elements);
+
 private:
     /// Update offsets of vertex elements.
     void UpdateOffsets();

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

@@ -655,12 +655,16 @@ void View::Render()
             {
                 BlitFramebuffer(currentRenderTarget_->GetParentTexture(), renderTarget_, false);
                 currentRenderTarget_ = renderTarget_;
+                lastCustomDepthSurface_ = 0;
             }
 
             graphics_->SetRenderTarget(0, currentRenderTarget_);
             for (unsigned i = 1; i < MAX_RENDERTARGETS; ++i)
                 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();
             IntRect viewport = (currentRenderTarget_ == renderTarget_) ? viewRect_ : IntRect(0, 0, rtSizeNow.x_,
                 rtSizeNow.y_);
@@ -1799,7 +1803,8 @@ void View::SetRenderTargets(RenderPathCommand& command)
         if (depthTexture)
         {
             useCustomDepth = true;
-            graphics_->SetDepthStencil(GetRenderSurfaceFromTexture(depthTexture));
+            lastCustomDepthSurface_ = GetRenderSurfaceFromTexture(depthTexture);
+            graphics_->SetDepthStencil(lastCustomDepthSurface_);
         }
     }
 
@@ -1978,6 +1983,7 @@ void View::AllocateScreenBuffers()
     bool needSubstitute = false;
     unsigned numViewportTextures = 0;
     depthOnlyDummyTexture_ = 0;
+    lastCustomDepthSurface_ = 0;
 
     // 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

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

@@ -336,6 +336,8 @@ private:
     Texture* viewportTextures_[MAX_VIEWPORT_TEXTURES];
     /// Color rendertarget active for the current renderpath command.
     RenderSurface* currentRenderTarget_;
+    /// Last used custom depth render surface.
+    RenderSurface* lastCustomDepthSurface_;
     /// Texture containing the latest viewport texture.
     Texture* currentViewportTexture_;
     /// 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()))
             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)
         return 0;
     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)
@@ -73,7 +73,7 @@ bool CompressStream(Serializer& dest, Deserializer& src)
     if (src.Read(srcBuffer, srcSize) != srcSize)
         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;
     success &= dest.WriteUInt(srcSize);
     success &= dest.WriteUInt(destSize);

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

@@ -76,7 +76,7 @@ extern "C"
 const char* SDL_Android_GetFilesDir();
 char** SDL_Android_GetFileList(const char* path, 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_GetDocumentsDir();
 #endif
@@ -89,6 +89,9 @@ namespace Atomic
 
 int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* context)
 {
+#ifdef TVOS
+    return -1;
+#else
 #if !defined(__EMSCRIPTEN__) && !defined(MINI_URHO)
     if (!redirectToLog)
 #endif
@@ -139,10 +142,14 @@ int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* cont
 
     return exitCode;
 #endif
+#endif
 }
 
 int DoSystemRun(const String& fileName, const Vector<String>& arguments)
 {
+#ifdef TVOS
+    return -1;
+#else
     String fixedFileName = GetNativePath(fileName);
 
 #ifdef _WIN32
@@ -193,6 +200,7 @@ int DoSystemRun(const String& fileName, const Vector<String>& arguments)
     else
         return -1;
 #endif
+#endif
 }
 
 /// 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
     // Files from this directory will be opened using special handling
     return APK;
-#elif defined(IOS)
+#elif defined(IOS) || defined(TVOS)
     return AddTrailingSlash(SDL_IOS_GetResourceDir());
 #elif defined(_WIN32)
     wchar_t exeName[MAX_PATH];
@@ -730,7 +738,7 @@ String FileSystem::GetUserDocumentsDir() const
 {
 #if defined(__ANDROID__)
     return AddTrailingSlash(SDL_Android_GetFilesDir());
-#elif defined(IOS)
+#elif defined(IOS) || defined(TVOS)
     return AddTrailingSlash(SDL_IOS_GetDocumentsDir());
 #elif defined(_WIN32)
     wchar_t pathName[MAX_PATH];

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

@@ -36,7 +36,7 @@ extern "C"
 // Need read/close for inotify
 #include "unistd.h"
 }
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
 extern "C"
 {
 #include "../IO/MacFileWatcher.h"
@@ -58,7 +58,7 @@ FileWatcher::FileWatcher(Context* context) :
 #ifdef ATOMIC_FILEWATCHER
 #ifdef __linux__
     watchHandle_ = inotify_init();
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
     supported_ = IsFileWatcherSupported();
 #endif
 #endif
@@ -176,7 +176,7 @@ bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs)
         ATOMIC_LOGDEBUG("Started watching path " + pathName);
         return true;
     }
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
     if (!supported_)
     {
         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);
 #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
         Stop();
 #endif
@@ -236,7 +236,7 @@ void FileWatcher::StopWatching()
         for (HashMap<int, String>::Iterator i = dirHandle_.Begin(); i != dirHandle_.End(); ++i)
             inotify_rm_watch(watchHandle_, i->first_);
         dirHandle_.Clear();
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
         CloseFileWatcher(watcher_);
 #endif
 
@@ -326,7 +326,7 @@ void FileWatcher::ThreadFunction()
             i += sizeof(inotify_event) + event->len;
         }
     }
-#elif defined(__APPLE__) && !defined(IOS)
+#elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
     while (shouldRun_)
     {
         Time::Sleep(100);

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

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

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

@@ -36,7 +36,7 @@
 #ifdef __ANDROID__
 #include <android/log.h>
 #endif
-#ifdef IOS
+#if defined(IOS) || defined(TVOS)
 extern "C" void SDL_IOS_LogMessage(const char* message);
 #endif
 
@@ -80,7 +80,7 @@ Log::~Log()
 
 void Log::Open(const String& fileName)
 {
-#if !defined(__ANDROID__) && !defined(IOS)
+#if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
     if (fileName.Empty())
         return;
     if (logFile_ && logFile_->IsOpen())
@@ -104,7 +104,7 @@ void Log::Open(const String& fileName)
 
 void Log::Close()
 {
-#if !defined(__ANDROID__) && !defined(IOS)
+#if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
     if (logFile_ && logFile_->IsOpen())
     {
         logFile_->Close();
@@ -173,7 +173,7 @@ void Log::Write(int level, const String& message)
 #if defined(__ANDROID__)
     int androidLevel = ANDROID_LOG_DEBUG + level;
     __android_log_print(androidLevel, "Atomic", "%s", message.CString());
-#elif defined(IOS)
+#elif defined(IOS) || defined(TVOS)
     SDL_IOS_LogMessage(message.CString());
 #else
     if (logInstance->quiet_)
@@ -232,7 +232,7 @@ void Log::WriteRaw(const String& message, bool error)
     }
     else
         __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());
 #else
     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);
 
 // 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
 #endif
 
@@ -413,6 +412,7 @@ Input::Input(Context* context) :
     mouseButtonPress_(0),
     lastVisibleMousePosition_(MOUSE_POSITION_OFFSCREEN),
     mouseMoveWheel_(0),
+    inputScale_(Vector2::ONE),
     windowID_(0),
     toggleFullscreen_(true),
 // ATOMIC BEGIN
@@ -2522,8 +2522,8 @@ void Input::HandleSDLEvent(void* sdlEvent)
 
             case SDL_WINDOWEVENT_MAXIMIZED:
             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
                 graphics_->Restore();
 #endif

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

@@ -288,6 +288,8 @@ public:
     int GetMouseMoveY() const;
     /// Return mouse wheel movement since last frame.
     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.
     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>
 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.
 /// Use a workaround for GCC, see https://github.com/urho3d/Urho3D/issues/655
 #ifndef __GNUC__
@@ -99,7 +106,7 @@ inline bool IsNaN(float value) { return value != value; }
 
 inline bool IsNaN(float value)
 {
-    unsigned u = *(unsigned*)(&value);
+    unsigned u = FloatToRawIntBits(value);
     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
 inline unsigned short FloatToHalf(float value)
 {
-    unsigned inu = *((unsigned*)&value);
+    unsigned inu = FloatToRawIntBits(value);
     unsigned t1 = inu & 0x7fffffff;         // Non-sign bits
     unsigned t2 = inu & 0x80000000;         // Sign bit
     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
     {
         return Matrix3(
@@ -275,6 +285,15 @@ public:
     /// Return float data.
     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.
     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.
     bool Equals(const Matrix3x4& rhs) const
     {
@@ -682,12 +692,22 @@ public:
 
     /// Return decomposition to translation, rotation and scale.
     void Decompose(Vector3& translation, Quaternion& rotation, Vector3& scale) const;
+
     /// Return inverse.
     Matrix3x4 Inverse() const;
 
     /// Return float data.
     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.
     String ToString() const;
 

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

@@ -96,7 +96,7 @@ public:
 #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) :
         m00_(matrix.m00_),
         m01_(matrix.m01_),
@@ -557,7 +557,7 @@ public:
     /// Return the rotation part.
     Quaternion Rotation() const { return Quaternion(RotationMatrix()); }
 
-    /// Return the scaling part
+    /// Return the scaling part.
     Vector3 Scale() const
     {
         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
     {
 #ifdef ATOMIC_SSE
@@ -621,12 +631,22 @@ public:
 
     /// Return decomposition to translation, rotation and scale.
     void Decompose(Vector3& translation, Quaternion& rotation, Vector3& scale) const;
+
     /// Return inverse.
     Matrix4 Inverse() const;
 
-    /// Return float data
+    /// Return float data.
     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.
     String ToString() const;
 
@@ -691,7 +711,7 @@ public:
     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; }
 
 }

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

@@ -221,6 +221,16 @@ float Quaternion::RollAngle() const
     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
 {
     return Matrix3(

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

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

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

@@ -23,6 +23,7 @@
 #pragma once
 
 #include "../Math/Vector2.h"
+#include "../Math/MathDefs.h"
 
 namespace Atomic
 {
@@ -356,6 +357,9 @@ public:
     /// Project vector onto axis.
     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.
     Vector3 CrossProduct(const Vector3& rhs) const
     {
@@ -415,6 +419,17 @@ public:
     /// Return as string.
     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.
     float x_;
     /// 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,
         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;
     }
 

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

@@ -34,6 +34,7 @@
 #include "../Physics/PhysicsEvents.h"
 #include "../Physics/PhysicsUtils.h"
 #include "../Physics/PhysicsWorld.h"
+#include "../Physics/RaycastVehicle.h"
 #include "../Physics/RigidBody.h"
 #include "../Scene/Scene.h"
 #include "../Scene/SceneEvents.h"
@@ -151,6 +152,7 @@ PhysicsWorld::PhysicsWorld(Context* context) :
     world_->setDebugDrawer(this);
     world_->setInternalTickCallback(InternalPreTickCallback, static_cast<void*>(this), true);
     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)
 {
     ATOMIC_PROFILE(PhysicsBodyQuery);
-    
+
     result.Clear();
-    
+
     if (!body || !body->GetBody())
         return;
 
     PhysicsQueryCallback callback(result, body->GetCollisionMask());
     world_->contactTest(body->GetBody(), callback);
-    
+
     // Remove the body itself from the returned list
     for (unsigned i = 0; i < result.Size(); i++)
     {
@@ -1081,6 +1083,7 @@ void RegisterPhysicsLibrary(Context* context)
     RigidBody::RegisterObject(context);
     Constraint::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';
 
     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());
         return false;
@@ -202,7 +202,7 @@ static void ToRapidjsonValue(rapidjson::Value& rapidjsonValue, const JSONValue&
                 const char* name = i->first_.CString();
                 rapidjson::Value value;
                 ToRapidjsonValue(value, i->second_, allocator);
-                rapidjsonValue.AddMember(name, value, allocator);
+                rapidjsonValue.AddMember(StringRef(name), value, allocator);
             }
         }
         break;

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

@@ -322,7 +322,7 @@ JSONObjectIterator JSONValue::End()
     // Convert to object type.
     SetType(JSON_OBJECT);
 
-    return objectValue_->Begin();
+    return objectValue_->End();
 }
 
 ConstJSONObjectIterator JSONValue::End() const
@@ -581,7 +581,8 @@ VariantMap JSONValue::GetVariantMap() const
 
     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();
         variantMap[key] = variant;
     }

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

@@ -26,6 +26,7 @@
 #include "../IO/File.h"
 #include "../IO/Log.h"
 #include "../Resource/Resource.h"
+#include "../Resource/XMLElement.h"
 
 namespace Atomic
 {
@@ -120,4 +121,66 @@ unsigned Resource::GetUseTimer()
         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/Timer.h"
+#include "../Resource/JSONValue.h"
 
 namespace Atomic
 {
 
 class Deserializer;
 class Serializer;
+class XMLElement;
 
 /// Asynchronous loading state of a resource.
 enum AsyncLoadState
@@ -106,6 +108,43 @@ private:
     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)
 {
     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())
     {
         String namePath = GetPath(name);
-        String exePath = fileSystem->GetProgramDir();
+        String exePath = fileSystem->GetProgramDir().Replaced("/./", "/");
         for (unsigned i = 0; i < resourceDirs_.Size(); ++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);
     /// 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);
-    /// 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);
     /// Remove a resource load directory.
     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 version of returning loaded resources of a specific type.
     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;
     /// Return memory budget for a resource type.
     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
 {
     return GetWorldTransform() * position;
@@ -1933,7 +1941,7 @@ Animatable* Node::FindAttributeAnimationTarget(const String& name, String& outNa
         {
             if (names[i].Front() != '#')
                 break;
-            
+
             String name = names[i].Substring(1, names[i].Length() - 1);
             char s = name.Front();
             if (s >= '0' && s <= '9')
@@ -1945,7 +1953,7 @@ Animatable* Node::FindAttributeAnimationTarget(const String& name, String& outNa
             {
                 node = node->GetChild(name, true);
             }
-            
+
             if (!node)
             {
                 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)
 {
-    // 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_)
     {
         using namespace NodeRemoved;

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

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

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

@@ -22,7 +22,9 @@
 
 #pragma once
 
+#include "../Container/Ptr.h"
 #include "../Container/RefCounted.h"
+#include "../Container/Vector.h"
 #include "../Scene/AnimationDefs.h"
 
 namespace Atomic
@@ -30,6 +32,7 @@ namespace Atomic
 
 class Object;
 class ValueAnimation;
+class Variant;
 struct VAnimEventFrame;
 
 /// 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 (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()
         set_target_properties(AtomicNETNative PROPERTIES
             FRAMEWORK TRUE

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