Xavier Maupeu 10 роки тому
батько
коміт
dbcfc9721e
100 змінених файлів з 2891 додано та 501 видалено
  1. 7 8
      .appveyor.yml
  2. 11 10
      .travis.yml
  3. 31 0
      CMake/Modules/CheckUrho3DLibrary.cpp
  4. 7 20
      CMake/Modules/FindBCM_VC.cmake
  5. 48 52
      CMake/Modules/FindDirect3D.cmake
  6. 8 14
      CMake/Modules/FindODBC.cmake
  7. 43 0
      CMake/Modules/FindPulseAudio.cmake
  8. 140 61
      CMake/Modules/FindUrho3D.cmake
  9. 71 64
      CMake/Modules/Urho3D-CMake-common.cmake
  10. 10 8
      CMakeLists.txt
  11. 135 1
      Docs/AngelScriptAPI.h
  12. 1 1
      Docs/Doxyfile.in
  13. 12 19
      Docs/GettingStarted.dox
  14. 13 0
      Docs/LuaScriptAPI.dox
  15. 77 67
      Docs/Reference.dox
  16. 166 1
      Docs/ScriptAPI.dox
  17. 5 3
      Docs/Urho3D.dox
  18. 3 1
      README.md
  19. 15 18
      Rakefile
  20. 5 5
      Source/Clang-Tools/CMakeLists.txt
  21. 15 2
      Source/Samples/14_SoundEffects/SoundEffects.cpp
  22. 2 0
      Source/Samples/14_SoundEffects/SoundEffects.h
  23. 2 2
      Source/Samples/Sample.inl
  24. 2 1
      Source/ThirdParty/JO/jo_jpeg.cpp
  25. 4 5
      Source/ThirdParty/LuaJIT/src/host/CMakeLists.txt
  26. 6 8
      Source/ThirdParty/SDL/CMakeLists.txt
  27. 4 5
      Source/ThirdParty/toluapp/src/bin/CMakeLists.txt
  28. 9 8
      Source/Tools/PackageTool/CMakeLists.txt
  29. 2 2
      Source/Tools/Urho3DPlayer/Urho3DPlayer.cpp
  30. 1 1
      Source/Urho3D/.soversion
  31. 2 0
      Source/Urho3D/AngelScript/APITemplates.h
  32. 4 2
      Source/Urho3D/AngelScript/GraphicsAPI.cpp
  33. 16 0
      Source/Urho3D/AngelScript/PhysicsAPI.cpp
  34. 6 1
      Source/Urho3D/AngelScript/ResourceAPI.cpp
  35. 58 0
      Source/Urho3D/AngelScript/SceneAPI.cpp
  36. 4 0
      Source/Urho3D/AngelScript/UIAPI.cpp
  37. 38 0
      Source/Urho3D/Audio/AudioEvents.h
  38. 46 1
      Source/Urho3D/Audio/SoundSource.cpp
  39. 5 3
      Source/Urho3D/Audio/SoundSource.h
  40. 7 4
      Source/Urho3D/CMakeLists.txt
  41. 4 0
      Source/Urho3D/Core/ProcessUtils.cpp
  42. 3 4
      Source/Urho3D/Engine/Console.cpp
  43. 2 2
      Source/Urho3D/Engine/Console.h
  44. 9 0
      Source/Urho3D/Graphics/AnimatedModel.cpp
  45. 2 0
      Source/Urho3D/Graphics/AnimatedModel.h
  46. 31 0
      Source/Urho3D/Graphics/Animation.cpp
  47. 37 0
      Source/Urho3D/Graphics/AnimationState.cpp
  48. 1 1
      Source/Urho3D/Graphics/Batch.cpp
  49. 15 4
      Source/Urho3D/Graphics/Direct3D11/D3D11Graphics.cpp
  50. 6 0
      Source/Urho3D/Graphics/Direct3D11/D3D11RenderSurface.cpp
  51. 5 0
      Source/Urho3D/Graphics/Direct3D11/D3D11RenderSurface.h
  52. 8 0
      Source/Urho3D/Graphics/Direct3D11/D3D11Texture2D.cpp
  53. 18 0
      Source/Urho3D/Graphics/DrawableEvents.h
  54. 311 19
      Source/Urho3D/Graphics/Material.cpp
  55. 14 0
      Source/Urho3D/Graphics/Material.h
  56. 4 0
      Source/Urho3D/Graphics/OpenGL/OGLGraphics.cpp
  57. 33 1
      Source/Urho3D/Graphics/ParticleEmitter.cpp
  58. 2 0
      Source/Urho3D/Graphics/ParticleEmitter.h
  59. 0 3
      Source/Urho3D/Graphics/Renderer.cpp
  60. 1 1
      Source/Urho3D/Graphics/View.cpp
  61. 8 2
      Source/Urho3D/IO/FileSystem.cpp
  62. 1 1
      Source/Urho3D/Input/Input.cpp
  63. 19 0
      Source/Urho3D/LuaScript/pkgs/Physics/PhysicsWorld.pkg
  64. 7 0
      Source/Urho3D/LuaScript/pkgs/Scene/Node.pkg
  65. 28 0
      Source/Urho3D/LuaScript/pkgs/Scene/Scene.pkg
  66. 5 0
      Source/Urho3D/LuaScript/pkgs/UI/UI.pkg
  67. 6 0
      Source/Urho3D/Navigation/CrowdAgent.cpp
  68. 43 0
      Source/Urho3D/Navigation/NavigationEvents.h
  69. 55 0
      Source/Urho3D/Navigation/NavigationMesh.cpp
  70. 23 0
      Source/Urho3D/Navigation/NavigationMesh.h
  71. 73 0
      Source/Urho3D/Physics/PhysicsWorld.cpp
  72. 12 3
      Source/Urho3D/Physics/PhysicsWorld.h
  73. 26 13
      Source/Urho3D/Physics/RigidBody.cpp
  74. 1 1
      Source/Urho3D/Physics/RigidBody.h
  75. 7 0
      Source/Urho3D/Resource/BackgroundLoader.cpp
  76. 3 0
      Source/Urho3D/Resource/BackgroundLoader.h
  77. 15 14
      Source/Urho3D/Resource/Image.cpp
  78. 3 3
      Source/Urho3D/Resource/JSONValue.cpp
  79. 98 0
      Source/Urho3D/Scene/Animatable.cpp
  80. 4 0
      Source/Urho3D/Scene/Animatable.h
  81. 11 0
      Source/Urho3D/Scene/Component.cpp
  82. 2 0
      Source/Urho3D/Scene/Component.h
  83. 120 1
      Source/Urho3D/Scene/Node.cpp
  84. 9 1
      Source/Urho3D/Scene/Node.h
  85. 63 0
      Source/Urho3D/Scene/ObjectAnimation.cpp
  86. 5 0
      Source/Urho3D/Scene/ObjectAnimation.h
  87. 266 8
      Source/Urho3D/Scene/Scene.cpp
  88. 27 1
      Source/Urho3D/Scene/Scene.h
  89. 139 0
      Source/Urho3D/Scene/Serializable.cpp
  90. 5 0
      Source/Urho3D/Scene/Serializable.h
  91. 66 1
      Source/Urho3D/Scene/UnknownComponent.cpp
  92. 6 1
      Source/Urho3D/Scene/UnknownComponent.h
  93. 84 0
      Source/Urho3D/Scene/ValueAnimation.cpp
  94. 5 0
      Source/Urho3D/Scene/ValueAnimation.h
  95. 9 5
      Source/Urho3D/UI/DropDownList.cpp
  96. 4 2
      Source/Urho3D/UI/DropDownList.h
  97. 62 11
      Source/Urho3D/UI/UI.cpp
  98. 11 0
      Source/Urho3D/UI/UI.h
  99. 4 0
      Source/Urho3D/UI/UIElement.cpp
  100. 9 0
      Source/Urho3D/Urho2D/Sprite2D.cpp

+ 7 - 8
.appveyor.yml

@@ -24,7 +24,6 @@ version: '{build}'
 platform:
   - x86
   - x64
-configuration: Release
 clone_depth: 50
 environment:
   SF_KEY:
@@ -32,6 +31,7 @@ environment:
   SF_API:
     secure: cc1q9CXo5BwIYqtgigHpkCGG90zEVM45xx/YzXTOjVp512oQNUzTJq0AmxEYXP78
   build_tree: native-Build
+  config: Release
   matrix:
     - URHO3D_LIB_TYPE: STATIC
     - URHO3D_LIB_TYPE: SHARED
@@ -43,20 +43,19 @@ matrix:
 install:
   - ps: if ($env:APPVEYOR_REPO_TAG -eq "true" -or (!$env:APPVEYOR_PULL_REQUEST_NUMBER -and (select-string '\[ci package\]' -inputobject $env:APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED)))
         {
-          if ($env:APPVEYOR_REPO_TAG -eq "true") { $env:RELEASE_TAG = $env:APPVEYOR_REPO_TAG_NAME } else { git fetch -q --unshallow };
+          if ($env:APPVEYOR_REPO_TAG -eq "true") { $env:RELEASE_TAG = $env:APPVEYOR_REPO_TAG_NAME } else { "Unshallowing git repository..."; git fetch -q --unshallow };
           if ($env:URHO3D_LIB_TYPE -eq "STATIC" -and ($env:Platform -eq "x64")) { $env:SF_DEFAULT = "windows:Windows-64bit-STATIC-3D11.zip" };
           $env:PACKAGE_UPLOAD = "1";
-          choco install doxygen.portable graphviz.portable >$null;
+          do { "Installing doxygen and graphviz..."; choco install doxygen.portable graphviz.portable >$null } until ($?);
         }
 build_script:
   - if "%PLATFORM%" == "x64" set "OPTS=URHO3D_64BIT=1"
 # Our free AppVeyor account is slow for normal daily CI, speed up the build a little bit by excluding Assimp and other tools in the normal build and use the Debug build configuration instead
-  - if "%PACKAGE_UPLOAD%" == "" set "OPTS=%OPTS% URHO3D_TOOLS=0" && set "Configuration=Debug"
-  - rake cmake vs2015 %OPTS% URHO3D_LIB_TYPE=%URHO3D_LIB_TYPE% URHO3D_D3D11=1 URHO3D_LUAJIT=1 URHO3D_LUAJIT_AMALG=1 URHO3D_EXTRAS=1 URHO3D_TESTING=1
-  - rake make config=%Configuration%
+  - if "%PACKAGE_UPLOAD%" == "" set "OPTS=%OPTS% URHO3D_TOOLS=0" && set "config=Debug"
+  - rake cmake vs2015 %OPTS% URHO3D_LIB_TYPE=%URHO3D_LIB_TYPE% URHO3D_D3D11=1 URHO3D_LUAJIT=1 URHO3D_LUAJIT_AMALG=1 URHO3D_EXTRAS=1 URHO3D_TESTING=1 && rake make
 # Could not run test target yet as AppVeyor does not currently allow its service to interact with desktop
-# - rake make config=%Configuration% target=RUN_TESTS
-  - if "%PACKAGE_UPLOAD%" == "1" rake ci_appveyor_package_upload
+# - rake make target=RUN_TESTS
+  - if "%PACKAGE_UPLOAD%" == "1" (echo "Packaging the build artifact..." && rake ci_appveyor_package_upload) else (echo Configuring downstream project using Urho3D library in its build tree... && rake scaffolding dir=UsingBuildTree >nul && cd UsingBuildTree && rake cmake vs2015 %OPTS% URHO3D_HOME=..\..\native-Build URHO3D_D3D11=1 && rake make && echo Installing Urho3D SDK to default system-wide location... && cd .. && rake make target=install >nul && echo Configuring downstream project using Urho3D SDK... && rake scaffolding dir=UsingSDK >null && cd UsingSDK && rake cmake vs2015 %OPTS% URHO3D_D3D11=1 && rake make)
 test: off
 artifacts:
   - path: native-Build\*.zip

+ 11 - 10
.travis.yml

@@ -40,7 +40,7 @@ matrix:
   fast_finish: true
   include:
     - &Linux-64bit
-      addons: {apt: {sources: [*default_sources, kubuntu-backports, ubuntu-toolchain-r-test], packages: [cmake, g++-4.9, &linux_packages [*default_packages, libasound2-dev, rpm]]}}
+      addons: {apt: {sources: [*default_sources, kubuntu-backports, ubuntu-toolchain-r-test], packages: [cmake, g++-4.9, &linux_packages [*default_packages, libasound2-dev, libpulse-dev, rpm]]}}
       compiler: gcc-64bit-static
       env: LINUX=1 URHO3D_LIB_TYPE=STATIC URHO3D_UPDATE_SOURCE_TREE=1 COVERITY_SCAN_THRESHOLD=100 SF_DEFAULT=linux:Linux-64bit-STATIC.tar.gz
     - &Linux-64bit-shared
@@ -132,7 +132,7 @@ cache: ccache
 sudo: false
 addons:
   apt:
-    packages: libasound2-dev
+    packages: [libasound2-dev, libpulse-dev]
   coverity_scan:
     project:
       name: urho3d/Urho3D
@@ -165,7 +165,7 @@ language: cpp
 compiler: gcc
 cache: ccache
 sudo: false
-addons: {apt: {sources: [kubuntu-backports, ubuntu-toolchain-r-test], packages: [cmake, g++-4.9, libasound2-dev]}}
+addons: {apt: {sources: [kubuntu-backports, ubuntu-toolchain-r-test], packages: [cmake, g++-4.9, libasound2-dev, libpulse-dev]}}
 env:
   global:
     - secure: DE9IUM+pIV757GU0ccfDJhA752442pKu3DyBthrzHW9+GbsqbfuJOx045CYNN5vOWutFPC0A51B9WxhLNpXXqD3mfU8MhP1gkF7SskrHvcAPrCyfdqZf1Q8XDP5phm2KbHhhwxQMYmmicd6yj8DPNy2wRoSgPSDp/ZUDk51XZDU=
@@ -192,7 +192,7 @@ language: android
 android: {components: [build-tools-22.0.1]}
 cache: {directories: $HOME/.ccache}
 sudo: false
-addons: {apt: {sources: &default_sources george-edison55-precise-backports, packages: &default_packages [doxygen, graphviz, libasound2-dev, g++-multilib]}}
+addons: {apt: {sources: &default_sources george-edison55-precise-backports, packages: &default_packages [doxygen, graphviz, g++-multilib]}}
 env:
   global:
     - secure: DE9IUM+pIV757GU0ccfDJhA752442pKu3DyBthrzHW9+GbsqbfuJOx045CYNN5vOWutFPC0A51B9WxhLNpXXqD3mfU8MhP1gkF7SskrHvcAPrCyfdqZf1Q8XDP5phm2KbHhhwxQMYmmicd6yj8DPNy2wRoSgPSDp/ZUDk51XZDU=
@@ -263,7 +263,7 @@ branch: {name: RPI-CI, active: yes}
 language: cpp
 cache: ccache
 sudo: false
-addons: {apt: {sources: &default_sources george-edison55-precise-backports, packages: &default_packages [doxygen, graphviz, libasound2-dev, g++-multilib, rpm]}}
+addons: {apt: {sources: &default_sources george-edison55-precise-backports, packages: &default_packages [doxygen, graphviz, g++-multilib, rpm]}}
 env:
   global:
     - secure: DE9IUM+pIV757GU0ccfDJhA752442pKu3DyBthrzHW9+GbsqbfuJOx045CYNN5vOWutFPC0A51B9WxhLNpXXqD3mfU8MhP1gkF7SskrHvcAPrCyfdqZf1Q8XDP5phm2KbHhhwxQMYmmicd6yj8DPNy2wRoSgPSDp/ZUDk51XZDU=
@@ -291,7 +291,7 @@ matrix:
     - compiler: gcc-armeabi-v7a-with-NEON-shared
       env: URHO3D_LIB_TYPE=SHARED RPI_ABI='armeabi-v7a with NEON'
 before_script:
-  - git clone --depth 1 https://github.com/raspberrypi/tools.git rpi-tools && export RPI_PREFIX=$(pwd)/rpi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf && git clone --depth 1 --branch strip https://github.com/urho3d/rpi-sysroot.git && export RPI_SYSROOT=$(pwd)/rpi-sysroot && for f in $RPI_PREFIX-{gcc,g++}; do touch -d "2015-01-01 00:00:00 +0800" $f; done
+  - git clone --depth 1 https://github.com/raspberrypi/tools.git rpi-tools && export RPI_PREFIX=$(pwd)/rpi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf && git clone --depth 1 https://github.com/urho3d/rpi-sysroot.git && export RPI_SYSROOT=$(pwd)/rpi-sysroot && for f in $RPI_PREFIX-{gcc,g++}; do touch -d "2015-01-01 00:00:00 +0800" $f; done
   - export TRAVIS_COMMIT=$TRAVIS_COMMIT~
   - export TAG=$(git describe --exact-match $TRAVIS_COMMIT 2>/dev/null); if [[ $TAG =~ [[:digit:]]+\.[[:digit:]]+ ]]; then export RELEASE_TAG=$TAG; fi
   - export COMMIT_MESSAGE=$(git log --format=%B -n 1 $TRAVIS_COMMIT)
@@ -317,11 +317,11 @@ env:
     - CCACHE_COMPRESS=1
     - CCACHE_MAXSIZE=300M
   matrix:
+    - MAKEFILE=1    URHO3D_LIB_TYPE=SHARED URHO3D_DEPLOYMENT_TARGET=generic
     - XCODE=1       URHO3D_LIB_TYPE=STATIC CMAKE_OSX_DEPLOYMENT_TARGET=10.11 SF_DEFAULT=mac:OSX-64bit-STATIC.tar.gz
     - XCODE=1       URHO3D_LIB_TYPE=SHARED CMAKE_OSX_DEPLOYMENT_TARGET=10.11
     - XCODE=1 IOS=1 URHO3D_LIB_TYPE=STATIC IPHONEOS_DEPLOYMENT_TARGET=9.1 URHO3D_64BIT=0
     - XCODE=1 IOS=1 URHO3D_LIB_TYPE=STATIC IPHONEOS_DEPLOYMENT_TARGET=9.1
-    - MAKEFILE=1    URHO3D_LIB_TYPE=SHARED URHO3D_DEPLOYMENT_TARGET=generic
 matrix:
   fast_finish: true
 before_script:
@@ -331,8 +331,9 @@ before_script:
   - export COMMIT_MESSAGE=$(git log --format=%B -n 1 $TRAVIS_COMMIT)
   - if [ $XCODE ] && ([ $RELEASE_TAG ] || (! [[ $TRAVIS_BRANCH =~ [^-]+-[^-]+-CI ]] && echo $COMMIT_MESSAGE |grep -cq '\[ci package\]')); then export PACKAGE_UPLOAD=1; fi
   # travis_retry brew update >/dev/null   # Intentionally do not update homebrew formulas to avoid pulling in CMake 3.4 which has caused unexplained iOS build tree configuration problem on Travis CI
-  - travis_retry brew install ccache cmake
-  - if [ $PACKAGE_UPLOAD ]; then travis_retry brew install doxygen graphviz; fi
+  - which cmake >/dev/null 2>&1 || cmake=cmake
+  - if [ $PACKAGE_UPLOAD ]; then doxygen='doxygen graphviz'; fi
+  - travis_retry brew install ccache $cmake $doxygen
   - export PATH=$(brew info ccache |grep -o '\S*lib\S*'):$PATH
   - if [ $XCODE ]; then sudo cp -p $(which ccache) $(dirname $(xcodebuild -find-executable clang)) && for compiler in clang clang++; do path=$(xcodebuild -find-executable $compiler); sudo mv $path{,.orig} && sudo ln -sf $(dirname $path)/clang.orig /usr/bin/$compiler && sudo ln -sf ccache $path; done; fi
   - rake ci_setup_cache
@@ -345,7 +346,7 @@ branch: {name: Emscripten-CI, active: yes}
 language: cpp
 cache: ccache
 sudo: false
-addons: {apt: {sources: [george-edison55-precise-backports, kubuntu-backports, ubuntu-toolchain-r-test], packages: [cmake, doxygen, graphviz, g++-4.9, libasound2-dev]}}
+addons: {apt: {sources: [george-edison55-precise-backports, kubuntu-backports, ubuntu-toolchain-r-test], packages: [cmake, doxygen, graphviz, g++-4.9]}}
 env:
   global:
     - secure: DE9IUM+pIV757GU0ccfDJhA752442pKu3DyBthrzHW9+GbsqbfuJOx045CYNN5vOWutFPC0A51B9WxhLNpXXqD3mfU8MhP1gkF7SskrHvcAPrCyfdqZf1Q8XDP5phm2KbHhhwxQMYmmicd6yj8DPNy2wRoSgPSDp/ZUDk51XZDU=

+ 31 - 0
CMake/Modules/CheckUrho3DLibrary.cpp

@@ -0,0 +1,31 @@
+//
+// Copyright (c) 2008-2015 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 <Urho3D/Revision.h>
+#include <iostream>
+
+using namespace Urho3D;
+
+int main(int argc, char* argv[])
+{
+    std::cout << GetRevision();
+}

+ 7 - 20
CMake/Modules/FindBCM_VC.cmake

@@ -27,36 +27,23 @@
 #  BCM_VC_LIBRARIES
 #
 
-if (NOT RPI)
-    return ()
-endif ()
-if (BCM_VC_FOUND)
-    return ()
-endif ()
-    
 # Only need to cater for raspbian as they are not in CMAKE_SYSTEM_PATH
 set (BCM_VC_INC_SEARCH_PATH /opt/vc/include)
 set (BCM_VC_LIB_SEARCH_PATH /opt/vc/lib)
 
 # Assume all the other headers are installed at same relative path as bcm_host.h
-find_path (BCM_VC_INCLUDE_DIRS bcm_host.h PATHS ${BCM_VC_INC_SEARCH_PATH} PATH_SUFFIXES vc)
+find_path (BCM_VC_INCLUDE_DIRS bcm_host.h PATHS ${BCM_VC_INC_SEARCH_PATH} PATH_SUFFIXES vc DOC "Broadcom VideoCore include directory")
 
 # Assume all the other libs are installed at the same relative path as libbcm_host.so
-find_library (BCM_VC_LIB_BCM_HOST bcm_host PATHS ${BCM_VC_LIB_SEARCH_PATH} PATH_SUFFIXES vc)
-find_library (BCM_VC_LIB_EGL EGL PATHS ${BCM_VC_LIB_SEARCH_PATH} PATH_SUFFIXES vc)
-find_library (BCM_VC_LIB_GLES2 GLESv2 PATHS ${BCM_VC_LIB_SEARCH_PATH} PATH_SUFFIXES vc)
+find_library (BCM_VC_LIB_BCM_HOST bcm_host PATHS ${BCM_VC_LIB_SEARCH_PATH} PATH_SUFFIXES vc DOC "Broadcom VideoCore BCM_HOST library directory")
+find_library (BCM_VC_LIB_EGL EGL PATHS ${BCM_VC_LIB_SEARCH_PATH} PATH_SUFFIXES vc DOC "Broadcom VideoCore EGL library directory")
+find_library (BCM_VC_LIB_GLES2 GLESv2 PATHS ${BCM_VC_LIB_SEARCH_PATH} PATH_SUFFIXES vc DOC "Broadcom VideoCore GLESv2 library directory")
 
-if (BCM_VC_INCLUDE_DIRS AND BCM_VC_LIB_BCM_HOST AND BCM_VC_LIB_EGL AND BCM_VC_LIB_GLES2)
+include (FindPackageHandleStandardArgs)
+find_package_handle_standard_args (BCM_VC REQUIRED_VARS BCM_VC_LIB_BCM_HOST BCM_VC_LIB_EGL BCM_VC_LIB_GLES2 BCM_VC_INCLUDE_DIRS FAIL_MESSAGE "Could NOT find Broadcom VideoCore firmware")
+if (BCM_VC_FOUND)
     list (APPEND BCM_VC_INCLUDE_DIRS ${BCM_VC_INCLUDE_DIRS}/interface/vcos/pthreads)  # Note: variable change to list context after this
     set (BCM_VC_LIBRARIES ${BCM_VC_LIB_BCM_HOST} ${BCM_VC_LIB_EGL} ${BCM_VC_LIB_GLES2})
-    set (BCM_VC_FOUND 1)
-endif ()
-
-if (BCM_VC_FOUND)
-    include (FindPackageMessage)
-    FIND_PACKAGE_MESSAGE (BCM_VC "Found Broadcom VideoCore firmware: ${BCM_VC_LIBRARIES} ${BCM_VC_INCLUDE_DIRS}" "[${BCM_VC_LIBRARIES}][${BCM_VC_INCLUDE_DIRS}]")
-elseif (BCM_VC_FIND_REQUIRED)
-    message (FATAL_ERROR "Could not find Broadcom VideoCore firmware")
 endif ()
 
 mark_as_advanced (BCM_VC_INCLUDE_DIRS BCM_VC_LIBRARIES BCM_VC_LIB_BCM_HOST BCM_VC_LIB_EGL BCM_VC_LIB_GLES2)

+ 48 - 52
CMake/Modules/FindDirect3D.cmake

@@ -30,47 +30,45 @@
 #  DIRECT3D_DLL
 #
 
-if (NOT WIN32 OR URHO3D_OPENGL OR DIRECT3D_FOUND)
-    return ()
-endif ()
-
-if (MINGW)
-    # Assume that 'libd3dcompiler.a' is a symlink pointing to 'libd3dcompiler_46.a' (See this discussion http://urho3d.prophpbb.com/topic504.html)
-    # Anyway, we could not do anything else in the build system automation with version lower than 46 and libd3dcompiler_46.a is the latest supported version
-    set (DLL_NAMES d3dcompiler_46.dll)
-else ()
-    set (DLL_NAMES d3dcompiler_47.dll d3dcompiler_46.dll)
-endif ()
-if (CMAKE_CL_64)
-    set (PATH_SUFFIX x64)
-else ()
-    set (PATH_SUFFIX x86)
-endif ()
-# Try to find the compiler DLL from the Microsoft SDK
-# Note: do not use default paths such as the PATH variable, to potentially avoid using a wrong architecture DLL
-find_file (DIRECT3D_DLL NAMES ${DLL_NAMES} PATHS
-    "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0;InstallationFolder]/Redist/D3D/${PATH_SUFFIX}"
-    "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v8.1;InstallationFolder]/Redist/D3D/${PATH_SUFFIX}"
-    "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v8.0;InstallationFolder]/Redist/D3D/${PATH_SUFFIX}"
-    NO_DEFAULT_PATH)
 # Microsoft SDK can be used on VS2012 and above; else fallback to search for the DirectX SDK
-if ((DIRECT3D_DLL AND MSVC_VERSION GREATER 1600) OR MINGW)  # MinGW compiler toolchain is assumed to come with its own necessary headers and libraries for Direct3D
-    if (NOT URHO3D_D3D11)
-        set (DIRECT3D_LIBRARIES d3d9 d3dcompiler)
+if (MSVC_VERSION GREATER 1600 OR MINGW)  # MinGW compiler toolchain is assumed to come with its own necessary headers and libraries for Direct3D
+    # Try to find the compiler DLL from the Microsoft SDK
+    if (MINGW)
+        # Assume that 'libd3dcompiler.a' is a symlink pointing to 'libd3dcompiler_46.a' (See this discussion http://urho3d.prophpbb.com/topic504.html)
+        # Anyway, we could not do anything else in the build system automation with version lower than 46 and libd3dcompiler_46.a is the latest supported version
+        set (DLL_NAMES d3dcompiler_46.dll)
     else ()
-        set (DIRECT3D_LIBRARIES d3d11 d3dcompiler dxgi dxguid)
+        set (DLL_NAMES d3dcompiler_47.dll d3dcompiler_46.dll)
+    endif ()
+    if (CMAKE_CL_64)
+        set (PATH_SUFFIX x64)
+    else ()
+        set (PATH_SUFFIX x86)
+    endif ()
+    find_file (DIRECT3D_DLL NAMES ${DLL_NAMES} PATHS
+        "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0;InstallationFolder]/Redist/D3D/${PATH_SUFFIX}"
+        "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v8.1;InstallationFolder]/Redist/D3D/${PATH_SUFFIX}"
+        "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v8.0;InstallationFolder]/Redist/D3D/${PATH_SUFFIX}"
+        DOC "Direct3D DLL"
+        NO_DEFAULT_PATH)    # Do not use default paths such as the PATH variable, to potentially avoid using a wrong architecture DLL
+    if (DIRECT3D_DLL OR MINGW)
+        if (URHO3D_D3D11)
+            set (DIRECT3D_LIBRARIES d3d11 d3dcompiler dxgi dxguid)
+        else ()
+            set (DIRECT3D_LIBRARIES d3d9 d3dcompiler)
+        endif ()
+        unset (DIRECT3D_INCLUDE_DIRS)
     endif ()
-    unset (DIRECT3D_INCLUDE_DIRS)
-    set (DIRECT3D_FOUND 1)
+    set (FAIL_MESSAGE "Could NOT find Direct3D using Windows SDK search paths")
+    mark_as_advanced (DIRECT3D_DLL)
 else ()
-    # The DLL from Microsoft SDK is not usable with DirectX SDK
-    unset (DIRECT3D_DLL CACHE)
+    # Try to find the DirectX SDK
     set (DIRECTX_INC_SEARCH_PATH
         "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)/Include"
         "C:/Program Files/Microsoft DirectX SDK (June 2010)/Include"
         "$ENV{DIRECTX_ROOT}/Include"
         "$ENV{DXSDK_DIR}/Include")
-    find_path (DIRECT3D_INCLUDE_DIRS NAMES d3dcompiler.h PATHS ${DIRECTX_INC_SEARCH_PATH})
+    find_path (DIRECT3D_INCLUDE_DIRS NAMES d3dcompiler.h PATHS ${DIRECTX_INC_SEARCH_PATH} DOC "Direct3D include directory")
     if (CMAKE_CL_64)
         set (DIRECTX_LIB_SEARCH_PATH
             "C:/Program Files (x86)/Microsoft DirectX SDK (June 2010)/Lib/x64"
@@ -88,30 +86,28 @@ else ()
             "$ENV{DXSDK_DIR}/Lib"
             "$ENV{DXSDK_DIR}/Lib/x86")
     endif ()
-    if (NOT URHO3D_D3D11)
-        find_library (DIRECT3D_LIB_D3D9 NAMES d3d9 PATHS ${DIRECTX_LIB_SEARCH_PATH})
-        find_library (DIRECT3D_LIB_D3DCOMPILER NAMES d3dcompiler PATHS ${DIRECTX_LIB_SEARCH_PATH})
-        if (DIRECT3D_INCLUDE_DIRS AND DIRECT3D_LIB_D3D9 AND DIRECT3D_LIB_D3DCOMPILER)
-            set (DIRECT3D_LIBRARIES ${DIRECT3D_LIB_D3D9} ${DIRECT3D_LIB_D3DCOMPILER})
-            set (DIRECT3D_FOUND 1)
-        endif ()
-    else ()
-        find_library (DIRECT3D_LIB_D3D11 NAMES d3d11 PATHS ${DIRECTX_LIB_SEARCH_PATH})
-        find_library (DIRECT3D_LIB_D3DCOMPILER NAMES d3dcompiler PATHS ${DIRECTX_LIB_SEARCH_PATH})
-        find_library (DIRECT3D_LIB_DXGI NAMES dxgi PATHS ${DIRECTX_LIB_SEARCH_PATH})
-        find_library (DIRECT3D_LIB_DXGUID NAMES dxguid PATHS ${DIRECTX_LIB_SEARCH_PATH})
+    if (URHO3D_D3D11)
+        find_library (DIRECT3D_LIB_D3D11 NAMES d3d11 PATHS ${DIRECTX_LIB_SEARCH_PATH} DOC "Direct3D d3d11 library directory")
+        find_library (DIRECT3D_LIB_D3DCOMPILER NAMES d3dcompiler PATHS ${DIRECTX_LIB_SEARCH_PATH} DOC "Direct3D d3dcompiler library directory")
+        find_library (DIRECT3D_LIB_DXGI NAMES dxgi PATHS ${DIRECTX_LIB_SEARCH_PATH} DOC "Direct3D dxgi library directory")
+        find_library (DIRECT3D_LIB_DXGUID NAMES dxguid PATHS ${DIRECTX_LIB_SEARCH_PATH} DOC "Direct3D dxguid library directory")
         if (DIRECT3D_INCLUDE_DIRS AND DIRECT3D_LIB_D3D11 AND DIRECT3D_LIB_D3DCOMPILER AND DIRECT3D_LIB_DXGI AND DIRECT3D_LIB_DXGUID)
             set (DIRECT3D_LIBRARIES ${DIRECT3D_LIB_D3D11} ${DIRECT3D_LIB_D3DCOMPILER} ${DIRECT3D_LIB_DXGI} ${DIRECT3D_LIB_DXGUID})
-            set (DIRECT3D_FOUND 1)
         endif ()
+        mark_as_advanced (DIRECT3D_LIB_D3D11 DIRECT3D_LIB_D3DCOMPILER DIRECT3D_LIB_DXGI DIRECT3D_LIB_DXGUID)
+    else ()
+        find_library (DIRECT3D_LIB_D3D9 NAMES d3d9 PATHS ${DIRECTX_LIB_SEARCH_PATH} DOC "Direct3D d3d9 library directory")
+        find_library (DIRECT3D_LIB_D3DCOMPILER NAMES d3dcompiler PATHS ${DIRECTX_LIB_SEARCH_PATH} DOC "Direct3D d3dcompiler library directory")
+        if (DIRECT3D_INCLUDE_DIRS AND DIRECT3D_LIB_D3D9 AND DIRECT3D_LIB_D3DCOMPILER)
+            set (DIRECT3D_LIBRARIES ${DIRECT3D_LIB_D3D9} ${DIRECT3D_LIB_D3DCOMPILER})
+        endif ()
+        mark_as_advanced (DIRECT3D_LIB_D3D9 DIRECT3D_LIB_D3DCOMPILER)
     endif ()
+    set (FAIL_MESSAGE "Could NOT find Direct3D using DirectX SDK search paths")
 endif ()
 
-if (DIRECT3D_FOUND)
-    include (FindPackageMessage)
-    FIND_PACKAGE_MESSAGE (Direct3D "Found Direct3D: ${DIRECT3D_LIBRARIES} ${DIRECT3D_INCLUDE_DIRS}" "[${DIRECT3D_LIBRARIES}][${DIRECT3D_INCLUDE_DIRS}]")
-elseif (Direct3D_FIND_REQUIRED)
-    message (FATAL_ERROR "Could not find Direct3D in Windows SDK and DirectX SDK")
-endif ()
+# DIRECT3D_INCLUDE_DIRS is not required for Windows SDK, it is required for DirectX SDK but it is implied when DIRECT3D_LIBRARIES can be set
+include (FindPackageHandleStandardArgs)
+find_package_handle_standard_args (Direct3D REQUIRED_VARS DIRECT3D_LIBRARIES FAIL_MESSAGE ${FAIL_MESSAGE})
 
-mark_as_advanced (DIRECT3D_INCLUDE_DIRS DIRECT3D_LIBRARIES DIRECT3D_DLL)
+mark_as_advanced (DIRECT3D_INCLUDE_DIRS DIRECT3D_LIBRARIES)

+ 8 - 14
CMake/Modules/FindODBC.cmake

@@ -29,20 +29,15 @@
 #  ODBC_VERSION
 #
 
-if (ODBC_FOUND)
-    return ()
-endif ()
-
 if (WIN32)
     set (ODBC_INCLUDE_DIRS)     # The headers should be available in the default include search path
     set (ODBC_LIBRARIES odbc32) # This should be also the case for MinGW cross-compiler toolchain
     set (ODBC_DEFINES)
     set (ODBC_VERSION 3)        # Assume it is 3
-    set (ODBC_FOUND 1)
 else ()
     # On Unix-like host system, use the ODBC config tool
     find_program (ODBC_CONFIG NAMES odbc_config iodbc-config DOC "ODBC config tool" NO_CMAKE_FIND_ROOT_PATH)
-    if (ODBC_CONFIG)
+    if (ODBC_CONFIG AND NOT ODBC_INCLUDE_DIRS AND NOT ODBC_LIBRARIES)   # Only do this once
         # Get ODBC compile and link flags and turn them into include dirs and libraries list
         execute_process (COMMAND ${ODBC_CONFIG} --cflags OUTPUT_VARIABLE ODBC_COMPILE_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
         execute_process (COMMAND ${ODBC_CONFIG} --libs OUTPUT_VARIABLE ODBC_LINK_FLAGS OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
@@ -51,17 +46,16 @@ else ()
         list (GET ODBC_INCLUDE_DIRS 0 ODBC_DEFINES)     # Assume the list of defines always come before the list of include dirs
         list (REMOVE_AT ODBC_INCLUDE_DIRS 0)
         list (REMOVE_AT ODBC_LIBRARIES 0)
+        set (ODBC_INCLUDE_DIRS ${ODBC_INCLUDE_DIRS} CACHE PATH "ODBC include directory")
+        set (ODBC_LIBRARIES ${ODBC_LIBRARIES} CACHE STRING "ODBC library")
+        set (ODBC_DEFINES ${ODBC_DEFINES} CACHE STRING "ODBC defines")
         # Get ODBC version
         execute_process (COMMAND ${ODBC_CONFIG} --odbcversion OUTPUT_VARIABLE ODBC_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
-        set (ODBC_FOUND 1)
+        set (ODBC_VERSION ${ODBC_VERSION} CACHE INTERNAL "ODBC version")
     endif ()
 endif ()
 
-if (ODBC_FOUND)
-    include (FindPackageMessage)
-    FIND_PACKAGE_MESSAGE (ODBC "Found ODBC driver manager: ${ODBC_LIBRARIES} ${ODBC_INCLUDE_DIRS}" "[${ODBC_LIBRARIES}][${ODBC_INCLUDE_DIRS}]")
-elseif (ODBC_FIND_REQUIRED)
-    message (FATAL_ERROR "Could not find ODBC driver manager, please install the development package of unixODBC or libiodbc")
-endif ()
+include (FindPackageHandleStandardArgs)
+find_package_handle_standard_args (ODBC REQUIRED_VARS ODBC_LIBRARIES ODBC_INCLUDE_DIRS VERSION_VAR ODBC_VERSION FAIL_MESSAGE "Could NOT find ODBC driver manager, please install the development package for unixODBC or libiodbc")
 
-mark_as_advanced (ODBC_INCLUDE_DIRS ODBC_LIBRARIES ODBC_DEFINES ODBC_VERSION ODBC_CONFIG)
+mark_as_advanced (ODBC_INCLUDE_DIRS ODBC_LIBRARIES ODBC_DEFINES ODBC_CONFIG)

+ 43 - 0
CMake/Modules/FindPulseAudio.cmake

@@ -0,0 +1,43 @@
+#
+# Copyright (c) 2008-2015 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.
+#
+
+# Find PulseAudio development library
+#
+#  PA_FOUND
+#  PA_INCLUDE_DIRS
+#  PA_LIBRARIES
+#  PA_VERSION
+#
+
+find_path (PA_INCLUDE_DIRS NAMES pulse/pulseaudio.h DOC "PulseAudio include directory")
+find_library (PA_LIBRARIES NAMES pulse-simple DOC "PulseAudio library")
+
+if (NOT PA_VERSION AND PA_INCLUDE_DIRS AND EXISTS ${PA_INCLUDE_DIRS}/pulse/version.h)   # Only do this once
+    file (STRINGS "${PA_INCLUDE_DIRS}/pulse/version.h" PA_VERSION REGEX "^.*pa_get_headers_version.+\"[^\"]*\".*$")
+    string (REGEX REPLACE "^.*pa_get_headers_version.+\"([^\"]*)\".*$" \\1 PA_VERSION "${PA_VERSION}")      # Stringify to guard against empty variable
+    set (PA_VERSION "${PA_VERSION}" CACHE INTERNAL "PulseAudio version")
+endif ()
+
+include (FindPackageHandleStandardArgs)
+find_package_handle_standard_args (PA REQUIRED_VARS PA_LIBRARIES PA_INCLUDE_DIRS VERSION_VAR PA_VERSION FAIL_MESSAGE "Could NOT find PulseAudio development library")
+
+mark_as_advanced (PA_INCLUDE_DIRS PA_LIBRARIES)

+ 140 - 61
CMake/Modules/FindUrho3D.cmake

@@ -20,60 +20,42 @@
 # THE SOFTWARE.
 #
 
-# Find Urho3D include directories and library in source & build tree or installed Urho3D SDK.
+# Find Urho3D include directories and libraries in the Urho3D SDK installation or build tree
+# This module should be able to find Urho3D automatically when the SDK is installed in a system-wide default location
+# If the SDK installation location is non-default or the Urho3D library is not installed at all (i.e. still in its build tree) then
+#   use URHO3D_HOME environment variable or build option to specify the location of the non-default SDK installation or build tree
+# When setting URHO3D_HOME variable, it should be set to a parent directory containing both the "include" and "lib" subdirectories
+#   e.g. set URHO3D_HOME=/home/john/usr/local, if the SDK is installed using DESTDIR=/home/john and CMAKE_INSTALL_PREFIX=/usr/local
+
 #
 #  URHO3D_FOUND
 #  URHO3D_INCLUDE_DIRS
 #  URHO3D_LIBRARIES
-#  URHO3D_LIBRARIES_REL (WIN32 only)
-#  URHO3D_LIBRARIES_DBG (WIN32 only)
-#  URHO3D_DLL (WIN32 only)
-#  URHO3D_DLL_REL (WIN32 only)
-#  URHO3D_DLL_DBG (WIN32 only)
-#
-#
-# For internal Urho3D project, the Urho3D "build tree" path is already known.
+#  URHO3D_VERSION
+#  URHO3D_LIB_TYPE (use as input variable if specified; or as output variable if it is set based on module's search result)
 #
-# For external project that attempts to use the Urho3D build tree or installed Urho3D SDK,
-# use URHO3D_HOME environment variable or build option to specify the path (not needed when the path is a system-wide default location).
-# When setting URHO3D_HOME variable, it should be set to a parent directory containing both the "include" or "lib" subdirectories.
-# For example: set URHO3D_HOME=/home/john/usr/local, if the SDK is installed using DESTDIR=/home/john and CMAKE_INSTALL_PREFIX=/usr/local
+# WIN32 only:
+#  URHO3D_LIBRARIES_REL
+#  URHO3D_LIBRARIES_DBG
+#  URHO3D_DLL
+#  URHO3D_DLL_REL
+#  URHO3D_DLL_DBG
 #
 
-if (URHO3D_FOUND)
-    # All the subprojects should use the same Urho3D library, so only need to search on the first (sub)project that requires Urho3D library
-    return ()
-endif ()
-
-# If the URHO3D_LIB_TYPE build option changes then invalidate the found library cache
-if (NOT URHO3D_LIB_TYPE STREQUAL URHO3D_FOUND_LIB_TYPE)
-    unset (URHO3D_LIBRARIES CACHE)
-    set (URHO3D_FOUND_LIB_TYPE ${URHO3D_LIB_TYPE} CACHE INTERNAL "Lib type when Urho3D library was last found")
-
-    # Urho3D prefers static library type by default while CMake prefers shared one, so we need to change CMake preference to agree with Urho3D
-    if (NOT URHO3D_LIB_TYPE STREQUAL SHARED)
-        list (REVERSE CMAKE_FIND_LIBRARY_SUFFIXES)
-    endif ()
-endif ()
-
-# Cater for the shared library extension in Emscripten build has been changed to ".bc"
-if (EMSCRIPTEN)
-    string (REPLACE .so .bc CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}")   # Stringify for string replacement
-endif ()
-
 set (PATH_SUFFIX Urho3D)
+set (COMPILE_RESULT TRUE)
 if (CMAKE_PROJECT_NAME STREQUAL Urho3D AND TARGET Urho3D)
-    # Library location is already known to be in the build tree
+    # A special case where library location is already known to be in the build tree of Urho3D project
     set (URHO3D_HOME ${CMAKE_BINARY_DIR})
-    set (URHO3D_INCLUDE_DIRS ${URHO3D_HOME}/include ${URHO3D_HOME}/include/${PATH_SUFFIX}/ThirdParty)
+    set (URHO3D_INCLUDE_DIRS ${URHO3D_HOME}/include ${URHO3D_HOME}/include/Urho3D/ThirdParty)
     if (URHO3D_PHYSICS)
         # Bullet library depends on its own include dir to be added in the header search path
         # This is more practical than patching its header files in many places to make them work with relative path
-        list (APPEND URHO3D_INCLUDE_DIRS ${URHO3D_HOME}/include/${PATH_SUFFIX}/ThirdParty/Bullet)
+        list (APPEND URHO3D_INCLUDE_DIRS ${URHO3D_HOME}/include/Urho3D/ThirdParty/Bullet)
     endif ()
     if (URHO3D_LUA)
         # ditto for Lua/LuaJIT
-        list (APPEND URHO3D_INCLUDE_DIRS ${URHO3D_HOME}/include/${PATH_SUFFIX}/ThirdParty/Lua${JIT})
+        list (APPEND URHO3D_INCLUDE_DIRS ${URHO3D_HOME}/include/Urho3D/ThirdParty/Lua${JIT})
     endif ()
     set (URHO3D_LIBRARIES Urho3D)
     set (FOUND_MESSAGE "Found Urho3D: as CMake target")
@@ -82,6 +64,49 @@ else ()
     if (NOT URHO3D_HOME AND DEFINED ENV{URHO3D_HOME})
         file (TO_CMAKE_PATH "$ENV{URHO3D_HOME}" URHO3D_HOME)
     endif ()
+    # If either of the URHO3D_LIB_TYPE or URHO3D_HOME build options changes then invalidate all the caches
+    if ((URHO3D_LIB_TYPE AND NOT URHO3D_LIB_TYPE STREQUAL URHO3D_FOUND_LIB_TYPE) OR NOT URHO3D_BASE_INCLUDE_DIR MATCHES "^${URHO3D_HOME}/include/Urho3D$")
+        unset (URHO3D_BASE_INCLUDE_DIR CACHE)
+        unset (URHO3D_LIBRARIES CACHE)
+        if (WIN32)
+            unset (URHO3D_LIBRARIES_DBG CACHE)
+            unset (URHO3D_DLL_REL CACHE)
+            unset (URHO3D_DLL_DBG CACHE)
+        endif ()
+        # Urho3D prefers static library type by default while CMake prefers shared one, so we need to change CMake preference to agree with Urho3D
+        set (CMAKE_FIND_LIBRARY_SUFFIXES_SAVED ${CMAKE_FIND_LIBRARY_SUFFIXES})
+        if (NOT CMAKE_FIND_LIBRARY_SUFFIXES MATCHES ^\\.\(a|lib\))
+            list (REVERSE CMAKE_FIND_LIBRARY_SUFFIXES)
+        endif ()
+        # If library type is specified then only search for the requested library type
+        if (NOT MSVC AND URHO3D_LIB_TYPE)      # MSVC static lib and import lib have a same extension, so cannot use it for searches
+            if (URHO3D_LIB_TYPE STREQUAL STATIC)
+                set (CMAKE_FIND_LIBRARY_SUFFIXES .a)
+            elseif (URHO3D_LIB_TYPE STREQUAL SHARED)
+                if (MINGW)
+                    set (CMAKE_FIND_LIBRARY_SUFFIXES .dll.a)
+                elseif (APPLE)
+                    set (CMAKE_FIND_LIBRARY_SUFFIXES .dylib)
+                else ()
+                    set (CMAKE_FIND_LIBRARY_SUFFIXES .so)
+                endif ()
+            else ()
+                message (FATAL_ERROR "Library type: '${URHO3D_LIB_TYPE}' is not supported")
+            endif ()
+        endif ()
+        # Cater for the shared library extension in Emscripten build which is ".bc" instead of ".so"
+        if (EMSCRIPTEN)
+            string (REPLACE .so .bc CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_FIND_LIBRARY_SUFFIXES}")   # Stringify for string replacement
+        endif ()
+        # The PATH_SUFFIX does not work for CMake on Windows host system, it actually needs a prefix instead
+        if (CMAKE_HOST_WIN32)
+            set (CMAKE_SYSTEM_PREFIX_PATH_SAVED ${CMAKE_SYSTEM_PREFIX_PATH})
+            string (REPLACE ";" "\\Urho3D;" CMAKE_SYSTEM_PREFIX_PATH "${CMAKE_SYSTEM_PREFIX_PATH_SAVED};")    # Stringify for string replacement
+            if (NOT URHO3D_64BIT)
+                list (REVERSE CMAKE_SYSTEM_PREFIX_PATH)
+            endif ()
+        endif ()
+    endif ()
     # URHO3D_HOME variable should be an absolute path, so use a non-rooted search even when we are cross-compiling
     if (URHO3D_HOME)
         list (APPEND CMAKE_PREFIX_PATH ${URHO3D_HOME})
@@ -98,28 +123,33 @@ else ()
     if (NOT URHO3D_64BIT)
         # For 32-bit, force to search in 'lib' path even when the host system defaulted to use 'lib64'
         set_property (GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS FALSE)
-    elseif (WIN32)
-        # For 64-bit, force to search in 'lib64' path even when the Windows platform is not defaulted to use it
+    elseif (MINGW AND CMAKE_CROSSCOMPILING)
+        # For 64-bit MinGW on Linux host system, force to search in 'lib64' path even when the Windows platform is not defaulted to use it
         set_property (GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS TRUE)
     endif ()
-    find_path (URHO3D_INCLUDE_DIRS Urho3D.h PATH_SUFFIXES ${PATH_SUFFIX} ${SEARCH_OPT})
-    if (URHO3D_INCLUDE_DIRS)
-        set (BASE_INCLUDE_DIR ${URHO3D_INCLUDE_DIRS})   # Preserve the base include dir because the original variable will be turned into a list below
-        get_filename_component (PATH ${URHO3D_INCLUDE_DIRS} PATH)
-        set (URHO3D_INCLUDE_DIRS ${PATH} ${URHO3D_INCLUDE_DIRS}/ThirdParty)
+    find_path (URHO3D_BASE_INCLUDE_DIR Urho3D.h PATH_SUFFIXES ${PATH_SUFFIX} ${SEARCH_OPT} DOC "Urho3D include directory")
+    if (URHO3D_BASE_INCLUDE_DIR)
+        get_filename_component (URHO3D_INCLUDE_DIRS ${URHO3D_BASE_INCLUDE_DIR} PATH)
+        if (NOT URHO3D_HOME)
+            # URHO3D_HOME is not set when using SDK installed on system-wide default location, so set it now
+            get_filename_component (URHO3D_HOME ${URHO3D_INCLUDE_DIRS} PATH)
+        endif ()
+        list (APPEND URHO3D_INCLUDE_DIRS ${URHO3D_BASE_INCLUDE_DIR}/ThirdParty)
         if (URHO3D_PHYSICS)
-            list (APPEND URHO3D_INCLUDE_DIRS ${BASE_INCLUDE_DIR}/ThirdParty/Bullet)
+            list (APPEND URHO3D_INCLUDE_DIRS ${URHO3D_BASE_INCLUDE_DIR}/ThirdParty/Bullet)
         endif ()
         if (URHO3D_LUA)
-            list (APPEND URHO3D_INCLUDE_DIRS ${BASE_INCLUDE_DIR}/ThirdParty/Lua${JIT})
+            list (APPEND URHO3D_INCLUDE_DIRS ${URHO3D_BASE_INCLUDE_DIR}/ThirdParty/Lua${JIT})
         endif ()
-        if (NOT URHO3D_HOME)
-            # URHO3D_HOME is not set when using SDK installed on system-wide default location, so set it now
-            get_filename_component (PATH ${PATH} PATH)
-            set (URHO3D_HOME ${PATH})
+        # Intentionally do no cache the URHO3D_VERSION as it has potential to change frequently
+        file (STRINGS ${URHO3D_BASE_INCLUDE_DIR}/librevision.h URHO3D_VERSION REGEX "^const char\\* revision=\"[^\"]*\".*$")
+        string (REGEX REPLACE "^const char\\* revision=\"([^\"]*)\".*$" \\1 URHO3D_VERSION "${URHO3D_VERSION}")      # Stringify to guard against empty variable
+        # The library type is baked into export header only for MSVC as it has no other way to differentiate them, fortunately both types cannot coexist for MSVC anyway unlike other compilers
+        if (MSVC)
+            file (STRINGS ${URHO3D_BASE_INCLUDE_DIR}/Urho3D.h MSVC_STATIC_LIB REGEX "^#define URHO3D_STATIC_DEFINE$")
         endif ()
     endif ()
-    find_library (URHO3D_LIBRARIES NAMES Urho3D ${URHO3D_LIB_SEARCH_HINT} PATH_SUFFIXES ${PATH_SUFFIX} ${SEARCH_OPT})
+    find_library (URHO3D_LIBRARIES NAMES Urho3D ${URHO3D_LIB_SEARCH_HINT} PATH_SUFFIXES ${PATH_SUFFIX} ${SEARCH_OPT} DOC "Urho3D library directory")
     if (WIN32)
         # For Windows platform, give a second chance to search for a debug version of the library
         find_library (URHO3D_LIBRARIES_DBG NAMES Urho3D_d ${URHO3D_LIB_SEARCH_HINT} PATH_SUFFIXES ${PATH_SUFFIX} ${SEARCH_OPT})
@@ -133,35 +163,84 @@ else ()
         else ()
             set (URHO3D_LIBRARIES ${URHO3D_LIBRARIES_DBG})
         endif ()
-        # For shared library type, also initialize the URHO3D_DLL variable for later use
+    endif ()
+    # Ensure the module has found the right one if the library type is specified
+    if (MSVC)
+        if (URHO3D_LIB_TYPE)
+            if (NOT ((URHO3D_LIB_TYPE STREQUAL STATIC AND MSVC_STATIC_LIB) OR (URHO3D_LIB_TYPE STREQUAL SHARED AND NOT MSVC_STATIC_LIB)))
+                unset (URHO3D_LIB_TYPE)
+            endif ()
+        else ()
+            if (MSVC_STATIC_LIB)
+                set (URHO3D_LIB_TYPE STATIC)
+            else ()
+                set (URHO3D_LIB_TYPE SHARED)
+            endif ()
+        endif ()
+    elseif (URHO3D_LIBRARIES)
+        get_filename_component (EXT ${URHO3D_LIBRARIES} EXT)
+        if (EXT STREQUAL .a)
+            set (URHO3D_LIB_TYPE STATIC)
+            # For Non-MSVC compiler the static define is not baked into the export header file so we need to define it for the try_compile below
+            set (COMPILER_STATIC_FLAG COMPILE_DEFINITIONS -DURHO3D_STATIC_DEFINE)
+        else ()
+            set (URHO3D_LIB_TYPE SHARED)
+        endif ()
+    endif ()
+    # Ensure the module has found the library with the right ABI for the chosen compiler and URHO3D_64BIT build option
+    if (URHO3D_LIBRARIES AND NOT IOS AND NOT ANDROID)
+        if (NOT URHO3D_64BIT AND NOT MSVC AND NOT MINGW AND NOT ANDROID AND NOT RPI AND NOT EMSCRIPTEN)
+            set (COMPILER_32BIT_FLAG -DCOMPILE_DEFINITIONS:STRING=-m32)
+        endif ()
+        # BCM_VC_LIBRARIES variable is only populated when targeting RPI and empty otherwise, so it is safe to always append the variable
+        try_compile (COMPILE_RESULT ${CMAKE_BINARY_DIR}/generated/FindUrho3D ${CMAKE_CURRENT_LIST_DIR}/CheckUrho3DLibrary.cpp
+            CMAKE_FLAGS -DLINK_LIBRARIES:STRING=${URHO3D_LIBRARIES}\;${BCM_VC_LIBRARIES} -DINCLUDE_DIRECTORIES:STRING=${URHO3D_INCLUDE_DIRS} ${COMPILER_32BIT_FLAG} ${COMPILER_STATIC_FLAG}
+            OUTPUT_VARIABLE TRY_COMPILE_OUT)
+    endif ()
+    # For shared library type, also initialize the URHO3D_DLL variable for later use
+    if (WIN32)
         if (URHO3D_LIB_TYPE STREQUAL SHARED AND URHO3D_HOME)
-            find_file (URHO3D_DLL_REL Urho3D.dll HINTS ${URHO3D_HOME}/bin NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
+            find_file (URHO3D_DLL_REL Urho3D.dll HINTS ${URHO3D_HOME}/bin NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH DOC "Urho3D release DLL")
             if (URHO3D_DLL_REL)
                 list (APPEND URHO3D_DLL ${URHO3D_DLL_REL})
             endif ()
-            find_file (URHO3D_DLL_DBG Urho3D_d.dll HINTS ${URHO3D_HOME}/bin NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
+            find_file (URHO3D_DLL_DBG Urho3D_d.dll HINTS ${URHO3D_HOME}/bin NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH DOC "Urho3D debug DLL")
             if (URHO3D_DLL_DBG)
                 list (APPEND URHO3D_DLL ${URHO3D_DLL_DBG})
             endif ()
         endif ()
     endif ()
+    # Restore CMake global settings
+    if (CMAKE_FIND_LIBRARY_SUFFIXES_SAVED)
+        set (CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_SAVED})
+    endif ()
+    if (CMAKE_SYSTEM_PREFIX_PATH_SAVED)
+        set (CMAKE_SYSTEM_PREFIX_PATH ${CMAKE_SYSTEM_PREFIX_PATH_SAVED})
+    endif ()
 endif ()
 
-if (URHO3D_INCLUDE_DIRS AND URHO3D_LIBRARIES)
+if (URHO3D_INCLUDE_DIRS AND URHO3D_LIBRARIES AND URHO3D_LIB_TYPE AND COMPILE_RESULT)
     set (URHO3D_FOUND 1)
     if (NOT FOUND_MESSAGE)
         set (FOUND_MESSAGE "Found Urho3D: ${URHO3D_LIBRARIES}")
+        if (URHO3D_VERSION)
+            set (FOUND_MESSAGE "${FOUND_MESSAGE} (found version \"${URHO3D_VERSION}\")")
+        endif ()
     endif ()
     include (FindPackageMessage)
-    FIND_PACKAGE_MESSAGE (Urho3D ${FOUND_MESSAGE} "[${URHO3D_LIBRARIES}][${URHO3D_INCLUDE_DIRS}]")
-    set (URHO3D_HOME ${URHO3D_HOME} CACHE PATH "Path to Urho3D build tree or SDK installation location (external project only)" FORCE)
+    find_package_message (Urho3D ${FOUND_MESSAGE} "[${URHO3D_LIBRARIES}][${URHO3D_INCLUDE_DIRS}]")
+    set (URHO3D_HOME ${URHO3D_HOME} CACHE PATH "Path to Urho3D build tree or SDK installation location" FORCE)
+    set (URHO3D_FOUND_LIB_TYPE ${URHO3D_LIB_TYPE} CACHE INTERNAL "Lib type (if specified) when Urho3D library was last found")
 elseif (Urho3D_FIND_REQUIRED)
     if (ANDROID)
         set (NOT_FOUND_MESSAGE "For Android platform, double check if you have specified to use the same ANDROID_ABI as the Urho3D library in the build tree or SDK.")
     endif ()
+    if (URHO3D_LIB_TYPE)
+        set (NOT_FOUND_MESSAGE "Ensure the specified location contains the Urho3D library of the requested library type. ${NOT_FOUND_MESSAGE}")
+    endif ()
     message (FATAL_ERROR
-        "Could not find Urho3D library in Urho3D build tree or SDK installation. "
-        "Use URHO3D_HOME environment variable or build option to specify the location of the build tree or SDK installation. ${NOT_FOUND_MESSAGE}")
+        "Could NOT find compatible Urho3D library in Urho3D SDK installation or build tree. "
+        "Use URHO3D_HOME environment variable or build option to specify the location of the non-default SDK installation or build tree. ${NOT_FOUND_MESSAGE} ${TRY_COMPILE_OUT}")
 endif ()
 
-mark_as_advanced (URHO3D_INCLUDE_DIRS URHO3D_LIBRARIES URHO3D_LIBRARIES_REL URHO3D_LIBRARIES_DBG URHO3D_DLL URHO3D_DLL_REL URHO3D_DLL_DBG URHO3D_HOME)
+mark_as_advanced (URHO3D_BASE_INCLUDE_DIR URHO3D_LIBRARIES URHO3D_LIBRARIES_DBG URHO3D_DLL_REL URHO3D_DLL_DBG URHO3D_HOME)

+ 71 - 64
CMake/Modules/Urho3D-CMake-common.cmake

@@ -111,7 +111,56 @@ if (IOS OR (RPI AND "${RPI_ABI}" MATCHES NEON))    # Stringify in case RPI_ABI i
     set (NEON TRUE)
 endif ()
 cmake_dependent_option (URHO3D_NEON "Enable NEON instruction set (ARM platforms with NEON only)" TRUE "NEON" FALSE)
+# The URHO3D_OPENGL option is not defined on non-Windows platforms as they should always use OpenGL
+if (MSVC)
+    # On MSVC compiler, default to false (i.e. prefers Direct3D)
+    # OpenGL can be manually enabled with -DURHO3D_OPENGL=1, but Windows graphics card drivers are usually better optimized for Direct3D
+    set (DEFAULT_OPENGL FALSE)
+else ()
+    # On non-MSVC compiler on Windows platform, default to true to enable use of OpenGL instead of Direct3D
+    # Direct3D can be manually enabled with -DURHO3D_OPENGL=0, but it is likely to fail unless the MinGW-w64 distribution is used due to dependency to Direct3D headers and libs
+    set (DEFAULT_OPENGL TRUE)
+endif ()
+cmake_dependent_option (URHO3D_OPENGL "Use OpenGL instead of Direct3D (Windows platform only)" ${DEFAULT_OPENGL} "WIN32" TRUE)      # Force the variable to TRUE when not WIN32
+# On Windows platform Direct3D11 can be optionally chosen
+# Using Direct3D11 on non-MSVC compiler may require copying and renaming Microsoft official libraries (.lib to .a), else link failures or non-functioning graphics may result
+cmake_dependent_option (URHO3D_D3D11 "Use Direct3D11 instead of Direct3D9 (Windows platform only); overrides URHO3D_OPENGL option" FALSE "WIN32" FALSE)
+if (CMAKE_HOST_WIN32)
+    if (NOT DEFINED URHO3D_MKLINK)
+        # Test whether the host system is capable of setting up symbolic link
+        execute_process (COMMAND cmd /C mklink test-link CMakeCache.txt RESULT_VARIABLE MKLINK_EXIT_CODE OUTPUT_QUIET ERROR_QUIET)
+        if (MKLINK_EXIT_CODE EQUAL 0)
+            set (URHO3D_MKLINK TRUE)
+            file (REMOVE ${CMAKE_BINARY_DIR}/test-link)
+        else ()
+            set (URHO3D_MKLINK FALSE)
+            message (WARNING "Could not use MKLINK to setup symbolic links as this Windows user account does not have the privilege to do so. "
+                "When MKLINK is not available then the build system will fallback to use file/directory copy of the library headers from source tree to build tree. "
+                "In order to prevent stale headers being used in the build, this file/directory copy will be redone also as a post-build step for each library targets. "
+                "This may slow down the build unnecessarily or even cause other unforseen issues due to incomplete or stale headers in the build tree. "
+                "Request your Windows Administrator to grant your user account to have privilege to create symlink via MKLINK command. "
+                "You are NOT advised to use the Administrator account directly to generate build tree in all cases.")
+        endif ()
+        set (URHO3D_MKLINK ${URHO3D_MKLINK} CACHE INTERNAL "MKLINK capability on the Windows host system")
+    endif ()
+    set (NULL_DEVICE nul)
+else ()
+    set (NULL_DEVICE /dev/null)
+endif ()
+# Find Direct3D include & library directories in MS Windows SDK or DirectX SDK when not using OpenGL.
+if (WIN32 AND NOT URHO3D_OPENGL)
+    find_package (Direct3D REQUIRED)
+    if (DIRECT3D_INCLUDE_DIRS)
+        include_directories (${DIRECT3D_INCLUDE_DIRS})
+    endif ()
+endif ()
+# For Raspbery Pi, find Broadcom VideoCore IV firmware
+if (RPI)
+    find_package (BCM_VC REQUIRED)
+    include_directories (${BCM_VC_INCLUDE_DIRS})
+endif ()
 if (CMAKE_PROJECT_NAME STREQUAL Urho3D)
+    set (URHO3D_LIB_TYPE STATIC CACHE STRING "Specify Urho3D library type, possible values are STATIC (default) and SHARED")
     cmake_dependent_option (URHO3D_LUAJIT_AMALG "Enable LuaJIT amalgamated build (LuaJIT only)" FALSE "URHO3D_LUAJIT" FALSE)
     cmake_dependent_option (URHO3D_SAFE_LUA "Enable Lua C++ wrapper safety checks (Lua/LuaJIT only)" FALSE "URHO3D_LUA OR URHO3D_LUAJIT" FALSE)
     if (CMAKE_BUILD_TYPE STREQUAL Release OR CMAKE_CONFIGURATION_TYPES)
@@ -138,9 +187,15 @@ if (CMAKE_PROJECT_NAME STREQUAL Urho3D)
         cmake_dependent_option (URHO3D_USE_LIB_DEB "Enable 64-bit DEB CPack generator using /usr/lib and disable all other generators (Redhat-based host only)" FALSE "URHO3D_64BIT AND HAS_LIB64" FALSE)
     endif ()
 else ()
-    set (URHO3D_HOME "" CACHE PATH "Path to Urho3D build tree or SDK installation location (external project only)")
-    if (URHO3D_PCH OR URHO3D_UPDATE_SOURCE_TREE)
-        # Just reference it to suppress "unused variable" CMake warning on external projects using this CMake module
+    set (URHO3D_LIB_TYPE "" CACHE STRING "Specify Urho3D library type, possible values are STATIC and SHARED")
+    set (URHO3D_HOME "" CACHE PATH "Path to Urho3D build tree or SDK installation location (downstream project only)")
+    if (URHO3D_PCH OR URHO3D_UPDATE_SOURCE_TREE OR URHO3D_TOOLS OR URHO3D_EXTRAS)
+        # Just reference it to suppress "unused variable" CMake warning on downstream projects using this CMake module
+    endif ()
+    # All Urho3D downstream projects require Urho3D library, so find Urho3D library here now
+    if (NOT CMAKE_PROJECT_NAME MATCHES ^Urho3D-ExternalProject-)
+        find_package (Urho3D REQUIRED)
+        include_directories (${URHO3D_INCLUDE_DIRS})
     endif ()
 endif ()
 option (URHO3D_PACKAGING "Enable resources packaging support, on Emscripten default to 1, on other platforms default to 0" ${EMSCRIPTEN})
@@ -167,46 +222,9 @@ else ()
         unset (EMSCRIPTEN_EMRUN_BROWSER CACHE)
     endif ()
 endif ()
-# The URHO3D_OPENGL option is not defined on non-Windows platforms as they should always use OpenGL
-if (MSVC)
-    # On MSVC compiler, default to false (i.e. prefers Direct3D)
-    # OpenGL can be manually enabled with -DURHO3D_OPENGL=1, but Windows graphics card drivers are usually better optimized for Direct3D
-    set (DEFAULT_OPENGL FALSE)
-else ()
-    # On non-MSVC compiler on Windows platform, default to true to enable use of OpenGL instead of Direct3D
-    # Direct3D can be manually enabled with -DURHO3D_OPENGL=0, but it is likely to fail unless the MinGW-w64 distribution is used due to dependency to Direct3D headers and libs
-    set (DEFAULT_OPENGL TRUE)
-endif ()
-cmake_dependent_option (URHO3D_OPENGL "Use OpenGL instead of Direct3D (Windows platform only)" ${DEFAULT_OPENGL} "WIN32" TRUE)      # Force the variable to TRUE when not WIN32
-# On Windows platform Direct3D11 can be optionally chosen
-# Using Direct3D11 on non-MSVC compiler may require copying and renaming Microsoft official libraries (.lib to .a), else link failures or non-functioning graphics may result
-cmake_dependent_option (URHO3D_D3D11 "Use Direct3D11 instead of Direct3D9 (Windows platform only); overrides URHO3D_OPENGL option" FALSE "WIN32" FALSE)
-if (CMAKE_HOST_WIN32)
-    if (NOT DEFINED URHO3D_MKLINK)
-        # Test whether the host system is capable of setting up symbolic link
-        execute_process (COMMAND cmd /C mklink test-link CMakeCache.txt RESULT_VARIABLE MKLINK_EXIT_CODE OUTPUT_QUIET ERROR_QUIET)
-        if (MKLINK_EXIT_CODE EQUAL 0)
-            set (URHO3D_MKLINK TRUE)
-            file (REMOVE ${CMAKE_BINARY_DIR}/test-link)
-        else ()
-            set (URHO3D_MKLINK FALSE)
-            message (WARNING "Could not use MKLINK to setup symbolic links as this Windows user account does not have the privilege to do so. "
-                "When MKLINK is not available then the build system will fallback to use file/directory copy of the library headers from source tree to build tree. "
-                "In order to prevent stale headers being used in the build, this file/directory copy will be redone also as a post-build step for each library targets. "
-                "This may slow down the build unnecessarily or even cause other unforseen issues due to incomplete or stale headers in the build tree. "
-                "Request your Windows Administrator to grant your user account to have privilege to create symlink via MKLINK command. "
-                "You are NOT advised to use the Administrator account directly to generate build tree in all cases.")
-        endif ()
-        set (URHO3D_MKLINK ${URHO3D_MKLINK} CACHE INTERNAL "MKLINK capability on the Windows host system")
-    endif ()
-    set (NULL_DEVICE nul)
-else ()
-    set (NULL_DEVICE /dev/null)
-endif ()
 cmake_dependent_option (URHO3D_STATIC_RUNTIME "Use static C/C++ runtime libraries and eliminate the need for runtime DLLs installation (VS only)" FALSE "MSVC" FALSE)
 cmake_dependent_option (URHO3D_WIN32_CONSOLE "Use console main() as entry point when setting up Windows executable targets (Windows platform only)" FALSE "WIN32" FALSE)
 cmake_dependent_option (URHO3D_MACOSX_BUNDLE "Use MACOSX_BUNDLE when setting up Mac OS X executable targets (Xcode native build only)" FALSE "XCODE AND NOT IOS" FALSE)
-set (URHO3D_LIB_TYPE STATIC CACHE STRING "Specify Urho3D library type, possible values are STATIC (default) and SHARED")
 if (CMAKE_CROSSCOMPILING AND NOT ANDROID)
     set (URHO3D_SCP_TO_TARGET "" CACHE STRING "Use scp to transfer executables to target system (non-Android cross-compiling build only), SSH digital key must be setup first for this to work, typical value has a pattern of usr@tgt:remote-loc")
 else ()
@@ -397,7 +415,9 @@ if (URHO3D_LIB_TYPE)
 endif ()
 if (NOT URHO3D_LIB_TYPE STREQUAL SHARED)
     set (URHO3D_LIB_TYPE STATIC)
-    add_definitions (-DURHO3D_STATIC_DEFINE)
+    if (NOT MSVC)   # This define will be baked into the export header for MSVC compiler
+        add_definitions (-DURHO3D_STATIC_DEFINE)
+    endif ()
 endif ()
 
 # Add definition for AngelScript
@@ -456,27 +476,13 @@ if (URHO3D_DATABASE_SQLITE)
     add_definitions (-DURHO3D_DATABASE -DURHO3D_DATABASE_SQLITE)
 endif ()
 
-# Find Direct3D include & library directories in MS Windows SDK or DirectX SDK when not using OpenGL.
-if (WIN32 AND NOT URHO3D_OPENGL)
-    find_package (Direct3D REQUIRED)
-    if (DIRECT3D_INCLUDE_DIRS)
-        include_directories (${DIRECT3D_INCLUDE_DIRS})
-    endif ()
-endif ()
-
-# For Raspbery Pi, find Broadcom VideoCore IV firmware
-if (RPI)
-    find_package (BCM_VC REQUIRED)
-    include_directories (${BCM_VC_INCLUDE_DIRS})
-endif ()
-
 # Platform and compiler specific options
 if (URHO3D_C++11)
     add_definitions (-DURHO3D_CPP11)   # Note the define is NOT 'URHO3D_C++11'!
     if (CMAKE_CXX_COMPILER_ID MATCHES GNU)
         # Use gnu++11/gnu++0x instead of c++11/c++0x as the latter does not work as expected when cross compiling
         foreach (STANDARD gnu++11 gnu++0x)  # Fallback to gnu++0x on older GCC version
-            execute_process (COMMAND echo COMMAND ${CMAKE_CXX_COMPILER} -E - RESULT_VARIABLE GCC_EXIT_CODE OUTPUT_QUIET ERROR_QUIET)
+            execute_process (COMMAND echo COMMAND ${CMAKE_CXX_COMPILER} -std=${STANDARD} -E - RESULT_VARIABLE GCC_EXIT_CODE OUTPUT_QUIET ERROR_QUIET)
             if (GCC_EXIT_CODE EQUAL 0)
                 set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=${STANDARD}")
                 break ()
@@ -1028,7 +1034,7 @@ macro (setup_executable)
             endforeach ()
         endif ()
     endif ()
-    # Need to check if the destination variable is defined first because this macro could be called by external project that does not wish to install anything
+    # Need to check if the destination variable is defined first because this macro could be called by downstream project that does not wish to install anything
     if (DEST_RUNTIME_DIR)
         if (EMSCRIPTEN)
             # todo: Just use generator-expression when CMake minimum version is 3.0
@@ -1169,7 +1175,7 @@ macro (setup_main_executable)
     if (NOT RESOURCE_DIRS)
         # If the macro caller has not defined the resource dirs then set them based on Urho3D project convention
         foreach (DIR ${CMAKE_SOURCE_DIR}/bin/CoreData ${CMAKE_SOURCE_DIR}/bin/Data)
-            # Do not assume external project always follows Urho3D project convention, so double check if this directory exists before using it
+            # Do not assume downstream project always follows Urho3D project convention, so double check if this directory exists before using it
             if (IS_DIRECTORY ${DIR})
                 list (APPEND RESOURCE_DIRS ${DIR})
             endif ()
@@ -1186,7 +1192,7 @@ macro (setup_main_executable)
                 set_source_files_properties (${RESOURCE_${DIR}_PATHNAME} PROPERTIES EMCC_OPTION preload-file EMCC_FILE_ALIAS "@/${NAME}.pak --use-preload-cache")
             endif ()
         endforeach ()
-        # Urho3D project builds the PackageTool as required; external project uses PackageTool found in the Urho3D build tree or Urho3D SDK
+        # Urho3D project builds the PackageTool as required; downstream project uses PackageTool found in the Urho3D build tree or Urho3D SDK
         find_Urho3d_tool (PACKAGE_TOOL PackageTool
             HINTS ${CMAKE_BINARY_DIR}/bin/tool ${URHO3D_HOME}/bin/tool
             DOC "Path to PackageTool" MSG_MODE WARNING)
@@ -1196,7 +1202,7 @@ macro (setup_main_executable)
         set (PACKAGING_COMMENT " and packaging")
         set_property (SOURCE ${RESOURCE_PAKS} PROPERTY GENERATED TRUE)
         if (EMSCRIPTEN)
-            # Check if shell-file is already added in source files list by external project
+            # Check if shell-file is already added in source files list by downstream project
             if (NOT CMAKE_PROJECT_NAME STREQUAL Urho3D)
                 foreach (FILE ${SOURCE_FILES})
                     get_property (EMCC_OPTION SOURCE ${FILE} PROPERTY EMCC_OPTION)
@@ -1217,7 +1223,7 @@ macro (setup_main_executable)
                 set (SHARED_RESOURCE_JS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CMAKE_PROJECT_NAME}.js)
                 list (APPEND SOURCE_FILES ${SHARED_RESOURCE_JS} ${SHARED_RESOURCE_JS}.data)
                 set_source_files_properties (${SHARED_RESOURCE_JS} PROPERTIES GENERATED TRUE EMCC_OPTION pre-js)
-                # Need to check if the destination variable is defined first because this macro could be called by external project that does not wish to install anything
+                # Need to check if the destination variable is defined first because this macro could be called by downstream project that does not wish to install anything
                 if (DEST_BUNDLE_DIR)
                     install (FILES ${SHARED_RESOURCE_JS} ${SHARED_RESOURCE_JS}.data DESTINATION ${DEST_BUNDLE_DIR})
                 endif ()
@@ -1251,7 +1257,7 @@ macro (setup_main_executable)
         endif ()
         # Add SDL native init function, SDL_Main() entry point must be defined by one of the source files in ${SOURCE_FILES}
         find_Urho3D_file (ANDROID_MAIN_C_PATH SDL_android_main.c
-            HINTS ${URHO3D_HOME}/include/${PATH_SUFFIX}/ThirdParty/SDL/android ${CMAKE_SOURCE_DIR}/Source/ThirdParty/SDL/src/main/android
+            HINTS ${URHO3D_HOME}/include/Urho3D/ThirdParty/SDL/android ${CMAKE_SOURCE_DIR}/Source/ThirdParty/SDL/src/main/android
             DOC "Path to SDL_android_main.c" MSG_MODE FATAL_ERROR)
         list (APPEND SOURCE_FILES ${ANDROID_MAIN_C_PATH})
         # Setup shared library output path
@@ -1400,7 +1406,7 @@ endmacro ()
 
 # Macro for defining external library dependencies
 # The purpose of this macro is emulate CMake to set the external library dependencies transitively
-# It works for both targets setup within Urho3D project and outside Urho3D project that uses Urho3D as external static/shared library
+# It works for both targets setup within Urho3D project and downstream projects that uses Urho3D as external static/shared library
 macro (define_dependency_libs TARGET)
     # ThirdParty/SDL external dependency
     if (${TARGET} MATCHES SDL|Urho3D)
@@ -1594,7 +1600,7 @@ endmacro ()
 #  BASE <value> - An absolute base path to be prepended to the destination path when installing to build tree, default to build tree
 #  DESTINATION <value> - A relative destination path to be installed to
 macro (install_header_files)
-    # Need to check if the destination variable is defined first because this macro could be called by external project that does not wish to install anything
+    # Need to check if the destination variable is defined first because this macro could be called by downstream project that does not wish to install anything
     if (DEST_INCLUDE_DIR)
         # Parse the arguments for the underlying install command for the SDK
         cmake_parse_arguments (ARG "FILES_MATCHING;USE_FILE_SYMLINK;BUILD_TREE_ONLY" "BASE;DESTINATION" "FILES;DIRECTORY;PATTERN" ${ARGN})
@@ -1686,6 +1692,7 @@ if (ANDROID)
         file (REMOVE ${ANDROID_LIBRARY_OUTPUT_PATH}/gdbserver)
     endif ()
     # Create symbolic links in the build tree
+    file (MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/Android/assets)
     foreach (I CoreData Data)
         if (NOT EXISTS ${CMAKE_SOURCE_DIR}/Android/assets/${I})
             create_symlink (${CMAKE_SOURCE_DIR}/bin/${I} ${CMAKE_SOURCE_DIR}/Android/assets/${I} FALLBACK_TO_COPY)

+ 10 - 8
CMakeLists.txt

@@ -20,12 +20,8 @@
 # THE SOFTWARE.
 #
 
-# Set project name
-project (Urho3D)
-
-# Set minimum version
+# Set CMake minimum version and CMake policy required by Urho3D-CMake-common module
 cmake_minimum_required (VERSION 2.8.6)
-
 if (COMMAND cmake_policy)
     cmake_policy (SET CMP0003 NEW)
     if (CMAKE_VERSION VERSION_GREATER 2.8.12 OR CMAKE_VERSION VERSION_EQUAL 2.8.12)
@@ -40,6 +36,9 @@ if (COMMAND cmake_policy)
     endif ()
 endif ()
 
+# Set project name
+project (Urho3D)
+
 # Set CMake modules search path
 set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMake/Modules)
 
@@ -47,12 +46,15 @@ set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMake/Modules)
 include (Urho3D-CMake-common)
 
 # Setup SDK install destinations
+set (PATH_SUFFIX Urho3D)
 if (WIN32)
     set (SCRIPT_EXT .bat)
+    if (CMAKE_HOST_WIN32)
+        set (PATH_SUFFIX .)
+    endif ()
 else ()
     set (SCRIPT_EXT .sh)
 endif ()
-set (PATH_SUFFIX Urho3D)
 if (ANDROID)
     # For Android platform, install to a path similar to ANDROID_LIBRARY_OUTPUT_PATH variable, e.g. libs/armeabi-v7a
     set (LIB_SUFFIX s/${ANDROID_NDK_ABI_NAME})
@@ -61,11 +63,11 @@ elseif (URHO3D_64BIT)
         set (HAS_LIB64 TRUE)
     endif ()
     # Install to 'lib64' when one of these conditions is true
-    if (WIN32 OR URHO3D_USE_LIB64_RPM OR (HAS_LIB64 AND NOT URHO3D_USE_LIB_DEB))
+    if ((MINGW AND CMAKE_CROSSCOMPILING) OR URHO3D_USE_LIB64_RPM OR (HAS_LIB64 AND NOT URHO3D_USE_LIB_DEB))
         set (LIB_SUFFIX 64)
     endif ()
 endif ()
-set (DEST_INCLUDE_DIR include/${PATH_SUFFIX})
+set (DEST_INCLUDE_DIR include/Urho3D)    # The include directory path contains the 'Urho3D' suffix regardless of PATH_SUFFIX variable
 set (DEST_SHARE_DIR share/${PATH_SUFFIX})
 set (DEST_RUNTIME_DIR bin)
 set (DEST_BUNDLE_DIR ${DEST_SHARE_DIR}/Applications)

Різницю між файлами не показано, бо вона завелика
+ 135 - 1
Docs/AngelScriptAPI.h


+ 1 - 1
Docs/Doxyfile.in

@@ -852,7 +852,7 @@ VERBATIM_HEADERS       = NO
 # of all compounds will be generated. Enable this if the project 
 # contains a lot of classes, structs, unions or interfaces.
 
-ALPHABETICAL_INDEX     = NO
+ALPHABETICAL_INDEX     = YES
 
 # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
 # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 

+ 12 - 19
Docs/GettingStarted.dox

@@ -12,7 +12,7 @@ Although all required third-party libraries are included as source code, there a
 
 - For Windows, the June 2010 DirectX SDK needs to be installed. This is not necessary if building on Visual Studio 2012 or newer, which install the Windows SDK with the necessary DirectX files.
 
-- For Linux, the following development packages need to be installed: libx11-dev, libxrandr-dev, libasound2-dev on Debian-based distros; libX11-devel, libXrandr-devel, alsa-lib-devel on RedHat-based distros. Also install the package libgl1-mesa-dev (Debian) or mesa-libGL-devel (RH) if your GPU driver does not include OpenGL headers & libs, but in most of the case it usually does and has better performance than Mesa's OpenGL implementation. Building as 32-bit on a 64-bit system requires installing also the 32-bit versions of the development libraries.
+- For Linux, the following development packages need to be installed: libx11-dev, libxrandr-dev, libasound2-dev on Debian-based distros; libX11-devel, libXrandr-devel, alsa-lib-devel on RedHat-based distros. Optionally install libpulse-dev (Deb) or pulseaudio-libs-devel (RH) to enable SDL support for PulseAudio. Building as 32-bit on a 64-bit system requires installing also the 32-bit versions of the development libraries.
 
 - For Raspberry Pi, the following development packages need to be installed. On Raspbian: libasound2-dev, libudev-dev. On Pidora: alsa-lib-devel, systemd-devel.
 
@@ -111,7 +111,7 @@ A number of build options can be defined when invoking the build scripts or when
 |URHO3D_UPDATE_SOURCE_TREE|0|Enable commands to copy back some of the generated build artifacts from build tree to source tree to facilitate devs to push them as part of a commit (for library devs with push right only)|
 |URHO3D_USE_LIB64_RPM |0|Enable 64-bit RPM CPack generator using /usr/lib64 and disable all other generators (Debian-based host only, which uses /usr/lib by default)|
 |URHO3D_USE_LIB_DEB   |0|Enable 64-bit DEB CPack generator using /usr/lib and disable all other generators (Redhat-based host only, which uses /usr/lib64 by default)|
-|URHO3D_HOME          |-|Path to Urho3D build tree or SDK installation location (external project only)|
+|URHO3D_HOME          |-|Path to Urho3D build tree or SDK installation location (downstream project only)|
 |URHO3D_DEPLOYMENT_TARGET|native|Specify the minimum CPU type on which the target binaries are to be deployed (Linux, MinGW, and non-Xcode OSX native build only), see GCC/Clang's -march option for possible values; Use 'generic' for targeting a wide range of generic processors|
 |CMAKE_BUILD_TYPE     |Release|Specify CMake build configuration (single-configuration generator only), possible values are Release (default), RelWithDebInfo, and Debug|
 |CMAKE_INSTALL_PREFIX |*|Install path prefix, prepended onto install directories; default to 'c:/Program Files/Urho3D' on Windows host and '/usr/local' on all other non-Windows hosts|
@@ -265,7 +265,7 @@ The build process first builds the Urho3D library target (either static or share
 
 To install the Urho3D library as an SDK, use the usual 'make install' command. There is an equivalent built-in target called "install" in Visual Studio solution and Xcode project to perform the SDK installation. This could be useful when you want your application to always link against a 'stable' installed version of the Urho3D library, while keeping your Urho3D project root tree in sync with origin/master; or when you want other users in the same host system to use the installed Urho3D SDK instead of them building from source again.
 
-The default install prefix is 'c:/Program Files/Urho3D' on Windows host and '/usr/local' on all other non-Windows hosts. You can use the CMAKE_INSTALL_PREFIX build option to alter this prefix path. However, when cross-compiling you may actually want to alter the final installation destination by supplying DESDIR environment variable instead of altering the CMAKE_INSTALL_PREFIX variable directly, especially when using Urho3D library as shared library type because the installed executables have their RPATH adjusted relative to CMAKE_INSTALL_PREFIX. The DESTDIR environment variable is not applicable for installation on Windows host, i.e. it can only use CMAKE_INSTALL_PREFIX variable to alter the final installation destination. This is not a lost, however, because there is no RPATH adjustment for Windows platform, but as the result the Urho3D.dll will be installed in a same directory as the installed executables.
+The default install prefix is 'c:/Program Files/Urho3D' on Windows host and '/usr/local' on all other non-Windows hosts. You can use the CMAKE_INSTALL_PREFIX build option to alter this prefix path. However, when cross-compiling you may actually want to alter the final installation destination by supplying DESTDIR environment variable instead of altering the CMAKE_INSTALL_PREFIX variable directly, especially when using Urho3D library as shared library type because the installed executables have their RPATH adjusted relative to CMAKE_INSTALL_PREFIX. The DESTDIR environment variable is not applicable for installation on Windows host, i.e. it can only use CMAKE_INSTALL_PREFIX variable to alter the final installation destination. This is not a lost, however, because there is no RPATH adjustment for Windows platform, but as the result the Urho3D.dll will be installed in a same directory as the installed executables.
 
 If the Urho3D SDK is installed to a system-wide default location then the Urho3D library can be found by FindUrho3D CMake module automatically without the help of URHO3D_HOME environment variable or build option. When cross-compiling, the system-wide default location is usually in the system root of the cross-compiling target. To get a corresponding system root path for a cross-compiling build tree, you can use SYSROOT internal variable stored in the CMake cache. You can then use the system root path to set the DESTDIR environment variable in order to stage the Urho3D SDK installation in the corresponding system root. For example:
 
@@ -480,11 +480,11 @@ F2          Toggle debug HUD
 
 \tableofcontents
 
-This page shows how to create a new C++ project linking against Urho3D library as external library. There are two approaches to do this. The first approach uses Urho3D library directly from the Urho3D project build tree. The second approach uses Urho3D SDK installation. It is imperative to clean the CMake cache when changing from one approach to the other. You should also clean the cache when you change the path in the URHO3D_HOME environment variable.
+This page shows how to create a new downstream C++ project linking against Urho3D library as external library. There are two approaches to do this. The first approach uses Urho3D library directly from the Urho3D project build tree. The second approach uses Urho3D SDK installation. It is imperative to clean the CMake cache when changing from one approach to the other, or simply delete and recreate your project build tree from scratch.
 
 > Migration note if you are migrating from Urho3D prior to release 1.4:
 >    - You are probably better of to regenerate your build tree from scratch due to some of the directory renaming in the project structure.
->    - You must adjust the URHO3D_HOME environment variable in your build. In release 1.4 onward, the URHO3D_HOME environment variable is supposed to point to the build tree of the Urho3D project. In prior releases, the URHO3D_HOME was supposed to point to Urho3D project root itself.
+>    - You must adjust the URHO3D_HOME environment variable in your build because we have changed the meaning of this variable. In release 1.4 onward, the URHO3D_HOME environment variable is supposed to point to the build tree of the Urho3D project. In prior releases, the URHO3D_HOME was supposed to point to Urho3D project root itself.
 
 First of all, structure your project similar to Urho3D project as below. Although this is not mandatory, it should increase the chance the CMake modules designed for Urho3D project also works out of the box for your project too. CMake and our CMake modules are case-sensitive. It is recommended that your project adheres to the same naming convention as Urho3D project uses, even when the filesystem in your host system is not case-sensitive.
 
@@ -501,17 +501,13 @@ First of all, structure your project similar to Urho3D project as below. Althoug
  └ *.bat or *.sh
 \endcode
 
-The physical project root directory is also the logical project source tree in CMake terminology where your project main CMakeLists.txt should reside. The 'bin' directory should contain the 'Data' and 'CoreData' resource subdirs for your own assets. You may want to copy (or symlink) the 'CMake' subdir from Urho3D project root directory (or from Urho3D SDK installation, which can be found in the 'share/Urho3D/CMake') to your project root directory. You may also want to copy (or symlink) the build scripts from Urho3D project root directory (or from Urho3D SDK installation, which can be found in the 'share/Urho3D/Scripts') to your project root directory. Alternatively, you can add the Urho3D project root directory into the PATH environment variable in your host system in order to make the build scripts available everwhere. The build scripts work together with the Urho3D CMake modules to configure and generate your initial project build tree. Both out-of-source build tree (recommended) and non out-of-source build tree are supported. Note that when you configure your project (either via one of the build script or via cmake-gui), you can also pass most of the \ref Build_Options supported by Urho3D project. In fact, you probably have to pass a compatible build options with the Urho3D library that you intend to use to avoid any conflict.
+The physical project root directory is also the logical project source tree in CMake terminology where your project main CMakeLists.txt should reside. The 'bin' directory should contain the 'Data' and 'CoreData' resource subdirs for your own assets. You must copy (or symlink) the 'CMake' subdir from Urho3D project root directory (or from Urho3D SDK installation, which can be found in the 'share/Urho3D/CMake') to your project root directory. You may also want to copy (or symlink) the build scripts from Urho3D project root directory (or from Urho3D SDK installation, which can be found in the 'share/Urho3D/Scripts') to your project root directory, unless you just want to use cmake-gui for your own project configuration and generation. Alternatively, you can add the Urho3D project root directory into the PATH environment variable in your host system in order to make the build scripts available everywhere. The build scripts work together with the Urho3D CMake modules to configure and generate your initial project build tree. Both out-of-source build tree (recommended) and non out-of-source build tree are supported. Note that when you configure your project (either via one of the build script or via cmake-gui), you can also pass most of the \ref Build_Options supported by Urho3D project. In fact, you probably have to pass a compatible build options with the Urho3D library that you intend to use to avoid any conflict. Although this may change in the near future as we will enhance our modules to be smart enough to auto-discover the original build options used by the Urho3D library, but until then it is still your responsibility to supply the compatible build options.
 
 In your own project root directory, create a main CMakeLists.txt file and add the following lines: (replace MyProjectName and MyExecutableName with the actual names you want)
 
 \code
-# Set project name
-project (MyProjectName)
-
-# Set minimum version
+# Set CMake minimum version and CMake policy required by Urho3D-CMake-common module
 cmake_minimum_required (VERSION 2.8.6)
-
 if (COMMAND cmake_policy)
     cmake_policy (SET CMP0003 NEW)
     if (CMAKE_VERSION VERSION_GREATER 2.8.12 OR CMAKE_VERSION VERSION_EQUAL 2.8.12)
@@ -526,16 +522,15 @@ if (COMMAND cmake_policy)
     endif ()
 endif ()
 
+# Set project name
+project (MyProjectName)
+
 # Set CMake modules search path
 set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMake/Modules)
 
 # Include Urho3D Cmake common module
 include (Urho3D-CMake-common)
 
-# Find Urho3D library
-find_package (Urho3D REQUIRED)
-include_directories (${URHO3D_INCLUDE_DIRS})
-
 # Define target name
 set (TARGET_NAME MyExecutableName)
 
@@ -546,9 +541,9 @@ define_source_files ()
 setup_main_executable ()
 \endcode
 
-The CMAKE_MODULE_PATH is setup so that CMake can find the Urho3D-specific CMake modules provided by Urho3D project inside your own project. The Urho3D-CMake-common.cmake is the module where all the reusable commands and macros are defined. It also gives your project cross-platform build capability similar to Urho3D project.
+The CMAKE_MODULE_PATH is setup so that CMake can find the Urho3D-specific CMake modules provided by Urho3D project inside your own project. The Urho3D-CMake-common.cmake is the module where all the reusable commands and macros are defined. It also gives your project cross-platform build capability similar to Urho3D project. It does this by automatically finding the required software library components specific for your target platform and configuring your project to use them together with the platform-specific compiler flags and definitions. It utilizes CMake-provided as well as Urho3D custom-made FindXXX modules to get the work done. So, it is important to get the CMAKE_MODULE_PATH setup correctly in your project early on.
 
-When both Urho3D static and shared library are built and available in the Urho3D project's library output directory in its build tree, the FindUrho3D.cmake module has precedence to first select static library type over over shared library type. However, you can use URHO3D_LIB_TYPE build option to explicitly specify which Urho3D library type to be selected. When linking statically against Urho3D static library, Urho3D-CMake-common.cmake module automatically set "URHO3D_STATIC_DEFINE" compiler define for your project. This compiler define is crucial for static linking, especially on MSVC. If you do not use our CMake modules to configure your project then you have to ensure you manually set this compiler define in your project.
+Your own project naturally depends on Urho3D project, or to be more precise it depends on Urho3D library. The Urho3D library needs to be built first so that it can be found later by your own project. When using GCC/Clang or one of its derivatives, both Urho3D static and shared libraries could be potentially built/installed at a same location and coexist. In such cases the FindUrho3D.cmake module, the module responsible to find Urho3D software library component, has precedence to first find the static library type over over shared library type. However, you can use URHO3D_LIB_TYPE build option to override this precedence. When using MSVC compiler, both static and shared libraries could not be built/installed at a same location because both the static library and import library have a same file extension. However, for MSVC, it is possible to have both Release and Debug versions of either static or shared library type built/installed at a same location. In such cases the FindUrho3D.cmake module would automatically utilize both of Release and Debug versions as appropriate in your project for Release and Debug build configuration, respectively, without user intervention.
 
 As described earlier there are two approaches on how to find the Urho3D library. The FindUrho3D.cmake module is designed not only able to find Urho3D library from the Urho3D SDK installation, but also from any Urho3D project build tree directly. When searching in Urho3D SDK installed in a system-wide default location then no additional variable need to be set. When searching in a non-default SDK installation or when searching in any Urho3D project build tree then the actual location need to be provided via URHO3D_HOME environment variable (set in the host system) or URHO3D_HOME build option (set using -D at command line or in cmake-gui).
 
@@ -635,8 +630,6 @@ rake cmake URHO3D_LUAJIT=1 && rake make
 rake cmake rpi URHO3D_LUAJIT=1 && rake make rpi
 \endcode
 
-You can in fact set and export any other key/value pair build options as environment variables to avoid repeating yourself when invoking any of our Rake tasks.
-
 
 \page Structure Overall structure
 

+ 13 - 0
Docs/LuaScriptAPI.dox

@@ -3660,6 +3660,7 @@ Methods:
 - Node* new()
 - void delete()
 - bool SaveXML(File* dest, const String indentation = "\t") const
+- bool SaveJSON(File* dest, const String indentation = "\t") const
 - void SetName(const String name)
 - void SetPosition(const Vector3& position)
 - void SetPosition2D(const Vector2& position)
@@ -3790,6 +3791,7 @@ Methods:
 - const PODVector<Node*>& GetChildrenWithComponent(const String type, bool recursive = false)
 - bool Load(Deserializer& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED)
 - bool LoadXML(const XMLElement& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED)
+- bool LoadJSON(const JSONValue& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED)
 - Node* CreateChild(unsigned id, CreateMode mode)
 - void AddComponent(Component* component, unsigned id, CreateMode mode)
 
@@ -4207,11 +4209,13 @@ Methods:
 - void SetMaxNetworkAngularVelocity(float velocity)
 - const PODVector<PhysicsRaycastResult>& Raycast(const Ray& ray, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED)
 - PhysicsRaycastResult RaycastSingle(const Ray& ray, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED)
+- PhysicsRaycastResult RaycastSingleSegmented(const Ray& ray, float maxDistance, float segmentDistance, unsigned collisionMask = M_MAX_UNSIGNED)
 - PhysicsRaycastResult SphereCast(const Ray& ray, float radius, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED)
 - PhysicsRaycastResult ConvexCast(CollisionShape* shape, const Vector3& startPos, const Quaternion& startRot, const Vector3& endPos, const Quaternion& endRot, unsigned collisionMask = M_MAX_UNSIGNED)
 - const PODVector<RigidBody*>& GetRigidBodies(const Sphere& sphere, unsigned collisionMask = M_MAX_UNSIGNED)
 - const PODVector<RigidBody*>& GetRigidBodies(const BoundingBox& box, unsigned collisionMask = M_MAX_UNSIGNED)
 - const PODVector<RigidBody*>& GetRigidBodies(const RigidBody* body)
+- const PODVector<RigidBody*>& GetCollidingBodies(const RigidBody* body)
 - void DrawDebugGeometry(bool depthTest)
 - void RemoveCachedGeometry(Model* model)
 - Vector3 GetGravity() const
@@ -5057,6 +5061,10 @@ Methods:
 - bool SaveXML(File* dest, const String indentation = "\t") const
 - bool LoadXML(const String fileName)
 - bool SaveXML(const String fileName, const String indentation = "\t") const
+- bool LoadJSON(File* source)
+- bool SaveJSON(File* dest, const String indentation = "\t") const
+- bool LoadJSON(const String fileName)
+- bool SaveJSON(const String fileName, const String indentation = "\t") const
 - Node* Instantiate(File* source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED)
 - Node* Instantiate(const String fileName, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED)
 - Node* InstantiateXML(File* source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED)
@@ -6242,6 +6250,9 @@ Methods:
 - void SetUseScreenKeyboard(bool enable)
 - void SetUseMutableGlyphs(bool enable)
 - void SetForceAutoHint(bool enable)
+- void SetScale(float scale)
+- void SetWidth(float size)
+- void SetHeight(float size)
 - UIElement* GetRoot() const
 - UIElement* GetRootModalElement() const
 - Cursor* GetCursor() const
@@ -6264,6 +6275,7 @@ Methods:
 - bool GetForceAutoHint() const
 - bool HasModalElement() const
 - bool IsDragging() const
+- float GetScale() const
 
 Properties:
 
@@ -6285,6 +6297,7 @@ Properties:
 - bool useMutableGlyphs
 - bool forceAutoHint
 - bool modalElement (readonly)
+- float scale
 
 <a name="Class_UIElement"></a>
 ### UIElement : Animatable

+ 77 - 67
Docs/Reference.dox

@@ -313,7 +313,7 @@ Unless you have extremely serious reasons for doing so, you should not subclass
 
 \section SceneModel_LoadSave Loading and saving scenes
 
-Scenes can be loaded and saved in either binary or XML format; see the functions \ref Scene::Load "Load()", \ref Scene::LoadXML "LoadXML()", \ref Scene::Save "Save()" and \ref Scene::SaveXML "SaveXML()". See \ref Serialization
+Scenes can be loaded and saved in either binary, JSON, or XML formats; see the functions \ref Scene::Load "Load()", \ref Scene::LoadXML "LoadXML()", \ref Scene::LoadJSON "LoadJSON", \ref Scene::Save "Save()" and \ref Scene::SaveXML "SaveXML()", and \ref Scene::SaveJSON "SaveJSON()". See \ref Serialization
 "Serialization" for the technical details on how this works. When a scene is loaded, all existing content in it (child nodes and components) is removed first.
 
 Nodes and components that are marked temporary will not be saved. See \ref Serializable::SetTemporary "SetTemporary()".
@@ -322,13 +322,13 @@ To be able to track the progress of loading a (large) scene without having the p
 
 \section SceneModel_Instantiation Object prefabs
 
-Just loading or saving whole scenes is not flexible enough for eg. games where new objects need to be dynamically created. On the other hand, creating complex objects and setting their properties in code will also be tedious. For this reason, it is also possible to save a scene node (and its child nodes, components and attributes) to either binary or XML to be able to instantiate it later into a scene. Such a saved object is often referred to as a prefab. There are three ways to do this:
+Just loading or saving whole scenes is not flexible enough for eg. games where new objects need to be dynamically created. On the other hand, creating complex objects and setting their properties in code will also be tedious. For this reason, it is also possible to save a scene node (and its child nodes, components and attributes) to either binary, JSON, or XML to be able to instantiate it later into a scene. Such a saved object is often referred to as a prefab. There are three ways to do this:
 
-- In code by calling \ref Node::Save "Save()" or \ref Node::SaveXML "SaveXML()" on the Node in question.
+- In code by calling \ref Node::Save "Save()", \ref Node::SaveJSON "SaveJSON()", or \ref Node::SaveXML "SaveXML()" on the Node in question.
 - In the editor, by selecting the node in the hierarchy window and choosing "Save node as" from the "File" menu.
 - Using the "node" command in AssetImporter, which will save the scene node hierarchy and any models contained in the input asset (eg. a Collada file)
 
-To instantiate the saved node into a scene, call \ref Scene::Instantiate "Instantiate()" or \ref Scene::InstantiateXML "InstantiateXML()" depending on the format. The node will be created as a child of the Scene but can be freely reparented after that. Position and rotation for placing the node need to be specified. The NinjaSnowWar example uses XML format for its object prefabs; these exist in the bin/Data/Objects directory.
+To instantiate the saved node into a scene, call \ref Scene::Instantiate "Instantiate()", \ref Scene::InstantiateJSON() or \ref Scene::InstantiateXML "InstantiateXML()" depending on the format. The node will be created as a child of the Scene but can be freely reparented after that. Position and rotation for placing the node need to be specified. The NinjaSnowWar example uses XML format for its object prefabs; these exist in the bin/Data/Objects directory.
 
 \section SceneModel_FurtherInformation Further information
 
@@ -351,6 +351,7 @@ Resources include most things in Urho3D that are loaded from mass storage during
 - Texture3D
 - TextureCube
 - XMLFile
+- JSONFile
 
 They are managed and loaded by the ResourceCache subsystem. Like with all other \ref ObjectTypes "typed objects", resource types are identified by 32-bit type name hashes (C++) or type names (script). An object factory must be registered for each resource type.
 
@@ -381,7 +382,7 @@ parse data, upload to GPU if necessary) and can therefore result in framerate dr
 
 If you know in advance what resources you need, you can request them to be loaded in a background thread by calling \ref ResourceCache::BackgroundLoadResource "BackgroundLoadResource()". The event E_RESOURCEBACKGROUNDLOADED will be sent after the loading is complete; it will tell if the loading actually was a success or a failure. Depending on the resource, only a part of the loading process may be moved to a background thread, for example the finishing GPU upload step always needs to happen in the main thread. Note that if you call GetResource() for a resource that is queued for background loading, the main thread will stall until its loading is complete.
 
-The asynchronous scene loading functionality \ref Scene::LoadAsync "LoadAsync()" and \ref Scene::LoadAsyncXML "LoadAsyncXML()" has the option to background load the resources first before proceeding to load the scene content. It can also be used to only load the resources without modifying the scene, by specifying the LOAD_RESOURCES_ONLY mode. This allows to prepare a scene or object prefab file for fast instantiation.
+The asynchronous scene loading functionality \ref Scene::LoadAsync "LoadAsync()", \ref Scene::LoadAsyncJSON "LoadAsyncJSON()" and \ref Scene::LoadAsyncXML "LoadAsyncXML()" have the option to background load the resources first before proceeding to load the scene content. It can also be used to only load the resources without modifying the scene, by specifying the LOAD_RESOURCES_ONLY mode. This allows to prepare a scene or object prefab file for fast instantiation.
 
 Finally the maximum time (in milliseconds) spent each frame on finishing background loaded resources can be configured, see \ref ResourceCache::SetFinishBackgroundResourcesMs "SetFinishBackgroundResourcesMs()".
 
@@ -409,16 +410,16 @@ JSON files must be in UTF8 encoding without BOM. Sample files are in the bin/Dat
 
 \code
 {
-	"string id 1":{
-		"language 1":"value11",
-		"language 2":"value12",
-		"language 3":"value13"
-	},
-	"string id 2":{
-		"language 1":"value21",
-		"language 2":"value22",
-		"language 3":"value23"
-	}
+    "string id 1":{
+        "language 1":"value11",
+        "language 2":"value12",
+        "language 3":"value13"
+    },
+    "string id 2":{
+        "language 1":"value21",
+        "language 2":"value22",
+        "language 3":"value23"
+    }
 }
 \endcode
 
@@ -939,7 +940,7 @@ See also \ref Materials "Materials", \ref Shaders "Shaders", \ref Lights "Lights
 
 See \ref RenderingModes "Rendering modes" for detailed discussion on the forward, light pre-pass and deferred rendering modes.
 
-See \ref APIDifferences "Differences between Direct3D and OpenGL" for what to watch out for when using the low-level rendering functionality directly.
+See \ref APIDifferences "Differences between rendering APIs" for what to watch out for when using the low-level rendering functionality directly.
 
 
 \page RenderingModes Rendering modes
@@ -989,7 +990,7 @@ Forward rendering makes it possible to use hardware multisampling and different
 Finally note that due to OpenGL framebuffer object limitations an extra framebuffer blit has to happen at the end in both light pre-pass and deferred rendering, which costs some performance. Also, because multiple rendertargets on OpenGL must have the same format, an R32F texture can not be used for linear depth, but instead 24-bit depth is manually encoded and decoded into RGB channels.
 
 
-\page APIDifferences Differences between Direct3D and OpenGL
+\page APIDifferences Differences between rendering APIs
 
 These differences need to be observed when using the low-level rendering functionality directly. The high-level rendering architecture, including the Renderer and UI subsystems and the Drawable subclasses already handle most of them transparently to the user.
 
@@ -1009,6 +1010,8 @@ These differences need to be observed when using the low-level rendering functio
 
 - To ensure similar UV addressing for render-to-texture viewports on both APIs, on OpenGL texture viewports will be rendered upside down.
 
+- Direct3D11 is strict about vertex attributes referenced by shaders. A model will not render (input layout fails to create) if the shader for example asks for UV coordinates and the model does not have them. For this particular case, see the NOUV define in LitSolid shader, which is defined in the NoTexture family of techniques to prevent the attempted reading of UV coords.
+
 OpenGL ES 2.0 has further limitations:
 
 - Of the DXT formats, only DXT1 compressed textures will be uploaded as compressed, and only if the EXT_texture_compression_dxt1 extension is present. Other DXT formats will be uploaded as uncompressed RGBA. ETC1 (Android) and PVRTC (iOS) compressed textures are supported through the .ktx and .pvr file formats.
@@ -1031,7 +1034,7 @@ OpenGL ES 2.0 has further limitations:
 
 \page Materials Materials
 
-Material and Technique resources define how to render 3D scene geometry. On the disk, they are XML data. Default and example materials exist in the bin/CoreData/Materials & bin/Data/Materials subdirectories, and techniques exist in the bin/CoreData/Techniques subdirectory.
+Material and Technique resources define how to render 3D scene geometry. On the disk, they are XML or JSON data. Default and example materials exist in the bin/CoreData/Materials & bin/Data/Materials subdirectories, and techniques exist in the bin/CoreData/Techniques subdirectory.
 
 A material defines the textures, shader parameters and culling & fill mode to use, and refers to one or several techniques. A technique defines the actual rendering passes, the shaders to use in each, and all other rendering states such as depth test, depth write, and blending.
 
@@ -1727,7 +1730,7 @@ For purposes of volume control, each SoundSource can be classified into a user d
 
 To control the category volumes, use \ref Audio::SetMasterGain "SetMasterGain()", which defines the category if it didn't already exist.
 
-The SoundSource components support automatic removal from the node they belong to, once playback is finished. To use, call \ref SoundSource::SetAutoRemove "SetAutoRemove()" on them. This may be useful when a game object plays several "fire and forget" sound effects.
+Note that the Audio subsystem is always instantiated, but in headless mode the playback of sounds is simulated, taking the sound length and frequency into account. This allows basing logic on whether a specific sound is still playing or not, even in server code.
 
 \section Audio_Parameters Sound parameters
 
@@ -1742,14 +1745,15 @@ A standard WAV file can not tell whether it should loop, and raw audio does not
 
 The frequency is in Hz, and loop start and end are bytes from the start of audio data. If a loop is enabled without specifying the start and end, it is assumed to be the whole sound. Ogg Vorbis compressed sounds do not support specifying the loop range, only whether whole sound looping is enabled or disabled.
 
-The Audio subsystem is always instantiated, but in headless mode it is not active. In headless mode the playback of sounds is simulated, taking the sound length and frequency into account. This allows basing logic on whether a specific sound is still playing or not, even in server code.
-
 \section Audio_Stream Sound streaming
 
 In addition to playing existing sound resources, sound can be generated during runtime using the SoundStream class and its subclasses. To start playback of a stream on a SoundSource, call \ref SoundSource::Play(SoundStream* stream) "Play(SoundStream* stream)".
 
 %Sound streaming is used internally to implement on-the-fly Ogg Vorbis decoding. It is only available in C++ code and not scripting due to its low-level nature. See the SoundSynthesis C++ sample for an example of using the BufferedSoundStream subclass, which allows the sound data to be queued for playback from the main thread.
 
+\section Audio_Events Audio events
+
+A sound source will send the E_SOUNDFINISHED event through its scene node when the playback of a sound has ended. This can be used for example to know when to remove a temporary node created just for playing a sound effect, or for tying game events to sound playback.
 
 \page Physics Physics
 
@@ -2015,22 +2019,22 @@ Cursor Shapes can be define in a number of different ways:
 
 XML:
 \code
-	<element type="Cursor">
-		<attribute name="Shapes">
-			<variant type="VariantVector" >
-				<variant type="String" value="Normal" />
-				<variant type="ResourceRef" value="Image;Textures/UI.png" />
-				<variant type="IntRect" value="0 0 12 24" />
-				<variant type="IntVector2" value="0 0" />
-			</variant>
-			<variant type="VariantVector" >
-				<variant type="String" value="Custom" />
-				<variant type="ResourceRef" value="Image;Textures/UI.png" />
-				<variant type="IntRect" value="12 0 12 36" />
-				<variant type="IntVector2" value="0 0" />
-			</variant>
-		</atrribute>
-	</element>
+    <element type="Cursor">
+        <attribute name="Shapes">
+            <variant type="VariantVector" >
+                <variant type="String" value="Normal" />
+                <variant type="ResourceRef" value="Image;Textures/UI.png" />
+                <variant type="IntRect" value="0 0 12 24" />
+                <variant type="IntVector2" value="0 0" />
+            </variant>
+            <variant type="VariantVector" >
+                <variant type="String" value="Custom" />
+                <variant type="ResourceRef" value="Image;Textures/UI.png" />
+                <variant type="IntRect" value="12 0 12 36" />
+                <variant type="IntVector2" value="0 0" />
+            </variant>
+        </atrribute>
+    </element>
 \endcode
 
 C++:
@@ -2041,27 +2045,33 @@ C++:
     Cursor* cursor = new Cursor(context_);
     Image* image = rc->GetResource<Image>("Textures/UI.png");
     if (image)
-	{
+    {
         cursor->DefineShape(CS_NORMAL, image, IntRect(0, 0, 12, 24), IntVector2(0, 0));
-		cursor->DefineShape("Custom", image, IntRect(12, 0, 12, 36), IntVector2(0, 0));
-	}
+        cursor->DefineShape("Custom", image, IntRect(12, 0, 12, 36), IntVector2(0, 0));
+    }
 
     ui->SetCursor(cursor);
 \endcode
 
 Angelcode:
 \code
-	Cursor@ cursor = new Cursor();
-	Image@ image = cache.GetResource("Image", "Textures/UI.png");
-	if (image !is null)
-	{
-		cursor.DefineShape(CS_NORMAL, image, IntRect(0, 0, 12, 24), IntVector2(0, 0));
-		cursor.DefineShape("Custom", image, IntRect(12, 0, 12, 36), IntVector2(0, 0));
-	}
+    Cursor@ cursor = new Cursor();
+    Image@ image = cache.GetResource("Image", "Textures/UI.png");
+    if (image !is null)
+    {
+        cursor.DefineShape(CS_NORMAL, image, IntRect(0, 0, 12, 24), IntVector2(0, 0));
+        cursor.DefineShape("Custom", image, IntRect(12, 0, 12, 36), IntVector2(0, 0));
+    }
 
-	ui.SetCursor(cursor);
+    ui.SetCursor(cursor);
 \endcode
 
+\section UI_Scaling Scaling
+
+By default the %UI is pixel perfect: the root element is sized equal to the application window size.
+
+The pixel scaling can be changed with the functions \ref UI::SetScale "SetScale()", \ref UI::SetWidth "SetWidth()" and \ref UI::SetHeight "SetHeight()".
+
 \page Urho2D Urho2D
 In order to make 2D games in Urho3D, the Urho2D sublibrary is provided. Urho2D includes 2D graphics and 2D physics.
 
@@ -2809,25 +2819,25 @@ As for any component, a \ref SplinePath::DrawDebugGeometry "debugging function"
 
 The following sample demonstrates how to build a path from 2 points, assign a controlled node and move it along the path according to speed and interpolation mode.
 \code
-	// Initial point
-	Node* startNode = scene_->CreateChild("Start");
-	startNode->SetPosition(Vector3(-20.0f, 0.0f, -20.0f));
-
-	// Target point
-	Node* targetNode = scene_->CreateChild("Target");
-	targetNode->SetPosition(Vector3(20.0f, 2.0f, 20.0f));
-
-	// Node to move along the path ('controlled node')
-	Node* movingNode =  scene_->CreateChild("MovingNode");
-
-	// Spline path
-	Node* pathNode = scene_->CreateChild("PathNode");
-	SplinePath* path = pathNode->CreateComponent<SplinePath>();
-	path->AddControlPoint(startNode, 0);
-	path->AddControlPoint(targetNode, 1);
-	path->SetInterpolationMode(LINEAR_CURVE);
-	path->SetSpeed(10.0f);
-	path->SetControlledNode(movingNode);
+    // Initial point
+    Node* startNode = scene_->CreateChild("Start");
+    startNode->SetPosition(Vector3(-20.0f, 0.0f, -20.0f));
+
+    // Target point
+    Node* targetNode = scene_->CreateChild("Target");
+    targetNode->SetPosition(Vector3(20.0f, 2.0f, 20.0f));
+
+    // Node to move along the path ('controlled node')
+    Node* movingNode =  scene_->CreateChild("MovingNode");
+
+    // Spline path
+    Node* pathNode = scene_->CreateChild("PathNode");
+    SplinePath* path = pathNode->CreateComponent<SplinePath>();
+    path->AddControlPoint(startNode, 0);
+    path->AddControlPoint(targetNode, 1);
+    path->SetInterpolationMode(LINEAR_CURVE);
+    path->SetSpeed(10.0f);
+    path->SetControlledNode(movingNode);
 \endcode
 
 In your update function, move the controlled node using \ref SplinePath::Move "Move()":

Різницю між файлами не показано, бо вона завелика
+ 166 - 1
Docs/ScriptAPI.dox


+ 5 - 3
Docs/Urho3D.dox

@@ -86,12 +86,13 @@ Urho3D development, contributions and bugfixes by:
 - Alex Parlett
 - Jordan Patterson
 - Vladimir Pobedinsky
+- Pranjal Raihan
 - Nick Royer
-- Jonathan Sandusky
 - Miika Santala
 - Hualin Song
 - James Thomas
 - Joshua Tippetts
+- Yusuf Umar
 - Daniel Wiberg
 - Steven Zhang
 - AGreatFish
@@ -124,6 +125,7 @@ Urho3D development, contributions and bugfixes by:
 - skaiware
 - szamq
 - thebluefish
+- tommy3
 - yushli
 
 Urho3D is greatly inspired by OGRE (http://www.ogre3d.org/) and Horde3D (http://www.horde3d.org/). Additional inspiration & research used:
@@ -385,7 +387,7 @@ V1.4
 - Fill mode added to materials.
 - AngelScript script objects can be stored in a %Variant.
 - Improved attribute registration: class name and variant type not needed.
-- Add new rake tasks to facilitate configuring/generating and building Urho3D project, including external project using Urho3D library.
+- Add new rake tasks to facilitate configuring/generating and building Urho3D project, including downstream project using Urho3D library.
 - Update PugiXml to 1.5.
 - Update STB libraries to latest.
 - Editor: particle effect editor window.
@@ -606,7 +608,7 @@ V1.32
 
 V1.31
 
-- Extensive build system improvements, especially for using Urho3D as a library in an external project.
+- Extensive build system improvements, especially for using Urho3D as a library in an downstream project.
 - LuaJIT support.
 - Improved Lua bindings, Lua coroutine support, automatic loading of compiled Lua scripts (.luc) if they exist.
 - HDR rendering, 3D textures, height fog and several new post process shaders.

+ 3 - 1
README.md

@@ -38,12 +38,13 @@ Urho3D development, contributions and bugfixes by:
 - Alex Parlett
 - Jordan Patterson
 - Vladimir Pobedinsky
+- Pranjal Raihan
 - Nick Royer
-- Jonathan Sandusky
 - Miika Santala
 - Hualin Song
 - James Thomas
 - Joshua Tippetts
+- Yusuf Umar
 - Daniel Wiberg
 - Steven Zhang
 - AGreatFish
@@ -76,6 +77,7 @@ Urho3D development, contributions and bugfixes by:
 - skaiware
 - szamq
 - thebluefish
+- tommy3
 - yushli
 
 Urho3D is greatly inspired by OGRE (http://www.ogre3d.org) and Horde3D

+ 15 - 18
Rakefile

@@ -35,6 +35,7 @@ desc 'Create a new project using Urho3D as external library'
 task :scaffolding do
   abort 'Usage: rake scaffolding dir=/path/to/new/project/root [project=Scaffolding] [target=Main]' unless ENV['dir']
   abs_path = (ENV['OS'] ? ENV['dir'][1, 1] == ':' : ENV['dir'][0, 1] == '/') ? ENV['dir'] : "#{Dir.pwd}/#{ENV['dir']}"
+  abs_path.gsub!(/\//, '\\') if ENV['OS']
   project = ENV['project'] || 'Scaffolding'
   target = ENV['target'] || 'Main'
   scaffolding(abs_path, project, target)
@@ -99,6 +100,7 @@ task :make do
   cmake_build_options = ''
   build_options = ''
   unfilter = false
+  ['config', 'target', 'sdk'].each { |var| ARGV << "#{var}=\"#{ENV[var]}\"" if ENV[var] and !ARGV.include? var }
   ARGV.each { |option|
     task option.to_sym do ; end; Rake::Task[option].clear   # No-op hack
     case option
@@ -129,7 +131,7 @@ task :make do
       build_options = "-jobs #{numjobs}#{build_options}"
     end
     filter = !unfilter && system('xcpretty -v >/dev/null 2>&1') ? '|xcpretty -c && exit ${PIPESTATUS[0]}' : ''
-  elsif !Dir.glob("#{build_tree}/*.sln").empty?
+  elsif !Dir.glob("#{build_tree}\\*.sln".gsub(/\\/, '/')).empty?
     # msbuild
     numjobs = ":#{numjobs}" unless numjobs.empty?
     build_options = "/maxcpucount#{numjobs}#{build_options}"
@@ -297,7 +299,7 @@ task :ci_site_update do
   # Update credits from README.md to about.yml
   system "ruby -lne 'BEGIN { credits = false }; puts $_ if credits; credits = true if /bugfixes by:/; credits = false if /^$/' README.md |ruby -i -le 'credits = STDIN.read; puts ARGF.read.gsub(/(?<=contributors:\n).*?\n\n/m, credits)' ../doc-Build/_data/about.yml" or abort 'Failed to update credits'
   # Setup doxygen to use minimal theme
-  system "ruby -i -pe 'BEGIN { a = {%q{HTML_HEADER} => %q{minimal-header.html}, %q{HTML_FOOTER} => %q{minimal-footer.html}, %q{HTML_STYLESHEET} => %q{minimal-doxygen.css}, %q{HTML_COLORSTYLE_HUE} => 200, %q{HTML_COLORSTYLE_SAT} => 0, %q{HTML_COLORSTYLE_GAMMA} => 20, %q{DOT_IMAGE_FORMAT} => %q{svg}, %q{INTERACTIVE_SVG} => %q{YES}} }; a.each {|k, v| gsub(/\#{k}\s*?=.*?\n/, %Q{\#{k} = \#{v}\n}) }' ../Build/Docs/generated/Doxyfile" or abort 'Failed to setup doxygen configuration file'
+  system "ruby -i -pe 'BEGIN { a = {%q{HTML_HEADER} => %q{minimal-header.html}, %q{HTML_FOOTER} => %q{minimal-footer.html}, %q{HTML_STYLESHEET} => %q{minimal-doxygen.css}, %q{HTML_COLORSTYLE_HUE} => 200, %q{HTML_COLORSTYLE_SAT} => 0, %q{HTML_COLORSTYLE_GAMMA} => 20, %q{DOT_IMAGE_FORMAT} => %q{svg}, %q{INTERACTIVE_SVG} => %q{YES}, %q{COLS_IN_ALPHA_INDEX} => 3} }; a.each {|k, v| gsub(/\#{k}\s*?=.*?\n/, %Q{\#{k} = \#{v}\n}) }' ../Build/Docs/generated/Doxyfile" or abort 'Failed to setup doxygen configuration file'
   system 'cp ../doc-Build/_includes/Doxygen/minimal-* ../Build/Docs' or abort 'Failed to copy minimal-themed template'
   release = ENV['RELEASE_TAG'] || 'HEAD'
   unless release == 'HEAD'
@@ -506,12 +508,8 @@ end
 
 def scaffolding dir, project = 'Scaffolding', target = 'Main'
   build_script = <<EOF
-# Set project name
-project (#{project})
-
-# Set minimum version
+# Set CMake minimum version and CMake policy required by Urho3D-CMake-common module
 cmake_minimum_required (VERSION 2.8.6)
-
 if (COMMAND cmake_policy)
     cmake_policy (SET CMP0003 NEW)
     if (CMAKE_VERSION VERSION_GREATER 2.8.12 OR CMAKE_VERSION VERSION_EQUAL 2.8.12)
@@ -526,16 +524,15 @@ if (COMMAND cmake_policy)
     endif ()
 endif ()
 
+# Set project name
+project (#{project})
+
 # Set CMake modules search path
 set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMake/Modules)
 
 # Include Urho3D CMake common module
 include (Urho3D-CMake-common)
 
-# Find Urho3D library
-find_package (Urho3D REQUIRED)
-include_directories (${URHO3D_INCLUDE_DIRS})
-
 # Define target name
 set (TARGET_NAME #{target})
 
@@ -555,7 +552,7 @@ endif ()
 EOF
   # TODO: Rewrite in pure Ruby when it supports symlink creation on Windows platform
   if ENV['OS']
-    system("@echo off && mkdir '#{dir}'\\bin && copy Source\\Tools\\Urho3DPlayer\\Urho3DPlayer.* '#{dir}' >nul && (for %f in (*.bat Rakefile) do mklink '#{dir}'\\%f %cd%\\%f >nul) && mklink /D '#{dir}'\\CMake %cd%\\CMake && (for %d in (CoreData,Data) do mklink /D '#{dir}'\\bin\\%d %cd%\\bin\\%d >nul)") && File.write("#{dir}/CMakeLists.txt", build_script) or abort 'Failed to create new project using Urho3D as external library'
+    system("@echo off && mkdir \"#{dir}\"\\bin && copy Source\\Tools\\Urho3DPlayer\\Urho3DPlayer.* \"#{dir}\" >nul && (for %f in (*.bat Rakefile) do mklink \"#{dir}\"\\%f %cd%\\%f >nul) && mklink /D \"#{dir}\"\\CMake %cd%\\CMake && (for %d in (CoreData,Data) do mklink /D \"#{dir}\"\\bin\\%d %cd%\\bin\\%d >nul)") && File.write("#{dir}/CMakeLists.txt", build_script) or abort 'Failed to create new project using Urho3D as external library'
   else
     system("bash -c \"mkdir -p '#{dir}'/bin && cp Source/Tools/Urho3DPlayer/Urho3DPlayer.* '#{dir}' && for f in {.,}*.sh Rakefile CMake; do ln -sf `pwd`/\\$f '#{dir}'; done && ln -sf `pwd`/bin/{Core,}Data '#{dir}'/bin\"") && File.write("#{dir}/CMakeLists.txt", build_script) or abort 'Failed to create new project using Urho3D as external library'
   end
@@ -589,13 +586,13 @@ def makefile_ci
   unless ENV['CI'] && ENV['HTML5'] && ENV['PACKAGE_UPLOAD']  # For Emscripten, skip scaffolding test when packaging
     # Create a new project on the fly that uses newly built Urho3D library in the build tree
     scaffolding "../Build/generated/UsingBuildTree"
-    system "cd ../Build/generated/UsingBuildTree && echo '\nExternal project referencing Urho3D library in its build tree' && ./cmake_generic.sh . #{$build_options} -DURHO3D_HOME=../.. -DURHO3D_LUA#{jit}=1 -DURHO3D_TESTING=#{$testing} -DCMAKE_BUILD_TYPE=#{$configuration} && make -j$NUMJOBS #{test}" or abort 'Failed to configure/build/test temporary project using Urho3D as external library'
+    system "cd ../Build/generated/UsingBuildTree && echo '\nConfiguring downstream project using Urho3D library in its build tree...' && ./cmake_generic.sh . #{$build_options} -DURHO3D_HOME=../.. -DURHO3D_LUA#{jit}=1 -DURHO3D_TESTING=#{$testing} -DCMAKE_BUILD_TYPE=#{$configuration} && make -j$NUMJOBS #{test}" or abort 'Failed to configure/build/test temporary downstream project using Urho3D as external library'
     ENV['DESTDIR'] = ENV['HOME'] || Dir.home
     puts "\nInstalling Urho3D SDK to #{ENV['DESTDIR']}/usr/local...\n"  # The default CMAKE_INSTALL_PREFIX is /usr/local
     system 'cd ../Build && make -j$NUMJOBS install >/dev/null' or abort 'Failed to install Urho3D SDK'
     # Create a new project on the fly that uses newly installed Urho3D SDK
     scaffolding "../Build/generated/UsingSDK"
-    system "export URHO3D_HOME=~/usr/local && cd ../Build/generated/UsingSDK && echo '\nExternal project referencing Urho3D SDK' && ./cmake_generic.sh . #{$build_options} -DURHO3D_LUA#{jit}=1 -DURHO3D_TESTING=#{$testing} -DCMAKE_BUILD_TYPE=#{$configuration} && make -j$NUMJOBS #{test}" or abort 'Failed to configure/build/test temporary project using Urho3D as external library'
+    system "export URHO3D_HOME=~/usr/local && cd ../Build/generated/UsingSDK && echo '\nConfiguring downstream project using Urho3D SDK...' && ./cmake_generic.sh . #{$build_options} -DURHO3D_LUA#{jit}=1 -DURHO3D_TESTING=#{$testing} -DCMAKE_BUILD_TYPE=#{$configuration} && make -j$NUMJOBS #{test}" or abort 'Failed to configure/build/test temporary downstream project using Urho3D as external library'
   end
   # Make, deploy, and test run Android APK in an Android (virtual) device
   if ENV['AVD'] && !ENV['PACKAGE_UPLOAD']
@@ -736,14 +733,14 @@ def xcode_ci
   unless ENV['CI'] && ENV['IOS'] && ENV['PACKAGE_UPLOAD']   # Skip scaffolding test when packaging for iOS
     # Create a new project on the fly that uses newly built Urho3D library in the build tree
     scaffolding "../Build/generated/UsingBuildTree"
-    system "cd ../Build/generated/UsingBuildTree && echo '\nExternal project referencing Urho3D library in its build tree' && ./cmake_macosx.sh . -DIOS=$IOS #{$build_options} -DURHO3D_HOME=../.. -DURHO3D_LUA#{jit}=1 -DURHO3D_TESTING=#{$testing}" or abort 'Failed to configure temporary project using Urho3D as external library'
-    xcode_build(ENV['IOS'], '../Build/generated/UsingBuildTree/Scaffolding.xcodeproj') or abort 'Failed to build/test temporary project using Urho3D as external library'
+    system "cd ../Build/generated/UsingBuildTree && echo '\nConfiguring downstream project using Urho3D library in its build tree...' && ./cmake_macosx.sh . -DIOS=$IOS #{$build_options} -DURHO3D_HOME=../.. -DURHO3D_LUA#{jit}=1 -DURHO3D_TESTING=#{$testing}" or abort 'Failed to configure temporary project using Urho3D as external library'
+    xcode_build(ENV['IOS'], '../Build/generated/UsingBuildTree/Scaffolding.xcodeproj') or abort 'Failed to build/test temporary downstream project using Urho3D as external library'
     ENV['DESTDIR'] = ENV['HOME'] || Dir.home
     wait_for_block("\nInstalling Urho3D SDK to #{ENV['DESTDIR']}/usr/local...") { Thread.current[:exit_code] = xcode_build(ENV['IOS'], '../Build/Urho3D.xcodeproj', 'install', '>/dev/null') } or abort 'Failed to install Urho3D SDK'
     # Create a new project on the fly that uses newly installed Urho3D SDK
     scaffolding "../Build/generated/UsingSDK"
-    system "export URHO3D_HOME=~/usr/local && cd ../Build/generated/UsingSDK && echo '\nExternal project referencing Urho3D SDK' && ./cmake_macosx.sh . -DIOS=$IOS #{$build_options} -DURHO3D_LUA#{jit}=1 -DURHO3D_TESTING=#{$testing}" or abort 'Failed to configure temporary project using Urho3D as external library'
-    xcode_build(ENV['IOS'], '../Build/generated/UsingSDK/Scaffolding.xcodeproj') or abort 'Failed to build/test temporary project using Urho3D as external library'
+    system "export URHO3D_HOME=~/usr/local && cd ../Build/generated/UsingSDK && echo '\nConfiguring downstream project using Urho3D SDK...' && ./cmake_macosx.sh . -DIOS=$IOS #{$build_options} -DURHO3D_LUA#{jit}=1 -DURHO3D_TESTING=#{$testing}" or abort 'Failed to configure temporary downstream project using Urho3D as external library'
+    xcode_build(ENV['IOS'], '../Build/generated/UsingSDK/Scaffolding.xcodeproj') or abort 'Failed to build/test temporary downstream project using Urho3D as external library'
   end
 end
 

+ 5 - 5
Source/Clang-Tools/CMakeLists.txt

@@ -20,15 +20,12 @@
 # THE SOFTWARE.
 #
 
-# Set project name
 if (CMAKE_PROJECT_NAME STREQUAL Urho3D)
+    # Set project name
     project (Urho3D-Clang-Tools)
 else ()
-    project (ExternalProject-${URHO3D_CLANG_TOOLS})
-
-    # Set minimum version
+    # Set CMake minimum version and CMake policy required by Urho3D-CMake-common module
     cmake_minimum_required (VERSION 2.8.6)
-
     if (COMMAND cmake_policy)
         cmake_policy (SET CMP0003 NEW)
         if (CMAKE_VERSION VERSION_GREATER 2.8.12 OR CMAKE_VERSION VERSION_EQUAL 2.8.12)
@@ -43,6 +40,9 @@ else ()
         endif ()
     endif ()
 
+    # Set project name
+    project (Urho3D-ExternalProject-${URHO3D_CLANG_TOOLS})
+
     # Set CMake modules search path
     set (CMAKE_MODULE_PATH ${BAKED_CMAKE_SOURCE_DIR}/CMake/Modules)
 

+ 15 - 2
Source/Samples/14_SoundEffects/SoundEffects.cpp

@@ -21,6 +21,7 @@
 //
 
 #include <Urho3D/Audio/Audio.h>
+#include <Urho3D/Audio/AudioEvents.h>
 #include <Urho3D/Audio/Sound.h>
 #include <Urho3D/Audio/SoundSource.h>
 #include <Urho3D/Engine/Engine.h>
@@ -181,8 +182,11 @@ void SoundEffects::HandlePlaySound(StringHash eventType, VariantMap& eventData)
         soundSource->Play(sound);
         // In case we also play music, set the sound volume below maximum so that we don't clip the output
         soundSource->SetGain(0.75f);
-        // Set the sound component to automatically remove its scene node from the scene when the sound is done playing
-        soundSource->SetAutoRemove(true);
+
+        // Subscribe to the "sound finished" event generated by the SoundSource for removing the node once the sound has played
+        // Note: the event is sent through the Node (similar to e.g. node physics collision and animation trigger events)
+        // to not require subscribing to the particular component
+        SubscribeToEvent(soundNode, E_SOUNDFINISHED, URHO3D_HANDLER(SoundEffects, HandleSoundFinished));
     }
 }
 
@@ -226,3 +230,12 @@ void SoundEffects::HandleMusicVolume(StringHash eventType, VariantMap& eventData
     float newVolume = eventData[P_VALUE].GetFloat();
     GetSubsystem<Audio>()->SetMasterGain(SOUND_MUSIC, newVolume);
 }
+
+void SoundEffects::HandleSoundFinished(StringHash eventType, VariantMap& eventData)
+{
+    using namespace SoundFinished;
+
+    Node* soundNode = static_cast<Node*>(eventData[P_NODE].GetPtr());
+    if (soundNode)
+        soundNode->Remove();
+}

+ 2 - 0
Source/Samples/14_SoundEffects/SoundEffects.h

@@ -80,6 +80,8 @@ private:
     void HandleSoundVolume(StringHash eventType, VariantMap& eventData);
     /// Handle music volume slider change.
     void HandleMusicVolume(StringHash eventType, VariantMap& eventData);
+    /// Handle sound effect finished.
+    void HandleSoundFinished(StringHash eventType, VariantMap& eventData);
 };
 
 

+ 2 - 2
Source/Samples/Sample.inl

@@ -62,9 +62,9 @@ void Sample::Setup()
 
     // Construct a search path to find the resource prefix with two entries:
     // The first entry is an empty path which will be substituted with program/bin directory -- this entry is for binary when it is still in build tree
-    // The second entry is a relative path from the installed program/bin directory to the asset directory -- this entry is for binary when it is in the Urho3D SDK installation location
+    // The second and third entries are possible relative paths from the installed program/bin directory to the asset directory -- these entries are for binary when it is in the Urho3D SDK installation location
     if (!engineParameters_.Contains("ResourcePrefixPaths"))
-        engineParameters_["ResourcePrefixPaths"] = ";../share/Urho3D/Resources";
+        engineParameters_["ResourcePrefixPaths"] = ";../share/Resources;../share/Urho3D/Resources";
 }
 
 void Sample::Start()

+ 2 - 1
Source/ThirdParty/JO/jo_jpeg.cpp

@@ -262,7 +262,8 @@ bool jo_write_jpg(const char *filename, const void *data, int width, int height,
 	fwrite(YTable, sizeof(YTable), 1, fp);
 	putc(1, fp);
 	fwrite(UVTable, sizeof(UVTable), 1, fp);
-	const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,height>>8,height&0xFF,width>>8,width&0xFF,3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
+    // Urho3D: modified to avoid narrowing conversion
+	const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,static_cast<unsigned char>(height>>8),static_cast<unsigned char>(height&0xFF),static_cast<unsigned char>(width>>8),static_cast<unsigned char>(width&0xFF),3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
 	fwrite(head1, sizeof(head1), 1, fp);
 	fwrite(std_dc_luminance_nrcodes+1, sizeof(std_dc_luminance_nrcodes)-1, 1, fp);
 	fwrite(std_dc_luminance_values, sizeof(std_dc_luminance_values), 1, fp);

+ 4 - 5
Source/ThirdParty/LuaJIT/src/host/CMakeLists.txt

@@ -25,12 +25,8 @@ if (CMAKE_PROJECT_NAME STREQUAL ExternalProject-tolua++)
     # We only want to keep the buildvm host tool which is built for cross-compiling target (the one built from internal LuaJIT CMake-target)
     set (DEST_RUNTIME_DIR "")   # In this particular case, it is not equivalent to unset() the variable
 elseif (NOT CMAKE_PROJECT_NAME STREQUAL Urho3D)
-    # Set project name
-    project (ExternalProject-buildvm)
-
-    # Set minimum version
+    # Set CMake minimum version and CMake policy required by Urho3D-CMake-common module
     cmake_minimum_required (VERSION 2.8.6)
-
     if (COMMAND cmake_policy)
         cmake_policy (SET CMP0003 NEW)
         if (CMAKE_VERSION VERSION_GREATER 2.8.12 OR CMAKE_VERSION VERSION_EQUAL 2.8.12)
@@ -45,6 +41,9 @@ elseif (NOT CMAKE_PROJECT_NAME STREQUAL Urho3D)
         endif ()
     endif ()
 
+    # Set project name
+    project (Urho3D-ExternalProject-buildvm)
+
     # Set CMake modules search path
     set (CMAKE_MODULE_PATH ${BAKED_CMAKE_SOURCE_DIR}/CMake/Modules)
 

+ 6 - 8
Source/ThirdParty/SDL/CMakeLists.txt

@@ -127,14 +127,12 @@ else ()
     # end todo
     include_directories (${ALSA_INCLUDE_DIRS})
 
-    # FIXME: There is a flaw in the current detection logic for PulseAudio development library as it actually only detects its presense in the host/build system which does not mean anything when we are cross-compiling
-    if (NOT CMAKE_CROSSCOMPILING)
-        include (FindPkgConfig)
-        pkg_check_modules (PKG_PULSEAUDIO libpulse-simple)
-        if (PKG_PULSEAUDIO_FOUND)
-          add_definitions (-DSDL_AUDIO_DRIVER_PULSEAUDIO=1 -DSDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC="libpulse-simple.so")
-          file (GLOB PULSEAUDIO_SOURCES src/audio/pulseaudio/*.c)
-        endif ()
+    find_package (PulseAudio)
+    if (PA_FOUND)
+      include_directories (${PA_INCLUDE_DIRS})
+      get_filename_component (PA_SIMPLE ${PA_LIBRARIES} NAME)
+      add_definitions (-DSDL_AUDIO_DRIVER_PULSEAUDIO=1 -DSDL_AUDIO_DRIVER_PULSEAUDIO_DYNAMIC="${PA_SIMPLE}")
+      file (GLOB PULSEAUDIO_SOURCES src/audio/pulseaudio/*.c)
     endif ()
 
     file (GLOB SYS_C_FILES

+ 4 - 5
Source/ThirdParty/toluapp/src/bin/CMakeLists.txt

@@ -21,12 +21,8 @@
 #
 
 if (NOT CMAKE_PROJECT_NAME STREQUAL Urho3D)
-    # Set project name
-    project (ExternalProject-tolua++)
-
-    # Set minimum version
+    # Set CMake minimum version and CMake policy required by Urho3D-CMake-common module
     cmake_minimum_required (VERSION 2.8.6)
-
     if (COMMAND cmake_policy)
         cmake_policy (SET CMP0003 NEW)
         if (CMAKE_VERSION VERSION_GREATER 2.8.12 OR CMAKE_VERSION VERSION_EQUAL 2.8.12)
@@ -41,6 +37,9 @@ if (NOT CMAKE_PROJECT_NAME STREQUAL Urho3D)
         endif ()
     endif ()
 
+    # Set project name
+    project (Urho3D-ExternalProject-tolua++)
+
     # Set CMake modules search path
     set (CMAKE_MODULE_PATH ${BAKED_CMAKE_SOURCE_DIR}/CMake/Modules)
 

+ 9 - 8
Source/Tools/PackageTool/CMakeLists.txt

@@ -21,12 +21,8 @@
 #
 
 if (NOT CMAKE_PROJECT_NAME STREQUAL Urho3D)
-    # Set project name
-    project (ExternalProject-PackageTool)
-
-    # Set minimum version
+    # Set CMake minimum version and CMake policy required by Urho3D-CMake-common module
     cmake_minimum_required (VERSION 2.8.6)
-
     if (COMMAND cmake_policy)
         cmake_policy (SET CMP0003 NEW)
         if (CMAKE_VERSION VERSION_GREATER 2.8.12 OR CMAKE_VERSION VERSION_EQUAL 2.8.12)
@@ -41,6 +37,9 @@ if (NOT CMAKE_PROJECT_NAME STREQUAL Urho3D)
         endif ()
     endif ()
 
+    # Set project name
+    project (Urho3D-ExternalProject-PackageTool)
+
     # Set CMake modules search path
     set (CMAKE_MODULE_PATH ${BAKED_CMAKE_SOURCE_DIR}/CMake/Modules)
 
@@ -85,11 +84,14 @@ if (NOT CMAKE_PROJECT_NAME STREQUAL Urho3D)
 
     # Check existence of stdint.h for LibCpuId
     include (CheckIncludeFiles)
-    CHECK_INCLUDE_FILES (stdint.h HAVE_STDINT_H)
+    check_include_files (stdint.h HAVE_STDINT_H)
     if (HAVE_STDINT_H)
         add_definitions (-DHAVE_STDINT_H)
     endif ()
 
+    # Define that we are building mini Urho
+    add_definitions (-DMINI_URHO)
+
     # Setup SDK-like include dir in the build tree for building the mini-urho
     set (DEST_INCLUDE_DIR include/Urho3D)
     file (MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/${DEST_INCLUDE_DIR}/ThirdParty)
@@ -97,9 +99,8 @@ if (NOT CMAKE_PROJECT_NAME STREQUAL Urho3D)
     # Add dependency targets
     add_subdirectory (${BAKED_CMAKE_SOURCE_DIR}/Source/ThirdParty/LibCpuId host/LibCpuId)
     add_subdirectory (${BAKED_CMAKE_SOURCE_DIR}/Source/ThirdParty/LZ4 host/LZ4)
-    add_subdirectory (${BAKED_CMAKE_SOURCE_DIR}/Source/ThirdParty/SDL host/SDL)
     set (INCLUDE_DIRS ${BAKED_CMAKE_BINARY_DIR}/include ${BAKED_CMAKE_BINARY_DIR}/include/Urho3D ${CMAKE_BINARY_DIR}/${DEST_INCLUDE_DIR}/ThirdParty)
-    set (LIBS LibCpuId SDL)
+    set (LIBS LibCpuId)
 endif ()
 
 # Define target name

+ 2 - 2
Source/Tools/Urho3DPlayer/Urho3DPlayer.cpp

@@ -122,9 +122,9 @@ void Urho3DPlayer::Setup()
 
     // Construct a search path to find the resource prefix with two entries:
     // The first entry is an empty path which will be substituted with program/bin directory -- this entry is for binary when it is still in build tree
-    // The second entry is a relative path from the installed program/bin directory to the asset directory -- this entry is for binary when it is in the Urho3D SDK installation location
+    // The second and third entries are possible relative paths from the installed program/bin directory to the asset directory -- these entries are for binary when it is in the Urho3D SDK installation location
     if (!engineParameters_.Contains("ResourcePrefixPaths"))
-        engineParameters_["ResourcePrefixPaths"] = ";../share/Urho3D/Resources";
+        engineParameters_["ResourcePrefixPaths"] = ";../share/Resources;../share/Urho3D/Resources";
 }
 
 void Urho3DPlayer::Start()

+ 1 - 1
Source/Urho3D/.soversion

@@ -1 +1 @@
-0.0.172
+0.0.180

+ 2 - 0
Source/Urho3D/AngelScript/APITemplates.h

@@ -437,6 +437,8 @@ template <class T> void RegisterSerializable(asIScriptEngine* engine, const char
     engine->RegisterObjectMethod(className, "bool Save(VectorBuffer&) const", asFUNCTION(SerializableSaveVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "bool LoadXML(const XMLElement&, bool setInstanceDefault = false)", asMETHODPR(T, LoadXML, (const XMLElement&, bool), bool), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool SaveXML(XMLElement&) const", asMETHODPR(T, SaveXML, (XMLElement&) const, bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "bool LoadJSON(const JSONValue&, bool setInstanceDefault = false)", asMETHODPR(T, LoadJSON, (const JSONValue&, bool), bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "bool SaveJSON(JSONValue&) const", asMETHODPR(T, SaveJSON, (JSONValue&) const, bool), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void MarkNetworkUpdate() const", asMETHODPR(T, MarkNetworkUpdate, (), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void ApplyAttributes()", asMETHODPR(T, ApplyAttributes, (), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "bool SetAttribute(const String&in, const Variant&in)", asMETHODPR(T, SetAttribute, (const String&, const Variant&), bool), asCALL_THISCALL);

+ 4 - 2
Source/Urho3D/AngelScript/GraphicsAPI.cpp

@@ -835,7 +835,9 @@ static void RegisterMaterial(asIScriptEngine* engine)
 
     RegisterResource<Material>(engine, "Material");
     engine->RegisterObjectMethod("Material", "bool Load(const XMLElement&in)", asMETHODPR(Material, Load, (const XMLElement&), bool), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Material", "bool Save(XMLElement&in) const", asMETHODPR(Material, Save, (XMLElement&) const, bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Material", "bool Load(const JSONValue&in)", asMETHODPR(Material, Load, (const JSONValue&), bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Material", "bool Save(XMLElement&) const", asMETHODPR(Material, Save, (XMLElement&) const, bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Material", "bool Save(JSONValue&) const", asMETHODPR(Material, Save, (JSONValue&) const, bool), asCALL_THISCALL);
     engine->RegisterObjectMethod("Material", "void SetTechnique(uint, Technique@+, uint qualityLevel = 0, float lodDistance = 0.0)", asMETHOD(Material, SetTechnique), asCALL_THISCALL);
     engine->RegisterObjectMethod("Material", "void SetUVTransform(const Vector2&in, float, const Vector2&in)", asMETHODPR(Material, SetUVTransform, (const Vector2&, float, const Vector2&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Material", "void SetUVTransform(const Vector2&in, float, float)", asMETHODPR(Material, SetUVTransform, (const Vector2&, float, float), void), asCALL_THISCALL);
@@ -1368,7 +1370,7 @@ static void RegisterParticleEffect(asIScriptEngine* engine)
 
     RegisterResource<ParticleEffect>(engine, "ParticleEffect");
     engine->RegisterObjectMethod("ParticleEffect", "bool Load(const XMLElement&in)", asMETHODPR(ParticleEffect, Load, (const XMLElement&), bool), asCALL_THISCALL);
-    engine->RegisterObjectMethod("ParticleEffect", "bool Save(XMLElement&in) const", asMETHODPR(ParticleEffect, Save, (XMLElement&) const, bool), asCALL_THISCALL);
+    engine->RegisterObjectMethod("ParticleEffect", "bool Save(XMLElement&) const", asMETHODPR(ParticleEffect, Save, (XMLElement&) const, bool), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "void set_material(Material@+)", asMETHOD(ParticleEffect, SetMaterial), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "Material@+ get_material() const", asMETHOD(ParticleEffect, GetMaterial), asCALL_THISCALL);
     engine->RegisterObjectMethod("ParticleEffect", "void set_numParticles(uint) const", asMETHOD(ParticleEffect, SetNumParticles), asCALL_THISCALL);

+ 16 - 0
Source/Urho3D/AngelScript/PhysicsAPI.cpp

@@ -242,6 +242,13 @@ static PhysicsRaycastResult PhysicsWorldRaycastSingle(const Ray& ray, float maxD
     return result;
 }
 
+static PhysicsRaycastResult PhysicsWorldRaycastSingleSegmented(const Ray& ray, float maxDistance, float segmentDistance, unsigned collisionMask, PhysicsWorld* ptr)
+{
+    PhysicsRaycastResult result;
+    ptr->RaycastSingleSegmented(result, ray, maxDistance, segmentDistance, collisionMask);
+    return result;
+}
+
 static PhysicsRaycastResult PhysicsWorldSphereCast(const Ray& ray, float radius, float maxDistance, unsigned collisionMask, PhysicsWorld* ptr)
 {
     PhysicsRaycastResult result;
@@ -280,6 +287,13 @@ static CScriptArray* PhysicsWorldGetRigidBodiesBody(RigidBody* body, PhysicsWorl
     return VectorToHandleArray<RigidBody>(result, "Array<RigidBody@>");
 }
 
+static CScriptArray* PhysicsWorldGetCollidingBodies(RigidBody* body, PhysicsWorld* ptr)
+{
+    PODVector<RigidBody*> result;
+    ptr->GetCollidingBodies(result, body);
+    return VectorToHandleArray<RigidBody>(result, "Array<RigidBody@>");
+}
+
 static void RegisterPhysicsWorld(asIScriptEngine* engine)
 {
     engine->RegisterObjectType("PhysicsRaycastResult", sizeof(PhysicsRaycastResult), asOBJ_VALUE | asOBJ_APP_CLASS_C);
@@ -296,6 +310,7 @@ static void RegisterPhysicsWorld(asIScriptEngine* engine)
     engine->RegisterObjectMethod("PhysicsWorld", "void UpdateCollisions()", asMETHOD(PhysicsWorld, UpdateCollisions), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "Array<PhysicsRaycastResult>@ Raycast(const Ray&in, float, uint collisionMask = 0xffff)", asFUNCTION(PhysicsWorldRaycast), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("PhysicsWorld", "PhysicsRaycastResult RaycastSingle(const Ray&in, float, uint collisionMask = 0xffff)", asFUNCTION(PhysicsWorldRaycastSingle), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("PhysicsWorld", "PhysicsRaycastResult RaycastSingleSegmented(const Ray&in, float, float, uint collisionMask = 0xffff)", asFUNCTION(PhysicsWorldRaycastSingleSegmented), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("PhysicsWorld", "PhysicsRaycastResult SphereCast(const Ray&in, float, float, uint collisionMask = 0xffff)", asFUNCTION(PhysicsWorldSphereCast), asCALL_CDECL_OBJLAST);
     // There seems to be a bug in AngelScript resulting in a crash if we use an auto handle with this function.
     // Work around by manually releasing the CollisionShape handle
@@ -303,6 +318,7 @@ static void RegisterPhysicsWorld(asIScriptEngine* engine)
     engine->RegisterObjectMethod("PhysicsWorld", "Array<RigidBody@>@ GetRigidBodies(const Sphere&in, uint collisionMask = 0xffff)", asFUNCTION(PhysicsWorldGetRigidBodiesSphere), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("PhysicsWorld", "Array<RigidBody@>@ GetRigidBodies(const BoundingBox&in, uint collisionMask = 0xffff)", asFUNCTION(PhysicsWorldGetRigidBodiesBox), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("PhysicsWorld", "Array<RigidBody@>@ GetRigidBodies(RigidBody@+)", asFUNCTION(PhysicsWorldGetRigidBodiesBody), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("PhysicsWorld", "Array<RigidBody@>@ GetCollidingBodies(RigidBody@+)", asFUNCTION(PhysicsWorldGetCollidingBodies), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("PhysicsWorld", "void DrawDebugGeometry(bool)", asMETHODPR(PhysicsWorld, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "void RemoveCachedGeometry(Model@+)", asMETHOD(PhysicsWorld, RemoveCachedGeometry), asCALL_THISCALL);
     engine->RegisterObjectMethod("PhysicsWorld", "void set_gravity(const Vector3&in)", asMETHOD(PhysicsWorld, SetGravity), asCALL_THISCALL);

+ 6 - 1
Source/Urho3D/AngelScript/ResourceAPI.cpp

@@ -360,8 +360,9 @@ static bool JSONFileSave(File* file, const String& indendation, JSONFile* ptr)
 static void RegisterJSONFile(asIScriptEngine* engine)
 {
     RegisterResource<JSONFile>(engine, "JSONFile");
-    engine->RegisterObjectMethod("JSONFile", "const JSONValue& GetRoot() const", asMETHODPR(JSONFile, GetRoot, () const, const JSONValue&), asCALL_THISCALL);
+    engine->RegisterObjectMethod("JSONFile", "JSONValue& GetRoot()", asMETHODPR(JSONFile, GetRoot, () const, const JSONValue&), asCALL_THISCALL);
     engine->RegisterObjectMethod("JSONFile", "bool Save(File@+, const String&in) const", asFUNCTION(JSONFileSave), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("JSONFile", "JSONValue& get_root()", asMETHODPR(JSONFile, GetRoot, () const, const JSONValue&), asCALL_THISCALL);
 }
 
 static void ConstructXMLElement(XMLElement* ptr)
@@ -460,6 +461,8 @@ static void RegisterXMLElement(asIScriptEngine* engine)
     engine->RegisterObjectMethod("XMLElement", "bool SetDouble(const String&in, double)", asMETHOD(XMLElement, SetDouble), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "bool SetInt(const String&in, int)", asMETHOD(XMLElement, SetInt), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "bool SetUInt(const String&in, uint)", asMETHOD(XMLElement, SetUInt), asCALL_THISCALL);
+    engine->RegisterObjectMethod("XMLElement", "bool SetIntRect(const String&in, const IntRect&in)", asMETHOD(XMLElement, SetIntRect), asCALL_THISCALL);
+    engine->RegisterObjectMethod("XMLElement", "bool SetIntVector2(const String&in, const IntVector2&in)", asMETHOD(XMLElement, SetIntVector2), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "bool SetQuaternion(const String&in, const Quaternion&in)", asMETHOD(XMLElement, SetQuaternion), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "bool SetVariant(const Variant&in)", asMETHOD(XMLElement, SetVariant), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "bool SetResourceRef(const String&in, const ResourceRef&in)", asMETHOD(XMLElement, SetResourceRef), asCALL_THISCALL);
@@ -489,6 +492,8 @@ static void RegisterXMLElement(asIScriptEngine* engine)
     engine->RegisterObjectMethod("XMLElement", "double GetDouble(const String&in) const", asMETHOD(XMLElement, GetDouble), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "uint GetUInt(const String&in) const", asMETHOD(XMLElement, GetUInt), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "int GetInt(const String&in) const", asMETHOD(XMLElement, GetInt), asCALL_THISCALL);
+    engine->RegisterObjectMethod("XMLElement", "IntRect GetIntRect(const String&in) const", asMETHOD(XMLElement, GetIntRect), asCALL_THISCALL);
+    engine->RegisterObjectMethod("XMLElement", "IntVector2 GetIntVector2(const String&in) const", asMETHOD(XMLElement, GetIntVector2), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "Quaternion GetQuaternion(const String&in) const", asMETHOD(XMLElement, GetQuaternion), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "Variant GetVariant() const", asMETHOD(XMLElement, GetVariant), asCALL_THISCALL);
     engine->RegisterObjectMethod("XMLElement", "ResourceRef GetResourceRef() const", asMETHOD(XMLElement, GetResourceRef), asCALL_THISCALL);

+ 58 - 0
Source/Urho3D/AngelScript/SceneAPI.cpp

@@ -100,11 +100,21 @@ static bool NodeSaveXML(File* file, const String& indentation, Node* ptr)
     return file && ptr->SaveXML(*file, indentation);
 }
 
+static bool NodeSaveJSON(File* file, Node* ptr)
+{
+    return file && ptr->SaveJSON(*file);
+}
+
 static bool NodeSaveXMLVectorBuffer(VectorBuffer& buffer, const String& indentation, Node* ptr)
 {
     return ptr->SaveXML(buffer, indentation);
 }
 
+static bool NodeSaveJSONVectorBuffer(VectorBuffer& buffer, Node* ptr)
+{
+    return ptr->SaveJSON(buffer);
+}
+
 static void RegisterNode(asIScriptEngine* engine)
 {
     engine->RegisterEnum("CreateMode");
@@ -129,6 +139,8 @@ static void RegisterNode(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Node", "bool get_enabledSelf() const", asMETHOD(Node, IsEnabledSelf), asCALL_THISCALL);
     engine->RegisterObjectMethod("Node", "bool SaveXML(File@+, const String&in indentation = \"\t\")", asFUNCTION(NodeSaveXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Node", "bool SaveXML(VectorBuffer&, const String&in indentation = \"\t\")", asFUNCTION(NodeSaveXMLVectorBuffer), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Node", "bool SaveJSON(File@+)", asFUNCTION(NodeSaveJSON), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Node", "bool SaveJSON(VectorBuffer&)", asFUNCTION(NodeSaveJSONVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Node", "Node@+ Clone(CreateMode mode = REPLICATED)", asMETHOD(Node, Clone), asCALL_THISCALL);
     RegisterObjectConstructor<Node>(engine, "Node");
     RegisterNamedObjectConstructor<Node>(engine, "Node");
@@ -153,16 +165,36 @@ static bool SceneLoadXMLVectorBuffer(VectorBuffer& buffer, Scene* ptr)
     return ptr->LoadXML(buffer);
 }
 
+static bool SceneLoadJSONVectorBuffer(VectorBuffer& buffer, Scene* ptr)
+{
+    return ptr->LoadJSON(buffer);
+}
+
+static bool SceneLoadJSON(File* file, Scene* ptr)
+{
+    return file && ptr->LoadJSON(*file);
+}
+
 static bool SceneSaveXML(File* file, const String& indentation, Scene* ptr)
 {
     return file && ptr->SaveXML(*file, indentation);
 }
 
+static bool SceneSaveJSON(File* file, const String& indentation, Scene* ptr)
+{
+    return file && ptr->SaveJSON(*file, indentation);
+}
+
 static bool SceneSaveXMLVectorBuffer(VectorBuffer& buffer, const String& indentation, Scene* ptr)
 {
     return ptr->SaveXML(buffer, indentation);
 }
 
+static bool SceneSaveJSONVectorBuffer(VectorBuffer& buffer, const String& indentation, Scene* ptr)
+{
+    return ptr->SaveJSON(buffer, indentation);
+}
+
 static Node* SceneInstantiate(File* file, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
 {
     return file ? ptr->Instantiate(*file, position, rotation, mode) : 0;
@@ -178,16 +210,31 @@ static Node* SceneInstantiateXML(File* file, const Vector3& position, const Quat
     return file ? ptr->InstantiateXML(*file, position, rotation, mode) : 0;
 }
 
+static Node* SceneInstantiateJSON(File* file, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
+{
+    return file ? ptr->InstantiateJSON(*file, position, rotation, mode) : 0;
+}
+
 static Node* SceneInstantiateXMLVectorBuffer(VectorBuffer& buffer, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
 {
     return ptr->InstantiateXML(buffer, position, rotation, mode);
 }
 
+static Node* SceneInstantiateJSONVectorBuffer(VectorBuffer& buffer, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
+{
+    return ptr->InstantiateJSON(buffer, position, rotation, mode);
+}
+
 static Node* SceneInstantiateXMLFile(XMLFile* xml, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
 {
     return xml ? ptr->InstantiateXML(xml->GetRoot(), position, rotation, mode) : 0;
 }
 
+static Node* SceneInstantiateJSONFile(JSONFile* json, const Vector3& position, const Quaternion& rotation, CreateMode mode, Scene* ptr)
+{
+    return json ? ptr->InstantiateJSON(json->GetRoot(), position, rotation, mode) : 0;
+}
+
 static CScriptArray* SceneGetRequiredPackageFiles(Scene* ptr)
 {
     return VectorToHandleArray<PackageFile>(ptr->GetRequiredPackageFiles(), "Array<PackageFile@>");
@@ -279,15 +326,26 @@ static void RegisterScene(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Scene", "bool LoadXML(VectorBuffer&)", asFUNCTION(SceneLoadXMLVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool SaveXML(File@+, const String&in indentation = \"\t\")", asFUNCTION(SceneSaveXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool SaveXML(VectorBuffer&, const String&in indentation = \"\t\")", asFUNCTION(SceneSaveXMLVectorBuffer), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "bool LoadJSON(File@+)", asFUNCTION(SceneLoadJSON), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "bool LoadJSON(VectorBuffer&)", asFUNCTION(SceneLoadJSONVectorBuffer), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "bool SaveJSON(File@+, const String&in indentation = \"\t\")", asFUNCTION(SceneSaveJSON), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "bool SaveJSON(VectorBuffer&, const String&in indentation = \"\t\")", asFUNCTION(SceneSaveJSONVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "bool LoadAsync(File@+, LoadMode mode = LOAD_SCENE_AND_RESOURCES)", asMETHOD(Scene, LoadAsync), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool LoadAsyncXML(File@+, LoadMode mode = LOAD_SCENE_AND_RESOURCES)", asMETHOD(Scene, LoadAsyncXML), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void StopAsyncLoading()", asMETHOD(Scene, StopAsyncLoading), asCALL_THISCALL);
+
     engine->RegisterObjectMethod("Scene", "Node@+ Instantiate(File@+, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiate), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Node@+ Instantiate(VectorBuffer&, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Node@+ InstantiateXML(File@+, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateXML), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Node@+ InstantiateXML(VectorBuffer&, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateXMLVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Node@+ InstantiateXML(XMLFile@+, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateXMLFile), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Node@+ InstantiateXML(const XMLElement&in, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asMETHODPR(Scene, InstantiateXML, (const XMLElement&, const Vector3&, const Quaternion&, CreateMode), Node*), asCALL_THISCALL);
+
+    engine->RegisterObjectMethod("Scene", "Node@+ InstantiateJSON(File@+, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateJSON), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "Node@+ InstantiateJSON(VectorBuffer&, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateJSONVectorBuffer), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "Node@+ InstantiateJSON(JSONFile@+, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asFUNCTION(SceneInstantiateJSONFile), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "Node@+ InstantiateJSON(const JSONValue&in, const Vector3&in, const Quaternion&in, CreateMode mode = REPLICATED)", asMETHODPR(Scene, InstantiateJSON, (const JSONValue&, const Vector3&, const Quaternion&, CreateMode), Node*), asCALL_THISCALL);
+
     engine->RegisterObjectMethod("Scene", "void Clear(bool clearReplicated = true, bool clearLocal = true)", asMETHOD(Scene, Clear), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void AddRequiredPackageFile(PackageFile@+)", asMETHOD(Scene, AddRequiredPackageFile), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void ClearRequiredPackageFiles()", asMETHOD(Scene, ClearRequiredPackageFiles), asCALL_THISCALL);

+ 4 - 0
Source/Urho3D/AngelScript/UIAPI.cpp

@@ -700,6 +700,8 @@ static void RegisterUI(asIScriptEngine* engine)
     engine->RegisterObjectMethod("UI", "bool SaveLayout(File@+, UIElement@+)", asFUNCTION(UISaveLayout), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("UI", "bool SaveLayout(VectorBuffer&, UIElement@+)", asFUNCTION(UISaveLayoutVectorBuffer), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("UI", "void SetFocusElement(UIElement@+, bool byKey = false)", asMETHOD(UI, SetFocusElement), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "void SetWidth(float value)", asMETHOD(UI, SetWidth), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "void SetHeight(float value)", asMETHOD(UI, SetHeight), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ GetElementAt(const IntVector2&in, bool activeOnly = true)", asMETHODPR(UI, GetElementAt, (const IntVector2&, bool), UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "UIElement@+ GetElementAt(int, int, bool activeOnly = true)", asMETHODPR(UI, GetElementAt, (int, int, bool), UIElement*), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "bool HasModalElement() const", asMETHOD(UI, HasModalElement), asCALL_THISCALL);
@@ -735,6 +737,8 @@ static void RegisterUI(asIScriptEngine* engine)
     engine->RegisterObjectMethod("UI", "bool get_useMutableGlyphs() const", asMETHOD(UI, GetUseMutableGlyphs), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "void set_forceAutoHint(bool)", asMETHOD(UI, SetForceAutoHint), asCALL_THISCALL);
     engine->RegisterObjectMethod("UI", "bool get_forceAutoHint() const", asMETHOD(UI, GetForceAutoHint), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "void set_scale(float value)", asMETHOD(UI, SetScale), asCALL_THISCALL);
+    engine->RegisterObjectMethod("UI", "float get_scale() const", asMETHOD(UI, GetScale), asCALL_THISCALL);
     engine->RegisterGlobalFunction("UI@+ get_ui()", asFUNCTION(GetUI), asCALL_CDECL);
 }
 

+ 38 - 0
Source/Urho3D/Audio/AudioEvents.h

@@ -0,0 +1,38 @@
+//
+// Copyright (c) 2008-2015 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 "../Core/Object.h"
+
+namespace Urho3D
+{
+
+/// Sound playback finished. Sent through the SoundSource's Node.
+URHO3D_EVENT(E_SOUNDFINISHED, SoundFinished)
+{
+    URHO3D_PARAM(P_NODE, Node);                     // Node pointer
+    URHO3D_PARAM(P_SOUNDSOURCE, SoundSource);       // SoundSource pointer
+    URHO3D_PARAM(P_SOUND, Sound);                   // Sound pointer
+}
+
+}

+ 46 - 1
Source/Urho3D/Audio/SoundSource.cpp

@@ -23,11 +23,15 @@
 #include "../Precompiled.h"
 
 #include "../Audio/Audio.h"
+#include "../Audio/AudioEvents.h"
 #include "../Audio/Sound.h"
 #include "../Audio/SoundSource.h"
 #include "../Audio/SoundStream.h"
 #include "../Core/Context.h"
+#include "../IO/Log.h"
 #include "../Resource/ResourceCache.h"
+#include "../Scene/Node.h"
+#include "../Scene/ReplicationState.h"
 
 #include "../DebugNew.h"
 
@@ -106,6 +110,7 @@ SoundSource::SoundSource(Context* context) :
     panning_(0.0f),
     autoRemoveTimer_(0.0f),
     autoRemove_(false),
+    sendFinishedEvent_(false),
     position_(0),
     fractPosition_(0),
     timePosition_(0.0f),
@@ -159,6 +164,20 @@ void SoundSource::Play(Sound* sound)
     else
         PlayLockless(sound);
 
+    // Forget the Sound & Is Playing attribute previous values so that they will be sent again, triggering
+    // the sound correctly on network clients even after the initial playback
+    if (networkState_ && networkState_->attributes_ && networkState_->previousValues_.Size())
+    {
+        for (unsigned i = 1; i < networkState_->previousValues_.Size(); ++i)
+        {
+            // The indexing is different for SoundSource & SoundSource3D, as SoundSource3D removes two attributes,
+            // so go by attribute types
+            VariantType type = networkState_->attributes_->At(i).type_;
+            if (type == VAR_RESOURCEREF || type == VAR_BOOL)
+                networkState_->previousValues_[i] = Variant::EMPTY;
+        }
+    }
+
     MarkNetworkUpdate();
 }
 
@@ -266,6 +285,9 @@ void SoundSource::SetPanning(float panning)
 
 void SoundSource::SetAutoRemove(bool enable)
 {
+    if (enable == true)
+        URHO3D_LOGWARNING("SoundSource::SetAutoRemove is deprecated. Consider using the SoundFinished event instead");
+
     autoRemove_ = enable;
 }
 
@@ -297,10 +319,31 @@ void SoundSource::Update(float timeStep)
     if (soundStream_ && !position_)
         StopLockless();
 
+    bool playing = IsPlaying();
+
+    if (!playing && sendFinishedEvent_)
+    {
+        sendFinishedEvent_ = false;
+
+        // Make a weak pointer to self to check for destruction during event handling
+        WeakPtr<SoundSource> self(this);
+
+        using namespace SoundFinished;
+
+        VariantMap& eventData = context_->GetEventDataMap();
+        eventData[P_NODE] = node_;
+        eventData[P_SOUNDSOURCE] = this;
+        eventData[P_SOUND] = sound_;
+        node_->SendEvent(E_SOUNDFINISHED, eventData);
+
+        if (self.Expired())
+            return;
+    }
+
     // Check for autoremove
     if (autoRemove_)
     {
-        if (!IsPlaying())
+        if (!playing)
         {
             autoRemoveTimer_ += timeStep;
             if (autoRemoveTimer_ > AUTOREMOVE_DELAY)
@@ -479,6 +522,7 @@ void SoundSource::PlayLockless(Sound* sound)
                 sound_ = sound;
                 position_ = start;
                 fractPosition_ = 0;
+                sendFinishedEvent_ = true;
                 return;
             }
         }
@@ -516,6 +560,7 @@ void SoundSource::PlayLockless(SharedPtr<SoundStream> stream)
         unusedStreamSize_ = 0;
         position_ = streamBuffer_->GetStart();
         fractPosition_ = 0;
+        sendFinishedEvent_ = true;
         return;
     }
 

+ 5 - 3
Source/Urho3D/Audio/SoundSource.h

@@ -70,8 +70,8 @@ public:
     void SetAttenuation(float attenuation);
     /// Set stereo panning. -1.0 is full left and 1.0 is full right.
     void SetPanning(float panning);
-    /// Set whether sound source will be automatically removed from the scene node when playback stops.
-    void SetAutoRemove(bool enable);
+    /// Set whether sound source will be automatically removed from the scene node when playback stops. Note: this is deprecated, consider subscribing to the SoundFinished event instead.
+    URHO3D_DEPRECATED void SetAutoRemove(bool enable);
     /// Set new playback position.
     void SetPlayPosition(signed char* pos);
 
@@ -100,7 +100,7 @@ public:
     float GetPanning() const { return panning_; }
 
     /// Return autoremove mode.
-    bool GetAutoRemove() const { return autoRemove_; }
+    URHO3D_DEPRECATED bool GetAutoRemove() const { return autoRemove_; }
 
     /// Return whether is playing.
     bool IsPlaying() const;
@@ -144,6 +144,8 @@ protected:
     float masterGain_;
     /// Autoremove flag.
     bool autoRemove_;
+    /// Whether finished event should be sent on playback stop.
+    bool sendFinishedEvent_;
 
 private:
     /// Play a sound without locking the audio mutex. Called internally.

+ 7 - 4
Source/Urho3D/CMakeLists.txt

@@ -169,7 +169,10 @@ endif ()
 
 # Generate platform specific export header file
 if (MSVC)
-    set (PRE_EXPORT_HEADER "\n#pragma warning(disable: 4251)\n#pragma warning(disable: 4275)\n")
+    if (URHO3D_LIB_TYPE STREQUAL STATIC)
+        set (BAKED_IN_DEFINE "\n#define URHO3D_STATIC_DEFINE\n")
+    endif ()
+    set (PRE_EXPORT_HEADER "\n#pragma warning(disable: 4251)\n#pragma warning(disable: 4275)\n${BAKED_IN_DEFINE}")
 endif ()
 if (URHO3D_CLANG_TOOLS)
     set (POST_EXPORT_HEADER "\n#define NONSCRIPTABLE __attribute__((annotate(\"nonscriptable\")))\n")
@@ -420,15 +423,15 @@ if (GLOBAL_INCLUDE_DIRS)
     string (REPLACE ";" "\" ${DASH}I\"" GLOBAL_INCLUDE_DIRS "${DASH}I\"${GLOBAL_INCLUDE_DIRS}\"")
     string (REPLACE "${SYSROOT}" "" GLOBAL_INCLUDE_DIRS ${GLOBAL_INCLUDE_DIRS})
 endif ()
-set (ENGINE_INCLUDE_DIRS "${DASH}I\"\${includedir}\" ${DASH}I\"\${includedir}/${PATH_SUFFIX}/ThirdParty\"")
+set (ENGINE_INCLUDE_DIRS "${DASH}I\"\${includedir}\" ${DASH}I\"\${includedir}/Urho3D/ThirdParty\"")
 if (URHO3D_PHYSICS)
     # Bullet library depends on its own include dir to be added in the header search path
     # This is more practical than patching its header files in many places to make them work with relative path
-    set (ENGINE_INCLUDE_DIRS "${ENGINE_INCLUDE_DIRS} ${DASH}I\"\${includedir}/${PATH_SUFFIX}/ThirdParty/Bullet\"")
+    set (ENGINE_INCLUDE_DIRS "${ENGINE_INCLUDE_DIRS} ${DASH}I\"\${includedir}/Urho3D/ThirdParty/Bullet\"")
 endif ()
 if (URHO3D_LUA)
     # ditto for Lua/LuaJIT
-    set (ENGINE_INCLUDE_DIRS "${ENGINE_INCLUDE_DIRS} ${DASH}I\"\${includedir}/${PATH_SUFFIX}/ThirdParty/Lua${JIT}\"")
+    set (ENGINE_INCLUDE_DIRS "${ENGINE_INCLUDE_DIRS} ${DASH}I\"\${includedir}/Urho3D/ThirdParty/Lua${JIT}\"")
 endif ()
 # todo: Reevaluate the command below when the CMake minimum required version is set to 2.8.12 (and remove only when the deprected add_compiler_export_flags() is not used anymore)
 string (REGEX REPLACE " -fvisibility[^ ]+" "" CLEANED_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})   # Remove visibility compiler options that are only used for building the library

+ 4 - 0
Source/Urho3D/Core/ProcessUtils.cpp

@@ -74,7 +74,9 @@ inline void SetFPUState(unsigned control)
 
 #endif
 
+#ifndef MINI_URHO
 #include <SDL/SDL.h>
+#endif
 
 #include "../DebugNew.h"
 
@@ -125,7 +127,9 @@ void InitFPU()
 
 void ErrorDialog(const String& title, const String& message)
 {
+#ifndef MINI_URHO
     SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title.CString(), message.CString(), 0);
+#endif
 }
 
 void ErrorExit(const String& message, int exitCode)

+ 3 - 4
Source/Urho3D/Engine/Console.cpp

@@ -27,7 +27,6 @@
 #include "../Engine/Console.h"
 #include "../Engine/EngineEvents.h"
 #include "../Graphics/Graphics.h"
-#include "../Graphics/GraphicsEvents.h"
 #include "../Input/Input.h"
 #include "../IO/IOEvents.h"
 #include "../IO/Log.h"
@@ -93,7 +92,7 @@ Console::Console(Context* context) :
     SubscribeToEvent(lineEdit_, E_TEXTFINISHED, URHO3D_HANDLER(Console, HandleTextFinished));
     SubscribeToEvent(lineEdit_, E_UNHANDLEDKEY, URHO3D_HANDLER(Console, HandleLineEditKey));
     SubscribeToEvent(closeButton_, E_RELEASED, URHO3D_HANDLER(Console, HandleCloseButtonPressed));
-    SubscribeToEvent(E_SCREENMODE, URHO3D_HANDLER(Console, HandleScreenMode));
+    SubscribeToEvent(uiRoot, E_RESIZED, URHO3D_HANDLER(Console, HandleRootElementResized));
     SubscribeToEvent(E_LOGMESSAGE, URHO3D_HANDLER(Console, HandleLogMessage));
     SubscribeToEvent(E_POSTUPDATE, URHO3D_HANDLER(Console, HandlePostUpdate));
 }
@@ -223,7 +222,7 @@ void Console::SetFocusOnShow(bool enable)
 
 void Console::UpdateElements()
 {
-    int width = GetSubsystem<Graphics>()->GetWidth();
+    int width = GetSubsystem<UI>()->GetRoot()->GetWidth();
     const IntRect& border = background_->GetLayoutBorder();
     const IntRect& panelBorder = rowContainer_->GetScrollPanel()->GetClipBorder();
     rowContainer_->SetFixedWidth(width - border.left_ - border.right_);
@@ -378,7 +377,7 @@ void Console::HandleCloseButtonPressed(StringHash eventType, VariantMap& eventDa
     SetVisible(false);
 }
 
-void Console::HandleScreenMode(StringHash eventType, VariantMap& eventData)
+void Console::HandleRootElementResized(StringHash eventType, VariantMap& eventData)
 {
     UpdateElements();
 }

+ 2 - 2
Source/Urho3D/Engine/Console.h

@@ -126,8 +126,8 @@ private:
     void HandleLineEditKey(StringHash eventType, VariantMap& eventData);
     /// Handle close button being pressed.
     void HandleCloseButtonPressed(StringHash eventType, VariantMap& eventData);
-    /// Handle rendering window resize.
-    void HandleScreenMode(StringHash eventType, VariantMap& eventData);
+    /// Handle UI root resize.
+    void HandleRootElementResized(StringHash eventType, VariantMap& eventData);
     /// Handle a log message.
     void HandleLogMessage(StringHash eventType, VariantMap& eventData);
     /// Handle the application post-update.

+ 9 - 0
Source/Urho3D/Graphics/AnimatedModel.cpp

@@ -131,6 +131,15 @@ bool AnimatedModel::LoadXML(const XMLElement& source, bool setInstanceDefault)
     return success;
 }
 
+bool AnimatedModel::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    loading_ = true;
+    bool success = Component::LoadJSON(source, setInstanceDefault);
+    loading_ = false;
+
+    return success;
+}
+
 void AnimatedModel::ApplyAttributes()
 {
     if (assignBonesPending_)

+ 2 - 0
Source/Urho3D/Graphics/AnimatedModel.h

@@ -51,6 +51,8 @@ public:
     virtual bool Load(Deserializer& source, bool setInstanceDefault = false);
     /// Load from XML data. Return true if successful.
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
+    /// Load from JSON data. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes();
     /// Process octree raycast. May be called from a worker thread.

+ 31 - 0
Source/Urho3D/Graphics/Animation.cpp

@@ -32,6 +32,7 @@
 #include "../IO/Serializer.h"
 #include "../Resource/ResourceCache.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 
 #include "../DebugNew.h"
 
@@ -184,6 +185,36 @@ bool Animation::BeginLoad(Deserializer& source)
         }
 
         memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
+        SetMemoryUse(memoryUse);
+        return true;
+    }
+
+    // Optionally read triggers from a JSON file
+    String jsonName = ReplaceExtension(GetName(), ".json");
+
+    SharedPtr<JSONFile> jsonFile(cache->GetTempResource<JSONFile>(jsonName, false));
+    if (jsonFile)
+    {
+        const JSONValue& rootVal = jsonFile->GetRoot();
+        JSONArray triggerArray = rootVal.Get("triggers").GetArray();
+
+        for (unsigned i = 0; i < triggerArray.Size(); i++)
+        {
+            const JSONValue& triggerValue = triggerArray.At(i);
+            JSONValue normalizedTimeValue = triggerValue.Get("normalizedTime");
+            if (!normalizedTimeValue.IsNull())
+                AddTrigger(normalizedTimeValue.GetFloat(), true, triggerValue.GetVariant());
+            else
+            {
+                JSONValue timeVal = triggerValue.Get("time");
+                if (!timeVal.IsNull())
+                    AddTrigger(timeVal.GetFloat(), false, triggerValue.GetVariant());
+            }
+        }
+
+        memoryUse += triggers_.Size() * sizeof(AnimationTriggerPoint);
+        SetMemoryUse(memoryUse);
+        return true;
     }
 
     SetMemoryUse(memoryUse);

+ 37 - 0
Source/Urho3D/Graphics/AnimationState.cpp

@@ -249,18 +249,54 @@ void AnimationState::AddTime(float delta)
     if (delta == 0.0f || length == 0.0f)
         return;
 
+    bool sendFinishEvent = false;
+
     float oldTime = GetTime();
     float time = oldTime + delta;
     if (looped_)
     {
         while (time >= length)
+        {
             time -= length;
+            sendFinishEvent = true;
+        }
         while (time < 0.0f)
+        {
             time += length;
+            sendFinishEvent = true;
+        }
     }
 
     SetTime(time);
 
+    if (!looped_)
+    {
+        if (delta > 0.0f && oldTime < length && GetTime() == length)
+            sendFinishEvent = true;
+        else if (delta < 0.0f && oldTime > 0.0f && GetTime() == 0.0f)
+            sendFinishEvent = true;
+    }
+
+    // Process finish event
+    if (sendFinishEvent)
+    {
+        using namespace AnimationFinished;
+
+        WeakPtr<AnimationState> self(this);
+        WeakPtr<Node> senderNode(model_ ? model_->GetNode() : node_);
+
+        VariantMap& eventData = senderNode->GetEventDataMap();
+        eventData[P_NODE] = senderNode;
+        eventData[P_ANIMATION] = animation_;
+        eventData[P_NAME] = animation_->GetAnimationName();
+        eventData[P_LOOPED] = looped_;
+
+        // Note: this may cause arbitrary deletion of animation states, including the one we are currently processing
+        senderNode->SendEvent(E_ANIMATIONFINISHED, eventData);
+        if (senderNode.Expired() || self.Expired())
+            return;
+    }
+
     // Process animation triggers
     if (animation_->GetNumTriggers())
     {
@@ -301,6 +337,7 @@ void AnimationState::AddTime(float delta)
 
                 VariantMap& eventData = senderNode->GetEventDataMap();
                 eventData[P_NODE] = senderNode;
+                eventData[P_ANIMATION] = animation_;
                 eventData[P_NAME] = animation_->GetAnimationName();
                 eventData[P_TIME] = i->time_;
                 eventData[P_DATA] = i->data_;

+ 1 - 1
Source/Urho3D/Graphics/Batch.cpp

@@ -495,7 +495,7 @@ void Batch::Prepare(View* view, Camera* camera, bool setModelTransform, bool all
 
                 float sizeX = 1.0f / (float)shadowMap->GetWidth();
                 float sizeY = 1.0f / (float)shadowMap->GetHeight();
-                graphics->SetShaderParameter(PSP_SHADOWMAPINVSIZE, Vector4(sizeX, sizeY, 0.0f, 0.0f));
+                graphics->SetShaderParameter(PSP_SHADOWMAPINVSIZE, Vector2(sizeX, sizeY));
 
                 Vector4 lightSplits(M_LARGE_VALUE, M_LARGE_VALUE, M_LARGE_VALUE, M_LARGE_VALUE);
                 if (lightQueue_->shadowSplits_.Size() > 1)

+ 15 - 4
Source/Urho3D/Graphics/Direct3D11/D3D11Graphics.cpp

@@ -220,8 +220,6 @@ static HWND GetWindowHandle(SDL_Window* window)
     return sysInfo.info.win.window;
 }
 
-static unsigned readableDepthFormat = 0;
-
 const Vector2 Graphics::pixelUVOffset(0.0f, 0.0f);
 
 Graphics::Graphics(Context* context) :
@@ -702,10 +700,15 @@ void Graphics::Clear(unsigned flags, const Color& color, float depth, unsigned s
 {
     IntVector2 rtSize = GetRenderTargetDimensions();
 
+    bool oldColorWrite = colorWrite_;
+    bool oldDepthWrite = depthWrite_;
+
     // D3D11 clear always clears the whole target regardless of viewport or scissor test settings
     // Emulate partial clear by rendering a quad
     if (!viewport_.left_ && !viewport_.top_ && viewport_.right_ == rtSize.x_ && viewport_.bottom_ == rtSize.y_)
     {
+        // Make sure we use the read-write version of the depth stencil
+        SetDepthWrite(true);
         PrepareDraw();
 
         if ((flags & CLEAR_COLOR) && impl_->renderTargetViews_[0])
@@ -748,11 +751,13 @@ void Graphics::Clear(unsigned flags, const Color& color, float depth, unsigned s
 
         geometry->Draw(this);
 
-        SetColorWrite(true);
-        SetDepthWrite(true);
         SetStencilTest(false);
         ClearParameterSources();
     }
+
+    // Restore color & depth write state now
+    SetColorWrite(oldColorWrite);
+    SetDepthWrite(oldDepthWrite);
 }
 
 bool Graphics::ResolveToTexture(Texture2D* destination, const IntRect& viewport)
@@ -1523,6 +1528,8 @@ void Graphics::SetDepthWrite(bool enable)
     {
         depthWrite_ = enable;
         depthStateDirty_ = true;
+        // Also affects whether a read-only version of depth-stencil should be bound, to allow sampling
+        renderTargetsDirty_ = true;
     }
 }
 
@@ -2493,6 +2500,10 @@ void Graphics::PrepareDraw()
         impl_->depthStencilView_ =
             depthStencil_ ? (ID3D11DepthStencilView*)depthStencil_->GetRenderTargetView() : impl_->defaultDepthStencilView_;
 
+        // If possible, bind a read-only depth stencil view to allow reading depth in shader
+        if (!depthWrite_ && depthStencil_ && depthStencil_->GetReadOnlyView())
+            impl_->depthStencilView_ = (ID3D11DepthStencilView*)depthStencil_->GetReadOnlyView();
+
         for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i)
             impl_->renderTargetViews_[i] =
                 renderTargets_[i] ? (ID3D11RenderTargetView*)renderTargets_[i]->GetRenderTargetView() : 0;

+ 6 - 0
Source/Urho3D/Graphics/Direct3D11/D3D11RenderSurface.cpp

@@ -37,6 +37,7 @@ namespace Urho3D
 RenderSurface::RenderSurface(Texture* parentTexture) :
     parentTexture_(parentTexture),
     renderTargetView_(0),
+    readOnlyView_(0),
     updateMode_(SURFACE_UPDATEVISIBLE),
     updateQueued_(false)
 {
@@ -124,6 +125,11 @@ void RenderSurface::Release()
 
         ((ID3D11View*)renderTargetView_)->Release();
         renderTargetView_ = 0;
+        if (readOnlyView_)
+        {
+            ((ID3D11View*)readOnlyView_)->Release();
+            readOnlyView_ = 0;
+        }
     }
 }
 

+ 5 - 0
Source/Urho3D/Graphics/Direct3D11/D3D11RenderSurface.h

@@ -63,6 +63,9 @@ public:
     /// Return Direct3D rendertarget or depth-stencil view.
     void* GetRenderTargetView() const { return renderTargetView_; }
 
+    /// Return Direct3D read-only depth-stencil view. May be null if not applicable
+    void* GetReadOnlyView() const { return readOnlyView_; }
+
     /// Return width.
     int GetWidth() const;
     /// Return height.
@@ -93,6 +96,8 @@ private:
     Texture* parentTexture_;
     /// Direct3D rendertarget or depth-stencil view.
     void* renderTargetView_;
+    /// Direct3D read-only depth-stencil view. Present only on depth-stencil surfaces.
+    void* readOnlyView_;
     /// Viewports.
     Vector<SharedPtr<Viewport> > viewports_;
     /// Linked color buffer.

+ 8 - 0
Source/Urho3D/Graphics/Direct3D11/D3D11Texture2D.cpp

@@ -531,6 +531,14 @@ bool Texture2D::Create()
             URHO3D_LOGERROR("Failed to create depth-stencil view for texture");
             return false;
         }
+
+        // Create also a read-only version of the view for simultaneous depth testing and sampling in shader
+        depthStencilViewDesc.Flags = D3D11_DSV_READ_ONLY_DEPTH;
+        graphics_->GetImpl()->GetDevice()->CreateDepthStencilView((ID3D11Resource*)object_, &depthStencilViewDesc,
+            (ID3D11DepthStencilView**)&renderSurface_->readOnlyView_);
+
+        if (!renderSurface_->readOnlyView_)
+            URHO3D_LOGWARNING("Failed to create read-only depth-stencil view for texture");
     }
 
     return true;

+ 18 - 0
Source/Urho3D/Graphics/DrawableEvents.h

@@ -37,10 +37,28 @@ URHO3D_EVENT(E_BONEHIERARCHYCREATED, BoneHierarchyCreated)
 URHO3D_EVENT(E_ANIMATIONTRIGGER, AnimationTrigger)
 {
     URHO3D_PARAM(P_NODE, Node);                    // Node pointer
+    URHO3D_PARAM(P_ANIMATION, Animation);          // Animation pointer
     URHO3D_PARAM(P_NAME, Name);                    // String
     URHO3D_PARAM(P_TIME, Time);                    // Float
     URHO3D_PARAM(P_DATA, Data);                    // User-defined data type
 }
+
+/// AnimatedModel animation finished or looped.
+URHO3D_EVENT(E_ANIMATIONFINISHED, AnimationFinished)
+{
+    URHO3D_PARAM(P_NODE, Node);                    // Node pointer
+    URHO3D_PARAM(P_ANIMATION, Animation);          // Animation pointer
+    URHO3D_PARAM(P_NAME, Name);                    // String
+    URHO3D_PARAM(P_LOOPED, Looped);                // Bool
+}
+
+/// Particle effect finished.
+URHO3D_EVENT(E_PARTICLEEFFECTFINISHED, ParticleEffectFinished)
+{
+    URHO3D_PARAM(P_NODE, Node);                    // Node pointer
+    URHO3D_PARAM(P_EFFECT, Effect);                // ParticleEffect pointer
+}
+
 /// Terrain geometry created.
 URHO3D_EVENT(E_TERRAINCREATED, TerrainCreated)
 {

+ 311 - 19
Source/Urho3D/Graphics/Material.cpp

@@ -36,6 +36,7 @@
 #include "../IO/VectorBuffer.h"
 #include "../Resource/ResourceCache.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/Scene.h"
 #include "../Scene/SceneEvents.h"
 #include "../Scene/ValueAnimation.h"
@@ -197,6 +198,63 @@ bool Material::BeginLoad(Deserializer& source)
     if (!graphics)
         return true;
 
+    String extension = GetExtension(source.GetName());
+
+    bool success = false;
+    if (extension == ".xml")
+    {
+        success = BeginLoadXML(source);
+        if (!success)
+            success = BeginLoadJSON(source);
+
+        if (success)
+            return true;
+    }
+    else // Load JSON file
+    {
+        success = BeginLoadJSON(source);
+        if (!success)
+            success = BeginLoadXML(source);
+
+        if (success)
+            return true;
+    }
+
+    // All loading failed
+    ResetToDefaults();
+    loadJSONFile_.Reset();
+    return false;
+}
+
+bool Material::EndLoad()
+{
+    // In headless mode, do not actually load the material, just return success
+    Graphics* graphics = GetSubsystem<Graphics>();
+    if (!graphics)
+        return true;
+
+    bool success = false;
+    if (loadXMLFile_)
+    {
+        // If async loading, get the techniques / textures which should be ready now
+        XMLElement rootElem = loadXMLFile_->GetRoot();
+        success = Load(rootElem);
+    }
+
+    if (loadJSONFile_)
+    {
+        JSONValue rootVal = loadJSONFile_->GetRoot();
+        success = Load(rootVal);
+    }
+
+    loadXMLFile_.Reset();
+    loadJSONFile_.Reset();
+    return success;
+}
+
+bool Material::BeginLoadXML(Deserializer& source)
+{
+    ResetToDefaults();
     loadXMLFile_ = new XMLFile(context_);
     if (loadXMLFile_->Load(source))
     {
@@ -239,34 +297,65 @@ bool Material::BeginLoad(Deserializer& source)
 
         return true;
     }
-    else
-    {
-        ResetToDefaults();
-        loadXMLFile_.Reset();
-        return false;
-    }
+
+    return false;
 }
 
-bool Material::EndLoad()
+bool Material::BeginLoadJSON(Deserializer& source)
 {
-    // In headless mode, do not actually load the material, just return success
-    Graphics* graphics = GetSubsystem<Graphics>();
-    if (!graphics)
-        return true;
+    // Attempt to load a JSON file
+    ResetToDefaults();
+    loadXMLFile_.Reset();
 
-    bool success = false;
-    if (loadXMLFile_)
+    // Attempt to load from JSON file instead
+    loadJSONFile_ = new JSONFile(context_);
+    if (loadJSONFile_->Load(source))
     {
-        // If async loading, get the techniques / textures which should be ready now
-        XMLElement rootElem = loadXMLFile_->GetRoot();
-        success = Load(rootElem);
+        // If async loading, scan the XML content beforehand for technique & texture resources
+        // and request them to also be loaded. Can not do anything else at this point
+        if (GetAsyncLoadState() == ASYNC_LOADING)
+        {
+            ResourceCache* cache = GetSubsystem<ResourceCache>();
+            const JSONValue& rootVal = loadJSONFile_->GetRoot();
+
+            JSONArray techniqueArray = rootVal.Get("techniques").GetArray();
+            for (unsigned i = 0; i < techniqueArray.Size(); i++)
+            {
+                const JSONValue& techVal = techniqueArray[i];
+                cache->BackgroundLoadResource<Technique>(techVal.Get("name").GetString(), true, this);
+            }
+
+            JSONObject textureObject = rootVal.Get("textures").GetObject();
+            for (JSONObject::ConstIterator it = textureObject.Begin(); it != textureObject.End(); it++)
+            {
+                String unitString = it->first_;
+                String name = it->second_.GetString();
+                // Detect cube maps by file extension: they are defined by an XML file
+                /// \todo Differentiate with 3D textures by actually reading the XML content
+                if (GetExtension(name) == ".xml")
+                {
+    #ifdef DESKTOP_GRAPHICS
+                    TextureUnit unit = TU_DIFFUSE;
+                    unit = ParseTextureUnitName(unitString);
+
+                    if (unit == TU_VOLUMEMAP)
+                        cache->BackgroundLoadResource<Texture3D>(name, true, this);
+                    else
+    #endif
+                        cache->BackgroundLoadResource<TextureCube>(name, true, this);
+                }
+                else
+                    cache->BackgroundLoadResource<Texture2D>(name, true, this);
+            }
+        }
+
+        // JSON material was successfully loaded
+        return true;
     }
 
-    loadXMLFile_.Reset();
-    return success;
+    return false;
 }
 
-
 bool Material::Save(Serializer& dest) const
 {
     SharedPtr<XMLFile> xml(new XMLFile(context_));
@@ -400,6 +489,137 @@ bool Material::Load(const XMLElement& source)
     return true;
 }
 
+bool Material::Load(const JSONValue& source)
+{
+    ResetToDefaults();
+
+    if (source.IsNull())
+    {
+        URHO3D_LOGERROR("Can not load material from null JSON element");
+        return false;
+    }
+
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    // Load techniques
+    JSONArray techniquesArray = source.Get("techniques").GetArray();
+    techniques_.Clear();
+    techniques_.Reserve(techniquesArray.Size());
+
+    for (unsigned i = 0; i < techniquesArray.Size(); i++)
+    {
+        const JSONValue& techVal = techniquesArray[i];
+        Technique* tech = cache->GetResource<Technique>(techVal.Get("name").GetString());
+        if (tech)
+        {
+            TechniqueEntry newTechnique;
+            newTechnique.technique_ = tech;
+            JSONValue qualityVal = techVal.Get("quality");
+            if (!qualityVal.IsNull())
+                newTechnique.qualityLevel_ = qualityVal.GetInt();
+            JSONValue lodDistanceVal = techVal.Get("loddistance");
+            if (!lodDistanceVal.IsNull())
+                newTechnique.lodDistance_ = lodDistanceVal.GetFloat();
+            techniques_.Push(newTechnique);
+        }
+    }
+
+    SortTechniques();
+
+    // Load textures
+    JSONObject textureObject = source.Get("textures").GetObject();
+    for (JSONObject::ConstIterator it = textureObject.Begin(); it != textureObject.End(); it++)
+    {
+        String textureUnit = it->first_;
+        String textureName = it->second_.GetString();
+
+        TextureUnit unit = TU_DIFFUSE;
+        unit = ParseTextureUnitName(textureUnit);
+
+        if (unit < MAX_TEXTURE_UNITS)
+        {
+            // Detect cube maps by file extension: they are defined by an XML file
+            /// \todo Differentiate with 3D textures by actually reading the XML content
+            if (GetExtension(textureName) == ".xml")
+            {
+#ifdef DESKTOP_GRAPHICS
+                if (unit == TU_VOLUMEMAP)
+                    SetTexture(unit, cache->GetResource<Texture3D>(textureName));
+                else
+#endif
+                    SetTexture(unit, cache->GetResource<TextureCube>(textureName));
+            }
+            else
+                SetTexture(unit, cache->GetResource<Texture2D>(textureName));
+        }
+    }
+
+    // Get shader parameters
+    batchedParameterUpdate_ = true;
+    JSONObject parameterObject = source.Get("shaderParameters").GetObject();
+
+    for (JSONObject::ConstIterator it = parameterObject.Begin(); it != parameterObject.End(); it++)
+    {
+        String name = it->first_;
+        SetShaderParameter(name, ParseShaderParameterValue(it->second_.GetString()));
+    }
+    batchedParameterUpdate_ = false;
+
+    // Load shader parameter animationss
+    JSONObject paramAnimationsObject = source.Get("shaderParameterAnimations").GetObject();
+    for (JSONObject::ConstIterator it = paramAnimationsObject.Begin(); it != paramAnimationsObject.End(); it++)
+    {
+        String name = it->first_;
+        JSONValue paramAnimVal = it->second_;
+
+        SharedPtr<ValueAnimation> animation(new ValueAnimation(context_));
+        if (!animation->LoadJSON(paramAnimVal))
+        {
+            URHO3D_LOGERROR("Could not load parameter animation");
+            return false;
+        }
+
+        String wrapModeString = paramAnimVal.Get("wrapmode").GetString();
+        WrapMode wrapMode = WM_LOOP;
+        for (int i = 0; i <= WM_CLAMP; ++i)
+        {
+            if (wrapModeString == wrapModeNames[i])
+            {
+                wrapMode = (WrapMode)i;
+                break;
+            }
+        }
+
+        float speed = paramAnimVal.Get("speed").GetFloat();
+        SetShaderParameterAnimation(name, animation, wrapMode, speed);
+    }
+
+    JSONValue cullVal = source.Get("cull");
+    if (!cullVal.IsNull())
+        SetCullMode((CullMode)GetStringListIndex(cullVal.GetString().CString(), cullModeNames, CULL_CCW));
+
+    JSONValue shadowCullVal = source.Get("shadowcull");
+    if (!shadowCullVal.IsNull())
+        SetShadowCullMode((CullMode)GetStringListIndex(shadowCullVal.GetString().CString(), cullModeNames, CULL_CCW));
+
+    JSONValue fillVal = source.Get("fill");
+    if (!fillVal.IsNull())
+        SetFillMode((FillMode)GetStringListIndex(fillVal.GetString().CString(), fillModeNames, FILL_SOLID));
+
+    JSONValue depthBiasVal = source.Get("depthbias");
+    if (!depthBiasVal.IsNull())
+        SetDepthBias(BiasParameters(depthBiasVal.Get("constant").GetFloat(), depthBiasVal.Get("slopescaled").GetFloat()));
+
+    JSONValue renderOrderVal = source.Get("renderorder");
+    if (!renderOrderVal.IsNull())
+        SetRenderOrder((unsigned char)renderOrderVal.Get("value").GetUInt());
+
+    RefreshShaderParameterHash();
+    RefreshMemoryUse();
+    CheckOcclusion();
+    return true;
+}
+
 bool Material::Save(XMLElement& dest) const
 {
     if (dest.IsNull())
@@ -479,6 +699,78 @@ bool Material::Save(XMLElement& dest) const
     return true;
 }
 
+bool Material::Save(JSONValue& dest) const
+{
+    // Write techniques
+    JSONArray techniquesArray;
+    techniquesArray.Reserve(techniques_.Size());
+    for (unsigned i = 0; i < techniques_.Size(); ++i)
+    {
+        const TechniqueEntry& entry = techniques_[i];
+        if (!entry.technique_)
+            continue;
+
+        JSONValue techniqueVal;
+        techniqueVal.Set("name", entry.technique_->GetName());
+        techniqueVal.Set("quality", (int) entry.qualityLevel_);
+        techniqueVal.Set("loddistance", entry.lodDistance_);
+        techniquesArray.Push(techniqueVal);
+    }
+    dest.Set("techniques", techniquesArray);
+
+    // Write texture units
+    JSONValue texturesValue;
+    for (unsigned j = 0; j < MAX_TEXTURE_UNITS; ++j)
+    {
+        Texture* texture = GetTexture((TextureUnit)j);
+        if (texture)
+            texturesValue.Set(textureUnitNames[j], texture->GetName());
+    }
+    dest.Set("textures", texturesValue);
+
+    // Write shader parameters
+    JSONValue shaderParamsVal;
+    for (HashMap<StringHash, MaterialShaderParameter>::ConstIterator j = shaderParameters_.Begin();
+         j != shaderParameters_.End(); ++j)
+    {
+        shaderParamsVal.Set(j->second_.name_, j->second_.value_.ToString());
+    }
+    dest.Set("shaderParameters", shaderParamsVal);
+
+    // Write shader parameter animations
+    JSONValue shaderParamAnimationsVal;
+    for (HashMap<StringHash, SharedPtr<ShaderParameterAnimationInfo> >::ConstIterator j = shaderParameterAnimationInfos_.Begin();
+         j != shaderParameterAnimationInfos_.End(); ++j)
+    {
+        ShaderParameterAnimationInfo* info = j->second_;
+        JSONValue paramAnimationVal;
+        if (!info->GetAnimation()->SaveJSON(paramAnimationVal))
+            return false;
+
+        paramAnimationVal.Set("wrapmode", wrapModeNames[info->GetWrapMode()]);
+        paramAnimationVal.Set("speed", info->GetSpeed());
+        shaderParamAnimationsVal.Set(info->GetName(), paramAnimationVal);
+    }
+    dest.Set("shaderParameterAnimations", shaderParamAnimationsVal);
+
+    // Write culling modes
+    dest.Set("cull", cullModeNames[cullMode_]);
+    dest.Set("shadowcull", cullModeNames[shadowCullMode_]);
+
+    // Write fill mode
+    dest.Set("fill", fillModeNames[fillMode_]);
+
+    // Write depth bias
+    JSONValue depthBiasValue;
+    depthBiasValue.Set("constant", depthBias_.constantBias_);
+    depthBiasValue.Set("slopescaled", depthBias_.slopeScaledBias_);
+
+    // Write render order
+    dest.Set("renderorder", (unsigned) renderOrder_);
+
+    return true;
+}
+
 void Material::SetNumTechniques(unsigned num)
 {
     if (!num)

+ 14 - 0
Source/Urho3D/Graphics/Material.h

@@ -39,6 +39,7 @@ class Texture;
 class Texture2D;
 class TextureCube;
 class ValueAnimationInfo;
+class JSONFile;
 
 static const unsigned char DEFAULT_RENDER_ORDER = 128;
 
@@ -123,6 +124,12 @@ public:
     bool Load(const XMLElement& source);
     /// Save to an XML element. Return true if successful.
     bool Save(XMLElement& dest) const;
+
+    /// Load from a JSON value. Return true if successful.
+    bool Load(const JSONValue& source);
+    /// Save to a JSON value. Return true if successful.
+    bool Save(JSONValue& dest) const;
+
     /// Set number of techniques.
     void SetNumTechniques(unsigned num);
     /// Set technique.
@@ -231,6 +238,11 @@ public:
     static Variant ParseShaderParameterValue(const String& value);
 
 private:
+    /// Helper function for loading JSON files
+    bool BeginLoadJSON(Deserializer& source);
+    /// Helper function for loading XML files
+    bool BeginLoadXML(Deserializer& source);
+
     /// Re-evaluate occlusion rendering.
     void CheckOcclusion();
     /// Reset to defaults.
@@ -278,6 +290,8 @@ private:
     bool batchedParameterUpdate_;
     /// XML file used while loading.
     SharedPtr<XMLFile> loadXMLFile_;
+    /// JSON file used while loading.
+    SharedPtr<JSONFile> loadJSONFile_;
     /// Associated scene for shader parameter animation updates.
     WeakPtr<Scene> scene_;
 };

+ 4 - 0
Source/Urho3D/Graphics/OpenGL/OGLGraphics.cpp

@@ -642,6 +642,10 @@ bool Graphics::BeginFrame()
             SetMode(width, height);
     }
 
+    // Re-enable depth test and depth func in case a third party program has modified it
+    glEnable(GL_DEPTH_TEST);
+    glDepthFunc(glCmpFunc[depthTestMode_]);
+
     // Set default rendertarget and depth buffer
     ResetRenderTargets();
 

+ 33 - 1
Source/Urho3D/Graphics/ParticleEmitter.cpp

@@ -24,6 +24,7 @@
 
 #include "../Core/Context.h"
 #include "../Core/Profiler.h"
+#include "../Graphics/DrawableEvents.h"
 #include "../Graphics/ParticleEffect.h"
 #include "../Graphics/ParticleEmitter.h"
 #include "../Resource/ResourceCache.h"
@@ -48,7 +49,8 @@ ParticleEmitter::ParticleEmitter(Context* context) :
     lastUpdateFrameNumber_(M_MAX_UNSIGNED),
     emitting_(true),
     needUpdate_(false),
-    serializeParticles_(true)
+    serializeParticles_(true),
+    sendFinishEvent_(true)
 {
     SetNumParticles(DEFAULT_NUM_PARTICLES);
 }
@@ -127,6 +129,7 @@ void ParticleEmitter::Update(const FrameInfo& frame)
         if (inactiveTime && periodTimer_ >= inactiveTime)
         {
             emitting_ = true;
+            sendFinishEvent_ = true;
             periodTimer_ -= inactiveTime;
         }
         // If emitter has an indefinite stop interval, keep period timer reset to allow restarting emission in the editor
@@ -292,6 +295,7 @@ void ParticleEmitter::SetEmitting(bool enable)
     if (enable != emitting_)
     {
         emitting_ = enable;
+        sendFinishEvent_ = enable;
         periodTimer_ = 0.0f;
         // Note: network update does not need to be marked as this is a file only attribute
     }
@@ -524,6 +528,34 @@ void ParticleEmitter::HandleScenePostUpdate(StringHash eventType, VariantMap& ev
         needUpdate_ = true;
         MarkForUpdate();
     }
+
+    if (node_ && !emitting_ && sendFinishEvent_)
+    {
+        // Send finished event only once all billboards are gone
+        bool hasEnabledBillboards = false;
+
+        for (unsigned i = 0; i < billboards_.Size(); ++i)
+        {
+            if (billboards_[i].enabled_)
+            {
+                hasEnabledBillboards = true;
+                break;
+            }
+        }
+
+        if (!hasEnabledBillboards)
+        {
+            sendFinishEvent_ = false;
+
+            using namespace ParticleEffectFinished;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_NODE] = node_;
+            eventData[P_EFFECT] = effect_;
+
+            node_->SendEvent(E_PARTICLEEFFECTFINISHED, eventData);
+        }
+    }
 }
 
 void ParticleEmitter::HandleEffectReloadFinished(StringHash eventType, VariantMap& eventData)

+ 2 - 0
Source/Urho3D/Graphics/ParticleEmitter.h

@@ -141,6 +141,8 @@ private:
     bool needUpdate_;
     /// Serialize particles flag.
     bool serializeParticles_;
+    /// Ready to send effect finish event flag.
+    bool sendFinishEvent_;
 };
 
 }

+ 0 - 3
Source/Urho3D/Graphics/Renderer.cpp

@@ -431,9 +431,6 @@ void Renderer::SetShadowMapFilter(Object* instance, ShadowMapFilter functionPtr)
 
 void Renderer::SetReuseShadowMaps(bool enable)
 {
-    if (enable == reuseShadowMaps_)
-        return;
-
     reuseShadowMaps_ = enable;
 }
 

+ 1 - 1
Source/Urho3D/Graphics/View.cpp

@@ -779,7 +779,7 @@ void View::SetGBufferShaderParameters(const IntVector2& texSize, const IntRect&
 
     float invSizeX = 1.0f / texWidth;
     float invSizeY = 1.0f / texHeight;
-    graphics_->SetShaderParameter(PSP_GBUFFERINVSIZE, Vector4(invSizeX, invSizeY, 0.0f, 0.0f));
+    graphics_->SetShaderParameter(PSP_GBUFFERINVSIZE, Vector2(invSizeX, invSizeY));
 }
 
 void View::GetDrawables()

+ 8 - 2
Source/Urho3D/IO/FileSystem.cpp

@@ -32,7 +32,11 @@
 #include "../IO/IOEvents.h"
 #include "../IO/Log.h"
 
+#ifndef MINI_URHO
 #include <SDL/SDL_filesystem.h>
+#else
+#include <stdio.h>
+#endif
 
 #include <sys/stat.h>
 
@@ -79,12 +83,12 @@ namespace Urho3D
 
 int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* context)
 {
-#if !defined(NO_POPEN)
+#if !defined(NO_POPEN) && !defined(MINI_URHO)
     if (!redirectToLog)
 #endif
         return system(commandLine.CString());
 
-#if !defined(NO_POPEN)
+#if !defined(NO_POPEN) && !defined(MINI_URHO)
     // Get a platform-agnostic temporary file name for stderr redirection
     String stderrFilename;
     String adjustedCommandLine(commandLine);
@@ -748,6 +752,7 @@ String FileSystem::GetUserDocumentsDir() const
 String FileSystem::GetAppPreferencesDir(const String& org, const String& app) const
 {
     String dir;
+#ifndef MINI_URHO
     char* prefPath = SDL_GetPrefPath(org.CString(), app.CString());
     if (prefPath)
     {
@@ -755,6 +760,7 @@ String FileSystem::GetAppPreferencesDir(const String& org, const String& app) co
         SDL_free(prefPath);
     }
     else
+#endif
         URHO3D_LOGWARNING("Could not get application preferences directory");
 
     return dir;

+ 1 - 1
Source/Urho3D/Input/Input.cpp

@@ -2039,7 +2039,7 @@ void Input::HandleScreenJoystickTouch(StringHash eventType, VariantMap& eventDat
 
     // Only interested in events from screen joystick(s)
     TouchState& state = touches_[eventData[P_TOUCHID].GetInt()];
-    IntVector2 position(state.position_.x_, state.position_.y_);
+    IntVector2 position(int(state.position_.x_ / GetSubsystem<UI>()->GetScale()), int(state.position_.y_ / GetSubsystem<UI>()->GetScale()));
     UIElement* element = eventType == E_TOUCHBEGIN ? GetSubsystem<UI>()->GetElementAt(position) : state.touchedElement_;
     if (!element)
         return;

+ 19 - 0
Source/Urho3D/LuaScript/pkgs/Physics/PhysicsWorld.pkg

@@ -29,6 +29,8 @@ class PhysicsWorld : public Component
     tolua_outside const PODVector<PhysicsRaycastResult>& PhysicsWorldRaycast @ Raycast(const Ray& ray, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
     // void RaycastSingle(PhysicsRaycastResult& result, const Ray& ray, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
     tolua_outside PhysicsRaycastResult PhysicsWorldRaycastSingle @ RaycastSingle(const Ray& ray, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
+    // void RaycastSingleSegmented(PhysicsRaycastResult& result, const Ray& ray, float maxDistance, float segmentDistance, unsigned collisionMask = M_MAX_UNSIGNED);
+    tolua_outside PhysicsRaycastResult PhysicsWorldRaycastSingleSegmented @ RaycastSingleSegmented(const Ray& ray, float maxDistance, float segmentDistance, unsigned collisionMask = M_MAX_UNSIGNED);
     // void SphereCast(PhysicsRaycastResult& result, const Ray& ray, float radius, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
     tolua_outside PhysicsRaycastResult PhysicsWorldSphereCast @ SphereCast(const Ray& ray, float radius, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
     // void ConvexCast(PhysicsRaycastResult& result, CollisionShape* shape, const Vector3& startPos, const Quaternion& startRot, const Vector3& endPos, const Quaternion& endRot, unsigned collisionMask = M_MAX_UNSIGNED);
@@ -40,6 +42,8 @@ class PhysicsWorld : public Component
     tolua_outside const PODVector<RigidBody*>& PhysicsWorldGetRigidBodiesBox @ GetRigidBodies(const BoundingBox& box, unsigned collisionMask = M_MAX_UNSIGNED);
     // void GetRigidBodies(PODVector<RigidBody*>& result, const RigidBody* body);
     tolua_outside const PODVector<RigidBody*>& PhysicsWorldGetRigidBodiesBody @ GetRigidBodies(const RigidBody* body);
+    // void GetCollidingBodies(PODVector<RigidBody*>& result, const RigidBody* body);
+    tolua_outside const PODVector<RigidBody*>& PhysicsWorldGetCollidingBodies @ GetCollidingBodies(const RigidBody* body);
 
     void DrawDebugGeometry(bool depthTest);
     void RemoveCachedGeometry(Model* model);
@@ -81,6 +85,13 @@ static PhysicsRaycastResult PhysicsWorldRaycastSingle(PhysicsWorld* physicsWorld
     return result;
 }
 
+static PhysicsRaycastResult PhysicsWorldRaycastSingleSegmented(PhysicsWorld* physicsWorld, const Ray& ray, float maxDistance, float segmentDistance, unsigned collisionMask = M_MAX_UNSIGNED)
+{
+    PhysicsRaycastResult result;
+    physicsWorld->RaycastSingleSegmented(result, ray, maxDistance, segmentDistance, collisionMask);
+    return result;
+}
+
 PhysicsRaycastResult PhysicsWorldSphereCast(PhysicsWorld* physicsWorld, const Ray& ray, float radius, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED)
 {
     PhysicsRaycastResult result;
@@ -119,4 +130,12 @@ static const PODVector<RigidBody*>& PhysicsWorldGetRigidBodiesBody(PhysicsWorld*
     return result;
 }
 
+static const PODVector<RigidBody*>& PhysicsWorldGetCollidingBodies(PhysicsWorld* physicsWorld, const RigidBody* body)
+{
+    static PODVector<RigidBody*> result;
+    result.Clear();
+    physicsWorld->GetCollidingBodies(result, body);
+    return result;
+}
+
 $}

+ 7 - 0
Source/Urho3D/LuaScript/pkgs/Scene/Node.pkg

@@ -23,6 +23,7 @@ class Node : public Animatable
     virtual ~Node();
 
     tolua_outside bool NodeSaveXML @ SaveXML(File* dest, const String indentation = "\t") const;
+    tolua_outside bool NodeSaveJSON @ SaveJSON(File* dest, const String indentation = "\t") const;
     void SetName(const String name);
 
     void SetPosition(const Vector3& position);
@@ -186,6 +187,7 @@ class Node : public Animatable
 
     bool Load(Deserializer& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED);
     bool LoadXML(const XMLElement& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED);
+    bool LoadJSON(const JSONValue& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false, CreateMode mode = REPLICATED);
 
     Node* CreateChild(unsigned id, CreateMode mode);
     void AddComponent(Component* component, unsigned id, CreateMode mode);
@@ -241,6 +243,11 @@ static bool NodeSaveXML(const Node* node, File* file, const String& indentation)
     return file ? node->SaveXML(*file, indentation) : false;
 }
 
+static bool NodeSaveJSON(const Node* node, File* file, const String& indentation)
+{
+    return file ? node->SaveJSON(*file, indentation) : false;
+}
+
 #define TOLUA_DISABLE_tolua_SceneLuaAPI_Node_CreateScriptObject00
 
 static int tolua_SceneLuaAPI_Node_CreateScriptObject00(lua_State* tolua_S)

+ 28 - 0
Source/Urho3D/LuaScript/pkgs/Scene/Scene.pkg

@@ -25,6 +25,10 @@ class Scene : public Node
     tolua_outside bool SceneSaveXML @ SaveXML(File* dest, const String indentation = "\t") const;
     tolua_outside bool SceneLoadXML @ LoadXML(const String fileName);
     tolua_outside bool SceneSaveXML @ SaveXML(const String fileName, const String indentation = "\t") const;
+    tolua_outside bool SceneLoadJSON @ LoadJSON(File* source);
+    tolua_outside bool SceneSaveJSON @ SaveJSON(File* dest, const String indentation = "\t") const;
+    tolua_outside bool SceneLoadJSON @ LoadJSON(const String fileName);
+    tolua_outside bool SceneSaveJSON @ SaveJSON(const String fileName, const String indentation = "\t") const;
     tolua_outside Node* SceneInstantiate @ Instantiate(File* source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
     tolua_outside Node* SceneInstantiate @ Instantiate(const String fileName, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
     tolua_outside Node* SceneInstantiateXML @ InstantiateXML(File* source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
@@ -152,6 +156,30 @@ static bool SceneSaveXML(const Scene* scene, const String& fileName, const Strin
     return scene->SaveXML(file, indentation);
 }
 
+static bool SceneLoadJSON(Scene* scene, File* file)
+{
+    return file ? scene->LoadJSON(*file) : false;
+}
+
+static bool SceneSaveJSON(const Scene* scene, File* file, const String& indentation)
+{
+    return file ? scene->SaveJSON(*file, indentation) : false;
+}
+
+static bool SceneLoadJSON(Scene* scene, const String& fileName)
+{
+    File file(scene->GetContext(), fileName, FILE_READ);
+    return file.IsOpen() && scene->LoadJSON(file);
+}
+
+static bool SceneSaveJSON(const Scene* scene, const String& fileName, const String& indentation)
+{
+    File file(scene->GetContext(), fileName, FILE_WRITE);
+    if (!file.IsOpen())
+        return false;
+    return scene->SaveJSON(file, indentation);
+}
+
 static bool SceneLoadAsync(Scene* scene, const String& fileName, LoadMode mode)
 {
     SharedPtr<File> file(new File(scene->GetContext(), fileName, FILE_READ));

+ 5 - 0
Source/Urho3D/LuaScript/pkgs/UI/UI.pkg

@@ -23,6 +23,9 @@ class UI : public Object
     void SetUseScreenKeyboard(bool enable);
     void SetUseMutableGlyphs(bool enable);
     void SetForceAutoHint(bool enable);
+    void SetScale(float scale);
+    void SetWidth(float size);
+    void SetHeight(float size);
 
     UIElement* GetRoot() const;
     UIElement* GetRootModalElement() const;
@@ -47,6 +50,7 @@ class UI : public Object
     bool GetForceAutoHint() const;
     bool HasModalElement() const;
     bool IsDragging() const;
+    float GetScale() const;
 
     tolua_readonly tolua_property__get_set UIElement* root;
     tolua_readonly tolua_property__get_set UIElement* rootModalElement;
@@ -67,6 +71,7 @@ class UI : public Object
     tolua_property__get_set bool useMutableGlyphs;
     tolua_property__get_set bool forceAutoHint;
     tolua_readonly tolua_property__has_set bool modalElement;
+    tolua_property__get_set float scale;
 };
 
 UI* GetUI();

+ 6 - 0
Source/Urho3D/Navigation/CrowdAgent.cpp

@@ -290,6 +290,9 @@ int CrowdAgent::AddAgentToCrowd(bool force)
             map[P_POSITION] = GetPosition();
             map[P_VELOCITY] = GetActualVelocity();
             crowdManager_->SendEvent(E_CROWD_AGENT_FAILURE, map);
+            Node* node = GetNode();
+            if (node)
+                node->SendEvent(E_CROWD_AGENT_NODE_FAILURE, map);
 
             // Reevaluate states as handling of event may have resulted in changes
             previousAgentState_ = GetAgentState();
@@ -526,6 +529,7 @@ void CrowdAgent::OnCrowdUpdate(dtCrowdAgent* ag, float dt)
             map[P_ARRIVED] = HasArrived();
             map[P_TIMESTEP] = dt;
             crowdManager_->SendEvent(E_CROWD_AGENT_REPOSITION, map);
+            node_->SendEvent(E_CROWD_AGENT_NODE_REPOSITION, map);
 
             if (updateNodePosition_)
             {
@@ -550,6 +554,7 @@ void CrowdAgent::OnCrowdUpdate(dtCrowdAgent* ag, float dt)
             map[P_POSITION] = newPos;
             map[P_VELOCITY] = newVel;
             crowdManager_->SendEvent(E_CROWD_AGENT_STATE_CHANGED, map);
+            node_->SendEvent(E_CROWD_AGENT_NODE_STATE_CHANGED, map);
 
             // Send a failure event if either state is a failed status
             if (newAgentState == CA_STATE_INVALID || newTargetState == CA_TARGET_FAILED)
@@ -562,6 +567,7 @@ void CrowdAgent::OnCrowdUpdate(dtCrowdAgent* ag, float dt)
                 map[P_POSITION] = newPos;
                 map[P_VELOCITY] = newVel;
                 crowdManager_->SendEvent(E_CROWD_AGENT_FAILURE, map);
+                node_->SendEvent(E_CROWD_AGENT_NODE_FAILURE, map);
             }
 
             // State may have been altered during the handling of the event

+ 43 - 0
Source/Urho3D/Navigation/NavigationEvents.h

@@ -53,6 +53,16 @@ URHO3D_EVENT(E_CROWD_AGENT_FORMATION, CrowdAgentFormation)
     URHO3D_PARAM(P_POSITION, Position); // Vector3 [in/out]
 }
 
+/// Crowd agent formation specific to a node.
+URHO3D_EVENT(E_CROWD_AGENT_NODE_FORMATION, CrowdAgentNodeFormation)
+{
+    URHO3D_PARAM(P_NODE, Node); // Node pointer
+    URHO3D_PARAM(P_CROWD_AGENT, CrowdAgent); // CrowdAgent pointer
+    URHO3D_PARAM(P_INDEX, Index); // unsigned
+    URHO3D_PARAM(P_SIZE, Size); // unsigned
+    URHO3D_PARAM(P_POSITION, Position); // Vector3 [in/out]
+}
+
 /// Crowd agent has been repositioned.
 URHO3D_EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
 {
@@ -64,6 +74,17 @@ URHO3D_EVENT(E_CROWD_AGENT_REPOSITION, CrowdAgentReposition)
     URHO3D_PARAM(P_TIMESTEP, TimeStep); // float
 }
 
+/// Crowd agent has been repositioned, specific to a node
+URHO3D_EVENT(E_CROWD_AGENT_NODE_REPOSITION, CrowdAgentNodeReposition)
+{
+    URHO3D_PARAM(P_NODE, Node); // Node pointer
+    URHO3D_PARAM(P_CROWD_AGENT, CrowdAgent); // CrowdAgent pointer
+    URHO3D_PARAM(P_POSITION, Position); // Vector3
+    URHO3D_PARAM(P_VELOCITY, Velocity); // Vector3
+    URHO3D_PARAM(P_ARRIVED, Arrived); // bool
+    URHO3D_PARAM(P_TIMESTEP, TimeStep); // float
+}
+
 /// Crowd agent's internal state has become invalidated. This is a special case of CrowdAgentStateChanged event.
 URHO3D_EVENT(E_CROWD_AGENT_FAILURE, CrowdAgentFailure)
 {
@@ -75,6 +96,17 @@ URHO3D_EVENT(E_CROWD_AGENT_FAILURE, CrowdAgentFailure)
     URHO3D_PARAM(P_CROWD_TARGET_STATE, CrowdTargetState); // int
 }
 
+/// Crowd agent's internal state has become invalidated. This is a special case of CrowdAgentStateChanged event.
+URHO3D_EVENT(E_CROWD_AGENT_NODE_FAILURE, CrowdAgentNodeFailure)
+{
+    URHO3D_PARAM(P_NODE, Node); // Node pointer
+    URHO3D_PARAM(P_CROWD_AGENT, CrowdAgent); // CrowdAgent pointer
+    URHO3D_PARAM(P_POSITION, Position); // Vector3
+    URHO3D_PARAM(P_VELOCITY, Velocity); // Vector3
+    URHO3D_PARAM(P_CROWD_AGENT_STATE, CrowdAgentState); // int
+    URHO3D_PARAM(P_CROWD_TARGET_STATE, CrowdTargetState); // int
+}
+
 /// Crowd agent's state has been changed.
 URHO3D_EVENT(E_CROWD_AGENT_STATE_CHANGED, CrowdAgentStateChanged)
 {
@@ -86,6 +118,17 @@ URHO3D_EVENT(E_CROWD_AGENT_STATE_CHANGED, CrowdAgentStateChanged)
     URHO3D_PARAM(P_CROWD_TARGET_STATE, CrowdTargetState); // int
 }
 
+/// Crowd agent's state has been changed.
+URHO3D_EVENT(E_CROWD_AGENT_NODE_STATE_CHANGED, CrowdAgentNodeStateChanged)
+{
+    URHO3D_PARAM(P_NODE, Node); // Node pointer
+    URHO3D_PARAM(P_CROWD_AGENT, CrowdAgent); // CrowdAgent pointer
+    URHO3D_PARAM(P_POSITION, Position); // Vector3
+    URHO3D_PARAM(P_VELOCITY, Velocity); // Vector3
+    URHO3D_PARAM(P_CROWD_AGENT_STATE, CrowdAgentState); // int
+    URHO3D_PARAM(P_CROWD_TARGET_STATE, CrowdTargetState); // int
+}
+
 /// Addition of obstacle to dynamic navigation mesh.
 URHO3D_EVENT(E_NAVIGATION_OBSTACLE_ADDED, NavigationObstacleAdded)
 {

+ 55 - 0
Source/Urho3D/Navigation/NavigationMesh.cpp

@@ -570,6 +570,61 @@ void NavigationMesh::FindPath(PODVector<Vector3>& dest, const Vector3& start, co
         dest.Push(transform * pathData_->pathPoints_[i]);
 }
 
+void NavigationMesh::FindPath(PODVector<NavigationPathPoint>& dest, const Vector3& start, const Vector3& end, const Vector3& extents,
+    const dtQueryFilter* filter)
+{
+    URHO3D_PROFILE(FindPath);
+    dest.Clear();
+
+
+    if (!InitializeQuery())
+        return;
+
+    // Navigation data is in local space. Transform path points from world to local
+    const Matrix3x4& transform = node_->GetWorldTransform();
+    Matrix3x4 inverse = transform.Inverse();
+
+    Vector3 localStart = inverse * start;
+    Vector3 localEnd = inverse * end;
+
+    const dtQueryFilter* queryFilter = filter ? filter : queryFilter_;
+    dtPolyRef startRef;
+    dtPolyRef endRef;
+    navMeshQuery_->findNearestPoly(&localStart.x_, &extents.x_, queryFilter, &startRef, 0);
+    navMeshQuery_->findNearestPoly(&localEnd.x_, &extents.x_, queryFilter, &endRef, 0);
+
+    if (!startRef || !endRef)
+        return;
+
+    int numPolys = 0;
+    int numPathPoints = 0;
+
+    navMeshQuery_->findPath(startRef, endRef, &localStart.x_, &localEnd.x_, queryFilter, pathData_->polys_, &numPolys,
+        MAX_POLYS);
+    if (!numPolys)
+        return;
+
+    Vector3 actualLocalEnd = localEnd;
+
+    // If full path was not found, clamp end point to the end polygon
+    if (pathData_->polys_[numPolys - 1] != endRef)
+        navMeshQuery_->closestPointOnPoly(pathData_->polys_[numPolys - 1], &localEnd.x_, &actualLocalEnd.x_, 0);
+
+    navMeshQuery_->findStraightPath(&localStart.x_, &actualLocalEnd.x_, pathData_->polys_, numPolys,
+        &pathData_->pathPoints_[0].x_, pathData_->pathFlags_, pathData_->pathPolys_, &numPathPoints, MAX_POLYS);
+
+    // Transform path result back to world space
+    for (int i = 0; i < numPathPoints; ++i)
+    {
+        NavigationPathPoint pt;
+        pt.position_ = transform * pathData_->pathPoints_[i];
+        pt.flag_ = (NavigationPathPointFlag) pathData_->pathFlags_[i];
+        pt.areaID_ = pathData_->pathAreras_[i];
+
+        dest.Push(pt);
+    }
+}
+
 Vector3 NavigationMesh::GetRandomPoint(const dtQueryFilter* filter, dtPolyRef* randomRef)
 {
     if (!InitializeQuery())

+ 23 - 0
Source/Urho3D/Navigation/NavigationMesh.h

@@ -63,6 +63,26 @@ struct NavigationGeometryInfo
     Matrix3x4 transform_;
     /// Bounding box relative to the navigation mesh root node.
     BoundingBox boundingBox_;
+
+};
+
+/// A flag representing the type of path point- none, the start of a path segment, the end of one, or an off-mesh connection.
+enum NavigationPathPointFlag
+{
+    NAVPATHFLAG_NONE = 0,
+    NAVPATHFLAG_START = 0x01,
+    NAVPATHFLAG_END = 0x02,
+    NAVPATHFLAG_OFF_MESH = 0x04
+};
+
+struct URHO3D_API NavigationPathPoint
+{
+    /// World-space position of the path point
+    Vector3 position_;
+    /// Detour flag
+    NavigationPathPointFlag flag_;
+    /// Detour area ID
+    unsigned char areaID_;
 };
 
 /// Navigation mesh component. Collects the navigation geometry from child nodes with the Navigable component and responds to path queries.
@@ -126,6 +146,9 @@ public:
     /// Find a path between world space points. Return non-empty list of points if successful. Extents specifies how far off the navigation mesh the points can be.
     void FindPath(PODVector<Vector3>& dest, const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE,
         const dtQueryFilter* filter = 0);
+    /// Find a path between world space points. Return non-empty list of navigation path points if successful. Extents specifies how far off the navigation mesh the points can be.
+    void FindPath(PODVector<NavigationPathPoint>& dest, const Vector3& start, const Vector3& end, const Vector3& extents = Vector3::ONE,
+        const dtQueryFilter* filter = 0);
     /// Return a random point on the navigation mesh.
     Vector3 GetRandomPoint(const dtQueryFilter* filter = 0, dtPolyRef* randomRef = 0);
     /// Return a random point on the navigation mesh within a circle. The circle radius is only a guideline and in practice the returned point may be further away.

+ 73 - 0
Source/Urho3D/Physics/PhysicsWorld.cpp

@@ -129,6 +129,7 @@ PhysicsWorld::PhysicsWorld(Context* context) :
     interpolation_(true),
     internalEdge_(true),
     applyingTransforms_(false),
+    simulating_(false),
     debugRenderer_(0),
     debugMode_(btIDebugDraw::DBG_DrawWireframe | btIDebugDraw::DBG_DrawConstraints | btIDebugDraw::DBG_DrawConstraintLimits)
 {
@@ -249,6 +250,7 @@ void PhysicsWorld::Update(float timeStep)
         maxSubSteps = Min(maxSubSteps, maxSubSteps_);
 
     delayedWorldTransforms_.Clear();
+    simulating_ = true;
 
     if (interpolation_)
         world_->stepSimulation(timeStep, maxSubSteps, internalTimeStep);
@@ -263,6 +265,8 @@ void PhysicsWorld::Update(float timeStep)
         }
     }
 
+    simulating_ = false;
+
     // Apply delayed (parented) world transforms now
     while (!delayedWorldTransforms_.Empty())
     {
@@ -402,6 +406,52 @@ void PhysicsWorld::RaycastSingle(PhysicsRaycastResult& result, const Ray& ray, f
     }
 }
 
+void PhysicsWorld::RaycastSingleSegmented(PhysicsRaycastResult& result, const Ray& ray, float maxDistance, float segmentDistance, unsigned collisionMask)
+{
+    URHO3D_PROFILE(PhysicsRaycastSingleSegmented);
+
+    if (maxDistance >= M_INFINITY)
+        URHO3D_LOGWARNING("Infinite maxDistance in physics raycast is not supported");
+
+    btVector3 start = ToBtVector3(ray.origin_);
+    btVector3 end;
+    btVector3 direction = ToBtVector3(ray.direction_);
+    float distance;
+
+    for (float remainingDistance = maxDistance; remainingDistance > 0; remainingDistance -= segmentDistance)
+    {
+        distance = Min(remainingDistance, segmentDistance);
+
+        end = start + distance * direction;
+
+        btCollisionWorld::ClosestRayResultCallback rayCallback(start, end);
+        rayCallback.m_collisionFilterGroup = (short)0xffff;
+        rayCallback.m_collisionFilterMask = (short)collisionMask;
+
+        world_->rayTest(rayCallback.m_rayFromWorld, rayCallback.m_rayToWorld, rayCallback);
+
+        if (rayCallback.hasHit())
+        {
+            result.position_ = ToVector3(rayCallback.m_hitPointWorld);
+            result.normal_ = ToVector3(rayCallback.m_hitNormalWorld);
+            result.distance_ = (result.position_ - ray.origin_).Length();
+            result.body_ = static_cast<RigidBody*>(rayCallback.m_collisionObject->getUserPointer());
+
+            // No need to cast the rest of the segments
+            return;
+        }
+
+        // Use the end position as the new start position
+        start = end;
+    }
+
+    // Didn't hit anything
+    result.position_ = Vector3::ZERO;
+    result.normal_ = Vector3::ZERO;
+    result.distance_ = M_INFINITY;
+    result.body_ = 0;
+}
+
 void PhysicsWorld::SphereCast(PhysicsRaycastResult& result, const Ray& ray, float radius, float maxDistance, unsigned collisionMask)
 {
     URHO3D_PROFILE(PhysicsSphereCast);
@@ -582,6 +632,29 @@ void PhysicsWorld::GetRigidBodies(PODVector<RigidBody*>& result, const BoundingB
 }
 
 void PhysicsWorld::GetRigidBodies(PODVector<RigidBody*>& result, const RigidBody* body)
+{
+    URHO3D_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++)
+    {
+        if (result[i] == body)
+        {
+            result.Erase(i);
+            break;
+        }
+    }
+}
+
+void PhysicsWorld::GetCollidingBodies(PODVector<RigidBody*>& result, const RigidBody* body)
 {
     URHO3D_PROFILE(GetCollidingBodies);
 

+ 12 - 3
Source/Urho3D/Physics/PhysicsWorld.h

@@ -160,6 +160,8 @@ public:
         (PODVector<PhysicsRaycastResult>& result, const Ray& ray, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
     /// Perform a physics world raycast and return the closest hit.
     void RaycastSingle(PhysicsRaycastResult& result, const Ray& ray, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
+    /// Perform a physics world segmented raycast and return the closest hit. Useful for big scenes with many bodies.
+    void RaycastSingleSegmented(PhysicsRaycastResult& result, const Ray& ray, float maxDistance, float segmentDistance, unsigned collisionMask = M_MAX_UNSIGNED);
     /// Perform a physics world swept sphere test and return the closest hit.
     void SphereCast
         (PhysicsRaycastResult& result, const Ray& ray, float radius, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
@@ -175,8 +177,10 @@ public:
     void GetRigidBodies(PODVector<RigidBody*>& result, const Sphere& sphere, unsigned collisionMask = M_MAX_UNSIGNED);
     /// Return rigid bodies by a box query.
     void GetRigidBodies(PODVector<RigidBody*>& result, const BoundingBox& box, unsigned collisionMask = M_MAX_UNSIGNED);
-    /// Return rigid bodies that have been in collision with a specific body on the last simulation step.
+    /// Return rigid bodies by contact test with the specified body. It needs to be active to return all contacts reliably.
     void GetRigidBodies(PODVector<RigidBody*>& result, const RigidBody* body);
+    /// Return rigid bodies that have been in collision with the specified body on the last simulation step. Only returns collisions that were sent as events (depends on collision event mode) and excludes e.g. static-static collisions.
+    void GetCollidingBodies(PODVector<RigidBody*>& result, const RigidBody* body);
 
     /// Return gravity.
     Vector3 GetGravity() const;
@@ -244,6 +248,9 @@ public:
     /// Return whether node dirtying should be disregarded.
     bool IsApplyingTransforms() const { return applyingTransforms_; }
 
+    /// Return whether is currently inside the Bullet substep loop.
+    bool IsSimulating() const { return simulating_; }
+
 protected:
     /// Handle scene being assigned.
     virtual void OnSceneSet(Scene* scene);
@@ -308,12 +315,14 @@ private:
     bool internalEdge_;
     /// Applying transforms flag.
     bool applyingTransforms_;
+    /// Simulating flag.
+    bool simulating_;
+    /// Debug draw depth test mode.
+    bool debugDepthTest_;
     /// Debug renderer.
     DebugRenderer* debugRenderer_;
     /// Debug draw flags.
     int debugMode_;
-    /// Debug draw depth test mode.
-    bool debugDepthTest_;
 };
 
 /// Register Physics library objects.

+ 26 - 13
Source/Urho3D/Physics/RigidBody.cpp

@@ -240,9 +240,14 @@ void RigidBody::SetPosition(const Vector3& position)
         worldTrans.setOrigin(ToBtVector3(position + ToQuaternion(worldTrans.getRotation()) * centerOfMass_));
 
         // When forcing the physics position, set also interpolated position so that there is no jitter
-        btTransform interpTrans = body_->getInterpolationWorldTransform();
-        interpTrans.setOrigin(worldTrans.getOrigin());
-        body_->setInterpolationWorldTransform(interpTrans);
+        // When not inside the simulation loop, this may lead to erratic movement of parented rigidbodies
+        // so skip in that case
+        if (physicsWorld_->IsSimulating())
+        {
+            btTransform interpTrans = body_->getInterpolationWorldTransform();
+            interpTrans.setOrigin(worldTrans.getOrigin());
+            body_->setInterpolationWorldTransform(interpTrans);
+        }
 
         Activate();
         MarkNetworkUpdate();
@@ -259,11 +264,15 @@ void RigidBody::SetRotation(const Quaternion& rotation)
         if (!centerOfMass_.Equals(Vector3::ZERO))
             worldTrans.setOrigin(ToBtVector3(oldPosition + rotation * centerOfMass_));
 
-        btTransform interpTrans = body_->getInterpolationWorldTransform();
-        interpTrans.setRotation(worldTrans.getRotation());
-        if (!centerOfMass_.Equals(Vector3::ZERO))
-            interpTrans.setOrigin(worldTrans.getOrigin());
-        body_->setInterpolationWorldTransform(interpTrans);
+        if (physicsWorld_->IsSimulating())
+        {
+            btTransform interpTrans = body_->getInterpolationWorldTransform();
+            interpTrans.setRotation(worldTrans.getRotation());
+            if (!centerOfMass_.Equals(Vector3::ZERO))
+                interpTrans.setOrigin(worldTrans.getOrigin());
+            body_->setInterpolationWorldTransform(interpTrans);
+        }
+        
         body_->updateInertiaTensor();
 
         Activate();
@@ -279,10 +288,14 @@ void RigidBody::SetTransform(const Vector3& position, const Quaternion& rotation
         worldTrans.setRotation(ToBtQuaternion(rotation));
         worldTrans.setOrigin(ToBtVector3(position + rotation * centerOfMass_));
 
-        btTransform interpTrans = body_->getInterpolationWorldTransform();
-        interpTrans.setOrigin(worldTrans.getOrigin());
-        interpTrans.setRotation(worldTrans.getRotation());
-        body_->setInterpolationWorldTransform(interpTrans);
+        if (physicsWorld_->IsSimulating())
+        {
+            btTransform interpTrans = body_->getInterpolationWorldTransform();
+            interpTrans.setOrigin(worldTrans.getOrigin());
+            interpTrans.setRotation(worldTrans.getRotation());
+            body_->setInterpolationWorldTransform(interpTrans);
+        }
+
         body_->updateInertiaTensor();
 
         Activate();
@@ -698,7 +711,7 @@ bool RigidBody::IsActive() const
 void RigidBody::GetCollidingBodies(PODVector<RigidBody*>& result) const
 {
     if (physicsWorld_)
-        physicsWorld_->GetRigidBodies(result, this);
+        physicsWorld_->GetCollidingBodies(result, this);
     else
         result.Clear();
 }

+ 1 - 1
Source/Urho3D/Physics/RigidBody.h

@@ -225,7 +225,7 @@ public:
     /// Return collision event signaling mode.
     CollisionEventMode GetCollisionEventMode() const { return collisionEventMode_; }
 
-    /// Return colliding rigid bodies from the last simulation step.
+    /// Return colliding rigid bodies from the last simulation step. Only returns collisions that were sent as events (depends on collision event mode) and excludes e.g. static-static collisions.
     void GetCollidingBodies(PODVector<RigidBody*>& result) const;
 
     /// Apply new world transform after a simulation step. Called internally.

+ 7 - 0
Source/Urho3D/Resource/BackgroundLoader.cpp

@@ -41,6 +41,13 @@ BackgroundLoader::BackgroundLoader(ResourceCache* owner) :
 {
 }
 
+BackgroundLoader::~BackgroundLoader()
+{
+    MutexLock lock(backgroundLoadMutex_);
+
+    backgroundLoadQueue_.Clear();
+}
+
 void BackgroundLoader::ThreadFunction()
 {
     while (shouldRun_)

+ 3 - 0
Source/Urho3D/Resource/BackgroundLoader.h

@@ -56,6 +56,9 @@ public:
     /// Construct.
     BackgroundLoader(ResourceCache* owner);
 
+    /// Destruct. Forcibly clear the load queue.
+    ~BackgroundLoader();
+
     /// Resource background loading loop.
     virtual void ThreadFunction();
 

+ 15 - 14
Source/Urho3D/Resource/Image.cpp

@@ -371,7 +371,7 @@ bool Image::BeginLoad(Deserializer& source)
             unsigned x = ddsd.dwWidth_ / 2;
             unsigned y = ddsd.dwHeight_ / 2;
             unsigned z = ddsd.dwDepth_ / 2;
-            for (unsigned level = ddsd.dwMipMapCount_; level > 0; x /= 2, y /= 2, z /= 2, level -= 1)
+            for (unsigned level = ddsd.dwMipMapCount_; level > 1; x /= 2, y /= 2, z /= 2, --level)
             {
                 blocksWide = (Max(x, 1) + 3) / 4;
                 blocksHeight = (Max(y, 1) + 3) / 4;
@@ -385,7 +385,7 @@ bool Image::BeginLoad(Deserializer& source)
             unsigned x = ddsd.dwWidth_ / 2;
             unsigned y = ddsd.dwHeight_ / 2;
             unsigned z = ddsd.dwDepth_ / 2;
-            for (unsigned level = ddsd.dwMipMapCount_; level > 0; x /= 2, y /= 2, z /= 2, level -= 1)
+            for (unsigned level = ddsd.dwMipMapCount_; level > 1; x /= 2, y /= 2, z /= 2, --level)
                 dataSize += (ddsd.ddpfPixelFormat_.dwRGBBitCount_ / 8) * Max(x, 1) * Max(y, 1) * Max(z, 1);
         }
 
@@ -428,23 +428,24 @@ bool Image::BeginLoad(Deserializer& source)
         {
             URHO3D_PROFILE(ConvertDDSToRGBA);
 
-            SharedPtr<Image> currentImage(this);
-            while (currentImage.NotNull())
+            currentImage = this;
+
+            while (currentImage)
             {
                 unsigned sourcePixelByteSize = ddsd.ddpfPixelFormat_.dwRGBBitCount_ >> 3;
                 unsigned numPixels = dataSize / sourcePixelByteSize;
 
 #define ADJUSTSHIFT(mask, l, r) \
                 if (mask && mask >= 0x100) \
-                                { \
-                                                    while ((mask >> r) >= 0x100) \
-                        ++r; \
-                                } \
-                        else if (mask && mask < 0x80) \
-                                { \
-                                                    while ((mask << l) < 0x80) \
-                        ++l; \
-                                }
+                { \
+                    while ((mask >> r) >= 0x100) \
+                    ++r; \
+                } \
+                else if (mask && mask < 0x80) \
+                { \
+                    while ((mask << l) < 0x80) \
+                    ++l; \
+                }
 
                 unsigned rShiftL = 0, gShiftL = 0, bShiftL = 0, aShiftL = 0;
                 unsigned rShiftR = 0, gShiftR = 0, bShiftR = 0, aShiftR = 0;
@@ -458,7 +459,6 @@ bool Image::BeginLoad(Deserializer& source)
                 ADJUSTSHIFT(aMask, aShiftL, aShiftR)
                 
                 SharedArrayPtr<unsigned char> rgbaData(new unsigned char[numPixels * 4]);
-                SetMemoryUse(numPixels * 4);
 
                 switch (sourcePixelByteSize)
                 {
@@ -514,6 +514,7 @@ bool Image::BeginLoad(Deserializer& source)
 
                 // Replace with converted data
                 currentImage->data_ = rgbaData;
+                currentImage->SetMemoryUse(numPixels * 4);
                 currentImage = currentImage->GetNextSibling();
             }
         }

+ 3 - 3
Source/Urho3D/Resource/JSONValue.cpp

@@ -367,7 +367,7 @@ void JSONValue::SetVariant(const Variant& variant, Context* context)
 Variant JSONValue::GetVariant() const
 {
     VariantType type = Variant::GetTypeFromName((*this)["type"].GetString());
-    return (*this)["value"].GetVariantValue(type);    
+    return (*this)["value"].GetVariantValue(type);
 }
 
 void JSONValue::SetVariantValue(const Variant& variant, Context* context)
@@ -411,7 +411,7 @@ void JSONValue::SetVariantValue(const Variant& variant, Context* context)
         {
             if (!context)
             {
-                URHO3D_LOGERROR("Context must not null for ResourceRef");
+                URHO3D_LOGERROR("Context must not be null for ResourceRef");
                 return;
             }
 
@@ -424,7 +424,7 @@ void JSONValue::SetVariantValue(const Variant& variant, Context* context)
         {
             if (!context)
             {
-                URHO3D_LOGERROR("Context must not null for ResourceRefList");
+                URHO3D_LOGERROR("Context must not be null for ResourceRefList");
                 return;
             }
 

+ 98 - 0
Source/Urho3D/Scene/Animatable.cpp

@@ -25,6 +25,7 @@
 #include "../Core/Context.h"
 #include "../IO/Log.h"
 #include "../Resource/ResourceCache.h"
+#include "../Resource/JSONValue.h"
 #include "../Resource/XMLElement.h"
 #include "../Scene/Animatable.h"
 #include "../Scene/ObjectAnimation.h"
@@ -127,6 +128,64 @@ bool Animatable::LoadXML(const XMLElement& source, bool setInstanceDefault)
     return true;
 }
 
+bool Animatable::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    if (!Serializable::LoadJSON(source, setInstanceDefault))
+        return false;
+
+    SetObjectAnimation(0);
+    attributeAnimationInfos_.Clear();
+
+    JSONValue value = source.Get("objectanimation");
+    if (!value.IsNull())
+    {
+        SharedPtr<ObjectAnimation> objectAnimation(new ObjectAnimation(context_));
+        if (!objectAnimation->LoadJSON(value))
+            return false;
+
+        SetObjectAnimation(objectAnimation);
+    }
+
+    JSONValue attributeAnimationValue = source.Get("attributeanimation");
+
+    if (attributeAnimationValue.IsNull())
+        return true;
+
+    if (!attributeAnimationValue.IsObject())
+    {
+        URHO3D_LOGWARNING("'attributeanimation' value is present in JSON data, but is not a JSON object; skipping it");
+        return true;
+    }
+
+    const JSONObject& attributeAnimationObject = attributeAnimationValue.GetObject();
+    for (JSONObject::ConstIterator it = attributeAnimationObject.Begin(); it != attributeAnimationObject.End(); it++)
+    {
+        String name = it->first_;
+        JSONValue value = it->second_;
+        SharedPtr<ValueAnimation> attributeAnimation(new ValueAnimation(context_));
+        if (!attributeAnimation->LoadJSON(it->second_))
+            return false;
+
+        String wrapModeString = source.Get("wrapmode").GetString();
+        WrapMode wrapMode = WM_LOOP;
+        for (int i = 0; i <= WM_CLAMP; ++i)
+        {
+            if (wrapModeString == wrapModeNames[i])
+            {
+                wrapMode = (WrapMode)i;
+                break;
+            }
+        }
+
+        float speed = value.Get("speed").GetFloat();
+        SetAttributeAnimation(name, attributeAnimation, wrapMode, speed);
+
+        it++;
+    }
+
+    return true;
+}
+
 bool Animatable::SaveXML(XMLElement& dest) const
 {
     if (!Serializable::SaveXML(dest))
@@ -140,6 +199,7 @@ bool Animatable::SaveXML(XMLElement& dest) const
             return false;
     }
 
+
     for (HashMap<String, SharedPtr<AttributeAnimationInfo> >::ConstIterator i = attributeAnimationInfos_.Begin();
          i != attributeAnimationInfos_.End(); ++i)
     {
@@ -160,6 +220,44 @@ bool Animatable::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool Animatable::SaveJSON(JSONValue& dest) const
+{
+    if (!Serializable::SaveJSON(dest))
+        return false;
+
+    // Object animation without name
+    if (objectAnimation_ && objectAnimation_->GetName().Empty())
+    {
+        JSONValue objectAnimationValue;
+        if (!objectAnimation_->SaveJSON(objectAnimationValue))
+            return false;
+        dest.Set("objectanimation", objectAnimationValue);
+    }
+
+    JSONValue attributeAnimationValue;
+
+    for (HashMap<String, SharedPtr<AttributeAnimationInfo> >::ConstIterator i = attributeAnimationInfos_.Begin();
+         i != attributeAnimationInfos_.End(); ++i)
+    {
+        ValueAnimation* attributeAnimation = i->second_->GetAnimation();
+        if (attributeAnimation->GetOwner())
+            continue;
+
+        const AttributeInfo& attr = i->second_->GetAttributeInfo();
+        JSONValue attributeValue;
+        attributeValue.Set("name", attr.name_);
+        if (!attributeAnimation->SaveJSON(attributeValue))
+            return false;
+
+        attributeValue.Set("wrapmode", wrapModeNames[i->second_->GetWrapMode()]);
+        attributeValue.Set("speed", (float) i->second_->GetSpeed());
+
+        attributeAnimationValue.Set(attr.name_, attributeValue);
+    }
+
+    return true;
+}
+
 void Animatable::SetAnimationEnabled(bool enable)
 {
     if (objectAnimation_)

+ 4 - 0
Source/Urho3D/Scene/Animatable.h

@@ -76,6 +76,10 @@ public:
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;
+    /// Load from JSON data. When setInstanceDefault is set to true, after setting the attribute value, store the value as instance's default value. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
+    /// Save as JSON data. Return true if successful.
+    virtual bool SaveJSON(JSONValue& dest) const;
 
     /// Set automatic update of animation, default true.
     void SetAnimationEnabled(bool enable);

+ 11 - 0
Source/Urho3D/Scene/Component.cpp

@@ -29,6 +29,7 @@
 #include "../Scene/SceneEvents.h"
 
 #include "../DebugNew.h"
+#include "./Resource/JSONValue.h"
 
 #ifdef _MSC_VER
 #pragma warning(disable:6293)
@@ -74,6 +75,16 @@ bool Component::SaveXML(XMLElement& dest) const
     return Animatable::SaveXML(dest);
 }
 
+bool Component::SaveJSON(JSONValue& dest) const
+{
+    // Write type and ID
+    dest.Set("type", GetTypeName());
+    dest.Set("id", (int) id_);
+
+    // Write attributes
+    return Animatable::SaveJSON(dest);
+}
+
 void Component::MarkNetworkUpdate()
 {
     if (!networkUpdate_ && id_ < FIRST_LOCAL_ID)

+ 2 - 0
Source/Urho3D/Scene/Component.h

@@ -54,6 +54,8 @@ public:
     virtual bool Save(Serializer& dest) const;
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;
+    /// Save as JSON data. Return true if successful.
+    virtual bool SaveJSON(JSONValue& dest) const;
     /// Mark for attribute check on the next network update.
     virtual void MarkNetworkUpdate();
     /// Return the depended on nodes to order network updates.

+ 120 - 1
Source/Urho3D/Scene/Node.cpp

@@ -27,6 +27,7 @@
 #include "../IO/Log.h"
 #include "../IO/MemoryBuffer.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/Component.h"
 #include "../Scene/ObjectAnimation.h"
 #include "../Scene/ReplicationState.h"
@@ -169,6 +170,25 @@ bool Node::LoadXML(const XMLElement& source, bool setInstanceDefault)
     return success;
 }
 
+bool Node::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    SceneResolver resolver;
+
+    // Read own ID. Will not be applied, only stored for resolving possible references
+    unsigned nodeID = source.Get("id").GetUInt();
+    resolver.AddNode(nodeID, this);
+
+    // Read attributes, components and child nodes
+    bool success = LoadJSON(source, resolver);
+    if (success)
+    {
+        resolver.Resolve();
+        ApplyAttributes();
+    }
+
+    return success;
+}
+
 bool Node::SaveXML(XMLElement& dest) const
 {
     // Write node ID
@@ -206,6 +226,50 @@ bool Node::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool Node::SaveJSON(JSONValue& dest) const
+{
+    // Write node ID
+    dest.Set("id", (unsigned) id_);
+
+    // Write attributes
+    if (!Animatable::SaveJSON(dest))
+        return false;
+
+    // Write components
+    JSONArray componentsArray;
+    componentsArray.Reserve(components_.Size());
+    for (unsigned i = 0; i < components_.Size(); ++i)
+    {
+        Component* component = components_[i];
+        if (component->IsTemporary())
+            continue;
+
+        JSONValue compVal;
+        if (!component->SaveJSON(compVal))
+            return false;
+        componentsArray.Push(compVal);
+    }
+    dest.Set("components", componentsArray);
+
+    // Write child nodes
+    JSONArray childrenArray;
+    childrenArray.Reserve(children_.Size());
+    for (unsigned i = 0; i < children_.Size(); ++i)
+    {
+        Node* node = children_[i];
+        if (node->IsTemporary())
+            continue;
+
+        JSONValue childVal;
+        if (!node->SaveJSON(childVal))
+            return false;
+        childrenArray.Push(childVal);
+    }
+    dest.Set("children", childrenArray);
+
+    return true;
+}
+
 void Node::ApplyAttributes()
 {
     for (unsigned i = 0; i < components_.Size(); ++i)
@@ -242,6 +306,17 @@ bool Node::SaveXML(Serializer& dest, const String& indentation) const
     return xml->Save(dest, indentation);
 }
 
+bool Node::SaveJSON(Serializer& dest, const String& indentation) const
+{
+    SharedPtr<JSONFile> json(new JSONFile(context_));
+    JSONValue& rootElem = json->GetRoot();
+
+    if (!SaveJSON(rootElem))
+        return false;
+
+    return json->Save(dest, indentation);
+}
+
 void Node::SetName(const String& name)
 {
     if (name != name_)
@@ -1353,6 +1428,50 @@ bool Node::LoadXML(const XMLElement& source, SceneResolver& resolver, bool readC
     return true;
 }
 
+bool Node::LoadJSON(const JSONValue& source, SceneResolver& resolver, bool readChildren, bool rewriteIDs, CreateMode mode)
+{
+    // Remove all children and components first in case this is not a fresh load
+    RemoveAllChildren();
+    RemoveAllComponents();
+
+    if (!Animatable::LoadJSON(source))
+        return false;
+
+    const JSONArray& componentsArray = source.Get("components").GetArray();
+
+    for (unsigned i = 0; i < componentsArray.Size(); i++)
+    {
+        const JSONValue& compVal = componentsArray.At(i);
+        String typeName = compVal.Get("type").GetString();
+        unsigned compID = compVal.Get("id").GetUInt();
+        Component* newComponent = SafeCreateComponent(typeName, StringHash(typeName),
+            (mode == REPLICATED && compID < FIRST_LOCAL_ID) ? REPLICATED : LOCAL, rewriteIDs ? 0 : compID);
+        if (newComponent)
+        {
+            resolver.AddComponent(compID, newComponent);
+            if (!newComponent->LoadJSON(compVal))
+                return false;
+        }
+    }
+
+    if (!readChildren)
+        return true;
+
+    const JSONArray& childrenArray = source.Get("children").GetArray();
+    for (unsigned i = 0; i < childrenArray.Size(); i++)
+    {
+        const JSONValue& childVal = childrenArray.At(i);
+
+        unsigned nodeID = childVal.Get("id").GetUInt();
+        Node* newNode = CreateChild(rewriteIDs ? 0 : nodeID, (mode == REPLICATED && nodeID < FIRST_LOCAL_ID) ? REPLICATED :
+            LOCAL);
+        resolver.AddNode(nodeID, newNode);
+        if (!newNode->LoadJSON(childVal, resolver, readChildren, rewriteIDs, mode))
+            return false;
+    }
+
+    return true;
+}
 
 void Node::PrepareNetworkUpdate()
 {
@@ -1909,4 +2028,4 @@ void Node::HandleAttributeAnimationUpdate(StringHash eventType, VariantMap& even
     UpdateAttributeAnimations(eventData[P_TIMESTEP].GetFloat());
 }
 
-}
+}

+ 9 - 1
Source/Urho3D/Scene/Node.h

@@ -70,10 +70,14 @@ public:
     virtual bool Load(Deserializer& source, bool setInstanceDefault = false);
     /// Load from XML data. Return true if successful.
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
+    /// Load from JSON data. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
     /// Save as binary data. Return true if successful.
     virtual bool Save(Serializer& dest) const;
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;
+    /// Save as JSON data. Return true if successful.
+    virtual bool SaveJSON(JSONValue& dest) const;
     /// Apply attribute changes that can not be applied immediately recursively to child nodes and components.
     virtual void ApplyAttributes();
 
@@ -87,6 +91,8 @@ public:
 
     /// Save to an XML file. Return true if successful.
     bool SaveXML(Serializer& dest, const String& indentation = "\t") const;
+    /// Save to a JSON file. Return true if successful.
+    bool SaveJSON(Serializer& dest, const String& indentation = "\t") const;
     /// Set name of the scene node. Names are not required to be unique.
     void SetName(const String& name);
     /// Set position in parent space. If the scene node is on the root level (is child of the scene itself), this is same as world space.
@@ -530,7 +536,9 @@ public:
     /// Load components from XML data and optionally load child nodes.
     bool LoadXML(const XMLElement& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false,
         CreateMode mode = REPLICATED);
-
+    /// Load components from XML data and optionally load child nodes.
+    bool LoadJSON(const JSONValue& source, SceneResolver& resolver, bool loadChildren = true, bool rewriteIDs = false,
+        CreateMode mode = REPLICATED);
     /// Return the depended on nodes to order network updates.
     const PODVector<Node*>& GetDependencyNodes() const { return dependencyNodes_; }
 

+ 63 - 0
Source/Urho3D/Scene/ObjectAnimation.cpp

@@ -24,6 +24,7 @@
 
 #include "../Core/Context.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/ObjectAnimation.h"
 #include "../Scene/SceneEvents.h"
 #include "../Scene/ValueAnimation.h"
@@ -129,6 +130,68 @@ bool ObjectAnimation::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool ObjectAnimation::LoadJSON(const JSONValue& source)
+{
+    attributeAnimationInfos_.Clear();
+
+    JSONValue attributeAnimationsValue = source.Get("attributeanimations");
+    if (attributeAnimationsValue.IsNull())
+        return true;
+    if (!attributeAnimationsValue.IsObject())
+        return true;
+
+    const JSONObject& attributeAnimationsObject = attributeAnimationsValue.GetObject();
+
+    for (JSONObject::ConstIterator it = attributeAnimationsObject.Begin(); it != attributeAnimationsObject.End(); it++)
+    {
+        String name = it->first_;
+        JSONValue value = it->second_;
+        SharedPtr<ValueAnimation> animation(new ValueAnimation(context_));
+        if (!animation->LoadJSON(value))
+            return false;
+
+        String wrapModeString = value.Get("wrapmode").GetString();
+        WrapMode wrapMode = WM_LOOP;
+        for (int i = 0; i <= WM_CLAMP; ++i)
+        {
+            if (wrapModeString == wrapModeNames[i])
+            {
+                wrapMode = (WrapMode)i;
+                break;
+            }
+        }
+
+        float speed = value.Get("speed").GetFloat();
+        AddAttributeAnimation(name, animation, wrapMode, speed);
+    }
+
+    return true;
+}
+
+bool ObjectAnimation::SaveJSON(JSONValue& dest) const
+{
+    JSONValue attributeAnimationsValue;
+
+    for (HashMap<String, SharedPtr<ValueAnimationInfo> >::ConstIterator i = attributeAnimationInfos_.Begin();
+         i != attributeAnimationInfos_.End(); ++i)
+    {
+        JSONValue animValue;
+        animValue.Set("name", i->first_);
+
+        const ValueAnimationInfo* info = i->second_;
+        if (!info->GetAnimation()->SaveJSON(animValue))
+            return false;
+
+        animValue.Set("wrapmode", wrapModeNames[info->GetWrapMode()]);
+        animValue.Set("speed", (float) info->GetSpeed());
+
+        attributeAnimationsValue.Set(i->first_, animValue);
+    }
+
+    dest.Set("attributeanimations", attributeAnimationsValue);
+    return true;
+}
+
 void ObjectAnimation::AddAttributeAnimation(const String& name, ValueAnimation* attributeAnimation, WrapMode wrapMode, float speed)
 {
     if (!attributeAnimation)

+ 5 - 0
Source/Urho3D/Scene/ObjectAnimation.h

@@ -31,6 +31,7 @@ namespace Urho3D
 class ValueAnimation;
 class ValueAnimationInfo;
 class XMLElement;
+class JSONValue;
 
 /// Object animation class, an object animation include one or more attribute animations and theirs wrap mode and speed for an Animatable object.
 class URHO3D_API ObjectAnimation : public Resource
@@ -53,6 +54,10 @@ public:
     bool LoadXML(const XMLElement& source);
     /// Save as XML data. Return true if successful.
     bool SaveXML(XMLElement& dest) const;
+    /// Load from JSON data. Return true if successful.
+    bool LoadJSON(const JSONValue& source);
+    /// Save as JSON data. Return true if successful.
+    bool SaveJSON(JSONValue& dest) const;
 
     /// Add attribute animation, attribute name can in following format: "attribute" or "#0/#1/attribute" or ""#0/#1/@component#1/attribute.
     void AddAttributeAnimation

+ 266 - 8
Source/Urho3D/Scene/Scene.cpp

@@ -32,6 +32,7 @@
 #include "../Resource/ResourceCache.h"
 #include "../Resource/ResourceEvents.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/Component.h"
 #include "../Scene/ObjectAnimation.h"
 #include "../Scene/ReplicationState.h"
@@ -178,6 +179,23 @@ bool Scene::LoadXML(const XMLElement& source, bool setInstanceDefault)
         return false;
 }
 
+bool Scene::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    URHO3D_PROFILE(LoadSceneJSON);
+
+    StopAsyncLoading();
+
+    // Load the whole scene, then perform post-load if successfully loaded
+    // Note: the scene filename and checksum can not be set, as we only used an XML element
+    if (Node::LoadJSON(source, setInstanceDefault))
+    {
+        FinishLoading(0);
+        return true;
+    }
+    else
+        return false;
+}
+
 void Scene::MarkNetworkUpdate()
 {
     if (!networkUpdate_)
@@ -219,6 +237,29 @@ bool Scene::LoadXML(Deserializer& source)
         return false;
 }
 
+bool Scene::LoadJSON(Deserializer& source)
+{
+    URHO3D_PROFILE(LoadSceneJSON);
+
+    StopAsyncLoading();
+
+    SharedPtr<JSONFile> json(new JSONFile(context_));
+    if (!json->Load(source))
+        return false;
+
+    URHO3D_LOGINFO("Loading scene from " + source.GetName());
+
+    Clear();
+
+    if (Node::LoadJSON(json->GetRoot()))
+    {
+        FinishLoading(&source);
+        return true;
+    }
+    else
+        return false;
+}
+
 bool Scene::SaveXML(Serializer& dest, const String& indentation) const
 {
     URHO3D_PROFILE(SaveSceneXML);
@@ -241,6 +282,30 @@ bool Scene::SaveXML(Serializer& dest, const String& indentation) const
         return false;
 }
 
+bool Scene::SaveJSON(Serializer& dest, const String& indentation) const
+{
+    URHO3D_PROFILE(SaveSceneJSON);
+
+    SharedPtr<JSONFile> json(new JSONFile(context_));
+    JSONValue rootVal;
+    if (!SaveJSON(rootVal))
+        return false;
+
+    Deserializer* ptr = dynamic_cast<Deserializer*>(&dest);
+    if (ptr)
+        URHO3D_LOGINFO("Saving scene to " + ptr->GetName());
+
+    json->GetRoot() = rootVal;
+
+    if (json->Save(dest, indentation))
+    {
+        FinishSaving(&dest);
+        return true;
+    }
+    else
+        return false;
+}
+
 bool Scene::LoadAsync(File* file, LoadMode mode)
 {
     if (!file)
@@ -383,12 +448,79 @@ bool Scene::LoadAsyncXML(File* file, LoadMode mode)
     return true;
 }
 
+bool Scene::LoadAsyncJSON(File* file, LoadMode mode)
+{
+    if (!file)
+    {
+        URHO3D_LOGERROR("Null file for async loading");
+        return false;
+    }
+
+    StopAsyncLoading();
+
+    SharedPtr<JSONFile> json(new JSONFile(context_));
+    if (!json->Load(*file))
+        return false;
+
+    if (mode > LOAD_RESOURCES_ONLY)
+    {
+        URHO3D_LOGINFO("Loading scene from " + file->GetName());
+        Clear();
+    }
+
+    asyncLoading_ = true;
+    asyncProgress_.jsonFile_ = json;
+    asyncProgress_.file_ = file;
+    asyncProgress_.mode_ = mode;
+    asyncProgress_.loadedNodes_ = asyncProgress_.totalNodes_ = asyncProgress_.loadedResources_ = asyncProgress_.totalResources_ = 0;
+    asyncProgress_.resources_.Clear();
+
+    if (mode > LOAD_RESOURCES_ONLY)
+    {
+        JSONValue rootVal = json->GetRoot();
+
+        // Preload resources if appropriate
+        if (mode != LOAD_SCENE)
+        {
+            URHO3D_PROFILE(FindResourcesToPreload);
+
+            PreloadResourcesJSON(rootVal);
+        }
+
+        // Store own old ID for resolving possible root node references
+        unsigned nodeID = rootVal.Get("id").GetUInt();
+        resolver_.AddNode(nodeID, this);
+
+        // Load the root level components first
+        if (!Node::LoadJSON(rootVal, resolver_, false))
+            return false;
+
+        // Then prepare for loading all root level child nodes in the async update
+        JSONArray childrenArray = rootVal.Get("children").GetArray();
+        asyncProgress_.jsonIndex_ = 0;
+
+        // Count the amount of child nodes
+        asyncProgress_.totalNodes_ = childrenArray.Size();
+    }
+    else
+    {
+        URHO3D_PROFILE(FindResourcesToPreload);
+
+        URHO3D_LOGINFO("Preloading resources from " + file->GetName());
+        PreloadResourcesJSON(json->GetRoot());
+    }
+
+    return true;
+}
+
 void Scene::StopAsyncLoading()
 {
     asyncLoading_ = false;
     asyncProgress_.file_.Reset();
     asyncProgress_.xmlFile_.Reset();
+    asyncProgress_.jsonFile_.Reset();
     asyncProgress_.xmlElement_ = XMLElement::EMPTY;
+    asyncProgress_.jsonIndex_ = 0;
     asyncProgress_.resources_.Clear();
     resolver_.Reset();
 }
@@ -439,6 +571,29 @@ Node* Scene::InstantiateXML(const XMLElement& source, const Vector3& position, c
     }
 }
 
+Node* Scene::InstantiateJSON(const JSONValue& source, const Vector3& position, const Quaternion& rotation, CreateMode mode)
+{
+    URHO3D_PROFILE(InstantiateJSON);
+
+    SceneResolver resolver;
+    unsigned nodeID = source.Get("id").GetUInt();
+    // Rewrite IDs when instantiating
+    Node* node = CreateChild(0, mode);
+    resolver.AddNode(nodeID, node);
+    if (node->LoadJSON(source, resolver, true, true, mode))
+    {
+        resolver.Resolve();
+        node->ApplyAttributes();
+        node->SetTransform(position, rotation);
+        return node;
+    }
+    else
+    {
+        node->Remove();
+        return 0;
+    }
+}
+
 Node* Scene::InstantiateXML(Deserializer& source, const Vector3& position, const Quaternion& rotation, CreateMode mode)
 {
     SharedPtr<XMLFile> xml(new XMLFile(context_));
@@ -448,6 +603,15 @@ Node* Scene::InstantiateXML(Deserializer& source, const Vector3& position, const
     return InstantiateXML(xml->GetRoot(), position, rotation, mode);
 }
 
+Node* Scene::InstantiateJSON(Deserializer& source, const Vector3& position, const Quaternion& rotation, CreateMode mode)
+{
+    SharedPtr<JSONFile> json(new JSONFile(context_));
+    if (!json->Load(source))
+        return 0;
+
+    return InstantiateJSON(json->GetRoot(), position, rotation, mode);
+}
+
 void Scene::Clear(bool clearReplicated, bool clearLocal)
 {
     StopAsyncLoading();
@@ -1001,22 +1165,33 @@ void Scene::UpdateAsyncLoading()
             return;
         }
 
-        // Read one child node with its full sub-hierarchy either from binary or XML
+
+        // Read one child node with its full sub-hierarchy either from binary, JSON, or XML
         /// \todo Works poorly in scenes where one root-level child node contains all content
-        if (!asyncProgress_.xmlFile_)
+        if (asyncProgress_.xmlFile_)
         {
-            unsigned nodeID = asyncProgress_.file_->ReadUInt();
+            unsigned nodeID = asyncProgress_.xmlElement_.GetUInt("id");
             Node* newNode = CreateChild(nodeID, nodeID < FIRST_LOCAL_ID ? REPLICATED : LOCAL);
             resolver_.AddNode(nodeID, newNode);
-            newNode->Load(*asyncProgress_.file_, resolver_);
+            newNode->LoadXML(asyncProgress_.xmlElement_, resolver_);
+            asyncProgress_.xmlElement_ = asyncProgress_.xmlElement_.GetNext("node");
         }
-        else
+        else if (asyncProgress_.jsonFile_) // Load from JSON
         {
-            unsigned nodeID = asyncProgress_.xmlElement_.GetUInt("id");
+            const JSONValue& childValue = asyncProgress_.jsonFile_->GetRoot().Get("children").GetArray().At(asyncProgress_.jsonIndex_);
+
+            unsigned nodeID =childValue.Get("id").GetUInt();
             Node* newNode = CreateChild(nodeID, nodeID < FIRST_LOCAL_ID ? REPLICATED : LOCAL);
             resolver_.AddNode(nodeID, newNode);
-            newNode->LoadXML(asyncProgress_.xmlElement_, resolver_);
-            asyncProgress_.xmlElement_ = asyncProgress_.xmlElement_.GetNext("node");
+            newNode->LoadJSON(childValue, resolver_);
+            ++asyncProgress_.jsonIndex_;
+        }
+        else // Load from binary
+        {
+            unsigned nodeID = asyncProgress_.file_->ReadUInt();
+            Node* newNode = CreateChild(nodeID, nodeID < FIRST_LOCAL_ID ? REPLICATED : LOCAL);
+            resolver_.AddNode(nodeID, newNode);
+            newNode->Load(*asyncProgress_.file_, resolver_);
         }
 
         ++asyncProgress_.loadedNodes_;
@@ -1231,6 +1406,89 @@ void Scene::PreloadResourcesXML(const XMLElement& element)
 #endif
 }
 
+void Scene::PreloadResourcesJSON(const JSONValue& value)
+{
+    // If not threaded, can not background load resources, so rather load synchronously later when needed
+#ifdef URHO3D_THREADING
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    // Node or Scene attributes do not include any resources; therefore skip to the components
+    JSONArray componentArray = value.Get("components").GetArray();
+
+    for (unsigned i = 0; i < componentArray.Size(); i++)
+    {
+        const JSONValue& compValue = componentArray.At(i);
+        String typeName = compValue.Get("type").GetString();
+
+        const Vector<AttributeInfo>* attributes = context_->GetAttributes(StringHash(typeName));
+        if (attributes)
+        {
+            JSONArray attributesArray = compValue.Get("attributes").GetArray();
+
+            unsigned startIndex = 0;
+
+            for (unsigned j = 0; j < attributesArray.Size(); j++)
+            {
+                const JSONValue& attrVal = attributesArray.At(j);
+                String name = attrVal.Get("name").GetString();
+                unsigned i = startIndex;
+                unsigned attempts = attributes->Size();
+
+                while (attempts)
+                {
+                    const AttributeInfo& attr = attributes->At(i);
+                    if ((attr.mode_ & AM_FILE) && !attr.name_.Compare(name, true))
+                    {
+                        if (attr.type_ == VAR_RESOURCEREF)
+                        {
+                            ResourceRef ref = attrVal.Get("value").GetVariantValue(attr.type_).GetResourceRef();
+                            String name = cache->SanitateResourceName(ref.name_);
+                            bool success = cache->BackgroundLoadResource(ref.type_, name);
+                            if (success)
+                            {
+                                ++asyncProgress_.totalResources_;
+                                asyncProgress_.resources_.Insert(StringHash(name));
+                            }
+                        }
+                        else if (attr.type_ == VAR_RESOURCEREFLIST)
+                        {
+                            ResourceRefList refList = attrVal.Get("value").GetVariantValue(attr.type_).GetResourceRefList();
+                            for (unsigned k = 0; k < refList.names_.Size(); ++k)
+                            {
+                                String name = cache->SanitateResourceName(refList.names_[k]);
+                                bool success = cache->BackgroundLoadResource(refList.type_, name);
+                                if (success)
+                                {
+                                    ++asyncProgress_.totalResources_;
+                                    asyncProgress_.resources_.Insert(StringHash(name));
+                                }
+                            }
+                        }
+
+                        startIndex = (i + 1) % attributes->Size();
+                        break;
+                    }
+                    else
+                    {
+                        i = (i + 1) % attributes->Size();
+                        --attempts;
+                    }
+                }
+
+            }
+        }
+
+    }
+
+    JSONArray childrenArray = value.Get("children").GetArray();
+    for (unsigned i = 0; i < childrenArray.Size(); i++)
+    {
+        const JSONValue& childVal = childrenArray.At(i);
+        PreloadResourcesJSON(childVal);
+    }
+#endif
+}
+
 void RegisterSceneLibrary(Context* context)
 {
     ValueAnimation::RegisterObject(context);

+ 27 - 1
Source/Urho3D/Scene/Scene.h

@@ -25,6 +25,7 @@
 #include "../Container/HashSet.h"
 #include "../Core/Mutex.h"
 #include "../Resource/XMLElement.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/Node.h"
 #include "../Scene/SceneResolver.h"
 
@@ -57,8 +58,15 @@ struct AsyncProgress
     SharedPtr<File> file_;
     /// XML file for XML mode.
     SharedPtr<XMLFile> xmlFile_;
+    /// JSON file for JSON mode
+    SharedPtr<JSONFile> jsonFile_;
+
     /// Current XML element for XML mode.
     XMLElement xmlElement_;
+
+    /// Current JSON child array and for JSON mode
+    unsigned jsonIndex_;
+
     /// Current load mode.
     LoadMode mode_;
     /// Resource name hashes left to load.
@@ -80,6 +88,7 @@ class URHO3D_API Scene : public Node
 
     using Node::GetComponent;
     using Node::SaveXML;
+    using Node::SaveJSON;
 
 public:
     /// Construct.
@@ -95,6 +104,8 @@ public:
     virtual bool Save(Serializer& dest) const;
     /// Load from XML data. Removes all existing child nodes and components first. Return true if successful.
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
+    /// Load from JSON data. Removes all existing child nodes and components first. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
     /// Mark for attribute check on the next network update.
     virtual void MarkNetworkUpdate();
     /// Add a replication state that is tracking this scene.
@@ -102,12 +113,18 @@ public:
 
     /// Load from an XML file. Return true if successful.
     bool LoadXML(Deserializer& source);
+    /// Load from a JSON file. Return true if successful.
+    bool LoadJSON(Deserializer& source);
     /// Save to an XML file. Return true if successful.
     bool SaveXML(Serializer& dest, const String& indentation = "\t") const;
+    /// Save to a JSON file. Return true if successful.
+    bool SaveJSON(Serializer& dest, const String& indentation = "\t") const;
     /// Load from a binary file asynchronously. Return true if started successfully. The LOAD_RESOURCES_ONLY mode can also be used to preload resources from object prefab files.
     bool LoadAsync(File* file, LoadMode mode = LOAD_SCENE_AND_RESOURCES);
     /// Load from an XML file asynchronously. Return true if started successfully. The LOAD_RESOURCES_ONLY mode can also be used to preload resources from object prefab files.
     bool LoadAsyncXML(File* file, LoadMode mode = LOAD_SCENE_AND_RESOURCES);
+    /// Load from a JSON file asynchronously. Return true if started successfully. The LOAD_RESOURCES_ONLY mode can also be used to preload resources from object prefab files.
+    bool LoadAsyncJSON(File* file, LoadMode mode = LOAD_SCENE_AND_RESOURCES);
     /// Stop asynchronous loading.
     void StopAsyncLoading();
     /// Instantiate scene content from binary data. Return root node if successful.
@@ -117,6 +134,13 @@ public:
         (const XMLElement& source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
     /// Instantiate scene content from XML data. Return root node if successful.
     Node* InstantiateXML(Deserializer& source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
+    /// Instantiate scene content from JSON data. Return root node if successful.
+    Node* InstantiateJSON
+        (const JSONValue& source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
+    /// Instantiate scene content from XML data. Return root node if successful.
+    Node* InstantiateJSON(Deserializer& source, const Vector3& position, const Quaternion& rotation, CreateMode mode = REPLICATED);
+
+
     /// Clear scene completely of either replicated, local or all nodes and components.
     void Clear(bool clearReplicated = true, bool clearLocal = true);
     /// Enable or disable scene update.
@@ -220,7 +244,7 @@ public:
     void CleanupConnection(Connection* connection);
     /// Mark a node for attribute check on the next network update.
     void MarkNetworkUpdate(Node* node);
-    /// Mark a comoponent for attribute check on the next network update.
+    /// Mark a component for attribute check on the next network update.
     void MarkNetworkUpdate(Component* component);
     /// Mark a node dirty in scene replication states. The node does not need to have own replication state yet.
     void MarkReplicationDirty(Node* node);
@@ -242,6 +266,8 @@ private:
     void PreloadResources(File* file, bool isSceneFile);
     /// Preload resources from an XML scene or object prefab file.
     void PreloadResourcesXML(const XMLElement& element);
+    /// Preload resources from a JSON scene or object prefab file.
+    void PreloadResourcesJSON(const JSONValue& value);
 
     /// Replicated scene nodes by ID.
     HashMap<unsigned, Node*> replicatedNodes_;

+ 139 - 0
Source/Urho3D/Scene/Serializable.cpp

@@ -27,6 +27,7 @@
 #include "../IO/Log.h"
 #include "../IO/Serializer.h"
 #include "../Resource/XMLElement.h"
+#include "../Resource/JSONValue.h"
 #include "../Scene/ReplicationState.h"
 #include "../Scene/SceneEvents.h"
 #include "../Scene/Serializable.h"
@@ -410,6 +411,99 @@ bool Serializable::LoadXML(const XMLElement& source, bool setInstanceDefault)
     return true;
 }
 
+bool Serializable::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    if (source.IsNull())
+    {
+        URHO3D_LOGERROR("Could not load " + GetTypeName() + ", null JSON source element");
+        return false;
+    }
+
+    const Vector<AttributeInfo>* attributes = GetAttributes();
+    if (!attributes)
+        return true;
+
+    // Get attributes value
+    JSONValue attributesValue = source.Get("attributes");
+    if (attributesValue.IsNull())
+        return true;
+    // Warn if the attributes value isn't an object
+    if (!attributesValue.IsObject())
+    {
+        URHO3D_LOGWARNING("'attributes' object is present in " + GetTypeName() + " but is not a JSON object; skipping load");
+        return true;
+    }
+
+    const JSONObject& attributesObject = attributesValue.GetObject();
+
+    unsigned startIndex = 0;
+
+    for (JSONObject::ConstIterator it = attributesObject.Begin(); it != attributesObject.End();)
+    {
+        String name = it->first_;
+        const JSONValue& value = it->second_;
+        unsigned i = startIndex;
+        unsigned attempts = attributes->Size();
+
+        while (attempts)
+        {
+            const AttributeInfo& attr = attributes->At(i);
+            if ((attr.mode_ & AM_FILE) && !attr.name_.Compare(name, true))
+            {
+                Variant varValue;
+
+                // If enums specified, do enum lookup ad int assignment. Otherwise assign variant directly
+                if (attr.enumNames_)
+                {
+                    String valueStr = value.GetString();
+                    bool enumFound = false;
+                    int enumValue = 0;
+                    const char** enumPtr = attr.enumNames_;
+                    while (*enumPtr)
+                    {
+                        if (!valueStr.Compare(*enumPtr, false))
+                        {
+                            enumFound = true;
+                            break;
+                        }
+                        ++enumPtr;
+                        ++enumValue;
+                    }
+                    if (enumFound)
+                        varValue = enumValue;
+                    else
+                        URHO3D_LOGWARNING("Unknown enum value " + valueStr + " in attribute " + attr.name_);
+                }
+                else
+                    varValue = value.GetVariantValue(attr.type_);
+
+                if (!varValue.IsEmpty())
+                {
+                    OnSetAttribute(attr, varValue);
+
+                    if (setInstanceDefault)
+                        SetInstanceDefault(attr.name_, varValue);
+                }
+
+                startIndex = (i + 1) % attributes->Size();
+                break;
+            }
+            else
+            {
+                i = (i + 1) % attributes->Size();
+                --attempts;
+            }
+        }
+
+        if (!attempts)
+            URHO3D_LOGWARNING("Unknown attribute " + name + " in JSON data");
+
+        it++;
+    }
+
+    return true;
+}
+
 bool Serializable::SaveXML(XMLElement& dest) const
 {
     if (dest.IsNull())
@@ -452,6 +546,51 @@ bool Serializable::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool Serializable::SaveJSON(JSONValue& dest) const
+{
+    if (dest.IsNull())
+    {
+        URHO3D_LOGERROR("Could not save " + GetTypeName() + ", null destination JSON value");
+        return false;
+    }
+
+    const Vector<AttributeInfo>* attributes = GetAttributes();
+    if (!attributes)
+        return true;
+
+    Variant value;
+    JSONValue attributesValue;
+
+    for (unsigned i = 0; i < attributes->Size(); ++i)
+    {
+        const AttributeInfo& attr = attributes->At(i);
+        if (!(attr.mode_ & AM_FILE))
+            continue;
+
+        OnGetAttribute(attr, value);
+        Variant defaultValue(GetAttributeDefault(i));
+
+        // In JSON serialization default values can be skipped. This will make the file easier to read or edit manually
+        if (value == defaultValue && !SaveDefaultAttributes())
+            continue;
+
+        JSONValue attrVal;
+        // If enums specified, set as an enum string. Otherwise set directly as a Variant
+        if (attr.enumNames_)
+        {
+            int enumValue = value.GetInt();
+            attrVal = attr.enumNames_[enumValue];
+        }
+        else
+            attrVal.SetVariantValue(value, context_);
+
+        attributesValue.Set(attr.name_, attrVal);
+    }
+    dest.Set("attributes", attributesValue);
+
+    return true;
+}
+
 bool Serializable::SetAttribute(unsigned index, const Variant& value)
 {
     const Vector<AttributeInfo>* attributes = GetAttributes();

+ 5 - 0
Source/Urho3D/Scene/Serializable.h

@@ -34,6 +34,7 @@ class Connection;
 class Deserializer;
 class Serializer;
 class XMLElement;
+class JSONValue;
 
 struct DirtyBits;
 struct NetworkState;
@@ -66,6 +67,10 @@ public:
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;
+    /// Load from JSON data. When setInstanceDefault is set to true, after setting the attribute value, store the value as instance's default value. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
+    /// Save as JSON data. Return true if successful.
+    virtual bool SaveJSON(JSONValue& dest) const;
 
     /// Apply attribute changes that can not be applied immediately. Called after scene load or a network update.
     virtual void ApplyAttributes() { }

+ 66 - 1
Source/Urho3D/Scene/UnknownComponent.cpp

@@ -27,6 +27,7 @@
 #include "../IO/Log.h"
 #include "../IO/Serializer.h"
 #include "../Resource/XMLElement.h"
+#include "../Resource/JSONValue.h"
 #include "../Scene/UnknownComponent.h"
 
 #include "../DebugNew.h"
@@ -136,6 +137,41 @@ bool UnknownComponent::LoadXML(const XMLElement& source, bool setInstanceDefault
     return true;
 }
 
+
+bool UnknownComponent::LoadJSON(const JSONValue& source, bool setInstanceDefault)
+{
+    useXML_ = true;
+    xmlAttributes_.Clear();
+    xmlAttributeInfos_.Clear();
+    binaryAttributes_.Clear();
+
+    JSONArray attributesArray = source.Get("attributes").GetArray();
+    for (unsigned i = 0; i < attributesArray.Size(); i++)
+    {
+        const JSONValue& attrVal = attributesArray.At(i);
+
+        AttributeInfo attr;
+        attr.mode_ = AM_FILE;
+        attr.name_ = attrVal.Get("name").GetString();
+        attr.type_ = VAR_STRING;
+
+        if (!attr.name_.Empty())
+        {
+            String attrValue = attrVal.Get("value").GetString();
+            attr.defaultValue_ = String::EMPTY;
+            xmlAttributeInfos_.Push(attr);
+            xmlAttributes_.Push(attrValue);
+        }
+    }
+
+    // Fix up pointers to the attributes after all have been read
+    for (unsigned i = 0; i < xmlAttributeInfos_.Size(); ++i)
+        xmlAttributeInfos_[i].ptr_ = &xmlAttributes_[i];
+
+    return true;
+}
+
+
 bool UnknownComponent::Save(Serializer& dest) const
 {
     if (useXML_)
@@ -162,7 +198,7 @@ bool UnknownComponent::SaveXML(XMLElement& dest) const
     }
 
     if (!useXML_)
-        URHO3D_LOGWARNING("UnknownComponent loaded in binary mode, attributes will be empty for XML save");
+        URHO3D_LOGWARNING("UnknownComponent loaded in binary or JSON mode, attributes will be empty for XML save");
 
     // Write type and ID
     if (!dest.SetString("type", GetTypeName()))
@@ -180,6 +216,35 @@ bool UnknownComponent::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool UnknownComponent::SaveJSON(JSONValue& dest) const
+{
+    if (dest.IsNull())
+    {
+        URHO3D_LOGERROR("Could not save " + GetTypeName() + ", null destination element");
+        return false;
+    }
+
+    if (!useXML_)
+        URHO3D_LOGWARNING("UnknownComponent loaded in binary mode, attributes will be empty for JSON save");
+
+    // Write type and ID
+    dest.Set("type", GetTypeName());
+    dest.Set("id", (int) id_);
+
+    JSONArray attributesArray;
+    attributesArray.Reserve(xmlAttributeInfos_.Size());
+    for (unsigned i = 0; i < xmlAttributeInfos_.Size(); ++i)
+    {
+        JSONValue attrVal;
+        attrVal.Set("name", xmlAttributeInfos_[i].name_);
+        attrVal.Set("value", xmlAttributes_[i]);
+        attributesArray.Push(attrVal);
+    }
+    dest.Set("attributes", attributesArray);
+
+    return true;
+}
+
 void UnknownComponent::SetTypeName(const String& typeName)
 {
     typeName_ = typeName;

+ 6 - 1
Source/Urho3D/Scene/UnknownComponent.h

@@ -50,10 +50,14 @@ public:
     virtual bool Load(Deserializer& source, bool setInstanceDefault = false);
     /// Load from XML data. Return true if successful.
     virtual bool LoadXML(const XMLElement& source, bool setInstanceDefault = false);
+    /// Load from JSON data. Return true if successful.
+    virtual bool LoadJSON(const JSONValue& source, bool setInstanceDefault = false);
     /// Save as binary data. Return true if successful.
     virtual bool Save(Serializer& dest) const;
     /// Save as XML data. Return true if successful.
     virtual bool SaveXML(XMLElement& dest) const;
+    /// Save as JSON data. Return true if successful.
+    virtual bool SaveJSON(JSONValue& dest) const;
 
     /// Initialize the type name. Called by Node when loading.
     void SetTypeName(const String& typeName);
@@ -93,8 +97,9 @@ private:
     Vector<String> xmlAttributes_;
     /// Binary attributes.
     PODVector<unsigned char> binaryAttributes_;
-    /// Flag of whether was loaded using XML data.
+    /// Flag of whether was loaded using XML/JSON data.
     bool useXML_;
+
 };
 
 }

+ 84 - 0
Source/Urho3D/Scene/ValueAnimation.cpp

@@ -27,6 +27,7 @@
 #include "../IO/Log.h"
 #include "../IO/Serializer.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/JSONFile.h"
 #include "../Scene/Animatable.h"
 #include "../Scene/ObjectAnimation.h"
 #include "../Scene/ValueAnimation.h"
@@ -155,6 +156,89 @@ bool ValueAnimation::SaveXML(XMLElement& dest) const
     return true;
 }
 
+bool ValueAnimation::LoadJSON(const JSONValue& source)
+{
+    valueType_ = VAR_NONE;
+    eventFrames_.Clear();
+
+    String interpMethodString = source.Get("interpolationmethod").GetString();
+    InterpMethod method = IM_LINEAR;
+    for (int i = 0; i <= IM_SPLINE; ++i)
+    {
+        if (interpMethodString == interpMethodNames[i])
+        {
+            method = (InterpMethod)i;
+            break;
+        }
+    }
+
+    SetInterpolationMethod(method);
+    if (interpolationMethod_ == IM_SPLINE)
+        splineTension_ = source.Get("splinetension").GetFloat();
+
+    // Load keyframes
+    JSONArray keyFramesArray = source.Get("keyframes").GetArray();
+    for (unsigned i = 0; i < keyFramesArray.Size(); i++)
+    {
+        const JSONValue& val = keyFramesArray[i];
+        float time = val.Get("time").GetFloat();
+        Variant value = val.Get("value").GetVariant();
+        SetKeyFrame(time, value);
+    }
+
+    // Load event frames
+    JSONArray eventFramesArray = source.Get("eventframes").GetArray();
+    for (unsigned i = 0; i < eventFramesArray.Size(); i++)
+    {
+        const JSONValue& eventFrameVal = eventFramesArray[i];
+        float time = eventFrameVal.Get("time").GetFloat();
+        unsigned eventType = eventFrameVal.Get("eventtype").GetUInt();
+        VariantMap eventData = eventFrameVal.Get("eventdata").GetVariantMap();
+        SetEventFrame(time, StringHash(eventType), eventData);
+    }
+
+    return true;
+}
+
+bool ValueAnimation::SaveJSON(JSONValue& dest) const
+{
+    dest.Set("interpolationmethod", interpMethodNames[interpolationMethod_]);
+    if (interpolationMethod_ == IM_SPLINE)
+        dest.Set("splinetension", (float) splineTension_);
+
+    JSONArray keyFramesArray;
+    keyFramesArray.Reserve(keyFrames_.Size());
+    for (unsigned i = 0; i < keyFrames_.Size(); ++i)
+    {
+        const VAnimKeyFrame& keyFrame = keyFrames_[i];
+        JSONValue keyFrameVal;
+        keyFrameVal.Set("time", keyFrame.time_);
+        JSONValue valueVal;
+        valueVal.SetVariant(keyFrame.value_);
+        keyFrameVal.Set("value", valueVal);
+        keyFramesArray.Push(keyFrameVal);
+    }
+    dest.Set("keyframes", keyFramesArray);
+
+    JSONArray eventFramesArray;
+    eventFramesArray.Reserve(eventFrames_.Size());
+    for (unsigned i = 0; i < eventFrames_.Size(); ++i)
+    {
+        const VAnimEventFrame& eventFrame = eventFrames_[i];
+        JSONValue eventFrameVal;
+        eventFrameVal.Set("time", eventFrame.time_);
+        eventFrameVal.Set("eventtype", eventFrame.eventType_.Value());
+        JSONValue eventDataVal;
+        eventDataVal.SetVariantMap(eventFrame.eventData_);
+        eventFrameVal.Set("eventdata", eventDataVal);
+
+        eventFramesArray.Push(eventFrameVal);
+    }
+    dest.Set("eventframes", eventFramesArray);
+
+    return true;
+}
+
 void ValueAnimation::SetValueType(VariantType valueType)
 {
     if (valueType == valueType_)

+ 5 - 0
Source/Urho3D/Scene/ValueAnimation.h

@@ -29,6 +29,7 @@ namespace Urho3D
 {
 
 class XMLElement;
+class JSONValue;
 
 /// Interpolation method.
 enum InterpMethod
@@ -80,6 +81,10 @@ public:
     bool LoadXML(const XMLElement& source);
     /// Save as XML data. Return true if successful.
     bool SaveXML(XMLElement& dest) const;
+    /// Load from JSON data. Return true if successful.
+    bool LoadJSON(const JSONValue& source);
+    /// Save as XML data. Return true if successful.
+    bool SaveJSON(JSONValue& dest) const;
 
     /// Set owner.
     void SetOwner(void* owner);

+ 9 - 5
Source/Urho3D/UI/DropDownList.cpp

@@ -63,6 +63,7 @@ DropDownList::DropDownList(Context* context) :
 
     SubscribeToEvent(listView_, E_ITEMCLICKED, URHO3D_HANDLER(DropDownList, HandleItemClicked));
     SubscribeToEvent(listView_, E_UNHANDLEDKEY, URHO3D_HANDLER(DropDownList, HandleListViewKey));
+    SubscribeToEvent(listView_, E_SELECTIONCHANGED, URHO3D_HANDLER(DropDownList, HandleSelectionChanged));
 }
 
 DropDownList::~DropDownList()
@@ -162,8 +163,8 @@ void DropDownList::InsertItem(unsigned index, UIElement* item)
     listView_->InsertItem(index, item);
 
     // If there was no selection, set to the first
-    if (listView_->GetSelection() == M_MAX_UNSIGNED)
-        listView_->SetSelection(0);
+    if (GetSelection() == M_MAX_UNSIGNED)
+        SetSelection(0);
 }
 
 void DropDownList::RemoveItem(UIElement* item)
@@ -184,9 +185,6 @@ void DropDownList::RemoveAllItems()
 void DropDownList::SetSelection(unsigned index)
 {
     listView_->SetSelection(index);
-
-    // Display the place holder text when there is no selection, however, the place holder text is only visible when the place holder itself is set to visible
-    placeholder_->GetChild(0)->SetVisible(index == M_MAX_UNSIGNED);
 }
 
 void DropDownList::SetPlaceholderText(const String& text)
@@ -340,4 +338,10 @@ void DropDownList::HandleListViewKey(StringHash eventType, VariantMap& eventData
         HandleItemClicked(eventType, eventData);
 }
 
+void DropDownList::HandleSelectionChanged(StringHash eventType, VariantMap& eventData)
+{
+    // Display the place holder text when there is no selection, however, the place holder text is only visible when the place holder itself is set to visible
+    placeholder_->GetChild(0)->SetVisible(GetSelection() == M_MAX_UNSIGNED);
+}
+
 }

+ 4 - 2
Source/Urho3D/UI/DropDownList.h

@@ -65,7 +65,7 @@ public:
     void RemoveAllItems();
     /// Set selection.
     void SetSelection(unsigned index);
-    /// Set place holder text. This is the text shown when there is no selection in drop down list.
+    /// Set place holder text. This is the text shown when there is no selection (-1) in drop down list. Note that if the list has items, the default is to show the first item, so the "no selection" state has to be set explicitly.
     void SetPlaceholderText(const String& text);
     /// Set whether popup should be automatically resized to match the dropdown button width.
     void SetResizePopup(bool enable);
@@ -112,8 +112,10 @@ protected:
 private:
     /// Handle listview item click event.
     void HandleItemClicked(StringHash eventType, VariantMap& eventData);
-    /// Handle a key press from the listview
+    /// Handle a key press from the listview.
     void HandleListViewKey(StringHash eventType, VariantMap& eventData);
+    /// Handle the listview selection change. Set placeholder text hidden/visible as necessary.
+    void HandleSelectionChanged(StringHash eventType, VariantMap& eventData);
 
     /// Selected item index attribute.
     unsigned selectionAttr_;

+ 62 - 11
Source/Urho3D/UI/UI.cpp

@@ -108,7 +108,8 @@ UI::UI(Context* context) :
     uiRendered_(false),
     nonModalBatchSize_(0),
     dragElementsCount_(0),
-    dragConfirmedCount_(0)
+    dragConfirmedCount_(0),
+    uiScale_(1.0f)
 {
     rootElement_->SetTraversalMode(TM_DEPTH_FIRST);
     rootModalElement_->SetTraversalMode(TM_DEPTH_FIRST);
@@ -362,7 +363,10 @@ void UI::Update(float timeStep)
     for (unsigned i = 0; i < numTouches; ++i)
     {
         TouchState* touch = input->GetTouch(i);
-        ProcessHover(touch->position_, TOUCHID_MASK(touch->touchID_), 0, 0);
+        IntVector2 touchPos = touch->position_;
+        touchPos.x_ = (int)(touchPos.x_ / uiScale_);
+        touchPos.y_ = (int)(touchPos.y_ / uiScale_);
+        ProcessHover(touchPos, TOUCHID_MASK(touch->touchID_), 0, 0);
     }
 
     // End hovers that expired without refreshing
@@ -404,6 +408,7 @@ void UI::RenderUpdate()
     batches_.Clear();
     vertexData_.Clear();
     const IntVector2& rootSize = rootElement_->GetSize();
+    // Note: the scissors operate on unscaled coordinates. Scissor scaling is only performed during render
     IntRect currentScissor = IntRect(0, 0, rootSize.x_, rootSize.y_);
     if (rootElement_->IsVisible())
         GetBatches(rootElement_, currentScissor);
@@ -698,8 +703,8 @@ void UI::Initialize()
     graphics_ = graphics;
     UIBatch::posAdjust = Vector3(Graphics::GetPixelUVOffset(), 0.0f);
 
-    rootElement_->SetSize(graphics->GetWidth(), graphics->GetHeight());
-    rootModalElement_->SetSize(rootElement_->GetSize());
+    // Apply initial UI scale to set the root elements size
+    SetScale(uiScale_);
 
     vertexBuffer_ = new VertexBuffer(context_);
     debugVertexBuffer_ = new VertexBuffer(context_);
@@ -756,9 +761,9 @@ void UI::Render(bool resetRenderTargets, VertexBuffer* buffer, const PODVector<U
     Vector2 offset(-1.0f, 1.0f);
 
     Matrix4 projection(Matrix4::IDENTITY);
-    projection.m00_ = scale.x_;
+    projection.m00_ = scale.x_ * uiScale_;
     projection.m03_ = offset.x_;
-    projection.m11_ = scale.y_;
+    projection.m11_ = scale.y_ * uiScale_;
     projection.m13_ = offset.y_;
     projection.m22_ = 1.0f;
     projection.m23_ = 0.0f;
@@ -819,8 +824,14 @@ void UI::Render(bool resetRenderTargets, VertexBuffer* buffer, const PODVector<U
         if (graphics_->NeedParameterUpdate(SP_MATERIAL, this))
             graphics_->SetShaderParameter(PSP_MATDIFFCOLOR, Color(1.0f, 1.0f, 1.0f, 1.0f));
 
+        IntRect scissor = batch.scissor_;
+        scissor.left_ = (int)(scissor.left_ * uiScale_);
+        scissor.top_ = (int)(scissor.top_ * uiScale_);
+        scissor.right_ = (int)(scissor.right_ * uiScale_);
+        scissor.bottom_ = (int)(scissor.bottom_ * uiScale_);
+
         graphics_->SetBlendMode(batch.blendMode_);
-        graphics_->SetScissorTest(true, batch.scissor_);
+        graphics_->SetScissorTest(true, scissor);
         graphics_->SetTexture(0, batch.texture_);
         graphics_->Draw(TRIANGLE_LIST, batch.vertexStart_ / UI_VERTEX_SIZE,
             (batch.vertexEnd_ - batch.vertexStart_) / UI_VERTEX_SIZE);
@@ -979,6 +990,9 @@ void UI::GetCursorPositionAndVisible(IntVector2& pos, bool& visible)
         if (!visible && cursor_)
             pos = cursor_->GetPosition();
     }
+
+    pos.x_ = (int)(pos.x_ / uiScale_);
+    pos.y_ = (int)(pos.y_ / uiScale_);
 }
 
 void UI::SetCursorShape(CursorShape shape)
@@ -1367,8 +1381,8 @@ void UI::HandleScreenMode(StringHash eventType, VariantMap& eventData)
         Initialize();
     else
     {
-        rootElement_->SetSize(eventData[P_WIDTH].GetInt(), eventData[P_HEIGHT].GetInt());
-        rootModalElement_->SetSize(rootElement_->GetSize());
+        // Reapply UI scale to resize the root elements
+        SetScale(uiScale_);
     }
 }
 
@@ -1506,6 +1520,8 @@ void UI::HandleTouchBegin(StringHash eventType, VariantMap& eventData)
     using namespace TouchBegin;
 
     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
+    pos.x_ = int(pos.x_ / uiScale_);
+    pos.y_ = int(pos.y_ / uiScale_);
     usingTouchInput_ = true;
 
     int touchId = TOUCHID_MASK(eventData[P_TOUCHID].GetInt());
@@ -1525,6 +1541,8 @@ void UI::HandleTouchEnd(StringHash eventType, VariantMap& eventData)
     using namespace TouchEnd;
 
     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
+    pos.x_ = int(pos.x_ / uiScale_);
+    pos.y_ = int(pos.y_ / uiScale_);
 
     // Get the touch index
     int touchId = TOUCHID_MASK(eventData[P_TOUCHID].GetInt());
@@ -1554,6 +1572,10 @@ void UI::HandleTouchMove(StringHash eventType, VariantMap& eventData)
 
     IntVector2 pos(eventData[P_X].GetInt(), eventData[P_Y].GetInt());
     IntVector2 deltaPos(eventData[P_DX].GetInt(), eventData[P_DY].GetInt());
+    pos.x_ = int(pos.x_ / uiScale_);
+    pos.y_ = int(pos.y_ / uiScale_);
+    deltaPos.x_ = int(deltaPos.x_ / uiScale_);
+    deltaPos.y_ = int(deltaPos.y_ / uiScale_);
     usingTouchInput_ = true;
 
     int touchId = TOUCHID_MASK(eventData[P_TOUCHID].GetInt());
@@ -1676,6 +1698,9 @@ void UI::HandleDropFile(StringHash eventType, VariantMap& eventData)
     if (input->IsMouseVisible())
     {
         IntVector2 screenPos = input->GetMousePosition();
+        screenPos.x_ = int(screenPos.x_ / uiScale_);
+        screenPos.y_ = int(screenPos.y_ / uiScale_);
+
         UIElement* element = GetElementAt(screenPos);
 
         using namespace UIDropFile;
@@ -1759,8 +1784,8 @@ IntVector2 UI::SumTouchPositions(UI::DragData* dragData, const IntVector2& oldSe
                 if (!ts)
                     break;
                 IntVector2 pos = ts->position_;
-                dragData->sumPos.x_ += pos.x_;
-                dragData->sumPos.y_ += pos.y_;
+                dragData->sumPos.x_ += (int)(pos.x_ / uiScale_);
+                dragData->sumPos.y_ += (int)(pos.y_ / uiScale_);
             }
         }
         sendPos.x_ = dragData->sumPos.x_ / dragData->numDragButtons;
@@ -1769,6 +1794,32 @@ IntVector2 UI::SumTouchPositions(UI::DragData* dragData, const IntVector2& oldSe
     return sendPos;
 }
 
+void UI::SetScale(float scale)
+{
+    uiScale_ = Max(scale, M_EPSILON);
+    Graphics* graphics = GetSubsystem<Graphics>();
+    if (graphics)
+    {
+        rootElement_->SetSize((int)((float)graphics->GetWidth() / uiScale_ + 0.5f), (int)((float)graphics_->GetHeight() /
+            uiScale_ + 0.5));
+        rootModalElement_->SetSize(rootElement_->GetSize());
+    }
+}
+
+void UI::SetWidth(float size)
+{
+    Graphics* graphics = GetSubsystem<Graphics>();
+    if (graphics)
+        SetScale((float)graphics->GetWidth() / size);
+}
+
+void UI::SetHeight(float size)
+{
+    Graphics* graphics = GetSubsystem<Graphics>();
+    if (graphics)
+        SetScale((float)graphics->GetHeight() / size);
+}
+
 void RegisterUILibrary(Context* context)
 {
     Font::RegisterObject(context);

+ 11 - 0
Source/Urho3D/UI/UI.h

@@ -95,6 +95,12 @@ public:
     void SetUseMutableGlyphs(bool enable);
     /// Set whether to force font autohinting instead of using FreeType's TTF bytecode interpreter.
     void SetForceAutoHint(bool enable);
+    /// Set %UI scale. 1.0 is default (pixel perfect). Resize the root element to match.
+    void SetScale(float scale);
+    /// Scale %UI to the specified width in pixels.
+    void SetWidth(float size);
+    /// Scale %UI to the specified height in pixels.
+    void SetHeight(float size);
 
     /// Return root UI element.
     UIElement* GetRoot() const { return rootElement_; }
@@ -181,6 +187,9 @@ public:
         IntVector2 dragBeginSumPos;
     };
 
+    /// Return current UI scale.
+    float GetScale() const { return uiScale_; }
+
 private:
     /// Initialize when screen mode initially set.
     void Initialize();
@@ -331,6 +340,8 @@ private:
     HashMap<WeakPtr<UIElement>, int> touchDragElements_;
     /// Confirmed drag elements cache.
     Vector<UIElement*> dragElementsConfirmed_;
+    /// Current scale of UI
+    float uiScale_;
 };
 
 /// Register UI library objects.

+ 4 - 0
Source/Urho3D/UI/UIElement.cpp

@@ -799,6 +799,9 @@ void UIElement::SetColor(Corner corner, const Color& color)
 
 void UIElement::SetPriority(int priority)
 {
+    if (priority_ == priority)
+        return;
+    
     priority_ = priority;
     if (parent_)
         parent_->sortOrderDirty_ = true;
@@ -1623,6 +1626,7 @@ void UIElement::SortChildren()
     if (sortChildren_ && sortOrderDirty_)
     {
         // Only sort when there is no layout
+        /// \todo Order is not stable when children have same priorities
         if (layoutMode_ == LM_FREE)
             Sort(children_.Begin(), children_.End(), CompareUIElements);
         sortOrderDirty_ = false;

+ 9 - 0
Source/Urho3D/Urho2D/Sprite2D.cpp

@@ -106,6 +106,15 @@ bool Sprite2D::EndLoad()
 void Sprite2D::SetTexture(Texture2D* texture)
 {
     texture_ = texture;
+    // Ensure the texture doesn't have wrap addressing as that will cause bleeding bugs on the edges.
+    // Could also choose border mode, but in that case a universally good border color (without alpha bugs)
+    // would be hard to choose. Ideal is for the user to configure the texture parameters in its parameter
+    // XML file.
+    if (texture_->GetAddressMode(COORD_U) == ADDRESS_WRAP)
+    {
+        texture_->SetAddressMode(COORD_U, ADDRESS_CLAMP);
+        texture_->SetAddressMode(COORD_V, ADDRESS_CLAMP);
+    }
 }
 
 void Sprite2D::SetRectangle(const IntRect& rectangle)

Деякі файли не було показано, через те що забагато файлів було змінено