diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3d190ab
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+syntax: regexp
+.idea/*
+syntax: glob
+CMake/*
+cmake-build-debug/
+bin/EditorData/GameSettings.xml
+
+AndroidGradle/*
+android/build/*
+android/**/build/*
+android/.*/*
+android/**/.*/*
+android/**/assets/*
+android/local.properties
+**.jar
diff --git a/AndroidBuildAndInstall.sh b/AndroidBuildAndInstall.sh
new file mode 100755
index 0000000..d3ad7f0
--- /dev/null
+++ b/AndroidBuildAndInstall.sh
@@ -0,0 +1,11 @@
+# !/bin/sh
+export ANDROID_NDK=~/Android/Sdk/ndk/21.0.6113669
+export ANDROID_SDK=~/Android/Sdk
+export PATH=$ANDROID_NDK/toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin:$ANDROID_NDK/build:$ANDROID_NDK/prebuilt/linux-x86_64/bin:$ANDROID_SDK/tools:$ANDROID_SDK/tools/bin:$ANDROID_SDK/platform-tools:$PATH
+
+export URHO3D_HOME=~/workspace/Urho3D/android/urho3d-lib/build/outputs/aar
+
+
+cd ./android
+
+./gradlew installDebug
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..5cf5e09
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,39 @@
+set(CMAKE_CXX_STANDARD 11) # C++11...
+set(CMAKE_CXX_STANDARD_REQUIRED ON) #...is required...
+set(CMAKE_CXX_EXTENSIONS OFF) #...without compiler extensions like gnu++11
+
+# Set CMake minimum version and CMake policy required by Urho3D-CMake-common module
+if (WIN32)
+ cmake_minimum_required (VERSION 3.2.3) # Going forward all platforms will use this as minimum version
+else ()
+ cmake_minimum_required (VERSION 2.8.6)
+endif ()
+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)
+ # INTERFACE_LINK_LIBRARIES defines the link interface
+ cmake_policy (SET CMP0022 NEW)
+ endif ()
+ if (CMAKE_VERSION VERSION_GREATER 3.0.0 OR CMAKE_VERSION VERSION_EQUAL 3.0.0)
+ # Disallow use of the LOCATION target property - so we set to OLD as we still need it
+ cmake_policy (SET CMP0026 OLD)
+ # MACOSX_RPATH is enabled by default
+ cmake_policy (SET CMP0042 NEW)
+ endif ()
+endif ()
+# Set project name
+project (MyMultiLineEdit)
+# Set CMake modules search path
+set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMake/Modules)
+# Include Urho3D Cmake common module
+include (UrhoCommon)
+# Define target name
+set (TARGET_NAME MyMultiLineEdit)
+# Define source files
+define_source_files (EXTRA_H_FILES ${COMMON_SAMPLE_H_FILES}
+ GLOB_CPP_PATTERNS *.cpp src/*.cpp
+ GLOB_H_PATTERNS *.h src/*.h GROUP )
+# Setup target with resource copying
+setup_main_executable ()
+
+INCLUDE_DIRECTORIES(./)
diff --git a/bin/Autoload/LargeData/Materials/PBR/Check.xml b/bin/Autoload/LargeData/Materials/PBR/Check.xml
new file mode 100644
index 0000000..9b9a2ed
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Check.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/ColorGrid.xml b/bin/Autoload/LargeData/Materials/PBR/ColorGrid.xml
new file mode 100644
index 0000000..88cf0bb
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/ColorGrid.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Concrete.xml b/bin/Autoload/LargeData/Materials/PBR/Concrete.xml
new file mode 100644
index 0000000..b939fa3
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Concrete.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/DiamonPlate.xml b/bin/Autoload/LargeData/Materials/PBR/DiamonPlate.xml
new file mode 100644
index 0000000..582d95c
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/DiamonPlate.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Dynamic.xml b/bin/Autoload/LargeData/Materials/PBR/Dynamic.xml
new file mode 100644
index 0000000..898541e
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Dynamic.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Emissive.xml b/bin/Autoload/LargeData/Materials/PBR/Emissive.xml
new file mode 100644
index 0000000..0d58f36
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Emissive.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/EmissivePannel.xml b/bin/Autoload/LargeData/Materials/PBR/EmissivePannel.xml
new file mode 100644
index 0000000..2259c88
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/EmissivePannel.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic0.xml b/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic0.xml
new file mode 100644
index 0000000..5082e50
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic0.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic10.xml b/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic10.xml
new file mode 100644
index 0000000..7c7fa74
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic10.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic3.xml b/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic3.xml
new file mode 100644
index 0000000..586911b
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic3.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic5.xml b/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic5.xml
new file mode 100644
index 0000000..1bc2d44
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic5.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic7.xml b/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic7.xml
new file mode 100644
index 0000000..7b20659
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/HighRoughMetallic7.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/HoverBikeGlass.xml b/bin/Autoload/LargeData/Materials/PBR/HoverBikeGlass.xml
new file mode 100644
index 0000000..e596c7e
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/HoverBikeGlass.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/HoverBikeHull.xml b/bin/Autoload/LargeData/Materials/PBR/HoverBikeHull.xml
new file mode 100644
index 0000000..92913de
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/HoverBikeHull.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Lead.xml b/bin/Autoload/LargeData/Materials/PBR/Lead.xml
new file mode 100644
index 0000000..60387a0
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Lead.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Leather.xml b/bin/Autoload/LargeData/Materials/PBR/Leather.xml
new file mode 100644
index 0000000..f870e7b
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Leather.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Metallic0.xml b/bin/Autoload/LargeData/Materials/PBR/Metallic0.xml
new file mode 100644
index 0000000..cd69cf6
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Metallic0.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Metallic10.xml b/bin/Autoload/LargeData/Materials/PBR/Metallic10.xml
new file mode 100644
index 0000000..54ceecc
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Metallic10.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Metallic3.xml b/bin/Autoload/LargeData/Materials/PBR/Metallic3.xml
new file mode 100644
index 0000000..a9bc4e0
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Metallic3.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Metallic5.xml b/bin/Autoload/LargeData/Materials/PBR/Metallic5.xml
new file mode 100644
index 0000000..f743a0d
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Metallic5.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Metallic7.xml b/bin/Autoload/LargeData/Materials/PBR/Metallic7.xml
new file mode 100644
index 0000000..c73e441
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Metallic7.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Metallic9.xml b/bin/Autoload/LargeData/Materials/PBR/Metallic9.xml
new file mode 100644
index 0000000..801a315
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Metallic9.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/MetallicRough0.xml b/bin/Autoload/LargeData/Materials/PBR/MetallicRough0.xml
new file mode 100644
index 0000000..57950c9
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/MetallicRough0.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/MetallicRough10.xml b/bin/Autoload/LargeData/Materials/PBR/MetallicRough10.xml
new file mode 100644
index 0000000..23b35ad
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/MetallicRough10.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/MetallicRough3.xml b/bin/Autoload/LargeData/Materials/PBR/MetallicRough3.xml
new file mode 100644
index 0000000..01a71af
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/MetallicRough3.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/MetallicRough5.xml b/bin/Autoload/LargeData/Materials/PBR/MetallicRough5.xml
new file mode 100644
index 0000000..04f6718
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/MetallicRough5.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/MetallicRough7.xml b/bin/Autoload/LargeData/Materials/PBR/MetallicRough7.xml
new file mode 100644
index 0000000..f3d2181
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/MetallicRough7.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Mud.xml b/bin/Autoload/LargeData/Materials/PBR/Mud.xml
new file mode 100644
index 0000000..89578c1
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Mud.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Roughness0.xml b/bin/Autoload/LargeData/Materials/PBR/Roughness0.xml
new file mode 100644
index 0000000..875a28c
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Roughness0.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Roughness10.xml b/bin/Autoload/LargeData/Materials/PBR/Roughness10.xml
new file mode 100644
index 0000000..8214ef2
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Roughness10.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Roughness3.xml b/bin/Autoload/LargeData/Materials/PBR/Roughness3.xml
new file mode 100644
index 0000000..3ce3e60
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Roughness3.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Roughness5.xml b/bin/Autoload/LargeData/Materials/PBR/Roughness5.xml
new file mode 100644
index 0000000..fb97560
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Roughness5.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Roughness7.xml b/bin/Autoload/LargeData/Materials/PBR/Roughness7.xml
new file mode 100644
index 0000000..ca6042b
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Roughness7.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Sand.xml b/bin/Autoload/LargeData/Materials/PBR/Sand.xml
new file mode 100644
index 0000000..830546a
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Sand.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/PBR/Tile.xml b/bin/Autoload/LargeData/Materials/PBR/Tile.xml
new file mode 100644
index 0000000..8e33248
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/PBR/Tile.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Materials/Skybox2.xml b/bin/Autoload/LargeData/Materials/Skybox2.xml
new file mode 100644
index 0000000..41ad1db
--- /dev/null
+++ b/bin/Autoload/LargeData/Materials/Skybox2.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Models/HoverBike.mdl b/bin/Autoload/LargeData/Models/HoverBike.mdl
new file mode 100644
index 0000000..cf93e8c
Binary files /dev/null and b/bin/Autoload/LargeData/Models/HoverBike.mdl differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Check/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/Check/Albedo.jpg
new file mode 100644
index 0000000..ef34d11
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Check/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Check/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/Check/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/Check/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/Check/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/Check/Normal.jpg
new file mode 100644
index 0000000..0192546
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Check/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Check/Properties.jpg b/bin/Autoload/LargeData/Textures/PBR/Check/Properties.jpg
new file mode 100644
index 0000000..523da6f
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Check/Properties.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Concrete/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/Concrete/Albedo.jpg
new file mode 100644
index 0000000..873bee5
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Concrete/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Concrete/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/Concrete/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/Concrete/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/Concrete/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/Concrete/Normal.jpg
new file mode 100644
index 0000000..d999284
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Concrete/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Concrete/PBR.jpg b/bin/Autoload/LargeData/Textures/PBR/Concrete/PBR.jpg
new file mode 100644
index 0000000..0ffc24c
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Concrete/PBR.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Albedo.jpg
new file mode 100644
index 0000000..f73c65e
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Normal.jpg
new file mode 100644
index 0000000..11a0609
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Properties.jpg b/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Properties.jpg
new file mode 100644
index 0000000..3f66042
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/DiamonPlate/Properties.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Albedo.jpg
new file mode 100644
index 0000000..b0a86c3
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Emissive.jpg b/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Emissive.jpg
new file mode 100644
index 0000000..0dc1bd3
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Emissive.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Normal.jpg
new file mode 100644
index 0000000..bcd4052
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/PBR.jpg b/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/PBR.jpg
new file mode 100644
index 0000000..194cd7e
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Emissive Pannel/PBR.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Albedo.jpg
new file mode 100644
index 0000000..2cfe0de
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Normal.jpg
new file mode 100644
index 0000000..e4a4d8d
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Properties.jpg b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Properties.jpg
new file mode 100644
index 0000000..5585c57
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Glass/Properties.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Albedo.jpg
new file mode 100644
index 0000000..1507898
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Normal.jpg
new file mode 100644
index 0000000..0ab5a01
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Properties.jpg b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Properties.jpg
new file mode 100644
index 0000000..0830b3b
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/HoverBike/Hull/Properties.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Lead/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/Lead/Albedo.jpg
new file mode 100644
index 0000000..1f025a7
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Lead/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Lead/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/Lead/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/Lead/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/Lead/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/Lead/Normal.jpg
new file mode 100644
index 0000000..c80a357
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Lead/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Lead/Properties.jpg b/bin/Autoload/LargeData/Textures/PBR/Lead/Properties.jpg
new file mode 100644
index 0000000..f32fdda
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Lead/Properties.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Leather/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/Leather/Albedo.jpg
new file mode 100644
index 0000000..685e72b
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Leather/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Leather/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/Leather/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/Leather/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/Leather/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/Leather/Normal.jpg
new file mode 100644
index 0000000..8a1a315
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Leather/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Leather/Properties.jpg b/bin/Autoload/LargeData/Textures/PBR/Leather/Properties.jpg
new file mode 100644
index 0000000..d6110f7
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Leather/Properties.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Mud/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/Mud/Albedo.jpg
new file mode 100644
index 0000000..7fdce4d
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Mud/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Mud/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/Mud/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/Mud/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/Mud/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/Mud/Normal.jpg
new file mode 100644
index 0000000..31a1fba
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Mud/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Mud/Properties.jpg b/bin/Autoload/LargeData/Textures/PBR/Mud/Properties.jpg
new file mode 100644
index 0000000..3c8a6d2
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Mud/Properties.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/PBRTextureLicence.txt b/bin/Autoload/LargeData/Textures/PBR/PBRTextureLicence.txt
new file mode 100644
index 0000000..a6aec47
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/PBRTextureLicence.txt
@@ -0,0 +1,10 @@
+All PBR textures gathered from https://share.allegorithmic.com/
+
+Concrete Tiles by Allegorithmic (https://share.allegorithmic.com/libraries/729)
+Fallen Painted Metal(Pannel) by Käy Vriend (https://share.allegorithmic.com/libraries/499)
+Door Panel by isaacpapismado (https://share.allegorithmic.com/libraries/979)
+
+
+IBL texture gathered from HDRLab's sIBL Archive (http://www.hdrlabs.com/sibl/archive.html)
+
+Dieselpunk Moto by allexandr007 is licensed under CC Attribution https://skfb.ly/EOsI
\ No newline at end of file
diff --git a/bin/Autoload/LargeData/Textures/PBR/Pannel/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/Pannel/Albedo.jpg
new file mode 100644
index 0000000..ed619bd
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Pannel/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Pannel/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/Pannel/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/Pannel/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/Pannel/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/Pannel/Normal.jpg
new file mode 100644
index 0000000..e305eda
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Pannel/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Pannel/PBR.jpg b/bin/Autoload/LargeData/Textures/PBR/Pannel/PBR.jpg
new file mode 100644
index 0000000..4659ebe
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Pannel/PBR.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Sand/Albedo.jpg b/bin/Autoload/LargeData/Textures/PBR/Sand/Albedo.jpg
new file mode 100644
index 0000000..c957941
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Sand/Albedo.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Sand/Albedo.xml b/bin/Autoload/LargeData/Textures/PBR/Sand/Albedo.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/PBR/Sand/Albedo.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/PBR/Sand/Normal.jpg b/bin/Autoload/LargeData/Textures/PBR/Sand/Normal.jpg
new file mode 100644
index 0000000..61899bc
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Sand/Normal.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/PBR/Sand/Properties.jpg b/bin/Autoload/LargeData/Textures/PBR/Sand/Properties.jpg
new file mode 100644
index 0000000..5edc658
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/PBR/Sand/Properties.jpg differ
diff --git a/bin/Autoload/LargeData/Textures/Skybox2.xml b/bin/Autoload/LargeData/Textures/Skybox2.xml
new file mode 100644
index 0000000..1cc4963
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/Skybox2.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/T_ColorGrid.png b/bin/Autoload/LargeData/Textures/T_ColorGrid.png
new file mode 100644
index 0000000..dade7b4
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/T_ColorGrid.png differ
diff --git a/bin/Autoload/LargeData/Textures/T_ColorGrid.xml b/bin/Autoload/LargeData/Textures/T_ColorGrid.xml
new file mode 100644
index 0000000..0098cf1
--- /dev/null
+++ b/bin/Autoload/LargeData/Textures/T_ColorGrid.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/Autoload/LargeData/Textures/output_pmrem_negx.dds b/bin/Autoload/LargeData/Textures/output_pmrem_negx.dds
new file mode 100644
index 0000000..2859314
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_pmrem_negx.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_pmrem_negy.dds b/bin/Autoload/LargeData/Textures/output_pmrem_negy.dds
new file mode 100644
index 0000000..e4756ff
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_pmrem_negy.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_pmrem_negz.dds b/bin/Autoload/LargeData/Textures/output_pmrem_negz.dds
new file mode 100644
index 0000000..3fddf04
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_pmrem_negz.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_pmrem_posx.dds b/bin/Autoload/LargeData/Textures/output_pmrem_posx.dds
new file mode 100644
index 0000000..e11aca0
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_pmrem_posx.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_pmrem_posy.dds b/bin/Autoload/LargeData/Textures/output_pmrem_posy.dds
new file mode 100644
index 0000000..c0ceb3d
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_pmrem_posy.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_pmrem_posz.dds b/bin/Autoload/LargeData/Textures/output_pmrem_posz.dds
new file mode 100644
index 0000000..17e3d26
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_pmrem_posz.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_skybox_negx.dds b/bin/Autoload/LargeData/Textures/output_skybox_negx.dds
new file mode 100644
index 0000000..c9d2a7b
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_skybox_negx.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_skybox_negy.dds b/bin/Autoload/LargeData/Textures/output_skybox_negy.dds
new file mode 100644
index 0000000..14c4e18
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_skybox_negy.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_skybox_negz.dds b/bin/Autoload/LargeData/Textures/output_skybox_negz.dds
new file mode 100644
index 0000000..343fca6
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_skybox_negz.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_skybox_posx.dds b/bin/Autoload/LargeData/Textures/output_skybox_posx.dds
new file mode 100644
index 0000000..0f60b5b
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_skybox_posx.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_skybox_posy.dds b/bin/Autoload/LargeData/Textures/output_skybox_posy.dds
new file mode 100644
index 0000000..5011d52
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_skybox_posy.dds differ
diff --git a/bin/Autoload/LargeData/Textures/output_skybox_posz.dds b/bin/Autoload/LargeData/Textures/output_skybox_posz.dds
new file mode 100644
index 0000000..9f7d49d
Binary files /dev/null and b/bin/Autoload/LargeData/Textures/output_skybox_posz.dds differ
diff --git a/bin/ConvertModels.bat b/bin/ConvertModels.bat
new file mode 100644
index 0000000..f249507
--- /dev/null
+++ b/bin/ConvertModels.bat
@@ -0,0 +1,10 @@
+@echo off
+set /p ASSETDIR="Enter source assets dir: "
+cd /d "%~dp0"
+tool/OgreImporter %ASSETDIR%/Jack.mesh.xml Data/Models/Jack.mdl -t
+tool/OgreImporter %ASSETDIR%/Level.mesh.xml Data/Models/NinjaSnowWar/Level.mdl -t
+tool/OgreImporter %ASSETDIR%/Mushroom.mesh.xml Data/Models/Mushroom.mdl -t
+tool/OgreImporter %ASSETDIR%/Ninja.mesh.xml Data/Models/NinjaSnowWar/Ninja.mdl -t
+tool/OgreImporter %ASSETDIR%/Potion.mesh.xml Data/Models/NinjaSnowWar/Potion.mdl -t
+tool/OgreImporter %ASSETDIR%/SnowBall.mesh.xml Data/Models/NinjaSnowWar/SnowBall.mdl -t
+tool/OgreImporter %ASSETDIR%/SnowCrate.mesh.xml Data/Models/NinjaSnowWar/SnowCrate.mdl -t
diff --git a/bin/CoreData/Materials/DefaultGrey.xml b/bin/CoreData/Materials/DefaultGrey.xml
new file mode 100644
index 0000000..844c6df
--- /dev/null
+++ b/bin/CoreData/Materials/DefaultGrey.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/CoreData/RenderPaths/Deferred.xml b/bin/CoreData/RenderPaths/Deferred.xml
new file mode 100644
index 0000000..bd41960
--- /dev/null
+++ b/bin/CoreData/RenderPaths/Deferred.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/RenderPaths/DeferredHWDepth.xml b/bin/CoreData/RenderPaths/DeferredHWDepth.xml
new file mode 100644
index 0000000..74d6da3
--- /dev/null
+++ b/bin/CoreData/RenderPaths/DeferredHWDepth.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/RenderPaths/Forward.xml b/bin/CoreData/RenderPaths/Forward.xml
new file mode 100644
index 0000000..4cf0a58
--- /dev/null
+++ b/bin/CoreData/RenderPaths/Forward.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/CoreData/RenderPaths/ForwardDepth.xml b/bin/CoreData/RenderPaths/ForwardDepth.xml
new file mode 100644
index 0000000..7cc68a6
--- /dev/null
+++ b/bin/CoreData/RenderPaths/ForwardDepth.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/CoreData/RenderPaths/ForwardHWDepth.xml b/bin/CoreData/RenderPaths/ForwardHWDepth.xml
new file mode 100644
index 0000000..7a518c8
--- /dev/null
+++ b/bin/CoreData/RenderPaths/ForwardHWDepth.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/CoreData/RenderPaths/PBRDeferred.xml b/bin/CoreData/RenderPaths/PBRDeferred.xml
new file mode 100644
index 0000000..28bb90c
--- /dev/null
+++ b/bin/CoreData/RenderPaths/PBRDeferred.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/RenderPaths/PBRDeferredHWDepth.xml b/bin/CoreData/RenderPaths/PBRDeferredHWDepth.xml
new file mode 100644
index 0000000..fff4109
--- /dev/null
+++ b/bin/CoreData/RenderPaths/PBRDeferredHWDepth.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/RenderPaths/Prepass.xml b/bin/CoreData/RenderPaths/Prepass.xml
new file mode 100644
index 0000000..0ff3280
--- /dev/null
+++ b/bin/CoreData/RenderPaths/Prepass.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/RenderPaths/PrepassHDR.xml b/bin/CoreData/RenderPaths/PrepassHDR.xml
new file mode 100644
index 0000000..2cb0b8f
--- /dev/null
+++ b/bin/CoreData/RenderPaths/PrepassHDR.xml
@@ -0,0 +1,3 @@
+
+ rgba16f
+
diff --git a/bin/CoreData/RenderPaths/PrepassHWDepth.xml b/bin/CoreData/RenderPaths/PrepassHWDepth.xml
new file mode 100644
index 0000000..3d40e45
--- /dev/null
+++ b/bin/CoreData/RenderPaths/PrepassHWDepth.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Shaders/GLSL/AutoExposure.glsl b/bin/CoreData/Shaders/GLSL/AutoExposure.glsl
new file mode 100644
index 0000000..4f6be60
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/AutoExposure.glsl
@@ -0,0 +1,73 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "PostProcess.glsl"
+
+varying vec2 vTexCoord;
+varying vec2 vScreenPos;
+
+#ifdef COMPILEPS
+uniform float cAutoExposureAdaptRate;
+uniform vec2 cAutoExposureLumRange;
+uniform float cAutoExposureMiddleGrey;
+uniform vec2 cHDR128InvSize;
+uniform vec2 cLum64InvSize;
+uniform vec2 cLum16InvSize;
+uniform vec2 cLum4InvSize;
+
+float GatherAvgLum(sampler2D texSampler, vec2 texCoord, vec2 texelSize)
+{
+ float lumAvg = 0.0;
+ lumAvg += texture2D(texSampler, texCoord + vec2(1.0, -1.0) * texelSize).r;
+ lumAvg += texture2D(texSampler, texCoord + vec2(-1.0, 1.0) * texelSize).r;
+ lumAvg += texture2D(texSampler, texCoord + vec2(1.0, 1.0) * texelSize).r;
+ lumAvg += texture2D(texSampler, texCoord + vec2(1.0, -1.0) * texelSize).r;
+ return lumAvg / 4.0;
+}
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vTexCoord = GetQuadTexCoord(gl_Position);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ #ifdef LUMINANCE64
+ float logLumSum = 0.0;
+ logLumSum += log(dot(texture2D(sDiffMap, vTexCoord + vec2(-1.0, -1.0) * cHDR128InvSize).rgb, LumWeights) + 1e-5);
+ logLumSum += log(dot(texture2D(sDiffMap, vTexCoord + vec2(-1.0, 1.0) * cHDR128InvSize).rgb, LumWeights) + 1e-5);
+ logLumSum += log(dot(texture2D(sDiffMap, vTexCoord + vec2(1.0, 1.0) * cHDR128InvSize).rgb, LumWeights) + 1e-5);
+ logLumSum += log(dot(texture2D(sDiffMap, vTexCoord + vec2(1.0, -1.0) * cHDR128InvSize).rgb, LumWeights) + 1e-5);
+ gl_FragColor.r = logLumSum;
+ #endif
+
+ #ifdef LUMINANCE16
+ gl_FragColor.r = GatherAvgLum(sDiffMap, vTexCoord, cLum64InvSize);
+ #endif
+
+ #ifdef LUMINANCE4
+ gl_FragColor.r = GatherAvgLum(sDiffMap, vTexCoord, cLum16InvSize);
+ #endif
+
+ #ifdef LUMINANCE1
+ gl_FragColor.r = exp(GatherAvgLum(sDiffMap, vTexCoord, cLum4InvSize) / 16.0);
+ #endif
+
+ #ifdef ADAPTLUMINANCE
+ float adaptedLum = texture2D(sDiffMap, vTexCoord).r;
+ float lum = clamp(texture2D(sNormalMap, vTexCoord).r, cAutoExposureLumRange.x, cAutoExposureLumRange.y);
+ gl_FragColor.r = adaptedLum + (lum - adaptedLum) * (1.0 - exp(-cDeltaTimePS * cAutoExposureAdaptRate));
+ #endif
+
+ #ifdef EXPOSE
+ vec3 color = texture2D(sDiffMap, vScreenPos).rgb;
+ float adaptedLum = texture2D(sNormalMap, vTexCoord).r;
+ gl_FragColor = vec4(color * (cAutoExposureMiddleGrey / adaptedLum), 1.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/BRDF.glsl b/bin/CoreData/Shaders/GLSL/BRDF.glsl
new file mode 100644
index 0000000..b30bd05
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/BRDF.glsl
@@ -0,0 +1,164 @@
+#line 20001
+#ifdef COMPILEPS
+ #ifdef PBR
+ // Following BRDF methods are based upon research Frostbite EA
+ //[Lagrade et al. 2014, "Moving Frostbite to Physically Based Rendering"]
+
+ //Schlick Fresnel
+ //specular = the rgb specular color value of the pixel
+ //VdotH = the dot product of the camera view direction and the half vector
+ vec3 SchlickFresnel(vec3 specular, float VdotH)
+ {
+ return specular + (vec3(1.0, 1.0, 1.0) - specular) * pow(1.0 - VdotH, 5.0);
+ }
+
+ //Schlick Gaussian Fresnel
+ //specular = the rgb specular color value of the pixel
+ //VdotH = the dot product of the camera view direction and the half vector
+ vec3 SchlickGaussianFresnel(in vec3 specular, in float VdotH)
+ {
+ float sphericalGaussian = pow(2.0, (-5.55473 * VdotH - 6.98316) * VdotH);
+ return specular + (vec3(1.0, 1.0, 1.0) - specular) * sphericalGaussian;
+ }
+
+ vec3 SchlickFresnelCustom(vec3 specular, float LdotH)
+ {
+ float ior = 0.25;
+ float airIor = 1.000277;
+ float f0 = (ior - airIor) / (ior + airIor);
+ float max_ior = 2.5;
+ f0 = clamp(f0 * f0, 0.0, (max_ior - airIor) / (max_ior + airIor));
+ return specular * (f0 + (1 - f0) * pow(2, (-5.55473 * LdotH - 6.98316) * LdotH));
+ }
+
+ //Get Fresnel
+ //specular = the rgb specular color value of the pixel
+ //VdotH = the dot product of the camera view direction and the half vector
+ vec3 Fresnel(vec3 specular, float VdotH, float LdotH)
+ {
+ return SchlickFresnelCustom(specular, LdotH);
+ //return SchlickFresnel(specular, VdotH);
+ }
+
+ // Smith GGX corrected Visibility
+ // NdotL = the dot product of the normal and direction to the light
+ // NdotV = the dot product of the normal and the camera view direction
+ // roughness = the roughness of the pixel
+ float SmithGGXSchlickVisibility(float NdotL, float NdotV, float roughness)
+ {
+ float rough2 = roughness * roughness;
+ float lambdaV = NdotL * sqrt((-NdotV * rough2 + NdotV) * NdotV + rough2);
+ float lambdaL = NdotV * sqrt((-NdotL * rough2 + NdotL) * NdotL + rough2);
+
+ return 0.5 / (lambdaV + lambdaL);
+ }
+
+ float NeumannVisibility(float NdotV, float NdotL)
+ {
+ return NdotL * NdotV / max(1e-7, max(NdotL, NdotV));
+ }
+
+ // Get Visibility
+ // NdotL = the dot product of the normal and direction to the light
+ // NdotV = the dot product of the normal and the camera view direction
+ // roughness = the roughness of the pixel
+ float Visibility(float NdotL, float NdotV, float roughness)
+ {
+ return NeumannVisibility(NdotV, NdotL);
+ //return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
+ }
+
+ // Blinn Distribution
+ // NdotH = the dot product of the normal and the half vector
+ // roughness = the roughness of the pixel
+ float BlinnPhongDistribution(in float NdotH, in float roughness)
+ {
+ float specPower = max((2.0 / (roughness * roughness)) - 2.0, 1e-4); // Calculate specular power from roughness
+ return pow(clamp(NdotH, 0.0, 1.0), specPower);
+ }
+
+ // Beckmann Distribution
+ // NdotH = the dot product of the normal and the half vector
+ // roughness = the roughness of the pixel
+ float BeckmannDistribution(in float NdotH, in float roughness)
+ {
+ float rough2 = roughness * roughness;
+ float roughnessA = 1.0 / (4.0 * rough2 * pow(NdotH, 4.0));
+ float roughnessB = NdotH * NdotH - 1.0;
+ float roughnessC = rough2 * NdotH * NdotH;
+ return roughnessA * exp(roughnessB / roughnessC);
+ }
+
+ // GGX Distribution
+ // NdotH = the dot product of the normal and the half vector
+ // roughness = the roughness of the pixel
+ float GGXDistribution(float NdotH, float roughness)
+ {
+ float rough2 = roughness * roughness;
+ float tmp = (NdotH * rough2 - NdotH) * NdotH + 1.0;
+ return rough2 / (tmp * tmp);
+ }
+
+ // Get Distribution
+ // NdotH = the dot product of the normal and the half vector
+ // roughness = the roughness of the pixel
+ float Distribution(float NdotH, float roughness)
+ {
+ return GGXDistribution(NdotH, roughness);
+ }
+
+ // Lambertian Diffuse
+ // diffuseColor = the rgb color value of the pixel
+ // roughness = the roughness of the pixel
+ // NdotV = the normal dot with the camera view direction
+ // NdotL = the normal dot with the light direction
+ // VdotH = the camera view direction dot with the half vector
+ vec3 LambertianDiffuse(vec3 diffuseColor)
+ {
+ return diffuseColor * (1.0 / M_PI) ;
+ }
+
+ // Custom Lambertian Diffuse
+ // diffuseColor = the rgb color value of the pixel
+ // roughness = the roughness of the pixel
+ // NdotV = the normal dot with the camera view direction
+ // NdotL = the normal dot with the light direction
+ // VdotH = the camera view direction dot with the half vector
+ vec3 CustomLambertianDiffuse(vec3 diffuseColor, float NdotV, float roughness)
+ {
+ return diffuseColor * (1.0 / M_PI) * pow(NdotV, 0.5 + 0.3 * roughness);
+ }
+
+ // Burley Diffuse
+ // diffuseColor = the rgb color value of the pixel
+ // roughness = the roughness of the pixel
+ // NdotV = the normal dot with the camera view direction
+ // NdotL = the normal dot with the light direction
+ // VdotH = the camera view direction dot with the half vector
+ vec3 BurleyDiffuse(vec3 diffuseColor, float roughness, float NdotV, float NdotL, float VdotH)
+ {
+ float energyBias = mix(roughness, 0.0, 0.5);
+ float energyFactor = mix(roughness, 1.0, 1.0 / 1.51);
+ float fd90 = energyBias + 2.0 * VdotH * VdotH * roughness;
+ float f0 = 1.0;
+ float lightScatter = f0 + (fd90 - f0) * pow(1.0 - NdotL, 5.0);
+ float viewScatter = f0 + (fd90 - f0) * pow(1.0 - NdotV, 5.0);
+
+ return diffuseColor * lightScatter * viewScatter * energyFactor;
+ }
+
+ //Get Diffuse
+ // diffuseColor = the rgb color value of the pixel
+ // roughness = the roughness of the pixel
+ // NdotV = the normal dot with the camera view direction
+ // NdotL = the normal dot with the light direction
+ // VdotH = the camera view direction dot with the half vector
+ vec3 Diffuse(vec3 diffuseColor, float roughness, float NdotV, float NdotL, float VdotH)
+ {
+ //return LambertianDiffuse(diffuseColor);
+ return CustomLambertianDiffuse(diffuseColor, NdotV, roughness);
+ //return BurleyDiffuse(diffuseColor, roughness, NdotV, NdotL, VdotH);
+ }
+
+ #endif
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/Basic.glsl b/bin/CoreData/Shaders/GLSL/Basic.glsl
new file mode 100644
index 0000000..fe6b937
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Basic.glsl
@@ -0,0 +1,53 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+
+#if defined(DIFFMAP) || defined(ALPHAMAP)
+ varying vec2 vTexCoord;
+#endif
+#ifdef VERTEXCOLOR
+ varying vec4 vColor;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+
+ #ifdef DIFFMAP
+ vTexCoord = iTexCoord;
+ #endif
+ #ifdef VERTEXCOLOR
+ vColor = iColor;
+ #endif
+}
+
+void PS()
+{
+ vec4 diffColor = cMatDiffColor;
+
+ #ifdef VERTEXCOLOR
+ diffColor *= vColor;
+ #endif
+
+ #if (!defined(DIFFMAP)) && (!defined(ALPHAMAP))
+ gl_FragColor = diffColor;
+ #endif
+ #ifdef DIFFMAP
+ vec4 diffInput = texture2D(sDiffMap, vTexCoord);
+ #ifdef ALPHAMASK
+ if (diffInput.a < 0.5)
+ discard;
+ #endif
+ gl_FragColor = diffColor * diffInput;
+ #endif
+ #ifdef ALPHAMAP
+ #ifdef GL3
+ float alphaInput = texture2D(sDiffMap, vTexCoord).r;
+ #else
+ float alphaInput = texture2D(sDiffMap, vTexCoord).a;
+ #endif
+ gl_FragColor = vec4(diffColor.rgb, diffColor.a * alphaInput);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/Bloom.glsl b/bin/CoreData/Shaders/GLSL/Bloom.glsl
new file mode 100644
index 0000000..12e5122
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Bloom.glsl
@@ -0,0 +1,57 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+
+varying vec2 vTexCoord;
+varying vec2 vScreenPos;
+
+#ifdef COMPILEPS
+uniform float cBloomThreshold;
+uniform vec2 cBloomMix;
+uniform vec2 cBlurHInvSize;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vTexCoord = GetQuadTexCoord(gl_Position);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ #ifdef BRIGHT
+ vec3 rgb = texture2D(sDiffMap, vScreenPos).rgb;
+ gl_FragColor = vec4((rgb - vec3(cBloomThreshold, cBloomThreshold, cBloomThreshold)) / (1.0 - cBloomThreshold), 1.0);
+ #endif
+
+ #ifdef BLURH
+ vec3 rgb = texture2D(sDiffMap, vTexCoord + vec2(-2.0, 0.0) * cBlurHInvSize).rgb * 0.1;
+ rgb += texture2D(sDiffMap, vTexCoord + vec2(-1.0, 0.0) * cBlurHInvSize).rgb * 0.25;
+ rgb += texture2D(sDiffMap, vTexCoord + vec2(0.0, 0.0) * cBlurHInvSize).rgb * 0.3;
+ rgb += texture2D(sDiffMap, vTexCoord + vec2(1.0, 0.0) * cBlurHInvSize).rgb * 0.25;
+ rgb += texture2D(sDiffMap, vTexCoord + vec2(2.0, 0.0) * cBlurHInvSize).rgb * 0.1;
+ gl_FragColor = vec4(rgb, 1.0);
+ #endif
+
+ #ifdef BLURV
+ vec3 rgb = texture2D(sDiffMap, vTexCoord + vec2(0.0, -2.0) * cBlurHInvSize).rgb * 0.1;
+ rgb += texture2D(sDiffMap, vTexCoord + vec2(0.0, -1.0) * cBlurHInvSize).rgb * 0.25;
+ rgb += texture2D(sDiffMap, vTexCoord + vec2(0.0, 0.0) * cBlurHInvSize).rgb * 0.3;
+ rgb += texture2D(sDiffMap, vTexCoord + vec2(0.0, 1.0) * cBlurHInvSize).rgb * 0.25;
+ rgb += texture2D(sDiffMap, vTexCoord + vec2(0.0, 2.0) * cBlurHInvSize).rgb * 0.1;
+ gl_FragColor = vec4(rgb, 1.0);
+ #endif
+
+ #ifdef COMBINE
+ vec3 original = texture2D(sDiffMap, vScreenPos).rgb * cBloomMix.x;
+ vec3 bloom = texture2D(sNormalMap, vTexCoord).rgb * cBloomMix.y;
+ // Prevent oversaturation
+ original *= max(vec3(1.0) - bloom, vec3(0.0));
+ gl_FragColor = vec4(original + bloom, 1.0);
+ #endif
+}
+
diff --git a/bin/CoreData/Shaders/GLSL/BloomHDR.glsl b/bin/CoreData/Shaders/GLSL/BloomHDR.glsl
new file mode 100644
index 0000000..0150fb5
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/BloomHDR.glsl
@@ -0,0 +1,73 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "PostProcess.glsl"
+
+varying vec2 vTexCoord;
+varying vec2 vScreenPos;
+
+#ifdef COMPILEPS
+uniform float cBloomHDRThreshold;
+uniform float cBloomHDRBlurSigma;
+uniform float cBloomHDRBlurRadius;
+uniform vec2 cBloomHDRBlurDir;
+uniform vec2 cBloomHDRMix;
+uniform vec2 cBright2InvSize;
+uniform vec2 cBright4InvSize;
+uniform vec2 cBright8InvSize;
+uniform vec2 cBright16InvSize;
+
+const int BlurKernelSize = 5;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vTexCoord = GetQuadTexCoord(gl_Position);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ #ifdef BRIGHT
+ vec3 color = texture2D(sDiffMap, vScreenPos).rgb;
+ gl_FragColor = vec4(max(color - cBloomHDRThreshold, 0.0), 1.0);
+ #endif
+
+ #ifdef BLUR16
+ gl_FragColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright16InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, sDiffMap, vScreenPos);
+ #endif
+
+ #ifdef BLUR8
+ gl_FragColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright8InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, sDiffMap, vScreenPos);
+ #endif
+
+ #ifdef BLUR4
+ gl_FragColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright4InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, sDiffMap, vScreenPos);
+ #endif
+
+ #ifdef BLUR2
+ gl_FragColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright2InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, sDiffMap, vScreenPos);
+ #endif
+
+ #ifdef COMBINE16
+ gl_FragColor = texture2D(sDiffMap, vScreenPos) + texture2D(sNormalMap, vTexCoord);
+ #endif
+
+ #ifdef COMBINE8
+ gl_FragColor = texture2D(sDiffMap, vScreenPos) + texture2D(sNormalMap, vTexCoord);
+ #endif
+
+ #ifdef COMBINE4
+ gl_FragColor = texture2D(sDiffMap, vScreenPos) + texture2D(sNormalMap, vTexCoord);
+ #endif
+
+ #ifdef COMBINE2
+ vec3 color = texture2D(sDiffMap, vScreenPos).rgb * cBloomHDRMix.x;
+ vec3 bloom = texture2D(sNormalMap, vTexCoord).rgb * cBloomHDRMix.y;
+ gl_FragColor = vec4(color + bloom, 1.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/Blur.glsl b/bin/CoreData/Shaders/GLSL/Blur.glsl
new file mode 100644
index 0000000..909aa71
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Blur.glsl
@@ -0,0 +1,43 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "PostProcess.glsl"
+
+varying vec2 vTexCoord;
+varying vec2 vScreenPos;
+
+#ifdef COMPILEPS
+uniform vec2 cBlurDir;
+uniform float cBlurRadius;
+uniform float cBlurSigma;
+uniform vec2 cBlurHInvSize;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vTexCoord = GetQuadTexCoord(gl_Position);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ #ifdef BLUR3
+ gl_FragColor = GaussianBlur(3, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, sDiffMap, vTexCoord);
+ #endif
+
+ #ifdef BLUR5
+ gl_FragColor = GaussianBlur(5, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, sDiffMap, vTexCoord);
+ #endif
+
+ #ifdef BLUR7
+ gl_FragColor = GaussianBlur(7, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, sDiffMap, vTexCoord);
+ #endif
+
+ #ifdef BLUR9
+ gl_FragColor = GaussianBlur(9, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, sDiffMap, vTexCoord);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/ColorCorrection.glsl b/bin/CoreData/Shaders/GLSL/ColorCorrection.glsl
new file mode 100644
index 0000000..0004e2b
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/ColorCorrection.glsl
@@ -0,0 +1,21 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "PostProcess.glsl"
+
+varying vec2 vScreenPos;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ vec3 color = texture2D(sDiffMap, vScreenPos).rgb;
+ gl_FragColor = vec4(ColorCorrection(color, sVolumeMap), 1.0);
+}
diff --git a/bin/CoreData/Shaders/GLSL/Constants.glsl b/bin/CoreData/Shaders/GLSL/Constants.glsl
new file mode 100644
index 0000000..a2d6c67
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Constants.glsl
@@ -0,0 +1,7 @@
+#define M_PI 3.14159265358979323846
+#define M_EPSILON 0.0001
+
+#ifdef PBR
+#define ROUGHNESS_FLOOR 0.004
+#define METALNESS_FLOOR 0.03
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/CopyFramebuffer.glsl b/bin/CoreData/Shaders/GLSL/CopyFramebuffer.glsl
new file mode 100644
index 0000000..bda7b58
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/CopyFramebuffer.glsl
@@ -0,0 +1,20 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+
+varying vec2 vScreenPos;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ gl_FragColor = texture2D(sDiffMap, vScreenPos);
+}
+
diff --git a/bin/CoreData/Shaders/GLSL/DeferredLight.glsl b/bin/CoreData/Shaders/GLSL/DeferredLight.glsl
new file mode 100644
index 0000000..c5e9e49
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/DeferredLight.glsl
@@ -0,0 +1,100 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Lighting.glsl"
+
+#ifdef DIRLIGHT
+ varying vec2 vScreenPos;
+#else
+ varying vec4 vScreenPos;
+#endif
+varying vec3 vFarRay;
+#ifdef ORTHO
+ varying vec3 vNearRay;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ #ifdef DIRLIGHT
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+ vFarRay = GetFarRay(gl_Position);
+ #ifdef ORTHO
+ vNearRay = GetNearRay(gl_Position);
+ #endif
+ #else
+ vScreenPos = GetScreenPos(gl_Position);
+ vFarRay = GetFarRay(gl_Position) * gl_Position.w;
+ #ifdef ORTHO
+ vNearRay = GetNearRay(gl_Position) * gl_Position.w;
+ #endif
+ #endif
+}
+
+
+void PS()
+{
+ // If rendering a directional light quad, optimize out the w divide
+ #ifdef DIRLIGHT
+ #ifdef HWDEPTH
+ float depth = ReconstructDepth(texture2D(sDepthBuffer, vScreenPos).r);
+ #else
+ float depth = DecodeDepth(texture2D(sDepthBuffer, vScreenPos).rgb);
+ #endif
+ #ifdef ORTHO
+ vec3 worldPos = mix(vNearRay, vFarRay, depth);
+ #else
+ vec3 worldPos = vFarRay * depth;
+ #endif
+ vec4 albedoInput = texture2D(sAlbedoBuffer, vScreenPos);
+ vec4 normalInput = texture2D(sNormalBuffer, vScreenPos);
+ #else
+ #ifdef HWDEPTH
+ float depth = ReconstructDepth(texture2DProj(sDepthBuffer, vScreenPos).r);
+ #else
+ float depth = DecodeDepth(texture2DProj(sDepthBuffer, vScreenPos).rgb);
+ #endif
+ #ifdef ORTHO
+ vec3 worldPos = mix(vNearRay, vFarRay, depth) / vScreenPos.w;
+ #else
+ vec3 worldPos = vFarRay * depth / vScreenPos.w;
+ #endif
+ vec4 albedoInput = texture2DProj(sAlbedoBuffer, vScreenPos);
+ vec4 normalInput = texture2DProj(sNormalBuffer, vScreenPos);
+ #endif
+
+ // Position acquired via near/far ray is relative to camera. Bring position to world space
+ vec3 eyeVec = -worldPos;
+ worldPos += cCameraPosPS;
+
+ vec3 normal = normalize(normalInput.rgb * 2.0 - 1.0);
+ vec4 projWorldPos = vec4(worldPos, 1.0);
+ vec3 lightColor;
+ vec3 lightDir;
+
+ float diff = GetDiffuse(normal, worldPos, lightDir);
+
+ #ifdef SHADOW
+ diff *= GetShadowDeferred(projWorldPos, normal, depth);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ vec4 spotPos = projWorldPos * cLightMatricesPS[0];
+ lightColor = spotPos.w > 0.0 ? texture2DProj(sLightSpotMap, spotPos).rgb * cLightColor.rgb : vec3(0.0);
+ #elif defined(CUBEMASK)
+ mat3 lightVecRot = mat3(cLightMatricesPS[0][0].xyz, cLightMatricesPS[0][1].xyz, cLightMatricesPS[0][2].xyz);
+ lightColor = textureCube(sLightCubeMap, (worldPos - cLightPosPS.xyz) * lightVecRot).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ #ifdef SPECULAR
+ float spec = GetSpecular(normal, eyeVec, lightDir, normalInput.a * 255.0);
+ gl_FragColor = diff * vec4(lightColor * (albedoInput.rgb + spec * cLightColor.a * albedoInput.aaa), 0.0);
+ #else
+ gl_FragColor = diff * vec4(lightColor * albedoInput.rgb, 0.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/Depth.glsl b/bin/CoreData/Shaders/GLSL/Depth.glsl
new file mode 100644
index 0000000..6840c45
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Depth.glsl
@@ -0,0 +1,24 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+
+varying vec3 vTexCoord;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vTexCoord = vec3(GetTexCoord(iTexCoord), GetDepth(gl_Position));
+}
+
+void PS()
+{
+ #ifdef ALPHAMASK
+ float alpha = texture2D(sDiffMap, vTexCoord.xy).a;
+ if (alpha < 0.5)
+ discard;
+ #endif
+
+ gl_FragColor = vec4(EncodeDepth(vTexCoord.z), 1.0);
+}
diff --git a/bin/CoreData/Shaders/GLSL/FXAA2.glsl b/bin/CoreData/Shaders/GLSL/FXAA2.glsl
new file mode 100644
index 0000000..eedb477
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/FXAA2.glsl
@@ -0,0 +1,88 @@
+/*============================================================================
+
+ FXAA v2 CONSOLE by TIMOTHY LOTTES @ NVIDIA
+
+============================================================================*/
+
+// Adapted for Urho3D from http://timothylottes.blogspot.com/2011/04/nvidia-fxaa-ii-for-console.html
+
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+
+varying vec2 vScreenPos;
+
+#ifdef COMPILEPS
+uniform vec3 cFXAAParams;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ float FXAA_SUBPIX_SHIFT = 1.0/4.0; // Not used
+ float FXAA_SPAN_MAX = 8.0;
+ float FXAA_REDUCE_MUL = 1.0/8.0;
+ float FXAA_REDUCE_MIN = 1.0/128.0;
+
+ vec2 posOffset = cGBufferInvSize.xy * cFXAAParams.x;
+
+ vec3 rgbNW = texture2D(sDiffMap, vScreenPos + vec2(-posOffset.x, -posOffset.y)).rgb;
+ vec3 rgbNE = texture2D(sDiffMap, vScreenPos + vec2(posOffset.x, -posOffset.y)).rgb;
+ vec3 rgbSW = texture2D(sDiffMap, vScreenPos + vec2(-posOffset.x, posOffset.y)).rgb;
+ vec3 rgbSE = texture2D(sDiffMap, vScreenPos + vec2(posOffset.x, posOffset.y)).rgb;
+ vec3 rgbM = texture2D(sDiffMap, vScreenPos).rgb;
+
+ vec3 luma = vec3(0.299, 0.587, 0.114);
+ float lumaNW = dot(rgbNW, luma);
+ float lumaNE = dot(rgbNE, luma);
+ float lumaSW = dot(rgbSW, luma);
+ float lumaSE = dot(rgbSE, luma);
+ float lumaM = dot(rgbM, luma);
+
+ float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
+ float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
+
+ if (((lumaMax - lumaMin) / lumaMin) >= cFXAAParams.y)
+ {
+ vec2 dir;
+ dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
+ dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
+
+ float dirReduce = max(
+ (lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL),
+ FXAA_REDUCE_MIN);
+ float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce);
+ dir = min(vec2( FXAA_SPAN_MAX, FXAA_SPAN_MAX),
+ max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
+ dir * rcpDirMin)) * cGBufferInvSize.xy;
+
+ dir *= cFXAAParams.z;
+
+ vec3 rgbA = (1.0/2.0) * (
+ texture2D(sDiffMap, vScreenPos + dir * (1.0/3.0 - 0.5)).xyz +
+ texture2D(sDiffMap, vScreenPos + dir * (2.0/3.0 - 0.5)).xyz);
+ vec3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * (
+ texture2D(sDiffMap, vScreenPos + dir * (0.0/3.0 - 0.5)).xyz +
+ texture2D(sDiffMap, vScreenPos + dir * (3.0/3.0 - 0.5)).xyz);
+ float lumaB = dot(rgbB, luma);
+
+ vec3 rgbOut;
+ if((lumaB < lumaMin) || (lumaB > lumaMax))
+ rgbOut = rgbA;
+ else
+ rgbOut = rgbB;
+
+ gl_FragColor = vec4(rgbOut, 1.0);
+ }
+ else
+ gl_FragColor = vec4(rgbM, 1.0);
+}
+
diff --git a/bin/CoreData/Shaders/GLSL/FXAA3.glsl b/bin/CoreData/Shaders/GLSL/FXAA3.glsl
new file mode 100644
index 0000000..27fe64d
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/FXAA3.glsl
@@ -0,0 +1,747 @@
+//----------------------------------------------------------------------------------
+//
+// Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of NVIDIA CORPORATION nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+//----------------------------------------------------------------------------------
+/*============================================================================
+
+
+ NVIDIA FXAA 3.11 by TIMOTHY LOTTES
+
+------------------------------------------------------------------------------
+
+ Modified for Urho3D
+
+============================================================================*/
+
+/*==========================================================================*/
+//
+// Urho3D specific preparations
+//
+/*--------------------------------------------------------------------------*/
+
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+
+varying vec2 vScreenPos;
+
+/*==========================================================================*/
+//
+// Shader Requirements
+//
+/*--------------------------------------------------------------------------*/
+
+#define FXAA_FAST_PIXEL_OFFSET 0
+
+/*--------------------------------------------------------------------------*/
+
+#if (FXAA_FAST_PIXEL_OFFSET == 1)
+ #define Fxaa_vec2 ivec2
+#else
+ #define Fxaa_vec2 vec2
+#endif
+
+/*============================================================================
+ FXAA QUALITY - TUNING KNOBS
+------------------------------------------------------------------------------
+NOTE the other tuning knobs are now in the shader function inputs!
+============================================================================*/
+#ifndef FXAA_QUALITY_PRESET
+ //
+ // Choose the quality preset.
+ // This needs to be compiled into the shader as it effects code.
+ // Best option to include multiple presets is to
+ // in each shader define the preset, then include this file.
+ //
+ // OPTIONS
+ // -----------------------------------------------------------------------
+ // 10 to 15 - default medium dither (10=fastest, 15=highest quality)
+ // 20 to 29 - less dither, more expensive (20=fastest, 29=highest quality)
+ // 39 - no dither, very expensive
+ //
+ // NOTES
+ // -----------------------------------------------------------------------
+ // 12 = slightly faster then FXAA 3.9 and higher edge quality (default)
+ // 13 = about same speed as FXAA 3.9 and better than 12
+ // 23 = closest to FXAA 3.9 visually and performance wise
+ // _ = the lowest digit is directly related to performance
+ // _ = the highest digit is directly related to style
+ //
+ #define FXAA_QUALITY_PRESET 12
+#endif
+
+
+/*============================================================================
+
+ FXAA QUALITY - PRESETS
+
+============================================================================*/
+
+/*============================================================================
+ FXAA QUALITY - MEDIUM DITHER PRESETS
+============================================================================*/
+#if (FXAA_QUALITY_PRESET == 10)
+ #define FXAA_QUALITY_PS 3
+ #define FXAA_QUALITY_P0 1.5
+ #define FXAA_QUALITY_P1 3.0
+ #define FXAA_QUALITY_P2 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 11)
+ #define FXAA_QUALITY_PS 4
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 3.0
+ #define FXAA_QUALITY_P3 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 12)
+ #define FXAA_QUALITY_PS 5
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 4.0
+ #define FXAA_QUALITY_P4 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 13)
+ #define FXAA_QUALITY_PS 6
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 4.0
+ #define FXAA_QUALITY_P5 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 14)
+ #define FXAA_QUALITY_PS 7
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 4.0
+ #define FXAA_QUALITY_P6 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 15)
+ #define FXAA_QUALITY_PS 8
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 4.0
+ #define FXAA_QUALITY_P7 12.0
+#endif
+
+/*============================================================================
+ FXAA QUALITY - LOW DITHER PRESETS
+============================================================================*/
+#if (FXAA_QUALITY_PRESET == 20)
+ #define FXAA_QUALITY_PS 3
+ #define FXAA_QUALITY_P0 1.5
+ #define FXAA_QUALITY_P1 2.0
+ #define FXAA_QUALITY_P2 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 21)
+ #define FXAA_QUALITY_PS 4
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 22)
+ #define FXAA_QUALITY_PS 5
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 23)
+ #define FXAA_QUALITY_PS 6
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 24)
+ #define FXAA_QUALITY_PS 7
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 3.0
+ #define FXAA_QUALITY_P6 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 25)
+ #define FXAA_QUALITY_PS 8
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 4.0
+ #define FXAA_QUALITY_P7 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 26)
+ #define FXAA_QUALITY_PS 9
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 2.0
+ #define FXAA_QUALITY_P7 4.0
+ #define FXAA_QUALITY_P8 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 27)
+ #define FXAA_QUALITY_PS 10
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 2.0
+ #define FXAA_QUALITY_P7 2.0
+ #define FXAA_QUALITY_P8 4.0
+ #define FXAA_QUALITY_P9 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 28)
+ #define FXAA_QUALITY_PS 11
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 2.0
+ #define FXAA_QUALITY_P7 2.0
+ #define FXAA_QUALITY_P8 2.0
+ #define FXAA_QUALITY_P9 4.0
+ #define FXAA_QUALITY_P10 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 29)
+ #define FXAA_QUALITY_PS 12
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 2.0
+ #define FXAA_QUALITY_P7 2.0
+ #define FXAA_QUALITY_P8 2.0
+ #define FXAA_QUALITY_P9 2.0
+ #define FXAA_QUALITY_P10 4.0
+ #define FXAA_QUALITY_P11 8.0
+#endif
+
+/*============================================================================
+ FXAA QUALITY - EXTREME QUALITY
+============================================================================*/
+#if (FXAA_QUALITY_PRESET == 39)
+ #define FXAA_QUALITY_PS 12
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.0
+ #define FXAA_QUALITY_P2 1.0
+ #define FXAA_QUALITY_P3 1.0
+ #define FXAA_QUALITY_P4 1.0
+ #define FXAA_QUALITY_P5 1.5
+ #define FXAA_QUALITY_P6 2.0
+ #define FXAA_QUALITY_P7 2.0
+ #define FXAA_QUALITY_P8 2.0
+ #define FXAA_QUALITY_P9 2.0
+ #define FXAA_QUALITY_P10 4.0
+ #define FXAA_QUALITY_P11 8.0
+#endif
+
+/*============================================================================
+
+ Support Functions
+
+============================================================================*/
+
+float CalcLuma(vec3 rgb)
+{
+ vec3 luma = vec3(0.299, 0.587, 0.114);
+ return dot(rgb, luma);
+}
+
+/*--------------------------------------------------------------------------*/
+
+#ifdef GL3
+
+#define FxaaTexTop(t, p) vec4(textureLod(t, p, 0.0).rgb, 1.0)
+#define LumaTop(t, p) CalcLuma(textureLod(t, p, 0.0).rgb)
+#if (FXAA_FAST_PIXEL_OFFSET == 1)
+ #define LumaOff(t, p, o, r) CalcLuma(textureLodOffset(t, p, 0.0, o).rgb)
+#else
+ #define LumaOff(t, p, o, r) CalcLuma(textureLod(t, p + (o * r), 0.0).rgb)
+#endif
+
+#else
+
+#define FxaaTexTop(t, p) vec4(texture2DLod(t, p, 0.0).rgb, 1.0)
+#define LumaTop(t, p) CalcLuma(texture2DLod(t, p, 0.0).rgb)
+#if (FXAA_FAST_PIXEL_OFFSET == 1)
+ #define LumaOff(t, p, o, r) CalcLuma(texture2DLodOffset(t, p, 0.0, o).rgb)
+#else
+ #define LumaOff(t, p, o, r) CalcLuma(texture2DLod(t, p + (o * r), 0.0).rgb)
+#endif
+
+#endif
+
+/*============================================================================
+
+ FXAA3 QUALITY - PC
+
+============================================================================*/
+vec4 FxaaPixelShader(
+ //
+ // Use noperspective interpolation here (turn off perspective interpolation).
+ // {xy} = center of pixel
+ vec2 pos,
+ //
+ // Input color texture.
+ // {rgb_} = color in linear or perceptual color space
+ // if (FXAA_GREEN_AS_LUMA == 0)
+ // {__a} = luma in perceptual color space (not linear)
+ sampler2D tex,
+ //
+ // Only used on FXAA Quality.
+ // This must be from a constant/uniform.
+ // {x_} = 1.0/screenWidthInPixels
+ // {_y} = 1.0/screenHeightInPixels
+ vec2 fxaaQualityRcpFrame,
+ //
+ // Only used on FXAA Quality.
+ // This used to be the FXAA_QUALITY_SUBPIX define.
+ // It is here now to allow easier tuning.
+ // Choose the amount of sub-pixel aliasing removal.
+ // This can effect sharpness.
+ // 1.00 - upper limit (softer)
+ // 0.75 - default amount of filtering
+ // 0.50 - lower limit (sharper, less sub-pixel aliasing removal)
+ // 0.25 - almost off
+ // 0.00 - completely off
+ float fxaaQualitySubpix,
+ //
+ // Only used on FXAA Quality.
+ // This used to be the FXAA_QUALITY_EDGE_THRESHOLD define.
+ // It is here now to allow easier tuning.
+ // The minimum amount of local contrast required to apply algorithm.
+ // 0.333 - too little (faster)
+ // 0.250 - low quality
+ // 0.166 - default
+ // 0.125 - high quality
+ // 0.063 - overkill (slower)
+ float fxaaQualityEdgeThreshold,
+ //
+ // Only used on FXAA Quality.
+ // This used to be the FXAA_QUALITY_EDGE_THRESHOLD_MIN define.
+ // It is here now to allow easier tuning.
+ // Trims the algorithm from processing darks.
+ // 0.0833 - upper limit (default, the start of visible unfiltered edges)
+ // 0.0625 - high quality (faster)
+ // 0.0312 - visible limit (slower)
+ // Special notes when using FXAA_GREEN_AS_LUMA,
+ // Likely want to set this to zero.
+ // As colors that are mostly not-green
+ // will appear very dark in the green channel!
+ // Tune by looking at mostly non-green content,
+ // then start at zero and increase until aliasing is a problem.
+ float fxaaQualityEdgeThresholdMin
+) {
+/*--------------------------------------------------------------------------*/
+ vec2 posM;
+ posM.x = pos.x;
+ posM.y = pos.y;
+
+ vec4 rgbyM = FxaaTexTop(tex, posM);
+ rgbyM.y = CalcLuma(rgbyM.rgb);
+ #define lumaM rgbyM.y
+ float lumaS = LumaOff(tex, posM, Fxaa_vec2( 0, 1), fxaaQualityRcpFrame.xy);
+ float lumaE = LumaOff(tex, posM, Fxaa_vec2( 1, 0), fxaaQualityRcpFrame.xy);
+ float lumaN = LumaOff(tex, posM, Fxaa_vec2( 0,-1), fxaaQualityRcpFrame.xy);
+ float lumaW = LumaOff(tex, posM, Fxaa_vec2(-1, 0), fxaaQualityRcpFrame.xy);
+/*--------------------------------------------------------------------------*/
+ float maxSM = max(lumaS, lumaM);
+ float minSM = min(lumaS, lumaM);
+ float maxESM = max(lumaE, maxSM);
+ float minESM = min(lumaE, minSM);
+ float maxWN = max(lumaN, lumaW);
+ float minWN = min(lumaN, lumaW);
+ float rangeMax = max(maxWN, maxESM);
+ float rangeMin = min(minWN, minESM);
+ float rangeMaxScaled = rangeMax * fxaaQualityEdgeThreshold;
+ float range = rangeMax - rangeMin;
+ float rangeMaxClamped = max(fxaaQualityEdgeThresholdMin, rangeMaxScaled);
+ bool earlyExit = range < rangeMaxClamped;
+/*--------------------------------------------------------------------------*/
+ if(earlyExit)
+ return FxaaTexTop(tex, pos);
+/*--------------------------------------------------------------------------*/
+ float lumaNW = LumaOff(tex, posM, Fxaa_vec2(-1,-1), fxaaQualityRcpFrame.xy);
+ float lumaSE = LumaOff(tex, posM, Fxaa_vec2( 1, 1), fxaaQualityRcpFrame.xy);
+ float lumaNE = LumaOff(tex, posM, Fxaa_vec2( 1,-1), fxaaQualityRcpFrame.xy);
+ float lumaSW = LumaOff(tex, posM, Fxaa_vec2(-1, 1), fxaaQualityRcpFrame.xy);
+/*--------------------------------------------------------------------------*/
+ float lumaNS = lumaN + lumaS;
+ float lumaWE = lumaW + lumaE;
+ float subpixRcpRange = 1.0/range;
+ float subpixNSWE = lumaNS + lumaWE;
+ float edgeHorz1 = (-2.0 * lumaM) + lumaNS;
+ float edgeVert1 = (-2.0 * lumaM) + lumaWE;
+/*--------------------------------------------------------------------------*/
+ float lumaNESE = lumaNE + lumaSE;
+ float lumaNWNE = lumaNW + lumaNE;
+ float edgeHorz2 = (-2.0 * lumaE) + lumaNESE;
+ float edgeVert2 = (-2.0 * lumaN) + lumaNWNE;
+/*--------------------------------------------------------------------------*/
+ float lumaNWSW = lumaNW + lumaSW;
+ float lumaSWSE = lumaSW + lumaSE;
+ float edgeHorz4 = (abs(edgeHorz1) * 2.0) + abs(edgeHorz2);
+ float edgeVert4 = (abs(edgeVert1) * 2.0) + abs(edgeVert2);
+ float edgeHorz3 = (-2.0 * lumaW) + lumaNWSW;
+ float edgeVert3 = (-2.0 * lumaS) + lumaSWSE;
+ float edgeHorz = abs(edgeHorz3) + edgeHorz4;
+ float edgeVert = abs(edgeVert3) + edgeVert4;
+/*--------------------------------------------------------------------------*/
+ float subpixNWSWNESE = lumaNWSW + lumaNESE;
+ float lengthSign = fxaaQualityRcpFrame.x;
+ bool horzSpan = edgeHorz >= edgeVert;
+ float subpixA = subpixNSWE * 2.0 + subpixNWSWNESE;
+/*--------------------------------------------------------------------------*/
+ if(!horzSpan) lumaN = lumaW;
+ if(!horzSpan) lumaS = lumaE;
+ if(horzSpan) lengthSign = fxaaQualityRcpFrame.y;
+ float subpixB = (subpixA * (1.0/12.0)) - lumaM;
+/*--------------------------------------------------------------------------*/
+ float gradientN = lumaN - lumaM;
+ float gradientS = lumaS - lumaM;
+ float lumaNN = lumaN + lumaM;
+ float lumaSS = lumaS + lumaM;
+ bool pairN = abs(gradientN) >= abs(gradientS);
+ float gradient = max(abs(gradientN), abs(gradientS));
+ if(pairN) lengthSign = -lengthSign;
+ float subpixC = clamp((abs(subpixB) * subpixRcpRange), 0.0, 1.0);
+/*--------------------------------------------------------------------------*/
+ vec2 posB;
+ posB.x = posM.x;
+ posB.y = posM.y;
+ vec2 offNP;
+ offNP.x = (!horzSpan) ? 0.0 : fxaaQualityRcpFrame.x;
+ offNP.y = ( horzSpan) ? 0.0 : fxaaQualityRcpFrame.y;
+ if(!horzSpan) posB.x += lengthSign * 0.5;
+ if( horzSpan) posB.y += lengthSign * 0.5;
+/*--------------------------------------------------------------------------*/
+ vec2 posN;
+ posN.x = posB.x - offNP.x * FXAA_QUALITY_P0;
+ posN.y = posB.y - offNP.y * FXAA_QUALITY_P0;
+ vec2 posP;
+ posP.x = posB.x + offNP.x * FXAA_QUALITY_P0;
+ posP.y = posB.y + offNP.y * FXAA_QUALITY_P0;
+ float subpixD = ((-2.0)*subpixC) + 3.0;
+ float lumaEndN = LumaTop(tex, posN);
+ float subpixE = subpixC * subpixC;
+ float lumaEndP = LumaTop(tex, posP);
+/*--------------------------------------------------------------------------*/
+ if(!pairN) lumaNN = lumaSS;
+ float gradientScaled = gradient * 1.0/4.0;
+ float lumaMM = lumaM - lumaNN * 0.5;
+ float subpixF = subpixD * subpixE;
+ bool lumaMLTZero = lumaMM < 0.0;
+/*--------------------------------------------------------------------------*/
+ lumaEndN -= lumaNN * 0.5;
+ lumaEndP -= lumaNN * 0.5;
+ bool doneN = abs(lumaEndN) >= gradientScaled;
+ bool doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P1;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P1;
+ bool doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P1;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P1;
+/*--------------------------------------------------------------------------*/
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P2;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P2;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P2;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P2;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 3)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P3;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P3;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P3;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P3;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 4)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P4;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P4;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P4;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P4;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 5)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P5;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P5;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P5;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P5;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 6)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P6;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P6;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P6;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P6;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 7)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P7;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P7;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P7;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P7;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 8)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P8;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P8;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P8;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P8;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 9)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P9;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P9;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P9;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P9;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 10)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P10;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P10;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P10;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P10;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 11)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P11;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P11;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P11;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P11;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 12)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(tex, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(tex, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P12;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P12;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P12;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P12;
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+/*--------------------------------------------------------------------------*/
+ float dstN = posM.x - posN.x;
+ float dstP = posP.x - posM.x;
+ if(!horzSpan) dstN = posM.y - posN.y;
+ if(!horzSpan) dstP = posP.y - posM.y;
+/*--------------------------------------------------------------------------*/
+ bool goodSpanN = (lumaEndN < 0.0) != lumaMLTZero;
+ float spanLength = (dstP + dstN);
+ bool goodSpanP = (lumaEndP < 0.0) != lumaMLTZero;
+ float spanLengthRcp = 1.0/spanLength;
+/*--------------------------------------------------------------------------*/
+ bool directionN = dstN < dstP;
+ float dst = min(dstN, dstP);
+ bool goodSpan = directionN ? goodSpanN : goodSpanP;
+ float subpixG = subpixF * subpixF;
+ float pixelOffset = (dst * (-spanLengthRcp)) + 0.5;
+ float subpixH = subpixG * fxaaQualitySubpix;
+/*--------------------------------------------------------------------------*/
+ float pixelOffsetGood = goodSpan ? pixelOffset : 0.0;
+ float pixelOffsetSubpix = max(pixelOffsetGood, subpixH);
+ if(!horzSpan) posM.x += pixelOffsetSubpix * lengthSign;
+ if( horzSpan) posM.y += pixelOffsetSubpix * lengthSign;
+ return FxaaTexTop(tex, posM);
+}
+/*==========================================================================*/
+
+/*============================================================================
+
+ Urho3D Vertex- and Pixelshader
+
+============================================================================*/
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ vec2 rcpFrame = vec2(cGBufferInvSize.x, cGBufferInvSize.y);
+
+ gl_FragColor = FxaaPixelShader(
+ vScreenPos, // vec2 pos,
+ sDiffMap, // sampler2D tex,
+ rcpFrame, // vec2 fxaaQualityRcpFrame,
+ 0.75, // float fxaaQualitySubpix,
+ 0.166, // float fxaaQualityEdgeThreshold,
+ 0.0833 // float fxaaQualityEdgeThresholdMin
+ );
+}
+
diff --git a/bin/CoreData/Shaders/GLSL/Fog.glsl b/bin/CoreData/Shaders/GLSL/Fog.glsl
new file mode 100644
index 0000000..8ac2c6d
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Fog.glsl
@@ -0,0 +1,24 @@
+#ifdef COMPILEPS
+vec3 GetFog(vec3 color, float fogFactor)
+{
+ return mix(cFogColor, color, fogFactor);
+}
+
+vec3 GetLitFog(vec3 color, float fogFactor)
+{
+ return color * fogFactor;
+}
+
+float GetFogFactor(float depth)
+{
+ return clamp((cFogParams.x - depth) * cFogParams.y, 0.0, 1.0);
+}
+
+float GetHeightFogFactor(float depth, float height)
+{
+ float fogFactor = GetFogFactor(depth);
+ float heightFogFactor = (height - cFogParams.z) * cFogParams.w;
+ heightFogFactor = 1.0 - clamp(exp(-(heightFogFactor * heightFogFactor)), 0.0, 1.0);
+ return min(heightFogFactor, fogFactor);
+}
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/GammaCorrection.glsl b/bin/CoreData/Shaders/GLSL/GammaCorrection.glsl
new file mode 100644
index 0000000..ae3217d
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/GammaCorrection.glsl
@@ -0,0 +1,21 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "PostProcess.glsl"
+
+varying vec2 vScreenPos;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ vec3 color = texture2D(sDiffMap, vScreenPos).rgb;
+ gl_FragColor = vec4(ToInverseGamma(color), 1.0);
+}
diff --git a/bin/CoreData/Shaders/GLSL/GreyScale.glsl b/bin/CoreData/Shaders/GLSL/GreyScale.glsl
new file mode 100644
index 0000000..6242d77
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/GreyScale.glsl
@@ -0,0 +1,22 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Lighting.glsl"
+
+varying vec2 vScreenPos;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ vec3 rgb = texture2D(sDiffMap, vScreenPos).rgb;
+ float intensity = GetIntensity(rgb);
+ gl_FragColor = vec4(intensity, intensity, intensity, 1.0);
+}
diff --git a/bin/CoreData/Shaders/GLSL/IBL.glsl b/bin/CoreData/Shaders/GLSL/IBL.glsl
new file mode 100644
index 0000000..cfa3f84
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/IBL.glsl
@@ -0,0 +1,76 @@
+#line 10001
+#ifdef COMPILEPS
+
+ float GetMipFromRoughness(float roughness)
+ {
+ return (roughness * 12.0 - pow(roughness, 6.0) * 1.5);
+ }
+
+
+ vec3 EnvBRDFApprox (vec3 SpecularColor, float Roughness, float NoV)
+ {
+ vec4 c0 = vec4(-1, -0.0275, -0.572, 0.022 );
+ vec4 c1 = vec4(1, 0.0425, 1.0, -0.04 );
+ vec4 r = Roughness * c0 + c1;
+ float a004 = min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y;
+ vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;
+ return SpecularColor * AB.x + AB.y;
+ }
+
+ vec3 FixCubeLookup(vec3 v)
+ {
+ float M = max(max(abs(v.x), abs(v.y)), abs(v.z));
+ float scale = (1024 - 1) / 1024;
+
+ if (abs(v.x) != M) v.x += scale;
+ if (abs(v.y) != M) v.y += scale;
+ if (abs(v.z) != M) v.z += scale;
+
+ return v;
+ }
+
+ /// Calculate IBL contributation
+ /// reflectVec: reflection vector for cube sampling
+ /// wsNormal: surface normal in word space
+ /// toCamera: normalized direction from surface point to camera
+ /// roughness: surface roughness
+ /// ambientOcclusion: ambient occlusion
+ vec3 ImageBasedLighting(vec3 reflectVec, vec3 wsNormal, vec3 toCamera, vec3 diffColor, vec3 specColor, float roughness, inout vec3 reflectionCubeColor)
+ {
+ roughness = max(roughness, 0.08);
+ reflectVec = GetSpecularDominantDir(wsNormal, reflectVec, roughness);
+ float ndv = clamp(dot(-toCamera, wsNormal), 0.0, 1.0);
+
+ // PMREM Mipmapmode https://seblagarde.wordpress.com/2012/06/10/amd-cubemapgen-for-physically-based-rendering/
+ //float GlossScale = 16.0;
+ //float GlossBias = 5.0;
+ float mipSelect = GetMipFromRoughness(roughness); //exp2(GlossScale * roughness * roughness + GlossBias) - exp2(GlossBias);
+
+ // OpenGL ES does not support textureLod without extensions and does not have the sZoneCubeMap sampler,
+ // so for now, sample without explicit LOD, and from the environment sampler, where the zone texture will be put
+ // on mobile hardware
+ #ifndef GL_ES
+ vec3 cube = textureLod(sZoneCubeMap, FixCubeLookup(reflectVec), mipSelect).rgb;
+ vec3 cubeD = textureLod(sZoneCubeMap, FixCubeLookup(wsNormal), 9.0).rgb;
+ #else
+ vec3 cube = textureCube(sEnvCubeMap, FixCubeLookup(reflectVec)).rgb;
+ vec3 cubeD = textureCube(sEnvCubeMap, FixCubeLookup(wsNormal)).rgb;
+ #endif
+
+ // Fake the HDR texture
+ float brightness = clamp(cAmbientColor.a, 0.0, 1.0);
+ float darknessCutoff = clamp((cAmbientColor.a - 1.0) * 0.1, 0.0, 0.25);
+
+ const float hdrMaxBrightness = 5.0;
+ vec3 hdrCube = pow(cube + darknessCutoff, vec3(max(1.0, cAmbientColor.a)));
+ hdrCube += max(vec3(0.0), hdrCube - vec3(1.0)) * hdrMaxBrightness;
+
+ vec3 hdrCubeD = pow(cubeD + darknessCutoff, vec3(max(1.0, cAmbientColor.a)));
+ hdrCubeD += max(vec3(0.0), hdrCubeD - vec3(1.0)) * hdrMaxBrightness;
+
+ vec3 environmentSpecular = EnvBRDFApprox(specColor, roughness, ndv);
+ vec3 environmentDiffuse = EnvBRDFApprox(diffColor, 1.0, ndv);
+
+ return (hdrCube * environmentSpecular + hdrCubeD * environmentDiffuse) * brightness;
+ }
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/Lighting.glsl b/bin/CoreData/Shaders/GLSL/Lighting.glsl
new file mode 100644
index 0000000..76af1d1
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Lighting.glsl
@@ -0,0 +1,387 @@
+#ifdef COMPILEVS
+vec3 GetAmbient(float zonePos)
+{
+ return cAmbientStartColor + zonePos * cAmbientEndColor;
+}
+
+#ifdef NUMVERTEXLIGHTS
+float GetVertexLight(int index, vec3 worldPos, vec3 normal)
+{
+ vec3 lightDir = cVertexLights[index * 3 + 1].xyz;
+ vec3 lightPos = cVertexLights[index * 3 + 2].xyz;
+ float invRange = cVertexLights[index * 3].w;
+ float cutoff = cVertexLights[index * 3 + 1].w;
+ float invCutoff = cVertexLights[index * 3 + 2].w;
+
+ // Directional light
+ if (invRange == 0.0)
+ {
+ #ifdef TRANSLUCENT
+ float NdotL = abs(dot(normal, lightDir));
+ #else
+ float NdotL = max(dot(normal, lightDir), 0.0);
+ #endif
+ return NdotL;
+ }
+ // Point/spot light
+ else
+ {
+ vec3 lightVec = (lightPos - worldPos) * invRange;
+ float lightDist = length(lightVec);
+ vec3 localDir = lightVec / lightDist;
+ #ifdef TRANSLUCENT
+ float NdotL = abs(dot(normal, localDir));
+ #else
+ float NdotL = max(dot(normal, localDir), 0.0);
+ #endif
+ float atten = clamp(1.0 - lightDist * lightDist, 0.0, 1.0);
+ float spotEffect = dot(localDir, lightDir);
+ float spotAtten = clamp((spotEffect - cutoff) * invCutoff, 0.0, 1.0);
+ return NdotL * atten * spotAtten;
+ }
+}
+
+float GetVertexLightVolumetric(int index, vec3 worldPos)
+{
+ vec3 lightDir = cVertexLights[index * 3 + 1].xyz;
+ vec3 lightPos = cVertexLights[index * 3 + 2].xyz;
+ float invRange = cVertexLights[index * 3].w;
+ float cutoff = cVertexLights[index * 3 + 1].w;
+ float invCutoff = cVertexLights[index * 3 + 2].w;
+
+ // Directional light
+ if (invRange == 0.0)
+ return 1.0;
+ // Point/spot light
+ else
+ {
+ vec3 lightVec = (lightPos - worldPos) * invRange;
+ float lightDist = length(lightVec);
+ vec3 localDir = lightVec / lightDist;
+ float atten = clamp(1.0 - lightDist * lightDist, 0.0, 1.0);
+ float spotEffect = dot(localDir, lightDir);
+ float spotAtten = clamp((spotEffect - cutoff) * invCutoff, 0.0, 1.0);
+ return atten * spotAtten;
+ }
+}
+#endif
+
+#ifdef SHADOW
+
+#if defined(DIRLIGHT) && (!defined(GL_ES) || defined(WEBGL))
+ #define NUMCASCADES 4
+#else
+ #define NUMCASCADES 1
+#endif
+
+vec4 GetShadowPos(int index, vec3 normal, vec4 projWorldPos)
+{
+ #ifdef NORMALOFFSET
+ float normalOffsetScale[4];
+ normalOffsetScale[0] = cNormalOffsetScale.x;
+ normalOffsetScale[1] = cNormalOffsetScale.y;
+ normalOffsetScale[2] = cNormalOffsetScale.z;
+ normalOffsetScale[3] = cNormalOffsetScale.w;
+
+ #ifdef DIRLIGHT
+ float cosAngle = clamp(1.0 - dot(normal, cLightDir), 0.0, 1.0);
+ #else
+ float cosAngle = clamp(1.0 - dot(normal, normalize(cLightPos.xyz - projWorldPos.xyz)), 0.0, 1.0);
+ #endif
+ projWorldPos.xyz += cosAngle * normalOffsetScale[index] * normal;
+ #endif
+
+ #if defined(DIRLIGHT)
+ return projWorldPos * cLightMatrices[index];
+ #elif defined(SPOTLIGHT)
+ return projWorldPos * cLightMatrices[1];
+ #else
+ return vec4(projWorldPos.xyz - cLightPos.xyz, 1.0);
+ #endif
+}
+
+#endif
+#endif
+
+#ifdef COMPILEPS
+float GetDiffuse(vec3 normal, vec3 worldPos, out vec3 lightDir)
+{
+ #ifdef DIRLIGHT
+ lightDir = cLightDirPS;
+ #ifdef TRANSLUCENT
+ return abs(dot(normal, lightDir));
+ #else
+ return max(dot(normal, lightDir), 0.0);
+ #endif
+ #else
+ vec3 lightVec = (cLightPosPS.xyz - worldPos) * cLightPosPS.w;
+ float lightDist = length(lightVec);
+ lightDir = lightVec / lightDist;
+ #ifdef TRANSLUCENT
+ return abs(dot(normal, lightDir)) * texture2D(sLightRampMap, vec2(lightDist, 0.0)).r;
+ #else
+ return max(dot(normal, lightDir), 0.0) * texture2D(sLightRampMap, vec2(lightDist, 0.0)).r;
+ #endif
+ #endif
+}
+
+float GetAtten(vec3 normal, vec3 worldPos, out vec3 lightDir)
+{
+ lightDir = cLightDirPS;
+ return clamp(dot(normal, lightDir), 0.0, 1.0);
+}
+
+float GetAttenPoint(vec3 normal, vec3 worldPos, out vec3 lightDir)
+{
+ vec3 lightVec = (cLightPosPS.xyz - worldPos) * cLightPosPS.w;
+ float lightDist = length(lightVec);
+ float falloff = pow(clamp(1.0 - pow(lightDist / 1.0, 4.0), 0.0, 1.0), 2.0) * 3.14159265358979323846 / (4.0 * 3.14159265358979323846)*(pow(lightDist, 2.0) + 1.0);
+ lightDir = lightVec / lightDist;
+ return clamp(dot(normal, lightDir), 0.0, 1.0) * falloff;
+
+}
+
+float GetAttenSpot(vec3 normal, vec3 worldPos, out vec3 lightDir)
+{
+ vec3 lightVec = (cLightPosPS.xyz - worldPos) * cLightPosPS.w;
+ float lightDist = length(lightVec);
+ float falloff = pow(clamp(1.0 - pow(lightDist / 1.0, 4.0), 0.0, 1.0), 2.0) / (pow(lightDist, 2.0) + 1.0);
+
+ lightDir = lightVec / lightDist;
+ return clamp(dot(normal, lightDir), 0.0, 1.0) * falloff;
+
+}
+
+float GetDiffuseVolumetric(vec3 worldPos)
+{
+ #ifdef DIRLIGHT
+ return 1.0;
+ #else
+ vec3 lightVec = (cLightPosPS.xyz - worldPos) * cLightPosPS.w;
+ float lightDist = length(lightVec);
+ return texture2D(sLightRampMap, vec2(lightDist, 0.0)).r;
+ #endif
+}
+
+float GetSpecular(vec3 normal, vec3 eyeVec, vec3 lightDir, float specularPower)
+{
+ vec3 halfVec = normalize(normalize(eyeVec) + lightDir);
+ return pow(max(dot(normal, halfVec), 0.0), specularPower);
+}
+
+float GetIntensity(vec3 color)
+{
+ return dot(color, vec3(0.299, 0.587, 0.114));
+}
+
+#ifdef SHADOW
+
+#if defined(DIRLIGHT) && (!defined(GL_ES) || defined(WEBGL))
+ #define NUMCASCADES 4
+#else
+ #define NUMCASCADES 1
+#endif
+
+#ifdef VSM_SHADOW
+float ReduceLightBleeding(float min, float p_max)
+{
+ return clamp((p_max - min) / (1.0 - min), 0.0, 1.0);
+}
+
+float Chebyshev(vec2 Moments, float depth)
+{
+ //One-tailed inequality valid if depth > Moments.x
+ float p = float(depth <= Moments.x);
+ //Compute variance.
+ float Variance = Moments.y - (Moments.x * Moments.x);
+
+ float minVariance = cVSMShadowParams.x;
+ Variance = max(Variance, minVariance);
+ //Compute probabilistic upper bound.
+ float d = depth - Moments.x;
+ float p_max = Variance / (Variance + d*d);
+ // Prevent light bleeding
+ p_max = ReduceLightBleeding(cVSMShadowParams.y, p_max);
+
+ return max(p, p_max);
+}
+#endif
+
+#ifndef GL_ES
+float GetShadow(vec4 shadowPos)
+{
+ #if defined(SIMPLE_SHADOW)
+ // Take one sample
+ #ifndef GL3
+ float inLight = shadow2DProj(sShadowMap, shadowPos).r;
+ #else
+ float inLight = textureProj(sShadowMap, shadowPos);
+ #endif
+ return cShadowIntensity.y + cShadowIntensity.x * inLight;
+ #elif defined(PCF_SHADOW)
+ // Take four samples and average them
+ // Note: in case of sampling a point light cube shadow, we optimize out the w divide as it has already been performed
+ #ifndef POINTLIGHT
+ vec2 offsets = cShadowMapInvSize * shadowPos.w;
+ #else
+ vec2 offsets = cShadowMapInvSize;
+ #endif
+ #ifndef GL3
+ return cShadowIntensity.y + cShadowIntensity.x * (shadow2DProj(sShadowMap, shadowPos).r +
+ shadow2DProj(sShadowMap, vec4(shadowPos.x + offsets.x, shadowPos.yzw)).r +
+ shadow2DProj(sShadowMap, vec4(shadowPos.x, shadowPos.y + offsets.y, shadowPos.zw)).r +
+ shadow2DProj(sShadowMap, vec4(shadowPos.xy + offsets.xy, shadowPos.zw)).r);
+ #else
+ return cShadowIntensity.y + cShadowIntensity.x * (textureProj(sShadowMap, shadowPos) +
+ textureProj(sShadowMap, vec4(shadowPos.x + offsets.x, shadowPos.yzw)) +
+ textureProj(sShadowMap, vec4(shadowPos.x, shadowPos.y + offsets.y, shadowPos.zw)) +
+ textureProj(sShadowMap, vec4(shadowPos.xy + offsets.xy, shadowPos.zw)));
+ #endif
+ #elif defined(VSM_SHADOW)
+ vec2 samples = texture2D(sShadowMap, shadowPos.xy / shadowPos.w).rg;
+ return cShadowIntensity.y + cShadowIntensity.x * Chebyshev(samples, shadowPos.z / shadowPos.w);
+ #endif
+}
+#else
+float GetShadow(highp vec4 shadowPos)
+{
+ #if defined(SIMPLE_SHADOW)
+ // Take one sample
+ return cShadowIntensity.y + (texture2DProj(sShadowMap, shadowPos).r * shadowPos.w > shadowPos.z ? cShadowIntensity.x : 0.0);
+ #elif defined(PCF_SHADOW)
+ // Take four samples and average them
+ vec2 offsets = cShadowMapInvSize * shadowPos.w;
+ vec4 inLight = vec4(
+ texture2DProj(sShadowMap, shadowPos).r * shadowPos.w > shadowPos.z,
+ texture2DProj(sShadowMap, vec4(shadowPos.x + offsets.x, shadowPos.yzw)).r * shadowPos.w > shadowPos.z,
+ texture2DProj(sShadowMap, vec4(shadowPos.x, shadowPos.y + offsets.y, shadowPos.zw)).r * shadowPos.w > shadowPos.z,
+ texture2DProj(sShadowMap, vec4(shadowPos.xy + offsets.xy, shadowPos.zw)).r * shadowPos.w > shadowPos.z
+ );
+ return cShadowIntensity.y + dot(inLight, vec4(cShadowIntensity.x));
+ #elif defined(VSM_SHADOW)
+ vec2 samples = texture2D(sShadowMap, shadowPos.xy / shadowPos.w).rg;
+ return cShadowIntensity.y + cShadowIntensity.x * Chebyshev(samples, shadowPos.z / shadowPos.w);
+ #endif
+}
+#endif
+
+#ifdef POINTLIGHT
+float GetPointShadow(vec3 lightVec)
+{
+ vec3 axis = textureCube(sFaceSelectCubeMap, lightVec).rgb;
+ float depth = abs(dot(lightVec, axis));
+
+ // Expand the maximum component of the light vector to get full 0.0 - 1.0 UV range from the cube map,
+ // and to avoid sampling across faces. Some GPU's filter across faces, while others do not, and in this
+ // case filtering across faces is wrong
+ const vec3 factor = vec3(1.0 / 256.0);
+ lightVec += factor * axis * lightVec;
+
+ // Read the 2D UV coordinates, adjust according to shadow map size and add face offset
+ vec4 indirectPos = textureCube(sIndirectionCubeMap, lightVec);
+ indirectPos.xy *= cShadowCubeAdjust.xy;
+ indirectPos.xy += vec2(cShadowCubeAdjust.z + indirectPos.z * 0.5, cShadowCubeAdjust.w + indirectPos.w);
+
+ vec4 shadowPos = vec4(indirectPos.xy, cShadowDepthFade.x + cShadowDepthFade.y / depth, 1.0);
+ return GetShadow(shadowPos);
+}
+#endif
+
+#ifdef DIRLIGHT
+float GetDirShadowFade(float inLight, float depth)
+{
+ return min(inLight + max((depth - cShadowDepthFade.z) * cShadowDepthFade.w, 0.0), 1.0);
+}
+
+#if !defined(GL_ES) || defined(WEBGL)
+float GetDirShadow(const vec4 iShadowPos[NUMCASCADES], float depth)
+{
+ vec4 shadowPos;
+
+ if (depth < cShadowSplits.x)
+ shadowPos = iShadowPos[0];
+ else if (depth < cShadowSplits.y)
+ shadowPos = iShadowPos[1];
+ else if (depth < cShadowSplits.z)
+ shadowPos = iShadowPos[2];
+ else
+ shadowPos = iShadowPos[3];
+
+ return GetDirShadowFade(GetShadow(shadowPos), depth);
+}
+#else
+float GetDirShadow(const highp vec4 iShadowPos[NUMCASCADES], float depth)
+{
+ return GetDirShadowFade(GetShadow(iShadowPos[0]), depth);
+}
+#endif
+
+#ifndef GL_ES
+float GetDirShadowDeferred(vec4 projWorldPos, vec3 normal, float depth)
+{
+ vec4 shadowPos;
+
+ #ifdef NORMALOFFSET
+ float cosAngle = clamp(1.0 - dot(normal, cLightDirPS), 0.0, 1.0);
+ if (depth < cShadowSplits.x)
+ shadowPos = vec4(projWorldPos.xyz + cosAngle * cNormalOffsetScalePS.x * normal, 1.0) * cLightMatricesPS[0];
+ else if (depth < cShadowSplits.y)
+ shadowPos = vec4(projWorldPos.xyz + cosAngle * cNormalOffsetScalePS.y * normal, 1.0) * cLightMatricesPS[1];
+ else if (depth < cShadowSplits.z)
+ shadowPos = vec4(projWorldPos.xyz + cosAngle * cNormalOffsetScalePS.z * normal, 1.0) * cLightMatricesPS[2];
+ else
+ shadowPos = vec4(projWorldPos.xyz + cosAngle * cNormalOffsetScalePS.w * normal, 1.0) * cLightMatricesPS[3];
+ #else
+ if (depth < cShadowSplits.x)
+ shadowPos = projWorldPos * cLightMatricesPS[0];
+ else if (depth < cShadowSplits.y)
+ shadowPos = projWorldPos * cLightMatricesPS[1];
+ else if (depth < cShadowSplits.z)
+ shadowPos = projWorldPos * cLightMatricesPS[2];
+ else
+ shadowPos = projWorldPos * cLightMatricesPS[3];
+ #endif
+
+ return GetDirShadowFade(GetShadow(shadowPos), depth);
+}
+#endif
+#endif
+
+#ifndef GL_ES
+float GetShadow(const vec4 iShadowPos[NUMCASCADES], float depth)
+#else
+float GetShadow(const highp vec4 iShadowPos[NUMCASCADES], float depth)
+#endif
+{
+ #if defined(DIRLIGHT)
+ return GetDirShadow(iShadowPos, depth);
+ #elif defined(SPOTLIGHT)
+ return GetShadow(iShadowPos[0]);
+ #else
+ return GetPointShadow(iShadowPos[0].xyz);
+ #endif
+}
+
+#ifndef GL_ES
+float GetShadowDeferred(vec4 projWorldPos, vec3 normal, float depth)
+{
+ #ifdef DIRLIGHT
+ return GetDirShadowDeferred(projWorldPos, normal, depth);
+ #else
+ #ifdef NORMALOFFSET
+ float cosAngle = clamp(1.0 - dot(normal, normalize(cLightPosPS.xyz - projWorldPos.xyz)), 0.0, 1.0);
+ projWorldPos.xyz += cosAngle * cNormalOffsetScalePS.x * normal;
+ #endif
+
+ #ifdef SPOTLIGHT
+ vec4 shadowPos = projWorldPos * cLightMatricesPS[1];
+ return GetShadow(shadowPos);
+ #else
+ vec3 shadowPos = projWorldPos.xyz - cLightPosPS.xyz;
+ return GetPointShadow(shadowPos);
+ #endif
+ #endif
+}
+#endif
+#endif
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/LitParticle.glsl b/bin/CoreData/Shaders/GLSL/LitParticle.glsl
new file mode 100644
index 0000000..5659f8b
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/LitParticle.glsl
@@ -0,0 +1,159 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Lighting.glsl"
+#include "Fog.glsl"
+
+varying vec2 vTexCoord;
+varying vec4 vWorldPos;
+#ifdef VERTEXCOLOR
+ varying vec4 vColor;
+#endif
+#ifdef SOFTPARTICLES
+ varying vec4 vScreenPos;
+ uniform float cSoftParticleFadeScale;
+#endif
+#ifdef PERPIXEL
+ #ifdef SHADOW
+ #ifndef GL_ES
+ varying vec4 vShadowPos[NUMCASCADES];
+ #else
+ varying highp vec4 vShadowPos[NUMCASCADES];
+ #endif
+ #endif
+ #ifdef SPOTLIGHT
+ varying vec4 vSpotPos;
+ #endif
+ #ifdef POINTLIGHT
+ varying vec3 vCubeMaskVec;
+ #endif
+#else
+ varying vec3 vVertexLight;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vTexCoord = GetTexCoord(iTexCoord);
+ vWorldPos = vec4(worldPos, GetDepth(gl_Position));
+
+ #ifdef SOFTPARTICLES
+ vScreenPos = GetScreenPos(gl_Position);
+ #endif
+
+ #ifdef VERTEXCOLOR
+ vColor = iColor;
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ vec4 projWorldPos = vec4(worldPos, 1.0);
+
+ #ifdef SHADOW
+ // Shadow projection: transform from world space to shadow space
+ for (int i = 0; i < NUMCASCADES; i++)
+ vShadowPos[i] = GetShadowPos(i, vec3(0, 0, 0), projWorldPos);
+ #endif
+
+ #ifdef SPOTLIGHT
+ // Spotlight projection: transform from world space to projector texture coordinates
+ vSpotPos = projWorldPos * cLightMatrices[0];
+ #endif
+
+ #ifdef POINTLIGHT
+ vCubeMaskVec = (worldPos - cLightPos.xyz) * mat3(cLightMatrices[0][0].xyz, cLightMatrices[0][1].xyz, cLightMatrices[0][2].xyz);
+ #endif
+ #else
+ // Ambient & per-vertex lighting
+ vVertexLight = GetAmbient(GetZonePos(worldPos));
+
+ #ifdef NUMVERTEXLIGHTS
+ for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+ vVertexLight += GetVertexLightVolumetric(i, worldPos) * cVertexLights[i * 3].rgb;
+ #endif
+ #endif
+}
+
+void PS()
+{
+ // Get material diffuse albedo
+ #ifdef DIFFMAP
+ vec4 diffInput = texture2D(sDiffMap, vTexCoord);
+ #ifdef ALPHAMASK
+ if (diffInput.a < 0.5)
+ discard;
+ #endif
+ vec4 diffColor = cMatDiffColor * diffInput;
+ #else
+ vec4 diffColor = cMatDiffColor;
+ #endif
+
+ #ifdef VERTEXCOLOR
+ diffColor *= vColor;
+ #endif
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(vWorldPos.w, vWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(vWorldPos.w);
+ #endif
+
+ // Soft particle fade
+ // In expand mode depth test should be off. In that case do manual alpha discard test first to reduce fill rate
+ #ifdef SOFTPARTICLES
+ #ifdef EXPAND
+ if (diffColor.a < 0.01)
+ discard;
+ #endif
+
+ float particleDepth = vWorldPos.w;
+ #ifdef HWDEPTH
+ float depth = ReconstructDepth(texture2DProj(sDepthBuffer, vScreenPos).r);
+ #else
+ float depth = DecodeDepth(texture2DProj(sDepthBuffer, vScreenPos).rgb);
+ #endif
+
+ #ifdef EXPAND
+ float diffZ = max(particleDepth - depth, 0.0) * (cFarClipPS - cNearClipPS);
+ float fade = clamp(diffZ * cSoftParticleFadeScale, 0.0, 1.0);
+ #else
+ float diffZ = (depth - particleDepth) * (cFarClipPS - cNearClipPS);
+ float fade = clamp(1.0 - diffZ * cSoftParticleFadeScale, 0.0, 1.0);
+ #endif
+
+ diffColor.a = max(diffColor.a - fade, 0.0);
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ vec3 lightColor;
+ vec3 lightDir;
+ vec3 finalColor;
+
+ float diff = GetDiffuseVolumetric(vWorldPos.xyz);
+
+ #ifdef SHADOW
+ diff *= GetShadow(vShadowPos, vWorldPos.w);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ lightColor = vSpotPos.w > 0.0 ? texture2DProj(sLightSpotMap, vSpotPos).rgb * cLightColor.rgb : vec3(0.0, 0.0, 0.0);
+ #elif defined(CUBEMASK)
+ lightColor = textureCube(sLightCubeMap, vCubeMaskVec).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ finalColor = diff * lightColor * diffColor.rgb;
+ gl_FragColor = vec4(GetLitFog(finalColor, fogFactor), diffColor.a);
+ #else
+ // Ambient & per-vertex lighting
+ vec3 finalColor = vVertexLight * diffColor.rgb;
+
+ gl_FragColor = vec4(GetFog(finalColor, fogFactor), diffColor.a);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/LitSolid.glsl b/bin/CoreData/Shaders/GLSL/LitSolid.glsl
new file mode 100644
index 0000000..3a6008e
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/LitSolid.glsl
@@ -0,0 +1,245 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Lighting.glsl"
+#include "Fog.glsl"
+
+#ifdef NORMALMAP
+ varying vec4 vTexCoord;
+ varying vec4 vTangent;
+#else
+ varying vec2 vTexCoord;
+#endif
+varying vec3 vNormal;
+varying vec4 vWorldPos;
+#ifdef VERTEXCOLOR
+ varying vec4 vColor;
+#endif
+#ifdef PERPIXEL
+ #ifdef SHADOW
+ #ifndef GL_ES
+ varying vec4 vShadowPos[NUMCASCADES];
+ #else
+ varying highp vec4 vShadowPos[NUMCASCADES];
+ #endif
+ #endif
+ #ifdef SPOTLIGHT
+ varying vec4 vSpotPos;
+ #endif
+ #ifdef POINTLIGHT
+ varying vec3 vCubeMaskVec;
+ #endif
+#else
+ varying vec3 vVertexLight;
+ varying vec4 vScreenPos;
+ #ifdef ENVCUBEMAP
+ varying vec3 vReflectionVec;
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ varying vec2 vTexCoord2;
+ #endif
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vNormal = GetWorldNormal(modelMatrix);
+ vWorldPos = vec4(worldPos, GetDepth(gl_Position));
+
+ #ifdef VERTEXCOLOR
+ vColor = iColor;
+ #endif
+
+ #ifdef NORMALMAP
+ vec4 tangent = GetWorldTangent(modelMatrix);
+ vec3 bitangent = cross(tangent.xyz, vNormal) * tangent.w;
+ vTexCoord = vec4(GetTexCoord(iTexCoord), bitangent.xy);
+ vTangent = vec4(tangent.xyz, bitangent.z);
+ #else
+ vTexCoord = GetTexCoord(iTexCoord);
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ vec4 projWorldPos = vec4(worldPos, 1.0);
+
+ #ifdef SHADOW
+ // Shadow projection: transform from world space to shadow space
+ for (int i = 0; i < NUMCASCADES; i++)
+ vShadowPos[i] = GetShadowPos(i, vNormal, projWorldPos);
+ #endif
+
+ #ifdef SPOTLIGHT
+ // Spotlight projection: transform from world space to projector texture coordinates
+ vSpotPos = projWorldPos * cLightMatrices[0];
+ #endif
+
+ #ifdef POINTLIGHT
+ vCubeMaskVec = (worldPos - cLightPos.xyz) * mat3(cLightMatrices[0][0].xyz, cLightMatrices[0][1].xyz, cLightMatrices[0][2].xyz);
+ #endif
+ #else
+ // Ambient & per-vertex lighting
+ #if defined(LIGHTMAP) || defined(AO)
+ // If using lightmap, disregard zone ambient light
+ // If using AO, calculate ambient in the PS
+ vVertexLight = vec3(0.0, 0.0, 0.0);
+ vTexCoord2 = iTexCoord1;
+ #else
+ vVertexLight = GetAmbient(GetZonePos(worldPos));
+ #endif
+
+ #ifdef NUMVERTEXLIGHTS
+ for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+ vVertexLight += GetVertexLight(i, worldPos, vNormal) * cVertexLights[i * 3].rgb;
+ #endif
+
+ vScreenPos = GetScreenPos(gl_Position);
+
+ #ifdef ENVCUBEMAP
+ vReflectionVec = worldPos - cCameraPos;
+ #endif
+ #endif
+}
+
+void PS()
+{
+ // Get material diffuse albedo
+ #ifdef DIFFMAP
+ vec4 diffInput = texture2D(sDiffMap, vTexCoord.xy);
+ #ifdef ALPHAMASK
+ if (diffInput.a < 0.5)
+ discard;
+ #endif
+ vec4 diffColor = cMatDiffColor * diffInput;
+ #else
+ vec4 diffColor = cMatDiffColor;
+ #endif
+
+ #ifdef VERTEXCOLOR
+ diffColor *= vColor;
+ #endif
+
+ // Get material specular albedo
+ #ifdef SPECMAP
+ vec3 specColor = cMatSpecColor.rgb * texture2D(sSpecMap, vTexCoord.xy).rgb;
+ #else
+ vec3 specColor = cMatSpecColor.rgb;
+ #endif
+
+ // Get normal
+ #ifdef NORMALMAP
+ mat3 tbn = mat3(vTangent.xyz, vec3(vTexCoord.zw, vTangent.w), vNormal);
+ vec3 normal = normalize(tbn * DecodeNormal(texture2D(sNormalMap, vTexCoord.xy)));
+ #else
+ vec3 normal = normalize(vNormal);
+ #endif
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(vWorldPos.w, vWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(vWorldPos.w);
+ #endif
+
+ #if defined(PERPIXEL)
+ // Per-pixel forward lighting
+ vec3 lightColor;
+ vec3 lightDir;
+ vec3 finalColor;
+
+ float diff = GetDiffuse(normal, vWorldPos.xyz, lightDir);
+
+ #ifdef SHADOW
+ diff *= GetShadow(vShadowPos, vWorldPos.w);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ lightColor = vSpotPos.w > 0.0 ? texture2DProj(sLightSpotMap, vSpotPos).rgb * cLightColor.rgb : vec3(0.0, 0.0, 0.0);
+ #elif defined(CUBEMASK)
+ lightColor = textureCube(sLightCubeMap, vCubeMaskVec).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ #ifdef SPECULAR
+ float spec = GetSpecular(normal, cCameraPosPS - vWorldPos.xyz, lightDir, cMatSpecColor.a);
+ finalColor = diff * lightColor * (diffColor.rgb + spec * specColor * cLightColor.a);
+ #else
+ finalColor = diff * lightColor * diffColor.rgb;
+ #endif
+
+ #ifdef AMBIENT
+ finalColor += cAmbientColor.rgb * diffColor.rgb;
+ finalColor += cMatEmissiveColor;
+ gl_FragColor = vec4(GetFog(finalColor, fogFactor), diffColor.a);
+ #else
+ gl_FragColor = vec4(GetLitFog(finalColor, fogFactor), diffColor.a);
+ #endif
+ #elif defined(PREPASS)
+ // Fill light pre-pass G-Buffer
+ float specPower = cMatSpecColor.a / 255.0;
+
+ gl_FragData[0] = vec4(normal * 0.5 + 0.5, specPower);
+ gl_FragData[1] = vec4(EncodeDepth(vWorldPos.w), 0.0);
+ #elif defined(DEFERRED)
+ // Fill deferred G-buffer
+ float specIntensity = specColor.g;
+ float specPower = cMatSpecColor.a / 255.0;
+
+ vec3 finalColor = vVertexLight * diffColor.rgb;
+ #ifdef AO
+ // If using AO, the vertex light ambient is black, calculate occluded ambient here
+ finalColor += texture2D(sEmissiveMap, vTexCoord2).rgb * cAmbientColor.rgb * diffColor.rgb;
+ #endif
+
+ #ifdef ENVCUBEMAP
+ finalColor += cMatEnvMapColor * textureCube(sEnvCubeMap, reflect(vReflectionVec, normal)).rgb;
+ #endif
+ #ifdef LIGHTMAP
+ finalColor += texture2D(sEmissiveMap, vTexCoord2).rgb * diffColor.rgb;
+ #endif
+ #ifdef EMISSIVEMAP
+ finalColor += cMatEmissiveColor * texture2D(sEmissiveMap, vTexCoord.xy).rgb;
+ #else
+ finalColor += cMatEmissiveColor;
+ #endif
+
+ gl_FragData[0] = vec4(GetFog(finalColor, fogFactor), 1.0);
+ gl_FragData[1] = fogFactor * vec4(diffColor.rgb, specIntensity);
+ gl_FragData[2] = vec4(normal * 0.5 + 0.5, specPower);
+ gl_FragData[3] = vec4(EncodeDepth(vWorldPos.w), 0.0);
+ #else
+ // Ambient & per-vertex lighting
+ vec3 finalColor = vVertexLight * diffColor.rgb;
+ #ifdef AO
+ // If using AO, the vertex light ambient is black, calculate occluded ambient here
+ finalColor += texture2D(sEmissiveMap, vTexCoord2).rgb * cAmbientColor.rgb * diffColor.rgb;
+ #endif
+
+ #ifdef MATERIAL
+ // Add light pre-pass accumulation result
+ // Lights are accumulated at half intensity. Bring back to full intensity now
+ vec4 lightInput = 2.0 * texture2DProj(sLightBuffer, vScreenPos);
+ vec3 lightSpecColor = lightInput.a * lightInput.rgb / max(GetIntensity(lightInput.rgb), 0.001);
+
+ finalColor += lightInput.rgb * diffColor.rgb + lightSpecColor * specColor;
+ #endif
+
+ #ifdef ENVCUBEMAP
+ finalColor += cMatEnvMapColor * textureCube(sEnvCubeMap, reflect(vReflectionVec, normal)).rgb;
+ #endif
+ #ifdef LIGHTMAP
+ finalColor += texture2D(sEmissiveMap, vTexCoord2).rgb * diffColor.rgb;
+ #endif
+ #ifdef EMISSIVEMAP
+ finalColor += cMatEmissiveColor * texture2D(sEmissiveMap, vTexCoord.xy).rgb;
+ #else
+ finalColor += cMatEmissiveColor;
+ #endif
+
+ gl_FragColor = vec4(GetFog(finalColor, fogFactor), diffColor.a);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/PBR.glsl b/bin/CoreData/Shaders/GLSL/PBR.glsl
new file mode 100644
index 0000000..d3433c8
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/PBR.glsl
@@ -0,0 +1,160 @@
+#include "BRDF.glsl"
+#ifdef COMPILEPS
+#line 100
+ vec3 GetSpecularDominantDir(vec3 normal, vec3 reflection, float roughness)
+ {
+ float smoothness = 1.0 - roughness;
+ float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
+ return mix(normal, reflection, lerpFactor);
+ }
+
+ vec3 SphereLight(vec3 worldPos, vec3 lightVec, vec3 normal, vec3 toCamera, float roughness, vec3 specColor, vec3 diffColor, out float ndl)
+ {
+ float specEnergy = 1.0f;
+
+ float radius = cLightRad / 100;
+ float rough2 = max(roughness, 0.08);
+ rough2 *= rough2;
+
+ float radius2 = radius * radius;
+ float distToLightSqrd = dot(lightVec,lightVec);
+ float invDistToLight = inversesqrt(distToLightSqrd);
+ float sinAlphaSqr = clamp(radius2 / distToLightSqrd, 0.0, 1.0);
+ float sinAlpha = sqrt(sinAlphaSqr);
+
+ ndl = dot(normal, (lightVec * invDistToLight));
+
+ if(ndl < sinAlpha)
+ {
+ ndl = max(ndl, -sinAlpha);
+ ndl = ((sinAlpha + ndl) * (sinAlpha + ndl)) / (4 * sinAlpha);
+ }
+
+ float sphereAngle = clamp(radius * invDistToLight, 0.0, 1.0);
+
+ specEnergy = rough2 / (rough2 + 0.5f * sphereAngle);
+ specEnergy *= specEnergy;
+
+ vec3 R = 2 * dot(toCamera, normal) * normal - toCamera;
+ R = GetSpecularDominantDir(normal, R, roughness);
+
+ // Find closest point on sphere to ray
+ vec3 closestPointOnRay = dot(lightVec, R) * R;
+ vec3 centerToRay = closestPointOnRay - lightVec;
+ float invDistToRay = inversesqrt(dot(centerToRay, centerToRay));
+ vec3 closestPointOnSphere = lightVec + centerToRay * clamp(radius * invDistToRay, 0.0, 1.0);
+
+ lightVec = closestPointOnSphere;
+ vec3 L = normalize(lightVec);
+
+ vec3 h = normalize(toCamera + L);
+ float hdn = clamp(dot(h, normal), 0.0, 1.0);
+ float hdv = dot(h, toCamera);
+ float ndv = clamp(dot(normal, toCamera),0.0, 1.0);
+ float hdl = clamp(dot(h, lightVec), 0.0, 1.0);
+
+ vec3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, hdv) * ndl;
+ vec3 fresnelTerm = Fresnel(specColor, hdv, hdl) ;
+ float distTerm = Distribution(hdn, roughness);
+ float visTerm = Visibility(ndl, ndv, roughness);
+ vec3 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+ return diffuseFactor + specularFactor;
+ }
+
+ vec3 TubeLight(vec3 worldPos, vec3 lightVec, vec3 normal, vec3 toCamera, float roughness, vec3 specColor, vec3 diffColor, out float ndl)
+ {
+ float radius = cLightRad / 100;
+ float len = cLightLength / 10;
+ vec3 pos = (cLightPosPS.xyz - worldPos);
+ vec3 reflectVec = reflect(-toCamera, normal);
+
+ vec3 L01 = cLightDirPS * len;
+ vec3 L0 = pos - 0.5 * L01;
+ vec3 L1 = pos + 0.5 * L01;
+ vec3 ld = L1 - L0;
+
+ float distL0 = length( L0 );
+ float distL1 = length( L1 );
+
+ float NoL0 = dot( L0, normal ) / ( 2.0 * distL0 );
+ float NoL1 = dot( L1, normal ) / ( 2.0 * distL1 );
+ ndl = ( 2.0 * clamp( NoL0 + NoL1, 0.0, 1.0 ) )
+ / ( distL0 * distL1 + dot( L0, L1 ) + 2.0 );
+
+ float a = len * len;
+ float b = dot( reflectVec, L01 );
+ float t = clamp( dot( L0, b * reflectVec - L01 ) / (a - b*b), 0.0, 1.0 );
+
+ vec3 closestPoint = L0 + ld * clamp(t, 0.0, 1.0);
+ vec3 centreToRay = dot( closestPoint, reflectVec ) * reflectVec - closestPoint;
+ closestPoint = closestPoint + centreToRay * clamp(radius / length(centreToRay), 0.0, 1.0);
+
+ vec3 l = normalize(closestPoint);
+ vec3 h = normalize(toCamera + l);
+
+ ndl = clamp(dot(normal, lightVec), 0.0, 1.0);
+ float hdn = clamp(dot(h, normal), 0.0, 1.0);
+ float hdv = dot(h, toCamera);
+ float ndv = clamp(dot(normal, toCamera), 0.0, 1.0);
+ float hdl = clamp(dot(h, lightVec), 0.0, 1.0);
+
+ float distL = length(closestPoint);
+ float alpha = max(roughness, 0.08) * max(roughness, 0.08);
+ float alphaPrime = clamp(radius / (distL * 2.0) + alpha, 0.0, 1.0);
+
+ vec3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, hdv) * ndl;
+ vec3 fresnelTerm = Fresnel(specColor, hdv, hdl) ;
+ float distTerm = Distribution(hdn, roughness);
+ float visTerm = Visibility(ndl, ndv, roughness);
+ vec3 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+ return diffuseFactor + specularFactor;
+ }
+
+ //Return the PBR BRDF value
+ // lightDir = the vector to the light
+ // lightVev = normalised lightDir
+ // toCamera = vector to the camera
+ // normal = surface normal of the pixel
+ // roughness = roughness of the pixel
+ // diffColor = the rgb color of the pixel
+ // specColor = the rgb specular color of the pixel
+ vec3 GetBRDF(vec3 worldPos, vec3 lightDir, vec3 lightVec, vec3 toCamera, vec3 normal, float roughness, vec3 diffColor, vec3 specColor)
+ {
+ vec3 Hn = normalize(toCamera + lightDir);
+ float vdh = clamp(dot(toCamera, Hn), M_EPSILON, 1.0);
+ float ndh = clamp(dot(normal, Hn), M_EPSILON, 1.0);
+ float ndl = clamp(dot(normal, lightVec), M_EPSILON, 1.0);
+ float ldh = clamp(dot(lightVec, Hn), M_EPSILON, 1.0);
+ float ndv = abs(dot(normal, toCamera)) + 1e-5;
+
+ vec3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, vdh);
+ vec3 specularFactor = vec3(0.0, 0.0, 0.0);
+
+ #ifdef SPECULAR
+ if(cLightRad > 0.0)
+ {
+ if(cLightLength > 0.0)
+ {
+ specularFactor = TubeLight(worldPos, lightVec, normal, toCamera, roughness, specColor, diffColor, ndl);
+ specularFactor *= ndl;
+ }
+ else
+ {
+ specularFactor = SphereLight(worldPos, lightVec, normal, toCamera, roughness, specColor, diffColor, ndl);
+ specularFactor *= ndl;
+ }
+ }
+ else
+ {
+ vec3 fresnelTerm = Fresnel(specColor, vdh, ldh) ;
+ float distTerm = Distribution(ndh, roughness);
+ float visTerm = Visibility(ndl, ndv, roughness);
+
+ specularFactor = fresnelTerm * distTerm * visTerm / M_PI;
+ return diffuseFactor + specularFactor;
+ }
+ #endif
+
+ return diffuseFactor + specularFactor;
+ }
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/PBRDeferred.glsl b/bin/CoreData/Shaders/GLSL/PBRDeferred.glsl
new file mode 100644
index 0000000..c1222e7
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/PBRDeferred.glsl
@@ -0,0 +1,125 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Lighting.glsl"
+#include "Constants.glsl"
+#include "PBR.glsl"
+#line 40007
+
+#ifdef DIRLIGHT
+ varying vec2 vScreenPos;
+#else
+ varying vec4 vScreenPos;
+#endif
+varying vec3 vFarRay;
+#ifdef ORTHO
+ varying vec3 vNearRay;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ #ifdef DIRLIGHT
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+ vFarRay = GetFarRay(gl_Position);
+ #ifdef ORTHO
+ vNearRay = GetNearRay(gl_Position);
+ #endif
+ #else
+ vScreenPos = GetScreenPos(gl_Position);
+ vFarRay = GetFarRay(gl_Position) * gl_Position.w;
+ #ifdef ORTHO
+ vNearRay = GetNearRay(gl_Position) * gl_Position.w;
+ #endif
+ #endif
+}
+
+
+void PS()
+{
+ // If rendering a directional light quad, optimize out the w divide
+ #ifdef DIRLIGHT
+ vec4 depthInput = texture2D(sDepthBuffer, vScreenPos);
+ #ifdef HWDEPTH
+ float depth = ReconstructDepth(depthInput.r);
+ #else
+ float depth = DecodeDepth(depthInput.rgb);
+ #endif
+ #ifdef ORTHO
+ vec3 worldPos = mix(vNearRay, vFarRay, depth);
+ #else
+ vec3 worldPos = vFarRay * depth;
+ #endif
+ vec4 albedoInput = texture2D(sAlbedoBuffer, vScreenPos);
+ vec4 normalInput = texture2D(sNormalBuffer, vScreenPos);
+ vec4 specularInput = texture2D(sSpecMap, vScreenPos);
+ #else
+ vec4 depthInput = texture2DProj(sDepthBuffer, vScreenPos);
+ #ifdef HWDEPTH
+ float depth = ReconstructDepth(depthInput.r);
+ #else
+ float depth = DecodeDepth(depthInput.rgb);
+ #endif
+ #ifdef ORTHO
+ vec3 worldPos = mix(vNearRay, vFarRay, depth) / vScreenPos.w;
+ #else
+ vec3 worldPos = vFarRay * depth / vScreenPos.w;
+ #endif
+ vec4 albedoInput = texture2DProj(sAlbedoBuffer, vScreenPos);
+ vec4 normalInput = texture2DProj(sNormalBuffer, vScreenPos);
+ vec4 specularInput = texture2DProj(sSpecMap, vScreenPos);
+ #endif
+
+ // Position acquired via near/far ray is relative to camera. Bring position to world space
+ vec3 eyeVec = -worldPos;
+ worldPos += cCameraPosPS;
+
+ vec3 normal = normalInput.rgb;
+ float roughness = length(normal);
+ normal = normalize(normal);
+
+ vec3 specColor = specularInput.rgb;
+
+ vec4 projWorldPos = vec4(worldPos, 1.0);
+
+ vec3 lightDir;
+
+ float atten = 1;
+
+ #if defined(DIRLIGHT)
+ atten = GetAtten(normal, worldPos, lightDir);
+ #elif defined(SPOTLIGHT)
+ atten = GetAttenSpot(normal, worldPos, lightDir);
+ #else
+ atten = GetAttenPoint(normal, worldPos, lightDir);
+ #endif
+
+ float shadow = 1;
+ #ifdef SHADOW
+ shadow *= GetShadowDeferred(projWorldPos, normal, depth);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ vec4 spotPos = projWorldPos * cLightMatricesPS[0];
+ vec3 lightColor = spotPos.w > 0.0 ? texture2DProj(sLightSpotMap, spotPos).rgb * cLightColor.rgb : vec3(0.0);
+ #elif defined(CUBEMASK)
+ mat3 lightVecRot = mat3(cLightMatricesPS[0][0].xyz, cLightMatricesPS[0][1].xyz, cLightMatricesPS[0][2].xyz);
+ vec3 lightColor = textureCube(sLightCubeMap, (worldPos - cLightPosPS.xyz) * lightVecRot).rgb * cLightColor.rgb;
+ #else
+ vec3 lightColor = cLightColor.rgb;
+ #endif
+
+ vec3 toCamera = normalize(eyeVec);
+ vec3 lightVec = normalize(lightDir);
+
+ float ndl = clamp(abs(dot(normal, lightVec)), M_EPSILON, 1.0);
+
+ vec3 BRDF = GetBRDF(worldPos, lightDir, lightVec, toCamera, normal, roughness, albedoInput.rgb, specColor);
+
+ gl_FragColor.a = 1.0;
+ gl_FragColor.rgb = BRDF * lightColor * (atten * shadow) / M_PI;
+
+}
diff --git a/bin/CoreData/Shaders/GLSL/PBRLitSolid.glsl b/bin/CoreData/Shaders/GLSL/PBRLitSolid.glsl
new file mode 100644
index 0000000..4a19925
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/PBRLitSolid.glsl
@@ -0,0 +1,261 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Lighting.glsl"
+#include "Constants.glsl"
+#include "Fog.glsl"
+#include "PBR.glsl"
+#include "IBL.glsl"
+#line 30010
+
+#if defined(NORMALMAP)
+ varying vec4 vTexCoord;
+ varying vec4 vTangent;
+#else
+ varying vec2 vTexCoord;
+#endif
+varying vec3 vNormal;
+varying vec4 vWorldPos;
+#ifdef VERTEXCOLOR
+ varying vec4 vColor;
+#endif
+#ifdef PERPIXEL
+ #ifdef SHADOW
+ #ifndef GL_ES
+ varying vec4 vShadowPos[NUMCASCADES];
+ #else
+ varying highp vec4 vShadowPos[NUMCASCADES];
+ #endif
+ #endif
+ #ifdef SPOTLIGHT
+ varying vec4 vSpotPos;
+ #endif
+ #ifdef POINTLIGHT
+ varying vec3 vCubeMaskVec;
+ #endif
+#else
+ varying vec3 vVertexLight;
+ varying vec4 vScreenPos;
+ #ifdef ENVCUBEMAP
+ varying vec3 vReflectionVec;
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ varying vec2 vTexCoord2;
+ #endif
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vNormal = GetWorldNormal(modelMatrix);
+ vWorldPos = vec4(worldPos, GetDepth(gl_Position));
+
+ #ifdef VERTEXCOLOR
+ vColor = iColor;
+ #endif
+
+ #if defined(NORMALMAP) || defined(DIRBILLBOARD)
+ vec4 tangent = GetWorldTangent(modelMatrix);
+ vec3 bitangent = cross(tangent.xyz, vNormal) * tangent.w;
+ vTexCoord = vec4(GetTexCoord(iTexCoord), bitangent.xy);
+ vTangent = vec4(tangent.xyz, bitangent.z);
+ #else
+ vTexCoord = GetTexCoord(iTexCoord);
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ vec4 projWorldPos = vec4(worldPos, 1.0);
+
+ #ifdef SHADOW
+ // Shadow projection: transform from world space to shadow space
+ for (int i = 0; i < NUMCASCADES; i++)
+ vShadowPos[i] = GetShadowPos(i, vNormal, projWorldPos);
+ #endif
+
+ #ifdef SPOTLIGHT
+ // Spotlight projection: transform from world space to projector texture coordinates
+ vSpotPos = projWorldPos * cLightMatrices[0];
+ #endif
+
+ #ifdef POINTLIGHT
+ vCubeMaskVec = (worldPos - cLightPos.xyz) * mat3(cLightMatrices[0][0].xyz, cLightMatrices[0][1].xyz, cLightMatrices[0][2].xyz);
+ #endif
+ #else
+ // Ambient & per-vertex lighting
+ #if defined(LIGHTMAP) || defined(AO)
+ // If using lightmap, disregard zone ambient light
+ // If using AO, calculate ambient in the PS
+ vVertexLight = vec3(0.0, 0.0, 0.0);
+ vTexCoord2 = iTexCoord1;
+ #else
+ vVertexLight = GetAmbient(GetZonePos(worldPos));
+ #endif
+
+ #ifdef NUMVERTEXLIGHTS
+ for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+ vVertexLight += GetVertexLight(i, worldPos, vNormal) * cVertexLights[i * 3].rgb;
+ #endif
+
+ vScreenPos = GetScreenPos(gl_Position);
+
+ #ifdef ENVCUBEMAP
+ vReflectionVec = worldPos - cCameraPos;
+ #endif
+ #endif
+}
+
+void PS()
+{
+ // Get material diffuse albedo
+ #ifdef DIFFMAP
+ vec4 diffInput = texture2D(sDiffMap, vTexCoord.xy);
+ #ifdef ALPHAMASK
+ if (diffInput.a < 0.5)
+ discard;
+ #endif
+ vec4 diffColor = cMatDiffColor * diffInput;
+ #else
+ vec4 diffColor = cMatDiffColor;
+ #endif
+
+ #ifdef VERTEXCOLOR
+ diffColor *= vColor;
+ #endif
+
+ #ifdef METALLIC
+ vec4 roughMetalSrc = texture2D(sSpecMap, vTexCoord.xy);
+
+ float roughness = roughMetalSrc.r + cRoughness;
+ float metalness = roughMetalSrc.g + cMetallic;
+ #else
+ float roughness = cRoughness;
+ float metalness = cMetallic;
+ #endif
+
+ roughness *= roughness;
+
+ roughness = clamp(roughness, ROUGHNESS_FLOOR, 1.0);
+ metalness = clamp(metalness, METALNESS_FLOOR, 1.0);
+
+ vec3 specColor = mix(0.08 * cMatSpecColor.rgb, diffColor.rgb, metalness);
+ diffColor.rgb = diffColor.rgb - diffColor.rgb * metalness;
+
+ // Get normal
+ #if defined(NORMALMAP) || defined(DIRBILLBOARD)
+ vec3 tangent = vTangent.xyz;
+ vec3 bitangent = vec3(vTexCoord.zw, vTangent.w);
+ mat3 tbn = mat3(tangent, bitangent, vNormal);
+ #endif
+
+ #ifdef NORMALMAP
+ vec3 nn = DecodeNormal(texture2D(sNormalMap, vTexCoord.xy));
+ //nn.rg *= 2.0;
+ vec3 normal = normalize(tbn * nn);
+ #else
+ vec3 normal = normalize(vNormal);
+ #endif
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(vWorldPos.w, vWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(vWorldPos.w);
+ #endif
+
+ #if defined(PERPIXEL)
+ // Per-pixel forward lighting
+ vec3 lightColor;
+ vec3 lightDir;
+ vec3 finalColor;
+
+ float atten = 1;
+
+ #if defined(DIRLIGHT)
+ atten = GetAtten(normal, vWorldPos.xyz, lightDir);
+ #elif defined(SPOTLIGHT)
+ atten = GetAttenSpot(normal, vWorldPos.xyz, lightDir);
+ #else
+ atten = GetAttenPoint(normal, vWorldPos.xyz, lightDir);
+ #endif
+
+ float shadow = 1.0;
+ #ifdef SHADOW
+ shadow = GetShadow(vShadowPos, vWorldPos.w);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ lightColor = vSpotPos.w > 0.0 ? texture2DProj(sLightSpotMap, vSpotPos).rgb * cLightColor.rgb : vec3(0.0, 0.0, 0.0);
+ #elif defined(CUBEMASK)
+ lightColor = textureCube(sLightCubeMap, vCubeMaskVec).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+ vec3 toCamera = normalize(cCameraPosPS - vWorldPos.xyz);
+ vec3 lightVec = normalize(lightDir);
+ float ndl = clamp((dot(normal, lightVec)), M_EPSILON, 1.0);
+
+ vec3 BRDF = GetBRDF(vWorldPos.xyz, lightDir, lightVec, toCamera, normal, roughness, diffColor.rgb, specColor);
+
+ finalColor.rgb = BRDF * lightColor * (atten * shadow) / M_PI;
+
+ #ifdef AMBIENT
+ finalColor += cAmbientColor.rgb * diffColor.rgb;
+ finalColor += cMatEmissiveColor;
+ gl_FragColor = vec4(GetFog(finalColor, fogFactor), diffColor.a);
+ #else
+ gl_FragColor = vec4(GetLitFog(finalColor, fogFactor), diffColor.a);
+ #endif
+ #elif defined(DEFERRED)
+ // Fill deferred G-buffer
+ const vec3 spareData = vec3(0,0,0); // Can be used to pass more data to deferred renderer
+ gl_FragData[0] = vec4(specColor, spareData.r);
+ gl_FragData[1] = vec4(diffColor.rgb, spareData.g);
+ gl_FragData[2] = vec4(normal * roughness, spareData.b);
+ gl_FragData[3] = vec4(EncodeDepth(vWorldPos.w), 0.0);
+ #else
+ // Ambient & per-vertex lighting
+ vec3 finalColor = vVertexLight * diffColor.rgb;
+ #ifdef AO
+ // If using AO, the vertex light ambient is black, calculate occluded ambient here
+ finalColor += texture2D(sEmissiveMap, vTexCoord2).rgb * cAmbientColor.rgb * diffColor.rgb;
+ #endif
+
+ #ifdef MATERIAL
+ // Add light pre-pass accumulation result
+ // Lights are accumulated at half intensity. Bring back to full intensity now
+ vec4 lightInput = 2.0 * texture2DProj(sLightBuffer, vScreenPos);
+ vec3 lightSpecColor = lightInput.a * lightInput.rgb / max(GetIntensity(lightInput.rgb), 0.001);
+
+ finalColor += lightInput.rgb * diffColor.rgb + lightSpecColor * specColor;
+ #endif
+
+ vec3 toCamera = normalize(vWorldPos.xyz - cCameraPosPS);
+ vec3 reflection = normalize(reflect(toCamera, normal));
+
+ vec3 cubeColor = vVertexLight.rgb;
+
+ #ifdef IBL
+ vec3 iblColor = ImageBasedLighting(reflection, normal, toCamera, diffColor.rgb, specColor.rgb, roughness, cubeColor);
+ float gamma = 0.0;
+ finalColor.rgb += iblColor;
+ #endif
+
+ #ifdef ENVCUBEMAP
+ finalColor += cMatEnvMapColor * textureCube(sEnvCubeMap, reflect(vReflectionVec, normal)).rgb;
+ #endif
+ #ifdef LIGHTMAP
+ finalColor += texture2D(sEmissiveMap, vTexCoord2).rgb * diffColor.rgb;
+ #endif
+ #ifdef EMISSIVEMAP
+ finalColor += cMatEmissiveColor * texture2D(sEmissiveMap, vTexCoord.xy).rgb;
+ #else
+ finalColor += cMatEmissiveColor;
+ #endif
+
+ gl_FragColor = vec4(GetFog(finalColor, fogFactor), diffColor.a);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/PostProcess.glsl b/bin/CoreData/Shaders/GLSL/PostProcess.glsl
new file mode 100644
index 0000000..548580e
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/PostProcess.glsl
@@ -0,0 +1,89 @@
+#ifdef COMPILEPS
+const float PI = 3.14159265;
+
+vec2 Noise(vec2 coord)
+{
+ float noiseX = clamp(fract(sin(dot(coord, vec2(12.9898, 78.233))) * 43758.5453), 0.0, 1.0);
+ float noiseY = clamp(fract(sin(dot(coord, vec2(12.9898, 78.233) * 2.0)) * 43758.5453), 0.0, 1.0);
+ return vec2(noiseX, noiseY);
+}
+
+// Adapted: http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html
+vec4 GaussianBlur(int blurKernelSize, vec2 blurDir, vec2 blurRadius, float sigma, sampler2D texSampler, vec2 texCoord)
+{
+ int blurKernelSizeHalfSize = blurKernelSize / 2;
+
+ // Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
+ vec3 gaussCoeff;
+ gaussCoeff.x = 1.0 / (sqrt(2.0 * PI) * sigma);
+ gaussCoeff.y = exp(-0.5 / (sigma * sigma));
+ gaussCoeff.z = gaussCoeff.y * gaussCoeff.y;
+
+ vec2 blurVec = blurRadius * blurDir;
+ vec4 avgValue = vec4(0.0);
+ float gaussCoeffSum = 0.0;
+
+ avgValue += texture2D(texSampler, texCoord) * gaussCoeff.x;
+ gaussCoeffSum += gaussCoeff.x;
+ gaussCoeff.xy *= gaussCoeff.yz;
+
+ for (int i = 1; i <= blurKernelSizeHalfSize; i++)
+ {
+ avgValue += texture2D(texSampler, texCoord - float(i) * blurVec) * gaussCoeff.x;
+ avgValue += texture2D(texSampler, texCoord + float(i) * blurVec) * gaussCoeff.x;
+
+ gaussCoeffSum += 2.0 * gaussCoeff.x;
+ gaussCoeff.xy *= gaussCoeff.yz;
+ }
+
+ return avgValue / gaussCoeffSum;
+}
+
+const vec3 LumWeights = vec3(0.2126, 0.7152, 0.0722);
+
+vec3 ReinhardEq3Tonemap(vec3 x)
+{
+ return x / (1.0 + x);
+}
+
+vec3 ReinhardEq4Tonemap(vec3 x, float white)
+{
+ return x * (1.0 + x / white) / (1.0 + x);
+}
+
+// Unchared2 tone mapping (See http://filmicgames.com)
+const float A = 0.15;
+const float B = 0.50;
+const float C = 0.10;
+const float D = 0.20;
+const float E = 0.02;
+const float F = 0.30;
+
+vec3 Uncharted2Tonemap(vec3 x)
+{
+ return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
+}
+
+#ifndef GL_ES
+vec3 ColorCorrection(vec3 color, sampler3D lut)
+{
+ float lutSize = 16.0;
+ float scale = (lutSize - 1.0) / lutSize;
+ float offset = 1.0 / (2.0 * lutSize);
+ return texture3D(lut, clamp(color, 0.0, 1.0) * scale + offset).rgb;
+}
+#endif
+
+const float Gamma = 2.2;
+const float InverseGamma = 1.0 / 2.2;
+
+vec3 ToGamma(vec3 color)
+{
+ return vec3(pow(color.r, Gamma), pow(color.g, Gamma), pow(color.b, Gamma));
+}
+
+vec3 ToInverseGamma(vec3 color)
+{
+ return vec3(pow(color.r, InverseGamma), pow(color.g, InverseGamma), pow(color.b, InverseGamma));
+}
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/PrepassLight.glsl b/bin/CoreData/Shaders/GLSL/PrepassLight.glsl
new file mode 100644
index 0000000..2a2b487
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/PrepassLight.glsl
@@ -0,0 +1,98 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Lighting.glsl"
+
+#ifdef DIRLIGHT
+ varying vec2 vScreenPos;
+#else
+ varying vec4 vScreenPos;
+#endif
+varying vec3 vFarRay;
+#ifdef ORTHO
+ varying vec3 vNearRay;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ #ifdef DIRLIGHT
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+ vFarRay = GetFarRay(gl_Position);
+ #ifdef ORTHO
+ vNearRay = GetNearRay(gl_Position);
+ #endif
+ #else
+ vScreenPos = GetScreenPos(gl_Position);
+ vFarRay = GetFarRay(gl_Position) * gl_Position.w;
+ #ifdef ORTHO
+ vNearRay = GetNearRay(gl_Position) * gl_Position.w;
+ #endif
+ #endif
+}
+
+void PS()
+{
+ // If rendering a directional light quad, optimize out the w divide
+ #ifdef DIRLIGHT
+ #ifdef HWDEPTH
+ float depth = ReconstructDepth(texture2D(sDepthBuffer, vScreenPos).r);
+ #else
+ float depth = DecodeDepth(texture2D(sDepthBuffer, vScreenPos).rgb);
+ #endif
+ #ifdef ORTHO
+ vec3 worldPos = mix(vNearRay, vFarRay, depth);
+ #else
+ vec3 worldPos = vFarRay * depth;
+ #endif
+ vec4 normalInput = texture2D(sNormalBuffer, vScreenPos);
+ #else
+ #ifdef HWDEPTH
+ float depth = ReconstructDepth(texture2DProj(sDepthBuffer, vScreenPos).r);
+ #else
+ float depth = DecodeDepth(texture2DProj(sDepthBuffer, vScreenPos).rgb);
+ #endif
+ #ifdef ORTHO
+ vec3 worldPos = mix(vNearRay, vFarRay, depth) / vScreenPos.w;
+ #else
+ vec3 worldPos = vFarRay * depth / vScreenPos.w;
+ #endif
+ vec4 normalInput = texture2DProj(sNormalBuffer, vScreenPos);
+ #endif
+
+ // Position acquired via near/far ray is relative to camera. Bring position to world space
+ vec3 eyeVec = -worldPos;
+ worldPos += cCameraPosPS;
+
+ vec3 normal = normalize(normalInput.rgb * 2.0 - 1.0);
+ vec4 projWorldPos = vec4(worldPos, 1.0);
+ vec3 lightColor;
+ vec3 lightDir;
+
+ // Accumulate light at half intensity to allow 2x "overburn"
+ float diff = 0.5 * GetDiffuse(normal, worldPos, lightDir);
+
+ #ifdef SHADOW
+ diff *= GetShadowDeferred(projWorldPos, normal, depth);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ vec4 spotPos = projWorldPos * cLightMatricesPS[0];
+ lightColor = spotPos.w > 0.0 ? texture2DProj(sLightSpotMap, spotPos).rgb * cLightColor.rgb : vec3(0.0);
+ #elif defined(CUBEMASK)
+ mat3 lightVecRot = mat3(cLightMatricesPS[0][0].xyz, cLightMatricesPS[0][1].xyz, cLightMatricesPS[0][2].xyz);
+ lightColor = textureCube(sLightCubeMap, (worldPos - cLightPosPS.xyz) * lightVecRot).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ #ifdef SPECULAR
+ float spec = lightColor.g * GetSpecular(normal, eyeVec, lightDir, normalInput.a * 255.0);
+ gl_FragColor = diff * vec4(lightColor, spec * cLightColor.a);
+ #else
+ gl_FragColor = diff * vec4(lightColor, 0.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/Samplers.glsl b/bin/CoreData/Shaders/GLSL/Samplers.glsl
new file mode 100644
index 0000000..08d50c4
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Samplers.glsl
@@ -0,0 +1,84 @@
+#ifdef COMPILEPS
+uniform sampler2D sDiffMap;
+uniform samplerCube sDiffCubeMap;
+uniform sampler2D sNormalMap;
+uniform sampler2D sSpecMap;
+uniform sampler2D sEmissiveMap;
+uniform sampler2D sEnvMap;
+uniform samplerCube sEnvCubeMap;
+uniform sampler2D sLightRampMap;
+uniform sampler2D sLightSpotMap;
+uniform samplerCube sLightCubeMap;
+#ifndef GL_ES
+ uniform sampler3D sVolumeMap;
+ uniform sampler2D sAlbedoBuffer;
+ uniform sampler2D sNormalBuffer;
+ uniform sampler2D sDepthBuffer;
+ uniform sampler2D sLightBuffer;
+ #ifdef VSM_SHADOW
+ uniform sampler2D sShadowMap;
+ #else
+ uniform sampler2DShadow sShadowMap;
+ #endif
+ uniform samplerCube sFaceSelectCubeMap;
+ uniform samplerCube sIndirectionCubeMap;
+ uniform samplerCube sZoneCubeMap;
+ uniform sampler3D sZoneVolumeMap;
+#else
+ uniform highp sampler2D sShadowMap;
+#endif
+
+#ifdef GL3
+#define texture2D texture
+#define texture2DProj textureProj
+#define texture3D texture
+#define textureCube texture
+#define texture2DLod textureLod
+#define texture2DLodOffset textureLodOffset
+#endif
+
+vec3 DecodeNormal(vec4 normalInput)
+{
+ #ifdef PACKEDNORMAL
+ vec3 normal;
+ normal.xy = normalInput.ag * 2.0 - 1.0;
+ normal.z = sqrt(max(1.0 - dot(normal.xy, normal.xy), 0.0));
+ return normal;
+ #else
+ return normalize(normalInput.rgb * 2.0 - 1.0);
+ #endif
+}
+
+vec3 EncodeDepth(float depth)
+{
+ #ifndef GL3
+ vec3 ret;
+ depth *= 255.0;
+ ret.x = floor(depth);
+ depth = (depth - ret.x) * 255.0;
+ ret.y = floor(depth);
+ ret.z = (depth - ret.y);
+ ret.xy *= 1.0 / 255.0;
+ return ret;
+ #else
+ // OpenGL 3 can use different MRT formats, so no need for encoding
+ return vec3(depth, 0.0, 0.0);
+ #endif
+}
+
+float DecodeDepth(vec3 depth)
+{
+ #ifndef GL3
+ const vec3 dotValues = vec3(1.0, 1.0 / 255.0, 1.0 / (255.0 * 255.0));
+ return dot(depth, dotValues);
+ #else
+ // OpenGL 3 can use different MRT formats, so no need for encoding
+ return depth.r;
+ #endif
+}
+
+float ReconstructDepth(float hwDepth)
+{
+ return dot(vec2(hwDepth, cDepthReconstruct.y / (hwDepth - cDepthReconstruct.x)), cDepthReconstruct.zw);
+}
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/ScreenPos.glsl b/bin/CoreData/Shaders/GLSL/ScreenPos.glsl
new file mode 100644
index 0000000..12ebc9c
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/ScreenPos.glsl
@@ -0,0 +1,58 @@
+#ifdef COMPILEVS
+mat3 GetCameraRot()
+{
+ return mat3(cViewInv[0][0], cViewInv[0][1], cViewInv[0][2],
+ cViewInv[1][0], cViewInv[1][1], cViewInv[1][2],
+ cViewInv[2][0], cViewInv[2][1], cViewInv[2][2]);
+}
+
+vec4 GetScreenPos(vec4 clipPos)
+{
+ return vec4(
+ clipPos.x * cGBufferOffsets.z + cGBufferOffsets.x * clipPos.w,
+ clipPos.y * cGBufferOffsets.w + cGBufferOffsets.y * clipPos.w,
+ 0.0,
+ clipPos.w);
+}
+
+vec2 GetScreenPosPreDiv(vec4 clipPos)
+{
+ return vec2(
+ clipPos.x / clipPos.w * cGBufferOffsets.z + cGBufferOffsets.x,
+ clipPos.y / clipPos.w * cGBufferOffsets.w + cGBufferOffsets.y);
+}
+
+vec2 GetQuadTexCoord(vec4 clipPos)
+{
+ return vec2(
+ clipPos.x / clipPos.w * 0.5 + 0.5,
+ clipPos.y / clipPos.w * 0.5 + 0.5);
+}
+
+vec2 GetQuadTexCoordNoFlip(vec3 worldPos)
+{
+ return vec2(
+ worldPos.x * 0.5 + 0.5,
+ -worldPos.y * 0.5 + 0.5);
+}
+
+vec3 GetFarRay(vec4 clipPos)
+{
+ vec3 viewRay = vec3(
+ clipPos.x / clipPos.w * cFrustumSize.x,
+ clipPos.y / clipPos.w * cFrustumSize.y,
+ cFrustumSize.z);
+
+ return viewRay * GetCameraRot();
+}
+
+vec3 GetNearRay(vec4 clipPos)
+{
+ vec3 viewRay = vec3(
+ clipPos.x / clipPos.w * cFrustumSize.x,
+ clipPos.y / clipPos.w * cFrustumSize.y,
+ 0.0);
+
+ return (viewRay * GetCameraRot()) * cDepthMode.x;
+}
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/Shadow.glsl b/bin/CoreData/Shaders/GLSL/Shadow.glsl
new file mode 100644
index 0000000..4f57e66
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Shadow.glsl
@@ -0,0 +1,37 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+
+#ifdef VSM_SHADOW
+ varying vec4 vTexCoord;
+#else
+ varying vec2 vTexCoord;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ #ifdef VSM_SHADOW
+ vTexCoord = vec4(GetTexCoord(iTexCoord), gl_Position.z, gl_Position.w);
+ #else
+ vTexCoord = GetTexCoord(iTexCoord);
+ #endif
+}
+
+void PS()
+{
+ #ifdef ALPHAMASK
+ float alpha = texture2D(sDiffMap, vTexCoord.xy).a;
+ if (alpha < 0.5)
+ discard;
+ #endif
+
+ #ifdef VSM_SHADOW
+ float depth = vTexCoord.z / vTexCoord.w * 0.5 + 0.5;
+ gl_FragColor = vec4(depth, depth * depth, 1.0, 1.0);
+ #else
+ gl_FragColor = vec4(1.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/ShadowBlur.glsl b/bin/CoreData/Shaders/GLSL/ShadowBlur.glsl
new file mode 100644
index 0000000..c4c6e1d
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/ShadowBlur.glsl
@@ -0,0 +1,34 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+
+#ifdef COMPILEPS
+uniform vec2 cBlurOffsets;
+#endif
+
+varying vec2 vScreenPos;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ vec2 color = vec2(0.0);
+
+ color += 0.015625 * texture2D(sDiffMap, vScreenPos + vec2(-3.0) * cBlurOffsets).rg;
+ color += 0.09375 * texture2D(sDiffMap, vScreenPos + vec2(-2.0) * cBlurOffsets).rg;
+ color += 0.234375 * texture2D(sDiffMap, vScreenPos + vec2(-1.0) * cBlurOffsets).rg;
+ color += 0.3125 * texture2D(sDiffMap, vScreenPos).rg;
+ color += 0.234375 * texture2D(sDiffMap, vScreenPos + vec2(1.0) * cBlurOffsets).rg;
+ color += 0.09375 * texture2D(sDiffMap, vScreenPos + vec2(2.0) * cBlurOffsets).rg;
+ color += 0.015625 * texture2D(sDiffMap, vScreenPos + vec2(3.0) * cBlurOffsets).rg;
+
+ gl_FragColor = vec4(color, 0.0, 0.0);
+}
+
diff --git a/bin/CoreData/Shaders/GLSL/Skybox.glsl b/bin/CoreData/Shaders/GLSL/Skybox.glsl
new file mode 100644
index 0000000..e5aee3a
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Skybox.glsl
@@ -0,0 +1,23 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+
+varying vec3 vTexCoord;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ gl_Position.z = gl_Position.w;
+ vTexCoord = iPos.xyz;
+}
+
+void PS()
+{
+ vec4 sky = cMatDiffColor * textureCube(sDiffCubeMap, vTexCoord);
+ #ifdef HDRSCALE
+ sky = pow(sky + clamp((cAmbientColor.a - 1.0) * 0.1, 0.0, 0.25), max(vec4(cAmbientColor.a), 1.0)) * clamp(cAmbientColor.a, 0.0, 1.0);
+ #endif
+ gl_FragColor = sky;
+}
diff --git a/bin/CoreData/Shaders/GLSL/Skydome.glsl b/bin/CoreData/Shaders/GLSL/Skydome.glsl
new file mode 100644
index 0000000..cc47913
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Skydome.glsl
@@ -0,0 +1,19 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+
+varying vec2 vTexCoord;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ gl_Position.z = gl_Position.w;
+ vTexCoord = iTexCoord.xy;
+}
+
+void PS()
+{
+ gl_FragColor = texture2D(sDiffMap, vTexCoord);
+}
diff --git a/bin/CoreData/Shaders/GLSL/Stencil.glsl b/bin/CoreData/Shaders/GLSL/Stencil.glsl
new file mode 100644
index 0000000..4d30ebe
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Stencil.glsl
@@ -0,0 +1,15 @@
+#include "Uniforms.glsl"
+#include "Transform.glsl"
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+}
+
+void PS()
+{
+ gl_FragColor = vec4(1.0);
+}
+
diff --git a/bin/CoreData/Shaders/GLSL/TerrainBlend.glsl b/bin/CoreData/Shaders/GLSL/TerrainBlend.glsl
new file mode 100644
index 0000000..8ef38c0
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/TerrainBlend.glsl
@@ -0,0 +1,195 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Lighting.glsl"
+#include "Fog.glsl"
+
+varying vec2 vTexCoord;
+
+#ifndef GL_ES
+varying vec2 vDetailTexCoord;
+#else
+varying mediump vec2 vDetailTexCoord;
+#endif
+
+varying vec3 vNormal;
+varying vec4 vWorldPos;
+#ifdef PERPIXEL
+ #ifdef SHADOW
+ #ifndef GL_ES
+ varying vec4 vShadowPos[NUMCASCADES];
+ #else
+ varying highp vec4 vShadowPos[NUMCASCADES];
+ #endif
+ #endif
+ #ifdef SPOTLIGHT
+ varying vec4 vSpotPos;
+ #endif
+ #ifdef POINTLIGHT
+ varying vec3 vCubeMaskVec;
+ #endif
+#else
+ varying vec3 vVertexLight;
+ varying vec4 vScreenPos;
+ #ifdef ENVCUBEMAP
+ varying vec3 vReflectionVec;
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ varying vec2 vTexCoord2;
+ #endif
+#endif
+
+uniform sampler2D sWeightMap0;
+uniform sampler2D sDetailMap1;
+uniform sampler2D sDetailMap2;
+uniform sampler2D sDetailMap3;
+
+#ifndef GL_ES
+uniform vec2 cDetailTiling;
+#else
+uniform mediump vec2 cDetailTiling;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vNormal = GetWorldNormal(modelMatrix);
+ vWorldPos = vec4(worldPos, GetDepth(gl_Position));
+ vTexCoord = GetTexCoord(iTexCoord);
+ vDetailTexCoord = cDetailTiling * vTexCoord;
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ vec4 projWorldPos = vec4(worldPos, 1.0);
+
+ #ifdef SHADOW
+ // Shadow projection: transform from world space to shadow space
+ for (int i = 0; i < NUMCASCADES; i++)
+ vShadowPos[i] = GetShadowPos(i, vNormal, projWorldPos);
+ #endif
+
+ #ifdef SPOTLIGHT
+ // Spotlight projection: transform from world space to projector texture coordinates
+ vSpotPos = projWorldPos * cLightMatrices[0];
+ #endif
+
+ #ifdef POINTLIGHT
+ vCubeMaskVec = (worldPos - cLightPos.xyz) * mat3(cLightMatrices[0][0].xyz, cLightMatrices[0][1].xyz, cLightMatrices[0][2].xyz);
+ #endif
+ #else
+ // Ambient & per-vertex lighting
+ #if defined(LIGHTMAP) || defined(AO)
+ // If using lightmap, disregard zone ambient light
+ // If using AO, calculate ambient in the PS
+ vVertexLight = vec3(0.0, 0.0, 0.0);
+ vTexCoord2 = iTexCoord1;
+ #else
+ vVertexLight = GetAmbient(GetZonePos(worldPos));
+ #endif
+
+ #ifdef NUMVERTEXLIGHTS
+ for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+ vVertexLight += GetVertexLight(i, worldPos, vNormal) * cVertexLights[i * 3].rgb;
+ #endif
+
+ vScreenPos = GetScreenPos(gl_Position);
+
+ #ifdef ENVCUBEMAP
+ vReflectionVec = worldPos - cCameraPos;
+ #endif
+ #endif
+}
+
+void PS()
+{
+ // Get material diffuse albedo
+ vec3 weights = texture2D(sWeightMap0, vTexCoord).rgb;
+ float sumWeights = weights.r + weights.g + weights.b;
+ weights /= sumWeights;
+ vec4 diffColor = cMatDiffColor * (
+ weights.r * texture2D(sDetailMap1, vDetailTexCoord) +
+ weights.g * texture2D(sDetailMap2, vDetailTexCoord) +
+ weights.b * texture2D(sDetailMap3, vDetailTexCoord)
+ );
+
+ // Get material specular albedo
+ vec3 specColor = cMatSpecColor.rgb;
+
+ // Get normal
+ vec3 normal = normalize(vNormal);
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(vWorldPos.w, vWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(vWorldPos.w);
+ #endif
+
+ #if defined(PERPIXEL)
+ // Per-pixel forward lighting
+ vec3 lightColor;
+ vec3 lightDir;
+ vec3 finalColor;
+
+ float diff = GetDiffuse(normal, vWorldPos.xyz, lightDir);
+
+ #ifdef SHADOW
+ diff *= GetShadow(vShadowPos, vWorldPos.w);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ lightColor = vSpotPos.w > 0.0 ? texture2DProj(sLightSpotMap, vSpotPos).rgb * cLightColor.rgb : vec3(0.0, 0.0, 0.0);
+ #elif defined(CUBEMASK)
+ lightColor = textureCube(sLightCubeMap, vCubeMaskVec).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ #ifdef SPECULAR
+ float spec = GetSpecular(normal, cCameraPosPS - vWorldPos.xyz, lightDir, cMatSpecColor.a);
+ finalColor = diff * lightColor * (diffColor.rgb + spec * specColor * cLightColor.a);
+ #else
+ finalColor = diff * lightColor * diffColor.rgb;
+ #endif
+
+ #ifdef AMBIENT
+ finalColor += cAmbientColor.rgb * diffColor.rgb;
+ finalColor += cMatEmissiveColor;
+ gl_FragColor = vec4(GetFog(finalColor, fogFactor), diffColor.a);
+ #else
+ gl_FragColor = vec4(GetLitFog(finalColor, fogFactor), diffColor.a);
+ #endif
+ #elif defined(PREPASS)
+ // Fill light pre-pass G-Buffer
+ float specPower = cMatSpecColor.a / 255.0;
+
+ gl_FragData[0] = vec4(normal * 0.5 + 0.5, specPower);
+ gl_FragData[1] = vec4(EncodeDepth(vWorldPos.w), 0.0);
+ #elif defined(DEFERRED)
+ // Fill deferred G-buffer
+ float specIntensity = specColor.g;
+ float specPower = cMatSpecColor.a / 255.0;
+
+ gl_FragData[0] = vec4(GetFog(vVertexLight * diffColor.rgb, fogFactor), 1.0);
+ gl_FragData[1] = fogFactor * vec4(diffColor.rgb, specIntensity);
+ gl_FragData[2] = vec4(normal * 0.5 + 0.5, specPower);
+ gl_FragData[3] = vec4(EncodeDepth(vWorldPos.w), 0.0);
+ #else
+ // Ambient & per-vertex lighting
+ vec3 finalColor = vVertexLight * diffColor.rgb;
+
+ #ifdef MATERIAL
+ // Add light pre-pass accumulation result
+ // Lights are accumulated at half intensity. Bring back to full intensity now
+ vec4 lightInput = 2.0 * texture2DProj(sLightBuffer, vScreenPos);
+ vec3 lightSpecColor = lightInput.a * lightInput.rgb / max(GetIntensity(lightInput.rgb), 0.001);
+
+ finalColor += lightInput.rgb * diffColor.rgb + lightSpecColor * specColor;
+ #endif
+
+ gl_FragColor = vec4(GetFog(finalColor, fogFactor), diffColor.a);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/Text.glsl b/bin/CoreData/Shaders/GLSL/Text.glsl
new file mode 100644
index 0000000..ec89911
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Text.glsl
@@ -0,0 +1,122 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+
+varying vec2 vTexCoord;
+varying vec4 vColor;
+
+#ifdef TEXT_EFFECT_SHADOW
+uniform vec2 cShadowOffset;
+uniform vec4 cShadowColor;
+#endif
+
+#ifdef TEXT_EFFECT_STROKE
+uniform vec4 cStrokeColor;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+
+ vTexCoord = iTexCoord;
+ vColor = iColor;
+}
+
+/*
+ 1) Simplest SDF shader:
+
+ float distance = texture2D(sDiffMap, vTexCoord).a;
+ if (distance >= 0.5)
+ gl_FragColor.a = vColor.a; // This is glyph
+ else
+ gl_FragColor.a = 0.0; // Outside glyph
+
+ 2) Glyph with antialiazed border:
+
+ float distance = texture2D(sDiffMap, vTexCoord).a;
+ gl_FragColor.a = vColor.a * smoothstep(0.495, 0.505, distance);
+
+ 3) Quality improvement for far and small text:
+
+ float distance = texture2D(sDiffMap, vTexCoord).a;
+ // How much "distance" is changed for neighboring pixels.
+ // If text is far then width is big. Far text will be blurred.
+ float width = fwidth(distance);
+ gl_FragColor.a = vColor.a * smoothstep(0.5 - width, 0.5 + width, distance);
+*/
+
+#if defined(COMPILEPS) && defined(SIGNED_DISTANCE_FIELD)
+ float GetAlpha(float distance, float width)
+ {
+ return smoothstep(0.5 - width, 0.5 + width, distance);
+ }
+
+ // Comment this define to turn off supersampling
+ #define SUPERSAMPLING
+#endif
+
+void PS()
+{
+#ifdef SIGNED_DISTANCE_FIELD
+ gl_FragColor.rgb = vColor.rgb;
+ float distance = texture2D(sDiffMap, vTexCoord).a;
+
+ #ifdef TEXT_EFFECT_STROKE
+ #ifdef SUPERSAMPLING
+ float outlineFactor = smoothstep(0.5, 0.525, distance); // Border of glyph
+ gl_FragColor.rgb = mix(cStrokeColor.rgb, vColor.rgb, outlineFactor);
+ #else
+ if (distance < 0.525)
+ gl_FragColor.rgb = cStrokeColor.rgb;
+ #endif
+ #endif
+
+ #ifdef TEXT_EFFECT_SHADOW
+ if (texture2D(sDiffMap, vTexCoord - cShadowOffset).a > 0.5 && distance <= 0.5)
+ gl_FragColor = cShadowColor;
+ #ifndef SUPERSAMPLING
+ else if (distance <= 0.5)
+ gl_FragColor.a = 0;
+ #endif
+ else
+ #endif
+ {
+ float width = fwidth(distance);
+ float alpha = GetAlpha(distance, width);
+
+ #ifdef SUPERSAMPLING
+ vec2 deltaUV = 0.354 * fwidth(vTexCoord); // (1.0 / sqrt(2.0)) / 2.0 = 0.354
+ vec4 square = vec4(vTexCoord - deltaUV, vTexCoord + deltaUV);
+
+ float distance2 = texture2D(sDiffMap, square.xy).a;
+ float distance3 = texture2D(sDiffMap, square.zw).a;
+ float distance4 = texture2D(sDiffMap, square.xw).a;
+ float distance5 = texture2D(sDiffMap, square.zy).a;
+
+ alpha += GetAlpha(distance2, width)
+ + GetAlpha(distance3, width)
+ + GetAlpha(distance4, width)
+ + GetAlpha(distance5, width);
+
+ // For calculating of average correct would be dividing by 5.
+ // But when text is blurred, its brightness is lost. Therefore divide by 4.
+ alpha = alpha * 0.25;
+ #endif
+
+ gl_FragColor.a = alpha;
+ }
+#else
+ #ifdef ALPHAMAP
+ gl_FragColor.rgb = vColor.rgb;
+ #ifdef GL3
+ gl_FragColor.a = vColor.a * texture2D(sDiffMap, vTexCoord).r;
+ #else
+ gl_FragColor.a = vColor.a * texture2D(sDiffMap, vTexCoord).a;
+ #endif
+ #else
+ gl_FragColor = vColor * texture2D(sDiffMap, vTexCoord);
+ #endif
+#endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/Tonemap.glsl b/bin/CoreData/Shaders/GLSL/Tonemap.glsl
new file mode 100644
index 0000000..ca11e69
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Tonemap.glsl
@@ -0,0 +1,40 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "PostProcess.glsl"
+
+varying vec2 vScreenPos;
+
+#ifdef COMPILEPS
+uniform float cTonemapExposureBias;
+uniform float cTonemapMaxWhite;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vScreenPos = GetScreenPosPreDiv(gl_Position);
+}
+
+void PS()
+{
+ #ifdef REINHARDEQ3
+ vec3 color = ReinhardEq3Tonemap(max(texture2D(sDiffMap, vScreenPos).rgb * cTonemapExposureBias, 0.0));
+ gl_FragColor = vec4(color, 1.0);
+ #endif
+
+ #ifdef REINHARDEQ4
+ vec3 color = ReinhardEq4Tonemap(max(texture2D(sDiffMap, vScreenPos).rgb * cTonemapExposureBias, 0.0), cTonemapMaxWhite);
+ gl_FragColor = vec4(color, 1.0);
+ #endif
+
+ #ifdef UNCHARTED2
+ vec3 color = Uncharted2Tonemap(max(texture2D(sDiffMap, vScreenPos).rgb * cTonemapExposureBias, 0.0)) /
+ Uncharted2Tonemap(vec3(cTonemapMaxWhite, cTonemapMaxWhite, cTonemapMaxWhite));
+ gl_FragColor = vec4(color, 1.0);
+ #endif
+}
+
diff --git a/bin/CoreData/Shaders/GLSL/Transform.glsl b/bin/CoreData/Shaders/GLSL/Transform.glsl
new file mode 100644
index 0000000..5799137
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Transform.glsl
@@ -0,0 +1,221 @@
+#ifdef COMPILEVS
+
+// Silence GLSL 150 deprecation warnings
+#ifdef GL3
+#define attribute in
+#define varying out
+#endif
+
+attribute vec4 iPos;
+attribute vec3 iNormal;
+attribute vec4 iColor;
+attribute vec2 iTexCoord;
+attribute vec2 iTexCoord1;
+attribute vec4 iTangent;
+attribute vec4 iBlendWeights;
+attribute vec4 iBlendIndices;
+attribute vec3 iCubeTexCoord;
+attribute vec4 iCubeTexCoord1;
+#ifdef INSTANCED
+ attribute vec4 iTexCoord4;
+ attribute vec4 iTexCoord5;
+ attribute vec4 iTexCoord6;
+#endif
+attribute float iObjectIndex;
+
+#ifdef SKINNED
+mat4 GetSkinMatrix(vec4 blendWeights, vec4 blendIndices)
+{
+ ivec4 idx = ivec4(blendIndices) * 3;
+ const vec4 lastColumn = vec4(0.0, 0.0, 0.0, 1.0);
+ return mat4(cSkinMatrices[idx.x], cSkinMatrices[idx.x + 1], cSkinMatrices[idx.x + 2], lastColumn) * blendWeights.x +
+ mat4(cSkinMatrices[idx.y], cSkinMatrices[idx.y + 1], cSkinMatrices[idx.y + 2], lastColumn) * blendWeights.y +
+ mat4(cSkinMatrices[idx.z], cSkinMatrices[idx.z + 1], cSkinMatrices[idx.z + 2], lastColumn) * blendWeights.z +
+ mat4(cSkinMatrices[idx.w], cSkinMatrices[idx.w + 1], cSkinMatrices[idx.w + 2], lastColumn) * blendWeights.w;
+}
+#endif
+
+#ifdef INSTANCED
+mat4 GetInstanceMatrix()
+{
+ const vec4 lastColumn = vec4(0.0, 0.0, 0.0, 1.0);
+ return mat4(iTexCoord4, iTexCoord5, iTexCoord6, lastColumn);
+}
+#endif
+
+mat3 GetNormalMatrix(mat4 modelMatrix)
+{
+ return mat3(modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz);
+}
+
+vec2 GetTexCoord(vec2 texCoord)
+{
+ return vec2(dot(texCoord, cUOffset.xy) + cUOffset.w, dot(texCoord, cVOffset.xy) + cVOffset.w);
+}
+
+vec4 GetClipPos(vec3 worldPos)
+{
+ vec4 ret = vec4(worldPos, 1.0) * cViewProj;
+ // While getting the clip coordinate, also automatically set gl_ClipVertex for user clip planes
+ #if !defined(GL_ES) && !defined(GL3)
+ gl_ClipVertex = ret;
+ #elif defined(GL3)
+ gl_ClipDistance[0] = dot(cClipPlane, ret);
+ #endif
+ return ret;
+}
+
+float GetZonePos(vec3 worldPos)
+{
+ return clamp((vec4(worldPos, 1.0) * cZone).z, 0.0, 1.0);
+}
+
+float GetDepth(vec4 clipPos)
+{
+ return dot(clipPos.zw, cDepthMode.zw);
+}
+
+#ifdef BILLBOARD
+vec3 GetBillboardPos(vec4 iPos, vec2 iSize, mat4 modelMatrix)
+{
+ return (iPos * modelMatrix).xyz + vec3(iSize.x, iSize.y, 0.0) * cBillboardRot;
+}
+
+vec3 GetBillboardNormal()
+{
+ return vec3(-cBillboardRot[0][2], -cBillboardRot[1][2], -cBillboardRot[2][2]);
+}
+#endif
+
+#ifdef DIRBILLBOARD
+mat3 GetFaceCameraRotation(vec3 position, vec3 direction)
+{
+ vec3 cameraDir = normalize(position - cCameraPos);
+ vec3 front = normalize(direction);
+ vec3 right = normalize(cross(front, cameraDir));
+ vec3 up = normalize(cross(front, right));
+
+ return mat3(
+ right.x, up.x, front.x,
+ right.y, up.y, front.y,
+ right.z, up.z, front.z
+ );
+}
+
+vec3 GetBillboardPos(vec4 iPos, vec3 iDirection, mat4 modelMatrix)
+{
+ vec3 worldPos = (iPos * modelMatrix).xyz;
+ return worldPos + vec3(iTexCoord1.x, 0.0, iTexCoord1.y) * GetFaceCameraRotation(worldPos, iDirection);
+}
+
+vec3 GetBillboardNormal(vec4 iPos, vec3 iDirection, mat4 modelMatrix)
+{
+ vec3 worldPos = (iPos * modelMatrix).xyz;
+ return vec3(0.0, 1.0, 0.0) * GetFaceCameraRotation(worldPos, iDirection);
+}
+#endif
+
+#ifdef TRAILFACECAM
+vec3 GetTrailPos(vec4 iPos, vec3 iFront, float iScale, mat4 modelMatrix)
+{
+ vec3 up = normalize(cCameraPos - iPos.xyz);
+ vec3 right = normalize(cross(iFront, up));
+ return (vec4((iPos.xyz + right * iScale), 1.0) * modelMatrix).xyz;
+}
+
+vec3 GetTrailNormal(vec4 iPos)
+{
+ return normalize(cCameraPos - iPos.xyz);
+}
+#endif
+
+#ifdef TRAILBONE
+vec3 GetTrailPos(vec4 iPos, vec3 iParentPos, float iScale, mat4 modelMatrix)
+{
+ vec3 right = iParentPos - iPos.xyz;
+ return (vec4((iPos.xyz + right * iScale), 1.0) * modelMatrix).xyz;
+}
+
+vec3 GetTrailNormal(vec4 iPos, vec3 iParentPos, vec3 iForward)
+{
+ vec3 left = normalize(iPos.xyz - iParentPos);
+ vec3 up = normalize(cross(normalize(iForward), left));
+ return up;
+}
+#endif
+
+#if defined(SKINNED)
+ #define iModelMatrix GetSkinMatrix(iBlendWeights, iBlendIndices)
+#elif defined(INSTANCED)
+ #define iModelMatrix GetInstanceMatrix()
+#else
+ #define iModelMatrix cModel
+#endif
+
+vec3 GetWorldPos(mat4 modelMatrix)
+{
+ #if defined(BILLBOARD)
+ return GetBillboardPos(iPos, iTexCoord1, modelMatrix);
+ #elif defined(DIRBILLBOARD)
+ return GetBillboardPos(iPos, iNormal, modelMatrix);
+ #elif defined(TRAILFACECAM)
+ return GetTrailPos(iPos, iTangent.xyz, iTangent.w, modelMatrix);
+ #elif defined(TRAILBONE)
+ return GetTrailPos(iPos, iTangent.xyz, iTangent.w, modelMatrix);
+ #else
+ return (iPos * modelMatrix).xyz;
+ #endif
+}
+
+vec3 GetWorldNormal(mat4 modelMatrix)
+{
+ #if defined(BILLBOARD)
+ return GetBillboardNormal();
+ #elif defined(DIRBILLBOARD)
+ return GetBillboardNormal(iPos, iNormal, modelMatrix);
+ #elif defined(TRAILFACECAM)
+ return GetTrailNormal(iPos);
+ #elif defined(TRAILBONE)
+ return GetTrailNormal(iPos, iTangent.xyz, iNormal);
+ #else
+ return normalize(iNormal * GetNormalMatrix(modelMatrix));
+ #endif
+}
+
+vec4 GetWorldTangent(mat4 modelMatrix)
+{
+ #if defined(BILLBOARD)
+ return vec4(normalize(vec3(1.0, 0.0, 0.0) * cBillboardRot), 1.0);
+ #elif defined(DIRBILLBOARD)
+ return vec4(normalize(vec3(1.0, 0.0, 0.0) * GetNormalMatrix(modelMatrix)), 1.0);
+ #else
+ return vec4(normalize(iTangent.xyz * GetNormalMatrix(modelMatrix)), iTangent.w);
+ #endif
+}
+
+#else
+
+// Silence GLSL 150 deprecation warnings
+#ifdef GL3
+#define varying in
+
+#ifndef MRT_COUNT
+
+#if defined(DEFERRED)
+#define MRT_COUNT 4
+#elif defined(PREPASS)
+#define MRT_COUNT 2
+#else
+#define MRT_COUNT 1
+#endif
+
+#endif
+
+out vec4 fragData[MRT_COUNT];
+
+
+#define gl_FragColor fragData[0]
+#define gl_FragData fragData
+#endif
+
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/Uniforms.glsl b/bin/CoreData/Shaders/GLSL/Uniforms.glsl
new file mode 100644
index 0000000..ed60dcf
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Uniforms.glsl
@@ -0,0 +1,222 @@
+// Use of constant buffers on OpenGL 3 commented out for now as it seems to be slower in practice
+//#define USE_CBUFFERS
+
+#if !defined(GL3) || !defined(USE_CBUFFERS)
+
+// OpenGL 2 uniforms (no constant buffers)
+
+#ifdef COMPILEVS
+
+// Vertex shader uniforms
+uniform vec3 cAmbientStartColor;
+uniform vec3 cAmbientEndColor;
+uniform mat3 cBillboardRot;
+uniform vec3 cCameraPos;
+uniform float cNearClip;
+uniform float cFarClip;
+uniform vec4 cDepthMode;
+uniform vec3 cFrustumSize;
+uniform float cDeltaTime;
+uniform float cElapsedTime;
+uniform vec4 cGBufferOffsets;
+uniform vec4 cLightPos;
+uniform vec3 cLightDir;
+uniform vec4 cNormalOffsetScale;
+uniform mat4 cModel;
+uniform mat4 cView;
+uniform mat4 cViewInv;
+uniform mat4 cViewProj;
+uniform vec4 cUOffset;
+uniform vec4 cVOffset;
+uniform mat4 cZone;
+#if !defined(GL_ES) || defined(WEBGL)
+ uniform mat4 cLightMatrices[4];
+#else
+ uniform highp mat4 cLightMatrices[2];
+#endif
+#ifdef SKINNED
+ uniform vec4 cSkinMatrices[MAXBONES*3];
+#endif
+#ifdef NUMVERTEXLIGHTS
+ uniform vec4 cVertexLights[4*3];
+#endif
+#ifdef GL3
+ uniform vec4 cClipPlane;
+#endif
+#endif
+
+#ifdef COMPILEPS
+
+// Fragment shader uniforms
+#ifdef GL_ES
+ precision mediump float;
+#endif
+
+uniform vec4 cAmbientColor;
+uniform vec3 cCameraPosPS;
+uniform float cDeltaTimePS;
+uniform vec4 cDepthReconstruct;
+uniform float cElapsedTimePS;
+uniform vec4 cFogParams;
+uniform vec3 cFogColor;
+uniform vec2 cGBufferInvSize;
+uniform vec4 cLightColor;
+uniform vec4 cLightPosPS;
+uniform vec3 cLightDirPS;
+uniform vec4 cNormalOffsetScalePS;
+uniform vec4 cMatDiffColor;
+uniform vec3 cMatEmissiveColor;
+uniform vec3 cMatEnvMapColor;
+uniform vec4 cMatSpecColor;
+#ifdef PBR
+ uniform float cRoughness;
+ uniform float cMetallic;
+ uniform float cLightRad;
+ uniform float cLightLength;
+#endif
+uniform vec3 cZoneMin;
+uniform vec3 cZoneMax;
+uniform float cNearClipPS;
+uniform float cFarClipPS;
+uniform vec4 cShadowCubeAdjust;
+uniform vec4 cShadowDepthFade;
+uniform vec2 cShadowIntensity;
+uniform vec2 cShadowMapInvSize;
+uniform vec4 cShadowSplits;
+uniform mat4 cLightMatricesPS[4];
+#ifdef VSM_SHADOW
+uniform vec2 cVSMShadowParams;
+#endif
+#endif
+
+#else
+
+// OpenGL 3 uniforms (using constant buffers)
+
+#ifdef COMPILEVS
+
+uniform FrameVS
+{
+ float cDeltaTime;
+ float cElapsedTime;
+};
+
+uniform CameraVS
+{
+ vec3 cCameraPos;
+ float cNearClip;
+ float cFarClip;
+ vec4 cDepthMode;
+ vec3 cFrustumSize;
+ vec4 cGBufferOffsets;
+ mat4 cView;
+ mat4 cViewInv;
+ mat4 cViewProj;
+ vec4 cClipPlane;
+};
+
+uniform ZoneVS
+{
+ vec3 cAmbientStartColor;
+ vec3 cAmbientEndColor;
+ mat4 cZone;
+};
+
+uniform LightVS
+{
+ vec4 cLightPos;
+ vec3 cLightDir;
+ vec4 cNormalOffsetScale;
+#ifdef NUMVERTEXLIGHTS
+ vec4 cVertexLights[4 * 3];
+#else
+ mat4 cLightMatrices[4];
+#endif
+};
+
+#ifndef CUSTOM_MATERIAL_CBUFFER
+uniform MaterialVS
+{
+ vec4 cUOffset;
+ vec4 cVOffset;
+};
+#endif
+
+uniform ObjectVS
+{
+ mat4 cModel;
+#ifdef BILLBOARD
+ mat3 cBillboardRot;
+#endif
+#ifdef SKINNED
+ uniform vec4 cSkinMatrices[MAXBONES*3];
+#endif
+};
+
+#endif
+
+#ifdef COMPILEPS
+
+// Pixel shader uniforms
+uniform FramePS
+{
+ float cDeltaTimePS;
+ float cElapsedTimePS;
+};
+
+uniform CameraPS
+{
+ vec3 cCameraPosPS;
+ vec4 cDepthReconstruct;
+ vec2 cGBufferInvSize;
+ float cNearClipPS;
+ float cFarClipPS;
+};
+
+uniform ZonePS
+{
+ vec4 cAmbientColor;
+ vec4 cFogParams;
+ vec3 cFogColor;
+ vec3 cZoneMin;
+ vec3 cZoneMax;
+};
+
+uniform LightPS
+{
+ vec4 cLightColor;
+ vec4 cLightPosPS;
+ vec3 cLightDirPS;
+ vec4 cNormalOffsetScalePS;
+ vec4 cShadowCubeAdjust;
+ vec4 cShadowDepthFade;
+ vec2 cShadowIntensity;
+ vec2 cShadowMapInvSize;
+ vec4 cShadowSplits;
+ mat4 cLightMatricesPS[4];
+#ifdef VSM_SHADOW
+ vec2 cVSMShadowParams;
+#endif
+#ifdef PBR
+ float cLightRad;
+ float cLightLength;
+#endif
+};
+
+#ifndef CUSTOM_MATERIAL_CBUFFER
+uniform MaterialPS
+{
+ vec4 cMatDiffColor;
+ vec3 cMatEmissiveColor;
+ vec3 cMatEnvMapColor;
+ vec4 cMatSpecColor;
+#ifdef PBR
+ float cRoughness;
+ float cMetallic;
+#endif
+};
+#endif
+
+#endif
+
+#endif
diff --git a/bin/CoreData/Shaders/GLSL/Unlit.glsl b/bin/CoreData/Shaders/GLSL/Unlit.glsl
new file mode 100644
index 0000000..6b2e378
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Unlit.glsl
@@ -0,0 +1,63 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Fog.glsl"
+
+varying vec2 vTexCoord;
+varying vec4 vWorldPos;
+#ifdef VERTEXCOLOR
+ varying vec4 vColor;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vTexCoord = GetTexCoord(iTexCoord);
+ vWorldPos = vec4(worldPos, GetDepth(gl_Position));
+
+ #ifdef VERTEXCOLOR
+ vColor = iColor;
+ #endif
+
+}
+
+void PS()
+{
+ // Get material diffuse albedo
+ #ifdef DIFFMAP
+ vec4 diffColor = cMatDiffColor * texture2D(sDiffMap, vTexCoord);
+ #ifdef ALPHAMASK
+ if (diffColor.a < 0.5)
+ discard;
+ #endif
+ #else
+ vec4 diffColor = cMatDiffColor;
+ #endif
+
+ #ifdef VERTEXCOLOR
+ diffColor *= vColor;
+ #endif
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(vWorldPos.w, vWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(vWorldPos.w);
+ #endif
+
+ #if defined(PREPASS)
+ // Fill light pre-pass G-Buffer
+ gl_FragData[0] = vec4(0.5, 0.5, 0.5, 1.0);
+ gl_FragData[1] = vec4(EncodeDepth(vWorldPos.w), 0.0);
+ #elif defined(DEFERRED)
+ gl_FragData[0] = vec4(GetFog(diffColor.rgb, fogFactor), diffColor.a);
+ gl_FragData[1] = vec4(0.0, 0.0, 0.0, 0.0);
+ gl_FragData[2] = vec4(0.5, 0.5, 0.5, 1.0);
+ gl_FragData[3] = vec4(EncodeDepth(vWorldPos.w), 0.0);
+ #else
+ gl_FragColor = vec4(GetFog(diffColor.rgb, fogFactor), diffColor.a);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/UnlitParticle.glsl b/bin/CoreData/Shaders/GLSL/UnlitParticle.glsl
new file mode 100644
index 0000000..9be25ef
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/UnlitParticle.glsl
@@ -0,0 +1,90 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Fog.glsl"
+
+varying vec2 vTexCoord;
+varying vec4 vWorldPos;
+#ifdef VERTEXCOLOR
+ varying vec4 vColor;
+#endif
+#ifdef SOFTPARTICLES
+ varying vec4 vScreenPos;
+ uniform float cSoftParticleFadeScale;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vTexCoord = GetTexCoord(iTexCoord);
+ vWorldPos = vec4(worldPos, GetDepth(gl_Position));
+
+ #ifdef SOFTPARTICLES
+ vScreenPos = GetScreenPos(gl_Position);
+ #endif
+
+ #ifdef VERTEXCOLOR
+ vColor = iColor;
+ #endif
+
+}
+
+void PS()
+{
+ // Get material diffuse albedo
+ #ifdef DIFFMAP
+ vec4 diffColor = cMatDiffColor * texture2D(sDiffMap, vTexCoord);
+ #ifdef ALPHAMASK
+ if (diffColor.a < 0.5)
+ discard;
+ #endif
+ #else
+ vec4 diffColor = cMatDiffColor;
+ #endif
+
+ #ifdef VERTEXCOLOR
+ diffColor *= vColor;
+ #endif
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(vWorldPos.w, vWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(vWorldPos.w);
+ #endif
+
+ // Soft particle fade
+ // In expand mode depth test should be off. In that case do manual alpha discard test first to reduce fill rate
+ #ifdef SOFTPARTICLES
+ #ifdef EXPAND
+ if (diffColor.a < 0.01)
+ discard;
+ #endif
+
+ float particleDepth = vWorldPos.w;
+ #ifdef HWDEPTH
+ float depth = ReconstructDepth(texture2DProj(sDepthBuffer, vScreenPos).r);
+ #else
+ float depth = DecodeDepth(texture2DProj(sDepthBuffer, vScreenPos).rgb);
+ #endif
+
+ #ifdef EXPAND
+ float diffZ = max(particleDepth - depth, 0.0) * (cFarClipPS - cNearClipPS);
+ float fade = clamp(diffZ * cSoftParticleFadeScale, 0.0, 1.0);
+ #else
+ float diffZ = (depth - particleDepth) * (cFarClipPS - cNearClipPS);
+ float fade = clamp(1.0 - diffZ * cSoftParticleFadeScale, 0.0, 1.0);
+ #endif
+
+ #ifndef ADDITIVE
+ diffColor.a = max(diffColor.a - fade, 0.0);
+ #else
+ diffColor.rgb = max(diffColor.rgb - fade, vec3(0.0, 0.0, 0.0));
+ #endif
+ #endif
+
+ gl_FragColor = vec4(GetFog(diffColor.rgb, fogFactor), diffColor.a);
+}
diff --git a/bin/CoreData/Shaders/GLSL/Urho2D.glsl b/bin/CoreData/Shaders/GLSL/Urho2D.glsl
new file mode 100644
index 0000000..a16f8f5
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Urho2D.glsl
@@ -0,0 +1,23 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+
+varying vec2 vTexCoord;
+varying vec4 vColor;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+
+ vTexCoord = iTexCoord;
+ vColor = iColor;
+}
+
+void PS()
+{
+ vec4 diffColor = cMatDiffColor * vColor;
+ vec4 diffInput = texture2D(sDiffMap, vTexCoord);
+ gl_FragColor = diffColor * diffInput;
+}
diff --git a/bin/CoreData/Shaders/GLSL/Vegetation.glsl b/bin/CoreData/Shaders/GLSL/Vegetation.glsl
new file mode 100644
index 0000000..e68c60b
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Vegetation.glsl
@@ -0,0 +1,114 @@
+#include "Uniforms.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Lighting.glsl"
+
+uniform float cWindHeightFactor;
+uniform float cWindHeightPivot;
+uniform float cWindPeriod;
+uniform vec2 cWindWorldSpacing;
+
+#ifdef NORMALMAP
+ varying vec4 vTexCoord;
+ varying vec4 vTangent;
+#else
+ varying vec2 vTexCoord;
+#endif
+varying vec3 vNormal;
+varying vec4 vWorldPos;
+#ifdef VERTEXCOLOR
+ varying vec4 vColor;
+#endif
+#ifdef PERPIXEL
+ #ifdef SHADOW
+ #ifndef GL_ES
+ varying vec4 vShadowPos[NUMCASCADES];
+ #else
+ varying highp vec4 vShadowPos[NUMCASCADES];
+ #endif
+ #endif
+ #ifdef SPOTLIGHT
+ varying vec4 vSpotPos;
+ #endif
+ #ifdef POINTLIGHT
+ varying vec3 vCubeMaskVec;
+ #endif
+#else
+ varying vec3 vVertexLight;
+ varying vec4 vScreenPos;
+ #ifdef ENVCUBEMAP
+ varying vec3 vReflectionVec;
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ varying vec2 vTexCoord2;
+ #endif
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+
+ float windStrength = max(iPos.y - cWindHeightPivot, 0.0) * cWindHeightFactor;
+ float windPeriod = cElapsedTime * cWindPeriod + dot(worldPos.xz, cWindWorldSpacing);
+ worldPos.x += windStrength * sin(windPeriod);
+ worldPos.z -= windStrength * cos(windPeriod);
+
+ gl_Position = GetClipPos(worldPos);
+ vNormal = GetWorldNormal(modelMatrix);
+ vWorldPos = vec4(worldPos, GetDepth(gl_Position));
+
+ #ifdef VERTEXCOLOR
+ vColor = iColor;
+ #endif
+
+ #ifdef NORMALMAP
+ vec4 tangent = GetWorldTangent(modelMatrix);
+ vec3 bitangent = cross(tangent.xyz, vNormal) * tangent.w;
+ vTexCoord = vec4(GetTexCoord(iTexCoord), bitangent.xy);
+ vTangent = vec4(tangent.xyz, bitangent.z);
+ #else
+ vTexCoord = GetTexCoord(iTexCoord);
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ vec4 projWorldPos = vec4(worldPos, 1.0);
+
+ #ifdef SHADOW
+ // Shadow projection: transform from world space to shadow space
+ for (int i = 0; i < NUMCASCADES; i++)
+ vShadowPos[i] = GetShadowPos(i, vNormal, projWorldPos);
+ #endif
+
+ #ifdef SPOTLIGHT
+ // Spotlight projection: transform from world space to projector texture coordinates
+ vSpotPos = projWorldPos * cLightMatrices[0];
+ #endif
+
+ #ifdef POINTLIGHT
+ vCubeMaskVec = (worldPos - cLightPos.xyz) * mat3(cLightMatrices[0][0].xyz, cLightMatrices[0][1].xyz, cLightMatrices[0][2].xyz);
+ #endif
+ #else
+ // Ambient & per-vertex lighting
+ #if defined(LIGHTMAP) || defined(AO)
+ // If using lightmap, disregard zone ambient light
+ // If using AO, calculate ambient in the PS
+ vVertexLight = vec3(0.0, 0.0, 0.0);
+ vTexCoord2 = iTexCoord1;
+ #else
+ vVertexLight = GetAmbient(GetZonePos(worldPos));
+ #endif
+
+ #ifdef NUMVERTEXLIGHTS
+ for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+ vVertexLight += GetVertexLight(i, worldPos, vNormal) * cVertexLights[i * 3].rgb;
+ #endif
+
+ vScreenPos = GetScreenPos(gl_Position);
+
+ #ifdef ENVCUBEMAP
+ vReflectionVec = worldPos - cCameraPos;
+ #endif
+ #endif
+}
diff --git a/bin/CoreData/Shaders/GLSL/VegetationDepth.glsl b/bin/CoreData/Shaders/GLSL/VegetationDepth.glsl
new file mode 100644
index 0000000..d676bc7
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/VegetationDepth.glsl
@@ -0,0 +1,24 @@
+#include "Uniforms.glsl"
+#include "Transform.glsl"
+
+uniform float cWindHeightFactor;
+uniform float cWindHeightPivot;
+uniform float cWindPeriod;
+uniform vec2 cWindWorldSpacing;
+
+varying vec3 vTexCoord;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+
+ float windStrength = max(iPos.y - cWindHeightPivot, 0.0) * cWindHeightFactor;
+ float windPeriod = cElapsedTime * cWindPeriod + dot(worldPos.xz, cWindWorldSpacing);
+ worldPos.x += windStrength * sin(windPeriod);
+ worldPos.z -= windStrength * cos(windPeriod);
+
+ gl_Position = GetClipPos(worldPos);
+ vTexCoord = vec3(GetTexCoord(iTexCoord), GetDepth(gl_Position));
+}
+
diff --git a/bin/CoreData/Shaders/GLSL/VegetationShadow.glsl b/bin/CoreData/Shaders/GLSL/VegetationShadow.glsl
new file mode 100644
index 0000000..b477630
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/VegetationShadow.glsl
@@ -0,0 +1,24 @@
+#include "Uniforms.glsl"
+#include "Transform.glsl"
+
+uniform float cWindHeightFactor;
+uniform float cWindHeightPivot;
+uniform float cWindPeriod;
+uniform vec2 cWindWorldSpacing;
+
+varying vec2 vTexCoord;
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+
+ float windStrength = max(iPos.y - cWindHeightPivot, 0.0) * cWindHeightFactor;
+ float windPeriod = cElapsedTime * cWindPeriod + dot(worldPos.xz, cWindWorldSpacing);
+ worldPos.x += windStrength * sin(windPeriod);
+ worldPos.z -= windStrength * cos(windPeriod);
+
+ gl_Position = GetClipPos(worldPos);
+ vTexCoord = GetTexCoord(iTexCoord);
+}
+
diff --git a/bin/CoreData/Shaders/GLSL/Water.glsl b/bin/CoreData/Shaders/GLSL/Water.glsl
new file mode 100644
index 0000000..478e5fb
--- /dev/null
+++ b/bin/CoreData/Shaders/GLSL/Water.glsl
@@ -0,0 +1,66 @@
+#include "Uniforms.glsl"
+#include "Samplers.glsl"
+#include "Transform.glsl"
+#include "ScreenPos.glsl"
+#include "Fog.glsl"
+
+#ifndef GL_ES
+varying vec4 vScreenPos;
+varying vec2 vReflectUV;
+varying vec2 vWaterUV;
+varying vec4 vEyeVec;
+#else
+varying highp vec4 vScreenPos;
+varying highp vec2 vReflectUV;
+varying highp vec2 vWaterUV;
+varying highp vec4 vEyeVec;
+#endif
+varying vec3 vNormal;
+
+#ifdef COMPILEVS
+uniform vec2 cNoiseSpeed;
+uniform float cNoiseTiling;
+#endif
+#ifdef COMPILEPS
+uniform float cNoiseStrength;
+uniform float cFresnelPower;
+uniform vec3 cWaterTint;
+#endif
+
+void VS()
+{
+ mat4 modelMatrix = iModelMatrix;
+ vec3 worldPos = GetWorldPos(modelMatrix);
+ gl_Position = GetClipPos(worldPos);
+ vScreenPos = GetScreenPos(gl_Position);
+ // GetQuadTexCoord() returns a vec2 that is OK for quad rendering; multiply it with output W
+ // coordinate to make it work with arbitrary meshes such as the water plane (perform divide in pixel shader)
+ // Also because the quadTexCoord is based on the clip position, and Y is flipped when rendering to a texture
+ // on OpenGL, must flip again to cancel it out
+ vReflectUV = GetQuadTexCoord(gl_Position);
+ vReflectUV.y = 1.0 - vReflectUV.y;
+ vReflectUV *= gl_Position.w;
+ vWaterUV = iTexCoord * cNoiseTiling + cElapsedTime * cNoiseSpeed;
+ vNormal = GetWorldNormal(modelMatrix);
+ vEyeVec = vec4(cCameraPos - worldPos, GetDepth(gl_Position));
+}
+
+void PS()
+{
+ vec2 refractUV = vScreenPos.xy / vScreenPos.w;
+ vec2 reflectUV = vReflectUV.xy / vScreenPos.w;
+
+ vec2 noise = (texture2D(sNormalMap, vWaterUV).rg - 0.5) * cNoiseStrength;
+ refractUV += noise;
+ // Do not shift reflect UV coordinate upward, because it will reveal the clipping of geometry below water
+ if (noise.y < 0.0)
+ noise.y = 0.0;
+ reflectUV += noise;
+
+ float fresnel = pow(1.0 - clamp(dot(normalize(vEyeVec.xyz), vNormal), 0.0, 1.0), cFresnelPower);
+ vec3 refractColor = texture2D(sEnvMap, refractUV).rgb * cWaterTint;
+ vec3 reflectColor = texture2D(sDiffMap, reflectUV).rgb;
+ vec3 finalColor = mix(refractColor, reflectColor, fresnel);
+
+ gl_FragColor = vec4(GetFog(finalColor, GetFogFactor(vEyeVec.w)), 1.0);
+}
diff --git a/bin/CoreData/Shaders/HLSL/AutoExposure.hlsl b/bin/CoreData/Shaders/HLSL/AutoExposure.hlsl
new file mode 100644
index 0000000..5fc8c90
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/AutoExposure.hlsl
@@ -0,0 +1,118 @@
+#include "Uniforms.hlsl"
+#include "Transform.hlsl"
+#include "Samplers.hlsl"
+#include "ScreenPos.hlsl"
+#include "PostProcess.hlsl"
+
+uniform float cAutoExposureAdaptRate;
+uniform float2 cAutoExposureLumRange;
+uniform float cAutoExposureMiddleGrey;
+uniform float2 cHDR128Offsets;
+uniform float2 cLum64Offsets;
+uniform float2 cLum16Offsets;
+uniform float2 cLum4Offsets;
+uniform float2 cHDR128InvSize;
+uniform float2 cLum64InvSize;
+uniform float2 cLum16InvSize;
+uniform float2 cLum4InvSize;
+
+#ifndef D3D11
+float GatherAvgLum(sampler2D texSampler, float2 texCoord, float2 texelSize)
+#else
+float GatherAvgLum(Texture2D tex, SamplerState texSampler, float2 texCoord, float2 texelSize)
+#endif
+{
+ float lumAvg = 0.0;
+ #ifndef D3D11
+ lumAvg += tex2D(texSampler, texCoord + float2(0.0, 0.0) * texelSize).r;
+ lumAvg += tex2D(texSampler, texCoord + float2(0.0, 2.0) * texelSize).r;
+ lumAvg += tex2D(texSampler, texCoord + float2(2.0, 2.0) * texelSize).r;
+ lumAvg += tex2D(texSampler, texCoord + float2(2.0, 0.0) * texelSize).r;
+ #else
+ lumAvg += tex.Sample(texSampler, texCoord + float2(0.0, 0.0) * texelSize).r;
+ lumAvg += tex.Sample(texSampler, texCoord + float2(0.0, 2.0) * texelSize).r;
+ lumAvg += tex.Sample(texSampler, texCoord + float2(2.0, 2.0) * texelSize).r;
+ lumAvg += tex.Sample(texSampler, texCoord + float2(2.0, 0.0) * texelSize).r;
+ #endif
+ return lumAvg / 4.0;
+}
+
+void VS(float4 iPos : POSITION,
+ out float2 oTexCoord : TEXCOORD0,
+ out float2 oScreenPos : TEXCOORD1,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+
+ oTexCoord = GetQuadTexCoord(oPos);
+
+ #ifdef LUMINANCE64
+ oTexCoord = GetQuadTexCoord(oPos) + cHDR128Offsets;
+ #endif
+
+ #ifdef LUMINANCE16
+ oTexCoord = GetQuadTexCoord(oPos) + cLum64Offsets;
+ #endif
+
+ #ifdef LUMINANCE4
+ oTexCoord = GetQuadTexCoord(oPos) + cLum16Offsets;
+ #endif
+
+ #ifdef LUMINANCE1
+ oTexCoord = GetQuadTexCoord(oPos) + cLum4Offsets;
+ #endif
+
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+ float2 iScreenPos : TEXCOORD1,
+ out float4 oColor : OUTCOLOR0)
+{
+ #ifdef LUMINANCE64
+ float logLumSum = 0.0;
+ logLumSum += log(dot(Sample2D(DiffMap, iTexCoord + float2(0.0, 0.0) * cHDR128InvSize).rgb, LumWeights) + 1e-5);
+ logLumSum += log(dot(Sample2D(DiffMap, iTexCoord + float2(0.0, 2.0) * cHDR128InvSize).rgb, LumWeights) + 1e-5);
+ logLumSum += log(dot(Sample2D(DiffMap, iTexCoord + float2(2.0, 2.0) * cHDR128InvSize).rgb, LumWeights) + 1e-5);
+ logLumSum += log(dot(Sample2D(DiffMap, iTexCoord + float2(2.0, 0.0) * cHDR128InvSize).rgb, LumWeights) + 1e-5);
+ oColor = logLumSum;
+ #endif
+
+ #ifdef LUMINANCE16
+ #ifndef D3D11
+ oColor = GatherAvgLum(sDiffMap, iTexCoord, cLum64InvSize);
+ #else
+ oColor = GatherAvgLum(tDiffMap, sDiffMap, iTexCoord, cLum64InvSize);
+ #endif
+ #endif
+
+ #ifdef LUMINANCE4
+ #ifndef D3D11
+ oColor = GatherAvgLum(sDiffMap, iTexCoord, cLum16InvSize);
+ #else
+ oColor = GatherAvgLum(tDiffMap, sDiffMap, iTexCoord, cLum16InvSize);
+ #endif
+ #endif
+
+ #ifdef LUMINANCE1
+ #ifndef D3D11
+ oColor = exp(GatherAvgLum(sDiffMap, iTexCoord, cLum4InvSize) / 16.0);
+ #else
+ oColor = exp(GatherAvgLum(tDiffMap, sDiffMap, iTexCoord, cLum4InvSize) / 16.0);
+ #endif
+ #endif
+
+ #ifdef ADAPTLUMINANCE
+ float adaptedLum = Sample2D(DiffMap, iTexCoord).r;
+ float lum = clamp(Sample2D(NormalMap, iTexCoord).r, cAutoExposureLumRange.x, cAutoExposureLumRange.y);
+ oColor = adaptedLum + (lum - adaptedLum) * (1.0 - exp(-cDeltaTimePS * cAutoExposureAdaptRate));
+ #endif
+
+ #ifdef EXPOSE
+ float3 color = Sample2D(DiffMap, iScreenPos).rgb;
+ float adaptedLum = Sample2D(NormalMap, iTexCoord).r;
+ oColor = float4(color * (cAutoExposureMiddleGrey / adaptedLum), 1.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/BRDF.hlsl b/bin/CoreData/Shaders/HLSL/BRDF.hlsl
new file mode 100644
index 0000000..2b58db1
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/BRDF.hlsl
@@ -0,0 +1,164 @@
+#ifdef COMPILEPS
+ #ifdef PBR
+
+ // Following BRDF methods are based upon research Frostbite EA
+ //[Lagrade et al. 2014, "Moving Frostbite to Physically Based Rendering"]
+
+ //Schlick Fresnel
+ //specular = the rgb specular color value of the pixel
+ //VdotH = the dot product of the camera view direction and the half vector
+ float3 SchlickFresnel(float3 specular, float VdotH)
+ {
+ return specular + (float3(1.0, 1.0, 1.0) - specular) * pow(1.0 - VdotH, 5.0);
+ }
+
+ //Schlick Gaussian Fresnel
+ //specular = the rgb specular color value of the pixel
+ //VdotH = the dot product of the camera view direction and the half vector
+ float3 SchlickGaussianFresnel(in float3 specular, in float VdotH)
+ {
+ float sphericalGaussian = pow(2.0, (-5.55473 * VdotH - 6.98316) * VdotH);
+ return specular + (float3(1.0, 1.0, 1.0) - specular) * sphericalGaussian;
+ }
+
+ float3 SchlickFresnelCustom(float3 specular, float LdotH)
+ {
+ float ior = 0.25;
+ float airIor = 1.000277;
+ float f0 = (ior - airIor) / (ior + airIor);
+ const float max_ior = 2.5;
+ f0 = clamp(f0 * f0, 0.0, (max_ior - airIor) / (max_ior + airIor));
+ return specular * (f0 + (1 - f0) * pow(2, (-5.55473 * LdotH - 6.98316) * LdotH));
+ }
+
+ //Get Fresnel
+ //specular = the rgb specular color value of the pixel
+ //VdotH = the dot product of the camera view direction and the half vector
+ float3 Fresnel(float3 specular, float VdotH, float LdotH)
+ {
+ return SchlickFresnelCustom(specular, LdotH);
+ //return SchlickFresnel(specular, VdotH);
+ }
+
+ // Smith GGX corrected Visibility
+ // NdotL = the dot product of the normal and direction to the light
+ // NdotV = the dot product of the normal and the camera view direction
+ // roughness = the roughness of the pixel
+ float SmithGGXSchlickVisibility(float NdotL, float NdotV, float roughness)
+ {
+ float rough2 = roughness * roughness;
+ float lambdaV = NdotL * sqrt((-NdotV * rough2 + NdotV) * NdotV + rough2);
+ float lambdaL = NdotV * sqrt((-NdotL * rough2 + NdotL) * NdotL + rough2);
+
+ return 0.5 / (lambdaV + lambdaL);
+ }
+
+ float NeumannVisibility(float NdotV, float NdotL)
+ {
+ return NdotL * NdotV / max(1e-7, max(NdotL, NdotV));
+ }
+
+ // Get Visibility
+ // NdotL = the dot product of the normal and direction to the light
+ // NdotV = the dot product of the normal and the camera view direction
+ // roughness = the roughness of the pixel
+ float Visibility(float NdotL, float NdotV, float roughness)
+ {
+ return NeumannVisibility(NdotV, NdotL);
+ //return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
+ }
+
+ // GGX Distribution
+ // NdotH = the dot product of the normal and the half vector
+ // roughness = the roughness of the pixel
+ float GGXDistribution(float NdotH, float roughness)
+ {
+ float rough2 = roughness * roughness;
+ float tmp = (NdotH * rough2 - NdotH) * NdotH + 1;
+ return rough2 / (tmp * tmp);
+ }
+
+ // Blinn Distribution
+ // NdotH = the dot product of the normal and the half vector
+ // roughness = the roughness of the pixel
+ float BlinnPhongDistribution(in float NdotH, in float roughness)
+ {
+ const float specPower = max((2.0 / (roughness * roughness)) - 2.0, 1e-4f); // Calculate specular power from roughness
+ return pow(saturate(NdotH), specPower);
+ }
+
+ // Beckmann Distribution
+ // NdotH = the dot product of the normal and the half vector
+ // roughness = the roughness of the pixel
+ float BeckmannDistribution(in float NdotH, in float roughness)
+ {
+ const float rough2 = roughness * roughness;
+ const float roughnessA = 1.0 / (4.0 * rough2 * pow(NdotH, 4.0));
+ const float roughnessB = NdotH * NdotH - 1.0;
+ const float roughnessC = rough2 * NdotH * NdotH;
+ return roughnessA * exp(roughnessB / roughnessC);
+ }
+
+ // Get Distribution
+ // NdotH = the dot product of the normal and the half vector
+ // roughness = the roughness of the pixel
+ float Distribution(float NdotH, float roughness)
+ {
+ return GGXDistribution(NdotH, roughness);
+ }
+
+ // Lambertian Diffuse
+ // diffuseColor = the rgb color value of the pixel
+ // roughness = the roughness of the pixel
+ // NdotV = the normal dot with the camera view direction
+ // NdotL = the normal dot with the light direction
+ // VdotH = the camera view direction dot with the half vector
+ float3 LambertianDiffuse(float3 diffuseColor)
+ {
+ return diffuseColor * (1.0 / M_PI) ;
+ }
+
+ // Custom Lambertian Diffuse
+ // diffuseColor = the rgb color value of the pixel
+ // roughness = the roughness of the pixel
+ // NdotV = the normal dot with the camera view direction
+ // NdotL = the normal dot with the light direction
+ // VdotH = the camera view direction dot with the half vector
+ float3 CustomLambertianDiffuse(float3 diffuseColor, float NdotV, float roughness)
+ {
+ return diffuseColor * (1.0 / M_PI) * pow(NdotV, 0.5 + 0.3 * roughness);
+ }
+
+ // Burley Diffuse
+ // diffuseColor = the rgb color value of the pixel
+ // roughness = the roughness of the pixel
+ // NdotV = the normal dot with the camera view direction
+ // NdotL = the normal dot with the light direction
+ // VdotH = the camera view direction dot with the half vector
+ float3 BurleyDiffuse(float3 diffuseColor, float roughness, float NdotV, float NdotL, float VdotH)
+ {
+ const float energyBias = lerp(0, 0.5, roughness);
+ const float energyFactor = lerp(1.0, 1.0 / 1.51, roughness);
+ const float fd90 = energyBias + 2.0 * VdotH * VdotH * roughness;
+ const float f0 = 1.0;
+ const float lightScatter = f0 + (fd90 - f0) * pow(1.0f - NdotL, 5.0f);
+ const float viewScatter = f0 + (fd90 - f0) * pow(1.0f - NdotV, 5.0f);
+
+ return diffuseColor * lightScatter * viewScatter * energyFactor;
+ }
+
+ //Get Diffuse
+ // diffuseColor = the rgb color value of the pixel
+ // roughness = the roughness of the pixel
+ // NdotV = the normal dot with the camera view direction
+ // NdotL = the normal dot with the light direction
+ // VdotH = the camera view direction dot with the half vector
+ float3 Diffuse(float3 diffuseColor, float roughness, float NdotV, float NdotL, float VdotH)
+ {
+ //return LambertianDiffuse(diffuseColor);
+ return CustomLambertianDiffuse(diffuseColor, NdotV, roughness);
+ //return BurleyDiffuse(diffuseColor, roughness, NdotV, NdotL, VdotH);
+ }
+
+ #endif
+#endif
diff --git a/bin/CoreData/Shaders/HLSL/Basic.hlsl b/bin/CoreData/Shaders/HLSL/Basic.hlsl
new file mode 100644
index 0000000..d8a8557
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Basic.hlsl
@@ -0,0 +1,88 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+
+void VS(float4 iPos : POSITION,
+ #ifdef DIFFMAP
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ #if defined(BILLBOARD) || defined(DIRBILLBOARD)
+ float2 iSize : TEXCOORD1,
+ #endif
+ #if defined(DIRBILLBOARD) || defined(TRAILBONE)
+ float3 iNormal : NORMAL,
+ #endif
+ #if defined(TRAILFACECAM) || defined(TRAILBONE)
+ float4 iTangent : TANGENT,
+ #endif
+ #ifdef DIFFMAP
+ out float2 oTexCoord : TEXCOORD0,
+ #endif
+ #ifdef VERTEXCOLOR
+ out float4 oColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ out float oClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+
+ #if defined(D3D11) && defined(CLIPPLANE)
+ oClip = dot(oPos, cClipPlane);
+ #endif
+
+ #ifdef VERTEXCOLOR
+ oColor = iColor;
+ #endif
+ #ifdef DIFFMAP
+ oTexCoord = iTexCoord;
+ #endif
+}
+
+void PS(
+ #if defined(DIFFMAP) || defined(ALPHAMAP)
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ float iClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ float4 diffColor = cMatDiffColor;
+
+ #ifdef VERTEXCOLOR
+ diffColor *= iColor;
+ #endif
+
+ #if (!defined(DIFFMAP)) && (!defined(ALPHAMAP))
+ oColor = diffColor;
+ #endif
+ #ifdef DIFFMAP
+ float4 diffInput = Sample2D(DiffMap, iTexCoord);
+ #ifdef ALPHAMASK
+ if (diffInput.a < 0.5)
+ discard;
+ #endif
+ oColor = diffColor * diffInput;
+ #endif
+ #ifdef ALPHAMAP
+ float alphaInput = Sample2D(DiffMap, iTexCoord).a;
+ oColor = float4(diffColor.rgb, diffColor.a * alphaInput);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/Bloom.hlsl b/bin/CoreData/Shaders/HLSL/Bloom.hlsl
new file mode 100644
index 0000000..9dea8b0
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Bloom.hlsl
@@ -0,0 +1,91 @@
+#include "Uniforms.hlsl"
+#include "Transform.hlsl"
+#include "Samplers.hlsl"
+#include "ScreenPos.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms
+uniform float cBloomThreshold;
+uniform float2 cBloomMix;
+uniform float2 cBlurHOffsets;
+uniform float2 cBlurHInvSize;
+
+#else
+
+// D3D11 constant buffers
+#ifdef COMPILEVS
+cbuffer CustomVS : register(b6)
+{
+ float2 cBlurHOffsets;
+}
+#else
+cbuffer CustomPS : register(b6)
+{
+ float cBloomThreshold;
+ float2 cBloomMix;
+ float2 cBlurHInvSize;
+}
+#endif
+
+#endif
+
+static const float offsets[5] = {
+ 2.0,
+ 1.0,
+ 0.0,
+ -1.0,
+ -2.0,
+};
+
+static const float weights[5] = {
+ 0.1,
+ 0.25,
+ 0.3,
+ 0.25,
+ 0.1
+};
+
+void VS(float4 iPos : POSITION,
+ out float2 oTexCoord : TEXCOORD0,
+ out float2 oScreenPos : TEXCOORD1,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oTexCoord = GetQuadTexCoord(oPos) + cBlurHOffsets;
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+ float2 iScreenPos : TEXCOORD1,
+ out float4 oColor : OUTCOLOR0)
+{
+ #ifdef BRIGHT
+ float3 rgb = Sample2D(DiffMap, iScreenPos).rgb;
+ oColor = float4((rgb - cBloomThreshold) / (1.0 - cBloomThreshold), 1.0);
+ #endif
+
+ #ifdef BLURH
+ float3 rgb = 0.0;
+ for (int i = 0; i < 5; ++i)
+ rgb += Sample2D(DiffMap, iTexCoord + (float2(offsets[i], 0.0)) * cBlurHInvSize).rgb * weights[i];
+ oColor = float4(rgb, 1.0);
+ #endif
+
+ #ifdef BLURV
+ float3 rgb = 0.0;
+ for (int i = 0; i < 5; ++i)
+ rgb += Sample2D(DiffMap, iTexCoord + (float2(0.0, offsets[i])) * cBlurHInvSize).rgb * weights[i];
+ oColor = float4(rgb, 1.0);
+ #endif
+
+ #ifdef COMBINE
+ float3 original = Sample2D(DiffMap, iScreenPos).rgb * cBloomMix.x;
+ float3 bloom = Sample2D(NormalMap, iTexCoord).rgb * cBloomMix.y;
+ // Prevent oversaturation
+ original *= saturate(1.0 - bloom);
+ oColor = float4(original + bloom, 1.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/BloomHDR.hlsl b/bin/CoreData/Shaders/HLSL/BloomHDR.hlsl
new file mode 100644
index 0000000..2f913a5
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/BloomHDR.hlsl
@@ -0,0 +1,164 @@
+#include "Uniforms.hlsl"
+#include "Transform.hlsl"
+#include "Samplers.hlsl"
+#include "ScreenPos.hlsl"
+#include "PostProcess.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms
+uniform float cBloomHDRThreshold;
+uniform float2 cBloomHDRBlurDir;
+uniform float cBloomHDRBlurRadius;
+uniform float cBloomHDRBlurSigma;
+uniform float2 cBloomHDRMix;
+uniform float2 cBright2Offsets;
+uniform float2 cBright4Offsets;
+uniform float2 cBright8Offsets;
+uniform float2 cBright16Offsets;
+uniform float2 cBright2InvSize;
+uniform float2 cBright4InvSize;
+uniform float2 cBright8InvSize;
+uniform float2 cBright16InvSize;
+
+#else
+
+// D3D11 constant buffers
+#ifdef COMPILEVS
+cbuffer CustomVS : register(b6)
+{
+ float2 cBright2Offsets;
+ float2 cBright4Offsets;
+ float2 cBright8Offsets;
+ float2 cBright16Offsets;
+}
+#else
+cbuffer CustomPS : register(b6)
+{
+ float cBloomHDRThreshold;
+ float2 cBloomHDRBlurDir;
+ float cBloomHDRBlurRadius;
+ float cBloomHDRBlurSigma;
+ float2 cBloomHDRMix;
+ float2 cBright2InvSize;
+ float2 cBright4InvSize;
+ float2 cBright8InvSize;
+ float2 cBright16InvSize;
+}
+#endif
+
+#endif
+
+static const int BlurKernelSize = 5;
+
+void VS(float4 iPos : POSITION,
+ out float2 oTexCoord : TEXCOORD0,
+ out float2 oScreenPos : TEXCOORD1,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+
+ oTexCoord = GetQuadTexCoord(oPos);
+
+ #ifdef BLUR2
+ oTexCoord = GetQuadTexCoord(oPos) + cBright2Offsets;
+ #endif
+
+ #ifdef BLUR4
+ oTexCoord = GetQuadTexCoord(oPos) + cBright4Offsets;
+ #endif
+
+ #ifdef BLUR8
+ oTexCoord = GetQuadTexCoord(oPos) + cBright8Offsets;
+ #endif
+
+ #ifdef BLUR16
+ oTexCoord = GetQuadTexCoord(oPos) + cBright16Offsets;
+ #endif
+
+ #ifdef COMBINE2
+ oTexCoord = GetQuadTexCoord(oPos) + cBright2Offsets;
+ #endif
+
+ #ifdef COMBINE4
+ oTexCoord = GetQuadTexCoord(oPos) + cBright4Offsets;
+ #endif
+
+ #ifdef COMBINE8
+ oTexCoord = GetQuadTexCoord(oPos) + cBright8Offsets;
+ #endif
+
+ #ifdef COMBINE16
+ oTexCoord = GetQuadTexCoord(oPos) + cBright16Offsets;
+ #endif
+
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+ float2 iScreenPos : TEXCOORD1,
+ out float4 oColor : OUTCOLOR0)
+{
+ #ifdef BRIGHT
+ float3 color = Sample2D(DiffMap, iScreenPos).rgb;
+ oColor = float4(max(color - cBloomHDRThreshold, 0.0), 1.0);
+ #endif
+
+ #ifndef D3D11
+
+ #ifdef BLUR16
+ oColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright16InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, sDiffMap, iTexCoord);
+ #endif
+
+ #ifdef BLUR8
+ oColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright8InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, sDiffMap, iTexCoord);
+ #endif
+
+ #ifdef BLUR4
+ oColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright4InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, sDiffMap, iTexCoord);
+ #endif
+
+ #ifdef BLUR2
+ oColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright2InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, sDiffMap, iTexCoord);
+ #endif
+
+ #else
+
+ #ifdef BLUR16
+ oColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright16InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, tDiffMap, sDiffMap, iTexCoord);
+ #endif
+
+ #ifdef BLUR8
+ oColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright8InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, tDiffMap, sDiffMap, iTexCoord);
+ #endif
+
+ #ifdef BLUR4
+ oColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright4InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, tDiffMap, sDiffMap, iTexCoord);
+ #endif
+
+ #ifdef BLUR2
+ oColor = GaussianBlur(BlurKernelSize, cBloomHDRBlurDir, cBright2InvSize * cBloomHDRBlurRadius, cBloomHDRBlurSigma, tDiffMap, sDiffMap, iTexCoord);
+ #endif
+
+ #endif
+
+ #ifdef COMBINE16
+ oColor = Sample2D(DiffMap, iScreenPos) + Sample2D(NormalMap, iTexCoord);
+ #endif
+
+ #ifdef COMBINE8
+ oColor = Sample2D(DiffMap, iScreenPos) + Sample2D(NormalMap, iTexCoord);
+ #endif
+
+ #ifdef COMBINE4
+ oColor = Sample2D(DiffMap, iScreenPos) + Sample2D(NormalMap, iTexCoord);
+ #endif
+
+ #ifdef COMBINE2
+ float3 color = Sample2D(DiffMap, iScreenPos).rgb * cBloomHDRMix.x;
+ float3 bloom = Sample2D(NormalMap, iTexCoord).rgb * cBloomHDRMix.y;
+ oColor = float4(color + bloom, 1.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/Blur.hlsl b/bin/CoreData/Shaders/HLSL/Blur.hlsl
new file mode 100644
index 0000000..2476373
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Blur.hlsl
@@ -0,0 +1,60 @@
+#include "Uniforms.hlsl"
+#include "Transform.hlsl"
+#include "Samplers.hlsl"
+#include "ScreenPos.hlsl"
+#include "PostProcess.hlsl"
+
+uniform float2 cBlurDir;
+uniform float cBlurRadius;
+uniform float cBlurSigma;
+uniform float2 cBlurHOffsets;
+uniform float2 cBlurHInvSize;
+
+void VS(float4 iPos : POSITION,
+ out float2 oTexCoord : TEXCOORD0,
+ out float2 oScreenPos : TEXCOORD1,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oTexCoord = GetQuadTexCoord(oPos) + cBlurHOffsets;
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+ float2 iScreenPos : TEXCOORD1,
+ out float4 oColor : OUTCOLOR0)
+{
+ #ifdef BLUR3
+ #ifndef D3D11
+ oColor = GaussianBlur(3, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, sDiffMap, iTexCoord);
+ #else
+ oColor = GaussianBlur(3, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, tDiffMap, sDiffMap, iTexCoord);
+ #endif
+ #endif
+
+ #ifdef BLUR5
+ #ifndef D3D11
+ oColor = GaussianBlur(5, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, sDiffMap, iTexCoord);
+ #else
+ oColor = GaussianBlur(5, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, tDiffMap, sDiffMap, iTexCoord);
+ #endif
+ #endif
+
+ #ifdef BLUR7
+ #ifndef D3D11
+ oColor = GaussianBlur(7, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, sDiffMap, iTexCoord);
+ #else
+ oColor = GaussianBlur(7, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, tDiffMap, sDiffMap, iTexCoord);
+ #endif
+ #endif
+
+ #ifdef BLUR9
+ #ifndef D3D11
+ oColor = GaussianBlur(9, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, sDiffMap, iTexCoord);
+ #else
+ oColor = GaussianBlur(9, cBlurDir, cBlurHInvSize * cBlurRadius, cBlurSigma, tDiffMap, sDiffMap, iTexCoord);
+ #endif
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/ClearFramebuffer.hlsl b/bin/CoreData/Shaders/HLSL/ClearFramebuffer.hlsl
new file mode 100644
index 0000000..fa7c380
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/ClearFramebuffer.hlsl
@@ -0,0 +1,17 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+
+void VS(float4 iPos : POSITION,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+}
+
+void PS(out float4 oColor : OUTCOLOR0)
+{
+ oColor = cMatDiffColor;
+}
diff --git a/bin/CoreData/Shaders/HLSL/ColorCorrection.hlsl b/bin/CoreData/Shaders/HLSL/ColorCorrection.hlsl
new file mode 100644
index 0000000..64598fa
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/ColorCorrection.hlsl
@@ -0,0 +1,26 @@
+#include "Uniforms.hlsl"
+#include "Transform.hlsl"
+#include "Samplers.hlsl"
+#include "ScreenPos.hlsl"
+#include "PostProcess.hlsl"
+
+void VS(float4 iPos : POSITION,
+ out float2 oScreenPos : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iScreenPos : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ float3 color = Sample2D(DiffMap, iScreenPos).rgb;
+ #ifndef D3D11
+ oColor = float4(ColorCorrection(color, sVolumeMap), 1.0);
+ #else
+ oColor = float4(ColorCorrection(color, tVolumeMap, sVolumeMap), 1.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/Constants.hlsl b/bin/CoreData/Shaders/HLSL/Constants.hlsl
new file mode 100644
index 0000000..a2d6c67
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Constants.hlsl
@@ -0,0 +1,7 @@
+#define M_PI 3.14159265358979323846
+#define M_EPSILON 0.0001
+
+#ifdef PBR
+#define ROUGHNESS_FLOOR 0.004
+#define METALNESS_FLOOR 0.03
+#endif
diff --git a/bin/CoreData/Shaders/HLSL/CopyFramebuffer.hlsl b/bin/CoreData/Shaders/HLSL/CopyFramebuffer.hlsl
new file mode 100644
index 0000000..2e3dfce
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/CopyFramebuffer.hlsl
@@ -0,0 +1,20 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+
+void VS(float4 iPos : POSITION,
+ out float2 oScreenPos : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iScreenPos : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ oColor = Sample2D(DiffMap, iScreenPos);
+}
diff --git a/bin/CoreData/Shaders/HLSL/DeferredLight.hlsl b/bin/CoreData/Shaders/HLSL/DeferredLight.hlsl
new file mode 100644
index 0000000..5a7ab18
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/DeferredLight.hlsl
@@ -0,0 +1,106 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Lighting.hlsl"
+
+void VS(float4 iPos : POSITION,
+ #ifdef DIRLIGHT
+ out float2 oScreenPos : TEXCOORD0,
+ #else
+ out float4 oScreenPos : TEXCOORD0,
+ #endif
+ out float3 oFarRay : TEXCOORD1,
+ #ifdef ORTHO
+ out float3 oNearRay : TEXCOORD2,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ #ifdef DIRLIGHT
+ oScreenPos = GetScreenPosPreDiv(oPos);
+ oFarRay = GetFarRay(oPos);
+ #ifdef ORTHO
+ oNearRay = GetNearRay(oPos);
+ #endif
+ #else
+ oScreenPos = GetScreenPos(oPos);
+ oFarRay = GetFarRay(oPos) * oPos.w;
+ #ifdef ORTHO
+ oNearRay = GetNearRay(oPos) * oPos.w;
+ #endif
+ #endif
+}
+
+void PS(
+ #ifdef DIRLIGHT
+ float2 iScreenPos : TEXCOORD0,
+ #else
+ float4 iScreenPos : TEXCOORD0,
+ #endif
+ float3 iFarRay : TEXCOORD1,
+ #ifdef ORTHO
+ float3 iNearRay : TEXCOORD2,
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ // If rendering a directional light quad, optimize out the w divide
+ #ifdef DIRLIGHT
+ float depth = Sample2DLod0(DepthBuffer, iScreenPos).r;
+ #ifdef HWDEPTH
+ depth = ReconstructDepth(depth);
+ #endif
+ #ifdef ORTHO
+ float3 worldPos = lerp(iNearRay, iFarRay, depth);
+ #else
+ float3 worldPos = iFarRay * depth;
+ #endif
+ float4 albedoInput = Sample2DLod0(AlbedoBuffer, iScreenPos);
+ float4 normalInput = Sample2DLod0(NormalBuffer, iScreenPos);
+ #else
+ float depth = Sample2DProj(DepthBuffer, iScreenPos).r;
+ #ifdef HWDEPTH
+ depth = ReconstructDepth(depth);
+ #endif
+ #ifdef ORTHO
+ float3 worldPos = lerp(iNearRay, iFarRay, depth) / iScreenPos.w;
+ #else
+ float3 worldPos = iFarRay * depth / iScreenPos.w;
+ #endif
+ float4 albedoInput = Sample2DProj(AlbedoBuffer, iScreenPos);
+ float4 normalInput = Sample2DProj(NormalBuffer, iScreenPos);
+ #endif
+
+ // Position acquired via near/far ray is relative to camera. Bring position to world space
+ float3 eyeVec = -worldPos;
+ worldPos += cCameraPosPS;
+
+ float3 normal = normalize(normalInput.rgb * 2.0 - 1.0);
+ float4 projWorldPos = float4(worldPos, 1.0);
+ float3 lightColor;
+ float3 lightDir;
+
+ float diff = GetDiffuse(normal, worldPos, lightDir);
+
+ #ifdef SHADOW
+ diff *= GetShadowDeferred(projWorldPos, normal, depth);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ float4 spotPos = mul(projWorldPos, cLightMatricesPS[0]);
+ lightColor = spotPos.w > 0.0 ? Sample2DProj(LightSpotMap, spotPos).rgb * cLightColor.rgb : 0.0;
+ #elif defined(CUBEMASK)
+ lightColor = texCUBE(sLightCubeMap, mul(worldPos - cLightPosPS.xyz, (float3x3)cLightMatricesPS[0])).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ #ifdef SPECULAR
+ float spec = GetSpecular(normal, eyeVec, lightDir, normalInput.a * 255.0);
+ oColor = diff * float4(lightColor * (albedoInput.rgb + spec * cLightColor.a * albedoInput.aaa), 0.0);
+ #else
+ oColor = diff * float4(lightColor * albedoInput.rgb, 0.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/Depth.hlsl b/bin/CoreData/Shaders/HLSL/Depth.hlsl
new file mode 100644
index 0000000..1355903
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Depth.hlsl
@@ -0,0 +1,41 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+
+void VS(float4 iPos : POSITION,
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ #ifndef NOUV
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ out float3 oTexCoord : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ // Define a 0,0 UV coord if not expected from the vertex data
+ #ifdef NOUV
+ float2 iTexCoord = float2(0.0, 0.0);
+ #endif
+
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oTexCoord = float3(GetTexCoord(iTexCoord), GetDepth(oPos));
+}
+
+void PS(
+ float3 iTexCoord : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ #ifdef ALPHAMASK
+ float alpha = Sample2D(DiffMap, iTexCoord.xy).a;
+ if (alpha < 0.5)
+ discard;
+ #endif
+
+ oColor = iTexCoord.z;
+}
diff --git a/bin/CoreData/Shaders/HLSL/FXAA2.hlsl b/bin/CoreData/Shaders/HLSL/FXAA2.hlsl
new file mode 100644
index 0000000..fb83d81
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/FXAA2.hlsl
@@ -0,0 +1,101 @@
+/*============================================================================
+
+ FXAA v2 CONSOLE by TIMOTHY LOTTES @ NVIDIA
+
+============================================================================*/
+
+// Adapted for Urho3D from http://timothylottes.blogspot.com/2011/04/nvidia-fxaa-ii-for-console.html
+
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms
+uniform float4 cFXAAParams;
+
+#else
+
+// D3D11 constant buffers
+#ifdef COMPILEPS
+cbuffer CustomPS : register(b6)
+{
+ float4 cFXAAParams;
+}
+#endif
+
+#endif
+
+void VS(float4 iPos : POSITION,
+ out float2 oScreenPos : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iScreenPos : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ float FXAA_SUBPIX_SHIFT = 1.0/4.0; // Not used
+ float FXAA_SPAN_MAX = 8.0;
+ float FXAA_REDUCE_MUL = 1.0/8.0;
+ float FXAA_REDUCE_MIN = 1.0/128.0;
+
+ float2 posOffset = cGBufferInvSize.xy * cFXAAParams.x;
+
+ float3 rgbNW = Sample2DLod0(DiffMap, iScreenPos + float2(-posOffset.x, -posOffset.y)).rgb;
+ float3 rgbNE = Sample2DLod0(DiffMap, iScreenPos + float2(posOffset.x, -posOffset.y)).rgb;
+ float3 rgbSW = Sample2DLod0(DiffMap, iScreenPos + float2(-posOffset.x, posOffset.y)).rgb;
+ float3 rgbSE = Sample2DLod0(DiffMap, iScreenPos + float2(posOffset.x, posOffset.y)).rgb;
+ float3 rgbM = Sample2DLod0(DiffMap, iScreenPos).rgb;
+
+ float3 luma = float3(0.299, 0.587, 0.114);
+ float lumaNW = dot(rgbNW, luma);
+ float lumaNE = dot(rgbNE, luma);
+ float lumaSW = dot(rgbSW, luma);
+ float lumaSE = dot(rgbSE, luma);
+ float lumaM = dot(rgbM, luma);
+
+ float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
+ float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
+
+ if (((lumaMax - lumaMin) / lumaMin) >= cFXAAParams.y)
+ {
+ float2 dir;
+ dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
+ dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
+
+ float dirReduce = max(
+ (lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL),
+ FXAA_REDUCE_MIN);
+ float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce);
+ dir = min(float2( FXAA_SPAN_MAX, FXAA_SPAN_MAX),
+ max(float2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
+ dir * rcpDirMin)) * cGBufferInvSize.xy;
+
+ dir *= cFXAAParams.z;
+
+ float3 rgbA = (1.0/2.0) * (
+ Sample2DLod0(DiffMap, iScreenPos + dir * (1.0/3.0 - 0.5)).xyz +
+ Sample2DLod0(DiffMap, iScreenPos + dir * (2.0/3.0 - 0.5)).xyz);
+ float3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * (
+ Sample2DLod0(DiffMap, iScreenPos + dir * (0.0/3.0 - 0.5)).xyz +
+ Sample2DLod0(DiffMap, iScreenPos + dir * (3.0/3.0 - 0.5)).xyz);
+ float lumaB = dot(rgbB, luma);
+
+ float3 rgbOut;
+ if((lumaB < lumaMin) || (lumaB > lumaMax))
+ rgbOut = rgbA;
+ else
+ rgbOut = rgbB;
+
+ oColor = float4(rgbOut, 1.0);
+ }
+ else
+ oColor = float4(rgbM, 1.0);
+}
diff --git a/bin/CoreData/Shaders/HLSL/FXAA3.hlsl b/bin/CoreData/Shaders/HLSL/FXAA3.hlsl
new file mode 100644
index 0000000..052a454
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/FXAA3.hlsl
@@ -0,0 +1,721 @@
+//----------------------------------------------------------------------------------
+//
+// Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of NVIDIA CORPORATION nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+//----------------------------------------------------------------------------------
+/*============================================================================
+
+
+ NVIDIA FXAA 3.11 by TIMOTHY LOTTES
+
+------------------------------------------------------------------------------
+
+ Modified for Urho3D
+
+============================================================================*/
+
+/*==========================================================================*/
+//
+// Urho3D specific preparations
+//
+/*--------------------------------------------------------------------------*/
+
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+
+#ifdef COMPILEPS
+/*============================================================================
+ FXAA QUALITY - TUNING KNOBS
+------------------------------------------------------------------------------
+NOTE the other tuning knobs are now in the shader function inputs!
+============================================================================*/
+#ifndef FXAA_QUALITY_PRESET
+ //
+ // Choose the quality preset.
+ // This needs to be compiled into the shader as it effects code.
+ // Best option to include multiple presets is to
+ // in each shader define the preset, then include this file.
+ //
+ // OPTIONS
+ // -----------------------------------------------------------------------
+ // 10 to 15 - default medium dither (10=fastest, 15=highest quality)
+ // 20 to 29 - less dither, more expensive (20=fastest, 29=highest quality)
+ // 39 - no dither, very expensive
+ //
+ // NOTES
+ // -----------------------------------------------------------------------
+ // 12 = slightly faster then FXAA 3.9 and higher edge quality (default)
+ // 13 = about same speed as FXAA 3.9 and better than 12
+ // 23 = closest to FXAA 3.9 visually and performance wise
+ // _ = the lowest digit is directly related to performance
+ // _ = the highest digit is directly related to style
+ //
+ #define FXAA_QUALITY_PRESET 12
+#endif
+
+
+/*============================================================================
+
+ FXAA QUALITY - PRESETS
+
+============================================================================*/
+
+/*============================================================================
+ FXAA QUALITY - MEDIUM DITHER PRESETS
+============================================================================*/
+#if (FXAA_QUALITY_PRESET == 10)
+ #define FXAA_QUALITY_PS 3
+ #define FXAA_QUALITY_P0 1.5
+ #define FXAA_QUALITY_P1 3.0
+ #define FXAA_QUALITY_P2 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 11)
+ #define FXAA_QUALITY_PS 4
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 3.0
+ #define FXAA_QUALITY_P3 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 12)
+ #define FXAA_QUALITY_PS 5
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 4.0
+ #define FXAA_QUALITY_P4 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 13)
+ #define FXAA_QUALITY_PS 6
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 4.0
+ #define FXAA_QUALITY_P5 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 14)
+ #define FXAA_QUALITY_PS 7
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 4.0
+ #define FXAA_QUALITY_P6 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 15)
+ #define FXAA_QUALITY_PS 8
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 4.0
+ #define FXAA_QUALITY_P7 12.0
+#endif
+
+/*============================================================================
+ FXAA QUALITY - LOW DITHER PRESETS
+============================================================================*/
+#if (FXAA_QUALITY_PRESET == 20)
+ #define FXAA_QUALITY_PS 3
+ #define FXAA_QUALITY_P0 1.5
+ #define FXAA_QUALITY_P1 2.0
+ #define FXAA_QUALITY_P2 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 21)
+ #define FXAA_QUALITY_PS 4
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 22)
+ #define FXAA_QUALITY_PS 5
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 23)
+ #define FXAA_QUALITY_PS 6
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 24)
+ #define FXAA_QUALITY_PS 7
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 3.0
+ #define FXAA_QUALITY_P6 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 25)
+ #define FXAA_QUALITY_PS 8
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 4.0
+ #define FXAA_QUALITY_P7 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 26)
+ #define FXAA_QUALITY_PS 9
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 2.0
+ #define FXAA_QUALITY_P7 4.0
+ #define FXAA_QUALITY_P8 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 27)
+ #define FXAA_QUALITY_PS 10
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 2.0
+ #define FXAA_QUALITY_P7 2.0
+ #define FXAA_QUALITY_P8 4.0
+ #define FXAA_QUALITY_P9 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 28)
+ #define FXAA_QUALITY_PS 11
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 2.0
+ #define FXAA_QUALITY_P7 2.0
+ #define FXAA_QUALITY_P8 2.0
+ #define FXAA_QUALITY_P9 4.0
+ #define FXAA_QUALITY_P10 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 29)
+ #define FXAA_QUALITY_PS 12
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.5
+ #define FXAA_QUALITY_P2 2.0
+ #define FXAA_QUALITY_P3 2.0
+ #define FXAA_QUALITY_P4 2.0
+ #define FXAA_QUALITY_P5 2.0
+ #define FXAA_QUALITY_P6 2.0
+ #define FXAA_QUALITY_P7 2.0
+ #define FXAA_QUALITY_P8 2.0
+ #define FXAA_QUALITY_P9 2.0
+ #define FXAA_QUALITY_P10 4.0
+ #define FXAA_QUALITY_P11 8.0
+#endif
+
+/*============================================================================
+ FXAA QUALITY - EXTREME QUALITY
+============================================================================*/
+#if (FXAA_QUALITY_PRESET == 39)
+ #define FXAA_QUALITY_PS 12
+ #define FXAA_QUALITY_P0 1.0
+ #define FXAA_QUALITY_P1 1.0
+ #define FXAA_QUALITY_P2 1.0
+ #define FXAA_QUALITY_P3 1.0
+ #define FXAA_QUALITY_P4 1.0
+ #define FXAA_QUALITY_P5 1.5
+ #define FXAA_QUALITY_P6 2.0
+ #define FXAA_QUALITY_P7 2.0
+ #define FXAA_QUALITY_P8 2.0
+ #define FXAA_QUALITY_P9 2.0
+ #define FXAA_QUALITY_P10 4.0
+ #define FXAA_QUALITY_P11 8.0
+#endif
+
+/*============================================================================
+
+ Support Functions
+
+============================================================================*/
+
+float CalcLuma(float3 rgb)
+{
+ float3 luma = float3(0.299, 0.587, 0.114);
+ return dot(rgb, luma);
+}
+
+/*--------------------------------------------------------------------------*/
+
+#ifndef D3D11
+#define FxaaTexTop(tex, p) float4(tex2Dlod(s##tex, float4(p, 0.0, 0.0)).rgb, 1.0)
+#define LumaTop(tex, p) CalcLuma(tex2Dlod(s##tex, float4(p, 0.0, 0.0)).rgb)
+#define LumaOff(tex, p, o, r) CalcLuma(tex2Dlod(s##tex, float4(p + (o * r), 0, 0)).rgb)
+#else
+#define FxaaTexTop(tex, p) float4(t##tex.SampleLevel(s##tex, p, 0.0).rgb, 1.0)
+#define LumaTop(tex, p) CalcLuma(t##tex.SampleLevel(s##tex, p, 0.0).rgb)
+#define LumaOff(tex, p, o, r) CalcLuma(t##tex.SampleLevel(s##tex, p + (o * r), 0.0).rgb)
+#endif
+
+/*============================================================================
+
+ FXAA3 QUALITY - PC
+
+============================================================================*/
+float4 FxaaPixelShader(
+ //
+ // Use noperspective interpolation here (turn off perspective interpolation).
+ // {xy} = center of pixel
+ float2 pos,
+ //
+ // Input color texture.
+ // {rgb_} = color in linear or perceptual color space
+ // if (FXAA_GREEN_AS_LUMA == 0)
+ // {__a} = luma in perceptual color space (not linear)
+ //sampler2D tex,
+ //
+ // Only used on FXAA Quality.
+ // This must be from a constant/uniform.
+ // {x_} = 1.0/screenWidthInPixels
+ // {_y} = 1.0/screenHeightInPixels
+ float2 fxaaQualityRcpFrame,
+ //
+ // Only used on FXAA Quality.
+ // This used to be the FXAA_QUALITY_SUBPIX define.
+ // It is here now to allow easier tuning.
+ // Choose the amount of sub-pixel aliasing removal.
+ // This can effect sharpness.
+ // 1.00 - upper limit (softer)
+ // 0.75 - default amount of filtering
+ // 0.50 - lower limit (sharper, less sub-pixel aliasing removal)
+ // 0.25 - almost off
+ // 0.00 - completely off
+ float fxaaQualitySubpix,
+ //
+ // Only used on FXAA Quality.
+ // This used to be the FXAA_QUALITY_EDGE_THRESHOLD define.
+ // It is here now to allow easier tuning.
+ // The minimum amount of local contrast required to apply algorithm.
+ // 0.333 - too little (faster)
+ // 0.250 - low quality
+ // 0.166 - default
+ // 0.125 - high quality
+ // 0.063 - overkill (slower)
+ float fxaaQualityEdgeThreshold,
+ //
+ // Only used on FXAA Quality.
+ // This used to be the FXAA_QUALITY_EDGE_THRESHOLD_MIN define.
+ // It is here now to allow easier tuning.
+ // Trims the algorithm from processing darks.
+ // 0.0833 - upper limit (default, the start of visible unfiltered edges)
+ // 0.0625 - high quality (faster)
+ // 0.0312 - visible limit (slower)
+ // Special notes when using FXAA_GREEN_AS_LUMA,
+ // Likely want to set this to zero.
+ // As colors that are mostly not-green
+ // will appear very dark in the green channel!
+ // Tune by looking at mostly non-green content,
+ // then start at zero and increase until aliasing is a problem.
+ float fxaaQualityEdgeThresholdMin
+) {
+/*--------------------------------------------------------------------------*/
+ float2 posM;
+ posM.x = pos.x;
+ posM.y = pos.y;
+
+ float4 rgbyM = FxaaTexTop(DiffMap, posM);
+ rgbyM.y = CalcLuma(rgbyM.rgb);
+ #define lumaM rgbyM.y
+ float lumaS = LumaOff(DiffMap, posM, float2( 0, 1), fxaaQualityRcpFrame.xy);
+ float lumaE = LumaOff(DiffMap, posM, float2( 1, 0), fxaaQualityRcpFrame.xy);
+ float lumaN = LumaOff(DiffMap, posM, float2( 0,-1), fxaaQualityRcpFrame.xy);
+ float lumaW = LumaOff(DiffMap, posM, float2(-1, 0), fxaaQualityRcpFrame.xy);
+/*--------------------------------------------------------------------------*/
+ float maxSM = max(lumaS, lumaM);
+ float minSM = min(lumaS, lumaM);
+ float maxESM = max(lumaE, maxSM);
+ float minESM = min(lumaE, minSM);
+ float maxWN = max(lumaN, lumaW);
+ float minWN = min(lumaN, lumaW);
+ float rangeMax = max(maxWN, maxESM);
+ float rangeMin = min(minWN, minESM);
+ float rangeMaxScaled = rangeMax * fxaaQualityEdgeThreshold;
+ float range = rangeMax - rangeMin;
+ float rangeMaxClamped = max(fxaaQualityEdgeThresholdMin, rangeMaxScaled);
+ bool earlyExit = range < rangeMaxClamped;
+/*--------------------------------------------------------------------------*/
+ if(earlyExit)
+ return FxaaTexTop(DiffMap, pos);
+/*--------------------------------------------------------------------------*/
+ float lumaNW = LumaOff(DiffMap, posM, float2(-1,-1), fxaaQualityRcpFrame.xy);
+ float lumaSE = LumaOff(DiffMap, posM, float2( 1, 1), fxaaQualityRcpFrame.xy);
+ float lumaNE = LumaOff(DiffMap, posM, float2( 1,-1), fxaaQualityRcpFrame.xy);
+ float lumaSW = LumaOff(DiffMap, posM, float2(-1, 1), fxaaQualityRcpFrame.xy);
+/*--------------------------------------------------------------------------*/
+ float lumaNS = lumaN + lumaS;
+ float lumaWE = lumaW + lumaE;
+ float subpixRcpRange = 1.0/range;
+ float subpixNSWE = lumaNS + lumaWE;
+ float edgeHorz1 = (-2.0 * lumaM) + lumaNS;
+ float edgeVert1 = (-2.0 * lumaM) + lumaWE;
+/*--------------------------------------------------------------------------*/
+ float lumaNESE = lumaNE + lumaSE;
+ float lumaNWNE = lumaNW + lumaNE;
+ float edgeHorz2 = (-2.0 * lumaE) + lumaNESE;
+ float edgeVert2 = (-2.0 * lumaN) + lumaNWNE;
+/*--------------------------------------------------------------------------*/
+ float lumaNWSW = lumaNW + lumaSW;
+ float lumaSWSE = lumaSW + lumaSE;
+ float edgeHorz4 = (abs(edgeHorz1) * 2.0) + abs(edgeHorz2);
+ float edgeVert4 = (abs(edgeVert1) * 2.0) + abs(edgeVert2);
+ float edgeHorz3 = (-2.0 * lumaW) + lumaNWSW;
+ float edgeVert3 = (-2.0 * lumaS) + lumaSWSE;
+ float edgeHorz = abs(edgeHorz3) + edgeHorz4;
+ float edgeVert = abs(edgeVert3) + edgeVert4;
+/*--------------------------------------------------------------------------*/
+ float subpixNWSWNESE = lumaNWSW + lumaNESE;
+ float lengthSign = fxaaQualityRcpFrame.x;
+ bool horzSpan = edgeHorz >= edgeVert;
+ float subpixA = subpixNSWE * 2.0 + subpixNWSWNESE;
+/*--------------------------------------------------------------------------*/
+ if(!horzSpan) lumaN = lumaW;
+ if(!horzSpan) lumaS = lumaE;
+ if(horzSpan) lengthSign = fxaaQualityRcpFrame.y;
+ float subpixB = (subpixA * (1.0/12.0)) - lumaM;
+/*--------------------------------------------------------------------------*/
+ float gradientN = lumaN - lumaM;
+ float gradientS = lumaS - lumaM;
+ float lumaNN = lumaN + lumaM;
+ float lumaSS = lumaS + lumaM;
+ bool pairN = abs(gradientN) >= abs(gradientS);
+ float gradient = max(abs(gradientN), abs(gradientS));
+ if(pairN) lengthSign = -lengthSign;
+ float subpixC = clamp((abs(subpixB) * subpixRcpRange), 0.0, 1.0);
+/*--------------------------------------------------------------------------*/
+ float2 posB;
+ posB.x = posM.x;
+ posB.y = posM.y;
+ float2 offNP;
+ offNP.x = (!horzSpan) ? 0.0 : fxaaQualityRcpFrame.x;
+ offNP.y = ( horzSpan) ? 0.0 : fxaaQualityRcpFrame.y;
+ if(!horzSpan) posB.x += lengthSign * 0.5;
+ if( horzSpan) posB.y += lengthSign * 0.5;
+/*--------------------------------------------------------------------------*/
+ float2 posN;
+ posN.x = posB.x - offNP.x * FXAA_QUALITY_P0;
+ posN.y = posB.y - offNP.y * FXAA_QUALITY_P0;
+ float2 posP;
+ posP.x = posB.x + offNP.x * FXAA_QUALITY_P0;
+ posP.y = posB.y + offNP.y * FXAA_QUALITY_P0;
+ float subpixD = ((-2.0)*subpixC) + 3.0;
+ float lumaEndN = LumaTop(DiffMap, posN);
+ float subpixE = subpixC * subpixC;
+ float lumaEndP = LumaTop(DiffMap, posP);
+/*--------------------------------------------------------------------------*/
+ if(!pairN) lumaNN = lumaSS;
+ float gradientScaled = gradient * 1.0/4.0;
+ float lumaMM = lumaM - lumaNN * 0.5;
+ float subpixF = subpixD * subpixE;
+ bool lumaMLTZero = lumaMM < 0.0;
+/*--------------------------------------------------------------------------*/
+ lumaEndN -= lumaNN * 0.5;
+ lumaEndP -= lumaNN * 0.5;
+ bool doneN = abs(lumaEndN) >= gradientScaled;
+ bool doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P1;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P1;
+ bool doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P1;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P1;
+/*--------------------------------------------------------------------------*/
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P2;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P2;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P2;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P2;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 3)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P3;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P3;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P3;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P3;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 4)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P4;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P4;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P4;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P4;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 5)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P5;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P5;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P5;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P5;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 6)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P6;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P6;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P6;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P6;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 7)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P7;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P7;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P7;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P7;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 8)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P8;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P8;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P8;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P8;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 9)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P9;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P9;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P9;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P9;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 10)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P10;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P10;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P10;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P10;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 11)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P11;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P11;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P11;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P11;
+/*--------------------------------------------------------------------------*/
+ #if (FXAA_QUALITY_PS > 12)
+ if(doneNP) {
+ if(!doneN) lumaEndN = LumaTop(DiffMap, posN.xy);
+ if(!doneP) lumaEndP = LumaTop(DiffMap, posP.xy);
+ if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+ if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+ doneN = abs(lumaEndN) >= gradientScaled;
+ doneP = abs(lumaEndP) >= gradientScaled;
+ if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P12;
+ if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P12;
+ doneNP = (!doneN) || (!doneP);
+ if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P12;
+ if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P12;
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+ #endif
+/*--------------------------------------------------------------------------*/
+ }
+/*--------------------------------------------------------------------------*/
+ float dstN = posM.x - posN.x;
+ float dstP = posP.x - posM.x;
+ if(!horzSpan) dstN = posM.y - posN.y;
+ if(!horzSpan) dstP = posP.y - posM.y;
+/*--------------------------------------------------------------------------*/
+ bool goodSpanN = (lumaEndN < 0.0) != lumaMLTZero;
+ float spanLength = (dstP + dstN);
+ bool goodSpanP = (lumaEndP < 0.0) != lumaMLTZero;
+ float spanLengthRcp = 1.0/spanLength;
+/*--------------------------------------------------------------------------*/
+ bool directionN = dstN < dstP;
+ float dst = min(dstN, dstP);
+ bool goodSpan = directionN ? goodSpanN : goodSpanP;
+ float subpixG = subpixF * subpixF;
+ float pixelOffset = (dst * (-spanLengthRcp)) + 0.5;
+ float subpixH = subpixG * fxaaQualitySubpix;
+/*--------------------------------------------------------------------------*/
+ float pixelOffsetGood = goodSpan ? pixelOffset : 0.0;
+ float pixelOffsetSubpix = max(pixelOffsetGood, subpixH);
+ if(!horzSpan) posM.x += pixelOffsetSubpix * lengthSign;
+ if( horzSpan) posM.y += pixelOffsetSubpix * lengthSign;
+ return FxaaTexTop(DiffMap, posM);
+}
+/*==========================================================================*/
+#endif
+
+/*============================================================================
+
+ Urho3D Vertex- and Pixelshader
+
+============================================================================*/
+
+void VS(float4 iPos : POSITION,
+ out float2 oScreenPos : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iScreenPos : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ float2 rcpFrame = float2(cGBufferInvSize.x, cGBufferInvSize.y);
+
+ oColor = FxaaPixelShader(
+ iScreenPos, // float2 pos,
+ rcpFrame, // float2 fxaaQualityRcpFrame,
+ 0.75f, // float fxaaQualitySubpix,
+ 0.166f, // float fxaaQualityEdgeThreshold,
+ 0.0833f // float fxaaQualityEdgeThresholdMin
+ );
+}
+
diff --git a/bin/CoreData/Shaders/HLSL/Fog.hlsl b/bin/CoreData/Shaders/HLSL/Fog.hlsl
new file mode 100644
index 0000000..03743e7
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Fog.hlsl
@@ -0,0 +1,24 @@
+#ifdef COMPILEPS
+float3 GetFog(float3 color, float fogFactor)
+{
+ return lerp(cFogColor, color, fogFactor);
+}
+
+float3 GetLitFog(float3 color, float fogFactor)
+{
+ return color * fogFactor;
+}
+
+float GetFogFactor(float depth)
+{
+ return saturate((cFogParams.x - depth) * cFogParams.y);
+}
+
+float GetHeightFogFactor(float depth, float height)
+{
+ float fogFactor = GetFogFactor(depth);
+ float heightFogFactor = (height - cFogParams.z) * cFogParams.w;
+ heightFogFactor = 1.0 - saturate(exp(-(heightFogFactor * heightFogFactor)));
+ return min(heightFogFactor, fogFactor);
+}
+#endif
diff --git a/bin/CoreData/Shaders/HLSL/GammaCorrection.hlsl b/bin/CoreData/Shaders/HLSL/GammaCorrection.hlsl
new file mode 100644
index 0000000..4141b2a
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/GammaCorrection.hlsl
@@ -0,0 +1,22 @@
+#include "Uniforms.hlsl"
+#include "Transform.hlsl"
+#include "Samplers.hlsl"
+#include "ScreenPos.hlsl"
+#include "PostProcess.hlsl"
+
+void VS(float4 iPos : POSITION,
+ out float2 oScreenPos : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iScreenPos : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ float3 color = Sample2D(DiffMap, iScreenPos).rgb;
+ oColor = float4(ToInverseGamma(color), 1.0);
+}
diff --git a/bin/CoreData/Shaders/HLSL/GreyScale.hlsl b/bin/CoreData/Shaders/HLSL/GreyScale.hlsl
new file mode 100644
index 0000000..98efbfa
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/GreyScale.hlsl
@@ -0,0 +1,23 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Lighting.hlsl"
+
+void VS(float4 iPos : POSITION,
+ out float2 oScreenPos : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iScreenPos : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ float3 rgb = Sample2D(DiffMap, iScreenPos).rgb;
+ float intensity = GetIntensity(rgb);
+ oColor = float4(intensity, intensity, intensity, 1.0);
+}
diff --git a/bin/CoreData/Shaders/HLSL/IBL.hlsl b/bin/CoreData/Shaders/HLSL/IBL.hlsl
new file mode 100644
index 0000000..47b8f44
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/IBL.hlsl
@@ -0,0 +1,77 @@
+#ifdef COMPILEPS
+
+ float GetMipFromRoughness(float roughness)
+ {
+ return (roughness * 12.0 - pow(roughness, 6.0) * 1.5);
+ }
+
+
+ float3 EnvBRDFApprox (float3 specColor, float roughness, float ndv)
+ {
+ const float4 c0 = float4(-1, -0.0275, -0.572, 0.022 );
+ const float4 c1 = float4(1, 0.0425, 1.0, -0.04 );
+ float4 r = roughness * c0 + c1;
+ float a004 = min( r.x * r.x, exp2( -9.28 * ndv ) ) * r.x + r.y;
+ float2 AB = float2( -1.04, 1.04 ) * a004 + r.zw;
+ return specColor * AB.x + AB.y;
+ }
+
+ float3 FixCubeLookup(float3 v)
+ {
+ float M = max(max(abs(v.x), abs(v.y)), abs(v.z));
+ float scale = (1024 - 1) / 1024;
+
+ if (abs(v.x) != M) v.x += scale;
+ if (abs(v.y) != M) v.y += scale;
+ if (abs(v.z) != M) v.z += scale;
+
+ return v;
+ }
+
+ /// Calculate IBL contributation
+ /// reflectVec: reflection vector for cube sampling
+ /// wsNormal: surface normal in word space
+ /// toCamera: normalized direction from surface point to camera
+ /// roughness: surface roughness
+ /// ambientOcclusion: ambient occlusion
+ float3 ImageBasedLighting(in float3 reflectVec, in float3 wsNormal, in float3 toCamera, in float3 diffColor, in float3 specColor, in float roughness, inout float3 reflectionCubeColor)
+ {
+ roughness = max(roughness, 0.08);
+ reflectVec = GetSpecularDominantDir(wsNormal, reflectVec, roughness);
+ const float ndv = saturate(dot(-toCamera, wsNormal));
+
+ /// Test: Parallax correction, currently not working
+
+ // float3 intersectMax = (cZoneMax - toCamera) / reflectVec;
+ // float3 intersectMin = (cZoneMin - toCamera) / reflectVec;
+
+ // float3 furthestPlane = max(intersectMax, intersectMin);
+
+ // float planeDistance = min(min(furthestPlane.x, furthestPlane.y), furthestPlane.z);
+
+ // // Get the intersection position
+ // float3 intersectionPos = toCamera + reflectVec * planeDistance;
+ // // Get corrected reflection
+ // reflectVec = intersectionPos - ((cZoneMin + cZoneMax )/ 2);
+
+ const float mipSelect = GetMipFromRoughness(roughness);
+ float3 cube = SampleCubeLOD(ZoneCubeMap, float4(FixCubeLookup(reflectVec), mipSelect)).rgb;
+ float3 cubeD = SampleCubeLOD(ZoneCubeMap, float4(FixCubeLookup(wsNormal), 9.0)).rgb;
+
+ // Fake the HDR texture
+ float brightness = clamp(cAmbientColor.a, 0.0, 1.0);
+ float darknessCutoff = clamp((cAmbientColor.a - 1.0) * 0.1, 0.0, 0.25);
+
+ const float hdrMaxBrightness = 5.0;
+ float3 hdrCube = pow(cube + darknessCutoff, max(1.0, cAmbientColor.a));
+ hdrCube += max(0.0, hdrCube - 1.0) * hdrMaxBrightness;
+
+ float3 hdrCubeD = pow(cubeD + darknessCutoff, max(1.0, cAmbientColor.a));
+ hdrCubeD += max(0.0, hdrCubeD - 1.0) * hdrMaxBrightness;
+
+ const float3 environmentSpecular = EnvBRDFApprox(specColor, roughness, ndv);
+ const float3 environmentDiffuse = EnvBRDFApprox(diffColor, 1.0, ndv);
+
+ return (hdrCube * environmentSpecular + hdrCubeD * environmentDiffuse) * brightness;
+ }
+#endif
diff --git a/bin/CoreData/Shaders/HLSL/Lighting.hlsl b/bin/CoreData/Shaders/HLSL/Lighting.hlsl
new file mode 100644
index 0000000..a218dc2
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Lighting.hlsl
@@ -0,0 +1,382 @@
+#pragma warning(disable:3571)
+
+#ifdef COMPILEVS
+float3 GetAmbient(float zonePos)
+{
+ return cAmbientStartColor + zonePos * cAmbientEndColor;
+}
+
+#ifdef NUMVERTEXLIGHTS
+float GetVertexLight(int index, float3 worldPos, float3 normal)
+{
+ float3 lightDir = cVertexLights[index * 3 + 1].xyz;
+ float3 lightPos = cVertexLights[index * 3 + 2].xyz;
+ float invRange = cVertexLights[index * 3].w;
+ float cutoff = cVertexLights[index * 3 + 1].w;
+ float invCutoff = cVertexLights[index * 3 + 2].w;
+
+ // Directional light
+ if (invRange == 0.0)
+ {
+ #ifdef TRANSLUCENT
+ float NdotL = abs(dot(normal, lightDir));
+ #else
+ float NdotL = max(dot(normal, lightDir), 0.0);
+ #endif
+ return NdotL;
+ }
+ // Point/spot light
+ else
+ {
+ float3 lightVec = (lightPos - worldPos) * invRange;
+ float lightDist = length(lightVec);
+ float3 localDir = lightVec / lightDist;
+ #ifdef TRANSLUCENT
+ float NdotL = abs(dot(normal, localDir));
+ #else
+ float NdotL = max(dot(normal, localDir), 0.0);
+ #endif
+ float atten = saturate(1.0 - lightDist * lightDist);
+ float spotEffect = dot(localDir, lightDir);
+ float spotAtten = saturate((spotEffect - cutoff) * invCutoff);
+ return NdotL * atten * spotAtten;
+ }
+}
+
+float GetVertexLightVolumetric(int index, float3 worldPos)
+{
+ float3 lightDir = cVertexLights[index * 3 + 1].xyz;
+ float3 lightPos = cVertexLights[index * 3 + 2].xyz;
+ float invRange = cVertexLights[index * 3].w;
+ float cutoff = cVertexLights[index * 3 + 1].w;
+ float invCutoff = cVertexLights[index * 3 + 2].w;
+
+ // Directional light
+ if (invRange == 0.0)
+ {
+ return 1.0;
+ }
+ // Point/spot light
+ else
+ {
+ float3 lightVec = (lightPos - worldPos) * invRange;
+ float lightDist = length(lightVec);
+ float3 localDir = lightVec / lightDist;
+ float atten = saturate(1.0 - lightDist * lightDist);
+ float spotEffect = dot(localDir, lightDir);
+ float spotAtten = saturate((spotEffect - cutoff) * invCutoff);
+ return atten * spotAtten;
+ }
+}
+#endif
+
+#ifdef SHADOW
+
+#ifdef DIRLIGHT
+ #define NUMCASCADES 4
+#else
+ #define NUMCASCADES 1
+#endif
+
+void GetShadowPos(float4 projWorldPos, float3 normal, out float4 shadowPos[NUMCASCADES])
+{
+ // Shadow projection: transform from world space to shadow space
+ #ifdef NORMALOFFSET
+ #ifdef DIRLIGHT
+ float cosAngle = saturate(1.0 - dot(normal, cLightDir));
+ #else
+ float cosAngle = saturate(1.0 - dot(normal, normalize(cLightPos.xyz - projWorldPos.xyz)));
+ #endif
+
+ #if defined(DIRLIGHT)
+ shadowPos[0] = mul(float4(projWorldPos.xyz + cosAngle * cNormalOffsetScale.x * normal, 1.0), cLightMatrices[0]);
+ shadowPos[1] = mul(float4(projWorldPos.xyz + cosAngle * cNormalOffsetScale.y * normal, 1.0), cLightMatrices[1]);
+ shadowPos[2] = mul(float4(projWorldPos.xyz + cosAngle * cNormalOffsetScale.z * normal, 1.0), cLightMatrices[2]);
+ shadowPos[3] = mul(float4(projWorldPos.xyz + cosAngle * cNormalOffsetScale.w * normal, 1.0), cLightMatrices[3]);
+ #elif defined(SPOTLIGHT)
+ shadowPos[0] = mul(float4(projWorldPos.xyz + cosAngle * cNormalOffsetScale.x * normal, 1.0), cLightMatrices[1]);
+ #else
+ shadowPos[0] = float4(projWorldPos.xyz + cosAngle * cNormalOffsetScale.x * normal - cLightPos.xyz, 0.0);
+ #endif
+ #else
+ #if defined(DIRLIGHT)
+ shadowPos[0] = mul(projWorldPos, cLightMatrices[0]);
+ shadowPos[1] = mul(projWorldPos, cLightMatrices[1]);
+ shadowPos[2] = mul(projWorldPos, cLightMatrices[2]);
+ shadowPos[3] = mul(projWorldPos, cLightMatrices[3]);
+ #elif defined(SPOTLIGHT)
+ shadowPos[0] = mul(projWorldPos, cLightMatrices[1]);
+ #else
+ shadowPos[0] = float4(projWorldPos.xyz - cLightPos.xyz, 0.0);
+ #endif
+ #endif
+}
+#endif
+#endif
+
+#ifdef COMPILEPS
+float GetDiffuse(float3 normal, float3 worldPos, out float3 lightDir)
+{
+ #ifdef DIRLIGHT
+ lightDir = cLightDirPS;
+ #ifdef TRANSLUCENT
+ return abs(dot(normal, lightDir));
+ #else
+ return saturate(dot(normal, lightDir));
+ #endif
+ #else
+ float3 lightVec = (cLightPosPS.xyz - worldPos) * cLightPosPS.w;
+ float lightDist = length(lightVec);
+ lightDir = lightVec / lightDist;
+ #ifdef TRANSLUCENT
+ return abs(dot(normal, lightDir)) * Sample2D(LightRampMap, float2(lightDist, 0.0)).r;
+ #else
+ return saturate(dot(normal, lightDir)) * Sample2D(LightRampMap, float2(lightDist, 0.0)).r;
+ #endif
+ #endif
+}
+
+float GetAtten(float3 normal, float3 worldPos, out float3 lightDir)
+{
+ lightDir = cLightDirPS;
+ return saturate(dot(normal, lightDir));
+
+}
+
+float GetAttenPoint(float3 normal, float3 worldPos, out float3 lightDir)
+{
+ float3 lightVec = (cLightPosPS.xyz - worldPos) * cLightPosPS.w;
+ float lightDist = length(lightVec);
+ float falloff = pow(saturate(1.0 - pow(lightDist / 1.0, 4.0)), 2.0) * 3.14159265358979323846 / (4 * 3.14159265358979323846)*(pow(lightDist, 2.0) + 1.0);
+ lightDir = lightVec / lightDist;
+ return saturate(dot(normal, lightDir)) * falloff;
+
+}
+
+float GetAttenSpot(float3 normal, float3 worldPos, out float3 lightDir)
+{
+ float3 lightVec = (cLightPosPS.xyz - worldPos) * cLightPosPS.w;
+ float lightDist = length(lightVec);
+ float falloff = pow(saturate(1.0 - pow(lightDist / 1.0, 4.0)), 2.0) / (pow(lightDist, 2.0) + 1.0);
+
+ lightDir = lightVec / lightDist;
+ return saturate(dot(normal, lightDir)) * falloff;
+
+}
+
+
+float GetDiffuseVolumetric(float3 worldPos)
+{
+ #ifdef DIRLIGHT
+ return 1.0;
+ #else
+ float3 lightVec = (cLightPosPS.xyz - worldPos) * cLightPosPS.w;
+ float lightDist = length(lightVec);
+ return Sample2D(LightRampMap, float2(lightDist, 0.0)).r;
+ #endif
+}
+
+float GetSpecular(float3 normal, float3 eyeVec, float3 lightDir, float specularPower)
+{
+ float3 halfVec = normalize(normalize(eyeVec) + lightDir);
+ return saturate(pow(dot(normal, halfVec), specularPower));
+}
+
+float GetIntensity(float3 color)
+{
+ return dot(color, float3(0.299, 0.587, 0.114));
+}
+
+#ifdef SHADOW
+
+#ifdef DIRLIGHT
+ #define NUMCASCADES 4
+#else
+ #define NUMCASCADES 1
+#endif
+
+#ifdef VSM_SHADOW
+float ReduceLightBleeding(float min, float p_max)
+{
+ return clamp((p_max - min) / (1.0 - min), 0.0, 1.0);
+}
+
+float Chebyshev(float2 Moments, float depth)
+{
+ //One-tailed inequality valid if depth > Moments.x
+ float p = float(depth <= Moments.x);
+ //Compute variance.
+ float Variance = Moments.y - (Moments.x * Moments.x);
+
+ float minVariance = cVSMShadowParams.x;
+ Variance = max(Variance, minVariance);
+ //Compute probabilistic upper bound.
+ float d = depth - Moments.x;
+ float p_max = Variance / (Variance + d*d);
+ // Prevent light bleeding
+ p_max = ReduceLightBleeding(cVSMShadowParams.y, p_max);
+
+ return max(p, p_max);
+}
+#endif
+
+float GetShadow(float4 shadowPos)
+{
+ #if defined(SIMPLE_SHADOW)
+ // Take one sample
+ #ifdef D3D11
+ shadowPos.xyz /= shadowPos.w;
+ #endif
+ float inLight = SampleShadow(ShadowMap, shadowPos).r;
+ #ifndef SHADOWCMP
+ return cShadowIntensity.y + cShadowIntensity.x * inLight;
+ #else
+ #ifndef POINTLIGHT
+ return cShadowIntensity.y + cShadowIntensity.x * (inLight * shadowPos.w > shadowPos.z);
+ #else
+ return cShadowIntensity.y + cShadowIntensity.x * (inLight > shadowPos.z);
+ #endif
+ #endif
+
+ #elif defined(PCF_SHADOW)
+ // Take four samples and average them
+ // Note: in case of sampling a point light cube shadow, we optimize out the w divide as it has already been performed
+ #ifdef D3D11
+ shadowPos.xyz /= shadowPos.w;
+ #endif
+ #if !defined(POINTLIGHT) && !defined(D3D11)
+ float2 offsets = cShadowMapInvSize * shadowPos.w;
+ #else
+ float2 offsets = cShadowMapInvSize;
+ #endif
+ float4 shadowPos2 = float4(shadowPos.x + offsets.x, shadowPos.yzw);
+ float4 shadowPos3 = float4(shadowPos.x, shadowPos.y + offsets.y, shadowPos.zw);
+ float4 shadowPos4 = float4(shadowPos.xy + offsets.xy, shadowPos.zw);
+
+ float4 inLight = float4(
+ SampleShadow(ShadowMap, shadowPos).r,
+ SampleShadow(ShadowMap, shadowPos2).r,
+ SampleShadow(ShadowMap, shadowPos3).r,
+ SampleShadow(ShadowMap, shadowPos4).r
+ );
+ #ifndef SHADOWCMP
+ return cShadowIntensity.y + dot(inLight, cShadowIntensity.x);
+ #else
+ #ifndef POINTLIGHT
+ return cShadowIntensity.y + dot(inLight * shadowPos.w > shadowPos.z, cShadowIntensity.x);
+ #else
+ return cShadowIntensity.y + dot(inLight > shadowPos.z, cShadowIntensity.x);
+ #endif
+ #endif
+
+ #elif defined(VSM_SHADOW)
+ float2 samples = Sample2D(ShadowMap, shadowPos.xy / shadowPos.w).rg;
+ return cShadowIntensity.y + cShadowIntensity.x * Chebyshev(samples, shadowPos.z/shadowPos.w);
+ #endif
+}
+
+#ifdef POINTLIGHT
+float GetPointShadow(float3 lightVec)
+{
+ float3 axis = SampleCube(FaceSelectCubeMap, lightVec).rgb;
+ float depth = abs(dot(lightVec, axis));
+
+ // Expand the maximum component of the light vector to get full 0.0 - 1.0 UV range from the cube map,
+ // and to avoid sampling across faces. Some GPU's filter across faces, while others do not, and in this
+ // case filtering across faces is wrong
+ const float factor = 1.0 / 256.0;
+ lightVec += factor * axis * lightVec;
+
+ // Read the 2D UV coordinates, adjust according to shadow map size and add face offset
+ float4 indirectPos = SampleCube(IndirectionCubeMap, lightVec);
+ indirectPos.xy *= cShadowCubeAdjust.xy;
+ indirectPos.xy += float2(cShadowCubeAdjust.z + indirectPos.z * 0.5, cShadowCubeAdjust.w + indirectPos.w);
+
+ float4 shadowPos = float4(indirectPos.xy, cShadowDepthFade.x + cShadowDepthFade.y / depth, 1.0);
+ return GetShadow(shadowPos);
+}
+#endif
+
+#ifdef DIRLIGHT
+float GetDirShadowFade(float inLight, float depth)
+{
+ return saturate(inLight + saturate((depth - cShadowDepthFade.z) * cShadowDepthFade.w));
+}
+
+float GetDirShadow(const float4 iShadowPos[NUMCASCADES], float depth)
+{
+ float4 shadowPos;
+
+ if (depth < cShadowSplits.x)
+ shadowPos = iShadowPos[0];
+ else if (depth < cShadowSplits.y)
+ shadowPos = iShadowPos[1];
+ else if (depth < cShadowSplits.z)
+ shadowPos = iShadowPos[2];
+ else
+ shadowPos = iShadowPos[3];
+
+ return GetDirShadowFade(GetShadow(shadowPos), depth);
+}
+
+float GetDirShadowDeferred(float4 projWorldPos, float3 normal, float depth)
+{
+ float4 shadowPos;
+
+ #ifdef NORMALOFFSET
+ float cosAngle = saturate(1.0 - dot(normal, cLightDirPS));
+ if (depth < cShadowSplits.x)
+ shadowPos = mul(float4(projWorldPos.xyz + cosAngle * cNormalOffsetScalePS.x * normal, 1.0), cLightMatricesPS[0]);
+ else if (depth < cShadowSplits.y)
+ shadowPos = mul(float4(projWorldPos.xyz + cosAngle * cNormalOffsetScalePS.y * normal, 1.0), cLightMatricesPS[1]);
+ else if (depth < cShadowSplits.z)
+ shadowPos = mul(float4(projWorldPos.xyz + cosAngle * cNormalOffsetScalePS.z * normal, 1.0), cLightMatricesPS[2]);
+ else
+ shadowPos = mul(float4(projWorldPos.xyz + cosAngle * cNormalOffsetScalePS.w * normal, 1.0), cLightMatricesPS[3]);
+ #else
+ if (depth < cShadowSplits.x)
+ shadowPos = mul(projWorldPos, cLightMatricesPS[0]);
+ else if (depth < cShadowSplits.y)
+ shadowPos = mul(projWorldPos, cLightMatricesPS[1]);
+ else if (depth < cShadowSplits.z)
+ shadowPos = mul(projWorldPos, cLightMatricesPS[2]);
+ else
+ shadowPos = mul(projWorldPos, cLightMatricesPS[3]);
+ #endif
+
+ return GetDirShadowFade(GetShadow(shadowPos), depth);
+}
+#endif
+
+float GetShadow(float4 iShadowPos[NUMCASCADES], float depth)
+{
+ #if defined(DIRLIGHT)
+ return GetDirShadow(iShadowPos, depth);
+ #elif defined(SPOTLIGHT)
+ return GetShadow(iShadowPos[0]);
+ #else
+ return GetPointShadow(iShadowPos[0].xyz);
+ #endif
+}
+
+float GetShadowDeferred(float4 projWorldPos, float3 normal, float depth)
+{
+ #ifdef DIRLIGHT
+ return GetDirShadowDeferred(projWorldPos, normal, depth);
+ #else
+ #ifdef NORMALOFFSET
+ float cosAngle = saturate(1.0 - dot(normal, normalize(cLightPosPS.xyz - projWorldPos.xyz)));
+ projWorldPos.xyz += cosAngle * cNormalOffsetScalePS.x * normal;
+ #endif
+
+ #ifdef SPOTLIGHT
+ float4 shadowPos = mul(projWorldPos, cLightMatricesPS[1]);
+ return GetShadow(shadowPos);
+ #else
+ float3 shadowPos = projWorldPos.xyz - cLightPosPS.xyz;
+ return GetPointShadow(shadowPos);
+ #endif
+ #endif
+}
+#endif
+#endif
\ No newline at end of file
diff --git a/bin/CoreData/Shaders/HLSL/LitParticle.hlsl b/bin/CoreData/Shaders/HLSL/LitParticle.hlsl
new file mode 100644
index 0000000..bcf0c34
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/LitParticle.hlsl
@@ -0,0 +1,222 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "Lighting.hlsl"
+#include "ScreenPos.hlsl"
+#include "Fog.hlsl"
+
+#if defined(COMPILEPS) && defined(SOFTPARTICLES)
+#ifndef D3D11
+// D3D9 uniform
+uniform float cSoftParticleFadeScale;
+#else
+// D3D11 constant buffer
+cbuffer CustomPS : register(b6)
+{
+ float cSoftParticleFadeScale;
+}
+#endif
+#endif
+
+void VS(float4 iPos : POSITION,
+ #if !defined(BILLBOARD) && !defined(TRAILFACECAM)
+ float3 iNormal : NORMAL,
+ #endif
+ #ifndef NOUV
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ #if defined(BILLBOARD) || defined(DIRBILLBOARD)
+ float2 iSize : TEXCOORD1,
+ #endif
+ #if defined(TRAILFACECAM) || defined(TRAILBONE)
+ float4 iTangent : TANGENT,
+ #endif
+ out float2 oTexCoord : TEXCOORD0,
+ #ifdef SOFTPARTICLES
+ out float4 oScreenPos : TEXCOORD1,
+ #endif
+ out float4 oWorldPos : TEXCOORD3,
+ #if PERPIXEL
+ #ifdef SHADOW
+ out float4 oShadowPos[NUMCASCADES] : TEXCOORD4,
+ #endif
+ #ifdef SPOTLIGHT
+ out float4 oSpotPos : TEXCOORD5,
+ #endif
+ #ifdef POINTLIGHT
+ out float3 oCubeMaskVec : TEXCOORD5,
+ #endif
+ #else
+ out float3 oVertexLight : TEXCOORD4,
+ #endif
+ #ifdef VERTEXCOLOR
+ out float4 oColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ out float oClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ // Define a 0,0 UV coord if not expected from the vertex data
+ #ifdef NOUV
+ float2 iTexCoord = float2(0.0, 0.0);
+ #endif
+
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oTexCoord = GetTexCoord(iTexCoord);
+ oWorldPos = float4(worldPos, GetDepth(oPos));
+
+ #if defined(D3D11) && defined(CLIPPLANE)
+ oClip = dot(oPos, cClipPlane);
+ #endif
+
+ #ifdef SOFTPARTICLES
+ oScreenPos = GetScreenPos(oPos);
+ #endif
+
+ #ifdef VERTEXCOLOR
+ oColor = iColor;
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ float4 projWorldPos = float4(worldPos.xyz, 1.0);
+
+ #ifdef SHADOW
+ // Shadow projection: transform from world space to shadow space
+ GetShadowPos(projWorldPos, float3(0, 0, 0), oShadowPos);
+ #endif
+
+ #ifdef SPOTLIGHT
+ // Spotlight projection: transform from world space to projector texture coordinates
+ oSpotPos = mul(projWorldPos, cLightMatrices[0]);
+ #endif
+
+ #ifdef POINTLIGHT
+ oCubeMaskVec = mul(worldPos - cLightPos.xyz, (float3x3)cLightMatrices[0]);
+ #endif
+ #else
+ // Ambient & per-vertex lighting
+ oVertexLight = GetAmbient(GetZonePos(worldPos));
+
+ #ifdef NUMVERTEXLIGHTS
+ for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+ oVertexLight += GetVertexLightVolumetric(i, worldPos) * cVertexLights[i * 3].rgb;
+ #endif
+ #endif
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+ #ifdef SOFTPARTICLES
+ float4 iScreenPos: TEXCOORD1,
+ #endif
+ float4 iWorldPos : TEXCOORD3,
+ #ifdef PERPIXEL
+ #ifdef SHADOW
+ float4 iShadowPos[NUMCASCADES] : TEXCOORD4,
+ #endif
+ #ifdef SPOTLIGHT
+ float4 iSpotPos : TEXCOORD5,
+ #endif
+ #ifdef POINTLIGHT
+ float3 iCubeMaskVec : TEXCOORD5,
+ #endif
+ #else
+ float3 iVertexLight : TEXCOORD4,
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ float iClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ // Get material diffuse albedo
+ #ifdef DIFFMAP
+ float4 diffInput = Sample2D(DiffMap, iTexCoord);
+ #ifdef ALPHAMASK
+ if (diffInput.a < 0.5)
+ discard;
+ #endif
+ float4 diffColor = cMatDiffColor * diffInput;
+ #else
+ float4 diffColor = cMatDiffColor;
+ #endif
+
+ #ifdef VERTEXCOLOR
+ diffColor *= iColor;
+ #endif
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(iWorldPos.w, iWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(iWorldPos.w);
+ #endif
+
+ // Soft particle fade
+ // In expand mode depth test should be off. In that case do manual alpha discard test first to reduce fill rate
+ #ifdef SOFTPARTICLES
+ #ifdef EXPAND
+ if (diffColor.a < 0.01)
+ discard;
+ #endif
+
+ float particleDepth = iWorldPos.w;
+ float depth = Sample2DProj(DepthBuffer, iScreenPos).r;
+ #ifdef HWDEPTH
+ depth = ReconstructDepth(depth);
+ #endif
+
+ #ifdef EXPAND
+ float diffZ = max(particleDepth - depth, 0.0) * (cFarClipPS - cNearClipPS);
+ float fade = saturate(diffZ * cSoftParticleFadeScale);
+ #else
+ float diffZ = (depth - particleDepth) * (cFarClipPS - cNearClipPS);
+ float fade = saturate(1.0 - diffZ * cSoftParticleFadeScale);
+ #endif
+
+ diffColor.a = max(diffColor.a - fade, 0.0);
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ float3 lightColor;
+ float3 finalColor;
+
+ float diff = GetDiffuseVolumetric(iWorldPos.xyz);
+
+ #ifdef SHADOW
+ diff *= GetShadow(iShadowPos, iWorldPos.w);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ lightColor = iSpotPos.w > 0.0 ? Sample2DProj(LightSpotMap, iSpotPos).rgb * cLightColor.rgb : 0.0;
+ #elif defined(CUBEMASK)
+ lightColor = texCUBE(sLightCubeMap, iCubeMaskVec).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ finalColor = diff * lightColor * diffColor.rgb;
+ oColor = float4(GetLitFog(finalColor, fogFactor), diffColor.a);
+ #else
+ // Ambient & per-vertex lighting
+ float3 finalColor = iVertexLight * diffColor.rgb;
+
+ oColor = float4(GetFog(finalColor, fogFactor), diffColor.a);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/LitSolid.hlsl b/bin/CoreData/Shaders/HLSL/LitSolid.hlsl
new file mode 100644
index 0000000..514b741
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/LitSolid.hlsl
@@ -0,0 +1,319 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Lighting.hlsl"
+#include "Fog.hlsl"
+
+void VS(float4 iPos : POSITION,
+ #if !defined(BILLBOARD) && !defined(TRAILFACECAM)
+ float3 iNormal : NORMAL,
+ #endif
+ #ifndef NOUV
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ float2 iTexCoord2 : TEXCOORD1,
+ #endif
+ #if (defined(NORMALMAP) || defined(TRAILFACECAM) || defined(TRAILBONE)) && !defined(BILLBOARD) && !defined(DIRBILLBOARD)
+ float4 iTangent : TANGENT,
+ #endif
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ #if defined(BILLBOARD) || defined(DIRBILLBOARD)
+ float2 iSize : TEXCOORD1,
+ #endif
+ #ifndef NORMALMAP
+ out float2 oTexCoord : TEXCOORD0,
+ #else
+ out float4 oTexCoord : TEXCOORD0,
+ out float4 oTangent : TEXCOORD3,
+ #endif
+ out float3 oNormal : TEXCOORD1,
+ out float4 oWorldPos : TEXCOORD2,
+ #ifdef PERPIXEL
+ #ifdef SHADOW
+ out float4 oShadowPos[NUMCASCADES] : TEXCOORD4,
+ #endif
+ #ifdef SPOTLIGHT
+ out float4 oSpotPos : TEXCOORD5,
+ #endif
+ #ifdef POINTLIGHT
+ out float3 oCubeMaskVec : TEXCOORD5,
+ #endif
+ #else
+ out float3 oVertexLight : TEXCOORD4,
+ out float4 oScreenPos : TEXCOORD5,
+ #ifdef ENVCUBEMAP
+ out float3 oReflectionVec : TEXCOORD6,
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ out float2 oTexCoord2 : TEXCOORD7,
+ #endif
+ #endif
+ #ifdef VERTEXCOLOR
+ out float4 oColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ out float oClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ // Define a 0,0 UV coord if not expected from the vertex data
+ #ifdef NOUV
+ float2 iTexCoord = float2(0.0, 0.0);
+ #endif
+
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oNormal = GetWorldNormal(modelMatrix);
+ oWorldPos = float4(worldPos, GetDepth(oPos));
+
+ #if defined(D3D11) && defined(CLIPPLANE)
+ oClip = dot(oPos, cClipPlane);
+ #endif
+
+ #ifdef VERTEXCOLOR
+ oColor = iColor;
+ #endif
+
+ #ifdef NORMALMAP
+ float4 tangent = GetWorldTangent(modelMatrix);
+ float3 bitangent = cross(tangent.xyz, oNormal) * tangent.w;
+ oTexCoord = float4(GetTexCoord(iTexCoord), bitangent.xy);
+ oTangent = float4(tangent.xyz, bitangent.z);
+ #else
+ oTexCoord = GetTexCoord(iTexCoord);
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ float4 projWorldPos = float4(worldPos.xyz, 1.0);
+
+ #ifdef SHADOW
+ // Shadow projection: transform from world space to shadow space
+ GetShadowPos(projWorldPos, oNormal, oShadowPos);
+ #endif
+
+ #ifdef SPOTLIGHT
+ // Spotlight projection: transform from world space to projector texture coordinates
+ oSpotPos = mul(projWorldPos, cLightMatrices[0]);
+ #endif
+
+ #ifdef POINTLIGHT
+ oCubeMaskVec = mul(worldPos - cLightPos.xyz, (float3x3)cLightMatrices[0]);
+ #endif
+ #else
+ // Ambient & per-vertex lighting
+ #if defined(LIGHTMAP) || defined(AO)
+ // If using lightmap, disregard zone ambient light
+ // If using AO, calculate ambient in the PS
+ oVertexLight = float3(0.0, 0.0, 0.0);
+ oTexCoord2 = iTexCoord2;
+ #else
+ oVertexLight = GetAmbient(GetZonePos(worldPos));
+ #endif
+
+ #ifdef NUMVERTEXLIGHTS
+ for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+ oVertexLight += GetVertexLight(i, worldPos, oNormal) * cVertexLights[i * 3].rgb;
+ #endif
+
+ oScreenPos = GetScreenPos(oPos);
+
+ #ifdef ENVCUBEMAP
+ oReflectionVec = worldPos - cCameraPos;
+ #endif
+ #endif
+}
+
+void PS(
+ #ifndef NORMALMAP
+ float2 iTexCoord : TEXCOORD0,
+ #else
+ float4 iTexCoord : TEXCOORD0,
+ float4 iTangent : TEXCOORD3,
+ #endif
+ float3 iNormal : TEXCOORD1,
+ float4 iWorldPos : TEXCOORD2,
+ #ifdef PERPIXEL
+ #ifdef SHADOW
+ float4 iShadowPos[NUMCASCADES] : TEXCOORD4,
+ #endif
+ #ifdef SPOTLIGHT
+ float4 iSpotPos : TEXCOORD5,
+ #endif
+ #ifdef POINTLIGHT
+ float3 iCubeMaskVec : TEXCOORD5,
+ #endif
+ #else
+ float3 iVertexLight : TEXCOORD4,
+ float4 iScreenPos : TEXCOORD5,
+ #ifdef ENVCUBEMAP
+ float3 iReflectionVec : TEXCOORD6,
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ float2 iTexCoord2 : TEXCOORD7,
+ #endif
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ float iClip : SV_CLIPDISTANCE0,
+ #endif
+ #ifdef PREPASS
+ out float4 oDepth : OUTCOLOR1,
+ #endif
+ #ifdef DEFERRED
+ out float4 oAlbedo : OUTCOLOR1,
+ out float4 oNormal : OUTCOLOR2,
+ out float4 oDepth : OUTCOLOR3,
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ // Get material diffuse albedo
+ #ifdef DIFFMAP
+ float4 diffInput = Sample2D(DiffMap, iTexCoord.xy);
+ #ifdef ALPHAMASK
+ if (diffInput.a < 0.5)
+ discard;
+ #endif
+ float4 diffColor = cMatDiffColor * diffInput;
+ #else
+ float4 diffColor = cMatDiffColor;
+ #endif
+
+ #ifdef VERTEXCOLOR
+ diffColor *= iColor;
+ #endif
+
+ // Get material specular albedo
+ #ifdef SPECMAP
+ float3 specColor = cMatSpecColor.rgb * Sample2D(SpecMap, iTexCoord.xy).rgb;
+ #else
+ float3 specColor = cMatSpecColor.rgb;
+ #endif
+
+ // Get normal
+ #ifdef NORMALMAP
+ float3x3 tbn = float3x3(iTangent.xyz, float3(iTexCoord.zw, iTangent.w), iNormal);
+ float3 normal = normalize(mul(DecodeNormal(Sample2D(NormalMap, iTexCoord.xy)), tbn));
+ #else
+ float3 normal = normalize(iNormal);
+ #endif
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(iWorldPos.w, iWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(iWorldPos.w);
+ #endif
+
+ #if defined(PERPIXEL)
+ // Per-pixel forward lighting
+ float3 lightDir;
+ float3 lightColor;
+ float3 finalColor;
+
+ float diff = GetDiffuse(normal, iWorldPos.xyz, lightDir);
+
+ #ifdef SHADOW
+ diff *= GetShadow(iShadowPos, iWorldPos.w);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ lightColor = iSpotPos.w > 0.0 ? Sample2DProj(LightSpotMap, iSpotPos).rgb * cLightColor.rgb : 0.0;
+ #elif defined(CUBEMASK)
+ lightColor = SampleCube(LightCubeMap, iCubeMaskVec).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ #ifdef SPECULAR
+ float spec = GetSpecular(normal, cCameraPosPS - iWorldPos.xyz, lightDir, cMatSpecColor.a);
+ finalColor = diff * lightColor * (diffColor.rgb + spec * specColor * cLightColor.a);
+ #else
+ finalColor = diff * lightColor * diffColor.rgb;
+ #endif
+
+ #ifdef AMBIENT
+ finalColor += cAmbientColor.rgb * diffColor.rgb;
+ finalColor += cMatEmissiveColor;
+ oColor = float4(GetFog(finalColor, fogFactor), diffColor.a);
+ #else
+ oColor = float4(GetLitFog(finalColor, fogFactor), diffColor.a);
+ #endif
+ #elif defined(PREPASS)
+ // Fill light pre-pass G-Buffer
+ float specPower = cMatSpecColor.a / 255.0;
+
+ oColor = float4(normal * 0.5 + 0.5, specPower);
+ oDepth = iWorldPos.w;
+ #elif defined(DEFERRED)
+ // Fill deferred G-buffer
+ float specIntensity = specColor.g;
+ float specPower = cMatSpecColor.a / 255.0;
+
+ float3 finalColor = iVertexLight * diffColor.rgb;
+ #ifdef AO
+ // If using AO, the vertex light ambient is black, calculate occluded ambient here
+ finalColor += Sample2D(EmissiveMap, iTexCoord2).rgb * cAmbientColor.rgb * diffColor.rgb;
+ #endif
+ #ifdef ENVCUBEMAP
+ finalColor += cMatEnvMapColor * SampleCube(EnvCubeMap, reflect(iReflectionVec, normal)).rgb;
+ #endif
+ #ifdef LIGHTMAP
+ finalColor += Sample2D(EmissiveMap, iTexCoord2).rgb * diffColor.rgb;
+ #endif
+ #ifdef EMISSIVEMAP
+ finalColor += cMatEmissiveColor * Sample2D(EmissiveMap, iTexCoord.xy).rgb;
+ #else
+ finalColor += cMatEmissiveColor;
+ #endif
+
+ oColor = float4(GetFog(finalColor, fogFactor), 1.0);
+ oAlbedo = fogFactor * float4(diffColor.rgb, specIntensity);
+ oNormal = float4(normal * 0.5 + 0.5, specPower);
+ oDepth = iWorldPos.w;
+ #else
+ // Ambient & per-vertex lighting
+ float3 finalColor = iVertexLight * diffColor.rgb;
+ #ifdef AO
+ // If using AO, the vertex light ambient is black, calculate occluded ambient here
+ finalColor += Sample2D(EmissiveMap, iTexCoord2).rgb * cAmbientColor.rgb * diffColor.rgb;
+ #endif
+
+ #ifdef MATERIAL
+ // Add light pre-pass accumulation result
+ // Lights are accumulated at half intensity. Bring back to full intensity now
+ float4 lightInput = 2.0 * Sample2DProj(LightBuffer, iScreenPos);
+ float3 lightSpecColor = lightInput.a * lightInput.rgb / max(GetIntensity(lightInput.rgb), 0.001);
+
+ finalColor += lightInput.rgb * diffColor.rgb + lightSpecColor * specColor;
+ #endif
+
+ #ifdef ENVCUBEMAP
+ finalColor += cMatEnvMapColor * SampleCube(EnvCubeMap, reflect(iReflectionVec, normal)).rgb;
+ #endif
+ #ifdef LIGHTMAP
+ finalColor += Sample2D(EmissiveMap, iTexCoord2).rgb * diffColor.rgb;
+ #endif
+ #ifdef EMISSIVEMAP
+ finalColor += cMatEmissiveColor * Sample2D(EmissiveMap, iTexCoord.xy).rgb;
+ #else
+ finalColor += cMatEmissiveColor;
+ #endif
+
+ oColor = float4(GetFog(finalColor, fogFactor), diffColor.a);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/PBR.hlsl b/bin/CoreData/Shaders/HLSL/PBR.hlsl
new file mode 100644
index 0000000..da2b450
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/PBR.hlsl
@@ -0,0 +1,161 @@
+#include "BRDF.hlsl"
+#ifdef COMPILEPS
+
+ float3 GetSpecularDominantDir(float3 normal, float3 reflection, float roughness)
+ {
+ const float smoothness = 1.0 - roughness;
+ const float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
+ return lerp(normal, reflection, lerpFactor);
+ }
+
+ float3 SphereLight(float3 worldPos, float3 lightVec, float3 normal, float3 toCamera, float roughness, float3 specColor, float3 diffColor, out float ndl)
+ {
+ float specEnergy = 1.0f;
+
+ float radius = cLightRad / 100;
+ float rough2 = max(roughness, 0.08);
+ rough2 *= rough2;
+
+ float radius2 = radius * radius;
+ float distToLightSqrd = dot(lightVec,lightVec);
+ float invDistToLight = rsqrt(distToLightSqrd);
+ float sinAlphaSqr = saturate(radius2 / distToLightSqrd);
+ float sinAlpha = sqrt(sinAlphaSqr);
+
+ ndl = dot(normal, (lightVec * invDistToLight));
+
+ if(ndl < sinAlpha)
+ {
+ ndl = max(ndl, -sinAlpha);
+ ndl = ((sinAlpha + ndl) * (sinAlpha + ndl)) / (4 * sinAlpha);
+ }
+
+ float sphereAngle = saturate(radius * invDistToLight);
+
+ specEnergy = rough2 / (rough2 + 0.5f * sphereAngle);
+ specEnergy *= specEnergy;
+
+ float3 R = 2 * dot(toCamera, normal) * normal - toCamera;
+ R = GetSpecularDominantDir(normal, R, roughness);
+
+ // Find closest point on sphere to ray
+ float3 closestPointOnRay = dot(lightVec, R) * R;
+ float3 centerToRay = closestPointOnRay - lightVec;
+ float invDistToRay = rsqrt(dot(centerToRay, centerToRay));
+ float3 closestPointOnSphere = lightVec + centerToRay * saturate(radius * invDistToRay);
+
+ lightVec = closestPointOnSphere;
+ float3 L = normalize(lightVec);
+
+ float3 h = normalize(toCamera + L);
+ float hdn = saturate(dot(h, normal));
+ float hdv = dot(h, toCamera);
+ float ndv = saturate(dot(normal, toCamera));
+ float hdl = saturate(dot(h, lightVec));
+
+ const float3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, hdv) * ndl;
+ const float3 fresnelTerm = Fresnel(specColor, hdv, hdl) ;
+ const float distTerm = Distribution(hdn, roughness);
+ const float visTerm = Visibility(ndl, ndv, roughness);
+ float3 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+ return diffuseFactor + specularFactor;
+
+ }
+
+ float3 TubeLight(float3 worldPos, float3 lightVec, float3 normal, float3 toCamera, float roughness, float3 specColor, float3 diffColor, out float ndl)
+ {
+ float radius = cLightRad / 100;
+ float len = cLightLength / 10;
+ float3 pos = (cLightPosPS.xyz - worldPos);
+ float3 reflectVec = reflect(-toCamera, normal);
+
+ float3 L01 = cLightDirPS * len;
+ float3 L0 = pos - 0.5 * L01;
+ float3 L1 = pos + 0.5 * L01;
+ float3 ld = L1 - L0;
+
+ float distL0 = length( L0 );
+ float distL1 = length( L1 );
+
+ float NoL0 = dot( L0, normal ) / ( 2.0 * distL0 );
+ float NoL1 = dot( L1, normal ) / ( 2.0 * distL1 );
+ ndl = ( 2.0 * clamp( NoL0 + NoL1, 0.0, 1.0 ) )
+ / ( distL0 * distL1 + dot( L0, L1 ) + 2.0 );
+
+ float a = len * len;
+ float b = dot( reflectVec, L01 );
+ float t = saturate( dot( L0, b * reflectVec - L01 ) / (a - b*b) );
+
+ float3 closestPoint = L0 + ld * saturate( t);
+ float3 centreToRay = dot( closestPoint, reflectVec ) * reflectVec - closestPoint;
+ closestPoint = closestPoint + centreToRay * saturate(radius / length(centreToRay));
+
+ float3 l = normalize(closestPoint);
+ float3 h = normalize(toCamera + l);
+
+ ndl = saturate(dot(normal, lightVec));
+ float hdn = saturate(dot(h, normal));
+ float hdv = dot(h, toCamera);
+ float ndv = saturate(dot(normal, toCamera));
+ float hdl = saturate(dot(h, lightVec));
+
+ float distL = length(closestPoint);
+ float alpha = max(roughness, 0.08) * max(roughness, 0.08);
+ float alphaPrime = saturate(radius / (distL * 2.0) + alpha);
+
+ const float3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, hdv) * ndl;
+ const float3 fresnelTerm = Fresnel(specColor, hdv, hdl) ;
+ const float distTerm = Distribution(hdn, roughness);
+ const float visTerm = Visibility(ndl, ndv, roughness);
+ float3 specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+ return diffuseFactor + specularFactor;
+ }
+
+ //Return the PBR BRDF value
+ // lightDir = the vector to the light
+ // lightVec = normalised lightDir
+ // toCamera = vector to the camera
+ // normal = surface normal of the pixel
+ // roughness = roughness of the pixel
+ // diffColor = the rgb color of the pixel
+ // specColor = the rgb specular color of the pixel
+ float3 GetBRDF(float3 worldPos, float3 lightDir, float3 lightVec, float3 toCamera, float3 normal, float roughness, float3 diffColor, float3 specColor)
+ {
+
+ const float3 Hn = normalize(toCamera + lightDir);
+ const float vdh = clamp((dot(toCamera, Hn)), M_EPSILON, 1.0);
+ const float ndh = clamp((dot(normal, Hn)), M_EPSILON, 1.0);
+ float ndl = clamp((dot(normal, lightVec)), M_EPSILON, 1.0);
+ const float ndv = clamp((dot(normal, toCamera)), M_EPSILON, 1.0);
+ const float ldh = clamp((dot(lightVec, Hn)), M_EPSILON, 1.0);
+
+ const float3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, vdh) * ndl;
+ float3 specularFactor = 0;
+
+ #ifdef SPECULAR
+ if(cLightRad > 0.0)
+ {
+ if(cLightLength > 0.0)
+ {
+ return TubeLight(worldPos, lightVec, normal, toCamera, roughness, specColor, diffColor, ndl);
+
+ }
+ else
+ {
+ return SphereLight(worldPos, lightVec, normal, toCamera, roughness, specColor, diffColor, ndl);
+ }
+ }
+ else
+ {
+ const float3 fresnelTerm = Fresnel(specColor, vdh, ldh) ;
+ const float distTerm = Distribution(ndh, roughness);
+ const float visTerm = Visibility(ndl, ndv, roughness);
+ specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
+ return diffuseFactor + specularFactor;
+ }
+
+ #endif
+
+ return diffuseFactor + specularFactor;
+ }
+#endif
diff --git a/bin/CoreData/Shaders/HLSL/PBRDeferred.hlsl b/bin/CoreData/Shaders/HLSL/PBRDeferred.hlsl
new file mode 100644
index 0000000..9422f0e
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/PBRDeferred.hlsl
@@ -0,0 +1,128 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Lighting.hlsl"
+#include "Constants.hlsl"
+#include "PBR.hlsl"
+#line 9
+
+void VS(float4 iPos : POSITION,
+ #ifdef DIRLIGHT
+ out float2 oScreenPos : TEXCOORD0,
+ #else
+ out float4 oScreenPos : TEXCOORD0,
+ #endif
+ out float3 oFarRay : TEXCOORD1,
+ #ifdef ORTHO
+ out float3 oNearRay : TEXCOORD2,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ #ifdef DIRLIGHT
+ oScreenPos = GetScreenPosPreDiv(oPos);
+ oFarRay = GetFarRay(oPos);
+ #ifdef ORTHO
+ oNearRay = GetNearRay(oPos);
+ #endif
+ #else
+ oScreenPos = GetScreenPos(oPos);
+ oFarRay = GetFarRay(oPos) * oPos.w;
+ #ifdef ORTHO
+ oNearRay = GetNearRay(oPos) * oPos.w;
+ #endif
+ #endif
+}
+
+void PS(
+ #ifdef DIRLIGHT
+ float2 iScreenPos : TEXCOORD0,
+ #else
+ float4 iScreenPos : TEXCOORD0,
+ #endif
+ float3 iFarRay : TEXCOORD1,
+ #ifdef ORTHO
+ float3 iNearRay : TEXCOORD2,
+ #endif
+
+ float2 iFragPos : VPOS,
+ out float4 oColor : OUTCOLOR0)
+{
+ // If rendering a directional light quad, optimize out the w divide
+ #ifdef DIRLIGHT
+ float3 depth = Sample2DLod0(DepthBuffer, iScreenPos).r;
+ #ifdef HWDEPTH
+ depth = ReconstructDepth(depth);
+ #endif
+ #ifdef ORTHO
+ float3 worldPos = lerp(iNearRay, iFarRay, depth);
+ #else
+ float3 worldPos = iFarRay * depth;
+ #endif
+ const float4 albedoInput = Sample2DLod0(AlbedoBuffer, iScreenPos);
+ const float4 normalInput = Sample2DLod0(NormalBuffer, iScreenPos);
+ const float4 specularInput = Sample2DLod0(SpecMap, iScreenPos);
+ #else
+ float depth = Sample2DProj(DepthBuffer, iScreenPos).r;
+ #ifdef HWDEPTH
+ depth = ReconstructDepth(depth);
+ #endif
+ #ifdef ORTHO
+ float3 worldPos = lerp(iNearRay, iFarRay, depth) / iScreenPos.w;
+ #else
+ float3 worldPos = iFarRay * depth / iScreenPos.w;
+ #endif
+ const float4 albedoInput = Sample2DProj(AlbedoBuffer, iScreenPos);
+ const float4 normalInput = Sample2DProj(NormalBuffer, iScreenPos);
+ const float4 specularInput = Sample2DProj(SpecMap, iScreenPos);
+ #endif
+
+ // Position acquired via near/far ray is relative to camera. Bring position to world space
+ float3 eyeVec = -worldPos;
+ worldPos += cCameraPosPS;
+
+ float3 normal = normalInput.rgb;
+ const float roughness = length(normal);
+ normal = normalize(normal);
+
+ const float3 specColor = specularInput.rgb;
+
+ const float4 projWorldPos = float4(worldPos, 1.0);
+
+ float3 lightDir;
+ float atten = 1;
+
+ #if defined(DIRLIGHT)
+ atten = GetAtten(normal, worldPos, lightDir);
+ #elif defined(SPOTLIGHT)
+ atten = GetAttenSpot(normal, worldPos, lightDir);
+ #else
+ atten = GetAttenPoint(normal, worldPos, lightDir);
+ #endif
+
+ float shadow = 1;
+ #ifdef SHADOW
+ shadow *= GetShadowDeferred(projWorldPos, normal, depth);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ const float4 spotPos = mul(projWorldPos, cLightMatricesPS[0]);
+ const float3 lightColor = spotPos.w > 0.0 ? Sample2DProj(LightSpotMap, spotPos).rgb * cLightColor.rgb : 0.0;
+ #elif defined(CUBEMASK)
+ const float3 lightColor = texCUBE(sLightCubeMap, mul(worldPos - cLightPosPS.xyz, (float3x3)cLightMatricesPS[0])).rgb * cLightColor.rgb;
+ #else
+ const float3 lightColor = cLightColor.rgb;
+ #endif
+
+ const float3 toCamera = normalize(eyeVec);
+ const float3 lightVec = normalize(lightDir);
+ const float ndl = clamp(abs(dot(normal, lightVec)), M_EPSILON, 1.0);
+
+ float3 BRDF = GetBRDF(worldPos, lightDir, lightVec, toCamera, normal, roughness, albedoInput.rgb, specColor);
+
+ oColor.a = 1;
+ oColor.rgb = BRDF * lightColor * shadow * atten / M_PI;
+}
diff --git a/bin/CoreData/Shaders/HLSL/PBRLitSolid.hlsl b/bin/CoreData/Shaders/HLSL/PBRLitSolid.hlsl
new file mode 100644
index 0000000..de4b636
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/PBRLitSolid.hlsl
@@ -0,0 +1,344 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Constants.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Lighting.hlsl"
+#include "Fog.hlsl"
+#include "PBR.hlsl"
+#include "IBL.hlsl"
+
+void VS(float4 iPos : POSITION,
+ #if !defined(BILLBOARD) && !defined(TRAILFACECAM)
+ float3 iNormal : NORMAL,
+ #endif
+ #ifndef NOUV
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ float2 iTexCoord2 : TEXCOORD1,
+ #endif
+ #if (defined(NORMALMAP) || defined(TRAILFACECAM) || defined(TRAILBONE)) && !defined(BILLBOARD) && !defined(DIRBILLBOARD)
+ float4 iTangent : TANGENT,
+ #endif
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ #if defined(BILLBOARD) || defined(DIRBILLBOARD)
+ float2 iSize : TEXCOORD1,
+ #endif
+ #ifndef NORMALMAP
+ out float2 oTexCoord : TEXCOORD0,
+ #else
+ out float4 oTexCoord : TEXCOORD0,
+ out float4 oTangent : TEXCOORD3,
+ #endif
+ out float3 oNormal : TEXCOORD1,
+ out float4 oWorldPos : TEXCOORD2,
+ #ifdef PERPIXEL
+ #ifdef SHADOW
+ out float4 oShadowPos[NUMCASCADES] : TEXCOORD4,
+ #endif
+ #ifdef SPOTLIGHT
+ out float4 oSpotPos : TEXCOORD5,
+ #endif
+ #ifdef POINTLIGHT
+ out float3 oCubeMaskVec : TEXCOORD5,
+ #endif
+ #else
+ out float3 oVertexLight : TEXCOORD4,
+ out float4 oScreenPos : TEXCOORD5,
+ #ifdef ENVCUBEMAP
+ out float3 oReflectionVec : TEXCOORD6,
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ out float2 oTexCoord2 : TEXCOORD7,
+ #endif
+ #endif
+ #ifdef VERTEXCOLOR
+ out float4 oColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ out float oClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ // Define a 0,0 UV coord if not expected from the vertex data
+ #ifdef NOUV
+ const float2 iTexCoord = float2(0.0, 0.0);
+ #endif
+
+ const float4x3 modelMatrix = iModelMatrix;
+ const float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oNormal = GetWorldNormal(modelMatrix);
+ oWorldPos = float4(worldPos, GetDepth(oPos));
+
+ #if defined(D3D11) && defined(CLIPPLANE)
+ oClip = dot(oPos, cClipPlane);
+ #endif
+
+ #ifdef VERTEXCOLOR
+ oColor = iColor;
+ #endif
+
+ #if defined(NORMALMAP)
+ const float4 tangent = GetWorldTangent(modelMatrix);
+ const float3 bitangent = cross(tangent.xyz, oNormal) * tangent.w;
+ oTexCoord = float4(GetTexCoord(iTexCoord), bitangent.xy);
+ oTangent = float4(tangent.xyz, bitangent.z);
+ #else
+ oTexCoord = GetTexCoord(iTexCoord);
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ const float4 projWorldPos = float4(worldPos.xyz, 1.0);
+
+ #ifdef SHADOW
+ // Shadow projection: transform from world space to shadow space
+ GetShadowPos(projWorldPos, oNormal, oShadowPos);
+ #endif
+
+ #ifdef SPOTLIGHT
+ // Spotlight projection: transform from world space to projector texture coordinates
+ oSpotPos = mul(projWorldPos, cLightMatrices[0]);
+ #endif
+
+ #ifdef POINTLIGHT
+ oCubeMaskVec = mul(worldPos - cLightPos.xyz, (float3x3)cLightMatrices[0]);
+ #endif
+ #else
+ // Ambient & per-vertex lighting
+ #if defined(LIGHTMAP) || defined(AO)
+ // If using lightmap, disregard zone ambient light
+ // If using AO, calculate ambient in the PS
+ oVertexLight = float3(0.0, 0.0, 0.0);
+ oTexCoord2 = iTexCoord2;
+ #else
+ oVertexLight = GetAmbient(GetZonePos(worldPos));
+ #endif
+
+ #ifdef NUMVERTEXLIGHTS
+ for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+ oVertexLight += GetVertexLight(i, worldPos, oNormal) * cVertexLights[i * 3].rgb;
+ #endif
+
+ oScreenPos = GetScreenPos(oPos);
+
+ #ifdef ENVCUBEMAP
+ oReflectionVec = worldPos - cCameraPos;
+ #endif
+ #endif
+}
+
+void PS(
+ #ifndef NORMALMAP
+ float2 iTexCoord : TEXCOORD0,
+ #else
+ float4 iTexCoord : TEXCOORD0,
+ float4 iTangent : TEXCOORD3,
+ #endif
+ float3 iNormal : TEXCOORD1,
+ float4 iWorldPos : TEXCOORD2,
+ #ifdef PERPIXEL
+ #ifdef SHADOW
+ float4 iShadowPos[NUMCASCADES] : TEXCOORD4,
+ #endif
+ #ifdef SPOTLIGHT
+ float4 iSpotPos : TEXCOORD5,
+ #endif
+ #ifdef POINTLIGHT
+ float3 iCubeMaskVec : TEXCOORD5,
+ #endif
+ #else
+ float3 iVertexLight : TEXCOORD4,
+ float4 iScreenPos : TEXCOORD5,
+ #ifdef ENVCUBEMAP
+ float3 iReflectionVec : TEXCOORD6,
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ float2 iTexCoord2 : TEXCOORD7,
+ #endif
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ float iClip : SV_CLIPDISTANCE0,
+ #endif
+ #ifdef PREPASS
+ out float4 oDepth : OUTCOLOR1,
+ #endif
+ #ifdef DEFERRED
+ out float4 oAlbedo : OUTCOLOR1,
+ out float4 oNormal : OUTCOLOR2,
+ out float4 oDepth : OUTCOLOR3,
+ #ifndef D3D11
+ float2 iFragPos : VPOS,
+ #else
+ float4 iFragPos : SV_Position,
+ #endif
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ // Get material diffuse albedo
+ #ifdef DIFFMAP
+ const float4 diffInput = Sample2D(DiffMap, iTexCoord.xy);
+ #ifdef ALPHAMASK
+ if (diffInput.a < 0.5)
+ discard;
+ #endif
+ float4 diffColor = cMatDiffColor * diffInput;
+ #else
+ float4 diffColor = cMatDiffColor;
+ #endif
+
+ #ifdef VERTEXCOLOR
+ diffColor *= iColor;
+ #endif
+
+ // Get material specular albedo
+ #ifdef METALLIC // METALNESS
+ float4 roughMetalSrc = Sample2D(RoughMetalFresnel, iTexCoord.xy);
+
+ float roughness = roughMetalSrc.r + cRoughness;
+ float metalness = roughMetalSrc.g + cMetallic;
+ #else
+ float roughness = cRoughness;
+ float metalness = cMetallic;
+ #endif
+
+ roughness *= roughness;
+
+ roughness = clamp(roughness, ROUGHNESS_FLOOR, 1.0);
+ metalness = clamp(metalness, METALNESS_FLOOR, 1.0);
+
+ float3 specColor = lerp(0.08 * cMatSpecColor.rgb, diffColor.rgb, metalness);
+ specColor *= cMatSpecColor.rgb;
+ diffColor.rgb = diffColor.rgb - diffColor.rgb * metalness; // Modulate down the diffuse
+
+ // Get normal
+ #if defined(NORMALMAP)
+ const float3 tangent = normalize(iTangent.xyz);
+ const float3 bitangent = normalize(float3(iTexCoord.zw, iTangent.w));
+ const float3x3 tbn = float3x3(tangent, bitangent, iNormal);
+ #endif
+
+ #ifdef NORMALMAP
+ const float3 nn = DecodeNormal(Sample2D(NormalMap, iTexCoord.xy));
+ //nn.rg *= 2.0;
+ const float3 normal = normalize(mul(nn, tbn));
+ #else
+ const float3 normal = normalize(iNormal);
+ #endif
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ const float fogFactor = GetHeightFogFactor(iWorldPos.w, iWorldPos.y);
+ #else
+ const float fogFactor = GetFogFactor(iWorldPos.w);
+ #endif
+
+ #if defined(PERPIXEL)
+ // Per-pixel forward lighting
+ float3 lightDir;
+ float3 lightColor;
+ float3 finalColor;
+ float atten = 1;
+
+ #if defined(DIRLIGHT)
+ atten = GetAtten(normal, iWorldPos.xyz, lightDir);
+ #elif defined(SPOTLIGHT)
+ atten = GetAttenSpot(normal, iWorldPos.xyz, lightDir);
+ #else
+ atten = GetAttenPoint(normal, iWorldPos.xyz, lightDir);
+ #endif
+
+ float shadow = 1.0;
+
+ #ifdef SHADOW
+ shadow *= GetShadow(iShadowPos, iWorldPos.w);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ lightColor = iSpotPos.w > 0.0 ? Sample2DProj(LightSpotMap, iSpotPos).rgb * cLightColor.rgb : 0.0;
+ #elif defined(CUBEMASK)
+ lightColor = SampleCube(LightCubeMap, iCubeMaskVec).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ const float3 toCamera = normalize(cCameraPosPS - iWorldPos.xyz);
+
+ const float3 lightVec = normalize(lightDir);
+ const float ndl = clamp((dot(normal, lightVec)), M_EPSILON, 1.0);
+
+
+ float3 BRDF = GetBRDF(iWorldPos.xyz, lightDir, lightVec, toCamera, normal, roughness, diffColor.rgb, specColor);
+ finalColor.rgb = BRDF * lightColor * (atten * shadow) / M_PI;
+
+ #ifdef AMBIENT
+ finalColor += cAmbientColor.rgb * diffColor.rgb;
+ finalColor += cMatEmissiveColor;
+ oColor = float4(GetFog(finalColor, fogFactor), diffColor.a);
+ #else
+ oColor = float4(GetLitFog(finalColor, fogFactor), diffColor.a);
+ #endif
+ #elif defined(DEFERRED)
+ // Fill deferred G-buffer
+ const float3 spareData = 0; // Can be used to pass more data to deferred renderer
+ oColor = float4(specColor, spareData.r);
+ oAlbedo = float4(diffColor.rgb, spareData.g);
+ oNormal = float4(normalize(normal) * roughness, spareData.b);
+ oDepth = iWorldPos.w;
+ #else
+ // Ambient & per-vertex lighting
+ float3 finalColor = iVertexLight * diffColor.rgb;
+ #ifdef AO
+ // If using AO, the vertex light ambient is black, calculate occluded ambient here
+ finalColor += Sample2D(EmissiveMap, iTexCoord2).rgb * cAmbientColor.rgb * diffColor.rgb;
+ #endif
+
+ #ifdef MATERIAL
+ // Add light pre-pass accumulation result
+ // Lights are accumulated at half intensity. Bring back to full intensity now
+ float4 lightInput = 2.0 * Sample2DProj(LightBuffer, iScreenPos);
+ float3 lightSpecColor = lightInput.a * lightInput.rgb / max(GetIntensity(lightInput.rgb), 0.001);
+
+ finalColor += lightInput.rgb * diffColor.rgb + lightSpecColor * specColor;
+ #endif
+
+ const float3 toCamera = normalize(iWorldPos.xyz - cCameraPosPS);
+
+ const float3 reflection = normalize(reflect(toCamera, normal));
+ float3 cubeColor = iVertexLight.rgb;
+
+ #ifdef IBL
+ const float3 iblColor = ImageBasedLighting(reflection, normal, toCamera, diffColor, specColor, roughness, cubeColor);
+ const float gamma = 0;
+ finalColor += iblColor;
+ #endif
+
+ #ifdef ENVCUBEMAP
+ finalColor += cMatEnvMapColor * SampleCube(EnvCubeMap, reflect(iReflectionVec, normal)).rgb;
+ #endif
+ #ifdef LIGHTMAP
+ finalColor += Sample2D(EmissiveMap, iTexCoord2).rgb * diffColor.rgb;
+ #endif
+ #ifdef EMISSIVEMAP
+ finalColor += cMatEmissiveColor * Sample2D(EmissiveMap, iTexCoord.xy).rgb;
+ #else
+ finalColor += cMatEmissiveColor;
+ #endif
+
+ oColor = float4(GetFog(finalColor, fogFactor), diffColor.a);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/PostProcess.hlsl b/bin/CoreData/Shaders/HLSL/PostProcess.hlsl
new file mode 100644
index 0000000..780c227
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/PostProcess.hlsl
@@ -0,0 +1,108 @@
+
+static const float PI = 3.14159265;
+
+float2 Noise(float2 coord)
+{
+ float noiseX = clamp(frac(sin(dot(coord, float2(12.9898, 78.233))) * 43758.5453), 0.0, 1.0);
+ float noiseY = clamp(frac(sin(dot(coord, float2(12.9898, 78.233) * 2.0)) * 43758.5453), 0.0, 1.0);
+ return float2(noiseX, noiseY);
+}
+
+// Adapted: http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html
+#ifndef D3D11
+float4 GaussianBlur(int blurKernelSize, float2 blurDir, float2 blurRadius, float sigma, sampler2D texSampler, float2 texCoord)
+#else
+float4 GaussianBlur(int blurKernelSize, float2 blurDir, float2 blurRadius, float sigma, Texture2D tex, SamplerState texSampler, float2 texCoord)
+#endif
+{
+ const int blurKernelHalfSize = blurKernelSize / 2;
+
+ // Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
+ float3 gaussCoeff;
+ gaussCoeff.x = 1.0 / (sqrt(2.0 * PI) * sigma);
+ gaussCoeff.y = exp(-0.5 / (sigma * sigma));
+ gaussCoeff.z = gaussCoeff.y * gaussCoeff.y;
+
+ float2 blurVec = blurRadius * blurDir;
+ float4 avgValue = float4(0.0, 0.0, 0.0, 0.0);
+ float gaussCoeffSum = 0.0;
+
+ #ifndef D3D11
+ avgValue += tex2D(texSampler, texCoord) * gaussCoeff.x;
+ #else
+ avgValue += tex.Sample(texSampler, texCoord) * gaussCoeff.x;
+ #endif
+
+ gaussCoeffSum += gaussCoeff.x;
+ gaussCoeff.xy *= gaussCoeff.yz;
+
+ for (int i = 1; i <= blurKernelHalfSize; i++)
+ {
+ #ifndef D3D11
+ avgValue += tex2D(texSampler, texCoord - i * blurVec) * gaussCoeff.x;
+ avgValue += tex2D(texSampler, texCoord + i * blurVec) * gaussCoeff.x;
+ #else
+ avgValue += tex.Sample(texSampler, texCoord - i * blurVec) * gaussCoeff.x;
+ avgValue += tex.Sample(texSampler, texCoord + i * blurVec) * gaussCoeff.x;
+ #endif
+
+ gaussCoeffSum += 2.0 * gaussCoeff.x;
+ gaussCoeff.xy *= gaussCoeff.yz;
+ }
+
+ return avgValue / gaussCoeffSum;
+}
+
+static const float3 LumWeights = float3(0.2126, 0.7152, 0.0722);
+
+float3 ReinhardEq3Tonemap(float3 x)
+{
+ return x / (x + 1.0);
+}
+
+float3 ReinhardEq4Tonemap(float3 x, float white)
+{
+ return x * (1.0 + x / white) / (1.0 + x);
+}
+
+// Unchared2 tone mapping (See http://filmicgames.com)
+static const float A = 0.15;
+static const float B = 0.50;
+static const float C = 0.10;
+static const float D = 0.20;
+static const float E = 0.02;
+static const float F = 0.30;
+
+float3 Uncharted2Tonemap(float3 x)
+{
+ return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
+}
+
+#ifndef D3D11
+float3 ColorCorrection(float3 color, sampler3D lut)
+#else
+float3 ColorCorrection(float3 color, Texture3D lut, SamplerState lutSampler)
+#endif
+{
+ float lutSize = 16.0;
+ float scale = (lutSize - 1.0) / lutSize;
+ float offset = 1.0 / (2.0 * lutSize);
+ #ifndef D3D11
+ return tex3D(lut, clamp(color, 0.0, 1.0) * scale + offset).rgb;
+ #else
+ return lut.Sample(lutSampler, clamp(color, 0.0, 1.0) * scale + offset).rgb;
+ #endif
+}
+
+static const float Gamma = 2.2;
+static const float InverseGamma = 1.0 / 2.2;
+
+float3 ToGamma(float3 color)
+{
+ return float3(pow(color, Gamma));
+}
+
+float3 ToInverseGamma(float3 color)
+{
+ return float3(pow(color, InverseGamma));
+}
diff --git a/bin/CoreData/Shaders/HLSL/PrepassLight.hlsl b/bin/CoreData/Shaders/HLSL/PrepassLight.hlsl
new file mode 100644
index 0000000..57b0f08
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/PrepassLight.hlsl
@@ -0,0 +1,105 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Lighting.hlsl"
+
+void VS(float4 iPos : POSITION,
+ #ifdef DIRLIGHT
+ out float2 oScreenPos : TEXCOORD0,
+ #else
+ out float4 oScreenPos : TEXCOORD0,
+ #endif
+ out float3 oFarRay : TEXCOORD1,
+ #ifdef ORTHO
+ out float3 oNearRay : TEXCOORD2,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ #ifdef DIRLIGHT
+ oScreenPos = GetScreenPosPreDiv(oPos);
+ oFarRay = GetFarRay(oPos);
+ #ifdef ORTHO
+ oNearRay = GetNearRay(oPos);
+ #endif
+ #else
+ oScreenPos = GetScreenPos(oPos);
+ oFarRay = GetFarRay(oPos) * oPos.w;
+ #ifdef ORTHO
+ oNearRay = GetNearRay(oPos) * oPos.w;
+ #endif
+ #endif
+}
+
+void PS(
+ #ifdef DIRLIGHT
+ float2 iScreenPos : TEXCOORD0,
+ #else
+ float4 iScreenPos : TEXCOORD0,
+ #endif
+ float3 iFarRay : TEXCOORD1,
+ #ifdef ORTHO
+ float3 iNearRay : TEXCOORD2,
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ // If rendering a directional light quad, optimize out the w divide
+ #ifdef DIRLIGHT
+ float depth = Sample2DLod0(DepthBuffer, iScreenPos).r;
+ #ifdef HWDEPTH
+ depth = ReconstructDepth(depth);
+ #endif
+ #ifdef ORTHO
+ float3 worldPos = lerp(iNearRay, iFarRay, depth);
+ #else
+ float3 worldPos = iFarRay * depth;
+ #endif
+ float4 normalInput = Sample2DLod0(NormalBuffer, iScreenPos);
+ #else
+ float depth = Sample2DProj(DepthBuffer, iScreenPos).r;
+ #ifdef HWDEPTH
+ depth = ReconstructDepth(depth);
+ #endif
+ #ifdef ORTHO
+ float3 worldPos = lerp(iNearRay, iFarRay, depth) / iScreenPos.w;
+ #else
+ float3 worldPos = iFarRay * depth / iScreenPos.w;
+ #endif
+ float4 normalInput = Sample2DProj(NormalBuffer, iScreenPos);
+ #endif
+
+ // Position acquired via near/far ray is relative to camera. Bring position to world space
+ float3 eyeVec = -worldPos;
+ worldPos += cCameraPosPS;
+
+ float3 normal = normalize(normalInput.rgb * 2.0 - 1.0);
+ float4 projWorldPos = float4(worldPos, 1.0);
+ float3 lightColor;
+ float3 lightDir;
+
+ // Accumulate light at half intensity to allow 2x "overburn"
+ float diff = 0.5 * GetDiffuse(normal, worldPos, lightDir);
+
+ #ifdef SHADOW
+ diff *= GetShadowDeferred(projWorldPos, normal, depth);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ float4 spotPos = mul(projWorldPos, cLightMatricesPS[0]);
+ lightColor = spotPos.w > 0.0 ? Sample2DProj(LightSpotMap, spotPos).rgb * cLightColor.rgb : 0.0;
+ #elif defined(CUBEMASK)
+ lightColor = texCUBE(sLightCubeMap, mul(worldPos - cLightPosPS.xyz, (float3x3)cLightMatricesPS[0])).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ #ifdef SPECULAR
+ float spec = lightColor.g * GetSpecular(normal, eyeVec, lightDir, normalInput.a * 255.0);
+ oColor = diff * float4(lightColor, spec * cLightColor.a);
+ #else
+ oColor = diff * float4(lightColor, 0.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/Samplers.hlsl b/bin/CoreData/Shaders/HLSL/Samplers.hlsl
new file mode 100644
index 0000000..c7efb7e
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Samplers.hlsl
@@ -0,0 +1,116 @@
+#ifdef D3D11
+// Make sampling macros also available for VS on D3D11
+#define Sample2D(tex, uv) t##tex.Sample(s##tex, uv)
+#define Sample2DProj(tex, uv) t##tex.Sample(s##tex, uv.xy / uv.w)
+#define Sample2DLod0(tex, uv) t##tex.SampleLevel(s##tex, uv, 0.0)
+#define SampleCube(tex, uv) t##tex.Sample(s##tex, uv)
+#define SampleCubeLOD(tex, uv) t##tex.SampleLevel(s##tex, uv.xyz, uv.w)
+#define SampleShadow(tex, uv) t##tex.SampleCmpLevelZero(s##tex, uv.xy, uv.z)
+#endif
+
+#ifdef COMPILEPS
+
+#ifndef D3D11
+
+// D3D9 samplers
+sampler2D sDiffMap : register(s0);
+samplerCUBE sDiffCubeMap : register(s0);
+sampler2D sAlbedoBuffer : register(s0);
+sampler2D sNormalMap : register(s1);
+sampler2D sNormalBuffer : register(s1);
+sampler2D sSpecMap : register(s2);
+sampler2D sRoughMetalFresnel : register(s2); //R: Roughness, G: Metal
+sampler2D sEmissiveMap : register(s3);
+sampler2D sEnvMap : register(s4);
+sampler3D sVolumeMap : register(s5);
+samplerCUBE sEnvCubeMap : register(s4);
+sampler2D sLightRampMap : register(s8);
+sampler2D sLightSpotMap : register(s9);
+samplerCUBE sLightCubeMap : register(s9);
+sampler2D sShadowMap : register(s10);
+samplerCUBE sFaceSelectCubeMap : register(s11);
+samplerCUBE sIndirectionCubeMap : register(s12);
+sampler2D sDepthBuffer : register(s13);
+sampler2D sLightBuffer : register(s14);
+samplerCUBE sZoneCubeMap : register(s15);
+sampler3D sZoneVolumeMap : register(s15);
+
+#define Sample2D(tex, uv) tex2D(s##tex, uv)
+#define Sample2DProj(tex, uv) tex2Dproj(s##tex, uv)
+#define Sample2DLod0(tex, uv) tex2Dlod(s##tex, float4(uv, 0.0, 0.0))
+#define SampleCube(tex, uv) texCUBE(s##tex, uv)
+#define SampleCubeLOD(tex, uv) texCUBElod(s##tex, uv)
+#define SampleShadow(tex, uv) tex2Dproj(s##tex, uv)
+
+#else
+
+// D3D11 textures and samplers
+
+Texture2D tDiffMap : register(t0);
+TextureCube tDiffCubeMap : register(t0);
+Texture2D tAlbedoBuffer : register(t0);
+Texture2D tNormalMap : register(t1);
+Texture2D tNormalBuffer : register(t1);
+Texture2D tSpecMap : register(t2);
+Texture2D tRoughMetalFresnel : register(t2); //R: Roughness, G: Metal
+Texture2D tEmissiveMap : register(t3);
+Texture2D tEnvMap : register(t4);
+Texture3D tVolumeMap : register(t5);
+TextureCube tEnvCubeMap : register(t4);
+Texture2D tLightRampMap : register(t8);
+Texture2D tLightSpotMap : register(t9);
+TextureCube tLightCubeMap : register(t9);
+Texture2D tShadowMap : register(t10);
+TextureCube tFaceSelectCubeMap : register(t11);
+TextureCube tIndirectionCubeMap : register(t12);
+Texture2D tDepthBuffer : register(t13);
+Texture2D tLightBuffer : register(t14);
+TextureCube tZoneCubeMap : register(t15);
+Texture3D tZoneVolumeMap : register(t15);
+
+SamplerState sDiffMap : register(s0);
+SamplerState sDiffCubeMap : register(s0);
+SamplerState sAlbedoBuffer : register(s0);
+SamplerState sNormalMap : register(s1);
+SamplerState sNormalBuffer : register(s1);
+SamplerState sSpecMap : register(s2);
+SamplerState sRoughMetalFresnel : register(s2); //R: Roughness, G: Metal
+SamplerState sEmissiveMap : register(s3);
+SamplerState sEnvMap : register(s4);
+SamplerState sVolumeMap : register(s5);
+SamplerState sEnvCubeMap : register(s4);
+SamplerState sLightRampMap : register(s8);
+SamplerState sLightSpotMap : register(s9);
+SamplerState sLightCubeMap : register(s9);
+#ifdef VSM_SHADOW
+ SamplerState sShadowMap : register(s10);
+#else
+ SamplerComparisonState sShadowMap : register(s10);
+#endif
+SamplerState sFaceSelectCubeMap : register(s11);
+SamplerState sIndirectionCubeMap : register(s12);
+SamplerState sDepthBuffer : register(s13);
+SamplerState sLightBuffer : register(s14);
+SamplerState sZoneCubeMap : register(s15);
+SamplerState sZoneVolumeMap : register(s15);
+
+#endif
+
+float3 DecodeNormal(float4 normalInput)
+{
+#ifdef PACKEDNORMAL
+ float3 normal;
+ normal.xy = normalInput.ag * 2.0 - 1.0;
+ normal.z = sqrt(max(1.0 - dot(normal.xy, normal.xy), 0.0));
+ return normal;
+#else
+ return normalInput.rgb * 2.0 - 1.0;
+#endif
+}
+
+float ReconstructDepth(float hwDepth)
+{
+ return dot(float2(hwDepth, cDepthReconstruct.y / (hwDepth - cDepthReconstruct.x)), cDepthReconstruct.zw);
+}
+
+#endif
diff --git a/bin/CoreData/Shaders/HLSL/ScreenPos.hlsl b/bin/CoreData/Shaders/HLSL/ScreenPos.hlsl
new file mode 100644
index 0000000..8c0c98a
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/ScreenPos.hlsl
@@ -0,0 +1,58 @@
+#ifdef COMPILEVS
+float3x3 GetCameraRot()
+{
+ return float3x3(cViewInv[0][0], cViewInv[0][1], cViewInv[0][2],
+ cViewInv[1][0], cViewInv[1][1], cViewInv[1][2],
+ cViewInv[2][0], cViewInv[2][1], cViewInv[2][2]);
+}
+
+float4 GetScreenPos(float4 clipPos)
+{
+ return float4(
+ clipPos.x * cGBufferOffsets.z + cGBufferOffsets.x * clipPos.w,
+ -clipPos.y * cGBufferOffsets.w + cGBufferOffsets.y * clipPos.w,
+ 0.0,
+ clipPos.w);
+}
+
+float2 GetScreenPosPreDiv(float4 clipPos)
+{
+ return float2(
+ clipPos.x / clipPos.w * cGBufferOffsets.z + cGBufferOffsets.x,
+ -clipPos.y / clipPos.w * cGBufferOffsets.w + cGBufferOffsets.y);
+}
+
+float2 GetQuadTexCoord(float4 clipPos)
+{
+ return float2(
+ clipPos.x / clipPos.w * 0.5 + 0.5,
+ -clipPos.y / clipPos.w * 0.5 + 0.5);
+}
+
+float2 GetQuadTexCoordNoFlip(float3 worldPos)
+{
+ return float2(
+ worldPos.x * 0.5 + 0.5,
+ -worldPos.y * 0.5 + 0.5);
+}
+
+float3 GetFarRay(float4 clipPos)
+{
+ float3 viewRay = float3(
+ clipPos.x / clipPos.w * cFrustumSize.x,
+ clipPos.y / clipPos.w * cFrustumSize.y,
+ cFrustumSize.z);
+
+ return mul(viewRay, GetCameraRot());
+}
+
+float3 GetNearRay(float4 clipPos)
+{
+ float3 viewRay = float3(
+ clipPos.x / clipPos.w * cFrustumSize.x,
+ clipPos.y / clipPos.w * cFrustumSize.y,
+ 0.0);
+
+ return mul(viewRay, GetCameraRot()) * cDepthMode.z;
+}
+#endif
diff --git a/bin/CoreData/Shaders/HLSL/Shadow.hlsl b/bin/CoreData/Shaders/HLSL/Shadow.hlsl
new file mode 100644
index 0000000..88d7972
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Shadow.hlsl
@@ -0,0 +1,61 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+
+void VS(float4 iPos : POSITION,
+ #ifndef NOUV
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ #if defined(BILLBOARD) || defined(DIRBILLBOARD)
+ float2 iSize : TEXCOORD1,
+ #endif
+ #ifdef VSM_SHADOW
+ out float4 oTexCoord : TEXCOORD0,
+ #else
+ out float2 oTexCoord : TEXCOORD0,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ // Define a 0,0 UV coord if not expected from the vertex data
+ #ifdef NOUV
+ float2 iTexCoord = float2(0.0, 0.0);
+ #endif
+
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ #ifdef VSM_SHADOW
+ oTexCoord = float4(GetTexCoord(iTexCoord), oPos.z, oPos.w);
+ #else
+ oTexCoord = GetTexCoord(iTexCoord);
+ #endif
+}
+
+void PS(
+ #ifdef VSM_SHADOW
+ float4 iTexCoord : TEXCOORD0,
+ #else
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ #ifdef ALPHAMASK
+ float alpha = Sample2D(DiffMap, iTexCoord.xy).a;
+ if (alpha < 0.5)
+ discard;
+ #endif
+
+ #ifdef VSM_SHADOW
+ float depth = iTexCoord.z / iTexCoord.w;
+ oColor = float4(depth, depth * depth, 1.0, 1.0);
+ #else
+ oColor = 1.0;
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/ShadowBlur.hlsl b/bin/CoreData/Shaders/HLSL/ShadowBlur.hlsl
new file mode 100644
index 0000000..fbcd919
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/ShadowBlur.hlsl
@@ -0,0 +1,48 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms
+uniform float2 cBlurOffsets;
+
+#else
+
+#ifdef COMPILEPS
+// D3D11 constant buffers
+cbuffer CustomPS : register(b6)
+{
+ float2 cBlurOffsets;
+}
+#endif
+
+#endif
+
+void VS(float4 iPos : POSITION,
+ out float2 oScreenPos : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iScreenPos : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ float2 color = 0.0;
+
+ color += 0.015625 * Sample2D(DiffMap, iScreenPos - 3.0 * cBlurOffsets).rg;
+ color += 0.09375 * Sample2D(DiffMap, iScreenPos - 2.0 * cBlurOffsets).rg;
+ color += 0.234375 * Sample2D(DiffMap, iScreenPos - cBlurOffsets).rg;
+ color += 0.3125 * Sample2D(DiffMap, iScreenPos).rg;
+ color += 0.234375 * Sample2D(DiffMap, iScreenPos + cBlurOffsets).rg;
+ color += 0.09375 * Sample2D(DiffMap, iScreenPos + 2.0 * cBlurOffsets).rg;
+ color += 0.015625 * Sample2D(DiffMap, iScreenPos + 3.0 * cBlurOffsets).rg;
+
+ oColor = float4(color, 0.0, 0.0);
+}
+
diff --git a/bin/CoreData/Shaders/HLSL/Skybox.hlsl b/bin/CoreData/Shaders/HLSL/Skybox.hlsl
new file mode 100644
index 0000000..cf99553
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Skybox.hlsl
@@ -0,0 +1,28 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+
+void VS(float4 iPos : POSITION,
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ out float3 oTexCoord : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+
+ oPos.z = oPos.w;
+ oTexCoord = iPos.xyz;
+}
+
+void PS(float3 iTexCoord : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ float4 sky = cMatDiffColor * SampleCube(DiffCubeMap, iTexCoord);
+ #ifdef HDRSCALE
+ sky = pow(sky + clamp((cAmbientColor.a - 1.0) * 0.1, 0.0, 0.25), max(cAmbientColor.a, 1.0)) * clamp(cAmbientColor.a, 0.0, 1.0);
+ #endif
+ oColor = sky;
+}
diff --git a/bin/CoreData/Shaders/HLSL/Skydome.hlsl b/bin/CoreData/Shaders/HLSL/Skydome.hlsl
new file mode 100644
index 0000000..86533f3
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Skydome.hlsl
@@ -0,0 +1,22 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+
+void VS(float4 iPos : POSITION,
+ float2 iTexCoord: TEXCOORD0,
+ out float2 oTexCoord : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+
+ oPos.z = oPos.w;
+ oTexCoord = iTexCoord;
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ oColor = cMatDiffColor * Sample2D(DiffMap, iTexCoord);
+}
diff --git a/bin/CoreData/Shaders/HLSL/Stencil.hlsl b/bin/CoreData/Shaders/HLSL/Stencil.hlsl
new file mode 100644
index 0000000..32104c2
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Stencil.hlsl
@@ -0,0 +1,15 @@
+#include "Uniforms.hlsl"
+#include "Transform.hlsl"
+
+void VS(float4 iPos : POSITION,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+}
+
+void PS(out float4 oColor : OUTCOLOR0)
+{
+ oColor = 1.0;
+}
diff --git a/bin/CoreData/Shaders/HLSL/TerrainBlend.hlsl b/bin/CoreData/Shaders/HLSL/TerrainBlend.hlsl
new file mode 100644
index 0000000..a3960a6
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/TerrainBlend.hlsl
@@ -0,0 +1,242 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Lighting.hlsl"
+#include "Fog.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms and samplers
+#ifdef COMPILEVS
+uniform float2 cDetailTiling;
+#else
+sampler2D sWeightMap0 : register(s0);
+sampler2D sDetailMap1 : register(s1);
+sampler2D sDetailMap2 : register(s2);
+sampler2D sDetailMap3 : register(s3);
+#endif
+
+#else
+
+// D3D11 constant buffers and samplers
+#ifdef COMPILEVS
+cbuffer CustomVS : register(b6)
+{
+ float2 cDetailTiling;
+}
+#else
+Texture2D tWeightMap0 : register(t0);
+Texture2D tDetailMap1 : register(t1);
+Texture2D tDetailMap2 : register(t2);
+Texture2D tDetailMap3 : register(t3);
+SamplerState sWeightMap0 : register(s0);
+SamplerState sDetailMap1 : register(s1);
+SamplerState sDetailMap2 : register(s2);
+SamplerState sDetailMap3 : register(s3);
+#endif
+
+#endif
+
+void VS(float4 iPos : POSITION,
+ float3 iNormal : NORMAL,
+ float2 iTexCoord : TEXCOORD0,
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ #if defined(BILLBOARD) || defined(DIRBILLBOARD)
+ float2 iSize : TEXCOORD1,
+ #endif
+ #if defined(TRAILFACECAM) || defined(TRAILBONE)
+ float4 iTangent : TANGENT,
+ #endif
+ out float2 oTexCoord : TEXCOORD0,
+ out float3 oNormal : TEXCOORD1,
+ out float4 oWorldPos : TEXCOORD2,
+ out float2 oDetailTexCoord : TEXCOORD3,
+ #ifdef PERPIXEL
+ #ifdef SHADOW
+ out float4 oShadowPos[NUMCASCADES] : TEXCOORD4,
+ #endif
+ #ifdef SPOTLIGHT
+ out float4 oSpotPos : TEXCOORD5,
+ #endif
+ #ifdef POINTLIGHT
+ out float3 oCubeMaskVec : TEXCOORD5,
+ #endif
+ #else
+ out float3 oVertexLight : TEXCOORD4,
+ out float4 oScreenPos : TEXCOORD5,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ out float oClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oNormal = GetWorldNormal(modelMatrix);
+ oWorldPos = float4(worldPos, GetDepth(oPos));
+ oTexCoord = GetTexCoord(iTexCoord);
+ oDetailTexCoord = cDetailTiling * oTexCoord;
+
+ #if defined(D3D11) && defined(CLIPPLANE)
+ oClip = dot(oPos, cClipPlane);
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ float4 projWorldPos = float4(worldPos.xyz, 1.0);
+
+ #ifdef SHADOW
+ // Shadow projection: transform from world space to shadow space
+ GetShadowPos(projWorldPos, oNormal, oShadowPos);
+ #endif
+
+ #ifdef SPOTLIGHT
+ // Spotlight projection: transform from world space to projector texture coordinates
+ oSpotPos = mul(projWorldPos, cLightMatrices[0]);
+ #endif
+
+ #ifdef POINTLIGHT
+ oCubeMaskVec = mul(worldPos - cLightPos.xyz, (float3x3)cLightMatrices[0]);
+ #endif
+ #else
+ // Ambient & per-vertex lighting
+ oVertexLight = GetAmbient(GetZonePos(worldPos));
+
+ #ifdef NUMVERTEXLIGHTS
+ for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+ oVertexLight += GetVertexLight(i, worldPos, oNormal) * cVertexLights[i * 3].rgb;
+ #endif
+
+ oScreenPos = GetScreenPos(oPos);
+ #endif
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+ float3 iNormal : TEXCOORD1,
+ float4 iWorldPos : TEXCOORD2,
+ float2 iDetailTexCoord : TEXCOORD3,
+ #ifdef PERPIXEL
+ #ifdef SHADOW
+ float4 iShadowPos[NUMCASCADES] : TEXCOORD4,
+ #endif
+ #ifdef SPOTLIGHT
+ float4 iSpotPos : TEXCOORD5,
+ #endif
+ #ifdef POINTLIGHT
+ float3 iCubeMaskVec : TEXCOORD5,
+ #endif
+ #else
+ float3 iVertexLight : TEXCOORD4,
+ float4 iScreenPos : TEXCOORD5,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ float iClip : SV_CLIPDISTANCE0,
+ #endif
+ #ifdef PREPASS
+ out float4 oDepth : OUTCOLOR1,
+ #endif
+ #ifdef DEFERRED
+ out float4 oAlbedo : OUTCOLOR1,
+ out float4 oNormal : OUTCOLOR2,
+ out float4 oDepth : OUTCOLOR3,
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ // Get material diffuse albedo
+ float3 weights = Sample2D(WeightMap0, iTexCoord).rgb;
+ float sumWeights = weights.r + weights.g + weights.b;
+ weights /= sumWeights;
+ float4 diffColor = cMatDiffColor * (
+ weights.r * Sample2D(DetailMap1, iDetailTexCoord) +
+ weights.g * Sample2D(DetailMap2, iDetailTexCoord) +
+ weights.b * Sample2D(DetailMap3, iDetailTexCoord)
+ );
+
+ // Get material specular albedo
+ float3 specColor = cMatSpecColor.rgb;
+
+ // Get normal
+ float3 normal = normalize(iNormal);
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(iWorldPos.w, iWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(iWorldPos.w);
+ #endif
+
+ #if defined(PERPIXEL)
+ // Per-pixel forward lighting
+ float3 lightDir;
+ float3 lightColor;
+ float3 finalColor;
+
+ float diff = GetDiffuse(normal, iWorldPos.xyz, lightDir);
+
+ #ifdef SHADOW
+ diff *= GetShadow(iShadowPos, iWorldPos.w);
+ #endif
+
+ #if defined(SPOTLIGHT)
+ lightColor = iSpotPos.w > 0.0 ? Sample2DProj(LightSpotMap, iSpotPos).rgb * cLightColor.rgb : 0.0;
+ #elif defined(CUBEMASK)
+ lightColor = SampleCube(LightCubeMap, iCubeMaskVec).rgb * cLightColor.rgb;
+ #else
+ lightColor = cLightColor.rgb;
+ #endif
+
+ #ifdef SPECULAR
+ float spec = GetSpecular(normal, cCameraPosPS - iWorldPos.xyz, lightDir, cMatSpecColor.a);
+ finalColor = diff * lightColor * (diffColor.rgb + spec * specColor * cLightColor.a);
+ #else
+ finalColor = diff * lightColor * diffColor.rgb;
+ #endif
+
+ #ifdef AMBIENT
+ finalColor += cAmbientColor.rgb * diffColor.rgb;
+ finalColor += cMatEmissiveColor;
+ oColor = float4(GetFog(finalColor, fogFactor), diffColor.a);
+ #else
+ oColor = float4(GetLitFog(finalColor, fogFactor), diffColor.a);
+ #endif
+ #elif defined(PREPASS)
+ // Fill light pre-pass G-Buffer
+ float specPower = cMatSpecColor.a / 255.0;
+
+ oColor = float4(normal * 0.5 + 0.5, specPower);
+ oDepth = iWorldPos.w;
+ #elif defined(DEFERRED)
+ // Fill deferred G-buffer
+ float specIntensity = specColor.g;
+ float specPower = cMatSpecColor.a / 255.0;
+
+ float3 finalColor = iVertexLight * diffColor.rgb;
+
+ oColor = float4(GetFog(finalColor, fogFactor), 1.0);
+ oAlbedo = fogFactor * float4(diffColor.rgb, specIntensity);
+ oNormal = float4(normal * 0.5 + 0.5, specPower);
+ oDepth = iWorldPos.w;
+ #else
+ // Ambient & per-vertex lighting
+ float3 finalColor = iVertexLight * diffColor.rgb;
+
+ #ifdef MATERIAL
+ // Add light pre-pass accumulation result
+ // Lights are accumulated at half intensity. Bring back to full intensity now
+ float4 lightInput = 2.0 * Sample2DProj(LightBuffer, iScreenPos);
+ float3 lightSpecColor = lightInput.a * (lightInput.rgb / GetIntensity(lightInput.rgb));
+
+ finalColor += lightInput.rgb * diffColor.rgb + lightSpecColor * specColor;
+ #endif
+
+ oColor = float4(GetFog(finalColor, fogFactor), diffColor.a);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/Text.hlsl b/bin/CoreData/Shaders/HLSL/Text.hlsl
new file mode 100644
index 0000000..c944309
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Text.hlsl
@@ -0,0 +1,112 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms
+uniform float2 cShadowOffset;
+uniform float4 cShadowColor;
+uniform float4 cStrokeColor;
+
+#else
+
+#ifdef COMPILEPS
+// D3D11 constant buffers
+cbuffer CustomPS : register(b6)
+{
+ float2 cShadowOffset;
+ float4 cShadowColor;
+ float4 cStrokeColor;
+}
+#endif
+
+#endif
+
+void VS(float4 iPos : POSITION,
+ float2 iTexCoord : TEXCOORD0,
+ out float2 oTexCoord : TEXCOORD0,
+ float4 iColor : COLOR0,
+ out float4 oColor : COLOR0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oColor = iColor;
+ oTexCoord = iTexCoord;
+}
+
+// See notes in GLSL shader
+#if defined(COMPILEPS) && defined(SIGNED_DISTANCE_FIELD)
+ float GetAlpha(float distance, float width)
+ {
+ return smoothstep(0.5 - width, 0.5 + width, distance);
+ }
+
+ // Comment this define to turn off supersampling
+ #define SUPERSAMPLING
+#endif
+
+
+void PS(float2 iTexCoord : TEXCOORD0,
+ float4 iColor : COLOR0,
+ out float4 oColor : OUTCOLOR0)
+{
+#ifdef SIGNED_DISTANCE_FIELD
+ oColor.rgb = iColor.rgb;
+ float distance = Sample2D(DiffMap, iTexCoord).a;
+
+ #ifdef TEXT_EFFECT_STROKE
+ #ifdef SUPERSAMPLING
+ float outlineFactor = smoothstep(0.5, 0.525, distance); // Border of glyph
+ oColor.rgb = lerp(cStrokeColor.rgb, iColor.rgb, outlineFactor);
+ #else
+ if (distance < 0.525)
+ oColor.rgb = cStrokeColor.rgb;
+ #endif
+ #endif
+
+ #ifdef TEXT_EFFECT_SHADOW
+ if (Sample2D(DiffMap, iTexCoord - cShadowOffset).a > 0.5 && distance <= 0.5)
+ oColor = cShadowColor;
+ #ifndef SUPERSAMPLING
+ else if (distance <= 0.5)
+ oColor.a = 0.0;
+ #endif
+ else
+ #endif
+ {
+ float width = fwidth(distance);
+ float alpha = GetAlpha(distance, width);
+
+ #ifdef SUPERSAMPLING
+ float2 deltaUV = 0.354 * fwidth(iTexCoord); // (1.0 / sqrt(2.0)) / 2.0 = 0.354
+ float4 square = float4(iTexCoord - deltaUV, iTexCoord + deltaUV);
+
+ float distance2 = Sample2D(DiffMap, square.xy).a;
+ float distance3 = Sample2D(DiffMap, square.zw).a;
+ float distance4 = Sample2D(DiffMap, square.xw).a;
+ float distance5 = Sample2D(DiffMap, square.zy).a;
+
+ alpha += GetAlpha(distance2, width)
+ + GetAlpha(distance3, width)
+ + GetAlpha(distance4, width)
+ + GetAlpha(distance5, width);
+
+ // For calculating of average correct would be dividing by 5.
+ // But when text is blurred, its brightness is lost. Therefore divide by 4.
+ alpha = alpha * 0.25;
+ #endif
+
+ oColor.a = alpha;
+ }
+#else
+ #ifdef ALPHAMAP
+ oColor.rgb = iColor.rgb;
+ oColor.a = iColor.a * Sample2D(DiffMap, iTexCoord).a;
+ #else
+ oColor = iColor* Sample2D(DiffMap, iTexCoord);
+ #endif
+#endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/Tonemap.hlsl b/bin/CoreData/Shaders/HLSL/Tonemap.hlsl
new file mode 100644
index 0000000..6e53e90
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Tonemap.hlsl
@@ -0,0 +1,54 @@
+#include "Uniforms.hlsl"
+#include "Transform.hlsl"
+#include "Samplers.hlsl"
+#include "ScreenPos.hlsl"
+#include "PostProcess.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms
+uniform float cTonemapExposureBias;
+uniform float cTonemapMaxWhite;
+
+#else
+
+#ifdef COMPILEPS
+// D3D11 constant buffers
+cbuffer CustomPS : register(b6)
+{
+ float cTonemapExposureBias;
+ float cTonemapMaxWhite;
+}
+#endif
+
+#endif
+
+void VS(float4 iPos : POSITION,
+ out float2 oScreenPos : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oScreenPos = GetScreenPosPreDiv(oPos);
+}
+
+void PS(float2 iScreenPos : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ #ifdef REINHARDEQ3
+ float3 color = ReinhardEq3Tonemap(max(Sample2D(DiffMap, iScreenPos).rgb * cTonemapExposureBias, 0.0));
+ oColor = float4(color, 1.0);
+ #endif
+
+ #ifdef REINHARDEQ4
+ float3 color = ReinhardEq4Tonemap(max(Sample2D(DiffMap, iScreenPos).rgb * cTonemapExposureBias, 0.0), cTonemapMaxWhite);
+ oColor = float4(color, 1.0);
+ #endif
+
+ #ifdef UNCHARTED2
+ float3 color = Uncharted2Tonemap(max(Sample2D(DiffMap, iScreenPos).rgb * cTonemapExposureBias, 0.0)) /
+ Uncharted2Tonemap(float3(cTonemapMaxWhite, cTonemapMaxWhite, cTonemapMaxWhite));
+ oColor = float4(color, 1.0);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/Transform.hlsl b/bin/CoreData/Shaders/HLSL/Transform.hlsl
new file mode 100644
index 0000000..94c4439
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Transform.hlsl
@@ -0,0 +1,165 @@
+#ifdef COMPILEVS
+
+#ifdef D3D11
+#define OUTPOSITION SV_POSITION
+#else
+#define OUTPOSITION POSITION
+#endif
+
+#ifdef SKINNED
+float4x3 GetSkinMatrix(float4 blendWeights, int4 blendIndices)
+{
+ return cSkinMatrices[blendIndices.x] * blendWeights.x +
+ cSkinMatrices[blendIndices.y] * blendWeights.y +
+ cSkinMatrices[blendIndices.z] * blendWeights.z +
+ cSkinMatrices[blendIndices.w] * blendWeights.w;
+}
+#endif
+
+float2 GetTexCoord(float2 iTexCoord)
+{
+ return float2(dot(iTexCoord, cUOffset.xy) + cUOffset.w, dot(iTexCoord, cVOffset.xy) + cVOffset.w);
+};
+
+float4 GetClipPos(float3 worldPos)
+{
+ return mul(float4(worldPos, 1.0), cViewProj);
+}
+
+float GetZonePos(float3 worldPos)
+{
+ return saturate(mul(float4(worldPos, 1.0), cZone).z);
+}
+
+float GetDepth(float4 clipPos)
+{
+ return dot(clipPos.zw, cDepthMode.zw);
+}
+
+#ifdef BILLBOARD
+float3 GetBillboardPos(float4 iPos, float2 iSize, float4x3 modelMatrix)
+{
+ return mul(iPos, modelMatrix) + mul(float3(iSize.x, iSize.y, 0.0), cBillboardRot);
+}
+
+float3 GetBillboardNormal()
+{
+ return float3(-cBillboardRot[2][0], -cBillboardRot[2][1], -cBillboardRot[2][2]);
+}
+#endif
+
+#ifdef DIRBILLBOARD
+float3x3 GetFaceCameraRotation(float3 position, float3 direction)
+{
+ float3 cameraDir = normalize(position - cCameraPos);
+ float3 front = normalize(direction);
+ float3 right = normalize(cross(front, cameraDir));
+ float3 up = normalize(cross(front, right));
+
+ return float3x3(
+ right.x, right.y, right.z,
+ up.x, up.y, up.z,
+ front.x, front.y, front.z
+ );
+}
+
+float3 GetBillboardPos(float4 iPos, float2 iSize, float3 iDirection, float4x3 modelMatrix)
+{
+ float3 worldPos = mul(iPos, modelMatrix);
+ return worldPos + mul(float3(iSize.x, 0.0, iSize.y), GetFaceCameraRotation(worldPos, iDirection));
+}
+
+float3 GetBillboardNormal(float4 iPos, float3 iDirection, float4x3 modelMatrix)
+{
+ float3 worldPos = mul(iPos, modelMatrix);
+ return mul(float3(0.0, 1.0, 0.0), GetFaceCameraRotation(worldPos, iDirection));
+}
+#endif
+
+#ifdef TRAILFACECAM
+float3 GetTrailPos(float4 iPos, float3 iFront, float iScale, float4x3 modelMatrix)
+{
+ float3 up = normalize(cCameraPos - iPos.xyz);
+ float3 left = normalize(cross(iFront, up));
+ return (mul(float4((iPos.xyz + left * iScale), 1.0), modelMatrix)).xyz;
+}
+
+float3 GetTrailNormal(float4 iPos)
+{
+ return normalize(cCameraPos - iPos.xyz);
+}
+#endif
+
+#ifdef TRAILBONE
+float3 GetTrailPos(float4 iPos, float3 iParentPos, float iScale, float4x3 modelMatrix)
+{
+ float3 right = iParentPos - iPos.xyz;
+ return (mul(float4((iPos.xyz + right * iScale), 1.0), modelMatrix)).xyz;
+}
+
+float3 GetTrailNormal(float4 iPos, float3 iParentPos, float3 iForward)
+{
+ float3 left = normalize(iPos.xyz - iParentPos);
+ float3 up = -normalize(cross(normalize(iForward), left));
+ return up;
+}
+#endif
+
+#if defined(SKINNED)
+ #define iModelMatrix GetSkinMatrix(iBlendWeights, iBlendIndices)
+#elif defined(INSTANCED)
+ #define iModelMatrix iModelInstance
+#else
+ #define iModelMatrix cModel
+#endif
+
+#if defined(BILLBOARD)
+ #define GetWorldPos(modelMatrix) GetBillboardPos(iPos, iSize, modelMatrix)
+#elif defined(DIRBILLBOARD)
+ #define GetWorldPos(modelMatrix) GetBillboardPos(iPos, iSize, iNormal, modelMatrix)
+#elif defined(TRAILFACECAM)
+ #define GetWorldPos(modelMatrix) GetTrailPos(iPos, iTangent.xyz, iTangent.w, modelMatrix)
+#elif defined(TRAILBONE)
+ #define GetWorldPos(modelMatrix) GetTrailPos(iPos, iTangent.xyz, iTangent.w, modelMatrix)
+#else
+ #define GetWorldPos(modelMatrix) mul(iPos, modelMatrix)
+#endif
+
+#if defined(BILLBOARD)
+ #define GetWorldNormal(modelMatrix) GetBillboardNormal()
+#elif defined(DIRBILLBOARD)
+ #define GetWorldNormal(modelMatrix) GetBillboardNormal(iPos, iNormal, modelMatrix)
+#elif defined(TRAILFACECAM)
+ #define GetWorldNormal(modelMatrix) GetTrailNormal(iPos)
+#elif defined(TRAILBONE)
+ #define GetWorldNormal(modelMatrix) GetTrailNormal(iPos, iTangent.xyz, iNormal)
+#else
+ #define GetWorldNormal(modelMatrix) normalize(mul(iNormal, (float3x3)modelMatrix))
+#endif
+
+#if defined(BILLBOARD)
+ #define GetWorldTangent(modelMatrix) float4(normalize(mul(float3(1.0, 0.0, 0.0), cBillboardRot)), 1.0)
+#elif defined(DIRBILLBOARD)
+ #define GetWorldTangent(modelMatrix) float4(normalize(mul(float3(1.0, 0.0, 0.0), (float3x3)modelMatrix)), 1.0)
+#else
+ #define GetWorldTangent(modelMatrix) float4(normalize(mul(iTangent.xyz, (float3x3)modelMatrix)), iTangent.w)
+#endif
+
+#endif
+
+#ifdef COMPILEPS
+
+#ifdef D3D11
+#define OUTCOLOR0 SV_TARGET
+#define OUTCOLOR1 SV_TARGET1
+#define OUTCOLOR2 SV_TARGET2
+#define OUTCOLOR3 SV_TARGET3
+#else
+#define OUTCOLOR0 COLOR0
+#define OUTCOLOR1 COLOR1
+#define OUTCOLOR2 COLOR2
+#define OUTCOLOR3 COLOR3
+#endif
+
+#endif
+
diff --git a/bin/CoreData/Shaders/HLSL/Uniforms.hlsl b/bin/CoreData/Shaders/HLSL/Uniforms.hlsl
new file mode 100644
index 0000000..4d211b6
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Uniforms.hlsl
@@ -0,0 +1,209 @@
+#ifndef D3D11
+
+// D3D9 uniforms (no constant buffers)
+
+#ifdef COMPILEVS
+
+// Vertex shader uniforms
+uniform float3 cAmbientStartColor;
+uniform float3 cAmbientEndColor;
+#ifdef BILLBOARD
+uniform float3x3 cBillboardRot;
+#endif
+uniform float3 cCameraPos;
+uniform float cNearClip;
+uniform float cFarClip;
+uniform float4 cDepthMode;
+uniform float cDeltaTime;
+uniform float cElapsedTime;
+uniform float3 cFrustumSize;
+uniform float4 cGBufferOffsets;
+uniform float4 cLightPos;
+uniform float3 cLightDir;
+uniform float4 cNormalOffsetScale;
+uniform float4x3 cModel;
+uniform float4x3 cView;
+uniform float4x3 cViewInv;
+uniform float4x4 cViewProj;
+uniform float4 cUOffset;
+uniform float4 cVOffset;
+uniform float4x3 cZone;
+#ifdef SKINNED
+ uniform float4x3 cSkinMatrices[MAXBONES];
+#endif
+#ifdef NUMVERTEXLIGHTS
+ uniform float4 cVertexLights[4*3];
+#else
+ uniform float4x4 cLightMatrices[4];
+#endif
+#endif
+
+#ifdef COMPILEPS
+
+// Pixel shader uniforms
+uniform float4 cAmbientColor;
+uniform float3 cCameraPosPS;
+uniform float cDeltaTimePS;
+uniform float4 cDepthReconstruct;
+uniform float cElapsedTimePS;
+uniform float4 cFogParams;
+uniform float3 cFogColor;
+uniform float2 cGBufferInvSize;
+uniform float4 cLightColor;
+uniform float4 cLightPosPS;
+uniform float3 cLightDirPS;
+uniform float4 cNormalOffsetScalePS;
+uniform float4 cMatDiffColor;
+uniform float3 cMatEmissiveColor;
+uniform float3 cMatEnvMapColor;
+uniform float4 cMatSpecColor;
+#ifdef PBR
+ uniform float cRoughness;
+ uniform float cMetallic;
+ uniform float cLightRad;
+ uniform float cLightLength;
+#endif
+uniform float3 cZoneMin;
+uniform float3 cZoneMax;
+uniform float cNearClipPS;
+uniform float cFarClipPS;
+uniform float4 cShadowCubeAdjust;
+uniform float4 cShadowDepthFade;
+uniform float2 cShadowIntensity;
+uniform float2 cShadowMapInvSize;
+uniform float4 cShadowSplits;
+uniform float4x4 cLightMatricesPS[4];
+#ifdef VSM_SHADOW
+uniform float2 cVSMShadowParams;
+#endif
+#endif
+
+#else
+
+// D3D11 uniforms (using constant buffers)
+
+#ifdef COMPILEVS
+
+// Vertex shader uniforms
+cbuffer FrameVS : register(b0)
+{
+ float cDeltaTime;
+ float cElapsedTime;
+}
+
+cbuffer CameraVS : register(b1)
+{
+ float3 cCameraPos;
+ float cNearClip;
+ float cFarClip;
+ float4 cDepthMode;
+ float3 cFrustumSize;
+ float4 cGBufferOffsets;
+ float4x3 cView;
+ float4x3 cViewInv;
+ float4x4 cViewProj;
+ float4 cClipPlane;
+}
+
+cbuffer ZoneVS : register(b2)
+{
+ float3 cAmbientStartColor;
+ float3 cAmbientEndColor;
+ float4x3 cZone;
+}
+
+cbuffer LightVS : register(b3)
+{
+ float4 cLightPos;
+ float3 cLightDir;
+ float4 cNormalOffsetScale;
+#ifdef NUMVERTEXLIGHTS
+ float4 cVertexLights[4 * 3];
+#else
+ float4x4 cLightMatrices[4];
+#endif
+}
+
+#ifndef CUSTOM_MATERIAL_CBUFFER
+cbuffer MaterialVS : register(b4)
+{
+ float4 cUOffset;
+ float4 cVOffset;
+}
+#endif
+
+cbuffer ObjectVS : register(b5)
+{
+ float4x3 cModel;
+#ifdef BILLBOARD
+ float3x3 cBillboardRot;
+#endif
+#ifdef SKINNED
+ uniform float4x3 cSkinMatrices[MAXBONES];
+#endif
+}
+#endif
+
+#ifdef COMPILEPS
+
+// Pixel shader uniforms
+cbuffer FramePS : register(b0)
+{
+ float cDeltaTimePS;
+ float cElapsedTimePS;
+}
+
+cbuffer CameraPS : register(b1)
+{
+ float3 cCameraPosPS;
+ float4 cDepthReconstruct;
+ float2 cGBufferInvSize;
+ float cNearClipPS;
+ float cFarClipPS;
+}
+
+cbuffer ZonePS : register(b2)
+{
+ float4 cAmbientColor;
+ float4 cFogParams;
+ float3 cFogColor;
+ float3 cZoneMin;
+ float3 cZoneMax;
+}
+
+cbuffer LightPS : register(b3)
+{
+ float4 cLightColor;
+ float4 cLightPosPS;
+ float3 cLightDirPS;
+ float4 cNormalOffsetScalePS;
+ float4 cShadowCubeAdjust;
+ float4 cShadowDepthFade;
+ float2 cShadowIntensity;
+ float2 cShadowMapInvSize;
+ float4 cShadowSplits;
+ float2 cVSMShadowParams;
+ float4x4 cLightMatricesPS[4];
+ #ifdef PBR
+ float cLightRad;
+ float cLightLength;
+ #endif
+}
+
+#ifndef CUSTOM_MATERIAL_CBUFFER
+cbuffer MaterialPS : register(b4)
+{
+ float4 cMatDiffColor;
+ float3 cMatEmissiveColor;
+ float3 cMatEnvMapColor;
+ float4 cMatSpecColor;
+ #ifdef PBR
+ float cRoughness;
+ float cMetallic;
+ #endif
+}
+#endif
+
+#endif
+
+#endif
diff --git a/bin/CoreData/Shaders/HLSL/Unlit.hlsl b/bin/CoreData/Shaders/HLSL/Unlit.hlsl
new file mode 100644
index 0000000..4dec226
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Unlit.hlsl
@@ -0,0 +1,112 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "Fog.hlsl"
+
+void VS(float4 iPos : POSITION,
+ #ifndef NOUV
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ #if defined(BILLBOARD) || defined(DIRBILLBOARD)
+ float2 iSize : TEXCOORD1,
+ #endif
+ #if defined(DIRBILLBOARD) || defined(TRAILBONE)
+ float3 iNormal : NORMAL,
+ #endif
+ #if defined(TRAILFACECAM) || defined(TRAILBONE)
+ float4 iTangent : TANGENT,
+ #endif
+ out float2 oTexCoord : TEXCOORD0,
+ out float4 oWorldPos : TEXCOORD2,
+ #ifdef VERTEXCOLOR
+ out float4 oColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ out float oClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ // Define a 0,0 UV coord if not expected from the vertex data
+ #ifdef NOUV
+ float2 iTexCoord = float2(0.0, 0.0);
+ #endif
+
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oTexCoord = GetTexCoord(iTexCoord);
+ oWorldPos = float4(worldPos, GetDepth(oPos));
+
+ #if defined(D3D11) && defined(CLIPPLANE)
+ oClip = dot(oPos, cClipPlane);
+ #endif
+
+ #ifdef VERTEXCOLOR
+ oColor = iColor;
+ #endif
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+ float4 iWorldPos: TEXCOORD2,
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ float iClip : SV_CLIPDISTANCE0,
+ #endif
+ #ifdef PREPASS
+ out float4 oDepth : OUTCOLOR1,
+ #endif
+ #ifdef DEFERRED
+ out float4 oAlbedo : OUTCOLOR1,
+ out float4 oNormal : OUTCOLOR2,
+ out float4 oDepth : OUTCOLOR3,
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ // Get material diffuse albedo
+ #ifdef DIFFMAP
+ float4 diffColor = cMatDiffColor * Sample2D(DiffMap, iTexCoord);
+ #ifdef ALPHAMASK
+ if (diffColor.a < 0.5)
+ discard;
+ #endif
+ #else
+ float4 diffColor = cMatDiffColor;
+ #endif
+
+ #ifdef VERTEXCOLOR
+ diffColor *= iColor;
+ #endif
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(iWorldPos.w, iWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(iWorldPos.w);
+ #endif
+
+ #if defined(PREPASS)
+ // Fill light pre-pass G-Buffer
+ oColor = float4(0.5, 0.5, 0.5, 1.0);
+ oDepth = iWorldPos.w;
+ #elif defined(DEFERRED)
+ // Fill deferred G-buffer
+ oColor = float4(GetFog(diffColor.rgb, fogFactor), diffColor.a);
+ oAlbedo = float4(0.0, 0.0, 0.0, 0.0);
+ oNormal = float4(0.5, 0.5, 0.5, 1.0);
+ oDepth = iWorldPos.w;
+ #else
+ oColor = float4(GetFog(diffColor.rgb, fogFactor), diffColor.a);
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/UnlitParticle.hlsl b/bin/CoreData/Shaders/HLSL/UnlitParticle.hlsl
new file mode 100644
index 0000000..a5c75d7
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/UnlitParticle.hlsl
@@ -0,0 +1,145 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Fog.hlsl"
+
+#if defined(COMPILEPS) && defined(SOFTPARTICLES)
+#ifndef D3D11
+// D3D9 uniform
+uniform float cSoftParticleFadeScale;
+#else
+// D3D11 constant buffer
+cbuffer CustomPS : register(b6)
+{
+ float cSoftParticleFadeScale;
+}
+#endif
+#endif
+
+void VS(float4 iPos : POSITION,
+ #ifndef NOUV
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ #if defined(BILLBOARD) || defined(DIRBILLBOARD)
+ float2 iSize : TEXCOORD1,
+ #endif
+ #if defined(DIRBILLBOARD) || defined(TRAILBONE)
+ float3 iNormal : NORMAL,
+ #endif
+ #if defined(TRAILFACECAM) || defined(TRAILBONE)
+ float4 iTangent : TANGENT,
+ #endif
+ out float2 oTexCoord : TEXCOORD0,
+ #ifdef SOFTPARTICLES
+ out float4 oScreenPos : TEXCOORD1,
+ #endif
+ out float4 oWorldPos : TEXCOORD2,
+ #ifdef VERTEXCOLOR
+ out float4 oColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ out float oClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ // Define a 0,0 UV coord if not expected from the vertex data
+ #ifdef NOUV
+ float2 iTexCoord = float2(0.0, 0.0);
+ #endif
+
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+ oTexCoord = GetTexCoord(iTexCoord);
+ oWorldPos = float4(worldPos, GetDepth(oPos));
+
+ #if defined(D3D11) && defined(CLIPPLANE)
+ oClip = dot(oPos, cClipPlane);
+ #endif
+
+ #ifdef SOFTPARTICLES
+ oScreenPos = GetScreenPos(oPos);
+ #endif
+
+ #ifdef VERTEXCOLOR
+ oColor = iColor;
+ #endif
+}
+
+void PS(float2 iTexCoord : TEXCOORD0,
+ #ifdef SOFTPARTICLES
+ float4 iScreenPos: TEXCOORD1,
+ #endif
+ float4 iWorldPos: TEXCOORD2,
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ float iClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ // Get material diffuse albedo
+ #ifdef DIFFMAP
+ float4 diffColor = cMatDiffColor * Sample2D(DiffMap, iTexCoord);
+ #ifdef ALPHAMASK
+ if (diffColor.a < 0.5)
+ discard;
+ #endif
+ #else
+ float4 diffColor = cMatDiffColor;
+ #endif
+
+ #ifdef VERTEXCOLOR
+ diffColor *= iColor;
+ #endif
+
+ // Get fog factor
+ #ifdef HEIGHTFOG
+ float fogFactor = GetHeightFogFactor(iWorldPos.w, iWorldPos.y);
+ #else
+ float fogFactor = GetFogFactor(iWorldPos.w);
+ #endif
+
+ // Soft particle fade
+ // In expand mode depth test should be off. In that case do manual alpha discard test first to reduce fill rate
+ #ifdef SOFTPARTICLES
+ #if defined(EXPAND) && !defined(ADDITIVE)
+ if (diffColor.a < 0.01)
+ discard;
+ #endif
+
+ float particleDepth = iWorldPos.w;
+ float depth = Sample2DProj(DepthBuffer, iScreenPos).r;
+ #ifdef HWDEPTH
+ depth = ReconstructDepth(depth);
+ #endif
+
+ #ifdef EXPAND
+ float diffZ = max(particleDepth - depth, 0.0) * (cFarClipPS - cNearClipPS);
+ float fade = saturate(diffZ * cSoftParticleFadeScale);
+ #else
+ float diffZ = (depth - particleDepth) * (cFarClipPS - cNearClipPS);
+ float fade = saturate(1.0 - diffZ * cSoftParticleFadeScale);
+ #endif
+
+ #ifndef ADDITIVE
+ diffColor.a = max(diffColor.a - fade, 0.0);
+ #else
+ diffColor.rgb = max(diffColor.rgb - fade, float3(0.0, 0.0, 0.0));
+ #endif
+ #endif
+
+ oColor = float4(GetFog(diffColor.rgb, fogFactor), diffColor.a);
+}
diff --git a/bin/CoreData/Shaders/HLSL/Urho2D.hlsl b/bin/CoreData/Shaders/HLSL/Urho2D.hlsl
new file mode 100644
index 0000000..f7e9f29
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Urho2D.hlsl
@@ -0,0 +1,27 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+
+void VS(float4 iPos : POSITION,
+ float2 iTexCoord : TEXCOORD0,
+ float4 iColor : COLOR0,
+ out float4 oColor : COLOR0,
+ out float2 oTexCoord : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+
+ oColor = iColor;
+ oTexCoord = iTexCoord;
+}
+
+void PS(float4 iColor : COLOR0,
+ float2 iTexCoord : TEXCOORD0,
+ out float4 oColor : OUTCOLOR0)
+{
+ float4 diffColor = cMatDiffColor * iColor;
+ float4 diffInput = Sample2D(DiffMap, iTexCoord);
+ oColor = diffColor * diffInput;
+}
diff --git a/bin/CoreData/Shaders/HLSL/Vegetation.hlsl b/bin/CoreData/Shaders/HLSL/Vegetation.hlsl
new file mode 100644
index 0000000..f00bc04
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Vegetation.hlsl
@@ -0,0 +1,164 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Lighting.hlsl"
+#include "Fog.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms
+uniform float cWindHeightFactor;
+uniform float cWindHeightPivot;
+uniform float cWindPeriod;
+uniform float2 cWindWorldSpacing;
+
+#else
+
+// D3D11 constant buffer
+cbuffer CustomVS : register(b6)
+{
+ float cWindHeightFactor;
+ float cWindHeightPivot;
+ float cWindPeriod;
+ float2 cWindWorldSpacing;
+}
+
+#endif
+
+void VS(float4 iPos : POSITION,
+ #if !defined(BILLBOARD) && !defined(TRAILFACECAM)
+ float3 iNormal : NORMAL,
+ #endif
+ #ifndef NOUV
+ float2 iTexCoord : TEXCOORD0,
+ #endif
+ #ifdef VERTEXCOLOR
+ float4 iColor : COLOR0,
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ float2 iTexCoord2 : TEXCOORD1,
+ #endif
+ #if (defined(NORMALMAP) || defined(TRAILFACECAM) || defined(TRAILBONE)) && !defined(BILLBOARD) && !defined(DIRBILLBOARD)
+ float4 iTangent : TANGENT,
+ #endif
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ #if defined(BILLBOARD) || defined(DIRBILLBOARD)
+ float2 iSize : TEXCOORD1,
+ #endif
+ #ifndef NORMALMAP
+ out float2 oTexCoord : TEXCOORD0,
+ #else
+ out float4 oTexCoord : TEXCOORD0,
+ out float4 oTangent : TEXCOORD3,
+ #endif
+ out float3 oNormal : TEXCOORD1,
+ out float4 oWorldPos : TEXCOORD2,
+ #ifdef PERPIXEL
+ #ifdef SHADOW
+ out float4 oShadowPos[NUMCASCADES] : TEXCOORD4,
+ #endif
+ #ifdef SPOTLIGHT
+ out float4 oSpotPos : TEXCOORD5,
+ #endif
+ #ifdef POINTLIGHT
+ out float3 oCubeMaskVec : TEXCOORD5,
+ #endif
+ #else
+ out float3 oVertexLight : TEXCOORD4,
+ out float4 oScreenPos : TEXCOORD5,
+ #ifdef ENVCUBEMAP
+ out float3 oReflectionVec : TEXCOORD6,
+ #endif
+ #if defined(LIGHTMAP) || defined(AO)
+ out float2 oTexCoord2 : TEXCOORD7,
+ #endif
+ #endif
+ #ifdef VERTEXCOLOR
+ out float4 oColor : COLOR0,
+ #endif
+ #if defined(D3D11) && defined(CLIPPLANE)
+ out float oClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ // Define a 0,0 UV coord if not expected from the vertex data
+ #ifdef NOUV
+ float2 iTexCoord = float2(0.0, 0.0);
+ #endif
+
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+
+ float windStrength = max(iPos.y - cWindHeightPivot, 0.0) * cWindHeightFactor;
+ float windPeriod = cElapsedTime * cWindPeriod + dot(worldPos.xz, cWindWorldSpacing);
+ worldPos.x += windStrength * sin(windPeriod);
+ worldPos.z -= windStrength * cos(windPeriod);
+
+ oPos = GetClipPos(worldPos);
+ oNormal = GetWorldNormal(modelMatrix);
+ oWorldPos = float4(worldPos, GetDepth(oPos));
+
+ #if defined(D3D11) && defined(CLIPPLANE)
+ oClip = dot(oPos, cClipPlane);
+ #endif
+
+ #ifdef VERTEXCOLOR
+ oColor = iColor;
+ #endif
+
+ #ifdef NORMALMAP
+ float4 tangent = GetWorldTangent(modelMatrix);
+ float3 bitangent = cross(tangent.xyz, oNormal) * tangent.w;
+ oTexCoord = float4(GetTexCoord(iTexCoord), bitangent.xy);
+ oTangent = float4(tangent.xyz, bitangent.z);
+ #else
+ oTexCoord = GetTexCoord(iTexCoord);
+ #endif
+
+ #ifdef PERPIXEL
+ // Per-pixel forward lighting
+ float4 projWorldPos = float4(worldPos.xyz, 1.0);
+
+ #ifdef SHADOW
+ // Shadow projection: transform from world space to shadow space
+ GetShadowPos(projWorldPos, oNormal, oShadowPos);
+ #endif
+
+ #ifdef SPOTLIGHT
+ // Spotlight projection: transform from world space to projector texture coordinates
+ oSpotPos = mul(projWorldPos, cLightMatrices[0]);
+ #endif
+
+ #ifdef POINTLIGHT
+ oCubeMaskVec = mul(worldPos - cLightPos.xyz, (float3x3)cLightMatrices[0]);
+ #endif
+ #else
+ // Ambient & per-vertex lighting
+ #if defined(LIGHTMAP) || defined(AO)
+ // If using lightmap, disregard zone ambient light
+ // If using AO, calculate ambient in the PS
+ oVertexLight = float3(0.0, 0.0, 0.0);
+ oTexCoord2 = iTexCoord2;
+ #else
+ oVertexLight = GetAmbient(GetZonePos(worldPos));
+ #endif
+
+ #ifdef NUMVERTEXLIGHTS
+ for (int i = 0; i < NUMVERTEXLIGHTS; ++i)
+ oVertexLight += GetVertexLight(i, worldPos, oNormal) * cVertexLights[i * 3].rgb;
+ #endif
+
+ oScreenPos = GetScreenPos(oPos);
+
+ #ifdef ENVCUBEMAP
+ oReflectionVec = worldPos - cCameraPos;
+ #endif
+ #endif
+}
diff --git a/bin/CoreData/Shaders/HLSL/VegetationDepth.hlsl b/bin/CoreData/Shaders/HLSL/VegetationDepth.hlsl
new file mode 100644
index 0000000..aa6c05d
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/VegetationDepth.hlsl
@@ -0,0 +1,48 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms
+uniform float cWindHeightFactor;
+uniform float cWindHeightPivot;
+uniform float cWindPeriod;
+uniform float2 cWindWorldSpacing;
+
+#else
+
+// D3D11 constant buffer
+cbuffer CustomVS : register(b6)
+{
+ float cWindHeightFactor;
+ float cWindHeightPivot;
+ float cWindPeriod;
+ float2 cWindWorldSpacing;
+}
+
+#endif
+
+void VS(float4 iPos : POSITION,
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ float2 iTexCoord : TEXCOORD0,
+ out float3 oTexCoord : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+
+ float windStrength = max(iPos.y - cWindHeightPivot, 0.0) * cWindHeightFactor;
+ float windPeriod = cElapsedTime * cWindPeriod + dot(worldPos.xz, cWindWorldSpacing);
+ worldPos.x += windStrength * sin(windPeriod);
+ worldPos.z -= windStrength * cos(windPeriod);
+
+ oPos = GetClipPos(worldPos);
+ oTexCoord = float3(GetTexCoord(iTexCoord), GetDepth(oPos));
+}
diff --git a/bin/CoreData/Shaders/HLSL/VegetationShadow.hlsl b/bin/CoreData/Shaders/HLSL/VegetationShadow.hlsl
new file mode 100644
index 0000000..ddcb780
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/VegetationShadow.hlsl
@@ -0,0 +1,48 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms
+uniform float cWindHeightFactor;
+uniform float cWindHeightPivot;
+uniform float cWindPeriod;
+uniform float2 cWindWorldSpacing;
+
+#else
+
+// D3D11 constant buffer
+cbuffer CustomVS : register(b6)
+{
+ float cWindHeightFactor;
+ float cWindHeightPivot;
+ float cWindPeriod;
+ float2 cWindWorldSpacing;
+}
+
+#endif
+
+void VS(float4 iPos : POSITION,
+ #ifdef SKINNED
+ float4 iBlendWeights : BLENDWEIGHT,
+ int4 iBlendIndices : BLENDINDICES,
+ #endif
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ float2 iTexCoord : TEXCOORD0,
+ out float2 oTexCoord : TEXCOORD0,
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+
+ float windStrength = max(iPos.y - cWindHeightPivot, 0.0) * cWindHeightFactor;
+ float windPeriod = cElapsedTime * cWindPeriod + dot(worldPos.xz, cWindWorldSpacing);
+ worldPos.x += windStrength * sin(windPeriod);
+ worldPos.z -= windStrength * cos(windPeriod);
+
+ oPos = GetClipPos(worldPos);
+ oTexCoord = GetTexCoord(iTexCoord);
+}
diff --git a/bin/CoreData/Shaders/HLSL/Water.hlsl b/bin/CoreData/Shaders/HLSL/Water.hlsl
new file mode 100644
index 0000000..043ee99
--- /dev/null
+++ b/bin/CoreData/Shaders/HLSL/Water.hlsl
@@ -0,0 +1,96 @@
+#include "Uniforms.hlsl"
+#include "Samplers.hlsl"
+#include "Transform.hlsl"
+#include "ScreenPos.hlsl"
+#include "Fog.hlsl"
+
+#ifndef D3D11
+
+// D3D9 uniforms
+uniform float2 cNoiseSpeed;
+uniform float cNoiseTiling;
+uniform float cNoiseStrength;
+uniform float cFresnelPower;
+uniform float3 cWaterTint;
+
+#else
+
+// D3D11 constant buffers
+#ifdef COMPILEVS
+cbuffer CustomVS : register(b6)
+{
+ float2 cNoiseSpeed;
+ float cNoiseTiling;
+}
+#else
+cbuffer CustomPS : register(b6)
+{
+ float cNoiseStrength;
+ float cFresnelPower;
+ float3 cWaterTint;
+}
+#endif
+
+#endif
+
+void VS(float4 iPos : POSITION,
+ float3 iNormal: NORMAL,
+ float2 iTexCoord : TEXCOORD0,
+ #ifdef INSTANCED
+ float4x3 iModelInstance : TEXCOORD4,
+ #endif
+ out float4 oScreenPos : TEXCOORD0,
+ out float2 oReflectUV : TEXCOORD1,
+ out float2 oWaterUV : TEXCOORD2,
+ out float3 oNormal : TEXCOORD3,
+ out float4 oEyeVec : TEXCOORD4,
+ #if defined(D3D11) && defined(CLIPPLANE)
+ out float oClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oPos : OUTPOSITION)
+{
+ float4x3 modelMatrix = iModelMatrix;
+ float3 worldPos = GetWorldPos(modelMatrix);
+ oPos = GetClipPos(worldPos);
+
+ oScreenPos = GetScreenPos(oPos);
+ // GetQuadTexCoord() returns a float2 that is OK for quad rendering; multiply it with output W
+ // coordinate to make it work with arbitrary meshes such as the water plane (perform divide in pixel shader)
+ oReflectUV = GetQuadTexCoord(oPos) * oPos.w;
+ oWaterUV = iTexCoord * cNoiseTiling + cElapsedTime * cNoiseSpeed;
+ oNormal = GetWorldNormal(modelMatrix);
+ oEyeVec = float4(cCameraPos - worldPos, GetDepth(oPos));
+
+ #if defined(D3D11) && defined(CLIPPLANE)
+ oClip = dot(oPos, cClipPlane);
+ #endif
+}
+
+void PS(
+ float4 iScreenPos : TEXCOORD0,
+ float2 iReflectUV : TEXCOORD1,
+ float2 iWaterUV : TEXCOORD2,
+ float3 iNormal : TEXCOORD3,
+ float4 iEyeVec : TEXCOORD4,
+ #if defined(D3D11) && defined(CLIPPLANE)
+ float iClip : SV_CLIPDISTANCE0,
+ #endif
+ out float4 oColor : OUTCOLOR0)
+{
+ float2 refractUV = iScreenPos.xy / iScreenPos.w;
+ float2 reflectUV = iReflectUV.xy / iScreenPos.w;
+
+ float2 noise = (Sample2D(NormalMap, iWaterUV).rg - 0.5) * cNoiseStrength;
+ refractUV += noise;
+ // Do not shift reflect UV coordinate upward, because it will reveal the clipping of geometry below water
+ if (noise.y < 0.0)
+ noise.y = 0.0;
+ reflectUV += noise;
+
+ float fresnel = pow(1.0 - saturate(dot(normalize(iEyeVec.xyz), iNormal)), cFresnelPower);
+ float3 refractColor = Sample2D(EnvMap, refractUV).rgb * cWaterTint;
+ float3 reflectColor = Sample2D(DiffMap, reflectUV).rgb;
+ float3 finalColor = lerp(refractColor, reflectColor, fresnel);
+
+ oColor = float4(GetFog(finalColor, GetFogFactor(iEyeVec.w)), 1.0);
+}
\ No newline at end of file
diff --git a/bin/CoreData/Techniques/BasicVColUnlitAlpha.xml b/bin/CoreData/Techniques/BasicVColUnlitAlpha.xml
new file mode 100644
index 0000000..73225c4
--- /dev/null
+++ b/bin/CoreData/Techniques/BasicVColUnlitAlpha.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/Diff.xml b/bin/CoreData/Techniques/Diff.xml
new file mode 100644
index 0000000..3ece401
--- /dev/null
+++ b/bin/CoreData/Techniques/Diff.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffAO.xml b/bin/CoreData/Techniques/DiffAO.xml
new file mode 100644
index 0000000..68ab46d
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffAO.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffAOAlpha.xml b/bin/CoreData/Techniques/DiffAOAlpha.xml
new file mode 100644
index 0000000..d788e08
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffAOAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffAdd.xml b/bin/CoreData/Techniques/DiffAdd.xml
new file mode 100644
index 0000000..65834f8
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffAdd.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffAddAlpha.xml b/bin/CoreData/Techniques/DiffAddAlpha.xml
new file mode 100644
index 0000000..bb6e015
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffAddAlpha.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffAlpha.xml b/bin/CoreData/Techniques/DiffAlpha.xml
new file mode 100644
index 0000000..785baa6
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffAlphaTranslucent.xml b/bin/CoreData/Techniques/DiffAlphaTranslucent.xml
new file mode 100644
index 0000000..1766552
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffAlphaTranslucent.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffEmissive.xml b/bin/CoreData/Techniques/DiffEmissive.xml
new file mode 100644
index 0000000..e21442d
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffEmissive.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffEmissiveAlpha.xml b/bin/CoreData/Techniques/DiffEmissiveAlpha.xml
new file mode 100644
index 0000000..2af2fd1
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffEmissiveAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffEnvCube.xml b/bin/CoreData/Techniques/DiffEnvCube.xml
new file mode 100644
index 0000000..e5dfc24
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffEnvCube.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffEnvCubeAO.xml b/bin/CoreData/Techniques/DiffEnvCubeAO.xml
new file mode 100644
index 0000000..b35a001
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffEnvCubeAO.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffEnvCubeAOAlpha.xml b/bin/CoreData/Techniques/DiffEnvCubeAOAlpha.xml
new file mode 100644
index 0000000..d842093
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffEnvCubeAOAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffEnvCubeAlpha.xml b/bin/CoreData/Techniques/DiffEnvCubeAlpha.xml
new file mode 100644
index 0000000..8859572
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffEnvCubeAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffLightMap.xml b/bin/CoreData/Techniques/DiffLightMap.xml
new file mode 100644
index 0000000..7fdc258
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffLightMap.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffLightMapAlpha.xml b/bin/CoreData/Techniques/DiffLightMapAlpha.xml
new file mode 100644
index 0000000..4b07848
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffLightMapAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffLitParticleAlpha.xml b/bin/CoreData/Techniques/DiffLitParticleAlpha.xml
new file mode 100644
index 0000000..527306d
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffLitParticleAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffLitParticleAlphaSoft.xml b/bin/CoreData/Techniques/DiffLitParticleAlphaSoft.xml
new file mode 100644
index 0000000..54c6e63
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffLitParticleAlphaSoft.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffLitParticleAlphaSoftExpand.xml b/bin/CoreData/Techniques/DiffLitParticleAlphaSoftExpand.xml
new file mode 100644
index 0000000..c58f1df
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffLitParticleAlphaSoftExpand.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffMultiply.xml b/bin/CoreData/Techniques/DiffMultiply.xml
new file mode 100644
index 0000000..89f7ef0
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffMultiply.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormal.xml b/bin/CoreData/Techniques/DiffNormal.xml
new file mode 100644
index 0000000..ea8e870
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormal.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalAO.xml b/bin/CoreData/Techniques/DiffNormalAO.xml
new file mode 100644
index 0000000..cb5cca6
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalAO.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalAOAlpha.xml b/bin/CoreData/Techniques/DiffNormalAOAlpha.xml
new file mode 100644
index 0000000..f857426
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalAOAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalAlpha.xml b/bin/CoreData/Techniques/DiffNormalAlpha.xml
new file mode 100644
index 0000000..041cd7b
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalAlphaTranslucent.xml b/bin/CoreData/Techniques/DiffNormalAlphaTranslucent.xml
new file mode 100644
index 0000000..d0e49a2
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalAlphaTranslucent.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalEmissive.xml b/bin/CoreData/Techniques/DiffNormalEmissive.xml
new file mode 100644
index 0000000..a573eb3
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalEmissive.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalEmissiveAlpha.xml b/bin/CoreData/Techniques/DiffNormalEmissiveAlpha.xml
new file mode 100644
index 0000000..b83121a
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalEmissiveAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalEnvCube.xml b/bin/CoreData/Techniques/DiffNormalEnvCube.xml
new file mode 100644
index 0000000..391867d
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalEnvCube.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalEnvCubeAlpha.xml b/bin/CoreData/Techniques/DiffNormalEnvCubeAlpha.xml
new file mode 100644
index 0000000..13b6b71
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalEnvCubeAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalSpec.xml b/bin/CoreData/Techniques/DiffNormalSpec.xml
new file mode 100644
index 0000000..a706300
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalSpec.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalSpecAO.xml b/bin/CoreData/Techniques/DiffNormalSpecAO.xml
new file mode 100644
index 0000000..96ec0d1
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalSpecAO.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalSpecAOAlpha.xml b/bin/CoreData/Techniques/DiffNormalSpecAOAlpha.xml
new file mode 100644
index 0000000..bdab67a
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalSpecAOAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalSpecAlpha.xml b/bin/CoreData/Techniques/DiffNormalSpecAlpha.xml
new file mode 100644
index 0000000..2df9c75
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalSpecAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalSpecEmissive.xml b/bin/CoreData/Techniques/DiffNormalSpecEmissive.xml
new file mode 100644
index 0000000..3d9418d
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalSpecEmissive.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffNormalSpecEmissiveAlpha.xml b/bin/CoreData/Techniques/DiffNormalSpecEmissiveAlpha.xml
new file mode 100644
index 0000000..8b3c451
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffNormalSpecEmissiveAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffOverlay.xml b/bin/CoreData/Techniques/DiffOverlay.xml
new file mode 100644
index 0000000..4dee05e
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffOverlay.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffSkybox.xml b/bin/CoreData/Techniques/DiffSkybox.xml
new file mode 100644
index 0000000..27502a9
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffSkybox.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/bin/CoreData/Techniques/DiffSkyboxHDRScale.xml b/bin/CoreData/Techniques/DiffSkyboxHDRScale.xml
new file mode 100644
index 0000000..b9b057c
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffSkyboxHDRScale.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffSkydome.xml b/bin/CoreData/Techniques/DiffSkydome.xml
new file mode 100644
index 0000000..d7b8814
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffSkydome.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffSkyplane.xml b/bin/CoreData/Techniques/DiffSkyplane.xml
new file mode 100644
index 0000000..7d72b15
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffSkyplane.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/bin/CoreData/Techniques/DiffSpec.xml b/bin/CoreData/Techniques/DiffSpec.xml
new file mode 100644
index 0000000..b7ec197
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffSpec.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffSpecAlpha.xml b/bin/CoreData/Techniques/DiffSpecAlpha.xml
new file mode 100644
index 0000000..5384ffa
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffSpecAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffUnlit.xml b/bin/CoreData/Techniques/DiffUnlit.xml
new file mode 100644
index 0000000..543ccc0
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffUnlit.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffUnlitAlpha.xml b/bin/CoreData/Techniques/DiffUnlitAlpha.xml
new file mode 100644
index 0000000..ac4af19
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffUnlitAlpha.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffUnlitParticleAdd.xml b/bin/CoreData/Techniques/DiffUnlitParticleAdd.xml
new file mode 100644
index 0000000..b79c291
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffUnlitParticleAdd.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffUnlitParticleAddSoft.xml b/bin/CoreData/Techniques/DiffUnlitParticleAddSoft.xml
new file mode 100644
index 0000000..f6c85a8
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffUnlitParticleAddSoft.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffUnlitParticleAddSoftExpand.xml b/bin/CoreData/Techniques/DiffUnlitParticleAddSoftExpand.xml
new file mode 100644
index 0000000..43a4f0b
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffUnlitParticleAddSoftExpand.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffUnlitParticleAlpha.xml b/bin/CoreData/Techniques/DiffUnlitParticleAlpha.xml
new file mode 100644
index 0000000..2780261
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffUnlitParticleAlpha.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffUnlitParticleAlphaSoft.xml b/bin/CoreData/Techniques/DiffUnlitParticleAlphaSoft.xml
new file mode 100644
index 0000000..21e04f5
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffUnlitParticleAlphaSoft.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffUnlitParticleAlphaSoftExpand.xml b/bin/CoreData/Techniques/DiffUnlitParticleAlphaSoftExpand.xml
new file mode 100644
index 0000000..2d26ee4
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffUnlitParticleAlphaSoftExpand.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffVCol.xml b/bin/CoreData/Techniques/DiffVCol.xml
new file mode 100644
index 0000000..9f2cd33
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffVCol.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/DiffVColAdd.xml b/bin/CoreData/Techniques/DiffVColAdd.xml
new file mode 100644
index 0000000..a9d5a37
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffVColAdd.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffVColAddAlpha.xml b/bin/CoreData/Techniques/DiffVColAddAlpha.xml
new file mode 100644
index 0000000..1c21358
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffVColAddAlpha.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffVColMultiply.xml b/bin/CoreData/Techniques/DiffVColMultiply.xml
new file mode 100644
index 0000000..561fe51
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffVColMultiply.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/DiffVColUnlitAlpha.xml b/bin/CoreData/Techniques/DiffVColUnlitAlpha.xml
new file mode 100644
index 0000000..334939d
--- /dev/null
+++ b/bin/CoreData/Techniques/DiffVColUnlitAlpha.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/NoTexture.xml b/bin/CoreData/Techniques/NoTexture.xml
new file mode 100644
index 0000000..92d3080
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTexture.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureAO.xml b/bin/CoreData/Techniques/NoTextureAO.xml
new file mode 100644
index 0000000..cfb9387
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureAO.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureAOAlpha.xml b/bin/CoreData/Techniques/NoTextureAOAlpha.xml
new file mode 100644
index 0000000..e2146e0
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureAOAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureAdd.xml b/bin/CoreData/Techniques/NoTextureAdd.xml
new file mode 100644
index 0000000..631179a
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureAdd.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureAddAlpha.xml b/bin/CoreData/Techniques/NoTextureAddAlpha.xml
new file mode 100644
index 0000000..b7c9048
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureAddAlpha.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureAlpha.xml b/bin/CoreData/Techniques/NoTextureAlpha.xml
new file mode 100644
index 0000000..d8e0dc9
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureEnvCube.xml b/bin/CoreData/Techniques/NoTextureEnvCube.xml
new file mode 100644
index 0000000..5cabb1e
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureEnvCube.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureEnvCubeAO.xml b/bin/CoreData/Techniques/NoTextureEnvCubeAO.xml
new file mode 100644
index 0000000..b3081be
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureEnvCubeAO.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureEnvCubeAOAlpha.xml b/bin/CoreData/Techniques/NoTextureEnvCubeAOAlpha.xml
new file mode 100644
index 0000000..246bd16
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureEnvCubeAOAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureEnvCubeAlpha.xml b/bin/CoreData/Techniques/NoTextureEnvCubeAlpha.xml
new file mode 100644
index 0000000..3aac524
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureEnvCubeAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureMultiply.xml b/bin/CoreData/Techniques/NoTextureMultiply.xml
new file mode 100644
index 0000000..3ef42b7
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureMultiply.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureNormal.xml b/bin/CoreData/Techniques/NoTextureNormal.xml
new file mode 100644
index 0000000..cb30ced
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureNormal.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureNormalAlpha.xml b/bin/CoreData/Techniques/NoTextureNormalAlpha.xml
new file mode 100644
index 0000000..c3bfddf
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureNormalAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureOverlay.xml b/bin/CoreData/Techniques/NoTextureOverlay.xml
new file mode 100644
index 0000000..4ef6e87
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureOverlay.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureUnlit.xml b/bin/CoreData/Techniques/NoTextureUnlit.xml
new file mode 100644
index 0000000..357a42f
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureUnlit.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureUnlitAlpha.xml b/bin/CoreData/Techniques/NoTextureUnlitAlpha.xml
new file mode 100644
index 0000000..936e3da
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureUnlitAlpha.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureUnlitVCol.xml b/bin/CoreData/Techniques/NoTextureUnlitVCol.xml
new file mode 100644
index 0000000..f8a45e4
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureUnlitVCol.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureVCol.xml b/bin/CoreData/Techniques/NoTextureVCol.xml
new file mode 100644
index 0000000..ec64e18
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureVCol.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureVColAdd.xml b/bin/CoreData/Techniques/NoTextureVColAdd.xml
new file mode 100644
index 0000000..ee3eee6
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureVColAdd.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureVColAddAlpha.xml b/bin/CoreData/Techniques/NoTextureVColAddAlpha.xml
new file mode 100644
index 0000000..b31970d
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureVColAddAlpha.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/NoTextureVColMultiply.xml b/bin/CoreData/Techniques/NoTextureVColMultiply.xml
new file mode 100644
index 0000000..110f9b4
--- /dev/null
+++ b/bin/CoreData/Techniques/NoTextureVColMultiply.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/DiffNormalSpecEmissive.xml b/bin/CoreData/Techniques/PBR/DiffNormalSpecEmissive.xml
new file mode 100644
index 0000000..74caf28
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/DiffNormalSpecEmissive.xml
@@ -0,0 +1,8 @@
+
+
+ >
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/DiffNormalSpecEmissiveAlpha.xml b/bin/CoreData/Techniques/PBR/DiffNormalSpecEmissiveAlpha.xml
new file mode 100644
index 0000000..1db7945
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/DiffNormalSpecEmissiveAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRDiff.xml b/bin/CoreData/Techniques/PBR/PBRDiff.xml
new file mode 100644
index 0000000..39443cb
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRDiff.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRDiffAlpha.xml b/bin/CoreData/Techniques/PBR/PBRDiffAlpha.xml
new file mode 100644
index 0000000..603ebc1
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRDiffAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRDiffNormal.xml b/bin/CoreData/Techniques/PBR/PBRDiffNormal.xml
new file mode 100644
index 0000000..0d88cfd
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRDiffNormal.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRDiffNormalAlpha.xml b/bin/CoreData/Techniques/PBR/PBRDiffNormalAlpha.xml
new file mode 100644
index 0000000..03f2d33
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRDiffNormalAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRDiffNormalEmissive.xml b/bin/CoreData/Techniques/PBR/PBRDiffNormalEmissive.xml
new file mode 100644
index 0000000..69e2176
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRDiffNormalEmissive.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRDiffNormalEmissiveAlpha.xml b/bin/CoreData/Techniques/PBR/PBRDiffNormalEmissiveAlpha.xml
new file mode 100644
index 0000000..c57fff3
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRDiffNormalEmissiveAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpec.xml b/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpec.xml
new file mode 100644
index 0000000..3c083dd
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpec.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpecEmissive.xml b/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpecEmissive.xml
new file mode 100644
index 0000000..3f9873e
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpecEmissive.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpecEmissiveAlpha.xml b/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpecEmissiveAlpha.xml
new file mode 100644
index 0000000..add01b6
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpecEmissiveAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffSpec.xml b/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffSpec.xml
new file mode 100644
index 0000000..d91c191
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffSpec.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffSpecAlpha.xml b/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffSpecAlpha.xml
new file mode 100644
index 0000000..08fd948
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRMetallicRoughDiffSpecAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRNoTexture.xml b/bin/CoreData/Techniques/PBR/PBRNoTexture.xml
new file mode 100644
index 0000000..a650a00
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRNoTexture.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/PBR/PBRNoTextureAlpha.xml b/bin/CoreData/Techniques/PBR/PBRNoTextureAlpha.xml
new file mode 100644
index 0000000..c721e99
--- /dev/null
+++ b/bin/CoreData/Techniques/PBR/PBRNoTextureAlpha.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/TerrainBlend.xml b/bin/CoreData/Techniques/TerrainBlend.xml
new file mode 100644
index 0000000..74aeee3
--- /dev/null
+++ b/bin/CoreData/Techniques/TerrainBlend.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/VegetationDiff.xml b/bin/CoreData/Techniques/VegetationDiff.xml
new file mode 100644
index 0000000..de94ff6
--- /dev/null
+++ b/bin/CoreData/Techniques/VegetationDiff.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/VegetationDiffUnlit.xml b/bin/CoreData/Techniques/VegetationDiffUnlit.xml
new file mode 100644
index 0000000..3a6c00d
--- /dev/null
+++ b/bin/CoreData/Techniques/VegetationDiffUnlit.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Techniques/Water.xml b/bin/CoreData/Techniques/Water.xml
new file mode 100644
index 0000000..6901928
--- /dev/null
+++ b/bin/CoreData/Techniques/Water.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/bin/CoreData/Textures/LUTIdentity.png b/bin/CoreData/Textures/LUTIdentity.png
new file mode 100644
index 0000000..6028567
Binary files /dev/null and b/bin/CoreData/Textures/LUTIdentity.png differ
diff --git a/bin/CoreData/Textures/LUTIdentity.xml b/bin/CoreData/Textures/LUTIdentity.xml
new file mode 100644
index 0000000..038eb75
--- /dev/null
+++ b/bin/CoreData/Textures/LUTIdentity.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/CoreData/Textures/Ramp.png b/bin/CoreData/Textures/Ramp.png
new file mode 100644
index 0000000..d49a963
Binary files /dev/null and b/bin/CoreData/Textures/Ramp.png differ
diff --git a/bin/CoreData/Textures/Ramp.xml b/bin/CoreData/Textures/Ramp.xml
new file mode 100644
index 0000000..774bd91
--- /dev/null
+++ b/bin/CoreData/Textures/Ramp.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Textures/RampExtreme.png b/bin/CoreData/Textures/RampExtreme.png
new file mode 100644
index 0000000..d4f07b4
Binary files /dev/null and b/bin/CoreData/Textures/RampExtreme.png differ
diff --git a/bin/CoreData/Textures/RampExtreme.xml b/bin/CoreData/Textures/RampExtreme.xml
new file mode 100644
index 0000000..4ff08db
--- /dev/null
+++ b/bin/CoreData/Textures/RampExtreme.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/CoreData/Textures/RampWide.png b/bin/CoreData/Textures/RampWide.png
new file mode 100644
index 0000000..b94d99a
Binary files /dev/null and b/bin/CoreData/Textures/RampWide.png differ
diff --git a/bin/CoreData/Textures/RampWide.xml b/bin/CoreData/Textures/RampWide.xml
new file mode 100644
index 0000000..4ff08db
--- /dev/null
+++ b/bin/CoreData/Textures/RampWide.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/CoreData/Textures/Spot.png b/bin/CoreData/Textures/Spot.png
new file mode 100644
index 0000000..349dc16
Binary files /dev/null and b/bin/CoreData/Textures/Spot.png differ
diff --git a/bin/CoreData/Textures/Spot.xml b/bin/CoreData/Textures/Spot.xml
new file mode 100644
index 0000000..30cf7ad
--- /dev/null
+++ b/bin/CoreData/Textures/Spot.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/bin/CoreData/Textures/SpotWide.png b/bin/CoreData/Textures/SpotWide.png
new file mode 100644
index 0000000..a495cbc
Binary files /dev/null and b/bin/CoreData/Textures/SpotWide.png differ
diff --git a/bin/CoreData/Textures/SpotWide.xml b/bin/CoreData/Textures/SpotWide.xml
new file mode 100644
index 0000000..3d93178
--- /dev/null
+++ b/bin/CoreData/Textures/SpotWide.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/CommandLine.txt b/bin/Data/CommandLine.txt
new file mode 100644
index 0000000..8713170
--- /dev/null
+++ b/bin/Data/CommandLine.txt
@@ -0,0 +1 @@
+Scripts/NinjaSnowWar.as
\ No newline at end of file
diff --git a/bin/Data/EditorStrings.json b/bin/Data/EditorStrings.json
new file mode 100644
index 0000000..1398781
--- /dev/null
+++ b/bin/Data/EditorStrings.json
@@ -0,0 +1,1430 @@
+{
+ "Language":{
+ "en":"Language",
+ "ru":"Язык",
+ "fr":"Langage",
+ "it":"Lingua"
+ },
+ "Open scene...":{
+ "en":"Open scene...",
+ "ru":"Открыть Ñцену...",
+ "fr":"Ouvrir une scène...",
+ "it":"Apri scena..."
+ },
+ "Save scene":{
+ "en":"Save scene",
+ "ru":"Сохранить Ñцену",
+ "fr":"Sauvegarder la scène",
+ "it":"Salva scena"
+ },
+ "Save scene as...":{
+ "en":"Save scene as...",
+ "ru":"Сохранить Ñцену как...",
+ "fr":"Sauvegarder la scène comme...",
+ "it":"Salva scena come..."
+ },
+ "Open recent scene":{
+ "en":"Open recent scene",
+ "ru":"Открыть прежнюю Ñцену",
+ "fr":"Ouvrir la scène récente",
+ "it":"Apri scena recente"
+ },
+ "menu Load node":{
+ "en":"Load node",
+ "ru":"Загрузить ноду",
+ "fr":"Charger la node",
+ "it":"Carica nodo"
+ },
+ "As replicated...":{
+ "en":"As replicated...",
+ "ru":"Реплицируемую...",
+ "fr":"Duplicata...",
+ "it":"Come duplicato..."
+ },
+ "As local...":{
+ "en":"As local...",
+ "ru":"Локальную...",
+ "fr":"Comme local...",
+ "it":"Come locale..."
+ },
+ "Save node as...":{
+ "en":"Save node as...",
+ "ru":"Сохранить ноду как...",
+ "fr":"Sauvegarder la node comme...",
+ "it":"Salva nodo come..."
+ },
+ "Import model...":{
+ "en":"Import model...",
+ "ru":"Импортировать модель...",
+ "fr":"Importer un modèle...",
+ "it":"Importa modello..."
+ },
+ "Import scene...":{
+ "en":"Import scene...",
+ "ru":"Импортировать Ñцену...",
+ "fr":"Importer une scène...",
+ "it":"Importa scena..."
+ },
+ "Run script...":{
+ "en":"Run script...",
+ "ru":"ЗапуÑтить Ñкрипт...",
+ "fr":"Exécuter un script...",
+ "it":"Esegui script..."
+ },
+ "Set resource path...":{
+ "en":"Set resource path...",
+ "ru":"Указать путь к реÑурÑам...",
+ "fr":"Fixer le chemin des ressources...",
+ "it":"Imposta percorso risorse..."
+ },
+ "Exit":{
+ "en":"Exit",
+ "ru":"Выход",
+ "fr":"Quitter",
+ "it":"Esci"
+ },
+ "Edit":{
+ "en":"Edit",
+ "ru":"Редактирование",
+ "fr":"Éditer",
+ "it":"Modifica"
+ },
+ "Undo":{
+ "en":"Undo",
+ "ru":"Отменить",
+ "fr":"Annuler",
+ "it":"Annulla"
+ },
+ "Redo":{
+ "en":"Redo",
+ "ru":"Повторить",
+ "fr":"Refaire",
+ "it":"Rifare"
+ },
+ "Cut":{
+ "en":"Cut",
+ "ru":"Вырезать",
+ "fr":"Couper",
+ "it":"Taglia"
+ },
+ "Duplicate":{
+ "en":"Duplicate",
+ "ru":"Дублировать",
+ "fr":"Dupliquer",
+ "it":"Duplica"
+ },
+ "Copy":{
+ "en":"Copy",
+ "ru":"Копировать",
+ "fr":"Copier",
+ "it":"Copia"
+ },
+ "Paste":{
+ "en":"Paste",
+ "ru":"Ð’Ñтавить",
+ "fr":"Coller",
+ "it":"Incolla"
+ },
+ "Delete":{
+ "en":"Delete",
+ "ru":"Удалить",
+ "fr":"Effacer",
+ "it":"Elimina"
+ },
+ "Select all":{
+ "en":"Select all",
+ "ru":"Выделить вÑÑ‘",
+ "fr":"Tout sélectionner",
+ "it":"Seleziona tutto"
+ },
+ "Deselect all":{
+ "en":"Deselect all",
+ "ru":"СнÑÑ‚ÑŒ вÑе выделениÑ",
+ "fr":"Tout désélectionner",
+ "it":"Deseleziona tutto"
+ },
+ "Reset to default":{
+ "en":"Reset to default",
+ "ru":"СброÑить",
+ "fr":"Ré-initiliser par défaut",
+ "it":"Reimposta condizioni iniziali"
+ },
+ "Reset position":{
+ "en":"Reset position",
+ "ru":"СброÑить положение",
+ "fr":"Ré-initiliser la position",
+ "it":"Reimposta posizione"
+ },
+ "Reset rotation":{
+ "en":"Reset rotation",
+ "ru":"СброÑить поворот",
+ "fr":"Ré-initiliser la rotation",
+ "it":"Reimposta rotazione"
+ },
+ "Reset scale":{
+ "en":"Reset scale",
+ "ru":"СброÑить маÑштаб",
+ "fr":"Ré-initialiser l'échelle",
+ "it":"Reimposta dimensione"
+ },
+ "Enable/disable":{
+ "en":"Enable/disable",
+ "ru":"Включить/Отключить",
+ "fr":"Activer/Désactiver",
+ "it":"Attiva/Disattiva"
+ },
+ "Unparent":{
+ "en":"Unparent",
+ "ru":"Разорвать родительÑкую ÑвÑзь",
+ "fr":"Dé-parenter",
+ "it":"Rimuovi parentela"
+ },
+ "Toggle update":{
+ "en":"Toggle update",
+ "ru":"Вкл/откл обновление",
+ "fr":"Basculer la mise à jour",
+ "it":"On/off aggiornamento"
+ },
+ "Stop test animation":{
+ "en":"Stop test animation",
+ "ru":"ОÑтановить теÑтовую анимацию",
+ "fr":"Arrêt du test d'animation",
+ "it":"Ferma test animazione"
+ },
+ "Rebuild navigation data":{
+ "en":"Rebuild navigation data",
+ "ru":"РеконÑтруировать навигационные данные",
+ "fr":"Recontruire les données de navigation",
+ "it":"Ricostruisci dati navigazione"
+ },
+ "Add children to SM-group":{
+ "en":"Add children to SM-group",
+ "ru":"Добавить потомка к StaticModelGroup",
+ "fr":"Ajouter l'enfant au groupe de modèle statique",
+ "it":"Aggiungi figlio al gruppo di modelli statici"
+ },
+ "Set children as spline path":{
+ "en":"Set children as spline path",
+ "ru":"УÑтановить потомка как Ñплайновый путь",
+ "fr":"Fixer l'enfant comme un chemin spline",
+ "it":"Imposta figlio come percorso spline"
+ },
+ "Non-cyclic":{
+ "en":"Non-cyclic",
+ "ru":"Ðецикличный",
+ "fr":"Non-cyclique",
+ "it":"Non ciclico"
+ },
+ "Cyclic":{
+ "en":"Cyclic",
+ "ru":"Цикличный",
+ "fr":"Cyclique",
+ "it":"Ciclico"
+ },
+ "Create":{
+ "en":"Create",
+ "ru":"Создать",
+ "fr":"Créer",
+ "it":"Crea"
+ },
+ "Replicated node":{
+ "en":"Replicated node",
+ "ru":"Ð ÐµÐ¿Ð»Ð¸Ñ†Ð¸Ñ€ÑƒÐµÐ¼Ð°Ñ Ð½Ð¾Ð´Ð°",
+ "fr":"Node répliquée",
+ "it":"Nodo duplicato"
+ },
+ "Local node":{
+ "en":"Local node",
+ "ru":"Ð›Ð¾ÐºÐ°Ð»ÑŒÐ½Ð°Ñ Ð½Ð¾Ð´Ð°",
+ "fr":"Node locale",
+ "it":"Nodo locale"
+ },
+ "Component":{
+ "en":"Component",
+ "ru":"Компонент",
+ "fr":"Composant",
+ "it":"Componente"
+ },
+ "Audio":{
+ "en":"Audio",
+ "ru":"Ðудио",
+ "fr":"Audio",
+ "it":"Audio"
+ },
+ "Geometry":{
+ "en":"Geometry",
+ "ru":"ГеометриÑ",
+ "fr":"Géométrie",
+ "it":"Geometria"
+ },
+ "Logic":{
+ "en":"Logic",
+ "ru":"Логика",
+ "fr":"Logique",
+ "it":"Logica"
+ },
+ "Navigation":{
+ "en":"Navigation",
+ "ru":"ÐавигациÑ",
+ "fr":"Navigation",
+ "it":"Navigazione"
+ },
+ "Network":{
+ "en":"Network",
+ "ru":"Сеть",
+ "fr":"Réseau",
+ "it":"Rete"
+ },
+ "Physics":{
+ "en":"Physics",
+ "ru":"Физика",
+ "fr":"Physique",
+ "it":"Fisica"
+ },
+ "Scene":{
+ "en":"Scene",
+ "ru":"Сцена",
+ "fr":"Scène",
+ "it":"Scena"
+ },
+ "Subsystem":{
+ "en":"Subsystem",
+ "ru":"ПодÑиÑтема",
+ "fr":"Sous-système",
+ "it":"Sotto-sistema"
+ },
+ "Urho2D":{
+ "en":"Urho2D",
+ "ru":"Urho2D",
+ "fr":"Urho2D",
+ "it":"Urho2D"
+ },
+ "Builtin object":{
+ "en":"Builtin object",
+ "ru":"Ð’Ñтроенный объект",
+ "fr":"Objet pré-assemblé",
+ "it":"Oggetti integrati"
+ },
+ "UI-element":{
+ "en":"UI-element",
+ "ru":"Ðлемент интерфейÑа",
+ "fr":"Élément interface utilisateur",
+ "it":"Elemento UI"
+ },
+ "UI-layout":{
+ "en":"UI-layout",
+ "ru":"Разметка интерфейÑа",
+ "fr":"Disposition d'interface utilisateur",
+ "it":"Schema UI"
+ },
+ "Open UI-layout...":{
+ "en":"Open UI-layout...",
+ "ru":"Открыть разметку...",
+ "fr":"Ouvrir une disposition d'interface utilisateur...",
+ "it":"Apri schema UI..."
+ },
+ "Save UI-layout":{
+ "en":"Save UI-layout",
+ "ru":"Сохранить разметку",
+ "fr":"Sauvegarder la disposition d'interface utilisateur",
+ "it":"Salva schema UI"
+ },
+ "Save UI-layout as...":{
+ "en":"Save UI-layout as...",
+ "ru":"Сохранить разметку как...",
+ "fr":"Sauvegarder la disposition d'interface utilisateur comme...",
+ "it":"Salva schema UI come..."
+ },
+ "Close UI-layout":{
+ "en":"Close UI-layout",
+ "ru":"Закрыть разметку",
+ "fr":"Fermer la disposition d'interface utilisateur",
+ "it":"Chiudi schema UI"
+ },
+ "Close all UI-layouts":{
+ "en":"Close all UI-layouts",
+ "ru":"Закрыть вÑе разметки",
+ "fr":"Fermer toutes les disposition IU",
+ "it":"Chiudi tutti gli schemi UI"
+ },
+ "Load child element...":{
+ "en":"Load child element...",
+ "ru":"Загрузить дочерний Ñлемент...",
+ "fr":"Charger l'élément enfant...",
+ "it":"Carica elemento figlio..."
+ },
+ "Save child element as...":{
+ "en":"Save child element as...",
+ "ru":"Сохранить дочерний Ñлемент как...",
+ "fr":"Sauvegarde l'élément enfant comme...",
+ "it":"Salva elemento figlio come..."
+ },
+ "Set default style...":{
+ "en":"Set default style...",
+ "ru":"УÑтановить Ñтандартный Ñтиль...",
+ "fr":"Régler le style par défaut...",
+ "it":"Imposta stile predefinito..."
+ },
+ "View":{
+ "en":"View",
+ "ru":"Вид",
+ "fr":"Affichage",
+ "it":"Visualizza"
+ },
+ "Hierarchy":{
+ "en":"Hierarchy",
+ "ru":"ИерархиÑ",
+ "fr":"Hiéarchie",
+ "it":"Gerarchia"
+ },
+ "Attribute inspector":{
+ "en":"Attribute inspector",
+ "ru":"Ðттрибуты",
+ "fr":"Inspecteur d'attributs",
+ "it":"Ispettore attributi"
+ },
+ "Material editor":{
+ "en":"Material editor",
+ "ru":"Редактор материалов",
+ "fr":"Éditeur de matériaux",
+ "it":"Editor materiali"
+ },
+ "Particle editor":{
+ "en":"Particle editor",
+ "ru":"Редактор чаÑтиц",
+ "fr":"Éditeur de particules",
+ "it":"Editor particelle"
+ },
+ "Spawn editor":{
+ "en":"Spawn editor",
+ "ru":"Редактор Ñпауна",
+ "fr":"Éditeur de progéniture",
+ "it":"Editor spawn"
+ },
+ "Spawn settings":{
+ "en":"Spawn settings",
+ "ru":"ÐаÑтройки Ñпауна",
+ "fr":"Paramètres de reproduction",
+ "it":"Impostazioni spawn"
+ },
+ "Sound Type editor":{
+ "en":"Sound Type editor",
+ "ru":"Тип звука",
+ "fr":"Éditeur de types de son",
+ "it":"Editor tipo di suono"
+ },
+ "Expand":{
+ "en":"Expand",
+ "ru":"Развернуть",
+ "fr":"Agrandir",
+ "it":"Espandi"
+ },
+ "Collapse":{
+ "en":"Collapse",
+ "ru":"Свернуть",
+ "fr":"Rétrécir",
+ "it":"Riduci"
+ },
+ "All":{
+ "en":"All",
+ "ru":"Ð’Ñе",
+ "fr":"Tous",
+ "it":"Tutto"
+ },
+ "Mode: ":{
+ "en":"Mode: ",
+ "ru":"Режим: ",
+ "fr":"Mode: ",
+ "it":"Modalità : "
+ },
+ " Axis: ":{
+ "en":" Axis: ",
+ "ru":" ОÑи: ",
+ "fr":" Axe: ",
+ "it":" Asse: "
+ },
+ " Pick: ":{
+ "en":" Pick: ",
+ "ru":" Выбор: ",
+ "fr":" Sélection: ",
+ "it":" Selezione: "
+ },
+ " Fill: ":{
+ "en":" Fill: ",
+ "ru":" Отображение: ",
+ "fr":" Remplissage: ",
+ "it":" Riempimento: "
+ },
+ " Updates: ":{
+ "en":" Updates: ",
+ "ru":" ОбновлениÑ: ",
+ "fr":" Mises à jour: ",
+ "it":" Aggiornamenti: "
+ },
+ "Running":{
+ "en":"Running",
+ "ru":"Запущены",
+ "fr":"En marche",
+ "it":"Esecuzione"
+ },
+ "Paused":{
+ "en":"Paused",
+ "ru":"Ðа паузе",
+ "fr":"Mis en pause",
+ "it":"In pausa"
+ },
+ "Tris: ":{
+ "en":"Tris: ",
+ "ru":"Треуг-в: ",
+ "fr":"Tris: ",
+ "it":"Tris: "
+ },
+ " Batches: ":{
+ "en":" Batches: ",
+ "ru":" Батчей: ",
+ "fr":" Lots: ",
+ "it":" Lotti: "
+ },
+ " Lights: ":{
+ "en":" Lights: ",
+ "ru":" ИÑÑ‚-в Ñвета: ",
+ "fr":" Lumières: ",
+ "it":" Luci: "
+ },
+ " Shadowmaps: ":{
+ "en":" Shadowmaps: ",
+ "ru":" Теневых карт: ",
+ "fr":" Textures d'ombre: ",
+ "it":" Textures ombra: "
+ },
+ " Occluders: ":{
+ "en":" Occluders: ",
+ "ru":" Окклюдеров: ",
+ "fr":" Objets d'occlusion: ",
+ "it":" Occlusori: "
+ },
+ "Move":{
+ "en":"Move",
+ "ru":"Перемещение",
+ "fr":"Déplacer",
+ "it":"Muovi"
+ },
+ "Rotate":{
+ "en":"Rotate",
+ "ru":"Вращение",
+ "fr":"Tourner",
+ "it":"Ruota"
+ },
+ "Scale":{
+ "en":"Scale",
+ "ru":"МаÑштабирование",
+ "fr":"Échelle",
+ "it":"Ridimensiona"
+ },
+ "Select":{
+ "en":"Select",
+ "ru":"Выделение",
+ "fr":"Sél.",
+ "it":"Seleziona"
+ },
+ "Spawn":{
+ "en":"Spawn",
+ "ru":"Спаун",
+ "fr":"Reproduire",
+ "it":"Spawn"
+ },
+ "World":{
+ "en":"World",
+ "ru":"Мировые",
+ "fr":"Global",
+ "it":"Mondo"
+ },
+ "Local":{
+ "en":"Local",
+ "ru":"Локальные",
+ "fr":"Local",
+ "it":"Locale"
+ },
+ "Geometries":{
+ "en":"Geometries",
+ "ru":"ГеометриÑ",
+ "fr":"Géométries",
+ "it":"Geometrie"
+ },
+ "Lights":{
+ "en":"Lights",
+ "ru":"ИÑточники Ñвета",
+ "fr":"Lumières",
+ "it":"Luci"
+ },
+ "Zones":{
+ "en":"Zones",
+ "ru":"Зоны",
+ "fr":"Zones",
+ "it":"Zone"
+ },
+ "Rigidbodies":{
+ "en":"Rigidbodies",
+ "ru":"Твердые тела",
+ "fr":"Corps rigide",
+ "it":"Corpi rigidi"
+ },
+ "UI-elements":{
+ "en":"UI-elements",
+ "ru":"Ðлементы интерфейÑа",
+ "fr":"Élément d'interface utilisateur",
+ "it":"Elementi UI"
+ },
+ "Solid":{
+ "en":"Solid",
+ "ru":"Материал",
+ "fr":"Solide",
+ "it":"Solido"
+ },
+ "Wire":{
+ "en":"Wire",
+ "ru":"Сетка",
+ "fr":"Fil de fer",
+ "it":"Fil di ferro"
+ },
+ "Point":{
+ "en":"Point",
+ "ru":"Вершины",
+ "fr":"Point",
+ "it":"Punto"
+ },
+ "Editor settings":{
+ "en":"Editor settings",
+ "ru":"ÐаÑтройки редактора",
+ "fr":"Réglages de l'éditeur",
+ "it":"Impostazioni editor"
+ },
+ "Editor preferences":{
+ "en":"Editor preferences",
+ "ru":"УÑтановки редактора",
+ "fr":"Préférences de l'éditeur",
+ "it":"Preferenze editor"
+ },
+ "Hide editor":{
+ "en":"Hide editor",
+ "ru":"Скрыть редактор",
+ "fr":"Cacher l'éditeur",
+ "it":"Nascondi editor"
+ },
+ "Resource Browser":{
+ "en":"Resource Browser",
+ "ru":"Браузер реÑурÑов",
+ "fr":"Navigateur de ressources",
+ "it":"Navigatore risorse"
+ },
+ "New scene":{
+ "en":"New scene",
+ "ru":"Создать новую Ñцену",
+ "fr":"Nouvelle scène",
+ "it":"Nuova scena"
+ },
+ "Files left to scan: ":{
+ "en":"Files left to scan: ",
+ "ru":"Файлов до конца ÑканированиÑ: ",
+ "fr":"Fichiers restant à analyser: ",
+ "it":"File rimasti per la scansione: "
+ },
+ "Scan complete":{
+ "en":"Scan complete",
+ "ru":"Сканирование окончено",
+ "fr":"Analyse complétée",
+ "it":"Scansione completa"
+ },
+ "Root":{
+ "en":"Root",
+ "ru":"Корень",
+ "fr":"Racine",
+ "it":"Radice"
+ },
+ "Showing files: ":{
+ "en":"Showing files: ",
+ "ru":"Показано файлов: ",
+ "fr":"Affiche les fichiers: ",
+ "it":"Files visualizzati: "
+ },
+ "Camera":{
+ "en":"Camera",
+ "ru":"Камера",
+ "fr":"Caméra",
+ "it":"Camera"
+ },
+ "RunUpdatePlay":{
+ "en":"RunUpdatePlay",
+ "ru":"ЗапуÑтить",
+ "fr":"Démarre, met à jour et joue",
+ "it":"Avvio"
+ },
+ "RunUpdatePause":{
+ "en":"RunUpdatePause",
+ "ru":"Пауза",
+ "fr":"Démarre, met à jour et pause",
+ "it":"Pausa"
+ },
+ "RevertOnPause":{
+ "en":"RevertOnPause",
+ "ru":"ВернутьÑÑ Ð½Ð° паузе",
+ "fr":"Annule en pause",
+ "it":"Ricomincia in pausa"
+ },
+ "EditMove":{
+ "en":"EditMove",
+ "ru":"Перемещение",
+ "fr":"Éditer le déplacement",
+ "it":"Modifica posizione"
+ },
+ "EditRotate":{
+ "en":"EditRotate",
+ "ru":"Вращение",
+ "fr":"Éditer la rotation",
+ "it":"Modifica rotazione"
+ },
+ "EditScale":{
+ "en":"EditScale",
+ "ru":"МаÑштабирование",
+ "fr":"Éditer l'échelle",
+ "it":"Modifica dimensione"
+ },
+ "EditSelect":{
+ "en":"EditSelect",
+ "ru":"Выделение",
+ "fr":"Éditer la sélection",
+ "it":"Modifica selezione"
+ },
+ "AxisWorld":{
+ "en":"AxisWorld",
+ "ru":"Мировые оÑи",
+ "fr":"Axe global",
+ "it":"Asse mondo"
+ },
+ "AxisLocal":{
+ "en":"AxisLocal",
+ "ru":"Локальные оÑи",
+ "fr":"Axe local",
+ "it":"Asse locale"
+ },
+ "MoveSnap":{
+ "en":"MoveSnap",
+ "ru":"ДиÑкретное перемещение",
+ "fr":"Magnétisme",
+ "it":"Muovi a scatto"
+ },
+ "RotateSnap":{
+ "en":"RotateSnap",
+ "ru":"ДиÑкретное вращение",
+ "fr":"Magnétisme de rotation",
+ "it":"Ruota a scatto"
+ },
+ "ScaleSnap":{
+ "en":"ScaleSnap",
+ "ru":"ДиÑкретное маÑштабирование",
+ "fr":"Magnétisme d'échelle",
+ "it":"Ridimensiona a scatto"
+ },
+ "SnapScaleHalf":{
+ "en":"SnapScaleHalf",
+ "ru":"Половина диÑкретного маÑштабированиÑ",
+ "fr":"Échelle de magnétisme de moitié",
+ "it":"Ridimensiona a metà scatto"
+ },
+ "SnapScaleQuarter":{
+ "en":"SnapScaleQuarter",
+ "ru":"Четверть диÑкретного маÑштабированиÑ",
+ "fr":"Échelle de magnétisme du quart",
+ "it":"Ridimensiona ad un quarto di scatto"
+ },
+ "PickGeometries":{
+ "en":"PickGeometries",
+ "ru":"Выделении геометрии",
+ "fr":"Sélection géométrie",
+ "it":"Seleziona geometrie"
+ },
+ "PickLights":{
+ "en":"PickLights",
+ "ru":"Выделение иÑточников Ñвета",
+ "fr":"Sélection lumières",
+ "it":"Seleziona luci"
+ },
+ "PickZones":{
+ "en":"PickZones",
+ "ru":"Выделение зон",
+ "fr":"Sélection de zones",
+ "it":"Seleziona zone"
+ },
+ "PickRigidBodies":{
+ "en":"PickRigidBodies",
+ "ru":"Выделение твердых тел",
+ "fr":"Sélection de corps rigides",
+ "it":"Seleziona corpi rigidi"
+ },
+ "PickUIElements":{
+ "en":"PickUIElements",
+ "ru":"Выделение Ñлементов интерфейÑа",
+ "fr":"Sélection d'interface utilisateur",
+ "it":"Seleziona elementi UI"
+ },
+ "FillPoint":{
+ "en":"FillPoint",
+ "ru":"Отображать вершины",
+ "fr":"Remplissage en points",
+ "it":"Riempimento a punti"
+ },
+ "FillWireFrame":{
+ "en":"FillWireFrame",
+ "ru":"Отображать Ñетку",
+ "fr":"Remplissage en fil de fer",
+ "it":"Riempimento a fil di ferro"
+ },
+ "FillSolid":{
+ "en":"FillSolid",
+ "ru":"Отображать материалы",
+ "fr":"Remplissage solide",
+ "it":"Riempimento solido"
+ },
+ "Light":{
+ "en":"Light",
+ "ru":"ИÑточник Ñвета",
+ "fr":"Lumière",
+ "it":"Luce"
+ },
+ "Zone":{
+ "en":"Zone",
+ "ru":"Зона",
+ "fr":"Zone",
+ "it":"Zona"
+ },
+ "StaticModel":{
+ "en":"StaticModel",
+ "ru":"СтатичеÑÐºÐ°Ñ Ð¼Ð¾Ð´ÐµÐ»ÑŒ",
+ "fr":"Modèle statique",
+ "it":"Modello statico"
+ },
+ "AnimatedModel":{
+ "en":"AnimatedModel",
+ "ru":"ÐÐ½Ð¸Ð¼Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð¼Ð¾Ð´ÐµÐ»ÑŒ",
+ "fr":"Modèle animé",
+ "it":"Modello animato"
+ },
+ "BillboardSet":{
+ "en":"BillboardSet",
+ "ru":"Ðабор билбордов",
+ "fr":"Groupe de panneaux",
+ "it":"Set di cartelloni"
+ },
+ "ParticleEmitter":{
+ "en":"ParticleEmitter",
+ "ru":"Излучатель чаÑтиц",
+ "fr":"Émetteur de particule",
+ "it":"Emettitore di particelle"
+ },
+ "Skybox":{
+ "en":"Skybox",
+ "ru":"СкайбокÑ",
+ "fr":"Boîte de ciel",
+ "it":"Cielo a scatola"
+ },
+ "Terrain":{
+ "en":"Terrain",
+ "ru":"Ландшафт",
+ "fr":"Terrain",
+ "it":"Terreno"
+ },
+ "Text3D":{
+ "en":"Text3D",
+ "ru":"ТекÑÑ‚ 3D",
+ "fr":"Texte 3D",
+ "it":"Testo 3D"
+ },
+ "SoundListener":{
+ "en":"SoundListener",
+ "ru":"Звукоприемник",
+ "fr":"Écouteur de son",
+ "it":"Ascoltatore suono"
+ },
+ "SoundSource3D":{
+ "en":"SoundSource3D",
+ "ru":"ИÑточник звука 3D",
+ "fr":"Source de son 3D",
+ "it":"Sorgente suono 3D"
+ },
+ "SoundSource":{
+ "en":"SoundSource",
+ "ru":"ИÑточник звука",
+ "fr":"Source de son",
+ "it":"Sorgente suono"
+ },
+ "RigidBody":{
+ "en":"RigidBody",
+ "ru":"Твердое тело",
+ "fr":"Corp rigide",
+ "it":"Corpo rigido"
+ },
+ "CollisionShape":{
+ "en":"CollisionShape",
+ "ru":"КолижнШейп",
+ "fr":"Forme de collision",
+ "it":"Forma collisione"
+ },
+ "Constraint":{
+ "en":"Constraint",
+ "ru":"Ограничитель",
+ "fr":"Contrainte",
+ "it":"Vincolo"
+ },
+ "AnimationController":{
+ "en":"AnimationController",
+ "ru":"Контроллер анимации",
+ "fr":"Contrôleur d'animation",
+ "it":"Controller animazione"
+ },
+ "ScriptInstance":{
+ "en":"ScriptInstance",
+ "ru":"Скрипт",
+ "fr":"Instance de script",
+ "it":"Istanza script"
+ },
+ "Navigable":{
+ "en":"Navigable",
+ "ru":"Navigable",
+ "fr":"Naviguable",
+ "it":"Navigabile"
+ },
+ "NavigationMesh":{
+ "en":"NavigationMesh",
+ "ru":"NavigationMesh",
+ "fr":"Maillage de navigation",
+ "it":"Mesh di navigazione"
+ },
+ "OffMeshConnection":{
+ "en":"OffMeshConnection",
+ "ru":"OffMeshConnection",
+ "fr":"Connection hors maillage",
+ "it":"Connessione off-mesh"
+ },
+ "NetworkPriority":{
+ "en":"NetworkPriority",
+ "ru":"Сетевой приоритет",
+ "fr":"Priorité du réseau",
+ "it":"Priorità rete"
+ },
+ "File":{
+ "en":"File",
+ "ru":"Файл",
+ "fr":"Fichier",
+ "it":"File"
+ },
+ "Settings":{
+ "en":"Settings",
+ "ru":"ÐаÑтройки",
+ "fr":"Réglagles",
+ "it":"Impostazioni"
+ },
+ "Enable all":{
+ "en":"Enable all",
+ "ru":"Включить вÑе",
+ "fr":"Activer tout",
+ "it":"Attiva tutto"
+ },
+ "Pick ":{
+ "en":"Pick ",
+ "ru":"Выбрать ",
+ "fr":"Activer tout ",
+ "it":"Seleziona "
+ },
+ "OK":{
+ "en":"OK",
+ "ru":"OK",
+ "fr":"OK",
+ "it":"OK"
+ },
+ "Cancel":{
+ "en":"Cancel",
+ "ru":"Отмена",
+ "fr":"Annuler",
+ "it":"Annulla"
+ },
+ "Save":{
+ "en":"Save",
+ "ru":"Сохранить",
+ "fr":"Sauvegarder",
+ "it":"Salva"
+ },
+ "Save material as":{
+ "en":"Save material as",
+ "ru":"Сохранить материал как",
+ "fr":"Sauvegarder le matériel comme",
+ "it":"Salva materiale come"
+ },
+ "Save particle effect as":{
+ "en":"Save particle effect as",
+ "ru":"Сохранить Ñффект из чаÑтиц как",
+ "fr":"Sauvegarder l'effêt de particule comme",
+ "it":"Salva effetto particellare come"
+ },
+ "Load":{
+ "en":"Load",
+ "ru":"Загрузить",
+ "fr":"Charger",
+ "it":"Carica"
+ },
+ "Load render path":{
+ "en":"Load render path",
+ "ru":"Загрузить render path",
+ "fr":"Charger le type de chemin de rendu",
+ "it":"Carica percorso rendering"
+ },
+ "Pick spawned object":{
+ "en":"Pick spawned object",
+ "ru":"Выбрать ÑпаунищийÑÑ Ð¾Ð±ÑŠÐµÐºÑ‚",
+ "fr":"Sélectionner l'objet reproduit",
+ "it":"Seleziona oggetto spawnato"
+ },
+ "Pick":{
+ "en":"Pick",
+ "ru":"Выбрать",
+ "fr":"Sélectionner",
+ "it":"Seleziona"
+ },
+ "Set":{
+ "en":"Set",
+ "ru":"УÑтановить",
+ "fr":"Ajuster",
+ "it":"Imposta"
+ },
+ "Run":{
+ "en":"Run",
+ "ru":"ЗапуÑтить",
+ "fr":"Démarrer",
+ "it":"Esegui"
+ },
+ "Open":{
+ "en":"Open",
+ "ru":"Открыть",
+ "fr":"Ouvrir",
+ "it":"Apri"
+ },
+ "Import":{
+ "en":"Import",
+ "ru":"Импорт",
+ "fr":"Importer",
+ "it":"Importa"
+ },
+ "Open scene":{
+ "en":"Open scene",
+ "ru":"Открыть Ñцену",
+ "fr":"Ouvrir la scène",
+ "it":"Apri scena"
+ },
+ "Save scene as":{
+ "en":"Save scene as",
+ "ru":"Сохранить Ñцену как...",
+ "fr":"Sauvegarder la scene comme",
+ "it":"Salva scena come"
+ },
+ "fileSelector Load node":{
+ "en":"Load node",
+ "ru":"Загрузка ноды",
+ "fr":"Charger la node",
+ "it":"Carica nodo"
+ },
+ "Save node":{
+ "en":"Save node",
+ "ru":"Сохранение ноды",
+ "fr":"Sauver la node",
+ "it":"Salva nodo"
+ },
+ "Import model":{
+ "en":"Import model",
+ "ru":"Импорт модели",
+ "fr":"Importer un modèle",
+ "it":"Importa modello"
+ },
+ "Import scene":{
+ "en":"Import scene",
+ "ru":"Импорт Ñцены",
+ "fr":"Importer une scène",
+ "it":"Importa scena"
+ },
+ "Run script":{
+ "en":"Run script",
+ "ru":"ЗапуÑк Ñкрипта",
+ "fr":"Exécuter un script",
+ "it":"Esegui script"
+ },
+ "Set resource path":{
+ "en":"Set resource path",
+ "ru":"Путь к реÑурÑам",
+ "fr":"Définir le chemin des ressources",
+ "it":"Imposta percorso risorse"
+ },
+ "Open UI-layout":{
+ "en":"Open UI-layout",
+ "ru":"Открытие разметки интерфейÑа",
+ "fr":"Ouvrir la disposition d'interface utilisateur",
+ "it":"Apri schema UI"
+ },
+ "Save UI-layout as":{
+ "en":"Save UI-layout as",
+ "ru":"Сохранить разметку интерфейÑа как",
+ "fr":"Sauvegarder la disposition d'interface utilisateur",
+ "it":"Salva schema UI come"
+ },
+ "Load child element":{
+ "en":"Load child element",
+ "ru":"Загрузка дочернего Ñлемента",
+ "fr":"Charger l'élément enfant",
+ "it":"Carica elemento figlio"
+ },
+ "Save child element":{
+ "en":"Save child element",
+ "ru":"Сохранение дочернего Ñлемента",
+ "fr":"Sauvegarde de l'élément enfant",
+ "it":"Salva elemento figlio"
+ },
+ "Set default style":{
+ "en":"Set default style",
+ "ru":"Выбор ÑÑ‚Ð¸Ð»Ñ Ð¿Ð¾ умолчанию",
+ "fr":"Définir le style par défaut",
+ "it":"Imposta stile predefinito"
+ },
+ "Parent to last":{
+ "en":"Parent to last",
+ "ru":"Сделать дочерним Ð´Ð»Ñ Ð¿Ð¾Ñледнего",
+ "fr":"Parenter au dernier",
+ "it":"Imparenta con l'ultimo"
+ },
+ "Style":{
+ "en":"Style",
+ "ru":"Стиль",
+ "fr":"Style",
+ "it":"Stile"
+ },
+ "New":{
+ "en":"New",
+ "ru":"Ðов.",
+ "fr":"Nouv.",
+ "it":"Nuovo"
+ },
+ "Del":{
+ "en":"Del",
+ "ru":"Удал.",
+ "fr":"Eff.",
+ "it":"Rim."
+ },
+ "smallButtonPick":{
+ "en":"Pick",
+ "ru":"Выбр.",
+ "fr":"Sél.",
+ "it":"Sel."
+ },
+ "smallButtonOpen":{
+ "en":"Open",
+ "ru":"Откр.",
+ "fr":"Ouv.",
+ "it":"Apri"
+ },
+ "smallButtonEdit":{
+ "en":"Edit",
+ "ru":"Ред.",
+ "fr":"Éditer",
+ "it":"Modif."
+ },
+ "smallButtonTest":{
+ "en":"Test",
+ "ru":"ТеÑÑ‚",
+ "fr":"Test",
+ "it":"Test"
+ },
+ "Select editable objects":{
+ "en":"Select editable objects",
+ "ru":"Выберите редактируемый объект",
+ "fr":"Sélection objets éditables",
+ "it":"Seleziona oggetti modificabili"
+ },
+ "Box":{
+ "en":"Box",
+ "ru":"Куб",
+ "fr":"Boite",
+ "it":"Scatola"
+ },
+ "Sphere":{
+ "en":"Sphere",
+ "ru":"Сфера",
+ "fr":"Sphère",
+ "it":"Sfera"
+ },
+ "Plane":{
+ "en":"Plane",
+ "ru":"ПлоÑкоÑÑ‚ÑŒ",
+ "fr":"Plan",
+ "it":"Piano"
+ },
+ "Cylinder":{
+ "en":"Cylinder",
+ "ru":"Цилиндр",
+ "fr":"Cylindre",
+ "it":"Cilindro"
+ },
+ "Cone":{
+ "en":"Cone",
+ "ru":"КонуÑ",
+ "fr":"Cône",
+ "it":"Cono"
+ },
+ "TeaPot":{
+ "en":"TeaPot",
+ "ru":"Чайник",
+ "fr":"Théière",
+ "it":"Teiera"
+ },
+ "Techniques":{
+ "en":"Techniques",
+ "ru":"Техники",
+ "fr":"Techniques",
+ "it":"Tecniche"
+ },
+ "Sort":{
+ "en":"Sort",
+ "ru":"Сортировать",
+ "fr":"Trier",
+ "it":"Ordina"
+ },
+ "Textures":{
+ "en":"Textures",
+ "ru":"ТекÑтуры",
+ "fr":"Textures",
+ "it":"Textures"
+ },
+ "Shader parameters":{
+ "en":"Shader parameters",
+ "ru":"Параметры шейдера",
+ "fr":"Paramètres du shader",
+ "it":"Parameteri shader"
+ },
+ "Revert":{
+ "en":"Revert",
+ "ru":"Вернуть",
+ "fr":"Annuler",
+ "it":"Annulla"
+ },
+ "Save as":{
+ "en":"Save as",
+ "ru":"Сохранить как",
+ "fr":"Sauvegarder sous",
+ "it":"Salva come"
+ },
+ "Color":{
+ "en":"Color",
+ "ru":"Цвет",
+ "fr":"Couleur",
+ "it":"Colore"
+ },
+ "Texture":{
+ "en":"Texture",
+ "ru":"ТекÑтура",
+ "fr":"Texture",
+ "it":"Texture"
+ },
+ "Reset Viewport":{
+ "en":"Reset Viewport",
+ "ru":"СброÑить вьюпорт",
+ "fr":"Mise à zéro de la fenêtre",
+ "it":"Reimposta regione di visualizzazione"
+ },
+ "Show Axes":{
+ "en":"Show Axes",
+ "ru":"Показывать оÑи",
+ "fr":"Montrer les axes",
+ "it":"Visualizza assi"
+ },
+ "Initial":{
+ "en":"Initial",
+ "ru":"Ðачальные",
+ "fr":"Initiale",
+ "it":"Iniziale"
+ },
+ "Variation":{
+ "en":"Variation",
+ "ru":"ИзменениÑ",
+ "fr":"Variation",
+ "it":"Variazione"
+ },
+ "Color Frames":{
+ "en":"Color Frames",
+ "ru":"Изменение цвета",
+ "fr":"Couleur de cadres",
+ "it":"Colore cornici"
+ },
+ "Remove":{
+ "en":"Remove",
+ "ru":"Удалить",
+ "fr":"Enlever",
+ "it":"Rimuovi"
+ },
+ "Texture Frames":{
+ "en":"Texture Frames",
+ "ru":"Изменение текÑтуры",
+ "fr":"Texture des cadres",
+ "it":"Texture cornici"
+ },
+ "Emitter":{
+ "en":"Emitter",
+ "ru":"Излучатель",
+ "fr":"Émetteur",
+ "it":"Emettitore"
+ },
+ "Renderer":{
+ "en":"Renderer",
+ "ru":"Рендерер",
+ "fr":"Renderer",
+ "it":"Renderer"
+ },
+ "Please, use only nodes with one StaticModel for instansing":{
+ "en":"Please, use only nodes with one StaticModel for instansing",
+ "ru":"ПожалуйÑта, иÑпользуйте только ноду Ñ Ð¾Ð´Ð½Ð¾Ð¹ ÑтатичеÑкой моделью Ð´Ð»Ñ Ð¸Ð½ÑтанÑинга",
+ "fr":"S'il-vous-plaît, n'utilisez qu'un seul modèle statique par node pour l'instantiation",
+ "it":"Si prega di utilizzare i nodi solo con un StaticModel per instanza"
+ },
+ " CameraFlyMode: ":{
+ "en":" CameraFlyMode: ",
+ "ru":" Камера в режиме полета: ",
+ "fr":" Mode vol caméra: ",
+ "it":" Modalità volo camera: "
+ },
+ "Light color":{
+ "en":"Light color",
+ "ru":"Цвет иÑточника Ñвета",
+ "fr":"Couleur de la lumière",
+ "it":"Colore luce"
+ },
+ "Specular intensity":{
+ "en":"Specular intensity",
+ "ru":"ИнтенÑивноÑÑ‚ÑŒ бликов",
+ "fr":"Intensité spéculaire",
+ "it":"Intensità speculare"
+ },
+ "Brightness multiplier":{
+ "en":"Brightness multiplier",
+ "ru":"Мультипликатор ÑркоÑти",
+ "fr":"Multiplicateur de luminosité",
+ "it":"Moltiplicatore luminosità "
+ },
+ "Ambient color":{
+ "en":"Ambient color",
+ "ru":"Цвет окружениÑ",
+ "fr":"Couleur ambiante",
+ "it":"Colore ambiente"
+ },
+ "Fog color":{
+ "en":"Fog color",
+ "ru":"Цвет тумана",
+ "fr":"Couleur du brouillard",
+ "it":"Colore nebbia"
+ },
+ "Move to layer":{
+ "en":"Move to layer",
+ "ru":"ПеремеÑтить на Ñлой",
+ "fr":"Déplacer sur un calque",
+ "it":"Sposta allo strato"
+ },
+ "Smart Duplicate":{
+ "en":"Smart Duplicate",
+ "ru":"Дублирование Ñо Ñмещением",
+ "fr":"Duplication intelligente",
+ "it":"Duplicazione intelligente"
+ },
+ "View closer":{
+ "en":"View closer",
+ "ru":"Показать поближе",
+ "fr":"Voir plus près",
+ "it":"Visualizza più da vicino"
+ },
+ "Color wheel":{
+ "en":"Color wheel",
+ "ru":"Цветовое колеÑо",
+ "fr":"Palette de couleurs",
+ "it":"Ruota dei colori"
+ },
+ "Diffuse color":{
+ "en":"Diffuse color",
+ "ru":"Диффузный цвет",
+ "fr":"Couleur diffuse",
+ "it":"Colore diffuso"
+ },
+ "Specular color":{
+ "en":"Specular color",
+ "ru":"Цвет бликов",
+ "fr":"Couleur spéculaire",
+ "it":"Colore speculare"
+ },
+ "Emissive color":{
+ "en":"Emissive color",
+ "ru":"Цвет ÑамоÑвечениÑ",
+ "fr":"Couleur émissive",
+ "it":"Colore emissivo"
+ },
+ "Environment map color":{
+ "en":"Environment map color",
+ "ru":"Цвет карты окружениÑ",
+ "fr":"Couleur map d'environement",
+ "it":"Colore mappa ambientale"
+ },
+ "Delete?":{
+ "en":"Delete?",
+ "ru":"Удалить?",
+ "fr":"Effacer?",
+ "it":"Elimina?"
+ },
+ "Opacity":{
+ "en":"Opacity",
+ "ru":"ÐепрозрачноÑÑ‚ÑŒ",
+ "fr":"Opacité",
+ "it":"Opacità "
+ },
+ "Color":{
+ "en":"Color",
+ "ru":"Цвет",
+ "fr":"Couleur",
+ "it":"Colore"
+ },
+ "Tags":{
+ "en":"Tags",
+ "ru":"Теги",
+ "fr":"Étiquettes",
+ "it":"Etichette"
+ },
+ "Export scene to OBJ...":{
+ "en":"Export scene to OBJ...",
+ "ru":"ÐкÑпортировать Ñцену в OBJ...",
+ "it":"Esporta scena a OBJ..."
+ },
+ "Export selected to OBJ...":{
+ "en":"Export selected to OBJ...",
+ "ru":"ÐкÑпортировать выбранное в OBJ...",
+ "it":"Esporta selezione a OBJ..."
+ },
+ "Reset transform":{
+ "en":"Reset transform",
+ "ru":"СброÑить транÑформации",
+ "it":"Reimposta trasformazione"
+ },
+ "Show components icons":{
+ "en":"Show components icons",
+ "ru":"Показывать значки компонентов",
+ "it":"Visualizza icone componenti"
+ },
+ "Render Zone Cubemap":{
+ "en":"Render Zone Cubemap",
+ "ru":"Отренедерить зону в Cubemap",
+ "it":"Rendera Cubemap della zona"
+ },
+ "Show Grid":{
+ "en":"Show Grid",
+ "ru":"Показывать Ñетку",
+ "it":"Visualizza griglia"
+ }
+}
diff --git a/bin/Data/Fonts/Anonymous Pro.sdf b/bin/Data/Fonts/Anonymous Pro.sdf
new file mode 100644
index 0000000..1638f4d
--- /dev/null
+++ b/bin/Data/Fonts/Anonymous Pro.sdf
@@ -0,0 +1,634 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Fonts/Anonymous Pro.ttf b/bin/Data/Fonts/Anonymous Pro.ttf
new file mode 100644
index 0000000..06aafc0
Binary files /dev/null and b/bin/Data/Fonts/Anonymous Pro.ttf differ
diff --git a/bin/Data/Fonts/Anonymous Pro_0.png b/bin/Data/Fonts/Anonymous Pro_0.png
new file mode 100644
index 0000000..edf9714
Binary files /dev/null and b/bin/Data/Fonts/Anonymous Pro_0.png differ
diff --git a/bin/Data/Fonts/Anonymous Pro_1.png b/bin/Data/Fonts/Anonymous Pro_1.png
new file mode 100644
index 0000000..93162c8
Binary files /dev/null and b/bin/Data/Fonts/Anonymous Pro_1.png differ
diff --git a/bin/Data/Fonts/BlueHighway.png b/bin/Data/Fonts/BlueHighway.png
new file mode 100644
index 0000000..52898f0
Binary files /dev/null and b/bin/Data/Fonts/BlueHighway.png differ
diff --git a/bin/Data/Fonts/BlueHighway.sdf b/bin/Data/Fonts/BlueHighway.sdf
new file mode 100644
index 0000000..7b02d0f
--- /dev/null
+++ b/bin/Data/Fonts/BlueHighway.sdf
@@ -0,0 +1,1091 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Fonts/BlueHighway.ttf b/bin/Data/Fonts/BlueHighway.ttf
new file mode 100644
index 0000000..716264f
Binary files /dev/null and b/bin/Data/Fonts/BlueHighway.ttf differ
diff --git a/bin/Data/Fonts/OFL.txt b/bin/Data/Fonts/OFL.txt
new file mode 100644
index 0000000..5ca1911
--- /dev/null
+++ b/bin/Data/Fonts/OFL.txt
@@ -0,0 +1,94 @@
+Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com),
+with Reserved Font Name Anonymous Pro.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/bin/Data/Fonts/read_me.html b/bin/Data/Fonts/read_me.html
new file mode 100644
index 0000000..9f53dc8
--- /dev/null
+++ b/bin/Data/Fonts/read_me.html
@@ -0,0 +1,2 @@
+
Larabie Fonts "read me" file, license and FAQLARABIE FONTS “README.TXT”
All Larabie Fonts in this file are free to use for personal and/or commercial purposes. No payment is necessary to use these fonts for personal or commercial use. For Software Products who want to include Larabie Fonts see the License Agreement below. You can add this font to a website but do not combine fonts into a single archive or alter them in any way.
All Larabie Fonts are free for commercial use but a sample of your product would be gratefully appreciated so I can see how the font looks in use. Contact www.larabiefonts.com/donation.html for mailing information.
Some Larabie Fonts have enhanced and expanded families available for sale at www.typodermic.com.
If you'd like to make a voluntary donation to Larabie Fonts for the use of the free fonts in any amount please go to www.larabiefonts.com/donation.html
I accept CDs, magazines, t-shirts, a sample of your merchandise or anything featuring Larabie Fonts. Please remember to list your item as a ‘gift’ on the customs form or I will have to pay import duties and taxes on the item. Mailing information is provided at the link above.
Font installation help is available at www.larabiefonts.com/help.html
LARABIE FONTS FREQUENTLY ASKED QUESTIONS
- Q: How do use these fonts in my favourite software?
- A: In Windows, you take the fonts out of the ZIP archive and place them in your fonts folder which can be found in your Control Panel. The next time you run your software, the font will be available. For example: If you install a new font, the next time you run Microsoft Word, that font will be available in the menu under Format / Font. For anything more complicated, or Mac installation, visit www.larabiefonts.com/help.html
- Q: How can I use this font in AOL Instant Messenger, MSN Messenger, Outlook, Outlook Express, Euodora or any other email software?
- A: At the time of this writing (Feb 2004) you can’t. After installing one of my fonts, you may be able to select it in the above applications but the person at the other end won’t see that same thing unless they have the font installed. If you really want to use my fonts in these applications, make sure the people at the other end have the same fonts installed.
- Q: How can I use these fonts on a web page?
- A: If you’re creating a web page using Flash, it’s easy. Consult your Flash manual. If you’re using Acrobat, make sure the font embedding settings are turned on. Consult your Acrobat manual. For anything else there are limitations: If you want to use one of my fonts as your main, text font you’re pretty much out of luck unless you explore a font embedding tool such as WEFT but I don’t recommend it. To use my fonts as headings or titles, use image creation software such as The Gimp, Photoshop, Paint Shop Pro, Pixia etc. Save the images as GIF files and place them on your web page. There’s a lot more to it than can be explained here but there are countless books available on web page design.
- Q: How can I make these fonts bigger?
- A: All my fonts are infinitely scalable; the limitations are in your software. A common problem is scaling fonts in Microsoft Word. If you choose Format / Font you can type in any number you like under “size”.
- Q: Are these fonts really free?
- A: Yes they are. Some fonts such as Neuropol have expanded font families available for sale at www.typodermic.com but the version you downloaded at Larabie Fonts is free.
- Q: Your licence agreement states that the fonts can’t be altered. Does that mean I can’t mess around with your fonts in Photoshop/Illustrator/Publisher etc?
- A: Those license restrictions refer to altering the actual fonts themselves, not what you make with them. As long as you don’t alter the font files in font creation software such as FontLab or Fontographer you’re free to create anything you like with them.
- Q: Can I use your fonts in a logo?
- A: Yes. But check with a lawyer if you’re not sure. It’s okay with me if you use it but do so at your own risk.
- Q: Can you make a custom font for me?
- A: Possibly. Check typodermic.com/custom.html for details. Keep in mind that making fonts is my full-time job so no freebies.
- Q: I want to sell rubber stamp alphabets, alphabet punches or stencil alphabets using your font designs.
- A: Contact me first at www.larabiefonts.com/email.html.
- Q: My software won’t let me embed one of your fonts.
- A: You may have an old version of one of my fonts. Uninstall it and install a current version on Larabie Fonts.
- Q: Can you help me find a font?
- A: I really don’t have the time but if you send a donation, I can give it a try. If not. post your question on my font forum: www.larabiefonts.com/info.html.
LARABIE FONTS END-USER LICENSE AGREEMENT FOR SOFTWARE PRODUCTS
SOFTWARE PRODUCT LICENSE
The SOFTWARE PRODUCT is protected by copyright laws and International copyright treaties, as well as other intellectual property laws and treaties. The SOFTWARE PRODUCT is licensed, not sold.
1. GRANT OF LICENSE. This document grants you the following rights:
- Installation and Use. You may install and use an unlimited number of copies of the SOFTWARE PRODUCT. You may copy and distribute unlimited copies of the SOFTWARE PRODUCT as you receive them, in any medium, provided that you publish on each copy an appropriate copyright notice. Keep intact all the notices that refer to this License and give any other recipients of the fonts a copy of this License along with the fonts.
2. DESCRIPTION OF OTHER RIGHTS AND LIMITATIONS.
- You may modify your copy or copies of the SOFTWARE PRODUCT or any portion of it, provided that you also meet all of these rules:
a) Do not alter in any way alphanumeric characters (A-Z, a-z, 1-9) contained in the font. An exception is converting between formats, here is allowed the nominal distortion that occurs during conversion from second order to third order quadratic curves (TrueType to Postscript) and vice versa.
b) Extra characters may be added; here it is allowed to use curves (shapes) from alphanumeric characters in fonts under same license.
c) It is allowed to modify and remove analpahbetics (punctuation, special characters, ligatures and symbols).
d) The original font name must be retained but can be augmented. (ie. a Font named Blue Highway can be renamed Blue Highway Cyrillic or Blue Highway ANSI, etc.)
e) Character mapping may be altered.
f) If the kerning information is altered or discarded it must be stated in the user notes or documentation.
g) All modifications must be released under this license.
LIMITED WARRANTY NO WARRANTIES. Larabie Fonts expressly disclaims any warranty for the SOFTWARE PRODUCT. The SOFTWARE PRODUCT and any related documentation is provided "as is" without warranty of any kind, either express or implied, including, without limitation, the implied warranties or merchantability, fitness for a particular purpose, or non-infringement. The entire risk arising out of use or performance of the SOFTWARE PRODUCT remains with you.NO LIABILITY FOR CONSEQUENTIAL DAMAGES. In no event shall Larabie Fonts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or any other pecuniary loss) arising out of the use of or inability to use this product, even if Larabie Fonts has been advised of the possibility of such damages.
3. MISCELLANEOUS
Should you have any questions concerning this document, or if you desire to contact Larabie Fonts for any reason, please email www.larabiefonts.com/email.html.
+
diff --git a/bin/Data/LuaScripts/01_HelloWorld.lua b/bin/Data/LuaScripts/01_HelloWorld.lua
new file mode 100644
index 0000000..afba388
--- /dev/null
+++ b/bin/Data/LuaScripts/01_HelloWorld.lua
@@ -0,0 +1,59 @@
+-- This first example, maintaining tradition, prints a "Hello World" message.
+-- Furthermore it shows:
+-- - Using the Sample utility functions as a base for the application
+-- - Adding a Text element to the graphical user interface
+-- - Subscribing to and handling of update events
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create "Hello World" Text
+ CreateText()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Finally, hook-up this HelloWorld instance to handle update events
+ SubscribeToEvents()
+end
+
+function CreateText()
+ -- Construct new Text object
+ local helloText = Text:new()
+
+ -- Set String to display
+ helloText.text = "Hello World from Urho3D!"
+
+ -- Set font and text color
+ helloText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 30)
+ helloText.color = Color(0.0, 1.0, 0.0)
+
+ -- Align Text center-screen
+ helloText.horizontalAlignment = HA_CENTER
+ helloText.verticalAlignment = VA_CENTER
+
+ -- Add Text instance to the UI root element
+ ui.root:AddChild(helloText)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Do nothing for now, could be extended to eg. animate the display
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/02_HelloGUI.lua b/bin/Data/LuaScripts/02_HelloGUI.lua
new file mode 100644
index 0000000..5efb94c
--- /dev/null
+++ b/bin/Data/LuaScripts/02_HelloGUI.lua
@@ -0,0 +1,183 @@
+-- A simple 'HelloWorld' GUI created purely from code.
+-- This sample demonstrates:
+-- - Creation of controls and building a UI hierarchy
+-- - Loading UI style from XML and applying it to controls
+-- - Handling of global and per-control events
+-- For more advanced users (beginners can skip this section):
+-- - Dragging UIElements
+-- - Displaying tooltips
+-- - Accessing available Events data (eventData)
+
+require "LuaScripts/Utilities/Sample"
+
+local window = nil
+local dragBeginPosition = IntVector2(0, 0)
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Enable OS cursor
+ input.mouseVisible = true
+
+ -- Load XML file containing default UI style sheet
+ local style = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+
+ -- Set the loaded style as default style
+ ui.root.defaultStyle = style
+
+ -- Initialize Window
+ InitWindow()
+
+ -- Create and add some controls to the Window
+ InitControls()
+
+ -- Create a draggable Fish
+ CreateDraggableFish()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+end
+
+function InitControls()
+ -- Create a CheckBox
+ local checkBox = CheckBox:new()
+ checkBox:SetName("CheckBox")
+
+ -- Create a Button
+ local button = Button:new()
+ button:SetName("Button")
+ button.minHeight = 24
+
+ -- Create a LineEdit
+ local lineEdit = LineEdit:new()
+ lineEdit:SetName("LineEdit")
+ lineEdit.minHeight = 24
+
+ -- Add controls to Window
+ window:AddChild(checkBox)
+ window:AddChild(button)
+ window:AddChild(lineEdit)
+
+ -- Apply previously set default style
+ checkBox:SetStyleAuto()
+ button:SetStyleAuto()
+ lineEdit:SetStyleAuto()
+end
+
+function InitWindow()
+ -- Create the Window and add it to the UI's root node
+ window = Window:new()
+ ui.root:AddChild(window)
+
+ -- Set Window size and layout settings
+ window.minWidth = 384
+ window:SetLayout(LM_VERTICAL, 6, IntRect(6, 6, 6, 6))
+ window:SetAlignment(HA_CENTER, VA_CENTER)
+ window:SetName("Window")
+
+ -- Create Window 'titlebar' container
+ local titleBar = UIElement:new()
+ titleBar:SetMinSize(0, 24)
+ titleBar.verticalAlignment = VA_TOP
+ titleBar.layoutMode = LM_HORIZONTAL
+
+ -- Create the Window title Text
+ local windowTitle = Text:new()
+ windowTitle.name = "WindowTitle"
+ windowTitle.text = "Hello GUI!"
+
+
+ -- Create the Window's close button
+ local buttonClose = Button:new()
+ buttonClose:SetName("CloseButton")
+
+ -- Add the controls to the title bar
+ titleBar:AddChild(windowTitle)
+ titleBar:AddChild(buttonClose)
+
+ -- Add the title bar to the Window
+ window:AddChild(titleBar)
+
+
+ -- Apply styles
+ window:SetStyleAuto()
+ windowTitle:SetStyleAuto()
+ buttonClose:SetStyle("CloseButton")
+
+ -- Subscribe to buttonClose release (following a 'press') events
+ SubscribeToEvent(buttonClose, "Released",
+ function (eventType, eventData)
+ engine:Exit()
+ end)
+
+ -- Subscribe also to all UI mouse clicks just to see where we have clicked
+ SubscribeToEvent("UIMouseClick", HandleControlClicked)
+end
+
+function CreateDraggableFish()
+ -- Create a draggable Fish button
+ local draggableFish = ui.root:CreateChild("Button", "Fish")
+ draggableFish.texture = cache:GetResource("Texture2D", "Textures/UrhoDecal.dds") -- Set texture
+ draggableFish.blendMode = BLEND_ADD
+ draggableFish:SetSize(128, 128)
+ draggableFish:SetPosition((GetGraphics().width - draggableFish.width) / 2, 200)
+
+ -- Add a tooltip to Fish button
+ local toolTip = draggableFish:CreateChild("ToolTip")
+ toolTip.position = IntVector2(draggableFish.width + 5, draggableFish.width/2) -- Slightly offset from fish
+ local textHolder = toolTip:CreateChild("BorderImage")
+ textHolder:SetStyle("ToolTipBorderImage")
+ local toolTipText = textHolder:CreateChild("Text")
+ toolTipText:SetStyle("ToolTipText")
+ toolTipText.text = "Please drag me!"
+
+ -- Subscribe draggableFish to Drag Events (in order to make it draggable)
+ -- See "Event list" in documentation's Main Page for reference on available Events and their eventData
+ SubscribeToEvent(draggableFish, "DragBegin",
+ function (eventType, eventData)
+ -- Get UIElement relative position where input (touch or click) occurred (top-left = IntVector2(0,0))
+ dragBeginPosition = IntVector2(eventData["ElementX"]:GetInt(), eventData["ElementY"]:GetInt())
+ end)
+
+ SubscribeToEvent(draggableFish, "DragMove",
+ function (eventType, eventData)
+ local dragCurrentPosition = IntVector2(eventData["X"]:GetInt(), eventData["Y"]:GetInt())
+ -- Get the dragged fish element
+ -- Note difference to C++: in C++ we would call GetPtr() and cast the pointer to UIElement, here we must specify
+ -- what kind of object we are getting. Null will be returned on type mismatch
+ local draggedElement = eventData["Element"]:GetPtr("UIElement")
+ draggedElement:SetPosition(dragCurrentPosition - dragBeginPosition)
+ end)
+
+ SubscribeToEvent(draggableFish, "DragEnd",
+ function (eventType, eventData)
+ end)
+end
+
+function HandleControlClicked(eventType, eventData)
+ -- Get the Text control acting as the Window's title
+ local element = window:GetChild("WindowTitle", true)
+ local windowTitle = tolua.cast(element, 'Text')
+
+ -- Get control that was clicked
+ local clicked = eventData["Element"]:GetPtr("UIElement")
+ local name = "...?"
+ if clicked ~= nil then
+ -- Get the name of the control that was clicked
+ name = clicked.name
+ end
+
+ -- Update the Window's title text
+ windowTitle.text = "Hello " .. name .. "!"
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/03_Sprites.lua b/bin/Data/LuaScripts/03_Sprites.lua
new file mode 100644
index 0000000..ccf46e2
--- /dev/null
+++ b/bin/Data/LuaScripts/03_Sprites.lua
@@ -0,0 +1,110 @@
+-- Moving sprites example.
+-- This sample demonstrates:
+-- - Adding Sprite elements to the UI
+-- - Storing custom data (sprite velocity) inside UI elements
+-- - Handling frame update events in which the sprites are moved
+
+require "LuaScripts/Utilities/Sample"
+
+local numSprites = 100
+local sprites = {}
+
+-- Custom variable identifier for storing sprite velocity within the UI element
+local VAR_VELOCITY = StringHash("Velocity")
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the sprites to the user interface
+ CreateSprites()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateSprites()
+ local decalTex = cache:GetResource("Texture2D", "Textures/UrhoDecal.dds")
+
+ local width = graphics.width
+ local height = graphics.height
+
+ for i = 1, numSprites do
+ -- Create a new sprite, set it to use the texture
+ local sprite = Sprite:new()
+ sprite.texture = decalTex
+ sprite:SetFullImageRect()
+
+ -- The UI root element is as big as the rendering window, set random position within it
+ sprite.position = Vector2(Random(width), Random(height))
+
+ -- Set sprite size & hotspot in its center
+ sprite:SetSize(128, 128)
+ sprite.hotSpot = IntVector2(64, 64)
+
+ -- Set random rotation in degrees and random scale
+ sprite.rotation = Random(360.0)
+ sprite.scale = Vector2(1.0, 1.0) * (Random(1.0) + 0.5)
+
+ -- Set random color and additive blending mode
+ sprite:SetColor(Color(Random(0.5) + 0.5, Random(0.5) + 0.5, Random(0.5) + 0.5, 1.0))
+ sprite.blendMode = BLEND_ADD
+
+ -- Add as a child of the root UI element
+ ui.root:AddChild(sprite)
+
+ -- Store sprite's velocity as a custom variable
+ sprite:SetVar(VAR_VELOCITY, Variant(Vector2(Random(200.0) - 100.0, Random(200.0) - 100.0)))
+
+ table.insert(sprites, sprite)
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function MoveSprites(timeStep)
+ local width = graphics.width
+ local height = graphics.height
+
+ for i = 1, numSprites do
+ local sprite = sprites[i]
+ sprite.rotation = sprite.rotation + timeStep * 30
+
+ local newPos = sprite.position
+ newPos = newPos + sprite:GetVar(VAR_VELOCITY):GetVector2() * timeStep
+
+ if newPos.x >= width then
+ newPos.x = newPos.x - width
+ elseif newPos.x < 0 then
+ newPos.x = newPos.x + width
+ end
+ if newPos.y >= height then
+ newPos.y = newPos.y - height
+ elseif newPos.y < 0 then
+ newPos.y = newPos.y + height
+ end
+ sprite.position = newPos
+ end
+end
+
+function HandleUpdate(eventType, eventData)
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ MoveSprites(timeStep)
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/04_StaticScene.lua b/bin/Data/LuaScripts/04_StaticScene.lua
new file mode 100644
index 0000000..fabd2f5
--- /dev/null
+++ b/bin/Data/LuaScripts/04_StaticScene.lua
@@ -0,0 +1,148 @@
+-- Static 3D scene example.
+-- This sample demonstrates:
+-- - Creating a 3D scene with static content
+-- - Displaying the scene using the Renderer subsystem
+-- - Handling keyboard and mouse input to move a freelook camera
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+
+ -- Create a child scene node (at world origin) and a StaticModel component into it. Set the StaticModel to show a simple
+ -- plane mesh with a "stone" material. Note that naming the scene nodes is optional. Scale the scene node larger
+ -- (100 x 100 world units)
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3(100.0, 1.0, 100.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Create a directional light to the world so that we can see something. The light scene node's orientation controls the
+ -- light direction we will use the SetDirection() function which calculates the orientation from a forward direction vector.
+ -- The light will use default settings (white light, no shadows)
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8) -- The direction vector does not need to be normalized
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+
+ -- Create more StaticModel objects to the scene, randomly positioned, rotated and scaled. For rotation, we construct a
+ -- quaternion from Euler angles where the Y angle (rotation about the Y axis) is randomized. The mushroom model contains
+ -- LOD levels, so the StaticModel component will automatically select the LOD level according to the view distance (you'll
+ -- see the model get simpler as it moves further away). Finally, rendering a large number of the same object with the
+ -- same material allows instancing to be used, if the GPU supports it. This reduces the amount of CPU work in rendering the
+ -- scene.
+ local NUM_OBJECTS = 200
+ for i = 1, NUM_OBJECTS do
+ local mushroomNode = scene_:CreateChild("Mushroom")
+ mushroomNode.position = Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0)
+ mushroomNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+ mushroomNode:SetScale(0.5 + Random(2.0))
+ local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+ mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ mushroomObject.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ end
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ cameraNode:CreateComponent("Camera")
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 5.0, 0.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw +MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ -- Use the Translate() function (default local space) to move relative to the node's orientation.
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
diff --git a/bin/Data/LuaScripts/05_AnimatingScene.lua b/bin/Data/LuaScripts/05_AnimatingScene.lua
new file mode 100644
index 0000000..c579adc
--- /dev/null
+++ b/bin/Data/LuaScripts/05_AnimatingScene.lua
@@ -0,0 +1,160 @@
+-- Animating 3D scene example.
+-- This sample demonstrates:
+-- - Creating a 3D scene and using a script component to animate the objects
+-- - Controlling scene ambience with the Zone component
+-- - Attaching a light to an object (the camera)
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene so that drawable objects can be rendered. Use default volume
+ -- (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_:CreateComponent("Octree")
+
+ -- Create a Zone component into a child scene node. The Zone controls ambient lighting and fog settings. Like the Octree,
+ -- it also defines its volume with a bounding box, but can be rotated (so it does not need to be aligned to the world X, Y
+ -- and Z axes.) Drawable objects "pick up" the zone they belong to and use it when rendering several zones can exist
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ -- Set same volume as the Octree, set a close bluish fog and some ambient light
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.05, 0.1, 0.15)
+ zone.fogColor = Color(0.1, 0.2, 0.3)
+ zone.fogStart = 10.0
+ zone.fogEnd = 100.0
+
+ -- Create randomly positioned and oriented box StaticModels in the scene
+ local NUM_OBJECTS = 2000
+ for i = 1, NUM_OBJECTS do
+ local boxNode = scene_:CreateChild("Box")
+ boxNode.position = Vector3(Random(200.0) - 100.0, Random(200.0) - 100.0, Random(200.0) - 100.0)
+ -- Orient using random pitch, yaw and roll Euler angles
+ boxNode.rotation = Quaternion(Random(360.0), Random(360.0), Random(360.0))
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+
+ -- Add the Rotator script object which will rotate the scene node each frame, when the scene sends its update event.
+ -- This requires the C++ component LuaScriptInstance in the scene node, which acts as a container. We need to tell the
+ -- class name to instantiate the object
+
+
+ local object = boxNode:CreateScriptObject("Rotator")
+ object.rotationSpeed = {10.0, 20.0, 30.0}
+ end
+
+ -- Create the camera. Let the starting position be at the world origin. As the fog limits maximum visible distance, we can
+ -- bring the far clip plane closer for more effective culling of distant objects
+ cameraNode = scene_:CreateChild("Camera")
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 100.0
+
+ -- Create a point light to the camera scene node
+ light = cameraNode:CreateComponent("Light")
+ light.lightType = LIGHT_POINT
+ light.range = 30.0
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+-- Rotator script object class. Script objects to be added to a scene node must implement the empty ScriptObject interface
+Rotator = ScriptObject()
+
+function Rotator:Start()
+ self.rotationSpeed = {0.0, 0.0, 0.0}
+end
+
+function Rotator:Update(timeStep)
+ local x = self.rotationSpeed[1] * timeStep
+ local y = self.rotationSpeed[2] * timeStep
+ local z = self.rotationSpeed[3] * timeStep
+ self.node:Rotate(Quaternion(x, y, z))
+end
diff --git a/bin/Data/LuaScripts/06_SkeletalAnimation.lua b/bin/Data/LuaScripts/06_SkeletalAnimation.lua
new file mode 100644
index 0000000..40b536b
--- /dev/null
+++ b/bin/Data/LuaScripts/06_SkeletalAnimation.lua
@@ -0,0 +1,241 @@
+-- Skeletal animation example.
+-- This sample demonstrates:
+-- - Populating a 3D scene with skeletally animated AnimatedModel components
+-- - Moving the animated models and advancing their animation using a script object
+-- - Enabling a cascaded shadow map on a directional light, which allows high-quality shadows
+-- over a large area (typically used in outdoor scenes for shadows cast by sunlight)
+-- - Displaying renderer debug geometry
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update and render post-update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ -- Also create a DebugRenderer component so that we can draw debug geometry
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create scene node & StaticModel component for showing a static plane
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3(50.0, 1.0, 50.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.5, 0.5, 0.5)
+ zone.fogColor = Color(0.4, 0.5, 0.8)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.color = Color(0.5, 0.5, 0.5)
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ -- Create animated models
+ local uint NUM_MODELS = 30
+ local MODEL_MOVE_SPEED = 2.0
+ local MODEL_ROTATE_SPEED = 100.0
+ local bounds = BoundingBox(Vector3(-20.0, 0.0, -20.0), Vector3(20.0, 0.0, 20.0))
+
+ for i = 1, NUM_MODELS do
+ local modelNode = scene_:CreateChild("Jill")
+ modelNode.position = Vector3(Random(40.0) - 20.0, 0.0, Random(40.0) - 20.0)
+ modelNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+
+ local modelObject = modelNode:CreateComponent("AnimatedModel")
+ modelObject.model = cache:GetResource("Model", "Models/Kachujin/Kachujin.mdl")
+ modelObject.material = cache:GetResource("Material", "Models/Kachujin/Materials/Kachujin.xml")
+ modelObject.castShadows = true
+
+ -- Create an AnimationState for a walk animation. Its time position will need to be manually updated to advance the
+ -- animation, The alternative would be to use an AnimationController component which updates the animation automatically,
+ -- but we need to update the model's position manually in any case
+ local walkAnimation = cache:GetResource("Animation", "Models/Kachujin/Kachujin_Walk.ani")
+ local state = modelObject:AddAnimationState(walkAnimation)
+ -- Enable full blending weight and looping
+ state.weight = 1.0
+ state.looped = true
+ state.time = Random(walkAnimation.length)
+
+ -- Create our Mover script object that will move & animate the model during each frame's update.
+
+ local object = modelNode:CreateScriptObject("Mover")
+ object:SetParameters(MODEL_MOVE_SPEED, MODEL_ROTATE_SPEED, bounds)
+ end
+
+ -- Create the camera. Limit far clip distance to match the fog
+ cameraNode = scene_:CreateChild("Camera")
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 5.0, 0.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move\n"..
+ "Space to toggle debug geometry")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, sent after Renderer subsystem is
+ -- done with defining the draw calls for the viewports (but before actually executing them.) We will request debug geometry
+ -- rendering during that event
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ -- Toggle debug geometry with space
+ if input:GetKeyPress(KEY_SPACE) then
+ drawDebug = not drawDebug
+ end
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ -- If draw debug mode is enabled, draw viewport debug geometry, which will show eg. drawable bounding boxes and skeleton
+ -- bones. Note that debug geometry has to be separately requested each frame. Disable depth test so that we can see the
+ -- bones properly
+ if drawDebug then
+ renderer:DrawDebugGeometry(false)
+ end
+end
+
+-- Mover script object class
+Mover = ScriptObject()
+
+function Mover:Start()
+ self.moveSpeed = 0.0
+ self.rotationSpeed = 0.0
+ self.bounds = BoundingBox()
+end
+
+function Mover:SetParameters(moveSpeed, rotationSpeed, bounds)
+ self.moveSpeed = moveSpeed
+ self.rotationSpeed = rotationSpeed
+ self.bounds = bounds
+end
+
+function Mover:Update(timeStep)
+ local node = self.node
+ node:Translate(Vector3(0.0, 0.0, 1.0) * self.moveSpeed * timeStep)
+
+ -- If in risk of going outside the plane, rotate the model right
+ local pos = node.position
+ local bounds = self.bounds
+ if pos.x < bounds.min.x or pos.x > bounds.max.x or pos.z < bounds.min.z or pos.z > bounds.max.z then
+ node:Yaw(self.rotationSpeed * timeStep)
+ end
+
+ -- Get the model's first (only) animation state and advance its time
+ local model = node:GetComponent("AnimatedModel", true)
+ local state = model:GetAnimationState(0)
+ if state ~= nil then
+ state:AddTime(timeStep)
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ ""..
+ " "..
+ " Debug"..
+ " "..
+ " "..
+ " "..
+ " "..
+ " "..
+ " "..
+ ""
+end
diff --git a/bin/Data/LuaScripts/07_Billboards.lua b/bin/Data/LuaScripts/07_Billboards.lua
new file mode 100644
index 0000000..99ff59d
--- /dev/null
+++ b/bin/Data/LuaScripts/07_Billboards.lua
@@ -0,0 +1,285 @@
+-- Billboard example.
+-- This sample demonstrates:
+-- - Populating a 3D scene with billboard sets and several shadow casting spotlights
+-- - Parenting scene nodes to allow more intuitive creation of groups of objects
+-- - Examining rendering performance with a somewhat large object and light count
+
+require "LuaScripts/Utilities/Sample"
+
+local lightNodes = {}
+local billboardNodes = {}
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update and render post-update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ -- Also create a DebugRenderer component so that we can draw debug geometry
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.1, 0.1, 0.1)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light without shadows
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.5, -1.0, 0.5)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.color = Color(0.2, 0.2, 0.2)
+ light.specularIntensity = 1.0
+
+ -- Create a "floor" consisting of several tiles
+ for y = -5, 5 do
+ for x = -5, 5 do
+ local floorNode = scene_:CreateChild("FloorTile")
+ floorNode.position = Vector3(x * 20.5, -0.5, y * 20.5)
+ floorNode.scale = Vector3(20.0, 1.0, 20.0)
+ local floorObject = floorNode:CreateComponent("StaticModel")
+ floorObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ floorObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+ end
+ end
+
+ -- Create groups of mushrooms, which act as shadow casters
+ local NUM_MUSHROOMGROUPS = 25
+ local NUM_MUSHROOMS = 25
+
+ for i = 1, NUM_MUSHROOMGROUPS do
+ -- First create a scene node for the group. The individual mushrooms nodes will be created as children
+ local groupNode = scene_:CreateChild("MushroomGroup")
+ groupNode.position = Vector3(Random(190.0) - 95.0, 0.0, Random(190.0) - 95.0)
+
+ for j = 1, NUM_MUSHROOMS do
+ local mushroomNode = groupNode:CreateChild("Mushroom")
+ mushroomNode.position = Vector3(Random(25.0) - 12.5, 0.0, Random(25.0) - 12.5)
+ mushroomNode.rotation = Quaternion(0.0, Random() * 360.0, 0.0)
+ mushroomNode:SetScale(1.0 + Random() * 4.0)
+ local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+ mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ mushroomObject.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ mushroomObject.castShadows = true
+ end
+ end
+
+ -- Create billboard sets (floating smoke)
+ local NUM_BILLBOARDNODES = 25
+ local NUM_BILLBOARDS = 10
+
+ for i = 1, NUM_BILLBOARDNODES do
+ local smokeNode = scene_:CreateChild("Smoke")
+ smokeNode.position = Vector3(Random(200.0) - 100.0, Random(20.0) + 10.0, Random(200.0) - 100.0)
+
+ local billboardObject = smokeNode:CreateComponent("BillboardSet")
+ billboardObject.numBillboards = NUM_BILLBOARDS
+ billboardObject.material = cache:GetResource("Material", "Materials/LitSmoke.xml")
+ billboardObject.sorted = true
+
+ for j = 1, NUM_BILLBOARDS do
+ local bb = billboardObject:GetBillboard(j - 1)
+ bb.position = Vector3(Random(12.0) - 6.0, Random(8.0) - 4.0, Random(12.0) - 6.0)
+ bb.size = Vector2(Random(2.0) + 3.0, Random(2.0) + 3.0)
+ bb.rotation = Random() * 360.0
+ bb.enabled = true
+ end
+
+ -- After modifying the billboards, they need to be "committed" so that the BillboardSet updates its internals
+ billboardObject:Commit()
+
+ table.insert(billboardNodes, smokeNode)
+ end
+
+ -- Create shadow casting spotlights
+ local NUM_LIGHTS = 9
+
+ for i = 0, NUM_LIGHTS - 1 do
+ local lightNode = scene_:CreateChild("SpotLight")
+ local light = lightNode:CreateComponent("Light")
+
+ local angle = 0.0
+
+ local position = Vector3((i % 3) * 60.0 - 60.0, 45.0, math.floor(i / 3) * 60.0 - 60.0)
+ local color = Color(((i + 1) % 2) * 0.5 + 0.5, (math.floor((i + 1) / 2) % 2) * 0.5 + 0.5, (math.floor((i + 1) / 4) % 2) * 0.5 + 0.5)
+
+ lightNode.position = position
+ lightNode.direction = Vector3(math.sin(M_DEGTORAD * angle), -1.5, math.cos(M_DEGTORAD * angle))
+
+ light.lightType = LIGHT_SPOT
+ light.range = 90.0
+ light.rampTexture = cache:GetResource("Texture2D", "Textures/RampExtreme.png")
+ light.fov = 45.0
+ light.color = color
+ light.specularIntensity = 1.0
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00002, 0.0)
+
+ -- Configure shadow fading for the lights. When they are far away enough, the lights eventually become unshadowed for
+ -- better GPU performance. Note that we could also set the maximum distance for each object to cast shadows
+ light.shadowFadeDistance = 100.0 -- Fade start distance
+ light.shadowDistance = 125.0 -- Fade end distance, shadows are disabled
+ -- Set half resolution for the shadow maps for increased performance
+ light.shadowResolution = 0.5
+ -- The spot lights will not have anything near them, so move the near plane of the shadow camera farther
+ -- for better shadow depth resolution
+ light.shadowNearFarRatio = 0.01
+
+ table.insert(lightNodes, lightNode)
+ end
+
+ -- Create the camera. Limit far clip distance to match the fog
+ cameraNode = scene_:CreateChild("Camera")
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 5.0, 0.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText.text =
+ "Use WASD keys and mouse to move\n"..
+ "Space to toggle debug geometry"
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ -- debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ -- Toggle debug geometry with space
+ if input:GetKeyPress(KEY_SPACE) then
+ drawDebug = not drawDebug
+ end
+end
+
+function AnimateScene(timeStep)
+ local LIGHT_ROTATION_SPEED = 20.0
+ local BILLBOARD_ROTATION_SPEED = 50.0
+
+ -- Rotate the lights around the world Y-axis
+ for i, v in ipairs(lightNodes) do
+ v:Rotate(Quaternion(0.0, LIGHT_ROTATION_SPEED * timeStep, 0.0), TS_WORLD)
+ end
+
+ -- Rotate the individual billboards within the billboard sets, then recommit to make the changes visible
+ for i, v in ipairs(billboardNodes) do
+ local billboardObject = v:GetComponent("BillboardSet")
+
+ for j = 1, billboardObject.numBillboards do
+ local bb = billboardObject:GetBillboard(j - 1)
+ bb.rotation = bb.rotation + BILLBOARD_ROTATION_SPEED * timeStep
+ end
+
+ billboardObject:Commit()
+ end
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera and animate the scene, scale movement with time step
+ MoveCamera(timeStep)
+ AnimateScene(timeStep)
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ -- If draw debug mode is enabled, draw viewport debug geometry. This time use depth test, as otherwise the result becomes
+ -- hard to interpret due to large object count
+ if drawDebug then
+ renderer:DrawDebugGeometry(true)
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Debug" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/08_Decals.lua b/bin/Data/LuaScripts/08_Decals.lua
new file mode 100644
index 0000000..87b1467
--- /dev/null
+++ b/bin/Data/LuaScripts/08_Decals.lua
@@ -0,0 +1,278 @@
+-- Decals example.
+-- This sample demonstrates:
+-- - Performing a raycast to the octree and adding a decal to the hit location
+-- - Defining a Cursor UI element which stays inside the window and can be shown/hidden
+-- - Marking suitable (large) objects as occluders for occlusion culling
+-- - Displaying renderer debug geometry to see the effect of occlusion
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateUI()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update and render post-update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ -- Also create a DebugRenderer component so that we can draw debug geometry
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create scene node & StaticModel component for showing a static plane
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3(100.0, 1.0, 100.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(0.5, 0.5, 0.7)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ -- Create some mushrooms
+ local NUM_MUSHROOMS = 240
+ for i = 1, NUM_MUSHROOMS do
+ local mushroomNode = scene_:CreateChild("Mushroom")
+ mushroomNode.position = Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0)
+ mushroomNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+ mushroomNode:SetScale(0.5 + Random(2.0))
+ local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+ mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ mushroomObject.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ mushroomObject.castShadows = true
+ end
+
+ -- Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+ -- rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+ local NUM_BOXES = 20
+ for i = 1, NUM_BOXES do
+ local boxNode = scene_:CreateChild("Box")
+ local size = 1.0 + Random(10.0)
+ boxNode.position = Vector3(Random(80.0) - 40.0, size * 0.5, Random(80.0) - 40.0)
+ boxNode:SetScale(size)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+ boxObject.castShadows = true
+ if size >= 3.0 then
+ boxObject.occluder = true
+ end
+ end
+
+ -- Create the camera. Limit far clip distance to match the fog
+ cameraNode = scene_:CreateChild("Camera")
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 5.0, 0.0)
+end
+
+function CreateUI()
+ -- Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ -- control the camera, and when visible, it will point the raycast target
+ local style = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ local cursor = ui.root:CreateChild("Cursor")
+ cursor:SetStyleAuto(style)
+ ui.cursor = cursor
+ -- Set starting position of the cursor at the rendering window center
+ cursor:SetPosition(graphics.width / 2, graphics.height / 2)
+
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText.text =
+ "Use WASD keys to move\n"..
+ "LMB to paint decals, RMB to rotate view\n"..
+ "Space to toggle debug geometry\n"..
+ "7 to toggle occlusion culling"
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ -- debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+end
+
+function MoveCamera(timeStep)
+ input.mouseVisible = input.mouseMode ~= MM_RELATIVE
+ mouseDown = input:GetMouseButtonDown(MOUSEB_RIGHT)
+
+ -- Override the MM_RELATIVE mouse grabbed settings, to allow interaction with UI
+ input.mouseGrabbed = mouseDown
+
+ -- Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = not mouseDown
+
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ -- Only move the camera when the cursor is hidden
+ if not ui.cursor.visible then
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+ end
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ -- Toggle debug geometry with space
+ if input:GetKeyPress(KEY_SPACE) then
+ drawDebug = not drawDebug
+ end
+
+ -- Paint decal with the left mousebutton cursor must be visible
+ if ui.cursor.visible and input:GetMouseButtonPress(MOUSEB_LEFT) then
+ PaintDecal()
+ end
+end
+
+function PaintDecal()
+ local hitPos, hitDrawable = Raycast(250.0)
+ if hitDrawable ~= nil then
+ -- Check if target scene node already has a DecalSet component. If not, create now
+ local targetNode = hitDrawable.node
+ local decal = targetNode:GetComponent("DecalSet")
+ if decal == nil then
+ decal = targetNode:CreateComponent("DecalSet")
+ decal.material = cache:GetResource("Material", "Materials/UrhoDecal.xml")
+ end
+ -- Add a square decal to the decal set using the geometry of the drawable that was hit, orient it to face the camera,
+ -- use full texture UV's (0,0) to (1,1). Note that if we create several decals to a large object (such as the ground
+ -- plane) over a large area using just one DecalSet component, the decals will all be culled as one unit. If that is
+ -- undesirable, it may be necessary to create more than one DecalSet based on the distance
+ decal:AddDecal(hitDrawable, hitPos, cameraNode.rotation, 0.5, 1.0, 1.0, Vector2(0.0, 0.0), Vector2(1.0, 1.0))
+ end
+end
+
+function Raycast(maxDistance)
+ local hitPos = nil
+ local hitDrawable = nil
+
+ local pos = ui.cursorPosition
+ -- Check the cursor is visible and there is no UI element in front of the cursor
+ if (not ui.cursor.visible) or (ui:GetElementAt(pos, true) ~= nil) then
+ return nil, nil
+ end
+
+ local camera = cameraNode:GetComponent("Camera")
+ local cameraRay = camera:GetScreenRay(pos.x / graphics.width, pos.y / graphics.height)
+ -- Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
+ local octree = scene_:GetComponent("Octree")
+ local result = octree:RaycastSingle(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY)
+ if result.drawable ~= nil then
+ return result.position, result.drawable
+ end
+
+ return nil, nil
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ -- If draw debug mode is enabled, draw viewport debug geometry. Disable depth test so that we can see the effect of occlusion
+ if drawDebug then
+ renderer:DrawDebugGeometry(false)
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Paint" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Debug" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/09_MultipleViewports.lua b/bin/Data/LuaScripts/09_MultipleViewports.lua
new file mode 100644
index 0000000..f3e0fd4
--- /dev/null
+++ b/bin/Data/LuaScripts/09_MultipleViewports.lua
@@ -0,0 +1,276 @@
+-- Multiple viewports example.
+-- This sample demonstrates:
+-- - Setting up two viewports with two separate cameras
+-- - Adding post processing effects to a viewport's render path and toggling them
+
+require "LuaScripts/Utilities/Sample"
+
+local rearCameraNode = nil
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewports for displaying the scene
+ SetupViewports()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update and render post-update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ -- Also create a DebugRenderer component so that we can draw debug geometry
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create scene node & StaticModel component for showing a static plane
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3(100.0, 1.0, 100.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(0.5, 0.5, 0.7)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ -- Create some mushrooms
+ local NUM_MUSHROOMS = 240
+ for i = 1, NUM_MUSHROOMS do
+ local mushroomNode = scene_:CreateChild("Mushroom")
+ mushroomNode.position = Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0)
+ mushroomNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+ mushroomNode:SetScale(0.5 + Random(2.0))
+ local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+ mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ mushroomObject.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ mushroomObject.castShadows = true
+ end
+
+ -- Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+ -- rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+ local NUM_BOXES = 20
+ for i = 1, NUM_BOXES do
+ local boxNode = scene_:CreateChild("Box")
+ local size = 1.0 + Random(10.0)
+ boxNode.position = Vector3(Random(80.0) - 40.0, size * 0.5, Random(80.0) - 40.0)
+ boxNode:SetScale(size)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+ boxObject.castShadows = true
+ if size >= 3.0 then
+ boxObject.occluder = true
+ end
+ end
+
+ -- Create the camera. Limit far clip distance to match the fog
+ cameraNode = scene_:CreateChild("Camera")
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+
+ -- Parent the rear camera node to the front camera node and turn it 180 degrees to face backward
+ -- Here, we use the angle-axis constructor for Quaternion instead of the usual Euler angles
+ rearCameraNode = cameraNode:CreateChild("RearCamera")
+ rearCameraNode:Rotate(Quaternion(180.0, Vector3(0.0, 1.0, 0.0)))
+ local rearCamera = rearCameraNode:CreateComponent("Camera")
+ rearCamera.farClip = 300.0
+ -- Because the rear viewport is rather small, disable occlusion culling from it. Use the camera's
+ -- "view override flags" for this. We could also disable eg. shadows or force low material quality
+ -- if we wanted
+ rearCamera.viewOverrideFlags = VO_DISABLE_OCCLUSION
+
+ -- Set an initial position for the front camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 5.0, 0.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText.text =
+ "Use WASD keys and mouse to move\n"..
+ "B to toggle bloom, F to toggle FXAA\n"..
+ "Space to toggle debug geometry\n"
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewports()
+ renderer.numViewports = 2
+
+ -- Set up the front camera viewport
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+
+ -- Clone the default render path so that we do not interfere with the other viewport, then add
+ -- bloom and FXAA post process effects to the front viewport. Render path commands can be tagged
+ -- for example with the effect name to allow easy toggling on and off. We start with the effects
+ -- disabled.
+ local effectRenderPath = viewport:GetRenderPath():Clone()
+ effectRenderPath:Append(cache:GetResource("XMLFile", "PostProcess/Bloom.xml"))
+ effectRenderPath:Append(cache:GetResource("XMLFile", "PostProcess/FXAA2.xml"))
+ -- Make the bloom mixing parameter more pronounced
+ effectRenderPath:SetShaderParameter("BloomMix", Variant(Vector2(0.9, 0.6)))
+ effectRenderPath:SetEnabled("Bloom", false)
+ effectRenderPath:SetEnabled("FXAA2", false)
+ viewport:SetRenderPath(effectRenderPath)
+
+ -- Set up the rear camera viewport on top of the front view ("rear view mirror")
+ -- The viewport index must be greater in that case, otherwise the view would be left behind
+ local rearViewport = Viewport:new(scene_, rearCameraNode:GetComponent("Camera"),
+ IntRect(graphics.width * 2 / 3, 32, graphics.width - 32, graphics.height / 3))
+ renderer:SetViewport(1, rearViewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ -- debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ -- Toggle post processing effects on the front viewport. Note that the rear viewport is unaffected
+ local effectRenderPath = renderer:GetViewport(0).renderPath
+ if input:GetKeyPress(KEY_B) then
+ effectRenderPath:ToggleEnabled("Bloom")
+ end
+ if input:GetKeyPress(KEY_F) then
+ effectRenderPath:ToggleEnabled("FXAA2")
+ end
+
+ -- Toggle debug geometry with space
+ if input:GetKeyPress(KEY_SPACE) then
+ drawDebug = not drawDebug
+ end
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ -- If draw debug mode is enabled, draw viewport debug geometry. Disable depth test so that we can see the effect of occlusion
+ if drawDebug then
+ renderer:DrawDebugGeometry(false)
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Bloom" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Debug" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/10_RenderToTexture.lua b/bin/Data/LuaScripts/10_RenderToTexture.lua
new file mode 100644
index 0000000..ff8e476
--- /dev/null
+++ b/bin/Data/LuaScripts/10_RenderToTexture.lua
@@ -0,0 +1,236 @@
+-- Render to texture example
+-- This sample demonstrates:
+-- - Creating two 3D scenes and rendering the other into a texture
+-- - Creating rendertarget textures and materials programmatically
+
+require "LuaScripts/Utilities/Sample"
+
+local rttScene_ = nil
+local rttCameraNode = nil
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ -- Create the scene which will be rendered to a texture
+ rttScene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ rttScene_:CreateComponent("Octree")
+
+ -- Create a Zone for ambient light & fog control
+ local zoneNode = rttScene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ -- Set same volume as the Octree, set a close bluish fog and some ambient light
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.05, 0.1, 0.15)
+ zone.fogColor = Color(0.1, 0.2, 0.3)
+ zone.fogStart = 10.0
+ zone.fogEnd = 100.0
+
+ -- Create randomly positioned and oriented box StaticModels in the scene
+ local NUM_OBJECTS = 2000
+ for i = 1, NUM_OBJECTS do
+ local boxNode = rttScene_:CreateChild("Box")
+ boxNode.position = Vector3(Random(200.0) - 100.0, Random(200.0) - 100.0, Random(200.0) - 100.0)
+ -- Orient using random pitch, yaw and roll Euler angles
+ boxNode.rotation = Quaternion(Random(360.0), Random(360.0), Random(360.0))
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+
+ -- Add our custom Rotator component which will rotate the scene node each frame, when the scene sends its update event.
+ -- Simply set same rotation speed for all objects
+ local rotator = boxNode:CreateScriptObject("Rotator")
+ rotator.rotationSpeed = { 10.0, 20.0, 30.0 }
+ end
+
+ -- Create a camera for the render-to-texture scene. Simply leave it at the world origin and let it observe the scene
+ rttCameraNode = rttScene_:CreateChild("Camera")
+ local camera = rttCameraNode:CreateComponent("Camera")
+ camera.farClip = 100.0
+
+ -- Create a point light to the camera scene node
+ local light = rttCameraNode:CreateComponent("Light")
+ light.lightType = LIGHT_POINT
+ light.range = 30.0
+
+ -- Create the scene in which we move around
+ scene_ = Scene()
+
+ -- Create octree, use also default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_:CreateComponent("Octree")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.1, 0.1, 0.1)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light without shadows
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.5, -1.0, 0.5)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.color = Color(0.2, 0.2, 0.2)
+ light.specularIntensity = 1.0
+
+ -- Create a "floor" consisting of several tiles
+ for y = -5, 5 do
+ for x = -5, 5 do
+ local floorNode = scene_:CreateChild("FloorTile")
+ floorNode.position = Vector3(x * 20.5, -0.5, y * 20.5)
+ floorNode.scale = Vector3(20.0, 1.0, 20.)
+ local floorObject = floorNode:CreateComponent("StaticModel")
+ floorObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ floorObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+ end
+ end
+
+ -- Create a "screen" like object for viewing the second scene. Construct it from two StaticModels, a box for the frame
+ -- and a plane for the actual view
+ local boxNode = scene_:CreateChild("ScreenBox")
+ boxNode.position = Vector3(0.0, 10.0, 0.0)
+ boxNode.scale = Vector3(21.0, 16.0, 0.5)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+
+ local screenNode = scene_:CreateChild("Screen")
+ screenNode.position = Vector3(0.0, 10.0, -0.27)
+ screenNode.rotation = Quaternion(-90.0, 0.0, 0.0)
+ screenNode.scale = Vector3(20.0, 0.0, 15.0)
+ local screenObject = screenNode:CreateComponent("StaticModel")
+ screenObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+
+ -- Create a renderable texture (1024x768, RGB format), enable bilinear filtering on it
+ local renderTexture = Texture2D:new()
+ renderTexture:SetSize(1024, 768, Graphics:GetRGBFormat(), TEXTURE_RENDERTARGET)
+ renderTexture.filterMode = FILTER_BILINEAR
+
+ -- Create a new material from scratch, use the diffuse unlit technique, assign the render texture
+ -- as its diffuse texture, then assign the material to the screen plane object
+ local renderMaterial = Material:new()
+ renderMaterial:SetTechnique(0, cache:GetResource("Technique", "Techniques/DiffUnlit.xml"))
+ renderMaterial:SetTexture(TU_DIFFUSE, renderTexture)
+ -- Since the screen material is on top of the box model and may Z-fight, use negative depth bias
+ -- to push it forward (particularly necessary on mobiles with possibly less Z resolution)
+ renderMaterial.depthBias = BiasParameters(-0.001, 0.0)
+ screenObject.material = renderMaterial
+
+ -- Get the texture's RenderSurface object (exists when the texture has been created in rendertarget mode)
+ -- and define the viewport for rendering the second scene, similarly as how backbuffer viewports are defined
+ -- to the Renderer subsystem. By default the texture viewport will be updated when the texture is visible
+ -- in the main view
+ local surface = renderTexture.renderSurface
+ local rttViewport = Viewport:new(rttScene_, rttCameraNode:GetComponent("Camera"))
+ surface:SetViewport(0, rttViewport)
+
+ -- Create the camera which we will move around. Limit far clip distance to match the fog
+ cameraNode = scene_:CreateChild("Camera")
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 7.0, -30.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText.text = "Use WASD keys and mouse to move"
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ local delta = MOVE_SPEED * timeStep
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+-- Rotator script object class. Script objects to be added to a scene node must implement the empty ScriptObject interface
+Rotator = ScriptObject()
+
+function Rotator:Start()
+ self.rotationSpeed = {0.0, 0.0, 0.0}
+end
+
+-- Update is called during the variable timestep scene update
+function Rotator:Update(timeStep)
+ local x = self.rotationSpeed[1] * timeStep
+ local y = self.rotationSpeed[2] * timeStep
+ local z = self.rotationSpeed[3] * timeStep
+ self.node:Rotate(Quaternion(x, y, z))
+end
diff --git a/bin/Data/LuaScripts/11_Physics.lua b/bin/Data/LuaScripts/11_Physics.lua
new file mode 100644
index 0000000..93d232f
--- /dev/null
+++ b/bin/Data/LuaScripts/11_Physics.lua
@@ -0,0 +1,266 @@
+-- Physics example.
+-- This sample demonstrates:
+-- - Creating both static and moving physics objects to a scene
+-- - Displaying physics debug geometry
+-- - Using the Skybox component for setting up an unmoving sky
+-- - Saving a scene to a file and loading it to restore a previous state
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update and render post-update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ -- Create a physics simulation world with default parameters, which will update at 60fps. Like the Octree must
+ -- exist before creating drawable components, the PhysicsWorld must exist before creating physics components.
+ -- Finally, create a DebugRenderer component so that we can draw physics debug geometry
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("PhysicsWorld")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(1.0, 1.0, 1.0)
+ zone.fogStart = 300.0
+ zone.fogEnd = 500.0
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ -- Create skybox. The Skybox component is used like StaticModel, but it will be always located at the camera, giving the
+ -- illusion of the box planes being far away. Use just the ordinary Box model and a suitable material, whose shader will
+ -- generate the necessary 3D texture coordinates for cube mapping
+ local skyNode = scene_:CreateChild("Sky")
+ skyNode:SetScale(500.0) -- The scale actually does not matter
+ local skybox = skyNode:CreateComponent("Skybox")
+ skybox.model = cache:GetResource("Model", "Models/Box.mdl")
+ skybox.material = cache:GetResource("Material", "Materials/Skybox.xml")
+
+ -- Create a floor object, 1000 x 1000 world units. Adjust position so that the ground is at zero Y
+ local floorNode = scene_:CreateChild("Floor")
+ floorNode.position = Vector3(0.0, -0.5, 0.0)
+ floorNode.scale = Vector3(1000.0, 1.0, 1000.0)
+ local floorObject = floorNode:CreateComponent("StaticModel")
+ floorObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ floorObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Make the floor physical by adding RigidBody and CollisionShape components. The RigidBody's default
+ -- parameters make the object static (zero mass.) Note that a CollisionShape by itself will not participate
+ -- in the physics simulation
+ local body = floorNode:CreateComponent("RigidBody")
+ local shape = floorNode:CreateComponent("CollisionShape")
+ -- Set a box shape of size 1 x 1 x 1 for collision. The shape will be scaled with the scene node scale, so the
+ -- rendering and physics representation sizes should match (the box model is also 1 x 1 x 1.)
+ shape:SetBox(Vector3(1.0, 1.0, 1.0))
+
+ -- Create a pyramid of movable physics objects
+ for y = 0, 7 do
+ for x = -y, y do
+ local boxNode = scene_:CreateChild("Box")
+ boxNode.position = Vector3(x, -y + 8.0, 0.0)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/StoneEnvMapSmall.xml")
+ boxObject.castShadows = true
+
+ -- Create RigidBody and CollisionShape components like above. Give the RigidBody mass to make it movable
+ -- and also adjust friction. The actual mass is not important only the mass ratios between colliding
+ -- objects are significant
+ local body = boxNode:CreateComponent("RigidBody")
+ body.mass = 1.0
+ body.friction = 0.75
+ local shape = boxNode:CreateComponent("CollisionShape")
+ shape:SetBox(Vector3(1.0, 1.0, 1.0))
+ end
+ end
+
+ -- Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside
+ -- the scene, because we want it to be unaffected by scene load / save
+ cameraNode = Node()
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 500.0
+
+ -- Set an initial position for the camera scene node above the floor
+ cameraNode.position = Vector3(0.0, 5.0, -20.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move\n"..
+ "LMB to spawn physics objects\n"..
+ "F5 to save scene, F7 to load\n"..
+ "Space to toggle physics debug geometry")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ -- debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ -- "Shoot" a physics object with left mousebutton
+ if input:GetMouseButtonPress(MOUSEB_LEFT) then
+ SpawnObject()
+ end
+
+ -- Check for loading/saving the scene. Save the scene to the file Data/Scenes/Physics.xml relative to the executable
+ -- directory
+ if input:GetKeyPress(KEY_F5) then
+ scene_:SaveXML(fileSystem:GetProgramDir().."Data/Scenes/Physics.xml")
+ end
+ if input:GetKeyPress(KEY_F7) then
+ scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/Physics.xml")
+ end
+
+ -- Toggle debug geometry with space
+ if input:GetKeyPress(KEY_SPACE) then
+ drawDebug = not drawDebug
+ end
+end
+
+function SpawnObject()
+ -- Create a smaller box at camera position
+ local boxNode = scene_:CreateChild("SmallBox")
+ boxNode.position = cameraNode.position
+ boxNode.rotation = cameraNode.rotation
+ boxNode:SetScale(0.25)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/StoneEnvMapSmall.xml")
+ boxObject.castShadows = true
+
+ -- Create physics components, use a smaller mass also
+ local body = boxNode:CreateComponent("RigidBody")
+ body.mass = 0.25
+ body.friction = 0.75
+ local shape = boxNode:CreateComponent("CollisionShape")
+ shape:SetBox(Vector3(1.0, 1.0, 1.0))
+
+ local OBJECT_VELOCITY = 10.0
+
+ -- Set initial velocity for the RigidBody based on camera forward vector. Add also a slight up component
+ -- to overcome gravity better
+ body.linearVelocity = cameraNode.rotation * Vector3(0.0, 0.25, 1.0) * OBJECT_VELOCITY
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+
+ -- If draw debug mode is enabled, draw physics debug geometry. Use depth test to make the result easier to interpret
+ -- Note the convenience accessor to the physics world component
+ if drawDebug then
+ scene_:GetComponent("PhysicsWorld"):DrawDebugGeometry(true)
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Spawn" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Debug" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/12_PhysicsStressTest.lua b/bin/Data/LuaScripts/12_PhysicsStressTest.lua
new file mode 100644
index 0000000..f53a4fc
--- /dev/null
+++ b/bin/Data/LuaScripts/12_PhysicsStressTest.lua
@@ -0,0 +1,280 @@
+-- Physics stress test example.
+-- This sample demonstrates:
+-- - Physics and rendering performance with a high (1000) moving object count
+-- - Using triangle meshes for collision
+-- - Optimizing physics simulation by leaving out collision event signaling
+-- - Usage of Lua Coroutine to yield/resume based on time step
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update and render post-update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ -- Create a physics simulation world with default parameters, which will update at 60ps. Like the Octree must
+ -- exist before creating drawable components, the PhysicsWorld must exist before creating physics components.
+ -- Finally, create a DebugRenderer component so that we can draw physics debug geometry
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("PhysicsWorld")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(0.5, 0.5, 0.7)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ if true then
+ -- Create a floor object, 500 x 500 world units. Adjust position so that the ground is at zero Y
+ local floorNode = scene_:CreateChild("Floor")
+ floorNode.position = Vector3(0.0, -0.5, 0.0)
+ floorNode.scale = Vector3(500.0, 1.0, 500.0)
+ local floorObject = floorNode:CreateComponent("StaticModel")
+ floorObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ floorObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Make the floor physical by adding RigidBody and CollisionShape components
+ local body = floorNode:CreateComponent("RigidBody")
+ local shape = floorNode:CreateComponent("CollisionShape")
+ -- Set a box shape of size 1 x 1 x 1 for collision. The shape will be scaled with the scene node scale, so the
+ -- rendering and physics representation sizes should match (the box model is also 1 x 1 x 1.)
+ shape:SetBox(Vector3(1.0, 1.0, 1.0))
+ end
+
+ -- Create static mushrooms with triangle mesh collision
+ local NUM_MUSHROOMS = 50
+ for i = 1, NUM_MUSHROOMS do
+ local mushroomNode = scene_:CreateChild("Mushroom")
+ mushroomNode.position = Vector3(Random(400.0) - 200.0, 0.0, Random(400.0) - 200.0)
+ mushroomNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+ mushroomNode:SetScale(5.0 + Random(5.0))
+ local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+ mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ mushroomObject.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ mushroomObject.castShadows = true
+
+ local body = mushroomNode:CreateComponent("RigidBody")
+ local shape = mushroomNode:CreateComponent("CollisionShape")
+ -- By default the highest LOD level will be used, the LOD level can be passed as an optional parameter
+ shape:SetTriangleMesh(mushroomObject.model)
+ end
+
+ -- Start coroutine to create a large amount of falling physics objects
+ coroutine.start(function()
+ local NUM_OBJECTS = 1000
+ for i = 1, NUM_OBJECTS do
+ local boxNode = scene_:CreateChild("Box")
+ boxNode.position = Vector3(0.0, 100.0, 0.0)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/StoneSmall.xml")
+ boxObject.castShadows = true
+
+ -- Give the RigidBody mass to make it movable and also adjust friction
+ local body = boxNode:CreateComponent("RigidBody")
+ body.mass = 1.0
+ body.friction = 1.0
+
+ -- Set linear velocity
+ body.linearVelocity = Vector3(0.0, -50.0, 0.0)
+
+ -- Disable collision event signaling to reduce CPU load of the physics simulation
+ body.collisionEventMode = COLLISION_NEVER
+ local shape = boxNode:CreateComponent("CollisionShape")
+ shape:SetBox(Vector3(1.0, 1.0, 1.0))
+ -- sleep coroutine
+ coroutine.sleep(0.1)
+ end
+ end)
+
+ -- Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
+ -- the scene, because we want it to be unaffected by scene load/save
+ cameraNode = Node()
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+
+ -- Set an initial position for the camera scene node above the floor
+ cameraNode.position = Vector3(0.0, 3.0, -20.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move\n"..
+ "LMB to spawn physics objects\n"..
+ "F5 to save scene, F7 to load\n"..
+ "Space to toggle physics debug geometry")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ -- debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch +MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ -- "Shoot" a physics object with left mousebutton
+ if input:GetMouseButtonPress(MOUSEB_LEFT) then
+ SpawnObject()
+ end
+
+ -- Check for loading/saving the scene. Save the scene to the file Data/Scenes/Physics.xml relative to the executable
+ -- directory
+ if input:GetKeyPress(KEY_F5) then
+ scene_:SaveXML(fileSystem:GetProgramDir().."Data/Scenes/PhysicsStressTest.xml")
+ end
+ if input:GetKeyPress(KEY_F7) then
+ scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/PhysicsStressTest.xml")
+ end
+
+ -- Toggle debug geometry with space
+ if input:GetKeyPress(KEY_SPACE) then
+ drawDebug = not drawDebug
+ end
+end
+
+function SpawnObject()
+ -- Create a smaller box at camera position
+ local boxNode = scene_:CreateChild("SmallBox")
+ boxNode.position = cameraNode.position
+ boxNode.rotation = cameraNode.rotation
+ boxNode:SetScale(0.25)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/StoneSmall.xml")
+ boxObject.castShadows = true
+
+ -- Create physics components, use a smaller mass also
+ local body = boxNode:CreateComponent("RigidBody")
+ body.mass = 0.25
+ body.friction = 0.75
+ local shape = boxNode:CreateComponent("CollisionShape")
+ shape:SetBox(Vector3(1.0, 1.0, 1.0))
+
+ local OBJECT_VELOCITY = 10.0
+
+ -- Set initial velocity for the RigidBody based on camera forward vector. Add also a slight up component
+ -- to overcome gravity better
+ body.linearVelocity = cameraNode.rotation * Vector3(0.0, 0.25, 1.0) * OBJECT_VELOCITY
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ -- If draw debug mode is enabled, draw physics debug geometry. Use depth test to make the result easier to interpret
+ if drawDebug then
+ scene_:GetComponent("PhysicsWorld"):DrawDebugGeometry(true)
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Spawn" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Debug" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/13_Ragdolls.lua b/bin/Data/LuaScripts/13_Ragdolls.lua
new file mode 100644
index 0000000..0549120
--- /dev/null
+++ b/bin/Data/LuaScripts/13_Ragdolls.lua
@@ -0,0 +1,393 @@
+-- Ragdoll example.
+-- This sample demonstrates:
+-- - Detecting physics collisions
+-- - Moving an AnimatedModel's bones with physics and connecting them with constraints
+-- - Using rolling friction to stop rolling objects from moving infinitely
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update and render post-update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ -- Create a physics simulation world with default parameters, which will update at 60fps. Like the Octree must
+ -- exist before creating drawable components, the PhysicsWorld must exist before creating physics components.
+ -- Finally, create a DebugRenderer component so that we can draw physics debug geometry
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("PhysicsWorld")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(0.5, 0.5, 0.7)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ -- Create a floor object, 500 x 500 world units. Adjust position so that the ground is at zero Y
+ local floorNode = scene_:CreateChild("Floor")
+ floorNode.position = Vector3(0.0, -0.5, 0.0)
+ floorNode.scale = Vector3(500.0, 1.0, 500.0)
+ local floorObject = floorNode:CreateComponent("StaticModel")
+ floorObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ floorObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Make the floor physical by adding RigidBody and CollisionShape components
+ local body = floorNode:CreateComponent("RigidBody")
+ -- We will be spawning spherical objects in this sample. The ground also needs non-zero rolling friction so that
+ -- the spheres will eventually come to rest
+ body.rollingFriction = 0.15
+ local shape = floorNode:CreateComponent("CollisionShape")
+ -- Set a box shape of size 1 x 1 x 1 for collision. The shape will be scaled with the scene node scale, so the
+ -- rendering and physics representation sizes should match (the box model is also 1 x 1 x 1.)
+ shape:SetBox(Vector3(1.0, 1.0, 1.0))
+
+ -- Create animated models
+ for z = -1, 1 do
+ for x = -4, 4 do
+ local modelNode = scene_:CreateChild("Jack")
+ modelNode.position = Vector3(x * 5.0, 0.0, z * 5.0)
+ modelNode.rotation = Quaternion(0.0, 180.0, 0.0)
+ local modelObject = modelNode:CreateComponent("AnimatedModel")
+ modelObject.model = cache:GetResource("Model", "Models/Jack.mdl")
+ modelObject.material = cache:GetResource("Material", "Materials/Jack.xml")
+ modelObject.castShadows = true
+ -- Set the model to also update when invisible to avoid staying invisible when the model should come into
+ -- view, but does not as the bounding box is not updated
+ modelObject.updateInvisible = true
+
+ -- Create a rigid body and a collision shape. These will act as a trigger for transforming the
+ -- model into a ragdoll when hit by a moving object
+ local body = modelNode:CreateComponent("RigidBody")
+ -- The trigger mode makes the rigid body only detect collisions, but impart no forces on the
+ -- colliding objects
+ body.trigger = true
+ local shape = modelNode:CreateComponent("CollisionShape")
+ -- Create the capsule shape with an offset so that it is correctly aligned with the model, which
+ -- has its origin at the feet
+ shape:SetCapsule(0.7, 2.0, Vector3(0.0, 1.0, 0.0))
+
+ -- Create a custom script object that reacts to collisions and creates the ragdoll
+ modelNode:CreateScriptObject("CreateRagdoll")
+ end
+ end
+
+ -- Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
+ -- the scene, because we want it to be unaffected by scene load / save
+ cameraNode = Node()
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+
+ -- Set an initial position for the camera scene node above the floor
+ cameraNode.position = Vector3(0.0, 5.0, -20.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText(
+ "Use WASD keys and mouse to move\n"..
+ "LMB to spawn physics objects\n"..
+ "F5 to save scene, F7 to load\n"..
+ "Space to toggle physics debug geometry")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ -- debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch +MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ -- "Shoot" a physics object with left mousebutton
+ if input:GetMouseButtonPress(MOUSEB_LEFT) then
+ SpawnObject()
+ end
+
+ -- Check for loading/saving the scene. Save the scene to the file Data/Scenes/Physics.xml relative to the executable
+ -- directory
+ if input:GetKeyPress(KEY_F5) then
+ scene_:SaveXML(fileSystem:GetProgramDir().."Data/Scenes/Ragdolls.xml")
+ end
+ if input:GetKeyPress(KEY_F7) then
+ scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/Ragdolls.xml")
+ end
+
+ -- Toggle debug geometry with space
+ if input:GetKeyPress(KEY_SPACE) then
+ drawDebug = not drawDebug
+ end
+end
+
+function SpawnObject()
+ local boxNode = scene_:CreateChild("Sphere")
+ boxNode.position = cameraNode.position
+ boxNode.rotation = cameraNode.rotation
+ boxNode:SetScale(0.25)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Sphere.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/StoneSmall.xml")
+ boxObject.castShadows = true
+
+ local body = boxNode:CreateComponent("RigidBody")
+ body.mass = 1.0
+ body.rollingFriction = 0.15
+ local shape = boxNode:CreateComponent("CollisionShape")
+ shape:SetSphere(1.0)
+
+ local OBJECT_VELOCITY = 10.0
+
+ -- Set initial velocity for the RigidBody based on camera forward vector. Add also a slight up component
+ -- to overcome gravity better
+ body.linearVelocity = cameraNode.rotation * Vector3(0.0, 0.25, 1.0) * OBJECT_VELOCITY
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ -- If draw debug mode is enabled, draw physics debug geometry. Use depth test to make the result easier to interpret
+ if drawDebug then
+ scene_:GetComponent("PhysicsWorld"):DrawDebugGeometry(true)
+ end
+end
+
+-- CreateRagdoll script object class
+CreateRagdoll = ScriptObject()
+
+function CreateRagdoll:Start()
+ -- Subscribe physics collisions that concern this scene node
+ self:SubscribeToEvent(self.node, "NodeCollision", "CreateRagdoll:HandleNodeCollision")
+end
+
+function CreateRagdoll:HandleNodeCollision(eventType, eventData)
+ -- Get the other colliding body, make sure it is moving (has nonzero mass)
+ local otherBody = eventData["OtherBody"]:GetPtr("RigidBody")
+
+ if otherBody.mass > 0.0 then
+ -- We do not need the physics components in the AnimatedModel's root scene node anymore
+ self.node:RemoveComponent("RigidBody")
+ self.node:RemoveComponent("CollisionShape")
+
+ -- Create RigidBody & CollisionShape components to bones
+ self:CreateRagdollBone("Bip01_Pelvis", SHAPE_BOX, Vector3(0.3, 0.2, 0.25), Vector3(0.0, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 0.0))
+ self:CreateRagdollBone("Bip01_Spine1", SHAPE_BOX, Vector3(0.35, 0.2, 0.3), Vector3(0.15, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 0.0))
+ self:CreateRagdollBone("Bip01_L_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 90.0))
+ self:CreateRagdollBone("Bip01_R_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 90.0))
+ self:CreateRagdollBone("Bip01_L_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 90.0))
+ self:CreateRagdollBone("Bip01_R_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 90.0))
+ self:CreateRagdollBone("Bip01_Head", SHAPE_BOX, Vector3(0.2, 0.2, 0.2), Vector3(0.1, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 0.0))
+ self:CreateRagdollBone("Bip01_L_UpperArm", SHAPE_CAPSULE, Vector3(0.15, 0.35, 0.15), Vector3(0.1, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 90.0))
+ self:CreateRagdollBone("Bip01_R_UpperArm", SHAPE_CAPSULE, Vector3(0.15, 0.35, 0.15), Vector3(0.1, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 90.0))
+ self:CreateRagdollBone("Bip01_L_Forearm", SHAPE_CAPSULE, Vector3(0.125, 0.4, 0.125), Vector3(0.2, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 90.0))
+ self:CreateRagdollBone("Bip01_R_Forearm", SHAPE_CAPSULE, Vector3(0.125, 0.4, 0.125), Vector3(0.2, 0.0, 0.0),
+ Quaternion(0.0, 0.0, 90.0))
+
+ -- Create Constraints between bones
+ self:CreateRagdollConstraint("Bip01_L_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0.0, 0.0, -1.0),
+ Vector3(0.0, 0.0, 1.0), Vector2(45.0, 45.0), Vector2(0.0, 0.0), true)
+ self:CreateRagdollConstraint("Bip01_R_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0.0, 0.0, -1.0),
+ Vector3(0.0, 0.0, 1.0), Vector2(45.0, 45.0), Vector2(0.0, 0.0), true)
+ self:CreateRagdollConstraint("Bip01_L_Calf", "Bip01_L_Thigh", CONSTRAINT_HINGE, Vector3(0.0, 0.0, -1.0),
+ Vector3(0.0, 0.0, -1.0), Vector2(90.0, 0.0), Vector2(0.0, 0.0), true)
+ self:CreateRagdollConstraint("Bip01_R_Calf", "Bip01_R_Thigh", CONSTRAINT_HINGE, Vector3(0.0, 0.0, -1.0),
+ Vector3(0.0, 0.0, -1.0), Vector2(90.0, 0.0), Vector2(0.0, 0.0), true)
+ self:CreateRagdollConstraint("Bip01_Spine1", "Bip01_Pelvis", CONSTRAINT_HINGE, Vector3(0.0, 0.0, 1.0),
+ Vector3(0.0, 0.0, 1.0), Vector2(45.0, 0.0), Vector2(-10.0, 0.0), true)
+ self:CreateRagdollConstraint("Bip01_Head", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(-1.0, 0.0, 0.0),
+ Vector3(-1.0, 0.0, 0.0), Vector2(0.0, 30.0), Vector2(0.0, 0.0), true)
+ self:CreateRagdollConstraint("Bip01_L_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0.0, -1.0, 0.0),
+ Vector3(0.0, 1.0, 0.0), Vector2(45.0, 45.0), Vector2(0.0, 0.0), false)
+ self:CreateRagdollConstraint("Bip01_R_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0.0, -1.0, 0.0),
+ Vector3(0.0, 1.0, 0.0), Vector2(45.0, 45.0), Vector2(0.0, 0.0), false)
+ self:CreateRagdollConstraint("Bip01_L_Forearm", "Bip01_L_UpperArm", CONSTRAINT_HINGE, Vector3(0.0, 0.0, -1.0),
+ Vector3(0.0, 0.0, -1.0), Vector2(90.0, 0.0), Vector2(0.0, 0.0), true)
+ self:CreateRagdollConstraint("Bip01_R_Forearm", "Bip01_R_UpperArm", CONSTRAINT_HINGE, Vector3(0.0, 0.0, -1.0),
+ Vector3(0.0, 0.0, -1.0), Vector2(90.0, 0.0), Vector2(0.0, 0.0), true)
+
+ -- Disable keyframe animation from all bones so that they will not interfere with the ragdoll
+ local model = self.node:GetComponent("AnimatedModel")
+ local skeleton = model.skeleton
+ for i = 0, skeleton.numBones - 1 do
+ skeleton:GetBone(i).animated = false
+ end
+
+ -- Finally remove self (the ScriptInstance which holds this script object) from the scene node. Note that this must
+ -- be the last operation performed in the function
+ self.instance:Remove()
+ end
+end
+
+function CreateRagdoll:CreateRagdollBone(boneName, type, size, position, rotation)
+ -- Find the correct child scene node recursively
+ local boneNode = self.node:GetChild(boneName, true)
+ if boneNode == nil then
+ print("Could not find bone " .. boneName .. " for creating ragdoll physics components\n")
+ return
+ end
+
+ local body = boneNode:CreateComponent("RigidBody")
+ -- Set mass to make movable
+ body.mass = 1.0
+ -- Set damping parameters to smooth out the motion
+ body.linearDamping = 0.05
+ body.angularDamping = 0.85
+ -- Set rest thresholds to ensure the ragdoll rigid bodies come to rest to not consume CPU endlessly
+ body.linearRestThreshold = 1.5
+ body.angularRestThreshold = 2.5
+
+ local shape = boneNode:CreateComponent("CollisionShape")
+ -- We use either a box or a capsule shape for all of the bones
+ if type == SHAPE_BOX then
+ shape:SetBox(size, position, rotation)
+ else
+ shape:SetCapsule(size.x, size.y, position, rotation)
+ end
+end
+
+function CreateRagdoll:CreateRagdollConstraint(boneName, parentName, type, axis, parentAxis, highLimit, lowLimit, disableCollision)
+ local boneNode = self.node:GetChild(boneName, true)
+ local parentNode = self.node:GetChild(parentName, true)
+ if boneNode == nil then
+ print("Could not find bone " .. boneName .. " for creating ragdoll constraint\n")
+ return
+ end
+ if parentNode == nil then
+ print("Could not find bone " .. parentName .. " for creating ragdoll constraint\n")
+ return
+ end
+
+ local constraint = boneNode:CreateComponent("Constraint")
+ constraint.constraintType = type
+ -- Most of the constraints in the ragdoll will work better when the connected bodies don't collide against each other
+ constraint.disableCollision = disableCollision
+ -- The connected body must be specified before setting the world position
+ constraint.otherBody = parentNode:GetComponent("RigidBody")
+ -- Position the constraint at the child bone we are connecting
+ constraint.worldPosition = boneNode.worldPosition
+ -- Configure axes and limits
+ constraint.axis = axis
+ constraint.otherAxis = parentAxis
+ constraint.highLimit = highLimit
+ constraint.lowLimit = lowLimit
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Spawn" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Debug" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/14_SoundEffects.lua b/bin/Data/LuaScripts/14_SoundEffects.lua
new file mode 100644
index 0000000..918d6ee
--- /dev/null
+++ b/bin/Data/LuaScripts/14_SoundEffects.lua
@@ -0,0 +1,163 @@
+-- Sound effects example
+-- This sample demonstrates:
+-- - Playing sound effects and music
+-- - Controlling sound and music master volume
+
+require "LuaScripts/Utilities/Sample"
+
+local soundNames = {
+ "Fist",
+ "Explosion",
+ "Power-up"
+ }
+
+local soundResourceNames = {
+ "Sounds/PlayerFistHit.wav",
+ "Sounds/BigExplosion.wav",
+ "Sounds/Powerup.wav"
+ }
+
+local musicSource
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create a scene which will not be actually rendered, but is used to hold SoundSource components while they play sounds
+ scene_ = Scene()
+
+ -- Create music sound source
+ musicSource = scene_:CreateComponent("SoundSource")
+ -- Set the sound type to music so that master volume control works correctly
+ musicSource.soundType = SOUND_MUSIC
+
+ -- Enable OS cursor
+ input.mouseVisible = true
+
+ -- Create the user interface
+ CreateUI()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+end
+
+function CreateUI()
+
+ local uiStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ -- Set style to the UI root so that elements will inherit it
+ ui.root.defaultStyle = uiStyle
+
+ -- Create buttons for playing back sounds
+ for i, v in ipairs(soundNames) do
+ local button = CreateButton((i - 1) * 140 + 20, 20, 120, 40, v)
+ -- Store the sound effect resource name as a custom variable into the button
+ button:SetVar(StringHash("SoundResource"), Variant(soundResourceNames[i]))
+ SubscribeToEvent(button, "Pressed", "HandlePlaySound")
+ end
+
+ -- Create buttons for playing/stopping music
+ local button = CreateButton(20, 80, 120, 40, "Play Music")
+ SubscribeToEvent(button, "Released", "HandlePlayMusic")
+
+ button = CreateButton(160, 80, 120, 40, "Stop Music")
+ SubscribeToEvent(button, "Released", "HandleStopMusic")
+
+ -- Create sliders for controlling sound and music master volume
+ local slider = CreateSlider(20, 140, 200, 20, "Sound Volume")
+ slider.value = audio:GetMasterGain(SOUND_EFFECT)
+ SubscribeToEvent(slider, "SliderChanged", "HandleSoundVolume")
+
+ slider = CreateSlider(20, 200, 200, 20, "Music Volume")
+ slider.value = audio:GetMasterGain(SOUND_MUSIC)
+ SubscribeToEvent(slider, "SliderChanged", "HandleMusicVolume")
+end
+
+function CreateButton(x, y, xSize, ySize, text)
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+
+ -- Create the button and center the text onto it
+ local button = ui.root:CreateChild("Button")
+ button:SetStyleAuto()
+ button:SetPosition(x, y)
+ button:SetSize(xSize, ySize)
+
+ local buttonText = button:CreateChild("Text")
+ buttonText:SetAlignment(HA_CENTER, VA_CENTER)
+ buttonText:SetFont(font, 12)
+ buttonText:SetText(text)
+
+ return button
+end
+
+function CreateSlider(x, y, xSize, ySize, text)
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+
+ -- Create text and slider below it
+ local sliderText = ui.root:CreateChild("Text")
+ sliderText:SetPosition(x, y)
+ sliderText:SetFont(font, 12)
+ sliderText:SetText(text)
+
+ local slider = ui.root:CreateChild("Slider")
+ slider:SetStyleAuto()
+ slider:SetPosition(x, y + 20)
+ slider:SetSize(xSize, ySize)
+ -- Use 0-1 range for controlling sound/music master volume
+ slider.range = 1.0
+
+ return slider
+end
+
+function HandlePlaySound(sender, eventType, eventData)
+ local button = tolua.cast(GetEventSender(), "Button")
+ local soundResourceName = button:GetVar(StringHash("SoundResource")):GetString()
+
+ -- Get the sound resource
+ local sound = cache:GetResource("Sound", soundResourceName)
+
+ if sound ~= nil then
+ -- Create a SoundSource component for playing the sound. The SoundSource component plays
+ -- non-positional audio, so its 3D position in the scene does not matter. For positional sounds the
+ -- SoundSource3D component would be used instead
+ local soundSource = scene_:CreateComponent("SoundSource")
+ soundSource:SetAutoRemoveMode(REMOVE_COMPONENT)
+ soundSource:Play(sound)
+ -- In case we also play music, set the sound volume below maximum so that we don't clip the output
+ soundSource.gain = 0.7
+ end
+end
+
+function HandlePlayMusic(eventType, eventData)
+ local music = cache:GetResource("Sound", "Music/Ninja Gods.ogg")
+ -- Set the song to loop
+ music.looped = true
+
+ musicSource:Play(music)
+end
+
+function HandleStopMusic(eventType, eventData)
+ musicSource:Stop()
+end
+
+function HandleSoundVolume(eventType, eventData)
+ local newVolume = eventData["Value"]:GetFloat()
+ audio:SetMasterGain(SOUND_EFFECT, newVolume)
+end
+
+function HandleMusicVolume(eventType, eventData)
+ local newVolume = eventData["Value"]:GetFloat()
+ audio:SetMasterGain(SOUND_MUSIC, newVolume)
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/15_Navigation.lua b/bin/Data/LuaScripts/15_Navigation.lua
new file mode 100644
index 0000000..e705741
--- /dev/null
+++ b/bin/Data/LuaScripts/15_Navigation.lua
@@ -0,0 +1,501 @@
+-- Navigation example.
+-- This sample demonstrates:
+-- - Generating a navigation mesh into the scene
+-- - Performing path queries to the navigation mesh
+-- - Rebuilding the navigation mesh partially when adding or removing objects
+-- - Visualizing custom debug geometry
+-- - Raycasting drawable components
+-- - Making a node follow the Detour path
+
+require "LuaScripts/Utilities/Sample"
+
+local endPos = nil
+local currentPath = {}
+
+local useStreaming = false
+local streamingDistance = 2
+local navigationTiles = {}
+local addedTiles = {}
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateUI()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update and render post-update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ -- Also create a DebugRenderer component so that we can draw debug geometry
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create scene node & StaticModel component for showing a static plane
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3(100.0, 1.0, 100.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(0.5, 0.5, 0.7)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ -- Create some mushrooms
+ local NUM_MUSHROOMS = 100
+ for i = 1, NUM_MUSHROOMS do
+ CreateMushroom(Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0))
+ end
+
+ -- Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+ -- rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+ local NUM_BOXES = 20
+ for i = 1, NUM_BOXES do
+ local boxNode = scene_:CreateChild("Box")
+ local size = 1.0 + Random(10.0)
+ boxNode.position = Vector3(Random(80.0) - 40.0, size * 0.5, Random(80.0) - 40.0)
+ boxNode:SetScale(size)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+ boxObject.castShadows = true
+ if size >= 3.0 then
+ boxObject.occluder = true
+ end
+ end
+
+ -- Create Jack node that will follow the path
+ jackNode = scene_:CreateChild("Jack")
+ jackNode.position = Vector3(-5, 0, 20)
+ local modelObject = jackNode:CreateComponent("AnimatedModel")
+ modelObject.model = cache:GetResource("Model", "Models/Jack.mdl")
+ modelObject.material = cache:GetResource("Material", "Materials/Jack.xml")
+ modelObject.castShadows = true
+
+ -- Create a NavigationMesh component to the scene root
+ local navMesh = scene_:CreateComponent("NavigationMesh")
+ -- Set small tiles to show navigation mesh streaming
+ navMesh.tileSize = 32
+ -- Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
+ -- navigation mesh. By default this is recursive, but the recursion could be turned off from Navigable
+ scene_:CreateComponent("Navigable")
+ -- Add padding to the navigation mesh in Y-direction so that we can add objects on top of the tallest boxes
+ -- in the scene and still update the mesh correctly
+ navMesh.padding = Vector3(0.0, 10.0, 0.0)
+ -- Now build the navigation geometry. This will take some time. Note that the navigation mesh will prefer to use
+ -- physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
+ -- it will use renderable geometry instead
+ navMesh:Build()
+
+ -- Create the camera. Limit far clip distance to match the fog
+ cameraNode = scene_:CreateChild("Camera")
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+
+ -- Set an initial position for the camera scene node above the plane and looking down
+ cameraNode.position = Vector3(0.0, 50.0, 0.0)
+ pitch = 80.0
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+end
+
+function CreateUI()
+ -- Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ -- control the camera, and when visible, it will point the raycast target
+ local style = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ local cursor = Cursor:new()
+ cursor:SetStyleAuto(style)
+ ui.cursor = cursor
+ -- Set starting position of the cursor at the rendering window center
+ cursor:SetPosition(graphics.width / 2, graphics.height / 2)
+
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText.text = "Use WASD keys to move, RMB to rotate view\n"..
+ "LMB to set destination, SHIFT+LMB to teleport\n"..
+ "MMB or O key to add or remove obstacles\n"..
+ "Tab to toggle navigation mesh streaming\n"..
+ "Space to toggle debug geometry"
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ -- debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+end
+
+function MoveCamera(timeStep)
+ input.mouseVisible = input.mouseMode ~= MM_RELATIVE
+ mouseDown = input:GetMouseButtonDown(MOUSEB_RIGHT)
+
+ -- Override the MM_RELATIVE mouse grabbed settings, to allow interaction with UI
+ input.mouseGrabbed = mouseDown
+
+ -- Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = not mouseDown
+
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ -- Only move the camera when the cursor is hidden
+ if not ui.cursor.visible then
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+ end
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ -- Set destination or teleport with left mouse button
+ if input:GetMouseButtonPress(MOUSEB_LEFT) then
+ SetPathPoint()
+ end
+ -- Add or remove objects with middle mouse button, then rebuild navigation mesh partially
+ if input:GetMouseButtonPress(MOUSEB_MIDDLE) or input:GetKeyPress(KEY_O) then
+ AddOrRemoveObject()
+ end
+ -- Toggle debug geometry with space
+ if input:GetKeyPress(KEY_SPACE) then
+ drawDebug = not drawDebug
+ end
+end
+
+function SetPathPoint()
+ local hitPos, hitDrawable = Raycast(250.0)
+ local navMesh = scene_:GetComponent("NavigationMesh")
+ if hitPos then
+ local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
+
+ if input:GetQualifierDown(QUAL_SHIFT) then
+ -- Teleport
+ currentPath = {}
+ jackNode:LookAt(Vector3(pathPos.x, jackNode.position.y, pathPos.z), Vector3(0.0, 1.0, 0.0))
+ jackNode.position = pathPos
+ else
+ -- Calculate path from Jack's current position to the end point
+ endPos = pathPos
+ currentPath = navMesh:FindPath(jackNode.position, endPos)
+ end
+ end
+end
+
+function AddOrRemoveObject()
+ -- Raycast and check if we hit a mushroom node. If yes, remove it, if no, create a new one
+ local hitPos, hitDrawable = Raycast(250.0)
+ if not useStreaming and hitDrawable then
+ -- The part of the navigation mesh we must update, which is the world bounding box of the associated
+ -- drawable component
+ local updateBox = nil
+
+ local hitNode = hitDrawable.node
+ if hitNode.name == "Mushroom" then
+ updateBox = hitDrawable.worldBoundingBox
+ hitNode:Remove()
+ else
+ local newNode = CreateMushroom(hitPos)
+ local newObject = newNode:GetComponent("StaticModel")
+ updateBox = newObject.worldBoundingBox
+ end
+
+ -- Rebuild part of the navigation mesh, then recalculate path if applicable
+ local navMesh = scene_:GetComponent("NavigationMesh")
+ navMesh:Build(updateBox)
+ if table.maxn(currentPath) > 0 then
+ currentPath = navMesh:FindPath(jackNode.position, endPos)
+ end
+ end
+end
+
+function CreateMushroom(pos)
+ local mushroomNode = scene_:CreateChild("Mushroom")
+ mushroomNode.position = pos
+ mushroomNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+ mushroomNode:SetScale(2.0 + Random(0.5))
+ local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+ mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ mushroomObject.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ mushroomObject.castShadows = true
+ return mushroomNode
+end
+
+function Raycast(maxDistance)
+
+ local pos = ui.cursorPosition
+ -- Check the cursor is visible and there is no UI element in front of the cursor
+ if (not ui.cursor.visible) or (ui:GetElementAt(pos, true) ~= nil) then
+ return nil, nil
+ end
+
+ local camera = cameraNode:GetComponent("Camera")
+ local cameraRay = camera:GetScreenRay(pos.x / graphics.width, pos.y / graphics.height)
+ -- Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
+ local octree = scene_:GetComponent("Octree")
+ local result = octree:RaycastSingle(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY)
+ if result.drawable ~= nil then
+ return result.position, result.drawable
+ end
+
+ return nil, nil
+end
+
+function ToggleStreaming(enabled)
+ local navMesh = scene_:GetComponent("NavigationMesh")
+ if enabled then
+ local maxTiles = (2 * streamingDistance + 1) * (2 * streamingDistance + 1)
+ local boundingBox = BoundingBox(navMesh.boundingBox)
+ SaveNavigationData()
+ navMesh:Allocate(boundingBox, maxTiles)
+ else
+ navMesh:Build()
+ end
+end
+
+function UpdateStreaming()
+ local navMesh = scene_:GetComponent("NavigationMesh")
+
+ -- Center the navigation mesh at the jack
+ local jackTile = navMesh:GetTileIndex(jackNode.worldPosition)
+ local beginTile = VectorMax(IntVector2(0, 0), jackTile - IntVector2(1, 1) * streamingDistance)
+ local endTile = VectorMin(jackTile + IntVector2(1, 1) * streamingDistance, navMesh.numTiles - IntVector2(1, 1))
+
+ -- Remove tiles
+ local numTiles = navMesh.numTiles
+ for i,tileIdx in pairs(addedTiles) do
+ if not (beginTile.x <= tileIdx.x and tileIdx.x <= endTile.x and beginTile.y <= tileIdx.y and tileIdx.y <= endTile.y) then
+ addedTiles[i] = nil
+ navMesh:RemoveTile(tileIdx)
+ end
+ end
+
+ -- Add tiles
+ for z = beginTile.y, endTile.y do
+ for x = beginTile.x, endTile.x do
+ local i = z * numTiles.x + x
+ if not navMesh:HasTile(IntVector2(x, z)) and navigationTiles[i] then
+ addedTiles[i] = IntVector2(x, z)
+ navMesh:AddTile(navigationTiles[i])
+ end
+ end
+ end
+end
+
+function SaveNavigationData()
+ local navMesh = scene_:GetComponent("NavigationMesh")
+ navigationTiles = {}
+ addedTiles = {}
+ local numTiles = navMesh.numTiles
+
+ for z = 0, numTiles.y - 1 do
+ for x = 0, numTiles.x - 1 do
+ local i = z * numTiles.x + x
+ navigationTiles[i] = navMesh:GetTileData(IntVector2(x, z))
+ end
+ end
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+
+ -- Make Jack follow the Detour path
+ FollowPath(timeStep)
+
+ -- Update streaming
+ if input:GetKeyPress(KEY_TAB) then
+ useStreaming = not useStreaming
+ ToggleStreaming(useStreaming)
+ end
+ if useStreaming then
+ UpdateStreaming()
+ end
+end
+
+function FollowPath(timeStep)
+ if table.maxn(currentPath) > 0 then
+ local nextWaypoint = currentPath[1] -- NB: currentPath[1] is the next waypoint in order
+
+ -- Rotate Jack toward next waypoint to reach and move. Check for not overshooting the target
+ local move = 5 * timeStep
+ local distance = (jackNode.position - nextWaypoint):Length()
+ if move > distance then
+ move = distance
+ end
+
+ jackNode:LookAt(nextWaypoint, Vector3(0.0, 1.0, 0.0))
+ jackNode:Translate(Vector3(0.0, 0.0, 1.0) * move)
+
+ -- Remove waypoint if reached it
+ if distance < 0.1 then
+ table.remove(currentPath, 1)
+ end
+ end
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ -- If draw debug mode is enabled, draw navigation mesh debug geometry
+ if drawDebug then
+ local navMesh = scene_:GetComponent("NavigationMesh")
+ navMesh:DrawDebugGeometry(true)
+ end
+
+ -- Visualize the start and end points and the last calculated path
+ local size = table.maxn(currentPath)
+ if size > 0 then
+ local debug = scene_:GetComponent("DebugRenderer")
+ debug:AddBoundingBox(BoundingBox(endPos - Vector3(0.1, 0.1, 0.1), endPos + Vector3(0.1, 0.1, 0.1)), Color(1.0, 1.0, 1.0))
+
+ -- Draw the path with a small upward bias so that it does not clip into the surfaces
+ local bias = Vector3(0.0, 0.05, 0.0)
+ debug:AddLine(jackNode.position + bias, currentPath[1] + bias, Color(1.0, 1.0, 1.0))
+
+ if size > 1 then
+ for i = 1, size - 1 do
+ debug:AddLine(currentPath[i] + bias, currentPath[i + 1] + bias, Color(1.0, 1.0, 1.0))
+ end
+ end
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Set" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Debug" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/16_Chat.lua b/bin/Data/LuaScripts/16_Chat.lua
new file mode 100644
index 0000000..694ec7e
--- /dev/null
+++ b/bin/Data/LuaScripts/16_Chat.lua
@@ -0,0 +1,224 @@
+-- Chat example
+-- This sample demonstrates:
+-- - Starting up a network server or connecting to it
+-- - Implementing simple chat functionality with network messages
+
+require "LuaScripts/Utilities/Sample"
+
+-- Identifier for the chat network messages
+local MSG_CHAT = 153
+-- UDP port we will use
+local CHAT_SERVER_PORT = 2345
+
+local chatHistory = {}
+local chatHistoryText = nil
+local buttonContainer = nil
+local textEdit = nil
+local sendButton = nil
+local connectButton = nil
+local disconnectButton = nil
+local startServerButton = nil
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Enable OS cursor
+ input.mouseVisible = true
+
+ -- Create the user interface
+ CreateUI()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Subscribe to UI and network events
+ SubscribeToEvents()
+end
+
+function CreateUI()
+ SetLogoVisible(false) -- We need the full rendering window
+
+ local uiStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ -- Set style to the UI root so that elements will inherit it
+ ui.root.defaultStyle = uiStyle
+
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+ chatHistoryText = ui.root:CreateChild("Text")
+ chatHistoryText:SetFont(font, 12)
+
+ buttonContainer = ui.root:CreateChild("UIElement")
+ buttonContainer:SetFixedSize(graphics.width, 20)
+ buttonContainer:SetPosition(0, graphics.height - 20)
+ buttonContainer.layoutMode = LM_HORIZONTAL
+
+ textEdit = buttonContainer:CreateChild("LineEdit")
+ textEdit:SetStyleAuto()
+
+ sendButton = CreateButton("Send", 70)
+ connectButton = CreateButton("Connect", 90)
+ disconnectButton = CreateButton("Disconnect", 100)
+ startServerButton = CreateButton("Start Server", 110)
+
+ UpdateButtons()
+
+ local size = (graphics.height - 100) / chatHistoryText.rowHeight
+ for i = 1, size do
+ table.insert(chatHistory, "")
+ end
+
+ -- No viewports or scene is defined. However, the default zone's fog color controls the fill color
+ renderer.defaultZone.fogColor = Color(0.0, 0.0, 0.1)
+end
+
+function SubscribeToEvents()
+ -- Subscribe to UI element events
+ SubscribeToEvent(textEdit, "TextFinished", "HandleSend")
+ SubscribeToEvent(sendButton, "Released", "HandleSend")
+ SubscribeToEvent(connectButton, "Released", "HandleConnect")
+ SubscribeToEvent(disconnectButton, "Released", "HandleDisconnect")
+ SubscribeToEvent(startServerButton, "Released", "HandleStartServer")
+
+ -- Subscribe to log messages so that we can pipe them to the chat window
+ SubscribeToEvent("LogMessage", "HandleLogMessage")
+
+ -- Subscribe to network events
+ SubscribeToEvent("NetworkMessage", "HandleNetworkMessage")
+ SubscribeToEvent("ServerConnected", "HandleConnectionStatus")
+ SubscribeToEvent("ServerDisconnected", "HandleConnectionStatus")
+ SubscribeToEvent("ConnectFailed", "HandleConnectionStatus")
+end
+
+function CreateButton(text, width)
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+
+ local button = buttonContainer:CreateChild("Button")
+ button:SetStyleAuto()
+ button:SetFixedWidth(width)
+
+ local buttonText = button:CreateChild("Text")
+ buttonText:SetFont(font, 12)
+ buttonText:SetAlignment(HA_CENTER, VA_CENTER)
+ buttonText.text = text
+
+ return button
+end
+
+function ShowChatText(row)
+ table.remove(chatHistory, 1)
+ table.insert(chatHistory, row)
+
+ -- Concatenate all the rows in history
+ local allRows = ""
+ for i, r in ipairs(chatHistory) do
+ allRows = allRows .. r .. "\n"
+ end
+ chatHistoryText.text = allRows
+end
+
+function UpdateButtons()
+ local serverConnection = network.serverConnection
+ local serverRunning = network.serverRunning
+
+ -- Show and hide buttons so that eg. Connect and Disconnect are never shown at the same time
+ sendButton.visible = serverConnection ~= nil
+ connectButton.visible = (serverConnection == nil) and (not serverRunning)
+ disconnectButton.visible = (serverConnection ~= nil) or serverRunning
+ startServerButton.visible = (serverConnection == nil) and (not serverRunning)
+end
+
+function HandleLogMessage(eventType, eventData)
+ ShowChatText(eventData["Message"]:GetString())
+end
+
+function HandleSend(eventType, eventData)
+ local text = textEdit.text
+ if text == "" then
+ return -- Do not send an empty message
+ end
+
+ local serverConnection = network.serverConnection
+ if serverConnection ~= nil then
+ -- A VectorBuffer object is convenient for constructing a message to send
+ local msg = VectorBuffer()
+ msg:WriteString(text)
+ -- Send the chat message as in-order and reliable
+ serverConnection:SendMessage(MSG_CHAT, true, true, msg)
+ -- Empty the text edit after sending
+ textEdit.text = ""
+ end
+end
+
+function HandleConnect(eventType, eventData)
+ local address = textEdit.text
+ if address == "" then
+ address = "localhost" -- Use localhost to connect if nothing else specified
+ end
+
+ -- Empty the text edit after reading the address to connect to
+ textEdit.text = ""
+
+ -- Connect to server, do not specify a client scene as we are not using scene replication, just messages.
+ -- At connect time we could also send identity parameters (such as username) in a VariantMap, but in this
+ -- case we skip it for simplicity
+ network:Connect(address, CHAT_SERVER_PORT, nil)
+
+ UpdateButtons()
+end
+
+function HandleDisconnect(eventType, eventData)
+ local serverConnection = network.serverConnection
+ -- If we were connected to server, disconnect
+ if serverConnection ~= nil then
+ serverConnection:Disconnect()
+ -- Or if we were running a server, stop it
+ else
+ if network.serverRunning then
+ network:StopServer()
+ end
+ end
+
+ UpdateButtons()
+end
+
+function HandleStartServer(eventType, eventData)
+ network:StartServer(CHAT_SERVER_PORT)
+
+ UpdateButtons()
+end
+
+function HandleNetworkMessage(eventType, eventData)
+ local msgID = eventData["MessageID"]:GetInt()
+ if msgID == MSG_CHAT then
+ local msg = eventData["Data"]:GetBuffer()
+ local text = msg:ReadString()
+ -- If we are the server, prepend the sender's IP address and port and echo to everyone
+ -- If we are a client, just display the message
+ if network.serverRunning then
+ local sender = eventData["Connection"]:GetPtr("Connection")
+ text = sender:ToString() .. " " .. text
+ local sendMsg = VectorBuffer()
+ sendMsg:WriteString(text)
+ -- Broadcast as in-order and reliable
+ network:BroadcastMessage(MSG_CHAT, true, true, sendMsg)
+ end
+ ShowChatText(text)
+ end
+end
+
+function HandleConnectionStatus(eventType, eventData)
+ UpdateButtons()
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/17_SceneReplication.lua b/bin/Data/LuaScripts/17_SceneReplication.lua
new file mode 100644
index 0000000..fc2e6d9
--- /dev/null
+++ b/bin/Data/LuaScripts/17_SceneReplication.lua
@@ -0,0 +1,415 @@
+-- Scene network replication example.
+-- This sample demonstrates:
+-- - Creating a scene in which network clients can join
+-- - Giving each client an object to control and sending the controls from the clients to the server,
+-- where the authoritative simulation happens
+-- - Controlling a physics object's movement by applying forces
+
+require "LuaScripts/Utilities/Sample"
+
+-- UDP port we will use
+local SERVER_PORT = 2345
+
+-- Control bits we define
+local CTRL_FORWARD = 1
+local CTRL_BACK = 2
+local CTRL_LEFT = 4
+local CTRL_RIGHT = 8
+
+local instructionsText = nil
+local buttonContainer = nil
+local textEdit = nil
+local connectButton = nil
+local disconnectButton = nil
+local startServerButton = nil
+local clients = {}
+local clientObjectID = 0
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateUI()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to necessary events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree and physics world with default settings. Create them as local so that they are not needlessly replicated
+ -- when a client connects
+ scene_:CreateComponent("Octree", LOCAL)
+ scene_:CreateComponent("PhysicsWorld", LOCAL)
+
+ -- All static scene content and the camera are also created as local, so that they are unaffected by scene replication and are
+ -- not removed from the client upon connection. Create a Zone component first for ambient lighting & fog control.
+ local zoneNode = scene_:CreateChild("Zone", LOCAL)
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.1, 0.1, 0.1)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light without shadows
+ local lightNode = scene_:CreateChild("DirectionalLight", LOCAL)
+ lightNode.direction = Vector3(0.5, -1.0, 0.5)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.color = Color(0.2, 0.2, 0.2)
+ light.specularIntensity = 1.0
+
+ -- Create a "floor" consisting of several tiles. Make the tiles physical but leave small cracks between them
+ for y = -20, 20 do
+ for x = -20, 20 do
+ local floorNode = scene_:CreateChild("FloorTile", LOCAL)
+ floorNode.position = Vector3(x * 20.2, -0.5, y * 20.2)
+ floorNode.scale = Vector3(20.0, 1.0, 20.0)
+ local floorObject = floorNode:CreateComponent("StaticModel")
+ floorObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ floorObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+
+ local body = floorNode:CreateComponent("RigidBody")
+ body.friction = 1.0
+ local shape = floorNode:CreateComponent("CollisionShape")
+ shape:SetBox(Vector3(1.0, 1.0, 1.0))
+ end
+ end
+
+ -- Create the camera. Limit far clip distance to match the fog
+ -- The camera needs to be created into a local node so that each client can retain its own camera, that is unaffected by
+ -- network messages. Furthermore, because the client removes all replicated scene nodes when connecting to a server scene,
+ -- the screen would become blank if the camera node was replicated (as only the locally created camera is assigned to a
+ -- viewport in SetupViewports() below)
+ cameraNode = scene_:CreateChild("Camera", LOCAL)
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 5.0, 0.0)
+end
+
+function CreateUI()
+ local uiStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ -- Set style to the UI root so that elements will inherit it
+ ui.root.defaultStyle = uiStyle
+
+ -- Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ -- control the camera, and when visible, it will point the raycast target
+ local cursor = ui.root:CreateChild("Cursor")
+ cursor:SetStyleAuto(uiStyle)
+ ui.cursor = cursor
+ -- Set starting position of the cursor at the rendering window center
+ cursor:SetPosition(graphics.width / 2, graphics.height / 2)
+
+ -- Construct the instructions text element
+ instructionsText = ui.root:CreateChild("Text")
+ instructionsText:SetText("Use WASD keys to move and RMB to rotate view")
+ instructionsText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- Position the text relative to the screen center
+ instructionsText.horizontalAlignment = HA_CENTER
+ instructionsText.verticalAlignment = VA_CENTER
+ instructionsText:SetPosition(0, graphics.height / 4)
+ -- Hide until connected
+ instructionsText.visible = false
+
+ buttonContainer = ui.root:CreateChild("UIElement")
+ buttonContainer:SetFixedSize(500, 20)
+ buttonContainer:SetPosition(20, 20)
+ buttonContainer.layoutMode = LM_HORIZONTAL
+
+ textEdit = buttonContainer:CreateChild("LineEdit")
+ textEdit:SetStyleAuto()
+
+ connectButton = CreateButton("Connect", 90)
+ disconnectButton = CreateButton("Disconnect", 100)
+ startServerButton = CreateButton("Start Server", 110)
+
+ UpdateButtons()
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe to fixed timestep physics updates for setting or applying controls
+ SubscribeToEvent("PhysicsPreStep", "HandlePhysicsPreStep")
+
+ -- Subscribe HandlePostUpdate() method for processing update events. Subscribe to PostUpdate instead
+ -- of the usual Update so that physics simulation has already proceeded for the frame, and can
+ -- accurately follow the object with the camera
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate")
+
+ -- Subscribe to button actions
+ SubscribeToEvent(connectButton, "Released", "HandleConnect")
+ SubscribeToEvent(disconnectButton, "Released", "HandleDisconnect")
+ SubscribeToEvent(startServerButton, "Released", "HandleStartServer")
+
+ -- Subscribe to network events
+ SubscribeToEvent("ServerConnected", "HandleConnectionStatus")
+ SubscribeToEvent("ServerDisconnected", "HandleConnectionStatus")
+ SubscribeToEvent("ConnectFailed", "HandleConnectionStatus")
+ SubscribeToEvent("ClientConnected", "HandleClientConnected")
+ SubscribeToEvent("ClientDisconnected", "HandleClientDisconnected")
+ -- This is a custom event, sent from the server to the client. It tells the node ID of the object the client should control
+ SubscribeToEvent("ClientObjectID", "HandleClientObjectID")
+ -- Events sent between client & server (remote events) must be explicitly registered or else they are not allowed to be received
+ network:RegisterRemoteEvent("ClientObjectID")
+end
+
+function CreateButton(text, width)
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+
+ local button = buttonContainer:CreateChild("Button")
+ button:SetStyleAuto()
+ button:SetFixedWidth(width)
+
+ local buttonText = button:CreateChild("Text")
+ buttonText:SetFont(font, 12)
+ buttonText:SetAlignment(HA_CENTER, VA_CENTER)
+ buttonText:SetText(text)
+
+ return button
+end
+
+function UpdateButtons()
+ local serverConnection = network:GetServerConnection()
+ local serverRunning = network.serverRunning
+
+ -- Show and hide buttons so that eg. Connect and Disconnect are never shown at the same time
+ connectButton.visible = serverConnection == nil and not serverRunning
+ disconnectButton.visible = serverConnection ~= nil or serverRunning
+ startServerButton.visible = serverConnection == nil and not serverRunning
+ textEdit.visible = serverConnection == nil and not serverRunning
+end
+
+function CreateControllableObject()
+ -- Create the scene node & visual representation. This will be a replicated object
+ local ballNode = scene_:CreateChild("Ball")
+ ballNode.position = Vector3(Random(40.0) - 20.0, 5.0, Random(40.0) - 20.0)
+ ballNode:SetScale(0.5)
+ local ballObject = ballNode:CreateComponent("StaticModel")
+ ballObject.model = cache:GetResource("Model", "Models/Sphere.mdl")
+ ballObject.material = cache:GetResource("Material", "Materials/StoneSmall.xml")
+
+ -- Create the physics components
+ local body = ballNode:CreateComponent("RigidBody")
+ body.mass = 1.0
+ body.friction = 1.0
+ -- In addition to friction, use motion damping so that the ball can not accelerate limitlessly
+ body.linearDamping = 0.5
+ body.angularDamping = 0.5
+ local shape = ballNode:CreateComponent("CollisionShape")
+ shape:SetSphere(1.0)
+
+ -- Create a random colored point light at the ball so that can see better where is going
+ local light = ballNode:CreateComponent("Light")
+ light.range = 3.0
+ light.color = Color(0.5 + RandomInt(2) * 0.5, 0.5 + RandomInt(2) * 0.5, 0.5 + RandomInt(2) * 0.5)
+
+ return ballNode
+end
+
+function MoveCamera()
+ input.mouseVisible = input.mouseMode ~= MM_RELATIVE
+ mouseDown = input:GetMouseButtonDown(MOUSEB_RIGHT)
+
+ -- Override the MM_RELATIVE mouse grabbed settings, to allow interaction with UI
+ input.mouseGrabbed = mouseDown
+
+ -- Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = not mouseDown
+
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch and only move the camera
+ -- when the cursor is hidden
+ if not ui.cursor.visible then
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, 1.0, 90.0)
+ end
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Only move the camera / show instructions if we have a controllable object
+ local showInstructions = false
+ if clientObjectID ~= 0 then
+ local ballNode = scene_:GetNode(clientObjectID)
+ if ballNode ~= nil then
+ local CAMERA_DISTANCE = 5.0
+
+ -- Move camera some distance away from the ball
+ cameraNode.position = ballNode.position + cameraNode.rotation * Vector3(0.0, 0.0, -1.0) * CAMERA_DISTANCE
+ showInstructions = true
+ end
+ end
+
+ instructionsText.visible = showInstructions
+end
+
+function HandlePostUpdate(eventType, eventData)
+
+ -- We only rotate the camera according to mouse movement since last frame, so do not need the time step
+ MoveCamera()
+end
+
+function HandlePhysicsPreStep(eventType, eventData)
+
+ -- This function is different on the client and server. The client collects controls (WASD controls + yaw angle)
+ -- and sets them to its server connection object, so that they will be sent to the server automatically at a
+ -- fixed rate, by default 30 FPS. The server will actually apply the controls (authoritative simulation.)
+ local serverConnection = network:GetServerConnection()
+
+ -- Client: collect controls
+ if serverConnection ~= nil then
+ local controls = Controls()
+
+ -- Copy mouse yaw
+ controls.yaw = yaw
+
+ -- Only apply WASD controls if there is no focused UI element
+ if ui.focusElement == nil then
+ controls:Set(CTRL_FORWARD, input:GetKeyDown(KEY_W))
+ controls:Set(CTRL_BACK, input:GetKeyDown(KEY_S))
+ controls:Set(CTRL_LEFT, input:GetKeyDown(KEY_A))
+ controls:Set(CTRL_RIGHT, input:GetKeyDown(KEY_D))
+ end
+
+ serverConnection.controls = controls
+ -- In case the server wants to do position-based interest management using the NetworkPriority components, we should also
+ -- tell it our observer (camera) position. In this sample it is not in use, but eg. the NinjaSnowWar game uses it
+ serverConnection.position = cameraNode.position
+ -- Server: apply controls to client objects
+ elseif network.serverRunning then
+ for i, v in ipairs(clients) do
+ local connection = v.connection
+ -- Get the object this connection is controlling
+ local ballNode = v.object
+
+ local body = ballNode:GetComponent("RigidBody")
+
+ -- Torque is relative to the forward vector
+ local rotation = Quaternion(0.0, connection.controls.yaw, 0.0)
+
+ local MOVE_TORQUE = 3.0
+
+ -- Movement torque is applied before each simulation step, which happen at 60 FPS. This makes the simulation
+ -- independent from rendering framerate. We could also apply forces (which would enable in-air control),
+ -- but want to emphasize that it's a ball which should only control its motion by rolling along the ground
+ if connection.controls:IsDown(CTRL_FORWARD) then
+ body:ApplyTorque(rotation * Vector3(1.0, 0.0, 0.0) * MOVE_TORQUE)
+ end
+ if connection.controls:IsDown(CTRL_BACK) then
+ body:ApplyTorque(rotation * Vector3(-1.0, 0.0, 0.0) * MOVE_TORQUE)
+ end
+ if connection.controls:IsDown(CTRL_LEFT) then
+ body:ApplyTorque(rotation * Vector3(0.0, 0.0, 1.0) * MOVE_TORQUE)
+ end
+ if connection.controls:IsDown(CTRL_RIGHT) then
+ body:ApplyTorque(rotation * Vector3(0.0, 0.0, -1.0) * MOVE_TORQUE)
+ end
+ end
+ end
+end
+
+function HandleConnect(eventType, eventData)
+ local address = textEdit.text
+ if address == "" then
+ address = "localhost" -- Use localhost to connect if nothing else specified
+ end
+
+ -- Connect to server, specify scene to use as a client for replication
+ clientObjectID = 0 -- Reset own object ID from possible previous connection
+ network:Connect(address, SERVER_PORT, scene_)
+
+ UpdateButtons()
+end
+
+function HandleDisconnect(eventType, eventData)
+ local serverConnection = network.serverConnection
+ -- If we were connected to server, disconnect. Or if we were running a server, stop it. In both cases clear the
+ -- scene of all replicated content, but let the local nodes & components (the static world + camera) stay
+ if serverConnection ~= nil then
+ serverConnection:Disconnect()
+ scene_:Clear(true, false)
+ clientObjectID = 0
+ -- Or if we were running a server, stop it
+ elseif network.serverRunning then
+ network:StopServer()
+ scene_:Clear(true, false)
+ end
+
+ UpdateButtons()
+end
+
+function HandleStartServer(eventType, eventData)
+ network:StartServer(SERVER_PORT)
+
+ UpdateButtons()
+end
+
+function HandleConnectionStatus(eventType, eventData)
+ UpdateButtons()
+end
+
+function HandleClientConnected(eventType, eventData)
+ -- When a client connects, assign to scene to begin scene replication
+ local newConnection = eventData["Connection"]:GetPtr("Connection")
+ newConnection.scene = scene_
+
+ -- Then create a controllable object for that client
+ local newObject = CreateControllableObject()
+ local newClient = {}
+ newClient.connection = newConnection
+ newClient.object = newObject
+ table.insert(clients, newClient)
+
+ -- Finally send the object's node ID using a remote event
+ local remoteEventData = VariantMap()
+ remoteEventData:SetInt("ID", newObject.ID)
+ newConnection:SendRemoteEvent("ClientObjectID", true, remoteEventData)
+end
+
+function HandleClientDisconnected(eventType, eventData)
+ -- When a client disconnects, remove the controlled object
+ local connection = eventData["Connection"]:GetPtr("Connection")
+ for i, v in ipairs(clients) do
+ if v.connection == connection then
+ v.object:Remove()
+ table.remove(clients, i)
+ break
+ end
+ end
+end
+
+function HandleClientObjectID(eventType, eventData)
+ clientObjectID = eventData["ID"]:GetUInt()
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/18_CharacterDemo.lua b/bin/Data/LuaScripts/18_CharacterDemo.lua
new file mode 100644
index 0000000..a165cf1
--- /dev/null
+++ b/bin/Data/LuaScripts/18_CharacterDemo.lua
@@ -0,0 +1,493 @@
+-- Moving character example.
+-- This sample demonstrates:
+-- - Controlling a humanoid character through physics
+-- - Driving animations using the AnimationController component
+-- - Manual control of a bone scene node
+-- - Implementing 1st and 3rd person cameras, using raycasts to avoid the 3rd person camera clipping into scenery
+-- - Saving and loading the variables of a script object
+-- - Using touch inputs/gyroscope for iOS/Android (implemented through an external file)
+
+require "LuaScripts/Utilities/Sample"
+require "LuaScripts/Utilities/Touch"
+
+-- Variables used by external file are made global in order to be accessed
+
+CTRL_FORWARD = 1
+CTRL_BACK = 2
+CTRL_LEFT = 4
+CTRL_RIGHT = 8
+local CTRL_JUMP = 16
+
+local MOVE_FORCE = 0.8
+local INAIR_MOVE_FORCE = 0.02
+local BRAKE_FORCE = 0.2
+local JUMP_FORCE = 7.0
+local YAW_SENSITIVITY = 0.1
+local INAIR_THRESHOLD_TIME = 0.1
+firstPerson = false -- First person camera flag
+
+local characterNode = nil
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create static scene content
+ CreateScene()
+
+ -- Create the controllable character
+ CreateCharacter()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Subscribe to necessary events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+
+ scene_ = Scene()
+
+ -- Create scene subsystem components
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("PhysicsWorld")
+
+ -- Create camera and define viewport. Camera does not necessarily have to belong to the scene
+ cameraNode = Node()
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+ renderer:SetViewport(0, Viewport:new(scene_, camera))
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(0.5, 0.5, 0.7)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ -- Create the floor object
+ local floorNode = scene_:CreateChild("Floor")
+ floorNode.position = Vector3(0.0, -0.5, 0.0)
+ floorNode.scale = Vector3(200.0, 1.0, 200.0)
+ local object = floorNode:CreateComponent("StaticModel")
+ object.model = cache:GetResource("Model", "Models/Box.mdl")
+ object.material = cache:GetResource("Material", "Materials/Stone.xml")
+
+ local body = floorNode:CreateComponent("RigidBody")
+ -- Use collision layer bit 2 to mark world scenery. This is what we will raycast against to prevent camera from going
+ -- inside geometry
+ body.collisionLayer = 2
+ local shape = floorNode:CreateComponent("CollisionShape")
+ shape:SetBox(Vector3(1.0, 1.0, 1.0))
+
+ -- Create mushrooms of varying sizes
+ local NUM_MUSHROOMS = 60
+ for i = 1, NUM_MUSHROOMS do
+ local objectNode = scene_:CreateChild("Mushroom")
+ objectNode.position = Vector3(Random(180.0) - 90.0, 0.0, Random(180.0) - 90.0)
+ objectNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+ objectNode:SetScale(2.0 + Random(5.0))
+ local object = objectNode:CreateComponent("StaticModel")
+ object.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ object.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ object.castShadows = true
+
+ local body = objectNode:CreateComponent("RigidBody")
+ body.collisionLayer = 2
+ local shape = objectNode:CreateComponent("CollisionShape")
+ shape:SetTriangleMesh(object.model, 0)
+ end
+
+ -- Create movable boxes. Let them fall from the sky at first
+ local NUM_BOXES = 100
+ for i = 1, NUM_BOXES do
+ local scale = Random(2.0) + 0.5
+
+ local objectNode = scene_:CreateChild("Box")
+ objectNode.position = Vector3(Random(180.0) - 90.0, Random(10.0) + 10.0, Random(180.0) - 90.0)
+ objectNode.rotation = Quaternion(Random(360.0), Random(360.0), Random(360.0))
+ objectNode:SetScale(scale)
+ local object = objectNode:CreateComponent("StaticModel")
+ object.model = cache:GetResource("Model", "Models/Box.mdl")
+ object.material = cache:GetResource("Material", "Materials/Stone.xml")
+ object.castShadows = true
+
+ local body = objectNode:CreateComponent("RigidBody")
+ body.collisionLayer = 2
+ -- Bigger boxes will be heavier and harder to move
+ body.mass = scale * 2.0
+ local shape = objectNode:CreateComponent("CollisionShape")
+ shape:SetBox(Vector3(1.0, 1.0, 1.0))
+ end
+end
+
+function CreateCharacter()
+ characterNode = scene_:CreateChild("Jack")
+ characterNode.position = Vector3(0.0, 1.0, 0.0)
+
+ -- spin node
+ local adjNode = characterNode:CreateChild("AdjNode")
+ adjNode.rotation = Quaternion(180.0, Vector3(0.0, 1.0, 0.0))
+
+ -- Create the rendering component + animation controller
+ local object = adjNode:CreateComponent("AnimatedModel")
+ object.model = cache:GetResource("Model", "Models/Mutant/Mutant.mdl")
+ object.material = cache:GetResource("Material", "Models/Mutant/Materials/mutant_M.xml")
+ object.castShadows = true
+ adjNode:CreateComponent("AnimationController")
+
+ -- Set the head bone for manual control
+ object.skeleton:GetBone("Mutant:Head").animated = false
+
+ -- Create rigidbody, and set non-zero mass so that the body becomes dynamic
+ local body = characterNode:CreateComponent("RigidBody")
+ body.collisionLayer = 1
+ body.mass = 1.0
+
+ -- Set zero angular factor so that physics doesn't turn the character on its own.
+ -- Instead we will control the character yaw manually
+ body.angularFactor = Vector3(0.0, 0.0, 0.0)
+
+ -- Set the rigidbody to signal collision also when in rest, so that we get ground collisions properly
+ body.collisionEventMode = COLLISION_ALWAYS
+
+ -- Set a capsule shape for collision
+ local shape = characterNode:CreateComponent("CollisionShape")
+ shape:SetCapsule(0.7, 1.8, Vector3(0.0, 0.9, 0.0))
+
+ -- Create the character logic object, which takes care of steering the rigidbody
+ characterNode:CreateScriptObject("Character")
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText(
+ "Use WASD keys and mouse to move\n"..
+ "Space to jump, F to toggle 1st/3rd person\n"..
+ "F5 to save scene, F7 to load")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SubscribeToEvents()
+ -- Subscribe to Update event for setting the character controls before physics simulation
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe to PostUpdate event for updating the camera position after physics simulation
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate")
+
+ -- Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ if characterNode == nil then
+ return
+ end
+
+ local character = characterNode:GetScriptObject()
+ if character == nil then
+ return
+ end
+
+ -- Clear previous controls
+ character.controls:Set(CTRL_FORWARD + CTRL_BACK + CTRL_LEFT + CTRL_RIGHT + CTRL_JUMP, false)
+
+ -- Update controls using touch utility
+ if touchEnabled then UpdateTouches(character.controls) end
+
+ -- Update controls using keys
+ if ui.focusElement == nil then
+ if not touchEnabled or not useGyroscope then
+ if input:GetKeyDown(KEY_W) then character.controls:Set(CTRL_FORWARD, true) end
+ if input:GetKeyDown(KEY_S) then character.controls:Set(CTRL_BACK, true) end
+ if input:GetKeyDown(KEY_A) then character.controls:Set(CTRL_LEFT, true) end
+ if input:GetKeyDown(KEY_D) then character.controls:Set(CTRL_RIGHT, true) end
+ end
+ if input:GetKeyDown(KEY_SPACE) then character.controls:Set(CTRL_JUMP, true) end
+
+ -- Add character yaw & pitch from the mouse motion or touch input
+ if touchEnabled then
+ for i=0, input.numTouches - 1 do
+ local state = input:GetTouch(i)
+ if not state.touchedElement then -- Touch on empty space
+ local camera = cameraNode:GetComponent("Camera")
+ if not camera then return end
+
+ character.controls.yaw = character.controls.yaw + TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.x
+ character.controls.pitch = character.controls.pitch + TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.y
+ end
+ end
+ else
+ character.controls.yaw = character.controls.yaw + input.mouseMoveX * YAW_SENSITIVITY
+ character.controls.pitch = character.controls.pitch + input.mouseMoveY * YAW_SENSITIVITY
+ end
+ -- Limit pitch
+ character.controls.pitch = Clamp(character.controls.pitch, -80.0, 80.0)
+ -- Set rotation already here so that it's updated every rendering frame instead of every physics frame
+ characterNode.rotation = Quaternion(character.controls.yaw, Vector3(0.0, 1.0, 0.0))
+
+ -- Switch between 1st and 3rd person
+ if input:GetKeyPress(KEY_F) then
+ firstPerson = not firstPerson
+ end
+
+ -- Turn on/off gyroscope on mobile platform
+ if input:GetKeyPress(KEY_G) then
+ useGyroscope = not useGyroscope
+ end
+
+ -- Check for loading / saving the scene
+ if input:GetKeyPress(KEY_F5) then
+ scene_:SaveXML(fileSystem:GetProgramDir().."Data/Scenes/CharacterDemo.xml")
+ end
+ if input:GetKeyPress(KEY_F7) then
+ scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/CharacterDemo.xml")
+ -- After loading we have to reacquire the character scene node, as it has been recreated
+ -- Simply find by name as there's only one of them
+ characterNode = scene_:GetChild("Jack", true)
+ if characterNode == nil then
+ return
+ end
+ end
+ end
+end
+
+function HandlePostUpdate(eventType, eventData)
+ if characterNode == nil then
+ return
+ end
+
+ local character = characterNode:GetScriptObject()
+ if character == nil then
+ return
+ end
+
+ -- Get camera lookat dir from character yaw + pitch
+ local rot = characterNode.rotation
+ local dir = rot * Quaternion(character.controls.pitch, Vector3(1.0, 0.0, 0.0))
+
+ -- Turn head to camera pitch, but limit to avoid unnatural animation
+ local headNode = characterNode:GetChild("Mutant:Head", true)
+ local limitPitch = Clamp(character.controls.pitch, -45.0, 45.0)
+ local headDir = rot * Quaternion(limitPitch, Vector3(1.0, 0.0, 0.0))
+ -- This could be expanded to look at an arbitrary target, now just look at a point in front
+ local headWorldTarget = headNode.worldPosition + headDir * Vector3(0.0, 0.0, -1.0)
+ headNode:LookAt(headWorldTarget, Vector3(0.0, 1.0, 0.0))
+
+ if firstPerson then
+ -- First person camera: position to the head bone + offset slightly forward & up
+ cameraNode.position = headNode.worldPosition + rot * Vector3(0.0, 0.15, 0.2)
+ cameraNode.rotation = dir
+ else
+ -- Third person camera: position behind the character
+ local aimPoint = characterNode.position + rot * Vector3(0.0, 1.7, 0.0) -- You can modify x Vector3 value to translate the fixed character position (indicative range[-2;2])
+
+ -- Collide camera ray with static physics objects (layer bitmask 2) to ensure we see the character properly
+ local rayDir = dir * Vector3(0.0, 0.0, -1.0) -- For indoor scenes you can use dir * Vector3(0.0, 0.0, -0.5) to prevent camera from crossing the walls
+ local rayDistance = cameraDistance
+ local result = scene_:GetComponent("PhysicsWorld"):RaycastSingle(Ray(aimPoint, rayDir), rayDistance, 2)
+ if result.body ~= nil then
+ rayDistance = Min(rayDistance, result.distance)
+ end
+ rayDistance = Clamp(rayDistance, CAMERA_MIN_DIST, cameraDistance)
+
+ cameraNode.position = aimPoint + rayDir * rayDistance
+ cameraNode.rotation = dir
+ end
+end
+
+-- Character script object class
+Character = ScriptObject()
+
+function Character:Start()
+ -- Character controls.
+ self.controls = Controls()
+ -- Grounded flag for movement.
+ self.onGround = false
+ -- Jump flag.
+ self.okToJump = true
+ -- In air timer. Due to possible physics inaccuracy, character can be off ground for max. 1/10 second and still be allowed to move.
+ self.inAirTimer = 0.0
+
+ self:SubscribeToEvent(self.node, "NodeCollision", "Character:HandleNodeCollision")
+end
+
+function Character:Load(deserializer)
+ self.controls.yaw = deserializer:ReadFloat()
+ self.controls.pitch = deserializer:ReadFloat()
+end
+
+function Character:Save(serializer)
+ serializer:WriteFloat(self.controls.yaw)
+ serializer:WriteFloat(self.controls.pitch)
+end
+
+function Character:HandleNodeCollision(eventType, eventData)
+ local contacts = eventData["Contacts"]:GetBuffer()
+
+ while not contacts.eof do
+ local contactPosition = contacts:ReadVector3()
+ local contactNormal = contacts:ReadVector3()
+ local contactDistance = contacts:ReadFloat()
+ local contactImpulse = contacts:ReadFloat()
+
+ -- If contact is below node center and pointing up, assume it's a ground contact
+ if contactPosition.y < self.node.position.y + 1.0 then
+ local level = contactNormal.y
+ if level > 0.75 then
+ self.onGround = true
+ end
+ end
+ end
+end
+
+function Character:FixedUpdate(timeStep)
+ -- Could cache the components for faster access instead of finding them each frame
+ local body = self.node:GetComponent("RigidBody")
+ local animCtrl = self.node:GetComponent("AnimationController", true)
+
+ -- Update the in air timer. Reset if grounded
+ if not self.onGround then
+ self.inAirTimer = self.inAirTimer + timeStep
+ else
+ self.inAirTimer = 0.0
+ end
+ -- When character has been in air less than 1/10 second, it's still interpreted as being on ground
+ local softGrounded = self.inAirTimer < INAIR_THRESHOLD_TIME
+
+ -- Update movement & animation
+ local rot = self.node.rotation
+ local moveDir = Vector3(0.0, 0.0, 0.0)
+ local velocity = body.linearVelocity
+ -- Velocity on the XZ plane
+ local planeVelocity = Vector3(velocity.x, 0.0, velocity.z)
+
+ if self.controls:IsDown(CTRL_FORWARD) then
+ moveDir = moveDir + Vector3(0.0, 0.0, 1.0)
+ end
+ if self.controls:IsDown(CTRL_BACK) then
+ moveDir = moveDir + Vector3(0.0, 0.0, -1.0)
+ end
+ if self.controls:IsDown(CTRL_LEFT) then
+ moveDir = moveDir + Vector3(-1.0, 0.0, 0.0)
+ end
+ if self.controls:IsDown(CTRL_RIGHT) then
+ moveDir = moveDir + Vector3(1.0, 0.0, 0.0)
+ end
+
+ -- Normalize move vector so that diagonal strafing is not faster
+ if moveDir:LengthSquared() > 0.0 then
+ moveDir:Normalize()
+ end
+
+ -- If in air, allow control, but slower than when on ground
+ if softGrounded then
+ body:ApplyImpulse(rot * moveDir * MOVE_FORCE)
+ else
+ body:ApplyImpulse(rot * moveDir * INAIR_MOVE_FORCE)
+ end
+
+ if softGrounded then
+ -- When on ground, apply a braking force to limit maximum ground velocity
+ local brakeForce = planeVelocity * -BRAKE_FORCE
+ body:ApplyImpulse(brakeForce)
+
+ -- Jump. Must release jump control between jumps
+ if self.controls:IsDown(CTRL_JUMP) then
+ if self.okToJump then
+ body:ApplyImpulse(Vector3(0.0, 1.0, 0.0) * JUMP_FORCE)
+ self.okToJump = false
+ animCtrl:PlayExclusive("Models/Mutant/Mutant_Jump1.ani", 0, false, 0.2)
+ end
+ else
+ self.okToJump = true
+ end
+ end
+
+ if not self.onGround then
+ animCtrl:PlayExclusive("Models/Mutant/Mutant_Jump1.ani", 0, false, 0.2)
+ else
+ -- Play walk animation if moving on ground, otherwise fade it out
+ if softGrounded and not moveDir:Equals(Vector3(0.0, 0.0, 0.0)) then
+ animCtrl:PlayExclusive("Models/Mutant/Mutant_Run.ani", 0, true, 0.2)
+ -- Set walk animation speed proportional to velocity
+ animCtrl:SetSpeed("Models/Mutant/Mutant_Run.ani", planeVelocity:Length() * 0.3)
+ else
+ animCtrl:PlayExclusive("Models/Mutant/Mutant_Idle0.ani", 0, true, 0.2)
+ end
+ end
+
+ -- Reset grounded flag for next frame
+ self.onGround = false
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " 1st/3rd" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Jump" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/19_VehicleDemo.lua b/bin/Data/LuaScripts/19_VehicleDemo.lua
new file mode 100644
index 0000000..9912fad
--- /dev/null
+++ b/bin/Data/LuaScripts/19_VehicleDemo.lua
@@ -0,0 +1,388 @@
+-- Vehicle example.
+-- This sample demonstrates:
+-- - Creating a heightmap terrain with collision
+-- - Constructing a physical vehicle with rigid bodies for the hull and the wheels, joined with constraints
+-- - Saving and loading the variables of a script object, including node & component references
+
+require "LuaScripts/Utilities/Sample"
+
+local CTRL_FORWARD = 1
+local CTRL_BACK = 2
+local CTRL_LEFT = 4
+local CTRL_RIGHT = 8
+
+local CAMERA_DISTANCE = 10.0
+local YAW_SENSITIVITY = 0.1
+local ENGINE_POWER = 10.0
+local DOWN_FORCE = 10.0
+local MAX_WHEEL_ANGLE = 22.5
+
+local vehicleNode = nil
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create static scene content
+ CreateScene()
+
+ -- Create the controllable vehicle
+ CreateVehicle()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Subscribe to necessary events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create scene subsystem components
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("PhysicsWorld")
+
+ -- Create camera and define viewport. Camera does not necessarily have to belong to the scene
+ cameraNode = Node()
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 500.0
+
+ renderer:SetViewport(0, Viewport:new(scene_, camera))
+
+ -- Create static scene content. First create a zone for ambient lighting and fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(0.5, 0.5, 0.7)
+ zone.fogStart = 300.0
+ zone.fogEnd = 500.0
+ zone.boundingBox = BoundingBox(-2000.0, 2000.0)
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.3, -0.5, 0.425)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+ light.specularIntensity = 0.5
+
+ -- Create heightmap terrain with collision
+ local terrainNode = scene_:CreateChild("Terrain")
+ terrainNode.position = Vector3(0.0, 0.0, 0.0)
+ local terrain = terrainNode:CreateComponent("Terrain")
+ terrain.patchSize = 64
+ terrain.spacing = Vector3(2.0, 0.1, 2.0) -- Spacing between vertices and vertical resolution of the height map
+ terrain.smoothing = true
+ terrain.heightMap = cache:GetResource("Image", "Textures/HeightMap.png")
+ terrain.material = cache:GetResource("Material", "Materials/Terrain.xml")
+ -- The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
+ -- terrain patches and other objects behind it
+ terrain.occluder = true
+
+ local body = terrainNode:CreateComponent("RigidBody")
+ body.collisionLayer = 2 -- Use layer bitmask 2 for static geometry
+ local shape = terrainNode:CreateComponent("CollisionShape")
+ shape:SetTerrain()
+
+ -- Create 1000 mushrooms in the terrain. Always face outward along the terrain normal
+ local NUM_MUSHROOMS = 1000
+ for i = 1, NUM_MUSHROOMS do
+ local objectNode = scene_:CreateChild("Mushroom")
+ local position = Vector3(Random(2000.0) - 1000.0, 0.0, Random(2000.0) - 1000.0)
+ position.y = terrain:GetHeight(position) - 0.1
+ objectNode.position = position
+ -- Create a rotation quaternion from up vector to terrain normal
+ objectNode.rotation = Quaternion(Vector3(0.0, 1.0, 0.0), terrain:GetNormal(position))
+ objectNode:SetScale(3.0)
+ local object = objectNode:CreateComponent("StaticModel")
+ object.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ object.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ object.castShadows = true
+
+ local body = objectNode:CreateComponent("RigidBody")
+ body.collisionLayer = 2
+ local shape = objectNode:CreateComponent("CollisionShape")
+ shape:SetTriangleMesh(object.model, 0)
+ end
+end
+
+function CreateVehicle()
+ vehicleNode = scene_:CreateChild("Vehicle")
+ vehicleNode.position = Vector3(0.0, 5.0, 0.0)
+
+ -- Create the vehicle logic script object
+ local vehicle = vehicleNode:CreateScriptObject("Vehicle")
+ -- Create the rendering and physics components
+ vehicle:Init()
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText.text = "Use WASD keys to drive, mouse/touch to rotate camera\n"..
+ "F5 to save scene, F7 to load"
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SubscribeToEvents()
+ -- Subscribe to Update event for setting the vehicle controls before physics simulation
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe to PostUpdate event for updating the camera position after physics simulation
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate")
+
+ -- Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ if vehicleNode == nil then
+ return
+ end
+
+ local vehicle = vehicleNode:GetScriptObject()
+ if vehicle == nil then
+ return
+ end
+
+ -- Get movement controls and assign them to the vehicle component. If UI has a focused element, clear controls
+ if ui.focusElement == nil then
+ vehicle.controls:Set(CTRL_FORWARD, input:GetKeyDown(KEY_W))
+ vehicle.controls:Set(CTRL_BACK, input:GetKeyDown(KEY_S))
+ vehicle.controls:Set(CTRL_LEFT, input:GetKeyDown(KEY_A))
+ vehicle.controls:Set(CTRL_RIGHT, input:GetKeyDown(KEY_D))
+
+ -- Add yaw & pitch from the mouse motion or touch input. Used only for the camera, does not affect motion
+ if touchEnabled then
+ for i=0, input.numTouches - 1 do
+ local state = input:GetTouch(i)
+ if not state.touchedElement then -- Touch on empty space
+ local camera = cameraNode:GetComponent("Camera")
+ if not camera then return end
+
+ vehicle.controls.yaw = vehicle.controls.yaw + TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.x
+ vehicle.controls.pitch = vehicle.controls.pitch + TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.y
+ end
+ end
+ else
+ vehicle.controls.yaw = vehicle.controls.yaw + input.mouseMoveX * YAW_SENSITIVITY
+ vehicle.controls.pitch = vehicle.controls.pitch + input.mouseMoveY * YAW_SENSITIVITY
+ end
+ -- Limit pitch
+ vehicle.controls.pitch = Clamp(vehicle.controls.pitch, 0.0, 80.0)
+
+ -- Check for loading / saving the scene
+ if input:GetKeyPress(KEY_F5) then
+ scene_:SaveXML(fileSystem:GetProgramDir() .. "Data/Scenes/VehicleDemo.xml")
+ end
+ if input:GetKeyPress(KEY_F7) then
+ scene_:LoadXML(fileSystem:GetProgramDir() .. "Data/Scenes/VehicleDemo.xml")
+ -- After loading we have to reacquire the vehicle scene node, as it has been recreated
+ -- Simply find by name as there's only one of them
+ vehicleNode = scene_:GetChild("Vehicle", true)
+ vehicleNode:GetScriptObject():PostInit()
+ end
+ else
+ vehicle.controls:Set(CTRL_FORWARD + CTRL_BACK + CTRL_LEFT + CTRL_RIGHT, false)
+ end
+end
+
+function HandlePostUpdate(eventType, eventData)
+ if vehicleNode == nil then
+ return
+ end
+
+ local vehicle = vehicleNode:GetScriptObject()
+ if vehicle == nil then
+ return
+ end
+
+ -- Physics update has completed. Position camera behind vehicle
+ local dir = Quaternion(vehicleNode.rotation:YawAngle(), Vector3(0.0, 1.0, 0.0))
+ dir = dir * Quaternion(vehicle.controls.yaw, Vector3(0.0, 1.0, 0.0))
+ dir = dir * Quaternion(vehicle.controls.pitch, Vector3(1.0, 0.0, 0.0))
+
+ local cameraTargetPos = vehicleNode.position - dir * Vector3(0.0, 0.0, CAMERA_DISTANCE)
+ local cameraStartPos = vehicleNode.position
+ -- Raycast camera against static objects (physics collision mask 2)
+ -- and move it closer to the vehicle if something in between
+ local cameraRay = Ray(cameraStartPos, (cameraTargetPos - cameraStartPos):Normalized())
+ local cameraRayLength = (cameraTargetPos - cameraStartPos):Length()
+ local physicsWorld = scene_:GetComponent("PhysicsWorld")
+ local result = physicsWorld:RaycastSingle(cameraRay, cameraRayLength, 2)
+ if result.body ~= nil then
+ cameraTargetPos = cameraStartPos + cameraRay.direction * (result.distance - 0.5)
+ end
+ cameraNode.position = cameraTargetPos
+ cameraNode.rotation = dir
+end
+
+-- Vehicle script object class
+--
+-- When saving, the node and component handles are automatically converted into nodeID or componentID attributes
+-- and are acquired from the scene when loading. The steering member variable will likewise be saved automatically.
+-- The Controls object can not be automatically saved, so handle it manually in the Load() and Save() methods
+
+Vehicle = ScriptObject()
+
+function Vehicle:Start()
+ -- Current left/right steering amount (-1 to 1.)
+ self.steering = 0.0
+ -- Vehicle controls.
+ self.controls = Controls()
+end
+
+function Vehicle:Load(deserializer)
+ self.controls.yaw = deserializer:ReadFloat()
+ self.controls.pitch = deserializer:ReadFloat()
+end
+
+function Vehicle:Save(serializer)
+ serializer:WriteFloat(self.controls.yaw)
+ serializer:WriteFloat(self.controls.pitch)
+end
+
+function Vehicle:Init()
+ -- This function is called only from the main program when initially creating the vehicle, not on scene load
+ local node = self.node
+ local hullObject = node:CreateComponent("StaticModel")
+ self.hullBody = node:CreateComponent("RigidBody")
+ local hullShape = node:CreateComponent("CollisionShape")
+
+ node.scale = Vector3(1.5, 1.0, 3.0)
+ hullObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ hullObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+ hullObject.castShadows = true
+ hullShape:SetBox(Vector3(1.0, 1.0, 1.0))
+
+ self.hullBody.mass = 4.0
+ self.hullBody.linearDamping = 0.2 -- Some air resistance
+ self.hullBody.angularDamping = 0.5
+ self.hullBody.collisionLayer = 1
+ self.frontLeft = self:InitWheel("FrontLeft", Vector3(-0.6, -0.4, 0.3))
+ self.frontRight = self:InitWheel("FrontRight", Vector3(0.6, -0.4, 0.3))
+ self.rearLeft = self:InitWheel("RearLeft", Vector3(-0.6, -0.4, -0.3))
+ self.rearRight = self:InitWheel("RearRight", Vector3(0.6, -0.4, -0.3))
+
+ self:PostInit()
+end
+
+function Vehicle:PostInit()
+ self.frontLeft = scene_:GetChild("FrontLeft")
+ self.frontRight = scene_:GetChild("FrontRight")
+ self.rearLeft = scene_:GetChild("RearLeft")
+ self.rearRight = scene_:GetChild("RearRight")
+
+ self.frontLeftAxis = self.frontLeft:GetComponent("Constraint")
+ self.frontRightAxis = self.frontRight:GetComponent("Constraint")
+
+ self.hullBody = self.node:GetComponent("RigidBody")
+
+ self.frontLeftBody = self.frontLeft:GetComponent("RigidBody")
+ self.frontRightBody = self.frontRight:GetComponent("RigidBody")
+ self.rearLeftBody = self.rearLeft:GetComponent("RigidBody")
+ self.rearRightBody = self.rearRight:GetComponent("RigidBody")
+end
+
+function Vehicle:InitWheel(name, offset)
+ -- Note: do not parent the wheel to the hull scene node. Instead create it on the root level and let the physics
+ -- constraint keep it together
+ local wheelNode = scene_:CreateChild(name)
+ local node = self.node
+ wheelNode.position = node:LocalToWorld(offset)
+ if offset.x >= 0.0 then
+ wheelNode.rotation = node.worldRotation * Quaternion(0.0, 0.0, -90.0)
+ else
+ wheelNode.rotation = node.worldRotation * Quaternion(0.0, 0.0, 90.0)
+ end
+ wheelNode.scale = Vector3(0.8, 0.5, 0.8)
+
+ local wheelObject = wheelNode:CreateComponent("StaticModel")
+ local wheelBody = wheelNode:CreateComponent("RigidBody")
+ local wheelShape = wheelNode:CreateComponent("CollisionShape")
+ local wheelConstraint = wheelNode:CreateComponent("Constraint")
+
+ wheelObject.model = cache:GetResource("Model", "Models/Cylinder.mdl")
+ wheelObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+ wheelObject.castShadows = true
+ wheelShape:SetSphere(1.0)
+ wheelBody.friction = 1
+ wheelBody.mass = 1
+ wheelBody.linearDamping = 0.2 -- Some air resistance
+ wheelBody.angularDamping = 0.75 -- Could also use rolling friction
+ wheelBody.collisionLayer = 1
+ wheelConstraint.constraintType = CONSTRAINT_HINGE
+ wheelConstraint.otherBody = node:GetComponent("RigidBody")
+ wheelConstraint.worldPosition = wheelNode.worldPosition -- Set constraint's both ends at wheel's location
+ wheelConstraint.axis = Vector3(0.0, 1.0, 0.0) -- Wheel rotates around its local Y-axis
+
+ if offset.x >= 0.0 then -- Wheel's hull axis points either left or right
+ wheelConstraint.otherAxis = Vector3(1.0, 0.0, 0.0)
+ else
+ wheelConstraint.otherAxis = Vector3(-1.0, 0.0, 0.0)
+ end
+
+ wheelConstraint.lowLimit = Vector2(-180.0, 0.0) -- Let the wheel rotate freely around the axis
+ wheelConstraint.highLimit = Vector2(180.0, 0.0)
+ wheelConstraint.disableCollision = true -- Let the wheel intersect the vehicle hull
+
+ return wheelNode
+end
+
+function Vehicle:FixedUpdate(timeStep)
+ local newSteering = 0.0
+ local accelerator = 0.0
+
+ if self.controls:IsDown(CTRL_LEFT) then
+ newSteering = -1.0
+ end
+ if self.controls:IsDown(CTRL_RIGHT) then
+ newSteering = 1.0
+ end
+ if self.controls:IsDown(CTRL_FORWARD) then
+ accelerator = 1.0
+ end
+ if self.controls:IsDown(CTRL_BACK) then
+ accelerator = -0.5
+ end
+
+ -- When steering, wake up the wheel rigidbodies so that their orientation is updated
+ if newSteering ~= 0.0 then
+ self.frontLeftBody:Activate()
+ self.frontRightBody:Activate()
+ self.steering = self.steering * 0.95 + newSteering * 0.05
+ else
+ self.steering = self.steering * 0.8 + newSteering * 0.2
+ end
+
+ local steeringRot = Quaternion(0.0, self.steering * MAX_WHEEL_ANGLE, 0.0)
+ self.frontLeftAxis.otherAxis = steeringRot * Vector3(-1.0, 0.0, 0.0)
+ self.frontRightAxis.otherAxis = steeringRot * Vector3(1.0, 0.0, 0.0)
+
+ if accelerator ~= 0.0 then
+ -- Torques are applied in world space, so need to take the vehicle & wheel rotation into account
+ local torqueVec = Vector3(ENGINE_POWER * accelerator, 0.0, 0.0)
+ local node = self.node
+ self.frontLeftBody:ApplyTorque(node.rotation * steeringRot * torqueVec)
+ self.frontRightBody:ApplyTorque(node.rotation * steeringRot * torqueVec)
+ self.rearLeftBody:ApplyTorque(node.rotation * torqueVec)
+ self.rearRightBody:ApplyTorque(node.rotation * torqueVec)
+ end
+
+ -- Apply downforce proportional to velocity
+ local localVelocity = self.hullBody.rotation:Inverse() * self.hullBody.linearVelocity
+ self.hullBody:ApplyForce(self.hullBody.rotation * Vector3(0.0, -1.0, 0.0) * Abs(localVelocity.z) * DOWN_FORCE)
+end
diff --git a/bin/Data/LuaScripts/20_HugeObjectCount.lua b/bin/Data/LuaScripts/20_HugeObjectCount.lua
new file mode 100644
index 0000000..7b0e241
--- /dev/null
+++ b/bin/Data/LuaScripts/20_HugeObjectCount.lua
@@ -0,0 +1,226 @@
+-- Huge object count example.
+-- This sample demonstrates:
+-- - Creating a scene with 250 x 250 simple objects
+-- - Competing with http://yosoygames.com.ar/wp/2013/07/ogre-2-0-is-up-to-3x-faster/ :)
+-- - Allowing examination of performance hotspots in the rendering code
+-- - Optionally speeding up rendering by grouping objects with the StaticModelGroup component
+
+require "LuaScripts/Utilities/Sample"
+
+local boxNodes = {}
+local animate = false
+local useGroups = false
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ if scene_ == nil then
+ scene_ = Scene()
+ else
+ scene_:Clear()
+ boxNodes = {}
+ end
+
+ -- Create the Octree component to the scene so that drawable objects can be rendered. Use default volume
+ -- (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_:CreateComponent("Octree")
+
+ -- Create a Zone for ambient light & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.fogColor = Color(0.2, 0.2, 0.2)
+ zone.fogStart = 200.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(-0.6, -1.0, -0.8) -- The direction vector does not need to be normalized
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+
+ if not useGroups then
+ light.color = Color(0.7, 0.35, 0.0)
+
+ -- Create individual box StaticModels in the scene
+ for y = -125, 125 do
+ for x = -125, 125 do
+ local boxNode = scene_:CreateChild("Box")
+ boxNode.position = Vector3(x * 0.3, 0.0, y * 0.3)
+ boxNode:SetScale(0.25)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ table.insert(boxNodes, boxNode)
+ end
+ end
+ else
+ light.color = Color(0.6, 0.6, 0.6)
+ light.specularIntensity = 1.5
+
+ -- Create StaticModelGroups in the scene
+ local lastGroup = nil
+
+ for y = -125, 125 do
+ for x = -125, 125 do
+ -- Create new group if no group yet, or the group has already "enough" objects. The tradeoff is between culling
+ -- accuracy and the amount of CPU processing needed for all the objects. Note that the group's own transform
+ -- does not matter, and it does not render anything if instance nodes are not added to it
+ if lastGroup == nil or lastGroup.numInstanceNodes >= 25 * 25 then
+ local boxGroupNode = scene_:CreateChild("BoxGroup")
+ lastGroup = boxGroupNode:CreateComponent("StaticModelGroup")
+ lastGroup.model = cache:GetResource("Model", "Models/Box.mdl")
+ end
+
+ local boxNode = scene_:CreateChild("Box")
+ boxNode.position = Vector3(x * 0.3, 0.0, y * 0.3)
+ boxNode:SetScale(0.25)
+ table.insert(boxNodes, boxNode)
+ lastGroup:AddInstanceNode(boxNode)
+ end
+ end
+ end
+
+ -- Create the camera. Create it outside the scene so that we can clear the whole scene without affecting it
+ if cameraNode == nil then
+ cameraNode = Node()
+ cameraNode.position = Vector3(0.0, 10.0, -100.0)
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+ end
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move\n"..
+ "Space to toggle animation\n"..
+ "G to toggle object group optimization")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
+
+function AnimateObjects(timeStep)
+ local ROTATE_SPEED = 15.0
+ local delta = ROTATE_SPEED * timeStep
+ local rotateQuat = Quaternion(delta, Vector3(0.0, 0.0, 1.0))
+ for i, v in ipairs(boxNodes) do
+ v:Rotate(rotateQuat)
+ end
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Toggle animation with space
+ if input:GetKeyPress(KEY_SPACE) then
+ animate = not animate
+ end
+
+ -- Toggle grouped / ungrouped mode
+ if input:GetKeyPress(KEY_G) then
+ useGroups = not useGroups
+ CreateScene()
+ end
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+
+ -- Animate scene if enabled
+ if animate then
+ AnimateObjects(timeStep)
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Group" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Animation" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/23_Water.lua b/bin/Data/LuaScripts/23_Water.lua
new file mode 100644
index 0000000..a6dce0e
--- /dev/null
+++ b/bin/Data/LuaScripts/23_Water.lua
@@ -0,0 +1,222 @@
+-- Water example.
+-- This sample demonstrates:
+-- - Creating a large plane to represent a water body for rendering
+-- - Setting up a second camera to render reflections on the water surface
+
+require "LuaScripts/Utilities/Sample"
+
+local reflectionCameraNode = nil
+local waterNode = nil
+local waterPlane = Plane()
+local waterClipPlane = Plane()
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update event
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_:CreateComponent("Octree")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(1.0, 1.0, 1.0)
+ zone.fogStart = 500.0
+ zone.fogEnd = 750.0
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+ light.specularIntensity = 0.5
+ -- Apply slightly overbright lighting to match the skybox
+ light.color = Color(1.2, 1.2, 1.2)
+
+ -- Create skybox. The Skybox component is used like StaticModel, but it will be always located at the camera, giving the
+ -- illusion of the box planes being far away. Use just the ordinary Box model and a suitable material, whose shader will
+ -- generate the necessary 3D texture coordinates for cube mapping
+ local skyNode = scene_:CreateChild("Sky")
+ skyNode:SetScale(500.0) -- The scale actually does not matter
+ local skybox = skyNode:CreateComponent("Skybox")
+ skybox.model = cache:GetResource("Model", "Models/Box.mdl")
+ skybox.material = cache:GetResource("Material", "Materials/Skybox.xml")
+
+ -- Create heightmap terrain
+ local terrainNode = scene_:CreateChild("Terrain")
+ terrainNode.position = Vector3(0.0, 0.0, 0.0)
+ local terrain = terrainNode:CreateComponent("Terrain")
+ terrain.patchSize = 64
+ terrain.spacing = Vector3(2.0, 0.5, 2.0) -- Spacing between vertices and vertical resolution of the height map
+ terrain.smoothing = true
+ terrain.heightMap = cache:GetResource("Image", "Textures/HeightMap.png")
+ terrain.material = cache:GetResource("Material", "Materials/Terrain.xml")
+ -- The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
+ -- terrain patches and other objects behind it
+ terrain.occluder = true
+
+ -- Create 1000 boxes in the terrain. Always face outward along the terrain normal
+ local NUM_OBJECTS = 1000
+ for i = 1, NUM_OBJECTS do
+ local objectNode = scene_:CreateChild("Box")
+ local position = Vector3(Random(2000.0) - 1000.0, 0.0, Random(2000.0) - 1000.0)
+ position.y = terrain:GetHeight(position) + 2.25
+ objectNode.position = position
+ -- Create a rotation quaternion from up vector to terrain normal
+ objectNode.rotation = Quaternion(Vector3(0.0, 1.0, 0.0), terrain:GetNormal(position))
+ objectNode:SetScale(5.0)
+ local object = objectNode:CreateComponent("StaticModel")
+ object.model = cache:GetResource("Model", "Models/Box.mdl")
+ object.material = cache:GetResource("Material", "Materials/Stone.xml")
+ object.castShadows = true
+ end
+
+ -- Create a water plane object that is as large as the terrain
+ waterNode = scene_:CreateChild("Water")
+ waterNode.scale = Vector3(2048.0, 1.0, 2048.0)
+ waterNode.position = Vector3(0.0, 5.0, 0.0)
+ local water = waterNode:CreateComponent("StaticModel")
+ water.model = cache:GetResource("Model", "Models/Plane.mdl")
+ water.material = cache:GetResource("Material", "Materials/Water.xml")
+ -- Set a different viewmask on the water plane to be able to hide it from the reflection camera
+ water.viewMask = 0x80000000
+
+ -- Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside
+ -- the scene, because we want it to be unaffected by scene load / save
+ cameraNode = Node()
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 750.0
+
+ -- Set an initial position for the camera scene node above the floor
+ cameraNode.position = Vector3(0.0, 7.0, -20.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+
+ -- Create a mathematical plane to represent the water in calculations
+ waterPlane = Plane(waterNode.worldRotation * Vector3(0.0, 1.0, 0.0), waterNode.worldPosition)
+ -- Create a downward biased plane for reflection view clipping. Biasing is necessary to avoid too aggressive clipping
+ waterClipPlane = Plane(waterNode.worldRotation * Vector3(0.0, 1.0, 0.0), waterNode.worldPosition -
+ Vector3(0.0, 0.1, 0.0))
+
+ -- Create camera for water reflection
+ -- It will have the same farclip and position as the main viewport camera, but uses a reflection plane to modify
+ -- its position when rendering
+ reflectionCameraNode = cameraNode:CreateChild()
+ local reflectionCamera = reflectionCameraNode:CreateComponent("Camera")
+ reflectionCamera.farClip = 750.0
+ reflectionCamera.viewMask = 0x7fffffff -- Hide objects with only bit 31 in the viewmask (the water plane)
+ reflectionCamera.autoAspectRatio = false
+ reflectionCamera.useReflection = true
+ reflectionCamera.reflectionPlane = waterPlane
+ reflectionCamera.useClipping = true -- Enable clipping of geometry behind water plane
+ reflectionCamera.clipPlane = waterClipPlane
+ -- The water reflection texture is rectangular. Set reflection camera aspect ratio to match
+ reflectionCamera.aspectRatio = graphics.width / graphics.height
+ -- View override flags could be used to optimize reflection rendering. For example disable shadows
+ --reflectionCamera.viewOverrideFlags = VO_DISABLE_SHADOWS
+
+ -- Create a texture and setup viewport for water reflection. Assign the reflection texture to the diffuse
+ -- texture unit of the water material
+ local texSize = 1024
+ local renderTexture = Texture2D:new()
+ renderTexture:SetSize(texSize, texSize, Graphics:GetRGBFormat(), TEXTURE_RENDERTARGET)
+ renderTexture.filterMode = FILTER_BILINEAR
+ local surface = renderTexture.renderSurface
+ local rttViewport = Viewport:new(scene_, reflectionCamera)
+ surface:SetViewport(0, rttViewport)
+ local waterMat = cache:GetResource("Material", "Materials/Water.xml")
+ waterMat:SetTexture(TU_DIFFUSE, renderTexture)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ -- In case resolution has changed, adjust the reflection camera aspect ratio
+ local reflectionCamera = reflectionCameraNode:GetComponent("Camera")
+ reflectionCamera.aspectRatio = graphics.width / graphics.height
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
diff --git a/bin/Data/LuaScripts/24_Urho2DSprite.lua b/bin/Data/LuaScripts/24_Urho2DSprite.lua
new file mode 100644
index 0000000..7c62632
--- /dev/null
+++ b/bin/Data/LuaScripts/24_Urho2DSprite.lua
@@ -0,0 +1,206 @@
+-- Urho2D sprite example.
+-- This sample demonstrates:
+-- - Creating a 2D scene with sprite
+-- - Displaying the scene using the Renderer subsystem
+-- - Handling keyboard to move and zoom 2D camera
+-- - Usage of Lua Closure to update scene
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 0.0, -10.0)
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.orthographic = true
+ camera.orthoSize = graphics.height * PIXEL_SIZE
+
+ local sprite = cache:GetResource("Sprite2D", "Urho2D/Aster.png")
+ if sprite == nil then
+ return
+ end
+
+ local spriteNodes = {}
+ local NUM_SPRITES = 200
+ local halfWidth = graphics.width * PIXEL_SIZE * 0.5
+ local halfHeight = graphics.height * PIXEL_SIZE * 0.5
+ for i = 1, NUM_SPRITES do
+ local spriteNode = scene_:CreateChild("StaticSprite2D")
+ spriteNode.position = Vector3(Random(-halfWidth, halfWidth), Random(-halfHeight, halfHeight), 0.0)
+
+ local staticSprite = spriteNode:CreateComponent("StaticSprite2D")
+ -- Set color
+ staticSprite.color = Color(Random(1.0), Random(1.0), Random(1.0), 1.0)
+ -- Set blend mode
+ staticSprite.blendMode = BLEND_ALPHA
+ -- Set sprite
+ staticSprite.sprite = sprite
+
+ -- Set move speed
+ spriteNode.moveSpeed = Vector3(Random(-2.0, 2.0), Random(-2.0, 2.0), 0.0)
+ -- Set rotate speed
+ spriteNode.rotateSpeed = Random(-90.0, 90.0)
+
+ table.insert(spriteNodes, spriteNode)
+ end
+
+ scene_.Update = function(self, timeStep)
+ for _, spriteNode in ipairs(spriteNodes) do
+ local position = spriteNode.position
+ local moveSpeed = spriteNode.moveSpeed
+ local newPosition = position + moveSpeed * timeStep
+
+ if newPosition.x < -halfWidth or newPosition.x > halfWidth then
+ newPosition.x = position.x
+ moveSpeed.x = -moveSpeed.x
+ end
+
+ if newPosition.y < -halfHeight or newPosition.y > halfHeight then
+ newPosition.y = position.y
+ moveSpeed.y = -moveSpeed.y
+ end
+
+ spriteNode.position = newPosition
+ spriteNode:Roll(spriteNode.rotateSpeed * timeStep)
+ end
+ end
+
+ local animationSet = cache:GetResource("AnimationSet2D", "Urho2D/GoldIcon.scml")
+ if animationSet == nil then
+ return
+ end
+
+ local spriteNode = scene_:CreateChild("AnimatedSprite2D")
+ spriteNode.position = Vector3(0.0, 0.0, -1.0)
+
+ local animatedSprite = spriteNode:CreateComponent("AnimatedSprite2D")
+ -- Set animation
+ animatedSprite.animationSet = animationSet
+ animatedSprite.animation = "idle"
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move, Use PageUp PageDown to zoom.")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 4.0
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 1.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, -1.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ if input:GetKeyDown(KEY_PAGEUP) then
+ local camera = cameraNode:GetComponent("Camera")
+ camera.zoom = camera.zoom * 1.01
+ end
+
+ if input:GetKeyDown(KEY_PAGEDOWN) then
+ local camera = cameraNode:GetComponent("Camera")
+ camera.zoom = camera.zoom * 0.99
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+
+ -- Update scene
+ scene_:Update(timeStep)
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Zoom In" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Zoom Out" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/25_Urho2DParticle.lua b/bin/Data/LuaScripts/25_Urho2DParticle.lua
new file mode 100644
index 0000000..67cdc02
--- /dev/null
+++ b/bin/Data/LuaScripts/25_Urho2DParticle.lua
@@ -0,0 +1,117 @@
+-- Urho2D particle example.
+-- This sample demonstrates:
+-- - Creating a 2D scene with particle
+-- - Displaying the scene using the Renderer subsystem
+-- - Handling mouse move to move particle
+
+require "LuaScripts/Utilities/Sample"
+
+local particleNode = nil
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Set mouse visible
+ input.mouseVisible = true
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 0.0, -10.0)
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.orthographic = true
+ camera.orthoSize = graphics.height * PIXEL_SIZE
+ camera.zoom = 1.2 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.2) is set for full visibility at 1280x800 resolution)
+
+ local particleEffect = cache:GetResource("ParticleEffect2D", "Urho2D/sun.pex")
+ if particleEffect == nil then
+ return
+ end
+
+ particleNode = scene_:CreateChild("ParticleEmitter2D")
+ local particleEmitter = particleNode:CreateComponent("ParticleEmitter2D")
+ particleEmitter.effect = particleEffect
+
+ local greenSpiralEffect = cache:GetResource("ParticleEffect2D", "Urho2D/greenspiral.pex")
+ if greenSpiralEffect == nil then
+ return
+ end
+
+ greenSpiralNode = scene_:CreateChild("GreenSpiral")
+ local greenSpiralEmitter = greenSpiralNode:CreateComponent("ParticleEmitter2D")
+ greenSpiralEmitter.effect = greenSpiralEffect
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use mouse to move the particle.")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleMouseMove() function for tracking mouse/touch move events
+ SubscribeToEvent("MouseMove", "HandleMouseMove")
+ if touchEnabled then SubscribeToEvent("TouchMove", "HandleMouseMove") end
+
+ -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleMouseMove(eventType, eventData)
+ if particleNode ~= nil then
+ local x = eventData["x"]:GetInt()
+ local y = eventData["y"]:GetInt()
+ local camera = cameraNode:GetComponent("Camera")
+ particleNode.position = camera:ScreenToWorldPoint(Vector3(x / graphics.width, y / graphics.height, 10.0))
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/26_ConsoleInput.lua b/bin/Data/LuaScripts/26_ConsoleInput.lua
new file mode 100644
index 0000000..000dc4a
--- /dev/null
+++ b/bin/Data/LuaScripts/26_ConsoleInput.lua
@@ -0,0 +1,250 @@
+-- Console input example.
+-- This sample demonstrates:
+-- - Implementing a crude text adventure game, which accepts input both through the engine console,
+-- and standard input.
+-- - Disabling default execution of console commands as immediate mode Lua script.
+-- - Adding autocomplete options to the engine console.
+
+require "LuaScripts/Utilities/Sample"
+
+local gameOn = false
+local foodAvailable = false
+local eatenLastTurn = false
+local numTurns = 0
+local hunger = 0
+local urhoThreat = 0
+
+-- Hunger level descriptions
+local hungerLevels = {
+ "bursting",
+ "well-fed",
+ "fed",
+ "hungry",
+ "very hungry",
+ "starving"
+}
+
+-- Urho threat level descriptions
+local urhoThreatLevels = {
+ "Suddenly Urho appears from a dark corner of the fish tank",
+ "Urho seems to have his eyes set on you",
+ "Urho is homing in on you mercilessly"
+}
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Disable default execution of Lua from the console
+ SetExecuteConsoleCommands(false)
+
+ -- Subscribe to console commands and the frame update
+ SubscribeToEvent("ConsoleCommand", "HandleConsoleCommand")
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe key down event
+ SubscribeToEvent("KeyDown", "HandleEscKeyDown")
+
+ -- Hide logo to make room for the console
+ SetLogoVisible(false)
+
+ -- Show the console by default, make it large
+ console.numRows = graphics.height / 16
+ console.numBufferedRows = 2 * console.numRows
+ console.commandInterpreter = "LuaScriptEventInvoker"
+ console.visible = true
+ console.closeButton.visible = false
+ console:AddAutoComplete("help")
+ console:AddAutoComplete("eat")
+ console:AddAutoComplete("hide")
+ console:AddAutoComplete("wait")
+ console:AddAutoComplete("score")
+ console:AddAutoComplete("quit")
+
+ -- Show OS mouse cursor
+ input.mouseVisible = true
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Open the operating system console window (for stdin / stdout) if not open yet
+ -- Do not open in fullscreen, as this would cause constant device loss
+ if not graphics.fullscreen then
+ OpenConsoleWindow()
+ end
+
+ -- Initialize game and print the welcome message
+ StartGame()
+
+ -- Randomize from system clock
+ SetRandomSeed(Time:GetSystemTime())
+end
+
+function HandleConsoleCommand(eventType, eventData)
+ if eventData["Id"]:GetString() == "LuaScriptEventInvoker" then
+ HandleInput(eventData["Command"]:GetString())
+ end
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Check if there is input from stdin
+ local input = GetConsoleInput()
+ if input:len() > 0 then
+ HandleInput(input)
+ end
+end
+
+function HandleEscKeyDown(eventType, eventData)
+ -- Unlike the other samples, exiting the engine when ESC is pressed instead of just closing the console
+ if eventData["Key"]:GetInt() == KEY_ESCAPE then
+ engine:Exit()
+ end
+end
+
+function StartGame()
+ print("Welcome to the Urho adventure game! You are the newest fish in the tank your\n" ..
+ "objective is to survive as long as possible. Beware of hunger and the merciless\n" ..
+ "predator cichlid Urho, who appears from time to time. Evading Urho is easier\n" ..
+ "with an empty stomach. Type 'help' for available commands.")
+
+ gameOn = true
+ foodAvailable = false
+ eatenLastTurn = false
+ numTurns = 0
+ hunger = 2
+ urhoThreat = 0
+end
+
+function EndGame(message)
+ print(message)
+ print("Game over! You survived " .. numTurns .. " turns.\n" ..
+ "Do you want to play again (Y/N)?")
+
+ gameOn = false
+end
+
+function Advance()
+ if urhoThreat > 0 then
+ urhoThreat = urhoThreat + 1
+ if urhoThreat > 3 then
+ EndGame("Urho has eaten you!")
+ return
+ end
+ elseif urhoThreat < 0 then
+ urhoThreat = urhoThreat + 1
+ elseif urhoThreat == 0 and Random() < 0.2 then
+ urhoThreat = urhoThreat + 1
+ end
+
+ if urhoThreat > 0 then
+ print(urhoThreatLevels[urhoThreat] .. ".")
+ end
+
+ if (numTurns % 4) == 0 and not eatenLastTurn then
+ hunger = hunger + 1
+ if hunger > 5 then
+ EndGame("You have died from starvation!")
+ return
+ else
+ print("You are " .. hungerLevels[hunger + 1] .. ".")
+ end
+ end
+
+ eatenLastTurn = false
+
+ if foodAvailable then
+ print("The floating pieces of fish food are quickly eaten by other fish in the tank.")
+ foodAvailable = false
+ elseif Random() < 0.15 then
+ print("The overhead dispenser drops pieces of delicious fish food to the water!")
+ foodAvailable = true
+ end
+
+ numTurns = numTurns + 1
+end
+
+function TrimInput(input)
+ return input:gsub("^%s*(.-)%s*$", "%1")
+end
+
+function HandleInput(input)
+ local inputLower = TrimInput(input:lower())
+ if inputLower:len() == 0 then
+ print("Empty input given!")
+ return
+ end
+
+ if inputLower == "quit" or inputLower == "exit" then
+ engine:Exit()
+ elseif gameOn then
+ -- Game is on
+ if inputLower == "help" then
+ print("The following commands are available: 'eat', 'hide', 'wait', 'score', 'quit'.")
+ elseif inputLower == "score" then
+ print("You have survived " .. numTurns .. " turns.")
+ elseif inputLower == "eat" then
+ if foodAvailable then
+ print("You eat several pieces of fish food.")
+ foodAvailable = false
+ eatenLastTurn = true
+ if hunger > 3 then
+ hunger = hunger - 2
+ else
+ hunger = hunger - 1
+ end
+ if hunger < 0 then
+ EndGame("You have killed yourself by over-eating!")
+ return
+ else
+ print("You are now " .. hungerLevels[hunger + 1] .. ".")
+ end
+ else
+ print("There is no food available.")
+ end
+
+ Advance()
+ elseif inputLower == "wait" then
+ print("Time passes...")
+ Advance()
+ elseif inputLower == "hide" then
+ if urhoThreat > 0 then
+ local evadeSuccess = hunger > 2 or Random() < 0.5
+ if evadeSuccess then
+ print("You hide behind the thick bottom vegetation, until Urho grows bored.")
+ urhoThreat = -2
+ else
+ print("Your movements are too slow you are unable to hide from Urho.")
+ end
+ else
+ print("There is nothing to hide from.")
+ end
+
+ Advance()
+ else
+ print("Cannot understand the input '" .. input .. "'.")
+ end
+ else
+ -- Game is over, wait for (y)es or (n)o reply
+ local c = inputLower:sub(1, 1)
+ if c == 'y' then
+ StartGame()
+ elseif c == 'n' then
+ engine:Exit()
+ else
+ print("Please answer 'y' or 'n'.")
+ end
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/27_Urho2DPhysics.lua b/bin/Data/LuaScripts/27_Urho2DPhysics.lua
new file mode 100644
index 0000000..5ffb5e0
--- /dev/null
+++ b/bin/Data/LuaScripts/27_Urho2DPhysics.lua
@@ -0,0 +1,196 @@
+-- Urho2D physics sample.
+-- This sample demonstrates:
+-- - Creating both static and moving 2D physics objects to a scene
+-- - Displaying physics debug geometry
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 0.0, -10.0)
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.orthographic = true
+ camera.orthoSize = graphics.height * PIXEL_SIZE
+ camera.zoom = 1.2 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.2) is set for full visibility at 1280x800 resolution)
+
+ -- Create 2D physics world component
+ scene_:CreateComponent("PhysicsWorld2D")
+
+ local boxSprite = cache:GetResource("Sprite2D", "Urho2D/Box.png")
+ local ballSprite = cache:GetResource("Sprite2D", "Urho2D/Ball.png")
+
+ -- Create ground.
+ local groundNode = scene_:CreateChild("Ground")
+ groundNode.position = Vector3(0.0, -3.0, 0.0)
+ groundNode.scale = Vector3(200.0, 1.0, 0.0)
+
+ -- Create 2D rigid body for gound
+ local groundBody = groundNode:CreateComponent("RigidBody2D")
+
+ local groundSprite = groundNode:CreateComponent("StaticSprite2D")
+ groundSprite.sprite = boxSprite
+
+ -- Create box collider for ground
+ local groundShape = groundNode:CreateComponent("CollisionBox2D")
+ -- Set box size
+ groundShape.size = Vector2(0.32, 0.32)
+ -- Set friction
+ groundShape.friction = 0.5
+
+ local NUM_OBJECTS = 100
+ for i = 1, NUM_OBJECTS do
+ local node = scene_:CreateChild("RigidBody")
+ node.position = Vector3(Random(-0.1, 0.1), 5.0 + i * 0.4, 0.0)
+
+ -- Create rigid body
+ local body = node:CreateComponent("RigidBody2D")
+ body.bodyType = BT_DYNAMIC
+
+ local staticSprite = node:CreateComponent("StaticSprite2D")
+ local shape = nil
+ if i % 2 == 0 then
+ staticSprite.sprite = boxSprite
+ -- Create box
+ shape = node:CreateComponent("CollisionBox2D")
+ -- Set size
+ shape.size = Vector2(0.32, 0.32)
+ else
+ staticSprite.sprite = ballSprite
+ -- Create circle
+ shape = node:CreateComponent("CollisionCircle2D")
+ -- Set radius
+ shape.radius = 0.16
+ end
+ -- Set density
+ shape.density = 1.0
+ -- Set friction
+ shape.friction = 0.5
+ -- Set restitution
+ shape.restitution = 0.1
+ end
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move, Use PageUp PageDown to zoom.")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 4.0
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 1.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, -1.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ if input:GetKeyDown(KEY_PAGEUP) then
+ local camera = cameraNode:GetComponent("Camera")
+ camera.zoom = camera.zoom * 1.01
+ end
+
+ if input:GetKeyDown(KEY_PAGEDOWN) then
+ local camera = cameraNode:GetComponent("Camera")
+ camera.zoom = camera.zoom * 0.99
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Zoom In" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Zoom Out" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/28_Urho2DPhysicsRope.lua b/bin/Data/LuaScripts/28_Urho2DPhysicsRope.lua
new file mode 100644
index 0000000..3a9254a
--- /dev/null
+++ b/bin/Data/LuaScripts/28_Urho2DPhysicsRope.lua
@@ -0,0 +1,200 @@
+-- Urho2D physics rope sample.
+-- This sample demonstrates:
+-- - Create revolute constraint
+-- - Create roop constraint
+-- - Displaying physics debug geometry
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 5.0, -10.0)
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.orthographic = true
+ camera.orthoSize = graphics.height * 0.05
+ camera.zoom = 1.5 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)
+
+ -- Create 2D physics world component
+ local physicsWorld = scene_:CreateComponent("PhysicsWorld2D")
+ physicsWorld.drawJoint = true
+
+ -- Create ground.
+ local groundNode = scene_:CreateChild("Ground")
+ -- Create 2D rigid body for gound
+ local groundBody = groundNode:CreateComponent("RigidBody2D")
+ -- Create edge collider for ground
+ local groundShape = groundNode:CreateComponent("CollisionEdge2D")
+ groundShape:SetVertices(Vector2(-40.0, 0.0), Vector2(40.0, 0.0))
+
+ local y = 15.0
+ local prevBody = groundBody
+
+ local NUM_OBJECTS = 10
+ for i = 0, NUM_OBJECTS - 1 do
+ local node = scene_:CreateChild("RigidBody")
+ -- Create rigid body
+ local body = node:CreateComponent("RigidBody2D")
+ body.bodyType = BT_DYNAMIC
+
+ -- Create box
+ local box = node:CreateComponent("CollisionBox2D")
+ -- Set friction
+ box.friction = 0.2
+ -- Set mask bits.
+ box.maskBits = 0xFFFF - 0x0002
+
+ if i == NUM_OBJECTS - 1 then
+ node.position = Vector3(1.0 * i, y, 0.0)
+ body.angularDamping = 0.4
+ box:SetSize(3.0, 3.0)
+ box.density = 100.0
+ box.categoryBits = 0x0002
+ else
+ node.position = Vector3(0.5 + 1.0 * i, y, 0.0)
+ box:SetSize(1.0, 0.25)
+ box.density = 20.0
+ box.categoryBits = 0x0001
+ end
+
+ local joint = node:CreateComponent("ConstraintRevolute2D")
+ joint.otherBody = prevBody
+ joint.anchor = Vector2(i, y)
+ joint.collideConnected = false
+
+ prevBody = body
+ end
+
+ local constraintRope = groundNode:CreateComponent("ConstraintRope2D")
+ constraintRope.otherBody = prevBody
+ constraintRope.ownerBodyAnchor = Vector2(0.0, y)
+ constraintRope.maxLength = NUM_OBJECTS - 1.0 + 0.01
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move, Use PageUp PageDown to zoom.")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 4.0
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 1.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, -1.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ if input:GetKeyDown(KEY_PAGEUP) then
+ local camera = cameraNode:GetComponent("Camera")
+ camera.zoom = camera.zoom * 1.01
+ end
+
+ if input:GetKeyDown(KEY_PAGEDOWN) then
+ local camera = cameraNode:GetComponent("Camera")
+ camera.zoom = camera.zoom * 0.99
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+
+ local physicsWorld = scene_:GetComponent("PhysicsWorld2D")
+ physicsWorld:DrawDebugGeometry()
+
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Zoom In" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Zoom Out" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/30_LightAnimation.lua b/bin/Data/LuaScripts/30_LightAnimation.lua
new file mode 100644
index 0000000..73adcb2
--- /dev/null
+++ b/bin/Data/LuaScripts/30_LightAnimation.lua
@@ -0,0 +1,199 @@
+-- Light animation example.
+-- This sample is base on StaticScene, and it demonstrates:
+-- - Usage of attribute animation for light color & UI animation
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+
+ -- Create a child scene node (at world origin) and a StaticModel component into it. Set the StaticModel to show a simple
+ -- plane mesh with a "stone" material. Note that naming the scene nodes is optional. Scale the scene node larger
+ -- (100 x 100 world units)
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3(100.0, 1.0, 100.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Create a point light to the world so that we can see something.
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_POINT
+ light.range = 10.0
+
+ -- Create light color animation
+ local colorAnimation = ValueAnimation:new()
+ colorAnimation:SetKeyFrame(0.0, Variant(Color(1,1,1)))
+ colorAnimation:SetKeyFrame(1.0, Variant(Color(1,0,0)))
+ colorAnimation:SetKeyFrame(2.0, Variant(Color(1,1,0)))
+ colorAnimation:SetKeyFrame(3.0, Variant(Color(0,1,0)))
+ colorAnimation:SetKeyFrame(4.0, Variant(Color(1,1,1)))
+ light:SetAttributeAnimation("Color", colorAnimation)
+
+ -- Create text animation
+ local textAnimation = ValueAnimation:new()
+ textAnimation:SetKeyFrame(0.0, Variant("WHITE"))
+ textAnimation:SetKeyFrame(1.0, Variant("RED"))
+ textAnimation:SetKeyFrame(2.0, Variant("YELLOW"))
+ textAnimation:SetKeyFrame(3.0, Variant("GREEN"))
+ textAnimation:SetKeyFrame(4.0, Variant("WHITE"))
+ ui.root:GetChild("animatingText"):SetAttributeAnimation("Text", textAnimation)
+
+ -- Create UI element animation
+ -- (note: a spritesheet and "Image Rect" attribute should be used in real use cases for better performance)
+ local spriteAnimation = ValueAnimation:new()
+ spriteAnimation:SetKeyFrame(0.0, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/1.png")))
+ spriteAnimation:SetKeyFrame(0.1, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/2.png")))
+ spriteAnimation:SetKeyFrame(0.2, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/3.png")))
+ spriteAnimation:SetKeyFrame(0.3, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/4.png")))
+ spriteAnimation:SetKeyFrame(0.4, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/5.png")))
+ spriteAnimation:SetKeyFrame(0.5, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/1.png")))
+ ui.root:GetChild("animatingSprite"):SetAttributeAnimation("Texture", spriteAnimation)
+
+ -- Create light position animation
+ local positionAnimation = ValueAnimation:new()
+ -- Use spline interpolation method
+ positionAnimation.interpolationMethod = IM_SPLINE
+ -- Set spline tension
+ positionAnimation.splineTension = 0.7
+ positionAnimation:SetKeyFrame(0.0, Variant(Vector3(-30.0, 5.0, -30.0)))
+ positionAnimation:SetKeyFrame(1.0, Variant(Vector3( 30.0, 5.0, -30.0)))
+ positionAnimation:SetKeyFrame(2.0, Variant(Vector3( 30.0, 5.0, 30.0)))
+ positionAnimation:SetKeyFrame(3.0, Variant(Vector3(-30.0, 5.0, 30.0)))
+ positionAnimation:SetKeyFrame(4.0, Variant(Vector3(-30.0, 5.0, -30.0)))
+ lightNode:SetAttributeAnimation("Position", positionAnimation)
+
+ -- Create more StaticModel objects to the scene, randomly positioned, rotated and scaled. For rotation, we construct a
+ -- quaternion from Euler angles where the Y angle (rotation about the Y axis) is randomized. The mushroom model contains
+ -- LOD levels, so the StaticModel component will automatically select the LOD level according to the view distance (you'll
+ -- see the model get simpler as it moves further away). Finally, rendering a large number of the same object with the
+ -- same material allows instancing to be used, if the GPU supports it. This reduces the amount of CPU work in rendering the
+ -- scene.
+ local NUM_OBJECTS = 200
+ for i = 1, NUM_OBJECTS do
+ local mushroomNode = scene_:CreateChild("Mushroom")
+ mushroomNode.position = Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0)
+ mushroomNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+ mushroomNode:SetScale(0.5 + Random(2.0))
+ local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+ mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ mushroomObject.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ end
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ cameraNode:CreateComponent("Camera")
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 5.0, 0.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move")
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+ instructionText:SetFont(font, 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+
+ -- Animating text
+ local text = ui.root:CreateChild("Text", "animatingText")
+ text:SetFont(font, 15)
+ text.horizontalAlignment = HA_CENTER
+ text.verticalAlignment = VA_CENTER
+ text:SetPosition(0, ui.root.height / 4 + 20)
+
+ -- Animating sprite in the top left corner
+ local sprite = ui.root:CreateChild("Sprite", "animatingSprite")
+ sprite:SetPosition(8, 8)
+ sprite:SetSize(64, 64)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw +MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ -- Use the Translate() function (default local space) to move relative to the node's orientation.
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
diff --git a/bin/Data/LuaScripts/31_MaterialAnimation.lua b/bin/Data/LuaScripts/31_MaterialAnimation.lua
new file mode 100644
index 0000000..42edeb3
--- /dev/null
+++ b/bin/Data/LuaScripts/31_MaterialAnimation.lua
@@ -0,0 +1,157 @@
+-- Material animation example.
+-- This sample is base on StaticScene, and it demonstrates:
+-- - Usage of material shader animation for mush room material
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+
+ -- Create a child scene node (at world origin) and a StaticModel component into it. Set the StaticModel to show a simple
+ -- plane mesh with a "stone" material. Note that naming the scene nodes is optional. Scale the scene node larger
+ -- (100 x 100 world units)
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3(100.0, 1.0, 100.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Create a directional light to the world so that we can see something. The light scene node's orientation controls the
+ -- light direction we will use the SetDirection() function which calculates the orientation from a forward direction vector.
+ -- The light will use default settings (white light, no shadows)
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8) -- The direction vector does not need to be normalized
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+
+ -- Create more StaticModel objects to the scene, randomly positioned, rotated and scaled. For rotation, we construct a
+ -- quaternion from Euler angles where the Y angle (rotation about the Y axis) is randomized. The mushroom model contains
+ -- LOD levels, so the StaticModel component will automatically select the LOD level according to the view distance (you'll
+ -- see the model get simpler as it moves further away). Finally, rendering a large number of the same object with the
+ -- same material allows instancing to be used, if the GPU supports it. This reduces the amount of CPU work in rendering the
+ -- scene.
+ local mushroomMat = cache:GetResource("Material", "Materials/Mushroom.xml")
+ -- Apply shader parameter animation to material
+ local specColorAnimation = ValueAnimation:new()
+ specColorAnimation:SetKeyFrame(0.0, Variant(Color(0.1, 0.1, 0.1, 16.0)))
+ specColorAnimation:SetKeyFrame(1.0, Variant(Color(1.0, 0.0, 0.0, 2.0)))
+ specColorAnimation:SetKeyFrame(2.0, Variant(Color(1.0, 1.0, 0.0, 2.0)))
+ specColorAnimation:SetKeyFrame(3.0, Variant(Color(0.1, 0.1, 0.1, 16.0)))
+ -- Optionally associate material with scene to make sure shader parameter animation respects scene time scale
+ mushroomMat.scene = scene_
+ mushroomMat:SetShaderParameterAnimation("MatSpecColor", specColorAnimation)
+
+ local NUM_OBJECTS = 200
+ for i = 1, NUM_OBJECTS do
+ local mushroomNode = scene_:CreateChild("Mushroom")
+ mushroomNode.position = Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0)
+ mushroomNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+ mushroomNode:SetScale(0.5 + Random(2.0))
+ local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+ mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ mushroomObject.material = mushroomMat
+ end
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ cameraNode:CreateComponent("Camera")
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 5.0, 0.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw +MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ -- Use the Translate() function (default local space) to move relative to the node's orientation.
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
diff --git a/bin/Data/LuaScripts/32_Urho2DConstraints.lua b/bin/Data/LuaScripts/32_Urho2DConstraints.lua
new file mode 100644
index 0000000..88cb2ed
--- /dev/null
+++ b/bin/Data/LuaScripts/32_Urho2DConstraints.lua
@@ -0,0 +1,469 @@
+-- Urho2D physics Constraints sample.
+-- This sample is designed to help understanding and chosing the right constraint.
+-- This sample demonstrates:
+-- - Creating physics constraints
+-- - Creating Edge and Polygon Shapes from vertices
+-- - Displaying physics debug geometry and constraints' joints
+-- - Using SetOrderInLayer to alter the way sprites are drawn in relation to each other
+-- - Using Text3D to display some text affected by zoom
+-- - Setting the background color for the scene
+
+require "LuaScripts/Utilities/Sample"
+
+local camera = nil
+local physicsWorld = nil
+local pickedNode = nil
+local dummyBody = nil
+
+function Start()
+ SampleStart()
+ CreateScene()
+ input.mouseVisible = true -- Show mouse cursor
+ CreateInstructions()
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+ physicsWorld = scene_:CreateComponent("PhysicsWorld2D")
+ physicsWorld.drawJoint = true -- Display the joints (Note that DrawDebugGeometry() must be set to true to acually draw the joints)
+ drawDebug = true -- Set DrawDebugGeometry() to true
+
+ -- Create camera
+ cameraNode = scene_:CreateChild("Camera")
+ cameraNode.position = Vector3(0, 0, 0) -- Note that Z setting is discarded; use camera.zoom instead (see MoveCamera() below for example)
+ camera = cameraNode:CreateComponent("Camera")
+ camera.orthographic = true
+ camera.orthoSize = graphics.height * PIXEL_SIZE
+ camera.zoom = 1.2 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.2) is set for full visibility at 1280x800 resolution)
+ renderer:SetViewport(0, Viewport:new(scene_, camera))
+ renderer.defaultZone.fogColor = Color(0.1, 0.1, 0.1) -- Set background color for the scene
+
+ -- Create a 4x3 grid
+ for i=0, 4 do
+ local edgeNode = scene_:CreateChild("VerticalEdge")
+ local edgeBody = edgeNode:CreateComponent("RigidBody2D")
+ if dummyBody == nil then dummyBody = edgeBody end -- Mark first edge as dummy body (used by mouse pick)
+ local edgeShape = edgeNode:CreateComponent("CollisionEdge2D")
+ edgeShape:SetVertices(Vector2(i*2.5 -5, -3), Vector2(i*2.5 -5, 3))
+ edgeShape.friction = 0.5 -- Set friction
+ end
+
+ for j=0, 3 do
+ local edgeNode = scene_:CreateChild("HorizontalEdge")
+ local edgeBody = edgeNode:CreateComponent("RigidBody2D")
+ local edgeShape = edgeNode:CreateComponent("CollisionEdge2D")
+ edgeShape:SetVertices(Vector2(-5, j*2 -3), Vector2(5, j*2 -3))
+ edgeShape.friction = 0.5 -- Set friction
+ end
+
+ -- Create a box (will be cloned later)
+ local box = scene_:CreateChild("Box")
+ box.position = Vector3(0.8, -2, 0)
+ local staticSprite = box:CreateComponent("StaticSprite2D")
+ staticSprite.sprite = cache:GetResource("Sprite2D", "Urho2D/Box.png")
+ local body = box:CreateComponent("RigidBody2D")
+ body.bodyType = BT_DYNAMIC
+ body.linearDamping = 0
+ body.angularDamping = 0
+ local shape = box:CreateComponent("CollisionBox2D") -- Create box shape
+ shape.size = Vector2(0.32, 0.32) -- Set size
+ shape.density = 1 -- Set shape density (kilograms per meter squared)
+ shape.friction = 0.5 -- Set friction
+ shape.restitution = 0.1 -- Set restitution (slight bounce)
+
+ -- Create a ball (will be cloned later)
+ local ball = scene_:CreateChild("Ball")
+ ball.position = Vector3(1.8, -2, 0)
+ local staticSprite = ball:CreateComponent("StaticSprite2D")
+ staticSprite.sprite = cache:GetResource("Sprite2D", "Urho2D/Ball.png")
+ local body = ball:CreateComponent("RigidBody2D")
+ body.bodyType = BT_DYNAMIC
+ body.linearDamping = 0
+ body.angularDamping = 0
+ local shape = ball:CreateComponent("CollisionCircle2D") -- Create circle shape
+ shape.radius = 0.16 -- Set radius
+ shape.density = 1 -- Set shape density (kilograms per meter squared)
+ shape.friction = 0.5 -- Set friction
+ shape.restitution = 0.6 -- Set restitution: make it bounce
+
+ -- Create a polygon
+ local node = scene_:CreateChild("Polygon")
+ node.position = Vector3(1.6, -2, 0)
+ node:SetScale(0.7)
+ local staticSprite = node:CreateComponent("StaticSprite2D")
+ staticSprite.sprite = cache:GetResource("Sprite2D", "Urho2D/Aster.png")
+ local body = node:CreateComponent("RigidBody2D")
+ body.bodyType = BT_DYNAMIC
+ local shape = node:CreateComponent("CollisionPolygon2D")
+ shape:SetVertices{Vector2(-0.8, -0.3), Vector2(0.5, -0.8), Vector2(0.8, -0.3), Vector2(0.8, 0.5), Vector2(0.5, 0.9), Vector2(-0.5, 0.7)}
+ shape.density = 1 -- Set shape density (kilograms per meter squared)
+ shape.friction = 0.3 -- Set friction
+ shape.restitution = 0 -- Set restitution (no bounce)
+
+ -- Create a ConstraintDistance2D
+ CreateFlag("ConstraintDistance2D", -4.97, 3) -- Display Text3D flag
+ local boxNode = box:Clone()
+ local ballNode = ball:Clone()
+ local ballBody = ballNode:GetComponent("RigidBody2D")
+ boxNode.position = Vector3(-4.5, 2, 0)
+ ballNode.position = Vector3(-3, 2, 0)
+
+ local constraintDistance = boxNode:CreateComponent("ConstraintDistance2D") -- Apply ConstraintDistance2D to box
+ constraintDistance.otherBody = ballBody -- Constrain ball to box
+ constraintDistance.ownerBodyAnchor = boxNode.position2D
+ constraintDistance.otherBodyAnchor = ballNode.position2D
+ -- Make the constraint soft (comment to make it rigid, which is its basic behavior)
+ constraintDistance.frequencyHz = 4
+ constraintDistance.dampingRatio = 0.5
+
+ -- Create a ConstraintFriction2D ********** Not functional. From Box2d samples it seems that 2 anchors are required, Urho2D only provides 1, needs investigation ***********
+ CreateFlag("ConstraintFriction2D", 0.03, 1) -- Display Text3D flag
+ local boxNode = box:Clone()
+ local ballNode = ball:Clone()
+ boxNode.position = Vector3(0.5, 0, 0)
+ ballNode.position = Vector3(1.5, 0, 0)
+
+ local constraintFriction = boxNode:CreateComponent("ConstraintFriction2D") -- Apply ConstraintDistance2D to box
+ constraintFriction.otherBody = ballNode:GetComponent("RigidBody2D") -- Constraint ball to box
+ constraintFriction.ownerBodyAnchor = boxNode.position2D
+ --constraintFriction.otherBodyAnchor = ballNode.position2D
+ --constraintFriction.maxForce = 10 -- ballBody.mass * gravity
+ --constraintDistance.maxTorque = 10 -- ballBody.mass * radius * gravity
+
+ -- Create a ConstraintGear2D
+ CreateFlag("ConstraintGear2D", -4.97, -1) -- Display Text3D flag
+ local baseNode = box:Clone()
+ baseNode:GetComponent("RigidBody2D").bodyType = BT_STATIC
+ baseNode.position = Vector3(-3.7, -2.5, 0)
+ local ball1Node = ball:Clone()
+ ball1Node.position = Vector3(-4.5, -2, 0)
+ local ball1Body = ball1Node:GetComponent("RigidBody2D")
+ local ball2Node = ball:Clone()
+ ball2Node.position = Vector3(-3, -2, 0)
+ local ball2Body = ball2Node:GetComponent("RigidBody2D")
+
+ local gear1 = baseNode:CreateComponent("ConstraintRevolute2D") -- Apply constraint to baseBox
+ gear1.otherBody = ball1Body -- Constrain ball1 to baseBox
+ gear1.anchor = ball1Node.position2D
+ local gear2 = baseNode:CreateComponent("ConstraintRevolute2D") -- Apply constraint to baseBox
+ gear2.otherBody = ball2Body -- Constrain ball2 to baseBox
+ gear2.anchor = ball2Node.position2D
+
+ local constraintGear = ball1Node:CreateComponent("ConstraintGear2D") -- Apply constraint to ball1
+ constraintGear.otherBody = ball2Body -- Constrain ball2 to ball1
+ constraintGear.ownerConstraint = gear1
+ constraintGear.otherConstraint = gear2
+ constraintGear.ratio=1
+
+ ball1Body:ApplyAngularImpulse(0.015, true) -- Animate
+
+ -- Create a vehicle from a compound of 2 ConstraintWheel2Ds
+ CreateFlag("ConstraintWheel2Ds compound", -2.45, -1) -- Display Text3D flag
+ local car = box:Clone()
+ car.scale = Vector3(4, 1, 0)
+ car.position = Vector3(-1.2, -2.3, 0)
+ car:GetComponent("StaticSprite2D").orderInLayer = 0 -- Draw car on top of the wheels (set to -1 to draw below)
+ local ball1Node = ball:Clone()
+ ball1Node.position = Vector3(-1.6, -2.5, 0)
+ local ball2Node = ball:Clone()
+ ball2Node.position = Vector3(-0.8, -2.5, 0)
+
+ local wheel1 = car:CreateComponent("ConstraintWheel2D")
+ wheel1.otherBody = ball1Node:GetComponent("RigidBody2D")
+ wheel1.anchor = ball1Node.position2D
+ wheel1.axis = Vector2(0, 1)
+ wheel1.maxMotorTorque = 20
+ wheel1.frequencyHz = 4
+ wheel1.dampingRatio = 0.4
+
+ local wheel2 = car:CreateComponent("ConstraintWheel2D")
+ wheel2.otherBody = ball2Node:GetComponent("RigidBody2D")
+ wheel2.anchor = ball2Node.position2D
+ wheel2.axis = Vector2(0, 1)
+ wheel2.maxMotorTorque = 10
+ wheel2.frequencyHz = 4
+ wheel2.dampingRatio = 0.4
+
+ -- Create a ConstraintMotor2D
+ CreateFlag("ConstraintMotor2D", 2.53, -1) -- Display Text3D flag
+ local boxNode = box:Clone()
+ boxNode:GetComponent("RigidBody2D").bodyType = BT_STATIC
+ local ballNode = ball:Clone()
+ boxNode.position = Vector3(3.8, -2.1, 0)
+ ballNode.position = Vector3(3.8, -1.5, 0)
+
+ constraintMotor = boxNode:CreateComponent("ConstraintMotor2D")
+ constraintMotor.otherBody = ballNode:GetComponent("RigidBody2D") -- Constrain ball to box
+ constraintMotor.linearOffset = Vector2(0, 0.8) -- Set ballNode position relative to boxNode position = (0,0)
+ constraintMotor.angularOffset = 0.1
+ constraintMotor.maxForce = 5
+ constraintMotor.maxTorque = 10
+ constraintMotor.correctionFactor = 1
+ constraintMotor.collideConnected = true -- doesn't work
+
+ -- Create a ConstraintMouse2D is demonstrated in HandleMouseButtonDown() function. It is used to "grasp" the sprites with the mouse.
+ CreateFlag("ConstraintMouse2D", 0.03, -1) -- Display Text3D flag
+
+ -- Create a ConstraintPrismatic2D
+ CreateFlag("ConstraintPrismatic2D", 2.53, 3) -- Display Text3D flag
+ local boxNode = box:Clone()
+ boxNode:GetComponent("RigidBody2D").bodyType = BT_STATIC
+ local ballNode = ball:Clone()
+ boxNode.position = Vector3(3.3, 2.5, 0)
+ ballNode.position = Vector3(4.3, 2, 0)
+
+ local constraintPrismatic = boxNode:CreateComponent("ConstraintPrismatic2D")
+ constraintPrismatic.otherBody = ballNode:GetComponent("RigidBody2D") -- Constrain ball to box
+ constraintPrismatic.axis = Vector2(1, 1) -- Slide from [0,0] to [1,1]
+ constraintPrismatic.anchor = Vector2(4, 2)
+ constraintPrismatic.lowerTranslation = -1
+ constraintPrismatic.upperTranslation = 0.5
+ constraintPrismatic.enableLimit = true
+ constraintPrismatic.maxMotorForce = 1
+ constraintPrismatic.motorSpeed = 0
+
+ -- Create a ConstraintPulley2D
+ CreateFlag("ConstraintPulley2D", 0.03, 3) -- Display Text3D flag
+ local boxNode = box:Clone()
+ local ballNode = ball:Clone()
+ boxNode.position = Vector3(0.5, 2, 0)
+ ballNode.position = Vector3(2, 2, 0)
+
+ local constraintPulley = boxNode:CreateComponent("ConstraintPulley2D") -- Apply constraint to box
+ constraintPulley.otherBody = ballNode:GetComponent("RigidBody2D") -- Constrain ball to box
+ constraintPulley.ownerBodyAnchor = boxNode.position2D
+ constraintPulley.otherBodyAnchor = ballNode.position2D
+ constraintPulley.ownerBodyGroundAnchor = boxNode.position2D + Vector2(0, 1)
+ constraintPulley.otherBodyGroundAnchor = ballNode.position2D + Vector2(0, 1)
+ constraintPulley.ratio = 1 -- Weight ratio between ownerBody and otherBody
+
+ -- Create a ConstraintRevolute2D
+ CreateFlag("ConstraintRevolute2D", -2.45, 3) -- Display Text3D flag
+ local boxNode = box:Clone()
+ boxNode:GetComponent("RigidBody2D").bodyType = BT_STATIC
+ local ballNode = ball:Clone()
+ boxNode.position = Vector3(-2, 1.5, 0)
+ ballNode.position = Vector3(-1, 2, 0)
+
+ local constraintRevolute = boxNode:CreateComponent("ConstraintRevolute2D") -- Apply constraint to box
+ constraintRevolute.otherBody = ballNode:GetComponent("RigidBody2D") -- Constrain ball to box
+ constraintRevolute.anchor = Vector2(-1, 1.5)
+ constraintRevolute.lowerAngle = -1 -- In radians
+ constraintRevolute.upperAngle = 0.5 -- In radians
+ constraintRevolute.enableLimit = true
+ constraintRevolute.maxMotorTorque = 10
+ constraintRevolute.motorSpeed = 0
+ constraintRevolute.enableMotor = true
+
+ -- Create a ConstraintRope2D
+ CreateFlag("ConstraintRope2D", -4.97, 1) -- Display Text3D flag
+ local boxNode = box:Clone()
+ boxNode:GetComponent("RigidBody2D").bodyType = BT_STATIC
+ local ballNode = ball:Clone()
+ boxNode.position = Vector3(-3.7, 0.7, 0)
+ ballNode.position = Vector3(-4.5, 0, 0)
+
+ local constraintRope = boxNode:CreateComponent("ConstraintRope2D")
+ constraintRope.otherBody = ballNode:GetComponent("RigidBody2D") -- Constrain ball to box
+ constraintRope.ownerBodyAnchor = Vector2(0, -0.5) -- Offset from box (OwnerBody) : the rope is rigid from OwnerBody center to this ownerBodyAnchor
+ constraintRope.maxLength = 0.9 -- Rope length
+ constraintRope.collideConnected = true
+
+ -- Create a ConstraintWeld2D
+ CreateFlag("ConstraintWeld2D", -2.45, 1) -- Display Text3D flag
+ local boxNode = box:Clone()
+ local ballNode = ball:Clone()
+ boxNode.position = Vector3(-0.5, 0, 0)
+ ballNode.position = Vector3(-2, 0, 0)
+
+ local constraintWeld = boxNode:CreateComponent("ConstraintWeld2D")
+ constraintWeld.otherBody = ballNode:GetComponent("RigidBody2D") -- Constrain ball to box
+ constraintWeld.anchor = boxNode.position2D
+ constraintWeld.frequencyHz = 4
+ constraintWeld.dampingRatio = 0.5
+
+ -- ConstraintWheel2D
+ CreateFlag("ConstraintWheel2D", 2.53, 1) -- Display Text3D flag
+ local boxNode = box:Clone()
+ local ballNode = ball:Clone()
+ boxNode.position = Vector3(3.8, 0, 0)
+ ballNode.position = Vector3(3.8, 0.9, 0)
+
+ local constraintWheel = boxNode:CreateComponent("ConstraintWheel2D")
+ constraintWheel.otherBody = ballNode:GetComponent("RigidBody2D") -- Constrain ball to box
+ constraintWheel.anchor = ballNode.position2D
+ constraintWheel.axis = Vector2(0, 1)
+ constraintWheel.enableMotor = true
+ constraintWheel.maxMotorTorque = 1
+ constraintWheel.motorSpeed = 0
+ constraintWheel.frequencyHz = 4
+ constraintWheel.dampingRatio = 0.5
+ constraintWheel.collideConnected = true -- doesn't work
+end
+
+function CreateFlag(text, x, y) -- Used to create Tex3D flags
+ local flagNode = scene_:CreateChild("Flag")
+ flagNode.position = Vector3(x, y, 0)
+ local flag3D = flagNode:CreateComponent("Text3D") -- We use Text3D in order to make the text affected by zoom (so that it sticks to 2D)
+ flag3D.text = text
+ flag3D:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move, Use PageUp PageDown to zoom.\n Space to toggle debug geometry and joints - F5 to save the scene.")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ instructionText.textAlignment = HA_CENTER -- Center rows in relation to each other
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function MoveCamera(timeStep)
+ if ui.focusElement ~= nil then return end -- Do not move if the UI has a focused element (the console)
+
+ local MOVE_SPEED = 4 -- Movement speed as world units per second
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then cameraNode:Translate(Vector3(0, 1, 0) * MOVE_SPEED * timeStep) end
+ if input:GetKeyDown(KEY_S) then cameraNode:Translate(Vector3(0, -1, 0) * MOVE_SPEED * timeStep) end
+ if input:GetKeyDown(KEY_A) then cameraNode:Translate(Vector3(-1, 0, 0) * MOVE_SPEED * timeStep) end
+ if input:GetKeyDown(KEY_D) then cameraNode:Translate(Vector3(1, 0, 0) * MOVE_SPEED * timeStep) end
+
+ if input:GetKeyDown(KEY_PAGEUP) then camera.zoom = camera.zoom * 1.01 end -- Zoom In
+ if input:GetKeyDown(KEY_PAGEDOWN) then camera.zoom = camera.zoom * 0.99 end -- Zoom Out
+end
+
+function SubscribeToEvents()
+ SubscribeToEvent("Update", "HandleUpdate")
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+ SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown")
+ if touchEnabled then SubscribeToEvent("TouchBegin", "HandleTouchBegin3") end
+ -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ local timestep = eventData["TimeStep"]:GetFloat()
+ MoveCamera(timestep) -- Move the camera according to frame's time step
+ if input:GetKeyPress(KEY_SPACE) then drawDebug = not drawDebug end -- Toggle debug geometry with space
+ if input:GetKeyPress(KEY_F5) then scene_:SaveXML(fileSystem:GetProgramDir().."Data/Scenes/Constraints.xml") end -- Save scene
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ if drawDebug then physicsWorld:DrawDebugGeometry() end
+end
+
+function HandleMouseButtonDown(eventType, eventData)
+ local rigidBody = physicsWorld:GetRigidBody(input.mousePosition.x, input.mousePosition.y) -- Raycast for RigidBody2Ds to pick
+ if rigidBody ~= nil then
+ pickedNode = rigidBody.node
+ local staticSprite = pickedNode:GetComponent("StaticSprite2D")
+ staticSprite.color = Color(1, 0, 0, 1) -- Temporary modify color of the picked sprite
+
+ -- ConstraintMouse2D - Temporary apply this constraint to the pickedNode to allow grasping and moving with the mouse
+ local constraintMouse = pickedNode:CreateComponent("ConstraintMouse2D")
+ constraintMouse.target = GetMousePositionXY()
+ constraintMouse.maxForce = 1000 * rigidBody.mass
+ constraintMouse.collideConnected = true
+ constraintMouse.otherBody = dummyBody -- Use dummy body instead of rigidBody. It's better to create a dummy body automatically in ConstraintMouse2D
+ end
+ SubscribeToEvent("MouseMove", "HandleMouseMove")
+ SubscribeToEvent("MouseButtonUp", "HandleMouseButtonUp")
+end
+
+function HandleMouseButtonUp(eventType, eventData)
+ if pickedNode ~= nil then
+ local staticSprite = pickedNode:GetComponent("StaticSprite2D")
+ staticSprite.color = Color(1, 1, 1, 1) -- Restore picked sprite color
+
+ pickedNode:RemoveComponent("ConstraintMouse2D") -- Remove temporary constraint
+ pickedNode = nil
+ end
+ UnsubscribeFromEvent("MouseMove")
+ UnsubscribeFromEvent("MouseButtonUp")
+end
+
+function GetMousePositionXY()
+ local screenPoint = Vector3(input.mousePosition.x / graphics.width, input.mousePosition.y / graphics.height, 0)
+ local worldPoint = camera:ScreenToWorldPoint(screenPoint)
+ return Vector2(worldPoint.x, worldPoint.y)
+end
+
+function HandleMouseMove(eventType, eventData)
+ if pickedNode ~= nil then
+ local constraintMouse = pickedNode:GetComponent("ConstraintMouse2D")
+ constraintMouse.target = GetMousePositionXY()
+ end
+end
+
+function HandleTouchBegin3(eventType, eventData)
+ local rigidBody = physicsWorld:GetRigidBody(eventData["X"]:GetInt(), eventData["Y"]:GetInt()) -- Raycast for RigidBody2Ds to pick
+ if rigidBody ~= nil then
+ pickedNode = rigidBody.node
+ local staticSprite = pickedNode:GetComponent("StaticSprite2D")
+ staticSprite.color = Color(1, 0, 0, 1) -- Temporary modify color of the picked sprite
+ local rigidBody = pickedNode:GetComponent("RigidBody2D")
+
+ -- ConstraintMouse2D - Temporary apply this constraint to the pickedNode to allow grasping and moving with touch
+ local constraintMouse = pickedNode:CreateComponent("ConstraintMouse2D")
+ constraintMouse.target = camera:ScreenToWorldPoint(Vector3(eventData["X"]:GetInt() / graphics.width, eventData["Y"]:GetInt() / graphics.height, 0))
+ constraintMouse.maxForce = 1000 * rigidBody.mass
+ constraintMouse.collideConnected = true
+ constraintMouse.otherBody = dummyBody -- Use dummy body instead of rigidBody. It's better to create a dummy body automatically in ConstraintMouse2D
+ constraintMouse.dampingRatio = 0
+ end
+ SubscribeToEvent("TouchMove", "HandleTouchMove3")
+ SubscribeToEvent("TouchEnd", "HandleTouchEnd3")
+end
+
+function HandleTouchMove3(eventType, eventData)
+ if pickedNode ~= nil then
+ local constraintMouse = pickedNode:GetComponent("ConstraintMouse2D")
+ constraintMouse.target = camera:ScreenToWorldPoint(Vector3(eventData["X"]:GetInt() / graphics.width, eventData["Y"]:GetInt() / graphics.height, 0))
+ end
+end
+
+function HandleTouchEnd3(eventType, eventData)
+ if pickedNode ~= nil then
+ local staticSprite = pickedNode:GetComponent("StaticSprite2D")
+ staticSprite.color = Color(1, 1, 1, 1) -- Restore picked sprite color
+
+ pickedNode:RemoveComponent("ConstraintMouse2D") -- Remove temporary constraint
+ pickedNode = nil
+ end
+ UnsubscribeFromEvent("TouchMove")
+ UnsubscribeFromEvent("TouchEnd")
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Zoom In" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Zoom Out" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/33_Urho2DSpriterAnimation.lua b/bin/Data/LuaScripts/33_Urho2DSpriterAnimation.lua
new file mode 100644
index 0000000..47f4dc7
--- /dev/null
+++ b/bin/Data/LuaScripts/33_Urho2DSpriterAnimation.lua
@@ -0,0 +1,163 @@
+-- Urho2D sprite example.
+-- This sample demonstrates:
+-- - Creating a 2D scene with spriter animation
+-- - Displaying the scene using the Renderer subsystem
+-- - Handling keyboard to move and zoom 2D camera
+
+require "LuaScripts/Utilities/Sample"
+
+local spriterNode = nil
+local spriterAnimationIndex = 0
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 0.0, -10.0)
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.orthographic = true
+ camera.orthoSize = graphics.height * PIXEL_SIZE
+ camera.zoom = 1.5 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)
+
+ local spriterAnimationSet = cache:GetResource("AnimationSet2D", "Urho2D/imp/imp.scml")
+ if spriterAnimationSet == nil then
+ return
+ end
+
+ spriterNode = scene_:CreateChild("SpriterAnimation")
+
+ local spriterAnimatedSprite = spriterNode:CreateComponent("AnimatedSprite2D")
+ spriterAnimatedSprite.animationSet = spriterAnimationSet
+ spriterAnimatedSprite:SetAnimation(spriterAnimationSet:GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Mouse click to play next animation, \nUse WASD keys and mouse to move, Use PageUp PageDown to zoom.")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ instructionText.textAlignment = HA_CENTER -- Center rows in relation to each other
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 4.0
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 1.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, -1.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ if input:GetKeyDown(KEY_PAGEUP) then
+ local camera = cameraNode:GetComponent("Camera")
+ camera.zoom = camera.zoom * 1.01
+ end
+
+ if input:GetKeyDown(KEY_PAGEDOWN) then
+ local camera = cameraNode:GetComponent("Camera")
+ camera.zoom = camera.zoom * 0.99
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+ SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown")
+
+ -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+function HandleMouseButtonDown(eventType, eventData)
+ local spriterAnimatedSprite = spriterNode:GetComponent("AnimatedSprite2D")
+ local spriterAnimationSet = spriterAnimatedSprite.animationSet
+ spriterAnimationIndex = (spriterAnimationIndex + 1) % spriterAnimationSet.numAnimations
+ spriterAnimatedSprite:SetAnimation(spriterAnimationSet:GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED)
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Zoom In" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Zoom Out" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/34_DynamicGeometry.lua b/bin/Data/LuaScripts/34_DynamicGeometry.lua
new file mode 100644
index 0000000..df6796a
--- /dev/null
+++ b/bin/Data/LuaScripts/34_DynamicGeometry.lua
@@ -0,0 +1,353 @@
+-- Dynamic geometry example.
+-- This sample demonstrates:
+-- - Cloning a Model resource
+-- - Modifying the vertex buffer data of the cloned models at runtime to efficiently animate them
+-- - Creating a Model resource and its buffer data from scratch
+
+require "LuaScripts/Utilities/Sample"
+
+local boxNodes = {}
+local animate = false
+local useGroups = false
+
+local animate = true
+local animTime = 0.0
+local originalVertexData = VectorBuffer()
+local animatingBuffers = {}
+local originalVertices = {}
+local vertexDuplicates = {}
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene so that drawable objects can be rendered. Use default volume
+ -- (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_:CreateComponent("Octree")
+
+ -- Create a Zone for ambient light & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.fogColor = Color(0.2, 0.2, 0.2)
+ zone.fogStart = 200.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(-0.6, -1.0, -0.8) -- The direction vector does not need to be normalized
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.color = Color(0.4, 1.0, 0.4)
+ light.specularIntensity = 1.5
+
+ -- Get the original model and its unmodified vertices, which are used as source data for the animation
+ local originalModel = cache:GetResource("Model", "Models/Box.mdl")
+ if originalModel == nil then
+ print("Model not found, cannot initialize example scene")
+ return
+ end
+
+ -- Get the vertex buffer from the first geometry's first LOD level
+ local buffer = originalModel:GetGeometry(0, 0):GetVertexBuffer(0)
+ originalVertexData = buffer:GetData()
+ local numVertices = buffer.vertexCount
+ local vertexSize = buffer.vertexSize
+ -- Copy the original vertex positions
+ for i = 0, numVertices - 1 do
+ originalVertexData:Seek(i * vertexSize)
+ originalVertices[i+1] = originalVertexData:ReadVector3()
+ end
+
+ -- Detect duplicate vertices to allow seamless animation
+ for i = 1, table.getn(originalVertices) do
+ vertexDuplicates[i] = i -- Assume not a duplicate
+ for j = 1, i - 1 do
+ if originalVertices[i]:Equals(originalVertices[j]) then
+ vertexDuplicates[i] = j
+ break
+ end
+ end
+ end
+
+ -- Create StaticModels in the scene. Clone the model for each so that we can modify the vertex data individually
+ for y = -1, 1 do
+ for x = -1, 1 do
+ local node = scene_:CreateChild("Object")
+ node.position = Vector3(x * 2.0, 0.0, y * 2.0)
+ local object = node:CreateComponent("StaticModel")
+ local cloneModel = originalModel:Clone()
+ object.model = cloneModel
+ -- Store the cloned vertex buffer that we will modify when animating
+ table.insert(animatingBuffers, cloneModel:GetGeometry(0, 0):GetVertexBuffer(0))
+ end
+ end
+
+ -- Finally create one model (pyramid shape) and a StaticModel to display it from scratch
+ -- Note: there are duplicated vertices to enable face normals. We will calculate normals programmatically
+ local numVertices = 18
+
+ local vertexData = {
+ -- Position Normal
+ 0.0, 0.5, 0.0, 0.0, 0.0, 0.0,
+ 0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+ 0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+
+ 0.0, 0.5, 0.0, 0.0, 0.0, 0.0,
+ -0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+ 0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+
+ 0.0, 0.5, 0.0, 0.0, 0.0, 0.0,
+ -0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+ -0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+
+ 0.0, 0.5, 0.0, 0.0, 0.0, 0.0,
+ 0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+ -0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+
+ 0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+ 0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+ -0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+
+ 0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+ -0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+ -0.5, -0.5, -0.5, 0.0, 0.0, 0.0
+ }
+
+ local indexData = {
+ 0, 1, 2,
+ 3, 4, 5,
+ 6, 7, 8,
+ 9, 10, 11,
+ 12, 13, 14,
+ 15, 16, 17
+ }
+
+ -- Calculate face normals now
+ for i = 0, numVertices - 1, 3 do
+ local v1 = Vector3(vertexData[6 * i + 1], vertexData[6 * i + 2], vertexData[6 * i + 3])
+ local v2 = Vector3(vertexData[6 * i + 7], vertexData[6 * i + 8], vertexData[6 * i + 9])
+ local v3 = Vector3(vertexData[6 * i + 13], vertexData[6 * i + 14], vertexData[6 * i + 15])
+
+ local edge1 = v1 - v2
+ local edge2 = v1 - v3
+ local normal = edge1:CrossProduct(edge2):Normalized()
+ vertexData[6 * i + 4] = normal.x
+ vertexData[6 * i + 5] = normal.y
+ vertexData[6 * i + 6] = normal.z
+ vertexData[6 * i + 10] = normal.x
+ vertexData[6 * i + 11] = normal.y
+ vertexData[6 * i + 12] = normal.z
+ vertexData[6 * i + 16] = normal.x
+ vertexData[6 * i + 17] = normal.y
+ vertexData[6 * i + 18] = normal.z
+ end
+
+ -- Create model, buffers and geometry without garbage collection, as they will be managed
+ -- by the StaticModel component once assigned to it
+ local fromScratchModel = Model:new()
+ local vb = VertexBuffer:new()
+ local ib = IndexBuffer:new()
+ local geom = Geometry:new()
+
+ -- Shadowed buffer needed for raycasts to work, and so that data can be automatically restored on device loss
+ vb.shadowed = true
+ -- We could use the "legacy" element bitmask to define elements for more compact code, but let's demonstrate
+ -- defining the vertex elements explicitly to allow any element types and order
+ local elements = {
+ VertexElement(TYPE_VECTOR3, SEM_POSITION),
+ VertexElement(TYPE_VECTOR3, SEM_NORMAL)
+ }
+ vb:SetSize(numVertices, elements)
+ local temp = VectorBuffer()
+ for i = 1, numVertices * 6 do
+ temp:WriteFloat(vertexData[i])
+ end
+ vb:SetData(temp)
+
+ ib.shadowed = true
+ ib:SetSize(numVertices, false)
+ temp:Clear()
+ for i = 1, numVertices do
+ temp:WriteUShort(indexData[i])
+ end
+ ib:SetData(temp)
+
+ geom:SetVertexBuffer(0, vb)
+ geom:SetIndexBuffer(ib)
+ geom:SetDrawRange(TRIANGLE_LIST, 0, numVertices)
+
+ fromScratchModel.numGeometries = 1
+ fromScratchModel:SetGeometry(0, 0, geom)
+ fromScratchModel.boundingBox = BoundingBox(Vector3(-0.5, -0.5, -0.5), Vector3(0.5, 0.5, 0.5))
+
+ -- Though not necessary to render, the vertex & index buffers must be listed in the model so that it can be saved properly
+ local vertexBuffers = {}
+ local indexBuffers = {}
+ table.insert(vertexBuffers, vb)
+ table.insert(indexBuffers, ib)
+ -- Morph ranges could also be not defined. Here we simply define a zero range (no morphing) for the vertex buffer
+ local morphRangeStarts = {}
+ local morphRangeCounts = {}
+ table.insert(morphRangeStarts, 0)
+ table.insert(morphRangeCounts, 0)
+ fromScratchModel:SetVertexBuffers(vertexBuffers, morphRangeStarts, morphRangeCounts)
+ fromScratchModel:SetIndexBuffers(indexBuffers)
+
+ local node = scene_:CreateChild("FromScratchObject")
+ node.position = Vector3(0.0, 3.0, 0.0)
+ local object = node:CreateComponent("StaticModel")
+ object.model = fromScratchModel
+
+ -- Create the camera. Create it outside the scene so that we can clear the whole scene without affecting it
+ cameraNode = Node()
+ cameraNode.position = Vector3(0.0, 2.0, -20.0)
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse/touch to move\n"..
+ "Space to toggle animation")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
+
+function AnimateObjects(timeStep)
+ animTime = animTime + timeStep * 100.0
+
+ -- Repeat for each of the cloned vertex buffers
+ for i = 1, table.getn(animatingBuffers) do
+ local startPhase = animTime + i * 30.0
+ local buffer = animatingBuffers[i]
+
+ -- Need to prepare a VectorBuffer with all data (positions, normals, uvs...)
+ local newData = VectorBuffer()
+ local numVertices = buffer.vertexCount
+ local vertexSize = buffer.vertexSize
+ for j = 1, numVertices do
+ -- If there are duplicate vertices, animate them in phase of the original
+ local phase = startPhase + vertexDuplicates[j] * 10.0
+ local src = originalVertices[j]
+ local dest = Vector3(src.x * (1.0 + 0.1 * Sin(phase)),
+ src.y * (1.0 + 0.1 * Sin(phase + 60.0)),
+ src.z * (1.0 + 0.1 * Sin(phase + 120.0)))
+
+ -- Write position
+ newData:WriteVector3(dest)
+ -- Copy other vertex elements
+ originalVertexData:Seek((j - 1) * vertexSize + 12) -- Seek past the vertex position
+ for k = 12, vertexSize - 4, 4 do
+ newData:WriteFloat(originalVertexData:ReadFloat())
+ end
+ end
+
+ buffer:SetData(newData)
+ end
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Toggle animation with space
+ if input:GetKeyPress(KEY_SPACE) then
+ animate = not animate
+ end
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+
+ -- Animate scene if enabled
+ if animate then
+ AnimateObjects(timeStep)
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Animation" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/35_SignedDistanceFieldText.lua b/bin/Data/LuaScripts/35_SignedDistanceFieldText.lua
new file mode 100644
index 0000000..963e2f9
--- /dev/null
+++ b/bin/Data/LuaScripts/35_SignedDistanceFieldText.lua
@@ -0,0 +1,170 @@
+-- Signed distance field text example.
+-- This sample demonstrates:
+-- - Creating a 3D scene with static content
+-- - Creating a 3D text use SDF Font
+-- - Displaying the scene using the Renderer subsystem
+-- - Handling keyboard and mouse input to move a freelook camera
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+
+ -- Create a child scene node (at world origin) and a StaticModel component into it. Set the StaticModel to show a simple
+ -- plane mesh with a "stone" material. Note that naming the scene nodes is optional. Scale the scene node larger
+ -- (100 x 100 world units)
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3(100.0, 1.0, 100.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Create a directional light to the world so that we can see something. The light scene node's orientation controls the
+ -- light direction we will use the SetDirection() function which calculates the orientation from a forward direction vector.
+ -- The light will use default settings (white light, no shadows)
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8) -- The direction vector does not need to be normalized
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+
+ -- Create more StaticModel objects to the scene, randomly positioned, rotated and scaled. For rotation, we construct a
+ -- quaternion from Euler angles where the Y angle (rotation about the Y axis) is randomized. The mushroom model contains
+ -- LOD levels, so the StaticModel component will automatically select the LOD level according to the view distance (you'll
+ -- see the model get simpler as it moves further away). Finally, rendering a large number of the same object with the
+ -- same material allows instancing to be used, if the GPU supports it. This reduces the amount of CPU work in rendering the
+ -- scene.
+ local NUM_OBJECTS = 200
+ for i = 1, NUM_OBJECTS do
+ local mushroomNode = scene_:CreateChild("Mushroom")
+ mushroomNode.position = Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0)
+ mushroomNode:SetScale(0.5 + Random(2.0))
+ local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+ mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ mushroomObject.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+
+ local mushroomTitleNode = mushroomNode:CreateChild("MushroomTitle")
+ mushroomTitleNode.position = Vector3(0.0, 1.2, 0.0)
+ local mushroomTitleText = mushroomTitleNode:CreateComponent("Text3D")
+ mushroomTitleText.text = "Mushroom " .. i
+
+ mushroomTitleText:SetFont(cache:GetResource("Font", "Fonts/BlueHighway.sdf"), 24)
+ mushroomTitleText.color = Color(1.0, 0.0, 0.0)
+
+ if (i % 3) == 1 then
+ mushroomTitleText.color = Color(0.0, 1.0, 0.0)
+ mushroomTitleText.textEffect = TE_SHADOW
+ mushroomTitleText.effectColor = Color(0.5, 0.5, 0.5)
+ end
+
+ if (i % 3) == 2 then
+ mushroomTitleText.color = Color(1.0, 1.0, 0.0)
+ mushroomTitleText.textEffect = TE_STROKE
+ mushroomTitleText.effectColor = Color(0.5, 0.5, 0.5)
+ end
+
+ mushroomTitleText:SetAlignment(HA_CENTER, VA_CENTER)
+ end
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ cameraNode:CreateComponent("Camera")
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 5.0, 0.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw +MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ -- Use the Translate() function (default local space) to move relative to the node's orientation.
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
diff --git a/bin/Data/LuaScripts/36_Urho2DTileMap.lua b/bin/Data/LuaScripts/36_Urho2DTileMap.lua
new file mode 100644
index 0000000..83c8b97
--- /dev/null
+++ b/bin/Data/LuaScripts/36_Urho2DTileMap.lua
@@ -0,0 +1,195 @@
+-- Urho2D tile map example.
+-- This sample demonstrates:
+-- - Creating a 2D scene with tile map
+-- - Displaying the scene using the Renderer subsystem
+-- - Handling keyboard to move and zoom 2D camera
+-- - Interacting with the tile map
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Enable OS cursor
+ input.mouseVisible = true
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 0.0, -10.0)
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.orthographic = true
+ camera.orthoSize = graphics.height * PIXEL_SIZE
+ camera.zoom = 1.0 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.0) is set for full visibility at 1280x800 resolution)
+
+ -- Get tmx file
+ local tmxFile = cache:GetResource("TmxFile2D", "Urho2D/isometric_grass_and_water.tmx")
+ if tmxFile == nil then
+ return
+ end
+
+ local tileMapNode = scene_:CreateChild("TileMap")
+ tileMapNode.position = Vector3(0.0, 0.0, -1.0)
+
+ local tileMap = tileMapNode:CreateComponent("TileMap2D")
+ tileMap.tmxFile = tmxFile
+
+ -- Set camera's position
+ local info = tileMap.info
+ local x = info.mapWidth * 0.5
+ local y = info.mapHeight * 0.5
+ cameraNode.position = Vector3(x, y, -10.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move, Use PageUp PageDown to zoom.\n LMB to remove a tile, RMB to swap grass and water.")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 4.0
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 1.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, -1.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ if input:GetKeyDown(KEY_PAGEUP) then
+ local camera = cameraNode:GetComponent("Camera")
+ camera.zoom = camera.zoom * 1.01
+ end
+
+ if input:GetKeyDown(KEY_PAGEDOWN) then
+ local camera = cameraNode:GetComponent("Camera")
+ camera.zoom = camera.zoom * 0.99
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Listen to mouse clicks
+ SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown")
+
+ -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+function HandleMouseButtonDown(eventType, eventData)
+ local tileMapNode = scene_:GetChild("TileMap", true)
+ local map = tileMapNode:GetComponent("TileMap2D")
+ local layer = map:GetLayer(0)
+
+ success, x, y = map:PositionToTileIndex(GetMousePositionXY())
+ if success then
+ -- Get tile's sprite. Note that layer.GetTile(x, y).sprite is read-only, so we get the sprite through tile's node
+ local n = layer:GetTileNode(x, y)
+ if n == nil then
+ return
+ end
+ local sprite = n:GetComponent("StaticSprite2D")
+
+ if input:GetMouseButtonDown(MOUSEB_RIGHT) then
+ -- Swap grass and water
+ if layer:GetTile(x, y).gid < 9 then -- First 8 sprites in the "isometric_grass_and_water.png" tileset are mostly grass and from 9 to 24 they are mostly water
+ sprite.sprite = layer:GetTile(0, 0).sprite -- Replace grass by water sprite used in top tile
+ else sprite.sprite = layer:GetTile(24, 24).sprite end -- Replace water by grass sprite used in bottom tile
+ else sprite.sprite = nil end -- 'Remove' sprite
+ end
+end
+
+function GetMousePositionXY()
+ local camera = cameraNode:GetComponent("Camera")
+ local screenPoint = Vector3(input.mousePosition.x / graphics.width, input.mousePosition.y / graphics.height, 10)
+ local worldPoint = camera:ScreenToWorldPoint(screenPoint)
+ return Vector2(worldPoint.x, worldPoint.y)
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Zoom In" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Zoom Out" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/37_UIDrag.lua b/bin/Data/LuaScripts/37_UIDrag.lua
new file mode 100644
index 0000000..ff5e2eb
--- /dev/null
+++ b/bin/Data/LuaScripts/37_UIDrag.lua
@@ -0,0 +1,178 @@
+-- Urho3D UI Drag Example:
+-- This sample demonstrates:
+-- - Creating GUI elements from AngelScript
+-- - Loading GUI Style from xml
+-- - Subscribing to GUI drag events and handling them
+-- - Working with GUI elements with specific tags.
+
+require "LuaScripts/Utilities/Sample"
+VAR_BUTTONS = StringHash("BUTTONS")
+VAR_START = StringHash("START")
+VAR_DELTA = StringHash("DELTA")
+
+function Start()
+ -- Execute base class startup
+ SampleStart()
+
+ -- Set mouse visible
+ local platform = GetPlatform()
+ if platform ~= "Android" and platform ~= "iOS" then
+ input.mouseVisible = true
+ end
+
+ -- Create the UI content
+ CreateGUI()
+ CreateInstructions()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Drag on the buttons to move them around.\n"..
+ "Touch input allows also multi-drag.\n"..
+ "Press SPACE to show/hide tagged UI elements.")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function CreateGUI()
+ -- Load the style sheet from xml
+ ui.root.defaultStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+
+ for i=0, 9, 1 do
+ local b = Button:new()
+ ui.root:AddChild(b)
+ b:SetStyleAuto()
+ b.minWidth = 250
+ b:SetPosition(IntVector2(50*i, 50*i))
+
+ -- Enable the bring-to-front flag and set the initial priority
+ b.bringToFront = true
+ b.priority = i
+
+ -- Set the layout mode to make the child text elements aligned vertically
+ b:SetLayout(LM_VERTICAL, 20, IntRect(40, 40, 40, 40))
+ local dragInfos = {"Num Touch", "Text", "Event Touch"}
+ for j=1, table.getn(dragInfos) do
+ b:CreateChild("Text", dragInfos[j]):SetStyleAuto()
+ end
+
+ if i % 2 == 0 then
+ b:AddTag("SomeTag")
+ end
+
+ SubscribeToEvent(b, "Click", "HandleClick")
+ SubscribeToEvent(b, "DragMove", "HandleDragMove")
+ SubscribeToEvent(b, "DragBegin", "HandleDragBegin")
+ SubscribeToEvent(b, "DragCancel", "HandleDragCancel")
+ end
+
+ for i = 0, 9, 1 do
+ local t = Text:new()
+ ui.root:AddChild(t)
+ t:SetStyleAuto()
+ t:SetName("Touch " .. i)
+ t.visible = false
+ t.priority = 100 -- Make sure it has higher priority than the buttons
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleClick(eventType, eventData)
+ local element = eventData["Element"]:GetPtr("UIElement")
+ element:BringToFront()
+end
+
+function HandleDragBegin(eventType, eventData)
+ local element = eventData["Element"]:GetPtr("UIElement")
+
+ local lx = eventData["X"]:GetInt()
+ local ly = eventData["Y"]:GetInt()
+
+ local p = element.position
+ element:SetVar(VAR_START, Variant(p))
+ element:SetVar(VAR_DELTA, Variant(Vector2(p.x - lx, p.y - ly)))
+
+ local buttons = eventData["Buttons"]:GetInt()
+ element:SetVar(VAR_BUTTONS, Variant(buttons))
+
+ element:GetChild("Text").text = "Drag Begin Buttons: " .. buttons
+
+ element:GetChild("Num Touch").text = "Number of buttons: " .. eventData["NumButtons"]:GetInt()
+end
+
+function HandleDragMove(eventType, eventData)
+ local element = eventData["Element"]:GetPtr("UIElement")
+ local buttons = eventData["Buttons"]:GetInt()
+ local d = element:GetVar(VAR_DELTA):GetVector2()
+ local X = eventData["X"]:GetInt() + d.x
+ local Y = eventData["Y"]:GetInt() + d.y
+ local BUTTONS = element:GetVar(VAR_BUTTONS):GetInt()
+
+ element:GetChild("Event Touch").text = "Drag Move Buttons: " .. buttons
+
+ element:SetPosition(IntVector2(X, Y))
+end
+
+function HandleDragCancel(eventType, eventData)
+ local element = eventData["Element"]:GetPtr("UIElement")
+ local P = element:GetVar(VAR_START):GetIntVector2()
+ element:SetPosition(P)
+end
+
+function HandleUpdate(eventType, eventData)
+ local n = input:GetNumTouches()
+ local i = 0
+ while i < n do
+ local t = tolua.cast(ui.root:GetChild("Touch " .. i), 'Text')
+ local ts = input:GetTouch(i)
+ t:SetText("Touch " .. ts.touchID)
+
+ local pos = IntVector2(ts.position)
+ pos.y = pos.y - 30
+
+ t:SetPosition(pos)
+ t:SetVisible(true)
+
+ i = i + 1
+ end
+
+ i = n
+ while i < 10 do
+ local t = tolua.cast(ui.root:GetChild("Touch " .. i), 'Text')
+ t:SetVisible(false)
+ i = i + 1
+ end
+
+ if input:GetKeyPress(KEY_SPACE) then
+ elements = ui.root:GetChildrenWithTag("SomeTag")
+ for i, element in ipairs(elements) do
+ element.visible = not element.visible
+ end
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return "" ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+
+end
diff --git a/bin/Data/LuaScripts/38_SceneAndUILoad.lua b/bin/Data/LuaScripts/38_SceneAndUILoad.lua
new file mode 100644
index 0000000..4def146
--- /dev/null
+++ b/bin/Data/LuaScripts/38_SceneAndUILoad.lua
@@ -0,0 +1,154 @@
+-- Scene & UI load example.
+-- This sample demonstrates:
+-- - Loading a scene from a file and showing it
+-- - Loading a UI layout from a file and showing it
+-- - Subscribing to the UI layout's events
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateUI()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Subscribe to global events for camera movement
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Load scene content prepared in the editor (XML format). GetFile() returns an open file from the resource system
+ -- which scene.LoadXML() will read
+ local file = cache:GetFile("Scenes/SceneLoadExample.xml")
+ scene_:LoadXML(file)
+ -- In Lua the file returned by GetFile() needs to be deleted manually
+ file:delete()
+
+ -- Create the camera (not included in the scene file)
+ cameraNode = scene_:CreateChild("Camera")
+ cameraNode:CreateComponent("Camera")
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 2.0, -10.0)
+end
+
+function CreateUI()
+ -- Set up global UI style into the root UI element
+ local style = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ ui.root.defaultStyle = style
+
+ -- Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ -- control the camera, and when visible, it will interact with the UI
+ local cursor = ui.root:CreateChild("Cursor")
+ cursor:SetStyleAuto()
+ ui.cursor = cursor
+ -- Set starting position of the cursor at the rendering window center
+ cursor:SetPosition(graphics.width / 2, graphics.height / 2)
+
+ -- Load UI content prepared in the editor and add to the UI hierarchy
+ local layoutRoot = ui:LoadLayout(cache:GetResource("XMLFile", "UI/UILoadExample.xml"))
+ ui.root:AddChild(layoutRoot)
+
+ -- Subscribe to button actions (toggle scene lights when pressed then released)
+ local button = layoutRoot:GetChild("ToggleLight1", true)
+ if button ~= nil then
+ SubscribeToEvent(button, "Released", "ToggleLight1")
+ end
+ button = layoutRoot:GetChild("ToggleLight2", true)
+ if button ~= nil then
+ SubscribeToEvent(button, "Released", "ToggleLight2")
+ end
+end
+
+function ToggleLight1()
+ local lightNode = scene_:GetChild("Light1", true)
+ if lightNode ~= nil then
+ lightNode.enabled = not lightNode.enabled
+ end
+end
+
+function ToggleLight2()
+ local lightNode = scene_:GetChild("Light2", true)
+ if lightNode ~= nil then
+ lightNode.enabled = not lightNode.enabled
+ end
+end
+
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for camera motion
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+function MoveCamera(timeStep)
+ input.mouseVisible = input.mouseMode ~= MM_RELATIVE
+ mouseDown = input:GetMouseButtonDown(MOUSEB_RIGHT)
+
+ -- Override the MM_RELATIVE mouse grabbed settings, to allow interaction with UI
+ input.mouseGrabbed = mouseDown
+
+ -- Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = not mouseDown
+
+ -- Do not move if the UI has a focused element
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ -- Only move the camera when the cursor is hidden
+ if not ui.cursor.visible then
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+ end
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
diff --git a/bin/Data/LuaScripts/39_CrowdNavigation.lua b/bin/Data/LuaScripts/39_CrowdNavigation.lua
new file mode 100644
index 0000000..ba0adc1
--- /dev/null
+++ b/bin/Data/LuaScripts/39_CrowdNavigation.lua
@@ -0,0 +1,612 @@
+-- CrowdNavigation example.
+-- This sample demonstrates:
+-- - Generating a dynamic navigation mesh into the scene
+-- - Performing path queries to the navigation mesh
+-- - Adding and removing obstacles/agents at runtime
+-- - Raycasting drawable components
+-- - Crowd movement management
+-- - Accessing crowd agents with the crowd manager
+-- - Using off-mesh connections to make boxes climbable
+-- - Using agents to simulate moving obstacles
+
+require "LuaScripts/Utilities/Sample"
+
+local INSTRUCTION = "instructionText"
+
+local useStreaming = false
+local streamingDistance = 2
+local navigationTiles = {}
+local addedTiles = {}
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateUI()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update and render post-update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ -- Also create a DebugRenderer component so that we can draw debug geometry
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+
+ -- Create scene node & StaticModel component for showing a static plane
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3(100.0, 1.0, 100.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Create a Zone component for ambient lighting & fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(0.5, 0.5, 0.7)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ -- Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+ -- rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+ local boxGroup = scene_:CreateChild("Boxes")
+ for i = 1, 20 do
+ local boxNode = boxGroup:CreateChild("Box")
+ local size = 1.0 + Random(10.0)
+ boxNode.position = Vector3(Random(80.0) - 40.0, size * 0.5, Random(80.0) - 40.0)
+ boxNode:SetScale(size)
+ local boxObject = boxNode:CreateComponent("StaticModel")
+ boxObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+ boxObject.castShadows = true
+ if size >= 3.0 then
+ boxObject.occluder = true
+ end
+ end
+
+ -- Create a DynamicNavigationMesh component to the scene root
+ local navMesh = scene_:CreateComponent("DynamicNavigationMesh")
+ -- Set small tiles to show navigation mesh streaming
+ navMesh.tileSize = 32
+ -- Enable drawing debug geometry for obstacles and off-mesh connections
+ navMesh.drawObstacles = true
+ navMesh.drawOffMeshConnections = true
+ -- Set the agent height large enough to exclude the layers under boxes
+ navMesh.agentHeight = 10
+ -- Set nav mesh cell height to minimum (allows agents to be grounded)
+ navMesh.cellHeight = 0.05
+ -- Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
+ -- navigation mesh. By default this is recursive, but the recursion could be turned off from Navigable
+ scene_:CreateComponent("Navigable")
+ -- Add padding to the navigation mesh in Y-direction so that we can add objects on top of the tallest boxes
+ -- in the scene and still update the mesh correctly
+ navMesh.padding = Vector3(0.0, 10.0, 0.0)
+ -- Now build the navigation geometry. This will take some time. Note that the navigation mesh will prefer to use
+ -- physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
+ -- it will use renderable geometry instead
+ navMesh:Build()
+
+ -- Create an off-mesh connection for each box to make it climbable (tiny boxes are skipped).
+ -- Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
+ -- Creating connections post-build here allows us to use FindNearestPoint() to procedurally set accurate positions for the connection
+ CreateBoxOffMeshConnections(navMesh, boxGroup)
+
+ -- Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
+ for i = 1, 100 do
+ CreateMushroom(Vector3(Random(90.0) - 45.0, 0.0, Random(90.0) - 45.0))
+ end
+
+ -- Create a CrowdManager component to the scene root (mandatory for crowd agents)
+ local crowdManager = scene_:CreateComponent("CrowdManager")
+ local params = crowdManager:GetObstacleAvoidanceParams(0)
+ -- Set the params to "High (66)" setting
+ params.velBias = 0.5
+ params.adaptiveDivs = 7
+ params.adaptiveRings = 3
+ params.adaptiveDepth = 3
+ crowdManager:SetObstacleAvoidanceParams(0, params)
+
+ -- Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
+ CreateMovingBarrels(navMesh)
+
+ -- Create Jack node as crowd agent
+ SpawnJack(Vector3(-5, 0, 20), scene_:CreateChild("Jacks"))
+
+ -- Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
+ -- the scene, because we want it to be unaffected by scene load / save
+ cameraNode = Node()
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 300.0
+
+ -- Set an initial position for the camera scene node above the plane and looking down
+ cameraNode.position = Vector3(0.0, 50.0, 0.0)
+ pitch = 80.0
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+end
+
+function CreateUI()
+ -- Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ -- control the camera, and when visible, it will point the raycast target
+ local style = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ local cursor = Cursor:new()
+ cursor:SetStyleAuto(style)
+ ui.cursor = cursor
+ -- Set starting position of the cursor at the rendering window center
+ cursor:SetPosition(graphics.width / 2, graphics.height / 2)
+
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text", INSTRUCTION)
+ instructionText.text = "Use WASD keys to move, RMB to rotate view\n"..
+ "LMB to set destination, SHIFT+LMB to spawn a Jack\n"..
+ "MMB or O key to add obstacles or remove obstacles/agents\n"..
+ "F5 to save scene, F7 to load\n"..
+ "Tab to toggle navigation mesh streaming\n"..
+ "Space to toggle debug geometry\n"..
+ "F12 to toggle this instruction text"
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+
+ -- Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
+ -- use a larger extents for finding a point on the navmesh to fix the agent's position
+ SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure")
+
+ -- Subscribe HandleCrowdAgentReposition() function for controlling the animation
+ SubscribeToEvent("CrowdAgentReposition", "HandleCrowdAgentReposition")
+
+ -- Subscribe HandleCrowdAgentFormation() function for positioning agent into a formation
+ SubscribeToEvent("CrowdAgentFormation", "HandleCrowdAgentFormation")
+end
+
+function SpawnJack(pos, jackGroup)
+ local jackNode = jackGroup:CreateChild("Jack")
+ jackNode.position = pos
+ local modelObject = jackNode:CreateComponent("AnimatedModel")
+ modelObject.model = cache:GetResource("Model", "Models/Jack.mdl")
+ modelObject.material = cache:GetResource("Material", "Materials/Jack.xml")
+ modelObject.castShadows = true
+ jackNode:CreateComponent("AnimationController")
+
+ -- Create a CrowdAgent component and set its height and realistic max speed/acceleration. Use default radius
+ local agent = jackNode:CreateComponent("CrowdAgent")
+ agent.height = 2.0
+ agent.maxSpeed = 3.0
+ agent.maxAccel = 5.0
+end
+
+function CreateMushroom(pos)
+ local mushroomNode = scene_:CreateChild("Mushroom")
+ mushroomNode.position = pos
+ mushroomNode.rotation = Quaternion(0.0, Random(360.0), 0.0)
+ mushroomNode:SetScale(2.0 + Random(0.5))
+ local mushroomObject = mushroomNode:CreateComponent("StaticModel")
+ mushroomObject.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ mushroomObject.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ mushroomObject.castShadows = true
+
+ -- Create the navigation Obstacle component and set its height & radius proportional to scale
+ local obstacle = mushroomNode:CreateComponent("Obstacle")
+ obstacle.radius = mushroomNode.scale.x
+ obstacle.height = mushroomNode.scale.y
+end
+
+function CreateBoxOffMeshConnections(navMesh, boxGroup)
+ boxes = boxGroup:GetChildren()
+ for i, box in ipairs(boxes) do
+ local boxPos = box.position
+ local boxHalfSize = box.scale.x / 2
+
+ -- Create 2 empty nodes for the start & end points of the connection. Note that order matters only when using one-way/unidirectional connection.
+ local connectionStart = box:CreateChild("ConnectionStart")
+ connectionStart.worldPosition = navMesh:FindNearestPoint(boxPos + Vector3(boxHalfSize, -boxHalfSize, 0)) -- Base of box
+ local connectionEnd = connectionStart:CreateChild("ConnectionEnd")
+ connectionEnd.worldPosition = navMesh:FindNearestPoint(boxPos + Vector3(boxHalfSize, boxHalfSize, 0)) -- Top of box
+
+ -- Create the OffMeshConnection component to one node and link the other node
+ local connection = connectionStart:CreateComponent("OffMeshConnection")
+ connection.endPoint = connectionEnd
+ end
+end
+
+function CreateMovingBarrels(navMesh)
+ local barrel = scene_:CreateChild("Barrel")
+ local model = barrel:CreateComponent("StaticModel")
+ model.model = cache:GetResource("Model", "Models/Cylinder.mdl")
+ model.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+ model.material:SetTexture(TU_DIFFUSE, cache:GetResource("Texture2D", "Textures/TerrainDetail2.dds"))
+ model.castShadows = true
+ for i = 1, 20 do
+ local clone = barrel:Clone()
+ local size = 0.5 + Random(1)
+ clone.scale = Vector3(size / 1.5, size * 2, size / 1.5)
+ clone.position = navMesh:FindNearestPoint(Vector3(Random(80.0) - 40.0, size * 0.5 , Random(80.0) - 40.0))
+ local agent = clone:CreateComponent("CrowdAgent")
+ agent.radius = clone.scale.x * 0.5
+ agent.height = size
+ agent.navigationQuality = NAVIGATIONQUALITY_LOW
+ end
+ barrel:Remove()
+end
+
+function SetPathPoint(spawning)
+ local hitPos, hitDrawable = Raycast(250.0)
+
+ if hitDrawable then
+ local navMesh = scene_:GetComponent("DynamicNavigationMesh")
+ local pathPos = navMesh:FindNearestPoint(hitPos, Vector3.ONE)
+ local jackGroup = scene_:GetChild("Jacks")
+ if spawning then
+ -- Spawn a jack at the target position
+ SpawnJack(pathPos, jackGroup)
+ else
+ -- Set crowd agents target position
+ scene_:GetComponent("CrowdManager"):SetCrowdTarget(pathPos, jackGroup)
+ end
+ end
+end
+
+function AddOrRemoveObject()
+ -- Raycast and check if we hit a mushroom node. If yes, remove it, if no, create a new one
+ local hitPos, hitDrawable = Raycast(250.0)
+ if hitDrawable then
+
+ local hitNode = hitDrawable.node
+ if hitNode.name == "Mushroom" then
+ hitNode:Remove()
+ elseif hitNode.name == "Jack" then
+ hitNode:Remove()
+ else
+ CreateMushroom(hitPos)
+ end
+ end
+end
+
+function Raycast(maxDistance)
+ local pos = ui.cursorPosition
+ -- Check the cursor is visible and there is no UI element in front of the cursor
+ if (not ui.cursor.visible) or (ui:GetElementAt(pos, true) ~= nil) then
+ return nil, nil
+ end
+
+ local camera = cameraNode:GetComponent("Camera")
+ local cameraRay = camera:GetScreenRay(pos.x / graphics.width, pos.y / graphics.height)
+ -- Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
+ local octree = scene_:GetComponent("Octree")
+ local result = octree:RaycastSingle(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY)
+ if result.drawable ~= nil then
+ return result.position, result.drawable
+ end
+
+ return nil, nil
+end
+
+function MoveCamera(timeStep)
+ input.mouseVisible = input.mouseMode ~= MM_RELATIVE
+ mouseDown = input:GetMouseButtonDown(MOUSEB_RIGHT)
+
+ -- Override the MM_RELATIVE mouse grabbed settings, to allow interaction with UI
+ input.mouseGrabbed = mouseDown
+
+ -- Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = not mouseDown
+
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ -- Only move the camera when the cursor is hidden
+ if not ui.cursor.visible then
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+ end
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+
+ -- Set destination or spawn a jack with left mouse button
+ if input:GetMouseButtonPress(MOUSEB_LEFT) then
+ SetPathPoint(input:GetQualifierDown(QUAL_SHIFT))
+ -- Add new obstacle or remove existing obstacle/agent with middle mouse button
+ elseif input:GetMouseButtonPress(MOUSEB_MIDDLE) or input:GetKeyPress(KEY_O) then
+ AddOrRemoveObject()
+ end
+
+ -- Check for loading/saving the scene from/to the file Data/Scenes/CrowdNavigation.xml relative to the executable directory
+ if input:GetKeyPress(KEY_F5) then
+ scene_:SaveXML(fileSystem:GetProgramDir().."Data/Scenes/CrowdNavigation.xml")
+ elseif input:GetKeyPress(KEY_F7) then
+ scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/CrowdNavigation.xml")
+
+ -- Toggle debug geometry with space
+ elseif input:GetKeyPress(KEY_SPACE) then
+ drawDebug = not drawDebug
+
+ -- Toggle instruction text with F12
+ elseif input:GetKeyPress(KEY_F12) then
+ instruction = ui.root:GetChild(INSTRUCTION)
+ instruction.visible = not instruction.visible
+ end
+end
+function ToggleStreaming(enabled)
+ local navMesh = scene_:GetComponent("DynamicNavigationMesh")
+ if enabled then
+ local maxTiles = (2 * streamingDistance + 1) * (2 * streamingDistance + 1)
+ local boundingBox = BoundingBox(navMesh.boundingBox)
+ SaveNavigationData()
+ navMesh:Allocate(boundingBox, maxTiles)
+ else
+ navMesh:Build()
+ end
+end
+
+function UpdateStreaming()
+ local navMesh = scene_:GetComponent("DynamicNavigationMesh")
+
+ -- Center the navigation mesh at the jacks crowd
+ local averageJackPosition = Vector3(0, 0, 0)
+ local jackGroup = scene_:GetChild("Jacks")
+ if jackGroup then
+ for i = 0,jackGroup:GetNumChildren()-1 do
+ averageJackPosition = averageJackPosition + jackGroup:GetChild(i).worldPosition
+ end
+ averageJackPosition = averageJackPosition / jackGroup:GetNumChildren()
+ end
+
+ local jackTile = navMesh:GetTileIndex(averageJackPosition)
+ local beginTile = VectorMax(IntVector2(0, 0), jackTile - IntVector2(1, 1) * streamingDistance)
+ local endTile = VectorMin(jackTile + IntVector2(1, 1) * streamingDistance, navMesh.numTiles - IntVector2(1, 1))
+
+ -- Remove tiles
+ local numTiles = navMesh.numTiles
+ for i,tileIdx in pairs(addedTiles) do
+ if not (beginTile.x <= tileIdx.x and tileIdx.x <= endTile.x and beginTile.y <= tileIdx.y and tileIdx.y <= endTile.y) then
+ addedTiles[i] = nil
+ navMesh:RemoveTile(tileIdx)
+ end
+ end
+
+ -- Add tiles
+ for z = beginTile.y, endTile.y do
+ for x = beginTile.x, endTile.x do
+ local i = z * numTiles.x + x
+ if not navMesh:HasTile(IntVector2(x, z)) and navigationTiles[i] then
+ addedTiles[i] = IntVector2(x, z)
+ navMesh:AddTile(navigationTiles[i])
+ end
+ end
+ end
+end
+
+function SaveNavigationData()
+ local navMesh = scene_:GetComponent("DynamicNavigationMesh")
+ navigationTiles = {}
+ addedTiles = {}
+ local numTiles = navMesh.numTiles
+
+ for z = 0, numTiles.y - 1 do
+ for x = 0, numTiles.x - 1 do
+ local i = z * numTiles.x + x
+ navigationTiles[i] = navMesh:GetTileData(IntVector2(x, z))
+ end
+ end
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+
+ -- Update streaming
+ if input:GetKeyPress(KEY_TAB) then
+ useStreaming = not useStreaming
+ ToggleStreaming(useStreaming)
+ end
+ if useStreaming then
+ UpdateStreaming()
+ end
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ if drawDebug then
+ -- Visualize navigation mesh, obstacles and off-mesh connections
+ scene_:GetComponent("DynamicNavigationMesh"):DrawDebugGeometry(true)
+ -- Visualize agents' path and position to reach
+ scene_:GetComponent("CrowdManager"):DrawDebugGeometry(true)
+ end
+end
+
+function HandleCrowdAgentFailure(eventType, eventData)
+ local node = eventData["Node"]:GetPtr("Node")
+ local agentState = eventData["CrowdAgentState"]:GetInt()
+
+ -- If the agent's state is invalid, likely from spawning on the side of a box, find a point in a larger area
+ if agentState == CA_STATE_INVALID then
+ -- Get a point on the navmesh using more generous extents
+ local newPos = scene_:GetComponent("DynamicNavigationMesh"):FindNearestPoint(node.position, Vector3(5, 5, 5))
+ -- Set the new node position, CrowdAgent component will automatically reset the state of the agent
+ node.position = newPos
+ end
+end
+
+function HandleCrowdAgentReposition(eventType, eventData)
+ local WALKING_ANI = "Models/Jack_Walk.ani"
+
+ local node = eventData["Node"]:GetPtr("Node")
+ local agent = eventData["CrowdAgent"]:GetPtr("CrowdAgent")
+ local velocity = eventData["Velocity"]:GetVector3()
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Only Jack agent has animation controller
+ local animCtrl = node:GetComponent("AnimationController")
+ if animCtrl ~= nil then
+ local speed = velocity:Length()
+ if animCtrl:IsPlaying(WALKING_ANI) then
+ local speedRatio = speed / agent.maxSpeed
+ -- Face the direction of its velocity but moderate the turning speed based on the speed ratio and timeStep
+ node.rotation = node.rotation:Slerp(Quaternion(Vector3.FORWARD, velocity), 10.0 * timeStep * speedRatio)
+ -- Throttle the animation speed based on agent speed ratio (ratio = 1 is full throttle)
+ animCtrl:SetSpeed(WALKING_ANI, speedRatio * 1.5)
+ else
+ animCtrl:Play(WALKING_ANI, 0, true, 0.1)
+ end
+
+ -- If speed is too low then stop the animation
+ if speed < agent.radius then
+ animCtrl:Stop(WALKING_ANI, 0.5)
+ end
+ end
+end
+
+function HandleCrowdAgentFormation(eventType, eventData)
+ local index = eventData["Index"]:GetUInt()
+ local size = eventData["Size"]:GetUInt()
+ local position = eventData["Position"]:GetVector3()
+
+ -- The first agent will always move to the exact position, all other agents will select a random point nearby
+ if index > 0 then
+ local crowdManager = GetEventSender()
+ local agent = eventData["CrowdAgent"]:GetPtr("CrowdAgent")
+ eventData["Position"] = crowdManager:GetRandomPointInCircle(position, agent.radius, agent.queryFilterType)
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Set" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Debug" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/40_Localization.lua b/bin/Data/LuaScripts/40_Localization.lua
new file mode 100644
index 0000000..0f7da32
--- /dev/null
+++ b/bin/Data/LuaScripts/40_Localization.lua
@@ -0,0 +1,179 @@
+-- Localization example.
+-- This sample demonstrates:
+-- - Loading a collection of strings from JSON-files
+-- - Creating text elements that automatically translates itself by changing the language
+-- - The manually reaction to change language
+
+require "LuaScripts/Utilities/Sample"
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Enable and center OS cursor
+ input.mouseVisible = true
+ input:CenterMousePosition()
+
+ -- Load strings from JSON files and subscribe to the change language event
+ InitLocalizationSystem()
+
+ -- Init the 3D space
+ CreateScene()
+
+ -- Init the user interface
+ CreateGUI()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+end
+
+function InitLocalizationSystem()
+ -- JSON files must be in UTF8 encoding without BOM
+ -- The first found language will be set as current
+ localization:LoadJSONFile("StringsEnRu.json")
+ -- You can load multiple files
+ localization:LoadJSONFile("StringsDe.json")
+ -- Hook up to the change language
+ SubscribeToEvent("ChangeLanguage", "HandleChangeLanguage")
+end
+
+function CreateGUI()
+ ui.root.defaultStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+
+ local window = Window:new()
+ ui.root:AddChild(window)
+ window:SetMinSize(384, 192)
+ window:SetLayout(LM_VERTICAL, 6, IntRect(6, 6, 6, 6))
+ window:SetAlignment(HA_CENTER, VA_CENTER)
+ window:SetStyleAuto()
+
+ local windowTitle = Text:new()
+ windowTitle.name = "WindowTitle"
+ windowTitle:SetStyleAuto()
+ window:AddChild(windowTitle)
+
+ -- In this place the current language is "en" because it was found first when loading the JSON files
+ local langName = localization.language
+ -- Languages are numbered in the loading order
+ local langIndex = localization.languageIndex -- == 0 at the beginning
+ -- Get string with identifier "title" in the current language
+ local localizedString = localization:Get("title")
+ -- Localization:Get returns String.EMPTY if the id is empty.
+ -- Localization:Get returns the id if translation is not found and will be added a warning into the log.
+
+ windowTitle.text = localizedString .. " (" .. langIndex .. " " .. langName .. ")"
+
+ local b = Button:new()
+ window:AddChild(b)
+ b:SetStyle("Button")
+ b.minHeight = 24
+
+ local t = b:CreateChild("Text", "ButtonTextChangeLang")
+ -- The showing text value will automatically change when language is changed
+ t.autoLocalizable = true
+ -- The text value used as a string identifier in this mode.
+ -- Remember that a letter case of the id and of the lang name is important.
+ t.text = "Press this button"
+
+ t:SetAlignment(HA_CENTER, VA_CENTER)
+ t:SetStyle("Text")
+ SubscribeToEvent(b, "Released", "HandleChangeLangButtonPressed")
+
+ b = Button:new()
+ window:AddChild(b)
+ b:SetStyle("Button")
+ b.minHeight = 24
+ t = b:CreateChild("Text", "ButtonTextQuit")
+ t:SetAlignment(HA_CENTER, VA_CENTER)
+ t:SetStyle("Text")
+
+ -- Manually set text in the current language
+ t.text = localization:Get("quit")
+
+ SubscribeToEvent(b, "Released", "HandleQuitButtonPressed")
+end
+
+function CreateScene()
+ scene_ = Scene:new()
+ scene_:CreateComponent("Octree")
+
+ local zone = scene_:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox:new(-1000.0, 1000.0)
+ zone.ambientColor = Color:new(0.5, 0.5, 0.5)
+ zone.fogColor = Color:new(0.4, 0.5, 0.8)
+ zone.fogStart = 1.0
+ zone.fogEnd = 100.0
+
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3:new(300.0, 1.0, 300.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3:new(0.6, -1.0, 0.8)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.color = Color:new(0.8, 0.8, 0.8)
+
+ cameraNode = scene_:CreateChild("Camera")
+ cameraNode:CreateComponent("Camera")
+ cameraNode.position = Vector3:new(0.0, 10.0, -30.0)
+
+ local text3DNode = scene_:CreateChild("Text3D")
+ text3DNode.position = Vector3:new(0.0, 0.1, 30.0)
+ text3DNode:SetScale(15)
+ local text3D = text3DNode:CreateComponent("Text3D")
+
+ -- Manually set text in the current language.
+ text3D.text = localization:Get("lang")
+
+ text3D:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 30)
+ text3D.color = Color.BLACK
+ text3D:SetAlignment(HA_CENTER, VA_BOTTOM)
+
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ local timeStep = eventData["TimeStep"]:GetFloat()
+ local MOUSE_SENSITIVITY = 0.1
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+end
+
+function HandleChangeLangButtonPressed(eventType, eventData)
+ -- Languages are numbered in the loading order
+ local lang = localization.languageIndex
+ lang = lang + 1
+ if lang >= localization.numLanguages then
+ lang = 0
+ end
+ localization:SetLanguage(lang)
+end
+
+function HandleQuitButtonPressed(eventType, eventData)
+ engine:Exit()
+end
+
+-- You can manually change texts, sprites and other aspects of the game when language is changed
+function HandleChangeLanguage(eventType, eventData)
+ local windowTitle = ui.root:GetChild("WindowTitle", true)
+ windowTitle.text = localization:Get("title") .. " (" ..
+ localization.languageIndex .. " " ..
+ localization.language .. ")"
+
+ local buttonText = ui.root:GetChild("ButtonTextQuit", true)
+ buttonText.text = localization:Get("quit")
+
+ local text3D = scene_:GetChild("Text3D"):GetComponent("Text3D")
+ text3D.text = localization:Get("lang")
+
+ -- A text on the button "Press this button" changes automatically
+end
diff --git a/bin/Data/LuaScripts/41_DatabaseDemo.lua b/bin/Data/LuaScripts/41_DatabaseDemo.lua
new file mode 100644
index 0000000..439a683
--- /dev/null
+++ b/bin/Data/LuaScripts/41_DatabaseDemo.lua
@@ -0,0 +1,176 @@
+-- Database demo. This sample demonstrates how to use database subsystem to connect to a database and execute adhoc SQL statements.
+
+require "LuaScripts/Utilities/Sample"
+
+local connection
+local row
+local maxRows = 50
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Disable default execution of Lua from the console
+ SetExecuteConsoleCommands(false)
+
+ -- Subscribe to console commands and the frame update
+ SubscribeToEvent("ConsoleCommand", "HandleConsoleCommand")
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe key down event
+ SubscribeToEvent("KeyDown", "HandleEscKeyDown")
+
+ -- Hide logo to make room for the console
+ SetLogoVisible(false)
+
+ -- Show the console by default, make it large
+ console.numRows = graphics.height / 16
+ console.numBufferedRows = 2 * console.numRows
+ console.commandInterpreter = "LuaScriptEventInvoker"
+ console.visible = true
+ console.closeButton.visible = false
+
+ -- Show OS mouse cursor
+ input.mouseVisible = true
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Open the operating system console window (for stdin / stdout) if not open yet
+ -- Do not open in fullscreen, as this would cause constant device loss
+ if not graphics.fullscreen then
+ OpenConsoleWindow()
+ end
+
+ -- In general, the connection string is really the only thing that need to be changed when switching underlying database API
+ -- and that when using ODBC API then the connection string must refer to an already installed ODBC driver
+ -- Although it has not been tested yet but the ODBC API should be able to interface with any vendor provided ODBC drivers
+ -- In this particular demo, however, when using ODBC API then the SQLite-ODBC driver need to be installed
+ -- The SQLite-ODBC driver can be built from source downloaded from http:--www.ch-werner.de/sqliteodbc/
+ -- You can try to install other ODBC driver and modify the connection string below to match your ODBC driver
+ -- Both DSN and DSN-less connection string should work
+ -- The ODBC API, i.e. URHO3D_DATABASE_ODBC build option, is only available for native (including RPI) platforms
+ -- and it is designed for development of game server connecting to ODBC-compliant databases in mind
+
+ -- This demo will always work when using SQLite API as the SQLite database engine is embedded inside Urho3D game engine
+ -- and this is also the case when targeting Web platform
+
+ -- We could have used #ifdef to init the connection string during compile time, but below shows how it is done during runtime
+ -- The "URHO3D_DATABASE_ODBC" compiler define is set when URHO3D_DATABASE_ODBC build option is enabled
+ -- Connect to a temporary in-memory SQLite database
+ connection = database:Connect(GetDBAPI() == DBAPI_ODBC and "Driver=SQLite3;Database=:memory:" or "file://")
+
+ -- Subscribe to database cursor event to loop through query resultset
+ SubscribeToEvent("DbCursor", "HandleDbCursor")
+
+ -- Show instruction
+ print([[This demo connects to temporary in-memory database.
+All the tables and their data will be lost after exiting the demo.
+Enter a valid SQL statement in the console input and press Enter to execute.
+Enter 'get/set maxrows [number]' to get/set the maximum rows to be printed out.
+Enter 'get/set connstr [string]' to get/set the database connection string and establish a new connection to it.
+Enter 'quit' or 'exit' to exit the demo.
+For example:]])
+ HandleInput("create table tbl1(col1 varchar(10), col2 smallint)")
+ HandleInput("insert into tbl1 values('Hello', 10)")
+ HandleInput("insert into tbl1 values('World', 20)")
+ HandleInput("select * from tbl1")
+end
+
+function HandleConsoleCommand(eventType, eventData)
+ if eventData["Id"]:GetString() == "LuaScriptEventInvoker" then
+ HandleInput(eventData["Command"]:GetString())
+ end
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Check if there is input from stdin
+ local input = GetConsoleInput()
+ if input:len() > 0 then
+ HandleInput(input)
+ end
+end
+
+function HandleEscKeyDown(eventType, eventData)
+ -- Unlike the other samples, exiting the engine when ESC is pressed instead of just closing the console
+ if eventData["Key"]:GetInt() == KEY_ESCAPE then
+ engine:Exit()
+ end
+end
+
+function HandleDbCursor(eventType, eventData)
+ -- In a real application the P_SQL can be used to do the logic branching in a shared event handler
+ -- However, this is not required in this sample demo
+ local colValues = eventData["ColValues"]:GetVariantVector()
+ local colHeaders = eventData["ColHeaders"]:GetStringVector()
+
+ -- In this sample demo we just use db cursor to dump each row immediately so we can filter out the row to conserve memory
+ -- In a real application this can be used to perform the client-side filtering logic
+ eventData["Filter"] = true
+ -- In this sample demo we abort the further cursor movement when maximum rows being dumped has been reached
+ row = row + 1
+ eventData["Abort"] = row >= maxRows
+
+ for i, colHeader in ipairs(colHeaders) do
+ print("Row #" .. row .. ": " .. colHeader .. " = " .. colValues[i])
+ end
+end
+
+function HandleInput(input)
+ -- Echo input string to stdout
+ print(input)
+ row = 0
+ if input == "quit" or input == "exit" then
+ engine:Exit()
+ elseif input:find("set") or input:find("get") then
+ -- We expect a key/value pair for 'set' command
+ local command, setting, value
+ _, _, command, setting, value = input:find("([gs]et)%s*(%a*)%s*(.*)")
+ if command == "set" and value ~= nil then
+ if setting == "maxrows" then
+ maxRows = Max(value, 1)
+ elseif (setting == "connstr") then
+ local newConnection = database:Connect(value)
+ if newConnection ~= nil then
+ database:Disconnect(connection)
+ connection = newConnection
+ end
+ end
+ end
+ if setting ~= nil then
+ if setting == "maxrows" then
+ print("maximum rows is set to " .. maxRows)
+ elseif setting == "connstr" then
+ print("connection string is set to " .. connection.connectionString)
+ else
+ print("Unrecognized setting: " .. setting)
+ end
+ else
+ print("Missing setting paramater. Recognized settings are: maxrows, connstr")
+ end
+ else
+ -- In this sample demo we use the dbCursor event to loop through each row as it is being fetched
+ -- Regardless of this event is being used or not, all the fetched rows will be made available in the DbResult object,
+ -- unless the dbCursor event handler has instructed to filter out the fetched row from the final result
+ local result = connection:Execute(input, true)
+
+ -- Number of affected rows is only meaningful for DML statements like insert/update/delete
+ if result.numAffectedRows ~= -1 then
+ print("Number of affected rows: " .. result.numAffectedRows)
+ end
+ end
+ print(" ")
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/42_PBRMaterials.lua b/bin/Data/LuaScripts/42_PBRMaterials.lua
new file mode 100644
index 0000000..8cacd5c
--- /dev/null
+++ b/bin/Data/LuaScripts/42_PBRMaterials.lua
@@ -0,0 +1,216 @@
+-- PBR materials example.
+-- This sample demonstrates:
+-- - Loading a scene that showcases physically based materials & shaders
+--
+-- To use with deferred rendering, a PBR deferred renderpath should be chosen:
+-- CoreData/RenderPaths/PBRDeferred.xml or CoreData/RenderPaths/PBRDeferredHWDepth.xml
+
+require "LuaScripts/Utilities/Sample"
+
+local dynamicMaterial = nil
+local roughnessLabel = nil
+local metallicLabel = nil
+local ambientLabel = nil
+local zone = nil
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateUI()
+
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Subscribe to global events for camera movement
+ SubscribeToEvents()
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use sliders to change Roughness and Metallic\n" ..
+ "Hold RMB and use WASD keys and mouse to move")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Load scene content prepared in the editor (XML format). GetFile() returns an open file from the resource system
+ -- which scene.LoadXML() will read
+ local file = cache:GetFile("Scenes/PBRExample.xml")
+ scene_:LoadXML(file)
+ -- In Lua the file returned by GetFile() needs to be deleted manually
+ file:delete()
+
+ local sphereWithDynamicMatNode = scene_:GetChild("SphereWithDynamicMat")
+ local staticModel = sphereWithDynamicMatNode:GetComponent("StaticModel")
+ dynamicMaterial = staticModel:GetMaterial(0)
+
+ local zoneNode = scene_:GetChild("Zone")
+ zone = zoneNode:GetComponent("Zone")
+
+ -- Create the camera (not included in the scene file)
+ cameraNode = scene_:CreateChild("Camera")
+ cameraNode:CreateComponent("Camera")
+
+ cameraNode.position = sphereWithDynamicMatNode.position + Vector3(2.0, 2.0, 2.0)
+ cameraNode:LookAt(sphereWithDynamicMatNode.position)
+ yaw = cameraNode.rotation:YawAngle()
+ pitch = cameraNode.rotation:PitchAngle()
+end
+
+function CreateUI()
+ -- Set up global UI style into the root UI element
+ local style = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ ui.root.defaultStyle = style
+
+ -- Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ -- control the camera, and when visible, it will interact with the UI
+ local cursor = ui.root:CreateChild("Cursor")
+ cursor:SetStyleAuto()
+ ui.cursor = cursor
+ -- Set starting position of the cursor at the rendering window center
+ cursor:SetPosition(graphics.width / 2, graphics.height / 2)
+
+ roughnessLabel = ui.root:CreateChild("Text")
+ roughnessLabel:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ roughnessLabel:SetPosition(370, 50)
+ roughnessLabel.textEffect = TE_SHADOW
+
+ metallicLabel = ui.root:CreateChild("Text")
+ metallicLabel:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ metallicLabel:SetPosition(370, 100)
+ metallicLabel.textEffect = TE_SHADOW
+
+ ambientLabel = ui.root:CreateChild("Text")
+ ambientLabel:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ ambientLabel:SetPosition(370, 150)
+ ambientLabel.textEffect = TE_SHADOW
+
+ local roughnessSlider = ui.root:CreateChild("Slider")
+ roughnessSlider:SetStyleAuto()
+ roughnessSlider:SetPosition(50, 50)
+ roughnessSlider:SetSize(300, 20)
+ roughnessSlider.range = 1.0 -- 0 - 1 range
+ SubscribeToEvent(roughnessSlider, "SliderChanged", "HandleRoughnessSliderChanged")
+ roughnessSlider.value = 0.5
+
+ local metallicSlider = ui.root:CreateChild("Slider")
+ metallicSlider:SetStyleAuto()
+ metallicSlider:SetPosition(50, 100)
+ metallicSlider:SetSize(300, 20)
+ metallicSlider.range = 1.0 -- 0 - 1 range
+ SubscribeToEvent(metallicSlider, "SliderChanged", "HandleMetallicSliderChanged")
+ metallicSlider.value = 0.5
+
+ local ambientSlider = ui.root:CreateChild("Slider")
+ ambientSlider:SetStyleAuto()
+ ambientSlider:SetPosition(50, 150)
+ ambientSlider:SetSize(300, 20)
+ ambientSlider.range = 10.0 -- 0 - 10 range
+ SubscribeToEvent(ambientSlider, "SliderChanged", "HandleAmbientSliderChanged")
+ ambientSlider.value = zone.ambientColor.a
+end
+
+function HandleRoughnessSliderChanged(eventType, eventData)
+ local newValue = eventData["Value"]:GetFloat()
+ dynamicMaterial:SetShaderParameter("Roughness", Variant(newValue))
+ roughnessLabel.text = "Roughness: " .. newValue
+end
+
+function HandleMetallicSliderChanged(eventType, eventData)
+ local newValue = eventData["Value"]:GetFloat()
+ dynamicMaterial:SetShaderParameter("Metallic", Variant(newValue))
+ metallicLabel.text = "Metallic: " .. newValue
+end
+
+function HandleAmbientSliderChanged(eventType, eventData)
+ local newValue = eventData["Value"]:GetFloat()
+ local col = Color(0, 0, 0, newValue)
+ zone.ambientColor = col
+ ambientLabel.text = "Ambient HDR Scale: " .. zone.ambientColor.a
+end
+
+function SetupViewport()
+ renderer.hdrRendering = true
+
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+
+ -- Add post-processing effects appropriate with the example scene
+ local effectRenderPath = viewport:GetRenderPath():Clone()
+ effectRenderPath:Append(cache:GetResource("XMLFile", "PostProcess/FXAA2.xml"))
+ effectRenderPath:Append(cache:GetResource("XMLFile", "PostProcess/GammaCorrection.xml"))
+ effectRenderPath:Append(cache:GetResource("XMLFile", "PostProcess/Tonemap.xml"))
+ effectRenderPath:Append(cache:GetResource("XMLFile", "PostProcess/AutoExposure.xml"))
+
+ viewport.renderPath = effectRenderPath
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for camera motion
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+end
+
+function MoveCamera(timeStep)
+ -- Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = not input:GetMouseButtonDown(MOUSEB_RIGHT)
+
+ -- Do not move if the UI has a focused element
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 10.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ -- Only move the camera when the cursor is hidden
+ if not ui.cursor.visible then
+ local mouseMove = input.mouseMove
+ yaw = yaw + MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+ end
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
diff --git a/bin/Data/LuaScripts/43_HttpRequestDemo.lua b/bin/Data/LuaScripts/43_HttpRequestDemo.lua
new file mode 100644
index 0000000..22b1edc
--- /dev/null
+++ b/bin/Data/LuaScripts/43_HttpRequestDemo.lua
@@ -0,0 +1,93 @@
+-- Http request example.
+-- This example demonstrates:
+-- - How to use Http request API
+
+require "LuaScripts/Utilities/Sample"
+
+local message = ""
+local text = nil
+local httpRequest = nil
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the user interface
+ CreateUI()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Subscribe to basic events such as update
+ SubscribeToEvents()
+end
+
+function CreateUI()
+ -- Construct new Text object
+ text = Text:new()
+
+ -- Set font and text color
+ text:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ text.color = Color(1.0, 1.0, 0.0)
+
+ -- Align Text center-screen
+ text.horizontalAlignment = HA_CENTER
+ text.verticalAlignment = VA_CENTER
+
+ -- Add Text instance to the UI root element
+ ui.root:AddChild(text)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing HTTP request
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Create HTTP request
+ if httpRequest == nil then
+ httpRequest = network:MakeHttpRequest("http://httpbin.org/ip")
+ else
+ -- Initializing HTTP request
+ if httpRequest.state == HTTP_INITIALIZING then
+ return
+ -- An error has occurred
+ elseif httpRequest.state == HTTP_ERROR then
+ text.text = "An error has occurred."
+ UnsubscribeFromEvent("Update")
+ -- Get message data
+ else
+ if httpRequest.availableSize > 0 then
+ message = message .. httpRequest:ReadLine()
+ else
+ text.text = "Processing..."
+
+ local json = JSONFile:new()
+ json:FromString(message)
+
+ local val = json:GetRoot():Get("origin")
+
+ if val.isNull then
+ text.text = "Invalid string."
+ else
+ text.text = "Your IP is: " .. val:GetString()
+ end
+
+ json:delete()
+ httpRequest:delete()
+
+ UnsubscribeFromEvent("Update")
+ end
+ end
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/44_RibbonTrailDemo.lua b/bin/Data/LuaScripts/44_RibbonTrailDemo.lua
new file mode 100644
index 0000000..56a8f68
--- /dev/null
+++ b/bin/Data/LuaScripts/44_RibbonTrailDemo.lua
@@ -0,0 +1,222 @@
+-- Ribbon trail demo.
+-- This sample demonstrates how to use both trail types of RibbonTrail component.
+
+require "LuaScripts/Utilities/Sample"
+
+local boxNode1 = nil
+local boxNode2 = nil
+local swordTrail = nil
+local ninjaAnimCtrl = nil
+local timeStepSum = 0.0
+local swordTrailStartTime = 0.2
+local swordTrailEndTime = 0.46
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_:CreateComponent("Octree")
+
+ -- Create scene node & StaticModel component for showing a static plane
+ local planeNode = scene_:CreateChild("Plane")
+ planeNode.scale = Vector3(100.0, 1.0, 100.0)
+ local planeObject = planeNode:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Create a directional light to the world.
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8) -- The direction vector does not need to be normalized
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00005, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ -- Create first box for face camera trail demo with 1 column.
+ boxNode1 = scene_:CreateChild("Box1")
+ local box1 = boxNode1:CreateComponent("StaticModel")
+ box1.model = cache:GetResource("Model", "Models/Box.mdl")
+ box1.castShadows = true
+ local boxTrail1 = boxNode1:CreateComponent("RibbonTrail")
+ boxTrail1.material = cache:GetResource("Material", "Materials/RibbonTrail.xml")
+ boxTrail1.startColor = Color(1.0, 0.5, 0.0, 1.0)
+ boxTrail1.endColor = Color(1.0, 1.0, 0.0, 0.0)
+ boxTrail1.width = 0.5
+ boxTrail1.updateInvisible = true
+
+ -- Create second box for face camera trail demo with 4 column.
+ -- This will produce less distortion than first trail.
+ boxNode2 = scene_:CreateChild("Box2")
+ local box2 = boxNode2:CreateComponent("StaticModel")
+ box2.model = cache:GetResource("Model", "Models/Box.mdl")
+ box2.castShadows = true
+ local boxTrail2 = boxNode2:CreateComponent("RibbonTrail")
+ boxTrail2.material = cache:GetResource("Material", "Materials/RibbonTrail.xml")
+ boxTrail2.startColor = Color(1.0, 0.5, 0.0, 1.0)
+ boxTrail2.endColor = Color(1.0, 1.0, 0.0, 0.0)
+ boxTrail2.width = 0.5
+ boxTrail2.tailColumn = 4
+ boxTrail2.updateInvisible = true
+
+ -- Load ninja animated model for bone trail demo.
+ local ninjaNode = scene_:CreateChild("Ninja")
+ ninjaNode.position = Vector3(5.0, 0.0, 0.0)
+ ninjaNode.rotation = Quaternion(0.0, 180.0, 0.0)
+ local ninja = ninjaNode:CreateComponent("AnimatedModel")
+ ninja.model = cache:GetResource("Model", "Models/NinjaSnowWar/Ninja.mdl")
+ ninja.material = cache:GetResource("Material", "Materials/NinjaSnowWar/Ninja.xml")
+ ninja.castShadows = true
+
+ -- Create animation controller and play attack animation.
+ ninjaAnimCtrl = ninjaNode:CreateComponent("AnimationController")
+ ninjaAnimCtrl:PlayExclusive("Models/NinjaSnowWar/Ninja_Attack3.ani", 0, true, 0.0)
+
+ -- Add ribbon trail to tip of sword.
+ local swordTip = ninjaNode:GetChild("Joint29", true)
+ swordTrail = swordTip:CreateComponent("RibbonTrail")
+
+ -- Set sword trail type to bone and set other parameters.
+ swordTrail.trailType = TT_BONE
+ swordTrail.material = cache:GetResource("Material", "Materials/SlashTrail.xml")
+ swordTrail.lifetime = 0.22
+ swordTrail.startColor = Color(1.0, 1.0, 1.0, 0.75)
+ swordTrail.endColor = Color(0.2, 0.5, 1.0, 0.0)
+ swordTrail.tailColumn = 4
+ swordTrail.updateInvisible = true
+
+ -- Add floating text for info.
+ local boxTextNode1 = scene_:CreateChild("BoxText1")
+ boxTextNode1.position = Vector3(-1.0, 2.0, 0.0)
+ local boxText1 = boxTextNode1:CreateComponent("Text3D")
+ boxText1.text = "Face Camera Trail (4 Column)"
+ boxText1:SetFont(cache:GetResource("Font", "Fonts/BlueHighway.sdf"), 24)
+
+ local boxTextNode2 = scene_:CreateChild("BoxText2")
+ boxTextNode2.position = Vector3(-6.0, 2.0, 0.0)
+ local boxText2 = boxTextNode2:CreateComponent("Text3D")
+ boxText2.text = "Face Camera Trail (1 Column)"
+ boxText2:SetFont(cache:GetResource("Font", "Fonts/BlueHighway.sdf"), 24)
+
+ local ninjaTextNode2 = scene_:CreateChild("NinjaText")
+ ninjaTextNode2.position = Vector3(4.0, 2.5, 0.0)
+ local ninjaText = ninjaTextNode2:CreateComponent("Text3D")
+ ninjaText.text = "Bone Trail (4 Column)"
+ ninjaText:SetFont(cache:GetResource("Font", "Fonts/BlueHighway.sdf"), 24)
+
+ -- Create the camera.
+ cameraNode = scene_:CreateChild("Camera")
+ cameraNode:CreateComponent("Camera")
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 2.0, -14.0)
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function MoveCamera(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Movement speed as world units per second
+ local MOVE_SPEED = 20.0
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ local mouseMove = input.mouseMove
+ yaw = yaw +MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0)
+
+ -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ -- Use the Translate() function (default local space) to move relative to the node's orientation.
+ if input:GetKeyDown(KEY_W) then
+ cameraNode:Translate(Vector3(0.0, 0.0, 1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_S) then
+ cameraNode:Translate(Vector3(0.0, 0.0, -1.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_A) then
+ cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+ if input:GetKeyDown(KEY_D) then
+ cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
+ end
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ MoveCamera(timeStep)
+
+ -- Sum of timesteps.
+ timeStepSum = timeStepSum + timeStep
+
+ -- Move first box with pattern.
+ boxNode1:SetTransform(Vector3(-4.0 + 3.0 * Cos(100.0 * timeStepSum), 0.5, -2.0 * Cos(400.0 * timeStepSum)), Quaternion())
+
+ -- Move second box with pattern.
+ boxNode2:SetTransform(Vector3(0.0 + 3.0 * Cos(100.0 * timeStepSum), 0.5, -2.0 * Cos(400.0 * timeStepSum)), Quaternion())
+
+ -- Get elapsed attack animation time.
+ local swordAnimTime = ninjaAnimCtrl:GetAnimationState("Models/NinjaSnowWar/Ninja_Attack3.ani").time
+
+ -- Stop emitting trail when sword is finished slashing.
+ if not swordTrail.emitting and swordAnimTime > swordTrailStartTime and swordAnimTime < swordTrailEndTime then
+ swordTrail.emitting = true
+ elseif swordTrail.emitting and swordAnimTime >= swordTrailEndTime then
+ swordTrail.emitting = false
+ end
+end
diff --git a/bin/Data/LuaScripts/45_InverseKinematics.lua b/bin/Data/LuaScripts/45_InverseKinematics.lua
new file mode 100644
index 0000000..7afc99c
--- /dev/null
+++ b/bin/Data/LuaScripts/45_InverseKinematics.lua
@@ -0,0 +1,236 @@
+-- Ribbon trail demo.
+-- This sample demonstrates how to adjust the position of animated feet so they match the ground's angle using IK.
+
+require "LuaScripts/Utilities/Sample"
+
+local jackAnimCtrl_
+local cameraRotateNode_
+local floorNode_
+local leftFoot_
+local rightFoot_
+local leftEffector_
+local rightEffector_
+local solver_
+local floorPitch_ = 0.0
+local floorRoll_ = 0.0
+local drawDebug_ = false
+
+function Start()
+ cache.autoReloadResources = true
+
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+ scene_:CreateComponent("PhysicsWorld")
+
+ -- Create scene node & StaticModel component for showing a static plane
+ floorNode_ = scene_:CreateChild("Plane")
+ floorNode_.scale = Vector3(50.0, 1.0, 50.0)
+ local planeObject = floorNode_:CreateComponent("StaticModel")
+ planeObject.model = cache:GetResource("Model", "Models/Plane.mdl")
+ planeObject.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
+
+ -- Set up collision, we need to raycast to determine foot height
+ floorNode_:CreateComponent("RigidBody")
+ local col = floorNode_:CreateComponent("CollisionShape")
+ col:SetBox(Vector3(1, 0, 1))
+
+ -- Create a directional light to the world.
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.6, -1.0, 0.8) -- The direction vector does not need to be normalized
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00005, 0.5)
+ -- Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+
+ -- Load Jack animated model
+ jackNode_ = scene_:CreateChild("Jack")
+ jackNode_.rotation = Quaternion(0.0, 270.0, 0.0)
+ jack = jackNode_:CreateComponent("AnimatedModel")
+ jack.model = cache:GetResource("Model", "Models/Jack.mdl")
+ jack.material = cache:GetResource("Material", "Materials/Jack.xml")
+ jack.castShadows = true
+
+ -- Create animation controller and play walk animation
+ jackAnimCtrl_ = jackNode_:CreateComponent("AnimationController")
+ jackAnimCtrl_:PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.0)
+
+ -- We need to attach two inverse kinematic effectors to Jack's feet to
+ -- control the grounding.
+ leftFoot_ = jackNode_:GetChild("Bip01_L_Foot", true)
+ rightFoot_ = jackNode_:GetChild("Bip01_R_Foot", true)
+ leftEffector_ = leftFoot_:CreateComponent("IKEffector")
+ rightEffector_ = rightFoot_:CreateComponent("IKEffector")
+ -- Control 2 segments up to the hips
+ leftEffector_.chainLength = 2
+ rightEffector_.chainLength = 2
+
+ -- For the effectors to work, an IKSolver needs to be attached to one of
+ -- the parent nodes. Typically, you want to place the solver as close as
+ -- possible to the effectors for optimal performance. Since in this case
+ -- we're solving the legs only, we can place the solver at the spine.
+ local spine = jackNode_:GetChild("Bip01_Spine", true)
+ solver_ = spine:CreateComponent("IKSolver")
+
+ -- Two-bone solver is more efficient and more stable than FABRIK (but only
+ -- works for two bones, obviously).
+ solver_.algorithm = IKSolver.TWO_BONE
+
+ -- Disable auto-solving, which means we can call Solve() manually.
+ solver_.AUTO_SOLVE = false
+
+ -- Only enable this so the debug draw shows us the pose before solving.
+ -- This should NOT be enabled for any other reason (it does nothing and is
+ -- a waste of performance).
+ solver_.UPDATE_ORIGINAL_POSE = true
+
+ -- Create the camera.
+ cameraRotateNode_ = scene_:CreateChild("CameraRotate")
+ cameraNode = cameraRotateNode_:CreateChild("Camera")
+ cameraNode:CreateComponent("Camera")
+
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 0.0, -4.0)
+ cameraRotateNode_.position = Vector3(0.0, 0.4, 0.0)
+ pitch = 20.0
+ yaw = 50.0
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Left-Click and drag to look around\nRight-Click and drag to change incline\nPress space to reset floor\nPress D to draw debug geometry")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function UpdateCameraAndFloor(timeStep)
+ -- Do not move if the UI has a focused element (the console)
+ if ui.focusElement ~= nil then
+ return
+ end
+
+ -- Mouse sensitivity as degrees per pixel
+ local MOUSE_SENSITIVITY = 0.1
+
+ -- Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ if input:GetMouseButtonDown(MOUSEB_LEFT) then
+ local mouseMove = input.mouseMove
+ yaw = yaw +MOUSE_SENSITIVITY * mouseMove.x
+ pitch = pitch + MOUSE_SENSITIVITY * mouseMove.y
+ pitch = Clamp(pitch, -90.0, 90.0)
+ end
+
+ if input:GetMouseButtonDown(MOUSEB_RIGHT) then
+ local mouseMoveInt = input.mouseMove
+ local mouseMove = Vector2()
+ mouseMove.x = -Cos(yaw) * mouseMoveInt.y - Sin(yaw) * mouseMoveInt.x
+ mouseMove.y = Sin(yaw) * mouseMoveInt.y - Cos(yaw) * mouseMoveInt.x
+
+ floorPitch_ = floorPitch_ + MOUSE_SENSITIVITY * mouseMove.x
+ floorPitch_ = Clamp(floorPitch_, -90.0, 90.0)
+ floorRoll_ = floorRoll_ + MOUSE_SENSITIVITY * mouseMove.y
+ end
+
+ if input:GetKeyPress(KEY_SPACE) then
+ floorPitch_ = 0.0
+ floorRoll_ = 0.0
+ end
+
+ if input:GetKeyPress(KEY_D) then
+ drawDebug_ = not drawDebug_
+ end
+
+ -- Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraRotateNode_.rotation = Quaternion(pitch, yaw, 0.0)
+ floorNode_.rotation = Quaternion(floorPitch_, 0.0, floorRoll_)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+ SubscribeToEvent("SceneDrawableUpdateFinished", "HandleSceneDrawableUpdateFinished")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Take the frame time step, which is stored as a float
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ -- Move the camera, scale movement with time step
+ UpdateCameraAndFloor(timeStep)
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ if drawDebug_ then
+ solver_:DrawDebugGeometry(false)
+ end
+end
+
+function HandleSceneDrawableUpdateFinished(eventType, eventData)
+ local physicsWorld = scene_:GetComponent("PhysicsWorld")
+ local leftFootPosition = leftFoot_.worldPosition
+ local rightFootPosition = rightFoot_.worldPosition
+
+ -- Cast ray down to get the normal of the underlying surface
+ local result = physicsWorld:RaycastSingle(Ray(leftFootPosition + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2)
+ if result.body then
+ -- Cast again, but this time along the normal. Set the target position
+ -- to the ray intersection
+ local oppositeNormal = result.normal * -1
+ result = physicsWorld:RaycastSingle(Ray(leftFootPosition + result.normal, oppositeNormal), 2)
+ -- The foot node has an offset relative to the root node
+ footOffset = leftFoot_.worldPosition.y - jackNode_.worldPosition.y
+ leftEffector_.targetPosition = result.position + result.normal * footOffset
+ -- Rotate foot according to normal
+ leftFoot_:Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD)
+ end
+
+ -- Same deal with the right foot
+ result = physicsWorld:RaycastSingle(Ray(rightFootPosition + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2)
+ if result.body then
+ local oppositeNormal = result.normal * -1
+ result = physicsWorld:RaycastSingle(Ray(rightFootPosition + result.normal, oppositeNormal), 2)
+ footOffset = rightFoot_.worldPosition.y - jackNode_.worldPosition.y
+ rightEffector_.targetPosition = result.position + result.normal * footOffset
+ rightFoot_:Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD)
+ end
+
+ solver_:Solve()
+end
diff --git a/bin/Data/LuaScripts/46_RaycastVehicleDemo.lua b/bin/Data/LuaScripts/46_RaycastVehicleDemo.lua
new file mode 100644
index 0000000..ffa46f4
--- /dev/null
+++ b/bin/Data/LuaScripts/46_RaycastVehicleDemo.lua
@@ -0,0 +1,440 @@
+-- Vehicle example.
+-- This sample demonstrates:
+-- - Creating a heightmap terrain with collision
+-- - Constructing a physical vehicle with rigid bodies for the hull and the wheels, joined with constraints
+-- - Saving and loading the variables of a script object, including node & component references
+
+require "LuaScripts/Utilities/Sample"
+
+local CTRL_FORWARD = 1
+local CTRL_BACK = 2
+local CTRL_LEFT = 4
+local CTRL_RIGHT = 8
+local CTRL_BRAKE = 16
+
+local CAMERA_DISTANCE = 10.0
+local YAW_SENSITIVITY = 0.1
+local ENGINE_FORCE = 2500.0
+local DOWN_FORCE = 100.0
+local MAX_WHEEL_ANGLE = 22.5
+local CHASSIS_WIDTH = 2.6
+
+local vehicleNode = nil
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create static scene content
+ CreateScene()
+
+ -- Create the controllable vehicle
+ CreateVehicle()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE)
+
+ -- Subscribe to necessary events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create scene subsystem components
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("PhysicsWorld")
+
+ -- Create camera and define viewport. Camera does not necessarily have to belong to the scene
+ cameraNode = Node()
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.farClip = 500.0
+
+ renderer:SetViewport(0, Viewport:new(scene_, camera))
+
+ -- Create static scene content. First create a zone for ambient lighting and fog control
+ local zoneNode = scene_:CreateChild("Zone")
+ local zone = zoneNode:CreateComponent("Zone")
+ zone.ambientColor = Color(0.15, 0.15, 0.15)
+ zone.fogColor = Color(0.5, 0.5, 0.7)
+ zone.fogStart = 300.0
+ zone.fogEnd = 500.0
+ zone.boundingBox = BoundingBox(-2000.0, 2000.0)
+
+ -- Create a directional light to the world. Enable cascaded shadows on it
+ local lightNode = scene_:CreateChild("DirectionalLight")
+ lightNode.direction = Vector3(0.3, -0.5, 0.425)
+ local light = lightNode:CreateComponent("Light")
+ light.lightType = LIGHT_DIRECTIONAL
+ light.castShadows = true
+ light.shadowBias = BiasParameters(0.00025, 0.5)
+ light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
+ light.specularIntensity = 0.5
+
+ -- Create heightmap terrain with collision
+ local terrainNode = scene_:CreateChild("Terrain")
+ terrainNode.position = Vector3(0.0, 0.0, 0.0)
+ local terrain = terrainNode:CreateComponent("Terrain")
+ terrain.patchSize = 64
+ terrain.spacing = Vector3(2.0, 0.1, 2.0) -- Spacing between vertices and vertical resolution of the height map
+ terrain.smoothing = true
+ terrain.heightMap = cache:GetResource("Image", "Textures/HeightMap.png")
+ terrain.material = cache:GetResource("Material", "Materials/Terrain.xml")
+ -- The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
+ -- terrain patches and other objects behind it
+ terrain.occluder = true
+
+ local body = terrainNode:CreateComponent("RigidBody")
+ body.collisionLayer = 2 -- Use layer bitmask 2 for static geometry
+ local shape = terrainNode:CreateComponent("CollisionShape")
+ shape:SetTerrain()
+
+ -- Create 1000 mushrooms in the terrain. Always face outward along the terrain normal
+ local NUM_MUSHROOMS = 1000
+ for i = 1, NUM_MUSHROOMS do
+ local objectNode = scene_:CreateChild("Mushroom")
+ local position = Vector3(Random(2000.0) - 1000.0, 0.0, Random(2000.0) - 1000.0)
+ position.y = terrain:GetHeight(position) - 0.1
+ objectNode.position = position
+ -- Create a rotation quaternion from up vector to terrain normal
+ objectNode.rotation = Quaternion(Vector3(0.0, 1.0, 0.0), terrain:GetNormal(position))
+ objectNode:SetScale(3.0)
+ local object = objectNode:CreateComponent("StaticModel")
+ object.model = cache:GetResource("Model", "Models/Mushroom.mdl")
+ object.material = cache:GetResource("Material", "Materials/Mushroom.xml")
+ object.castShadows = true
+
+ local body = objectNode:CreateComponent("RigidBody")
+ body.collisionLayer = 2
+ local shape = objectNode:CreateComponent("CollisionShape")
+ shape:SetTriangleMesh(object.model, 0)
+ end
+end
+
+function CreateVehicle()
+ vehicleNode = scene_:CreateChild("Vehicle")
+ vehicleNode.position = Vector3(0.0, 5.0, 0.0)
+
+ -- Create the vehicle logic script object
+ local vehicle = vehicleNode:CreateScriptObject("Vehicle")
+ -- Create the rendering and physics components
+ vehicle:Init()
+ local hullBody = vehicleNode:GetComponent("RigidBody")
+ hullBody.mass = 800.0
+ hullBody.linearDamping = 0.2
+ hullBody.angularDamping = 0.5
+ hullBody.collisionLayer = 1
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText.text = "Use WASD keys to drive, mouse/touch to rotate camera\n"..
+ "F5 to save scene, F7 to load"
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+ -- The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SubscribeToEvents()
+ -- Subscribe to Update event for setting the vehicle controls before physics simulation
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe to PostUpdate event for updating the camera position after physics simulation
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate")
+
+ -- Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ if vehicleNode == nil then
+ return
+ end
+
+ local vehicle = vehicleNode:GetScriptObject()
+ if vehicle == nil then
+ return
+ end
+
+ -- Get movement controls and assign them to the vehicle component. If UI has a focused element, clear controls
+ if ui.focusElement == nil then
+ vehicle.controls:Set(CTRL_FORWARD, input:GetKeyDown(KEY_W))
+ vehicle.controls:Set(CTRL_BACK, input:GetKeyDown(KEY_S))
+ vehicle.controls:Set(CTRL_LEFT, input:GetKeyDown(KEY_A))
+ vehicle.controls:Set(CTRL_RIGHT, input:GetKeyDown(KEY_D))
+ vehicle.controls:Set(CTRL_BRAKE, input:GetKeyDown(KEY_F))
+
+ -- Add yaw & pitch from the mouse motion or touch input. Used only for the camera, does not affect motion
+ if touchEnabled then
+ for i=0, input.numTouches - 1 do
+ local state = input:GetTouch(i)
+ if not state.touchedElement then -- Touch on empty space
+ local camera = cameraNode:GetComponent("Camera")
+ if not camera then return end
+
+ vehicle.controls.yaw = vehicle.controls.yaw + TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.x
+ vehicle.controls.pitch = vehicle.controls.pitch + TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.y
+ end
+ end
+ else
+ vehicle.controls.yaw = vehicle.controls.yaw + input.mouseMoveX * YAW_SENSITIVITY
+ vehicle.controls.pitch = vehicle.controls.pitch + input.mouseMoveY * YAW_SENSITIVITY
+ end
+ -- Limit pitch
+ vehicle.controls.pitch = Clamp(vehicle.controls.pitch, 0.0, 80.0)
+
+ -- Check for loading / saving the scene
+ if input:GetKeyPress(KEY_F5) then
+ scene_:SaveXML(fileSystem:GetProgramDir() .. "Data/Scenes/VehicleDemo.xml")
+ end
+ if input:GetKeyPress(KEY_F7) then
+ scene_:LoadXML(fileSystem:GetProgramDir() .. "Data/Scenes/VehicleDemo.xml")
+ vehicleNode = scene_:GetChild("Vehicle", true)
+ vehicleNode:GetScriptObject():PostInit()
+ end
+ else
+ vehicle.controls:Set(CTRL_FORWARD + CTRL_BACK + CTRL_LEFT + CTRL_RIGHT, false)
+ end
+end
+
+function HandlePostUpdate(eventType, eventData)
+ if vehicleNode == nil then
+ return
+ end
+
+ local vehicle = vehicleNode:GetScriptObject()
+ if vehicle == nil then
+ return
+ end
+
+ -- Physics update has completed. Position camera behind vehicle
+ local dir = Quaternion(vehicleNode.rotation:YawAngle(), Vector3(0.0, 1.0, 0.0))
+ dir = dir * Quaternion(vehicle.controls.yaw, Vector3(0.0, 1.0, 0.0))
+ dir = dir * Quaternion(vehicle.controls.pitch, Vector3(1.0, 0.0, 0.0))
+
+ local cameraTargetPos = vehicleNode.position - dir * Vector3(0.0, 0.0, CAMERA_DISTANCE)
+ local cameraStartPos = vehicleNode.position
+ -- Raycast camera against static objects (physics collision mask 2)
+ -- and move it closer to the vehicle if something in between
+ local cameraRay = Ray(cameraStartPos, (cameraTargetPos - cameraStartPos):Normalized())
+ local cameraRayLength = (cameraTargetPos - cameraStartPos):Length()
+ local physicsWorld = scene_:GetComponent("PhysicsWorld")
+ local result = physicsWorld:RaycastSingle(cameraRay, cameraRayLength, 2)
+ if result.body ~= nil then
+ cameraTargetPos = cameraStartPos + cameraRay.direction * (result.distance - 0.5)
+ end
+ cameraNode.position = cameraTargetPos
+ cameraNode.rotation = dir
+end
+
+-- Vehicle script object class
+--
+-- When saving, the node and component handles are automatically converted into nodeID or componentID attributes
+-- and are acquired from the scene when loading. The steering member variable will likewise be saved automatically.
+-- The Controls object can not be automatically saved, so handle it manually in the Load() and Save() methods
+
+Vehicle = ScriptObject()
+
+function Vehicle:Start()
+ -- Current left/right steering amount (-1 to 1.)
+ self.steering = 0.0
+ -- Vehicle controls.
+ self.controls = Controls()
+ self.suspensionRestLength = 0.6
+ self.suspensionStiffness = 14.0
+ self.suspensionDamping = 2.0
+ self.suspensionCompression = 4.0
+ self.wheelFriction = 1000.0
+ self.rollInfluence = 0.12
+ self.maxEngineForce = ENGINE_FORCE
+ self.wheelWidth = 0.4
+ self.wheelRadius = 0.5
+ self.brakingForce = 50.0
+ self.connectionPoints = {}
+ self.particleEmitterNodeList = {}
+ self.prevVelocity = Vector3()
+end
+
+function Vehicle:Load(deserializer)
+ self.controls.yaw = deserializer:ReadFloat()
+ self.controls.pitch = deserializer:ReadFloat()
+end
+
+function Vehicle:Save(serializer)
+ serializer:WriteFloat(self.controls.yaw)
+ serializer:WriteFloat(self.controls.pitch)
+end
+
+function Vehicle:Init()
+ -- This function is called only from the main program when initially creating the vehicle, not on scene load
+ local node = self.node
+ local hullObject = node:CreateComponent("StaticModel")
+ self.hullBody = node:CreateComponent("RigidBody")
+ local hullShape = node:CreateComponent("CollisionShape")
+
+ node.scale = Vector3(2.3, 1.0, 4.0)
+ hullObject.model = cache:GetResource("Model", "Models/Box.mdl")
+ hullObject.material = cache:GetResource("Material", "Materials/Stone.xml")
+ hullObject.castShadows = true
+ hullShape:SetBox(Vector3(1.0, 1.0, 1.0))
+
+ self.hullBody.mass = 800.0
+ self.hullBody.linearDamping = 0.2 -- Some air resistance
+ self.hullBody.angularDamping = 0.5
+ self.hullBody.collisionLayer = 1
+ local raycastVehicle = node:CreateComponent("RaycastVehicle")
+ raycastVehicle:Init()
+ local connectionHeight = -0.4
+ local isFrontWheel = true
+ local wheelDirection = Vector3(0, -1, 0)
+ local wheelAxle = Vector3(-1, 0, 0)
+ local wheelX = CHASSIS_WIDTH / 2.0 - self.wheelWidth
+ -- Front left
+ table.insert(self.connectionPoints, Vector3(-wheelX, connectionHeight, 2.5 - self.wheelRadius * 2.0))
+ -- Front right
+ table.insert(self.connectionPoints, Vector3(wheelX, connectionHeight, 2.5 - self.wheelRadius * 2.0))
+ -- Back left
+ table.insert(self.connectionPoints, Vector3(-wheelX, connectionHeight, -2.5 + self.wheelRadius * 2.0))
+ -- Back right
+ table.insert(self.connectionPoints, Vector3(wheelX, connectionHeight, -2.5 + self.wheelRadius * 2.0))
+
+ local LtBrown = Color(0.972, 0.780, 0.412)
+ for i = 1, #self.connectionPoints do
+ local wheelNode = scene_:CreateChild()
+ local connectionPoint = self.connectionPoints[i]
+ -- Front wheels are at front (z > 0)
+ -- Back wheels are at z < 0
+ -- Setting rotation according to wheel position
+ local isFrontWheel = connectionPoint.z > 0.0
+ if connectionPoint.x >= 0.0 then
+ wheelNode.rotation = Quaternion(0.0, 0.0, -90.0)
+ else
+ wheelNode.rotation = Quaternion(0.0, 0.0, 90.0)
+ end
+ wheelNode.worldPosition = node.worldPosition + node.worldRotation * connectionPoint
+ wheelNode.scale = Vector3(1.0, 0.65, 1.0)
+ raycastVehicle:AddWheel(wheelNode, wheelDirection, wheelAxle, self.suspensionRestLength, self.wheelRadius, isFrontWheel)
+ raycastVehicle:SetWheelSuspensionStiffness(i - 1, self.suspensionStiffness)
+ raycastVehicle:SetWheelDampingRelaxation(i - 1, self.suspensionDamping)
+ raycastVehicle:SetWheelDampingCompression(i - 1, self.suspensionCompression)
+ raycastVehicle:SetWheelRollInfluence(i - 1, self.rollInfluence)
+ local pWheel = wheelNode:CreateComponent("StaticModel")
+ pWheel.model = cache:GetResource("Model", "Models/Cylinder.mdl")
+ pWheel.material = cache:GetResource("Material", "Materials/Stone.xml")
+ pWheel.castShadows = true
+ end
+
+ self:PostInit()
+end
+
+function Vehicle:CreateEmitter(place)
+ local emitter = scene_:CreateChild()
+ local node = self.node
+ emitter.worldPosition = node.worldPosition + node.worldRotation * place + Vector3(0, -self.wheelRadius, 0)
+ local particleEmitter = emitter:CreateComponent("ParticleEmitter")
+ particleEmitter.effect = cache:GetResource("ParticleEffect", "Particle/Dust.xml")
+ particleEmitter.emitting = false
+ emitter.temporary = true
+ table.insert(self.particleEmitterNodeList, emitter)
+end
+
+function Vehicle:CreateEmitters()
+ self.particleEmitterNodeList = {}
+ local node = self.node
+ local raycastVehicle = node:GetComponent("RaycastVehicle")
+ for id = 0, raycastVehicle:GetNumWheels() do
+ local connectionPoint = raycastVehicle:GetWheelConnectionPoint(id)
+ self:CreateEmitter(connectionPoint)
+ end
+end
+
+function Vehicle:PostInit()
+ local node = self.node
+ local raycastVehicle = node:GetComponent("RaycastVehicle")
+ self.hullBody = node:GetComponent("RigidBody")
+ self:CreateEmitters()
+ raycastVehicle:ResetWheels()
+end
+
+function Vehicle:FixedUpdate(timeStep)
+ local node = self.node
+ local newSteering = 0.0
+ local accelerator = 0.0
+ local brake = false
+
+ if self.controls:IsDown(CTRL_LEFT) then
+ newSteering = -1.0
+ end
+ if self.controls:IsDown(CTRL_RIGHT) then
+ newSteering = 1.0
+ end
+ if self.controls:IsDown(CTRL_FORWARD) then
+ accelerator = 1.0
+ end
+ if self.controls:IsDown(CTRL_BACK) then
+ accelerator = -0.5
+ end
+
+ if self.controls:IsDown(CTRL_BRAKE) then
+ brake = true
+ end
+
+ -- When steering, wake up the wheel rigidbodies so that their orientation is updated
+ if newSteering ~= 0.0 then
+ self.steering = self.steering * 0.95 + newSteering * 0.05
+ else
+ self.steering = self.steering * 0.8 + newSteering * 0.2
+ end
+
+ local steeringRot = Quaternion(0.0, self.steering * MAX_WHEEL_ANGLE, 0.0)
+ local raycastVehicle = node:GetComponent("RaycastVehicle")
+ raycastVehicle:SetSteeringValue(0, self.steering)
+ raycastVehicle:SetSteeringValue(1, self.steering)
+ raycastVehicle:SetEngineForce(2, self.maxEngineForce * accelerator)
+ raycastVehicle:SetEngineForce(3, self.maxEngineForce * accelerator)
+ for i = 0, raycastVehicle:GetNumWheels() - 1 do
+ if brake then
+ raycastVehicle:SetBrake(i, self.brakingForce)
+ else
+ raycastVehicle:SetBrake(i, 0.0)
+ end
+ end
+
+ -- Apply downforce proportional to velocity
+ local localVelocity = self.hullBody.rotation:Inverse() * self.hullBody.linearVelocity
+ self.hullBody:ApplyForce(self.hullBody.rotation * Vector3(0.0, -1.0, 0.0) * Abs(localVelocity.z) * DOWN_FORCE)
+end
+
+function Vehicle:PostUpdate(timeStep)
+ local node = self.node
+ local raycastVehicle = node:GetComponent("RaycastVehicle")
+ if #self.particleEmitterNodeList == 0 then
+ return
+ end
+ local velocity = self.hullBody.linearVelocity
+ local accel = (velocity - self.prevVelocity) / timeStep
+ local planeAccel = Vector3(accel.x, 0.0, accel.z):Length()
+ for i = 0, raycastVehicle:GetNumWheels() - 1 do
+ local emitter = self.particleEmitterNodeList[i + 1]
+ local particleEmitter = emitter:GetComponent("ParticleEmitter")
+ if raycastVehicle:WheelIsGrounded(i) and (raycastVehicle:GetWheelSkidInfoCumulative(i) < 0.9 or raycastVehicle:GetBrake(i) > 2.0 or planeAccel > 15.0) then
+ emitter.worldPosition = raycastVehicle:GetContactPosition(i)
+ if not particleEmitter.emitting then
+ particleEmitter.emitting = true
+ end
+ else if particleEmitter.emitting then
+ particleEmitter.emitting = false
+ end
+ end
+ end
+ self.prevVelocity = velocity
+end
diff --git a/bin/Data/LuaScripts/47_Typography.lua b/bin/Data/LuaScripts/47_Typography.lua
new file mode 100644
index 0000000..021c04f
--- /dev/null
+++ b/bin/Data/LuaScripts/47_Typography.lua
@@ -0,0 +1,221 @@
+-- Text rendering example.
+-- Displays text at various sizes, with checkboxes to change the rendering parameters.
+
+require "LuaScripts/Utilities/Sample"
+
+-- Tag used to find all Text elements
+local TEXT_TAG = "Typography_text_tag"
+
+-- Top-level container for this sample's UI
+local uielement = nil
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Enable OS cursor
+ input.mouseVisible = true
+
+ -- Load XML file containing default UI style sheet
+ local style = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+
+ -- Set the loaded style as default style
+ ui.root.defaultStyle = style
+
+ -- Create a UIElement to hold all our content
+ -- (Don't modify the root directly, as the base Sample class uses it)
+ uielement = UIElement:new()
+ uielement:SetAlignment(HA_CENTER, VA_CENTER)
+ uielement:SetLayout(LM_VERTICAL, 10, IntRect(20, 40, 20, 40))
+ ui.root:AddChild(uielement)
+
+ -- Add some sample text
+ CreateText()
+
+ -- Add a checkbox to toggle the background color.
+ CreateCheckbox("White background", "HandleWhiteBackground")
+ :SetChecked(false)
+
+ -- Add a checkbox to toggle SRGB output conversion (if available).
+ -- This will give more correct text output for FreeType fonts, as the FreeType rasterizer
+ -- outputs linear coverage values rather than SRGB values. However, this feature isn't
+ -- available on all platforms.
+ CreateCheckbox("Graphics::SetSRGB", "HandleSRGB")
+ :SetChecked(graphics:GetSRGB())
+
+ -- Add a checkbox for the global ForceAutoHint setting. This affects character spacing.
+ CreateCheckbox("UI::SetForceAutoHint", "HandleForceAutoHint")
+ :SetChecked(ui:GetForceAutoHint())
+
+ -- Add a drop-down menu to control the font hinting level.
+ local levels = {
+ "FONT_HINT_LEVEL_NONE",
+ "FONT_HINT_LEVEL_LIGHT",
+ "FONT_HINT_LEVEL_NORMAL"
+ }
+ CreateMenu("UI::SetFontHintLevel", levels, "HandleFontHintLevel")
+ :SetSelection(ui:GetFontHintLevel())
+
+ -- Add a drop-down menu to control the subpixel threshold.
+ local thresholds = {
+ "0",
+ "3",
+ "6",
+ "9",
+ "12",
+ "15",
+ "18",
+ "21"
+ }
+ CreateMenu("UI::SetFontSubpixelThreshold", thresholds, "HandleFontSubpixel")
+ :SetSelection(ui:GetFontSubpixelThreshold() / 3)
+
+ -- Add a drop-down menu to control oversampling.
+ local limits = {
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8"
+ }
+ CreateMenu("UI::SetFontOversampling", limits, "HandleFontOversampling")
+ :SetSelection(ui:GetFontOversampling() - 1)
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+end
+
+function CreateText()
+ local container = UIElement:new()
+ container:SetAlignment(HA_LEFT, VA_TOP)
+ container:SetLayout(LM_VERTICAL)
+ uielement:AddChild(container)
+
+ local font = cache:GetResource("Font", "Fonts/BlueHighway.ttf")
+
+ for size = 1, 18, 0.5 do
+ local text = Text:new()
+ text.text = "The quick brown fox jumps over the lazy dog (" .. size .. "pt)"
+ text:SetFont(font, size)
+ text:AddTag(TEXT_TAG)
+ container:AddChild(text)
+ end
+end
+
+function CreateCheckbox(label, handler)
+ local container = UIElement:new()
+ container:SetAlignment(HA_LEFT, VA_TOP)
+ container:SetLayout(LM_HORIZONTAL, 8)
+ uielement:AddChild(container)
+
+ local box = CheckBox:new()
+ container:AddChild(box)
+ box:SetStyleAuto()
+
+ local text = Text:new()
+ container:AddChild(text)
+ text.text = label
+ text:SetStyleAuto()
+ text:AddTag(TEXT_TAG)
+
+ SubscribeToEvent(box, "Toggled", handler)
+ return box
+end
+
+function CreateMenu(label, items, handler)
+ local container = UIElement:new()
+ container:SetAlignment(HA_LEFT, VA_TOP)
+ container:SetLayout(LM_HORIZONTAL, 8)
+ uielement:AddChild(container)
+
+ local text = Text:new()
+ container:AddChild(text)
+ text.text = label
+ text:SetStyleAuto()
+ text:AddTag(TEXT_TAG)
+
+ local list = DropDownList:new()
+ container:AddChild(list)
+ list:SetStyleAuto()
+
+ for i, item in ipairs(items) do
+ local t = Text:new()
+ list:AddItem(t)
+ t.text = item
+ t:SetStyleAuto()
+ t:SetMinWidth(t:GetRowWidth(0) + 10)
+ t:AddTag(TEXT_TAG)
+ end
+
+ text:SetMaxWidth(text:GetRowWidth(0))
+
+ SubscribeToEvent(list, "ItemSelected", handler)
+ return list
+end
+
+function HandleWhiteBackground(eventType, eventData)
+ local box = eventData["Element"]:GetPtr("CheckBox")
+ local checked = box:IsChecked()
+
+ local fg = checked and Color.BLACK or Color.WHITE
+ local bg = checked and Color.WHITE or Color.BLACK
+
+ renderer.defaultZone.fogColor = bg
+
+ local elements = uielement:GetChildrenWithTag(TEXT_TAG, true)
+ for i, element in ipairs(elements) do
+ element.color = fg
+ end
+end
+
+function HandleForceAutoHint(eventType, eventData)
+ local box = eventData["Element"]:GetPtr("CheckBox")
+ local checked = box:IsChecked()
+
+ ui:SetForceAutoHint(checked)
+end
+
+function HandleSRGB(eventType, eventData)
+ local box = eventData["Element"]:GetPtr("CheckBox")
+ local checked = box:IsChecked()
+
+ if graphics:GetSRGBWriteSupport() then
+ graphics:SetSRGB(checked)
+ else
+ log:Write(LOG_WARNING, "graphics:GetSRGBWriteSupport returned false")
+ end
+end
+
+function HandleFontHintLevel(eventType, eventData)
+ local list = eventData["Element"]:GetPtr("DropDownList")
+ local i = list:GetSelection()
+
+ ui:SetFontHintLevel(i)
+end
+
+function HandleFontSubpixel(eventType, eventData)
+ local list = eventData["Element"]:GetPtr("DropDownList")
+ local i = list:GetSelection()
+
+ ui:SetFontSubpixelThreshold(i * 3)
+end
+
+function HandleFontOversampling(eventType, eventData)
+ local list = eventData["Element"]:GetPtr("DropDownList")
+ local i = list:GetSelection()
+
+ ui:SetFontOversampling(i + 1)
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/48_Hello3DUI.lua b/bin/Data/LuaScripts/48_Hello3DUI.lua
new file mode 100644
index 0000000..08b8fb7
--- /dev/null
+++ b/bin/Data/LuaScripts/48_Hello3DUI.lua
@@ -0,0 +1,293 @@
+-- A 3D UI demonstration based on the HelloGUI sample. Renders UI alternatively
+-- either to a 3D scene object using UIComponent, or directly to the backbuffer.
+
+require "LuaScripts/Utilities/Sample"
+
+local window = nil
+local dragBeginPosition = IntVector2(0, 0)
+local textureRoot = nil
+local current = nil
+local renderOnCube = false
+local drawDebug = false
+local animateCube = true
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Enable OS cursor
+ input.mouseVisible = true
+
+ -- Load XML file containing default UI style sheet
+ local style = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+
+ -- Set the loaded style as default style
+ ui.root.defaultStyle = style
+
+ -- Initialize Scene
+ InitScene()
+
+ -- Initialize Window
+ InitWindow()
+
+ -- Create and add some controls to the Window
+ InitControls()
+
+ -- Create a draggable Fish
+ CreateDraggableFish()
+
+ -- Create 3D UI rendered on a cube.
+ Init3DUI()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+end
+
+function InitControls()
+ -- Create a CheckBox
+ local checkBox = CheckBox:new()
+ checkBox:SetName("CheckBox")
+
+ -- Create a Button
+ local button = Button:new()
+ button:SetName("Button")
+ button.minHeight = 24
+
+ -- Create a LineEdit
+ local lineEdit = LineEdit:new()
+ lineEdit:SetName("LineEdit")
+ lineEdit.minHeight = 24
+
+ -- Add controls to Window
+ window:AddChild(checkBox)
+ window:AddChild(button)
+ window:AddChild(lineEdit)
+
+ -- Apply previously set default style
+ checkBox:SetStyleAuto()
+ button:SetStyleAuto()
+ lineEdit:SetStyleAuto()
+
+ local instructions = Text:new()
+ instructions:SetStyleAuto()
+ instructions.text = "[TAB] - toggle between rendering on screen or cube.\n"..
+ "[Space] - toggle cube rotation."
+ ui.root:AddChild(instructions)
+end
+
+function InitScene()
+ scene_ = Scene()
+
+ scene_:CreateComponent("Octree")
+ local zone = scene_:CreateComponent("Zone")
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0)
+ zone.fogColor = Color(0.5, 0.5, 0.5)
+ zone.fogStart = 100.0
+ zone.fogEnd = 300.0
+
+ -- Create a child scene node (at world origin) and a StaticModel component into it.
+ local boxNode = scene_:CreateChild("Box")
+ boxNode.scale = Vector3(5.0, 5.0, 5.0)
+ boxNode.rotation = Quaternion(90, Vector3(1, 0, 0))
+
+ -- Create a box model and hide it initially.
+ local boxModel = boxNode:CreateComponent("StaticModel")
+ boxModel.model = cache:GetResource("Model", "Models/Box.mdl")
+ boxNode.enabled = false
+
+ -- Create a camera.
+ cameraNode = scene_:CreateChild("Camera")
+ cameraNode:CreateComponent("Camera")
+
+ -- Set an initial position for the camera scene node.
+ cameraNode.position = Vector3(0.0, 0.0, -10.0)
+
+ -- Set up a viewport so 3D scene can be visible.
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+
+ -- Subscribe to update event and animate cube and handle input.
+ SubscribeToEvent("Update", "HandleUpdate")
+end
+
+function InitWindow()
+ -- Create the Window and add it to the UI's root node
+ window = Window:new()
+ ui.root:AddChild(window)
+
+ -- Set Window size and layout settings
+ window.minWidth = 384
+ window:SetLayout(LM_VERTICAL, 6, IntRect(6, 6, 6, 6))
+ window:SetAlignment(HA_CENTER, VA_CENTER)
+ window:SetName("Window")
+
+ -- Create Window 'titlebar' container
+ local titleBar = UIElement:new()
+ titleBar:SetMinSize(0, 24)
+ titleBar.verticalAlignment = VA_TOP
+ titleBar.layoutMode = LM_HORIZONTAL
+
+ -- Create the Window title Text
+ local windowTitle = Text:new()
+ windowTitle.name = "WindowTitle"
+ windowTitle.text = "Hello GUI!"
+
+ -- Create the Window's close button
+ local buttonClose = Button:new()
+ buttonClose:SetName("CloseButton")
+
+ -- Add the controls to the title bar
+ titleBar:AddChild(windowTitle)
+ titleBar:AddChild(buttonClose)
+
+ -- Add the title bar to the Window
+ window:AddChild(titleBar)
+
+ -- Create a list.
+ local list = window:CreateChild("ListView")
+ list.selectOnClickEnd = true
+ list.highlightMode = HM_ALWAYS
+ list.minHeight = 200
+
+ for i = 0, 31 do
+ local text = Text:new()
+ text:SetStyleAuto()
+ text.text = "List item " .. i
+ text.name = "Item " .. i
+ list:AddItem(text)
+ end
+
+ -- Apply styles
+ window:SetStyleAuto()
+ list:SetStyleAuto()
+ windowTitle:SetStyleAuto()
+ buttonClose:SetStyle("CloseButton")
+
+ -- Subscribe to buttonClose release (following a 'press') events
+ SubscribeToEvent(buttonClose, "Released",
+ function (eventType, eventData)
+ engine:Exit()
+ end)
+
+ -- Subscribe also to all UI mouse clicks just to see where we have clicked
+ SubscribeToEvent("UIMouseClick", HandleControlClicked)
+end
+
+function CreateDraggableFish()
+ -- Create a draggable Fish button
+ local draggableFish = ui.root:CreateChild("Button", "Fish")
+ draggableFish.texture = cache:GetResource("Texture2D", "Textures/UrhoDecal.dds") -- Set texture
+ draggableFish.blendMode = BLEND_ADD
+ draggableFish:SetSize(128, 128)
+ draggableFish:SetPosition((GetGraphics().width - draggableFish.width) / 2, 200)
+
+ -- Add a tooltip to Fish button
+ local toolTip = draggableFish:CreateChild("ToolTip")
+ toolTip.position = IntVector2(draggableFish.width + 5, draggableFish.width/2) -- Slightly offset from fish
+ local textHolder = toolTip:CreateChild("BorderImage")
+ textHolder:SetStyle("ToolTipBorderImage")
+ local toolTipText = textHolder:CreateChild("Text")
+ toolTipText:SetStyle("ToolTipText")
+ toolTipText.text = "Please drag me!"
+
+ -- Subscribe draggableFish to Drag Events (in order to make it draggable)
+ -- See "Event list" in documentation's Main Page for reference on available Events and their eventData
+ SubscribeToEvent(draggableFish, "DragBegin",
+ function (eventType, eventData)
+ -- Get UIElement relative position where input (touch or click) occurred (top-left = IntVector2(0,0))
+ dragBeginPosition = IntVector2(eventData["ElementX"]:GetInt(), eventData["ElementY"]:GetInt())
+ end)
+
+ SubscribeToEvent(draggableFish, "DragMove",
+ function (eventType, eventData)
+ local dragCurrentPosition = IntVector2(eventData["X"]:GetInt(), eventData["Y"]:GetInt())
+ -- Get the dragged fish element
+ -- Note difference to C++: in C++ we would call GetPtr() and cast the pointer to UIElement, here we must specify
+ -- what kind of object we are getting. Null will be returned on type mismatch
+ local draggedElement = eventData["Element"]:GetPtr("UIElement")
+ draggedElement:SetPosition(dragCurrentPosition - dragBeginPosition)
+ end)
+
+ SubscribeToEvent(draggableFish, "DragEnd",
+ function (eventType, eventData)
+ end)
+end
+
+function HandleControlClicked(eventType, eventData)
+ -- Get the Text control acting as the Window's title
+ local element = window:GetChild("WindowTitle", true)
+ local windowTitle = tolua.cast(element, 'Text')
+
+ -- Get control that was clicked
+ local clicked = eventData["Element"]:GetPtr("UIElement")
+ local name = "...?"
+ if clicked ~= nil then
+ -- Get the name of the control that was clicked
+ name = clicked.name
+ end
+
+ -- Update the Window's title text
+ windowTitle.text = "Hello " .. name .. "!"
+end
+
+function Init3DUI()
+ -- Node that will get UI rendered on it.
+ local boxNode = scene_:GetChild("Box")
+ -- Create a component that sets up UI rendering. It sets material to StaticModel of the node.
+ local component = boxNode:CreateComponent("UIComponent")
+ -- Optionally modify material. Technique is changed so object is visible without any lights.
+ component.material:SetTechnique(0, cache:GetResource("Technique", "Techniques/DiffUnlit.xml"))
+ -- Save root element of texture UI for later use.
+ textureRoot = component.root
+ -- Set size of root element. This is size of texture as well.
+ textureRoot:SetSize(512, 512)
+end
+
+function HandleUpdate(eventType, eventData)
+ local timeStep = eventData["TimeStep"]:GetFloat()
+ local node = scene_:GetChild("Box")
+
+ if current ~= nil and drawDebug then
+ ui:DebugDraw(current)
+ end
+
+ if input:GetMouseButtonPress(MOUSEB_LEFT) then
+ current = ui:GetElementAt(input.mousePosition)
+ end
+
+ if input:GetKeyPress(KEY_TAB) then
+ renderOnCube = not renderOnCube
+ -- Toggle between rendering on screen or to texture.
+ if renderOnCube then
+ node.enabled = true
+ textureRoot:AddChild(window)
+ else
+ node.enabled = false
+ ui.root:AddChild(window)
+ end
+ end
+
+ if input:GetKeyPress(KEY_SPACE) then
+ animateCube = not animateCube
+ end
+
+ if input:GetKeyPress(KEY_F2) then
+ drawDebug = not drawDebug
+ end
+
+ if animateCube then
+ node:Yaw(6.0 * timeStep * 1.5)
+ node:Roll(-6.0 * timeStep * 1.5)
+ node:Pitch(-6.0 * timeStep * 1.5)
+ end
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/49_Urho2DIsometricDemo.lua b/bin/Data/LuaScripts/49_Urho2DIsometricDemo.lua
new file mode 100644
index 0000000..ff97dc8
--- /dev/null
+++ b/bin/Data/LuaScripts/49_Urho2DIsometricDemo.lua
@@ -0,0 +1,183 @@
+-- Urho2D tile map example.
+-- This sample demonstrates:
+-- - Creating an isometric 2D scene with tile map
+-- - Displaying the scene using the Renderer subsystem
+-- - Handling keyboard to move a character and zoom 2D camera
+-- - Generating physics shapes from the tmx file's objects
+-- - Displaying debug geometry for physics and tile map
+-- Note that this sample uses some functions from Sample2D utility class.
+
+require "LuaScripts/Utilities/Sample"
+require "LuaScripts/Utilities/2D/Sample2D"
+
+
+function Start()
+ -- Set filename for load/save functions
+ demoFilename = "Isometric2D"
+
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateUIContent("ISOMETRIC 2.5D DEMO")
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+ local physicsWorld = scene_:CreateComponent("PhysicsWorld2D")
+ physicsWorld.gravity = Vector2.ZERO -- Neutralize gravity as the character will always be grounded
+
+ -- Create camera
+ cameraNode = Node()
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.orthographic = true
+ camera.orthoSize = graphics.height * PIXEL_SIZE
+ zoom = 2 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (2) is set for full visibility at 1280x800 resolution)
+ camera.zoom = zoom
+
+ -- Setup the viewport for displaying the scene
+ renderer:SetViewport(0, Viewport:new(scene_, camera))
+ renderer.defaultZone.fogColor = Color(0.2, 0.2, 0.2) -- Set background color for the scene
+
+ -- Create tile map from tmx file
+ local tmxFile = cache:GetResource("TmxFile2D", "Urho2D/Tilesets/atrium.tmx")
+ local tileMapNode = scene_:CreateChild("TileMap")
+ local tileMap = tileMapNode:CreateComponent("TileMap2D")
+ tileMap.tmxFile = tmxFile
+ local info = tileMap.info
+
+ -- Create Spriter Imp character (from sample 33_SpriterAnimation)
+ CreateCharacter(info, true, 0, Vector3(-5, 11, 0), 0.15)
+
+ -- Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
+ local tileMapLayer = tileMap:GetLayer(tileMap.numLayers - 1)
+ CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info)
+
+ -- Instantiate enemies and moving platforms at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
+ PopulateMovingEntities(tileMap:GetLayer(tileMap.numLayers - 2))
+
+ -- Instantiate coins to pick at each placeholder of "Coins" layer (placeholders for coins are Rectangle objects)
+ PopulateCoins(tileMap:GetLayer(tileMap.numLayers - 3))
+
+ -- Check when scene is rendered
+ SubscribeToEvent("EndRendering", HandleSceneRendered)
+end
+
+function HandleSceneRendered()
+ UnsubscribeFromEvent("EndRendering")
+ SaveScene(true) -- Save the scene so we can reload it later
+ scene_.updateEnabled = false -- Pause the scene as long as the UI is hiding it
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostUpdate() function for processing post update events
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate")
+
+ -- Subscribe to PostRenderUpdate to draw physics shapes
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+
+ -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Zoom in/out
+ Zoom(cameraNode:GetComponent("Camera"))
+
+ -- Toggle debug geometry with spacebar
+ if input:GetKeyPress(KEY_Z) then drawDebug = not drawDebug end
+
+ -- Check for loading / saving the scene
+ if input:GetKeyPress(KEY_F5) then
+ SaveScene()
+ end
+ if input:GetKeyPress(KEY_F7) then
+ ReloadScene(false)
+ end
+end
+
+function HandlePostUpdate(eventType, eventData)
+ if character2DNode == nil or cameraNode == nil then
+ return
+ end
+ cameraNode.position = Vector3(character2DNode.position.x, character2DNode.position.y, -10) -- Camera tracks character
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ if drawDebug then
+ scene_:GetComponent("PhysicsWorld2D"):DrawDebugGeometry(true)
+ end
+end
+
+-- Character2D script object class
+Character2D = ScriptObject()
+
+function Character2D:Start()
+ self.wounded = false
+ self.killed = false
+ self.timer = 0
+ self.maxCoins = 0
+ self.remainingCoins = 0
+ self.remainingLifes = 3
+end
+
+function Character2D:Update(timeStep)
+ local node = self.node
+ local animatedSprite = node:GetComponent("AnimatedSprite2D")
+
+ -- Set direction
+ local moveDir = Vector3.ZERO -- Reset
+ local speedX = Clamp(MOVE_SPEED_X / zoom, 0.4, 1)
+ local speedY = speedX
+
+ if input:GetKeyDown(KEY_LEFT) or input:GetKeyDown(KEY_A) then
+ moveDir = moveDir + Vector3.LEFT * speedX
+ animatedSprite.flipX = false -- Flip sprite (reset to default play on the X axis)
+ end
+ if input:GetKeyDown(KEY_RIGHT) or input:GetKeyDown(KEY_D) then
+ moveDir = moveDir + Vector3.RIGHT * speedX
+ animatedSprite.flipX = true -- Flip sprite (flip animation on the X axis)
+ end
+
+ if not moveDir:Equals(Vector3.ZERO) then
+ speedY = speedX * MOVE_SPEED_SCALE
+ end
+
+ if input:GetKeyDown(KEY_UP) or input:GetKeyDown(KEY_W) then
+ moveDir = moveDir + Vector3.UP * speedY
+ end
+ if input:GetKeyDown(KEY_DOWN) or input:GetKeyDown(KEY_S) then
+ moveDir = moveDir + Vector3.DOWN * speedY
+ end
+
+ -- Move
+ if not moveDir:Equals(Vector3.ZERO) then
+ node:Translate(moveDir * timeStep)
+ end
+
+ -- Animate
+ if input:GetKeyDown(KEY_SPACE) then
+ if animatedSprite.animation ~= "attack" then
+ animatedSprite:SetAnimation("attack", LM_FORCE_LOOPED)
+ end
+ elseif not moveDir:Equals(Vector3.ZERO) then
+ if animatedSprite.animation ~= "run" then
+ animatedSprite:SetAnimation("run")
+ end
+ elseif animatedSprite.animation ~= "idle" then
+ animatedSprite:SetAnimation("idle")
+ end
+end
diff --git a/bin/Data/LuaScripts/50_Urho2DPlatformer.lua b/bin/Data/LuaScripts/50_Urho2DPlatformer.lua
new file mode 100644
index 0000000..65ea6cf
--- /dev/null
+++ b/bin/Data/LuaScripts/50_Urho2DPlatformer.lua
@@ -0,0 +1,453 @@
+-- Urho2D platformer example.
+-- This sample demonstrates:
+-- - Creating an orthogonal 2D scene from tile map file
+-- - Displaying the scene using the Renderer subsystem
+-- - Handling keyboard to move a character and zoom 2D camera
+-- - Generating physics shapes from the tmx file's objects
+-- - Mixing physics and translations to move the character
+-- - Using Box2D Contact listeners to handle the gameplay
+-- - Displaying debug geometry for physics and tile map
+-- Note that this sample uses some functions from Sample2D utility class.
+
+require "LuaScripts/Utilities/Sample"
+require "LuaScripts/Utilities/2D/Sample2D"
+
+
+function Start()
+ -- Set filename for load/save functions
+ demoFilename = "Platformer2D"
+
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateUIContent("PLATFORMER 2D DEMO")
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
+ scene_:CreateComponent("Octree")
+ scene_:CreateComponent("DebugRenderer")
+ scene_:CreateComponent("PhysicsWorld2D")
+
+ -- Create camera
+ cameraNode = Node()
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.orthographic = true
+ camera.orthoSize = graphics.height * PIXEL_SIZE
+ camera.zoom = 1.8 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.8) is set for full visibility at 1280x800 resolution)
+
+ -- Setup the viewport for displaying the scene
+ renderer:SetViewport(0, Viewport:new(scene_, camera))
+ renderer.defaultZone.fogColor = Color(0.2, 0.2, 0.2) -- Set background color for the scene
+
+ -- Create tile map from tmx file
+ local tileMapNode = scene_:CreateChild("TileMap")
+ local tileMap = tileMapNode:CreateComponent("TileMap2D")
+ tileMap.tmxFile = cache:GetResource("TmxFile2D", "Urho2D/Tilesets/Ortho.tmx")
+ local info = tileMap.info
+
+ -- Create Spriter Imp character (from sample 33_SpriterAnimation)
+ CreateCharacter(info, true, 0.8, Vector3(1, 8, 0), 0.2)
+
+ -- Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
+ local tileMapLayer = tileMap:GetLayer(tileMap.numLayers - 1)
+ CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info)
+
+ -- Instantiate enemies and moving platforms at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
+ PopulateMovingEntities(tileMap:GetLayer(tileMap.numLayers - 2))
+
+ -- Instantiate coins to pick at each placeholder of "Coins" layer (placeholders for coins are Rectangle objects)
+ PopulateCoins(tileMap:GetLayer(tileMap.numLayers - 3))
+
+ -- Instantiate triggers (for ropes, ladders, lava, slopes...) at each placeholder of "Triggers" layer (placeholders for triggers are Rectangle objects)
+ PopulateTriggers(tileMap:GetLayer(tileMap.numLayers - 4))
+
+ -- Create background
+ CreateBackgroundSprite(info, 3.5, "Textures/HeightMap.png", true)
+
+ -- Check when scene is rendered
+ SubscribeToEvent("EndRendering", HandleSceneRendered)
+end
+
+function HandleSceneRendered()
+ UnsubscribeFromEvent("EndRendering")
+ SaveScene(true) -- Save the scene so we can reload it later
+ scene_.updateEnabled = false -- Pause the scene as long as the UI is hiding it
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+
+ -- Subscribe HandlePostUpdate() function for processing post update events
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate")
+
+ -- Subscribe to PostRenderUpdate to draw debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
+
+ -- Subscribe to Box2D contact listeners
+ SubscribeToEvent("PhysicsBeginContact2D", "HandleCollisionBegin")
+ SubscribeToEvent("PhysicsEndContact2D", "HandleCollisionEnd")
+
+ -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ -- Zoom in/out
+ if cameraNode ~= nil then
+ Zoom(cameraNode:GetComponent("Camera"))
+ end
+
+ -- Toggle debug geometry with 'Z' key
+ if input:GetKeyPress(KEY_Z) then drawDebug = not drawDebug end
+
+ -- Check for loading / saving the scene
+ if input:GetKeyPress(KEY_F5) then
+ SaveScene(false)
+ end
+ if input:GetKeyPress(KEY_F7) then
+ ReloadScene(false)
+ end
+end
+
+function HandlePostUpdate(eventType, eventData)
+ if character2DNode == nil or cameraNode == nil then
+ return
+ end
+ cameraNode.position = Vector3(character2DNode.position.x, character2DNode.position.y, -10) -- Camera tracks character
+end
+
+function HandlePostRenderUpdate(eventType, eventData)
+ if drawDebug then
+ scene_:GetComponent("PhysicsWorld2D"):DrawDebugGeometry()
+
+ local tileMapNode = scene_:GetChild("TileMap", true)
+ local map = tileMapNode:GetComponent("TileMap2D")
+ map:DrawDebugGeometry(scene_:GetComponent("DebugRenderer"), false)
+ end
+end
+
+function HandleCollisionBegin(eventType, eventData)
+ -- Get colliding node
+ local hitNode = eventData["NodeA"]:GetPtr("Node")
+ if hitNode.name == "Imp" then
+ hitNode = eventData["NodeB"]:GetPtr("Node")
+ end
+ local nodeName = hitNode.name
+ local character = character2DNode:GetScriptObject()
+
+ -- Handle ropes and ladders climbing
+ if nodeName == "Climb" then
+ if character.isClimbing then -- If transition between rope and top of rope (as we are using split triggers)
+ character.climb2 = true
+ else
+ character.isClimbing = true
+
+ -- Override gravity so that the character doesn't fall
+ local body = character2DNode:GetComponent("RigidBody2D")
+ body.gravityScale = 0
+
+ -- Clear forces so that the character stops (should be performed by setting linear velocity to zero, but currently doesn't work)
+ body.linearVelocity = Vector2.ZERO
+ body.awake = false
+ body.awake = true
+ end
+ end
+
+ if nodeName == "CanJump" then
+ character.aboveClimbable = true
+ end
+
+ -- Handle coins picking
+ if nodeName == "Coin" then
+ hitNode:Remove()
+ character.remainingCoins = character.remainingCoins - 1
+ if character.remainingCoins == 0 then
+ ui.root:GetChild("Instructions", true).text = "!!! Go to the Exit !!!"
+ end
+ ui.root:GetChild("CoinsText", true).text = character.remainingCoins -- Update coins UI counter
+ PlaySound("Powerup.wav")
+ end
+
+ -- Handle interactions with enemies
+ if nodeName == "Enemy" or nodeName == "Orc" then
+ local animatedSprite = character2DNode:GetComponent("AnimatedSprite2D")
+ local deltaX = character2DNode.position.x - hitNode.position.x
+
+ -- Orc killed if character is fighting in its direction when the contact occurs (flowers are not destroyable)
+ if nodeName == "Orc" and animatedSprite.animation == "attack" and (deltaX < 0 == animatedSprite.flipX) then
+ hitNode:GetScriptObject().emitTime = 1
+ if not hitNode:GetChild("Emitter", true) then
+ hitNode:GetComponent("RigidBody2D"):Remove() -- Remove Orc's body
+ SpawnEffect(hitNode)
+ PlaySound("BigExplosion.wav")
+ end
+
+ -- Player killed if not fighting in the direction of the Orc when the contact occurs, or when colliding with a flower
+ else
+ if not character2DNode:GetChild("Emitter", true) then
+ character.wounded = true
+ if nodeName == "Orc" then
+ hitNode:GetScriptObject().fightTimer = 1
+ end
+ SpawnEffect(character2DNode)
+ PlaySound("BigExplosion.wav")
+ end
+ end
+ end
+
+ -- Handle exiting the level when all coins have been gathered
+ if nodeName == "Exit" and character.remainingCoins == 0 then
+ -- Update UI
+ local instructions = ui.root:GetChild("Instructions", true)
+ instructions.text = "!!! WELL DONE !!!"
+ instructions.position = IntVector2.ZERO
+
+ -- Put the character outside of the scene and magnify him
+ character2DNode.position = Vector3(-20, 0, 0)
+ character2DNode:SetScale(1.2)
+ end
+
+ -- Handle falling into lava
+ if nodeName == "Lava" then
+ local body = character2DNode:GetComponent("RigidBody2D")
+ body:ApplyLinearImpulse(Vector2(0, 1) * MOVE_SPEED, body.massCenter, true) -- Violently project character out of lava
+ if not character2DNode:GetChild("Emitter", true) then
+ character.wounded = true
+ SpawnEffect(character2DNode)
+ PlaySound("BigExplosion.wav")
+ end
+ end
+
+ -- Handle climbing a slope
+ if nodeName == "Slope" then
+ character.onSlope = true
+ end
+end
+
+function HandleCollisionEnd(eventType, eventData)
+ -- Get colliding node
+ local hitNode = eventData["NodeA"]:GetPtr("Node")
+ if hitNode.name == "Imp" then
+ hitNode = eventData["NodeB"]:GetPtr("Node")
+ end
+ local nodeName = hitNode.name
+ local character = character2DNode:GetScriptObject()
+
+ -- Handle leaving a rope or ladder
+ if nodeName == "Climb" then
+ if character.climb2 then
+ character.climb2 = false
+ else
+ character.isClimbing = false
+ local body = character2DNode:GetComponent("RigidBody2D")
+ body.gravityScale = 1 -- Restore gravity
+ end
+ end
+
+ if nodeName == "CanJump" then
+ character.aboveClimbable = false
+ end
+
+ -- Handle leaving a slope
+ if nodeName == "Slope" then
+ character.onSlope = false
+ -- Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
+ local body = character2DNode:GetComponent("RigidBody2D")
+ body.linearVelocity = Vector2.ZERO
+ body.awake = false
+ body.awake = true
+ end
+end
+
+
+-- Character2D script object class
+Character2D = ScriptObject()
+
+function Character2D:Start()
+ self.wounded = false
+ self.killed = false
+ self.timer = 0
+ self.maxCoins = 0
+ self.remainingCoins = 0
+ self.remainingLifes = 3
+ self.isClimbing = false
+ self.climb2 = false -- Used only for ropes, as they are split into 2 shapes
+ self.aboveClimbable = false
+ self.onSlope = false
+end
+
+function Character2D:Save(serializer)
+ self.isClimbing = false -- Overwrite before auto-deserialization
+end
+
+function Character2D:Update(timeStep)
+ if character2DNode == nil then
+ return
+ end
+
+ -- Handle wounded/killed states
+ if self.killed then
+ return
+ end
+
+ if self.wounded then
+ self:HandleWoundedState(timeStep)
+ return
+ end
+
+ -- Set temporary variables
+ local node = self.node
+ local body = node:GetComponent("RigidBody2D")
+ local animatedSprite = node:GetComponent("AnimatedSprite2D")
+ local onGround = false
+ local jump = false
+
+ -- Collision detection (AABB query)
+ local characterHalfSize = Vector2(0.16, 0.16)
+ local collidingBodies = scene_:GetComponent("PhysicsWorld2D"):GetRigidBodies(Rect(node.worldPosition2D - characterHalfSize - Vector2(0, 0.1), node.worldPosition2D + characterHalfSize))
+
+ if table.maxn(collidingBodies) > 1 and not self.isClimbing then
+ onGround = true
+ end
+
+ -- Set direction
+ local moveDir = Vector2.ZERO -- Reset
+
+ if input:GetKeyDown(KEY_LEFT) or input:GetKeyDown(KEY_A) then
+ moveDir = moveDir + Vector2.LEFT
+ animatedSprite.flipX = false -- Flip sprite (reset to default play on the X axis)
+ end
+
+ if input:GetKeyDown(KEY_RIGHT) or input:GetKeyDown(KEY_D) then
+ moveDir = moveDir + Vector2.RIGHT
+ animatedSprite.flipX = true -- Flip sprite (flip animation on the X axis)
+ end
+
+ -- Jump
+ if (onGround or self.aboveClimbable) and (input:GetKeyPress(KEY_UP) or input:GetKeyPress(KEY_W)) then
+ jump = true
+ end
+
+ -- Climb
+ if self.isClimbing then
+ if not self.aboveClimbable and (input:GetKeyDown(KEY_UP) or input:GetKeyDown(KEY_W)) then
+ moveDir = moveDir + Vector2.UP
+ end
+
+ if input:GetKeyDown(KEY_DOWN) or input:GetKeyDown(KEY_S) then
+ moveDir = moveDir + Vector2.DOWN
+ end
+ end
+
+ -- Move
+ if not moveDir:Equals(Vector2.ZERO) or jump then
+ if self.onSlope then
+ body:ApplyForceToCenter(moveDir * MOVE_SPEED / 2, true) -- When climbing a slope, apply force (todo: replace by setting linear velocity to zero when will work)
+ else
+ node:Translate(moveDir * timeStep * 1.8)
+ end
+ if jump then
+ body:ApplyLinearImpulse(Vector2(0, 0.17) * MOVE_SPEED, body.massCenter, true)
+ end
+ end
+
+ -- Animate
+ if input:GetKeyDown(KEY_SPACE) then
+ if animatedSprite.animation ~= "attack" then
+ animatedSprite:SetAnimation("attack", LM_FORCE_LOOPED)
+ animatedSprite.speed = 1.5
+ end
+ elseif not moveDir:Equals(Vector2.ZERO) then
+ if animatedSprite.animation ~= "run" then
+ animatedSprite:SetAnimation("run")
+ end
+ elseif animatedSprite.animation ~= "idle" then
+ animatedSprite:SetAnimation("idle")
+ end
+end
+
+function Character2D:HandleWoundedState(timeStep)
+ local node = self.node
+ local body = node:GetComponent("RigidBody2D")
+ local animatedSprite = node:GetComponent("AnimatedSprite2D")
+
+ -- Play "hit" animation in loop
+ if animatedSprite.animation ~= "hit" then
+ animatedSprite:SetAnimation("hit", LM_FORCE_LOOPED)
+ end
+
+ -- Update timer
+ self.timer = self.timer + timeStep
+
+ -- End of timer
+ if self.timer > 2 then
+ -- Reset timer
+ self.timer = 0
+
+ -- Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
+ body.linearVelocity = Vector2.ZERO
+ body.awake = false
+ body.awake = true
+
+ -- Remove particle emitter
+ node:GetChild("Emitter", true):Remove()
+
+ -- Update lifes UI and counter
+ self.remainingLifes = self.remainingLifes - 1
+ ui.root:GetChild("LifeText", true).text = self.remainingLifes -- Update lifes UI counter
+
+ -- Reset wounded state
+ self.wounded = false
+
+ -- Handle death
+ if self.remainingLifes == 0 then
+ self:HandleDeath()
+ return
+ end
+
+ -- Re-position the character to the nearest point
+ if node.position.x < 15 then
+ node.position = Vector3(1, 8, 0)
+ else
+ node.position = Vector3(18.8, 9.2, 0)
+ end
+ end
+end
+
+function Character2D:HandleDeath()
+ local node = self.node
+ local body = node:GetComponent("RigidBody2D")
+ local animatedSprite = node:GetComponent("AnimatedSprite2D")
+
+ -- Set state to 'killed'
+ self.killed = true
+
+ -- Update UI elements
+ local instructions = ui.root:GetChild("Instructions", true)
+ instructions.text = "!!! GAME OVER !!!"
+ ui.root:GetChild("ExitButton", true).visible = true
+ ui.root:GetChild("PlayButton", true).visible = true
+
+ -- Show mouse cursor so that we can click
+ input.mouseVisible = true
+
+ -- Put character outside of the scene and magnify him
+ node.position = Vector3(-20, 0, 0)
+ node:SetScale(1.2)
+
+ -- Play death animation once
+ if animatedSprite.animation ~= "dead2" then
+ animatedSprite:SetAnimation("dead2")
+ end
+end
diff --git a/bin/Data/LuaScripts/51_Urho2DStretchableSprite.lua b/bin/Data/LuaScripts/51_Urho2DStretchableSprite.lua
new file mode 100644
index 0000000..77a9c6b
--- /dev/null
+++ b/bin/Data/LuaScripts/51_Urho2DStretchableSprite.lua
@@ -0,0 +1,199 @@
+-- Urho2D stretchable sprite example.
+-- This sample demonstrates:
+-- - Creating a 2D scene with both static and stretchable sprites
+-- - Difference in scaling static and stretchable sprites
+-- - Similarity in otherwise transforming static and stretchable sprites
+-- - Displaying the scene using the Renderer subsystem
+-- - Handling keyboard to transform nodes
+
+require "LuaScripts/Utilities/Sample"
+
+local selectTransform = 0 -- Transform mode tracking index.
+local refSpriteNode = nil -- Reference (static) sprite node.
+local stretchSpriteNode = nil -- Stretchable sprite node.
+
+function Start()
+ -- Execute base class startup
+ SampleStart()
+
+ -- Create the scene content
+ CreateScene()
+
+ -- Create the UI content
+ CreateInstructions()
+
+ -- Setup the viewport for displaying the scene
+ SetupViewport()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Hook up to the frame update events
+ SubscribeToEvents()
+end
+
+function CreateScene()
+ scene_ = Scene()
+
+ -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
+ -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ -- optimizing manner
+ scene_:CreateComponent("Octree")
+
+ -- Create a scene node for the camera, which we will move around
+ -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_:CreateChild("Camera")
+ -- Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0, 0.0, -10.0)
+ local camera = cameraNode:CreateComponent("Camera")
+ camera.orthographic = true
+ camera.orthoSize = graphics.height * PIXEL_SIZE
+
+ -- Setup sprites and nodes on the scene
+ refSpriteNode = scene_:CreateChild("regular sprite")
+ stretchSpriteNode = scene_:CreateChild("stretch sprite")
+ local sprite = cache:GetResource("Sprite2D", "Urho2D/Stretchable.png")
+ if sprite then
+ local staticSprite = refSpriteNode:CreateComponent("StaticSprite2D")
+ staticSprite.sprite = sprite
+
+ local stretchSprite = stretchSpriteNode:CreateComponent("StretchableSprite2D")
+ stretchSprite.sprite = sprite
+ stretchSprite.border = IntRect(25, 25, 25, 25)
+
+ refSpriteNode:Translate2D(Vector2(-2, 0))
+ stretchSpriteNode:Translate2D(Vector2(2, 0))
+ end
+end
+
+function CreateInstructions()
+ -- Construct new Text object, set string to display and font to use
+ local instructionText = ui.root:CreateChild("Text")
+ instructionText:SetText("Use WASD keys and mouse to move, Use PageUp PageDown to zoom.")
+ instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
+
+ -- Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER
+ instructionText.verticalAlignment = VA_CENTER
+ instructionText:SetPosition(0, ui.root.height / 4)
+end
+
+function SetupViewport()
+ -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ -- use, but now we just use full screen and default render path configured in the engine command line options
+ local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
+ renderer:SetViewport(0, viewport)
+end
+
+function SubscribeToEvents()
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate")
+ -- Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("KeyUp", "HandleKeyUp")
+
+ -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate")
+end
+
+function HandleUpdate(eventType, eventData)
+ local timeStep = eventData["TimeStep"]:GetFloat()
+
+ if selectTransform == 0 then
+ ScaleSprites(timeStep)
+ elseif selectTransform == 1 then
+ RotateSprites(timeStep)
+ elseif selectTransform == 2 then
+ TranslateSprites(timeStep)
+ else
+ log:Write(LOG_ERROR, "bad transform selection " .. selectTransform)
+ end
+end
+
+function HandleKeyUp(eventType, eventData)
+ local key = eventData["Key"]:GetInt()
+
+ if key == KEY_TAB then
+ selectTransform = (selectTransform + 1) % 3
+ elseif key == KEY_ESCAPE then
+ engine:Exit()
+ end
+end
+
+function TranslateSprites(timeStep)
+ local speed = 1
+ local left = input:GetKeyDown(KEY_A)
+ local right = input:GetKeyDown(KEY_D)
+ local up = input:GetKeyDown(KEY_W)
+ local down = input:GetKeyDown(KEY_S)
+
+ if left or right or up or down then
+ local quantum = timeStep * speed
+ local translate = Vector2((left and -quantum or 0) + (right and quantum or 0),
+ (down and -quantum or 0) + (up and quantum or 0))
+
+ refSpriteNode:Translate2D(translate)
+ stretchSpriteNode:Translate2D(translate)
+ end
+end
+
+function RotateSprites(timeStep)
+ local speed = 45
+ local left = input:GetKeyDown(KEY_A)
+ local right = input:GetKeyDown(KEY_D)
+ local up = input:GetKeyDown(KEY_W)
+ local down = input:GetKeyDown(KEY_S)
+ local ctrl = input:GetKeyDown(KEY_CTRL)
+
+ if left or right or up or down then
+ local quantum = timeStep * speed
+ local xrot = (up and -quantum or 0) + (down and quantum or 0)
+ local rot2 = (left and -quantum or 0) + (right and quantum or 0)
+ local totalRot = Quaternion(xrot, ctrl and 0 or rot2, ctrl and rot2 or 0)
+
+ refSpriteNode:Rotate(totalRot)
+ stretchSpriteNode:Rotate(totalRot)
+ end
+end
+
+function ScaleSprites(timeStep)
+ local speed = 0.5
+ local left = input:GetKeyDown(KEY_A)
+ local right = input:GetKeyDown(KEY_D)
+ local up = input:GetKeyDown(KEY_W)
+ local down = input:GetKeyDown(KEY_S)
+
+ if left or right or up or down then
+ local quantum = timeStep * speed
+ local scale = Vector2(1 + (right and quantum or left and -quantum or 0),
+ 1 + (up and quantum or down and -quantum or 0))
+
+ refSpriteNode:Scale2D(scale)
+ stretchSpriteNode:Scale2D(scale)
+ end
+end
+
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " TAB" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " CTRL" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/52_NATPunchtrough.lua b/bin/Data/LuaScripts/52_NATPunchtrough.lua
new file mode 100644
index 0000000..0e14fdd
--- /dev/null
+++ b/bin/Data/LuaScripts/52_NATPunchtrough.lua
@@ -0,0 +1,254 @@
+-- Chat example
+-- This sample demonstrates:
+-- - Starting up a network server or connecting to it
+-- - Implementing simple chat functionality with network messages
+
+require "LuaScripts/Utilities/Sample"
+
+local natServerAddress = nil
+local natServerPort = nil
+local saveNatSettingsButton = nil
+
+local startServerButton = nil
+
+local serverGuid = nil
+local connectButton = nil
+
+local logHistory = {}
+local logHistoryText = nil
+
+local guid = nil
+
+-- Local server port
+local SERVER_PORT = 54654
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Enable OS cursor
+ input.mouseVisible = true
+
+ -- Create the user interface
+ CreateUI()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Subscribe to UI and network events
+ SubscribeToEvents()
+end
+
+function CreateUI()
+
+ SetLogoVisible(true) -- We need the full rendering window
+
+ local uiStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ -- Set style to the UI root so that elements will inherit it
+ ui.root.defaultStyle = uiStyle
+
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+ logHistoryText = ui.root:CreateChild("Text")
+ logHistoryText:SetFont(font, 12)
+ logHistoryText:SetPosition(20, 200);
+
+ local marginTop = 40
+ CreateLabel("1. Run NAT server somewhere, enter NAT server info and press 'Save NAT settings'", IntVector2(20, marginTop-20));
+ natServerAddress = CreateLineEdit("127.0.0.1", 200, IntVector2(20, marginTop));
+ natServerPort = CreateLineEdit("61111", 100, IntVector2(240, marginTop));
+ saveNatSettingsButton = CreateButton("Save NAT settings", 160, IntVector2(360, marginTop));
+
+
+ marginTop = 120;
+ CreateLabel("2. Create server and give others your server GUID", IntVector2(20, marginTop-20));
+ guid = CreateLineEdit("Your server GUID", 200, IntVector2(20, marginTop));
+ startServerButton = CreateButton("Start server", 160, IntVector2(240, marginTop));
+
+
+ marginTop = 200;
+ CreateLabel("3. Input local or remote server GUID", IntVector2(20, marginTop-20));
+ serverGuid = CreateLineEdit("Remote server GUID", 200, IntVector2(20, marginTop));
+ connectButton = CreateButton("Connect", 160, IntVector2(240, marginTop));
+
+ local size = 20
+ for i = 1, size do
+ table.insert(logHistory, "")
+ end
+
+ -- No viewports or scene is defined. However, the default zone's fog color controls the fill color
+ renderer.defaultZone.fogColor = Color(0.0, 0.0, 0.1)
+end
+
+function SubscribeToEvents()
+ SubscribeToEvent("ServerConnected", "HandleServerConnected");
+ SubscribeToEvent("ServerDisconnected", "HandleServerDisconnected");
+ SubscribeToEvent("ConnectFailed", "HandleConnectFailed");
+
+ -- NAT server connection related events
+ SubscribeToEvent("NetworkNatMasterConnectionFailed", "HandleNatConnectionFailed");
+ SubscribeToEvent("NetworkNatMasterConnectionSucceeded", "HandleNatConnectionSucceeded");
+
+ -- NAT punchtrough request events
+ SubscribeToEvent("NetworkNatPunchtroughSucceeded", "HandleNatPunchtroughSucceeded");
+ SubscribeToEvent("NetworkNatPunchtroughFailed", "HandleNatPunchtroughFailed");
+
+ SubscribeToEvent("ClientConnected", "HandleClientConnected");
+ SubscribeToEvent("ClientDisconnected", "HandleClientDisconnected");
+
+ -- Subscribe to UI element events
+ SubscribeToEvent(saveNatSettingsButton, "Released", "HandleSaveNatSettings");
+ SubscribeToEvent(startServerButton, "Released", "HandleStartServer");
+ SubscribeToEvent(connectButton, "Released", "HandleConnect");
+end
+
+function CreateButton(text, width, position)
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+
+ local button = ui.root:CreateChild("Button")
+ button:SetStyleAuto()
+ button:SetFixedWidth(width)
+ button:SetFixedHeight(30)
+ button:SetPosition(position.x, position.y)
+
+ local buttonText = button:CreateChild("Text")
+ buttonText:SetFont(font, 12)
+ buttonText:SetAlignment(HA_CENTER, VA_CENTER)
+ buttonText.text = text
+
+ return button
+end
+
+function CreateLabel(text, position)
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+ local label = ui.root:CreateChild("Text")
+ label:SetFont(font, 12)
+ label.color = Color(0.0, 1.0, 0.0)
+ label:SetPosition(position.x, position.y)
+ label.text = text
+end
+
+function CreateLineEdit(placeholder, width, position)
+ local textEdit = ui.root:CreateChild("LineEdit")
+ textEdit:SetStyleAuto()
+ textEdit:SetFixedWidth(width)
+ textEdit:SetFixedHeight(30)
+ textEdit.text = placeholder
+ textEdit:SetPosition(position.x, position.y)
+ return textEdit
+end
+
+function ShowLogMessage(row)
+ table.remove(logHistory, 1)
+ table.insert(logHistory, row)
+
+ -- Concatenate all the rows in history
+ local allRows = ""
+ for i, r in ipairs(logHistory) do
+ allRows = allRows .. r .. "\n"
+ end
+ logHistoryText.text = allRows
+end
+
+
+function HandleLogMessage(eventType, eventData)
+ ShowChatText(eventData["Message"]:GetString())
+end
+
+function HandleSaveNatSettings(eventType, eventData)
+
+ local address = natServerAddress.text
+ local port = natServerPort.text
+ -- Save NAT server configuration
+ network:SetNATServerInfo(address, port);
+ ShowLogMessage("Saving NAT settings: " .. address .. ":" .. port);
+end
+
+function HandleServerConnected(eventType, eventData)
+
+end
+
+function HandleConnect(eventType, eventData)
+ local address = textEdit.text
+ if address == "" then
+ address = "localhost" -- Use localhost to connect if nothing else specified
+ end
+
+ -- Empty the text edit after reading the address to connect to
+ textEdit.text = ""
+
+ -- Connect to server, do not specify a client scene as we are not using scene replication, just messages.
+ -- At connect time we could also send identity parameters (such as username) in a VariantMap, but in this
+ -- case we skip it for simplicity
+ network:Connect(address, CHAT_SERVER_PORT, nil)
+
+end
+
+function HandleServerConnected(eventType, eventData)
+ ShowLogMessage("Client: Server connected!");
+end
+
+function HandleServerDisconnected(eventType, eventData)
+ ShowLogMessage("Client: Server disconnected!");
+end
+
+function HandleConnectFailed(eventType, eventData)
+ ShowLogMessage("Client: Connection failed!");
+end
+
+function HandleStartServer(eventType, eventData)
+ network:StartServer(SERVER_PORT);
+ ShowLogMessage("Server: Server started on port: " .. SERVER_PORT);
+
+ -- Connect to the NAT server
+ network:StartNATClient();
+ ShowLogMessage("Server: Starting NAT client for server...");
+
+ -- Output our assigned GUID which others will use to connect to our server
+ guid.text = network:GetGUID();
+end
+
+function HandleConnect(eventType, eventData)
+ local userData = VariantMap()
+ userData["Name"] = "Urho3D";
+
+ -- Attempt connecting to server using custom GUID, Scene = null as a second parameter and user identity is passed as third parameter
+ network:AttemptNATPunchtrough(serverGuid.text, null, userData);
+ ShowLogMessage("Client: Attempting NAT punchtrough to guid: " + serverGuid.text);
+end
+
+function HandleNatConnectionFailed(eventType, eventData)
+ ShowLogMessage("Connection to NAT master server failed!");
+end
+
+function HandleNatConnectionSucceeded(eventType, eventData)
+ ShowLogMessage("Connection to NAT master server succeeded!");
+end
+
+function HandleNatPunchtroughSucceeded(eventType, eventData)
+ ShowLogMessage("NAT punchtrough succeeded!");
+end
+
+function HandleNatPunchtroughFailed(eventType, eventData)
+ ShowLogMessage("NAT punchtrough failed!");
+end
+
+function HandleClientConnected(eventType, eventData)
+ ShowLogMessage("Server: Client connected!");
+end
+
+function HandleClientDisconnected(eventType, eventData)
+ ShowLogMessage("Server: Client disconnected!");
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/53_LANDiscovery.lua b/bin/Data/LuaScripts/53_LANDiscovery.lua
new file mode 100644
index 0000000..8d32082
--- /dev/null
+++ b/bin/Data/LuaScripts/53_LANDiscovery.lua
@@ -0,0 +1,137 @@
+-- Chat example
+-- This sample demonstrates:
+-- - Starting up a network server or connecting to it
+-- - Implementing simple chat functionality with network messages
+
+require "LuaScripts/Utilities/Sample"
+
+local startServer = nil;
+local stopServer = nil;
+local refreshServerList = nil;
+local serverList = nil;
+
+-- Local server port
+local SERVER_PORT = 54654
+
+function Start()
+ -- Execute the common startup for samples
+ SampleStart()
+
+ -- Enable OS cursor
+ input.mouseVisible = true
+
+ -- Create the user interface
+ CreateUI()
+
+ -- Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE)
+
+ -- Subscribe to UI and network events
+ SubscribeToEvents()
+end
+
+function CreateUI()
+
+ SetLogoVisible(true) -- We need the full rendering window
+
+ local uiStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ -- Set style to the UI root so that elements will inherit it
+ ui.root.defaultStyle = uiStyle
+
+
+ local marginTop = 20
+ CreateLabel("1. Start server", IntVector2(20, marginTop-20));
+ startServer = CreateButton("Start server", 160, IntVector2(20, marginTop));
+ stopServer = CreateButton("Stop server", 160, IntVector2(20, marginTop));
+ stopServer.visible = false;
+
+
+ marginTop = marginTop + 80;
+ CreateLabel("2. Discover LAN servers", IntVector2(20, marginTop-20));
+ refreshServerList = CreateButton("Search...", 160, IntVector2(20, marginTop));
+
+
+ marginTop = marginTop + 80;
+ CreateLabel("Local servers:", IntVector2(20, marginTop - 20));
+ serverList = CreateLabel("", IntVector2(20, marginTop));
+
+end
+
+function SubscribeToEvents()
+ SubscribeToEvent("NetworkHostDiscovered", "HandleNetworkHostDiscovered");
+
+ SubscribeToEvent(startServer, "Released", "HandleStartServer");
+ SubscribeToEvent(stopServer, "Released", "HandleStopServer");
+ SubscribeToEvent(refreshServerList, "Released", "HandleDoNetworkDiscovery");
+end
+
+function CreateButton(text, width, position)
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+
+ local button = ui.root:CreateChild("Button")
+ button:SetStyleAuto()
+ button:SetFixedWidth(width)
+ button:SetFixedHeight(30)
+ button:SetPosition(position.x, position.y)
+
+ local buttonText = button:CreateChild("Text")
+ buttonText:SetFont(font, 12)
+ buttonText:SetAlignment(HA_CENTER, VA_CENTER)
+ buttonText.text = text
+
+ return button
+end
+
+function CreateLabel(text, position)
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+ local label = ui.root:CreateChild("Text")
+ label:SetFont(font, 12)
+ label.color = Color(0.0, 1.0, 0.0)
+ label:SetPosition(position.x, position.y)
+ label.text = text
+ return label
+end
+
+function HandleNetworkHostDiscovered(eventType, eventData)
+ local text = serverList.text
+ local data = eventData["Beacon"]:GetVariantMap()
+ text = text .. "\n" .. data["Name"]:GetString() .. "(" .. data["Players"]:GetInt() .. ")" .. eventData["Address"]:GetString() .. ":" .. eventData["Port"]:GetInt()
+ serverList:SetText(text)
+end
+
+function HandleStartServer(eventType, eventData)
+ if network:StartServer(SERVER_PORT) == true
+ then
+ local data = VariantMap();
+ data["Name"] = "Test server";
+ data["Players"] = 100;
+ -- Set data which will be sent to all who requests LAN network discovery
+ network:SetDiscoveryBeacon(data);
+ startServer.visible = false;
+ stopServer.visible = true;
+ end
+end
+
+function HandleStopServer(eventType, eventData)
+ network:StopServer();
+ startServer.visible = true;
+ stopServer.visible = false;
+end
+
+function HandleDoNetworkDiscovery(eventType, eventData)
+ network:DiscoverHosts(SERVER_PORT)
+ serverList:SetText("")
+end
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/Utilities/2D/Mover.lua b/bin/Data/LuaScripts/Utilities/2D/Mover.lua
new file mode 100644
index 0000000..5e0d1c7
--- /dev/null
+++ b/bin/Data/LuaScripts/Utilities/2D/Mover.lua
@@ -0,0 +1,121 @@
+-- Mover script object class
+-- - Handles entity (enemy, platform...) translation along a path (set of Vector2 points)
+-- - Supports looping paths and animation flip
+-- - Default speed is 0.8 if 'Speed' property is not set in the tmx file objects
+
+Mover = ScriptObject()
+
+function Mover:Start()
+ self.speed = 0.8
+ self.path = {}
+ self.currentPathID = 2
+ self.emitTime = 0
+ self.fightTimer = 0
+ self.flip = 0
+end
+
+function Mover:Load(deserializer)
+ self:SetPathAttr(deserializer:ReadBuffer())
+end
+
+function Mover:Save(serializer)
+ serializer:WriteBuffer(self:GetPathAttr())
+end
+
+function Mover:SetPathAttr(buffer)
+ if buffer.size == 0 then
+ return
+ end
+
+ while not buffer.eof do
+ table.insert(self.path, buffer:ReadVector2())
+ end
+end
+
+function Mover:GetPathAttr()
+ local ret = VectorBuffer()
+
+ for i=1, table.maxn(self.path) do
+ ret:WriteVector2(self.path[i])
+ end
+
+ return ret
+end
+
+function Mover:Update(timeStep)
+ if table.maxn(self.path) < 2 then
+ return
+ end
+
+ local node = self.node
+
+ -- Handle Orc states (idle/wounded/fighting)
+ if node.name == "Orc" then
+ local animatedSprite = node:GetComponent("AnimatedSprite2D")
+ local anim = "run"
+
+ -- Handle wounded state
+ if self.emitTime > 0 then
+ self.emitTime = self.emitTime + timeStep
+ anim = "dead"
+
+ -- Handle dead
+ if self.emitTime >= 3 then
+ self.node:Remove()
+ return
+ end
+ else
+ -- Handle fighting state
+ if self.fightTimer > 0 then
+ anim = "attack"
+ self.flip = character2DNode.position.x - node.position.x
+ self.fightTimer = self.fightTimer + timeStep
+ if self.fightTimer >= 3 then
+ self.fightTimer = 0 -- Reset
+ end
+ end
+ -- Flip Orc animation according to speed, or player position when fighting
+ animatedSprite.flipX = self.flip >= 0
+ end
+ -- Animate
+ if animatedSprite.animation ~= anim then
+ animatedSprite:SetAnimation(anim)
+ end
+ end
+
+ -- Don't move if fighting or wounded
+ if self.fightTimer > 0 or self.emitTime > 0 then
+ return
+ end
+
+ -- Set direction and move to target
+ local dir = self.path[self.currentPathID] - node.position2D
+ local dirNormal = dir:Normalized()
+ node:Translate(Vector3(dirNormal.x, dirNormal.y, 0) * Abs(self.speed) * timeStep)
+ self.flip = dir.x
+
+ if Abs(dir:Length()) < 0.1 then
+ if self.speed > 0 then
+ if self.currentPathID + 1 <= table.maxn(self.path) then
+ self.currentPathID = self.currentPathID + 1
+ else
+ -- If loop, go to first waypoint, which equates to last one (and never reverse)
+ if self.path[self.currentPathID] == self.path[1] then
+ self.currentPathID = 1
+ return
+ end
+ -- Reverse path if not looping
+ self.currentPathID = self.currentPathID - 1
+ self.speed = -self.speed
+ end
+ else
+ if self.currentPathID - 1 > 0 then
+ self.currentPathID = self.currentPathID - 1
+ else
+ -- Reverse path
+ self.currentPathID = 2
+ self.speed = -self.speed
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/bin/Data/LuaScripts/Utilities/2D/Sample2D.lua b/bin/Data/LuaScripts/Utilities/2D/Sample2D.lua
new file mode 100644
index 0000000..6f62724
--- /dev/null
+++ b/bin/Data/LuaScripts/Utilities/2D/Sample2D.lua
@@ -0,0 +1,497 @@
+-- Convenient functions for Urho2D samples:
+-- - Generate collision shapes from a tmx file objects
+-- - Create Spriter Imp character
+-- - Load Mover script object class from file
+-- - Create enemies, coins and platforms to tile map placeholders
+-- - Handle camera zoom using PageUp, PageDown and MouseWheel
+-- - Create UI interface
+-- - Create a particle emitter attached to a given node
+-- - Play a non-looping sound effect
+-- - Create a background sprite
+-- - Set global variables
+-- - Set XML patch instructions for screen joystick
+
+CAMERA_MIN_DIST = 0.1
+CAMERA_MAX_DIST = 6
+
+MOVE_SPEED = 23 -- Movement speed as world units per second
+MOVE_SPEED_X = 2.5 -- Movement speed for isometric maps
+MOVE_SPEED_SCALE = 1 -- Scaling factor based on tiles' aspect ratio
+
+LIFES = 3
+zoom = 2 -- Speed is scaled according to zoom
+demoFilename = ""
+character2DNode = nil
+
+
+function CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info)
+ -- Create rigid body to the root node
+ local body = tileMapNode:CreateComponent("RigidBody2D")
+ body.bodyType = BT_STATIC
+
+ -- Generate physics collision shapes from the tmx file's objects located in "Physics" layer
+ for i=0, tileMapLayer:GetNumObjects() -1 do
+ local tileMapObject = tileMapLayer:GetObject(i) -- Get physics objects (TileMapObject2D)
+ local objectType = tileMapObject.objectType
+
+ -- Create collision shape from tmx object
+ local shape
+
+ if objectType == OT_RECTANGLE then
+ shape = tileMapNode:CreateComponent("CollisionBox2D")
+ local size = tileMapObject.size
+ shape.size = size
+ if info.orientation == O_ORTHOGONAL then
+ shape.center = tileMapObject.position + size / 2
+ else
+ shape.center = tileMapObject.position + Vector2(info.tileWidth / 2, 0)
+ shape.angle = 45 -- If our tile map is isometric then shape is losange
+ end
+
+ elseif objectType == OT_ELLIPSE then
+ shape = tileMapNode:CreateComponent("CollisionCircle2D") -- Ellipse is built as a circle shape as there's no equivalent in Box2D
+ local size = tileMapObject.size
+ shape.radius = size.x / 2
+ if info.orientation == O_ORTHOGONAL then
+ shape.center = tileMapObject.position + size / 2
+ else
+ shape.center = tileMapObject.position + Vector2(info.tileWidth / 2, 0)
+ end
+
+ elseif objectType == OT_POLYGON then
+ shape = tileMapNode:CreateComponent("CollisionPolygon2D")
+
+ elseif objectType == OT_POLYLINE then
+ shape = tileMapNode:CreateComponent("CollisionChain2D")
+
+ else break
+ end
+
+ if objectType == OT_POLYGON or objectType == OT_POLYLINE then -- Build shape from vertices
+ local numVertices = tileMapObject.numPoints
+ shape.vertexCount = numVertices
+ for i=0, numVertices - 1 do
+ shape:SetVertex(i, tileMapObject:GetPoint(i))
+ end
+ end
+
+ shape.friction = 0.8
+ if tileMapObject:HasProperty("Friction") then
+ shape.friction = ToFloat(tileMapObject:GetProperty("Friction"))
+ end
+ end
+end
+
+function CreateCharacter(info, createObject, friction, position, scale)
+ character2DNode = scene_:CreateChild("Imp")
+ character2DNode.position = position
+ character2DNode:SetScale(scale)
+ local animatedSprite = character2DNode:CreateComponent("AnimatedSprite2D")
+ local animationSet = cache:GetResource("AnimationSet2D", "Urho2D/imp/imp.scml")
+ animatedSprite.animationSet = animationSet
+ animatedSprite.animation = "idle"
+ animatedSprite:SetLayer(3) -- Put character over tile map (which is on layer 0) and over Orcs (which are on layer 1)
+--
+ local body = character2DNode:CreateComponent("RigidBody2D")
+ body.bodyType = BT_DYNAMIC
+ body.allowSleep = false
+
+ local shape = character2DNode:CreateComponent("CollisionCircle2D")
+ shape.radius = 1.1 -- Set shape size
+ shape.friction = friction -- Set friction
+ shape.restitution = 0.1 -- Slight bounce
+ if createObject then
+ character2DNode:CreateScriptObject("Character2D") -- Create a ScriptObject to handle character behavior
+ end
+
+ -- Scale character's speed on the Y axis according to tiles' aspect ratio (for isometric only)
+ MOVE_SPEED_SCALE = info.tileHeight / info.tileWidth
+end
+
+function CreateTrigger()
+ local node = scene_:CreateChild("Trigger") -- Clones will be renamed according to object type
+ local body = node:CreateComponent("RigidBody2D")
+ body.bodyType = BT_STATIC
+ local shape = node:CreateComponent("CollisionBox2D") -- Create box shape
+ shape.trigger = true
+ return node
+end
+
+function CreateEnemy()
+ local node = scene_:CreateChild("Enemy")
+ local staticSprite = node:CreateComponent("StaticSprite2D")
+ staticSprite.sprite = cache:GetResource("Sprite2D", "Urho2D/Aster.png")
+ local body = node:CreateComponent("RigidBody2D")
+ body.bodyType = BT_STATIC
+ local shape = node:CreateComponent("CollisionCircle2D") -- Create circle shape
+ shape.radius = 0.25 -- Set radius
+ return node
+end
+
+function CreateOrc()
+ local node = scene_:CreateChild("Orc")
+ node.scale = character2DNode.scale -- Use same scale as player character
+ local animatedSprite = node:CreateComponent("AnimatedSprite2D")
+ -- Get scml file and Play "run" anim
+ local animationSet = cache:GetResource("AnimationSet2D", "Urho2D/Orc/Orc.scml")
+ animatedSprite.animationSet = animationSet
+ animatedSprite.animation = "run"
+ animatedSprite:SetLayer(2) -- Make orc always visible
+ local body = node:CreateComponent("RigidBody2D")
+ local shape = node:CreateComponent("CollisionCircle2D") -- Create circle shape
+ shape.radius = 1.3 -- Set shape size
+ shape.trigger = true
+ return node
+end
+
+function CreateCoin()
+ local node = scene_:CreateChild("Coin")
+ node:SetScale(0.5)
+ local animatedSprite = node:CreateComponent("AnimatedSprite2D")
+ -- Get scml file and Play "idle" anim
+ local animationSet = cache:GetResource("AnimationSet2D", "Urho2D/GoldIcon.scml")
+ animatedSprite.animationSet = animationSet
+ animatedSprite.animation = "idle"
+ animatedSprite:SetLayer(2)
+ local body = node:CreateComponent("RigidBody2D")
+ body.bodyType = BT_STATIC
+ local shape = node:CreateComponent("CollisionCircle2D") -- Create circle shape
+ shape.radius = 0.32 -- Set radius
+ shape.trigger = true
+ return node
+end
+
+function CreateMovingPlatform()
+ local node = scene_:CreateChild("MovingPlatform")
+ node.scale = Vector3(3, 1, 0)
+ local staticSprite = node:CreateComponent("StaticSprite2D")
+ staticSprite.sprite = cache:GetResource("Sprite2D", "Urho2D/Box.png")
+ local body = node:CreateComponent("RigidBody2D")
+ body.bodyType = BT_STATIC
+ local shape = node:CreateComponent("CollisionBox2D") -- Create box shape
+ shape.size = Vector2(0.32, 0.32) -- Set box size
+ shape.friction = 0.8 -- Set friction
+ return node
+end
+
+function PopulateMovingEntities(movingEntitiesLayer)
+ -- Create enemy, Orc and moving platform nodes (will be cloned at each placeholder)
+ local enemyNode = CreateEnemy()
+ local orcNode = CreateOrc()
+ local platformNode = CreateMovingPlatform()
+
+ -- Instantiate enemies and moving platforms at each placeholder (placeholders are Poly Line objects defining a path from points)
+ for i=0, movingEntitiesLayer:GetNumObjects() -1 do
+ -- Get placeholder object (TileMapObject2D)
+ local movingObject = movingEntitiesLayer:GetObject(i)
+ if movingObject.objectType == OT_POLYLINE then
+ -- Clone the moving entity node and position it at placeholder point
+ local movingClone = nil
+ local offset = Vector2.ZERO
+ if movingObject.type == "Enemy" then
+ movingClone = enemyNode:Clone()
+ offset = Vector2(0, -0.32)
+ elseif movingObject.type == "Orc" then
+ movingClone = orcNode:Clone()
+ elseif movingObject.type == "MovingPlatform" then
+ movingClone = platformNode:Clone()
+ else
+ break
+ end
+ movingClone.position2D = movingObject:GetPoint(0) + offset
+
+ -- Create script object that handles entity translation along its path (load from file)
+ local mover = movingClone:CreateScriptObject("LuaScripts/Utilities/2D/Mover.lua", "Mover")
+
+ -- Set path from points
+ mover.path = CreatePathFromPoints(movingObject, offset)
+
+ -- Override default speed
+ if movingObject:HasProperty("Speed") then
+ mover.speed = movingObject:GetProperty("Speed")
+ end
+ end
+ end
+
+ -- Remove nodes used for cloning purpose
+ enemyNode:Remove()
+ orcNode:Remove()
+ platformNode:Remove()
+end
+
+function PopulateCoins(coinsLayer)
+ -- Create coin (will be cloned at each placeholder)
+ local coinNode = CreateCoin()
+
+ -- Instantiate coins to pick at each placeholder
+ for i=0, coinsLayer:GetNumObjects() -1 do
+ local coinObject = coinsLayer:GetObject(i) -- Get placeholder object (TileMapObject2D)
+ local coinClone = coinNode:Clone()
+ coinClone.position2D = coinObject.position + coinObject.size / 2 + Vector2(0, 0.16)
+ end
+
+ -- Init coins counters
+ local character = character2DNode:GetScriptObject()
+ character.remainingCoins = coinsLayer.numObjects
+ character.maxCoins = coinsLayer.numObjects
+
+ -- Remove node used for cloning purpose
+ coinNode:Remove()
+end
+
+function PopulateTriggers(triggersLayer)
+ -- Create trigger node (will be cloned at each placeholder)
+ local triggerNode = CreateTrigger()
+
+ -- Instantiate triggers at each placeholder (Rectangle objects)
+ for i=0, triggersLayer:GetNumObjects() -1 do
+ local triggerObject = triggersLayer:GetObject(i) -- Get placeholder object (TileMapObject2D)
+ if triggerObject.objectType == OT_RECTANGLE then
+ local triggerClone = triggerNode:Clone()
+ triggerClone.name = triggerObject.type
+ triggerClone:GetComponent("CollisionBox2D").size = triggerObject.size
+ triggerClone.position2D = triggerObject.position + triggerObject.size / 2
+ end
+ end
+end
+
+function Zoom(camera)
+ if input.mouseMoveWheel then
+ zoom = Clamp(camera.zoom + input.mouseMoveWheel * 0.1, CAMERA_MIN_DIST, CAMERA_MAX_DIST)
+ camera.zoom = zoom
+ end
+
+ if input:GetKeyDown(KEY_PAGEUP) then
+ zoom = Clamp(camera.zoom * 1.01, CAMERA_MIN_DIST, CAMERA_MAX_DIST)
+ camera.zoom = zoom
+ end
+
+ if input:GetKeyDown(KEY_PAGEDOWN) then
+ zoom = Clamp(camera.zoom * 0.99, CAMERA_MIN_DIST, CAMERA_MAX_DIST)
+ camera.zoom = zoom
+ end
+end
+
+function CreatePathFromPoints(object, offset)
+ local path = {}
+ for i=0, object.numPoints -1 do
+ table.insert(path, object:GetPoint(i) + offset)
+ end
+ return path
+end
+
+function CreateUIContent(demoTitle)
+ -- Set the default UI style and font
+ ui.root.defaultStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ local font = cache:GetResource("Font", "Fonts/Anonymous Pro.ttf")
+
+ -- We create in-game UIs (coins and lifes) first so that they are hidden by the fullscreen UI (we could also temporary hide them using SetVisible)
+
+ -- Create the UI for displaying the remaining coins
+ local coinsUI = ui.root:CreateChild("BorderImage", "Coins")
+ coinsUI.texture = cache:GetResource("Texture2D", "Urho2D/GoldIcon.png")
+ coinsUI:SetSize(50, 50)
+ coinsUI.imageRect = IntRect(0, 64, 60, 128)
+ coinsUI:SetAlignment(HA_LEFT, VA_TOP)
+ coinsUI:SetPosition(5, 5)
+ local coinsText = coinsUI:CreateChild("Text", "CoinsText")
+ coinsText:SetAlignment(HA_CENTER, VA_CENTER)
+ coinsText:SetFont(font, 24)
+ coinsText.textEffect = TE_SHADOW
+ coinsText.text = character2DNode:GetScriptObject().remainingCoins
+
+ -- Create the UI for displaying the remaining lifes
+ local lifeUI = ui.root:CreateChild("BorderImage", "Life")
+ lifeUI.texture = cache:GetResource("Texture2D", "Urho2D/imp/imp_all.png")
+ lifeUI:SetSize(70, 80)
+ lifeUI:SetAlignment(HA_RIGHT, VA_TOP)
+ lifeUI:SetPosition(-5, 5)
+ local lifeText = lifeUI:CreateChild("Text", "LifeText")
+ lifeText:SetAlignment(HA_CENTER, VA_CENTER)
+ lifeText:SetFont(font, 24)
+ lifeText.textEffect = TE_SHADOW
+ lifeText.text = LIFES
+
+ -- Create the fullscreen UI for start/end
+ local fullUI = ui.root:CreateChild("Window", "FullUI")
+ fullUI:SetStyleAuto()
+ fullUI:SetSize(ui.root.width, ui.root.height)
+ fullUI.enabled = false -- Do not react to input, only the 'Exit' and 'Play' buttons will
+
+ -- Create the title
+ local title = fullUI:CreateChild("BorderImage", "Title")
+ title:SetMinSize(fullUI.width, 50)
+ title.texture = cache:GetResource("Texture2D", "Textures/HeightMap.png")
+ title:SetFullImageRect()
+ title:SetAlignment(HA_CENTER, VA_TOP)
+ local titleText = title:CreateChild("Text", "TitleText")
+ titleText:SetAlignment(HA_CENTER, VA_CENTER)
+ titleText:SetFont(font, 24)
+ titleText.text = demoTitle
+
+ -- Create the image
+ local spriteUI = fullUI:CreateChild("BorderImage", "Sprite")
+ spriteUI.texture = cache:GetResource("Texture2D", "Urho2D/imp/imp_all.png")
+ spriteUI:SetSize(238, 271)
+ spriteUI:SetAlignment(HA_CENTER, VA_CENTER)
+ spriteUI:SetPosition(0, - ui.root.height / 4)
+
+ -- Create the 'EXIT' button
+ local exitButton = ui.root:CreateChild("Button", "ExitButton")
+ exitButton:SetStyleAuto()
+ exitButton.focusMode = FM_RESETFOCUS
+ exitButton:SetSize(100, 50)
+ exitButton:SetAlignment(HA_CENTER, VA_CENTER)
+ exitButton:SetPosition(-100, 0)
+ local exitText = exitButton:CreateChild("Text", "ExitText")
+ exitText:SetAlignment(HA_CENTER, VA_CENTER)
+ exitText:SetFont(font, 24)
+ exitText.text = "EXIT"
+ SubscribeToEvent(exitButton, "Released", "HandleExitButton")
+
+ -- Create the 'PLAY' button
+ local playButton = ui.root:CreateChild("Button", "PlayButton")
+ playButton:SetStyleAuto()
+ playButton.focusMode = FM_RESETFOCUS
+ playButton:SetSize(100, 50)
+ playButton:SetAlignment(HA_CENTER, VA_CENTER)
+ playButton:SetPosition(100, 0)
+ local playText = playButton:CreateChild("Text", "PlayText")
+ playText:SetAlignment(HA_CENTER, VA_CENTER)
+ playText:SetFont(font, 24)
+ playText.text = "PLAY"
+ SubscribeToEvent(playButton, "Released", "HandlePlayButton")
+
+ -- Create the instructions
+ local instructionText = ui.root:CreateChild("Text", "Instructions")
+ instructionText:SetFont(font, 15)
+ instructionText.textAlignment = HA_CENTER -- Center rows in relation to each other
+ instructionText.text = "Use WASD keys or Arrows to move\nPageUp/PageDown/MouseWheel to zoom\nF5/F7 to save/reload scene\n'Z' to toggle debug geometry\nSpace to fight"
+ instructionText:SetAlignment(HA_CENTER, VA_CENTER)
+ instructionText:SetPosition(0, ui.root.height / 4)
+
+ -- Show mouse cursor
+ input.mouseVisible = true
+end
+
+function HandleExitButton()
+ engine:Exit()
+end
+
+function HandlePlayButton()
+ -- Remove fullscreen UI and unfreeze the scene
+ if ui.root:GetChild("FullUI", true) then
+ ui.root:GetChild("FullUI", true):Remove()
+ scene_.updateEnabled = true
+ else
+ -- Reload the scene
+ ReloadScene(true)
+ end
+
+ -- Hide Instructions and Play/Exit buttons
+ ui.root:GetChild("Instructions", true).text = ""
+ ui.root:GetChild("ExitButton", true).visible = false
+ ui.root:GetChild("PlayButton", true).visible = false
+
+ -- Hide mouse cursor
+ input.mouseVisible = false
+end
+
+function SaveScene(initial)
+ local filename = demoFilename
+ if not initial then
+ filename = demoFilename .. "InGame"
+ end
+
+ scene_:SaveXML(fileSystem:GetProgramDir() .. "Data/Scenes/" .. filename .. ".xml")
+end
+
+function ReloadScene(reInit)
+ local filename = demoFilename
+ if not reInit then
+ filename = demoFilename .. "InGame"
+ end
+
+ scene_:LoadXML(fileSystem:GetProgramDir().."Data/Scenes/" .. filename .. ".xml")
+ -- After loading we have to reacquire the character scene node, as it has been recreated
+ -- Simply find by name as there's only one of them
+ character2DNode = scene_:GetChild("Imp", true)
+ if character2DNode == nil then
+ return
+ end
+
+ -- Set what value to use depending whether reload is requested from 'PLAY' button (reInit=true) or 'F7' key (reInit=false)
+ local character = character2DNode:GetScriptObject()
+ local lifes = character.remainingLifes
+ local coins =character.remainingCoins
+ if reInit then
+ lifes = LIFES
+ coins = character.maxCoins
+ end
+
+ -- Update lifes UI and value
+ local lifeText = ui.root:GetChild("LifeText", true)
+ lifeText.text = lifes
+ character.remainingLifes = lifes
+
+ -- Update coins UI and value
+ local coinsText = ui.root:GetChild("CoinsText", true)
+ coinsText.text = coins
+ character.remainingCoins = coins
+end
+
+function SpawnEffect(node)
+ local particleNode = node:CreateChild("Emitter")
+ particleNode:SetScale(0.5 / node.scale.x)
+ local particleEmitter = particleNode:CreateComponent("ParticleEmitter2D")
+ particleEmitter.effect = cache:GetResource("ParticleEffect2D", "Urho2D/sun.pex")
+end
+
+function PlaySound(soundName)
+ local soundNode = scene_:CreateChild("Sound")
+ local source = soundNode:CreateComponent("SoundSource")
+ source:Play(cache:GetResource("Sound", "Sounds/" .. soundName))
+end
+
+function CreateBackgroundSprite(info, scale, texture, animate)
+ local node = scene_:CreateChild("Background")
+ node.position = Vector3(info.mapWidth, info.mapHeight, 0) / 2
+ node:SetScale(scale)
+ local sprite = node:CreateComponent("StaticSprite2D")
+ sprite.sprite = cache:GetResource("Sprite2D", texture)
+ SetRandomSeed(time:GetSystemTime()) -- Randomize from system clock
+ sprite.color = Color(Random(0, 1), Random(0, 1), Random(0, 1), 1)
+
+ -- Create rotation animation
+ if animate then
+ local animation = ValueAnimation:new()
+ animation:SetKeyFrame(0, Variant(Quaternion(0, 0, 0)))
+ animation:SetKeyFrame(1, Variant(Quaternion(0, 0, 180)))
+ animation:SetKeyFrame(2, Variant(Quaternion(0, 0, 0)))
+ node:SetAttributeAnimation("Rotation", animation, WM_LOOP, 0.05)
+ end
+end
+
+
+-- Create XML patch instructions for screen joystick layout specific to this sample app
+function GetScreenJoystickPatchString()
+ return
+ "" ..
+ " " ..
+ " Fight" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " Jump" ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ " " ..
+ ""
+end
diff --git a/bin/Data/LuaScripts/Utilities/Network.lua b/bin/Data/LuaScripts/Utilities/Network.lua
new file mode 100644
index 0000000..f911165
--- /dev/null
+++ b/bin/Data/LuaScripts/Utilities/Network.lua
@@ -0,0 +1,32 @@
+runServer = false
+runClient = false
+serverAddress = ''
+serverPort = 1234
+userName = ''
+nobgm = false
+
+function ParseNetworkArguments()
+ local arguments = GetArguments()
+ local skipNext = false
+ for i, argument in ipairs(arguments) do
+ if skipNext == false and string.sub(argument, 1, 1) == '-' then
+ argument = string.lower(string.sub(argument, 1))
+ if argument == "server" then
+ runServer = true
+ runClient = false
+ elseif argument == "address" then
+ runClient = true
+ runServer = false
+ serverAddress = arguments[i + 1]
+ skipNext = true
+ elseif argument == "username" then
+ userName = arguments[i + 1]
+ skipNext = true
+ elseif argument == "nobgm" then
+ nobgm = true
+ end
+ end
+ skipNext = false
+ end
+end
+
diff --git a/bin/Data/LuaScripts/Utilities/Rotator.lua b/bin/Data/LuaScripts/Utilities/Rotator.lua
new file mode 100644
index 0000000..3b4a506
--- /dev/null
+++ b/bin/Data/LuaScripts/Utilities/Rotator.lua
@@ -0,0 +1,18 @@
+-- Rotator script object class. Script objects to be added to a scene node must implement the empty ScriptObject interface
+Rotator = ScriptObject()
+
+function Rotator:Start()
+ self.rotationSpeed = Vector3(0, 0, 0)
+end
+
+function Rotator:SetRotationSpeed(speed)
+ -- Need to make a copy of the speed object because in this sample demo the speed object is transient on C++ side
+ self.rotationSpeed = Vector3(speed)
+end
+
+function Rotator:Update(timeStep)
+ local x = self.rotationSpeed.x * timeStep
+ local y = self.rotationSpeed.y * timeStep
+ local z = self.rotationSpeed.z * timeStep
+ self.node:Rotate(Quaternion(x, y, z))
+end
diff --git a/bin/Data/LuaScripts/Utilities/Sample.lua b/bin/Data/LuaScripts/Utilities/Sample.lua
new file mode 100644
index 0000000..02fab62
--- /dev/null
+++ b/bin/Data/LuaScripts/Utilities/Sample.lua
@@ -0,0 +1,312 @@
+-- Common sample initialization as a framework for all samples.
+-- - Create Urho3D logo at screen
+-- - Set custom window title and icon
+-- - Create Console and Debug HUD, and use F1 and F2 key to toggle them
+-- - Toggle rendering options from the keys 1-8
+-- - Take screenshots with key 9
+-- - Handle Esc key down to hide Console or exit application
+-- - Init touch input on mobile platform using screen joysticks (patched for each individual sample)
+
+local logoSprite = nil
+scene_ = nil -- Scene
+screenJoystickIndex = M_MAX_UNSIGNED -- Screen joystick index for navigational controls (mobile platforms only)
+screenJoystickSettingsIndex = M_MAX_UNSIGNED -- Screen joystick index for settings (mobile platforms only)
+touchEnabled = false -- Flag to indicate whether touch input has been enabled
+paused = false -- Pause flag
+drawDebug = false -- Draw debug geometry flag
+cameraNode = nil -- Camera scene node
+yaw = 0 -- Camera yaw angle
+pitch = 0 -- Camera pitch angle
+TOUCH_SENSITIVITY = 2
+useMouseMode_ = MM_ABSOLUTE
+
+function SampleStart()
+ if GetPlatform() == "Android" or GetPlatform() == "iOS" or input.touchEmulation then
+ -- On mobile platform, enable touch by adding a screen joystick
+ InitTouchInput()
+ elseif input:GetNumJoysticks() == 0 then
+ -- On desktop platform, do not detect touch when we already got a joystick
+ SubscribeToEvent("TouchBegin", "HandleTouchBegin")
+ end
+ -- Create logo
+ CreateLogo()
+
+ -- Set custom window Title & Icon
+ SetWindowTitleAndIcon()
+
+ -- Create console and debug HUD
+ CreateConsoleAndDebugHud()
+
+ -- Subscribe key down event
+ SubscribeToEvent("KeyDown", "HandleKeyDown")
+
+ -- Subscribe key up event
+ SubscribeToEvent("KeyUp", "HandleKeyUp")
+
+ -- Subscribe scene update event
+ SubscribeToEvent("SceneUpdate", "HandleSceneUpdate")
+end
+
+function InitTouchInput()
+ touchEnabled = true
+ local layout = cache:GetResource("XMLFile", "UI/ScreenJoystick_Samples.xml")
+ local patchString = GetScreenJoystickPatchString()
+ if patchString ~= "" then
+ -- Patch the screen joystick layout further on demand
+ local patchFile = XMLFile()
+ if patchFile:FromString(patchString) then layout:Patch(patchFile) end
+ end
+ screenJoystickIndex = input:AddScreenJoystick(layout, cache:GetResource("XMLFile", "UI/DefaultStyle.xml"))
+ input:SetScreenJoystickVisible(screenJoystickSettingsIndex, true)
+end
+
+function SampleInitMouseMode(mode)
+ useMouseMode_ = mode
+ if GetPlatform() ~= "Web" then
+ if useMouseMode_ == MM_FREE then
+ input.mouseVisible = true
+ end
+
+ if useMouseMode_ ~= MM_ABSOLUTE then
+ input.mouseMode = useMouseMode_
+
+ if console ~= nil and console.visible then
+ input:SetMouseMode(MM_ABSOLUTE, true)
+ end
+ end
+ else
+ input.mouseVisible = true
+ SubscribeToEvent("MouseButtonDown", "HandleMouseModeRequest")
+ SubscribeToEvent("MouseModeChanged", "HandleMouseModeChange")
+ end
+end
+
+function SetLogoVisible(enable)
+ if logoSprite ~= nil then
+ logoSprite.visible = enable
+ end
+end
+
+function CreateLogo()
+ -- Get logo texture
+ local logoTexture = cache:GetResource("Texture2D", "Textures/FishBoneLogo.png")
+ if logoTexture == nil then
+ return
+ end
+
+ -- Create logo sprite and add to the UI layout
+ logoSprite = ui.root:CreateChild("Sprite")
+
+ -- Set logo sprite texture
+ logoSprite:SetTexture(logoTexture)
+
+ local textureWidth = logoTexture.width
+ local textureHeight = logoTexture.height
+
+ -- Set logo sprite scale
+ logoSprite:SetScale(256 / textureWidth)
+
+ -- Set logo sprite size
+ logoSprite:SetSize(textureWidth, textureHeight)
+
+ -- Set logo sprite hot spot
+ logoSprite.hotSpot = IntVector2(textureWidth, textureHeight)
+
+ -- Set logo sprite alignment
+ logoSprite:SetAlignment(HA_RIGHT, VA_BOTTOM)
+
+ -- Make logo not fully opaque to show the scene underneath
+ logoSprite.opacity = 0.9
+
+ -- Set a low priority for the logo so that other UI elements can be drawn on top
+ logoSprite.priority = -100
+end
+
+function SetWindowTitleAndIcon()
+ local icon = cache:GetResource("Image", "Textures/UrhoIcon.png")
+ graphics:SetWindowIcon(icon)
+ graphics.windowTitle = "Urho3D Sample"
+end
+
+function CreateConsoleAndDebugHud()
+ -- Get default style
+ local uiStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
+ if uiStyle == nil then
+ return
+ end
+
+ -- Create console
+ engine:CreateConsole()
+ console.defaultStyle = uiStyle
+ console.background.opacity = 0.8
+
+ -- Create debug HUD
+ engine:CreateDebugHud()
+ debugHud.defaultStyle = uiStyle
+end
+
+function HandleKeyUp(eventType, eventData)
+ local key = eventData["Key"]:GetInt()
+ -- Close console (if open) or exit when ESC is pressed
+ if key == KEY_ESCAPE then
+ if console:IsVisible() then
+ console:SetVisible(false)
+ else
+ if GetPlatform() == "Web" then
+ input.mouseVisible = true
+ if (useMouseMode_ ~= MM_ABSOLUTE) then
+ input.mouseMode = MM_FREE
+ end
+ else
+ engine:Exit()
+ end
+ end
+ end
+end
+
+function HandleKeyDown(eventType, eventData)
+ local key = eventData["Key"]:GetInt()
+
+ if key == KEY_F1 then
+ console:Toggle()
+
+ elseif key == KEY_F2 then
+ debugHud:ToggleAll()
+
+ elseif ui.focusElement == nil then
+ -- Preferences / Pause
+ if key == KEY_SELECT and touchEnabled then
+ paused = not paused
+ if screenJoystickSettingsIndex == M_MAX_UNSIGNED then
+ -- Lazy initialization
+ screenJoystickSettingsIndex = input:AddScreenJoystick(cache:GetResource("XMLFile", "UI/ScreenJoystickSettings_Samples.xml"), cache:GetResource("XMLFile", "UI/DefaultStyle.xml"))
+ else
+ input:SetScreenJoystickVisible(screenJoystickSettingsIndex, paused)
+ end
+
+ -- Texture quality
+ elseif key == KEY_1 then
+ local quality = renderer.textureQuality
+ quality = quality + 1
+ if quality > QUALITY_HIGH then
+ quality = QUALITY_LOW
+ end
+ renderer.textureQuality = quality
+
+ -- Material quality
+ elseif key == KEY_2 then
+ local quality = renderer.materialQuality
+ quality = quality + 1
+ if quality > QUALITY_HIGH then
+ quality = QUALITY_LOW
+ end
+ renderer.materialQuality = quality
+
+ -- Specular lighting
+ elseif key == KEY_3 then
+ renderer.specularLighting = not renderer.specularLighting
+
+ -- Shadow rendering
+ elseif key == KEY_4 then
+ renderer.drawShadows = not renderer.drawShadows
+
+ -- Shadow map resolution
+ elseif key == KEY_5 then
+ local shadowMapSize = renderer.shadowMapSize
+ shadowMapSize = shadowMapSize * 2
+ if shadowMapSize > 2048 then
+ shadowMapSize = 512
+ end
+ renderer.shadowMapSize = shadowMapSize
+
+ -- Shadow depth and filtering quality
+ elseif key == KEY_6 then
+ local quality = renderer.shadowQuality
+ quality = quality + 1
+ if quality > SHADOWQUALITY_BLUR_VSM then
+ quality = SHADOWQUALITY_SIMPLE_16BIT
+ end
+ renderer.shadowQuality = quality
+
+ -- Occlusion culling
+ elseif key == KEY_7 then
+ local occlusion = renderer.maxOccluderTriangles > 0
+ occlusion = not occlusion
+ if occlusion then
+ renderer.maxOccluderTriangles = 5000
+ else
+ renderer.maxOccluderTriangles = 0
+ end
+
+ -- Instancing
+ elseif key == KEY_8 then
+ renderer.dynamicInstancing = not renderer.dynamicInstancing
+
+ -- Take screenshot
+ elseif key == KEY_9 then
+ local screenshot = Image()
+ graphics:TakeScreenShot(screenshot)
+ local timeStamp = Time:GetTimeStamp()
+ timeStamp = string.gsub(timeStamp, "[:. ]", "_")
+ -- Here we save in the Data folder with date and time appended
+ screenshot:SavePNG(fileSystem:GetProgramDir() .. "Data/Screenshot_" .. timeStamp .. ".png")
+ end
+ end
+end
+
+function HandleSceneUpdate(eventType, eventData)
+ -- Move the camera by touch, if the camera node is initialized by descendant sample class
+ if touchEnabled and cameraNode then
+ for i=0, input:GetNumTouches()-1 do
+ local state = input:GetTouch(i)
+ if not state.touchedElement then -- Touch on empty space
+ if state.delta.x or state.delta.y then
+ local camera = cameraNode:GetComponent("Camera")
+ if not camera then return end
+
+ yaw = yaw + TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.x
+ pitch = pitch + TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.y
+
+ -- Construct new orientation for the camera scene node from yaw and pitch; roll is fixed to zero
+ cameraNode:SetRotation(Quaternion(pitch, yaw, 0))
+ else
+ -- Move the cursor to the touch position
+ local cursor = ui:GetCursor()
+ if cursor and cursor:IsVisible() then cursor:SetPosition(state.position) end
+ end
+ end
+ end
+ end
+end
+
+function HandleTouchBegin(eventType, eventData)
+ -- On some platforms like Windows the presence of touch input can only be detected dynamically
+ InitTouchInput()
+ UnsubscribeFromEvent("TouchBegin")
+end
+
+-- If the user clicks the canvas, attempt to switch to relative mouse mode on web platform
+function HandleMouseModeRequest(eventType, eventData)
+ if console ~= nil and console.visible then
+ return
+ end
+
+ if input.mouseMode == MM_ABSOLUTE then
+ input.mouseVisible = false
+ elseif useMouseMode_ == MM_FREE then
+ input.mouseVisible = true
+ end
+
+ input.mouseMode = useMouseMode_
+end
+
+-- If the user clicks the canvas, attempt to switch to relative mouse mode on web platform
+function HandleMouseModeChange(eventType, eventData)
+ mouseLocked = eventData["MouseLocked"]:GetBool()
+ input.mouseVisible = not mouseLocked
+end
+
+-- Create empty XML patch instructions for screen joystick layout if none defined
+function GetScreenJoystickPatchString()
+ return ""
+end
diff --git a/bin/Data/LuaScripts/Utilities/ScriptCompiler.lua b/bin/Data/LuaScripts/Utilities/ScriptCompiler.lua
new file mode 100644
index 0000000..b8ed667
--- /dev/null
+++ b/bin/Data/LuaScripts/Utilities/ScriptCompiler.lua
@@ -0,0 +1,20 @@
+-- Script to recursively compile lua files located in specified rootFolder to luc (bytecode)
+-- Usage: require "LuaScripts/Utilities/LuaScriptCompiler"
+
+
+-- Set root folder containing lua files to convert
+local rootFolder = "Data/LuaScripts/" -- Starting from bin folder
+if not fileSystem:DirExists(rootFolder) then log:Write(LOG_WARNING, "Cannot find " .. rootFolder) return end -- Ensure that rootFolder exists
+
+-- Get lua files recursively
+local files = fileSystem:ScanDir(fileSystem:GetProgramDir() .. rootFolder, "*.lua", SCAN_FILES, true)
+if table.maxn(files) == 0 then log:Write(LOG_WARNING, "No lua file found in " .. rootFolder .. " and subfolders") return end -- Ensure that at least one file was found
+
+-- Compile each lua file found in rootFolder and subfolders to luc
+for i=1, table.maxn(files) do
+ local filename = rootFolder .. files[i] -- Get file with its path
+ if not fileSystem:FileExists(filename) then log:Write(LOG_WARNING, "Cannot find " .. filename) return end
+ print(filename .. "\n")
+ local args = {"-b", filename, ReplaceExtension(filename, ".luc")} -- Set arguments to pass to the luajit command line app
+ fileSystem:SystemRun(fileSystem:GetProgramDir() .. "luajit", args) -- Compile lua file to luc
+end
diff --git a/bin/Data/LuaScripts/Utilities/Touch.lua b/bin/Data/LuaScripts/Utilities/Touch.lua
new file mode 100644
index 0000000..f15cefd
--- /dev/null
+++ b/bin/Data/LuaScripts/Utilities/Touch.lua
@@ -0,0 +1,48 @@
+-- Mobile framework for Android/iOS
+-- Gamepad from Ninja Snow War
+-- Touches patterns:
+-- - 1 finger touch = pick object through raycast
+-- - 1 or 2 fingers drag = rotate camera
+-- - 2 fingers sliding in opposite direction (up/down) = zoom in/out
+
+-- Setup: Call the update function 'UpdateTouches()' from HandleUpdate or equivalent update handler function
+
+local GYROSCOPE_THRESHOLD = 0.1
+CAMERA_MIN_DIST = 1.0
+CAMERA_MAX_DIST = 20.0
+
+local zoom = false
+useGyroscope = false
+cameraDistance = 5.0
+
+function UpdateTouches(controls) -- Called from HandleUpdate
+
+ zoom = false -- reset bool
+
+ -- Zoom in/out
+ if input.numTouches == 2 then
+ local touch1 = input:GetTouch(0)
+ local touch2 = input:GetTouch(1)
+
+ -- Check for zoom pattern (touches moving in opposite directions and on empty space)
+ if not touch1.touchedElement and not touch2.touchedElement and ((touch1.delta.y > 0 and touch2.delta.y < 0) or (touch1.delta.y < 0 and touch2.delta.y > 0)) then zoom = true else zoom = false end
+
+ -- Check for zoom direction (in/out)
+ if zoom then
+ if Abs(touch1.position.y - touch2.position.y) > Abs(touch1.lastPosition.y - touch2.lastPosition.y) then sens = -1 else sens = 1 end
+ cameraDistance = cameraDistance + Abs(touch1.delta.y - touch2.delta.y) * sens * TOUCH_SENSITIVITY / 50
+ cameraDistance = Clamp(cameraDistance, CAMERA_MIN_DIST, CAMERA_MAX_DIST) -- Restrict zoom range to [1;20]
+ end
+ end
+
+ -- Gyroscope (emulated by SDL through a virtual joystick)
+ if useGyroscope and input.numJoysticks > 0 then -- numJoysticks = 1 on iOS & Android
+ local joystick = input:GetJoystickByIndex(0) -- JoystickState
+ if joystick.numAxes >= 2 then
+ if joystick:GetAxisPosition(0) < -GYROSCOPE_THRESHOLD then controls:Set(CTRL_LEFT, true) end
+ if joystick:GetAxisPosition(0) > GYROSCOPE_THRESHOLD then controls:Set(CTRL_RIGHT, true) end
+ if joystick:GetAxisPosition(1) < -GYROSCOPE_THRESHOLD then controls:Set(CTRL_FORWARD, true) end
+ if joystick:GetAxisPosition(1) > GYROSCOPE_THRESHOLD then controls:Set(CTRL_BACK, true) end
+ end
+ end
+end
diff --git a/bin/Data/Materials/Editor/BlueUnlit.xml b/bin/Data/Materials/Editor/BlueUnlit.xml
new file mode 100644
index 0000000..398dce1
--- /dev/null
+++ b/bin/Data/Materials/Editor/BlueUnlit.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/BrightBlueUnlit.xml b/bin/Data/Materials/Editor/BrightBlueUnlit.xml
new file mode 100644
index 0000000..41d63d6
--- /dev/null
+++ b/bin/Data/Materials/Editor/BrightBlueUnlit.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/BrightGreenUnlit.xml b/bin/Data/Materials/Editor/BrightGreenUnlit.xml
new file mode 100644
index 0000000..9944979
--- /dev/null
+++ b/bin/Data/Materials/Editor/BrightGreenUnlit.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/BrightRedUnlit.xml b/bin/Data/Materials/Editor/BrightRedUnlit.xml
new file mode 100644
index 0000000..8641d7f
--- /dev/null
+++ b/bin/Data/Materials/Editor/BrightRedUnlit.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/DebugIconCamera.xml b/bin/Data/Materials/Editor/DebugIconCamera.xml
new file mode 100644
index 0000000..c1bad27
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconCamera.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/DebugIconCollisionTrigger.xml b/bin/Data/Materials/Editor/DebugIconCollisionTrigger.xml
new file mode 100644
index 0000000..c3da6dd
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconCollisionTrigger.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/Data/Materials/Editor/DebugIconCustomGeometry.xml b/bin/Data/Materials/Editor/DebugIconCustomGeometry.xml
new file mode 100644
index 0000000..042cc6e
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconCustomGeometry.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/DebugIconLight.xml b/bin/Data/Materials/Editor/DebugIconLight.xml
new file mode 100644
index 0000000..1409963
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconLight.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/DebugIconParticleEmitter.xml b/bin/Data/Materials/Editor/DebugIconParticleEmitter.xml
new file mode 100644
index 0000000..a1764f4
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconParticleEmitter.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/DebugIconPointLight.xml b/bin/Data/Materials/Editor/DebugIconPointLight.xml
new file mode 100644
index 0000000..ac9c1c4
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconPointLight.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/DebugIconSoundListener.xml b/bin/Data/Materials/Editor/DebugIconSoundListener.xml
new file mode 100644
index 0000000..12e6155
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconSoundListener.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/DebugIconSoundSource.xml b/bin/Data/Materials/Editor/DebugIconSoundSource.xml
new file mode 100644
index 0000000..fcfd1f6
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconSoundSource.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/DebugIconSplinePathPoint.xml b/bin/Data/Materials/Editor/DebugIconSplinePathPoint.xml
new file mode 100644
index 0000000..7794253
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconSplinePathPoint.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/Data/Materials/Editor/DebugIconSpotLight.xml b/bin/Data/Materials/Editor/DebugIconSpotLight.xml
new file mode 100644
index 0000000..6c22cd2
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconSpotLight.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/DebugIconZone.xml b/bin/Data/Materials/Editor/DebugIconZone.xml
new file mode 100644
index 0000000..06a3e71
--- /dev/null
+++ b/bin/Data/Materials/Editor/DebugIconZone.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/Data/Materials/Editor/GreenUnlit.xml b/bin/Data/Materials/Editor/GreenUnlit.xml
new file mode 100644
index 0000000..58c7bea
--- /dev/null
+++ b/bin/Data/Materials/Editor/GreenUnlit.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/RedUnlit.xml b/bin/Data/Materials/Editor/RedUnlit.xml
new file mode 100644
index 0000000..6b2c69c
--- /dev/null
+++ b/bin/Data/Materials/Editor/RedUnlit.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Editor/TexturedUnlit.xml b/bin/Data/Materials/Editor/TexturedUnlit.xml
new file mode 100644
index 0000000..1bbacbb
--- /dev/null
+++ b/bin/Data/Materials/Editor/TexturedUnlit.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/Data/Materials/GreenTransparent.xml b/bin/Data/Materials/GreenTransparent.xml
new file mode 100644
index 0000000..7181458
--- /dev/null
+++ b/bin/Data/Materials/GreenTransparent.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Jack.xml b/bin/Data/Materials/Jack.xml
new file mode 100644
index 0000000..a1b9267
--- /dev/null
+++ b/bin/Data/Materials/Jack.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/JackEnvMap.xml b/bin/Data/Materials/JackEnvMap.xml
new file mode 100644
index 0000000..03b5fce
--- /dev/null
+++ b/bin/Data/Materials/JackEnvMap.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/LitSmoke.xml b/bin/Data/Materials/LitSmoke.xml
new file mode 100644
index 0000000..e210482
--- /dev/null
+++ b/bin/Data/Materials/LitSmoke.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/LitSmokeSoft.xml b/bin/Data/Materials/LitSmokeSoft.xml
new file mode 100644
index 0000000..392144b
--- /dev/null
+++ b/bin/Data/Materials/LitSmokeSoft.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Mushroom.xml b/bin/Data/Materials/Mushroom.xml
new file mode 100644
index 0000000..2718115
--- /dev/null
+++ b/bin/Data/Materials/Mushroom.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/MushroomEnvMap.xml b/bin/Data/Materials/MushroomEnvMap.xml
new file mode 100644
index 0000000..3a6b7ac
--- /dev/null
+++ b/bin/Data/Materials/MushroomEnvMap.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/MushroomWind.xml b/bin/Data/Materials/MushroomWind.xml
new file mode 100644
index 0000000..b3919a9
--- /dev/null
+++ b/bin/Data/Materials/MushroomWind.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/MushroomWindUnlit.xml b/bin/Data/Materials/MushroomWindUnlit.xml
new file mode 100644
index 0000000..ebecfd9
--- /dev/null
+++ b/bin/Data/Materials/MushroomWindUnlit.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/NinjaSnowWar/CloudPlane.xml b/bin/Data/Materials/NinjaSnowWar/CloudPlane.xml
new file mode 100644
index 0000000..716051f
--- /dev/null
+++ b/bin/Data/Materials/NinjaSnowWar/CloudPlane.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/Data/Materials/NinjaSnowWar/Ninja.xml b/bin/Data/Materials/NinjaSnowWar/Ninja.xml
new file mode 100644
index 0000000..f6220fc
--- /dev/null
+++ b/bin/Data/Materials/NinjaSnowWar/Ninja.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/NinjaSnowWar/Potion.xml b/bin/Data/Materials/NinjaSnowWar/Potion.xml
new file mode 100644
index 0000000..4acea00
--- /dev/null
+++ b/bin/Data/Materials/NinjaSnowWar/Potion.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/NinjaSnowWar/Snow.xml b/bin/Data/Materials/NinjaSnowWar/Snow.xml
new file mode 100644
index 0000000..f7324c3
--- /dev/null
+++ b/bin/Data/Materials/NinjaSnowWar/Snow.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/NinjaSnowWar/SnowCrate.xml b/bin/Data/Materials/NinjaSnowWar/SnowCrate.xml
new file mode 100644
index 0000000..0160616
--- /dev/null
+++ b/bin/Data/Materials/NinjaSnowWar/SnowCrate.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Particle.xml b/bin/Data/Materials/Particle.xml
new file mode 100644
index 0000000..794d5fd
--- /dev/null
+++ b/bin/Data/Materials/Particle.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/RibbonTrail.xml b/bin/Data/Materials/RibbonTrail.xml
new file mode 100644
index 0000000..ddfb57b
--- /dev/null
+++ b/bin/Data/Materials/RibbonTrail.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Materials/Skybox.xml b/bin/Data/Materials/Skybox.xml
new file mode 100644
index 0000000..04ba89c
--- /dev/null
+++ b/bin/Data/Materials/Skybox.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/Data/Materials/SlashTrail.xml b/bin/Data/Materials/SlashTrail.xml
new file mode 100644
index 0000000..00a5f9f
--- /dev/null
+++ b/bin/Data/Materials/SlashTrail.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Materials/Smoke.xml b/bin/Data/Materials/Smoke.xml
new file mode 100644
index 0000000..ae61151
--- /dev/null
+++ b/bin/Data/Materials/Smoke.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/SmokeSoft.xml b/bin/Data/Materials/SmokeSoft.xml
new file mode 100644
index 0000000..33e5926
--- /dev/null
+++ b/bin/Data/Materials/SmokeSoft.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Stone.xml b/bin/Data/Materials/Stone.xml
new file mode 100644
index 0000000..776e16c
--- /dev/null
+++ b/bin/Data/Materials/Stone.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/StoneEnvMap.xml b/bin/Data/Materials/StoneEnvMap.xml
new file mode 100644
index 0000000..7cf67ad
--- /dev/null
+++ b/bin/Data/Materials/StoneEnvMap.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/StoneEnvMapSmall.xml b/bin/Data/Materials/StoneEnvMapSmall.xml
new file mode 100644
index 0000000..358438b
--- /dev/null
+++ b/bin/Data/Materials/StoneEnvMapSmall.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/StoneSmall.xml b/bin/Data/Materials/StoneSmall.xml
new file mode 100644
index 0000000..2350709
--- /dev/null
+++ b/bin/Data/Materials/StoneSmall.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/StoneTiled.xml b/bin/Data/Materials/StoneTiled.xml
new file mode 100644
index 0000000..c776fa7
--- /dev/null
+++ b/bin/Data/Materials/StoneTiled.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/StoneTiledH.xml b/bin/Data/Materials/StoneTiledH.xml
new file mode 100644
index 0000000..679dd19
--- /dev/null
+++ b/bin/Data/Materials/StoneTiledH.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Terrain.xml b/bin/Data/Materials/Terrain.xml
new file mode 100644
index 0000000..08487cb
--- /dev/null
+++ b/bin/Data/Materials/Terrain.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Materials/UrhoDecal.xml b/bin/Data/Materials/UrhoDecal.xml
new file mode 100644
index 0000000..d4dbcaa
--- /dev/null
+++ b/bin/Data/Materials/UrhoDecal.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/UrhoDecalAlpha.xml b/bin/Data/Materials/UrhoDecalAlpha.xml
new file mode 100644
index 0000000..9fb73ed
--- /dev/null
+++ b/bin/Data/Materials/UrhoDecalAlpha.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/UrhoDecalAlphaMaskTwoSided.xml b/bin/Data/Materials/UrhoDecalAlphaMaskTwoSided.xml
new file mode 100644
index 0000000..b004769
--- /dev/null
+++ b/bin/Data/Materials/UrhoDecalAlphaMaskTwoSided.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Materials/VColUnlit.xml b/bin/Data/Materials/VColUnlit.xml
new file mode 100644
index 0000000..10a2d43
--- /dev/null
+++ b/bin/Data/Materials/VColUnlit.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Materials/Water.xml b/bin/Data/Materials/Water.xml
new file mode 100644
index 0000000..860e8d2
--- /dev/null
+++ b/bin/Data/Materials/Water.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/Models/Box.mdl b/bin/Data/Models/Box.mdl
new file mode 100644
index 0000000..27a7f2c
Binary files /dev/null and b/bin/Data/Models/Box.mdl differ
diff --git a/bin/Data/Models/Cone.mdl b/bin/Data/Models/Cone.mdl
new file mode 100644
index 0000000..95591b7
Binary files /dev/null and b/bin/Data/Models/Cone.mdl differ
diff --git a/bin/Data/Models/Cylinder.mdl b/bin/Data/Models/Cylinder.mdl
new file mode 100644
index 0000000..14e99b3
Binary files /dev/null and b/bin/Data/Models/Cylinder.mdl differ
diff --git a/bin/Data/Models/Dome.mdl b/bin/Data/Models/Dome.mdl
new file mode 100644
index 0000000..dcaf04f
Binary files /dev/null and b/bin/Data/Models/Dome.mdl differ
diff --git a/bin/Data/Models/Editor/Axes.mdl b/bin/Data/Models/Editor/Axes.mdl
new file mode 100644
index 0000000..d8641a0
Binary files /dev/null and b/bin/Data/Models/Editor/Axes.mdl differ
diff --git a/bin/Data/Models/Editor/ImagePlane.mdl b/bin/Data/Models/Editor/ImagePlane.mdl
new file mode 100644
index 0000000..be32896
Binary files /dev/null and b/bin/Data/Models/Editor/ImagePlane.mdl differ
diff --git a/bin/Data/Models/Editor/RotateAxes.mdl b/bin/Data/Models/Editor/RotateAxes.mdl
new file mode 100644
index 0000000..e91bc7c
Binary files /dev/null and b/bin/Data/Models/Editor/RotateAxes.mdl differ
diff --git a/bin/Data/Models/Editor/ScaleAxes.mdl b/bin/Data/Models/Editor/ScaleAxes.mdl
new file mode 100644
index 0000000..20d832f
Binary files /dev/null and b/bin/Data/Models/Editor/ScaleAxes.mdl differ
diff --git a/bin/Data/Models/Jack.mdl b/bin/Data/Models/Jack.mdl
new file mode 100644
index 0000000..c8a61bc
Binary files /dev/null and b/bin/Data/Models/Jack.mdl differ
diff --git a/bin/Data/Models/Jack_Walk.ani b/bin/Data/Models/Jack_Walk.ani
new file mode 100644
index 0000000..c14aa6e
Binary files /dev/null and b/bin/Data/Models/Jack_Walk.ani differ
diff --git a/bin/Data/Models/Kachujin/Kachujin.mdl b/bin/Data/Models/Kachujin/Kachujin.mdl
new file mode 100644
index 0000000..f0d379a
Binary files /dev/null and b/bin/Data/Models/Kachujin/Kachujin.mdl differ
diff --git a/bin/Data/Models/Kachujin/Kachujin_Walk.ani b/bin/Data/Models/Kachujin/Kachujin_Walk.ani
new file mode 100644
index 0000000..7fc080a
Binary files /dev/null and b/bin/Data/Models/Kachujin/Kachujin_Walk.ani differ
diff --git a/bin/Data/Models/Kachujin/License.txt b/bin/Data/Models/Kachujin/License.txt
new file mode 100644
index 0000000..19a9d32
--- /dev/null
+++ b/bin/Data/Models/Kachujin/License.txt
@@ -0,0 +1,36 @@
+--------------------------
+note:
+--------------------------
+There is no requirement in the Mixamo License to provide any license information with any content.
+However, for the purpose of learning, I have provided some pertinent information below.
+
+--------------------------
+info:
+--------------------------
+website: https://www.mixamo.com/
+model: Kachujin_G_Rosales
+license: http://www.adobe.com/legal/terms.html
+
+--------------------------
+license short explanation (from the forum):
+--------------------------
+Jeanette Mathews
+May 31, 2016 07:24
+
+Thanks for the feedback, and glad to hear the tutorial was useful getting you started. :)
+
+Yes, all Fuse and Mixamo content is available for commercial and non commercial use, royalty free.
+You can edit them to your hearts content!
+
+The only requirement we have is that the final product you are creating must have our content in an 'embedded', non-editable format.
+So you can't edit the characters and sell them directly, because then other people have access to the character 3d data files.
+Games, movies, 3d prints, 2D illustrations, or any other file format where the character/animation files are not-editable is fine.
+Selling characters/animations directly is against the TOS/EULA.
+
+Hope that helps clarify!
+
+reference: https://community.mixamo.com/hc/en-us/community/posts/211496987-Mixamo-Store-Characters-Licence
+
+--------------------------
+
+
diff --git a/bin/Data/Models/Kachujin/Materials/Kachujin.xml b/bin/Data/Models/Kachujin/Materials/Kachujin.xml
new file mode 100644
index 0000000..9d71a82
--- /dev/null
+++ b/bin/Data/Models/Kachujin/Materials/Kachujin.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Models/Kachujin/Textures/Kachujin_diffuse.png b/bin/Data/Models/Kachujin/Textures/Kachujin_diffuse.png
new file mode 100644
index 0000000..3f8a14b
Binary files /dev/null and b/bin/Data/Models/Kachujin/Textures/Kachujin_diffuse.png differ
diff --git a/bin/Data/Models/Kachujin/Textures/Kachujin_normal.png b/bin/Data/Models/Kachujin/Textures/Kachujin_normal.png
new file mode 100644
index 0000000..b432103
Binary files /dev/null and b/bin/Data/Models/Kachujin/Textures/Kachujin_normal.png differ
diff --git a/bin/Data/Models/Kachujin/Textures/Kachujin_specular.png b/bin/Data/Models/Kachujin/Textures/Kachujin_specular.png
new file mode 100644
index 0000000..ed726d9
Binary files /dev/null and b/bin/Data/Models/Kachujin/Textures/Kachujin_specular.png differ
diff --git a/bin/Data/Models/LinePrimitives/Basis.mdl b/bin/Data/Models/LinePrimitives/Basis.mdl
new file mode 100644
index 0000000..3cc014e
Binary files /dev/null and b/bin/Data/Models/LinePrimitives/Basis.mdl differ
diff --git a/bin/Data/Models/LinePrimitives/Box1x1x1.mdl b/bin/Data/Models/LinePrimitives/Box1x1x1.mdl
new file mode 100644
index 0000000..a4663e0
Binary files /dev/null and b/bin/Data/Models/LinePrimitives/Box1x1x1.mdl differ
diff --git a/bin/Data/Models/LinePrimitives/CubicBezier.mdl b/bin/Data/Models/LinePrimitives/CubicBezier.mdl
new file mode 100644
index 0000000..6a4f452
Binary files /dev/null and b/bin/Data/Models/LinePrimitives/CubicBezier.mdl differ
diff --git a/bin/Data/Models/LinePrimitives/LinearBezier.mdl b/bin/Data/Models/LinePrimitives/LinearBezier.mdl
new file mode 100644
index 0000000..3fa40f0
Binary files /dev/null and b/bin/Data/Models/LinePrimitives/LinearBezier.mdl differ
diff --git a/bin/Data/Models/LinePrimitives/QuadraticBezier.mdl b/bin/Data/Models/LinePrimitives/QuadraticBezier.mdl
new file mode 100644
index 0000000..8058be5
Binary files /dev/null and b/bin/Data/Models/LinePrimitives/QuadraticBezier.mdl differ
diff --git a/bin/Data/Models/LinePrimitives/UnitX.mdl b/bin/Data/Models/LinePrimitives/UnitX.mdl
new file mode 100644
index 0000000..f45b158
Binary files /dev/null and b/bin/Data/Models/LinePrimitives/UnitX.mdl differ
diff --git a/bin/Data/Models/LinePrimitives/UnitY.mdl b/bin/Data/Models/LinePrimitives/UnitY.mdl
new file mode 100644
index 0000000..29a809f
Binary files /dev/null and b/bin/Data/Models/LinePrimitives/UnitY.mdl differ
diff --git a/bin/Data/Models/LinePrimitives/UnitZ.mdl b/bin/Data/Models/LinePrimitives/UnitZ.mdl
new file mode 100644
index 0000000..2c4b31c
Binary files /dev/null and b/bin/Data/Models/LinePrimitives/UnitZ.mdl differ
diff --git a/bin/Data/Models/MaterialPreview.mdl b/bin/Data/Models/MaterialPreview.mdl
new file mode 100644
index 0000000..af0709d
Binary files /dev/null and b/bin/Data/Models/MaterialPreview.mdl differ
diff --git a/bin/Data/Models/Mushroom.mdl b/bin/Data/Models/Mushroom.mdl
new file mode 100644
index 0000000..f9b0156
Binary files /dev/null and b/bin/Data/Models/Mushroom.mdl differ
diff --git a/bin/Data/Models/Mutant/Layer/Mutant_Block_LY.ani b/bin/Data/Models/Mutant/Layer/Mutant_Block_LY.ani
new file mode 100644
index 0000000..132b1fb
Binary files /dev/null and b/bin/Data/Models/Mutant/Layer/Mutant_Block_LY.ani differ
diff --git a/bin/Data/Models/Mutant/Layer/Mutant_HitHead_LY.ani b/bin/Data/Models/Mutant/Layer/Mutant_HitHead_LY.ani
new file mode 100644
index 0000000..e6cb051
Binary files /dev/null and b/bin/Data/Models/Mutant/Layer/Mutant_HitHead_LY.ani differ
diff --git a/bin/Data/Models/Mutant/Layer/Mutant_Throw_LY.ani b/bin/Data/Models/Mutant/Layer/Mutant_Throw_LY.ani
new file mode 100644
index 0000000..d4feaaa
Binary files /dev/null and b/bin/Data/Models/Mutant/Layer/Mutant_Throw_LY.ani differ
diff --git a/bin/Data/Models/Mutant/Layer/Mutant_Wave_LY.ani b/bin/Data/Models/Mutant/Layer/Mutant_Wave_LY.ani
new file mode 100644
index 0000000..4df3893
Binary files /dev/null and b/bin/Data/Models/Mutant/Layer/Mutant_Wave_LY.ani differ
diff --git a/bin/Data/Models/Mutant/License.txt b/bin/Data/Models/Mutant/License.txt
new file mode 100644
index 0000000..a1f029d
--- /dev/null
+++ b/bin/Data/Models/Mutant/License.txt
@@ -0,0 +1,36 @@
+--------------------------
+note:
+--------------------------
+There is no requirement in the Mixamo License to provide any license information with any content.
+However, for the purpose of learning, I have provided some pertinent information below.
+
+--------------------------
+info:
+--------------------------
+website: https://www.mixamo.com/
+model: Mutant
+license: http://www.adobe.com/legal/terms.html
+
+--------------------------
+license short explanation (from the forum):
+--------------------------
+Jeanette Mathews
+May 31, 2016 07:24
+
+Thanks for the feedback, and glad to hear the tutorial was useful getting you started. :)
+
+Yes, all Fuse and Mixamo content is available for commercial and non commercial use, royalty free.
+You can edit them to your hearts content!
+
+The only requirement we have is that the final product you are creating must have our content in an 'embedded', non-editable format.
+So you can't edit the characters and sell them directly, because then other people have access to the character 3d data files.
+Games, movies, 3d prints, 2D illustrations, or any other file format where the character/animation files are not-editable is fine.
+Selling characters/animations directly is against the TOS/EULA.
+
+Hope that helps clarify!
+
+reference: https://community.mixamo.com/hc/en-us/community/posts/211496987-Mixamo-Store-Characters-Licence
+
+--------------------------
+
+
diff --git a/bin/Data/Models/Mutant/Materials/mutant_M.xml b/bin/Data/Models/Mutant/Materials/mutant_M.xml
new file mode 100644
index 0000000..777c32a
--- /dev/null
+++ b/bin/Data/Models/Mutant/Materials/mutant_M.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Models/Mutant/Mutant.mdl b/bin/Data/Models/Mutant/Mutant.mdl
new file mode 100644
index 0000000..22a7a2d
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant.mdl differ
diff --git a/bin/Data/Models/Mutant/Mutant_Death.ani b/bin/Data/Models/Mutant/Mutant_Death.ani
new file mode 100644
index 0000000..b9cdfdd
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_Death.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_HipHop1.ani b/bin/Data/Models/Mutant/Mutant_HipHop1.ani
new file mode 100644
index 0000000..9427142
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_HipHop1.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_Idle0.ani b/bin/Data/Models/Mutant/Mutant_Idle0.ani
new file mode 100644
index 0000000..db2af08
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_Idle0.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_Idle1.ani b/bin/Data/Models/Mutant/Mutant_Idle1.ani
new file mode 100644
index 0000000..503f195
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_Idle1.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_Jump.ani b/bin/Data/Models/Mutant/Mutant_Jump.ani
new file mode 100644
index 0000000..0b3c662
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_Jump.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_Jump1.ani b/bin/Data/Models/Mutant/Mutant_Jump1.ani
new file mode 100644
index 0000000..bfc3503
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_Jump1.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_JumpAttack.ani b/bin/Data/Models/Mutant/Mutant_JumpAttack.ani
new file mode 100644
index 0000000..a9a09e5
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_JumpAttack.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_JumpStop.ani b/bin/Data/Models/Mutant/Mutant_JumpStop.ani
new file mode 100644
index 0000000..f58ff3e
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_JumpStop.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_Kick.ani b/bin/Data/Models/Mutant/Mutant_Kick.ani
new file mode 100644
index 0000000..47ca4ae
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_Kick.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_Punch.ani b/bin/Data/Models/Mutant/Mutant_Punch.ani
new file mode 100644
index 0000000..0acfe0f
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_Punch.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_Run.ani b/bin/Data/Models/Mutant/Mutant_Run.ani
new file mode 100644
index 0000000..a0d98f8
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_Run.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_Swipe.ani b/bin/Data/Models/Mutant/Mutant_Swipe.ani
new file mode 100644
index 0000000..bddbd5c
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_Swipe.ani differ
diff --git a/bin/Data/Models/Mutant/Mutant_Walk.ani b/bin/Data/Models/Mutant/Mutant_Walk.ani
new file mode 100644
index 0000000..16729e1
Binary files /dev/null and b/bin/Data/Models/Mutant/Mutant_Walk.ani differ
diff --git a/bin/Data/Models/Mutant/RootMotion/Mutant_JumpAttack_RM.ani b/bin/Data/Models/Mutant/RootMotion/Mutant_JumpAttack_RM.ani
new file mode 100644
index 0000000..f2db753
Binary files /dev/null and b/bin/Data/Models/Mutant/RootMotion/Mutant_JumpAttack_RM.ani differ
diff --git a/bin/Data/Models/Mutant/Textures/Mutant_diffuse.jpg b/bin/Data/Models/Mutant/Textures/Mutant_diffuse.jpg
new file mode 100644
index 0000000..e867c2b
Binary files /dev/null and b/bin/Data/Models/Mutant/Textures/Mutant_diffuse.jpg differ
diff --git a/bin/Data/Models/Mutant/Textures/Mutant_normal.jpg b/bin/Data/Models/Mutant/Textures/Mutant_normal.jpg
new file mode 100644
index 0000000..4fe7ed1
Binary files /dev/null and b/bin/Data/Models/Mutant/Textures/Mutant_normal.jpg differ
diff --git a/bin/Data/Models/NinjaSnowWar/CloudPlane.mdl b/bin/Data/Models/NinjaSnowWar/CloudPlane.mdl
new file mode 100644
index 0000000..eec1781
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/CloudPlane.mdl differ
diff --git a/bin/Data/Models/NinjaSnowWar/Level.mdl b/bin/Data/Models/NinjaSnowWar/Level.mdl
new file mode 100644
index 0000000..c7801f1
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Level.mdl differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja.mdl b/bin/Data/Models/NinjaSnowWar/Ninja.mdl
new file mode 100644
index 0000000..0d58545
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja.mdl differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Attack1.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Attack1.ani
new file mode 100644
index 0000000..182070e
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Attack1.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Attack2.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Attack2.ani
new file mode 100644
index 0000000..b3d595b
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Attack2.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Attack3.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Attack3.ani
new file mode 100644
index 0000000..88bcc3b
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Attack3.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Backflip.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Backflip.ani
new file mode 100644
index 0000000..9adfde4
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Backflip.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Block.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Block.ani
new file mode 100644
index 0000000..fbfc825
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Block.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Climb.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Climb.ani
new file mode 100644
index 0000000..1d445b7
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Climb.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Crouch.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Crouch.ani
new file mode 100644
index 0000000..9535677
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Crouch.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Death1.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Death1.ani
new file mode 100644
index 0000000..99addb0
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Death1.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Death2.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Death2.ani
new file mode 100644
index 0000000..f10d48c
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Death2.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_HighJump.ani b/bin/Data/Models/NinjaSnowWar/Ninja_HighJump.ani
new file mode 100644
index 0000000..7d9d608
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_HighJump.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Idle1.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Idle1.ani
new file mode 100644
index 0000000..38a5ada
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Idle1.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Idle2.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Idle2.ani
new file mode 100644
index 0000000..145d81f
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Idle2.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Idle3.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Idle3.ani
new file mode 100644
index 0000000..a5f67c4
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Idle3.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Jump.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Jump.ani
new file mode 100644
index 0000000..4845d1a
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Jump.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_JumpNoHeight.ani b/bin/Data/Models/NinjaSnowWar/Ninja_JumpNoHeight.ani
new file mode 100644
index 0000000..01d68f3
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_JumpNoHeight.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Kick.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Kick.ani
new file mode 100644
index 0000000..24c9741
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Kick.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_SideKick.ani b/bin/Data/Models/NinjaSnowWar/Ninja_SideKick.ani
new file mode 100644
index 0000000..37e6421
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_SideKick.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Spin.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Spin.ani
new file mode 100644
index 0000000..eb8b3cf
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Spin.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Stealth.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Stealth.ani
new file mode 100644
index 0000000..fc67685
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Stealth.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Stealth.xml b/bin/Data/Models/NinjaSnowWar/Ninja_Stealth.xml
new file mode 100644
index 0000000..d3a0a84
--- /dev/null
+++ b/bin/Data/Models/NinjaSnowWar/Ninja_Stealth.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Walk.ani b/bin/Data/Models/NinjaSnowWar/Ninja_Walk.ani
new file mode 100644
index 0000000..1e56152
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Ninja_Walk.ani differ
diff --git a/bin/Data/Models/NinjaSnowWar/Ninja_Walk.xml b/bin/Data/Models/NinjaSnowWar/Ninja_Walk.xml
new file mode 100644
index 0000000..ea456be
--- /dev/null
+++ b/bin/Data/Models/NinjaSnowWar/Ninja_Walk.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/bin/Data/Models/NinjaSnowWar/Potion.mdl b/bin/Data/Models/NinjaSnowWar/Potion.mdl
new file mode 100644
index 0000000..745a9c5
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/Potion.mdl differ
diff --git a/bin/Data/Models/NinjaSnowWar/SnowBall.mdl b/bin/Data/Models/NinjaSnowWar/SnowBall.mdl
new file mode 100644
index 0000000..623dfef
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/SnowBall.mdl differ
diff --git a/bin/Data/Models/NinjaSnowWar/SnowCrate.mdl b/bin/Data/Models/NinjaSnowWar/SnowCrate.mdl
new file mode 100644
index 0000000..80e5ee9
Binary files /dev/null and b/bin/Data/Models/NinjaSnowWar/SnowCrate.mdl differ
diff --git a/bin/Data/Models/Plane.mdl b/bin/Data/Models/Plane.mdl
new file mode 100644
index 0000000..4fce89d
Binary files /dev/null and b/bin/Data/Models/Plane.mdl differ
diff --git a/bin/Data/Models/Pyramid.mdl b/bin/Data/Models/Pyramid.mdl
new file mode 100644
index 0000000..a413833
Binary files /dev/null and b/bin/Data/Models/Pyramid.mdl differ
diff --git a/bin/Data/Models/Sphere.mdl b/bin/Data/Models/Sphere.mdl
new file mode 100644
index 0000000..86982da
Binary files /dev/null and b/bin/Data/Models/Sphere.mdl differ
diff --git a/bin/Data/Models/TeaPot.mdl b/bin/Data/Models/TeaPot.mdl
new file mode 100644
index 0000000..b8634f6
Binary files /dev/null and b/bin/Data/Models/TeaPot.mdl differ
diff --git a/bin/Data/Models/Torus.mdl b/bin/Data/Models/Torus.mdl
new file mode 100644
index 0000000..7dc585c
Binary files /dev/null and b/bin/Data/Models/Torus.mdl differ
diff --git a/bin/Data/Music/Ninja Gods.ogg b/bin/Data/Music/Ninja Gods.ogg
new file mode 100644
index 0000000..233659b
Binary files /dev/null and b/bin/Data/Music/Ninja Gods.ogg differ
diff --git a/bin/Data/NinjaSnowWarShaders.xml b/bin/Data/NinjaSnowWarShaders.xml
new file mode 100644
index 0000000..434a685
--- /dev/null
+++ b/bin/Data/NinjaSnowWarShaders.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Objects/LightFlash.xml b/bin/Data/Objects/LightFlash.xml
new file mode 100644
index 0000000..65e2583
--- /dev/null
+++ b/bin/Data/Objects/LightFlash.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Objects/Ninja.xml b/bin/Data/Objects/Ninja.xml
new file mode 100644
index 0000000..60a11e2
--- /dev/null
+++ b/bin/Data/Objects/Ninja.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Objects/Potion.xml b/bin/Data/Objects/Potion.xml
new file mode 100644
index 0000000..cf6d9e9
--- /dev/null
+++ b/bin/Data/Objects/Potion.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Objects/SnowBall.xml b/bin/Data/Objects/SnowBall.xml
new file mode 100644
index 0000000..0635e4a
--- /dev/null
+++ b/bin/Data/Objects/SnowBall.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Objects/SnowCrate.xml b/bin/Data/Objects/SnowCrate.xml
new file mode 100644
index 0000000..356e091
--- /dev/null
+++ b/bin/Data/Objects/SnowCrate.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Particle/Burst.xml b/bin/Data/Particle/Burst.xml
new file mode 100644
index 0000000..6fe702f
--- /dev/null
+++ b/bin/Data/Particle/Burst.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Particle/Disco.xml b/bin/Data/Particle/Disco.xml
new file mode 100644
index 0000000..5f2c7fc
--- /dev/null
+++ b/bin/Data/Particle/Disco.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Particle/Dust.xml b/bin/Data/Particle/Dust.xml
new file mode 100644
index 0000000..a4739a4
--- /dev/null
+++ b/bin/Data/Particle/Dust.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Particle/Fire.xml b/bin/Data/Particle/Fire.xml
new file mode 100644
index 0000000..57a7970
--- /dev/null
+++ b/bin/Data/Particle/Fire.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Particle/Smoke.xml b/bin/Data/Particle/Smoke.xml
new file mode 100644
index 0000000..92ccdcb
--- /dev/null
+++ b/bin/Data/Particle/Smoke.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Particle/SmokeStack.xml b/bin/Data/Particle/SmokeStack.xml
new file mode 100644
index 0000000..4adf6a7
--- /dev/null
+++ b/bin/Data/Particle/SmokeStack.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Particle/SnowExplosion.xml b/bin/Data/Particle/SnowExplosion.xml
new file mode 100644
index 0000000..a931e28
--- /dev/null
+++ b/bin/Data/Particle/SnowExplosion.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Particle/SnowExplosionBig.xml b/bin/Data/Particle/SnowExplosionBig.xml
new file mode 100644
index 0000000..fad53d5
--- /dev/null
+++ b/bin/Data/Particle/SnowExplosionBig.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Particle/SnowExplosionFade.xml b/bin/Data/Particle/SnowExplosionFade.xml
new file mode 100644
index 0000000..423bdd3
--- /dev/null
+++ b/bin/Data/Particle/SnowExplosionFade.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/PostProcess/AutoExposure.xml b/bin/Data/PostProcess/AutoExposure.xml
new file mode 100644
index 0000000..2ca8ee6
--- /dev/null
+++ b/bin/Data/PostProcess/AutoExposure.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/PostProcess/Bloom.xml b/bin/Data/PostProcess/Bloom.xml
new file mode 100644
index 0000000..c19661a
--- /dev/null
+++ b/bin/Data/PostProcess/Bloom.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/Data/PostProcess/BloomHDR.xml b/bin/Data/PostProcess/BloomHDR.xml
new file mode 100644
index 0000000..fdc7556
--- /dev/null
+++ b/bin/Data/PostProcess/BloomHDR.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/PostProcess/Blur.xml b/bin/Data/PostProcess/Blur.xml
new file mode 100644
index 0000000..f1d8109
--- /dev/null
+++ b/bin/Data/PostProcess/Blur.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/PostProcess/ColorCorrection.xml b/bin/Data/PostProcess/ColorCorrection.xml
new file mode 100644
index 0000000..a515a02
--- /dev/null
+++ b/bin/Data/PostProcess/ColorCorrection.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/bin/Data/PostProcess/FXAA2.xml b/bin/Data/PostProcess/FXAA2.xml
new file mode 100644
index 0000000..1ab185f
--- /dev/null
+++ b/bin/Data/PostProcess/FXAA2.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/bin/Data/PostProcess/FXAA3.xml b/bin/Data/PostProcess/FXAA3.xml
new file mode 100644
index 0000000..bc16d7e
--- /dev/null
+++ b/bin/Data/PostProcess/FXAA3.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/Data/PostProcess/GammaCorrection.xml b/bin/Data/PostProcess/GammaCorrection.xml
new file mode 100644
index 0000000..cf7bfd7
--- /dev/null
+++ b/bin/Data/PostProcess/GammaCorrection.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/Data/PostProcess/GreyScale.xml b/bin/Data/PostProcess/GreyScale.xml
new file mode 100644
index 0000000..c37c0f9
--- /dev/null
+++ b/bin/Data/PostProcess/GreyScale.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/bin/Data/PostProcess/Tonemap.xml b/bin/Data/PostProcess/Tonemap.xml
new file mode 100644
index 0000000..528dad6
--- /dev/null
+++ b/bin/Data/PostProcess/Tonemap.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Scenes/Isometric2D.xml b/bin/Data/Scenes/Isometric2D.xml
new file mode 100644
index 0000000..f0e0119
--- /dev/null
+++ b/bin/Data/Scenes/Isometric2D.xml
@@ -0,0 +1,730 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Scenes/LinePrimitives.xml b/bin/Data/Scenes/LinePrimitives.xml
new file mode 100644
index 0000000..07841e5
--- /dev/null
+++ b/bin/Data/Scenes/LinePrimitives.xml
@@ -0,0 +1,255 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Scenes/NinjaSnowWar.xml b/bin/Data/Scenes/NinjaSnowWar.xml
new file mode 100644
index 0000000..4aeb076
--- /dev/null
+++ b/bin/Data/Scenes/NinjaSnowWar.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Scenes/PBRExample.xml b/bin/Data/Scenes/PBRExample.xml
new file mode 100644
index 0000000..af228f1
--- /dev/null
+++ b/bin/Data/Scenes/PBRExample.xml
@@ -0,0 +1,4820 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Scenes/Platformer2D.xml b/bin/Data/Scenes/Platformer2D.xml
new file mode 100644
index 0000000..4f4b419
--- /dev/null
+++ b/bin/Data/Scenes/Platformer2D.xml
@@ -0,0 +1,2795 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Scenes/SceneLoadExample.xml b/bin/Data/Scenes/SceneLoadExample.xml
new file mode 100644
index 0000000..e89920e
--- /dev/null
+++ b/bin/Data/Scenes/SceneLoadExample.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bin/Data/Scripts/01_HelloWorld.as b/bin/Data/Scripts/01_HelloWorld.as
new file mode 100644
index 0000000..eca159b
--- /dev/null
+++ b/bin/Data/Scripts/01_HelloWorld.as
@@ -0,0 +1,61 @@
+// This first example, maintaining tradition, prints a "Hello World" message.
+// Furthermore it shows:
+// - Using the Sample utility functions as a base for the application
+// - Adding a Text element to the graphical user interface
+// - Subscribing to and handling of update events
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create "Hello World" Text
+ CreateText();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Finally, hook-up this HelloWorld instance to handle update events
+ SubscribeToEvents();
+}
+
+void CreateText()
+{
+ // Construct new Text object
+ Text@ helloText = Text();
+
+ // Set String to display
+ helloText.text = "Hello World from Urho3D!";
+
+ // Set font and text color
+ helloText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 30);
+ helloText.color = Color(0.0f, 1.0f, 0.0f);
+
+ // Align Text center-screen
+ helloText.horizontalAlignment = HA_CENTER;
+ helloText.verticalAlignment = VA_CENTER;
+
+ // Add Text instance to the UI root element
+ ui.root.AddChild(helloText);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Do nothing for now, could be extended to eg. animate the display
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/02_HelloGUI.as b/bin/Data/Scripts/02_HelloGUI.as
new file mode 100644
index 0000000..67a4a9d
--- /dev/null
+++ b/bin/Data/Scripts/02_HelloGUI.as
@@ -0,0 +1,189 @@
+// A simple 'HelloWorld' GUI created purely from code.
+// This sample demonstrates:
+// - Creation of controls and building a UI hierarchy
+// - Loading UI style from XML and applying it to controls
+// - Handling of global and per-control events
+// For more advanced users (beginners can skip this section):
+// - Dragging UIElements
+// - Displaying tooltips
+// - Accessing available Events data (eventData)
+
+#include "Scripts/Utilities/Sample.as"
+
+Window@ window;
+IntVector2 dragBeginPosition = IntVector2::ZERO;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Enable OS cursor
+ input.mouseVisible = true;
+
+ // Load XML file containing default UI style sheet
+ XMLFile@ style = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+
+ // Set the loaded style as default style
+ ui.root.defaultStyle = style;
+
+ // Initialize Window
+ InitWindow();
+
+ // Create and add some controls to the Window
+ InitControls();
+
+ // Create a draggable Fish
+ CreateDraggableFish();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+}
+
+void InitControls()
+{
+ // Create a CheckBox
+ CheckBox@ checkBox = CheckBox();
+ checkBox.name = "CheckBox";
+
+ // Create a Button
+ Button@ button = Button();
+ button.name = "Button";
+ button.minHeight = 24;
+
+ // Create a LineEdit
+ LineEdit@ lineEdit = LineEdit();
+ lineEdit.name = "LineEdit";
+ lineEdit.minHeight = 24;
+
+ // Add controls to Window
+ window.AddChild(checkBox);
+ window.AddChild(button);
+ window.AddChild(lineEdit);
+
+ // Apply previously set default style
+ checkBox.SetStyleAuto();
+ button.SetStyleAuto();
+ lineEdit.SetStyleAuto();
+}
+
+void InitWindow()
+{
+ // Create the Window and add it to the UI's root node
+ window = Window();
+ ui.root.AddChild(window);
+
+ // Set Window size and layout settings
+ window.minWidth = 384;
+ window.SetLayout(LM_VERTICAL, 6, IntRect(6, 6, 6, 6));
+ window.SetAlignment(HA_CENTER, VA_CENTER);
+ window.name = "Window";
+
+ // Create Window 'titlebar' container
+ UIElement@ titleBar = UIElement();
+ titleBar.SetMinSize(0, 24);
+ titleBar.verticalAlignment = VA_TOP;
+ titleBar.layoutMode = LM_HORIZONTAL;
+
+ // Create the Window title Text
+ Text@ windowTitle = Text();
+ windowTitle.name = "WindowTitle";
+ windowTitle.text = "Hello GUI!";
+
+ // Create the Window's close button
+ Button@ buttonClose = Button();
+ buttonClose.name = "CloseButton";
+
+ // Add the controls to the title bar
+ titleBar.AddChild(windowTitle);
+ titleBar.AddChild(buttonClose);
+
+ // Add the title bar to the Window
+ window.AddChild(titleBar);
+
+ // Apply styles
+ window.SetStyleAuto();
+ windowTitle.SetStyleAuto();
+ buttonClose.style = "CloseButton";
+
+ // Subscribe to buttonClose release (following a 'press') events
+ SubscribeToEvent(buttonClose, "Released", "HandleClosePressed");
+
+ // Subscribe also to all UI mouse clicks just to see where we have clicked
+ SubscribeToEvent("UIMouseClick", "HandleControlClicked");
+}
+
+void CreateDraggableFish()
+{
+ // Create a draggable Fish button
+ Button@ draggableFish = ui.root.CreateChild("Button", "Fish");
+ draggableFish.texture = cache.GetResource("Texture2D", "Textures/UrhoDecal.dds"); // Set texture
+ draggableFish.blendMode = BLEND_ADD;
+ draggableFish.SetSize(128, 128);
+ draggableFish.SetPosition((graphics.width - draggableFish.width) / 2, 200);
+
+ // Add a tooltip to Fish button
+ ToolTip@ toolTip = draggableFish.CreateChild("ToolTip");
+ toolTip.position = IntVector2(draggableFish.width + 5, draggableFish.width/2); // slightly offset from fish
+ BorderImage@ textHolder = toolTip.CreateChild("BorderImage");
+ textHolder.SetStyle("ToolTipBorderImage");
+ Text@ toolTipText = textHolder.CreateChild("Text");
+ toolTipText.SetStyle("ToolTipText");
+ toolTipText.text = "Please drag me!";
+
+ // Subscribe draggableFish to Drag Events (in order to make it draggable)
+ // See "Event list" in documentation's Main Page for reference on available Events and their eventData
+ SubscribeToEvent(draggableFish, "DragBegin", "HandleDragBegin");
+ SubscribeToEvent(draggableFish, "DragMove", "HandleDragMove");
+ SubscribeToEvent(draggableFish, "DragEnd", "HandleDragEnd");
+}
+
+void HandleDragBegin(StringHash eventType, VariantMap& eventData)
+{
+ // Get UIElement relative position where input (touch or click) occurred (top-left = IntVector2(0,0))
+ dragBeginPosition = IntVector2(eventData["ElementX"].GetInt(), eventData["ElementY"].GetInt());
+}
+
+void HandleDragMove(StringHash eventType, VariantMap& eventData)
+{
+ IntVector2 dragCurrentPosition = IntVector2(eventData["X"].GetInt(), eventData["Y"].GetInt());
+ // Get the element (fish) that is being dragged. GetPtr() returns a RefCounted handle which can be cast implicitly
+ UIElement@ draggedElement = eventData["Element"].GetPtr();
+ draggedElement.position = dragCurrentPosition - dragBeginPosition;
+}
+
+void HandleDragEnd(StringHash eventType, VariantMap& eventData) // For reference (not used here)
+{
+}
+
+void HandleClosePressed(StringHash eventType, VariantMap& eventData)
+{
+ engine.Exit();
+}
+
+void HandleControlClicked(StringHash eventType, VariantMap& eventData)
+{
+ // Get the Text control acting as the Window's title
+ Text@ windowTitle = window.GetChild("WindowTitle", true);
+
+ // Get control that was clicked
+ UIElement@ clicked = eventData["Element"].GetPtr();
+
+ String name = "...?";
+ if (clicked !is null)
+ {
+ // Get the name of the control that was clicked
+ name = clicked.name;
+ }
+
+ // Update the Window's title text
+ windowTitle.text = "Hello " + name + "!";
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/03_Sprites.as b/bin/Data/Scripts/03_Sprites.as
new file mode 100644
index 0000000..337f024
--- /dev/null
+++ b/bin/Data/Scripts/03_Sprites.as
@@ -0,0 +1,119 @@
+// Moving sprites example.
+// This sample demonstrates:
+// - Adding Sprite elements to the UI
+// - Storing custom data (sprite velocity) inside UI elements
+// - Handling frame update events in which the sprites are moved
+
+#include "Scripts/Utilities/Sample.as"
+
+// Number of sprites to draw
+const uint NUM_SPRITES = 100;
+
+Array sprites;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the sprites to the user interface
+ CreateSprites();
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+}
+
+void CreateSprites()
+{
+ // Get rendering window size as floats
+ float width = graphics.width;
+ float height = graphics.height;
+
+ // Get the Urho3D fish texture
+ Texture2D@ decalTex = cache.GetResource("Texture2D", "Textures/UrhoDecal.dds");
+
+ for (uint i = 0; i < NUM_SPRITES; ++i)
+ {
+ // Create a new sprite, set it to use the texture
+ Sprite@ sprite = Sprite();
+ sprite.texture = decalTex;
+
+ // The UI root element is as big as the rendering window, set random position within it
+ sprite.position = Vector2(Random() * width, Random() * height);
+
+ // Set sprite size & hotspot in its center
+ sprite.size = IntVector2(128, 128);
+ sprite.hotSpot = IntVector2(64, 64);
+
+ // Set random rotation in degrees and random scale
+ sprite.rotation = Random() * 360.0f;
+ sprite.SetScale(Random(1.0f) + 0.5f);
+
+ // Set random color and additive blending mode
+ sprite.color = Color(Random(0.5f) + 0.5f, Random(0.5f) + 0.5f, Random(0.5f) + 0.5f);
+ sprite.blendMode = BLEND_ADD;
+
+ // Add as a child of the root UI element
+ ui.root.AddChild(sprite);
+
+ // Store sprite's velocity as a custom variable
+ sprite.vars["Velocity"] = Vector2(Random(200.0f) - 100.0f, Random(200.0f) - 100.0f);
+
+ // Store sprites to our own container for easy movement update iteration
+ sprites.Push(sprite);
+ }
+}
+
+void MoveSprites(float timeStep)
+{
+ float width = graphics.width;
+ float height = graphics.height;
+
+ // Go through all sprites
+ for (uint i = 0; i < sprites.length; ++i)
+ {
+ Sprite@ sprite = sprites[i];
+
+ // Rotate
+ float newRot = sprite.rotation + timeStep * 30.0f;
+ sprite.rotation = newRot;
+
+ // Move, wrap around rendering window edges
+ Vector2 newPos = sprite.position + sprite.vars["Velocity"].GetVector2() * timeStep;
+ if (newPos.x < 0.0f)
+ newPos.x += width;
+ if (newPos.x >= width)
+ newPos.x -= width;
+ if (newPos.y < 0.0f)
+ newPos.y += height;
+ if (newPos.y >= height)
+ newPos.y -= height;
+ sprite.position = newPos;
+ }
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move sprites, scale movement with time step
+ MoveSprites(timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/04_StaticScene.as b/bin/Data/Scripts/04_StaticScene.as
new file mode 100644
index 0000000..ab77ea1
--- /dev/null
+++ b/bin/Data/Scripts/04_StaticScene.as
@@ -0,0 +1,154 @@
+// Static 3D scene example.
+// This sample demonstrates:
+// - Creating a 3D scene with static content
+// - Displaying the scene using the Renderer subsystem
+// - Handling keyboard and mouse input to move a freelook camera
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+
+ // Create a child scene node (at world origin) and a StaticModel component into it. Set the StaticModel to show a simple
+ // plane mesh with a "stone" material. Note that naming the scene nodes is optional. Scale the scene node larger
+ // (100 x 100 world units)
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(100.0f, 1.0f, 100.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Create a directional light to the world so that we can see something. The light scene node's orientation controls the
+ // light direction; we will use the SetDirection() function which calculates the orientation from a forward direction vector.
+ // The light will use default settings (white light, no shadows)
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f); // The direction vector does not need to be normalized
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+
+ // Create more StaticModel objects to the scene, randomly positioned, rotated and scaled. For rotation, we construct a
+ // quaternion from Euler angles where the Y angle (rotation about the Y axis) is randomized. The mushroom model contains
+ // LOD levels, so the StaticModel component will automatically select the LOD level according to the view distance (you'll
+ // see the model get simpler as it moves further away). Finally, rendering a large number of the same object with the
+ // same material allows instancing to be used, if the GPU supports it. This reduces the amount of CPU work in rendering the
+ // scene.
+ const uint NUM_OBJECTS = 200;
+ for (uint i = 0; i < NUM_OBJECTS; ++i)
+ {
+ Node@ mushroomNode = scene_.CreateChild("Mushroom");
+ mushroomNode.position = Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f);
+ mushroomNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+ mushroomNode.SetScale(0.5f + Random(2.0f));
+ StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+ mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ mushroomObject.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ }
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ cameraNode.CreateComponent("Camera");
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ // Use the Translate() function (default local space) to move relative to the node's orientation.
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/05_AnimatingScene.as b/bin/Data/Scripts/05_AnimatingScene.as
new file mode 100644
index 0000000..2bb935a
--- /dev/null
+++ b/bin/Data/Scripts/05_AnimatingScene.as
@@ -0,0 +1,167 @@
+// Animating 3D scene example.
+// This sample demonstrates:
+// - Creating a 3D scene and using a script component to animate the objects
+// - Controlling scene ambience with the Zone component
+// - Attaching a light to an object (the camera)
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene so that drawable objects can be rendered. Use default volume
+ // (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_.CreateComponent("Octree");
+
+ // Create a Zone component into a child scene node. The Zone controls ambient lighting and fog settings. Like the Octree,
+ // it also defines its volume with a bounding box, but can be rotated (so it does not need to be aligned to the world X, Y
+ // and Z axes.) Drawable objects "pick up" the zone they belong to and use it when rendering; several zones can exist
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ // Set same volume as the Octree, set a close bluish fog and some ambient light
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.05f, 0.1f, 0.15f);
+ zone.fogColor = Color(0.1f, 0.2f, 0.3f);
+ zone.fogStart = 10.0f;
+ zone.fogEnd = 100.0f;
+
+ // Create randomly positioned and oriented box StaticModels in the scene
+ const uint NUM_OBJECTS = 2000;
+ for (uint i = 0; i < NUM_OBJECTS; ++i)
+ {
+ Node@ boxNode = scene_.CreateChild("Box");
+ boxNode.position = Vector3(Random(200.0f) - 100.0f, Random(200.0f) - 100.0f, Random(200.0f) - 100.0f);
+ // Orient using random pitch, yaw and roll Euler angles
+ boxNode.rotation = Quaternion(Random(360.0f), Random(360.0f), Random(360.0f));
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+
+ // Add the Rotator script object which will rotate the scene node each frame, when the scene sends its update event.
+ // This requires the C++ component ScriptInstance in the scene node, which acts as a container. We need to tell the
+ // script file and class name to instantiate the object (scriptFile is a global property which refers to the currently
+ // executing script file.) There is also a shortcut for creating the ScriptInstance component and the script object,
+ // which is shown in a later sample, but this is what happens "under the hood."
+ ScriptInstance@ instance = boxNode.CreateComponent("ScriptInstance");
+ instance.CreateObject(scriptFile, "Rotator");
+ // Retrieve the created script object and set its rotation speed member variable
+ Rotator@ rotator = cast(instance.scriptObject);
+ rotator.rotationSpeed = Vector3(10.0f, 20.0f, 30.0f);
+ }
+
+ // Create the camera. Let the starting position be at the world origin. As the fog limits maximum visible distance, we can
+ // bring the far clip plane closer for more effective culling of distant objects
+ cameraNode = scene_.CreateChild("Camera");
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 100.0f;
+
+ // Create a point light to the camera scene node
+ Light@ light = cameraNode.CreateComponent("Light");
+ light.lightType = LIGHT_POINT;
+ light.range = 30.0f;
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+// Rotator script object class. Script objects to be added to a scene node must implement the empty ScriptObject interface
+class Rotator : ScriptObject
+{
+ Vector3 rotationSpeed;
+
+ // Update is called during the variable timestep scene update
+ void Update(float timeStep)
+ {
+ node.Rotate(Quaternion(rotationSpeed.x * timeStep, rotationSpeed.y * timeStep, rotationSpeed.z * timeStep));
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/06_SkeletalAnimation.as b/bin/Data/Scripts/06_SkeletalAnimation.as
new file mode 100644
index 0000000..003695c
--- /dev/null
+++ b/bin/Data/Scripts/06_SkeletalAnimation.as
@@ -0,0 +1,242 @@
+// Skeletal animation example.
+// This sample demonstrates:
+// - Populating a 3D scene with skeletally animated AnimatedModel components
+// - Moving the animated models and advancing their animation using a script object
+// - Enabling a cascaded shadow map on a directional light, which allows high-quality shadows
+// over a large area (typically used in outdoor scenes for shadows cast by sunlight)
+// - Displaying renderer debug geometry
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update and render post-update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ // Also create a DebugRenderer component so that we can draw debug geometry
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create scene node & StaticModel component for showing a static plane
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(50.0f, 1.0f, 50.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.5f, 0.5f, 0.5f);
+ zone.fogColor = Color(0.4f, 0.5f, 0.8f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.color = Color(0.5f, 0.5f, 0.5f);
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ // Create animated models
+ const uint NUM_MODELS = 30;
+ const float MODEL_MOVE_SPEED = 2.0f;
+ const float MODEL_ROTATE_SPEED = 100.0f;
+ const BoundingBox bounds(Vector3(-20.0f, 0.0f, -20.0f), Vector3(20.0f, 0.0f, 20.0f));
+
+ for (uint i = 0; i < NUM_MODELS; ++i)
+ {
+ Node@ modelNode = scene_.CreateChild("Jill");
+ modelNode.position = Vector3(Random(40.0f) - 20.0f, 0.0f, Random(40.0f) - 20.0f);
+ modelNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+
+ AnimatedModel@ modelObject = modelNode.CreateComponent("AnimatedModel");
+ modelObject.model = cache.GetResource("Model", "Models/Kachujin/Kachujin.mdl");
+ modelObject.material = cache.GetResource("Material", "Models/Kachujin/Materials/Kachujin.xml");
+ modelObject.castShadows = true;
+
+ // Create an AnimationState for a walk animation. Its time position will need to be manually updated to advance the
+ // animation, The alternative would be to use an AnimationController component which updates the animation automatically,
+ // but we need to update the model's position manually in any case
+ Animation@ walkAnimation = cache.GetResource("Animation", "Models/Kachujin/Kachujin_Walk.ani");
+ AnimationState@ state = modelObject.AddAnimationState(walkAnimation);
+ // Enable full blending weight and looping
+ state.weight = 1.0f;
+ state.looped = true;
+ state.time = Random(walkAnimation.length);
+
+ // Create our Mover script object that will move & animate the model during each frame's update. Here we use a shortcut
+ // script-only API function, CreateScriptObject, which creates a ScriptInstance component into the scene node, then uses
+ // it to instantiate the object (using the script file & class name provided)
+ Mover@ mover = cast(modelNode.CreateScriptObject(scriptFile, "Mover"));
+ mover.SetParameters(MODEL_MOVE_SPEED, MODEL_ROTATE_SPEED, bounds);
+ }
+
+ // Create the camera. Limit far clip distance to match the fog
+ cameraNode = scene_.CreateChild("Camera");
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys and mouse to move\n"
+ "Space to toggle debug geometry";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, sent after Renderer subsystem is
+ // done with defining the draw calls for the viewports (but before actually executing them.) We will request debug geometry
+ // rendering during that event
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ // Toggle debug geometry with space
+ if (input.keyPress[KEY_SPACE])
+ drawDebug = !drawDebug;
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // If draw debug mode is enabled, draw viewport debug geometry, which will show eg. drawable bounding boxes and skeleton
+ // bones. Note that debug geometry has to be separately requested each frame. Disable depth test so that we can see the
+ // bones properly
+ if (drawDebug)
+ renderer.DrawDebugGeometry(false);
+}
+
+// Mover script object class
+class Mover : ScriptObject
+{
+ float moveSpeed = 0.0f;
+ float rotationSpeed = 0.0f;
+ BoundingBox bounds;
+
+ void SetParameters(float moveSpeed_, float rotationSpeed_, const BoundingBox& bounds_)
+ {
+ moveSpeed = moveSpeed_;
+ rotationSpeed = rotationSpeed_;
+ bounds = bounds_;
+ }
+
+ void Update(float timeStep)
+ {
+ node.Translate(Vector3::FORWARD * moveSpeed * timeStep);
+
+ // If in risk of going outside the plane, rotate the model right
+ Vector3 pos = node.position;
+ if (pos.x < bounds.min.x || pos.x > bounds.max.x || pos.z < bounds.min.z || pos.z > bounds.max.z)
+ node.Yaw(rotationSpeed * timeStep);
+
+ // Get the model's first (only) animation state and advance its time
+ AnimatedModel@ model = node.GetComponent("AnimatedModel", true);
+ AnimationState@ state = model.GetAnimationState(0);
+ if (state !is null)
+ state.AddTime(timeStep);
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ ""+
+ " "+
+ " Debug"+
+ " "+
+ " "+
+ " "+
+ " "+
+ " "+
+ " "+
+ "";
+
diff --git a/bin/Data/Scripts/07_Billboards.as b/bin/Data/Scripts/07_Billboards.as
new file mode 100644
index 0000000..19c3f0b
--- /dev/null
+++ b/bin/Data/Scripts/07_Billboards.as
@@ -0,0 +1,290 @@
+// Billboard example.
+// This sample demonstrates:
+// - Populating a 3D scene with billboard sets and several shadow casting spotlights
+// - Parenting scene nodes to allow more intuitive creation of groups of objects
+// - Examining rendering performance with a somewhat large object and light count
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update and render post-update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ // Also create a DebugRenderer component so that we can draw debug geometry
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.1f, 0.1f, 0.1f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light without shadows
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.5f, -1.0f, 0.5f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.color = Color(0.2f, 0.2f, 0.2f);
+ light.specularIntensity = 1.0f;
+
+ // Create a "floor" consisting of several tiles
+ for (int y = -5; y <= 5; ++y)
+ {
+ for (int x = -5; x <= 5; ++x)
+ {
+ Node@ floorNode = scene_.CreateChild("FloorTile");
+ floorNode.position = Vector3(x * 20.5f, -0.5f, y * 20.5f);
+ floorNode.scale = Vector3(20.0f, 1.0f, 20.f);
+ StaticModel@ floorObject = floorNode.CreateComponent("StaticModel");
+ floorObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ floorObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+ }
+ }
+
+ // Create groups of mushrooms, which act as shadow casters
+ const uint NUM_MUSHROOMGROUPS = 25;
+ const uint NUM_MUSHROOMS = 25;
+
+ for (uint i = 0; i < NUM_MUSHROOMGROUPS; ++i)
+ {
+ // First create a scene node for the group. The individual mushrooms nodes will be created as children
+ Node@ groupNode = scene_.CreateChild("MushroomGroup");
+ groupNode.position = Vector3(Random(190.0f) - 95.0f, 0.0f, Random(190.0f) - 95.0f);
+
+ for (uint j = 0; j < NUM_MUSHROOMS; ++j)
+ {
+ Node@ mushroomNode = groupNode.CreateChild("Mushroom");
+ mushroomNode.position = Vector3(Random(25.0f) - 12.5f, 0.0f, Random(25.0f) - 12.5f);
+ mushroomNode.rotation = Quaternion(0.0f, Random() * 360.0f, 0.0f);
+ mushroomNode.SetScale(1.0f + Random() * 4.0f);
+ StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+ mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ mushroomObject.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ mushroomObject.castShadows = true;
+ }
+ }
+
+ // Create billboard sets (floating smoke)
+ const uint NUM_BILLBOARDNODES = 25;
+ const uint NUM_BILLBOARDS = 10;
+
+ for (uint i = 0; i < NUM_BILLBOARDNODES; ++i)
+ {
+ Node@ smokeNode = scene_.CreateChild("Smoke");
+ smokeNode.position = Vector3(Random(200.0f) - 100.0f, Random(20.0f) + 10.0f, Random(200.0f) - 100.0f);
+
+ BillboardSet@ billboardObject = smokeNode.CreateComponent("BillboardSet");
+ billboardObject.numBillboards = NUM_BILLBOARDS;
+ billboardObject.material = cache.GetResource("Material", "Materials/LitSmoke.xml");
+ billboardObject.sorted = true;
+
+ for (uint j = 0; j < NUM_BILLBOARDS; ++j)
+ {
+ Billboard@ bb = billboardObject.billboards[j];
+ bb.position = Vector3(Random(12.0f) - 6.0f, Random(8.0f) - 4.0f, Random(12.0f) - 6.0f);
+ bb.size = Vector2(Random(2.0f) + 3.0f, Random(2.0f) + 3.0f);
+ bb.rotation = Random() * 360.0f;
+ bb.enabled = true;
+ }
+
+ // After modifying the billboards, they need to be "committed" so that the BillboardSet updates its internals
+ billboardObject.Commit();
+ }
+
+ // Create shadow casting spotlights
+ const uint NUM_LIGHTS = 9;
+
+ for (uint i = 0; i < NUM_LIGHTS; ++i)
+ {
+ Node@ lightNode = scene_.CreateChild("SpotLight");
+ Light@ light = lightNode.CreateComponent("Light");
+
+ float angle = 0.0f;
+
+ Vector3 position((i % 3) * 60.0f - 60.0f, 45.0f, (i / 3) * 60.0f - 60.0f);
+ Color color(((i + 1) & 1) * 0.5f + 0.5f, (((i + 1) >> 1) & 1) * 0.5f + 0.5f, (((i + 1) >> 2) & 1) * 0.5f + 0.5f);
+
+ lightNode.position = position;
+ lightNode.direction = Vector3(Sin(angle), -1.5f, Cos(angle));
+
+ light.lightType = LIGHT_SPOT;
+ light.range = 90.0f;
+ light.rampTexture = cache.GetResource("Texture2D", "Textures/RampExtreme.png");
+ light.fov = 45.0f;
+ light.color = color;
+ light.specularIntensity = 1.0f;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00002f, 0.0f);
+
+ // Configure shadow fading for the lights. When they are far away enough, the lights eventually become unshadowed for
+ // better GPU performance. Note that we could also set the maximum distance for each object to cast shadows
+ light.shadowFadeDistance = 100.0f; // Fade start distance
+ light.shadowDistance = 125.0f; // Fade end distance, shadows are disabled
+ // Set half resolution for the shadow maps for increased performance
+ light.shadowResolution = 0.5f;
+ // The spot lights will not have anything near them, so move the near plane of the shadow camera farther
+ // for better shadow depth resolution
+ light.shadowNearFarRatio = 0.01f;
+ }
+
+ // Create the camera. Limit far clip distance to match the fog
+ cameraNode = scene_.CreateChild("Camera");
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys and mouse to move\n"
+ "Space to toggle debug geometry";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ // debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ // Toggle debug geometry with space
+ if (input.keyPress[KEY_SPACE])
+ drawDebug = !drawDebug;
+}
+
+void AnimateScene(float timeStep)
+{
+ // Get the light and billboard scene nodes
+ Array lightNodes = scene_.GetChildrenWithComponent("Light");
+ Array billboardNodes = scene_.GetChildrenWithComponent("BillboardSet");
+
+ const float LIGHT_ROTATION_SPEED = 20.0f;
+ const float BILLBOARD_ROTATION_SPEED = 50.0f;
+
+ // Rotate the lights around the world Y-axis
+ for (uint i = 0; i < lightNodes.length; ++i)
+ lightNodes[i].Rotate(Quaternion(0.0f, LIGHT_ROTATION_SPEED * timeStep, 0.0f), TS_WORLD);
+
+ // Rotate the individual billboards within the billboard sets, then recommit to make the changes visible
+ for (uint i = 0; i < billboardNodes.length; ++i)
+ {
+ BillboardSet@ billboardObject = billboardNodes[i].GetComponent("BillboardSet");
+
+ for (uint j = 0; j < billboardObject.numBillboards; ++j)
+ {
+ Billboard@ bb = billboardObject.billboards[j];
+ bb.rotation += BILLBOARD_ROTATION_SPEED * timeStep;
+ }
+
+ billboardObject.Commit();
+ }
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera and animate the scene, scale movement with time step
+ MoveCamera(timeStep);
+ AnimateScene(timeStep);
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // If draw debug mode is enabled, draw viewport debug geometry. This time use depth test, as otherwise the result becomes
+ // hard to interpret due to large object count
+ if (drawDebug)
+ renderer.DrawDebugGeometry(true);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Debug" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/08_Decals.as b/bin/Data/Scripts/08_Decals.as
new file mode 100644
index 0000000..bd2307d
--- /dev/null
+++ b/bin/Data/Scripts/08_Decals.as
@@ -0,0 +1,285 @@
+// Decals example.
+// This sample demonstrates:
+// - Performing a raycast to the octree and adding a decal to the hit location
+// - Defining a Cursor UI element which stays inside the window and can be shown/hidden
+// - Marking suitable (large) objects as occluders for occlusion culling
+// - Displaying renderer debug geometry to see the effect of occlusion
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateUI();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update and render post-update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ // Also create a DebugRenderer component so that we can draw debug geometry
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create scene node & StaticModel component for showing a static plane
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(100.0f, 1.0f, 100.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(0.5f, 0.5f, 0.7f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ // Create some mushrooms
+ const uint NUM_MUSHROOMS = 240;
+ for (uint i = 0; i < NUM_MUSHROOMS; ++i)
+ {
+ Node@ mushroomNode = scene_.CreateChild("Mushroom");
+ mushroomNode.position = Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f);
+ mushroomNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+ mushroomNode.SetScale(0.5f + Random(2.0f));
+ StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+ mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ mushroomObject.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ mushroomObject.castShadows = true;
+ }
+
+ // Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+ // rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+ const uint NUM_BOXES = 20;
+ for (uint i = 0; i < NUM_BOXES; ++i)
+ {
+ Node@ boxNode = scene_.CreateChild("Box");
+ float size = 1.0f + Random(10.0f);
+ boxNode.position = Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f);
+ boxNode.SetScale(size);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+ boxObject.castShadows = true;
+ if (size >= 3.0f)
+ boxObject.occluder = true;
+ }
+
+ // Create the camera. Limit far clip distance to match the fog
+ cameraNode = scene_.CreateChild("Camera");
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+}
+
+void CreateUI()
+{
+ // Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ // control the camera, and when visible, it will point the raycast target
+ XMLFile@ style = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+ Cursor@ cursor = Cursor();
+ cursor.SetStyleAuto(style);
+ ui.cursor = cursor;
+ // Set starting position of the cursor at the rendering window center
+ cursor.SetPosition(graphics.width / 2, graphics.height / 2);
+
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys to move\n"
+ "LMB to paint decals, RMB to rotate view\n"
+ "Space to toggle debug geometry\n"
+ "7 to toggle occlusion culling";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ // debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ input.mouseVisible = input.mouseMode != MM_RELATIVE;
+ bool mouseDown = input.mouseButtonDown[MOUSEB_RIGHT];
+
+ // Override the MM_RELATIVE mouse grabbed settings, to allow interaction with UI
+ input.mouseGrabbed = mouseDown;
+
+ // Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = !mouseDown;
+
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ // Only move the camera when the cursor is hidden
+ if (!ui.cursor.visible)
+ {
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+ }
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ // Toggle debug geometry with space
+ if (input.keyPress[KEY_SPACE])
+ drawDebug = !drawDebug;
+
+ // Paint decal with the left mousebutton; cursor must be visible
+ if (ui.cursor.visible && input.mouseButtonPress[MOUSEB_LEFT])
+ PaintDecal();
+}
+
+void PaintDecal()
+{
+ Vector3 hitPos;
+ Drawable@ hitDrawable;
+
+ if (Raycast(250.0f, hitPos, hitDrawable))
+ {
+ // Check if target scene node already has a DecalSet component. If not, create now
+ Node@ targetNode = hitDrawable.node;
+ DecalSet@ decal = targetNode.GetComponent("DecalSet");
+ if (decal is null)
+ {
+ decal = targetNode.CreateComponent("DecalSet");
+ decal.material = cache.GetResource("Material", "Materials/UrhoDecal.xml");
+ }
+ // Add a square decal to the decal set using the geometry of the drawable that was hit, orient it to face the camera,
+ // use full texture UV's (0,0) to (1,1). Note that if we create several decals to a large object (such as the ground
+ // plane) over a large area using just one DecalSet component, the decals will all be culled as one unit. If that is
+ // undesirable, it may be necessary to create more than one DecalSet based on the distance
+ decal.AddDecal(hitDrawable, hitPos, cameraNode.rotation, 0.5f, 1.0f, 1.0f, Vector2::ZERO, Vector2::ONE);
+ }
+}
+
+bool Raycast(float maxDistance, Vector3& hitPos, Drawable@& hitDrawable)
+{
+ hitDrawable = null;
+
+ IntVector2 pos = ui.cursorPosition;
+ // Check the cursor is visible and there is no UI element in front of the cursor
+ if (!ui.cursor.visible || ui.GetElementAt(pos, true) !is null)
+ return false;
+
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
+ // Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
+ // Note the convenience accessor to scene's Octree component
+ RayQueryResult result = scene_.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY);
+ if (result.drawable !is null)
+ {
+ hitPos = result.position;
+ hitDrawable = result.drawable;
+ return true;
+ }
+
+ return false;
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // If draw debug mode is enabled, draw viewport debug geometry. Disable depth test so that we can see the effect of occlusion
+ if (drawDebug)
+ renderer.DrawDebugGeometry(false);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Paint" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Debug" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/09_MultipleViewports.as b/bin/Data/Scripts/09_MultipleViewports.as
new file mode 100644
index 0000000..cbef0f7
--- /dev/null
+++ b/bin/Data/Scripts/09_MultipleViewports.as
@@ -0,0 +1,274 @@
+// Multiple viewports example.
+// This sample demonstrates:
+// - Setting up two viewports with two separate cameras
+// - Adding post processing effects to a viewport's render path and toggling them
+
+#include "Scripts/Utilities/Sample.as"
+
+Node@ rearCameraNode;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewports for displaying the scene
+ SetupViewports();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update and render post-update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ // Also create a DebugRenderer component so that we can draw debug geometry
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create scene node & StaticModel component for showing a static plane
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(100.0f, 1.0f, 100.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(0.5f, 0.5f, 0.7f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ // Create some mushrooms
+ const uint NUM_MUSHROOMS = 240;
+ for (uint i = 0; i < NUM_MUSHROOMS; ++i)
+ {
+ Node@ mushroomNode = scene_.CreateChild("Mushroom");
+ mushroomNode.position = Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f);
+ mushroomNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+ mushroomNode.SetScale(0.5f + Random(2.0f));
+ StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+ mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ mushroomObject.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ mushroomObject.castShadows = true;
+ }
+
+ // Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+ // rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+ const uint NUM_BOXES = 20;
+ for (uint i = 0; i < NUM_BOXES; ++i)
+ {
+ Node@ boxNode = scene_.CreateChild("Box");
+ float size = 1.0f + Random(10.0f);
+ boxNode.position = Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f);
+ boxNode.SetScale(size);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+ boxObject.castShadows = true;
+ if (size >= 3.0f)
+ boxObject.occluder = true;
+ }
+
+ // Create the cameras. Limit far clip distance to match the fog
+ cameraNode = scene_.CreateChild("Camera");
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+
+ // Parent the rear camera node to the front camera node and turn it 180 degrees to face backward
+ // Here, we use the angle-axis constructor for Quaternion instead of the usual Euler angles
+ rearCameraNode = cameraNode.CreateChild("RearCamera");
+ rearCameraNode.Rotate(Quaternion(180.0f, Vector3::UP));
+ Camera@ rearCamera = rearCameraNode.CreateComponent("Camera");
+ rearCamera.farClip = 300.0f;
+ // Because the rear viewport is rather small, disable occlusion culling from it. Use the camera's
+ // "view override flags" for this. We could also disable eg. shadows or force low material quality
+ // if we wanted
+ rearCamera.viewOverrideFlags = VO_DISABLE_OCCLUSION;
+
+ // Set an initial position for the front camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys and mouse to move\n"
+ "B to toggle bloom, F to toggle FXAA\n"
+ "Space to toggle debug geometry\n";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewports()
+{
+ renderer.numViewports = 2;
+
+ // Set up the front camera viewport
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+
+ // Clone the default render path so that we do not interfere with the other viewport, then add
+ // bloom and FXAA post process effects to the front viewport. Render path commands can be tagged
+ // for example with the effect name to allow easy toggling on and off. We start with the effects
+ // disabled.
+ RenderPath@ effectRenderPath = viewport.renderPath.Clone();
+ effectRenderPath.Append(cache.GetResource("XMLFile", "PostProcess/Bloom.xml"));
+ effectRenderPath.Append(cache.GetResource("XMLFile", "PostProcess/FXAA2.xml"));
+ // Make the bloom mixing parameter more pronounced
+ effectRenderPath.shaderParameters["BloomMix"] = Variant(Vector2(0.9f, 0.6f));
+ effectRenderPath.SetEnabled("Bloom", false);
+ effectRenderPath.SetEnabled("FXAA2", false);
+ viewport.renderPath = effectRenderPath;
+
+ // Set up the rear camera viewport on top of the front view ("rear view mirror")
+ // The viewport index must be greater in that case, otherwise the view would be left behind
+ Viewport@ rearViewport = Viewport(scene_, rearCameraNode.GetComponent("Camera"),
+ IntRect(graphics.width * 2 / 3, 32, graphics.width - 32, graphics.height / 3));
+ renderer.viewports[1] = rearViewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ // debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ // Toggle post processing effects on the front viewport. Note that the rear viewport is unaffected
+ RenderPath@ effectRenderPath = renderer.viewports[0].renderPath;
+ if (input.keyPress[KEY_B])
+ effectRenderPath.ToggleEnabled("Bloom");
+ if (input.keyPress[KEY_F])
+ effectRenderPath.ToggleEnabled("FXAA2");
+
+ // Toggle debug geometry with space
+ if (input.keyPress[KEY_SPACE])
+ drawDebug = !drawDebug;
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // If draw debug mode is enabled, draw viewport debug geometry. Disable depth test so that we can see the effect of occlusion
+ if (drawDebug)
+ renderer.DrawDebugGeometry(false);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Bloom" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Debug" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/10_RenderToTexture.as b/bin/Data/Scripts/10_RenderToTexture.as
new file mode 100644
index 0000000..9c7feb0
--- /dev/null
+++ b/bin/Data/Scripts/10_RenderToTexture.as
@@ -0,0 +1,247 @@
+// Render to texture example
+// This sample demonstrates:
+// - Creating two 3D scenes and rendering the other into a texture
+// - Creating rendertarget texture and material programmatically
+
+#include "Scripts/Utilities/Sample.as"
+
+Scene@ rttScene_;
+Node@ rttCameraNode;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ {
+ // Create the scene which will be rendered to a texture
+ rttScene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ rttScene_.CreateComponent("Octree");
+
+ // Create a Zone for ambient light & fog control
+ Node@ zoneNode = rttScene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ // Set same volume as the Octree, set a close bluish fog and some ambient light
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.05f, 0.1f, 0.15f);
+ zone.fogColor = Color(0.1f, 0.2f, 0.3f);
+ zone.fogStart = 10.0f;
+ zone.fogEnd = 100.0f;
+
+ // Create randomly positioned and oriented box StaticModels in the scene
+ const uint NUM_OBJECTS = 2000;
+ for (uint i = 0; i < NUM_OBJECTS; ++i)
+ {
+ Node@ boxNode = rttScene_.CreateChild("Box");
+ boxNode.position = Vector3(Random(200.0f) - 100.0f, Random(200.0f) - 100.0f, Random(200.0f) - 100.0f);
+ // Orient using random pitch, yaw and roll Euler angles
+ boxNode.rotation = Quaternion(Random(360.0f), Random(360.0f), Random(360.0f));
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+
+ // Add our custom Rotator component which will rotate the scene node each frame, when the scene sends its update event.
+ // Simply set same rotation speed for all objects
+ Rotator@ rotator = cast(boxNode.CreateScriptObject(scriptFile, "Rotator"));
+ rotator.rotationSpeed = Vector3(10.0f, 20.0f, 30.0f);
+ }
+
+ // Create a camera for the render-to-texture scene. Simply leave it at the world origin and let it observe the scene
+ rttCameraNode = rttScene_.CreateChild("Camera");
+ Camera@ camera = rttCameraNode.CreateComponent("Camera");
+ camera.farClip = 100.0f;
+
+ // Create a point light to the camera scene node
+ Light@ light = rttCameraNode.CreateComponent("Light");
+ light.lightType = LIGHT_POINT;
+ light.range = 30.0f;
+ }
+
+ {
+ // Create the scene in which we move around
+ scene_ = Scene();
+
+ // Create octree, use also default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_.CreateComponent("Octree");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.1f, 0.1f, 0.1f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light without shadows
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.5f, -1.0f, 0.5f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.color = Color(0.2f, 0.2f, 0.2f);
+ light.specularIntensity = 1.0f;
+
+ // Create a "floor" consisting of several tiles
+ for (int y = -5; y <= 5; ++y)
+ {
+ for (int x = -5; x <= 5; ++x)
+ {
+ Node@ floorNode = scene_.CreateChild("FloorTile");
+ floorNode.position = Vector3(x * 20.5f, -0.5f, y * 20.5f);
+ floorNode.scale = Vector3(20.0f, 1.0f, 20.f);
+ StaticModel@ floorObject = floorNode.CreateComponent("StaticModel");
+ floorObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ floorObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+ }
+ }
+
+ // Create a "screen" like object for viewing the second scene. Construct it from two StaticModels, a box for the frame
+ // and a plane for the actual view
+ {
+ Node@ boxNode = scene_.CreateChild("ScreenBox");
+ boxNode.position = Vector3(0.0f, 10.0f, 0.0f);
+ boxNode.scale = Vector3(21.0f, 16.0f, 0.5f);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+
+ Node@ screenNode = scene_.CreateChild("Screen");
+ screenNode.position = Vector3(0.0f, 10.0f, -0.27f);
+ screenNode.rotation = Quaternion(-90.0f, 0.0f, 0.0f);
+ screenNode.scale = Vector3(20.0f, 0.0f, 15.0f);
+ StaticModel@ screenObject = screenNode.CreateComponent("StaticModel");
+ screenObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+
+ // Create a renderable texture (1024x768, RGB format), enable bilinear filtering on it
+ Texture2D@ renderTexture = Texture2D();
+ renderTexture.SetSize(1024, 768, GetRGBFormat(), TEXTURE_RENDERTARGET);
+ renderTexture.filterMode = FILTER_BILINEAR;
+
+ // Create a new material from scratch, use the diffuse unlit technique, assign the render texture
+ // as its diffuse texture, then assign the material to the screen plane object
+ Material@ renderMaterial = Material();
+ renderMaterial.SetTechnique(0, cache.GetResource("Technique", "Techniques/DiffUnlit.xml"));
+ renderMaterial.textures[TU_DIFFUSE] = renderTexture;
+ // Since the screen material is on top of the box model and may Z-fight, use negative depth bias
+ // to push it forward (particularly necessary on mobiles with possibly less Z resolution)
+ renderMaterial.depthBias = BiasParameters(-0.001, 0.0);
+ screenObject.material = renderMaterial;
+
+ // Get the texture's RenderSurface object (exists when the texture has been created in rendertarget mode)
+ // and define the viewport for rendering the second scene, similarly as how backbuffer viewports are defined
+ // to the Renderer subsystem. By default the texture viewport will be updated when the texture is visible
+ // in the main view
+ RenderSurface@ surface = renderTexture.renderSurface;
+ Viewport@ rttViewport = Viewport(rttScene_, rttCameraNode.GetComponent("Camera"));
+ surface.viewports[0] = rttViewport;
+ }
+
+ // Create the camera which we will move around. Limit far clip distance to match the fog
+ cameraNode = scene_.CreateChild("Camera");
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 7.0f, -30.0f);
+ }
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+// Rotator script object class. Script objects to be added to a scene node must implement the empty ScriptObject interface
+class Rotator : ScriptObject
+{
+ Vector3 rotationSpeed;
+
+ // Update is called during the variable timestep scene update
+ void Update(float timeStep)
+ {
+ node.Rotate(Quaternion(rotationSpeed.x * timeStep, rotationSpeed.y * timeStep, rotationSpeed.z * timeStep));
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/11_Physics.as b/bin/Data/Scripts/11_Physics.as
new file mode 100644
index 0000000..ced0cd4
--- /dev/null
+++ b/bin/Data/Scripts/11_Physics.as
@@ -0,0 +1,275 @@
+// Physics example.
+// This sample demonstrates:
+// - Creating both static and moving physics objects to a scene
+// - Displaying physics debug geometry
+// - Using the Skybox component for setting up an unmoving sky
+// - Saving a scene to a file and loading it to restore a previous state
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update and render post-update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ // Create a physics simulation world with default parameters, which will update at 60fps. Like the Octree must
+ // exist before creating drawable components, the PhysicsWorld must exist before creating physics components.
+ // Finally, create a DebugRenderer component so that we can draw physics debug geometry
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("PhysicsWorld");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(1.0f, 1.0f, 1.0f);
+ zone.fogStart = 300.0f;
+ zone.fogEnd = 500.0f;
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ // Create skybox. The Skybox component is used like StaticModel, but it will be always located at the camera, giving the
+ // illusion of the box planes being far away. Use just the ordinary Box model and a suitable material, whose shader will
+ // generate the necessary 3D texture coordinates for cube mapping
+ Node@ skyNode = scene_.CreateChild("Sky");
+ skyNode.SetScale(500.0f); // The scale actually does not matter
+ Skybox@ skybox = skyNode.CreateComponent("Skybox");
+ skybox.model = cache.GetResource("Model", "Models/Box.mdl");
+ skybox.material = cache.GetResource("Material", "Materials/Skybox.xml");
+
+ {
+ // Create a floor object, 1000 x 1000 world units. Adjust position so that the ground is at zero Y
+ Node@ floorNode = scene_.CreateChild("Floor");
+ floorNode.position = Vector3(0.0f, -0.5f, 0.0f);
+ floorNode.scale = Vector3(1000.0f, 1.0f, 1000.0f);
+ StaticModel@ floorObject = floorNode.CreateComponent("StaticModel");
+ floorObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ floorObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Make the floor physical by adding RigidBody and CollisionShape components. The RigidBody's default
+ // parameters make the object static (zero mass.) Note that a CollisionShape by itself will not participate
+ // in the physics simulation
+ RigidBody@ body = floorNode.CreateComponent("RigidBody");
+ CollisionShape@ shape = floorNode.CreateComponent("CollisionShape");
+ // Set a box shape of size 1 x 1 x 1 for collision. The shape will be scaled with the scene node scale, so the
+ // rendering and physics representation sizes should match (the box model is also 1 x 1 x 1.)
+ shape.SetBox(Vector3::ONE);
+ }
+
+ {
+ // Create a pyramid of movable physics objects
+ for (int y = 0; y < 8; ++y)
+ {
+ for (int x = -y; x <= y; ++x)
+ {
+ Node@ boxNode = scene_.CreateChild("Box");
+ boxNode.position = Vector3(x, -y + 8.0f, 0.0f);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/StoneEnvMapSmall.xml");
+ boxObject.castShadows = true;
+
+ // Create RigidBody and CollisionShape components like above. Give the RigidBody mass to make it movable
+ // and also adjust friction. The actual mass is not important; only the mass ratios between colliding
+ // objects are significant
+ RigidBody@ body = boxNode.CreateComponent("RigidBody");
+ body.mass = 1.0f;
+ body.friction = 0.75f;
+ CollisionShape@ shape = boxNode.CreateComponent("CollisionShape");
+ shape.SetBox(Vector3::ONE);
+ }
+ }
+ }
+
+ // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside
+ // the scene, because we want it to be unaffected by scene load / save
+ cameraNode = Node();
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 500.0f;
+
+ // Set an initial position for the camera scene node above the floor
+ cameraNode.position = Vector3(0.0f, 5.0f, -20.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys and mouse to move\n"
+ "LMB to spawn physics objects\n"
+ "F5 to save scene, F7 to load\n"
+ "Space to toggle physics debug geometry";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ // debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ // "Shoot" a physics object with left mousebutton
+ if (input.mouseButtonPress[MOUSEB_LEFT])
+ SpawnObject();
+
+ // Check for loading / saving the scene. Save the scene to the file Data/Scenes/Physics.xml relative to the executable
+ // directory
+ if (input.keyPress[KEY_F5])
+ {
+ File saveFile(fileSystem.programDir + "Data/Scenes/Physics.xml", FILE_WRITE);
+ scene_.SaveXML(saveFile);
+ }
+ if (input.keyPress[KEY_F7])
+ {
+ File loadFile(fileSystem.programDir + "Data/Scenes/Physics.xml", FILE_READ);
+ scene_.LoadXML(loadFile);
+ }
+
+ // Toggle debug geometry with space
+ if (input.keyPress[KEY_SPACE])
+ drawDebug = !drawDebug;
+}
+
+void SpawnObject()
+{
+ // Create a smaller box at camera position
+ Node@ boxNode = scene_.CreateChild("SmallBox");
+ boxNode.position = cameraNode.position;
+ boxNode.rotation = cameraNode.rotation;
+ boxNode.SetScale(0.25f);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/StoneEnvMapSmall.xml");
+ boxObject.castShadows = true;
+
+ // Create physics components, use a smaller mass also
+ RigidBody@ body = boxNode.CreateComponent("RigidBody");
+ body.mass = 0.25f;
+ body.friction = 0.75f;
+ CollisionShape@ shape = boxNode.CreateComponent("CollisionShape");
+ shape.SetBox(Vector3::ONE);
+
+ const float OBJECT_VELOCITY = 10.0f;
+
+ // Set initial velocity for the RigidBody based on camera forward vector. Add also a slight up component
+ // to overcome gravity better
+ body.linearVelocity = cameraNode.rotation * Vector3(0.0f, 0.25f, 1.0f) * OBJECT_VELOCITY;
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // If draw debug mode is enabled, draw physics debug geometry. Use depth test to make the result easier to interpret
+ // Note the convenience accessor to the physics world component
+ if (drawDebug)
+ scene_.physicsWorld.DrawDebugGeometry(true);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Spawn" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Debug" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/12_PhysicsStressTest.as b/bin/Data/Scripts/12_PhysicsStressTest.as
new file mode 100644
index 0000000..ac72620
--- /dev/null
+++ b/bin/Data/Scripts/12_PhysicsStressTest.as
@@ -0,0 +1,280 @@
+// Physics stress test example.
+// This sample demonstrates:
+// - Physics and rendering performance with a high (1000) moving object count
+// - Using triangle meshes for collision
+// - Optimizing physics simulation by leaving out collision event signaling
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update and render post-update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ // Create a physics simulation world with default parameters, which will update at 60fps. Like the Octree must
+ // exist before creating drawable components, the PhysicsWorld must exist before creating physics components.
+ // Finally, create a DebugRenderer component so that we can draw physics debug geometry
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("PhysicsWorld");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(0.5f, 0.5f, 0.7f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ {
+ // Create a floor object, 500 x 500 world units. Adjust position so that the ground is at zero Y
+ Node@ floorNode = scene_.CreateChild("Floor");
+ floorNode.position = Vector3(0.0f, -0.5f, 0.0f);
+ floorNode.scale = Vector3(500.0f, 1.0f, 500.0f);
+ StaticModel@ floorObject = floorNode.CreateComponent("StaticModel");
+ floorObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ floorObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Make the floor physical by adding RigidBody and CollisionShape components
+ RigidBody@ body = floorNode.CreateComponent("RigidBody");
+ CollisionShape@ shape = floorNode.CreateComponent("CollisionShape");
+ // Set a box shape of size 1 x 1 x 1 for collision. The shape will be scaled with the scene node scale, so the
+ // rendering and physics representation sizes should match (the box model is also 1 x 1 x 1.)
+ shape.SetBox(Vector3::ONE);
+ }
+
+ {
+ // Create static mushrooms with triangle mesh collision
+ const uint NUM_MUSHROOMS = 50;
+ for (uint i = 0; i < NUM_MUSHROOMS; ++i)
+ {
+ Node@ mushroomNode = scene_.CreateChild("Mushroom");
+ mushroomNode.position = Vector3(Random(400.0f) - 200.0f, 0.0f, Random(400.0f) - 200.0f);
+ mushroomNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+ mushroomNode.SetScale(5.0f + Random(5.0f));
+ StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+ mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ mushroomObject.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ mushroomObject.castShadows = true;
+
+ RigidBody@ body = mushroomNode.CreateComponent("RigidBody");
+ CollisionShape@ shape = mushroomNode.CreateComponent("CollisionShape");
+ // By default the highest LOD level will be used, the LOD level can be passed as an optional parameter
+ shape.SetTriangleMesh(mushroomObject.model);
+ }
+ }
+
+ {
+ // Create a large amount of falling physics objects
+ const uint NUM_OBJECTS = 1000;
+ for (uint i = 0; i < NUM_OBJECTS; ++i)
+ {
+ Node@ boxNode = scene_.CreateChild("Box");
+ boxNode.position = Vector3(0.0f, i * 2.0f + 100.0f, 0.0f);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/StoneSmall.xml");
+ boxObject.castShadows = true;
+
+ // Give the RigidBody mass to make it movable and also adjust friction
+ RigidBody@ body = boxNode.CreateComponent("RigidBody");
+ body.mass = 1.0f;
+ body.friction = 1.0f;
+ // Disable collision event signaling to reduce CPU load of the physics simulation
+ body.collisionEventMode = COLLISION_NEVER;
+ CollisionShape@ shape = boxNode.CreateComponent("CollisionShape");
+ shape.SetBox(Vector3::ONE);
+ }
+ }
+
+ // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
+ // the scene, because we want it to be unaffected by scene load / save
+ cameraNode = Node();
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+
+ // Set an initial position for the camera scene node above the floor
+ cameraNode.position = Vector3(0.0f, 3.0f, -20.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys and mouse to move\n"
+ "LMB to spawn physics objects\n"
+ "F5 to save scene, F7 to load\n"
+ "Space to toggle physics debug geometry";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ // debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ // "Shoot" a physics object with left mousebutton
+ if (input.mouseButtonPress[MOUSEB_LEFT])
+ SpawnObject();
+
+ // Check for loading / saving the scene
+ if (input.keyPress[KEY_F5])
+ {
+ File saveFile(fileSystem.programDir + "Data/Scenes/PhysicsStressTest.xml", FILE_WRITE);
+ scene_.SaveXML(saveFile);
+ }
+ if (input.keyPress[KEY_F7])
+ {
+ File loadFile(fileSystem.programDir + "Data/Scenes/PhysicsStressTest.xml", FILE_READ);
+ scene_.LoadXML(loadFile);
+ }
+
+ // Toggle debug geometry with space
+ if (input.keyPress[KEY_SPACE])
+ drawDebug = !drawDebug;
+}
+
+void SpawnObject()
+{
+ // Create a smaller box at camera position
+ Node@ boxNode = scene_.CreateChild("SmallBox");
+ boxNode.position = cameraNode.position;
+ boxNode.rotation = cameraNode.rotation;
+ boxNode.SetScale(0.25f);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/StoneSmall.xml");
+ boxObject.castShadows = true;
+
+ // Create physics components, use a smaller mass also
+ RigidBody@ body = boxNode.CreateComponent("RigidBody");
+ body.mass = 0.25f;
+ body.friction = 0.75f;
+ CollisionShape@ shape = boxNode.CreateComponent("CollisionShape");
+ shape.SetBox(Vector3::ONE);
+
+ const float OBJECT_VELOCITY = 10.0f;
+
+ // Set initial velocity for the RigidBody based on camera forward vector. Add also a slight up component
+ // to overcome gravity better
+ body.linearVelocity = cameraNode.rotation * Vector3(0.0f, 0.25f, 1.0f) * OBJECT_VELOCITY;
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // If draw debug mode is enabled, draw physics debug geometry. Use depth test to make the result easier to interpret
+ if (drawDebug)
+ scene_.physicsWorld.DrawDebugGeometry(true);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Spawn" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Debug" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/13_Ragdolls.as b/bin/Data/Scripts/13_Ragdolls.as
new file mode 100644
index 0000000..7956f58
--- /dev/null
+++ b/bin/Data/Scripts/13_Ragdolls.as
@@ -0,0 +1,409 @@
+// Ragdoll example.
+// This sample demonstrates:
+// - Detecting physics collisions
+// - Moving an AnimatedModel's bones with physics and connecting them with constraints
+// - Using rolling friction to stop rolling objects from moving infinitely
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update and render post-update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ // Create a physics simulation world with default parameters, which will update at 60fps. Like the Octree must
+ // exist before creating drawable components, the PhysicsWorld must exist before creating physics components.
+ // Finally, create a DebugRenderer component so that we can draw physics debug geometry
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("PhysicsWorld");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(0.5f, 0.5f, 0.7f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ {
+ // Create a floor object, 500 x 500 world units. Adjust position so that the ground is at zero Y
+ Node@ floorNode = scene_.CreateChild("Floor");
+ floorNode.position = Vector3(0.0f, -0.5f, 0.0f);
+ floorNode.scale = Vector3(500.0f, 1.0f, 500.0f);
+ StaticModel@ floorObject = floorNode.CreateComponent("StaticModel");
+ floorObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ floorObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Make the floor physical by adding RigidBody and CollisionShape components
+ RigidBody@ body = floorNode.CreateComponent("RigidBody");
+ // We will be spawning spherical objects in this sample. The ground also needs non-zero rolling friction so that
+ // the spheres will eventually come to rest
+ body.rollingFriction = 0.15f;
+ CollisionShape@ shape = floorNode.CreateComponent("CollisionShape");
+ // Set a box shape of size 1 x 1 x 1 for collision. The shape will be scaled with the scene node scale, so the
+ // rendering and physics representation sizes should match (the box model is also 1 x 1 x 1.)
+ shape.SetBox(Vector3::ONE);
+ }
+
+ // Create animated models
+ for (int z = -1; z <= 1; ++z)
+ {
+ for (int x = -4; x <= 4; ++x)
+ {
+ Node@ modelNode = scene_.CreateChild("Jack");
+ modelNode.position = Vector3(x * 5.0f, 0.0f, z * 5.0f);
+ modelNode.rotation = Quaternion(0.0f, 180.0f, 0.0f);
+ AnimatedModel@ modelObject = modelNode.CreateComponent("AnimatedModel");
+ modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
+ modelObject.material = cache.GetResource("Material", "Materials/Jack.xml");
+ modelObject.castShadows = true;
+ // Set the model to also update when invisible to avoid staying invisible when the model should come into
+ // view, but does not as the bounding box is not updated
+ modelObject.updateInvisible = true;
+
+ // Create a rigid body and a collision shape. These will act as a trigger for transforming the
+ // model into a ragdoll when hit by a moving object
+ RigidBody@ body = modelNode.CreateComponent("RigidBody");
+ // The trigger mode makes the rigid body only detect collisions, but impart no forces on the
+ // colliding objects
+ body.trigger = true;
+ CollisionShape@ shape = modelNode.CreateComponent("CollisionShape");
+ // Create the capsule shape with an offset so that it is correctly aligned with the model, which
+ // has its origin at the feet
+ shape.SetCapsule(0.7f, 2.0f, Vector3(0.0f, 1.0f, 0.0f));
+
+ // Create a custom script object that reacts to collisions and creates the ragdoll
+ modelNode.CreateScriptObject(scriptFile, "CreateRagdoll");
+ }
+ }
+
+ // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
+ // the scene, because we want it to be unaffected by scene load / save
+ cameraNode = Node();
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+
+ // Set an initial position for the camera scene node above the floor
+ cameraNode.position = Vector3(0.0f, 5.0f, -20.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys and mouse to move\n"
+ "LMB to spawn physics objects\n"
+ "F5 to save scene, F7 to load\n"
+ "Space to toggle physics debug geometry";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ // debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ // "Shoot" a physics object with left mousebutton
+ if (input.mouseButtonPress[MOUSEB_LEFT])
+ SpawnObject();
+
+ // Check for loading / saving the scene
+ if (input.keyPress[KEY_F5])
+ {
+ File saveFile(fileSystem.programDir + "Data/Scenes/Ragdolls.xml", FILE_WRITE);
+ scene_.SaveXML(saveFile);
+ }
+ if (input.keyPress[KEY_F7])
+ {
+ File loadFile(fileSystem.programDir + "Data/Scenes/Ragdolls.xml", FILE_READ);
+ scene_.LoadXML(loadFile);
+ }
+
+ // Toggle debug geometry with space
+ if (input.keyPress[KEY_SPACE])
+ drawDebug = !drawDebug;
+}
+
+void SpawnObject()
+{
+ Node@ boxNode = scene_.CreateChild("Sphere");
+ boxNode.position = cameraNode.position;
+ boxNode.rotation = cameraNode.rotation;
+ boxNode.SetScale(0.25f);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Sphere.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/StoneSmall.xml");
+ boxObject.castShadows = true;
+
+ RigidBody@ body = boxNode.CreateComponent("RigidBody");
+ body.mass = 1.0f;
+ body.rollingFriction = 0.15f;
+ CollisionShape@ shape = boxNode.CreateComponent("CollisionShape");
+ shape.SetSphere(1.0f);
+
+ const float OBJECT_VELOCITY = 10.0f;
+
+ // Set initial velocity for the RigidBody based on camera forward vector. Add also a slight up component
+ // to overcome gravity better
+ body.linearVelocity = cameraNode.rotation * Vector3(0.0f, 0.25f, 1.0f) * OBJECT_VELOCITY;
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // If draw debug mode is enabled, draw physics debug geometry. Use depth test to make the result easier to interpret
+ if (drawDebug)
+ scene_.physicsWorld.DrawDebugGeometry(true);
+}
+
+// CreateRagdoll script object class
+class CreateRagdoll : ScriptObject
+{
+ void Start()
+ {
+ // Subscribe physics collisions that concern this scene node
+ SubscribeToEvent(node, "NodeCollision", "HandleNodeCollision");
+ }
+
+ void HandleNodeCollision(StringHash eventType, VariantMap& eventData)
+ {
+ // Get the other colliding body, make sure it is moving (has nonzero mass)
+ RigidBody@ otherBody = eventData["OtherBody"].GetPtr();
+
+ if (otherBody.mass > 0.0f)
+ {
+ // We do not need the physics components in the AnimatedModel's root scene node anymore
+ node.RemoveComponent("RigidBody");
+ node.RemoveComponent("CollisionShape");
+
+ // Create RigidBody & CollisionShape components to bones
+ CreateRagdollBone("Bip01_Pelvis", SHAPE_BOX, Vector3(0.3f, 0.2f, 0.25f), Vector3(0.0f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 0.0f));
+ CreateRagdollBone("Bip01_Spine1", SHAPE_BOX, Vector3(0.35f, 0.2f, 0.3f), Vector3(0.15f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 0.0f));
+ CreateRagdollBone("Bip01_L_Thigh", SHAPE_CAPSULE, Vector3(0.175f, 0.45f, 0.175f), Vector3(0.25f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 90.0f));
+ CreateRagdollBone("Bip01_R_Thigh", SHAPE_CAPSULE, Vector3(0.175f, 0.45f, 0.175f), Vector3(0.25f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 90.0f));
+ CreateRagdollBone("Bip01_L_Calf", SHAPE_CAPSULE, Vector3(0.15f, 0.55f, 0.15f), Vector3(0.25f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 90.0f));
+ CreateRagdollBone("Bip01_R_Calf", SHAPE_CAPSULE, Vector3(0.15f, 0.55f, 0.15f), Vector3(0.25f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 90.0f));
+ CreateRagdollBone("Bip01_Head", SHAPE_BOX, Vector3(0.2f, 0.2f, 0.2f), Vector3(0.1f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 0.0f));
+ CreateRagdollBone("Bip01_L_UpperArm", SHAPE_CAPSULE, Vector3(0.15f, 0.35f, 0.15f), Vector3(0.1f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 90.0f));
+ CreateRagdollBone("Bip01_R_UpperArm", SHAPE_CAPSULE, Vector3(0.15f, 0.35f, 0.15f), Vector3(0.1f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 90.0f));
+ CreateRagdollBone("Bip01_L_Forearm", SHAPE_CAPSULE, Vector3(0.125f, 0.4f, 0.125f), Vector3(0.2f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 90.0f));
+ CreateRagdollBone("Bip01_R_Forearm", SHAPE_CAPSULE, Vector3(0.125f, 0.4f, 0.125f), Vector3(0.2f, 0.0f, 0.0f),
+ Quaternion(0.0f, 0.0f, 90.0f));
+
+ // Create Constraints between bones
+ CreateRagdollConstraint("Bip01_L_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3::BACK,
+ Vector3::FORWARD, Vector2(45.0f, 45.0f), Vector2::ZERO);
+ CreateRagdollConstraint("Bip01_R_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3::BACK,
+ Vector3::FORWARD, Vector2(45.0f, 45.0f), Vector2::ZERO);
+ CreateRagdollConstraint("Bip01_L_Calf", "Bip01_L_Thigh", CONSTRAINT_HINGE, Vector3::BACK,
+ Vector3::BACK, Vector2(90.0f, 0.0f), Vector2::ZERO);
+ CreateRagdollConstraint("Bip01_R_Calf", "Bip01_R_Thigh", CONSTRAINT_HINGE, Vector3::BACK,
+ Vector3::BACK, Vector2(90.0f, 0.0f), Vector2::ZERO);
+ CreateRagdollConstraint("Bip01_Spine1", "Bip01_Pelvis", CONSTRAINT_HINGE, Vector3::FORWARD,
+ Vector3::FORWARD, Vector2(45.0f, 0.0f), Vector2(-10.0f, 0.0f));
+ CreateRagdollConstraint("Bip01_Head", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3::LEFT,
+ Vector3::LEFT, Vector2(0.0f, 30.0f), Vector2::ZERO);
+ CreateRagdollConstraint("Bip01_L_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3::DOWN,
+ Vector3::UP, Vector2(45.0f, 45.0f), Vector2::ZERO, false);
+ CreateRagdollConstraint("Bip01_R_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3::DOWN,
+ Vector3::UP, Vector2(45.0f, 45.0f), Vector2::ZERO, false);
+ CreateRagdollConstraint("Bip01_L_Forearm", "Bip01_L_UpperArm", CONSTRAINT_HINGE, Vector3::BACK,
+ Vector3::BACK, Vector2(90.0f, 0.0f), Vector2::ZERO);
+ CreateRagdollConstraint("Bip01_R_Forearm", "Bip01_R_UpperArm", CONSTRAINT_HINGE, Vector3::BACK,
+ Vector3::BACK, Vector2(90.0f, 0.0f), Vector2::ZERO);
+
+ // Disable keyframe animation from all bones so that they will not interfere with the ragdoll
+ AnimatedModel@ model = node.GetComponent("AnimatedModel");
+ Skeleton@ skeleton = model.skeleton;
+ for (uint i = 0; i < skeleton.numBones; ++i)
+ skeleton.bones[i].animated = false;
+
+ // Finally remove self (the ScriptInstance which holds this script object) from the scene node. Note that this must
+ // be the last operation performed in the function
+ self.Remove();
+ }
+ }
+
+ void CreateRagdollBone(const String&in boneName, ShapeType type, const Vector3&in size, const Vector3&in position,
+ const Quaternion&in rotation)
+ {
+ // Find the correct child scene node recursively
+ Node@ boneNode = node.GetChild(boneName, true);
+ if (boneNode is null)
+ {
+ log.Warning("Could not find bone " + boneName + " for creating ragdoll physics components");
+ return;
+ }
+
+ RigidBody@ body = boneNode.CreateComponent("RigidBody");
+ // Set mass to make movable
+ body.mass = 1.0f;
+ // Set damping parameters to smooth out the motion
+ body.linearDamping = 0.05f;
+ body.angularDamping = 0.85f;
+ // Set rest thresholds to ensure the ragdoll rigid bodies come to rest to not consume CPU endlessly
+ body.linearRestThreshold = 1.5f;
+ body.angularRestThreshold = 2.5f;
+
+ CollisionShape@ shape = boneNode.CreateComponent("CollisionShape");
+ // We use either a box or a capsule shape for all of the bones
+ if (type == SHAPE_BOX)
+ shape.SetBox(size, position, rotation);
+ else
+ shape.SetCapsule(size.x, size.y, position, rotation);
+ }
+
+ void CreateRagdollConstraint(const String&in boneName, const String&in parentName, ConstraintType type,
+ const Vector3&in axis, const Vector3&in parentAxis, const Vector2&in highLimit, const Vector2&in lowLimit,
+ bool disableCollision = true)
+ {
+ Node@ boneNode = node.GetChild(boneName, true);
+ Node@ parentNode = node.GetChild(parentName, true);
+ if (boneNode is null)
+ {
+ log.Warning("Could not find bone " + boneName + " for creating ragdoll constraint");
+ return;
+ }
+ if (parentNode is null)
+ {
+ log.Warning("Could not find bone " + parentName + " for creating ragdoll constraint");
+ return;
+ }
+
+ Constraint@ constraint = boneNode.CreateComponent("Constraint");
+ constraint.constraintType = type;
+ // Most of the constraints in the ragdoll will work better when the connected bodies don't collide against each other
+ constraint.disableCollision = disableCollision;
+ // The connected body must be specified before setting the world position
+ constraint.otherBody = parentNode.GetComponent("RigidBody");
+ // Position the constraint at the child bone we are connecting
+ constraint.worldPosition = boneNode.worldPosition;
+ // Configure axes and limits
+ constraint.axis = axis;
+ constraint.otherAxis = parentAxis;
+ constraint.highLimit = highLimit;
+ constraint.lowLimit = lowLimit;
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Spawn" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Debug" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/14_SoundEffects.as b/bin/Data/Scripts/14_SoundEffects.as
new file mode 100644
index 0000000..d26dbe3
--- /dev/null
+++ b/bin/Data/Scripts/14_SoundEffects.as
@@ -0,0 +1,171 @@
+// Sound effects example
+// This sample demonstrates:
+// - Playing sound effects and music
+// - Controlling sound and music master volume
+
+#include "Scripts/Utilities/Sample.as"
+
+Array soundNames = {
+ "Fist",
+ "Explosion",
+ "Power-up"
+};
+
+Array soundResourceNames = {
+ "Sounds/PlayerFistHit.wav",
+ "Sounds/BigExplosion.wav",
+ "Sounds/Powerup.wav"
+};
+
+SoundSource@ musicSource;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create a scene which will not be actually rendered, but is used to hold SoundSource components while they play sounds
+ scene_ = Scene();
+
+ // Create music sound source
+ @musicSource = scene_.CreateComponent("SoundSource");
+ // Set the sound type to music so that master volume control works correctly
+ musicSource.soundType = SOUND_MUSIC;
+
+ // Enable OS cursor
+ input.mouseVisible = true;
+
+ // Create the user interface
+ CreateUI();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+}
+
+void CreateUI()
+{
+ XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+ // Set style to the UI root so that elements will inherit it
+ ui.root.defaultStyle = uiStyle;
+
+ // Create buttons for playing back sounds
+ for (uint i = 0; i < soundNames.length; ++i)
+ {
+ Button@ button = CreateButton(i * 140 + 20, 20, 120, 40, soundNames[i]);
+ // Store the sound effect resource name as a custom variable into the button
+ button.vars["SoundResource"] = soundResourceNames[i];
+ SubscribeToEvent(button, "Pressed", "HandlePlaySound");
+ }
+
+ // Create buttons for playing/stopping music
+ Button@ button = CreateButton(20, 80, 120, 40, "Play Music");
+ SubscribeToEvent(button, "Released", "HandlePlayMusic");
+
+ button = CreateButton(160, 80, 120, 40, "Stop Music");
+ SubscribeToEvent(button, "Released", "HandleStopMusic");
+
+ // Create sliders for controlling sound and music master volume
+ Slider@ slider = CreateSlider(20, 140, 200, 20, "Sound Volume");
+ slider.value = audio.masterGain[SOUND_EFFECT];
+ SubscribeToEvent(slider, "SliderChanged", "HandleSoundVolume");
+
+ slider = CreateSlider(20, 200, 200, 20, "Music Volume");
+ slider.value = audio.masterGain[SOUND_MUSIC];
+ SubscribeToEvent(slider, "SliderChanged", "HandleMusicVolume");
+}
+
+Button@ CreateButton(int x, int y, int xSize, int ySize, const String&in text)
+{
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+
+ // Create the button and center the text onto it
+ Button@ button = ui.root.CreateChild("Button");
+ button.SetStyleAuto();
+ button.SetPosition(x, y);
+ button.SetSize(xSize, ySize);
+
+ Text@ buttonText = button.CreateChild("Text");
+ buttonText.SetAlignment(HA_CENTER, VA_CENTER);
+ buttonText.SetFont(font, 12);
+ buttonText.text = text;
+
+ return button;
+}
+
+Slider@ CreateSlider(int x, int y, int xSize, int ySize, const String& text)
+{
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+
+ // Create text and slider below it
+ Text@ sliderText = ui.root.CreateChild("Text");
+ sliderText.SetPosition(x, y);
+ sliderText.SetFont(font, 12);
+ sliderText.text = text;
+
+ Slider@ slider = ui.root.CreateChild("Slider");
+ slider.SetStyleAuto();
+ slider.SetPosition(x, y + 20);
+ slider.SetSize(xSize, ySize);
+ // Use 0-1 range for controlling sound/music master volume
+ slider.range = 1.0f;
+
+ return slider;
+}
+
+void HandlePlaySound(StringHash eventType, VariantMap& eventData)
+{
+ Button@ button = GetEventSender();
+ String soundResourceName = button.vars["SoundResource"].GetString();
+
+ // Get the sound resource
+ Sound@ sound = cache.GetResource("Sound", soundResourceName);
+
+ if (sound !is null)
+ {
+ // Create a SoundSource component for playing the sound. The SoundSource component plays
+ // non-positional audio, so its 3D position in the scene does not matter. For positional sounds the
+ // SoundSource3D component would be used instead
+ SoundSource@ soundSource = scene_.CreateComponent("SoundSource");
+ soundSource.autoRemoveMode = REMOVE_COMPONENT;
+ soundSource.Play(sound);
+ // In case we also play music, set the sound volume below maximum so that we don't clip the output
+ soundSource.gain = 0.7f;
+ }
+}
+
+void HandlePlayMusic(StringHash eventType, VariantMap& eventData)
+{
+ Sound@ music = cache.GetResource("Sound", "Music/Ninja Gods.ogg");
+ // Set the song to loop
+ music.looped = true;
+
+ musicSource.Play(music);
+}
+
+void HandleStopMusic(StringHash eventType, VariantMap& eventData)
+{
+ musicSource.Stop();
+}
+
+void HandleSoundVolume(StringHash eventType, VariantMap& eventData)
+{
+ float newVolume = eventData["Value"].GetFloat();
+ audio.masterGain[SOUND_EFFECT] = newVolume;
+}
+
+void HandleMusicVolume(StringHash eventType, VariantMap& eventData)
+{
+ float newVolume = eventData["Value"].GetFloat();
+ audio.masterGain[SOUND_MUSIC] = newVolume;
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/15_Navigation.as b/bin/Data/Scripts/15_Navigation.as
new file mode 100644
index 0000000..c74b725
--- /dev/null
+++ b/bin/Data/Scripts/15_Navigation.as
@@ -0,0 +1,538 @@
+// Navigation example.
+// This sample demonstrates:
+// - Generating a navigation mesh into the scene
+// - Performing path queries to the navigation mesh
+// - Rebuilding the navigation mesh partially when adding or removing objects
+// - Visualizing custom debug geometry
+// - Raycasting drawable components
+// - Making a node follow the Detour path
+
+#include "Scripts/Utilities/Sample.as"
+
+Vector3 endPos;
+Array currentPath;
+Node@ jackNode;
+bool useStreaming = false;
+// Used for streaming only
+const int STREAMING_DISTANCE = 2;
+Array navigationTilesData;
+Array navigationTilesIdx;
+Array addedTiles;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateUI();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update and render post-update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ // Also create a DebugRenderer component so that we can draw debug geometry
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create scene node & StaticModel component for showing a static plane
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(100.0f, 1.0f, 100.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(0.5f, 0.5f, 0.7f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ // Create some mushrooms
+ const uint NUM_MUSHROOMS = 100;
+ for (uint i = 0; i < NUM_MUSHROOMS; ++i)
+ CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
+
+ // Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+ // rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+ const uint NUM_BOXES = 20;
+ for (uint i = 0; i < NUM_BOXES; ++i)
+ {
+ Node@ boxNode = scene_.CreateChild("Box");
+ float size = 1.0f + Random(10.0f);
+ boxNode.position = Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f);
+ boxNode.SetScale(size);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+ boxObject.castShadows = true;
+ if (size >= 3.0f)
+ boxObject.occluder = true;
+ }
+
+ // Create Jack node that will follow the path
+ jackNode = scene_.CreateChild("Jack");
+ jackNode.position = Vector3(-5.0f, 0.0f, 20.0f);
+ AnimatedModel@ modelObject = jackNode.CreateComponent("AnimatedModel");
+ modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
+ modelObject.material = cache.GetResource("Material", "Materials/Jack.xml");
+ modelObject.castShadows = true;
+
+ // Create a NavigationMesh component to the scene root
+ NavigationMesh@ navMesh = scene_.CreateComponent("NavigationMesh");
+ // Set small tiles to show navigation mesh streaming
+ navMesh.tileSize = 32;
+ // Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
+ // navigation mesh. By default this is recursive, but the recursion could be turned off from Navigable
+ scene_.CreateComponent("Navigable");
+ // Add padding to the navigation mesh in Y-direction so that we can add objects on top of the tallest boxes
+ // in the scene and still update the mesh correctly
+ navMesh.padding = Vector3(0.0f, 10.0f, 0.0f);
+ // Now build the navigation geometry. This will take some time. Note that the navigation mesh will prefer to use
+ // physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
+ // it will use renderable geometry instead
+ navMesh.Build();
+
+ // Create the camera. Limit far clip distance to match the fog
+ cameraNode = scene_.CreateChild("Camera");
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+
+ // Set an initial position for the camera scene node above the plane and looking down
+ cameraNode.position = Vector3(0.0f, 50.0f, 0.0f);
+ pitch = 80.0f;
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+}
+
+void CreateUI()
+{
+ // Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ // control the camera, and when visible, it will point the raycast target
+ XMLFile@ style = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+ Cursor@ cursor = Cursor();
+ cursor.SetStyleAuto(style);
+ ui.cursor = cursor;
+ // Set starting position of the cursor at the rendering window center
+ cursor.SetPosition(graphics.width / 2, graphics.height / 2);
+
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys to move, RMB to rotate view\n"
+ "LMB to set destination, SHIFT+LMB to teleport\n"
+ "MMB or O key to add or remove obstacles\n"
+ "Tab to toggle navigation mesh streaming\n"
+ "Space to toggle debug geometry";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request
+ // debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ input.mouseVisible = input.mouseMode != MM_RELATIVE;
+ bool mouseDown = input.mouseButtonDown[MOUSEB_RIGHT];
+
+ // Override the MM_RELATIVE mouse grabbed settings, to allow interaction with UI
+ input.mouseGrabbed = mouseDown;
+
+ // Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = !mouseDown;
+
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ // Only move the camera when the cursor is hidden
+ if (!ui.cursor.visible)
+ {
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+ }
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ // Set destination or teleport with left mouse button
+ if (input.mouseButtonPress[MOUSEB_LEFT])
+ SetPathPoint();
+ // Add or remove objects with middle mouse button, then rebuild navigation mesh partially
+ if (input.mouseButtonPress[MOUSEB_MIDDLE] || input.keyPress[KEY_O])
+ AddOrRemoveObject();
+
+ // Toggle debug geometry with space
+ if (input.keyPress[KEY_SPACE])
+ drawDebug = !drawDebug;
+}
+
+void SetPathPoint()
+{
+ Vector3 hitPos;
+ Drawable@ hitDrawable;
+ NavigationMesh@ navMesh = scene_.GetComponent("NavigationMesh");
+
+ if (Raycast(250.0f, hitPos, hitDrawable))
+ {
+ Vector3 pathPos = navMesh.FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
+
+ if (input.qualifierDown[QUAL_SHIFT])
+ {
+ // Teleport
+ currentPath.Clear();
+ jackNode.LookAt(Vector3(pathPos.x, jackNode.position.y, pathPos.z), Vector3::UP);
+ jackNode.position = pathPos;
+ }
+ else
+ {
+ // Calculate path from Jack's current position to the end point
+ endPos = pathPos;
+ currentPath = navMesh.FindPath(jackNode.position, endPos);
+ }
+ }
+}
+
+void AddOrRemoveObject()
+{
+ // Raycast and check if we hit a mushroom node. If yes, remove it, if no, create a new one
+ Vector3 hitPos;
+ Drawable@ hitDrawable;
+
+ if (!useStreaming && Raycast(250.0f, hitPos, hitDrawable))
+ {
+ // The part of the navigation mesh we must update, which is the world bounding box of the associated
+ // drawable component
+ BoundingBox updateBox;
+
+ Node@ hitNode = hitDrawable.node;
+ if (hitNode.name == "Mushroom")
+ {
+ updateBox = hitDrawable.worldBoundingBox;
+ hitNode.Remove();
+ }
+ else
+ {
+ Node@ newNode = CreateMushroom(hitPos);
+ StaticModel@ newObject = newNode.GetComponent("StaticModel");
+ updateBox = newObject.worldBoundingBox;
+ }
+
+ // Rebuild part of the navigation mesh, then rebuild the path if applicable
+ NavigationMesh@ navMesh = scene_.GetComponent("NavigationMesh");
+ navMesh.Build(updateBox);
+ if (currentPath.length > 0)
+ currentPath = navMesh.FindPath(jackNode.position, endPos);
+ }
+}
+
+Node@ CreateMushroom(const Vector3& pos)
+{
+ Node@ mushroomNode = scene_.CreateChild("Mushroom");
+ mushroomNode.position = pos;
+ mushroomNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+ mushroomNode.SetScale(2.0f + Random(0.5f));
+ StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+ mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ mushroomObject.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ mushroomObject.castShadows = true;
+
+ return mushroomNode;
+}
+
+bool Raycast(float maxDistance, Vector3& hitPos, Drawable@& hitDrawable)
+{
+ hitDrawable = null;
+
+ IntVector2 pos = ui.cursorPosition;
+ // Check the cursor is visible and there is no UI element in front of the cursor
+ if (!ui.cursor.visible || ui.GetElementAt(pos, true) !is null)
+ return false;
+
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
+ // Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
+ // Note the convenience accessor to scene's Octree component
+ RayQueryResult result = scene_.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY);
+ if (result.drawable !is null)
+ {
+ hitPos = result.position;
+ hitDrawable = result.drawable;
+ return true;
+ }
+
+ return false;
+}
+
+void FollowPath(float timeStep)
+{
+ if (currentPath.length > 0)
+ {
+ Vector3 nextWaypoint = currentPath[0]; // NB: currentPath[0] is the next waypoint in order
+
+ // Rotate Jack toward next waypoint to reach and move. Check for not overshooting the target
+ float move = 5.0f * timeStep;
+ float distance = (jackNode.position - nextWaypoint).length;
+ if (move > distance)
+ move = distance;
+
+ jackNode.LookAt(nextWaypoint, Vector3::UP);
+ jackNode.Translate(Vector3::FORWARD * move);
+
+ // Remove waypoint if reached it
+ if (distance < 0.1)
+ currentPath.Erase(0);
+ }
+}
+
+void ToggleStreaming(bool enabled)
+{
+ NavigationMesh@ navMesh = scene_.GetComponent("NavigationMesh");
+ if (enabled)
+ {
+ int maxTiles = (2 * STREAMING_DISTANCE + 1) * (2 * STREAMING_DISTANCE + 1);
+ BoundingBox boundingBox = navMesh.boundingBox;
+ SaveNavigationData();
+ navMesh.Allocate(boundingBox, maxTiles);
+ }
+ else
+ navMesh.Build();
+}
+
+void UpdateStreaming()
+{
+ NavigationMesh@ navMesh = scene_.GetComponent("NavigationMesh");
+
+ // Center the navigation mesh at the jack
+ IntVector2 jackTile = navMesh.GetTileIndex(jackNode.worldPosition);
+ IntVector2 beginTile = VectorMax(IntVector2(0, 0), jackTile - IntVector2(1, 1) * STREAMING_DISTANCE);
+ IntVector2 endTile = VectorMin(jackTile + IntVector2(1, 1) * STREAMING_DISTANCE, navMesh.numTiles - IntVector2(1, 1));
+
+ // Remove tiles
+ for (uint i = 0; i < addedTiles.length;)
+ {
+ IntVector2 tileIdx = addedTiles[i];
+ if (beginTile.x <= tileIdx.x && tileIdx.x <= endTile.x && beginTile.y <= tileIdx.y && tileIdx.y <= endTile.y)
+ ++i;
+ else
+ {
+ addedTiles.Erase(i);
+ navMesh.RemoveTile(tileIdx);
+ }
+ }
+
+ // Add tiles
+ for (int z = beginTile.y; z <= endTile.y; ++z)
+ for (int x = beginTile.x; x <= endTile.x; ++x)
+ {
+ const IntVector2 tileIdx(x, z);
+ int tileDataIdx = navigationTilesIdx.Find(tileIdx);
+ if (!navMesh.HasTile(tileIdx) && tileDataIdx != -1)
+ {
+ addedTiles.Push(tileIdx);
+ navMesh.AddTile(navigationTilesData[tileDataIdx]);
+ }
+ }
+}
+
+void SaveNavigationData()
+{
+ NavigationMesh@ navMesh = scene_.GetComponent("NavigationMesh");
+ navigationTilesData.Clear();
+ navigationTilesIdx.Clear();
+ addedTiles.Clear();
+ IntVector2 numTiles = navMesh.numTiles;
+ for (int z = 0; z < numTiles.y; ++z)
+ for (int x = 0; x < numTiles.x; ++x)
+ {
+ IntVector2 idx(x, z);
+ navigationTilesData.Push(navMesh.GetTileData(idx));
+ navigationTilesIdx.Push(idx);
+ }
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+
+ // Make Jack follow the Detour path
+ FollowPath(timeStep);
+
+ // Update streaming
+ if (input.keyPress[KEY_TAB])
+ {
+ useStreaming = !useStreaming;
+ ToggleStreaming(useStreaming);
+ }
+ if (useStreaming)
+ UpdateStreaming();
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // If draw debug mode is enabled, draw navigation mesh debug geometry
+ if (drawDebug)
+ {
+ NavigationMesh@ navMesh = scene_.GetComponent("NavigationMesh");
+ navMesh.DrawDebugGeometry(true);
+ }
+
+ if (currentPath.length > 0)
+ {
+ // Visualize the current calculated path
+ // Note the convenience accessor to the DebugRenderer component
+ DebugRenderer@ debug = scene_.debugRenderer;
+ debug.AddBoundingBox(BoundingBox(endPos - Vector3(0.1f, 0.1f, 0.1f), endPos + Vector3(0.1f, 0.1f, 0.1f)),
+ Color(1.0f, 1.0f, 1.0f));
+
+ // Draw the path with a small upward bias so that it does not clip into the surfaces
+ Vector3 bias(0.0f, 0.05f, 0.0f);
+ debug.AddLine(jackNode.position + bias, currentPath[0] + bias, Color(1.0f, 1.0f, 1.0f));
+
+ if (currentPath.length > 1)
+ {
+ for (uint i = 0; i < currentPath.length - 1; ++i)
+ debug.AddLine(currentPath[i] + bias, currentPath[i + 1] + bias, Color(1.0f, 1.0f, 1.0f));
+ }
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Set" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Debug" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/16_Chat.as b/bin/Data/Scripts/16_Chat.as
new file mode 100644
index 0000000..d22850e
--- /dev/null
+++ b/bin/Data/Scripts/16_Chat.as
@@ -0,0 +1,234 @@
+// Chat example
+// This sample demonstrates:
+// - Starting up a network server or connecting to it
+// - Implementing simple chat functionality with network messages
+
+#include "Scripts/Utilities/Sample.as"
+
+// Identifier for the chat network messages
+const int MSG_CHAT = 153;
+// UDP port we will use
+const uint CHAT_SERVER_PORT = 2345;
+
+Array chatHistory;
+Text@ chatHistoryText;
+UIElement@ buttonContainer;
+LineEdit@ textEdit;
+Button@ sendButton;
+Button@ connectButton;
+Button@ disconnectButton;
+Button@ startServerButton;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Enable OS cursor
+ input.mouseVisible = true;
+
+ // Create the user interface
+ CreateUI();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Subscribe to UI and network events
+ SubscribeToEvents();
+}
+
+void CreateUI()
+{
+ SetLogoVisible(false); // We need the full rendering window
+
+ XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+ // Set style to the UI root so that elements will inherit it
+ ui.root.defaultStyle = uiStyle;
+
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+ chatHistoryText = ui.root.CreateChild("Text");
+ chatHistoryText.SetFont(font, 12);
+
+ buttonContainer = ui.root.CreateChild("UIElement");
+ buttonContainer.SetFixedSize(graphics.width, 20);
+ buttonContainer.SetPosition(0, graphics.height - 20);
+ buttonContainer.layoutMode = LM_HORIZONTAL;
+
+ textEdit = buttonContainer.CreateChild("LineEdit");
+ textEdit.SetStyleAuto();
+
+ sendButton = CreateButton("Send", 70);
+ connectButton = CreateButton("Connect", 90);
+ disconnectButton = CreateButton("Disconnect", 100);
+ startServerButton = CreateButton("Start Server", 110);
+
+ UpdateButtons();
+
+ chatHistory.Resize((graphics.height - 100) / chatHistoryText.rowHeight);
+
+ // No viewports or scene is defined. However, the default zone's fog color controls the fill color
+ renderer.defaultZone.fogColor = Color(0.0f, 0.0f, 0.1f);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe to UI element events
+ SubscribeToEvent(textEdit, "TextFinished", "HandleSend");
+ SubscribeToEvent(sendButton, "Released", "HandleSend");
+ SubscribeToEvent(connectButton, "Released", "HandleConnect");
+ SubscribeToEvent(disconnectButton, "Released", "HandleDisconnect");
+ SubscribeToEvent(startServerButton, "Released", "HandleStartServer");
+
+ // Subscribe to log messages so that we can pipe them to the chat window
+ SubscribeToEvent("LogMessage", "HandleLogMessage");
+
+ // Subscribe to network events
+ SubscribeToEvent("NetworkMessage", "HandleNetworkMessage");
+ SubscribeToEvent("ServerConnected", "HandleConnectionStatus");
+ SubscribeToEvent("ServerDisconnected", "HandleConnectionStatus");
+ SubscribeToEvent("ConnectFailed", "HandleConnectionStatus");
+}
+
+Button@ CreateButton(const String&in text, int width)
+{
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+
+ Button@ button = buttonContainer.CreateChild("Button");
+ button.SetStyleAuto();
+ button.SetFixedWidth(width);
+
+ Text@ buttonText = button.CreateChild("Text");
+ buttonText.SetFont(font, 12);
+ buttonText.SetAlignment(HA_CENTER, VA_CENTER);
+ buttonText.text = text;
+
+ return button;
+}
+
+void ShowChatText(const String& row)
+{
+ chatHistory.Erase(0);
+ chatHistory.Push(row);
+
+ // Concatenate all the rows in history
+ String allRows;
+ for (uint i = 0; i < chatHistory.length; ++i)
+ allRows += chatHistory[i] + "\n";
+
+ chatHistoryText.text = allRows;
+}
+
+void UpdateButtons()
+{
+ Connection@ serverConnection = network.serverConnection;
+ bool serverRunning = network.serverRunning;
+
+ // Show and hide buttons so that eg. Connect and Disconnect are never shown at the same time
+ sendButton.visible = serverConnection !is null;
+ connectButton.visible = serverConnection is null && !serverRunning;
+ disconnectButton.visible = serverConnection !is null || serverRunning;
+ startServerButton.visible = serverConnection is null && !serverRunning;
+}
+
+void HandleLogMessage(StringHash eventType, VariantMap& eventData)
+{
+ ShowChatText(eventData["Message"].GetString());
+}
+
+void HandleSend(StringHash eventType, VariantMap& eventData)
+{
+ String text = textEdit.text;
+ if (text.empty)
+ return; // Do not send an empty message
+
+ Connection@ serverConnection = network.serverConnection;
+
+ if (serverConnection !is null)
+ {
+ // A VectorBuffer object is convenient for constructing a message to send
+ VectorBuffer msg;
+ msg.WriteString(text);
+ // Send the chat message as in-order and reliable
+ serverConnection.SendMessage(MSG_CHAT, true, true, msg);
+ // Empty the text edit after sending
+ textEdit.text = "";
+ }
+}
+
+void HandleConnect(StringHash eventType, VariantMap& eventData)
+{
+ String address = textEdit.text.Trimmed();
+ if (address.empty)
+ address = "localhost"; // Use localhost to connect if nothing else specified
+ // Empty the text edit after reading the address to connect to
+ textEdit.text = "";
+
+ // Connect to server, do not specify a client scene as we are not using scene replication, just messages.
+ // At connect time we could also send identity parameters (such as username) in a VariantMap, but in this
+ // case we skip it for simplicity
+ network.Connect(address, CHAT_SERVER_PORT, null);
+
+ UpdateButtons();
+}
+
+void HandleDisconnect(StringHash eventType, VariantMap& eventData)
+{
+ Connection@ serverConnection = network.serverConnection;
+ // If we were connected to server, disconnect
+ if (serverConnection !is null)
+ serverConnection.Disconnect();
+ // Or if we were running a server, stop it
+ else if (network.serverRunning)
+ network.StopServer();
+
+ UpdateButtons();
+}
+
+void HandleStartServer(StringHash eventType, VariantMap& eventData)
+{
+ network.StartServer(CHAT_SERVER_PORT);
+
+ UpdateButtons();
+}
+
+void HandleNetworkMessage(StringHash eventType, VariantMap& eventData)
+{
+ int msgID = eventData["MessageID"].GetInt();
+ if (msgID == MSG_CHAT)
+ {
+ VectorBuffer msg = eventData["Data"].GetBuffer();
+ String text = msg.ReadString();
+
+ // If we are the server, prepend the sender's IP address and port and echo to everyone
+ // If we are a client, just display the message
+ if (network.serverRunning)
+ {
+ Connection@ sender = eventData["Connection"].GetPtr();
+
+ text = sender.ToString() + " " + text;
+
+ VectorBuffer sendMsg;
+ sendMsg.WriteString(text);
+ // Broadcast as in-order and reliable
+ network.BroadcastMessage(MSG_CHAT, true, true, sendMsg);
+ }
+
+ ShowChatText(text);
+ }
+}
+
+void HandleConnectionStatus(StringHash eventType, VariantMap& eventData)
+{
+ UpdateButtons();
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/17_SceneReplication.as b/bin/Data/Scripts/17_SceneReplication.as
new file mode 100644
index 0000000..038c8b4
--- /dev/null
+++ b/bin/Data/Scripts/17_SceneReplication.as
@@ -0,0 +1,446 @@
+// Scene network replication example.
+// This sample demonstrates:
+// - Creating a scene in which network clients can join
+// - Giving each client an object to control and sending the controls from the clients to the server,
+// where the authoritative simulation happens
+// - Controlling a physics object's movement by applying forces
+
+#include "Scripts/Utilities/Sample.as"
+
+// UDP port we will use
+const uint SERVER_PORT = 2345;
+
+// Control bits we define
+const uint CTRL_FORWARD = 1;
+const uint CTRL_BACK = 2;
+const uint CTRL_LEFT = 4;
+const uint CTRL_RIGHT = 8;
+
+// Record for each connected client
+class Client
+{
+ Connection@ connection;
+ Node@ object;
+}
+
+Text@ instructionsText;
+UIElement@ buttonContainer;
+LineEdit@ textEdit;
+Button@ connectButton;
+Button@ disconnectButton;
+Button@ startServerButton;
+Array clients;
+uint clientObjectID = 0;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateUI();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to necessary events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree and physics world with default settings. Create them as local so that they are not needlessly replicated
+ // when a client connects
+ scene_.CreateComponent("Octree", LOCAL);
+ scene_.CreateComponent("PhysicsWorld", LOCAL);
+
+ // All static scene content and the camera are also created as local, so that they are unaffected by scene replication and are
+ // not removed from the client upon connection. Create a Zone component first for ambient lighting & fog control.
+ Node@ zoneNode = scene_.CreateChild("Zone", LOCAL);
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.1f, 0.1f, 0.1f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light without shadows
+ Node@ lightNode = scene_.CreateChild("DirectionalLight", LOCAL);
+ lightNode.direction = Vector3(0.5f, -1.0f, 0.5f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.color = Color(0.2f, 0.2f, 0.2f);
+ light.specularIntensity = 1.0f;
+
+ // Create a "floor" consisting of several tiles. Make the tiles physical but leave small cracks between them
+ for (int y = -20; y <= 20; ++y)
+ {
+ for (int x = -20; x <= 20; ++x)
+ {
+ Node@ floorNode = scene_.CreateChild("FloorTile", LOCAL);
+ floorNode.position = Vector3(x * 20.2f, -0.5f, y * 20.2f);
+ floorNode.scale = Vector3(20.0f, 1.0f, 20.0f);
+ StaticModel@ floorObject = floorNode.CreateComponent("StaticModel");
+ floorObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ floorObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+
+ RigidBody@ body = floorNode.CreateComponent("RigidBody");
+ body.friction = 1.0f;
+ CollisionShape@ shape = floorNode.CreateComponent("CollisionShape");
+ shape.SetBox(Vector3::ONE);
+ }
+ }
+
+ // Create the camera. Limit far clip distance to match the fog
+ // The camera needs to be created into a local node so that each client can retain its own camera, that is unaffected by
+ // network messages. Furthermore, because the client removes all replicated scene nodes when connecting to a server scene,
+ // the screen would become blank if the camera node was replicated (as only the locally created camera is assigned to a
+ // viewport in SetupViewports() below)
+ cameraNode = scene_.CreateChild("Camera", LOCAL);
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+}
+
+void CreateUI()
+{
+ XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+ // Set style to the UI root so that elements will inherit it
+ ui.root.defaultStyle = uiStyle;
+
+ // Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ // control the camera, and when visible, it will point the raycast target
+ Cursor@ cursor = Cursor();
+ cursor.SetStyleAuto(uiStyle);
+ ui.cursor = cursor;
+ // Set starting position of the cursor at the rendering window center
+ cursor.SetPosition(graphics.width / 2, graphics.height / 2);
+
+ // Construct the instructions text element
+ instructionsText = ui.root.CreateChild("Text");
+ instructionsText.text = "Use WASD keys to move and RMB to rotate view";
+ instructionsText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // Position the text relative to the screen center
+ instructionsText.horizontalAlignment = HA_CENTER;
+ instructionsText.verticalAlignment = VA_CENTER;
+ instructionsText.SetPosition(0, graphics.height / 4);
+ // Hide until connected
+ instructionsText.visible = false;
+
+ buttonContainer = ui.root.CreateChild("UIElement");
+ buttonContainer.SetFixedSize(500, 20);
+ buttonContainer.SetPosition(20, 20);
+ buttonContainer.layoutMode = LM_HORIZONTAL;
+
+ textEdit = buttonContainer.CreateChild("LineEdit");
+ textEdit.SetStyleAuto();
+
+ connectButton = CreateButton("Connect", 90);
+ disconnectButton = CreateButton("Disconnect", 100);
+ startServerButton = CreateButton("Start Server", 110);
+
+ UpdateButtons();
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe to fixed timestep physics updates for setting or applying controls
+ SubscribeToEvent("PhysicsPreStep", "HandlePhysicsPreStep");
+
+ // Subscribe HandlePostUpdate() method for processing update events. Subscribe to PostUpdate instead
+ // of the usual Update so that physics simulation has already proceeded for the frame, and can
+ // accurately follow the object with the camera
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate");
+
+ // Subscribe to button actions
+ SubscribeToEvent(connectButton, "Released", "HandleConnect");
+ SubscribeToEvent(disconnectButton, "Released", "HandleDisconnect");
+ SubscribeToEvent(startServerButton, "Released", "HandleStartServer");
+
+ // Subscribe to network events
+ SubscribeToEvent("ServerConnected", "HandleConnectionStatus");
+ SubscribeToEvent("ServerDisconnected", "HandleConnectionStatus");
+ SubscribeToEvent("ConnectFailed", "HandleConnectionStatus");
+ SubscribeToEvent("ClientConnected", "HandleClientConnected");
+ SubscribeToEvent("ClientDisconnected", "HandleClientDisconnected");
+ // This is a custom event, sent from the server to the client. It tells the node ID of the object the client should control
+ SubscribeToEvent("ClientObjectID", "HandleClientObjectID");
+ // Events sent between client & server (remote events) must be explicitly registered or else they are not allowed to be received
+ network.RegisterRemoteEvent("ClientObjectID");
+}
+
+Button@ CreateButton(const String& text, int width)
+{
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+
+ Button@ button = buttonContainer.CreateChild("Button");
+ button.SetStyleAuto();
+ button.SetFixedWidth(width);
+
+ Text@ buttonText = button.CreateChild("Text");
+ buttonText.SetFont(font, 12);
+ buttonText.SetAlignment(HA_CENTER, VA_CENTER);
+ buttonText.text = text;
+
+ return button;
+}
+
+void UpdateButtons()
+{
+ Connection@ serverConnection = network.serverConnection;
+ bool serverRunning = network.serverRunning;
+
+ // Show and hide buttons so that eg. Connect and Disconnect are never shown at the same time
+ connectButton.visible = serverConnection is null && !serverRunning;
+ disconnectButton.visible = serverConnection !is null || serverRunning;
+ startServerButton.visible = serverConnection is null && !serverRunning;
+ textEdit.visible = serverConnection is null && !serverRunning;
+}
+
+Node@ CreateControllableObject()
+{
+ // Create the scene node & visual representation. This will be a replicated object
+ Node@ ballNode = scene_.CreateChild("Ball");
+ ballNode.position = Vector3(Random(40.0f) - 20.0f, 5.0f, Random(40.0f) - 20.0f);
+ ballNode.SetScale(0.5f);
+ StaticModel@ ballObject = ballNode.CreateComponent("StaticModel");
+ ballObject.model = cache.GetResource("Model", "Models/Sphere.mdl");
+ ballObject.material = cache.GetResource("Material", "Materials/StoneSmall.xml");
+
+ // Create the physics components
+ RigidBody@ body = ballNode.CreateComponent("RigidBody");
+ body.mass = 1.0f;
+ body.friction = 1.0f;
+ // In addition to friction, use motion damping so that the ball can not accelerate limitlessly
+ body.linearDamping = 0.5f;
+ body.angularDamping = 0.5f;
+ CollisionShape@ shape = ballNode.CreateComponent("CollisionShape");
+ shape.SetSphere(1.0f);
+
+ // Create a random colored point light at the ball so that can see better where is going
+ Light@ light = ballNode.CreateComponent("Light");
+ light.range = 3.0f;
+ light.color = Color(0.5f + (RandomInt() & 1) * 0.5f, 0.5f + (RandomInt() & 1) * 0.5f, 0.5f + (RandomInt() & 1) * 0.5f);
+
+ return ballNode;
+}
+
+void MoveCamera()
+{
+ input.mouseVisible = input.mouseMode != MM_RELATIVE;
+ bool mouseDown = input.mouseButtonDown[MOUSEB_RIGHT];
+
+ // Override the MM_RELATIVE mouse grabbed settings, to allow interaction with UI
+ input.mouseGrabbed = mouseDown;
+
+ // Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = !mouseDown;
+
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch and only move the camera
+ // when the cursor is hidden
+ if (!ui.cursor.visible)
+ {
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, 1.0f, 90.0f);
+ }
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Only move the camera / show instructions if we have a controllable object
+ bool showInstructions = false;
+ if (clientObjectID != 0)
+ {
+ Node@ ballNode = scene_.GetNode(clientObjectID);
+ if (ballNode !is null)
+ {
+ const float CAMERA_DISTANCE = 5.0f;
+
+ // Move camera some distance away from the ball
+ cameraNode.position = ballNode.position + cameraNode.rotation * Vector3::BACK * CAMERA_DISTANCE;
+ showInstructions = true;
+ }
+ }
+
+ instructionsText.visible = showInstructions;
+}
+
+void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // We only rotate the camera according to mouse movement since last frame, so do not need the time step
+ MoveCamera();
+}
+
+void HandlePhysicsPreStep(StringHash eventType, VariantMap& eventData)
+{
+ // This function is different on the client and server. The client collects controls (WASD controls + yaw angle)
+ // and sets them to its server connection object, so that they will be sent to the server automatically at a
+ // fixed rate, by default 30 FPS. The server will actually apply the controls (authoritative simulation.)
+ Connection@ serverConnection = network.serverConnection;
+
+ // Client: collect controls
+ if (serverConnection !is null)
+ {
+ Controls controls;
+
+ // Copy mouse yaw
+ controls.yaw = yaw;
+
+ // Only apply WASD controls if there is no focused UI element
+ if (ui.focusElement is null)
+ {
+ controls.Set(CTRL_FORWARD, input.keyDown[KEY_W]);
+ controls.Set(CTRL_BACK, input.keyDown[KEY_S]);
+ controls.Set(CTRL_LEFT, input.keyDown[KEY_A]);
+ controls.Set(CTRL_RIGHT, input.keyDown[KEY_D]);
+ }
+
+ serverConnection.controls = controls;
+ // In case the server wants to do position-based interest management using the NetworkPriority components, we should also
+ // tell it our observer (camera) position. In this sample it is not in use, but eg. the NinjaSnowWar game uses it
+ serverConnection.position = cameraNode.position;
+ }
+ // Server: apply controls to client objects
+ else if (network.serverRunning)
+ {
+ for (uint i = 0; i < clients.length; ++i)
+ {
+ Connection@ connection = clients[i].connection;
+ // Get the object this connection is controlling
+ Node@ ballNode = clients[i].object;
+
+ RigidBody@ body = ballNode.GetComponent("RigidBody");
+
+ // Torque is relative to the forward vector
+ Quaternion rotation(0.0f, connection.controls.yaw, 0.0f);
+
+ const float MOVE_TORQUE = 3.0f;
+
+ // Movement torque is applied before each simulation step, which happen at 60 FPS. This makes the simulation
+ // independent from rendering framerate. We could also apply forces (which would enable in-air control),
+ // but want to emphasize that it's a ball which should only control its motion by rolling along the ground
+ if (connection.controls.IsDown(CTRL_FORWARD))
+ body.ApplyTorque(rotation * Vector3::RIGHT * MOVE_TORQUE);
+ if (connection.controls.IsDown(CTRL_BACK))
+ body.ApplyTorque(rotation * Vector3::LEFT * MOVE_TORQUE);
+ if (connection.controls.IsDown(CTRL_LEFT))
+ body.ApplyTorque(rotation * Vector3::FORWARD * MOVE_TORQUE);
+ if (connection.controls.IsDown(CTRL_RIGHT))
+ body.ApplyTorque(rotation * Vector3::BACK * MOVE_TORQUE);
+ }
+ }
+}
+
+void HandleConnect(StringHash eventType, VariantMap& eventData)
+{
+ String address = textEdit.text.Trimmed();
+ if (address.empty)
+ address = "localhost"; // Use localhost to connect if nothing else specified
+
+ // Connect to server, specify scene to use as a client for replication
+ clientObjectID = 0; // Reset own object ID from possible previous connection
+ network.Connect(address, SERVER_PORT, scene_);
+
+ UpdateButtons();
+}
+
+void HandleDisconnect(StringHash eventType, VariantMap& eventData)
+{
+ Connection@ serverConnection = network.serverConnection;
+ // If we were connected to server, disconnect. Or if we were running a server, stop it. In both cases clear the
+ // scene of all replicated content, but let the local nodes & components (the static world + camera) stay
+ if (serverConnection !is null)
+ {
+ serverConnection.Disconnect();
+ scene_.Clear(true, false);
+ clientObjectID = 0;
+ }
+ // Or if we were running a server, stop it
+ else if (network.serverRunning)
+ {
+ network.StopServer();
+ scene_.Clear(true, false);
+ }
+
+ UpdateButtons();
+}
+
+void HandleStartServer(StringHash eventType, VariantMap& eventData)
+{
+ network.StartServer(SERVER_PORT);
+
+ UpdateButtons();
+}
+
+void HandleConnectionStatus(StringHash eventType, VariantMap& eventData)
+{
+ UpdateButtons();
+}
+
+void HandleClientConnected(StringHash eventType, VariantMap& eventData)
+{
+ // When a client connects, assign to scene to begin scene replication
+ Connection@ newConnection = eventData["Connection"].GetPtr();
+ newConnection.scene = scene_;
+
+ // Then create a controllable object for that client
+ Node@ newObject = CreateControllableObject();
+ Client newClient;
+ newClient.connection = newConnection;
+ newClient.object = newObject;
+ clients.Push(newClient);
+
+ // Finally send the object's node ID using a remote event
+ VariantMap remoteEventData;
+ remoteEventData["ID"] = newObject.id;
+ newConnection.SendRemoteEvent("ClientObjectID", true, remoteEventData);
+}
+
+void HandleClientDisconnected(StringHash eventType, VariantMap& eventData)
+{
+ // When a client disconnects, remove the controlled object
+ Connection@ connection = eventData["Connection"].GetPtr();
+ for (uint i = 0; i < clients.length; ++i)
+ {
+ if (clients[i].connection is connection)
+ {
+ clients[i].object.Remove();
+ clients.Erase(i);
+ break;
+ }
+ }
+}
+
+void HandleClientObjectID(StringHash eventType, VariantMap& eventData)
+{
+ clientObjectID = eventData["ID"].GetUInt();
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/18_CharacterDemo.as b/bin/Data/Scripts/18_CharacterDemo.as
new file mode 100644
index 0000000..f77d9a2
--- /dev/null
+++ b/bin/Data/Scripts/18_CharacterDemo.as
@@ -0,0 +1,514 @@
+// Moving character example.
+// This sample demonstrates:
+// - Controlling a humanoid character through physics
+// - Driving animations using the AnimationController component
+// - Manual control of a bone scene node
+// - Implementing 1st and 3rd person cameras, using raycasts to avoid the 3rd person camera clipping into scenery
+// - Saving and loading the variables of a script object
+// - Using touch inputs/gyroscope for iOS/Android (implemented through an external file)
+
+#include "Scripts/Utilities/Sample.as"
+#include "Scripts/Utilities/Touch.as"
+
+const int CTRL_FORWARD = 1;
+const int CTRL_BACK = 2;
+const int CTRL_LEFT = 4;
+const int CTRL_RIGHT = 8;
+const int CTRL_JUMP = 16;
+
+const float MOVE_FORCE = 0.8f;
+const float INAIR_MOVE_FORCE = 0.02f;
+const float BRAKE_FORCE = 0.2f;
+const float JUMP_FORCE = 7.0f;
+const float YAW_SENSITIVITY = 0.1f;
+const float INAIR_THRESHOLD_TIME = 0.1f;
+bool firstPerson = false; // First person camera flag
+
+Node@ characterNode;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create static scene content
+ CreateScene();
+
+ // Create the controllable character
+ CreateCharacter();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Subscribe to necessary events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create scene subsystem components
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("PhysicsWorld");
+
+ // Create camera and define viewport. Camera does not necessarily have to belong to the scene
+ cameraNode = Node();
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+ renderer.viewports[0] = Viewport(scene_, camera);
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(0.5f, 0.5f, 0.7f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ // Create the floor object
+ Node@ floorNode = scene_.CreateChild("Floor");
+ floorNode.position = Vector3(0.0f, -0.5f, 0.0f);
+ floorNode.scale = Vector3(200.0f, 1.0f, 200.0f);
+ StaticModel@ object = floorNode.CreateComponent("StaticModel");
+ object.model = cache.GetResource("Model", "Models/Box.mdl");
+ object.material = cache.GetResource("Material", "Materials/Stone.xml");
+
+ RigidBody@ body = floorNode.CreateComponent("RigidBody");
+ // Use collision layer bit 2 to mark world scenery. This is what we will raycast against to prevent camera from going
+ // inside geometry
+ body.collisionLayer = 2;
+ CollisionShape@ shape = floorNode.CreateComponent("CollisionShape");
+ shape.SetBox(Vector3::ONE);
+
+ // Create mushrooms of varying sizes
+ const uint NUM_MUSHROOMS = 60;
+ for (uint i = 0; i < NUM_MUSHROOMS; ++i)
+ {
+ Node@ objectNode = scene_.CreateChild("Mushroom");
+ objectNode.position = Vector3(Random(180.0f) - 90.0f, 0.0f, Random(180.0f) - 90.0f);
+ objectNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+ objectNode.SetScale(2.0f + Random(5.0f));
+ StaticModel@ object = objectNode.CreateComponent("StaticModel");
+ object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ object.castShadows = true;
+
+ RigidBody@ body = objectNode.CreateComponent("RigidBody");
+ body.collisionLayer = 2;
+ CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
+ shape.SetTriangleMesh(object.model, 0);
+ }
+
+ // Create movable boxes. Let them fall from the sky at first
+ const uint NUM_BOXES = 100;
+ for (uint i = 0; i < NUM_BOXES; ++i)
+ {
+ float scale = Random(2.0f) + 0.5f;
+
+ Node@ objectNode = scene_.CreateChild("Box");
+ objectNode.position = Vector3(Random(180.0f) - 90.0f, Random(10.0f) + 10.0f, Random(180.0f) - 90.0f);
+ objectNode.rotation = Quaternion(Random(360.0f), Random(360.0f), Random(360.0f));
+ objectNode.SetScale(scale);
+ StaticModel@ object = objectNode.CreateComponent("StaticModel");
+ object.model = cache.GetResource("Model", "Models/Box.mdl");
+ object.material = cache.GetResource("Material", "Materials/Stone.xml");
+ object.castShadows = true;
+
+ RigidBody@ body = objectNode.CreateComponent("RigidBody");
+ body.collisionLayer = 2;
+ // Bigger boxes will be heavier and harder to move
+ body.mass = scale * 2.0f;
+ CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
+ shape.SetBox(Vector3::ONE);
+ }
+}
+
+void CreateCharacter()
+{
+ characterNode = scene_.CreateChild("Jack");
+ characterNode.position = Vector3(0.0f, 1.0f, 0.0f);
+
+ Node@ adjNode = characterNode.CreateChild("AdjNode");
+ adjNode.rotation = Quaternion(180.0f, Vector3::UP);
+
+ // Create the rendering component + animation controller
+ AnimatedModel@ object = adjNode.CreateComponent("AnimatedModel");
+ object.model = cache.GetResource("Model", "Models/Mutant/Mutant.mdl");
+ object.material = cache.GetResource("Material", "Models/Mutant/Materials/mutant_M.xml");
+ object.castShadows = true;
+ adjNode.CreateComponent("AnimationController");
+
+ // Set the head bone for manual control
+ object.skeleton.GetBone("Mutant:Head").animated = false;
+
+ // Create rigidbody, and set non-zero mass so that the body becomes dynamic
+ RigidBody@ body = characterNode.CreateComponent("RigidBody");
+ body.collisionLayer = 1;
+ body.mass = 1.0f;
+
+ // Set zero angular factor so that physics doesn't turn the character on its own.
+ // Instead we will control the character yaw manually
+ body.angularFactor = Vector3::ZERO;
+
+ // Set the rigidbody to signal collision also when in rest, so that we get ground collisions properly
+ body.collisionEventMode = COLLISION_ALWAYS;
+
+ // Set a capsule shape for collision
+ CollisionShape@ shape = characterNode.CreateComponent("CollisionShape");
+ shape.SetCapsule(0.7f, 1.8f, Vector3(0.0f, 0.9f, 0.0f));
+
+ // Create the character logic object, which takes care of steering the rigidbody
+ characterNode.CreateScriptObject(scriptFile, "Character");
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys and mouse to move\n"
+ "Space to jump, F to toggle 1st/3rd person\n"
+ "F5 to save scene, F7 to load";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe to Update event for setting the character controls before physics simulation
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe to PostUpdate event for updating the camera position after physics simulation
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate");
+
+ // Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (characterNode is null)
+ return;
+
+ Character@ character = cast(characterNode.scriptObject);
+ if (character is null)
+ return;
+
+ // Clear previous controls
+ character.controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT | CTRL_JUMP, false);
+
+ // Update controls using touch utility
+ if (touchEnabled)
+ UpdateTouches(character.controls);
+
+ // Update controls using keys (desktop)
+ if (ui.focusElement is null)
+ {
+ if (touchEnabled || !useGyroscope)
+ {
+ character.controls.Set(CTRL_FORWARD, input.keyDown[KEY_W]);
+ character.controls.Set(CTRL_BACK, input.keyDown[KEY_S]);
+ character.controls.Set(CTRL_LEFT, input.keyDown[KEY_A]);
+ character.controls.Set(CTRL_RIGHT, input.keyDown[KEY_D]);
+ }
+ character.controls.Set(CTRL_JUMP, input.keyDown[KEY_SPACE]);
+
+ // Add character yaw & pitch from the mouse motion or touch input
+ if (touchEnabled)
+ {
+ for (uint i = 0; i < input.numTouches; ++i)
+ {
+ TouchState@ state = input.touches[i];
+ if (state.touchedElement is null) // Touch on empty space
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ if (camera is null)
+ return;
+
+ character.controls.yaw += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.x;
+ character.controls.pitch += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.y;
+ }
+ }
+ }
+ else
+ {
+ character.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
+ character.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
+ }
+ // Limit pitch
+ character.controls.pitch = Clamp(character.controls.pitch, -80.0f, 80.0f);
+ // Set rotation already here so that it's updated every rendering frame instead of every physics frame
+ characterNode.rotation = Quaternion(character.controls.yaw, Vector3::UP);
+
+ // Switch between 1st and 3rd person
+ if (input.keyPress[KEY_F])
+ firstPerson = !firstPerson;
+
+ // Turn on/off gyroscope on mobile platform
+ if (input.keyPress[KEY_G])
+ useGyroscope = !useGyroscope;
+
+ // Check for loading / saving the scene
+ if (input.keyPress[KEY_F5])
+ {
+ File saveFile(fileSystem.programDir + "Data/Scenes/CharacterDemo.xml", FILE_WRITE);
+ scene_.SaveXML(saveFile);
+ }
+ if (input.keyPress[KEY_F7])
+ {
+ File loadFile(fileSystem.programDir + "Data/Scenes/CharacterDemo.xml", FILE_READ);
+ scene_.LoadXML(loadFile);
+ // After loading we have to reacquire the character scene node, as it has been recreated
+ // Simply find by name as there's only one of them
+ characterNode = scene_.GetChild("Jack", true);
+ if (characterNode is null)
+ return;
+ }
+ }
+}
+
+void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (characterNode is null)
+ return;
+
+ Character@ character = cast(characterNode.scriptObject);
+ if (character is null)
+ return;
+
+ // Get camera lookat dir from character yaw + pitch
+ Quaternion rot = characterNode.rotation;
+ Quaternion dir = rot * Quaternion(character.controls.pitch, Vector3::RIGHT);
+
+ // Turn head to camera pitch, but limit to avoid unnatural animation
+ Node@ headNode = characterNode.GetChild("Mutant:Head", true);
+ float limitPitch = Clamp(character.controls.pitch, -45.0f, 45.0f);
+ Quaternion headDir = rot * Quaternion(limitPitch, Vector3(1.0f, 0.0f, 0.0f));
+ // This could be expanded to look at an arbitrary target, now just look at a point in front
+ Vector3 headWorldTarget = headNode.worldPosition + headDir * Vector3(0.0f, 0.0f, -1.0f);
+ headNode.LookAt(headWorldTarget, Vector3(0.0f, 1.0f, 0.0f));
+
+ if (firstPerson)
+ {
+ // First person camera: position to the head bone + offset slightly forward & up
+ cameraNode.position = headNode.worldPosition + rot * Vector3(0.0f, 0.15f, 0.2f);
+ cameraNode.rotation = dir;
+ }
+ else
+ {
+ // Third person camera: position behind the character
+ Vector3 aimPoint = characterNode.position + rot * Vector3(0.0f, 1.7f, 0.0f); // You can modify x Vector3 value to translate the fixed character position (indicative range[-2;2])
+
+ // Collide camera ray with static physics objects (layer bitmask 2) to ensure we see the character properly
+ Vector3 rayDir = dir * Vector3::BACK; // For indoor scenes you can use dir * Vector3(0.0, 0.0, -0.5) to prevent camera from crossing the walls
+ float rayDistance = cameraDistance;
+ PhysicsRaycastResult result = scene_.physicsWorld.RaycastSingle(Ray(aimPoint, rayDir), rayDistance, 2);
+ if (result.body !is null)
+ rayDistance = Min(rayDistance, result.distance);
+ rayDistance = Clamp(rayDistance, CAMERA_MIN_DIST, cameraDistance);
+
+ cameraNode.position = aimPoint + rayDir * rayDistance;
+ cameraNode.rotation = dir;
+ }
+}
+
+// Character script object class
+//
+// Those public member variables that can be expressed with a Variant and do not begin with an underscore are automatically
+// loaded / saved as attributes of the ScriptInstance component. We also have variables which can not be automatically saved
+// (yaw and pitch inside the Controls object) so we write manual binary format load / save methods for them. These functions
+// will be called by ScriptInstance when the script object is being loaded or saved.
+class Character : ScriptObject
+{
+ // Character controls.
+ Controls controls;
+ // Grounded flag for movement.
+ bool onGround = false;
+ // Jump flag.
+ bool okToJump = true;
+ // In air timer. Due to possible physics inaccuracy, character can be off ground for max. 1/10 second and still be allowed to move.
+ float inAirTimer = 0.0f;
+
+ void Start()
+ {
+ SubscribeToEvent(node, "NodeCollision", "HandleNodeCollision");
+ }
+
+ void Load(Deserializer& deserializer)
+ {
+ controls.yaw = deserializer.ReadFloat();
+ controls.pitch = deserializer.ReadFloat();
+ }
+
+ void Save(Serializer& serializer)
+ {
+ serializer.WriteFloat(controls.yaw);
+ serializer.WriteFloat(controls.pitch);
+ }
+
+ void HandleNodeCollision(StringHash eventType, VariantMap& eventData)
+ {
+ VectorBuffer contacts = eventData["Contacts"].GetBuffer();
+
+ while (!contacts.eof)
+ {
+ Vector3 contactPosition = contacts.ReadVector3();
+ Vector3 contactNormal = contacts.ReadVector3();
+ float contactDistance = contacts.ReadFloat();
+ float contactImpulse = contacts.ReadFloat();
+
+ // If contact is below node center and pointing up, assume it's a ground contact
+ if (contactPosition.y < (node.position.y + 1.0f))
+ {
+ float level = contactNormal.y;
+ if (level > 0.75)
+ onGround = true;
+ }
+ }
+ }
+
+ void FixedUpdate(float timeStep)
+ {
+ /// \todo Could cache the components for faster access instead of finding them each frame
+ RigidBody@ body = node.GetComponent("RigidBody");
+ AnimationController@ animCtrl = node.GetComponent("AnimationController", true);
+
+ // Update the in air timer. Reset if grounded
+ if (!onGround)
+ inAirTimer += timeStep;
+ else
+ inAirTimer = 0.0f;
+ // When character has been in air less than 1/10 second, it's still interpreted as being on ground
+ bool softGrounded = inAirTimer < INAIR_THRESHOLD_TIME;
+
+ // Update movement & animation
+ Quaternion rot = node.rotation;
+ Vector3 moveDir(0.0f, 0.0f, 0.0f);
+ Vector3 velocity = body.linearVelocity;
+ // Velocity on the XZ plane
+ Vector3 planeVelocity(velocity.x, 0.0f, velocity.z);
+
+ if (controls.IsDown(CTRL_FORWARD))
+ moveDir += Vector3::FORWARD;
+ if (controls.IsDown(CTRL_BACK))
+ moveDir += Vector3::BACK;
+ if (controls.IsDown(CTRL_LEFT))
+ moveDir += Vector3::LEFT;
+ if (controls.IsDown(CTRL_RIGHT))
+ moveDir += Vector3::RIGHT;
+
+ // Normalize move vector so that diagonal strafing is not faster
+ if (moveDir.lengthSquared > 0.0f)
+ moveDir.Normalize();
+
+ // If in air, allow control, but slower than when on ground
+ body.ApplyImpulse(rot * moveDir * (softGrounded ? MOVE_FORCE : INAIR_MOVE_FORCE));
+
+ if (softGrounded)
+ {
+ // When on ground, apply a braking force to limit maximum ground velocity
+ Vector3 brakeForce = -planeVelocity * BRAKE_FORCE;
+ body.ApplyImpulse(brakeForce);
+
+ // Jump. Must release jump control between jumps
+ if (controls.IsDown(CTRL_JUMP))
+ {
+ if (okToJump)
+ {
+ body.ApplyImpulse(Vector3::UP * JUMP_FORCE);
+ okToJump = false;
+ animCtrl.PlayExclusive("Models/Mutant/Mutant_Jump1.ani", 0, false, 0.2f);
+ }
+ }
+ else
+ okToJump = true;
+ }
+
+ if (!onGround)
+ {
+ animCtrl.PlayExclusive("Models/Mutant/Mutant_Jump1.ani", 0, false, 0.2f);
+ }
+ else
+ {
+ // Play walk animation if moving on ground, otherwise fade it out
+ if (softGrounded && !moveDir.Equals(Vector3::ZERO))
+ {
+ animCtrl.PlayExclusive("Models/Mutant/Mutant_Run.ani", 0, true, 0.2f);
+ // Set walk animation speed proportional to velocity
+ animCtrl.SetSpeed("Models/Mutant/Mutant_Run.ani", planeVelocity.length * 0.3f);
+ }
+ else
+ animCtrl.PlayExclusive("Models/Mutant/Mutant_Idle0.ani", 0, true, 0.2f);
+
+ }
+
+ // Reset grounded flag for next frame
+ onGround = false;
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " 1st/3rd" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Jump" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/19_VehicleDemo.as b/bin/Data/Scripts/19_VehicleDemo.as
new file mode 100644
index 0000000..18a5ba5
--- /dev/null
+++ b/bin/Data/Scripts/19_VehicleDemo.as
@@ -0,0 +1,396 @@
+// Vehicle example.
+// This sample demonstrates:
+// - Creating a heightmap terrain with collision
+// - Constructing a physical vehicle with rigid bodies for the hull and the wheels, joined with constraints
+// - Saving and loading the variables of a script object, including node & component references
+
+#include "Scripts/Utilities/Sample.as"
+
+const int CTRL_FORWARD = 1;
+const int CTRL_BACK = 2;
+const int CTRL_LEFT = 4;
+const int CTRL_RIGHT = 8;
+
+const float CAMERA_DISTANCE = 10.0f;
+const float YAW_SENSITIVITY = 0.1f;
+const float ENGINE_POWER = 10.0f;
+const float DOWN_FORCE = 10.0f;
+const float MAX_WHEEL_ANGLE = 22.5f;
+
+Node@ vehicleNode;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create static scene content
+ CreateScene();
+
+ // Create the controllable vehicle
+ CreateVehicle();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Subscribe to necessary events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create scene subsystem components
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("PhysicsWorld");
+
+ // Create camera and define viewport. Camera does not necessarily have to belong to the scene
+ cameraNode = Node();
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 500.0f;
+ renderer.viewports[0] = Viewport(scene_, camera);
+
+ // Create static scene content. First create a zone for ambient lighting and fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(0.5f, 0.5f, 0.7f);
+ zone.fogStart = 300.0f;
+ zone.fogEnd = 500.0f;
+ zone.boundingBox = BoundingBox(-2000.0f, 2000.0f);
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.3f, -0.5f, 0.425f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+ light.specularIntensity = 0.5f;
+
+ // Create heightmap terrain with collision
+ Node@ terrainNode = scene_.CreateChild("Terrain");
+ terrainNode.position = Vector3::ZERO;
+ Terrain@ terrain = terrainNode.CreateComponent("Terrain");
+ terrain.patchSize = 64;
+ terrain.spacing = Vector3(2.0f, 0.1f, 2.0f); // Spacing between vertices and vertical resolution of the height map
+ terrain.smoothing = true;
+ terrain.heightMap = cache.GetResource("Image", "Textures/HeightMap.png");
+ terrain.material = cache.GetResource("Material", "Materials/Terrain.xml");
+ // The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
+ // terrain patches and other objects behind it
+ terrain.occluder = true;
+
+ RigidBody@ body = terrainNode.CreateComponent("RigidBody");
+ body.collisionLayer = 2; // Use layer bitmask 2 for static geometry
+ CollisionShape@ shape = terrainNode.CreateComponent("CollisionShape");
+ shape.SetTerrain();
+
+ // Create 1000 mushrooms in the terrain. Always face outward along the terrain normal
+ const uint NUM_MUSHROOMS = 1000;
+ for (uint i = 0; i < NUM_MUSHROOMS; ++i)
+ {
+ Node@ objectNode = scene_.CreateChild("Mushroom");
+ Vector3 position(Random(2000.0f) - 1000.0f, 0.0f, Random(2000.0f) - 1000.0f);
+ position.y = terrain.GetHeight(position) - 0.1f;
+ objectNode.position = position;
+ // Create a rotation quaternion from up vector to terrain normal
+ objectNode.rotation = Quaternion(Vector3::UP, terrain.GetNormal(position));
+ objectNode.SetScale(3.0f);
+ StaticModel@ object = objectNode.CreateComponent("StaticModel");
+ object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ object.castShadows = true;
+
+ RigidBody@ body = objectNode.CreateComponent("RigidBody");
+ body.collisionLayer = 2;
+ CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
+ shape.SetTriangleMesh(object.model, 0);
+ }
+}
+
+void CreateVehicle()
+{
+ vehicleNode = scene_.CreateChild("Vehicle");
+ vehicleNode.position = Vector3(0.0f, 5.0f, 0.0f);
+
+ // Create the vehicle logic script object
+ Vehicle@ vehicle = cast(vehicleNode.CreateScriptObject(scriptFile, "Vehicle"));
+ // Create the rendering and physics components
+ vehicle.Init();
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys to drive, mouse/touch to rotate camera\n"
+ "F5 to save scene, F7 to load";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe to Update event for setting the vehicle controls before physics simulation
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe to PostUpdate event for updating the camera position after physics simulation
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate");
+
+ // Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (vehicleNode is null)
+ return;
+
+ Vehicle@ vehicle = cast(vehicleNode.scriptObject);
+ if (vehicle is null)
+ return;
+
+ // Get movement controls and assign them to the vehicle component. If UI has a focused element, clear controls
+ if (ui.focusElement is null)
+ {
+ vehicle.controls.Set(CTRL_FORWARD, input.keyDown[KEY_W]);
+ vehicle.controls.Set(CTRL_BACK, input.keyDown[KEY_S]);
+ vehicle.controls.Set(CTRL_LEFT, input.keyDown[KEY_A]);
+ vehicle.controls.Set(CTRL_RIGHT, input.keyDown[KEY_D]);
+
+ // Add yaw & pitch from the mouse motion. Used only for the camera, does not affect motion
+ if (touchEnabled)
+ {
+ for (uint i = 0; i < input.numTouches; ++i)
+ {
+ TouchState@ state = input.touches[i];
+ if (state.touchedElement is null) // Touch on empty space
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ if (camera is null)
+ return;
+
+ vehicle.controls.yaw += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.x;
+ vehicle.controls.pitch += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.y;
+ }
+ }
+ }
+ else
+ {
+ vehicle.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
+ vehicle.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
+ }
+ // Limit pitch
+ vehicle.controls.pitch = Clamp(vehicle.controls.pitch, 0.0f, 80.0f);
+
+ // Check for loading / saving the scene
+ if (input.keyPress[KEY_F5])
+ {
+ File saveFile(fileSystem.programDir + "Data/Scenes/VehicleDemo.xml", FILE_WRITE);
+ scene_.SaveXML(saveFile);
+ }
+ if (input.keyPress[KEY_F7])
+ {
+ File loadFile(fileSystem.programDir + "Data/Scenes/VehicleDemo.xml", FILE_READ);
+ scene_.LoadXML(loadFile);
+ // After loading we have to reacquire the vehicle scene node, as it has been recreated
+ // Simply find by name as there's only one of them
+ vehicleNode = scene_.GetChild("Vehicle", true);
+ }
+ }
+ else
+ vehicle.controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT, false);
+}
+
+void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (vehicleNode is null)
+ return;
+
+ Vehicle@ vehicle = cast(vehicleNode.scriptObject);
+ if (vehicle is null)
+ return;
+
+ // Physics update has completed. Position camera behind vehicle
+ Quaternion dir(vehicleNode.rotation.yaw, Vector3::UP);
+ dir = dir * Quaternion(vehicle.controls.yaw, Vector3::UP);
+ dir = dir * Quaternion(vehicle.controls.pitch, Vector3::RIGHT);
+
+ Vector3 cameraTargetPos = vehicleNode.position - dir * Vector3(0.0f, 0.0f, CAMERA_DISTANCE);
+ Vector3 cameraStartPos = vehicleNode.position;
+
+ // Raycast camera against static objects (physics collision mask 2)
+ // and move it closer to the vehicle if something in between
+ Ray cameraRay(cameraStartPos, (cameraTargetPos - cameraStartPos).Normalized());
+ float cameraRayLength = (cameraTargetPos - cameraStartPos).length;
+ PhysicsRaycastResult result = scene_.physicsWorld.RaycastSingle(cameraRay, cameraRayLength, 2);
+ if (result.body !is null)
+ cameraTargetPos = cameraStartPos + cameraRay.direction * (result.distance - 0.5f);
+
+ cameraNode.position = cameraTargetPos;
+ cameraNode.rotation = dir;
+}
+
+// Vehicle script object class
+//
+// When saving, the node and component handles are automatically converted into nodeID or componentID attributes
+// and are acquired from the scene when loading. The steering member variable will likewise be saved automatically.
+// The Controls object can not be automatically saved, so handle it manually in the Load() and Save() methods
+
+class Vehicle : ScriptObject
+{
+ Node@ frontLeft;
+ Node@ frontRight;
+ Node@ rearLeft;
+ Node@ rearRight;
+ Constraint@ frontLeftAxis;
+ Constraint@ frontRightAxis;
+ RigidBody@ hullBody;
+ RigidBody@ frontLeftBody;
+ RigidBody@ frontRightBody;
+ RigidBody@ rearLeftBody;
+ RigidBody@ rearRightBody;
+
+ // Current left/right steering amount (-1 to 1.)
+ float steering = 0.0f;
+ // Vehicle controls.
+ Controls controls;
+
+ void Load(Deserializer& deserializer)
+ {
+ controls.yaw = deserializer.ReadFloat();
+ controls.pitch = deserializer.ReadFloat();
+ }
+
+ void Save(Serializer& serializer)
+ {
+ serializer.WriteFloat(controls.yaw);
+ serializer.WriteFloat(controls.pitch);
+ }
+
+ void Init()
+ {
+ // This function is called only from the main program when initially creating the vehicle, not on scene load
+ StaticModel@ hullObject = node.CreateComponent("StaticModel");
+ hullBody = node.CreateComponent("RigidBody");
+ CollisionShape@ hullShape = node.CreateComponent("CollisionShape");
+
+ node.scale = Vector3(1.5f, 1.0f, 3.0f);
+ hullObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ hullObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+ hullObject.castShadows = true;
+ hullShape.SetBox(Vector3::ONE);
+ hullBody.mass = 4.0f;
+ hullBody.linearDamping = 0.2f; // Some air resistance
+ hullBody.angularDamping = 0.5f;
+ hullBody.collisionLayer = 1;
+
+ frontLeft = InitWheel("FrontLeft", Vector3(-0.6f, -0.4f, 0.3f));
+ frontRight = InitWheel("FrontRight", Vector3(0.6f, -0.4f, 0.3f));
+ rearLeft = InitWheel("RearLeft", Vector3(-0.6f, -0.4f, -0.3f));
+ rearRight = InitWheel("RearRight", Vector3(0.6f, -0.4f, -0.3f));
+
+ frontLeftAxis = frontLeft.GetComponent("Constraint");
+ frontRightAxis = frontRight.GetComponent("Constraint");
+ frontLeftBody = frontLeft.GetComponent("RigidBody");
+ frontRightBody = frontRight.GetComponent("RigidBody");
+ rearLeftBody = rearLeft.GetComponent("RigidBody");
+ rearRightBody = rearRight.GetComponent("RigidBody");
+ }
+
+ Node@ InitWheel(const String&in name, const Vector3&in offset)
+ {
+ // Note: do not parent the wheel to the hull scene node. Instead create it on the root level and let the physics
+ // constraint keep it together
+ Node@ wheelNode = scene.CreateChild(name);
+ wheelNode.position = node.LocalToWorld(offset);
+ wheelNode.rotation = node.worldRotation * (offset.x >= 0.0f ? Quaternion(0.0f, 0.0f, -90.0f) :
+ Quaternion(0.0f, 0.0f, 90.0f));
+ wheelNode.scale = Vector3(0.8f, 0.5f, 0.8f);
+
+ StaticModel@ wheelObject = wheelNode.CreateComponent("StaticModel");
+ RigidBody@ wheelBody = wheelNode.CreateComponent("RigidBody");
+ CollisionShape@ wheelShape = wheelNode.CreateComponent("CollisionShape");
+ Constraint@ wheelConstraint = wheelNode.CreateComponent("Constraint");
+
+ wheelObject.model = cache.GetResource("Model", "Models/Cylinder.mdl");
+ wheelObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+ wheelObject.castShadows = true;
+ wheelShape.SetSphere(1.0f);
+ wheelBody.friction = 1;
+ wheelBody.mass = 1;
+ wheelBody.linearDamping = 0.2f; // Some air resistance
+ wheelBody.angularDamping = 0.75f; // Could also use rolling friction
+ wheelBody.collisionLayer = 1;
+ wheelConstraint.constraintType = CONSTRAINT_HINGE;
+ wheelConstraint.otherBody = node.GetComponent("RigidBody");
+ wheelConstraint.worldPosition = wheelNode.worldPosition; // Set constraint's both ends at wheel's location
+ wheelConstraint.axis = Vector3::UP; // Wheel rotates around its local Y-axis
+ wheelConstraint.otherAxis = offset.x >= 0.0f ? Vector3::RIGHT : Vector3::LEFT; // Wheel's hull axis points either left or right
+ wheelConstraint.lowLimit = Vector2(-180.0f, 0.0f); // Let the wheel rotate freely around the axis
+ wheelConstraint.highLimit = Vector2(180.0f, 0.0f);
+ wheelConstraint.disableCollision = true; // Let the wheel intersect the vehicle hull
+
+ return wheelNode;
+ }
+
+ void FixedUpdate(float timeStep)
+ {
+ float newSteering = 0.0f;
+ float accelerator = 0.0f;
+
+ if (controls.IsDown(CTRL_LEFT))
+ newSteering = -1.0f;
+ if (controls.IsDown(CTRL_RIGHT))
+ newSteering = 1.0f;
+ if (controls.IsDown(CTRL_FORWARD))
+ accelerator = 1.0f;
+ if (controls.IsDown(CTRL_BACK))
+ accelerator = -0.5f;
+
+ // When steering, wake up the wheel rigidbodies so that their orientation is updated
+ if (newSteering != 0.0f)
+ {
+ frontLeftBody.Activate();
+ frontRightBody.Activate();
+ steering = steering * 0.95f + newSteering * 0.05f;
+ }
+ else
+ steering = steering * 0.8f + newSteering * 0.2f;
+
+ Quaternion steeringRot(0.0f, steering * MAX_WHEEL_ANGLE, 0.0f);
+
+ frontLeftAxis.otherAxis = steeringRot * Vector3::LEFT;
+ frontRightAxis.otherAxis = steeringRot * Vector3::RIGHT;
+
+ if (accelerator != 0.0f)
+ {
+ // Torques are applied in world space, so need to take the vehicle & wheel rotation into account
+ Vector3 torqueVec = Vector3(ENGINE_POWER * accelerator, 0.0f, 0.0f);
+
+ frontLeftBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
+ frontRightBody.ApplyTorque(node.rotation * steeringRot * torqueVec);
+ rearLeftBody.ApplyTorque(node.rotation * torqueVec);
+ rearRightBody.ApplyTorque(node.rotation * torqueVec);
+ }
+
+ // Apply downforce proportional to velocity
+ Vector3 localVelocity = hullBody.rotation.Inverse() * hullBody.linearVelocity;
+ hullBody.ApplyForce(hullBody.rotation * Vector3::DOWN * Abs(localVelocity.z) * DOWN_FORCE);
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/20_HugeObjectCount.as b/bin/Data/Scripts/20_HugeObjectCount.as
new file mode 100644
index 0000000..9a5b2eb
--- /dev/null
+++ b/bin/Data/Scripts/20_HugeObjectCount.as
@@ -0,0 +1,237 @@
+// Huge object count example.
+// This sample demonstrates:
+// - Creating a scene with 250 x 250 simple objects
+// - Competing with http://yosoygames.com.ar/wp/2013/07/ogre-2-0-is-up-to-3x-faster/ :)
+// - Allowing examination of performance hotspots in the rendering code
+// - Optionally speeding up rendering by grouping objects with the StaticModelGroup component
+
+#include "Scripts/Utilities/Sample.as"
+
+Array boxNodes;
+bool animate = false;
+bool useGroups = false;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ if (scene_ is null)
+ scene_ = Scene();
+ else
+ {
+ scene_.Clear();
+ boxNodes.Clear();
+ }
+
+ // Create the Octree component to the scene so that drawable objects can be rendered. Use default volume
+ // (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_.CreateComponent("Octree");
+
+ // Create a Zone for ambient light & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.fogColor = Color(0.2f, 0.2f, 0.2f);
+ zone.fogStart = 200.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(-0.6f, -1.0f, -0.8f); // The direction vector does not need to be normalized
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+
+ if (!useGroups)
+ {
+ light.color = Color(0.7f, 0.35f, 0.0f);
+
+ // Create individual box StaticModels in the scene
+ for (int y = -125; y < 125; ++y)
+ {
+ for (int x = -125; x < 125; ++x)
+ {
+ Node@ boxNode = scene_.CreateChild("Box");
+ boxNode.position = Vector3(x * 0.3f, 0.0f, y * 0.3f);
+ boxNode.SetScale(0.25f);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxNodes.Push(boxNode);
+ }
+ }
+ }
+ else
+ {
+ light.color = Color(0.6f, 0.6f, 0.6f);
+ light.specularIntensity = 1.5f;
+
+ // Create StaticModelGroups in the scene
+ StaticModelGroup@ lastGroup;
+
+ for (int y = -125; y < 125; ++y)
+ {
+ for (int x = -125; x < 125; ++x)
+ {
+ // Create new group if no group yet, or the group has already "enough" objects. The tradeoff is between culling
+ // accuracy and the amount of CPU processing needed for all the objects. Note that the group's own transform
+ // does not matter, and it does not render anything if instance nodes are not added to it
+ if (lastGroup is null || lastGroup.numInstanceNodes >= 25 * 25)
+ {
+ Node@ boxGroupNode = scene_.CreateChild("BoxGroup");
+ lastGroup = boxGroupNode.CreateComponent("StaticModelGroup");
+ lastGroup.model = cache.GetResource("Model", "Models/Box.mdl");
+ }
+
+ Node@ boxNode = scene_.CreateChild("Box");
+ boxNode.position = Vector3(x * 0.3f, 0.0f, y * 0.3f);
+ boxNode.SetScale(0.25f);
+ boxNodes.Push(boxNode);
+ lastGroup.AddInstanceNode(boxNode);
+ }
+ }
+ }
+
+ // Create the camera. Create it outside the scene so that we can clear the whole scene without affecting it
+ if (cameraNode is null)
+ {
+ cameraNode = Node("Camera");
+ cameraNode.position = Vector3(0.0f, 10.0f, -100.0f);
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+ }
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys and mouse to move\n"
+ "Space to toggle animation\n"
+ "G to toggle object group optimization";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+void AnimateObjects(float timeStep)
+{
+ const float ROTATE_SPEED = 15.0f;
+ // Rotate about the Z axis (roll)
+ Quaternion rotateQuat(ROTATE_SPEED * timeStep, Vector3::FORWARD);
+
+ for (uint i = 0; i < boxNodes.length; ++i)
+ boxNodes[i].Rotate(rotateQuat);
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Toggle animation with space
+ if (input.keyPress[KEY_SPACE])
+ animate = !animate;
+
+ // Toggle grouped / ungrouped mode
+ if (input.keyPress[KEY_G])
+ {
+ useGroups = !useGroups;
+ CreateScene();
+ }
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+
+ // Animate scene if enabled
+ if (animate)
+ AnimateObjects(timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Group" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Animation" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/23_Water.as b/bin/Data/Scripts/23_Water.as
new file mode 100644
index 0000000..1c0e433
--- /dev/null
+++ b/bin/Data/Scripts/23_Water.as
@@ -0,0 +1,229 @@
+// Water example.
+// This sample demonstrates:
+// - Creating a large plane to represent a water body for rendering
+// - Setting up a second camera to render reflections on the water surface
+
+#include "Scripts/Utilities/Sample.as"
+
+Node@ reflectionCameraNode;
+Node@ waterNode;
+Plane waterPlane;
+Plane waterClipPlane;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewports for displaying the scene and rendering the water reflection
+ SetupViewports();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update and render post-update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(1.0f, 1.0f, 1.0f);
+ zone.fogStart = 500.0f;
+ zone.fogEnd = 750.0f;
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.3f, -0.5f, 0.425f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+ light.specularIntensity = 0.5f;
+ // Apply slightly overbright lighting to match the skybox
+ light.color = Color(1.2f, 1.2f, 1.2f);
+
+ // Create skybox. The Skybox component is used like StaticModel, but it will be always located at the camera, giving the
+ // illusion of the box planes being far away. Use just the ordinary Box model and a suitable material, whose shader will
+ // generate the necessary 3D texture coordinates for cube mapping
+ Node@ skyNode = scene_.CreateChild("Sky");
+ skyNode.SetScale(500.0); // The scale actually does not matter
+ Skybox@ skybox = skyNode.CreateComponent("Skybox");
+ skybox.model = cache.GetResource("Model", "Models/Box.mdl");
+ skybox.material = cache.GetResource("Material", "Materials/Skybox.xml");
+
+ // Create heightmap terrain
+ Node@ terrainNode = scene_.CreateChild("Terrain");
+ terrainNode.position = Vector3(0.0f, 0.0f, 0.0f);
+ Terrain@ terrain = terrainNode.CreateComponent("Terrain");
+ terrain.patchSize = 64;
+ terrain.spacing = Vector3(2.0f, 0.5f, 2.0f); // Spacing between vertices and vertical resolution of the height map
+ terrain.smoothing = true;
+ terrain.heightMap = cache.GetResource("Image", "Textures/HeightMap.png");
+ terrain.material = cache.GetResource("Material", "Materials/Terrain.xml");
+ // The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
+ // terrain patches and other objects behind it
+ terrain.occluder = true;
+
+ // Create 1000 boxes in the terrain. Always face outward along the terrain normal
+ const uint NUM_OBJECTS = 1000;
+ for (uint i = 0; i < NUM_OBJECTS; ++i)
+ {
+ Node@ objectNode = scene_.CreateChild("Box");
+ Vector3 position(Random(2000.0f) - 1000.0f, 0.0f, Random(2000.0f) - 1000.0f);
+ position.y = terrain.GetHeight(position) + 2.25f;
+ objectNode.position = position;
+ // Create a rotation quaternion from up vector to terrain normal
+ objectNode.rotation = Quaternion(Vector3(0.0f, 1.0f, 0.0f), terrain.GetNormal(position));
+ objectNode.SetScale(5.0f);
+ StaticModel@ object = objectNode.CreateComponent("StaticModel");
+ object.model = cache.GetResource("Model", "Models/Box.mdl");
+ object.material = cache.GetResource("Material", "Materials/Stone.xml");
+ object.castShadows = true;
+ }
+
+ // Create a water plane object that is as large as the terrain
+ waterNode = scene_.CreateChild("Water");
+ waterNode.scale = Vector3(2048.0f, 1.0f, 2048.0f);
+ waterNode.position = Vector3(0.0f, 5.0f, 0.0f);
+ StaticModel@ water = waterNode.CreateComponent("StaticModel");
+ water.model = cache.GetResource("Model", "Models/Plane.mdl");
+ water.material = cache.GetResource("Material", "Materials/Water.xml");
+ // Set a different viewmask on the water plane to be able to hide it from the reflection camera
+ water.viewMask = 0x80000000;
+
+ // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside
+ // the scene, because we want it to be unaffected by scene load / save
+ cameraNode = Node();
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 750.0f;
+
+ // Set an initial position for the camera scene node above the ground
+ cameraNode.position = Vector3(0.0f, 7.0f, -20.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewports()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+
+ // Create a mathematical plane to represent the water in calculations
+ waterPlane = Plane(waterNode.worldRotation * Vector3(0.0f, 1.0f, 0.0f), waterNode.worldPosition);
+ // Create a downward biased plane for reflection view clipping. Biasing is necessary to avoid too aggressive clipping
+ waterClipPlane = Plane(waterNode.worldRotation * Vector3(0.0f, 1.0f, 0.0f), waterNode.worldPosition -
+ Vector3(0.0f, 0.1f, 0.0f));
+
+ // Create camera for water reflection
+ // It will have the same farclip and position as the main viewport camera, but uses a reflection plane to modify
+ // its position when rendering
+ reflectionCameraNode = cameraNode.CreateChild();
+ Camera@ reflectionCamera = reflectionCameraNode.CreateComponent("Camera");
+ reflectionCamera.farClip = 750.0;
+ reflectionCamera.viewMask = 0x7fffffff; // Hide objects with only bit 31 in the viewmask (the water plane)
+ reflectionCamera.autoAspectRatio = false;
+ reflectionCamera.useReflection = true;
+ reflectionCamera.reflectionPlane = waterPlane;
+ reflectionCamera.useClipping = true; // Enable clipping of geometry behind water plane
+ reflectionCamera.clipPlane = waterClipPlane;
+ // The water reflection texture is rectangular. Set reflection camera aspect ratio to match
+ reflectionCamera.aspectRatio = float(graphics.width) / float(graphics.height);
+ // View override flags could be used to optimize reflection rendering. For example disable shadows
+ //reflectionCamera.viewOverrideFlags = VO_DISABLE_SHADOWS;
+
+ // Create a texture and setup viewport for water reflection. Assign the reflection texture to the diffuse
+ // texture unit of the water material
+ int texSize = 1024;
+ Texture2D@ renderTexture = Texture2D();
+ renderTexture.SetSize(texSize, texSize, GetRGBFormat(), TEXTURE_RENDERTARGET);
+ renderTexture.filterMode = FILTER_BILINEAR;
+ RenderSurface@ surface = renderTexture.renderSurface;
+ Viewport@ rttViewport = Viewport(scene_, reflectionCamera);
+ surface.viewports[0] = rttViewport;
+ Material@ waterMat = cache.GetResource("Material", "Materials/Water.xml");
+ waterMat.textures[TU_DIFFUSE] = renderTexture;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 30.0;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0, 90.0);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ // In case resolution has changed, adjust the reflection camera aspect ratio
+ Camera@ reflectionCamera = reflectionCameraNode.GetComponent("Camera");
+ reflectionCamera.aspectRatio = float(graphics.width) / float(graphics.height);
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/24_Urho2DSprite.as b/bin/Data/Scripts/24_Urho2DSprite.as
new file mode 100644
index 0000000..a673521
--- /dev/null
+++ b/bin/Data/Scripts/24_Urho2DSprite.as
@@ -0,0 +1,216 @@
+// Urho2D sprite example.
+// This sample demonstrates:
+// - Creating a 2D scene with sprite
+// - Displaying the scene using the Renderer subsystem
+// - Handling keyboard to move and zoom 2D camera
+
+#include "Scripts/Utilities/Sample.as"
+
+Array spriteNodes;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 0.0f, -10.0f);
+
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.orthographic = true;
+ camera.orthoSize = graphics.height * PIXEL_SIZE;
+
+ Sprite2D@ sprite = cache.GetResource("Sprite2D", "Urho2D/Aster.png");
+ if (sprite is null)
+ return;
+
+ uint halfWidth = uint(graphics.width * PIXEL_SIZE * 0.5f);
+ uint halfHeight = uint(graphics.height * PIXEL_SIZE * 0.5f);
+ // Create more StaticModel objects to the scene, randomly positioned, rotated and scaled. For rotation, we construct a
+ // quaternion from Euler angles where the Y angle (rotation about the Y axis) is randomized. The mushroom model contains
+ // LOD levels, so the StaticModel component will automatically select the LOD level according to the view distance (you'll
+ // see the model get simpler as it moves further away). Finally, rendering a large number of the same object with the
+ // same material allows instancing to be used, if the GPU supports it. This reduces the amount of CPU work in rendering the
+ // scene.
+ const uint NUM_SPRITES = 200;
+ for (uint i = 0; i < NUM_SPRITES; ++i)
+ {
+ Node@ spriteNode = scene_.CreateChild("StaticSprite2D");
+ spriteNode.position = Vector3(Random(-halfWidth, halfWidth), Random(-halfHeight, halfHeight), 0.0f);
+
+ StaticSprite2D@ staticSprite = spriteNode.CreateComponent("StaticSprite2D");
+ // Set color
+ staticSprite.color = Color(Random(1.0f), Random(1.0f), Random(1.0f), 1.0f);
+ // Set blend mode
+ staticSprite.blendMode = BLEND_ALPHA;
+ // Set sprite
+ staticSprite.sprite = sprite;
+
+ spriteNode.vars["MoveSpeed"] = Vector3(Random(-2.0f, 2.0f), Random(-2.0f, 2.0f), 0.0f);
+ spriteNode.vars["RotateSpeed"] = Random(-90.0f, 90.0f);
+
+ spriteNodes.Push(spriteNode);
+ }
+
+ AnimationSet2D@ animationSet = cache.GetResource("AnimationSet2D", "Urho2D/GoldIcon.scml");
+ if (animationSet is null)
+ return;
+ Node@ spriteNode = scene_.CreateChild("AnimatedSprite2D");
+ spriteNode.position = Vector3(0.0f, 0.0f, -1.0f);
+
+ AnimatedSprite2D@ animatedSprite = spriteNode.CreateComponent("AnimatedSprite2D");
+ // Set animation
+ animatedSprite.animationSet = animationSet;
+ animatedSprite.animation = "idle";
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move, Use PageUp PageDown to zoom.";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 4.0f;
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::UP * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::DOWN * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ if (input.keyDown[KEY_PAGEUP])
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ camera.zoom = camera.zoom * 1.01f;
+ }
+
+ if (input.keyDown[KEY_PAGEDOWN])
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ camera.zoom = camera.zoom * 0.99f;
+ }
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+
+ float halfWidth = graphics.width * 0.5f * PIXEL_SIZE;
+ float halfHeight = graphics.height * 0.5f * PIXEL_SIZE;
+
+ // Go through all sprites
+ for (uint i = 0; i < spriteNodes.length; ++i)
+ {
+ Node@ spriteNode = spriteNodes[i];
+
+ Vector3 moveSpeed = spriteNode.vars["MoveSpeed"].GetVector3();
+ Vector3 newPosition = spriteNode.position + moveSpeed * timeStep;
+
+ if (newPosition.x < -halfWidth || newPosition.x > halfWidth)
+ {
+ newPosition.x = spriteNode.position.x;
+ moveSpeed.x = -moveSpeed.x;
+ spriteNode.vars["MoveSpeed"] = moveSpeed;
+ }
+
+ if (newPosition.y < -halfHeight || newPosition.y > halfHeight)
+ {
+ newPosition.y = spriteNode.position.y;
+ moveSpeed.y = -moveSpeed.y;
+ spriteNode.vars["MoveSpeed"] = moveSpeed;
+ }
+
+ spriteNode.position = newPosition;
+ spriteNode.Roll(spriteNode.vars["RotateSpeed"].GetFloat() * timeStep);
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Zoom In" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Zoom Out" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/25_Urho2DParticle.as b/bin/Data/Scripts/25_Urho2DParticle.as
new file mode 100644
index 0000000..5e18964
--- /dev/null
+++ b/bin/Data/Scripts/25_Urho2DParticle.as
@@ -0,0 +1,123 @@
+// Urho2D particle example.
+// This sample demonstrates:
+// - Creating a 2D scene with particle
+// - Displaying the scene using the Renderer subsystem
+// - Handling mouse move to move particle
+
+#include "Scripts/Utilities/Sample.as"
+
+Node@ particleNode;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Set mouse visible
+ input.mouseVisible = true;
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 0.0f, -10.0f);
+
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.orthographic = true;
+ camera.orthoSize = graphics.height * PIXEL_SIZE;
+ camera.zoom = 1.2f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.2) is set for full visibility at 1280x800 resolution)
+
+ ParticleEffect2D@ particleEffect = cache.GetResource("ParticleEffect2D", "Urho2D/sun.pex");
+ if (particleEffect is null)
+ return;
+
+ particleNode = scene_.CreateChild("ParticleEmitter2D");
+ ParticleEmitter2D@ particleEmitter = particleNode.CreateComponent("ParticleEmitter2D");
+ particleEmitter.effect = particleEffect;
+
+ ParticleEffect2D@ greenSpiralEffect = cache.GetResource("ParticleEffect2D", "Urho2D/greenspiral.pex");
+ if (greenSpiralEffect is null)
+ return;
+
+ Node@ greenSpiralNode = scene_.CreateChild("GreenSpiral");
+ ParticleEmitter2D@ greenSpiralEmitter = greenSpiralNode.CreateComponent("ParticleEmitter2D");
+ greenSpiralEmitter.effect = greenSpiralEffect;
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use mouse to move the particle.";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleMouseMove() function for tracking mouse/touch move events
+ SubscribeToEvent("MouseMove", "HandleMouseMove");
+ if (touchEnabled)
+ SubscribeToEvent("TouchMove", "HandleMouseMove");
+
+ // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleMouseMove(StringHash eventType, VariantMap& eventData)
+{
+ if (particleNode !is null)
+ {
+ float x = eventData["x"].GetInt();
+ float y = eventData["y"].GetInt();
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ particleNode.position = camera.ScreenToWorldPoint(Vector3(x / graphics.width, y / graphics.height, 10.0f));
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/26_ConsoleInput.as b/bin/Data/Scripts/26_ConsoleInput.as
new file mode 100644
index 0000000..4abbad3
--- /dev/null
+++ b/bin/Data/Scripts/26_ConsoleInput.as
@@ -0,0 +1,261 @@
+// Console input example.
+// This sample demonstrates:
+// - Implementing a crude text adventure game, which accepts input both through the engine console,
+// and standard input.
+// - Disabling default execution of console commands as immediate mode AngelScript.
+// - Adding autocomplete options to the engine console.
+
+#include "Scripts/Utilities/Sample.as"
+
+bool gameOn;
+bool foodAvailable;
+bool eatenLastTurn;
+int numTurns;
+int hunger;
+int urhoThreat;
+
+// Hunger level descriptions
+String[] hungerLevels = {
+ "bursting",
+ "well-fed",
+ "fed",
+ "hungry",
+ "very hungry",
+ "starving"
+};
+
+// Urho threat level descriptions
+String[] urhoThreatLevels = {
+ "Suddenly Urho appears from a dark corner of the fish tank",
+ "Urho seems to have his eyes set on you",
+ "Urho is homing in on you mercilessly"
+};
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Disable default execution of AngelScript from the console
+ script.executeConsoleCommands = false;
+
+ // Subscribe to console commands and the frame update
+ SubscribeToEvent("ConsoleCommand", "HandleConsoleCommand");
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe key down event
+ SubscribeToEvent("KeyDown", "HandleEscKeyDown");
+
+ // Hide logo to make room for the console
+ SetLogoVisible(false);
+
+ // Show the console by default, make it large
+ console.numRows = graphics.height / 16;
+ console.numBufferedRows = 2 * console.numRows;
+ console.commandInterpreter = "ScriptEventInvoker";
+ console.visible = true;
+ console.closeButton.visible = false;
+ console.AddAutoComplete("help");
+ console.AddAutoComplete("eat");
+ console.AddAutoComplete("hide");
+ console.AddAutoComplete("wait");
+ console.AddAutoComplete("score");
+ console.AddAutoComplete("quit");
+
+ // Show OS mouse cursor
+ input.mouseVisible = true;
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Open the operating system console window (for stdin / stdout) if not open yet
+ // Do not open in fullscreen, as this would cause constant device loss
+ if (!graphics.fullscreen)
+ OpenConsoleWindow();
+
+ // Initialize game and print the welcome message
+ StartGame();
+
+ // Randomize from system clock
+ SetRandomSeed(time.systemTime);
+}
+
+void HandleConsoleCommand(StringHash eventType, VariantMap& eventData)
+{
+ if (eventData["Id"].GetString() == "ScriptEventInvoker")
+ HandleInput(eventData["Command"].GetString());
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Check if there is input from stdin
+ String input = GetConsoleInput();
+ if (input.length > 0)
+ HandleInput(input);
+}
+
+void HandleEscKeyDown(StringHash eventType, VariantMap& eventData)
+{
+ // Unlike the other samples, exiting the engine when ESC is pressed instead of just closing the console
+ if (eventData["Key"].GetInt() == KEY_ESCAPE)
+ engine.Exit();
+}
+
+void StartGame()
+{
+ Print("Welcome to the Urho adventure game! You are the newest fish in the tank; your\n"
+ "objective is to survive as long as possible. Beware of hunger and the merciless\n"
+ "predator cichlid Urho, who appears from time to time. Evading Urho is easier\n"
+ "with an empty stomach. Type 'help' for available commands.");
+
+ gameOn = true;
+ foodAvailable = false;
+ eatenLastTurn = false;
+ numTurns = 0;
+ hunger = 2;
+ urhoThreat = 0;
+}
+
+void EndGame(const String&in message)
+{
+ Print(message);
+ Print("Game over! You survived " + String(numTurns) + " turns.\n"
+ "Do you want to play again (Y/N)?");
+
+ gameOn = false;
+}
+
+void Advance()
+{
+ if (urhoThreat > 0)
+ {
+ ++urhoThreat;
+ if (urhoThreat > 3)
+ {
+ EndGame("Urho has eaten you!");
+ return;
+ }
+ }
+ else if (urhoThreat < 0)
+ ++urhoThreat;
+ if (urhoThreat == 0 && Random() < 0.2f)
+ ++urhoThreat;
+
+ if (urhoThreat > 0)
+ Print(urhoThreatLevels[urhoThreat - 1] + ".");
+
+ if ((numTurns & 3) == 0 && !eatenLastTurn)
+ {
+ ++hunger;
+ if (hunger > 5)
+ {
+ EndGame("You have died from starvation!");
+ return;
+ }
+ else
+ Print("You are " + hungerLevels[hunger] + ".");
+ }
+
+ eatenLastTurn = false;
+
+ if (foodAvailable)
+ {
+ Print("The floating pieces of fish food are quickly eaten by other fish in the tank.");
+ foodAvailable = false;
+ }
+ else if (Random() < 0.15f)
+ {
+ Print("The overhead dispenser drops pieces of delicious fish food to the water!");
+ foodAvailable = true;
+ }
+
+ ++numTurns;
+}
+
+void HandleInput(const String&in input)
+{
+ String inputLower = input.ToLower().Trimmed();
+ if (inputLower.empty)
+ {
+ Print("Empty input given!");
+ return;
+ }
+
+ if (inputLower == "quit" || inputLower == "exit")
+ engine.Exit();
+ else if (gameOn)
+ {
+ // Game is on
+ if (inputLower == "help")
+ Print("The following commands are available: 'eat', 'hide', 'wait', 'score', 'quit'.");
+ else if (inputLower == "score")
+ Print("You have survived " + String(numTurns) + " turns.");
+ else if (inputLower == "eat")
+ {
+ if (foodAvailable)
+ {
+ Print("You eat several pieces of fish food.");
+ foodAvailable = false;
+ eatenLastTurn = true;
+ hunger -= (hunger > 3) ? 2 : 1;
+ if (hunger < 0)
+ {
+ EndGame("You have killed yourself by over-eating!");
+ return;
+ }
+ else
+ Print("You are now " + hungerLevels[hunger] + ".");
+ }
+ else
+ Print("There is no food available.");
+
+ Advance();
+ }
+ else if (inputLower == "wait")
+ {
+ Print("Time passes...");
+ Advance();
+ }
+ else if (inputLower == "hide")
+ {
+ if (urhoThreat > 0)
+ {
+ bool evadeSuccess = hunger > 2 || Random() < 0.5f;
+ if (evadeSuccess)
+ {
+ Print("You hide behind the thick bottom vegetation, until Urho grows bored.");
+ urhoThreat = -2;
+ }
+ else
+ Print("Your movements are too slow; you are unable to hide from Urho.");
+ }
+ else
+ Print("There is nothing to hide from.");
+
+ Advance();
+ }
+ else
+ Print("Cannot understand the input '" + input + "'.");
+ }
+ else
+ {
+ // Game is over, wait for (y)es or (n)o reply
+ if (inputLower[0] == 'y')
+ StartGame();
+ else if (inputLower[0] == 'n')
+ engine.Exit();
+ else
+ Print("Please answer 'y' or 'n'.");
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/27_Urho2DPhysics.as b/bin/Data/Scripts/27_Urho2DPhysics.as
new file mode 100644
index 0000000..3fd27d0
--- /dev/null
+++ b/bin/Data/Scripts/27_Urho2DPhysics.as
@@ -0,0 +1,211 @@
+// Urho2D physics sample.
+// This sample demonstrates:
+// - Creating both static and moving 2D physics objects to a scene
+// - Displaying physics debug geometry
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 0.0f, -10.0f);
+
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.orthographic = true;
+ camera.orthoSize = graphics.height * PIXEL_SIZE;
+ camera.zoom = 1.2f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.2) is set for full visibility at 1280x800 resolution)
+
+ // Create 2D physics world component
+ scene_.CreateComponent("PhysicsWorld2D");
+
+ Sprite2D@ boxSprite = cache.GetResource("Sprite2D", "Urho2D/Box.png");
+ Sprite2D@ ballSprite = cache.GetResource("Sprite2D", "Urho2D/Ball.png");
+
+ // Create ground.
+ Node@ groundNode = scene_.CreateChild("Ground");
+ groundNode.position = Vector3(0.0f, -3.0f, 0.0f);
+ groundNode.scale = Vector3(200.0f, 1.0f, 0.0f);
+
+ // Create 2D rigid body for gound
+ RigidBody2D@ groundBody = groundNode.CreateComponent("RigidBody2D");
+
+ StaticSprite2D@ groundSprite = groundNode.CreateComponent("StaticSprite2D");
+ groundSprite.sprite = boxSprite;
+
+ // Create box collider for ground
+ CollisionBox2D@ groundShape = groundNode.CreateComponent("CollisionBox2D");
+ // Set box size
+ groundShape.size = Vector2(0.32f, 0.32f);
+ // Set friction
+ groundShape.friction = 0.5f;
+
+ const uint NUM_OBJECTS = 100;
+ for (uint i = 0; i < NUM_OBJECTS; ++i)
+ {
+ Node@ node = scene_.CreateChild("RigidBody");
+ node.position = Vector3(Random(-0.1f, 0.1f), 5.0f + i * 0.4f, 0.0f);
+
+ // Create rigid body
+ RigidBody2D@ body = node.CreateComponent("RigidBody2D");
+ body.bodyType = BT_DYNAMIC;
+
+ StaticSprite2D@ staticSprite = node.CreateComponent("StaticSprite2D");
+
+ if (i % 2 == 0)
+ {
+ staticSprite.sprite = boxSprite;
+
+ // Create box
+ CollisionBox2D@ box = node.CreateComponent("CollisionBox2D");
+ // Set size
+ box.size = Vector2(0.32f, 0.32f);
+ // Set density
+ box.density = 1.0f;
+ // Set friction
+ box.friction = 0.5f;
+ // Set restitution
+ box.restitution = 0.1f;
+ }
+ else
+ {
+ staticSprite.sprite = ballSprite;
+
+ // Create circle
+ CollisionCircle2D@ circle = node.CreateComponent("CollisionCircle2D");
+ // Set radius
+ circle.radius = 0.16f;
+ // Set density
+ circle.density = 1.0f;
+ // Set friction.
+ circle.friction = 0.5f;
+ // Set restitution
+ circle.restitution = 0.1f;
+ }
+ }
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move, Use PageUp PageDown to zoom.";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 4.0f;
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::UP * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::DOWN * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ if (input.keyDown[KEY_PAGEUP])
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ camera.zoom = camera.zoom * 1.01f;
+ }
+
+ if (input.keyDown[KEY_PAGEDOWN])
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ camera.zoom = camera.zoom * 0.99f;
+ }
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Zoom In" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Zoom Out" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/28_Urho2DPhysicsRope.as b/bin/Data/Scripts/28_Urho2DPhysicsRope.as
new file mode 100644
index 0000000..3be4f14
--- /dev/null
+++ b/bin/Data/Scripts/28_Urho2DPhysicsRope.as
@@ -0,0 +1,206 @@
+// Urho2D physics rope sample.
+// This sample demonstrates.
+// - Create revolute constraint
+// - Create roop constraint
+// - Displaying physics debug geometry
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 5.0f, -10.0f);
+
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.orthographic = true;
+ camera.orthoSize = graphics.height * 0.05f;
+ camera.zoom = 1.5f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)
+
+ // Create 2D physics world component
+ PhysicsWorld2D@ physicsWorld = scene_.CreateComponent("PhysicsWorld2D");
+ physicsWorld.drawJoint = true;
+
+ // Create ground.
+ Node@ groundNode = scene_.CreateChild("Ground");
+ // Create 2D rigid body for gound
+ RigidBody2D@ groundBody = groundNode.CreateComponent("RigidBody2D");
+ // Create edge collider for ground
+ CollisionEdge2D@ groundShape = groundNode.CreateComponent("CollisionEdge2D");
+ groundShape.SetVertices(Vector2(-40.0f, 0.0f), Vector2(40.0f, 0.0f));
+
+ const float y = 15.0f;
+ RigidBody2D@ prevBody = groundBody;
+
+ const uint NUM_OBJECTS = 10;
+ for (uint i = 0; i < NUM_OBJECTS; ++i)
+ {
+ Node@ node = scene_.CreateChild("RigidBody");
+ // Create rigid body
+ RigidBody2D@ body = node.CreateComponent("RigidBody2D");
+ body.bodyType = BT_DYNAMIC;
+
+ // Create box
+ CollisionBox2D@ box = node.CreateComponent("CollisionBox2D");
+ // Set friction
+ box.friction = 0.2f;
+ // Set mask bits.
+ box.maskBits = 0xFFFF & ~0x0002;
+
+ if (i == NUM_OBJECTS - 1)
+ {
+ node.position = Vector3(1.0f * i, y, 0.0f);
+ body.angularDamping = 0.4f;
+ box.SetSize(3.0f, 3.0f);
+ box.density = 100.0f;
+ box.categoryBits = 0x0002;
+ }
+ else
+ {
+ node.position = Vector3(0.5f + 1.0f * i, y, 0.0f);
+ box.SetSize(1.0f, 0.25f);
+ box.density = 20.0f;
+ box.categoryBits = 0x0001;
+ }
+
+ ConstraintRevolute2D@ joint = node.CreateComponent("ConstraintRevolute2D");
+ joint.otherBody = prevBody;
+ joint.anchor = Vector2(i, y);
+ joint.collideConnected = false;
+
+ prevBody = body;
+ }
+
+ ConstraintRope2D@ constraintRope = groundNode.CreateComponent("ConstraintRope2D");
+ constraintRope.otherBody = prevBody;
+ constraintRope.ownerBodyAnchor = Vector2(0.0f, y);
+ constraintRope.maxLength = NUM_OBJECTS - 1.0f + 0.01f;
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move, Use PageUp PageDown to zoom.";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 4.0f;
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::UP * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::DOWN * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ if (input.keyDown[KEY_PAGEUP])
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ camera.zoom = camera.zoom * 1.01f;
+ }
+
+ if (input.keyDown[KEY_PAGEDOWN])
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ camera.zoom = camera.zoom * 0.99f;
+ }
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+
+ PhysicsWorld2D@ physicsWorld = scene_.GetComponent("PhysicsWorld2D");
+ physicsWorld.DrawDebugGeometry();
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Zoom In" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Zoom Out" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/30_LightAnimation.as b/bin/Data/Scripts/30_LightAnimation.as
new file mode 100644
index 0000000..2b44b4a
--- /dev/null
+++ b/bin/Data/Scripts/30_LightAnimation.as
@@ -0,0 +1,205 @@
+// Light animation example.
+// This sample is base on StaticScene, and it demonstrates:
+// - Usage of attribute animation for light color & UI animation
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Create the scene content
+ CreateScene();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+
+ // Create a child scene node (at world origin) and a StaticModel component into it. Set the StaticModel to show a simple
+ // plane mesh with a "stone" material. Note that naming the scene nodes is optional. Scale the scene node larger
+ // (100 x 100 world units)
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(100.0f, 1.0f, 100.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Create a point light to the world so that we can see something.
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_POINT;
+ light.range = 10.0f;
+
+ // Create light color animation
+ ValueAnimation@ colorAnimation = ValueAnimation();
+ colorAnimation.SetKeyFrame(0.0f, Variant(WHITE));
+ colorAnimation.SetKeyFrame(1.0f, Variant(RED));
+ colorAnimation.SetKeyFrame(2.0f, Variant(YELLOW));
+ colorAnimation.SetKeyFrame(3.0f, Variant(GREEN));
+ colorAnimation.SetKeyFrame(4.0f, Variant(WHITE));
+ light.SetAttributeAnimation("Color", colorAnimation);
+
+ // Create text animation
+ ValueAnimation@ textAnimation = ValueAnimation();
+ textAnimation.SetKeyFrame(0.0f, Variant("WHITE"));
+ textAnimation.SetKeyFrame(1.0f, Variant("RED"));
+ textAnimation.SetKeyFrame(2.0f, Variant("YELLOW"));
+ textAnimation.SetKeyFrame(3.0f, Variant("GREEN"));
+ textAnimation.SetKeyFrame(4.0f, Variant("WHITE"));
+ ui.root.GetChild("animatingText").SetAttributeAnimation("Text", textAnimation);
+
+ // Create UI element animation
+ // (note: a spritesheet and "Image Rect" attribute should be used in real use cases for better performance)
+ ValueAnimation@ spriteAnimation = ValueAnimation();
+ spriteAnimation.SetKeyFrame(0.0f, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/1.png")));
+ spriteAnimation.SetKeyFrame(0.1f, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/2.png")));
+ spriteAnimation.SetKeyFrame(0.2f, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/3.png")));
+ spriteAnimation.SetKeyFrame(0.3f, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/4.png")));
+ spriteAnimation.SetKeyFrame(0.4f, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/5.png")));
+ spriteAnimation.SetKeyFrame(0.5f, Variant(ResourceRef("Texture2D", "Urho2D/GoldIcon/1.png")));
+ ui.root.GetChild("animatingSprite").SetAttributeAnimation("Texture", spriteAnimation);
+
+ // Create light position animation
+ ValueAnimation@ positionAnimation = ValueAnimation();
+ // Use spline interpolation method
+ positionAnimation.interpolationMethod = IM_SPLINE;
+ // Set spline tension
+ positionAnimation.splineTension = 0.7f;
+ positionAnimation.SetKeyFrame(0.0f, Variant(Vector3(-30.0f, 5.0f, -30.0f)));
+ positionAnimation.SetKeyFrame(1.0f, Variant(Vector3( 30.0f, 5.0f, -30.0f)));
+ positionAnimation.SetKeyFrame(2.0f, Variant(Vector3( 30.0f, 5.0f, 30.0f)));
+ positionAnimation.SetKeyFrame(3.0f, Variant(Vector3(-30.0f, 5.0f, 30.0f)));
+ positionAnimation.SetKeyFrame(4.0f, Variant(Vector3(-30.0f, 5.0f, -30.0f)));
+ lightNode.SetAttributeAnimation("Position", positionAnimation);
+
+ // Create more StaticModel objects to the scene, randomly positioned, rotated and scaled. For rotation, we construct a
+ // quaternion from Euler angles where the Y angle (rotation about the Y axis) is randomized. The mushroom model contains
+ // LOD levels, so the StaticModel component will automatically select the LOD level according to the view distance (you'll
+ // see the model get simpler as it moves further away). Finally, rendering a large number of the same object with the
+ // same material allows instancing to be used, if the GPU supports it. This reduces the amount of CPU work in rendering the
+ // scene.
+ const uint NUM_OBJECTS = 200;
+ for (uint i = 0; i < NUM_OBJECTS; ++i)
+ {
+ Node@ mushroomNode = scene_.CreateChild("Mushroom");
+ mushroomNode.position = Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f);
+ mushroomNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+ mushroomNode.SetScale(0.5f + Random(2.0f));
+ StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+ mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ mushroomObject.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ }
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ cameraNode.CreateComponent("Camera");
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move";
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+ instructionText.SetFont(font, 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+
+ // Animating text
+ Text@ text = ui.root.CreateChild("Text", "animatingText");
+ text.SetFont(font, 15);
+ text.horizontalAlignment = HA_CENTER;
+ text.verticalAlignment = VA_CENTER;
+ text.SetPosition(0, ui.root.height / 4 + 20);
+
+ // Animating sprite in the top left corner
+ Sprite@ sprite = ui.root.CreateChild("Sprite", "animatingSprite");
+ sprite.SetPosition(8, 8);
+ sprite.SetSize(64, 64);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ // Use the Translate() function (default local space) to move relative to the node's orientation.
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/31_MaterialAnimation.as b/bin/Data/Scripts/31_MaterialAnimation.as
new file mode 100644
index 0000000..f2a4109
--- /dev/null
+++ b/bin/Data/Scripts/31_MaterialAnimation.as
@@ -0,0 +1,163 @@
+// Material animation example.
+// This sample is base on StaticScene, and it demonstrates:
+// - Usage of material shader animation for mush room material
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+
+ // Create a child scene node (at world origin) and a StaticModel component into it. Set the StaticModel to show a simple
+ // plane mesh with a "stone" material. Note that naming the scene nodes is optional. Scale the scene node larger
+ // (100 x 100 world units)
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(100.0f, 1.0f, 100.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Create a directional light to the world so that we can see something. The light scene node's orientation controls the
+ // light direction; we will use the SetDirection() function which calculates the orientation from a forward direction vector.
+ // The light will use default settings (white light, no shadows)
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f); // The direction vector does not need to be normalized
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+
+ // Create more StaticModel objects to the scene, randomly positioned, rotated and scaled. For rotation, we construct a
+ // quaternion from Euler angles where the Y angle (rotation about the Y axis) is randomized. The mushroom model contains
+ // LOD levels, so the StaticModel component will automatically select the LOD level according to the view distance (you'll
+ // see the model get simpler as it moves further away). Finally, rendering a large number of the same object with the
+ // same material allows instancing to be used, if the GPU supports it. This reduces the amount of CPU work in rendering the
+ // scene.
+ Material@ mushroomMat = cache.GetResource("Material", "Materials/Mushroom.xml");
+ // Apply shader parameter animation to material
+ ValueAnimation@ specColorAnimation = ValueAnimation();
+ specColorAnimation.SetKeyFrame(0.0f, Variant(Color(0.1f, 0.1f, 0.1f, 16.0f)));
+ specColorAnimation.SetKeyFrame(1.0f, Variant(Color(1.0f, 0.0f, 0.0f, 2.0f)));
+ specColorAnimation.SetKeyFrame(2.0f, Variant(Color(1.0f, 1.0f, 0.0f, 2.0f)));
+ specColorAnimation.SetKeyFrame(3.0f, Variant(Color(0.1f, 0.1f, 0.1f, 16.0f)));
+ // Optionally associate material with scene to make sure shader parameter animation respects scene time scale
+ mushroomMat.scene = scene_;
+ mushroomMat.SetShaderParameterAnimation("MatSpecColor", specColorAnimation);
+
+ const uint NUM_OBJECTS = 200;
+ for (uint i = 0; i < NUM_OBJECTS; ++i)
+ {
+ Node@ mushroomNode = scene_.CreateChild("Mushroom");
+ mushroomNode.position = Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f);
+ mushroomNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+ mushroomNode.SetScale(0.5f + Random(2.0f));
+ StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+ mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ mushroomObject.material = mushroomMat;
+ }
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ cameraNode.CreateComponent("Camera");
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ // Use the Translate() function (default local space) to move relative to the node's orientation.
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/32_Urho2DConstraints.as b/bin/Data/Scripts/32_Urho2DConstraints.as
new file mode 100644
index 0000000..85f1f3c
--- /dev/null
+++ b/bin/Data/Scripts/32_Urho2DConstraints.as
@@ -0,0 +1,510 @@
+// Urho2D physics Constraints sample.
+// This sample is designed to help understanding and chosing the right constraint.
+// This sample demonstrates:
+// - Creating physics constraints
+// - Creating Edge and Polygon Shapes from vertices
+// - Displaying physics debug geometry and constraints' joints
+// - Using SetOrderInLayer to alter the way sprites are drawn in relation to each other
+// - Using Text3D to display some text affected by zoom
+// - Setting the background color for the scene
+
+#include "Scripts/Utilities/Sample.as"
+
+Camera@ camera;
+Node@ pickedNode;
+RigidBody2D@ dummyBody;
+
+void Start()
+{
+ SampleStart();
+ CreateScene();
+ input.mouseVisible = true; // Show mouse cursor
+ CreateInstructions();
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+ PhysicsWorld2D@ physicsWorld = scene_.CreateComponent("PhysicsWorld2D");
+ physicsWorld.drawJoint = true; // Display the joints (Note that DrawDebugGeometry() must be set to true to acually draw the joints)
+ drawDebug = true; // Set DrawDebugGeometry() to true
+
+ // Create camera
+ cameraNode = scene_.CreateChild("Camera");
+ cameraNode.position = Vector3(0.0f, 0.0f, 0.0f); // Note that Z setting is discarded; use camera.zoom instead (see MoveCamera() below for example)
+ camera = cameraNode.CreateComponent("Camera");
+ camera.orthographic = true;
+ camera.orthoSize = graphics.height * PIXEL_SIZE;
+ camera.zoom = 1.2f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.2) is set for full visibility at 1280x800 resolution)
+ renderer.viewports[0] = Viewport(scene_, camera);
+ renderer.defaultZone.fogColor = Color(0.1f, 0.1f, 0.1f); // Set background color for the scene
+
+ // Create 4x3 grid
+ for (uint i = 0; i<5; ++i)
+ {
+ Node@ edgeNode = scene_.CreateChild("VerticalEdge");
+ RigidBody2D@ edgeBody = edgeNode.CreateComponent("RigidBody2D");
+ if (dummyBody is null)
+ dummyBody = edgeBody; // Mark first edge as dummy body (used by mouse pick)
+ CollisionEdge2D@ edgeShape = edgeNode.CreateComponent("CollisionEdge2D");
+ edgeShape.SetVertices(Vector2(i*2.5f -5.0f, -3.0f), Vector2(i*2.5f -5.0f, 3.0f));
+ edgeShape.friction = 0.5f; // Set friction
+ }
+
+ for (uint j = 0; j<4; ++j)
+ {
+ Node@ edgeNode = scene_.CreateChild("HorizontalEdge");
+ RigidBody2D@ edgeBody = edgeNode.CreateComponent("RigidBody2D");
+ CollisionEdge2D@ edgeShape = edgeNode.CreateComponent("CollisionEdge2D");
+ edgeShape.SetVertices(Vector2(-5.0f, j*2.0f -3.0f), Vector2(5.0f, j*2.0f -3.0f));
+ edgeShape.friction = 0.5f; // Set friction
+ }
+
+ // Create a box (will be cloned later)
+ Node@ box = scene_.CreateChild("Box");
+ box.position = Vector3(0.8f, -2.0f, 0.0f);
+ StaticSprite2D@ boxSprite = box.CreateComponent("StaticSprite2D");
+ boxSprite.sprite = cache.GetResource("Sprite2D", "Urho2D/Box.png");
+ RigidBody2D@ boxBody = box.CreateComponent("RigidBody2D");
+ boxBody.bodyType = BT_DYNAMIC;
+ boxBody.linearDamping = 0.0f;
+ boxBody.angularDamping = 0.0f;
+ CollisionBox2D@ shape = box.CreateComponent("CollisionBox2D"); // Create box shape
+ shape.size = Vector2(0.32, 0.32); // Set size
+ shape.density = 1.0f; // Set shape density (kilograms per meter squared)
+ shape.friction = 0.5f; // Set friction
+ shape.restitution = 0.1f; // Set restitution (slight bounce)
+
+ // Create a ball (will be cloned later)
+ Node@ ball = scene_.CreateChild("Ball");
+ ball.position = Vector3(1.8f, -2.0f, 0.0f);
+ StaticSprite2D@ ballSprite = ball.CreateComponent("StaticSprite2D");
+ ballSprite.sprite = cache.GetResource("Sprite2D", "Urho2D/Ball.png");
+ RigidBody2D@ ballBody = ball.CreateComponent("RigidBody2D");
+ ballBody.bodyType = BT_DYNAMIC;
+ ballBody.linearDamping = 0.0f;
+ ballBody.angularDamping = 0.0f;
+ CollisionCircle2D@ ballShape = ball.CreateComponent("CollisionCircle2D"); // Create circle shape
+ ballShape.radius = 0.16f; // Set radius
+ ballShape.density = 1.0f; // Set shape density (kilograms per meter squared)
+ ballShape.friction = 0.5f; // Set friction
+ ballShape.restitution = 0.6f; // Set restitution: make it bounce
+
+ // Create a polygon
+ Node@ polygon = scene_.CreateChild("Polygon");
+ polygon.position = Vector3(1.6f, -2.0f, 0.0f);
+ polygon.SetScale(0.7f);
+ StaticSprite2D@ polygonSprite = polygon.CreateComponent("StaticSprite2D");
+ polygonSprite.sprite = cache.GetResource("Sprite2D", "Urho2D/Aster.png");
+ RigidBody2D@ polygonBody = polygon.CreateComponent("RigidBody2D");
+ polygonBody.bodyType = BT_DYNAMIC;
+ CollisionPolygon2D@ polygonShape = polygon.CreateComponent("CollisionPolygon2D");
+ Array polygonVertices = {Vector2(-0.8f, -0.3f), Vector2(0.5f, -0.8f), Vector2(0.8f, -0.3f), Vector2(0.8f, 0.5f), Vector2(0.5f, 0.9f), Vector2(-0.5f, 0.7f)};
+ polygonShape.SetVertices(polygonVertices);
+ polygonShape.density = 1.0f; // Set shape density (kilograms per meter squared)
+ polygonShape.friction = 0.3f; // Set friction
+ polygonShape.restitution = 0.0f; // Set restitution (no bounce)
+
+ // Create a ConstraintDistance2D
+ CreateFlag("ConstraintDistance2D", -4.97f, 3.0f); // Display Text3D flag
+ Node@ boxDistanceNode = box.Clone();
+ Node@ ballDistanceNode = ball.Clone();
+ RigidBody2D@ ballDistanceBody = ballDistanceNode.GetComponent("RigidBody2D");
+ boxDistanceNode.position = Vector3(-4.5f, 2.0f, 0.0f);
+ ballDistanceNode.position = Vector3(-3.0f, 2.0f, 0.0f);
+
+ ConstraintDistance2D@ constraintDistance = boxDistanceNode.CreateComponent("ConstraintDistance2D"); // Apply ConstraintDistance2D to box
+ constraintDistance.otherBody = ballDistanceBody; // Constrain ball to box
+ constraintDistance.ownerBodyAnchor = boxDistanceNode.position2D;
+ constraintDistance.otherBodyAnchor = ballDistanceNode.position2D;
+ // Make the constraint soft (comment to make it rigid, which is its basic behavior)
+ constraintDistance.frequencyHz = 4.0f;
+ constraintDistance.dampingRatio = 0.5f;
+
+ // Create a ConstraintFriction2D ********** Not functional. From Box2d samples it seems that 2 anchors are required, Urho2D only provides 1, needs investigation ***********
+ CreateFlag("ConstraintFriction2D", 0.03f, 1.0f); // Display Text3D flag
+ Node@ boxFrictionNode = box.Clone();
+ Node@ ballFrictionNode = ball.Clone();
+ boxFrictionNode.position = Vector3(0.5f, 0.0f, 0.0f);
+ ballFrictionNode.position = Vector3(1.5f, 0.0f, 0.0f);
+
+ ConstraintFriction2D@ constraintFriction = boxFrictionNode.CreateComponent("ConstraintFriction2D"); // Apply ConstraintDistance2D to box
+ constraintFriction.otherBody = ballFrictionNode.GetComponent("RigidBody2D"); // Constraint ball to box
+ //constraintFriction.ownerBodyAnchor = boxNode.position2D;
+ //constraintFriction.otherBodyAnchor = ballNode.position2D;
+ //constraintFriction.maxForce = 10.0f; // ballBody.mass * gravity
+ //constraintDistance.maxTorque = 10.0f; // ballBody.mass * radius * gravity
+
+ // Create a ConstraintGear2D
+ CreateFlag("ConstraintGear2D", -4.97f, -1.0f); // Display Text3D flag
+ Node@ baseNode = box.Clone();
+ RigidBody2D@ tempBody = baseNode.GetComponent("RigidBody2D"); // Get body to make it static
+ tempBody.bodyType = BT_STATIC;
+ baseNode.position = Vector3(-3.7f, -2.5f, 0.0f);
+ Node@ ball1Node = ball.Clone();
+ ball1Node.position = Vector3(-4.5f, -2.0f, 0.0f);
+ RigidBody2D@ ball1Body = ball1Node.GetComponent("RigidBody2D");
+ Node@ ball2Node = ball.Clone();
+ ball2Node.position = Vector3(-3.0f, -2.0f, 0.0f);
+ RigidBody2D@ ball2Body = ball2Node.GetComponent("RigidBody2D");
+
+ ConstraintRevolute2D@ gear1 = baseNode.CreateComponent("ConstraintRevolute2D"); // Apply constraint to baseBox
+ gear1.otherBody = ball1Body; // Constrain ball1 to baseBox
+ gear1.anchor = ball1Node.position2D;
+ ConstraintRevolute2D@ gear2 = baseNode.CreateComponent("ConstraintRevolute2D"); // Apply constraint to baseBox
+ gear2.otherBody = ball2Body; // Constrain ball2 to baseBox
+ gear2.anchor = ball2Node.position2D;
+
+ ConstraintGear2D@ constraintGear = ball1Node.CreateComponent("ConstraintGear2D"); // Apply constraint to ball1
+ constraintGear.otherBody = ball2Body; // Constrain ball2 to ball1
+ constraintGear.ownerConstraint = gear1;
+ constraintGear.otherConstraint = gear2;
+ constraintGear.ratio=1.0f;
+
+ ball1Body.ApplyAngularImpulse(0.015f, true); // Animate
+
+ // Create a vehicle from a compound of 2 ConstraintWheel2Ds
+ CreateFlag("ConstraintWheel2Ds compound", -2.45f, -1.0f); // Display Text3D flag
+ Node@ car = box.Clone();
+ car.scale = Vector3(4.0f, 1.0f, 0.0f);
+ car.position = Vector3(-1.2f, -2.3f, 0.0f);
+ StaticSprite2D@ tempSprite = car.GetComponent("StaticSprite2D"); // Get car Sprite in order to draw it on top
+ tempSprite.orderInLayer = 0; // Draw car on top of the wheels (set to -1 to draw below)
+ Node@ ball1WheelNode = ball.Clone();
+ ball1WheelNode.position = Vector3(-1.6f, -2.5f, 0.0f);
+ Node@ ball2WheelNode = ball.Clone();
+ ball2WheelNode.position = Vector3(-0.8f, -2.5f, 0.0f);
+
+ ConstraintWheel2D@ wheel1 = car.CreateComponent("ConstraintWheel2D");
+ wheel1.otherBody = ball1WheelNode.GetComponent("RigidBody2D");
+ wheel1.anchor = ball1WheelNode.position2D;
+ wheel1.axis = Vector2(0.0f, 1.0f);
+ wheel1.maxMotorTorque = 20.0f;
+ wheel1.frequencyHz = 4.0f;
+ wheel1.dampingRatio = 0.4f;
+
+ ConstraintWheel2D@ wheel2 = car.CreateComponent("ConstraintWheel2D");
+ wheel2.otherBody = ball2WheelNode.GetComponent("RigidBody2D");
+ wheel2.anchor = ball2WheelNode.position2D;
+ wheel2.axis = Vector2(0.0f, 1.0f);
+ wheel2.maxMotorTorque = 10.0f;
+ wheel2.frequencyHz = 4.0f;
+ wheel2.dampingRatio = 0.4f;
+
+ // ConstraintMotor2D
+ CreateFlag("ConstraintMotor2D", 2.53f, -1.0f); // Display Text3D flag
+ Node@ boxMotorNode = box.Clone();
+ tempBody = boxMotorNode.GetComponent("RigidBody2D"); // Get body to make it static
+ tempBody.bodyType = BT_STATIC;
+ Node@ ballMotorNode = ball.Clone();
+ boxMotorNode.position = Vector3(3.8f, -2.1f, 0.0f);
+ ballMotorNode.position = Vector3(3.8f, -1.5f, 0.0f);
+
+ ConstraintMotor2D@ constraintMotor = boxMotorNode.CreateComponent("ConstraintMotor2D");
+ constraintMotor.otherBody = ballMotorNode.GetComponent("RigidBody2D"); // Constrain ball to box
+ constraintMotor.linearOffset = Vector2(0.0f, 0.8f); // Set ballNode position relative to boxNode position = (0,0)
+ constraintMotor.angularOffset = 0.1f;
+ constraintMotor.maxForce = 5.0f;
+ constraintMotor.maxTorque = 10.0f;
+ constraintMotor.correctionFactor = 1.0f;
+ constraintMotor.collideConnected = true; // doesn't work
+
+ // ConstraintMouse2D is demonstrated in HandleMouseButtonDown() function. It is used to "grasp" the sprites with the mouse.
+ CreateFlag("ConstraintMouse2D", 0.03f, -1.0f); // Display Text3D flag
+
+ // Create a ConstraintPrismatic2D
+ CreateFlag("ConstraintPrismatic2D", 2.53f, 3.0f); // Display Text3D flag
+ Node@ boxPrismaticNode = box.Clone();
+ tempBody = boxPrismaticNode.GetComponent("RigidBody2D"); // Get body to make it static
+ tempBody.bodyType = BT_STATIC;
+ Node@ ballPrismaticNode = ball.Clone();
+ boxPrismaticNode.position = Vector3(3.3f, 2.5f, 0.0f);
+ ballPrismaticNode.position = Vector3(4.3f, 2.0f, 0.0f);
+
+ ConstraintPrismatic2D@ constraintPrismatic = boxPrismaticNode.CreateComponent("ConstraintPrismatic2D");
+ constraintPrismatic.otherBody = ballPrismaticNode.GetComponent("RigidBody2D"); // Constrain ball to box
+ constraintPrismatic.axis = Vector2(1.0f, 1.0f); // Slide from [0,0] to [1,1]
+ constraintPrismatic.anchor = Vector2(4.0f, 2.0f);
+ constraintPrismatic.lowerTranslation = -1.0f;
+ constraintPrismatic.upperTranslation = 0.5f;
+ constraintPrismatic.enableLimit = true;
+ constraintPrismatic.maxMotorForce = 1.0f;
+ constraintPrismatic.motorSpeed = 0.0f;
+
+ // ConstraintPulley2D
+ CreateFlag("ConstraintPulley2D", 0.03f, 3.0f); // Display Text3D flag
+ Node@ boxPulleyNode = box.Clone();
+ Node@ ballPulleyNode = ball.Clone();
+ boxPulleyNode.position = Vector3(0.5f, 2.0f, 0.0f);
+ ballPulleyNode.position = Vector3(2.0f, 2.0f, 0.0f);
+
+ ConstraintPulley2D@ constraintPulley = boxPulleyNode.CreateComponent("ConstraintPulley2D"); // Apply constraint to box
+ constraintPulley.otherBody = ballPulleyNode.GetComponent("RigidBody2D"); // Constrain ball to box
+ constraintPulley.ownerBodyAnchor = boxPulleyNode.position2D;
+ constraintPulley.otherBodyAnchor = ballPulleyNode.position2D;
+ constraintPulley.ownerBodyGroundAnchor = boxPulleyNode.position2D + Vector2(0.0f, 1.0f);
+ constraintPulley.otherBodyGroundAnchor = ballPulleyNode.position2D + Vector2(0.0f, 1.0f);
+ constraintPulley.ratio = 1.0; // Weight ratio between ownerBody and otherBody
+
+ // Create a ConstraintRevolute2D
+ CreateFlag("ConstraintRevolute2D", -2.45f, 3.0f); // Display Text3D flag
+ Node@ boxRevoluteNode = box.Clone();
+ tempBody = boxRevoluteNode.GetComponent("RigidBody2D"); // Get body to make it static
+ tempBody.bodyType = BT_STATIC;
+ Node@ ballRevoluteNode = ball.Clone();
+ boxRevoluteNode.position = Vector3(-2.0f, 1.5f, 0.0f);
+ ballRevoluteNode.position = Vector3(-1.0f, 2.0f, 0.0f);
+
+ ConstraintRevolute2D@ constraintRevolute = boxRevoluteNode.CreateComponent("ConstraintRevolute2D"); // Apply constraint to box
+ constraintRevolute.otherBody = ballRevoluteNode.GetComponent("RigidBody2D"); // Constrain ball to box
+ constraintRevolute.anchor = Vector2(-1.0f, 1.5f);
+ constraintRevolute.lowerAngle = -1.0f; // In radians
+ constraintRevolute.upperAngle = 0.5f; // In radians
+ constraintRevolute.enableLimit = true;
+ constraintRevolute.maxMotorTorque = 10.0f;
+ constraintRevolute.motorSpeed = 0.0f;
+ constraintRevolute.enableMotor = true;
+
+ // Create a ConstraintRope2D
+ CreateFlag("ConstraintRope2D", -4.97f, 1.0f); // Display Text3D flag
+ Node@ boxRopeNode = box.Clone();
+ tempBody = boxRopeNode.GetComponent("RigidBody2D");
+ tempBody.bodyType = BT_STATIC;
+ Node@ ballRopeNode = ball.Clone();
+ boxRopeNode.position = Vector3(-3.7f, 0.7f, 0.0f);
+ ballRopeNode.position = Vector3(-4.5f, 0.0f, 0.0f);
+
+ ConstraintRope2D@ constraintRope = boxRopeNode.CreateComponent("ConstraintRope2D");
+ constraintRope.otherBody = ballRopeNode.GetComponent("RigidBody2D"); // Constrain ball to box
+ constraintRope.ownerBodyAnchor = Vector2(0.0f, -0.5f); // Offset from box (OwnerBody) : the rope is rigid from OwnerBody center to this ownerBodyAnchor
+ constraintRope.maxLength = 0.9f; // Rope length
+ constraintRope.collideConnected = true;
+
+ // Create a ConstraintWeld2D
+ CreateFlag("ConstraintWeld2D", -2.45f, 1.0f); // Display Text3D flag
+ Node@ boxWeldNode = box.Clone();
+ Node@ ballWeldNode = ball.Clone();
+ boxWeldNode.position = Vector3(-0.5f, 0.0f, 0.0f);
+ ballWeldNode.position = Vector3(-2.0f, 0.0f, 0.0f);
+
+ ConstraintWeld2D@ constraintWeld = boxWeldNode.CreateComponent("ConstraintWeld2D");
+ constraintWeld.otherBody = ballWeldNode.GetComponent("RigidBody2D"); // Constrain ball to box
+ constraintWeld.anchor = boxWeldNode.position2D;
+ constraintWeld.frequencyHz = 4.0f;
+ constraintWeld.dampingRatio = 0.5f;
+
+ // Create a ConstraintWheel2D
+ CreateFlag("ConstraintWheel2D", 2.53f, 1.0f); // Display Text3D flag
+ Node@ boxWheelNode = box.Clone();
+ Node@ ballWheelNode = ball.Clone();
+ boxWheelNode.position = Vector3(3.8f, 0.0f, 0.0f);
+ ballWheelNode.position = Vector3(3.8f, 0.9f, 0.0f);
+
+ ConstraintWheel2D@ constraintWheel = boxWheelNode.CreateComponent("ConstraintWheel2D");
+ constraintWheel.otherBody = ballWheelNode.GetComponent("RigidBody2D"); // Constrain ball to box
+ constraintWheel.anchor = ballWheelNode.position2D;
+ constraintWheel.axis = Vector2(0.0f, 1.0f);
+ constraintWheel.enableMotor = true;
+ constraintWheel.maxMotorTorque = 1.0f;
+ constraintWheel.motorSpeed = 0.0f;
+ constraintWheel.frequencyHz = 4.0f;
+ constraintWheel.dampingRatio = 0.5f;
+ constraintWheel.collideConnected = true; // doesn't work
+}
+
+void CreateFlag(const String&in text, float x, float y) // Used to create Tex3D flags
+{
+ Node@ flagNode = scene_.CreateChild("Flag");
+ flagNode.position = Vector3(x, y, 0.0f);
+ Text3D@ flag3D = flagNode.CreateComponent("Text3D"); // We use Text3D in order to make the text affected by zoom (so that it sticks to 2D)
+ flag3D.text = text;
+ flag3D.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move, Use PageUp PageDown to zoom.\n Space to toggle debug geometry and joints - F5 to save the scene.";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ instructionText.textAlignment = HA_CENTER; // Center rows in relation to each other
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+
+ instructionText.SetPosition(0.0f, ui.root.height / 4);
+}
+
+
+void MoveCamera(float timeStep)
+{
+ if (ui.focusElement !is null) return; // Do not move if the UI has a focused element (the console)
+
+ uint MOVE_SPEED = 4; // Movement speed as world units per second
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W]) cameraNode.Translate(Vector3::UP * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S]) cameraNode.Translate(Vector3::DOWN * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A]) cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D]) cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ if (input.keyDown[KEY_PAGEUP]) camera.zoom = camera.zoom * 1.01f; // Zoom In
+ if (input.keyDown[KEY_PAGEDOWN]) camera.zoom = camera.zoom * 0.99f; // Zoom Out
+}
+
+void SubscribeToEvents()
+{
+ SubscribeToEvent("Update", "HandleUpdate");
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+ SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown");
+
+ if (touchEnabled)
+ SubscribeToEvent("TouchBegin", "HandleTouchBegin3");
+
+ // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ float timeStep = eventData["TimeStep"].GetFloat();
+ MoveCamera(timeStep); // Move the camera according to frame's time step
+ if (input.keyPress[KEY_SPACE]) drawDebug = !drawDebug; // Toggle debug geometry with space
+ if (input.keyPress[KEY_F5]) // Save scene
+ {
+ File saveFile(fileSystem.programDir + "Data/Scenes/Constraints.xml", FILE_WRITE);
+ scene_.SaveXML(saveFile);
+ }
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ PhysicsWorld2D@ physicsWorld = scene_.GetComponent("PhysicsWorld2D");
+ if (drawDebug) physicsWorld.DrawDebugGeometry();
+}
+
+void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
+{
+ PhysicsWorld2D@ physicsWorld = scene_.GetComponent("PhysicsWorld2D");
+ RigidBody2D@ rigidBody = physicsWorld.GetRigidBody(input.mousePosition.x, input.mousePosition.y, M_MAX_UNSIGNED); // Raycast for RigidBody2Ds to pick
+ if (rigidBody !is null)
+ {
+ pickedNode = rigidBody.node;
+ StaticSprite2D@ staticSprite = pickedNode.GetComponent("StaticSprite2D");
+ staticSprite.color = Color(1.0f, 0.0f, 0.0f, 1.0f); // Temporary modify color of the picked sprite
+
+ // Create a ConstraintMouse2D - Temporary apply this constraint to the pickedNode to allow grasping and moving with the mouse
+ ConstraintMouse2D@ constraintMouse = pickedNode.CreateComponent("ConstraintMouse2D");
+ constraintMouse.target = GetMousePositionXY();
+ constraintMouse.maxForce = 1000 * rigidBody.mass;
+ constraintMouse.collideConnected = true;
+ constraintMouse.otherBody = dummyBody; // Use dummy body instead of rigidBody. It's better to create a dummy body automatically in ConstraintMouse2D
+ }
+ SubscribeToEvent("MouseMove", "HandleMouseMove");
+ SubscribeToEvent("MouseButtonUp", "HandleMouseButtonUp");
+}
+
+void HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
+{
+ if (pickedNode !is null)
+ {
+ StaticSprite2D@ staticSprite = pickedNode.GetComponent("StaticSprite2D");
+ staticSprite.color = Color(1.0f, 1.0f, 1.0f, 1.0f); // Restore picked sprite color
+
+ pickedNode.RemoveComponent("ConstraintMouse2D"); // Remove temporary constraint
+ pickedNode = null;
+ }
+ UnsubscribeFromEvent("MouseMove");
+ UnsubscribeFromEvent("MouseButtonUp");
+}
+
+Vector2 GetMousePositionXY()
+{
+ Vector3 screenPoint = Vector3(float(input.mousePosition.x) / graphics.width, float(input.mousePosition.y) / graphics.height, 0.0f);
+ Vector3 worldPoint = camera.ScreenToWorldPoint(screenPoint);
+ return Vector2(worldPoint.x, worldPoint.y);
+}
+
+void HandleMouseMove(StringHash eventType, VariantMap& eventData)
+{
+ if (pickedNode !is null)
+ {
+ ConstraintMouse2D@ constraintMouse = pickedNode.GetComponent("ConstraintMouse2D");
+ constraintMouse.target = GetMousePositionXY();
+ }
+}
+
+void HandleTouchBegin3(StringHash eventType, VariantMap& eventData)
+{
+ PhysicsWorld2D@ physicsWorld = scene_.GetComponent("PhysicsWorld2D");
+ RigidBody2D@ rigidBody = physicsWorld.GetRigidBody(eventData["X"].GetInt(), eventData["Y"].GetInt(), M_MAX_UNSIGNED); // Raycast for RigidBody2Ds to pick
+ if (rigidBody !is null)
+ {
+ pickedNode = rigidBody.node;
+ StaticSprite2D@ staticSprite = pickedNode.GetComponent("StaticSprite2D");
+ staticSprite.color = Color(1.0f, 0.0f, 0.0f, 1.0f); // Temporary modify color of the picked sprite
+ RigidBody2D@ rigidBody = pickedNode.GetComponent("RigidBody2D");
+
+ // Create a ConstraintMouse2D - Temporary apply this constraint to the pickedNode to allow grasping and moving with touch
+ ConstraintMouse2D@ constraintMouse = pickedNode.CreateComponent("ConstraintMouse2D");
+ Vector3 pos = camera.ScreenToWorldPoint(Vector3(float(eventData["X"].GetInt()) / graphics.width, float(eventData["Y"].GetInt()) / graphics.height, 0.0f));
+ constraintMouse.target = Vector2(pos.x, pos.y);
+ constraintMouse.maxForce = 1000 * rigidBody.mass;
+ constraintMouse.collideConnected = true;
+ constraintMouse.otherBody = dummyBody; // Use dummy body instead of rigidBody. It's better to create a dummy body automatically in ConstraintMouse2D
+ constraintMouse.dampingRatio = 0;
+ }
+ SubscribeToEvent("TouchMove", "HandleTouchMove3");
+ SubscribeToEvent("TouchEnd", "HandleTouchEnd3");
+}
+
+void HandleTouchMove3(StringHash eventType, VariantMap& eventData)
+{
+ if (pickedNode !is null)
+ {
+ ConstraintMouse2D@ constraintMouse = pickedNode.GetComponent("ConstraintMouse2D");
+ Vector3 pos = camera.ScreenToWorldPoint(Vector3(float(eventData["X"].GetInt()) / graphics.width, float(eventData["Y"].GetInt()) / graphics.height, 0.0f));
+ constraintMouse.target = Vector2(pos.x, pos.y);
+ }
+}
+
+void HandleTouchEnd3(StringHash eventType, VariantMap& eventData)
+{
+ if (pickedNode !is null)
+ {
+ StaticSprite2D@ staticSprite = pickedNode.GetComponent("StaticSprite2D");
+ staticSprite.color = Color(1.0f, 1.0f, 1.0f, 1.0f); // Restore picked sprite color
+
+ pickedNode.RemoveComponent("ConstraintMouse2D"); // Remove temporary constraint
+ pickedNode = null;
+ }
+ UnsubscribeFromEvent("TouchMove");
+ UnsubscribeFromEvent("TouchEnd");
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Zoom In" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Zoom Out" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/33_Urho2DSpriterAnimation.as b/bin/Data/Scripts/33_Urho2DSpriterAnimation.as
new file mode 100644
index 0000000..90a39a3
--- /dev/null
+++ b/bin/Data/Scripts/33_Urho2DSpriterAnimation.as
@@ -0,0 +1,165 @@
+// Urho2D sprite example.
+// This sample demonstrates:
+// - Creating a 2D scene with spriter animation
+// - Displaying the scene using the Renderer subsystem
+// - Handling keyboard to move and zoom 2D camera
+
+#include "Scripts/Utilities/Sample.as"
+
+Node@ spriterNode;
+int spriterAnimationIndex = 0;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 0.0f, -10.0f);
+
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.orthographic = true;
+ camera.orthoSize = graphics.height * PIXEL_SIZE;
+ camera.zoom = 1.5f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.5) is set for full visibility at 1280x800 resolution)
+
+ AnimationSet2D@ spriterAnimationSet = cache.GetResource("AnimationSet2D", "Urho2D/imp/imp.scml");
+ if (spriterAnimationSet is null)
+ return;
+
+ spriterNode = scene_.CreateChild("SpriterAnimation");
+ AnimatedSprite2D@ spriterAnimatedSprite = spriterNode.CreateComponent("AnimatedSprite2D");
+ spriterAnimatedSprite.animationSet = spriterAnimationSet;
+ spriterAnimatedSprite.SetAnimation(spriterAnimationSet.GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Mouse click to play next animation, \nUse WASD keys to move, use PageUp PageDown keys to zoom.";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ instructionText.textAlignment = HA_CENTER; // Center rows in relation to each other
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 4.0f;
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::UP * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::DOWN * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ if (input.keyDown[KEY_PAGEUP])
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ camera.zoom = camera.zoom * 1.01f;
+ }
+
+ if (input.keyDown[KEY_PAGEDOWN])
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ camera.zoom = camera.zoom * 0.99f;
+ }
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+ SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown");
+
+ // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
+{
+ AnimatedSprite2D@ spriterAnimatedSprite = spriterNode.GetComponent("AnimatedSprite2D");
+ AnimationSet2D@ spriterAnimationSet = spriterAnimatedSprite.animationSet;
+ spriterAnimationIndex = (spriterAnimationIndex + 1) % spriterAnimationSet.numAnimations;
+ spriterAnimatedSprite.SetAnimation(spriterAnimationSet.GetAnimation(spriterAnimationIndex), LM_FORCE_LOOPED);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Zoom In" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Zoom Out" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/34_DynamicGeometry.as b/bin/Data/Scripts/34_DynamicGeometry.as
new file mode 100644
index 0000000..fc6c0c6
--- /dev/null
+++ b/bin/Data/Scripts/34_DynamicGeometry.as
@@ -0,0 +1,350 @@
+// Dynamic geometry example.
+// This sample demonstrates:
+// - Cloning a Model resource
+// - Modifying the vertex buffer data of the cloned models at runtime to efficiently animate them
+// - Creating a Model resource and its buffer data from scratch
+
+#include "Scripts/Utilities/Sample.as"
+
+bool animate = true;
+float animTime = 0.0;
+VectorBuffer originalVertexData;
+Array animatingBuffers;
+Array originalVertices;
+Array vertexDuplicates;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene so that drawable objects can be rendered. Use default volume
+ // (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_.CreateComponent("Octree");
+
+ // Create a Zone for ambient light & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0, 1000.0);
+ zone.fogColor = Color(0.2, 0.2, 0.2);
+ zone.fogStart = 200.0;
+ zone.fogEnd = 300.0;
+
+ // Create a directional light
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(-0.6, -1.0, -0.8); // The direction vector does not need to be normalized
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.color = Color(0.4, 1.0, 0.4);
+ light.specularIntensity = 1.5;
+
+ // Get the original model and its unmodified vertices, which are used as source data for the animation
+ Model@ originalModel = cache.GetResource("Model", "Models/Box.mdl");
+ if (originalModel is null)
+ {
+ Print("Model not found, cannot initialize example scene");
+ return;
+ }
+ // Get the vertex buffer from the first geometry's first LOD level
+ VertexBuffer@ buffer = originalModel.GetGeometry(0, 0).vertexBuffers[0];
+ originalVertexData = buffer.GetData();
+ uint numVertices = buffer.vertexCount;
+ uint vertexSize = buffer.vertexSize;
+ // Copy the original vertex positions
+ for (uint i = 0; i < numVertices; ++i)
+ {
+ originalVertexData.Seek(i * vertexSize);
+ originalVertices.Push(originalVertexData.ReadVector3());
+ }
+
+ // Detect duplicate vertices to allow seamless animation
+ vertexDuplicates.Resize(originalVertices.length);
+ for (uint i = 0; i < originalVertices.length; ++i)
+ {
+ vertexDuplicates[i] = i; // Assume not a duplicate
+ for (uint j = 0; j < i; ++j)
+ {
+ if (originalVertices[i].Equals(originalVertices[j]))
+ {
+ vertexDuplicates[i] = j;
+ break;
+ }
+ }
+ }
+
+ // Create StaticModels in the scene. Clone the model for each so that we can modify the vertex data individually
+ for (int y = -1; y <= 1; ++y)
+ {
+ for (int x = -1; x <= 1; ++x)
+ {
+ Node@ node = scene_.CreateChild("Object");
+ node.position = Vector3(x * 2.0, 0.0, y * 2.0);
+ StaticModel@ object = node.CreateComponent("StaticModel");
+ Model@ cloneModel = originalModel.Clone();
+ object.model = cloneModel;
+ // Store the cloned vertex buffer that we will modify when animating
+ animatingBuffers.Push(cloneModel.GetGeometry(0, 0).vertexBuffers[0]);
+ }
+ }
+
+ // Finally create one model (pyramid shape) and a StaticModel to display it from scratch
+ // Note: there are duplicated vertices to enable face normals. We will calculate normals programmatically
+ {
+ const uint numVertices = 18;
+
+ float[] vertexData = {
+ // Position Normal
+ 0.0, 0.5, 0.0, 0.0, 0.0, 0.0,
+ 0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+ 0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+
+ 0.0, 0.5, 0.0, 0.0, 0.0, 0.0,
+ -0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+ 0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+
+ 0.0, 0.5, 0.0, 0.0, 0.0, 0.0,
+ -0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+ -0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+
+ 0.0, 0.5, 0.0, 0.0, 0.0, 0.0,
+ 0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+ -0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+
+ 0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+ 0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+ -0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+
+ 0.5, -0.5, -0.5, 0.0, 0.0, 0.0,
+ -0.5, -0.5, 0.5, 0.0, 0.0, 0.0,
+ -0.5, -0.5, -0.5, 0.0, 0.0, 0.0
+ };
+
+ const uint16[] indexData = {
+ 0, 1, 2,
+ 3, 4, 5,
+ 6, 7, 8,
+ 9, 10, 11,
+ 12, 13, 14,
+ 15, 16, 17
+ };
+
+ // Calculate face normals now
+ for (uint i = 0; i < numVertices; i += 3)
+ {
+ Vector3 v1(vertexData[6 * i], vertexData[6 * i + 1], vertexData[6 * i + 2]);
+ Vector3 v2(vertexData[6 * i + 6], vertexData[6 * i + 7], vertexData[6 * i + 8]);
+ Vector3 v3(vertexData[6 * i + 12], vertexData[6 * i + 13], vertexData[6 * i + 14]);
+
+ Vector3 edge1 = v1 - v2;
+ Vector3 edge2 = v1 - v3;
+ Vector3 normal = edge1.CrossProduct(edge2).Normalized();
+ vertexData[6 * i + 3] = vertexData[6 * i + 9] = vertexData[6 * i + 15] = normal.x;
+ vertexData[6 * i + 4] = vertexData[6 * i + 10] = vertexData[6 * i + 16] = normal.y;
+ vertexData[6 * i + 5] = vertexData[6 * i + 11] = vertexData[6 * i + 17] = normal.z;
+ }
+
+ Model@ fromScratchModel = Model();
+ VertexBuffer@ vb = VertexBuffer();
+ IndexBuffer@ ib = IndexBuffer();
+ Geometry@ geom = Geometry();
+
+ // Shadowed buffer needed for raycasts to work, and so that data can be automatically restored on device loss
+ vb.shadowed = true;
+ // We could use the "legacy" element bitmask to define elements for more compact code, but let's demonstrate
+ // defining the vertex elements explicitly to allow any element types and order
+ Array elements;
+ elements.Push(VertexElement(TYPE_VECTOR3, SEM_POSITION));
+ elements.Push(VertexElement(TYPE_VECTOR3, SEM_NORMAL));
+ vb.SetSize(numVertices, elements);
+ VectorBuffer temp;
+ for (uint i = 0; i < numVertices * 6; ++i)
+ temp.WriteFloat(vertexData[i]);
+ vb.SetData(temp);
+
+ ib.shadowed = true;
+ ib.SetSize(numVertices, false);
+ temp.Clear();
+ for (uint i = 0; i < numVertices; ++i)
+ temp.WriteUShort(indexData[i]);
+ ib.SetData(temp);
+
+ geom.SetVertexBuffer(0, vb);
+ geom.SetIndexBuffer(ib);
+ geom.SetDrawRange(TRIANGLE_LIST, 0, numVertices);
+
+ fromScratchModel.numGeometries = 1;
+ fromScratchModel.SetGeometry(0, 0, geom);
+ fromScratchModel.boundingBox = BoundingBox(Vector3(-0.5, -0.5, -0.5), Vector3(0.5, 0.5, 0.5));
+
+ // Though not necessary to render, the vertex & index buffers must be listed in the model so that it can be saved properly
+ Array vertexBuffers;
+ Array indexBuffers;
+ vertexBuffers.Push(vb);
+ indexBuffers.Push(ib);
+ // Morph ranges could also be not defined. Here we simply define a zero range (no morphing) for the vertex buffer
+ Array morphRangeStarts;
+ Array morphRangeCounts;
+ morphRangeStarts.Push(0);
+ morphRangeCounts.Push(0);
+ fromScratchModel.SetVertexBuffers(vertexBuffers, morphRangeStarts, morphRangeCounts);
+ fromScratchModel.SetIndexBuffers(indexBuffers);
+
+ Node@ node = scene_.CreateChild("FromScratchObject");
+ node.position = Vector3(0.0, 3.0, 0.0);
+ StaticModel@ object = node.CreateComponent("StaticModel");
+ object.model = fromScratchModel;
+ }
+
+ // Create the camera
+ cameraNode = Node("Camera");
+ cameraNode.position = Vector3(0.0, 2.0, -20.0);
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys and mouse/touch to move\n"
+ "Space to toggle animation";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+void AnimateObjects(float timeStep)
+{
+ animTime += timeStep * 100.0;
+
+ // Repeat for each of the cloned vertex buffers
+ for (uint i = 0; i < animatingBuffers.length; ++i)
+ {
+ float startPhase = animTime + i * 30.0;
+ VertexBuffer@ buffer = animatingBuffers[i];
+
+ // Need to prepare a VectorBuffer with all data (positions, normals, uvs...)
+ VectorBuffer newData;
+ uint numVertices = buffer.vertexCount;
+ uint vertexSize = buffer.vertexSize;
+ for (uint j = 0; j < numVertices; ++j)
+ {
+ // If there are duplicate vertices, animate them in phase of the original
+ float phase = startPhase + vertexDuplicates[j] * 10.0f;
+ Vector3 src = originalVertices[j];
+ Vector3 dest;
+ dest.x = src.x * (1.0 + 0.1 * Sin(phase));
+ dest.y = src.y * (1.0 + 0.1 * Sin(phase + 60.0));
+ dest.z = src.z * (1.0 + 0.1 * Sin(phase + 120.0));
+
+ // Write position
+ newData.WriteVector3(dest);
+ // Copy other vertex elements
+ originalVertexData.Seek(j * vertexSize + 12); // Seek past the vertex position
+ for (uint k = 12; k < vertexSize; k += 4)
+ newData.WriteFloat(originalVertexData.ReadFloat());
+ }
+
+ buffer.SetData(newData);
+ }
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Toggle animation with space
+ if (input.keyPress[KEY_SPACE])
+ animate = !animate;
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+
+ // Animate objects' vertex data if enabled
+ if (animate)
+ AnimateObjects(timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ ""
+ " "
+ " Animation"
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ "";
diff --git a/bin/Data/Scripts/35_SignedDistanceFieldText.as b/bin/Data/Scripts/35_SignedDistanceFieldText.as
new file mode 100644
index 0000000..36b9a1f
--- /dev/null
+++ b/bin/Data/Scripts/35_SignedDistanceFieldText.as
@@ -0,0 +1,177 @@
+// Signed distance field text example.
+// This sample demonstrates.
+// - Creating a 3D scene with static content
+// - Creating a 3D text use SDF Font
+// - Displaying the scene using the Renderer subsystem
+// - Handling keyboard and mouse input to move a freelook camera
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+
+ // Create a child scene node (at world origin) and a StaticModel component into it. Set the StaticModel to show a simple
+ // plane mesh with a "stone" material. Note that naming the scene nodes is optional. Scale the scene node larger
+ // (100 x 100 world units)
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(100.0f, 1.0f, 100.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Create a directional light to the world so that we can see something. The light scene node's orientation controls the
+ // light direction; we will use the SetDirection() function which calculates the orientation from a forward direction vector.
+ // The light will use default settings (white light, no shadows)
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f); // The direction vector does not need to be normalized
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+
+ // Create more StaticModel objects to the scene, randomly positioned, rotated and scaled. For rotation, we construct a
+ // quaternion from Euler angles where the Y angle (rotation about the Y axis) is randomized. The mushroom model contains
+ // LOD levels, so the StaticModel component will automatically select the LOD level according to the view distance (you'll
+ // see the model get simpler as it moves further away). Finally, rendering a large number of the same object with the
+ // same material allows instancing to be used, if the GPU supports it. This reduces the amount of CPU work in rendering the
+ // scene.
+ const uint NUM_OBJECTS = 200;
+ for (uint i = 0; i < NUM_OBJECTS; ++i)
+ {
+ Node@ mushroomNode = scene_.CreateChild("Mushroom");
+ mushroomNode.position = Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f);
+ mushroomNode.SetScale(0.5f + Random(2.0f));
+ StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+ mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ mushroomObject.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+
+ Node@ mushroomTitleNode = mushroomNode.CreateChild("MushroomTitle");
+ mushroomTitleNode.position = Vector3(0.0f, 1.2f, 0.0f);
+ Text3D@ mushroomTitleText = mushroomTitleNode.CreateComponent("Text3D");
+ mushroomTitleText.text = "Mushroom " + i;
+
+ mushroomTitleText.SetFont(cache.GetResource("Font", "Fonts/BlueHighway.sdf"), 24);
+ mushroomTitleText.color = Color(1.0f, 0.0f, 0.0f);
+
+ if (i % 3 == 1)
+ {
+ mushroomTitleText.color = Color(0.0f, 1.0f, 0.0f);
+ mushroomTitleText.textEffect = TE_SHADOW;
+ mushroomTitleText.effectColor = Color(0.5f, 0.5f, 0.5f);
+ }
+ else if (i % 3 == 2)
+ {
+ mushroomTitleText.color = Color(1.0f, 1.0f, 0.0f);
+ mushroomTitleText.textEffect = TE_STROKE;
+ mushroomTitleText.effectColor = Color(0.5f, 0.5f, 0.5f);
+ }
+
+ mushroomTitleText.SetAlignment(HA_CENTER, VA_CENTER);
+ }
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ cameraNode.CreateComponent("Camera");
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ // Use the Translate() function (default local space) to move relative to the node's orientation.
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/36_Urho2DTileMap.as b/bin/Data/Scripts/36_Urho2DTileMap.as
new file mode 100644
index 0000000..f379a02
--- /dev/null
+++ b/bin/Data/Scripts/36_Urho2DTileMap.as
@@ -0,0 +1,202 @@
+// Urho2D tile map example.
+// This sample demonstrates:
+// - Creating a 2D scene with tile map
+// - Displaying the scene using the Renderer subsystem
+// - Handling keyboard to move and zoom 2D camera
+// - Interacting with the tile map
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Enable OS cursor
+ input.mouseVisible = true;
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 0.0f, -10.0f);
+
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.orthographic = true;
+ camera.orthoSize = graphics.height * PIXEL_SIZE;
+ camera.zoom = 1.0f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.0) is set for full visibility at 1280x800 resolution)
+
+ // Get tmx file
+ TmxFile2D@ tmxFile = cache.GetResource("TmxFile2D", "Urho2D/isometric_grass_and_water.tmx");
+ if (tmxFile is null)
+ return;
+
+ Node@ tileMapNode = scene_.CreateChild("TileMap");
+ tileMapNode.position = Vector3(0.0f, 0.0f, -1.0f);
+
+ TileMap2D@ tileMap = tileMapNode.CreateComponent("TileMap2D");
+ tileMap.tmxFile = tmxFile;
+
+ // Set camera's position;
+ TileMapInfo2D@ info = tileMap.info;
+ float x = info.mapWidth * 0.5f;
+ float y = info.mapHeight * 0.5f;
+ cameraNode.position = Vector3(x, y, -10.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move, Use PageUp PageDown to zoom.\n LMB to remove a tile, RMB to swap grass and water.";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 4.0f;
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::UP * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::DOWN * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ if (input.keyDown[KEY_PAGEUP])
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ camera.zoom = camera.zoom * 1.01f;
+ }
+
+ if (input.keyDown[KEY_PAGEDOWN])
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ camera.zoom = camera.zoom * 0.99f;
+ }
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Listen to mouse clicks
+ SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown");
+
+ // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
+{
+ Node@ tileMapNode = scene_.GetChild("TileMap", true);
+ TileMap2D@ map = tileMapNode.GetComponent("TileMap2D");
+ TileMapLayer2D@ layer = map.GetLayer(0);
+
+ Vector2 pos = GetMousePositionXY();
+ int x, y;
+ if (map.PositionToTileIndex(x, y, pos))
+ {
+ // Get tile's sprite. Note that layer.GetTile(x, y).sprite is read-only, so we get the sprite through tile's node
+ Node@ n = layer.GetTileNode(x, y);
+ if (n is null)
+ return;
+ StaticSprite2D@ sprite = n.GetComponent("StaticSprite2D");
+
+ if (input.mouseButtonDown[MOUSEB_RIGHT])
+ {
+ // Swap grass and water
+ if (layer.GetTile(x, y).gid < 9) // First 8 sprites in the "isometric_grass_and_water.png" tileset are mostly grass and from 9 to 24 they are mostly water
+ sprite.sprite = layer.GetTile(0, 0).sprite; // Replace grass by water sprite used in top tile
+ else sprite.sprite = layer.GetTile(24, 24).sprite; // Replace water by grass sprite used in bottom tile
+ }
+ else sprite.sprite = null; // 'Remove' sprite
+ }
+}
+
+Vector2 GetMousePositionXY()
+{
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ Vector3 screenPoint = Vector3(float(input.mousePosition.x) / graphics.width, float(input.mousePosition.y) / graphics.height, 10.0f);
+ Vector3 worldPoint = camera.ScreenToWorldPoint(screenPoint);
+ return Vector2(worldPoint.x, worldPoint.y);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " Zoom In" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Zoom Out" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/37_UIDrag.as b/bin/Data/Scripts/37_UIDrag.as
new file mode 100644
index 0000000..6c77e7c
--- /dev/null
+++ b/bin/Data/Scripts/37_UIDrag.as
@@ -0,0 +1,188 @@
+// Urho3D UI Drag Example:
+// This sample demonstrates:
+// - Creating GUI elements from AngelScript
+// - Loading GUI Style from xml
+// - Subscribing to GUI drag events and handling them
+// - Working with GUI elements with specific tags.
+
+#include "Scripts/Utilities/Sample.as"
+StringHash VAR_BUTTONS("BUTTONS");
+StringHash VAR_START("START");
+StringHash VAR_DELTA("DELTA");
+
+void Start()
+{
+ // Execute base class startup
+ SampleStart();
+
+ // Set mouse visible
+ String platform = GetPlatform();
+ if (platform != "Android" and platform != "iOS")
+ input.mouseVisible = true;
+
+ // Create the UI content
+ CreateGUI();
+ CreateInstructions();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateGUI()
+{
+ UIElement@ root = ui.root;
+ // Load the style sheet from xml
+ root.defaultStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+
+ for (int i=0; i < 10; i++)
+ {
+ Button@ b = Button();
+ root.AddChild(b);
+ b.SetStyleAuto();
+ b.minWidth = 250;
+ b.position = IntVector2(50*i, 50*i);
+
+ // Enable the bring-to-front flag and set the initial priority
+ b.bringToFront = true;
+ b.priority = i;
+
+ // Set the layout mode to make the child text elements aligned vertically
+ b.SetLayout(LM_VERTICAL, 20, IntRect(40, 40, 40, 40));
+ Array dragInfos = {"Num Touch", "Text", "Event Touch"};
+ for (uint j = 0; j < dragInfos.length; ++j)
+ b.CreateChild("Text", dragInfos[j]).SetStyleAuto();
+
+ if (i % 2 == 0)
+ b.AddTag("SomeTag");
+
+ SubscribeToEvent(b, "Click", "HandleClick");
+ SubscribeToEvent(b, "DragMove", "HandleDragMove");
+ SubscribeToEvent(b, "DragBegin", "HandleDragBegin");
+ SubscribeToEvent(b, "DragCancel", "HandleDragCancel");
+ }
+
+ for (int i = 0; i < 10; i++)
+ {
+ Text@ t = Text();
+ root.AddChild(t);
+ t.SetStyleAuto();
+ t.name = "Touch "+ String(i);
+ t.visible = false;
+ t.priority = 100; // Make sure it has higher priority than the buttons
+ }
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Drag on the buttons to move them around.\n" +
+ "Touch input allows also multi-drag.\n" +
+ "Press SPACE to show/hide tagged UI elements.";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleClick(StringHash eventType, VariantMap& eventData)
+{
+ Button@ element = eventData["Element"].GetPtr();
+ element.BringToFront();
+}
+
+void HandleDragBegin(StringHash eventType, VariantMap& eventData)
+{
+ Button@ element = eventData["Element"].GetPtr();
+
+ int lx = eventData["X"].GetInt();
+ int ly = eventData["Y"].GetInt();
+
+ IntVector2 p = element.position;
+ element.vars[VAR_START] = p;
+ element.vars[VAR_DELTA] = IntVector2(p.x - lx, p.y - ly);
+
+ int buttons = eventData["Buttons"].GetInt();
+ element.vars[VAR_BUTTONS] = buttons;
+
+ Text@ t = element.GetChild(String("Text"));
+ t.text = "Drag Begin Buttons: " + String(buttons);
+
+ t = element.GetChild(String("Num Touch"));
+ t.text = "Number of buttons: " + String(eventData["NumButtons"].GetInt());
+}
+
+void HandleDragMove(StringHash eventType, VariantMap& eventData)
+{
+ Button@ element = eventData["Element"].GetPtr();
+ int buttons = eventData["Buttons"].GetInt();
+ IntVector2 d = element.vars[VAR_DELTA].GetIntVector2();
+ int X = eventData["X"].GetInt() + d.x;
+ int Y = eventData["Y"].GetInt() + d.y;
+ int BUTTONS = element.vars[VAR_BUTTONS].GetInt();
+
+ Text@ t = element.GetChild(String("Event Touch"));
+ t.text = "Drag Move Buttons: " + String(buttons);
+
+ if (buttons == BUTTONS)
+ element.position = IntVector2(X, Y);
+}
+
+void HandleDragCancel(StringHash eventType, VariantMap& eventData)
+{
+ Button@ element = eventData["Element"].GetPtr();
+ IntVector2 P = element.vars[VAR_START].GetIntVector2();
+ element.position = P;
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ root = ui.root;
+
+ uint n = input.numTouches;
+ for (uint i = 0; i < n; i++)
+ {
+ Text@ t = root.GetChild("Touch " + String(i));
+ TouchState@ ts = input.touches[i];
+ t.text = "Touch "+ String(ts.touchID);
+
+ IntVector2 pos = ts.position;
+ pos.y -= 30;
+
+ t.position = pos;
+ t.visible = true;
+ }
+
+ for (uint i = n; i < 10; i++)
+ {
+ Text@ t = root.GetChild("Touch " + String(i));
+ t.visible = false;
+ }
+
+ if (input.keyPress[KEY_SPACE])
+ {
+ Array@ elements = root.GetChildrenWithTag("SomeTag");
+ for (uint i = 0; i < elements.length; ++i)
+ elements[i].visible = !elements[i].visible;
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/38_SceneAndUILoad.as b/bin/Data/Scripts/38_SceneAndUILoad.as
new file mode 100644
index 0000000..2cd7a96
--- /dev/null
+++ b/bin/Data/Scripts/38_SceneAndUILoad.as
@@ -0,0 +1,154 @@
+// Scene & UI load example.
+// This sample demonstrates:
+// - Loading a scene from a file and showing it
+// - Loading a UI layout from a file and showing it
+// - Subscribing to the UI layout's events
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content and subscribe to UI events
+ CreateUI();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Subscribe to global events for camera movement
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Load scene content prepared in the editor (XML format). GetFile() returns an open file from the resource system
+ // which scene.LoadXML() will read
+ scene_.LoadXML(cache.GetFile("Scenes/SceneLoadExample.xml"));
+
+ // Create the camera (not included in the scene file)
+ cameraNode = scene_.CreateChild("Camera");
+ cameraNode.CreateComponent("Camera");
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 2.0f, -10.0f);
+}
+
+void CreateUI()
+{
+ // Set up global UI style into the root UI element
+ XMLFile@ style = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+ ui.root.defaultStyle = style;
+
+ // Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ // control the camera, and when visible, it will interact with the UI
+ Cursor@ cursor = Cursor();
+ cursor.SetStyleAuto();
+ ui.cursor = cursor;
+ // Set starting position of the cursor at the rendering window center
+ cursor.SetPosition(graphics.width / 2, graphics.height / 2);
+
+ // Load UI content prepared in the editor and add to the UI hierarchy
+ UIElement@ layoutRoot = ui.LoadLayout(cache.GetResource("XMLFile", "UI/UILoadExample.xml"));
+ ui.root.AddChild(layoutRoot);
+
+ // Subscribe to button actions (toggle scene lights when pressed then released)
+ Button@ button = layoutRoot.GetChild("ToggleLight1", true);
+ if (button !is null)
+ SubscribeToEvent(button, "Released", "ToggleLight1");
+ button = layoutRoot.GetChild("ToggleLight2", true);
+ if (button !is null)
+ SubscribeToEvent(button, "Released", "ToggleLight2");
+}
+
+void ToggleLight1()
+{
+ Node@ lightNode = scene_.GetChild("Light1", true);
+ if (lightNode !is null)
+ lightNode.enabled = !lightNode.enabled;
+}
+
+void ToggleLight2()
+{
+ Node@ lightNode = scene_.GetChild("Light2", true);
+ if (lightNode !is null)
+ lightNode.enabled = !lightNode.enabled;
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for camera motion
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+void MoveCamera(float timeStep)
+{
+ input.mouseVisible = input.mouseMode != MM_RELATIVE;
+ bool mouseDown = input.mouseButtonDown[MOUSEB_RIGHT];
+
+ // Override the MM_RELATIVE mouse grabbed settings, to allow interaction with UI
+ input.mouseGrabbed = mouseDown;
+
+ // Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = !mouseDown;
+
+ // Do not move if the UI has a focused element
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ // Only move the camera when the cursor is hidden
+ if (!ui.cursor.visible)
+ {
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+ }
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/39_CrowdNavigation.as b/bin/Data/Scripts/39_CrowdNavigation.as
new file mode 100644
index 0000000..f134f70
--- /dev/null
+++ b/bin/Data/Scripts/39_CrowdNavigation.as
@@ -0,0 +1,662 @@
+// CrowdNavigation example.
+// This sample demonstrates:
+// - Generating a dynamic navigation mesh into the scene
+// - Performing path queries to the navigation mesh
+// - Adding and removing obstacles/agents at runtime
+// - Raycasting drawable components
+// - Crowd movement management
+// - Accessing crowd agents with the crowd manager
+// - Using off-mesh connections to make boxes climbable
+// - Using agents to simulate moving obstacles
+
+#include "Scripts/Utilities/Sample.as"
+
+const String INSTRUCTION("instructionText");
+
+bool useStreaming = false;
+// Used for streaming only
+const int STREAMING_DISTANCE = 2;
+Array navigationTilesData;
+Array navigationTilesIdx;
+Array addedTiles;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateUI();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update and render post-update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ // Also create a DebugRenderer component so that we can draw debug geometry
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+
+ // Create scene node & StaticModel component for showing a static plane
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(100.0f, 1.0f, 100.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Create a Zone component for ambient lighting & fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(0.5f, 0.5f, 0.7f);
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ // Create randomly sized boxes. If boxes are big enough, make them occluders. Occluders will be software rasterized before
+ // rendering to a low-resolution depth-only buffer to test the objects in the view frustum for visibility
+ Node@ boxGroup = scene_.CreateChild("Boxes");
+ for (uint i = 0; i < 20; ++i)
+ {
+ Node@ boxNode = boxGroup.CreateChild("Box");
+ float size = 1.0f + Random(10.0f);
+ boxNode.position = Vector3(Random(80.0f) - 40.0f, size * 0.5f, Random(80.0f) - 40.0f);
+ boxNode.SetScale(size);
+ StaticModel@ boxObject = boxNode.CreateComponent("StaticModel");
+ boxObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+ boxObject.castShadows = true;
+ if (size >= 3.0f)
+ boxObject.occluder = true;
+ }
+
+ // Create a DynamicNavigationMesh component to the scene root
+ DynamicNavigationMesh@ navMesh = scene_.CreateComponent("DynamicNavigationMesh");
+ // Set small tiles to show navigation mesh streaming
+ navMesh.tileSize = 32;
+ // Enable drawing debug geometry for obstacles and off-mesh connections
+ navMesh.drawObstacles = true;
+ navMesh.drawOffMeshConnections = true;
+ // Set the agent height large enough to exclude the layers under boxes
+ navMesh.agentHeight = 10;
+ // Set nav mesh cell height to minimum (allows agents to be grounded)
+ navMesh.cellHeight = 0.05f;
+ // Create a Navigable component to the scene root. This tags all of the geometry in the scene as being part of the
+ // navigation mesh. By default this is recursive, but the recursion could be turned off from Navigable
+ scene_.CreateComponent("Navigable");
+ // Add padding to the navigation mesh in Y-direction so that we can add objects on top of the tallest boxes
+ // in the scene and still update the mesh correctly
+ navMesh.padding = Vector3(0.0f, 10.0f, 0.0f);
+ // Now build the navigation geometry. This will take some time. Note that the navigation mesh will prefer to use
+ // physics geometry from the scene nodes, as it often is simpler, but if it can not find any (like in this example)
+ // it will use renderable geometry instead
+ navMesh.Build();
+
+ // Create an off-mesh connection to each box to make it climbable (tiny boxes are skipped). A connection is built from 2 nodes.
+ // Note that OffMeshConnections must be added before building the navMesh, but as we are adding Obstacles next, tiles will be automatically rebuilt.
+ // Creating connections post-build here allows us to use FindNearestPoint() to procedurally set accurate positions for the connection
+ CreateBoxOffMeshConnections(navMesh, boxGroup);
+
+ // Create some mushrooms as obstacles. Note that obstacles are non-walkable areas
+ for (uint i = 0; i < 100; ++i)
+ CreateMushroom(Vector3(Random(90.0f) - 45.0f, 0.0f, Random(90.0f) - 45.0f));
+
+ // Create a CrowdManager component to the scene root (mandatory for crowd agents)
+ CrowdManager@ crowdManager = scene_.CreateComponent("CrowdManager");
+ CrowdObstacleAvoidanceParams params = crowdManager.GetObstacleAvoidanceParams(0);
+ // Set the params to "High (66)" setting
+ params.velBias = 0.5f;
+ params.adaptiveDivs = 7;
+ params.adaptiveRings = 3;
+ params.adaptiveDepth = 3;
+ crowdManager.SetObstacleAvoidanceParams(0, params);
+
+ // Create some movable barrels. We create them as crowd agents, as for moving entities it is less expensive and more convenient than using obstacles
+ CreateMovingBarrels(navMesh);
+
+ // Create Jack node as crowd agent
+ SpawnJack(Vector3(-5.0f, 0, 20.0f), scene_.CreateChild("Jacks"));
+
+ // Create the camera. Limit far clip distance to match the fog. Note: now we actually create the camera node outside
+ // the scene, because we want it to be unaffected by scene load / save
+ cameraNode = Node();
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 300.0f;
+
+ // Set an initial position for the camera scene node above the plane and looking down
+ cameraNode.position = Vector3(0.0f, 50.0f, 0.0f);
+ pitch = 80.0f;
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+}
+
+void CreateUI()
+{
+ // Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ // control the camera, and when visible, it will point the raycast target
+ XMLFile@ style = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+ Cursor@ cursor = Cursor();
+ cursor.SetStyleAuto(style);
+ ui.cursor = cursor;
+ // Set starting position of the cursor at the rendering window center
+ cursor.SetPosition(graphics.width / 2, graphics.height / 2);
+
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text", INSTRUCTION);
+ instructionText.text =
+ "Use WASD keys to move, RMB to rotate view\n"
+ "LMB to set destination, SHIFT+LMB to spawn a Jack\n"
+ "MMB or O key to add obstacles or remove obstacles/agents\n"
+ "F5 to save scene, F7 to load\n"
+ "Tab to toggle navigation mesh streaming\n"
+ "Space to toggle debug geometry\n"
+ "F12 to toggle this instruction text";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostRenderUpdate() function for processing the post-render update event, during which we request debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+
+ // Subscribe HandleCrowdAgentFailure() function for resolving invalidation issues with agents, during which we
+ // use a larger extents for finding a point on the navmesh to fix the agent's position
+ SubscribeToEvent("CrowdAgentFailure", "HandleCrowdAgentFailure");
+
+ // Subscribe HandleCrowdAgentReposition() function for controlling the animation
+ SubscribeToEvent("CrowdAgentReposition", "HandleCrowdAgentReposition");
+
+ // Subscribe HandleCrowdAgentFormation() function for positioning agent into a formation
+ SubscribeToEvent("CrowdAgentFormation", "HandleCrowdAgentFormation");
+}
+
+void CreateMushroom(const Vector3& pos)
+{
+ Node@ mushroomNode = scene_.CreateChild("Mushroom");
+ mushroomNode.position = pos;
+ mushroomNode.rotation = Quaternion(0.0f, Random(360.0f), 0.0f);
+ mushroomNode.SetScale(2.0f + Random(0.5f));
+ StaticModel@ mushroomObject = mushroomNode.CreateComponent("StaticModel");
+ mushroomObject.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ mushroomObject.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ mushroomObject.castShadows = true;
+
+ // Create the navigation Obstacle component and set its height & radius proportional to scale
+ Obstacle@ obstacle = mushroomNode.CreateComponent("Obstacle");
+ obstacle.radius = mushroomNode.scale.x;
+ obstacle.height = mushroomNode.scale.y;
+}
+
+void SpawnJack(const Vector3& pos, Node@ jackGroup)
+{
+ Node@ jackNode = jackGroup.CreateChild("Jack");
+ jackNode.position = pos;
+ AnimatedModel@ modelObject = jackNode.CreateComponent("AnimatedModel");
+ modelObject.model = cache.GetResource("Model", "Models/Jack.mdl");
+ modelObject.material = cache.GetResource("Material", "Materials/Jack.xml");
+ modelObject.castShadows = true;
+ jackNode.CreateComponent("AnimationController");
+
+ // Create a CrowdAgent component and set its height and realistic max speed/acceleration. Use default radius
+ CrowdAgent@ agent = jackNode.CreateComponent("CrowdAgent");
+ agent.height = 2.0f;
+ agent.maxSpeed = 3.0f;
+ agent.maxAccel = 5.0f;
+}
+
+void CreateBoxOffMeshConnections(DynamicNavigationMesh@ navMesh, Node@ boxGroup)
+{
+ Array@ boxes = boxGroup.GetChildren();
+ for (uint i=0; i < boxes.length; ++i)
+ {
+ Node@ box = boxes[i];
+ Vector3 boxPos = box.position;
+ float boxHalfSize = box.scale.x / 2;
+
+ // Create 2 empty nodes for the start & end points of the connection. Note that order matters only when using one-way/unidirectional connection.
+ Node@ connectionStart = box.CreateChild("ConnectionStart");
+ connectionStart.worldPosition = navMesh.FindNearestPoint(boxPos + Vector3(boxHalfSize, -boxHalfSize, 0)); // Base of box
+ Node@ connectionEnd = connectionStart.CreateChild("ConnectionEnd");
+ connectionEnd.worldPosition = navMesh.FindNearestPoint(boxPos + Vector3(boxHalfSize, boxHalfSize, 0)); // Top of box
+
+ // Create the OffMeshConnection component to one node and link the other node
+ OffMeshConnection@ connection = connectionStart.CreateComponent("OffMeshConnection");
+ connection.endPoint = connectionEnd;
+ }
+}
+
+void CreateMovingBarrels(DynamicNavigationMesh@ navMesh)
+{
+ Node@ barrel = scene_.CreateChild("Barrel");
+ StaticModel@ model = barrel.CreateComponent("StaticModel");
+ model.model = cache.GetResource("Model", "Models/Cylinder.mdl");
+ Material@ material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+ model.material = material;
+ material.textures[TU_DIFFUSE] = cache.GetResource("Texture2D", "Textures/TerrainDetail2.dds");
+ model.castShadows = true;
+ for (uint i = 0; i < 20; ++i)
+ {
+ Node@ clone = barrel.Clone();
+ float size = 0.5 + Random(1);
+ clone.scale = Vector3(size / 1.5, size * 2.0, size / 1.5);
+ clone.position = navMesh.FindNearestPoint(Vector3(Random(80.0) - 40.0, size * 0.5 , Random(80.0) - 40.0));
+ CrowdAgent@ agent = clone.CreateComponent("CrowdAgent");
+ agent.radius = clone.scale.x * 0.5f;
+ agent.height = size;
+ agent.navigationQuality = NAVIGATIONQUALITY_LOW;
+ }
+ barrel.Remove();
+}
+
+void SetPathPoint(bool spawning)
+{
+ Vector3 hitPos;
+ Drawable@ hitDrawable;
+
+ if (Raycast(250.0f, hitPos, hitDrawable))
+ {
+ DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
+ Vector3 pathPos = navMesh.FindNearestPoint(hitPos, Vector3(1.0f, 1.0f, 1.0f));
+ Node@ jackGroup = scene_.GetChild("Jacks");
+ if (spawning)
+ // Spawn a jack at the target position
+ SpawnJack(pathPos, jackGroup);
+ else
+ // Set crowd agents target position
+ cast(scene_.GetComponent("CrowdManager")).SetCrowdTarget(pathPos, jackGroup);
+ }
+}
+
+void AddOrRemoveObject()
+{
+ // Raycast and check if we hit a mushroom node. If yes, remove it, if no, create a new one
+ Vector3 hitPos;
+ Drawable@ hitDrawable;
+
+ if (Raycast(250.0f, hitPos, hitDrawable))
+ {
+ Node@ hitNode = hitDrawable.node;
+
+ // Note that navmesh rebuild happens when the Obstacle component is removed
+ if (hitNode.name == "Mushroom")
+ hitNode.Remove();
+ else if (hitNode.name == "Jack")
+ hitNode.Remove();
+ else
+ CreateMushroom(hitPos);
+ }
+}
+
+bool Raycast(float maxDistance, Vector3& hitPos, Drawable@& hitDrawable)
+{
+ hitDrawable = null;
+
+ IntVector2 pos = ui.cursorPosition;
+ // Check the cursor is visible and there is no UI element in front of the cursor
+ if (!ui.cursor.visible || ui.GetElementAt(pos, true) !is null)
+ return false;
+
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
+ // Pick only geometry objects, not eg. zones or lights, only get the first (closest) hit
+ // Note the convenience accessor to scene's Octree component
+ RayQueryResult result = scene_.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY);
+ if (result.drawable !is null)
+ {
+ hitPos = result.position;
+ hitDrawable = result.drawable;
+ return true;
+ }
+
+ return false;
+}
+
+void MoveCamera(float timeStep)
+{
+ input.mouseVisible = input.mouseMode != MM_RELATIVE;
+ bool mouseDown = input.mouseButtonDown[MOUSEB_RIGHT];
+
+ // Override the MM_RELATIVE mouse grabbed settings, to allow interaction with UI
+ input.mouseGrabbed = mouseDown;
+
+ // Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = !mouseDown;
+
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ // Only move the camera when the cursor is hidden
+ if (!ui.cursor.visible)
+ {
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+ }
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+
+ // Set destination or spawn a jack with left mouse button
+ if (input.mouseButtonPress[MOUSEB_LEFT])
+ SetPathPoint(input.qualifierDown[QUAL_SHIFT]);
+ // Add new obstacle or remove existing obstacle/agent with middle mouse button
+ else if (input.mouseButtonPress[MOUSEB_MIDDLE] || input.keyPress[KEY_O])
+ AddOrRemoveObject();
+
+ // Check for loading/saving the scene from/to the file Data/Scenes/CrowdNavigation.xml relative to the executable directory
+ if (input.keyPress[KEY_F5])
+ {
+ File saveFile(fileSystem.programDir + "Data/Scenes/CrowdNavigation.xml", FILE_WRITE);
+ scene_.SaveXML(saveFile);
+ }
+ else if (input.keyPress[KEY_F7])
+ {
+ File loadFile(fileSystem.programDir + "Data/Scenes/CrowdNavigation.xml", FILE_READ);
+ scene_.LoadXML(loadFile);
+ }
+
+ // Toggle debug geometry with space
+ else if (input.keyPress[KEY_SPACE])
+ drawDebug = !drawDebug;
+
+ // Toggle instruction text with F12
+ else if (input.keyPress[KEY_F12])
+ {
+ UIElement@ instruction = ui.root.GetChild(INSTRUCTION);
+ instruction.visible = !instruction.visible;
+ }
+}
+
+void ToggleStreaming(bool enabled)
+{
+ DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
+ if (enabled)
+ {
+ int maxTiles = (2 * STREAMING_DISTANCE + 1) * (2 * STREAMING_DISTANCE + 1);
+ BoundingBox boundingBox = navMesh.boundingBox;
+ SaveNavigationData();
+ navMesh.Allocate(boundingBox, maxTiles);
+ }
+ else
+ navMesh.Build();
+}
+
+void UpdateStreaming()
+{
+ DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
+
+ // Center the navigation mesh at the crowd of jacks
+ Vector3 averageJackPosition;
+ Node@ jackGroup = scene_.GetChild("Jacks");
+ if (jackGroup !is null)
+ {
+ for (uint i = 0; i < jackGroup.numChildren; ++i)
+ averageJackPosition += jackGroup.children[i].worldPosition;
+ averageJackPosition /= jackGroup.numChildren;
+ }
+
+ // Compute currently loaded area
+ IntVector2 jackTile = navMesh.GetTileIndex(averageJackPosition);
+ IntVector2 beginTile = VectorMax(IntVector2(0, 0), jackTile - IntVector2(1, 1) * STREAMING_DISTANCE);
+ IntVector2 endTile = VectorMin(jackTile + IntVector2(1, 1) * STREAMING_DISTANCE, navMesh.numTiles - IntVector2(1, 1));
+
+ // Remove tiles
+ for (uint i = 0; i < addedTiles.length;)
+ {
+ IntVector2 tileIdx = addedTiles[i];
+ if (beginTile.x <= tileIdx.x && tileIdx.x <= endTile.x && beginTile.y <= tileIdx.y && tileIdx.y <= endTile.y)
+ ++i;
+ else
+ {
+ addedTiles.Erase(i);
+ navMesh.RemoveTile(tileIdx);
+ }
+ }
+
+ // Add tiles
+ for (int z = beginTile.y; z <= endTile.y; ++z)
+ for (int x = beginTile.x; x <= endTile.x; ++x)
+ {
+ const IntVector2 tileIdx(x, z);
+ int tileDataIdx = navigationTilesIdx.Find(tileIdx);
+ if (!navMesh.HasTile(tileIdx) && tileDataIdx != -1)
+ {
+ addedTiles.Push(tileIdx);
+ navMesh.AddTile(navigationTilesData[tileDataIdx]);
+ }
+ }
+}
+
+void SaveNavigationData()
+{
+ DynamicNavigationMesh@ navMesh = scene_.GetComponent("DynamicNavigationMesh");
+ navigationTilesData.Clear();
+ navigationTilesIdx.Clear();
+ addedTiles.Clear();
+ IntVector2 numTiles = navMesh.numTiles;
+ for (int z = 0; z < numTiles.y; ++z)
+ for (int x = 0; x < numTiles.x; ++x)
+ {
+ IntVector2 idx(x, z);
+ navigationTilesData.Push(navMesh.GetTileData(idx));
+ navigationTilesIdx.Push(idx);
+ }
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+
+ // Update streaming
+ if (input.keyPress[KEY_TAB])
+ {
+ useStreaming = !useStreaming;
+ ToggleStreaming(useStreaming);
+ }
+ if (useStreaming)
+ UpdateStreaming();
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (drawDebug)
+ {
+ // Visualize navigation mesh, obstacles and off-mesh connections
+ cast(scene_.GetComponent("DynamicNavigationMesh")).DrawDebugGeometry(true);
+ // Visualize agents' path and position to reach
+ cast(scene_.GetComponent("CrowdManager")).DrawDebugGeometry(true);
+ }
+}
+
+void HandleCrowdAgentFailure(StringHash eventType, VariantMap& eventData)
+{
+ Node@ node = eventData["Node"].GetPtr();
+ int state = eventData["CrowdAgentState"].GetInt();
+
+ // If the agent's state is invalid, likely from spawning on the side of a box, find a point in a larger area
+ if (state == CA_STATE_INVALID)
+ {
+ // Get a point on the navmesh using more generous extents
+ Vector3 newPos = cast(scene_.GetComponent("DynamicNavigationMesh")).FindNearestPoint(node.position, Vector3(5.0f,5.0f,5.0f));
+ // Set the new node position, CrowdAgent component will automatically reset the state of the agent
+ node.position = newPos;
+ }
+}
+
+void HandleCrowdAgentFormation(StringHash eventType, VariantMap& eventData)
+{
+ uint index = eventData["Index"].GetUInt();
+ uint size = eventData["Size"].GetUInt();
+ Vector3 position = eventData["Position"].GetVector3();
+
+ // The first agent will always move to the exact position, all other agents will select a random point nearby
+ if (index > 0)
+ {
+ CrowdManager@ crowdManager =GetEventSender();
+ CrowdAgent@ agent = eventData["CrowdAgent"].GetPtr();
+ eventData["Position"] = crowdManager.GetRandomPointInCircle(position, agent.radius, agent.queryFilterType);
+ }
+}
+
+void HandleCrowdAgentReposition(StringHash eventType, VariantMap& eventData)
+{
+ const String WALKING_ANI = "Models/Jack_Walk.ani";
+
+ Node@ node = eventData["Node"].GetPtr();
+ CrowdAgent@ agent = eventData["CrowdAgent"].GetPtr();
+ Vector3 velocity = eventData["Velocity"].GetVector3();
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Only Jack agent has animation controller
+ AnimationController@ animCtrl = node.GetComponent("AnimationController");
+ if (animCtrl !is null)
+ {
+ float speed = velocity.length;
+ if (animCtrl.IsPlaying(WALKING_ANI))
+ {
+ float speedRatio = speed / agent.maxSpeed;
+ // Face the direction of its velocity but moderate the turning speed based on the speed ratio and timeStep
+ node.rotation = node.rotation.Slerp(Quaternion(Vector3::FORWARD, velocity), 10.f * timeStep * speedRatio);
+ // Throttle the animation speed based on agent speed ratio (ratio = 1 is full throttle)
+ animCtrl.SetSpeed(WALKING_ANI, speedRatio * 1.5f);
+ }
+ else
+ animCtrl.Play(WALKING_ANI, 0, true, 0.1f);
+
+ // If speed is too low then stop the animation
+ if (speed < agent.radius)
+ animCtrl.Stop(WALKING_ANI, 0.5f);
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Set" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " Debug" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/40_Localization.as b/bin/Data/Scripts/40_Localization.as
new file mode 100644
index 0000000..061a865
--- /dev/null
+++ b/bin/Data/Scripts/40_Localization.as
@@ -0,0 +1,188 @@
+// Localization example.
+// This sample demonstrates:
+// - Loading a collection of strings from JSON-files
+// - Creating text elements that automatically translates itself by changing the language
+// - The manually reaction to change language
+
+#include "Scripts/Utilities/Sample.as"
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Enable and center OS cursor
+ input.mouseVisible = true;
+ input.CenterMousePosition();
+
+ // Load strings from JSON files and subscribe to the change language event
+ InitLocalizationSystem();
+
+ // Init the 3D space
+ CreateScene();
+
+ // Init the user interface
+ CreateGUI();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+}
+
+void InitLocalizationSystem()
+{
+ // JSON files must be in UTF8 encoding without BOM
+ // The first found language will be set as current
+ localization.LoadJSONFile("StringsEnRu.json");
+ // You can load multiple files
+ localization.LoadJSONFile("StringsDe.json");
+ // Hook up to the change language
+ SubscribeToEvent("ChangeLanguage", "HandleChangeLanguage");
+}
+
+void CreateGUI()
+{
+ ui.root.defaultStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+
+ Window@ window = Window();
+ ui.root.AddChild(window);
+ window.SetMinSize(384, 192);
+ window.SetLayout(LM_VERTICAL, 6, IntRect(6, 6, 6, 6));
+ window.SetAlignment(HA_CENTER, VA_CENTER);
+ window.SetStyleAuto();
+
+ Text@ windowTitle = Text();
+ windowTitle.name = "WindowTitle";
+ windowTitle.SetStyleAuto();
+ window.AddChild(windowTitle);
+
+ // In this place the current language is "en" because it was found first when loading the JSON files
+ String langName = localization.language;
+ // Languages are numbered in the loading order
+ int langIndex = localization.languageIndex; // == 0 at the beginning
+ // Get string with identifier "title" in the current language
+ String localizedString = localization.Get("title");
+ // Localization.Get returns String::EMPTY if the id is empty.
+ // Localization.Get returns the id if translation is not found and will be added a warning into the log.
+
+ windowTitle.text = localizedString + " (" + String(langIndex) + " " + langName + ")";
+
+ Button@ b = Button();
+ window.AddChild(b);
+ b.SetStyle("Button");
+ b.minHeight = 24;
+
+ Text@ t = b.CreateChild("Text", "ButtonTextChangeLang");
+ // The showing text value will automatically change when language is changed
+ t.autoLocalizable = true;
+ // The text value used as a string identifier in this mode.
+ // Remember that a letter case of the id and of the lang name is important.
+ t.text = "Press this button";
+
+ t.SetAlignment(HA_CENTER, VA_CENTER);
+ t.SetStyle("Text");
+ SubscribeToEvent(b, "Released", "HandleChangeLangButtonPressed");
+
+ b = Button();
+ window.AddChild(b);
+ b.SetStyle("Button");
+ b.minHeight = 24;
+ t = b.CreateChild("Text", "ButtonTextQuit");
+ t.SetAlignment(HA_CENTER, VA_CENTER);
+ t.SetStyle("Text");
+
+ // Manually set text in the current language
+ t.text = localization.Get("quit");
+
+ SubscribeToEvent(b, "Released", "HandleQuitButtonPressed");
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+ scene_.CreateComponent("Octree");
+
+ Zone@ zone = scene_.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.ambientColor = Color(0.5f, 0.5f, 0.5f);
+ zone.fogColor = Color(0.4f, 0.5f, 0.8f);
+ zone.fogStart = 1.0f;
+ zone.fogEnd = 100.0f;
+
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(300.0f, 1.0f, 300.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.color = Color(0.8f, 0.8f, 0.8f);
+
+ cameraNode = scene_.CreateChild("Camera");
+ cameraNode.CreateComponent("Camera");
+ cameraNode.position = Vector3(0.0f, 10.0f, -30.0f);
+
+ Node@ text3DNode = scene_.CreateChild("Text3D");
+ text3DNode.position = Vector3(0.0f, 0.1f, 30.0f);
+ text3DNode.SetScale(15);
+ Text3D@ text3D = text3DNode.CreateComponent("Text3D");
+
+ // Manually set text in the current language.
+ text3D.text = localization.Get("lang");
+
+ text3D.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 30);
+ text3D.color = BLACK;
+ text3D.SetAlignment(HA_CENTER, VA_BOTTOM);
+
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ float timeStep = eventData["TimeStep"].GetFloat();
+ const float MOUSE_SENSITIVITY = 0.1f;
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+}
+
+void HandleChangeLangButtonPressed(StringHash eventType, VariantMap& eventData)
+{
+ // Languages are numbered in the loading order
+ int lang = localization.languageIndex;
+ lang++;
+ if (lang >= localization.numLanguages)
+ lang = 0;
+ localization.SetLanguage(lang);
+}
+
+void HandleQuitButtonPressed(StringHash eventType, VariantMap& eventData)
+{
+ engine.Exit();
+}
+
+// You can manually change texts, sprites and other aspects of the game when language is changed
+void HandleChangeLanguage(StringHash eventType, VariantMap& eventData)
+{
+ Text@ windowTitle = ui.root.GetChild("WindowTitle", true);
+ windowTitle.text = localization.Get("title") + " (" +
+ String(localization.languageIndex) + " " +
+ localization.language + ")";
+
+ Text@ buttonText = ui.root.GetChild("ButtonTextQuit", true);
+ buttonText.text = localization.Get("quit");
+
+ Text3D@ text3D = scene_.GetChild("Text3D").GetComponent("Text3D");
+ text3D.text = localization.Get("lang");
+
+ // A text on the button "Press this button" changes automatically
+}
+
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/41_DatabaseDemo.as b/bin/Data/Scripts/41_DatabaseDemo.as
new file mode 100644
index 0000000..3e6cd7d
--- /dev/null
+++ b/bin/Data/Scripts/41_DatabaseDemo.as
@@ -0,0 +1,180 @@
+/// Database demo. This sample demonstrates how to use database subsystem to connect to a database and execute adhoc SQL statements.
+
+#include "Scripts/Utilities/Sample.as"
+
+DbConnection@ connection;
+uint row;
+uint maxRows = 50;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Disable default execution of AngelScript from the console
+ script.executeConsoleCommands = false;
+
+ // Subscribe to console commands and the frame update
+ SubscribeToEvent("ConsoleCommand", "HandleConsoleCommand");
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe key down event
+ SubscribeToEvent("KeyDown", "HandleEscKeyDown");
+
+ // Hide logo to make room for the console
+ SetLogoVisible(false);
+
+ // Show the console by default, make it large
+ console.numRows = graphics.height / 16;
+ console.numBufferedRows = 2 * console.numRows;
+ console.commandInterpreter = "ScriptEventInvoker";
+ console.visible = true;
+ console.closeButton.visible = false;
+
+ // Show OS mouse cursor
+ input.mouseVisible = true;
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Open the operating system console window (for stdin / stdout) if not open yet
+ // Do not open in fullscreen, as this would cause constant device loss
+ if (!graphics.fullscreen)
+ OpenConsoleWindow();
+
+ // In general, the connection string is really the only thing that need to be changed when switching underlying database API
+ // and that when using ODBC API then the connection string must refer to an already installed ODBC driver
+ // Although it has not been tested yet but the ODBC API should be able to interface with any vendor provided ODBC drivers
+ // In this particular demo, however, when using ODBC API then the SQLite-ODBC driver need to be installed
+ // The SQLite-ODBC driver can be built from source downloaded from http://www.ch-werner.de/sqliteodbc/
+ // You can try to install other ODBC driver and modify the connection string below to match your ODBC driver
+ // Both DSN and DSN-less connection string should work
+ // The ODBC API, i.e. URHO3D_DATABASE_ODBC build option, is only available for native (including RPI) platforms
+ // and it is designed for development of game server connecting to ODBC-compliant databases in mind
+
+ // This demo will always work when using SQLite API as the SQLite database engine is embedded inside Urho3D game engine
+ // and this is also the case when targeting Web platform
+
+ // We could have used #ifdef to init the connection string during compile time, but below shows how it is done during runtime
+ // The "URHO3D_DATABASE_ODBC" compiler define is set when URHO3D_DATABASE_ODBC build option is enabled
+ // Connect to a temporary in-memory SQLite database
+ connection = database.Connect(DBAPI == DBAPI_ODBC ? "Driver=SQLite3;Database=:memory:" : "file://");
+
+ // Subscribe to database cursor event to loop through query resultset
+ SubscribeToEvent("DbCursor", "HandleDbCursor");
+
+ // Show instruction
+ Print("This demo connects to temporary in-memory database.\n"
+ "All the tables and their data will be lost after exiting the demo.\n"
+ "Enter a valid SQL statement in the console input and press Enter to execute.\n"
+ "Enter 'get/set maxrows [number]' to get/set the maximum rows to be printed out.\n"
+ "Enter 'get/set connstr [string]' to get/set the database connection string and establish a new connection to it.\n"
+ "Enter 'quit' or 'exit' to exit the demo.\n"
+ "For example:\n ");
+ HandleInput("create table tbl1(col1 varchar(10), col2 smallint)");
+ HandleInput("insert into tbl1 values('Hello', 10)");
+ HandleInput("insert into tbl1 values('World', 20)");
+ HandleInput("select * from tbl1");
+}
+
+void HandleConsoleCommand(StringHash eventType, VariantMap& eventData)
+{
+ if (eventData["Id"].GetString() == "ScriptEventInvoker")
+ HandleInput(eventData["Command"].GetString());
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Check if there is input from stdin
+ String input = GetConsoleInput();
+ if (input.length > 0)
+ HandleInput(input);
+}
+
+void HandleEscKeyDown(StringHash eventType, VariantMap& eventData)
+{
+ // Unlike the other samples, exiting the engine when ESC is pressed instead of just closing the console
+ if (eventData["Key"].GetInt() == KEY_ESCAPE)
+ engine.Exit();
+}
+
+void HandleDbCursor(StringHash eventType, VariantMap& eventData)
+{
+ // In a real application the P_SQL can be used to do the logic branching in a shared event handler
+ // However, this is not required in this sample demo
+ uint numCols = eventData["NumCols"].GetUInt();
+ Array@ colValues = eventData["ColValues"].GetVariantVector();
+ Array@ colHeaders = eventData["ColHeaders"].GetStringVector();
+
+ // In this sample demo we just use db cursor to dump each row immediately so we can filter out the row to conserve memory
+ // In a real application this can be used to perform the client-side filtering logic
+ eventData["Filter"] = true;
+ // In this sample demo we abort the further cursor movement when maximum rows being dumped has been reached
+ eventData["Abort"] = ++row >= maxRows;
+
+ for (uint i = 0; i < numCols; ++i)
+ Print("Row #" + row + ": " + colHeaders[i] + " = " + colValues[i].ToString());
+}
+
+void HandleInput(const String&in input)
+{
+ // Echo input string to stdout
+ Print(input);
+ row = 0;
+ if (input == "quit" || input == "exit")
+ engine.Exit();
+ else if (input.StartsWith("set") || input.StartsWith("get"))
+ {
+ // We expect a key/value pair for 'set' command
+ Array tokens = input.Substring(3).Split(' ');
+ String setting = tokens.length > 0 ? tokens[0] : "";
+ if (input.StartsWith("set") && tokens.length > 1)
+ {
+ if (setting == "maxrows")
+ maxRows = Max(tokens[1].ToUInt(), 1);
+ else if (setting == "connstr") {
+ String newConnectionString(input.Substring(input.Find(" ", input.Find("connstr")) + 1));
+ DbConnection@ newConnection = database.Connect(newConnectionString);
+ if (newConnection !is null)
+ {
+ database.Disconnect(connection);
+ connection = newConnection;
+ }
+ }
+ }
+ if (tokens.length > 0)
+ {
+ if (setting == "maxrows")
+ Print("maximum rows is set to " + maxRows);
+ else if (setting == "connstr")
+ Print("connection string is set to " + connection.connectionString);
+ else
+ Print("Unrecognized setting: " + setting);
+ }
+ else
+ Print("Missing setting paramater. Recognized settings are: maxrows, connstr");
+ }
+ else
+ {
+ // In this sample demo we use the dbCursor event to loop through each row as it is being fetched
+ // Regardless of this event is being used or not, all the fetched rows will be made available in the DbResult object,
+ // unless the dbCursor event handler has instructed to filter out the fetched row from the final result
+ DbResult result = connection.Execute(input, true);
+
+ // Number of affected rows is only meaningful for DML statements like insert/update/delete
+ if (result.numAffectedRows != -1)
+ Print("Number of affected rows: " + result.numAffectedRows);
+ }
+ Print(" ");
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/42_PBRMaterials.as b/bin/Data/Scripts/42_PBRMaterials.as
new file mode 100644
index 0000000..94d7bb4
--- /dev/null
+++ b/bin/Data/Scripts/42_PBRMaterials.as
@@ -0,0 +1,223 @@
+// PBR materials example.
+// This sample demonstrates:
+// - Loading a scene that showcases physically based materials & shaders
+//
+// To use with deferred rendering, a PBR deferred renderpath should be chosen:
+// CoreData/RenderPaths/PBRDeferred.xml or CoreData/RenderPaths/PBRDeferredHWDepth.xml
+
+#include "Scripts/Utilities/Sample.as"
+
+Material@ dynamicMaterial;
+Text@ roughnessLabel;
+Text@ metallicLabel;
+Text@ ambientLabel;
+Zone@ zone;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content and subscribe to UI events
+ CreateUI();
+
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Subscribe to global events for camera movement
+ SubscribeToEvents();
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use sliders to change Roughness and Metallic\n" +
+ "Hold RMB and use WASD keys and mouse to move";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Load scene content prepared in the editor (XML format). GetFile() returns an open file from the resource system
+ // which scene.LoadXML() will read
+ scene_.LoadXML(cache.GetFile("Scenes/PBRExample.xml"));
+
+ Node@ sphereWithDynamicMatNode = scene_.GetChild("SphereWithDynamicMat");
+ StaticModel@ staticModel = sphereWithDynamicMatNode.GetComponent("StaticModel");
+ dynamicMaterial = staticModel.materials[0];
+
+ Node@ zoneNode = scene_.GetChild("Zone");
+ zone = zoneNode.GetComponent("Zone");
+
+ // Create the camera (not included in the scene file)
+ cameraNode = scene_.CreateChild("Camera");
+ cameraNode.CreateComponent("Camera");
+
+ cameraNode.position = sphereWithDynamicMatNode.position + Vector3(2.0f, 2.0f, 2.0f);
+ cameraNode.LookAt(sphereWithDynamicMatNode.position);
+ yaw = cameraNode.rotation.yaw;
+ pitch = cameraNode.rotation.pitch;
+}
+
+void CreateUI()
+{
+ // Set up global UI style into the root UI element
+ XMLFile@ style = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+ ui.root.defaultStyle = style;
+
+ // Create a Cursor UI element because we want to be able to hide and show it at will. When hidden, the mouse cursor will
+ // control the camera, and when visible, it will interact with the UI
+ Cursor@ cursor = Cursor();
+ cursor.SetStyleAuto();
+ ui.cursor = cursor;
+ // Set starting position of the cursor at the rendering window center
+ cursor.SetPosition(graphics.width / 2, graphics.height / 2);
+
+ roughnessLabel = ui.root.CreateChild("Text");
+ roughnessLabel.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ roughnessLabel.SetPosition(370, 50);
+ roughnessLabel.textEffect = TE_SHADOW;
+
+ metallicLabel = ui.root.CreateChild("Text");
+ metallicLabel.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ metallicLabel.SetPosition(370, 100);
+ metallicLabel.textEffect = TE_SHADOW;
+
+ ambientLabel = ui.root.CreateChild("Text");
+ ambientLabel.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ ambientLabel.SetPosition(370, 150);
+ ambientLabel.textEffect = TE_SHADOW;
+
+ Slider@ roughnessSlider = ui.root.CreateChild("Slider");
+ roughnessSlider.SetStyleAuto();
+ roughnessSlider.SetPosition(50, 50);
+ roughnessSlider.SetSize(300, 20);
+ roughnessSlider.range = 1.0f; // 0 - 1 range
+ SubscribeToEvent(roughnessSlider, "SliderChanged", "HandleRoughnessSliderChanged");
+ roughnessSlider.value = 0.5f;
+
+ Slider@ metallicSlider = ui.root.CreateChild("Slider");
+ metallicSlider.SetStyleAuto();
+ metallicSlider.SetPosition(50, 100);
+ metallicSlider.SetSize(300, 20);
+ metallicSlider.range = 1.0f; // 0 - 1 range
+ SubscribeToEvent(metallicSlider, "SliderChanged", "HandleMetallicSliderChanged");
+ metallicSlider.value = 0.5f;
+
+ Slider@ ambientSlider = ui.root.CreateChild("Slider");
+ ambientSlider.SetStyleAuto();
+ ambientSlider.SetPosition(50, 150);
+ ambientSlider.SetSize(300, 20);
+ ambientSlider.range = 10.0f; // 0 - 10 range
+ SubscribeToEvent(ambientSlider, "SliderChanged", "HandleAmbientSliderChanged");
+ ambientSlider.value = zone.ambientColor.a;
+}
+
+void HandleRoughnessSliderChanged(StringHash eventType, VariantMap& eventData)
+{
+ float newValue = eventData["Value"].GetFloat();
+ dynamicMaterial.shaderParameters["Roughness"] = newValue;
+ roughnessLabel.text = "Roughness: " + newValue;
+}
+
+void HandleMetallicSliderChanged(StringHash eventType, VariantMap& eventData)
+{
+ float newValue = eventData["Value"].GetFloat();
+ dynamicMaterial.shaderParameters["Metallic"] = newValue;
+ metallicLabel.text = "Metallic: " + newValue;
+}
+
+void HandleAmbientSliderChanged(StringHash eventType, VariantMap& eventData)
+{
+ float newValue = eventData["Value"].GetFloat();
+ Color col = Color(0.0, 0.0, 0.0, newValue);
+ zone.ambientColor = col;
+ ambientLabel.text = "Ambient HDR Scale: " + zone.ambientColor.a;
+}
+
+void SetupViewport()
+{
+ renderer.hdrRendering = true;
+
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+
+ // Add post-processing effects appropriate with the example scene
+ RenderPath@ effectRenderPath = viewport.renderPath.Clone();
+ effectRenderPath.Append(cache.GetResource("XMLFile", "PostProcess/FXAA2.xml"));
+ effectRenderPath.Append(cache.GetResource("XMLFile", "PostProcess/GammaCorrection.xml"));
+ effectRenderPath.Append(cache.GetResource("XMLFile", "PostProcess/Tonemap.xml"));
+ effectRenderPath.Append(cache.GetResource("XMLFile", "PostProcess/AutoExposure.xml"));
+
+ viewport.renderPath = effectRenderPath;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for camera motion
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+}
+
+void MoveCamera(float timeStep)
+{
+ // Right mouse button controls mouse cursor visibility: hide when pressed
+ ui.cursor.visible = !input.mouseButtonDown[MOUSEB_RIGHT];
+
+ // Do not move if the UI has a focused element
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 10.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ // Only move the camera when the cursor is hidden
+ if (!ui.cursor.visible)
+ {
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+ }
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/43_HttpRequestDemo.as b/bin/Data/Scripts/43_HttpRequestDemo.as
new file mode 100644
index 0000000..3e9d4c6
--- /dev/null
+++ b/bin/Data/Scripts/43_HttpRequestDemo.as
@@ -0,0 +1,97 @@
+// Http request example.
+// This example demonstrates:
+// - How to use Http request API
+
+#include "Scripts/Utilities/Sample.as"
+
+String message;
+Text@ text;
+HttpRequest@ httpRequest;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the user interface
+ CreateUI();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Subscribe to basic events such as update
+ SubscribeToEvents();
+}
+
+void CreateUI()
+{
+ // Construct new Text object
+ text = Text();
+
+ // Set font and text color
+ text.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ text.color = Color(1.0f, 1.0f, 0.0f);
+
+ // Align Text center-screen
+ text.horizontalAlignment = HA_CENTER;
+ text.verticalAlignment = VA_CENTER;
+
+ // Add Text instance to the UI root element
+ ui.root.AddChild(text);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing HTTP request
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Create HTTP request
+ if (httpRequest is null)
+ httpRequest = network.MakeHttpRequest("http://httpbin.org/ip");
+ else
+ {
+ // Initializing HTTP request
+ if (httpRequest.state == HTTP_INITIALIZING)
+ return;
+ // An error has occurred
+ else if (httpRequest.state == HTTP_ERROR)
+ {
+ text.text = "An error has occurred.";
+ UnsubscribeFromEvent("Update");
+ }
+ // Get message data
+ else
+ {
+ if (httpRequest.availableSize > 0)
+ message += httpRequest.ReadLine();
+ else
+ {
+ text.text = "Processing...";
+
+ JSONFile@ json = JSONFile();
+ json.FromString(message);
+
+ JSONValue val = json.GetRoot().Get("origin");
+
+ if (val.isNull)
+ text.text = "Invalid string.";
+ else
+ text.text = "Your IP is: " + val.GetString();
+
+ UnsubscribeFromEvent("Update");
+ }
+ }
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
+
diff --git a/bin/Data/Scripts/44_RibbonTrailDemo.as b/bin/Data/Scripts/44_RibbonTrailDemo.as
new file mode 100644
index 0000000..b48a128
--- /dev/null
+++ b/bin/Data/Scripts/44_RibbonTrailDemo.as
@@ -0,0 +1,226 @@
+// Ribbon trail demo.
+// This sample demonstrates how to use both trail types of RibbonTrail component.
+
+#include "Scripts/Utilities/Sample.as"
+
+Node@ boxNode1;
+Node@ boxNode2;
+RibbonTrail@ swordTrail;
+AnimationController@ ninjaAnimCtrl;
+float timeStepSum = 0.0f;
+float swordTrailStartTime = 0.2f;
+float swordTrailEndTime = 0.46f;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_.CreateComponent("Octree");
+
+ // Create scene node & StaticModel component for showing a static plane
+ Node@ planeNode = scene_.CreateChild("Plane");
+ planeNode.scale = Vector3(100.0f, 1.0f, 100.0f);
+ StaticModel@ planeObject = planeNode.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Create a directional light to the world.
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f); // The direction vector does not need to be normalized
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00005f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ // Create first box for face camera trail demo with 1 column.
+ boxNode1 = scene_.CreateChild("Box1");
+ StaticModel@ box1 = boxNode1.CreateComponent("StaticModel");
+ box1.model = cache.GetResource("Model", "Models/Box.mdl");
+ box1.castShadows = true;
+ RibbonTrail@ boxTrail1 = boxNode1.CreateComponent("RibbonTrail");
+ boxTrail1.material = cache.GetResource("Material", "Materials/RibbonTrail.xml");
+ boxTrail1.startColor = Color(1.0f, 0.5f, 0.0f, 1.0f);
+ boxTrail1.endColor = Color(1.0f, 1.0f, 0.0f, 0.0f);
+ boxTrail1.width = 0.5f;
+ boxTrail1.updateInvisible = true;
+
+ // Create second box for face camera trail demo with 4 column.
+ // This will produce less distortion than first trail.
+ boxNode2 = scene_.CreateChild("Box2");
+ StaticModel@ box2 = boxNode2.CreateComponent("StaticModel");
+ box2.model = cache.GetResource("Model", "Models/Box.mdl");
+ box2.castShadows = true;
+ RibbonTrail@ boxTrail2 = boxNode2.CreateComponent("RibbonTrail");
+ boxTrail2.material = cache.GetResource("Material", "Materials/RibbonTrail.xml");
+ boxTrail2.startColor = Color(1.0f, 0.5f, 0.0f, 1.0f);
+ boxTrail2.endColor = Color(1.0f, 1.0f, 0.0f, 0.0f);
+ boxTrail2.width = 0.5f;
+ boxTrail2.tailColumn = 4;
+ boxTrail2.updateInvisible = true;
+
+ // Load ninja animated model for bone trail demo.
+ Node@ ninjaNode = scene_.CreateChild("Ninja");
+ ninjaNode.position = Vector3(5.0f, 0.0f, 0.0f);
+ ninjaNode.rotation = Quaternion(0.0f, 180.0f, 0.0f);
+ AnimatedModel@ ninja = ninjaNode.CreateComponent("AnimatedModel");
+ ninja.model = cache.GetResource("Model", "Models/NinjaSnowWar/Ninja.mdl");
+ ninja.material = cache.GetResource("Material", "Materials/NinjaSnowWar/Ninja.xml");
+ ninja.castShadows = true;
+
+ // Create animation controller and play attack animation.
+ ninjaAnimCtrl = ninjaNode.CreateComponent("AnimationController");
+ ninjaAnimCtrl.PlayExclusive("Models/NinjaSnowWar/Ninja_Attack3.ani", 0, true, 0.0f);
+
+ // Add ribbon trail to tip of sword.
+ Node@ swordTip = ninjaNode.GetChild("Joint29", true);
+ swordTrail = swordTip.CreateComponent("RibbonTrail");
+
+ // Set sword trail type to bone and set other parameters.
+ swordTrail.trailType = TT_BONE;
+ swordTrail.material = cache.GetResource("Material", "Materials/SlashTrail.xml");
+ swordTrail.lifetime = 0.22f;
+ swordTrail.startColor = Color(1.0f, 1.0f, 1.0f, 0.75f);
+ swordTrail.endColor = Color(0.2, 0.5f, 1.0f, 0.0f);
+ swordTrail.tailColumn = 4;
+ swordTrail.updateInvisible = true;
+
+ // Add floating text for info.
+ Node@ boxTextNode1 = scene_.CreateChild("BoxText1");
+ boxTextNode1.position = Vector3(-1.0f, 2.0f, 0.0f);
+ Text3D@ boxText1 = boxTextNode1.CreateComponent("Text3D");
+ boxText1.text = "Face Camera Trail (4 Column)";
+ boxText1.SetFont(cache.GetResource("Font", "Fonts/BlueHighway.sdf"), 24);
+
+ Node@ boxTextNode2 = scene_.CreateChild("BoxText2");
+ boxTextNode2.position = Vector3(-6.0f, 2.0f, 0.0f);
+ Text3D@ boxText2 = boxTextNode2.CreateComponent("Text3D");
+ boxText2.text = "Face Camera Trail (1 Column)";
+ boxText2.SetFont(cache.GetResource("Font", "Fonts/BlueHighway.sdf"), 24);
+
+ Node@ ninjaTextNode2 = scene_.CreateChild("NinjaText");
+ ninjaTextNode2.position = Vector3(4.0f, 2.5f, 0.0f);
+ Text3D@ ninjaText = ninjaTextNode2.CreateComponent("Text3D");
+ ninjaText.text = "Bone Trail (4 Column)";
+ ninjaText.SetFont(cache.GetResource("Font", "Fonts/BlueHighway.sdf"), 24);
+
+ // Create the camera.
+ cameraNode = scene_.CreateChild("Camera");
+ cameraNode.CreateComponent("Camera");
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 2.0f, -14.0f);
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys and mouse to move";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void MoveCamera(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Movement speed as world units per second
+ const float MOVE_SPEED = 20.0f;
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraNode.rotation = Quaternion(pitch, yaw, 0.0f);
+
+ // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
+ // Use the Translate() function (default local space) to move relative to the node's orientation.
+ if (input.keyDown[KEY_W])
+ cameraNode.Translate(Vector3::FORWARD * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_S])
+ cameraNode.Translate(Vector3::BACK * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_A])
+ cameraNode.Translate(Vector3::LEFT * MOVE_SPEED * timeStep);
+ if (input.keyDown[KEY_D])
+ cameraNode.Translate(Vector3::RIGHT * MOVE_SPEED * timeStep);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ MoveCamera(timeStep);
+
+ // Sum of timesteps.
+ timeStepSum += timeStep;
+
+ // Move first box with pattern.
+ boxNode1.SetTransform(Vector3(-4.0 + 3.0 * Cos(100.0 * timeStepSum), 0.5, -2.0 * Cos(400.0 * timeStepSum)), Quaternion());
+
+ // Move second box with pattern.
+ boxNode2.SetTransform(Vector3(0.0 + 3.0 * Cos(100.0 * timeStepSum), 0.5, -2.0 * Cos(400.0 * timeStepSum)), Quaternion());
+
+ // Get elapsed attack animation time.
+ float swordAnimTime = ninjaAnimCtrl.GetAnimationState("Models/NinjaSnowWar/Ninja_Attack3.ani").time;
+
+ // Stop emitting trail when sword is finished slashing.
+ if (!swordTrail.emitting && swordAnimTime > swordTrailStartTime && swordAnimTime < swordTrailEndTime)
+ swordTrail.emitting = true;
+ else if (swordTrail.emitting && swordAnimTime >= swordTrailEndTime)
+ swordTrail.emitting = false;
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/45_InverseKinematics.as b/bin/Data/Scripts/45_InverseKinematics.as
new file mode 100644
index 0000000..dc14fb4
--- /dev/null
+++ b/bin/Data/Scripts/45_InverseKinematics.as
@@ -0,0 +1,250 @@
+// Inverse Kinematics
+// This sample demonstrates how to use the IK solver to create "grounders" for a walking character on a slope.
+
+#include "Scripts/Utilities/Sample.as"
+
+AnimationController@ jackAnimCtrl_;
+Node@ cameraRotateNode_;
+Node@ floorNode_;
+Node@ leftFoot_;
+Node@ rightFoot_;
+Node@ jackNode_;
+IKEffector@ leftEffector_;
+IKEffector@ rightEffector_;
+IKSolver@ solver_;
+float floorPitch_ = 0.0f;
+float floorRoll_ = 0.0f;
+bool drawDebug_ = false;
+
+void Start()
+{
+ cache.autoReloadResources = true;
+
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000)
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+ scene_.CreateComponent("PhysicsWorld");
+
+ // Create scene node & StaticModel component for showing a static plane
+ floorNode_ = scene_.CreateChild("Plane");
+ floorNode_.scale = Vector3(50.0f, 1.0f, 50.0f);
+ StaticModel@ planeObject = floorNode_.CreateComponent("StaticModel");
+ planeObject.model = cache.GetResource("Model", "Models/Plane.mdl");
+ planeObject.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
+
+ // Set up collision, we need to raycast to determine foot height
+ floorNode_.CreateComponent("RigidBody");
+ CollisionShape@ col = floorNode_.CreateComponent("CollisionShape");
+ col.SetBox(Vector3(1, 0, 1));
+
+ // Create a directional light to the world.
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.6f, -1.0f, 0.8f); // The direction vector does not need to be normalized
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00005f, 0.5f);
+ // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+
+ // Load Jack animated model
+ jackNode_ = scene_.CreateChild("Jack");
+ jackNode_.rotation = Quaternion(0.0f, 270.0f, 0.0f);
+ AnimatedModel@ jack = jackNode_.CreateComponent("AnimatedModel");
+ jack.model = cache.GetResource("Model", "Models/Jack.mdl");
+ jack.material = cache.GetResource("Material", "Materials/Jack.xml");
+ jack.castShadows = true;
+
+ // Create animation controller and play walk animation
+ jackAnimCtrl_ = jackNode_.CreateComponent("AnimationController");
+ jackAnimCtrl_.PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.0f);
+
+ // We need to attach two inverse kinematic effectors to Jack's feet to
+ // control the grounding.
+ leftFoot_ = jackNode_.GetChild("Bip01_L_Foot", true);
+ rightFoot_ = jackNode_.GetChild("Bip01_R_Foot", true);
+ leftEffector_ = leftFoot_.CreateComponent("IKEffector");
+ rightEffector_ = rightFoot_.CreateComponent("IKEffector");
+ // Control 2 segments up to the hips
+ leftEffector_.chainLength = 2;
+ rightEffector_.chainLength = 2;
+
+ // For the effectors to work, an IKSolver needs to be attached to one of
+ // the parent nodes. Typically, you want to place the solver as close as
+ // possible to the effectors for optimal performance. Since in this case
+ // we're solving the legs only, we can place the solver at the spine.
+ Node@ spine = jackNode_.GetChild("Bip01_Spine", true);
+ solver_ = spine.CreateComponent("IKSolver");
+
+ // Two-bone solver is more efficient and more stable than FABRIK (but only
+ // works for two bones, obviously).
+ solver_.algorithm = IKAlgorithm::TWO_BONE;
+
+ // Disable auto-solving, which means we can call Solve() manually.
+ solver_.AUTO_SOLVE = false;
+
+ // Only enable this so the debug draw shows us the pose before solving.
+ // This should NOT be enabled for any other reason (it does nothing and is
+ // a waste of performance).
+ solver_.UPDATE_ORIGINAL_POSE = true;
+
+ // Create the camera.
+ cameraRotateNode_ = scene_.CreateChild("CameraRotate");
+ cameraNode = cameraRotateNode_.CreateChild("Camera");
+ cameraNode.CreateComponent("Camera");
+
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 0.0f, -4.0f);
+ cameraRotateNode_.position = Vector3(0.0f, 0.4f, 0.0f);
+ pitch = 20.0f;
+ yaw = 50.0f;
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Left-Click and drag to look around\nRight-Click and drag to change incline\nPress space to reset floor\nPress D to draw debug geometry";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void UpdateCameraAndFloor(float timeStep)
+{
+ // Do not move if the UI has a focused element (the console)
+ if (ui.focusElement !is null)
+ return;
+
+ // Mouse sensitivity as degrees per pixel
+ const float MOUSE_SENSITIVITY = 0.1f;
+
+ // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees
+ if (input.mouseButtonDown[MOUSEB_LEFT])
+ {
+ IntVector2 mouseMove = input.mouseMove;
+ yaw += MOUSE_SENSITIVITY * mouseMove.x;
+ pitch += MOUSE_SENSITIVITY * mouseMove.y;
+ pitch = Clamp(pitch, -90.0f, 90.0f);
+ }
+
+ if (input.mouseButtonDown[MOUSEB_RIGHT])
+ {
+ IntVector2 mouseMoveInt = input.mouseMove;
+ Vector2 mouseMove = Matrix2(
+ -Cos(yaw), Sin(yaw),
+ Sin(yaw), Cos(yaw)
+ ) * Vector2(mouseMoveInt.y, -mouseMoveInt.x);
+ floorPitch_ += MOUSE_SENSITIVITY * mouseMove.x;
+ floorPitch_ = Clamp(floorPitch_, -90.0f, 90.0f);
+ floorRoll_ += MOUSE_SENSITIVITY * mouseMove.y;
+ }
+
+ if (input.keyPress[KEY_SPACE])
+ {
+ floorPitch_ = 0.0f;
+ floorRoll_ = 0.0f;
+ }
+
+ if (input.keyPress[KEY_D])
+ {
+ drawDebug_ = !drawDebug_;
+ }
+
+ // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
+ cameraRotateNode_.rotation = Quaternion(pitch, yaw, 0.0f);
+ floorNode_.rotation = Quaternion(floorPitch_, 0.0f, floorRoll_);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+ SubscribeToEvent("SceneDrawableUpdateFinished", "HandleSceneDrawableUpdateFinished");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ // Move the camera, scale movement with time step
+ UpdateCameraAndFloor(timeStep);
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (drawDebug_)
+ solver_.DrawDebugGeometry(false);
+}
+
+void HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData)
+{
+ Vector3 leftFootPosition = leftFoot_.worldPosition;
+ Vector3 rightFootPosition = rightFoot_.worldPosition;
+
+ // Cast ray down to get the normal of the underlying surface
+ PhysicsRaycastResult result = scene_.physicsWorld.RaycastSingle(Ray(leftFootPosition + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2);
+ if (result.body !is null)
+ {
+ // Cast again, but this time along the normal. Set the target position
+ // to the ray intersection
+ result = scene_.physicsWorld.RaycastSingle(Ray(leftFootPosition + result.normal, -result.normal), 2);
+ // The foot node has an offset relative to the root node
+ float footOffset = leftFoot_.worldPosition.y - jackNode_.worldPosition.y;
+ leftEffector_.targetPosition = result.position + result.normal * footOffset;
+ // Rotate foot according to normal
+ leftFoot_.Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD);
+ }
+
+ // Same deal with the right foot
+ result = scene_.physicsWorld.RaycastSingle(Ray(rightFootPosition + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2);
+ if (result.body !is null)
+ {
+ result = scene_.physicsWorld.RaycastSingle(Ray(rightFootPosition + result.normal, -result.normal), 2);
+ float footOffset = rightFoot_.worldPosition.y - jackNode_.worldPosition.y;
+ rightEffector_.targetPosition = result.position + result.normal * footOffset;
+ rightFoot_.Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD);
+ }
+
+ solver_.Solve();
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
diff --git a/bin/Data/Scripts/46_RaycastVehicleDemo.as b/bin/Data/Scripts/46_RaycastVehicleDemo.as
new file mode 100644
index 0000000..c00a229
--- /dev/null
+++ b/bin/Data/Scripts/46_RaycastVehicleDemo.as
@@ -0,0 +1,437 @@
+// Vehicle example.
+// This sample demonstrates:
+// - Creating a heightmap terrain with collision
+// - Constructing a physical vehicle with rigid bodies for the hull and the wheels, joined with constraints
+// - Saving and loading the variables of a script object, including node & component references
+
+#include "Scripts/Utilities/Sample.as"
+
+const int CTRL_FORWARD = 1;
+const int CTRL_BACK = 2;
+const int CTRL_LEFT = 4;
+const int CTRL_RIGHT = 8;
+const int CTRL_BRAKE = 16;
+
+const float CAMERA_DISTANCE = 10.0f;
+const float YAW_SENSITIVITY = 0.1f;
+const float ENGINE_FORCE = 2500.0f;
+const float DOWN_FORCE = 100.0f;
+const float MAX_WHEEL_ANGLE = 22.5f;
+const float CHASSIS_WIDTH = 2.6f;
+
+Node@ vehicleNode;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+ // Create static scene content
+ CreateScene();
+ // Create the controllable vehicle
+ CreateVehicle();
+ // Create the UI content
+ CreateInstructions();
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_RELATIVE);
+ // Subscribe to necessary events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+ // Create scene subsystem components
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("PhysicsWorld");
+ // Create camera and define viewport. Camera does not necessarily have to belong to the scene
+ cameraNode = Node();
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.farClip = 500.0f;
+ renderer.viewports[0] = Viewport(scene_, camera);
+ // Create static scene content. First create a zone for ambient lighting and fog control
+ Node@ zoneNode = scene_.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.ambientColor = Color(0.15f, 0.15f, 0.15f);
+ zone.fogColor = Color(0.5f, 0.5f, 0.7f);
+ zone.fogStart = 300.0f;
+ zone.fogEnd = 500.0f;
+ zone.boundingBox = BoundingBox(-2000.0f, 2000.0f);
+ // Create a directional light to the world. Enable cascaded shadows on it
+ Node@ lightNode = scene_.CreateChild("DirectionalLight");
+ lightNode.direction = Vector3(0.3f, -0.5f, 0.425f);
+ Light@ light = lightNode.CreateComponent("Light");
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.castShadows = true;
+ light.shadowBias = BiasParameters(0.00025f, 0.5f);
+ light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+ light.specularIntensity = 0.5f;
+ // Create heightmap terrain with collision
+ Node@ terrainNode = scene_.CreateChild("Terrain");
+ terrainNode.position = Vector3(0.0f, 0.0f, 0.0f);
+ Terrain@ terrain = terrainNode.CreateComponent("Terrain");
+ terrain.patchSize = 64;
+ terrain.spacing = Vector3(2.0f, 0.1f, 2.0f); // Spacing between vertices and vertical resolution of the height map
+ terrain.smoothing = true;
+ terrain.heightMap = cache.GetResource("Image", "Textures/HeightMap.png");
+ terrain.material = cache.GetResource("Material", "Materials/Terrain.xml");
+ // The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
+ // terrain patches and other objects behind it
+ terrain.occluder = true;
+ RigidBody@ body = terrainNode.CreateComponent("RigidBody");
+ body.collisionLayer = 2; // Use layer bitmask 2 for static geometry
+ CollisionShape@ shape = terrainNode.CreateComponent("CollisionShape");
+ shape.SetTerrain();
+ // Create 1000 mushrooms in the terrain. Always face outward along the terrain normal
+ const uint NUM_MUSHROOMS = 1000;
+ for (uint i = 0; i < NUM_MUSHROOMS; ++i)
+ {
+ Node@ objectNode = scene_.CreateChild("Mushroom");
+ Vector3 position(Random(2000.0f) - 1000.0f, 0.0f, Random(2000.0f) - 1000.0f);
+ position.y = terrain.GetHeight(position) - 0.1f;
+ objectNode.position = position;
+ // Create a rotation quaternion from up vector to terrain normal
+ objectNode.rotation = Quaternion(Vector3(0.0f, 1.0f, 0.0), terrain.GetNormal(position));
+ objectNode.SetScale(3.0f);
+ StaticModel@ object = objectNode.CreateComponent("StaticModel");
+ object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+ object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+ object.castShadows = true;
+ RigidBody@ mushroomBody = objectNode.CreateComponent("RigidBody");
+ mushroomBody.collisionLayer = 2;
+ CollisionShape@ mushroomShape = objectNode.CreateComponent("CollisionShape");
+ mushroomShape.SetTriangleMesh(object.model, 0);
+ }
+}
+
+void CreateVehicle()
+{
+ vehicleNode = scene_.CreateChild("Vehicle");
+ vehicleNode.position = Vector3(0.0f, 5.0f, 0.0f);
+ // First createing player-controlled vehicle
+ // Create the vehicle logic script object
+ Vehicle@ vehicle = cast(vehicleNode.CreateScriptObject(scriptFile, "Vehicle"));
+ // Initialize vehicle component
+ vehicle.Init();
+ vehicleNode.AddTag("vehicle");
+ // Set RigidBody physics parameters
+ // (The RigidBody was created by vehicle.Init()
+ RigidBody@ hullBody = vehicleNode.GetComponent("RigidBody");
+ hullBody.mass = 800.0f;
+ hullBody.linearDamping = 0.2f; // Some air resistance
+ hullBody.angularDamping = 0.5f;
+ hullBody.collisionLayer = 1;
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text =
+ "Use WASD keys to drive, F to brake, mouse/touch to rotate camera\n"
+ "F5 to save scene, F7 to load";
+ instructionText.
+ SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15);
+ // The text has multiple rows. Center them in relation to each other
+ instructionText.textAlignment = HA_CENTER;
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe to Update event for setting the vehicle controls before physics simulation
+ SubscribeToEvent("Update", "HandleUpdate");
+ // Subscribe to PostUpdate event for updating the camera position after physics simulation
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate");
+ // Unsubscribe the SceneUpdate event from base class as the camera node is being controlled in HandlePostUpdate() in this sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (vehicleNode is null)
+ return;
+
+ Vehicle@ vehicle = cast < Vehicle > (vehicleNode.scriptObject);
+ if (vehicle is null)
+ return;
+ // Get movement controls and assign them to the vehicle component. If UI has a focused element, clear controls
+ if (ui.focusElement is null)
+ {
+ vehicle.controls.Set(CTRL_FORWARD, input.keyDown[KEY_W]);
+ vehicle.controls.Set(CTRL_BACK, input.keyDown[KEY_S]);
+ vehicle.controls.Set(CTRL_LEFT, input.keyDown[KEY_A]);
+ vehicle.controls.Set(CTRL_RIGHT, input.keyDown[KEY_D]);
+ vehicle.controls.Set(CTRL_BRAKE, input.keyDown[KEY_F]);
+ // Add yaw & pitch from the mouse motion. Used only for the camera, does not affect motion
+ if (touchEnabled)
+ {
+ for (uint i = 0; i < input.numTouches; ++i)
+ {
+ TouchState@ state = input.touches[i];
+ if (state.touchedElement is null) // Touch on empty space
+ {
+ Camera@ camera = cameraNode.GetComponent("Camera");
+ if (camera is null)
+ return;
+ vehicle.controls.yaw += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.x;
+ vehicle.controls.pitch += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.y;
+ }
+ }
+ }
+ else
+ {
+ vehicle.controls.yaw += input.mouseMoveX * YAW_SENSITIVITY;
+ vehicle.controls.pitch += input.mouseMoveY * YAW_SENSITIVITY;
+ }
+ // Limit pitch
+ vehicle.controls.pitch = Clamp(vehicle.controls.pitch, 0.0f, 80.0f);
+ // Check for loading / saving the scene
+ if (input.keyPress[KEY_F5])
+ {
+ File saveFile(fileSystem.programDir + "Data/Scenes/RaycastScriptVehicleDemo.xml", FILE_WRITE);
+ scene_.SaveXML(saveFile);
+ }
+ if (input.keyPress[KEY_F7])
+ {
+ File loadFile(fileSystem.programDir + "Data/Scenes/RaycastScriptVehicleDemo.xml", FILE_READ);
+ scene_.LoadXML(loadFile);
+ // After loading we have to reacquire the vehicle scene node, as it has been recreated
+ // Simply find by name as there's only one of them
+ Array@ vehicles = scene_.GetChildrenWithTag("vehicle");
+ for (int i = 0; i < vehicles.length; i++)
+ {
+ Vehicle@ vehicleData = cast(vehicles[i].scriptObject);
+ vehicleData.CreateEmitters();
+ }
+ vehicleNode = scene_.GetChild("Vehicle", true);
+ }
+ }
+ else
+ vehicle.controls.Set(CTRL_FORWARD | CTRL_BACK | CTRL_LEFT | CTRL_RIGHT | CTRL_BRAKE, false);
+}
+
+void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (vehicleNode is null)
+ return;
+ Vehicle@ vehicle = cast < Vehicle > (vehicleNode.scriptObject);
+ if (vehicle is null)
+ return;
+ // Physics update has completed. Position camera behind vehicle
+ Quaternion dir(vehicleNode.rotation.yaw, Vector3(0.0f, 1.0f, 0.0f));
+ dir = dir * Quaternion(vehicle.controls.yaw, Vector3(0.0f, 1.0f, 0.0f));
+ dir = dir * Quaternion(vehicle.controls.pitch, Vector3(1.0f, 0.0f, 0.0f));
+ Vector3 cameraTargetPos = vehicleNode.position - dir * Vector3(0.0f, 0.0f, CAMERA_DISTANCE);
+ Vector3 cameraStartPos = vehicleNode.position;
+ // Raycast camera against static objects (physics collision mask 2)
+ // and move it closer to the vehicle if something in between
+ Ray cameraRay(cameraStartPos, (cameraTargetPos - cameraStartPos).Normalized());
+ float cameraRayLength = (cameraTargetPos - cameraStartPos).length;
+ PhysicsRaycastResult result = scene_.physicsWorld.RaycastSingle(cameraRay, cameraRayLength, 2);
+ if (result.body !is null)
+ cameraTargetPos = cameraStartPos + cameraRay.direction * (result.distance - 0.5f);
+ cameraNode.position = cameraTargetPos;
+ cameraNode.rotation = dir;
+}
+
+// Vehicle script object class
+//
+// When saving, the node and component handles are automatically converted into nodeID or componentID attributes
+// and are acquired from the scene when loading. The steering member variable will likewise be saved automatically.
+// The Controls object can not be automatically saved, so handle it manually in the Load() and Save() methods
+
+class Vehicle:ScriptObject
+{
+
+ RigidBody@ hullBody;
+
+ // Current left/right steering amount (-1 to 1.)
+ float steering = 0.0f;
+
+ // Vehicle controls.
+ Controls controls;
+
+ float suspensionRestLength = 0.6f;
+ float suspensionStiffness = 14.0f;
+ float suspensionDamping = 2.0f;
+ float suspensionCompression = 4.0f;
+ float wheelFriction = 1000.0f;
+ float rollInfluence = 0.12f;
+ float maxEngineForce = ENGINE_FORCE;
+
+ float wheelWidth = 0.4f;
+ float wheelRadius = 0.5f;
+ float brakingForce = 50.0f;
+ Array connectionPoints;
+ Array particleEmitterNodeList;
+ protected Vector3 prevVelocity;
+
+ void Load(Deserializer& deserializer)
+ {
+ controls.yaw = deserializer.ReadFloat();
+ controls.pitch = deserializer.ReadFloat();
+ }
+
+ void Save(Serializer& serializer)
+ {
+ serializer.WriteFloat(controls.yaw);
+ serializer.WriteFloat(controls.pitch);
+ }
+
+ void Init()
+ {
+ // This function is called only from the main program when initially creating the vehicle, not on scene load
+ StaticModel@ hullObject = node.CreateComponent("StaticModel");
+ hullBody = node.CreateComponent("RigidBody");
+ CollisionShape@ hullShape = node.CreateComponent("CollisionShape");
+ node.scale = Vector3(2.3f, 1.0f, 4.0f);
+ hullObject.model = cache.GetResource("Model", "Models/Box.mdl");
+ hullObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+ hullObject.castShadows = true;
+ hullShape.SetBox(Vector3(1.0f, 1.0f, 1.0f));
+ hullBody.mass = 800.0f;
+ hullBody.linearDamping = 0.2f; // Some air resistance
+ hullBody.angularDamping = 0.5f;
+ hullBody.collisionLayer = 1;
+ RaycastVehicle@ raycastVehicle = node.CreateComponent("RaycastVehicle");
+ raycastVehicle.Init();
+ connectionPoints.Reserve(4);
+ float connectionHeight = -0.4f; //1.2f;
+ bool isFrontWheel = true;
+ Vector3 wheelDirection(0, -1, 0);
+ Vector3 wheelAxle(-1, 0, 0);
+ float wheelX = CHASSIS_WIDTH / 2.0 - wheelWidth;
+ // Front left
+ connectionPoints.Push(Vector3(-wheelX, connectionHeight, 2.5f - wheelRadius * 2.0f));
+ // Front right
+ connectionPoints.Push(Vector3(wheelX, connectionHeight, 2.5f - wheelRadius * 2.0f));
+ // Back left
+ connectionPoints.Push(Vector3(-wheelX, connectionHeight, -2.5f + wheelRadius * 2.0f));
+ // Back right
+ connectionPoints.Push(Vector3(wheelX, connectionHeight, -2.5f + wheelRadius * 2.0f));
+ const Color LtBrown(0.972f, 0.780f, 0.412f);
+ for (int id = 0; id < connectionPoints.length; id++)
+ {
+ Node@ wheelNode = scene_.CreateChild();
+ Vector3 connectionPoint = connectionPoints[id];
+ // Front wheels are at front (z > 0)
+ // back wheels are at z < 0
+ // Setting rotation according to wheel position
+ bool isFrontWheel = connectionPoints[id].z > 0.0f;
+ wheelNode.rotation = (connectionPoint.x >= 0.0 ? Quaternion(0.0f, 0.0f, -90.0f) : Quaternion(0.0f, 0.0f, 90.0f));
+ wheelNode.worldPosition = (node.worldPosition + node.worldRotation * connectionPoints[id]);
+ wheelNode.scale = Vector3(1.0f, 0.65f, 1.0f);
+ raycastVehicle.AddWheel(wheelNode, wheelDirection, wheelAxle, suspensionRestLength, wheelRadius, isFrontWheel);
+ raycastVehicle.SetWheelSuspensionStiffness(id, suspensionStiffness);
+ raycastVehicle.SetWheelDampingRelaxation(id, suspensionDamping);
+ raycastVehicle.SetWheelDampingCompression(id, suspensionCompression);
+ raycastVehicle.SetWheelFrictionSlip(id, wheelFriction);
+ raycastVehicle.SetWheelRollInfluence(id, rollInfluence);
+ StaticModel@ pWheel = wheelNode.CreateComponent("StaticModel");
+ pWheel.model = cache.GetResource("Model", "Models/Cylinder.mdl");
+ pWheel.material = cache.GetResource("Material", "Materials/Stone.xml");
+ pWheel.castShadows = true;
+ }
+ CreateEmitters();
+ raycastVehicle.ResetWheels();
+ }
+
+ void CreateEmitter(Vector3 place)
+ {
+ Node@ emitter = scene_.CreateChild();
+ emitter.worldPosition = node.worldPosition + node.worldRotation * place + Vector3(0, -wheelRadius, 0);
+ ParticleEmitter@ particleEmitter = emitter.CreateComponent("ParticleEmitter");
+ particleEmitter.effect = cache.GetResource("ParticleEffect", "Particle/Dust.xml");
+ particleEmitter.emitting = false;
+ particleEmitterNodeList.Push(emitter);
+ emitter.temporary = true;
+ }
+
+ void CreateEmitters()
+ {
+ particleEmitterNodeList.Clear();
+ RaycastVehicle@ raycastVehicle = node.GetComponent("RaycastVehicle");
+ for (int id = 0; id < raycastVehicle.numWheels; id++)
+ {
+ Vector3 connectionPoint = raycastVehicle.GetWheelConnectionPoint(id);
+ CreateEmitter(connectionPoint);
+ }
+ }
+
+ void FixedUpdate(float timeStep)
+ {
+ float newSteering = 0.0f;
+ float accelerator = 0.0f;
+ bool brake = false;
+ RaycastVehicle@ raycastVehicle = node.GetComponent("RaycastVehicle");
+ if (controls.IsDown(CTRL_LEFT))
+ newSteering = -1.0f;
+ if (controls.IsDown(CTRL_RIGHT))
+ newSteering = 1.0f;
+ if (controls.IsDown(CTRL_FORWARD))
+ accelerator = 1.0f;
+ if (controls.IsDown(CTRL_BACK))
+ accelerator = -0.5f;
+ if (controls.IsDown(CTRL_BRAKE))
+ brake = true;
+ // When steering, wake up the wheel rigidbodies so that their orientation is updated
+ if (newSteering != 0.0f)
+ {
+ steering = steering * 0.95f + newSteering * 0.05f;
+ }
+ else
+ steering = steering * 0.8f + newSteering * 0.2f;
+ Quaternion steeringRot(0.0f, steering * MAX_WHEEL_ANGLE, 0.0f);
+ raycastVehicle.SetSteeringValue(0, steering);
+ raycastVehicle.SetSteeringValue(1, steering);
+ raycastVehicle.SetEngineForce(2, maxEngineForce * accelerator);
+ raycastVehicle.SetEngineForce(3, maxEngineForce * accelerator);
+ for (int i = 0; i < raycastVehicle.numWheels; i++)
+ {
+ if (brake)
+ {
+ raycastVehicle.SetBrake(i, brakingForce);
+ }
+ else
+ {
+ raycastVehicle.SetBrake(i, 0.0f);
+ }
+ }
+ // Apply downforce proportional to velocity
+ Vector3 localVelocity = hullBody.rotation.Inverse() * hullBody.linearVelocity;
+ hullBody.ApplyForce(hullBody.rotation * Vector3(0.0f, -1.0f, 0.0f) * Abs(localVelocity.z) * DOWN_FORCE);
+ }
+
+ void PostUpdate(float timeStep)
+ {
+ if (particleEmitterNodeList.length == 0)
+ return;
+ RaycastVehicle@ raycastVehicle = node.GetComponent("RaycastVehicle");
+ RigidBody@ vehicleBody = node.GetComponent("RigidBody");
+ Vector3 velocity = hullBody.linearVelocity;
+ Vector3 accel = (velocity - prevVelocity) / timeStep;
+ float planeAccel = Vector3(accel.x, 0.0f, accel.z).length;
+ for (int i = 0; i < raycastVehicle.numWheels; i++)
+ {
+ Node@ emitter = particleEmitterNodeList[i];
+ ParticleEmitter@ particleEmitter = emitter.GetComponent("ParticleEmitter");
+ if (raycastVehicle.WheelIsGrounded(i) && (raycastVehicle.GetWheelSkidInfoCumulative(i) < 0.9f || raycastVehicle.GetBrake(i) > 2.0f ||
+ planeAccel > 15.0f))
+ {
+ emitter.worldPosition = raycastVehicle.GetContactPosition(i);
+ if (!particleEmitter.emitting)
+ particleEmitter.emitting = true;
+ }
+ else if (particleEmitter.emitting)
+ particleEmitter.emitting = false;
+ }
+ prevVelocity = velocity;
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";
+
diff --git a/bin/Data/Scripts/47_Typography.as b/bin/Data/Scripts/47_Typography.as
new file mode 100644
index 0000000..461fabe
--- /dev/null
+++ b/bin/Data/Scripts/47_Typography.as
@@ -0,0 +1,235 @@
+// Text rendering example.
+// Displays text at various sizes, with checkboxes to change the rendering parameters.
+
+#include "Scripts/Utilities/Sample.as"
+
+// Tag used to find all Text elements
+String TEXT_TAG = "Typography_text_tag";
+
+// Top-level container for this sample's UI
+UIElement@ uielement;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Enable OS cursor
+ input.mouseVisible = true;
+
+ // Load XML file containing default UI style sheet
+ XMLFile@ style = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+
+ // Set the loaded style as default style
+ ui.root.defaultStyle = style;
+
+ // Create a UIElement to hold all our content
+ // (Don't modify the root directly, as the base Sample class uses it)
+ uielement = UIElement();
+ uielement.SetAlignment(HA_CENTER, VA_CENTER);
+ uielement.SetLayout(LM_VERTICAL, 10, IntRect(20, 40, 20, 40));
+ ui.root.AddChild(uielement);
+
+ // Add some sample text
+ CreateText();
+
+ // Add a checkbox to toggle the background color.
+ CreateCheckbox("White background", "HandleWhiteBackground")
+ .checked = false;
+
+ // Add a checkbox to toggle SRGB output conversion (if available).
+ // This will give more correct text output for FreeType fonts, as the FreeType rasterizer
+ // outputs linear coverage values rather than SRGB values. However, this feature isn't
+ // available on all platforms.
+ CreateCheckbox("Graphics::SetSRGB", "HandleSRGB")
+ .checked = graphics.sRGB;
+
+ // Add a checkbox for the global ForceAutoHint setting. This affects character spacing.
+ CreateCheckbox("UI::SetForceAutoHint", "HandleForceAutoHint")
+ .checked = ui.forceAutoHint;
+
+ // Add a drop-down menu to control the font hinting level.
+ Array levels = {
+ "FONT_HINT_LEVEL_NONE",
+ "FONT_HINT_LEVEL_LIGHT",
+ "FONT_HINT_LEVEL_NORMAL"
+ };
+ CreateMenu("UI::SetFontHintLevel", levels, "HandleFontHintLevel")
+ .selection = ui.fontHintLevel;
+
+ // Add a drop-down menu to control the subpixel threshold.
+ Array thresholds = {
+ "0",
+ "3",
+ "6",
+ "9",
+ "12",
+ "15",
+ "18",
+ "21"
+ };
+ CreateMenu("UI::SetFontSubpixelThreshold", thresholds, "HandleFontSubpixel")
+ .selection = ui.fontSubpixelThreshold / 3;
+
+ // Add a drop-down menu to control oversampling.
+ Array limits = {
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8"
+ };
+ CreateMenu("UI::SetFontOversampling", limits, "HandleFontOversampling")
+ .selection = ui.fontOversampling - 1;
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+}
+
+void CreateText()
+{
+ UIElement@ container = UIElement();
+ container.SetAlignment(HA_LEFT, VA_TOP);
+ container.SetLayout(LM_VERTICAL);
+ uielement.AddChild(container);
+
+ Font@ font = cache.GetResource("Font", "Fonts/BlueHighway.ttf");
+
+ for (float size = 1; size <= 18; size += 0.5)
+ {
+ Text@ text = Text();
+ text.text = "The quick brown fox jumps over the lazy dog (" + size + "pt)";
+ text.SetFont(font, size);
+ text.AddTag(TEXT_TAG);
+ container.AddChild(text);
+ }
+}
+
+CheckBox@ CreateCheckbox(String label, String handler)
+{
+ UIElement@ container = UIElement();
+ container.SetAlignment(HA_LEFT, VA_TOP);
+ container.SetLayout(LM_HORIZONTAL, 8);
+ uielement.AddChild(container);
+
+ CheckBox@ box = CheckBox();
+ container.AddChild(box);
+ box.SetStyleAuto();
+
+ Text@ text = Text();
+ container.AddChild(text);
+ text.text = label;
+ text.SetStyleAuto();
+ text.AddTag(TEXT_TAG);
+
+ SubscribeToEvent(box, "Toggled", handler);
+ return box;
+}
+
+DropDownList@ CreateMenu(String label, Array items, String handler)
+{
+ UIElement@ container = UIElement();
+ container.SetAlignment(HA_LEFT, VA_TOP);
+ container.SetLayout(LM_HORIZONTAL, 8);
+ uielement.AddChild(container);
+
+ Text@ text = Text();
+ container.AddChild(text);
+ text.text = label;
+ text.SetStyleAuto();
+ text.AddTag(TEXT_TAG);
+
+ DropDownList@ list = DropDownList();
+ container.AddChild(list);
+ list.SetStyleAuto();
+
+ for (int i = 0; i < items.length; ++i)
+ {
+ Text@ t = Text();
+ list.AddItem(t);
+ t.text = items[i];
+ t.SetStyleAuto();
+ t.minWidth = t.rowWidths[0] + 10;
+ t.AddTag(TEXT_TAG);
+ }
+
+ text.maxWidth = text.rowWidths[0];
+
+ SubscribeToEvent(list, "ItemSelected", handler);
+ return list;
+}
+
+void HandleWhiteBackground(StringHash eventType, VariantMap& eventData)
+{
+ CheckBox@ box = eventData["Element"].GetPtr();
+ bool checked = box.checked;
+
+ Color fg = checked ? BLACK : WHITE;
+ Color bg = checked ? WHITE : BLACK;
+
+ renderer.defaultZone.fogColor = bg;
+
+ Array@ elements = uielement.GetChildrenWithTag(TEXT_TAG, true);
+ for (int i = 0; i < elements.length; ++i)
+ {
+ elements[i].color = fg;
+ }
+}
+
+void HandleForceAutoHint(StringHash eventType, VariantMap& eventData)
+{
+ CheckBox@ box = eventData["Element"].GetPtr();
+ bool checked = box.checked;
+
+ ui.forceAutoHint = checked;
+}
+
+void HandleSRGB(StringHash eventType, VariantMap& eventData)
+{
+ CheckBox@ box = eventData["Element"].GetPtr();
+ bool checked = box.checked;
+
+ if (graphics.sRGBWriteSupport)
+ {
+ graphics.sRGB = checked;
+ }
+ else
+ {
+ log.Warning("graphics.sRGBWriteSupport is false");
+ }
+}
+
+void HandleFontHintLevel(StringHash eventType, VariantMap& eventData)
+{
+ DropDownList@ list = eventData["Element"].GetPtr();
+ int i = list.selection;
+
+ ui.fontHintLevel = FontHintLevel(i);
+}
+
+void HandleFontSubpixel(StringHash eventType, VariantMap& eventData)
+{
+ DropDownList@ list = eventData["Element"].GetPtr();
+ int i = list.selection;
+
+ ui.fontSubpixelThreshold = i * 3;
+}
+
+void HandleFontOversampling(StringHash eventType, VariantMap& eventData)
+{
+ DropDownList@ list = eventData["Element"].GetPtr();
+ int i = list.selection;
+
+ ui.fontOversampling = i + 1;
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/48_Hello3DUI.as b/bin/Data/Scripts/48_Hello3DUI.as
new file mode 100644
index 0000000..44eb0ea
--- /dev/null
+++ b/bin/Data/Scripts/48_Hello3DUI.as
@@ -0,0 +1,306 @@
+/// A 3D UI demonstration based on the HelloGUI sample. Renders UI alternatively
+/// either to a 3D scene object using UIComponent, or directly to the backbuffer.
+
+#include "Scripts/Utilities/Sample.as"
+
+Window@ window;
+IntVector2 dragBeginPosition = IntVector2::ZERO;
+UIElement@ textureRoot;
+WeakHandle current;
+bool renderOnCube = false;
+bool drawDebug_ = false;
+bool animateCube = true;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Enable OS cursor
+ input.mouseVisible = true;
+
+ // Load XML file containing default UI style sheet
+ XMLFile@ style = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+
+ // Set the loaded style as default style
+ ui.root.defaultStyle = style;
+
+ // Initialize Scene
+ InitScene();
+
+ // Initialize Window
+ InitWindow();
+
+ // Create and add some controls to the Window
+ InitControls();
+
+ // Create a draggable Fish
+ CreateDraggableFish();
+
+ // Create 3D UI rendered on a cube.
+ Init3DUI();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+}
+
+void InitControls()
+{
+ // Create a CheckBox
+ CheckBox@ checkBox = CheckBox();
+ checkBox.name = "CheckBox";
+
+ // Create a Button
+ Button@ button = Button();
+ button.name = "Button";
+ button.minHeight = 24;
+
+ // Create a LineEdit
+ LineEdit@ lineEdit = LineEdit();
+ lineEdit.name = "LineEdit";
+ lineEdit.minHeight = 24;
+
+ // Add controls to Window
+ window.AddChild(checkBox);
+ window.AddChild(button);
+ window.AddChild(lineEdit);
+
+ // Apply previously set default style
+ checkBox.SetStyleAuto();
+ button.SetStyleAuto();
+ lineEdit.SetStyleAuto();
+
+ Text@ instructions = Text();
+ instructions.SetStyleAuto();
+ instructions.text = "[TAB] - toggle between rendering on screen or cube.\n"
+ "[Space] - toggle cube rotation.";
+ ui.root.AddChild(instructions);
+}
+
+void InitScene()
+{
+ scene_ = Scene();
+
+ scene_.CreateComponent("Octree");
+ Zone@ zone = scene_.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000.0f, 1000.0f);
+ zone.fogColor = GRAY;
+ zone.fogStart = 100.0f;
+ zone.fogEnd = 300.0f;
+
+ // Create a child scene node (at world origin) and a StaticModel component into it.
+ Node@ boxNode = scene_.CreateChild("Box");
+ boxNode.scale = Vector3(5.0f, 5.0f, 5.0f);
+ boxNode.rotation = Quaternion(90, Vector3::LEFT);
+
+ // Create a box model and hide it initially.
+ StaticModel@ boxModel = boxNode.CreateComponent("StaticModel");
+ boxModel.model = cache.GetResource("Model", "Models/Box.mdl");
+ boxNode.enabled = false;
+
+ // Create a camera.
+ cameraNode = scene_.CreateChild("Camera");
+ cameraNode.CreateComponent("Camera");
+
+ // Set an initial position for the camera scene node.
+ cameraNode.position = Vector3(0.0f, 0.0f, -10.0f);
+
+ // Set up a viewport so 3D scene can be visible.
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+
+ // Subscribe to update event and animate cube and handle input.
+ SubscribeToEvent("Update", "HandleUpdate");
+}
+
+void InitWindow()
+{
+ // Create the Window and add it to the UI's root node
+ window = Window();
+ ui.root.AddChild(window);
+
+ // Set Window size and layout settings
+ window.minWidth = 384;
+ window.SetLayout(LM_VERTICAL, 6, IntRect(6, 6, 6, 6));
+ window.SetAlignment(HA_CENTER, VA_CENTER);
+ window.name = "Window";
+
+ // Create Window 'titlebar' container
+ UIElement@ titleBar = UIElement();
+ titleBar.SetMinSize(0, 24);
+ titleBar.verticalAlignment = VA_TOP;
+ titleBar.layoutMode = LM_HORIZONTAL;
+
+ // Create the Window title Text
+ Text@ windowTitle = Text();
+ windowTitle.name = "WindowTitle";
+ windowTitle.text = "Hello GUI!";
+
+ // Create the Window's close button
+ Button@ buttonClose = Button();
+ buttonClose.name = "CloseButton";
+
+ // Add the controls to the title bar
+ titleBar.AddChild(windowTitle);
+ titleBar.AddChild(buttonClose);
+
+ // Add the title bar to the Window
+ window.AddChild(titleBar);
+
+ // Create a list.
+ ListView@ list = window.CreateChild("ListView");
+ list.selectOnClickEnd = true;
+ list.highlightMode = HM_ALWAYS;
+ list.minHeight = 200;
+
+ for (int i = 0; i < 32; i++)
+ {
+ Text@ text = Text();
+ text.SetStyleAuto();
+ text.text = "List item " + i;
+ text.name = "Item " + i;
+ list.AddItem(text);
+ }
+
+ // Apply styles
+ window.SetStyleAuto();
+ list.SetStyleAuto();
+ windowTitle.SetStyleAuto();
+ buttonClose.style = "CloseButton";
+
+ // Subscribe to buttonClose release (following a 'press') events
+ SubscribeToEvent(buttonClose, "Released", "HandleClosePressed");
+
+ // Subscribe also to all UI mouse clicks just to see where we have clicked
+ SubscribeToEvent("UIMouseClick", "HandleControlClicked");
+}
+
+void CreateDraggableFish()
+{
+ // Create a draggable Fish button
+ Button@ draggableFish = ui.root.CreateChild("Button", "Fish");
+ draggableFish.texture = cache.GetResource("Texture2D", "Textures/UrhoDecal.dds"); // Set texture
+ draggableFish.blendMode = BLEND_ADD;
+ draggableFish.SetSize(128, 128);
+ draggableFish.SetPosition((graphics.width - draggableFish.width) / 2, 200);
+
+ // Add a tooltip to Fish button
+ ToolTip@ toolTip = draggableFish.CreateChild("ToolTip");
+ toolTip.position = IntVector2(draggableFish.width + 5, draggableFish.width/2); // slightly offset from fish
+ BorderImage@ textHolder = toolTip.CreateChild("BorderImage");
+ textHolder.SetStyle("ToolTipBorderImage");
+ Text@ toolTipText = textHolder.CreateChild("Text");
+ toolTipText.SetStyle("ToolTipText");
+ toolTipText.text = "Please drag me!";
+
+ // Subscribe draggableFish to Drag Events (in order to make it draggable)
+ // See "Event list" in documentation's Main Page for reference on available Events and their eventData
+ SubscribeToEvent(draggableFish, "DragBegin", "HandleDragBegin");
+ SubscribeToEvent(draggableFish, "DragMove", "HandleDragMove");
+ SubscribeToEvent(draggableFish, "DragEnd", "HandleDragEnd");
+}
+
+void HandleDragBegin(StringHash eventType, VariantMap& eventData)
+{
+ // Get UIElement relative position where input (touch or click) occurred (top-left = IntVector2(0,0))
+ dragBeginPosition = IntVector2(eventData["ElementX"].GetInt(), eventData["ElementY"].GetInt());
+}
+
+void HandleDragMove(StringHash eventType, VariantMap& eventData)
+{
+ IntVector2 dragCurrentPosition = IntVector2(eventData["X"].GetInt(), eventData["Y"].GetInt());
+ // Get the element (fish) that is being dragged. GetPtr() returns a RefCounted handle which can be cast implicitly
+ UIElement@ draggedElement = eventData["Element"].GetPtr();
+ draggedElement.position = dragCurrentPosition - dragBeginPosition;
+}
+
+void HandleDragEnd(StringHash eventType, VariantMap& eventData) // For reference (not used here)
+{
+}
+
+void HandleClosePressed(StringHash eventType, VariantMap& eventData)
+{
+ engine.Exit();
+}
+
+void HandleControlClicked(StringHash eventType, VariantMap& eventData)
+{
+ // Get the Text control acting as the Window's title
+ Text@ windowTitle = window.GetChild("WindowTitle", true);
+
+ // Get control that was clicked
+ UIElement@ clicked = eventData["Element"].GetPtr();
+
+ String name = "...?";
+ if (clicked !is null)
+ {
+ // Get the name of the control that was clicked
+ name = clicked.name;
+ }
+
+ // Update the Window's title text
+ windowTitle.text = "Hello " + name + "!";
+}
+
+void Init3DUI()
+{
+ // Node that will get UI rendered on it.
+ Node@ boxNode = scene_.GetChild("Box");
+ // Create a component that sets up UI rendering. It sets material to StaticModel of the node.
+ UIComponent@ component = boxNode.CreateComponent("UIComponent");
+ // Optionally modify material. Technique is changed so object is visible without any lights.
+ component.material.SetTechnique(0, cache.GetResource("Technique", "Techniques/DiffUnlit.xml"));
+ // Save root element of texture UI for later use.
+ textureRoot = component.root;
+ // Set size of root element. This is size of texture as well.
+ textureRoot.SetSize(512, 512);
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ float timeStep = eventData["TimeStep"].GetFloat();
+ Node@ node = scene_.GetChild("Box");
+
+ if (current.Get() !is null && drawDebug_)
+ ui.DebugDraw(cast(current.Get()));
+
+ if (input.mouseButtonPress[MOUSEB_LEFT])
+ current = ui.GetElementAt(input.mousePosition);
+
+ if (input.keyPress[KEY_TAB])
+ {
+ renderOnCube = !renderOnCube;
+ // Toggle between rendering on screen or to texture.
+ if (renderOnCube)
+ {
+ node.enabled = true;
+ textureRoot.AddChild(window);
+ }
+ else
+ {
+ node.enabled = false;
+ ui.root.AddChild(window);
+ }
+ }
+
+ if (input.keyPress[KEY_SPACE])
+ animateCube = !animateCube;
+
+ if (input.keyPress[KEY_F2])
+ drawDebug_ = !drawDebug_;
+
+ if (animateCube)
+ {
+ node.Yaw(6.0f * timeStep * 1.5f);
+ node.Roll(-6.0f * timeStep * 1.5f);
+ node.Pitch(-6.0f * timeStep * 1.5f);
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/49_Urho2DIsometricDemo.as b/bin/Data/Scripts/49_Urho2DIsometricDemo.as
new file mode 100644
index 0000000..033cd3f
--- /dev/null
+++ b/bin/Data/Scripts/49_Urho2DIsometricDemo.as
@@ -0,0 +1,341 @@
+// Urho2D tile map example.
+// This sample demonstrates:
+// - Creating an isometric 2D scene with tile map
+// - Displaying the scene using the Renderer subsystem
+// - Handling keyboard to move a 2D character and zoom 2D camera
+// - Generating physics shapes from the tmx file's objects
+// - Displaying debug geometry for physics and tile map
+// Note that this sample uses some functions from Sample2D utility class.
+
+#include "Scripts/Utilities/Sample.as"
+#include "Scripts/Utilities/2D/Sample2D.as"
+
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateUIContent("ISOMETRIC 2.5D DEMO");
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+ PhysicsWorld2D@ physicsWorld = scene_.CreateComponent("PhysicsWorld2D");
+ physicsWorld.gravity = Vector2(0.0f, 0.0f); // Neutralize gravity as the character will always be grounded
+
+ // Create camera and define viewport
+ cameraNode = Node();
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.orthographic = true;
+ camera.orthoSize = graphics.height * PIXEL_SIZE;
+ camera.zoom = 2.0f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (2.0) is set for full visibility at 1280x800 resolution)
+ renderer.viewports[0] = Viewport(scene_, camera);
+
+ // Create tile map from tmx file
+ TmxFile2D@ tmxFile = cache.GetResource("TmxFile2D", "Urho2D/Tilesets/atrium.tmx");
+ if (tmxFile is null)
+ return;
+
+ Node@ tileMapNode = scene_.CreateChild("TileMap");
+ TileMap2D@ tileMap = tileMapNode.CreateComponent("TileMap2D");
+ tileMap.tmxFile = tmxFile;
+ TileMapInfo2D@ info = tileMap.info;
+
+ // Create Spriter Imp character (from sample 33_SpriterAnimation)
+ CreateCharacter(info, true, 0.0f, Vector3(-5.0f, 11.0f, 0.0f), 0.15f);
+
+ // Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
+ TileMapLayer2D@ tileMapLayer = tileMap.GetLayer(tileMap.numLayers - 1);
+ CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info);
+
+ // Instantiate enemies and moving platforms at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
+ PopulateMovingEntities(tileMap.GetLayer(tileMap.numLayers - 2));
+
+ // Instantiate coins to pick at each placeholder of "Coins" layer (in this sample, placeholders for coins are Rectangle objects)
+ PopulateCoins(tileMap.GetLayer(tileMap.numLayers - 3));
+
+ // Check when scene is rendered
+ SubscribeToEvent("EndRendering", "HandleSceneRendered");
+}
+
+void HandleSceneRendered()
+{
+ UnsubscribeFromEvent("EndRendering");
+ // Save the scene so we can reload it later
+ SaveScene(true);
+ // Pause the scene as long as the UI is hiding it
+ scene_.updateEnabled = false;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostUpdate() function for processing post update events
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate");
+
+ // Subscribe to PostRenderUpdate to draw physics shapes
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+
+ // Subscribe to Box2D contact listeners
+ SubscribeToEvent("PhysicsBeginContact2D", "HandleCollisionBegin");
+
+ // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Zoom in/out
+ if (cameraNode !is null)
+ Zoom(cameraNode.GetComponent("Camera"));
+
+ // Toggle debug geometry with spacebar
+ if (input.keyPress[KEY_Z]) drawDebug = !drawDebug;
+
+ // Check for loading / saving the scene
+ if (input.keyPress[KEY_F5])
+ {
+ SaveScene(false);
+ }
+ if (input.keyPress[KEY_F7])
+ {
+ ReloadScene(false);
+ }
+}
+
+void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (character2DNode is null || cameraNode is null)
+ return;
+ cameraNode.position = Vector3(character2DNode.position.x, character2DNode.position.y, -10.0f); // Camera tracks character
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (drawDebug)
+ {
+ PhysicsWorld2D@ physicsWorld = scene_.GetComponent("PhysicsWorld2D");
+ physicsWorld.DrawDebugGeometry();
+ Node@ tileMapNode = scene_.GetChild("TileMap", true);
+ TileMap2D@ map = tileMapNode.GetComponent("TileMap2D");
+ map.DrawDebugGeometry(scene_.GetComponent("DebugRenderer"), false);
+ }
+}
+
+void HandleCollisionBegin(StringHash eventType, VariantMap& eventData)
+{
+ // Get colliding node
+ Node@ hitNode = eventData["NodeA"].GetPtr();
+ if (hitNode.name == "Imp")
+ hitNode = eventData["NodeB"].GetPtr();
+ String nodeName = hitNode.name;
+ Character2D@ character = cast(character2DNode.scriptObject);
+
+ // Handle coins picking
+ if (nodeName == "Coin")
+ {
+ hitNode.Remove();
+ character.remainingCoins = character.remainingCoins - 1;
+ if (character.remainingCoins == 0)
+ {
+ Text@ instructions = ui.root.GetChild("Instructions", true);
+ instructions.text = "!!! You got all the coins !!!";
+ }
+ Text@ coinsText = ui.root.GetChild("CoinsText", true);
+ coinsText.text = character.remainingCoins; // Update coins UI counter
+ PlaySound("Powerup.wav");
+ }
+
+ // Handle interactions with enemies
+ if (nodeName == "Orc")
+ {
+ AnimatedSprite2D@ animatedSprite = character2DNode.GetComponent("AnimatedSprite2D");
+ float deltaX = character2DNode.position.x - hitNode.position.x;
+
+ // Orc killed if character is fighting in its direction when the contact occurs
+ if (animatedSprite.animation == "attack" && (deltaX < 0 == animatedSprite.flipX))
+ {
+ cast(hitNode.scriptObject).emitTime = 1;
+ if (hitNode.GetChild("Emitter", true) is null)
+ {
+ hitNode.GetComponent("RigidBody2D").Remove(); // Remove Orc's body
+ SpawnEffect(hitNode);
+ PlaySound("BigExplosion.wav");
+ }
+ }
+ // Player killed if not fighting in the direction of the Orc when the contact occurs
+ else
+ {
+ if (character2DNode.GetChild("Emitter", true) is null)
+ {
+ character.wounded = true;
+ if (nodeName == "Orc")
+ cast(hitNode.scriptObject).fightTimer = 1;
+ SpawnEffect(character2DNode);
+ PlaySound("BigExplosion.wav");
+ }
+ }
+ }
+}
+
+// Character2D script object class
+class Character2D : ScriptObject
+{
+ bool wounded = false;
+ bool killed = false;
+ float timer = 0.0f;
+ int maxCoins = 0;
+ int remainingCoins = 0;
+ int remainingLifes = 3;
+
+ void Update(float timeStep)
+ {
+ if (character2DNode is null)
+ return;
+
+ // Handle wounded/killed states
+ if (killed)
+ return;
+
+ if (wounded)
+ {
+ HandleWoundedState(timeStep);
+ return;
+ }
+
+ AnimatedSprite2D@ animatedSprite = character2DNode.GetComponent("AnimatedSprite2D");
+
+ // Set direction
+ Vector3 moveDir = Vector3(0.0f, 0.0f, 0.0f); // Reset
+ float speedX = Clamp(MOVE_SPEED_X / zoom, 0.4f, 1.0f);
+ float speedY = speedX;
+
+ if (input.keyDown['A'] || input.keyDown[KEY_LEFT])
+ {
+ moveDir = moveDir + Vector3(-1.0f, 0.0f, 0.0f) * speedX;
+ animatedSprite.flipX = false; // Flip sprite (reset to default play on the X axis)
+ }
+ if (input.keyDown['D'] || input.keyDown[KEY_RIGHT])
+ {
+ moveDir = moveDir + Vector3(1.0f, 0.0f, 0.0f) * speedX;
+ animatedSprite.flipX = true; // Flip sprite (flip animation on the X axis)
+ }
+
+ if (!moveDir.Equals(Vector3(0.0f, 0.0f, 0.0f)))
+ speedY = speedX * MOVE_SPEED_SCALE;
+
+ if (input.keyDown['W'] || input.keyDown[KEY_UP])
+ moveDir = moveDir + Vector3(0.0f, 1.0f, 0.0f) * speedY;
+ if (input.keyDown['S'] || input.keyDown[KEY_DOWN])
+ moveDir = moveDir + Vector3(0.0f, -1.0f, 0.0f) * speedY;
+
+ // Move
+ if (!moveDir.Equals(Vector3(0.0f, 0.0f, 0.0f)))
+ character2DNode.Translate(moveDir * timeStep);
+
+ // Animate
+ if (input.keyDown[KEY_SPACE])
+ {
+ if (animatedSprite.animation != "attack")
+ animatedSprite.SetAnimation("attack", LM_FORCE_LOOPED);
+ }
+ else if (!moveDir.Equals(Vector3(0.0f, 0.0f, 0.0f)))
+ {
+ if (animatedSprite.animation != "run")
+ animatedSprite.SetAnimation("run");
+ }
+ else if (animatedSprite.animation != "idle")
+ {
+ animatedSprite.SetAnimation("idle");
+ }
+ }
+
+ void HandleWoundedState(float timeStep)
+ {
+ RigidBody2D@ body = node.GetComponent("RigidBody2D");
+ AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
+
+ // Play "hit" animation in loop
+ if (animatedSprite.animation != "hit")
+ animatedSprite.SetAnimation("hit", LM_FORCE_LOOPED);
+
+ // Update timer
+ timer = timer + timeStep;
+
+ if (timer > 2.0f)
+ {
+ // Reset timer
+ timer = 0.0f;
+
+ // Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
+ body.linearVelocity = Vector2(0.0f, 0.0f);
+ body.awake = false;
+ body.awake = true;
+
+ // Remove particle emitter
+ node.GetChild("Emitter", true).Remove();
+
+ // Update lifes UI and counter
+ remainingLifes = remainingLifes - 1;
+ Text@ lifeText = ui.root.GetChild("LifeText", true);
+ lifeText.text = remainingLifes; // Update lifes UI counter
+
+ // Reset wounded state
+ wounded = false;
+
+ // Handle death
+ if (remainingLifes == 0)
+ {
+ HandleDeath();
+ return;
+ }
+
+ // Re-position the character to the nearest point
+ if (node.position.x < 15.0f)
+ node.position = Vector3(1.0f, 8.0f, 0.0f);
+ else
+ node.position = Vector3(18.8f, 9.2f, 0.0f);
+ }
+ }
+
+ void HandleDeath()
+ {
+ RigidBody2D@ body = node.GetComponent("RigidBody2D");
+ AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
+
+ // Set state to 'killed'
+ killed = true;
+
+ // Update UI elements
+ Text@ instructions = ui.root.GetChild("Instructions", true);
+ instructions.text = "!!! GAME OVER !!!";
+ ui.root.GetChild("ExitButton", true).visible = true;
+ ui.root.GetChild("PlayButton", true).visible = true;
+
+ // Show mouse cursor so that we can click
+ input.mouseVisible = true;
+
+ // Put character outside of the scene and magnify him
+ node.position = Vector3(-20.0f, 0.0f, 0.0f);
+ node.SetScale(1.2f);
+
+ // Play death animation once
+ if (animatedSprite.animation != "dead2")
+ animatedSprite.SetAnimation("dead2");
+ }
+}
\ No newline at end of file
diff --git a/bin/Data/Scripts/50_Urho2DPlatformer.as b/bin/Data/Scripts/50_Urho2DPlatformer.as
new file mode 100644
index 0000000..bf08a06
--- /dev/null
+++ b/bin/Data/Scripts/50_Urho2DPlatformer.as
@@ -0,0 +1,477 @@
+// Urho2D platformer example.
+// This sample demonstrates:
+// - Creating an orthogonal 2D scene from tile map file
+// - Displaying the scene using the Renderer subsystem
+// - Handling keyboard to move a character and zoom 2D camera
+// - Generating physics shapes from the tmx file's objects
+// - Mixing physics and translations to move the character
+// - Using Box2D Contact listeners to handle the gameplay
+// - Displaying debug geometry for physics and tile map
+// Note that this sample uses some functions from Sample2D utility class.
+
+#include "Scripts/Utilities/Sample.as"
+#include "Scripts/Utilities/2D/Sample2D.as"
+
+
+void Start()
+{
+ // Set filename for load/save functions
+ demoFilename = "Platformer2D";
+
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateUIContent("PLATFORMER 2D DEMO");
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree, DebugRenderer and PhysicsWorld2D components to the scene
+ scene_.CreateComponent("Octree");
+ scene_.CreateComponent("DebugRenderer");
+ scene_.CreateComponent("PhysicsWorld2D");
+
+ // Create camera
+ cameraNode = Node();
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.orthographic = true;
+ camera.orthoSize = graphics.height * PIXEL_SIZE;
+ camera.zoom = 1.8f * Min(graphics.width / 1280.0f, graphics.height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.8) is set for full visibility at 1280x800 resolution)
+
+ // Setup the viewport for displaying the scene
+ renderer.viewports[0] = Viewport(scene_, camera);
+ renderer.defaultZone.fogColor = Color(0.2f, 0.2f, 0.2f); // Set background color for the scene
+
+ // Create tile map from tmx file
+ Node@ tileMapNode = scene_.CreateChild("TileMap");
+ TileMap2D@ tileMap = tileMapNode.CreateComponent("TileMap2D");
+ tileMap.tmxFile = cache.GetResource("TmxFile2D", "Urho2D/Tilesets/Ortho.tmx");
+ TileMapInfo2D@ info = tileMap.info;
+
+ // Create Spriter Imp character (from sample 33_SpriterAnimation)
+ CreateCharacter(info, true, 0.8f, Vector3(1.0f, 8.0f, 0.0f), 0.2f);
+
+ // Generate physics collision shapes from the tmx file's objects located in "Physics" (top) layer
+ TileMapLayer2D@ tileMapLayer = tileMap.GetLayer(tileMap.numLayers - 1);
+ CreateCollisionShapesFromTMXObjects(tileMapNode, tileMapLayer, info);
+
+ // Instantiate enemies and moving platforms at each placeholder of "MovingEntities" layer (placeholders are Poly Line objects defining a path from points)
+ PopulateMovingEntities(tileMap.GetLayer(tileMap.numLayers - 2));
+
+ // Instantiate coins to pick at each placeholder of "Coins" layer (in this sample, placeholders for coins are Rectangle objects)
+ PopulateCoins(tileMap.GetLayer(tileMap.numLayers - 3));
+
+ // Instantiate triggers (for ropes, ladders, lava, slopes...) at each placeholder of "Triggers" layer (in this sample, placeholders for triggers are Rectangle objects)
+ TileMapLayer2D@ triggersLayer = tileMap.GetLayer(tileMap.numLayers - 4);
+
+ // Instantiate triggers at each placeholder (Rectangle objects)
+ PopulateTriggers(triggersLayer);
+
+ // Create background
+ CreateBackgroundSprite(info, 3.5, "Textures/HeightMap.png", true);
+
+ // Check when scene is rendered
+ SubscribeToEvent("EndRendering", "HandleSceneRendered");
+}
+
+void HandleSceneRendered()
+{
+ UnsubscribeFromEvent("EndRendering");
+ // Save the scene so we can reload it later
+ SaveScene(true);
+ // Pause the scene as long as the UI is hiding it
+ scene_.updateEnabled = false;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Subscribe HandlePostUpdate() function for processing post update events
+ SubscribeToEvent("PostUpdate", "HandlePostUpdate");
+
+ // Subscribe to PostRenderUpdate to draw debug geometry
+ SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+
+ // Subscribe to Box2D contact listeners
+ SubscribeToEvent("PhysicsBeginContact2D", "HandleCollisionBegin");
+ SubscribeToEvent("PhysicsEndContact2D", "HandleCollisionEnd");
+
+ // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Zoom in/out
+ if (cameraNode !is null)
+ Zoom(cameraNode.GetComponent("Camera"));
+
+ // Toggle debug geometry with 'Z' key
+ if (input.keyPress[KEY_Z]) drawDebug = !drawDebug;
+
+ // Check for loading / saving the scene
+ if (input.keyPress[KEY_F5])
+ {
+ SaveScene(false);
+ }
+ if (input.keyPress[KEY_F7])
+ ReloadScene(false);
+}
+
+void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (character2DNode is null || cameraNode is null)
+ return;
+ cameraNode.position = Vector3(character2DNode.position.x, character2DNode.position.y, -10.0f); // Camera tracks character
+}
+
+void HandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ if (drawDebug)
+ {
+ PhysicsWorld2D@ physicsWorld = scene_.GetComponent("PhysicsWorld2D");
+ physicsWorld.DrawDebugGeometry();
+
+ Node@ tileMapNode = scene_.GetChild("TileMap", true);
+ TileMap2D@ map = tileMapNode.GetComponent("TileMap2D");
+ map.DrawDebugGeometry(scene_.GetComponent("DebugRenderer"), false);
+ }
+}
+
+void HandleCollisionBegin(StringHash eventType, VariantMap& eventData)
+{
+ // Get colliding node
+ Node@ hitNode = eventData["NodeA"].GetPtr();
+ if (hitNode.name == "Imp")
+ hitNode = eventData["NodeB"].GetPtr();
+ String nodeName = hitNode.name;
+ Character2D@ character = cast(character2DNode.scriptObject);
+
+ // Handle ropes and ladders climbing
+ if (nodeName == "Climb")
+ {
+ if (character.isClimbing) // If transition between rope and top of rope (as we are using split triggers)
+ character.climb2 = true;
+ else
+ {
+ character.isClimbing = true;
+ RigidBody2D@ body = character2DNode.GetComponent("RigidBody2D");
+ body.gravityScale = 0.0f; // Override gravity so that the character doesn't fall
+ // Clear forces so that the character stops (should be performed by setting linear velocity to zero, but currently doesn't work)
+ body.linearVelocity = Vector2(0.0f, 0.0f);
+ body.awake = false;
+ body.awake = true;
+ }
+ }
+
+ if (nodeName == "CanJump")
+ character.aboveClimbable = true;
+
+ // Handle coins picking
+ if (nodeName == "Coin")
+ {
+ hitNode.Remove();
+ character.remainingCoins = character.remainingCoins - 1;
+ if (character.remainingCoins == 0)
+ {
+ Text@ instructions = ui.root.GetChild("Instructions", true);
+ instructions.text = "!!! Go to the Exit !!!";
+ }
+ Text@ coinsText = ui.root.GetChild("CoinsText", true);
+ coinsText.text = character.remainingCoins; // Update coins UI counter
+ PlaySound("Powerup.wav");
+ }
+
+ // Handle interactions with enemies
+ if (nodeName == "Enemy" || nodeName == "Orc")
+ {
+ AnimatedSprite2D@ animatedSprite = character2DNode.GetComponent("AnimatedSprite2D");
+ float deltaX = character2DNode.position.x - hitNode.position.x;
+
+ // Orc killed if character is fighting in its direction when the contact occurs (flowers are not destroyable)
+ if (nodeName == "Orc" && animatedSprite.animation == "attack" && (deltaX < 0 == animatedSprite.flipX))
+ {
+ cast(hitNode.scriptObject).emitTime = 1;
+ if (hitNode.GetChild("Emitter", true) is null)
+ {
+ hitNode.GetComponent("RigidBody2D").Remove(); // Remove Orc's body
+ SpawnEffect(hitNode);
+ PlaySound("BigExplosion.wav");
+ }
+ }
+ // Player killed if not fighting in the direction of the Orc when the contact occurs, or when colliding with a flower
+ else
+ {
+ if (character2DNode.GetChild("Emitter", true) is null)
+ {
+ character.wounded = true;
+ if (nodeName == "Orc")
+ cast(hitNode.scriptObject).fightTimer = 1;
+ SpawnEffect(character2DNode);
+ PlaySound("BigExplosion.wav");
+ }
+ }
+ }
+
+ // Handle exiting the level when all coins have been gathered
+ if (nodeName == "Exit" && character.remainingCoins == 0)
+ {
+ // Update UI
+ Text@ instructions = ui.root.GetChild("Instructions", true);
+ instructions.text = "!!! WELL DONE !!!";
+ instructions.position = IntVector2(0, 0);
+
+ // Put the character outside of the scene and magnify him
+ character2DNode.position = Vector3(-20.0f, 0.0f, 0.0f);
+ character2DNode.SetScale(1.2f);
+ }
+
+ // Handle falling into lava
+ if (nodeName == "Lava")
+ {
+ RigidBody2D@ body = character2DNode.GetComponent("RigidBody2D");
+ body.ApplyLinearImpulse(Vector2(0.0f, 1.0f) * MOVE_SPEED, body.massCenter, true); // Violently project character out of lava
+ if (character2DNode.GetChild("Emitter", true) is null)
+ {
+ character.wounded = true;
+ SpawnEffect(character2DNode);
+ PlaySound("BigExplosion.wav");
+ }
+ }
+
+ // Handle climbing a slope
+ if (nodeName == "Slope")
+ character.onSlope = true;
+}
+
+void HandleCollisionEnd(StringHash eventType, VariantMap& eventData)
+{
+ // Get colliding node
+ Node@ hitNode = eventData["NodeA"].GetPtr();
+ if (hitNode.name == "Imp")
+ hitNode = eventData["NodeB"].GetPtr();
+ String nodeName = hitNode.name;
+ Character2D@ character = cast(character2DNode.scriptObject);
+
+ // Handle leaving a rope or ladder
+ if (nodeName == "Climb")
+ {
+ if (character.climb2)
+ character.climb2 = false;
+ else
+ {
+ character.isClimbing = false;
+ RigidBody2D@ body = character2DNode.GetComponent("RigidBody2D");
+ body.gravityScale = 1.0f; // Restore gravity
+ }
+ }
+
+ if (nodeName == "CanJump")
+ character.aboveClimbable = false;
+
+ // Handle leaving a slope
+ if (nodeName == "Slope")
+ {
+ character.onSlope = false;
+ // Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
+ RigidBody2D@ body = character2DNode.GetComponent("RigidBody2D");
+ body.linearVelocity = Vector2(0.0f, 0.0f);
+ body.awake = false;
+ body.awake = true;
+ }
+}
+
+
+// Character2D script object class
+class Character2D : ScriptObject
+{
+ bool wounded = false;
+ bool killed = false;
+ float timer = 0.0f;
+ int maxCoins = 0;
+ int remainingCoins = 0;
+ int remainingLifes = 3;
+ bool isClimbing = false;
+ bool climb2 = false; // Used only for ropes, as they are split into 2 shapes
+ bool aboveClimbable = false;
+ bool onSlope = false;
+
+ void Save(Serializer& serializer)
+ {
+ isClimbing = false; // Overwrite before auto-deserialization
+ }
+
+ void Update(float timeStep)
+ {
+ if (character2DNode is null)
+ return;
+
+ // Handle wounded/killed states
+ if (killed)
+ return;
+
+ if (wounded)
+ {
+ HandleWoundedState(timeStep);
+ return;
+ }
+
+ // Set temporary variables
+ RigidBody2D@ body = node.GetComponent("RigidBody2D");
+ AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
+ bool onGround = false;
+ bool jump = false;
+
+ // Collision detection (AABB query)
+ Vector2 characterHalfSize = Vector2(0.16f, 0.16f);
+ PhysicsWorld2D@ physicsWorld = scene_.GetComponent("PhysicsWorld2D");
+ RigidBody2D@[]@ collidingBodies = physicsWorld.GetRigidBodies(Rect(node.worldPosition2D - characterHalfSize - Vector2(0.0f, 0.1f), node.worldPosition2D + characterHalfSize));
+
+ if (collidingBodies.length > 1 && !isClimbing)
+ onGround = true;
+
+ // Set direction
+ Vector2 moveDir = Vector2(0.0f, 0.0f); // Reset
+
+ if (input.keyDown['A'] || input.keyDown[KEY_LEFT])
+ {
+ moveDir = moveDir + Vector2(-1.0f, 0.0f);
+ animatedSprite.flipX = false; // Flip sprite (reset to default play on the X axis);
+ }
+
+ if (input.keyDown['D'] || input.keyDown[KEY_RIGHT])
+ {
+ moveDir = moveDir + Vector2(1.0f, 0.0f);
+ animatedSprite.flipX = true; // Flip sprite (flip animation on the X axis)
+ }
+
+ // Jump
+ if ((onGround || aboveClimbable) && (input.keyPress['W'] || input.keyPress[KEY_UP]))
+ jump = true;
+
+ // Climb
+ if (isClimbing)
+ {
+ if (!aboveClimbable && (input.keyDown[KEY_UP] || input.keyDown[KEY_W]))
+ moveDir = moveDir + Vector2(0.0f, 1.0f);
+
+ if (input.keyDown[KEY_DOWN] || input.keyDown[KEY_S])
+ moveDir = moveDir + Vector2(0.0f, -1.0f);
+ }
+
+ // Move
+ if (!moveDir.Equals(Vector2(0.0f, 0.0f)) || jump)
+ {
+ if (onSlope)
+ body.ApplyForceToCenter(moveDir * MOVE_SPEED / 2, true); // When climbing a slope, apply force (todo: replace by setting linear velocity to zero when will work)
+ else
+ node.Translate(Vector3(moveDir.x, moveDir.y, 0) * timeStep * 1.8f);
+ if (jump)
+ body.ApplyLinearImpulse(Vector2(0.0f, 0.17f) * MOVE_SPEED, body.massCenter, true);
+ }
+
+ // Animate
+ if (input.keyDown[KEY_SPACE])
+ {
+ if (animatedSprite.animation != "attack")
+ {
+ animatedSprite.SetAnimation("attack", LM_FORCE_LOOPED);
+ animatedSprite.speed = 1.5f;
+ }
+ }
+ else if (!moveDir.Equals(Vector2(0.0f, 0.0f)))
+ {
+ if (animatedSprite.animation != "run")
+ animatedSprite.SetAnimation("run");
+ }
+ else if (animatedSprite.animation != "idle")
+ {
+ animatedSprite.SetAnimation("idle");
+ }
+ }
+
+ void HandleWoundedState(float timeStep)
+ {
+ RigidBody2D@ body = node.GetComponent("RigidBody2D");
+ AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
+
+ // Play "hit" animation in loop
+ if (animatedSprite.animation != "hit")
+ animatedSprite.SetAnimation("hit", LM_FORCE_LOOPED);
+
+ // Update timer
+ timer = timer + timeStep;
+
+ if (timer > 2.0f)
+ {
+ // Reset timer
+ timer = 0.0f;
+
+ // Clear forces (should be performed by setting linear velocity to zero, but currently doesn't work)
+ body.linearVelocity = Vector2(0.0f, 0.0f);
+ body.awake = false;
+ body.awake = true;
+
+ // Remove particle emitter
+ node.GetChild("Emitter", true).Remove();
+
+ // Update lifes UI and counter
+ remainingLifes = remainingLifes - 1;
+ Text@ lifeText = ui.root.GetChild("LifeText", true);
+ lifeText.text = remainingLifes; // Update lifes UI counter
+
+ // Reset wounded state
+ wounded = false;
+
+ // Handle death
+ if (remainingLifes == 0)
+ {
+ HandleDeath();
+ return;
+ }
+
+ // Re-position the character to the nearest point
+ if (node.position.x < 15.0f)
+ node.position = Vector3(1.0f, 8.0f, 0.0f);
+ else
+ node.position = Vector3(18.8f, 9.2f, 0.0f);
+ }
+ }
+
+ void HandleDeath()
+ {
+ RigidBody2D@ body = node.GetComponent("RigidBody2D");
+ AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D");
+
+ // Set state to 'killed'
+ killed = true;
+
+ // Update UI elements
+ Text@ instructions = ui.root.GetChild("Instructions", true);
+ instructions.text = "!!! GAME OVER !!!";
+ ui.root.GetChild("ExitButton", true).visible = true;
+ ui.root.GetChild("PlayButton", true).visible = true;
+
+ // Show mouse cursor so that we can click
+ input.mouseVisible = true;
+
+ // Put character outside of the scene and magnify him
+ node.position = Vector3(-20.0f, 0.0f, 0.0f);
+ node.SetScale(1.2f);
+
+ // Play death animation once
+ if (animatedSprite.animation != "dead2")
+ animatedSprite.SetAnimation("dead2");
+ }
+}
\ No newline at end of file
diff --git a/bin/Data/Scripts/51_Urho2DStretchableSprite.as b/bin/Data/Scripts/51_Urho2DStretchableSprite.as
new file mode 100644
index 0000000..de1bfd9
--- /dev/null
+++ b/bin/Data/Scripts/51_Urho2DStretchableSprite.as
@@ -0,0 +1,229 @@
+// Urho2D stretchable sprite example.
+// This sample demonstrates:
+// - Creating a 2D scene with both static and stretchable sprites
+// - Difference in scaling static and stretchable sprites
+// - Similarity in otherwise transforming static and stretchable sprites
+// - Displaying the scene using the Renderer subsystem
+// - Handling keyboard to transform nodes
+
+#include "Scripts/Utilities/Sample.as"
+
+Node@ refSpriteNode = null;
+Node@ stretchSpriteNode = null;
+int selectTransform = 0;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create the scene content
+ CreateScene();
+
+ // Create the UI content
+ CreateInstructions();
+
+ // Setup the viewport for displaying the scene
+ SetupViewport();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Hook up to the frame update events
+ SubscribeToEvents();
+}
+
+void CreateScene()
+{
+ scene_ = Scene();
+
+ // Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
+ // show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates; it
+ // is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
+ // optimizing manner
+ scene_.CreateComponent("Octree");
+
+ // Create a scene node for the camera, which we will move around
+ // The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
+ cameraNode = scene_.CreateChild("Camera");
+ // Set an initial position for the camera scene node above the plane
+ cameraNode.position = Vector3(0.0f, 0.0f, -10.0f);
+
+ Camera@ camera = cameraNode.CreateComponent("Camera");
+ camera.orthographic = true;
+ camera.orthoSize = graphics.height * PIXEL_SIZE;
+
+ @refSpriteNode = scene_.CreateChild("regular sprite");
+ @stretchSpriteNode = scene_.CreateChild("stretchable sprite");
+
+ Sprite2D@ sprite = cache.GetResource("Sprite2D", "Urho2D/Stretchable.png");
+ if (sprite !is null)
+ {
+ StaticSprite2D@ staticSprite = refSpriteNode.CreateComponent("StaticSprite2D");
+ staticSprite.sprite = sprite;
+
+ StretchableSprite2D@ stretchSprite = stretchSpriteNode.CreateComponent("StretchableSprite2D");
+ stretchSprite.sprite = sprite;
+ stretchSprite.border = IntRect(25, 25, 25, 25);
+
+ refSpriteNode.Translate2D(Vector2(-2.0f, 0.0f));
+ stretchSpriteNode.Translate2D(Vector2(2.0f, 0.0f));
+ }
+}
+
+void CreateInstructions()
+{
+ // Construct new Text object, set string to display and font to use
+ Text@ instructionText = ui.root.CreateChild("Text");
+ instructionText.text = "Use WASD keys to transform, Tab key to cycle through\n"
+ "Scale, Rotate, and Translate transform modes. In Rotate\n"
+ "mode, combine A/D keys with Ctrl key to rotate about\n"
+ "the Z axis";
+ instructionText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 12);
+
+ // Position the text relative to the screen center
+ instructionText.horizontalAlignment = HA_CENTER;
+ instructionText.verticalAlignment = VA_CENTER;
+ instructionText.SetPosition(0, ui.root.height / 4);
+}
+
+void SetupViewport()
+{
+ // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
+ // at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
+ // use, but now we just use full screen and default render path configured in the engine command line options
+ Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+ renderer.viewports[0] = viewport;
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe to Key Up event, to handle individual key presses
+ SubscribeToEvent("KeyUp", "OnKeyUp");
+
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("Update", "HandleUpdate");
+
+ // Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
+ UnsubscribeFromEvent("SceneUpdate");
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // Take the frame time step, which is stored as a float
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ switch (selectTransform)
+ {
+ case 0:
+ ScaleSprites(timeStep);
+ break;
+ case 1:
+ RotateSprites(timeStep);
+ break;
+ case 2:
+ TranslateSprites(timeStep);
+ break;
+ default:
+ log.Error("Bad transform selection: " + selectTransform);
+ }
+}
+
+void OnKeyUp(StringHash eventType, VariantMap& eventData)
+{
+ int key = eventData["Key"].GetInt();
+
+ if(key == KEY_TAB)
+ {
+ ++selectTransform;
+ selectTransform %= 3;
+ }
+ else if(key == KEY_ESCAPE)
+ engine.Exit();
+}
+
+void TranslateSprites(float timeStep)
+{
+ const float speed = 1.0f;
+ const bool left = input.keyDown[KEY_A],
+ right = input.keyDown[KEY_D],
+ up = input.keyDown[KEY_W],
+ down = input.keyDown[KEY_S];
+
+ if (left || right || up || down)
+ {
+ const float quantum = timeStep * speed;
+ const Vector2 translate = Vector2(
+ (left ? -quantum : 0.0f) + (right ? quantum : 0.0f),
+ (down ? -quantum : 0.0f) + (up ? quantum : 0.0f));
+
+ refSpriteNode.Translate2D(translate);
+ stretchSpriteNode.Translate2D(translate);
+ }
+}
+
+void RotateSprites(float timeStep)
+{
+ const float speed = 45.0f;
+
+ const bool left = input.keyDown[KEY_A],
+ right = input.keyDown[KEY_D],
+ up = input.keyDown[KEY_W],
+ down = input.keyDown[KEY_S],
+ ctrl = input.keyDown[KEY_CTRL];
+
+ if (left || right || up || down)
+ {
+ const float quantum = timeStep * speed;
+
+ const float xrot = (up ? -quantum : 0.0f) + (down ? quantum: 0.0f);
+ const float rot2 = (left ? -quantum: 0.0f) + (right ? quantum : 0.0f);
+ const Quaternion totalRot = Quaternion(xrot,
+ ctrl ? 0.0f : rot2,
+ ctrl ? rot2 : 0.0f);
+
+ refSpriteNode.Rotate(totalRot);
+ stretchSpriteNode.Rotate(totalRot);
+ }
+}
+
+void ScaleSprites(float timeStep)
+{
+ const float speed = 0.5f;
+
+ const bool left = input.keyDown[KEY_A],
+ right = input.keyDown[KEY_D],
+ up = input.keyDown[KEY_W],
+ down = input.keyDown[KEY_S];
+ if (left || right || up || down)
+ {
+ const float quantum = timeStep * speed;
+ const Vector2 scale = Vector2(
+ 1.0f + (right ? quantum : left ? -quantum : 0.0f),
+ 1.0f + (up ? quantum : down ? -quantum : 0.0f));
+
+ refSpriteNode.Scale2D(scale);
+ stretchSpriteNode.Scale2D(scale);
+ }
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ ""
+ " "
+ " TAB"
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ " CTRL"
+ " "
+ " "
+ " "
+ " "
+ " "
+ " "
+ "";
diff --git a/bin/Data/Scripts/52_NATPunchtrough.as b/bin/Data/Scripts/52_NATPunchtrough.as
new file mode 100644
index 0000000..00445bc
--- /dev/null
+++ b/bin/Data/Scripts/52_NATPunchtrough.as
@@ -0,0 +1,231 @@
+// This first example, maintaining tradition, prints a "Hello World" message.
+// Furthermore it shows:
+// - Using the Sample utility functions as a base for the application
+// - Adding a Text element to the graphical user interface
+// - Subscribing to and handling of update events
+
+#include "Scripts/Utilities/Sample.as"
+
+LineEdit@ natServerAddress;
+LineEdit@ natServerPort;
+Button@ saveNatSettingsButton;
+
+Button@ startServerButton;
+
+LineEdit@ serverGuid;
+Button@ connectButton;
+
+Text@ logHistoryText;
+Array logHistory;
+
+LineEdit@ guid;
+
+const int SERVER_PORT = 54654;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create "Hello World" Text
+ CreateText();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Finally, hook-up this HelloWorld instance to handle update events
+ SubscribeToEvents();
+}
+
+void CreateText()
+{
+ XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+ // Set style to the UI root so that elements will inherit it
+ ui.root.defaultStyle = uiStyle;
+
+ // Create log element to view latest logs from the system
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+ logHistoryText = ui.root.CreateChild("Text");
+ logHistoryText.SetFont(font, 12);
+ logHistoryText.SetPosition(20, 200);
+
+ logHistory.Resize(20);
+
+ // Create NAT server config fields
+ int marginTop = 40;
+ CreateLabel("1. Run NAT server somewhere, enter NAT server info and press 'Save NAT settings'", IntVector2(20, marginTop-20));
+ natServerAddress = CreateLineEdit("127.0.0.1", 200, IntVector2(20, marginTop));
+ natServerPort = CreateLineEdit("61111", 100, IntVector2(240, marginTop));
+ saveNatSettingsButton = CreateButton("Save NAT settings", 160, IntVector2(360, marginTop));
+
+ // Create server start button
+ marginTop = 120;
+ CreateLabel("2. Create server and give others your server GUID", IntVector2(20, marginTop-20));
+ guid = CreateLineEdit("Your server GUID", 200, IntVector2(20, marginTop));
+ startServerButton = CreateButton("Start server", 160, IntVector2(240, marginTop));
+
+ // Create client connection related fields
+ marginTop = 200;
+ CreateLabel("3. Input local or remote server GUID", IntVector2(20, marginTop-20));
+ serverGuid = CreateLineEdit("Remote server GUID", 200, IntVector2(20, marginTop));
+ connectButton = CreateButton("Connect", 160, IntVector2(240, marginTop));
+}
+
+void SubscribeToEvents()
+{
+ // Subscribe HandleUpdate() function for processing update events
+ SubscribeToEvent("ServerConnected", "HandleServerConnected");
+ SubscribeToEvent("ServerDisconnected", "HandleServerDisconnected");
+ SubscribeToEvent("ConnectFailed", "HandleConnectFailed");
+
+ // NAT server connection related events
+ SubscribeToEvent("NetworkNatMasterConnectionFailed", "HandleNatConnectionFailed");
+ SubscribeToEvent("NetworkNatMasterConnectionSucceeded", "HandleNatConnectionSucceeded");
+
+ // NAT punchtrough request events
+ SubscribeToEvent("NetworkNatPunchtroughSucceeded", "HandleNatPunchtroughSucceeded");
+ SubscribeToEvent("NetworkNatPunchtroughFailed", "HandleNatPunchtroughFailed");
+
+ SubscribeToEvent("ClientConnected", "HandleClientConnected");
+ SubscribeToEvent("ClientDisconnected", "HandleClientDisconnected");
+
+ SubscribeToEvent(saveNatSettingsButton, "Released", "HandleSaveNatSettings");
+ SubscribeToEvent(startServerButton, "Released", "HandleStartServer");
+ SubscribeToEvent(connectButton, "Released", "HandleConnect");
+}
+
+void CreateLabel(const String&in text, IntVector2 pos)
+{
+ // Create log element to view latest logs from the system
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+ Text@ label = ui.root.CreateChild("Text");
+ label.SetFont(font, 12);
+ label.color = Color(0.0f, 1.0f, 0.0f);
+ label.SetPosition(pos.x, pos.y);
+ label.text = text;
+}
+
+void ShowLogMessage(const String& row)
+{
+ logHistory.Erase(0);
+ logHistory.Push(row);
+
+ // Concatenate all the rows in history
+ String allRows;
+ for (uint i = 0; i < logHistory.length; ++i)
+ allRows += logHistory[i] + "\n";
+
+ logHistoryText.text = allRows;
+}
+
+LineEdit@ CreateLineEdit(const String&in placeholder, int width, IntVector2 pos)
+{
+ LineEdit@ textEdit = ui.root.CreateChild("LineEdit");
+ textEdit.SetStyleAuto();
+ textEdit.SetFixedWidth(width);
+ textEdit.SetFixedHeight(30);
+ textEdit.text = placeholder;
+ textEdit.SetPosition(pos.x, pos.y);
+ return textEdit;
+}
+
+Button@ CreateButton(const String&in text, int width, IntVector2 pos)
+{
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+
+ Button@ button = ui.root.CreateChild("Button");
+ button.SetStyleAuto();
+ button.SetFixedWidth(width);
+ button.SetFixedHeight(30);
+ button.SetPosition(pos.x, pos.y);
+
+ Text@ buttonText = button.CreateChild("Text");
+ buttonText.SetFont(font, 12);
+ buttonText.SetAlignment(HA_CENTER, VA_CENTER);
+ buttonText.text = text;
+
+ return button;
+}
+
+void HandleSaveNatSettings(StringHash eventType, VariantMap& eventData)
+{
+ // Save NAT server configuration
+ network.SetNATServerInfo(natServerAddress.text, natServerPort.text.ToInt());
+ ShowLogMessage("Saving NAT settings: " + natServerAddress.text + ":" + natServerPort.text);
+}
+
+void HandleServerConnected(StringHash eventType, VariantMap& eventData)
+{
+ ShowLogMessage("Client: Server connected!");
+}
+
+void HandleServerDisconnected(StringHash eventType, VariantMap& eventData)
+{
+ ShowLogMessage("Client: Server disconnected!");
+}
+
+void HandleConnectFailed(StringHash eventType, VariantMap& eventData)
+{
+ ShowLogMessage("Client: Connection failed!");
+}
+
+void HandleStartServer(StringHash eventType, VariantMap& eventData)
+{
+ network.StartServer(SERVER_PORT);
+ ShowLogMessage("Server: Server started on port: " + String(SERVER_PORT));
+
+ // Connect to the NAT server
+ network.StartNATClient();
+ ShowLogMessage("Server: Starting NAT client for server...");
+
+ // Output our assigned GUID which others will use to connect to our server
+ guid.text = network.guid;
+}
+
+void HandleConnect(StringHash eventType, VariantMap& eventData)
+{
+ VariantMap userData;
+ userData["Name"] = "Urho3D";
+
+ // Attempt connecting to server using custom GUID, Scene = null as a second parameter and user identity is passed as third parameter
+ network.AttemptNATPunchtrough(serverGuid.text, null, userData);
+ ShowLogMessage("Client: Attempting NAT punchtrough to guid: " + serverGuid.text);
+}
+
+void HandleNatConnectionFailed(StringHash eventType, VariantMap& eventData)
+{
+ ShowLogMessage("Connection to NAT master server failed!");
+}
+
+void HandleNatConnectionSucceeded(StringHash eventType, VariantMap& eventData)
+{
+ ShowLogMessage("Connection to NAT master server succeeded!");
+}
+
+void HandleNatPunchtroughSucceeded(StringHash eventType, VariantMap& eventData)
+{
+ ShowLogMessage("NAT punchtrough succeeded!");
+}
+
+void HandleNatPunchtroughFailed(StringHash eventType, VariantMap& eventData)
+{
+ ShowLogMessage("NAT punchtrough failed!");
+}
+
+void HandleClientConnected(StringHash eventType, VariantMap& eventData)
+{
+ ShowLogMessage("Server: Client connected!");
+}
+
+void HandleClientDisconnected(StringHash eventType, VariantMap& eventData)
+{
+ ShowLogMessage("Server: Client disconnected!");
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/53_LANDiscovery.as b/bin/Data/Scripts/53_LANDiscovery.as
new file mode 100644
index 0000000..b63a1c5
--- /dev/null
+++ b/bin/Data/Scripts/53_LANDiscovery.as
@@ -0,0 +1,134 @@
+// This first example, maintaining tradition, prints a "Hello World" message.
+// Furthermore it shows:
+// - Using the Sample utility functions as a base for the application
+// - Adding a Text element to the graphical user interface
+// - Subscribing to and handling of update events
+
+#include "Scripts/Utilities/Sample.as"
+
+Button@ startServer;
+Button@ stopServer;
+Button@ refreshServerList;
+Text@ serverList;
+
+const int SERVER_PORT = 54654;
+
+void Start()
+{
+ // Execute the common startup for samples
+ SampleStart();
+
+ // Create "Hello World" Text
+ CreateText();
+
+ // Set the mouse mode to use in the sample
+ SampleInitMouseMode(MM_FREE);
+
+ // Finally, hook-up this HelloWorld instance to handle update events
+ SubscribeToEvents();
+}
+
+void CreateText()
+{
+ XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+ // Set style to the UI root so that elements will inherit it
+ ui.root.defaultStyle = uiStyle;
+
+ int marginTop = 20;
+ CreateLabel("1. Start server", IntVector2(20, marginTop-20));
+ startServer = CreateButton("Start server", 160, IntVector2(20, marginTop));
+ stopServer = CreateButton("Stop server", 160, IntVector2(20, marginTop));
+ stopServer.visible = false;
+
+ // Create client connection related fields
+ marginTop += 80;
+ CreateLabel("2. Discover LAN servers", IntVector2(20, marginTop-20));
+ refreshServerList = CreateButton("Search...", 160, IntVector2(20, marginTop));
+
+ marginTop += 80;
+ CreateLabel("Local servers:", IntVector2(20, marginTop - 20));
+ serverList = CreateLabel("", IntVector2(20, marginTop));
+}
+
+void SubscribeToEvents()
+{
+ SubscribeToEvent("NetworkHostDiscovered", "HandleNetworkHostDiscovered");
+
+ SubscribeToEvent(startServer, "Released", "HandleStartServer");
+ SubscribeToEvent(stopServer, "Released", "HandleStopServer");
+ SubscribeToEvent(refreshServerList, "Released", "HandleDoNetworkDiscovery");
+}
+
+Text@ CreateLabel(const String&in text, IntVector2 pos)
+{
+ // Create log element to view latest logs from the system
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+ Text@ label = ui.root.CreateChild("Text");
+ label.SetFont(font, 12);
+ label.color = Color(0.0f, 1.0f, 0.0f);
+ label.SetPosition(pos.x, pos.y);
+ label.text = text;
+ return label;
+}
+
+Button@ CreateButton(const String&in text, int width, IntVector2 pos)
+{
+ Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf");
+
+ Button@ button = ui.root.CreateChild("Button");
+ button.SetStyleAuto();
+ button.SetFixedWidth(width);
+ button.SetFixedHeight(30);
+ button.SetPosition(pos.x, pos.y);
+
+ Text@ buttonText = button.CreateChild("Text");
+ buttonText.SetFont(font, 12);
+ buttonText.SetAlignment(HA_CENTER, VA_CENTER);
+ buttonText.text = text;
+
+ return button;
+}
+
+void HandleNetworkHostDiscovered(StringHash eventType, VariantMap& eventData)
+{
+ log.Info("Server discovered!");
+ String text = serverList.text;
+ VariantMap data = eventData["Beacon"].GetVariantMap();
+ text += "\n" + data["Name"].GetString() + "(" + String(data["Players"].GetInt()) + ")" + eventData["Address"].GetString() + ":" + String(eventData["Port"].GetInt());
+ serverList.text = text;
+}
+
+void HandleStartServer(StringHash eventType, VariantMap& eventData)
+{
+ if (network.StartServer(SERVER_PORT)) {
+ VariantMap data;
+ data["Name"] = "Test server";
+ data["Players"] = 100;
+ /// Set data which will be sent to all who requests LAN network discovery
+ network.SetDiscoveryBeacon(data);
+ startServer.visible = false;
+ stopServer.visible = true;
+ }
+}
+
+void HandleStopServer(StringHash eventType, VariantMap& eventData)
+{
+ network.StopServer();
+ startServer.visible = true;
+ stopServer.visible = false;
+}
+
+void HandleDoNetworkDiscovery(StringHash eventType, VariantMap& eventData)
+{
+ /// Pass in the port that should be checked
+ network.DiscoverHosts(SERVER_PORT);
+ serverList.text = "";
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions =
+ "" +
+ " " +
+ " " +
+ " " +
+ "";
diff --git a/bin/Data/Scripts/Editor.as b/bin/Data/Scripts/Editor.as
new file mode 100644
index 0000000..d811267
--- /dev/null
+++ b/bin/Data/Scripts/Editor.as
@@ -0,0 +1,453 @@
+// Urho3D editor
+
+#include "Scripts/Editor/EditorHierarchyWindow.as"
+#include "Scripts/Editor/EditorView.as"
+#include "Scripts/Editor/EditorScene.as"
+#include "Scripts/Editor/EditorActions.as"
+#include "Scripts/Editor/EditorUIElement.as"
+#include "Scripts/Editor/EditorGizmo.as"
+#include "Scripts/Editor/EditorMaterial.as"
+#include "Scripts/Editor/EditorParticleEffect.as"
+#include "Scripts/Editor/EditorSettings.as"
+#include "Scripts/Editor/EditorPreferences.as"
+#include "Scripts/Editor/EditorToolBar.as"
+#include "Scripts/Editor/EditorSecondaryToolbar.as"
+#include "Scripts/Editor/EditorUI.as"
+#include "Scripts/Editor/EditorImport.as"
+#include "Scripts/Editor/EditorExport.as"
+#include "Scripts/Editor/EditorResourceBrowser.as"
+#include "Scripts/Editor/EditorSpawn.as"
+#include "Scripts/Editor/EditorSoundType.as"
+#include "Scripts/Editor/EditorTerrain.as"
+#include "Scripts/Editor/EditorLayers.as"
+#include "Scripts/Editor/EditorColorWheel.as"
+#include "Scripts/Editor/EditorEventsHandlers.as"
+#include "Scripts/Editor/EditorViewDebugIcons.as"
+#include "Scripts/Editor/EditorViewSelectableOrigins.as"
+#include "Scripts/Editor/EditorViewPaintSelection.as"
+
+String configFileName;
+
+void Start()
+{
+ // Assign the value ASAP because configFileName is needed on exit, including exit on error
+ configFileName = fileSystem.GetAppPreferencesDir("urho3d", "Editor") + "Config.xml";
+ localization.LoadJSONFile("EditorStrings.json");
+
+ if (engine.headless)
+ {
+ ErrorDialog("Urho3D Editor", "Headless mode is not supported. The program will now exit.");
+ engine.Exit();
+ return;
+ }
+
+ // Use the first frame to setup when the resolution is initialized
+ SubscribeToEvent("Update", "FirstFrame");
+
+ SubscribeToEvent(input, "ExitRequested", "HandleExitRequested");
+
+ // Disable Editor auto exit, check first if it is OK to exit
+ engine.autoExit = false;
+ // Pause completely when minimized to save OS resources, reduce defocused framerate
+ engine.pauseMinimized = true;
+ engine.maxInactiveFps = 10;
+ // Enable console commands from the editor script
+ script.defaultScriptFile = scriptFile;
+ // Enable automatic resource reloading
+ cache.autoReloadResources = true;
+ // Return resources which exist but failed to load due to error, so that we will not lose resource refs
+ cache.returnFailedResources = true;
+ // Use OS mouse without grabbing it
+ input.mouseVisible = true;
+ // If input is scaled the double the UI size (High DPI display)
+ if (input.inputScale != Vector2::ONE)
+ {
+ // Should we use the inputScale itself to scale UI?
+ ui.scale = 2;
+ // When UI scale is increased, also set the UI atlas to nearest filtering to avoid artifacts
+ // (there is no padding) and to have a sharper look
+ Texture2D@ uiTex = cache.GetResource("Texture2D", "Textures/UI.png");
+ if (uiTex !is null)
+ uiTex.filterMode = FILTER_NEAREST;
+ }
+ // Use system clipboard to allow transport of text in & out from the editor
+ ui.useSystemClipboard = true;
+}
+
+void FirstFrame()
+{
+ // Create root scene node
+ CreateScene();
+ // Load editor settings and preferences
+ LoadConfig();
+ // Create user interface for the editor
+ CreateUI();
+ // Create root UI element where all 'editable' UI elements would be parented to
+ CreateRootUIElement();
+ // Load the initial scene if provided
+ ParseArguments();
+ // Switch to real frame handler after initialization
+ SubscribeToEvent("Update", "HandleUpdate");
+ SubscribeToEvent("ReloadFinished", "HandleReloadFinishOrFail");
+ SubscribeToEvent("ReloadFailed", "HandleReloadFinishOrFail");
+ EditorSubscribeToEvents();
+}
+
+void Stop()
+{
+ SaveConfig();
+}
+
+void ParseArguments()
+{
+ Array arguments = GetArguments();
+ bool loaded = false;
+
+ // Scan for a scene to load
+ for (uint i = 1; i < arguments.length; ++i)
+ {
+ if (arguments[i].ToLower() == "-scene")
+ {
+ if (++i < arguments.length)
+ {
+ loaded = LoadScene(arguments[i]);
+ break;
+ }
+ }
+ if (arguments[i].ToLower() == "-language")
+ {
+ if (++i < arguments.length)
+ {
+ localization.SetLanguage(arguments[i]);
+ break;
+ }
+ }
+ }
+
+ if (!loaded)
+ ResetScene();
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+ float timeStep = eventData["TimeStep"].GetFloat();
+
+ DoResourceBrowserWork();
+ UpdateView(timeStep);
+ UpdateViewports(timeStep);
+ UpdateStats(timeStep);
+ UpdateScene(timeStep);
+ UpdateTestAnimation(timeStep);
+ UpdateGizmo();
+ UpdateDirtyUI();
+ UpdateViewDebugIcons();
+ UpdateOrigins();
+ UpdatePaintSelection();
+
+ // Handle Particle Editor looping.
+ if (particleEffectWindow !is null and particleEffectWindow.visible)
+ {
+ if (!particleEffectEmitter.emitting)
+ {
+ if (particleResetTimer == 0.0f)
+ particleResetTimer = editParticleEffect.maxTimeToLive + 0.2f;
+ else
+ {
+ particleResetTimer = Max(particleResetTimer - timeStep, 0.0f);
+ if (particleResetTimer <= 0.0001f)
+ {
+ particleEffectEmitter.Reset();
+ particleResetTimer = 0.0f;
+ }
+ }
+ }
+ }
+}
+
+void HandleReloadFinishOrFail(StringHash eventType, VariantMap& eventData)
+{
+ Resource@ res = cast(GetEventSender());
+ // Only refresh inspector when reloading scripts (script attributes may change)
+ if (res !is null && (res.typeName == "ScriptFile" || res.typeName == "LuaFile"))
+ attributesFullDirty = true;
+}
+
+void LoadConfig()
+{
+ if (!fileSystem.FileExists(configFileName))
+ return;
+
+ XMLFile config;
+ config.Load(File(configFileName, FILE_READ));
+
+ XMLElement configElem = config.root;
+ if (configElem.isNull)
+ return;
+
+ XMLElement cameraElem = configElem.GetChild("camera");
+ XMLElement objectElem = configElem.GetChild("object");
+ XMLElement renderingElem = configElem.GetChild("rendering");
+ XMLElement uiElem = configElem.GetChild("ui");
+ XMLElement hierarchyElem = configElem.GetChild("hierarchy");
+ XMLElement inspectorElem = configElem.GetChild("attributeinspector");
+ XMLElement viewElem = configElem.GetChild("view");
+ XMLElement resourcesElem = configElem.GetChild("resources");
+ XMLElement consoleElem = configElem.GetChild("console");
+ XMLElement varNamesElem = configElem.GetChild("varnames");
+ XMLElement soundTypesElem = configElem.GetChild("soundtypes");
+ XMLElement cubeMapElem = configElem.GetChild("cubegen");
+ XMLElement defaultTagsElem = configElem.GetChild("tags");
+
+ if (!cameraElem.isNull)
+ {
+ if (cameraElem.HasAttribute("nearclip")) viewNearClip = cameraElem.GetFloat("nearclip");
+ if (cameraElem.HasAttribute("farclip")) viewFarClip = cameraElem.GetFloat("farclip");
+ if (cameraElem.HasAttribute("fov")) viewFov = cameraElem.GetFloat("fov");
+ if (cameraElem.HasAttribute("speed")) cameraBaseSpeed = cameraElem.GetFloat("speed");
+ if (cameraElem.HasAttribute("limitrotation")) limitRotation = cameraElem.GetBool("limitrotation");
+ if (cameraElem.HasAttribute("viewportmode")) viewportMode = cameraElem.GetUInt("viewportmode");
+ if (cameraElem.HasAttribute("mouseorbitmode")) mouseOrbitMode = cameraElem.GetInt("mouseorbitmode");
+ if (cameraElem.HasAttribute("mmbpan")) mmbPanMode = cameraElem.GetBool("mmbpan");
+ if (cameraElem.HasAttribute("rotatearoundselect")) rotateAroundSelect = cameraElem.GetBool("rotatearoundselect");
+
+ UpdateViewParameters();
+ }
+
+ if (!objectElem.isNull)
+ {
+ if (objectElem.HasAttribute("cameraflymode")) cameraFlyMode = objectElem.GetBool("cameraflymode");
+ if (objectElem.HasAttribute("hotkeymode")) hotKeyMode = objectElem.GetInt("hotkeymode");
+ if (objectElem.HasAttribute("newnodemode")) newNodeMode = objectElem.GetInt("newnodemode");
+ if (objectElem.HasAttribute("movestep")) moveStep = objectElem.GetFloat("movestep");
+ if (objectElem.HasAttribute("rotatestep")) rotateStep = objectElem.GetFloat("rotatestep");
+ if (objectElem.HasAttribute("scalestep")) scaleStep = objectElem.GetFloat("scalestep");
+ if (objectElem.HasAttribute("movesnap")) moveSnap = objectElem.GetBool("movesnap");
+ if (objectElem.HasAttribute("rotatesnap")) rotateSnap = objectElem.GetBool("rotatesnap");
+ if (objectElem.HasAttribute("scalesnap")) scaleSnap = objectElem.GetBool("scalesnap");
+ if (objectElem.HasAttribute("applymateriallist")) applyMaterialList = objectElem.GetBool("applymateriallist");
+ if (objectElem.HasAttribute("importoptions")) importOptions = objectElem.GetAttribute("importoptions");
+ if (objectElem.HasAttribute("pickmode")) pickMode = objectElem.GetInt("pickmode");
+ if (objectElem.HasAttribute("axismode")) axisMode = AxisMode(objectElem.GetInt("axismode"));
+ if (objectElem.HasAttribute("revertonpause")) revertOnPause = objectElem.GetBool("revertonpause");
+ }
+
+ if (!resourcesElem.isNull)
+ {
+ if (resourcesElem.HasAttribute("rememberresourcepath")) rememberResourcePath = resourcesElem.GetBool("rememberresourcepath");
+ if (rememberResourcePath && resourcesElem.HasAttribute("resourcepath"))
+ {
+ String newResourcePath = resourcesElem.GetAttribute("resourcepath");
+ if (fileSystem.DirExists(newResourcePath))
+ SetResourcePath(resourcesElem.GetAttribute("resourcepath"), false);
+ }
+ if (resourcesElem.HasAttribute("importpath"))
+ {
+ String newImportPath = resourcesElem.GetAttribute("importpath");
+ if (fileSystem.DirExists(newImportPath))
+ uiImportPath = newImportPath;
+ }
+ if (resourcesElem.HasAttribute("recentscenes"))
+ {
+ uiRecentScenes = resourcesElem.GetAttribute("recentscenes").Split(';');
+ }
+ }
+
+ if (!renderingElem.isNull)
+ {
+ if (renderingElem.HasAttribute("renderpath")) renderPathName = renderingElem.GetAttribute("renderpath");
+ if (renderingElem.HasAttribute("texturequality")) renderer.textureQuality = renderingElem.GetInt("texturequality");
+ if (renderingElem.HasAttribute("materialquality")) renderer.materialQuality = renderingElem.GetInt("materialquality");
+ if (renderingElem.HasAttribute("shadowresolution")) SetShadowResolution(renderingElem.GetInt("shadowresolution"));
+ if (renderingElem.HasAttribute("shadowquality")) renderer.shadowQuality = ShadowQuality(renderingElem.GetInt("shadowquality"));
+ if (renderingElem.HasAttribute("maxoccludertriangles")) renderer.maxOccluderTriangles = renderingElem.GetInt("maxoccludertriangles");
+ if (renderingElem.HasAttribute("specularlighting")) renderer.specularLighting = renderingElem.GetBool("specularlighting");
+ if (renderingElem.HasAttribute("dynamicinstancing")) renderer.dynamicInstancing = renderingElem.GetBool("dynamicinstancing");
+ if (renderingElem.HasAttribute("framelimiter")) engine.maxFps = renderingElem.GetBool("framelimiter") ? 200 : 0;
+ if (renderingElem.HasAttribute("gammacorrection")) gammaCorrection = renderingElem.GetBool("gammacorrection");
+ if (renderingElem.HasAttribute("hdr")) HDR = renderingElem.GetBool("hdr");
+ }
+
+ if (!uiElem.isNull)
+ {
+ if (uiElem.HasAttribute("minopacity")) uiMinOpacity = uiElem.GetFloat("minopacity");
+ if (uiElem.HasAttribute("maxopacity")) uiMaxOpacity = uiElem.GetFloat("maxopacity");
+ if (uiElem.HasAttribute("languageindex")) localization.SetLanguage(uiElem.GetInt("languageindex"));
+ }
+
+ if (!hierarchyElem.isNull)
+ {
+ if (hierarchyElem.HasAttribute("showinternaluielement")) showInternalUIElement = hierarchyElem.GetBool("showinternaluielement");
+ if (hierarchyElem.HasAttribute("showtemporaryobject")) showTemporaryObject = hierarchyElem.GetBool("showtemporaryobject");
+ if (inspectorElem.HasAttribute("nodecolor")) nodeTextColor = inspectorElem.GetColor("nodecolor");
+ if (inspectorElem.HasAttribute("componentcolor")) componentTextColor = inspectorElem.GetColor("componentcolor");
+ }
+
+ if (!inspectorElem.isNull)
+ {
+ if (inspectorElem.HasAttribute("originalcolor")) normalTextColor = inspectorElem.GetColor("originalcolor");
+ if (inspectorElem.HasAttribute("modifiedcolor")) modifiedTextColor = inspectorElem.GetColor("modifiedcolor");
+ if (inspectorElem.HasAttribute("noneditablecolor")) nonEditableTextColor = inspectorElem.GetColor("noneditablecolor");
+ if (inspectorElem.HasAttribute("shownoneditable")) showNonEditableAttribute = inspectorElem.GetBool("shownoneditable");
+ }
+
+ if (!viewElem.isNull)
+ {
+ if (viewElem.HasAttribute("defaultzoneambientcolor")) renderer.defaultZone.ambientColor = viewElem.GetColor("defaultzoneambientcolor");
+ if (viewElem.HasAttribute("defaultzonefogcolor")) renderer.defaultZone.fogColor = viewElem.GetColor("defaultzonefogcolor");
+ if (viewElem.HasAttribute("defaultzonefogstart")) renderer.defaultZone.fogStart = viewElem.GetInt("defaultzonefogstart");
+ if (viewElem.HasAttribute("defaultzonefogend")) renderer.defaultZone.fogEnd = viewElem.GetInt("defaultzonefogend");
+ if (viewElem.HasAttribute("showgrid")) showGrid = viewElem.GetBool("showgrid");
+ if (viewElem.HasAttribute("grid2dmode")) grid2DMode = viewElem.GetBool("grid2dmode");
+ if (viewElem.HasAttribute("gridsize")) gridSize = viewElem.GetInt("gridsize");
+ if (viewElem.HasAttribute("gridsubdivisions")) gridSubdivisions = viewElem.GetInt("gridsubdivisions");
+ if (viewElem.HasAttribute("gridscale")) gridScale = viewElem.GetFloat("gridscale");
+ if (viewElem.HasAttribute("gridcolor")) gridColor = viewElem.GetColor("gridcolor");
+ if (viewElem.HasAttribute("gridsubdivisioncolor")) gridSubdivisionColor = viewElem.GetColor("gridsubdivisioncolor");
+ }
+
+ if (!consoleElem.isNull)
+ {
+ // Console does not exist yet at this point, so store the string in a global variable
+ if (consoleElem.HasAttribute("commandinterpreter")) consoleCommandInterpreter = consoleElem.GetAttribute("commandinterpreter");
+ }
+
+ if (!varNamesElem.isNull)
+ globalVarNames = varNamesElem.GetVariantMap();
+
+ if (!soundTypesElem.isNull)
+ LoadSoundTypes(soundTypesElem);
+
+ if (!cubeMapElem.isNull)
+ {
+ cubeMapGen_Name = cubeMapElem.HasAttribute("name") ? cubeMapElem.GetAttribute("name") : "";
+ cubeMapGen_Path = cubeMapElem.HasAttribute("path") ? cubeMapElem.GetAttribute("path") : cubemapDefaultOutputPath;
+ cubeMapGen_Size = cubeMapElem.HasAttribute("size") ? cubeMapElem.GetInt("size") : 128;
+ }
+ else
+ {
+ cubeMapGen_Name = "";
+ cubeMapGen_Path = cubemapDefaultOutputPath;
+ cubeMapGen_Size = 128;
+ }
+
+ if (!defaultTagsElem.isNull)
+ {
+ if (defaultTagsElem.HasAttribute("tags")) defaultTags = defaultTagsElem.GetAttribute("tags");
+ }
+}
+
+void SaveConfig()
+{
+ XMLFile config;
+ XMLElement configElem = config.CreateRoot("configuration");
+ XMLElement cameraElem = configElem.CreateChild("camera");
+ XMLElement objectElem = configElem.CreateChild("object");
+ XMLElement renderingElem = configElem.CreateChild("rendering");
+ XMLElement uiElem = configElem.CreateChild("ui");
+ XMLElement hierarchyElem = configElem.CreateChild("hierarchy");
+ XMLElement inspectorElem = configElem.CreateChild("attributeinspector");
+ XMLElement viewElem = configElem.CreateChild("view");
+ XMLElement resourcesElem = configElem.CreateChild("resources");
+ XMLElement consoleElem = configElem.CreateChild("console");
+ XMLElement varNamesElem = configElem.CreateChild("varnames");
+ XMLElement soundTypesElem = configElem.CreateChild("soundtypes");
+ XMLElement cubeGenElem = configElem.CreateChild("cubegen");
+ XMLElement defaultTagsElem = configElem.CreateChild("tags");
+
+ cameraElem.SetFloat("nearclip", viewNearClip);
+ cameraElem.SetFloat("farclip", viewFarClip);
+ cameraElem.SetFloat("fov", viewFov);
+ cameraElem.SetFloat("speed", cameraBaseSpeed);
+ cameraElem.SetBool("limitrotation", limitRotation);
+ cameraElem.SetUInt("viewportmode", viewportMode);
+ cameraElem.SetInt("mouseorbitmode", mouseOrbitMode);
+ cameraElem.SetBool("mmbpan", mmbPanMode);
+ cameraElem.SetBool("rotatearoundselect", rotateAroundSelect);
+
+ objectElem.SetBool("cameraflymode", cameraFlyMode);
+ objectElem.SetInt("hotkeymode", hotKeyMode);
+ objectElem.SetInt("newnodemode", newNodeMode);
+ objectElem.SetFloat("movestep", moveStep);
+ objectElem.SetFloat("rotatestep", rotateStep);
+ objectElem.SetFloat("scalestep", scaleStep);
+ objectElem.SetBool("movesnap", moveSnap);
+ objectElem.SetBool("rotatesnap", rotateSnap);
+ objectElem.SetBool("scalesnap", scaleSnap);
+ objectElem.SetBool("applymateriallist", applyMaterialList);
+ objectElem.SetAttribute("importoptions", importOptions);
+ objectElem.SetInt("pickmode", pickMode);
+ objectElem.SetInt("axismode", axisMode);
+ objectElem.SetBool("revertonpause", revertOnPause);
+
+ resourcesElem.SetBool("rememberresourcepath", rememberResourcePath);
+ resourcesElem.SetAttribute("resourcepath", sceneResourcePath);
+ resourcesElem.SetAttribute("importpath", uiImportPath);
+ resourcesElem.SetAttribute("recentscenes", Join(uiRecentScenes, ";"));
+
+ if (renderer !is null && graphics !is null)
+ {
+ renderingElem.SetAttribute("renderpath", renderPathName);
+ renderingElem.SetInt("texturequality", renderer.textureQuality);
+ renderingElem.SetInt("materialquality", renderer.materialQuality);
+ renderingElem.SetInt("shadowresolution", GetShadowResolution());
+ renderingElem.SetInt("maxoccludertriangles", renderer.maxOccluderTriangles);
+ renderingElem.SetBool("specularlighting", renderer.specularLighting);
+ renderingElem.SetInt("shadowquality", int(renderer.shadowQuality));
+ renderingElem.SetBool("dynamicinstancing", renderer.dynamicInstancing);
+ }
+
+ renderingElem.SetBool("framelimiter", engine.maxFps > 0);
+ renderingElem.SetBool("gammacorrection", gammaCorrection);
+ renderingElem.SetBool("hdr", HDR);
+
+ uiElem.SetFloat("minopacity", uiMinOpacity);
+ uiElem.SetFloat("maxopacity", uiMaxOpacity);
+ uiElem.SetInt("languageindex", localization.languageIndex);
+
+ hierarchyElem.SetBool("showinternaluielement", showInternalUIElement);
+ hierarchyElem.SetBool("showtemporaryobject", showTemporaryObject);
+ inspectorElem.SetColor("nodecolor", nodeTextColor);
+ inspectorElem.SetColor("componentcolor", componentTextColor);
+
+ inspectorElem.SetColor("originalcolor", normalTextColor);
+ inspectorElem.SetColor("modifiedcolor", modifiedTextColor);
+ inspectorElem.SetColor("noneditablecolor", nonEditableTextColor);
+ inspectorElem.SetBool("shownoneditable", showNonEditableAttribute);
+
+ viewElem.SetBool("showgrid", showGrid);
+ viewElem.SetBool("grid2dmode", grid2DMode);
+ viewElem.SetColor("defaultzoneambientcolor", renderer.defaultZone.ambientColor);
+ viewElem.SetColor("defaultzonefogcolor", renderer.defaultZone.fogColor);
+ viewElem.SetFloat("defaultzonefogstart", renderer.defaultZone.fogStart);
+ viewElem.SetFloat("defaultzonefogend", renderer.defaultZone.fogEnd);
+ viewElem.SetInt("gridsize", gridSize);
+ viewElem.SetInt("gridsubdivisions", gridSubdivisions);
+ viewElem.SetFloat("gridscale", gridScale);
+ viewElem.SetColor("gridcolor", gridColor);
+ viewElem.SetColor("gridsubdivisioncolor", gridSubdivisionColor);
+
+ consoleElem.SetAttribute("commandinterpreter", console.commandInterpreter);
+
+ varNamesElem.SetVariantMap(globalVarNames);
+
+ cubeGenElem.SetAttribute("name", cubeMapGen_Name);
+ cubeGenElem.SetAttribute("path", cubeMapGen_Path);
+ cubeGenElem.SetAttribute("size", cubeMapGen_Size);
+
+ defaultTagsElem.SetAttribute("tags", defaultTags);
+
+ SaveSoundTypes(soundTypesElem);
+
+ config.Save(File(configFileName, FILE_WRITE));
+}
+
+void MakeBackup(const String&in fileName)
+{
+ fileSystem.Rename(fileName, fileName + ".old");
+}
+
+void RemoveBackup(bool success, const String&in fileName)
+{
+ if (success)
+ fileSystem.Delete(fileName + ".old");
+}
diff --git a/bin/Data/Scripts/Editor/AttributeEditor.as b/bin/Data/Scripts/Editor/AttributeEditor.as
new file mode 100644
index 0000000..165936b
--- /dev/null
+++ b/bin/Data/Scripts/Editor/AttributeEditor.as
@@ -0,0 +1,1559 @@
+// Attribute editor
+//
+// Functions that caller must implement:
+// - void SetAttributeEditorID(UIElement@ attrEdit, Array@ serializables);
+// - bool PreEditAttribute(Array@ serializables, uint index);
+// - void PostEditAttribute(Array@ serializables, uint index, const Array& oldValues);
+// - Array@ GetAttributeEditorTargets(UIElement@ attrEdit);
+// - String GetVariableName(StringHash hash);
+
+const uint MIN_NODE_ATTRIBUTES = 4;
+const uint MAX_NODE_ATTRIBUTES = 8;
+const int ATTRNAME_WIDTH = 150;
+const int ATTR_HEIGHT = 19;
+const StringHash TEXT_CHANGED_EVENT_TYPE("TextChanged");
+
+bool inLoadAttributeEditor = false;
+bool inEditAttribute = false;
+bool inUpdateBitSelection = false;
+bool showNonEditableAttribute = false;
+
+Color normalTextColor(1.0f, 1.0f, 1.0f);
+Color modifiedTextColor(1.0f, 0.8f, 0.5f);
+Color nonEditableTextColor(0.7f, 0.7f, 0.7f);
+
+String sceneResourcePath = AddTrailingSlash(fileSystem.programDir + "Data");
+bool rememberResourcePath = true;
+
+// Exceptions for string attributes that should not be continuously edited
+Array noTextChangedAttrs = {"Script File", "Class Name", "Script Object Type", "Script File Name"};
+
+// List of attributes that should be created with a bit selection editor
+const Array bitSelectionAttrs = {"Collision Mask", "Collision Layer", "Light Mask", "Zone Mask", "View Mask", "Shadow Mask"};
+
+// Number of editable bits for bit selection editor
+const int MAX_BITMASK_BITS = 8;
+const int MAX_BITMASK_VALUE = (1 << MAX_BITMASK_BITS) - 1;
+Color nonEditableBitSelectorColor(0.5f, 0.5f, 0.5f);
+Color editableBitSelectorColor(1.0f, 1.0f, 1.0f);
+
+WeakHandle testAnimState;
+
+bool dragEditAttribute = false;
+
+UIElement@ SetEditable(UIElement@ element, bool editable)
+{
+ if (element is null)
+ return element;
+
+ element.editable = editable;
+ element.colors[C_TOPLEFT] = editable ? element.colors[C_BOTTOMRIGHT] : nonEditableTextColor;
+ element.colors[C_BOTTOMLEFT] = element.colors[C_TOPLEFT];
+ element.colors[C_TOPRIGHT] = element.colors[C_TOPLEFT];
+ return element;
+}
+
+UIElement@ SetValue(LineEdit@ element, const String&in value, bool sameValue)
+{
+ element.text = sameValue ? value : STRIKED_OUT;
+ element.cursorPosition = 0;
+ return element;
+}
+
+UIElement@ SetValue(CheckBox@ element, bool value, bool sameValue)
+{
+ element.checked = sameValue ? value : false;
+ return element;
+}
+
+UIElement@ SetValue(DropDownList@ element, int value, bool sameValue)
+{
+ element.selection = sameValue ? value : M_MAX_UNSIGNED;
+ return element;
+}
+
+UIElement@ CreateAttributeEditorParentWithSeparatedLabel(ListView@ list, const String&in name, uint index, uint subIndex, bool suppressedSeparatedLabel = false)
+{
+ UIElement@ editorParent = UIElement("Edit" + String(index) + "_" + String(subIndex));
+ editorParent.vars["Index"] = index;
+ editorParent.vars["SubIndex"] = subIndex;
+ editorParent.SetLayout(LM_VERTICAL, 2);
+ list.AddItem(editorParent);
+
+ if (suppressedSeparatedLabel)
+ {
+ UIElement@ placeHolder = UIElement(name);
+ editorParent.AddChild(placeHolder);
+ }
+ else
+ {
+ Text@ attrNameText = Text();
+ editorParent.AddChild(attrNameText);
+ attrNameText.style = "EditorAttributeText";
+ attrNameText.text = name;
+ }
+
+ return editorParent;
+}
+
+UIElement@ CreateAttributeEditorParentAsListChild(ListView@ list, const String&in name, uint index, uint subIndex)
+{
+ UIElement@ editorParent = UIElement("Edit" + String(index) + "_" + String(subIndex));
+ editorParent.vars["Index"] = index;
+ editorParent.vars["SubIndex"] = subIndex;
+ editorParent.SetLayout(LM_HORIZONTAL);
+ list.AddChild(editorParent);
+
+ UIElement@ placeHolder = UIElement(name);
+ editorParent.AddChild(placeHolder);
+
+ return editorParent;
+}
+
+UIElement@ CreateAttributeEditorParent(ListView@ list, const String&in name, uint index, uint subIndex)
+{
+ UIElement@ editorParent = UIElement("Edit" + String(index) + "_" + String(subIndex));
+ editorParent.vars["Index"] = index;
+ editorParent.vars["SubIndex"] = subIndex;
+ editorParent.SetLayout(LM_HORIZONTAL);
+ editorParent.SetFixedHeight(ATTR_HEIGHT);
+ list.AddItem(editorParent);
+
+ Text@ attrNameText = Text();
+ editorParent.AddChild(attrNameText);
+ attrNameText.style = "EditorAttributeText";
+ attrNameText.text = name;
+ attrNameText.SetFixedWidth(ATTRNAME_WIDTH);
+
+ return editorParent;
+}
+
+LineEdit@ CreateAttributeLineEdit(UIElement@ parent, Array@ serializables, uint index, uint subIndex)
+{
+ LineEdit@ attrEdit = LineEdit();
+ parent.AddChild(attrEdit);
+ attrEdit.dragDropMode = DD_TARGET;
+ attrEdit.style = "EditorAttributeEdit";
+ attrEdit.SetFixedHeight(ATTR_HEIGHT - 2);
+ attrEdit.vars["Index"] = index;
+ attrEdit.vars["SubIndex"] = subIndex;
+ SetAttributeEditorID(attrEdit, serializables);
+
+ return attrEdit;
+}
+
+LineEdit@ CreateAttributeBitSelector(UIElement@ parent, Array@ serializables, uint index, uint subIndex)
+{
+ UIElement@ container = UIElement();
+ parent.AddChild(container);
+ parent.SetFixedHeight(38);
+ container.SetFixedWidth(16 * 4 + 4);
+
+ for (int i = 0; i < 2; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ CheckBox@ bitBox = CheckBox();
+ bitBox.name = "BitSelect_" + String(i * 4 + j);
+ container.AddChild(bitBox);
+ bitBox.position = IntVector2(16 * j, 16 * i);
+ bitBox.style = "CheckBox";
+ bitBox.SetFixedHeight(16);
+
+ SubscribeToEvent(bitBox,"Toggled", "HandleBitSelectionToggled");
+ }
+ }
+
+ LineEdit@ attrEdit = CreateAttributeLineEdit(parent, serializables, index, subIndex);
+ attrEdit.name = "LineEdit";
+ SubscribeToEvent(attrEdit, "TextChanged", "HandleBitSelectionEdit");
+ SubscribeToEvent(attrEdit, "TextFinished", "HandleBitSelectionEdit");
+ return attrEdit;
+}
+
+void UpdateBitSelection(UIElement@ parent)
+{
+ int mask = 0;
+ for (int i = 0; i < MAX_BITMASK_BITS; i++)
+ {
+ CheckBox@ bitBox = parent.GetChild("BitSelect_" + String(i), true);
+ mask = mask | (bitBox.checked ? 1 << i : 0);
+ }
+
+ if (mask == MAX_BITMASK_VALUE)
+ mask = -1;
+
+ inUpdateBitSelection = true;
+ LineEdit@ attrEdit = parent.parent.GetChild("LineEdit", true);
+ attrEdit.text = String(mask);
+ inUpdateBitSelection = false;
+}
+
+void SetBitSelection(UIElement@ parent, int value)
+{
+ int mask = value;
+ bool enabled = true;
+
+ if (mask == -1)
+ mask = MAX_BITMASK_VALUE;
+ else if (mask > MAX_BITMASK_VALUE)
+ enabled = false;
+
+ for (int i = 0; i < MAX_BITMASK_BITS; i++)
+ {
+ CheckBox@ bitBox = parent.GetChild("BitSelect_" + String(i), true);
+ bitBox.enabled = enabled;
+ if (!enabled)
+ bitBox.color = nonEditableBitSelectorColor;
+ else
+ bitBox.color = editableBitSelectorColor;
+
+ if ((1 << i) & mask != 0)
+ bitBox.checked = true;
+ else
+ bitBox.checked = false;
+ }
+}
+
+void HandleBitSelectionToggled(StringHash eventType, VariantMap& eventData)
+{
+ if (inUpdateBitSelection)
+ return;
+
+ CheckBox@ bitBox = eventData["Element"].GetPtr();
+
+ UpdateBitSelection(bitBox.parent);
+}
+
+void HandleBitSelectionEdit(StringHash eventType, VariantMap& eventData)
+{
+ if (!inUpdateBitSelection)
+ {
+ LineEdit@ attrEdit = eventData["Element"].GetPtr();
+
+ inUpdateBitSelection = true;
+ SetBitSelection(attrEdit.parent, attrEdit.text.ToInt());
+ inUpdateBitSelection = false;
+ }
+
+ EditAttribute(eventType, eventData);
+}
+
+UIElement@ CreateStringAttributeEditor(ListView@ list, Array@ serializables, const AttributeInfo&in info, uint index, uint subIndex)
+{
+ UIElement@ parent = CreateAttributeEditorParent(list, info.name, index, subIndex);
+ LineEdit@ attrEdit = CreateAttributeLineEdit(parent, serializables, index, subIndex);
+ attrEdit.dragDropMode = DD_TARGET;
+ // Do not subscribe to continuous edits of certain attributes (script class names) to prevent unnecessary errors getting printed
+ if (noTextChangedAttrs.Find(info.name) == -1)
+ SubscribeToEvent(attrEdit, "TextChanged", "EditAttribute");
+ SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
+
+ return parent;
+}
+
+UIElement@ CreateBoolAttributeEditor(ListView@ list, Array@ serializables, const AttributeInfo&in info, uint index, uint subIndex)
+{
+ bool isUIElement = cast(serializables[0]) !is null;
+ UIElement@ parent;
+ if (info.name == (isUIElement ? "Is Visible" : "Is Enabled"))
+ parent = CreateAttributeEditorParentAsListChild(list, info.name, index, subIndex);
+ else
+ parent = CreateAttributeEditorParent(list, info.name, index, subIndex);
+
+ CheckBox@ attrEdit = CheckBox();
+ parent.AddChild(attrEdit);
+ attrEdit.style = AUTO_STYLE;
+ attrEdit.vars["Index"] = index;
+ attrEdit.vars["SubIndex"] = subIndex;
+ SetAttributeEditorID(attrEdit, serializables);
+ SubscribeToEvent(attrEdit, "Toggled", "EditAttribute");
+
+ return parent;
+}
+
+UIElement@ CreateNumAttributeEditor(ListView@ list, Array@ serializables, const AttributeInfo&in info, uint index, uint subIndex)
+{
+ UIElement@ parent = CreateAttributeEditorParent(list, info.name, index, subIndex);
+ VariantType type = info.type;
+ uint numCoords = 1;
+ if (type == VAR_VECTOR2 || type == VAR_INTVECTOR2)
+ numCoords = 2;
+ if (type == VAR_VECTOR3 || type == VAR_INTVECTOR3 || type == VAR_QUATERNION)
+ numCoords = 3;
+ else if (type == VAR_VECTOR4 || type == VAR_COLOR || type == VAR_INTRECT || type == VAR_RECT)
+ numCoords = 4;
+
+ for (uint i = 0; i < numCoords; ++i)
+ {
+ LineEdit@ attrEdit = CreateAttributeLineEdit(parent, serializables, index, subIndex);
+ attrEdit.vars["Coordinate"] = i;
+
+ CreateDragSlider(attrEdit);
+
+ SubscribeToEvent(attrEdit, "TextChanged", "EditAttribute");
+ SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
+ }
+
+ return parent;
+}
+
+UIElement@ CreateIntAttributeEditor(ListView@ list, Array@ serializables, const AttributeInfo&in info, uint index, uint subIndex)
+{
+ UIElement@ parent = CreateAttributeEditorParent(list, info.name, index, subIndex);
+
+ // Check for masks and layers
+ if (bitSelectionAttrs.Find(info.name) > -1)
+ {
+ LineEdit@ attrEdit = CreateAttributeBitSelector(parent, serializables, index, subIndex);
+ }
+ // Check for enums
+ else if (info.enumNames is null || info.enumNames.empty)
+ {
+ // No enums, create a numeric editor
+ LineEdit@ attrEdit = CreateAttributeLineEdit(parent, serializables, index, subIndex);
+ CreateDragSlider(attrEdit);
+ // If the attribute is a counter for things like billboards or animation states, disable apply at each change
+ if (info.name.Find(" Count", 0, false) == NPOS)
+ SubscribeToEvent(attrEdit, "TextChanged", "EditAttribute");
+ SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
+ // If the attribute is a node ID, make it a drag/drop target
+ if (info.name.Contains("NodeID", false) || info.name.Contains("Node ID", false) || (info.mode & AM_NODEID) != 0)
+ attrEdit.dragDropMode = DD_TARGET;
+ }
+ else
+ {
+ DropDownList@ attrEdit = DropDownList();
+ parent.AddChild(attrEdit);
+ attrEdit.style = AUTO_STYLE;
+ attrEdit.SetFixedHeight(ATTR_HEIGHT - 2);
+ attrEdit.resizePopup = true;
+ attrEdit.placeholderText = STRIKED_OUT;
+ attrEdit.vars["Index"] = index;
+ attrEdit.vars["SubIndex"] = subIndex;
+ attrEdit.SetLayout(LM_HORIZONTAL, 0, IntRect(4, 1, 4, 1));
+ SetAttributeEditorID(attrEdit, serializables);
+
+ for (uint i = 0; i < info.enumNames.length; ++i)
+ {
+ Text@ choice = Text();
+ attrEdit.AddItem(choice);
+ choice.style = "EditorEnumAttributeText";
+ choice.text = info.enumNames[i];
+ }
+ SubscribeToEvent(attrEdit, "ItemSelected", "EditAttribute");
+ }
+
+ return parent;
+}
+
+UIElement@ CreateResourceRefAttributeEditor(ListView@ list, Array@ serializables, const AttributeInfo&in info, uint index, uint subIndex, bool suppressedSeparatedLabel = false)
+{
+ UIElement@ parent;
+ StringHash resourceType;
+
+ // Get the real attribute info from the serializable for the correct resource type
+ AttributeInfo attrInfo = serializables[0].attributeInfos[index];
+ if (attrInfo.type == VAR_RESOURCEREF)
+ resourceType = serializables[0].attributes[index].GetResourceRef().type;
+ else if (attrInfo.type == VAR_RESOURCEREFLIST)
+ resourceType = serializables[0].attributes[index].GetResourceRefList().type;
+ else if (attrInfo.type == VAR_VARIANTVECTOR)
+ resourceType = serializables[0].attributes[index].GetVariantVector()[subIndex].GetResourceRef().type;
+
+ ResourcePicker@ picker = GetResourcePicker(resourceType);
+
+ // Create the attribute name on a separate non-interactive line to allow for more space
+ parent = CreateAttributeEditorParentWithSeparatedLabel(list, info.name, index, subIndex, suppressedSeparatedLabel);
+
+ UIElement@ container = UIElement();
+ container.SetLayout(LM_HORIZONTAL, 4, IntRect(info.name.StartsWith(" ") ? 20 : 10, 0, 4, 0)); // Left margin is indented more when the name is so
+ container.SetFixedHeight(ATTR_HEIGHT);
+ parent.AddChild(container);
+
+ LineEdit@ attrEdit = CreateAttributeLineEdit(container, serializables, index, subIndex);
+ attrEdit.vars[TYPE_VAR] = resourceType.value;
+ SubscribeToEvent(attrEdit, "TextFinished", "EditAttribute");
+
+ if (picker !is null)
+ {
+ if ((picker.actions & ACTION_PICK) != 0)
+ {
+ Button@ pickButton = CreateResourcePickerButton(container, serializables, index, subIndex, "smallButtonPick");
+ SubscribeToEvent(pickButton, "Released", "PickResource");
+ }
+ if ((picker.actions & ACTION_OPEN) != 0)
+ {
+ Button@ openButton = CreateResourcePickerButton(container, serializables, index, subIndex, "smallButtonOpen");
+ SubscribeToEvent(openButton, "Released", "OpenResource");
+ }
+ if ((picker.actions & ACTION_EDIT) != 0)
+ {
+ Button@ editButton = CreateResourcePickerButton(container, serializables, index, subIndex, "smallButtonEdit");
+ SubscribeToEvent(editButton, "Released", "EditResource");
+ }
+ if ((picker.actions & ACTION_TEST) != 0)
+ {
+ Button@ testButton = CreateResourcePickerButton(container, serializables, index, subIndex, "smallButtonTest");
+ SubscribeToEvent(testButton, "Released", "TestResource");
+ }
+ }
+
+ return parent;
+}
+
+Button@ CreateResourcePickerButton(UIElement@ container, Array@ serializables, uint index, uint subIndex, const String&in text)
+{
+ Button@ button = Button();
+ container.AddChild(button);
+ button.style = AUTO_STYLE;
+ button.SetFixedSize(36, ATTR_HEIGHT - 2);
+ button.vars["Index"] = index;
+ button.vars["SubIndex"] = subIndex;
+ SetAttributeEditorID(button, serializables);
+
+ Text@ buttonText = Text();
+ button.AddChild(buttonText);
+ buttonText.style = "EditorAttributeText";
+ buttonText.SetAlignment(HA_CENTER, VA_CENTER);
+ buttonText.text = text;
+ buttonText.autoLocalizable = true;
+
+ return button;
+}
+
+// Use internally for nested variant vector
+uint nestedSubIndex;
+Array@ nestedVector;
+
+UIElement@ CreateAttributeEditor(ListView@ list, Array@ serializables, const AttributeInfo&in info, uint index, uint subIndex, bool suppressedSeparatedLabel = false)
+{
+ UIElement@ parent;
+
+ VariantType type = info.type;
+ if (type == VAR_STRING || type == VAR_BUFFER)
+ parent = CreateStringAttributeEditor(list, serializables, info, index, subIndex);
+ else if (type == VAR_BOOL)
+ parent = CreateBoolAttributeEditor(list, serializables, info, index, subIndex);
+ else if ((type >= VAR_FLOAT && type <= VAR_VECTOR4) || type == VAR_QUATERNION || type == VAR_COLOR || type == VAR_INTVECTOR2 || type == VAR_INTVECTOR3 || type == VAR_INTRECT || type == VAR_DOUBLE || type == VAR_RECT)
+ parent = CreateNumAttributeEditor(list, serializables, info, index, subIndex);
+ else if (type == VAR_INT)
+ parent = CreateIntAttributeEditor(list, serializables, info, index, subIndex);
+ else if (type == VAR_RESOURCEREF)
+ parent = CreateResourceRefAttributeEditor(list, serializables, info, index, subIndex, suppressedSeparatedLabel);
+ else if (type == VAR_RESOURCEREFLIST)
+ {
+ uint numRefs = serializables[0].attributes[index].GetResourceRefList().length;
+
+ // Straightly speaking the individual resource reference in the list is not an attribute of the serializable
+ // However, the AttributeInfo structure is used here to reduce the number of parameters being passed in the function
+ AttributeInfo refInfo;
+ refInfo.name = info.name;
+ refInfo.type = VAR_RESOURCEREF;
+ for (uint i = 0; i < numRefs; ++i)
+ CreateAttributeEditor(list, serializables, refInfo, index, i, i > 0);
+ }
+ else if (type == VAR_VARIANTVECTOR)
+ {
+ uint nameIndex = 0;
+ uint repeat = M_MAX_UNSIGNED;
+
+ VectorStruct@ vectorStruct;
+ Array@ vector;
+ bool emptyNestedVector = false;
+ if (info.name.Contains('>'))
+ {
+ @vector = @nestedVector;
+ vectorStruct = GetNestedVectorStruct(serializables, info.name);
+ repeat = vector[subIndex].GetUInt(); // Nested VariantVector must have a predefined repeat count at the start of the vector
+ emptyNestedVector = repeat == 0;
+ }
+ else
+ {
+ @vector = serializables[0].attributes[index].GetVariantVector();
+ vectorStruct = GetVectorStruct(serializables, index);
+ subIndex = 0;
+ }
+ if (vectorStruct is null)
+ return null;
+
+ for (uint i = subIndex; i < vector.length; ++i)
+ {
+ // The individual variant in the vector is not an attribute of the serializable, the structure is reused for convenience
+ AttributeInfo vectorInfo;
+ vectorInfo.name = vectorStruct.variableNames[nameIndex++];
+ bool nested = vectorInfo.name.Contains('>');
+ if (nested)
+ {
+ vectorInfo.type = VAR_VARIANTVECTOR;
+ @nestedVector = @vector;
+ }
+ else
+ vectorInfo.type = vector[i].type;
+ CreateAttributeEditor(list, serializables, vectorInfo, index, i);
+ if (nested)
+ {
+ i = nestedSubIndex;
+ @nestedVector = null;
+ }
+ if (emptyNestedVector)
+ {
+ nestedSubIndex = i;
+ break;
+ }
+ if (nameIndex >= vectorStruct.variableNames.length)
+ {
+ if (--repeat == 0)
+ {
+ nestedSubIndex = i;
+ break;
+ }
+ nameIndex = vectorStruct.restartIndex;
+
+ // Create small divider for repeated instances
+ UIElement@ divider = UIElement();
+ divider.SetFixedHeight(8);
+ list.AddItem(divider);
+ }
+ }
+ }
+ else if (type == VAR_VARIANTMAP)
+ {
+ VariantMap map = serializables[0].attributes[index].GetVariantMap();
+ Array@ keys = map.keys;
+ for (uint i = 0; i < keys.length; ++i)
+ {
+ String varName = GetVarName(keys[i]);
+ bool shouldHide = false;
+
+ if (varName.empty)
+ {
+ // UIElements will contain internal vars, which do not have known mappings. Hide these
+ if (cast(serializables[0]) !is null)
+ shouldHide = true;
+ // Else, for scene nodes, show as hexadecimal hashes if nothing else is available
+ varName = keys[i].ToString();
+ }
+ Variant value = map[keys[i]];
+
+ // The individual variant in the map is not an attribute of the serializable, the structure is reused for convenience
+ AttributeInfo mapInfo;
+ mapInfo.name = varName + " (Var)";
+ mapInfo.type = value.type;
+ parent = CreateAttributeEditor(list, serializables, mapInfo, index, i);
+ // Add the variant key to the parent. We may fail to add the editor in case it is unsupported
+ if (parent !is null)
+ {
+ parent.vars["Key"] = keys[i].value;
+ // If variable name is not registered (i.e. it is an editor internal variable) then hide it
+ if (varName.empty || shouldHide)
+ parent.visible = false;
+ }
+ }
+ }
+
+ return parent;
+}
+
+uint GetAttributeEditorCount(Array@ serializables)
+{
+ uint count = 0;
+
+ if (!serializables.empty)
+ {
+ /// \todo When multi-editing, this only counts the editor count of the first serializable
+ bool isUIElement = cast(serializables[0]) !is null;
+ for (uint i = 0; i < serializables[0].numAttributes; ++i)
+ {
+ AttributeInfo info = serializables[0].attributeInfos[i];
+ if (!showNonEditableAttribute && info.mode & AM_NOEDIT != 0)
+ continue;
+ // "Is Enabled" is not inserted into the main attribute list, so do not count
+ // Similarly, for UIElement, "Is Visible" is not inserted
+ if (info.name == (isUIElement ? "Is Visible" : "Is Enabled"))
+ continue;
+ // Tags are also handled separately
+ if (info.name == "Tags")
+ continue;
+ if (info.type == VAR_RESOURCEREFLIST)
+ count += serializables[0].attributes[i].GetResourceRefList().length;
+ else if (info.type == VAR_VARIANTVECTOR && GetVectorStruct(serializables, i) !is null)
+ count += serializables[0].attributes[i].GetVariantVector().length;
+ else if (info.type == VAR_VARIANTMAP)
+ count += serializables[0].attributes[i].GetVariantMap().length;
+ else
+ ++count;
+ }
+ }
+
+ return count;
+}
+
+UIElement@ GetAttributeEditorParent(UIElement@ parent, uint index, uint subIndex)
+{
+ return parent.GetChild("Edit" + String(index) + "_" + String(subIndex), true);
+}
+
+void LoadAttributeEditor(ListView@ list, Array@ serializables, const AttributeInfo&in info, uint index)
+{
+ bool editable = info.mode & AM_NOEDIT == 0;
+
+ UIElement@ parent = GetAttributeEditorParent(list, index, 0);
+ if (parent is null)
+ return;
+
+ inLoadAttributeEditor = true;
+
+ bool sameName = true;
+ bool sameValue = true;
+ Variant value = serializables[0].attributes[index];
+ Array values;
+ for (uint i = 0; i < serializables.length; ++i)
+ {
+ if (index >= serializables[i].numAttributes || serializables[i].attributeInfos[index].name != info.name)
+ {
+ sameName = false;
+ break;
+ }
+
+ Variant val = serializables[i].attributes[index];
+ if (val != value)
+ sameValue = false;
+ values.Push(val);
+ }
+
+ // Attribute with different values from multiple-select is loaded with default/empty value and non-editable
+ if (sameName)
+ LoadAttributeEditor(parent, value, info, editable, sameValue, values);
+ else
+ parent.visible = false;
+
+ inLoadAttributeEditor = false;
+}
+
+void LoadAttributeEditor(UIElement@ parent, const Variant&in value, const AttributeInfo&in info, bool editable, bool sameValue, const Array&in values)
+{
+ uint index = parent.vars["Index"].GetUInt();
+
+ // Assume the first child is always a text label element or a container that containing a text label element
+ UIElement@ label = parent.children[0];
+ if (label.type == UI_ELEMENT_TYPE && label.numChildren > 0)
+ label = label.children[0];
+ if (label.type == TEXT_TYPE)
+ {
+ bool modified;
+ if (info.defaultValue.type == VAR_NONE || info.defaultValue.type == VAR_RESOURCEREFLIST)
+ modified = !value.zero;
+ else
+ modified = value != info.defaultValue;
+ cast(label).color = (editable ? (modified ? modifiedTextColor : normalTextColor) : nonEditableTextColor);
+ }
+
+ VariantType type = info.type;
+ if (type == VAR_FLOAT || type == VAR_DOUBLE || type == VAR_STRING || type == VAR_BUFFER)
+ SetEditable(SetValue(parent.children[1], value.ToString(), sameValue), editable && sameValue);
+ else if (type == VAR_BOOL)
+ SetEditable(SetValue(parent.children[1], value.GetBool(), sameValue), editable && sameValue);
+ else if (type == VAR_INT)
+ {
+ if (bitSelectionAttrs.Find(info.name) > -1)
+ SetEditable(SetValue(parent.GetChild("LineEdit", true), value.ToString(), sameValue), editable && sameValue);
+ else if (info.enumNames is null || info.enumNames.empty)
+ SetEditable(SetValue(parent.children[1], value.ToString(), sameValue), editable && sameValue);
+ else
+ SetEditable(SetValue(parent.children[1], value.GetInt(), sameValue), editable && sameValue);
+ }
+ else if (type == VAR_RESOURCEREF)
+ {
+ SetEditable(SetValue(parent.children[1].children[0], value.GetResourceRef().name, sameValue), editable && sameValue);
+ SetEditable(parent.children[1].children[1], editable && sameValue); // If editable then can pick
+ for (uint i = 2; i < parent.children[1].numChildren; ++i)
+ SetEditable(parent.children[1].children[i], sameValue); // If same value then can open/edit/test
+ }
+ else if (type == VAR_RESOURCEREFLIST)
+ {
+ UIElement@ list = parent.parent;
+ ResourceRefList refList = value.GetResourceRefList();
+ for (uint subIndex = 0; subIndex < refList.length; ++subIndex)
+ {
+ parent = GetAttributeEditorParent(list, index, subIndex);
+ if (parent is null)
+ break;
+
+ String firstName = refList.names[subIndex];
+ bool nameSameValue = true;
+ if (!sameValue)
+ {
+ // Reevaluate each name in the list
+ for (uint i = 0; i < values.length; ++i)
+ {
+ ResourceRefList rList = values[i].GetResourceRefList();
+ if (subIndex >= rList.length || rList.names[subIndex] != firstName)
+ {
+ nameSameValue = false;
+ break;
+ }
+ }
+ }
+ SetEditable(SetValue(parent.children[1].children[0], firstName, nameSameValue), editable && nameSameValue);
+ }
+ }
+ else if (type == VAR_VARIANTVECTOR)
+ {
+ UIElement@ list = parent.parent;
+ Array@ vector = value.GetVariantVector();
+ for (uint subIndex = 0; subIndex < vector.length; ++subIndex)
+ {
+ parent = GetAttributeEditorParent(list, index, subIndex);
+ if (parent is null)
+ break;
+
+ Variant firstValue = vector[subIndex];
+ bool sameVal = true;
+ Array varValues;
+
+ // Reevaluate each variant in the vector
+ for (uint i = 0; i < values.length; ++i)
+ {
+ Array@ vec = values[i].GetVariantVector();
+ if (subIndex < vec.length)
+ {
+ Variant v = vec[subIndex];
+ varValues.Push(v);
+ if (v != firstValue)
+ sameVal = false;
+ }
+ else
+ sameVal = false;
+ }
+
+ // The individual variant in the list is not an attribute of the serializable, the structure is reused for convenience
+ AttributeInfo ai;
+ ai.type = firstValue.type;
+ LoadAttributeEditor(parent, firstValue, ai, editable, sameVal, varValues);
+ }
+ }
+ else if (type == VAR_VARIANTMAP)
+ {
+ UIElement@ list = parent.parent;
+ VariantMap map = value.GetVariantMap();
+ Array@ keys = map.keys;
+ for (uint subIndex = 0; subIndex < keys.length; ++subIndex)
+ {
+ parent = GetAttributeEditorParent(list, index, subIndex);
+ if (parent is null)
+ break;
+
+ String varName = GetVarName(keys[subIndex]);
+ if (varName.empty)
+ varName = keys[subIndex].ToString(); // Use hexadecimal if nothing else is available
+
+ Variant firstValue = map[keys[subIndex]];
+ bool sameVal = true;
+ Array varValues;
+
+ // Reevaluate each variant in the map
+ for (uint i = 0; i < values.length; ++i)
+ {
+ VariantMap m = values[i].GetVariantMap();
+ if (m.Contains(keys[subIndex]))
+ {
+ Variant v = m[keys[subIndex]];
+ varValues.Push(v);
+ if (v != firstValue)
+ sameVal = false;
+ }
+ else
+ sameVal = false;
+ }
+
+ // The individual variant in the map is not an attribute of the serializable, the structure is reused for convenience
+ AttributeInfo ai;
+ ai.type = firstValue.type;
+ LoadAttributeEditor(parent, firstValue, ai, editable, sameVal, varValues);
+ }
+ }
+ else
+ {
+ Array > coordinates;
+ for (uint i = 0; i < values.length; ++i)
+ {
+ Variant v = values[i];
+
+ // Convert Quaternion value to Vector3 value first
+ if (type == VAR_QUATERNION)
+ v = v.GetQuaternion().eulerAngles;
+
+ coordinates.Push(v.ToString().Split(' '));
+ }
+ for (uint i = 0; i < coordinates[0].length; ++i)
+ {
+ String str = coordinates[0][i];
+ bool coordinateSameValue = true;
+ if (!sameValue)
+ {
+ // Reevaluate each coordinate
+ for (uint j = 1; j < coordinates.length; ++j)
+ {
+ if (coordinates[j][i] != str)
+ {
+ coordinateSameValue = false;
+ break;
+ }
+ }
+ }
+ SetEditable(SetValue(parent.children[i + 1], str, coordinateSameValue), editable && coordinateSameValue);
+ }
+ }
+}
+
+void StoreAttributeEditor(UIElement@ parent, Array@ serializables, uint index, uint subIndex, uint coordinate)
+{
+ AttributeInfo info = serializables[0].attributeInfos[index];
+
+ if (info.type == VAR_RESOURCEREFLIST)
+ {
+ for (uint i = 0; i < serializables.length; ++i)
+ {
+ ResourceRefList refList = serializables[i].attributes[index].GetResourceRefList();
+ Variant[] values(1);
+ GetEditorValue(parent, VAR_RESOURCEREF, null, coordinate, values);
+ ResourceRef ref = values[0].GetResourceRef();
+ refList.names[subIndex] = ref.name;
+ serializables[i].attributes[index] = Variant(refList);
+ }
+ }
+ else if (info.type == VAR_VARIANTVECTOR)
+ {
+ for (uint i = 0; i < serializables.length; ++i)
+ {
+ Array@ vector = serializables[i].attributes[index].GetVariantVector();
+ Variant[] values;
+ values.Push(vector[subIndex]); // Each individual variant may have multiple coordinates itself
+ GetEditorValue(parent, vector[subIndex].type, null, coordinate, values);
+ vector[subIndex] = values[0];
+ serializables[i].attributes[index] = Variant(vector);
+ }
+ }
+ else if (info.type == VAR_VARIANTMAP)
+ {
+ StringHash key(parent.vars["Key"].GetUInt());
+ for (uint i = 0; i < serializables.length; ++i)
+ {
+ VariantMap map = serializables[i].attributes[index].GetVariantMap();
+ Variant[] values;
+ values.Push(map[key]); // Each individual variant may have multiple coordinates itself
+ GetEditorValue(parent, map[key].type, null, coordinate, values);
+ map[key] = values[0];
+ serializables[i].attributes[index] = Variant(map);
+ }
+ }
+ else
+ {
+ Array values;
+ for (uint i = 0; i < serializables.length; ++i)
+ values.Push(serializables[i].attributes[index]);
+ GetEditorValue(parent, info.type, info.enumNames, coordinate, values);
+ for (uint i = 0; i < serializables.length; ++i)
+ serializables[i].attributes[index] = values[i];
+ }
+}
+
+void FillValue(Array& values, const Variant&in value)
+{
+ for (uint i = 0; i < values.length; ++i)
+ values[i] = value;
+}
+
+void SanitizeNumericalValue(VariantType type, String& value)
+{
+ if ((type >= VAR_FLOAT && type <= VAR_COLOR) || type == VAR_RECT)
+ value = String(value.ToFloat());
+ else if (type == VAR_INT || type == VAR_INTRECT || type == VAR_INTVECTOR2 || type == VAR_INTVECTOR3)
+ value = String(value.ToInt());
+ else if (type == VAR_DOUBLE)
+ value = String(value.ToDouble());
+}
+
+void GetEditorValue(UIElement@ parent, VariantType type, Array@ enumNames, uint coordinate, Array& values)
+{
+ LineEdit@ attrEdit = parent.children[coordinate + 1];
+
+ if (attrEdit is null)
+ attrEdit = parent.GetChild("LineEdit", true);
+
+ if (type == VAR_STRING)
+ FillValue(values, Variant(attrEdit.text.Trimmed()));
+ else if (type == VAR_BOOL)
+ {
+ CheckBox@ cb = parent.children[1];
+ FillValue(values, Variant(cb.checked));
+ }
+ else if (type == VAR_FLOAT)
+ FillValue(values, Variant(attrEdit.text.ToFloat()));
+ else if (type == VAR_DOUBLE)
+ FillValue(values, Variant(attrEdit.text.ToDouble()));
+ else if (type == VAR_QUATERNION)
+ {
+ float value = attrEdit.text.ToFloat();
+ for (uint i = 0; i < values.length; ++i)
+ {
+ float[] data = values[i].GetQuaternion().eulerAngles.data;
+ data[coordinate] = value;
+ values[i] = Quaternion(Vector3(data));
+ }
+ }
+ else if (type == VAR_INT)
+ {
+ if (enumNames is null || enumNames.empty)
+ FillValue(values, Variant(attrEdit.text.ToInt()));
+ else
+ {
+ DropDownList@ ddl = parent.children[1];
+ FillValue(values, Variant(ddl.selection));
+ }
+ }
+ else if (type == VAR_RESOURCEREF)
+ {
+ LineEdit@ le = parent.children[0];
+ ResourceRef ref;
+ ref.name = le.text.Trimmed();
+ ref.type = StringHash(le.vars[TYPE_VAR].GetUInt());
+ FillValue(values, Variant(ref));
+ }
+ else
+ {
+ String value = attrEdit.text;
+ SanitizeNumericalValue(type, value);
+ for (uint i = 0; i < values.length; ++i)
+ {
+ String[] data = values[i].ToString().Split(' ');
+ data[coordinate] = value;
+ values[i] = Variant(type, Join(data, " "));
+ }
+ }
+}
+
+void UpdateAttributes(Array@ serializables, ListView@ list, bool& fullUpdate)
+{
+ // If attributes have changed structurally, do a full update
+ uint count = GetAttributeEditorCount(serializables);
+ if (fullUpdate == false)
+ {
+ if (list.contentElement.numChildren != count)
+ fullUpdate = true;
+ }
+
+ // Remember the old scroll position so that a full update does not feel as jarring
+ IntVector2 oldViewPos = list.viewPosition;
+
+ if (fullUpdate)
+ {
+ list.RemoveAllItems();
+ Array children = list.GetChildren();
+ for (uint i = 0; i < children.length; ++i)
+ {
+ if (!children[i].internal)
+ children[i].Remove();
+ }
+ }
+
+ if (serializables.empty)
+ return;
+
+ // If there are many serializables, they must share same attribute structure (up to certain number if not all)
+ for (uint i = 0; i < serializables[0].numAttributes; ++i)
+ {
+ AttributeInfo info = serializables[0].attributeInfos[i];
+ if (!showNonEditableAttribute && info.mode & AM_NOEDIT != 0)
+ continue;
+
+ // Use the default value (could be instance's default value) of the first serializable as the default for all
+ info.defaultValue = serializables[0].attributeDefaults[i];
+
+ if (fullUpdate)
+ CreateAttributeEditor(list, serializables, info, i, 0);
+
+ LoadAttributeEditor(list, serializables, info, i);
+ }
+
+ if (fullUpdate)
+ list.viewPosition = oldViewPos;
+}
+
+void CreateDragSlider(LineEdit@ parent)
+{
+ Button@ dragSld = Button();
+ dragSld.style = "EditorDragSlider";
+ dragSld.SetFixedHeight(ATTR_HEIGHT - 3);
+ dragSld.SetFixedWidth(dragSld.height);
+ dragSld.SetAlignment(HA_RIGHT, VA_TOP);
+ dragSld.focusMode = FM_NOTFOCUSABLE;
+ parent.AddChild(dragSld);
+
+ SubscribeToEvent(dragSld, "DragBegin", "LineDragBegin");
+ SubscribeToEvent(dragSld, "DragMove", "LineDragMove");
+ SubscribeToEvent(dragSld, "DragEnd", "LineDragEnd");
+ SubscribeToEvent(dragSld, "DragCancel", "LineDragCancel");
+}
+
+void EditAttribute(StringHash eventType, VariantMap& eventData)
+{
+ // Changing elements programmatically may cause events to be sent. Stop possible infinite loop in that case.
+ if (inLoadAttributeEditor)
+ return;
+
+ UIElement@ attrEdit = eventData["Element"].GetPtr();
+ UIElement@ parent = attrEdit.parent;
+ Array@ serializables = GetAttributeEditorTargets(attrEdit);
+ if (serializables.empty)
+ return;
+
+ uint index = attrEdit.vars["Index"].GetUInt();
+ uint subIndex = attrEdit.vars["SubIndex"].GetUInt();
+ uint coordinate = attrEdit.vars["Coordinate"].GetUInt();
+ bool intermediateEdit = eventType == TEXT_CHANGED_EVENT_TYPE;
+
+ // Do the editor pre logic before attribute is being modified
+ if (!PreEditAttribute(serializables, index))
+ return;
+
+ inEditAttribute = true;
+
+ Array oldValues;
+
+ if (!dragEditAttribute)
+ {
+ // Store old values so that PostEditAttribute can create undo actions
+ for (uint i = 0; i < serializables.length; ++i)
+ oldValues.Push(serializables[i].attributes[index]);
+ }
+
+ StoreAttributeEditor(parent, serializables, index, subIndex, coordinate);
+ for (uint i = 0; i < serializables.length; ++i)
+ serializables[i].ApplyAttributes();
+
+ if (!dragEditAttribute)
+ {
+ // Do the editor post logic after attribute has been modified.
+ PostEditAttribute(serializables, index, oldValues);
+ }
+
+ inEditAttribute = false;
+
+ // If not an intermediate edit, reload the editor fields with validated values
+ // (attributes may have interactions; therefore we load everything, not just the value being edited)
+ if (!intermediateEdit)
+ attributesDirty = true;
+}
+
+void LineDragBegin(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ label = eventData["Element"].GetPtr();
+ int x = eventData["X"].GetInt();
+ label.vars["posX"] = x;
+
+ // Store the old value before dragging
+ dragEditAttribute = false;
+ LineEdit@ selectedNumEditor = label.parent;
+
+ selectedNumEditor.vars["DragBeginValue"] = selectedNumEditor.text;
+ selectedNumEditor.cursorPosition = 0;
+
+ // Set mouse mode to user preference
+ SetMouseMode(true);
+}
+
+void LineDragMove(StringHash eventTypem, VariantMap& eventData)
+{
+ UIElement@ label = eventData["Element"].GetPtr();
+ LineEdit@ selectedNumEditor = label.parent;
+
+ // Prevent undo
+ dragEditAttribute = true;
+
+ int x = eventData["X"].GetInt();
+ int posx = label.vars["posX"].GetInt();
+ float val = input.mouseMoveX;
+
+ float fieldVal = selectedNumEditor.text.ToFloat();
+ fieldVal += val/100;
+ label.vars["posX"] = x;
+ selectedNumEditor.text = fieldVal;
+ selectedNumEditor.cursorPosition = 0;
+}
+
+void LineDragEnd(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ label = eventData["Element"].GetPtr();
+ LineEdit@ selectedNumEditor = label.parent;
+
+ // Prepare the attributes to store an undo with:
+ // - old value = drag begin value
+ // - new value = final value
+
+ String finalValue = selectedNumEditor.text;
+ // Reset attribute to begin value, and prevent undo
+ dragEditAttribute = true;
+ selectedNumEditor.text = selectedNumEditor.vars["DragBeginValue"].GetString();
+
+ // Store final value, allow undo
+ dragEditAttribute = false;
+ selectedNumEditor.text = finalValue;
+ selectedNumEditor.cursorPosition = 0;
+
+ // Revert mouse to normal behaviour
+ SetMouseMode(false);
+}
+
+void LineDragCancel(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ label = eventData["Element"].GetPtr();
+
+ // Reset value to what it was when drag edit began, preventing undo.
+ dragEditAttribute = true;
+ LineEdit@ selectedNumEditor = label.parent;
+ selectedNumEditor.text = selectedNumEditor.vars["DragBeginValue"].GetString();
+ selectedNumEditor.cursorPosition = 0;
+
+ // Revert mouse to normal behaviour
+ SetMouseMode(false);
+}
+
+// Resource picker functionality
+const uint ACTION_PICK = 1;
+const uint ACTION_OPEN = 2;
+const uint ACTION_EDIT = 4;
+const uint ACTION_TEST = 8;
+
+class ResourcePicker
+{
+ String typeName;
+ StringHash type;
+ String lastPath;
+ uint lastFilter;
+ Array filters;
+ uint actions;
+
+ ResourcePicker(const String&in typeName_, const String&in filter_, uint actions_ = ACTION_PICK | ACTION_OPEN)
+ {
+ typeName = typeName_;
+ type = StringHash(typeName_);
+ actions = actions_;
+ filters.Push(filter_);
+ filters.Push("*.*");
+ lastFilter = 0;
+ }
+
+ ResourcePicker(const String&in typeName_, const Array@ filters_, uint actions_ = ACTION_PICK | ACTION_OPEN)
+ {
+ typeName = typeName_;
+ type = StringHash(typeName_);
+ filters = filters_;
+ actions = actions_;
+ filters.Push("*.*");
+ lastFilter = 0;
+ }
+};
+
+Array resourcePickers;
+Array resourceTargets;
+uint resourcePickIndex = 0;
+uint resourcePickSubIndex = 0;
+ResourcePicker@ resourcePicker = null;
+
+void InitResourcePicker()
+{
+ // Fill resource picker data
+ Array fontFilters = {"*.ttf", "*.otf", "*.fnt", "*.xml", "*.sdf"};
+ Array imageFilters = {"*.png", "*.jpg", "*.bmp", "*.tga", "*.hdr"};
+ Array luaFileFilters = {"*.lua", "*.luc"};
+ Array scriptFilters = {"*.as", "*.asc"};
+ Array soundFilters = {"*.wav","*.ogg"};
+ Array textureFilters = {"*.dds", "*.png", "*.jpg", "*.bmp", "*.tga", "*.ktx", "*.pvr", "*.hdr"};
+ Array materialFilters = {"*.xml", "*.material", "*.json"};
+ Array anmSetFilters = {"*.scml"};
+ Array pexFilters = {"*.pex"};
+ Array tmxFilters = {"*.tmx"};
+ resourcePickers.Push(ResourcePicker("Animation", "*.ani", ACTION_PICK | ACTION_TEST));
+ resourcePickers.Push(ResourcePicker("Font", fontFilters));
+ resourcePickers.Push(ResourcePicker("Image", imageFilters));
+ resourcePickers.Push(ResourcePicker("LuaFile", luaFileFilters));
+ resourcePickers.Push(ResourcePicker("Material", materialFilters, ACTION_PICK | ACTION_OPEN | ACTION_EDIT));
+ resourcePickers.Push(ResourcePicker("Model", "*.mdl", ACTION_PICK));
+ resourcePickers.Push(ResourcePicker("ParticleEffect", "*.xml", ACTION_PICK | ACTION_OPEN | ACTION_EDIT));
+ resourcePickers.Push(ResourcePicker("ScriptFile", scriptFilters));
+ resourcePickers.Push(ResourcePicker("Sound", soundFilters));
+ resourcePickers.Push(ResourcePicker("Technique", "*.xml"));
+ resourcePickers.Push(ResourcePicker("Texture2D", textureFilters));
+ resourcePickers.Push(ResourcePicker("TextureCube", "*.xml"));
+ resourcePickers.Push(ResourcePicker("Texture3D", "*.xml"));
+ resourcePickers.Push(ResourcePicker("XMLFile", "*.xml"));
+ resourcePickers.Push(ResourcePicker("JSONFile", "*.json"));
+ resourcePickers.Push(ResourcePicker("Sprite2D", textureFilters, ACTION_PICK | ACTION_OPEN));
+ resourcePickers.Push(ResourcePicker("AnimationSet2D", anmSetFilters, ACTION_PICK | ACTION_OPEN));
+ resourcePickers.Push(ResourcePicker("ParticleEffect2D", pexFilters, ACTION_PICK | ACTION_OPEN));
+ resourcePickers.Push(ResourcePicker("TmxFile2D", tmxFilters, ACTION_PICK | ACTION_OPEN));
+}
+
+ResourcePicker@ GetResourcePicker(StringHash resourceType)
+{
+ for (uint i = 0; i < resourcePickers.length; ++i)
+ {
+ if (resourcePickers[i].type == resourceType)
+ return resourcePickers[i];
+ }
+ return null;
+}
+
+void PickResource(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ button = eventData["Element"].GetPtr();
+ LineEdit@ attrEdit = button.parent.children[0];
+
+ Array@ targets = GetAttributeEditorTargets(attrEdit);
+ if (targets.empty)
+ return;
+
+ resourcePickIndex = attrEdit.vars["Index"].GetUInt();
+ resourcePickSubIndex = attrEdit.vars["SubIndex"].GetUInt();
+ AttributeInfo info = targets[0].attributeInfos[resourcePickIndex];
+
+ StringHash resourceType;
+ if (info.type == VAR_RESOURCEREF)
+ resourceType = targets[0].attributes[resourcePickIndex].GetResourceRef().type;
+ else if (info.type == VAR_RESOURCEREFLIST)
+ resourceType = targets[0].attributes[resourcePickIndex].GetResourceRefList().type;
+ else if (info.type == VAR_VARIANTVECTOR)
+ resourceType = targets[0].attributes[resourcePickIndex].GetVariantVector()[resourcePickSubIndex].GetResourceRef().type;
+
+ @resourcePicker = GetResourcePicker(resourceType);
+ if (resourcePicker is null)
+ return;
+
+ resourceTargets.Clear();
+ for (uint i = 0; i < targets.length; ++i)
+ resourceTargets.Push(targets[i]);
+
+ String lastPath = resourcePicker.lastPath;
+ if (lastPath.empty)
+ lastPath = sceneResourcePath;
+ CreateFileSelector(localization.Get("Pick ") + resourcePicker.typeName, "OK", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter, false);
+ SubscribeToEvent(uiFileSelector, "FileSelected", "PickResourceDone");
+}
+
+void PickResourceDone(StringHash eventType, VariantMap& eventData)
+{
+ StoreResourcePickerPath();
+ CloseFileSelector();
+
+ if (!eventData["OK"].GetBool())
+ {
+ resourceTargets.Clear();
+ @resourcePicker = null;
+ return;
+ }
+
+ if (resourcePicker is null)
+ return;
+
+ // Validate the resource. It must come from within a registered resource directory, and be loaded successfully
+ String resourceName = eventData["FileName"].GetString();
+ Resource@ res = GetPickedResource(resourceName);
+ if (res is null)
+ {
+ @resourcePicker = null;
+ return;
+ }
+
+ // Store old values so that PostEditAttribute can create undo actions
+ Array oldValues;
+ for (uint i = 0; i < resourceTargets.length; ++i)
+ oldValues.Push(resourceTargets[i].attributes[resourcePickIndex]);
+
+ for (uint i = 0; i < resourceTargets.length; ++i)
+ {
+ Serializable@ target = resourceTargets[i];
+
+ AttributeInfo info = target.attributeInfos[resourcePickIndex];
+ if (info.type == VAR_RESOURCEREF)
+ {
+ ResourceRef ref = target.attributes[resourcePickIndex].GetResourceRef();
+ ref.type = res.type;
+ ref.name = res.name;
+ target.attributes[resourcePickIndex] = Variant(ref);
+ target.ApplyAttributes();
+ }
+ else if (info.type == VAR_RESOURCEREFLIST)
+ {
+ ResourceRefList refList = target.attributes[resourcePickIndex].GetResourceRefList();
+ if (resourcePickSubIndex < refList.length)
+ {
+ refList.names[resourcePickSubIndex] = res.name;
+ target.attributes[resourcePickIndex] = Variant(refList);
+ target.ApplyAttributes();
+ }
+ }
+ else if (info.type == VAR_VARIANTVECTOR)
+ {
+ Array@ attrs = target.attributes[resourcePickIndex].GetVariantVector();
+ ResourceRef ref = attrs[resourcePickSubIndex].GetResourceRef();
+ ref.type = res.type;
+ ref.name = res.name;
+ attrs[resourcePickSubIndex] = ref;
+ target.attributes[resourcePickIndex] = Variant(attrs);
+ target.ApplyAttributes();
+ }
+ }
+
+ PostEditAttribute(resourceTargets, resourcePickIndex, oldValues);
+ UpdateAttributeInspector(false);
+
+ resourceTargets.Clear();
+ @resourcePicker = null;
+}
+
+void StoreResourcePickerPath()
+{
+ // Store filter and directory for next time
+ if (resourcePicker !is null && uiFileSelector !is null)
+ {
+ resourcePicker.lastPath = uiFileSelector.path;
+ resourcePicker.lastFilter = uiFileSelector.filterIndex;
+ }
+}
+
+Resource@ GetPickedResource(String resourceName)
+{
+ resourceName = GetResourceNameFromFullName(resourceName);
+ String type = resourcePicker.typeName;
+ // Cube and 3D textures both use .xml extension. In that case interrogate the proper resource type
+ // from the file itself
+ if (type == "Texture3D" || type == "TextureCube")
+ {
+ XMLFile@ xmlRes = cache.GetResource("XMLFile", resourceName);
+ if (xmlRes !is null)
+ {
+ if (xmlRes.root.name.Compare("cubemap", false) == 0 || xmlRes.root.name.Compare("texturecube", false) == 0)
+ type = "TextureCube";
+ else if (xmlRes.root.name.Compare("texture3d", false) == 0)
+ type = "Texture3D";
+ }
+ }
+
+ Resource@ res = cache.GetResource(type, resourceName);
+
+ if (res is null)
+ log.Warning("Cannot find resource type: " + type + " Name:" + resourceName);
+
+ return res;
+}
+
+String GetResourceNameFromFullName(const String&in resourceName)
+{
+ Array@ resourceDirs = cache.resourceDirs;
+
+ for (uint i = 0; i < resourceDirs.length; ++i)
+ {
+ if (!resourceName.ToLower().StartsWith(resourceDirs[i].ToLower()))
+ continue;
+ return resourceName.Substring(resourceDirs[i].length);
+ }
+
+ return ""; // Not found
+}
+
+void OpenResource(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ button = eventData["Element"].GetPtr();
+ LineEdit@ attrEdit = button.parent.children[0];
+
+ String fileName = attrEdit.text.Trimmed();
+ if (fileName.empty)
+ return;
+
+ OpenResource(fileName);
+}
+
+void OpenResource(String fileName)
+{
+ Array@ resourceDirs = cache.resourceDirs;
+ for (uint i = 0; i < resourceDirs.length; ++i)
+ {
+ String fullPath = resourceDirs[i] + fileName;
+ if (fileSystem.FileExists(fullPath))
+ {
+ fileSystem.SystemOpen(fullPath, "");
+ return;
+ }
+ }
+}
+
+void EditResource(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ button = eventData["Element"].GetPtr();
+ LineEdit@ attrEdit = button.parent.children[0];
+
+ String fileName = attrEdit.text.Trimmed();
+ if (fileName.empty)
+ return;
+
+ StringHash resourceType(attrEdit.vars[TYPE_VAR].GetUInt());
+ Resource@ resource = cache.GetResource(resourceType, fileName);
+
+ if (resource !is null)
+ {
+ // For now only Materials can be edited
+ if (resource.typeName == "Material")
+ EditMaterial(cast(resource));
+ else if (resource.typeName == "ParticleEffect")
+ EditParticleEffect(cast(resource));
+ }
+}
+
+void TestResource(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ button = eventData["Element"].GetPtr();
+ LineEdit@ attrEdit = button.parent.children[0];
+
+ StringHash resourceType(attrEdit.vars[TYPE_VAR].GetUInt());
+
+ // For now only Animations can be tested
+ StringHash animType("Animation");
+ if (resourceType == animType)
+ TestAnimation(attrEdit);
+}
+
+void TestAnimation(UIElement@ attrEdit)
+{
+ // Note: only supports the AnimationState array in AnimatedModel, and if only 1 model selected
+ Array@ targets = GetAttributeEditorTargets(attrEdit);
+ if (targets.length != 1)
+ return;
+ AnimatedModel@ model = cast(targets[0]);
+ if (model is null)
+ return;
+
+ uint animStateIndex = (attrEdit.vars["SubIndex"].GetUInt() - 1) / 6;
+ if (testAnimState.Get() is null)
+ {
+ testAnimState = model.GetAnimationState(animStateIndex);
+ AnimationState@ animState = testAnimState.Get();
+ if (animState !is null)
+ animState.time = 0; // Start from beginning
+ }
+ else
+ testAnimState = null;
+}
+
+void UpdateTestAnimation(float timeStep)
+{
+ AnimationState@ animState = testAnimState.Get();
+ if (animState !is null)
+ {
+ // If has also an AnimationController, and scene update is enabled, check if it is also driving the animation
+ // and skip in that case (avoid double speed animation)
+ if (runUpdate)
+ {
+ AnimatedModel@ model = animState.model;
+ if (model !is null)
+ {
+ Node@ node = model.node;
+ if (node !is null)
+ {
+ AnimationController@ ctrl = node.GetComponent("AnimationController");
+ Animation@ anim = animState.animation;
+ if (ctrl !is null && anim !is null)
+ {
+ if (ctrl.IsPlaying(anim.name))
+ return;
+ }
+ }
+ }
+ }
+
+ animState.AddTime(timeStep);
+ }
+}
+
+// VariantVector decoding & editing for certain components
+
+class VectorStruct
+{
+ String componentTypeName;
+ String attributeName;
+ Array variableNames;
+ uint restartIndex;
+
+ VectorStruct(const String&in componentTypeName_, const String&in attributeName_, const Array@ variableNames_, uint restartIndex_)
+ {
+ componentTypeName = componentTypeName_;
+ attributeName = attributeName_;
+ variableNames = variableNames_;
+ restartIndex = restartIndex_;
+ }
+};
+
+Array vectorStructs;
+
+void InitVectorStructs()
+{
+ Array crowdManagerAreaCostVariables = {
+ " Area Count",
+ " Cost"
+ };
+ vectorStructs.Push(VectorStruct("CrowdManager", " >AreaCost", crowdManagerAreaCostVariables, 1));
+
+ Array categories = GetObjectCategories();
+ for (uint categoryIndex = 0; categoryIndex < categories.length; categoryIndex++)
+ {
+ Array objectsNames = GetObjectsByCategory(categories[categoryIndex]);
+ for (uint objectIndex = 0; objectIndex < objectsNames.length; objectIndex++)
+ {
+ String objectName = objectsNames[objectIndex];
+ Array attributes = GetObjectAttributeInfos(objectName);
+
+ for (uint attributeIndex = 0; attributeIndex < attributes.length; attributeIndex++)
+ {
+ AttributeInfo attribute = attributes[attributeIndex];
+ if (attribute.type == VAR_VARIANTVECTOR and attribute.metadata.Contains("VectorStructElements"))
+ {
+ Array@ elementsNames = attribute.metadata["VectorStructElements"].GetStringVector();
+ vectorStructs.Push(VectorStruct(objectName, attribute.name, elementsNames, 1));
+ }
+ }
+ }
+ }
+}
+
+VectorStruct@ GetVectorStruct(Array@ serializables, uint index)
+{
+ AttributeInfo info = serializables[0].attributeInfos[index];
+ for (uint i = 0; i < vectorStructs.length; ++i)
+ {
+ if (vectorStructs[i].componentTypeName == serializables[0].typeName && vectorStructs[i].attributeName == info.name)
+ return vectorStructs[i];
+ }
+ return null;
+}
+
+VectorStruct@ GetNestedVectorStruct(Array@ serializables, const String&in name)
+{
+ for (uint i = 0; i < vectorStructs.length; ++i)
+ {
+ if (vectorStructs[i].componentTypeName == serializables[0].typeName && vectorStructs[i].attributeName == name)
+ return vectorStructs[i];
+ }
+ return null;
+}
+
+int GetAttributeIndex(Serializable@ serializable, const String&in attrName)
+{
+ for (uint i = 0; i < serializable.numAttributes; ++i)
+ {
+ if (serializable.attributeInfos[i].name.Compare(attrName, false) == 0)
+ return i;
+ }
+
+ return -1;
+}
diff --git a/bin/Data/Scripts/Editor/EditorActions.as b/bin/Data/Scripts/Editor/EditorActions.as
new file mode 100644
index 0000000..8a1b88e
--- /dev/null
+++ b/bin/Data/Scripts/Editor/EditorActions.as
@@ -0,0 +1,1152 @@
+class EditAction
+{
+ void Undo()
+ {
+ }
+
+ void Redo()
+ {
+ }
+}
+
+class EditActionGroup
+{
+ Array actions;
+}
+
+class CreateDrawableMaskAction : EditAction
+{
+ uint nodeID;
+ uint drawableID;
+ int oldMask;
+ int redoMask;
+ int typeMask;
+
+ void Define(Drawable@ drawable, int editMaskType)
+ {
+ drawableID = drawable.id;
+ nodeID = drawable.node.id;
+
+ switch (editMaskType)
+ {
+ case EDIT_VIEW_MASK:
+ oldMask = drawable.viewMask;
+ break;
+ case EDIT_LIGHT_MASK:
+ oldMask = drawable.lightMask;
+ break;
+ case EDIT_SHADOW_MASK:
+ oldMask = drawable.shadowMask;
+ break;
+ case EDIT_ZONE_MASK:
+ oldMask = drawable.zoneMask;
+ break;
+ }
+
+ typeMask = editMaskType;
+ redoMask = oldMask;
+ }
+
+ void Undo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ Drawable@ drawable = editorScene.GetComponent(drawableID);
+ if (node !is null && drawable !is null)
+ {
+ switch (typeMask)
+ {
+ case EDIT_VIEW_MASK:
+ redoMask = drawable.viewMask;
+ drawable.viewMask = oldMask;
+ break;
+ case EDIT_LIGHT_MASK:
+ redoMask = drawable.lightMask;
+ drawable.lightMask = oldMask;
+ break;
+ case EDIT_SHADOW_MASK:
+ redoMask = drawable.shadowMask;
+ drawable.shadowMask = oldMask;
+ break;
+ case EDIT_ZONE_MASK:
+ redoMask = drawable.zoneMask;
+ drawable.zoneMask = oldMask;
+ break;
+ }
+ }
+ }
+
+ void Redo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ Drawable@ drawable = editorScene.GetComponent(drawableID);
+ if (node !is null && drawable !is null)
+ {
+ switch (typeMask)
+ {
+ case EDIT_VIEW_MASK:
+ oldMask = drawable.viewMask;
+ drawable.viewMask = redoMask;
+ break;
+ case EDIT_LIGHT_MASK:
+ oldMask = drawable.lightMask;
+ drawable.lightMask = redoMask;
+ break;
+ case EDIT_SHADOW_MASK:
+ oldMask = drawable.shadowMask;
+ drawable.shadowMask = redoMask;
+ break;
+ case EDIT_ZONE_MASK:
+ oldMask = drawable.zoneMask;
+ drawable.zoneMask = redoMask;
+ break;
+ }
+ }
+ }
+}
+
+class CreateNodeAction : EditAction
+{
+ uint nodeID;
+ uint parentID;
+ XMLFile@ nodeData;
+
+ void Define(Node@ node)
+ {
+ nodeID = node.id;
+ parentID = node.parent.id;
+ nodeData = XMLFile();
+ XMLElement rootElem = nodeData.CreateRoot("node");
+ node.SaveXML(rootElem);
+ }
+
+ void Undo()
+ {
+ Node@ parent = editorScene.GetNode(parentID);
+ Node@ node = editorScene.GetNode(nodeID);
+ if (parent !is null && node !is null)
+ {
+ parent.RemoveChild(node);
+ hierarchyList.ClearSelection();
+ }
+ }
+
+ void Redo()
+ {
+ Node@ parent = editorScene.GetNode(parentID);
+ if (parent !is null)
+ {
+ Node@ node = parent.CreateChild("", IsReplicatedID(nodeID) ? REPLICATED : LOCAL, nodeID);
+ node.LoadXML(nodeData.root);
+ FocusNode(node);
+ }
+ }
+}
+
+class DeleteNodeAction : EditAction
+{
+ uint nodeID;
+ uint parentID;
+ XMLFile@ nodeData;
+
+ void Define(Node@ node)
+ {
+ nodeID = node.id;
+ parentID = node.parent.id;
+ nodeData = XMLFile();
+ XMLElement rootElem = nodeData.CreateRoot("node");
+ node.SaveXML(rootElem);
+ rootElem.SetUInt("listItemIndex", GetListIndex(node));
+ }
+
+ void Undo()
+ {
+ Node@ parent = editorScene.GetNode(parentID);
+ if (parent !is null)
+ {
+ // Handle update manually so that the node can be reinserted back into its previous list index
+ suppressSceneChanges = true;
+
+ Node@ node = parent.CreateChild("", IsReplicatedID(nodeID) ? REPLICATED : LOCAL, nodeID);
+ if (node.LoadXML(nodeData.root))
+ {
+ uint listItemIndex = nodeData.root.GetUInt("listItemIndex");
+ UIElement@ parentItem = hierarchyList.items[GetListIndex(parent)];
+ UpdateHierarchyItem(listItemIndex, node, parentItem);
+ FocusNode(node);
+ }
+
+ suppressSceneChanges = false;
+ }
+ }
+
+ void Redo()
+ {
+ Node@ parent = editorScene.GetNode(parentID);
+ Node@ node = editorScene.GetNode(nodeID);
+ if (parent !is null && node !is null)
+ {
+ parent.RemoveChild(node);
+ hierarchyList.ClearSelection();
+ }
+ }
+}
+
+class ReparentNodeAction : EditAction
+{
+ uint nodeID;
+ uint oldParentID;
+ uint newParentID;
+ Array nodeList; // 2 uints get inserted per node (node, node.parent)
+ bool multiple;
+
+ void Define(Node@ node, Node@ newParent)
+ {
+ multiple = false;
+ nodeID = node.id;
+ oldParentID = node.parent.id;
+ newParentID = newParent.id;
+ }
+
+ void Define(Array nodes, Node@ newParent)
+ {
+ multiple = true;
+ newParentID = newParent.id;
+ for(uint i = 0; i < nodes.length; ++i)
+ {
+ Node@ node = nodes[i];
+ nodeList.Push(node.id);
+ nodeList.Push(node.parent.id);
+ }
+ }
+
+ void Undo()
+ {
+ if (multiple)
+ {
+ for (uint i = 0; i < nodeList.length; i+=2)
+ {
+ uint nodeID_ = nodeList[i];
+ uint oldParentID_ = nodeList[i+1];
+ Node@ parent = editorScene.GetNode(oldParentID_);
+ Node@ node = editorScene.GetNode(nodeID_);
+ if (parent !is null && node !is null)
+ node.parent = parent;
+ }
+ }
+ else
+ {
+ Node@ parent = editorScene.GetNode(oldParentID);
+ Node@ node = editorScene.GetNode(nodeID);
+ if (parent !is null && node !is null)
+ node.parent = parent;
+ }
+ }
+
+ void Redo()
+ {
+ if (multiple)
+ {
+ Node@ parent = editorScene.GetNode(newParentID);
+ if (parent is null)
+ return;
+
+ for (uint i = 0; i < nodeList.length; i+=2)
+ {
+ uint nodeID_ = nodeList[i];
+ Node@ node = editorScene.GetNode(nodeID_);
+ if (node !is null)
+ node.parent = parent;
+ }
+ }
+ else
+ {
+ Node@ parent = editorScene.GetNode(newParentID);
+ Node@ node = editorScene.GetNode(nodeID);
+ if (parent !is null && node !is null)
+ node.parent = parent;
+ }
+ }
+}
+
+class ReorderNodeAction : EditAction
+{
+ uint nodeID;
+ uint parentID;
+ uint oldChildIndex;
+ uint newChildIndex;
+
+ void Define(Node@ node, uint newIndex)
+ {
+ nodeID = node.id;
+ parentID = node.parent.id;
+ oldChildIndex = SceneFindChildIndex(node.parent, node);
+ newChildIndex = newIndex;
+ }
+
+ void Undo()
+ {
+ Node@ parent = editorScene.GetNode(parentID);
+ Node@ node = editorScene.GetNode(nodeID);
+ if (parent !is null && node !is null)
+ PerformReorder(parent, node, oldChildIndex);
+ }
+
+ void Redo()
+ {
+ Node@ parent = editorScene.GetNode(parentID);
+ Node@ node = editorScene.GetNode(nodeID);
+ if (parent !is null && node !is null)
+ PerformReorder(parent, node, newChildIndex);
+ }
+}
+
+class ReorderComponentAction : EditAction
+{
+ uint componentID;
+ uint nodeID;
+ uint oldComponentIndex;
+ uint newComponentIndex;
+
+ void Define(Component@ component, uint newIndex)
+ {
+ componentID = component.id;
+ nodeID = component.node.id;
+ oldComponentIndex = SceneFindComponentIndex(component.node, component);
+ newComponentIndex = newIndex;
+ }
+
+ void Undo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ Component@ component = editorScene.GetComponent(componentID);
+ if (node !is null && component !is null)
+ PerformReorder(node, component, oldComponentIndex);
+ }
+
+ void Redo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ Component@ component = editorScene.GetComponent(componentID);
+ if (node !is null && component !is null)
+ PerformReorder(node, component, newComponentIndex);
+ }
+}
+
+class CreateComponentAction : EditAction
+{
+ uint nodeID;
+ uint componentID;
+ XMLFile@ componentData;
+
+ void Define(Component@ component)
+ {
+ componentID = component.id;
+ nodeID = component.node.id;
+ componentData = XMLFile();
+ XMLElement rootElem = componentData.CreateRoot("component");
+ component.SaveXML(rootElem);
+ }
+
+ void Undo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ Component@ component = editorScene.GetComponent(componentID);
+ if (node !is null && component !is null)
+ {
+ node.RemoveComponent(component);
+ hierarchyList.ClearSelection();
+ }
+ }
+
+ void Redo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ if (node !is null)
+ {
+ Component@ component = node.CreateComponent(componentData.root.GetAttribute("type"), IsReplicatedID(componentID) ?
+ REPLICATED : LOCAL, componentID);
+ component.LoadXML(componentData.root);
+ component.ApplyAttributes();
+ FocusComponent(component);
+ }
+ }
+
+}
+
+class DeleteComponentAction : EditAction
+{
+ uint nodeID;
+ uint componentID;
+ XMLFile@ componentData;
+
+ void Define(Component@ component)
+ {
+ componentID = component.id;
+ nodeID = component.node.id;
+ componentData = XMLFile();
+ XMLElement rootElem = componentData.CreateRoot("component");
+ component.SaveXML(rootElem);
+ rootElem.SetUInt("listItemIndex", GetComponentListIndex(component));
+ }
+
+ void Undo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ if (node !is null)
+ {
+ // Handle update manually so that the component can be reinserted back into its previous list index
+ suppressSceneChanges = true;
+
+ Component@ component = node.CreateComponent(componentData.root.GetAttribute("type"), IsReplicatedID(componentID) ?
+ REPLICATED : LOCAL, componentID);
+ if (component.LoadXML(componentData.root))
+ {
+ component.ApplyAttributes();
+
+ uint listItemIndex = componentData.root.GetUInt("listItemIndex");
+ UIElement@ parentItem = hierarchyList.items[GetListIndex(node)];
+ UpdateHierarchyItem(listItemIndex, component, parentItem);
+ FocusComponent(component);
+ }
+
+ suppressSceneChanges = false;
+ }
+ }
+
+ void Redo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ Component@ component = editorScene.GetComponent(componentID);
+ if (node !is null && component !is null)
+ {
+ node.RemoveComponent(component);
+ hierarchyList.ClearSelection();
+ }
+ }
+}
+
+class EditAttributeAction : EditAction
+{
+ int targetType;
+ uint targetID;
+ uint attrIndex;
+ Variant undoValue;
+ Variant redoValue;
+
+ void Define(Serializable@ target, uint index, const Variant&in oldValue)
+ {
+ attrIndex = index;
+ undoValue = oldValue;
+ redoValue = target.attributes[index];
+
+ targetType = GetType(target);
+ targetID = GetID(target, targetType);
+ }
+
+ Serializable@ GetTarget()
+ {
+ switch (targetType)
+ {
+ case ITEM_NODE:
+ return editorScene.GetNode(targetID);
+ case ITEM_COMPONENT:
+ return editorScene.GetComponent(targetID);
+ case ITEM_UI_ELEMENT:
+ return GetUIElementByID(targetID);
+ }
+
+ return null;
+ }
+
+ void Undo()
+ {
+ Serializable@ target = GetTarget();
+ if (target !is null)
+ {
+ target.attributes[attrIndex] = undoValue;
+ target.ApplyAttributes();
+ // Can't know if need a full update, so assume true
+ attributesFullDirty = true;
+ // Apply side effects
+ PostEditAttribute(target, attrIndex);
+
+ if (targetType == ITEM_UI_ELEMENT)
+ SetUIElementModified(target);
+ else
+ SetSceneModified();
+ }
+ }
+
+ void Redo()
+ {
+ Serializable@ target = GetTarget();
+ if (target !is null)
+ {
+ target.attributes[attrIndex] = redoValue;
+ target.ApplyAttributes();
+ // Can't know if need a full update, so assume true
+ attributesFullDirty = true;
+ // Apply side effects
+ PostEditAttribute(target, attrIndex);
+
+ if (targetType == ITEM_UI_ELEMENT)
+ SetUIElementModified(target);
+ else
+ SetSceneModified();
+ }
+ }
+}
+
+class ResetAttributesAction : EditAction
+{
+ int targetType;
+ uint targetID;
+ Array undoValues;
+ VariantMap internalVars; // UIElement specific
+
+ void Define(Serializable@ target)
+ {
+ for (uint i = 0; i < target.numAttributes; ++i)
+ undoValues.Push(target.attributes[i]);
+
+ targetType = GetType(target);
+ targetID = GetID(target, targetType);
+
+ if (targetType == ITEM_UI_ELEMENT)
+ {
+ // Special handling for UIElement to preserve the internal variables containing the element's generated ID among others
+ UIElement@ element = target;
+ Array keys = element.vars.keys;
+ for (uint i = 0; i < keys.length; ++i)
+ {
+ // If variable name is empty (or unregistered) then it is an internal variable and should be preserved
+ String name = GetVarName(keys[i]);
+ if (name.empty)
+ internalVars[keys[i]] = element.vars[keys[i]];
+ }
+ }
+ }
+
+ Serializable@ GetTarget()
+ {
+ switch (targetType)
+ {
+ case ITEM_NODE:
+ return editorScene.GetNode(targetID);
+ case ITEM_COMPONENT:
+ return editorScene.GetComponent(targetID);
+ case ITEM_UI_ELEMENT:
+ return GetUIElementByID(targetID);
+ }
+
+ return null;
+ }
+
+ void SetInternalVars(UIElement@ element)
+ {
+ // Revert back internal variables
+ Array keys = internalVars.keys;
+ for (uint i = 0; i < keys.length; ++i)
+ element.vars[keys[i]] = internalVars[keys[i]];
+
+ if (element.vars.Contains(FILENAME_VAR))
+ CenterDialog(element);
+ }
+
+ void Undo()
+ {
+ ui.cursor.shape = CS_BUSY;
+
+ Serializable@ target = GetTarget();
+ if (target !is null)
+ {
+ for (uint i = 0; i < target.numAttributes; ++i)
+ {
+ AttributeInfo info = target.attributeInfos[i];
+ if (info.mode & AM_NOEDIT != 0 || info.mode & AM_NODEID != 0 || info.mode & AM_COMPONENTID != 0)
+ continue;
+
+ target.attributes[i] = undoValues[i];
+ }
+ target.ApplyAttributes();
+
+ // Apply side effects
+ for (uint i = 0; i < target.numAttributes; ++i)
+ PostEditAttribute(target, i);
+
+ if (targetType == ITEM_UI_ELEMENT)
+ SetUIElementModified(target);
+ else
+ SetSceneModified();
+
+ attributesFullDirty = true;
+ }
+ }
+
+ void Redo()
+ {
+ ui.cursor.shape = CS_BUSY;
+
+ Serializable@ target = GetTarget();
+ if (target !is null)
+ {
+ for (uint i = 0; i < target.numAttributes; ++i)
+ {
+ AttributeInfo info = target.attributeInfos[i];
+ if (info.mode & AM_NOEDIT != 0 || info.mode & AM_NODEID != 0 || info.mode & AM_COMPONENTID != 0)
+ continue;
+
+ target.attributes[i] = target.attributeDefaults[i];
+ }
+ if (targetType == ITEM_UI_ELEMENT)
+ SetInternalVars(target);
+ target.ApplyAttributes();
+
+ // Apply side effects
+ for (uint i = 0; i < target.numAttributes; ++i)
+ PostEditAttribute(target, i);
+
+ if (targetType == ITEM_UI_ELEMENT)
+ SetUIElementModified(target);
+ else
+ SetSceneModified();
+
+ attributesFullDirty = true;
+ }
+ }
+}
+
+class ToggleNodeEnabledAction : EditAction
+{
+ uint nodeID;
+ bool undoValue;
+
+ void Define(Node@ node, bool oldEnabled)
+ {
+ nodeID = node.id;
+ undoValue = oldEnabled;
+ }
+
+ void Undo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ if (node !is null)
+ node.SetEnabledRecursive(undoValue);
+ }
+
+ void Redo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ if (node !is null)
+ node.SetEnabledRecursive(!undoValue);
+ }
+}
+
+class Transform
+{
+ Vector3 position;
+ Quaternion rotation;
+ Vector3 scale;
+
+ void Define(Node@ node)
+ {
+ position = node.position;
+ rotation = node.rotation;
+ scale = node.scale;
+ }
+
+ void Apply(Node@ node)
+ {
+ node.SetTransform(position, rotation, scale);
+ }
+}
+
+class EditNodeTransformAction : EditAction
+{
+ uint nodeID;
+ Transform undoTransform;
+ Transform redoTransform;
+
+ void Define(Node@ node, const Transform&in oldTransform)
+ {
+ nodeID = node.id;
+ undoTransform = oldTransform;
+ redoTransform.Define(node);
+ }
+
+ void Undo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ if (node !is null)
+ {
+ undoTransform.Apply(node);
+ UpdateNodeAttributes();
+ }
+ }
+
+ void Redo()
+ {
+ Node@ node = editorScene.GetNode(nodeID);
+ if (node !is null)
+ {
+ redoTransform.Apply(node);
+ UpdateNodeAttributes();
+ }
+ }
+}
+
+class CreateUIElementAction : EditAction
+{
+ Variant elementID;
+ Variant parentID;
+ XMLFile@ elementData;
+ XMLFile@ styleFile;
+
+ void Define(UIElement@ element)
+ {
+ elementID = GetUIElementID(element);
+ parentID = GetUIElementID(element.parent);
+ elementData = XMLFile();
+ XMLElement rootElem = elementData.CreateRoot("element");
+ element.SaveXML(rootElem);
+ styleFile = element.defaultStyle;
+ }
+
+ void Undo()
+ {
+ UIElement@ parent = GetUIElementByID(parentID);
+ UIElement@ element = GetUIElementByID(elementID);
+ if (parent !is null && element !is null)
+ {
+ parent.RemoveChild(element);
+ hierarchyList.ClearSelection();
+ SetUIElementModified(parent);
+ }
+ }
+
+ void Redo()
+ {
+ UIElement@ parent = GetUIElementByID(parentID);
+ if (parent !is null)
+ {
+ // Have to update manually because the element ID var is not set yet when the E_ELEMENTADDED event is sent
+ suppressUIElementChanges = true;
+
+ if (parent.LoadChildXML(elementData.root, styleFile) !is null)
+ {
+ UIElement@ element = parent.children[parent.numChildren - 1];
+ UpdateHierarchyItem(element);
+ FocusUIElement(element);
+ SetUIElementModified(parent);
+ }
+
+ suppressUIElementChanges = false;
+
+ }
+ }
+}
+
+class DeleteUIElementAction : EditAction
+{
+ Variant elementID;
+ Variant parentID;
+ XMLFile@ elementData;
+ XMLFile@ styleFile;
+
+ void Define(UIElement@ element)
+ {
+ elementID = GetUIElementID(element);
+ parentID = GetUIElementID(element.parent);
+ elementData = XMLFile();
+ XMLElement rootElem = elementData.CreateRoot("element");
+ element.SaveXML(rootElem);
+ rootElem.SetUInt("index", element.parent.FindChild(element));
+ rootElem.SetUInt("listItemIndex", GetListIndex(element));
+ styleFile = element.defaultStyle;
+ }
+
+ void Undo()
+ {
+ UIElement@ parent = GetUIElementByID(parentID);
+ if (parent !is null)
+ {
+ // Have to update manually because the element ID var is not set yet when the E_ELEMENTADDED event is sent
+ suppressUIElementChanges = true;
+
+ if (parent.LoadChildXML(elementData.root, styleFile) !is null)
+ {
+ XMLElement rootElem = elementData.root;
+ uint index = rootElem.GetUInt("index");
+ uint listItemIndex = rootElem.GetUInt("listItemIndex");
+ UIElement@ element = parent.children[index];
+ UIElement@ parentItem = hierarchyList.items[GetListIndex(parent)];
+ UpdateHierarchyItem(listItemIndex, element, parentItem);
+ FocusUIElement(element);
+ SetUIElementModified(parent);
+ }
+
+ suppressUIElementChanges = false;
+ }
+ }
+
+ void Redo()
+ {
+ UIElement@ parent = GetUIElementByID(parentID);
+ UIElement@ element = GetUIElementByID(elementID);
+ if (parent !is null && element !is null)
+ {
+ parent.RemoveChild(element);
+ hierarchyList.ClearSelection();
+ SetUIElementModified(parent);
+ }
+ }
+}
+
+class ReparentUIElementAction : EditAction
+{
+ Variant elementID;
+ Variant oldParentID;
+ uint oldChildIndex;
+ Variant newParentID;
+
+ void Define(UIElement@ element, UIElement@ newParent)
+ {
+ elementID = GetUIElementID(element);
+ oldParentID = GetUIElementID(element.parent);
+ oldChildIndex = element.parent.FindChild(element);
+ newParentID = GetUIElementID(newParent);
+ }
+
+ void Undo()
+ {
+ UIElement@ parent = GetUIElementByID(oldParentID);
+ UIElement@ element = GetUIElementByID(elementID);
+ if (parent !is null && element !is null)
+ {
+ element.SetParent(parent, oldChildIndex);
+ SetUIElementModified(parent);
+ }
+ }
+
+ void Redo()
+ {
+ UIElement@ parent = GetUIElementByID(newParentID);
+ UIElement@ element = GetUIElementByID(elementID);
+ if (parent !is null && element !is null)
+ {
+ element.parent = parent;
+ SetUIElementModified(parent);
+ }
+ }
+}
+
+class ReorderUIElementAction : EditAction
+{
+ Variant elementID;
+ Variant parentID;
+ uint oldChildIndex;
+ uint newChildIndex;
+
+ void Define(UIElement@ element, uint newIndex)
+ {
+ elementID = GetUIElementID(element);
+ parentID = GetUIElementID(element.parent);
+ oldChildIndex = element.parent.FindChild(element);
+ newChildIndex = newIndex;
+ }
+
+ void Undo()
+ {
+ UIElement@ parent = GetUIElementByID(parentID);
+ UIElement@ element = GetUIElementByID(elementID);
+ if (parent !is null && element !is null)
+ PerformReorder(parent, element, oldChildIndex);
+ }
+
+ void Redo()
+ {
+ UIElement@ parent = GetUIElementByID(parentID);
+ UIElement@ element = GetUIElementByID(elementID);
+ if (parent !is null && element !is null)
+ PerformReorder(parent, element, newChildIndex);
+ }
+}
+
+class ApplyUIElementStyleAction : EditAction
+{
+ Variant elementID;
+ Variant parentID;
+ XMLFile@ elementData;
+ XMLFile@ styleFile;
+ String elementOldStyle;
+ String elementNewStyle;
+
+ void Define(UIElement@ element, const String&in newStyle)
+ {
+ elementID = GetUIElementID(element);
+ parentID = GetUIElementID(element.parent);
+ elementData = XMLFile();
+ XMLElement rootElem = elementData.CreateRoot("element");
+ element.SaveXML(rootElem);
+ rootElem.SetUInt("index", element.parent.FindChild(element));
+ rootElem.SetUInt("listItemIndex", GetListIndex(element));
+ styleFile = element.defaultStyle;
+ elementOldStyle = element.style;
+ elementNewStyle = newStyle;
+ }
+
+ void ApplyStyle(const String&in style)
+ {
+ UIElement@ parent = GetUIElementByID(parentID);
+ UIElement@ element = GetUIElementByID(elementID);
+ if (parent !is null && element !is null)
+ {
+ // Apply the style in the XML data
+ elementData.root.SetAttribute("style", style);
+
+ // Have to update manually because the element ID var is not set yet when the E_ELEMENTADDED event is sent
+ suppressUIElementChanges = true;
+
+ parent.RemoveChild(element);
+ if (parent.LoadChildXML(elementData.root, styleFile) !is null)
+ {
+ XMLElement rootElem = elementData.root;
+ uint index = rootElem.GetUInt("index");
+ uint listItemIndex = rootElem.GetUInt("listItemIndex");
+ UIElement@ elem = parent.children[index];
+ UIElement@ parentItem = hierarchyList.items[GetListIndex(parent)];
+ UpdateHierarchyItem(listItemIndex, elem, parentItem);
+ SetUIElementModified(elem);
+ hierarchyUpdateSelections.Push(listItemIndex);
+ }
+
+ suppressUIElementChanges = false;
+ }
+ }
+
+ void Undo()
+ {
+ ApplyStyle(elementOldStyle);
+ }
+
+ void Redo()
+ {
+ ApplyStyle(elementNewStyle);
+ }
+}
+
+class EditMaterialAction : EditAction
+{
+ XMLFile@ oldState;
+ XMLFile@ newState;
+ WeakHandle material;
+
+ void Define(Material@ material_, XMLFile@ oldState_)
+ {
+ material = material_;
+ oldState = oldState_;
+ newState = XMLFile();
+
+ XMLElement materialElem = newState.CreateRoot("material");
+ material_.Save(materialElem);
+ }
+
+ void Undo()
+ {
+ Material@ mat = material.Get();
+ if (mat !is null)
+ {
+ mat.Load(oldState.root);
+ RefreshMaterialEditor();
+ }
+ }
+
+ void Redo()
+ {
+ Material@ mat = material.Get();
+ if (mat !is null)
+ {
+ mat.Load(newState.root);
+ RefreshMaterialEditor();
+ }
+ }
+}
+
+class EditParticleEffectAction : EditAction
+{
+ XMLFile@ oldState;
+ XMLFile@ newState;
+ WeakHandle particleEffect;
+ ParticleEmitter@ particleEmitter;
+
+ void Define(ParticleEmitter@ particleEmitter_, ParticleEffect@ particleEffect_, XMLFile@ oldState_)
+ {
+ particleEmitter = particleEmitter_;
+ particleEffect = particleEffect_;
+ oldState = oldState_;
+ newState = XMLFile();
+
+ XMLElement particleElem = newState.CreateRoot("particleeffect");
+ particleEffect_.Save(particleElem);
+ }
+
+ void Undo()
+ {
+ ParticleEffect@ effect = particleEffect.Get();
+ if (effect !is null)
+ {
+ effect.Load(oldState.root);
+ particleEmitter.ApplyEffect();
+ RefreshParticleEffectEditor();
+ }
+ }
+
+ void Redo()
+ {
+ ParticleEffect@ effect = particleEffect.Get();
+ if (effect !is null)
+ {
+ effect.Load(newState.root);
+ particleEmitter.ApplyEffect();
+ RefreshParticleEffectEditor();
+ }
+ }
+}
+
+class AssignMaterialAction : EditAction
+{
+ WeakHandle model;
+ Array oldMaterials;
+ String newMaterialName;
+
+ void Define(StaticModel@ model_, Array oldMaterials_, Material@ newMaterial_)
+ {
+ model = model_;
+ oldMaterials = oldMaterials_;
+ newMaterialName = newMaterial_.name;
+ }
+
+ void Undo()
+ {
+ StaticModel@ staticModel = model.Get();
+ if (staticModel is null)
+ return;
+
+ for(uint i=0; i colorFastItem;
+Array colorFast;
+int colorFastSelectedIndex = -1;
+int colorFastHoverIndex = -1;
+
+IntVector2 lastColorWheelWindowPosition;
+
+
+bool isColorWheelHovering = false;
+bool isBWGradientHovering = false;
+bool isAGradientHovering = false;
+
+Color wheelIncomingColor = Color(1,1,1,1);
+Color wheelColor = Color(1,1,1,1);
+float colorHValue = 1;
+float colorSValue = 1;
+float colorVValue = 1;
+float colorAValue = 0.5;
+int high = 0;
+float aValue = 1;
+
+const int IMAGE_SIZE = 256;
+const int HALF_IMAGE_SIZE = 128;
+const float MAX_ANGLE = 360;
+const float ROUND_VALUE_MAX = 0.99f;
+const float ROUND_VALUE_MIN = 0.01f;
+
+// for handlers outside
+String eventTypeWheelChangeColor = "WheelChangeColor";
+String eventTypeWheelSelectColor = "WheelSelectColor";
+String eventTypeWheelDiscardColor ="WheelDiscardColor";
+
+void CreateColorWheel()
+{
+ if (colorWheelWindow !is null)
+ return;
+
+ colorWheelWindow = LoadEditorUI("UI/EditorColorWheel.xml");
+ ui.root.AddChild(colorWheelWindow);
+ colorWheelWindow.opacity = uiMaxOpacity;
+
+ colorWheel = colorWheelWindow.GetChild("ColorWheel", true);
+ colorCursor = colorWheelWindow.GetChild("ColorCursor", true);
+
+ closeButton = colorWheelWindow.GetChild("CloseButton", true);
+ okButton = colorWheelWindow.GetChild("okButton", true);
+ cancelButton = colorWheelWindow.GetChild("cancelButton", true);
+
+ colorCheck = colorWheelWindow.GetChild("ColorCheck", true);
+ bwGradient = colorWheelWindow.GetChild("BWGradient", true);
+ bwCursor = colorWheelWindow.GetChild("BWCursor", true);
+
+ AGradient = colorWheelWindow.GetChild("AGradient", true);
+ ACursor = colorWheelWindow.GetChild("ACursor", true);
+
+ rLineEdit = colorWheelWindow.GetChild("R", true);
+ gLineEdit = colorWheelWindow.GetChild("G", true);
+ bLineEdit = colorWheelWindow.GetChild("B", true);
+
+ hLineEdit = colorWheelWindow.GetChild("H", true);
+ sLineEdit = colorWheelWindow.GetChild("S", true);
+ vLineEdit = colorWheelWindow.GetChild("V", true);
+
+ aLineEdit = colorWheelWindow.GetChild("A", true);
+
+ colorFastItem.Resize(8);
+ colorFast.Resize(8);
+
+ // Init some gradient for fast colors palette
+ for (int i=0; i<8; i++)
+ {
+ colorFastItem[i] = colorWheelWindow.GetChild("h"+String(i), true);
+ colorFast[i] = Color(i*0.125,i*0.125,i*0.125);
+ colorFastItem[i].color = colorFast[i];
+ }
+
+ SubscribeToEvent(closeButton, "Pressed", "HandleWheelButtons");
+ SubscribeToEvent(okButton, "Pressed", "HandleWheelButtons");
+ SubscribeToEvent(cancelButton, "Pressed", "HandleWheelButtons");
+
+ CenterDialog(colorWheelWindow);
+ lastColorWheelWindowPosition = colorWheelWindow.position;
+
+ HideColorWheel();
+}
+
+bool ShowColorWheelWithColor(Color oldColor)
+{
+ wheelIncomingColor = oldColor;
+ wheelColor = oldColor;
+ return ShowColorWheel();
+}
+
+bool ShowColorWheel()
+{
+ if (ui.focusElement !is null && colorWheelWindow.visible)
+ return false;
+
+ colorFastSelectedIndex = -1;
+ colorFastHoverIndex = -1;
+
+ EstablishColorWheelUIFromColor(wheelColor);
+
+ colorWheelWindow.opacity = 1;
+ colorWheelWindow.position = lastColorWheelWindowPosition;
+ colorWheelWindow.visible = true;
+ colorWheelWindow.BringToFront();
+
+ return true;
+}
+
+void HideColorWheel()
+{
+ if (colorWheelWindow.visible)
+ {
+ colorWheelWindow.visible = false;
+ lastColorWheelWindowPosition = colorWheelWindow.position;
+ }
+}
+
+void HandleWheelButtons(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ edit = eventData["Element"].GetPtr();
+
+ if (edit is null) return;
+
+ if (edit is cancelButton)
+ {
+ VariantMap vm;
+ eventData["Color"] = wheelIncomingColor;
+ SendEvent(eventTypeWheelDiscardColor, vm);
+ HideColorWheel();
+ }
+
+ if (edit is closeButton)
+ {
+ VariantMap vm;
+ eventData["Color"] = wheelIncomingColor;
+ SendEvent(eventTypeWheelDiscardColor, vm);
+ HideColorWheel();
+ }
+
+ if (edit is okButton)
+ {
+ VariantMap vm;
+ eventData["Color"] = wheelColor;
+ SendEvent(eventTypeWheelSelectColor, vm);
+ HideColorWheel();
+ }
+}
+
+void HandleColorWheelKeyDown(StringHash eventType, VariantMap& eventData)
+{
+ if (colorWheelWindow.visible == false) return;
+
+ int key = eventData["Key"].GetInt();
+
+ if (key == KEY_ESCAPE)
+ {
+ SendEvent(eventTypeWheelDiscardColor, eventData);
+ HideColorWheel();
+ }
+}
+
+void HandleColorWheelMouseButtonDown(StringHash eventType, VariantMap& eventData)
+{
+ if (colorWheelWindow.visible == false) return;
+
+ int x = eventData["X"].GetInt();
+ int y = eventData["Y"].GetInt();
+ int button = eventData["Button"].GetInt();
+
+ if (button == 1)
+ {
+ // check for select
+ if (colorFastHoverIndex != -1)
+ {
+ colorFastSelectedIndex = colorFastHoverIndex;
+ EstablishColorWheelUIFromColor(colorFast[colorFastSelectedIndex]);
+ SendEventChangeColor();
+ }
+ }
+}
+
+// handler only for BWGradient
+void HandleColorWheelMouseWheel(StringHash eventType, VariantMap& eventData)
+{
+ if (colorWheelWindow.visible == false || !isBWGradientHovering ) return;
+
+ int multipler = 16;
+ int wheelValue = eventData["Wheel"].GetInt();
+
+ wheelValue = wheelValue * multipler;
+
+ if (wheelValue != 0)
+ {
+ if (wheelValue > 0)
+ {
+ high = high + wheelValue;
+ high = Min(high, IMAGE_SIZE); // limit BWGradietn by high
+ }
+ else if (wheelValue < 0)
+ {
+ high = high + wheelValue;
+ high = Max(high, 0);
+ }
+
+ bwCursor.SetPosition(bwCursor.position.x, high - 7);
+ colorVValue = float(IMAGE_SIZE - high) / IMAGE_SIZE;
+
+ wheelColor.FromHSV(colorHValue, colorSValue, colorVValue, colorAValue);
+ SendEventChangeColor();
+ UpdateColorInformation();
+ }
+}
+
+void HandleColorWheelMouseMove(StringHash eventType, VariantMap& eventData)
+{
+ if (colorWheelWindow.visible == false) return;
+
+ int x = eventData["X"].GetInt();
+ int y = eventData["Y"].GetInt();
+ int button = eventData["Button"].GetInt();
+
+ if (colorWheelWindow.IsInside(IntVector2(x,y), true))
+ colorWheelWindow.opacity = 1.0f;
+
+ int cwx = 0;
+ int cwy = 0;
+ int cx = 0;
+ int cy = 0;
+ IntVector2 i;
+ bool inWheel = false;
+
+ isBWGradientHovering = false;
+ isColorWheelHovering = false;
+
+ // if mouse cursor move on wheel rectangle
+ if (colorWheel.IsInside(IntVector2(x,y), true))
+ {
+ isColorWheelHovering = true;
+ // get element pos win screen
+ IntVector2 ep = colorWheel.screenPosition;
+
+ // math diff between mouse cursor & element pos = mouse pos on element
+ cwx = x - ep.x;
+ cwy = y - ep.y;
+
+ // shift mouse pos to center of wheel
+ cx = cwx - HALF_IMAGE_SIZE;
+ cy = cwy - HALF_IMAGE_SIZE;
+
+ // get direction vector of H on circle
+ Vector2 d = Vector2(cx, cy);
+
+ // if out of circle place colorCurcor back to circle
+ if (d.length > HALF_IMAGE_SIZE)
+ {
+ d.Normalize();
+ d = d * HALF_IMAGE_SIZE;
+
+ i = IntVector2(int(d.x), int(d.y));
+ inWheel = false;
+
+ }
+ else
+ {
+ inWheel = true;
+ }
+
+ if (isColorWheelHovering && inWheel && input.mouseButtonDown[MOUSEB_LEFT] || input.mouseButtonDown[MOUSEB_RIGHT])
+ {
+ Vector2 pointOnCircle = Vector2(cx,-cy);
+ float angle = GetAngle(pointOnCircle);
+
+ i = i + IntVector2(cwx,cwy);
+ colorCursor.position = IntVector2(i.x-7, i.y-7);
+
+ colorHValue = GetHueFromWheelDegAngle(angle);
+
+ if (colorHValue < ROUND_VALUE_MIN) colorHValue = 0.0;
+ if (colorHValue > ROUND_VALUE_MAX) colorHValue = 1.0;
+
+ colorSValue = d.length / HALF_IMAGE_SIZE;
+
+ if (colorSValue < ROUND_VALUE_MIN) colorSValue = 0.0;
+ if (colorSValue > ROUND_VALUE_MAX) colorSValue = 1.0;
+
+ wheelColor.FromHSV(colorHValue, colorSValue, colorVValue, colorAValue);
+ SendEventChangeColor();
+ UpdateColorInformation();
+ }
+ }
+ // if mouse cursor move on bwGradient rectangle
+ else if (bwGradient.IsInside(IntVector2(x,y), true))
+ {
+ isBWGradientHovering = true;
+ IntVector2 ep = bwGradient.screenPosition;
+ int high = y - ep.y;
+
+ if (input.mouseButtonDown[MOUSEB_LEFT] || input.mouseButtonDown[MOUSEB_RIGHT])
+ {
+ bwCursor.SetPosition(bwCursor.position.x, high - 7);
+ colorVValue = float(IMAGE_SIZE - high) / IMAGE_SIZE;
+
+ if (colorVValue < 0.01) colorVValue = 0.0;
+ if (colorVValue > 0.99) colorVValue = 1.0;
+
+ wheelColor.FromHSV(colorHValue, colorSValue, colorVValue, colorAValue);
+ SendEventChangeColor();
+ }
+
+ UpdateColorInformation();
+ }
+ // if mouse cursor move on AlphaGradient rectangle
+ else if (AGradient.IsInside(IntVector2(x,y), true))
+ {
+ IntVector2 ep = AGradient.screenPosition;
+ int aValue = x - ep.x;
+
+ if (input.mouseButtonDown[MOUSEB_LEFT] || input.mouseButtonDown[MOUSEB_RIGHT])
+ {
+ ACursor.SetPosition(aValue - 7, ACursor.position.y);
+ colorAValue = float((aValue) / 200); // 200pix image
+
+ // round values for min or max
+ if (colorAValue < 0.01) colorAValue = 0.0;
+ if (colorAValue > 0.99) colorAValue = 1.0;
+
+ wheelColor.FromHSV(colorHValue, colorSValue, colorVValue, colorAValue);
+ SendEventChangeColor();
+ }
+
+ UpdateColorInformation();
+ }
+
+ // cheking for history select
+ for (int j=0; j<8; j++)
+ {
+ if (colorFastItem[j].IsInside(IntVector2(x,y), true))
+ colorFastHoverIndex = j;
+ }
+}
+
+void UpdateColorInformation()
+{
+ // fill UI from current color
+ hLineEdit.text = String(colorHValue).Substring(0,5);
+ sLineEdit.text = String(colorSValue).Substring(0,5);
+ vLineEdit.text = String(colorVValue).Substring(0,5);
+
+ rLineEdit.text = String(wheelColor.r).Substring(0,5);
+ gLineEdit.text = String(wheelColor.g).Substring(0,5);
+ bLineEdit.text = String(wheelColor.b).Substring(0,5);
+
+ aLineEdit.text = String(colorAValue).Substring(0,5);
+
+ colorCheck.color = wheelColor;
+ colorWheel.color = Color(colorVValue,colorVValue,colorVValue);
+ AGradient.color = Color(wheelColor.r, wheelColor.g, wheelColor.b);
+
+ // update selected fast-colors
+ if (colorFastSelectedIndex != -1)
+ {
+ colorFastItem[colorFastSelectedIndex].color = wheelColor;
+ colorFast[colorFastSelectedIndex] = wheelColor;
+ }
+}
+
+void SendEventChangeColor()
+{
+ VariantMap eventData;
+ eventData["Color"] = wheelColor;
+ SendEvent("WheelChangeColor", eventData);
+}
+
+void EstablishColorWheelUIFromColor(Color c)
+{
+ wheelColor = c;
+ colorHValue = c.Hue();
+ colorSValue = c.SaturationHSV();
+ colorVValue = c.Value();
+ colorAValue = c.a;
+
+ // convert color value to BWGradient high
+ high = int(IMAGE_SIZE - colorVValue * IMAGE_SIZE);
+ bwCursor.SetPosition(bwCursor.position.x, high - 7);
+
+ // convert color alpha to shift on x-axis for ACursor
+ aValue = 200 * colorAValue;
+ ACursor.SetPosition(int(aValue - 7), ACursor.position.y);
+
+ // rotate vector to H-angle with scale(shifting) by S to calculate final point position
+ Quaternion q(colorHValue * -MAX_ANGLE);
+ Vector3 pos = Vector3(1, 0, 0);
+ pos = q * pos;
+ pos = pos * (colorSValue * HALF_IMAGE_SIZE);
+ pos = pos + Vector3(HALF_IMAGE_SIZE, HALF_IMAGE_SIZE);
+
+ colorCursor.position = IntVector2(int(pos.x) - 7, int(pos.y) - 7);
+
+ // Update information on UI about color
+ UpdateColorInformation();
+}
+
+float GetHueFromWheelDegAngle(float angle)
+{
+ return angle / MAX_ANGLE;
+}
+
+float GetAngle(Vector2 point)
+{
+ float angle = Atan2( point.y, point.x );
+
+ if (angle < 0)
+ angle += MAX_ANGLE;
+
+ return angle;
+}
\ No newline at end of file
diff --git a/bin/Data/Scripts/Editor/EditorCubeCapture.as b/bin/Data/Scripts/Editor/EditorCubeCapture.as
new file mode 100644
index 0000000..5361930
--- /dev/null
+++ b/bin/Data/Scripts/Editor/EditorCubeCapture.as
@@ -0,0 +1,268 @@
+
+/// Settings
+String cubeMapGen_Name;
+String cubeMapGen_Path;
+int cubeMapGen_Size;
+
+Array activeCubeCapture; // Editor capture tasks in progress, Stop() method of EditorCubeCapture moves forward through the queue as captures are finished
+Array cloneZones; // Duplicate zones constructed to prevent IBL doubling
+Array disabledZones; // Zones that were disabled to prevent IBL doubling
+const String cubemapDefaultOutputPath = "Textures/Cubemaps";
+
+void PrepareZonesForCubeRendering()
+{
+ // Only clone zones when we aren't actively processing
+ if (cloneZones.length > 0)
+ return;
+
+ Array@ zones = editorScene.GetComponents("Zone", true);
+ for (uint i = 0; i < zones.length; ++i)
+ {
+ Zone@ srcZone = cast(zones[i]);
+ if (zones[i].enabled)
+ {
+ Zone@ cloneZone = srcZone.node.CreateComponent("Zone");
+ cloneZone.zoneMask = srcZone.zoneMask;
+ cloneZone.priority = srcZone.priority;
+ cloneZone.boundingBox = srcZone.boundingBox;
+
+ cloneZone.ambientColor = srcZone.ambientColor;
+ cloneZone.ambientGradient = srcZone.ambientGradient;
+
+ cloneZone.fogColor = srcZone.fogColor;
+ cloneZone.fogStart = srcZone.fogStart;
+ cloneZone.fogEnd = srcZone.fogEnd;
+ cloneZone.fogHeight = srcZone.fogHeight;
+ cloneZone.fogHeightScale = srcZone.fogHeightScale;
+ cloneZone.heightFog = srcZone.heightFog;
+
+ srcZone.enabled = false;
+
+ // Add the zones to our temporary lists
+ cloneZones.Push(cloneZone);
+ disabledZones.Push(srcZone);
+ }
+ }
+
+ // Hide grid and debugIcons until bake
+ if (grid !is null)
+ grid.viewMask = 0;
+ if (debugIconsNode !is null)
+ debugIconsNode.enabled = false;
+ debugRenderDisabled = true;
+}
+
+void UnprepareZonesForCubeRendering()
+{
+ // Clean up the clones
+ for (uint i = 0; i < cloneZones.length; ++i)
+ cloneZones[i].Remove();
+ cloneZones.Clear();
+
+ // Reenable anyone we disabled
+ for (uint i = 0; i < disabledZones.length; ++i)
+ disabledZones[i].enabled = true;
+ disabledZones.Clear();
+
+ // Show grid and debug icons
+ if (grid !is null)
+ grid.viewMask = 0x80000000;
+ if (debugIconsNode !is null)
+ debugIconsNode.enabled = true;
+ debugRenderDisabled = false;
+}
+
+class EditorCubeCapture : ScriptObject // script object in order to get events
+{
+ private String name_;
+ private String path_;
+ private Node@ camNode_;
+ private Camera@ camera_;
+ private Zone@ target_;
+ private Viewport@ viewport_;
+ private Texture2D@ renderImage_;
+ private RenderSurface@ renderSurface_;
+ int updateCycle_;
+ String imagePath_;
+
+ EditorCubeCapture(Zone@ forZone)
+ {
+ PrepareZonesForCubeRendering();
+
+ // Store name and path because if we have a lot of zones it could take long enough to queue another go, and it may have different settings
+ name_ = cubeMapGen_Name;
+ path_ = sceneResourcePath + cubeMapGen_Path;
+
+ updateCycle_ = 0;
+
+ target_ = forZone;
+
+ camNode_ = scene.CreateChild("RenderCamera");
+ camera_ = camNode_.GetOrCreateComponent("Camera");
+ camera_.fov = 90.0f;
+ camera_.nearClip = 0.0001f;
+ camera_.aspectRatio = 1.0f;
+ camNode_.worldPosition = forZone.node.worldPosition;
+
+ viewport_ = Viewport(scene, camera_);
+ viewport_.renderPath = renderer.viewports[0].renderPath;
+
+ updateCycle_ = 0;
+ }
+
+ ~EditorCubeCapture()
+ {
+ camNode_.Remove();
+ }
+
+ void Start()
+ {
+ // Construct render surface
+ renderImage_ = Texture2D();
+ renderImage_.SetSize(cubeMapGen_Size, cubeMapGen_Size, GetRGBAFormat(), TEXTURE_RENDERTARGET);
+
+ renderSurface_ = renderImage_.renderSurface;
+ renderSurface_.viewports[0] = viewport_;
+ renderSurface_.updateMode = SURFACE_UPDATEALWAYS;
+
+ SubscribeToEvent("BeginFrame", "HandlePreRender");
+ SubscribeToEvent("EndFrame", "HandlePostRender");
+ }
+
+ private void Stop()
+ {
+ camNode_.Remove();
+ camNode_ = null;
+ viewport_ = null;
+ renderSurface_ = null;
+
+ UnsubscribeFromEvent("BeginFrame");
+ UnsubscribeFromEvent("EndFrame");
+
+ WriteXML();
+
+ // Remove ourselves from the processing list and if necessary clean things up
+ // If others are pending in the queue then start the next one
+ activeCubeCapture.Erase(activeCubeCapture.FindByRef(this));
+ if (activeCubeCapture.length == 0)
+ UnprepareZonesForCubeRendering();
+ else
+ activeCubeCapture[0].Start();
+ }
+
+ // Position camera accordingly
+ void HandlePreRender(StringHash eventType, VariantMap& eventData)
+ {
+ if (camNode_ !is null)
+ {
+ ++updateCycle_;
+
+ if (updateCycle_ < 7)
+ camNode_.worldRotation = RotationOf(GetFaceForCycle(updateCycle_));
+ else
+ Stop();
+ }
+ }
+
+ // Save our image
+ void HandlePostRender(StringHash eventType, VariantMap& eventData)
+ {
+ Image@ img = renderImage_.GetImage();
+ String sceneName = editorScene.name.length > 0 ? editorScene.name + "/" : "";
+ String path = path_ + "/" + sceneName;
+ fileSystem.CreateDir(path);
+ path = path + "/" + String(target_.id) + "_" + GetFaceName(GetFaceForCycle(updateCycle_)) + ".png";
+ img.SavePNG(path);
+ }
+
+ private void WriteXML()
+ {
+ String sceneName = editorScene.name.length > 0 ? "/" + editorScene.name + "/" : "";
+ String basePath = AddTrailingSlash(path_ + sceneName);
+ String cubeName = name_.length > 0 ? (name_ + "_") : "";
+ String xmlPath = basePath + "/" + name_ + String(target_.id) + ".xml";
+ XMLFile@ file = XMLFile();
+ XMLElement rootElem = file.CreateRoot("cubemap");
+
+ for (int i = 0; i < 6; ++i)
+ {
+ XMLElement faceElem = rootElem.CreateChild("face");
+ faceElem.SetAttribute("name", GetResourceNameFromFullName(basePath + cubeName + String(target_.id) + "_" + GetFaceName(CubeMapFace(i)) + ".png"));
+ }
+
+ file.Save(File(xmlPath, FILE_WRITE), " ");
+
+ ResourceRef ref;
+ ref.type = StringHash("TextureCube");
+ ref.name = GetResourceNameFromFullName(xmlPath);
+ target_.SetAttribute("Zone Texture", Variant(ref));
+ }
+
+ private CubeMapFace GetFaceForCycle(int cycle)
+ {
+ switch (updateCycle_)
+ {
+ case 1:
+ return FACE_POSITIVE_X;
+ case 2:
+ return FACE_POSITIVE_Y;
+ case 3:
+ return FACE_POSITIVE_Z;
+ case 4:
+ return FACE_NEGATIVE_X;
+ case 5:
+ return FACE_NEGATIVE_Y;
+ case 6:
+ return FACE_NEGATIVE_Z;
+ }
+ return FACE_POSITIVE_X;
+ }
+
+ private String GetFaceName(CubeMapFace face)
+ {
+ switch (face)
+ {
+ case FACE_POSITIVE_X:
+ return "PosX";
+ case FACE_POSITIVE_Y:
+ return "PosY";
+ case FACE_POSITIVE_Z:
+ return "PosZ";
+ case FACE_NEGATIVE_X:
+ return "NegX";
+ case FACE_NEGATIVE_Y:
+ return "NegY";
+ case FACE_NEGATIVE_Z:
+ return "NegZ";
+ }
+ return "PosX";
+ }
+
+ private Quaternion RotationOf(CubeMapFace face)
+ {
+ Quaternion result;
+ switch (face)
+ {
+ // Rotate camera according to probe rotation
+ case FACE_POSITIVE_X:
+ result = Quaternion(0, 90, 0);
+ break;
+ case FACE_NEGATIVE_X:
+ result = Quaternion(0, -90, 0);
+ break;
+ case FACE_POSITIVE_Y:
+ result = Quaternion(-90, 0, 0);
+ break;
+ case FACE_NEGATIVE_Y:
+ result = Quaternion(90, 0, 0);
+ break;
+ case FACE_POSITIVE_Z:
+ result = Quaternion(0, 0, 0);
+ break;
+ case FACE_NEGATIVE_Z:
+ result = Quaternion(0, 180, 0);
+ break;
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/bin/Data/Scripts/Editor/EditorEventsHandlers.as b/bin/Data/Scripts/Editor/EditorEventsHandlers.as
new file mode 100644
index 0000000..010dad5
--- /dev/null
+++ b/bin/Data/Scripts/Editor/EditorEventsHandlers.as
@@ -0,0 +1,187 @@
+// Editor main handlers (add your local handler in proper main handler to prevent losing events)
+const String EDITOR_EVENT_SCENE_LOADED("EditorEventSceneLoaded");
+const String EDITOR_EVENT_ORIGIN_START_HOVER("EditorEventOriginStartHover");
+const String EDITOR_EVENT_ORIGIN_END_HOVER("EditorEventOriginEndHover");
+
+void EditorSubscribeToEvents()
+{
+ SubscribeToEvent("KeyDown", "EditorMainHandleKeyDown");
+ SubscribeToEvent("KeyUp", "EditorMainHandleKeyUp");
+
+ SubscribeToEvent("MouseMove", "EditorMainHandleMouseMove");
+ SubscribeToEvent("MouseWheel", "EditorMainHandleMouseWheel");
+ SubscribeToEvent("MouseButtonDown", "EditorMainHandleMouseButtonDown");
+ SubscribeToEvent("MouseButtonUp", "EditorMainHandleMouseButtonUp");
+
+ SubscribeToEvent("PostRenderUpdate", "EditorMainHandlePostRenderUpdate");
+
+ SubscribeToEvent("UIMouseClick", "EditorMainHandleUIMouseClick");
+ SubscribeToEvent("UIMouseClickEnd", "EditorMainHandleUIMouseClickEnd");
+
+ SubscribeToEvent("BeginViewUpdate", "EditorMainHandleBeginViewUpdate");
+ SubscribeToEvent("EndViewUpdate", "EditorMainHandleEndViewUpdate");
+ SubscribeToEvent("BeginViewRender", "EditorMainHandleBeginViewRender");
+ SubscribeToEvent("EndViewRender", "EditorMainHandleEndViewRender");
+
+ SubscribeToEvent(EDITOR_EVENT_SCENE_LOADED, "EditorMainHandleSceneLoaded");
+
+ SubscribeToEvent("HoverBegin", "EditorMainHandleHoverBegin");
+ SubscribeToEvent("HoverEnd", "EditorMainHandleHoverEnd");
+
+ SubscribeToEvent(EDITOR_EVENT_ORIGIN_START_HOVER, "EditorMainHandleOriginStartHover");
+ SubscribeToEvent(EDITOR_EVENT_ORIGIN_END_HOVER, "EditorMainHandleOriginEndHover");
+
+ SubscribeToEvent("NodeAdded", "EditorMainHandleNodeAdded");
+ SubscribeToEvent("NodeRemoved", "EditorMainHandleNodeRemoved");
+
+ SubscribeToEvent("NodeNameChanged", "EditorMainHandleNodeNameChanged");
+}
+
+void EditorMainHandleKeyDown(StringHash eventType, VariantMap& eventData)
+{
+ // EditorUI.as handler
+ HandleKeyDown(eventType, eventData);
+
+ // EditorColorWheel.as handler
+ HandleColorWheelKeyDown(eventType, eventData);
+}
+
+void EditorMainHandleKeyUp(StringHash eventType, VariantMap& eventData)
+{
+ // EditorUI.as handler
+ UnfadeUI();
+}
+
+void EditorMainHandleMouseMove(StringHash eventType, VariantMap& eventData)
+{
+ // EditorView.as handler
+ ViewMouseMove();
+
+ // EditorColorWheel.as handler
+ HandleColorWheelMouseMove(eventType, eventData);
+
+ // EditorLayer.as handler
+ HandleHideLayerEditor(eventType, eventData);
+
+ // PaintSelectionMouseMove
+ HandlePaintSelectionMouseMove(eventType, eventData);
+}
+
+void EditorMainHandleMouseWheel(StringHash eventType, VariantMap& eventData)
+{
+ // EditorColorWheel.as handler
+ HandleColorWheelMouseWheel(eventType, eventData);
+
+ // EditorLayer.as handler
+ HandleMaskTypeScroll(eventType, eventData);
+ // PaintSelection handler
+ HandlePaintSelectionWheel(eventType, eventData);
+}
+
+void EditorMainHandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
+{
+ // EditorColorWheel.as handler
+ HandleColorWheelMouseButtonDown(eventType, eventData);
+
+ // EditorLayer.as handler
+ HandleHideLayerEditor(eventType, eventData);
+
+}
+
+void EditorMainHandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
+{
+ // EditorUI.as handler
+ UnfadeUI();
+}
+
+void EditorMainHandlePostRenderUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // EditorView.as handler
+ HandlePostRenderUpdate();
+}
+
+void EditorMainHandleUIMouseClick(StringHash eventType, VariantMap& eventData)
+{
+ // EditorView.as handler
+ ViewMouseClick();
+ HandleOriginToggled(eventType, eventData);
+}
+
+void EditorMainHandleUIMouseClickEnd(StringHash eventType, VariantMap& eventData)
+{
+ // EditorView.as handler
+ ViewMouseClickEnd();
+}
+
+void EditorMainHandleBeginViewUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // EditorView.as handler
+ HandleBeginViewUpdate(eventType, eventData);
+}
+
+void EditorMainHandleEndViewUpdate(StringHash eventType, VariantMap& eventData)
+{
+ // EditorView.as handler
+ HandleEndViewUpdate(eventType, eventData);
+}
+
+void EditorMainHandleBeginViewRender(StringHash eventType, VariantMap& eventData)
+{
+ HandleBeginViewRender(eventType, eventData);
+}
+
+void EditorMainHandleEndViewRender(StringHash eventType, VariantMap& eventData)
+{
+ HandleEndViewRender(eventType, eventData);
+}
+
+void EditorMainHandleSceneLoaded(StringHash eventType, VariantMap& eventData)
+{
+ HandleSceneLoadedForOrigins();
+}
+
+void EditorMainHandleHoverBegin(StringHash eventType, VariantMap& eventData)
+{
+ HandleOriginsHoverBegin(eventType, eventData);
+}
+
+void EditorMainHandleHoverEnd(StringHash eventType, VariantMap& eventData)
+{
+ HandleOriginsHoverEnd(eventType, eventData);
+}
+
+void EditorMainHandleNodeAdded(StringHash eventType, VariantMap& eventData)
+{
+ if (GetEventSender() !is editorScene)
+ return;
+
+ HandleNodeAdded(eventType, eventData);
+ rebuildSceneOrigins = true;
+}
+
+void EditorMainHandleNodeRemoved(StringHash eventType, VariantMap& eventData)
+{
+ if (GetEventSender() !is editorScene)
+ return;
+
+ HandleNodeRemoved(eventType, eventData);
+ rebuildSceneOrigins = true;
+}
+
+void EditorMainHandleNodeNameChanged(StringHash eventType, VariantMap& eventData)
+{
+ if (GetEventSender() !is editorScene)
+ return;
+
+ HandleNodeNameChanged(eventType, eventData);
+}
+
+void EditorMainHandleOriginStartHover(StringHash eventType, VariantMap& eventData)
+{
+
+}
+
+void EditorMainHandleOriginEndHover(StringHash eventType, VariantMap& eventData)
+{
+
+}
\ No newline at end of file
diff --git a/bin/Data/Scripts/Editor/EditorExport.as b/bin/Data/Scripts/Editor/EditorExport.as
new file mode 100644
index 0000000..b98a640
--- /dev/null
+++ b/bin/Data/Scripts/Editor/EditorExport.as
@@ -0,0 +1,125 @@
+
+bool objExportZUp_ = false;
+bool objExportRightHanded_ = true;
+
+void ExportSceneToOBJ(String fileName)
+{
+ if (fileName.empty)
+ {
+ MessageBox("File name for OBJ export unspecified");
+ return;
+ }
+ // append obj extension if missing
+ if (GetExtension(fileName).empty)
+ fileName += ".obj";
+
+ Octree@ octree = scene.GetComponent("Octree");
+ if (octree is null)
+ {
+ MessageBox("Octree missing from scene");
+ return;
+ }
+
+ Array drawables = octree.GetAllDrawables();
+ if (drawables.length == 0)
+ {
+ MessageBox("No drawables to export in the scene");
+ return;
+ }
+
+ RemoveDebugDrawables(drawables);
+
+ File@ file = File(fileName, FILE_WRITE);
+ if (WriteDrawablesToOBJ(drawables, file, objExportZUp_, objExportRightHanded_))
+ {
+ MessageBox("OBJ file written to " + fileName, "Success");
+ file.Close();
+ }
+ else
+ {
+ // Cleanup our file so we don't mislead anyone
+ MessageBox("Unable to write OBJ file");
+ file.Close();
+ fileSystem.Delete(fileName);
+ }
+}
+
+void ExportSelectedToOBJ(String fileName)
+{
+ if (fileName.empty)
+ {
+ MessageBox("File name for OBJ export unspecified");
+ return;
+ }
+ if (GetExtension(fileName).empty)
+ fileName += ".obj";
+
+ Array drawables;
+
+ // Add any explicitly selected drawables
+ for (uint i = 0; i < selectedComponents.length; ++i)
+ {
+ Drawable@ drawable = cast(selectedComponents[i]);
+ if (drawable !is null)
+ drawables.Push(drawable);
+ }
+
+ // Add drawables of any selected nodes
+ for (uint i = 0; i < selectedNodes.length; ++i)
+ {
+ Array@ components = selectedNodes[i].GetComponents();
+ for (uint comp = 0; comp < components.length; ++comp)
+ {
+ Drawable@ drawable = cast(components[comp]);
+ if (drawable !is null && drawables.FindByRef(drawable) < 0)
+ drawables.Push(drawable);
+ }
+ }
+
+ RemoveDebugDrawables(drawables);
+
+ if (drawables.length > 0)
+ {
+ File@ file = File(fileName, FILE_WRITE);
+ if (WriteDrawablesToOBJ(drawables, file, objExportZUp_, objExportRightHanded_))
+ {
+ MessageBox("OBJ file written to " + fileName, "Success");
+ file.Close();
+ }
+ else
+ {
+ MessageBox("Unable to write OBJ file");
+ // Cleanup our file so we don't mislead anyone
+ file.Close();
+ fileSystem.Delete(fileName);
+ }
+ }
+ else
+ {
+ MessageBox("No selected drawables to export to OBJ");
+ }
+}
+
+void RemoveDebugDrawables(Array@ drawables)
+{
+ for (uint i = 0; i < drawables.length;)
+ {
+ if (drawables[i].node !is null && (drawables[i].node.name == "EditorGizmo" || drawables[i].node.name == "DebugIconsContainer"
+ || drawables[i].node.name == "EditorGrid"))
+ drawables.Erase(i);
+ else
+ ++i;
+ }
+}
+
+void HandleOBJZUpChanged(StringHash eventType, VariantMap& eventData)
+{
+ CheckBox@ checkBox = cast(eventData["Element"].GetPtr());
+ objExportZUp_ = checkBox.checked;
+}
+
+void HandleOBJRightHandedChanged(StringHash eventType, VariantMap& eventData)
+{
+ CheckBox@ checkBox = cast(eventData["Element"].GetPtr());
+ objExportRightHanded_ = checkBox.checked;
+}
\ No newline at end of file
diff --git a/bin/Data/Scripts/Editor/EditorGizmo.as b/bin/Data/Scripts/Editor/EditorGizmo.as
new file mode 100644
index 0000000..1c77f04
--- /dev/null
+++ b/bin/Data/Scripts/Editor/EditorGizmo.as
@@ -0,0 +1,470 @@
+// Urho3D editor node transform gizmo handling
+
+Node@ gizmoNode;
+StaticModel@ gizmo;
+
+const float axisMaxD = 0.1;
+const float axisMaxT = 1.0;
+const float rotSensitivity = 50.0;
+
+EditMode lastGizmoMode;
+
+// For undo
+bool previousGizmoDrag;
+bool needGizmoUndo;
+Array oldGizmoTransforms;
+
+class GizmoAxis
+{
+ Ray axisRay;
+ bool selected;
+ bool lastSelected;
+ float t;
+ float d;
+ float lastT;
+ float lastD;
+
+ GizmoAxis()
+ {
+ selected = false;
+ lastSelected = false;
+ t = 0.0;
+ d = 0.0;
+ lastT = 0.0;
+ lastD = 0.0;
+ }
+
+ void Update(Ray cameraRay, float scale, bool drag)
+ {
+ // Do not select when UI has modal element
+ if (ui.HasModalElement())
+ {
+ selected = false;
+ return;
+ }
+
+ Vector3 closest = cameraRay.ClosestPoint(axisRay);
+ Vector3 projected = axisRay.Project(closest);
+ d = axisRay.Distance(closest);
+ t = (projected - axisRay.origin).DotProduct(axisRay.direction);
+
+ // Determine the sign of d from a plane that goes through the camera position to the axis
+ Plane axisPlane(cameraNode.position, axisRay.origin, axisRay.origin + axisRay.direction);
+ if (axisPlane.Distance(closest) < 0.0)
+ d = -d;
+
+ // Update selected status only when not dragging
+ if (!drag)
+ {
+ selected = Abs(d) < axisMaxD * scale && t >= -axisMaxD * scale && t <= axisMaxT * scale;
+ lastT = t;
+ lastD = d;
+ }
+ }
+
+ void Moved()
+ {
+ lastT = t;
+ lastD = d;
+ }
+}
+
+GizmoAxis gizmoAxisX;
+GizmoAxis gizmoAxisY;
+GizmoAxis gizmoAxisZ;
+
+void CreateGizmo()
+{
+ gizmoNode = Node();
+ gizmo = gizmoNode.CreateComponent("StaticModel");
+ gizmo.model = cache.GetResource("Model", "Models/Editor/Axes.mdl");
+ gizmo.materials[0] = cache.GetResource("Material", "Materials/Editor/RedUnlit.xml");
+ gizmo.materials[1] = cache.GetResource("Material", "Materials/Editor/GreenUnlit.xml");
+ gizmo.materials[2] = cache.GetResource("Material", "Materials/Editor/BlueUnlit.xml");
+ gizmo.enabled = false;
+ gizmo.viewMask = 0x80000000; // Editor raycasts use viewmask 0x7fffffff
+ gizmo.occludee = false;
+ gizmoNode.name = "EditorGizmo";
+
+ gizmoAxisX.lastSelected = false;
+ gizmoAxisY.lastSelected = false;
+ gizmoAxisZ.lastSelected = false;
+ lastGizmoMode = EDIT_MOVE;
+}
+
+void HideGizmo()
+{
+ if (gizmo !is null)
+ gizmo.enabled = false;
+}
+
+void ShowGizmo()
+{
+ if (gizmo !is null)
+ {
+ gizmo.enabled = true;
+
+ // Because setting enabled = false detaches the gizmo from octree,
+ // and it is a manually added drawable, must readd to octree when showing
+ if (editorScene.octree !is null)
+ editorScene.octree.AddManualDrawable(gizmo);
+ }
+}
+
+void UpdateGizmo()
+{
+ UseGizmo();
+ PositionGizmo();
+ ResizeGizmo();
+}
+
+void PositionGizmo()
+{
+ if (gizmo is null)
+ return;
+
+ Vector3 center(0, 0, 0);
+ bool containsScene = false;
+
+ for (uint i = 0; i < editNodes.length; ++i)
+ {
+ // Scene's transform should not be edited, so hide gizmo if it is included
+ if (editNodes[i] is editorScene)
+ {
+ containsScene = true;
+ break;
+ }
+ center += editNodes[i].worldPosition;
+ }
+
+ if (editNodes.empty || containsScene)
+ {
+ HideGizmo();
+ return;
+ }
+
+ center /= editNodes.length;
+ gizmoNode.position = center;
+
+ if (axisMode == AXIS_WORLD || editNodes.length > 1)
+ gizmoNode.rotation = Quaternion();
+ else
+ gizmoNode.rotation = editNodes[0].worldRotation;
+
+ if (editMode != lastGizmoMode)
+ {
+ switch (editMode)
+ {
+ case EDIT_MOVE:
+ gizmo.model = cache.GetResource("Model", "Models/Editor/Axes.mdl");
+ break;
+
+ case EDIT_ROTATE:
+ gizmo.model = cache.GetResource("Model", "Models/Editor/RotateAxes.mdl");
+ break;
+
+ case EDIT_SCALE:
+ gizmo.model = cache.GetResource("Model", "Models/Editor/ScaleAxes.mdl");
+ break;
+ }
+
+ lastGizmoMode = editMode;
+ }
+
+ if ((editMode != EDIT_SELECT && !orbiting) && !gizmo.enabled)
+ ShowGizmo();
+ else if ((editMode == EDIT_SELECT || orbiting) && gizmo.enabled)
+ HideGizmo();
+}
+
+void ResizeGizmo()
+{
+ if (gizmo is null || !gizmo.enabled)
+ return;
+
+ float scale = 0.1 / camera.zoom;
+
+ if (camera.orthographic)
+ scale *= camera.orthoSize;
+ else
+ scale *= (camera.view * gizmoNode.position).z;
+
+ gizmoNode.scale = Vector3(scale, scale, scale);
+}
+
+void CalculateGizmoAxes()
+{
+ gizmoAxisX.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(1, 0, 0));
+ gizmoAxisY.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 1, 0));
+ gizmoAxisZ.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 0, 1));
+}
+
+void GizmoMoved()
+{
+ gizmoAxisX.Moved();
+ gizmoAxisY.Moved();
+ gizmoAxisZ.Moved();
+}
+
+void UseGizmo()
+{
+ if (gizmo is null || !gizmo.enabled || editMode == EDIT_SELECT)
+ {
+ StoreGizmoEditActions();
+ previousGizmoDrag = false;
+ return;
+ }
+
+ IntVector2 pos = ui.cursorPosition;
+ if (ui.GetElementAt(pos) !is null)
+ return;
+ Ray cameraRay = GetActiveViewportCameraRay();
+ float scale = gizmoNode.scale.x;
+
+ // Recalculate axes only when not left-dragging
+ bool drag = input.mouseButtonDown[MOUSEB_LEFT];
+ if (!drag)
+ CalculateGizmoAxes();
+
+ gizmoAxisX.Update(cameraRay, scale, drag);
+ gizmoAxisY.Update(cameraRay, scale, drag);
+ gizmoAxisZ.Update(cameraRay, scale, drag);
+
+ if (gizmoAxisX.selected != gizmoAxisX.lastSelected)
+ {
+ gizmo.materials[0] = cache.GetResource("Material", gizmoAxisX.selected ? "Materials/Editor/BrightRedUnlit.xml" :
+ "Materials/Editor/RedUnlit.xml");
+ gizmoAxisX.lastSelected = gizmoAxisX.selected;
+ }
+ if (gizmoAxisY.selected != gizmoAxisY.lastSelected)
+ {
+ gizmo.materials[1] = cache.GetResource("Material", gizmoAxisY.selected ? "Materials/Editor/BrightGreenUnlit.xml" :
+ "Materials/Editor/GreenUnlit.xml");
+ gizmoAxisY.lastSelected = gizmoAxisY.selected;
+ }
+ if (gizmoAxisZ.selected != gizmoAxisZ.lastSelected)
+ {
+ gizmo.materials[2] = cache.GetResource("Material", gizmoAxisZ.selected ? "Materials/Editor/BrightBlueUnlit.xml" :
+ "Materials/Editor/BlueUnlit.xml");
+ gizmoAxisZ.lastSelected = gizmoAxisZ.selected;
+ };
+
+ if (drag)
+ {
+ // Store initial transforms for undo when gizmo drag started
+ if (!previousGizmoDrag)
+ {
+ oldGizmoTransforms.Resize(editNodes.length);
+ for (uint i = 0; i < editNodes.length; ++i)
+ oldGizmoTransforms[i].Define(editNodes[i]);
+ }
+
+ bool moved = false;
+
+ if (editMode == EDIT_MOVE)
+ {
+ Vector3 adjust(0, 0, 0);
+ if (gizmoAxisX.selected)
+ adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
+ if (gizmoAxisY.selected)
+ adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
+ if (gizmoAxisZ.selected)
+ adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);
+
+ moved = MoveNodes(adjust);
+ }
+ else if (editMode == EDIT_ROTATE)
+ {
+ Vector3 adjust(0, 0, 0);
+ if (gizmoAxisX.selected)
+ adjust.x = (gizmoAxisX.d - gizmoAxisX.lastD) * rotSensitivity / scale;
+ if (gizmoAxisY.selected)
+ adjust.y = -(gizmoAxisY.d - gizmoAxisY.lastD) * rotSensitivity / scale;
+ if (gizmoAxisZ.selected)
+ adjust.z = (gizmoAxisZ.d - gizmoAxisZ.lastD) * rotSensitivity / scale;
+
+ moved = RotateNodes(adjust);
+ }
+ else if (editMode == EDIT_SCALE)
+ {
+ Vector3 adjust(0, 0, 0);
+ if (gizmoAxisX.selected)
+ adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
+ if (gizmoAxisY.selected)
+ adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
+ if (gizmoAxisZ.selected)
+ adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);
+
+ // Special handling for uniform scale: use the unmodified X-axis movement only
+ if (editMode == EDIT_SCALE && gizmoAxisX.selected && gizmoAxisY.selected && gizmoAxisZ.selected)
+ {
+ float x = gizmoAxisX.t - gizmoAxisX.lastT;
+ adjust = Vector3(x, x, x);
+ }
+
+ moved = ScaleNodes(adjust);
+ }
+
+ if (moved)
+ {
+ GizmoMoved();
+ UpdateNodeAttributes();
+ needGizmoUndo = true;
+ }
+ }
+ else
+ {
+ if (previousGizmoDrag)
+ StoreGizmoEditActions();
+ }
+
+ previousGizmoDrag = drag;
+}
+
+bool IsGizmoSelected()
+{
+ return gizmo !is null && gizmo.enabled && (gizmoAxisX.selected || gizmoAxisY.selected || gizmoAxisZ.selected);
+}
+
+bool MoveNodes(Vector3 adjust)
+{
+ bool moved = false;
+
+ if (adjust.length > M_EPSILON)
+ {
+ for (uint i = 0; i < editNodes.length; ++i)
+ {
+ if (moveSnap)
+ {
+ float moveStepScaled = moveStep * snapScale;
+ adjust.x = Floor(adjust.x / moveStepScaled + 0.5) * moveStepScaled;
+ adjust.y = Floor(adjust.y / moveStepScaled + 0.5) * moveStepScaled;
+ adjust.z = Floor(adjust.z / moveStepScaled + 0.5) * moveStepScaled;
+ }
+
+ Node@ node = editNodes[i];
+ Vector3 nodeAdjust = adjust;
+ if (axisMode == AXIS_LOCAL && editNodes.length == 1)
+ nodeAdjust = node.worldRotation * nodeAdjust;
+
+ Vector3 worldPos = node.worldPosition;
+ Vector3 oldPos = node.position;
+
+ worldPos += nodeAdjust;
+
+ if (node.parent is null)
+ node.position = worldPos;
+ else
+ node.position = node.parent.WorldToLocal(worldPos);
+
+ if (node.position != oldPos)
+ moved = true;
+ }
+ }
+
+ return moved;
+}
+
+bool RotateNodes(Vector3 adjust)
+{
+ bool moved = false;
+
+ if (rotateSnap)
+ {
+ float rotateStepScaled = rotateStep * snapScale;
+ adjust.x = Floor(adjust.x / rotateStepScaled + 0.5) * rotateStepScaled;
+ adjust.y = Floor(adjust.y / rotateStepScaled + 0.5) * rotateStepScaled;
+ adjust.z = Floor(adjust.z / rotateStepScaled + 0.5) * rotateStepScaled;
+ }
+
+ if (adjust.length > M_EPSILON)
+ {
+ moved = true;
+
+ for (uint i = 0; i < editNodes.length; ++i)
+ {
+ Node@ node = editNodes[i];
+ Quaternion rotQuat(adjust);
+ if (axisMode == AXIS_LOCAL && editNodes.length == 1)
+ node.rotation = node.rotation * rotQuat;
+ else
+ {
+ Vector3 offset = node.worldPosition - gizmoAxisX.axisRay.origin;
+ if (node.parent !is null && node.parent.worldRotation != Quaternion(1, 0, 0, 0))
+ rotQuat = node.parent.worldRotation.Inverse() * rotQuat * node.parent.worldRotation;
+ node.rotation = rotQuat * node.rotation;
+ Vector3 newPosition = gizmoAxisX.axisRay.origin + rotQuat * offset;
+ if (node.parent !is null)
+ newPosition = node.parent.WorldToLocal(newPosition);
+ node.position = newPosition;
+ }
+ }
+ }
+
+ return moved;
+}
+
+bool ScaleNodes(Vector3 adjust)
+{
+ bool moved = false;
+
+ if (adjust.length > M_EPSILON)
+ {
+ for (uint i = 0; i < editNodes.length; ++i)
+ {
+ Node@ node = editNodes[i];
+
+ Vector3 scale = node.scale;
+ Vector3 oldScale = scale;
+
+ if (!scaleSnap)
+ scale += adjust;
+ else
+ {
+ float scaleStepScaled = scaleStep * snapScale;
+ if (adjust.x != 0)
+ {
+ scale.x += adjust.x * scaleStepScaled;
+ scale.x = Floor(scale.x / scaleStepScaled + 0.5) * scaleStepScaled;
+ }
+ if (adjust.y != 0)
+ {
+ scale.y += adjust.y * scaleStepScaled;
+ scale.y = Floor(scale.y / scaleStepScaled + 0.5) * scaleStepScaled;
+ }
+ if (adjust.z != 0)
+ {
+ scale.z += adjust.z * scaleStepScaled;
+ scale.z = Floor(scale.z / scaleStepScaled + 0.5) * scaleStepScaled;
+ }
+ }
+
+ if (scale != oldScale)
+ moved = true;
+
+ node.scale = scale;
+ }
+ }
+
+ return moved;
+}
+
+void StoreGizmoEditActions()
+{
+ if (needGizmoUndo && editNodes.length > 0 && oldGizmoTransforms.length == editNodes.length)
+ {
+ EditActionGroup group;
+
+ for (uint i = 0; i < editNodes.length; ++i)
+ {
+ EditNodeTransformAction action;
+ action.Define(editNodes[i], oldGizmoTransforms[i]);
+ group.actions.Push(action);
+ }
+
+ SaveEditActionGroup(group);
+ SetSceneModified();
+ }
+
+ needGizmoUndo = false;
+}
diff --git a/bin/Data/Scripts/Editor/EditorHierarchyWindow.as b/bin/Data/Scripts/Editor/EditorHierarchyWindow.as
new file mode 100644
index 0000000..dae305e
--- /dev/null
+++ b/bin/Data/Scripts/Editor/EditorHierarchyWindow.as
@@ -0,0 +1,1945 @@
+// Urho3D editor hierarchy window handling
+
+const int ITEM_NONE = 0;
+const int ITEM_NODE = 1;
+const int ITEM_COMPONENT = 2;
+const int ITEM_UI_ELEMENT = 3;
+const uint NO_ITEM = M_MAX_UNSIGNED;
+const StringHash SCENE_TYPE("Scene");
+const StringHash NODE_TYPE("Node");
+const StringHash STATICMODEL_TYPE("StaticModel");
+const StringHash ANIMATEDMODEL_TYPE("AnimatedModel");
+const StringHash STATICMODELGROUP_TYPE("StaticModelGroup");
+const StringHash SPLINEPATH_TYPE("SplinePath");
+const StringHash CONSTRAINT_TYPE("Constraint");
+const String NO_CHANGE(uint8(0));
+const StringHash TYPE_VAR("Type");
+const StringHash NODE_ID_VAR("NodeID");
+const StringHash COMPONENT_ID_VAR("ComponentID");
+const StringHash UI_ELEMENT_ID_VAR("UIElementID");
+const StringHash DRAGDROPCONTENT_VAR("DragDropContent");
+const StringHash[] ID_VARS = { StringHash(""), NODE_ID_VAR, COMPONENT_ID_VAR, UI_ELEMENT_ID_VAR };
+Color nodeTextColor(1.0f, 1.0f, 1.0f);
+Color componentTextColor(0.7f, 1.0f, 0.7f);
+
+Window@ hierarchyWindow;
+ListView@ hierarchyList;
+bool showID = true;
+
+// UIElement does not have unique ID, so use a running number to generate a new ID each time an item is inserted into hierarchy list
+const uint UI_ELEMENT_BASE_ID = 1;
+uint uiElementNextID = UI_ELEMENT_BASE_ID;
+bool showInternalUIElement = false;
+bool showTemporaryObject = false;
+Array hierarchyUpdateSelections;
+
+Variant GetUIElementID(UIElement@ element)
+{
+ Variant elementID = element.GetVar(UI_ELEMENT_ID_VAR);
+ if (elementID.empty)
+ {
+ // Generate new ID
+ elementID = uiElementNextID++;
+ // Store the generated ID
+ element.vars[UI_ELEMENT_ID_VAR] = elementID;
+ }
+
+ return elementID;
+}
+
+UIElement@ GetUIElementByID(const Variant&in id)
+{
+ return id == UI_ELEMENT_BASE_ID ? editorUIElement : editorUIElement.GetChild(UI_ELEMENT_ID_VAR, id, true);
+}
+
+void CreateHierarchyWindow()
+{
+ if (hierarchyWindow !is null)
+ return;
+
+ hierarchyWindow = LoadEditorUI("UI/EditorHierarchyWindow.xml");
+ hierarchyList = hierarchyWindow.GetChild("HierarchyList");
+ ui.root.AddChild(hierarchyWindow);
+ int height = Min(ui.root.height - 60, 500);
+ hierarchyWindow.SetSize(300, height);
+ hierarchyWindow.SetPosition(35, 100);
+ hierarchyWindow.opacity = uiMaxOpacity;
+ hierarchyWindow.BringToFront();
+
+ UpdateHierarchyItem(editorScene);
+
+ // Set selection to happen on click end, so that we can drag nodes to the inspector without resetting the inspector view
+ hierarchyList.selectOnClickEnd = true;
+
+ // Set drag & drop target mode on the node list background, which is used to parent nodes back to the root node
+ hierarchyList.contentElement.dragDropMode = DD_TARGET;
+ hierarchyList.scrollPanel.dragDropMode = DD_TARGET;
+
+ SubscribeToEvent(hierarchyWindow.GetChild("CloseButton", true), "Released", "HideHierarchyWindow");
+ SubscribeToEvent(hierarchyWindow.GetChild("ExpandButton", true), "Released", "ExpandCollapseHierarchy");
+ SubscribeToEvent(hierarchyWindow.GetChild("CollapseButton", true), "Released", "ExpandCollapseHierarchy");
+ SubscribeToEvent(hierarchyWindow.GetChild("ResetButton", true), "Released", "CollapseHierarchy");
+ SubscribeToEvent(hierarchyWindow.GetChild("ShowID", true), "Toggled", "HandleShowID");
+ SubscribeToEvent(hierarchyList, "SelectionChanged", "HandleHierarchyListSelectionChange");
+ SubscribeToEvent(hierarchyList, "ItemDoubleClicked", "HandleHierarchyListDoubleClick");
+ SubscribeToEvent(hierarchyList, "ItemClicked", "HandleHierarchyItemClick");
+ SubscribeToEvent("DragDropTest", "HandleDragDropTest");
+ SubscribeToEvent("DragDropFinish", "HandleDragDropFinish");
+ SubscribeToEvent(editorScene, "ComponentAdded", "HandleComponentAdded");
+ SubscribeToEvent(editorScene, "ComponentRemoved", "HandleComponentRemoved");
+ SubscribeToEvent(editorScene, "NodeEnabledChanged", "HandleNodeEnabledChanged");
+ SubscribeToEvent(editorScene, "ComponentEnabledChanged", "HandleComponentEnabledChanged");
+ SubscribeToEvent("TemporaryChanged", "HandleTemporaryChanged");
+}
+
+bool ToggleHierarchyWindow()
+{
+ if (hierarchyWindow.visible == false)
+ ShowHierarchyWindow();
+ else
+ HideHierarchyWindow();
+ return true;
+}
+
+void ShowHierarchyWindow()
+{
+ hierarchyWindow.visible = true;
+ hierarchyWindow.BringToFront();
+}
+
+void HideHierarchyWindow()
+{
+ if(viewportMode == VIEWPORT_COMPACT)
+ return;
+ hierarchyWindow.visible = false;
+}
+
+void ExpandCollapseHierarchy(StringHash eventType, VariantMap& eventData)
+{
+ Button@ button = eventData["Element"].GetPtr();
+ bool enable = button.name == "ExpandButton";
+ CheckBox@ checkBox = hierarchyWindow.GetChild("AllCheckBox", true);
+ bool all = checkBox.checked;
+ checkBox.checked = false; // Auto-reset
+
+ Array selections = hierarchyList.selections;
+ for (uint i = 0; i < selections.length; ++i)
+ hierarchyList.Expand(selections[i], enable, all);
+}
+
+void EnableExpandCollapseButtons(bool enable)
+{
+ String[] buttons = { "ExpandButton", "CollapseButton", "AllCheckBox" };
+ for (uint i = 0; i < buttons.length; ++i)
+ {
+ UIElement@ element = hierarchyWindow.GetChild(buttons[i], true);
+ element.enabled = enable;
+ element.children[0].color = enable ? normalTextColor : nonEditableTextColor;
+ }
+}
+
+void UpdateHierarchyItem(Serializable@ serializable, bool clear = false)
+{
+ if (clear)
+ {
+ // Remove the current selection before updating the list item (in turn trigger an update on the attribute editor)
+ hierarchyList.ClearSelection();
+
+ // Clear copybuffer when whole window refreshed
+ sceneCopyBuffer.Clear();
+ uiElementCopyBuffer.Clear();
+ }
+
+ // In case of item's parent is not found in the hierarchy list then the item will be inserted at the list root level
+ Serializable@ parent;
+ switch (GetType(serializable))
+ {
+ case ITEM_NODE:
+ parent = cast(serializable).parent;
+ break;
+
+ case ITEM_COMPONENT:
+ parent = cast(serializable).node;
+ break;
+
+ case ITEM_UI_ELEMENT:
+ parent = cast(serializable).parent;
+ break;
+
+ default:
+ break;
+ }
+ UIElement@ parentItem = hierarchyList.items[GetListIndex(parent)];
+ UpdateHierarchyItem(GetListIndex(serializable), serializable, parentItem);
+}
+
+uint UpdateHierarchyItem(uint itemIndex, Serializable@ serializable, UIElement@ parentItem)
+{
+ // Whenever we're updating, disable layout update to optimize speed
+ hierarchyList.contentElement.DisableLayoutUpdate();
+
+ if (serializable is null)
+ {
+ hierarchyList.RemoveItem(itemIndex);
+ hierarchyList.contentElement.EnableLayoutUpdate();
+ hierarchyList.contentElement.UpdateLayout();
+ return itemIndex;
+ }
+
+ int itemType = GetType(serializable);
+ Variant id = GetID(serializable, itemType);
+
+ // Remove old item if exists
+ if (itemIndex < hierarchyList.numItems && MatchID(hierarchyList.items[itemIndex], id, itemType))
+ hierarchyList.RemoveItem(itemIndex);
+
+ Text@ text = Text();
+ hierarchyList.InsertItem(itemIndex, text, parentItem);
+ text.style = "FileSelectorListText";
+
+ if (serializable.type == SCENE_TYPE || serializable is editorUIElement)
+ // The root node (scene) and editor's root UIElement cannot be moved by drag and drop
+ text.dragDropMode = DD_TARGET;
+ else
+ // Internal UIElement is not able to participate in drag and drop action
+ text.dragDropMode = itemType == ITEM_UI_ELEMENT && cast(serializable).internal ? DD_DISABLED : DD_SOURCE_AND_TARGET;
+
+ // Advance the index for the child items
+ if (itemIndex == M_MAX_UNSIGNED)
+ itemIndex = hierarchyList.numItems;
+ else
+ ++itemIndex;
+
+ String iconType = serializable.typeName;
+ if (serializable is editorUIElement)
+ iconType = "Root" + iconType;
+ IconizeUIElement(text, iconType);
+
+ SetID(text, serializable, itemType);
+ switch (itemType)
+ {
+ case ITEM_NODE:
+ {
+ Node@ node = cast(serializable);
+
+ text.text = GetNodeTitle(node);
+ text.color = nodeTextColor;
+ SetIconEnabledColor(text, node.enabled);
+
+ // Update components first
+ for (uint i = 0; i < node.numComponents; ++i)
+ {
+ Component@ component = node.components[i];
+ if (showTemporaryObject || !component.temporary)
+ AddComponentItem(itemIndex++, component, text);
+ }
+
+ // Then update child nodes recursively
+ for (uint i = 0; i < node.numChildren; ++i)
+ {
+ Node@ childNode = node.children[i];
+ if (showTemporaryObject || !childNode.temporary)
+ itemIndex = UpdateHierarchyItem(itemIndex, childNode, text);
+ }
+
+ break;
+ }
+
+ case ITEM_COMPONENT:
+ {
+ Component@ component = cast(serializable);
+ text.text = GetComponentTitle(component);
+ text.color = componentTextColor;
+ SetIconEnabledColor(text, component.enabledEffective);
+ break;
+ }
+
+ case ITEM_UI_ELEMENT:
+ {
+ UIElement@ element = cast(serializable);
+
+ text.text = GetUIElementTitle(element);
+ SetIconEnabledColor(text, element.visible);
+
+ // Update child elements recursively
+ for (uint i = 0; i < element.numChildren; ++i)
+ {
+ UIElement@ childElement = element.children[i];
+ if ((showInternalUIElement || !childElement.internal) && (showTemporaryObject || !childElement.temporary))
+ itemIndex = UpdateHierarchyItem(itemIndex, childElement, text);
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ // Re-enable layout update (and do manual layout) now
+ hierarchyList.contentElement.EnableLayoutUpdate();
+ hierarchyList.contentElement.UpdateLayout();
+
+ return itemIndex;
+}
+
+void UpdateHierarchyItemText(uint itemIndex, bool iconEnabled, const String&in textTitle = NO_CHANGE)
+{
+ Text@ text = hierarchyList.items[itemIndex];
+ if (text is null)
+ return;
+
+ SetIconEnabledColor(text, iconEnabled);
+
+ if (textTitle != NO_CHANGE)
+ text.text = textTitle;
+}
+
+void AddComponentItem(uint compItemIndex, Component@ component, UIElement@ parentItem)
+{
+ Text@ text = Text();
+ hierarchyList.InsertItem(compItemIndex, text, parentItem);
+ text.style = "FileSelectorListText";
+ text.vars[TYPE_VAR] = ITEM_COMPONENT;
+ text.vars[NODE_ID_VAR] = component.node.id;
+ text.vars[COMPONENT_ID_VAR] = component.id;
+ text.text = GetComponentTitle(component);
+ text.color = componentTextColor;
+ text.dragDropMode = DD_SOURCE_AND_TARGET;
+
+ IconizeUIElement(text, component.typeName);
+ SetIconEnabledColor(text, component.enabledEffective);
+}
+
+int GetType(Serializable@ serializable)
+{
+ if (cast(serializable) !is null)
+ return ITEM_NODE;
+ else if (cast(serializable) !is null)
+ return ITEM_COMPONENT;
+ else if (cast(serializable) !is null)
+ return ITEM_UI_ELEMENT;
+ else
+ return ITEM_NONE;
+}
+
+void SetID(Text@ text, Serializable@ serializable, int itemType = ITEM_NONE)
+{
+ // If item type is not provided, auto detect it
+ if (itemType == ITEM_NONE)
+ itemType = GetType(serializable);
+
+ text.vars[TYPE_VAR] = itemType;
+ text.vars[ID_VARS[itemType]] = GetID(serializable, itemType);
+
+ // Set node ID as drag and drop content for node ID editing
+ if (itemType == ITEM_NODE)
+ text.vars[DRAGDROPCONTENT_VAR] = String(text.vars[NODE_ID_VAR].GetUInt());
+
+ switch (itemType)
+ {
+ case ITEM_COMPONENT:
+ text.vars[NODE_ID_VAR] = cast(serializable).node.id;
+ break;
+
+ case ITEM_UI_ELEMENT:
+ // Subscribe to UI-element events
+ SubscribeToEvent(serializable, "NameChanged", "HandleElementNameChanged");
+ SubscribeToEvent(serializable, "VisibleChanged", "HandleElementVisibilityChanged");
+ SubscribeToEvent(serializable, "Resized", "HandleElementAttributeChanged");
+ SubscribeToEvent(serializable, "Positioned", "HandleElementAttributeChanged");
+ break;
+
+ default:
+ break;
+ }
+}
+
+uint GetID(Serializable@ serializable, int itemType = ITEM_NONE)
+{
+ // If item type is not provided, auto detect it
+ if (itemType == ITEM_NONE)
+ itemType = GetType(serializable);
+
+ switch (itemType)
+ {
+ case ITEM_NODE:
+ return cast(serializable).id;
+
+ case ITEM_COMPONENT:
+ return cast(serializable).id;
+
+ case ITEM_UI_ELEMENT:
+ return GetUIElementID(cast(serializable)).GetUInt();
+ }
+
+ return M_MAX_UNSIGNED;
+}
+
+bool MatchID(UIElement@ element, const Variant&in id, int itemType)
+{
+ return element.GetVar(TYPE_VAR).GetInt() == itemType && element.GetVar(ID_VARS[itemType]) == id;
+}
+
+uint GetListIndex(Serializable@ serializable)
+{
+ if (serializable is null)
+ return NO_ITEM;
+
+ int itemType = GetType(serializable);
+ Variant id = GetID(serializable, itemType);
+
+ uint numItems = hierarchyList.numItems;
+ for (uint i = 0; i < numItems; ++i)
+ {
+ if (MatchID(hierarchyList.items[i], id, itemType))
+ return i;
+ }
+
+ return NO_ITEM;
+}
+
+UIElement@ GetListUIElement(uint index)
+{
+ UIElement@ item = hierarchyList.items[index];
+ if (item is null)
+ return null;
+
+ // Get the text item's ID and use it to retrieve the actual UIElement the text item is associated to
+ return GetUIElementByID(GetUIElementID(item));
+}
+
+Node@ GetListNode(uint index)
+{
+ UIElement@ item = hierarchyList.items[index];
+ if (item is null)
+ return null;
+
+ return editorScene.GetNode(item.vars[NODE_ID_VAR].GetUInt());
+}
+
+Component@ GetListComponent(uint index)
+{
+ UIElement@ item = hierarchyList.items[index];
+ return GetListComponent(item);
+}
+
+Component@ GetListComponent(UIElement@ item)
+{
+ if (item is null)
+ return null;
+
+ if (item.vars[TYPE_VAR].GetInt() != ITEM_COMPONENT)
+ return null;
+
+ return editorScene.GetComponent(item.vars[COMPONENT_ID_VAR].GetUInt());
+}
+
+uint GetComponentListIndex(Component@ component)
+{
+ if (component is null)
+ return NO_ITEM;
+
+ uint numItems = hierarchyList.numItems;
+ for (uint i = 0; i < numItems; ++i)
+ {
+ UIElement@ item = hierarchyList.items[i];
+ if (item.vars[TYPE_VAR].GetInt() == ITEM_COMPONENT && item.vars[COMPONENT_ID_VAR].GetUInt() == component.id)
+ return i;
+ }
+
+ return NO_ITEM;
+}
+
+String GetUIElementTitle(UIElement@ element)
+{
+ String ret;
+
+ // Only top level UI-element has this variable
+ String modifiedStr = element.GetVar(MODIFIED_VAR).GetBool() ? "*" : "";
+ ret = (element.name.empty ? element.typeName : element.name) + modifiedStr + " [" + GetUIElementID(element).ToString() + "]";
+
+ if (element.temporary)
+ ret += " (Temp)";
+
+ return ret;
+}
+
+String GetNodeTitle(Node@ node)
+{
+ String ret;
+
+ if (node.name.empty)
+ ret = node.typeName;
+ else
+ ret = node.name;
+
+ if (showID)
+ {
+ if (node.replicated)
+ ret += " (" + String(node.id) + ")";
+ else
+ ret += " (Local " + String(node.id) + ")";
+
+ if (node.temporary)
+ ret += " (Temp)";
+ }
+
+ return ret;
+}
+
+String GetComponentTitle(Component@ component)
+{
+ String ret = component.typeName;
+
+ if (showID)
+ {
+ if (!component.replicated)
+ ret += " (Local)";
+
+ if (component.temporary)
+ ret += " (Temp)";
+ }
+ return ret;
+}
+
+void SelectNode(Node@ node, bool multiselect)
+{
+ if (node is null && !multiselect)
+ {
+ hierarchyList.ClearSelection();
+ return;
+ }
+
+ lastSelectedNode = node;
+ uint index = GetListIndex(node);
+ uint numItems = hierarchyList.numItems;
+
+ if (index < numItems)
+ {
+ // Expand the node chain now
+ if (!multiselect || !hierarchyList.IsSelected(index))
+ {
+ // Go in the parent chain up to make sure the chain is expanded
+ Node@ current = node;
+ do
+ {
+ hierarchyList.Expand(GetListIndex(current), true);
+ current = current.parent;
+ }
+ while (current !is null);
+ }
+
+ // This causes an event to be sent, in response we set the node/component selections, and refresh editors
+ if (!multiselect)
+ hierarchyList.selection = index;
+ else
+ hierarchyList.ToggleSelection(index);
+ }
+ else if (!multiselect)
+ hierarchyList.ClearSelection();
+}
+
+void DeselectNode(Node@ node)
+{
+ if (node is null)
+ {
+ hierarchyList.ClearSelection();
+ return;
+ }
+
+ uint index = GetListIndex(node);
+ uint numItems = hierarchyList.numItems;
+
+ if (index < numItems)
+ {
+ hierarchyList.ToggleSelection(index);
+ }
+}
+
+void SelectComponent(Component@ component, bool multiselect)
+{
+ if (component is null && !multiselect)
+ {
+ hierarchyList.ClearSelection();
+ return;
+ }
+
+ Node@ node = component.node;
+ if (node is null && !multiselect)
+ {
+ hierarchyList.ClearSelection();
+ return;
+ }
+
+ uint nodeIndex = GetListIndex(node);
+ uint componentIndex = GetComponentListIndex(component);
+ uint numItems = hierarchyList.numItems;
+
+ if (nodeIndex < numItems && componentIndex < numItems)
+ {
+ // Expand the node chain now
+ if (!multiselect || !hierarchyList.IsSelected(componentIndex))
+ {
+ // Go in the parent chain up to make sure the chain is expanded
+ Node@ current = node;
+ do
+ {
+ hierarchyList.Expand(GetListIndex(current), true);
+ current = current.parent;
+ }
+ while (current !is null);
+ }
+
+ // This causes an event to be sent, in response we set the node/component selections, and refresh editors
+ if (!multiselect)
+ hierarchyList.selection = componentIndex;
+ else
+ hierarchyList.ToggleSelection(componentIndex);
+ }
+ else if (!multiselect)
+ hierarchyList.ClearSelection();
+}
+
+void SelectUIElement(UIElement@ element, bool multiselect)
+{
+ uint index = GetListIndex(element);
+ uint numItems = hierarchyList.numItems;
+
+ if (index < numItems)
+ {
+ // Expand the node chain now
+ if (!multiselect || !hierarchyList.IsSelected(index))
+ {
+ // Go in the parent chain up to make sure the chain is expanded
+ UIElement@ current = element;
+ do
+ {
+ hierarchyList.Expand(GetListIndex(current), true);
+ current = current.parent;
+ }
+ while (current !is null);
+ }
+
+ if (!multiselect)
+ hierarchyList.selection = index;
+ else
+ hierarchyList.ToggleSelection(index);
+ }
+ else if (!multiselect)
+ hierarchyList.ClearSelection();
+}
+
+// Find the first selected StaticModel/AtimatedModel component or node with it
+Model@ FindFirstSelectedModel()
+{
+ for (uint i = 0; i < selectedComponents.length; ++i)
+ {
+ // Get SM, but also works well for AnimatedModel
+ StaticModel@ sm = cast(selectedComponents[i]);
+ if (sm !is null && sm.model !is null)
+ return sm.model;
+ }
+
+ for (uint i = 0; i < selectedNodes.length; ++i)
+ {
+ for (uint j = 0; j < selectedNodes[i].numComponents; ++j)
+ {
+ StaticModel@ sm = cast(selectedNodes[i].components[j]);
+ if (sm !is null && sm.model !is null)
+ return sm.model;
+ }
+ }
+
+ return null;
+}
+
+void UpdateModelInfo(Model@ model)
+{
+ if (model is null)
+ {
+ modelInfoText.text = "";
+ return;
+ }
+
+ String infoStr = "Model: " + model.name;
+
+ infoStr += "\n Morphs: " + model.numMorphs;
+
+ for (uint g = 0; g < model.numGeometries; ++g)
+ {
+ uint numLods = model.numGeometryLodLevels[g];
+ infoStr += "\n Geometry " + g + "\n Lods: " + numLods;
+ for (uint l = 0; l < numLods; l++)
+ {
+ Geometry@ geom = model.GetGeometry(g, l);
+ infoStr += "\n Vertex Count: " + geom.vertexCount;
+ infoStr += "\n Index Count: " + geom.indexCount;
+ }
+ }
+
+ modelInfoText.text = infoStr;
+}
+
+void HandleHierarchyListSelectionChange()
+{
+ if (inSelectionModify)
+ return;
+
+ ClearSceneSelection();
+ ClearUIElementSelection();
+
+ Array indices = hierarchyList.selections;
+
+ // Enable Expand/Collapse button when there is selection
+ EnableExpandCollapseButtons(indices.length > 0);
+
+ for (uint i = 0; i < indices.length; ++i)
+ {
+ uint index = indices[i];
+ UIElement@ item = hierarchyList.items[index];
+ int type = item.vars[TYPE_VAR].GetInt();
+ if (type == ITEM_COMPONENT)
+ {
+ Component@ comp = GetListComponent(index);
+ if (comp !is null)
+ selectedComponents.Push(comp);
+ }
+ else if (type == ITEM_NODE)
+ {
+ Node@ node = GetListNode(index);
+ if (node !is null)
+ selectedNodes.Push(node);
+ }
+ else if (type == ITEM_UI_ELEMENT)
+ {
+ UIElement@ element = GetListUIElement(index);
+ if (element !is null && element !is editorUIElement)
+ selectedUIElements.Push(element);
+ }
+ }
+
+ // If only one node/UIElement selected, use it for editing
+ if (selectedNodes.length == 1)
+ editNode = selectedNodes[0];
+ if (selectedUIElements.length == 1)
+ editUIElement = selectedUIElements[0];
+
+ // If selection contains only components, and they have a common node, use it for editing
+ if (selectedNodes.empty && !selectedComponents.empty)
+ {
+ Node@ commonNode;
+ for (uint i = 0; i < selectedComponents.length; ++i)
+ {
+ if (i == 0)
+ commonNode = selectedComponents[i].node;
+ else
+ {
+ if (selectedComponents[i].node !is commonNode)
+ commonNode = null;
+ }
+ }
+ editNode = commonNode;
+ }
+
+ UpdateModelInfo(FindFirstSelectedModel());
+
+ // Now check if the component(s) can be edited. If many selected, must have same type or have same edit node
+ if (!selectedComponents.empty)
+ {
+ if (editNode is null)
+ {
+ StringHash compType = selectedComponents[0].type;
+ bool sameType = true;
+ for (uint i = 1; i < selectedComponents.length; ++i)
+ {
+ if (selectedComponents[i].type != compType)
+ {
+ sameType = false;
+ break;
+ }
+ }
+ if (sameType)
+ editComponents = selectedComponents;
+ }
+ else
+ {
+ editComponents = selectedComponents;
+ numEditableComponentsPerNode = selectedComponents.length;
+ }
+ }
+
+ // If just nodes selected, and no components, show as many matching components for editing as possible
+ if (!selectedNodes.empty && selectedComponents.empty && selectedNodes[0].numComponents > 0)
+ {
+ uint count = 0;
+ for (uint j = 0; j < selectedNodes[0].numComponents; ++j)
+ {
+ StringHash compType = selectedNodes[0].components[j].type;
+ bool sameType = true;
+ for (uint i = 1; i < selectedNodes.length; ++i)
+ {
+ if (selectedNodes[i].numComponents <= j || selectedNodes[i].components[j].type != compType)
+ {
+ sameType = false;
+ break;
+ }
+ }
+
+ if (sameType)
+ {
+ ++count;
+ for (uint i = 0; i < selectedNodes.length; ++i)
+ editComponents.Push(selectedNodes[i].components[j]);
+ }
+ }
+ if (count > 1)
+ numEditableComponentsPerNode = count;
+ }
+
+ if (selectedNodes.empty && editNode !is null)
+ editNodes.Push(editNode);
+ else
+ {
+ editNodes = selectedNodes;
+
+ // Cannot multi-edit on scene and node(s) together as scene and node do not share identical attributes,
+ // editing via gizmo does not make too much sense either
+ if (editNodes.length > 1 && editNodes[0] is editorScene)
+ editNodes.Erase(0);
+ }
+
+ if (selectedUIElements.empty && editUIElement !is null)
+ editUIElements.Push(editUIElement);
+ else
+ editUIElements = selectedUIElements;
+
+ PositionGizmo();
+ UpdateAttributeInspector();
+ UpdateCameraPreview();
+}
+
+void HandleHierarchyListDoubleClick(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ item = eventData["Item"].GetPtr();
+ int type = item.vars[TYPE_VAR].GetInt();
+ // Locate nodes from the scene by double-clicking
+ if (type == ITEM_NODE)
+ {
+ Node@ node = editorScene.GetNode(item.vars[NODE_ID_VAR].GetUInt());
+ Array nodes;
+ nodes.Push(node);
+ LocateNodes(nodes);
+ }
+ else if (type == ITEM_COMPONENT)
+ {
+ Component@ component = editorScene.GetComponent(item.vars[COMPONENT_ID_VAR].GetUInt());
+ Array components;
+ components.Push(component);
+ LocateComponents(components);
+ }
+
+ bool isExpanded = hierarchyList.IsExpanded(hierarchyList.selection);
+
+ if (!isExpanded && eventData["Button"].GetInt() == MOUSEB_LEFT)
+ {
+ isExpanded = !isExpanded;
+ hierarchyList.Expand(hierarchyList.selection, isExpanded, false);
+ }
+}
+
+void HandleHierarchyItemClick(StringHash eventType, VariantMap& eventData)
+{
+ if (eventData["Button"].GetInt() != MOUSEB_RIGHT)
+ return;
+
+ UIElement@ uiElement = eventData["Item"].GetPtr();
+ int selectionIndex = eventData["Selection"].GetInt();
+
+ Array actions;
+ int type = uiElement.vars[TYPE_VAR].GetInt();
+
+ // Adds left clicked items to selection which is not normal listview behavior
+ if (type == ITEM_COMPONENT || type == ITEM_NODE)
+ {
+ if (input.keyDown[KEY_LSHIFT])
+ hierarchyList.AddSelection(selectionIndex);
+ else
+ {
+ hierarchyList.ClearSelection();
+ hierarchyList.AddSelection(selectionIndex);
+ }
+ }
+
+ if (type == ITEM_COMPONENT)
+ {
+ Component@ targetComponent = editorScene.GetComponent(uiElement.vars[COMPONENT_ID_VAR].GetUInt());
+ if (targetComponent is null)
+ return;
+
+ actions.Push(CreateContextMenuItem("Copy", "HandleHierarchyContextCopy"));
+ actions.Push(CreateContextMenuItem("Cut", "HandleHierarchyContextCut"));
+ actions.Push(CreateContextMenuItem("Delete", "HandleHierarchyContextDelete"));
+ actions.Push(CreateContextMenuItem("Paste", "HandleHierarchyContextPaste"));
+ actions.Push(CreateContextMenuItem("Enable/disable", "HandleHierarchyContextEnableDisable"));
+
+ /* actions.Push(CreateBrowserFileActionMenu("Edit", "HandleBrowserEditResource", file)); */
+ }
+ else if (type == ITEM_NODE)
+ {
+ actions.Push(CreateContextMenuItem("Create Replicated Node", "HandleHierarchyContextCreateReplicatedNode"));
+ actions.Push(CreateContextMenuItem("Create Local Node", "HandleHierarchyContextCreateLocalNode"));
+ actions.Push(CreateContextMenuItem("Duplicate", "HandleHierarchyContextDuplicate"));
+ actions.Push(CreateContextMenuItem("Copy", "HandleHierarchyContextCopy"));
+ actions.Push(CreateContextMenuItem("Cut", "HandleHierarchyContextCut"));
+ actions.Push(CreateContextMenuItem("Delete", "HandleHierarchyContextDelete"));
+ actions.Push(CreateContextMenuItem("Paste", "HandleHierarchyContextPaste"));
+ actions.Push(CreateContextMenuItem("Reset to default", "HandleHierarchyContextResetToDefault"));
+ actions.Push(CreateContextMenuItem("Reset position", "HandleHierarchyContextResetPosition"));
+ actions.Push(CreateContextMenuItem("Reset rotation", "HandleHierarchyContextResetRotation"));
+ actions.Push(CreateContextMenuItem("Reset scale", "HandleHierarchyContextResetScale"));
+ actions.Push(CreateContextMenuItem("Enable/disable", "HandleHierarchyContextEnableDisable"));
+ actions.Push(CreateContextMenuItem("Unparent", "HandleHierarchyContextUnparent"));
+ }
+ else if (type == ITEM_UI_ELEMENT)
+ {
+ // close ui element
+ actions.Push(CreateContextMenuItem("Close UI-Layout", "HandleHierarchyContextUIElementCloseUILayout"));
+ actions.Push(CreateContextMenuItem("Close all UI-layouts", "HandleHierarchyContextUIElementCloseAllUILayouts"));
+ }
+
+ if (actions.length > 0)
+ ActivateContextMenu(actions);
+}
+
+void HandleDragDropTest(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ source = eventData["Source"].GetPtr();
+ UIElement@ target = eventData["Target"].GetPtr();
+ int itemType;
+ eventData["Accept"] = TestDragDrop(source, target, itemType);
+}
+
+void HandleDragDropFinish(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ source = eventData["Source"].GetPtr();
+ UIElement@ target = eventData["Target"].GetPtr();
+ int itemType = ITEM_NONE;
+ bool accept = TestDragDrop(source, target, itemType);
+ eventData["Accept"] = accept;
+ if (!accept)
+ return;
+
+ // Resource browser
+ if (source !is null && source.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt() > 0)
+ {
+ int type = source.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt();
+
+ BrowserFile@ browserFile = GetBrowserFileFromId(source.vars[TEXT_VAR_FILE_ID].GetUInt());
+ if (browserFile is null)
+ return;
+
+ Component@ createdComponent;
+ if (itemType == ITEM_NODE)
+ {
+ Node@ targetNode = editorScene.GetNode(target.vars[NODE_ID_VAR].GetUInt());
+ if (targetNode is null)
+ return;
+
+ if (type == RESOURCE_TYPE_PREFAB)
+ {
+ LoadNode(browserFile.GetFullPath(), targetNode);
+ }
+ else if(type == RESOURCE_TYPE_SCRIPTFILE)
+ {
+ // TODO: not sure what to do here. lots of choices.
+ }
+ else if(type == RESOURCE_TYPE_MODEL)
+ {
+ CreateModelWithStaticModel(browserFile.resourceKey, targetNode);
+ return;
+ }
+ else if (type == RESOURCE_TYPE_PARTICLEEFFECT)
+ {
+ if (browserFile.extension == "xml")
+ {
+ ParticleEffect@ effect = cache.GetResource("ParticleEffect", browserFile.resourceKey);
+ if (effect is null)
+ return;
+
+ ParticleEmitter@ emitter = targetNode.CreateComponent("ParticleEmitter");
+ emitter.effect = effect;
+ createdComponent = emitter;
+ }
+ }
+ else if (type == RESOURCE_TYPE_2D_PARTICLE_EFFECT)
+ {
+ if (browserFile.extension == "xml")
+ {
+ Resource@ effect = cache.GetResource("ParticleEffect2D", browserFile.resourceKey);
+ if (effect is null)
+ return;
+
+ ResourceRef effectRef;
+ effectRef.type = effect.type;
+ effectRef.name = effect.name;
+
+ Component@ emitter = targetNode.CreateComponent("ParticleEmitter2D");
+ emitter.SetAttribute("Particle Effect", Variant(effectRef));
+ createdComponent = emitter;
+ }
+ }
+ }
+ else if (itemType == ITEM_COMPONENT)
+ {
+ Component@ targetComponent = editorScene.GetComponent(target.vars[COMPONENT_ID_VAR].GetUInt());
+
+ if (targetComponent is null)
+ return;
+
+ if (type == RESOURCE_TYPE_MATERIAL)
+ {
+ StaticModel@ model = cast(targetComponent);
+ if (model is null)
+ return;
+
+ AssignMaterial(model, browserFile.resourceKey);
+ }
+ else if (type == RESOURCE_TYPE_MODEL)
+ {
+ StaticModel@ staticModel = cast(targetComponent);
+ if (staticModel is null)
+ return;
+
+ AssignModel(staticModel, browserFile.resourceKey);
+ }
+ }
+ else
+ {
+ LineEdit@ text = cast(target);
+ if (text is null)
+ return;
+ text.text = browserFile.resourceKey;
+ VariantMap data();
+ data["Element"] = text;
+ data["Text"] = text.text;
+ text.SendEvent("TextFinished", data);
+ }
+
+ if (createdComponent !is null)
+ {
+ CreateLoadedComponent(createdComponent);
+ }
+ return;
+ }
+
+ if (itemType == ITEM_NODE)
+ {
+ Node@ targetNode = editorScene.GetNode(target.vars[NODE_ID_VAR].GetUInt());
+ Array sourceNodes = GetMultipleSourceNodes(source);
+
+ if (sourceNodes.length > 0)
+ {
+ if (input.qualifierDown[QUAL_CTRL] && sourceNodes.length == 1)
+ SceneReorder(sourceNodes[0], targetNode);
+ else
+ {
+ // If target is null, parent to scene
+ if (targetNode is null)
+ targetNode = editorScene;
+
+ if (sourceNodes.length > 1)
+ SceneChangeParent(sourceNodes[0], sourceNodes, targetNode);
+ else
+ SceneChangeParent(sourceNodes[0], targetNode);
+ }
+
+ // Focus the node at its new position in the list which in turn should trigger a refresh in attribute inspector
+ FocusNode(sourceNodes[0]);
+ }
+ }
+ else if (itemType == ITEM_UI_ELEMENT)
+ {
+ UIElement@ sourceElement = GetUIElementByID(source.vars[UI_ELEMENT_ID_VAR].GetUInt());
+ UIElement@ targetElement = GetUIElementByID(target.vars[UI_ELEMENT_ID_VAR].GetUInt());
+
+ // If target is null, cannot proceed
+ if (targetElement is null)
+ return;
+
+ if (input.qualifierDown[QUAL_CTRL])
+ {
+ if (!UIElementReorder(sourceElement, targetElement))
+ return;
+ }
+ else
+ {
+ if (!UIElementChangeParent(sourceElement, targetElement))
+ return;
+ }
+
+ // Focus the element at its new position in the list which in turn should trigger a refresh in attribute inspector
+ FocusUIElement(sourceElement);
+ }
+ else if (itemType == ITEM_COMPONENT)
+ {
+ Component@ targetComponent = editorScene.GetComponent(target.vars[COMPONENT_ID_VAR].GetUInt());
+ Array sourceNodes = GetMultipleSourceNodes(source);
+
+ if (input.qualifierDown[QUAL_CTRL] && sourceNodes.length == 1)
+ {
+ // Reorder components within node
+ Component@ sourceComponent = editorScene.GetComponent(source.vars[COMPONENT_ID_VAR].GetUInt());
+ SceneReorder(sourceComponent, targetComponent);
+ }
+ else
+ {
+ if (targetComponent !is null && sourceNodes.length > 0)
+ {
+ // Drag node to StaticModelGroup to make it an instance
+ StaticModelGroup@ smg = cast(targetComponent);
+ if (smg !is null)
+ {
+ // Save undo action
+ EditAttributeAction action;
+ uint attrIndex = GetAttributeIndex(smg, "Instance Nodes");
+ Variant oldIDs = smg.attributes[attrIndex];
+
+ for (uint i = 0; i < sourceNodes.length; ++i)
+ smg.AddInstanceNode(sourceNodes[i]);
+
+ action.Define(smg, attrIndex, oldIDs);
+ SaveEditAction(action);
+ SetSceneModified();
+ UpdateAttributeInspector(false);
+ }
+
+ // Drag node to SplinePath to make it a control point
+ SplinePath@ spline = cast(targetComponent);
+ if (spline !is null)
+ {
+ // Save undo action
+ EditAttributeAction action;
+ uint attrIndex = GetAttributeIndex(spline, "Control Points");
+ Variant oldIDs = spline.attributes[attrIndex];
+
+ for (uint i = 0; i < sourceNodes.length; ++i)
+ spline.AddControlPoint(sourceNodes[i]);
+
+ action.Define(spline, attrIndex, oldIDs);
+ SaveEditAction(action);
+ SetSceneModified();
+ UpdateAttributeInspector(false);
+ }
+
+ // Drag a node to Constraint to make it the remote end of the constraint
+ Constraint@ constraint = cast(targetComponent);
+ RigidBody@ rigidBody = sourceNodes[0].GetComponent("RigidBody");
+ if (constraint !is null && rigidBody !is null)
+ {
+ // Save undo action
+ EditAttributeAction action;
+ uint attrIndex = GetAttributeIndex(constraint, "Other Body NodeID");
+ Variant oldID = constraint.attributes[attrIndex];
+
+ constraint.otherBody = rigidBody;
+
+ action.Define(constraint, attrIndex, oldID);
+ SaveEditAction(action);
+ SetSceneModified();
+ UpdateAttributeInspector(false);
+ }
+ }
+ }
+ }
+}
+
+Array GetMultipleSourceNodes(UIElement@ source)
+{
+ Array nodeList;
+
+ Node@ node = editorScene.GetNode(source.vars[NODE_ID_VAR].GetUInt());
+ if (node !is null)
+ nodeList.Push(node);
+
+ // Handle additional selected children from a ListView
+ if (source.parent !is null && source.parent.typeName == "HierarchyContainer")
+ {
+ ListView@ listView_ = cast(source.parent.parent.parent);
+ if (listView_ is null)
+ return nodeList;
+
+ bool sourceIsSelected = false;
+ for (uint i = 0; i < listView_.selectedItems.length; ++i)
+ {
+ if (listView_.selectedItems[i] is source)
+ {
+ sourceIsSelected = true;
+ break;
+ }
+ }
+
+ if (sourceIsSelected)
+ {
+ for (uint i = 0; i < listView_.selectedItems.length; ++i)
+ {
+ UIElement@ item_ = listView_.selectedItems[i];
+ // The source item is already added
+ if (item_ is source)
+ continue;
+
+ if (item_.vars[TYPE_VAR] == ITEM_NODE)
+ {
+ Node@ n = editorScene.GetNode(item_.vars[NODE_ID_VAR].GetUInt());
+ if (n !is null)
+ nodeList.Push(n);
+ }
+ }
+ }
+ }
+
+ return nodeList;
+}
+
+bool TestDragDrop(UIElement@ source, UIElement@ target, int& itemType)
+{
+ int targetItemType = target.GetVar(TYPE_VAR).GetInt();
+
+ if (targetItemType == ITEM_NODE)
+ {
+ Node@ sourceNode;
+ Node@ targetNode;
+ Variant variant = source.GetVar(NODE_ID_VAR);
+ if (!variant.empty)
+ sourceNode = editorScene.GetNode(variant.GetUInt());
+ variant = target.GetVar(NODE_ID_VAR);
+ if (!variant.empty)
+ targetNode = editorScene.GetNode(variant.GetUInt());
+ Array sourceNodes = GetMultipleSourceNodes(source);
+
+ if (sourceNode !is null && targetNode !is null)
+ {
+ itemType = ITEM_NODE;
+
+ // Ctrl pressed: reorder
+ if (input.qualifierDown[QUAL_CTRL] && sourceNodes.length == 1)
+ {
+ // Must be within the same parent
+ if (sourceNode.parent is null || sourceNode.parent !is targetNode.parent)
+ return false;
+ }
+ // No ctrl: Reparent
+ else
+ {
+ if (sourceNode.parent is targetNode)
+ return false;
+ if (targetNode.parent is sourceNode)
+ return false;
+ }
+ }
+
+ // Resource browser
+ if (sourceNode is null && targetNode !is null)
+ {
+ itemType = ITEM_NODE;
+ int type = source.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt();
+ return type == RESOURCE_TYPE_PREFAB ||
+ type == RESOURCE_TYPE_SCRIPTFILE ||
+ type == RESOURCE_TYPE_MODEL ||
+ type == RESOURCE_TYPE_PARTICLEEFFECT ||
+ type == RESOURCE_TYPE_2D_PARTICLE_EFFECT;
+ }
+
+ return true;
+ }
+ else if (targetItemType == ITEM_UI_ELEMENT)
+ {
+ UIElement@ sourceElement;
+ UIElement@ targetElement;
+ Variant variant = source.GetVar(UI_ELEMENT_ID_VAR);
+ if (!variant.empty)
+ sourceElement = GetUIElementByID(variant.GetUInt());
+ variant = target.GetVar(UI_ELEMENT_ID_VAR);
+ if (!variant.empty)
+ targetElement = GetUIElementByID(variant.GetUInt());
+
+ if (sourceElement !is null && targetElement !is null)
+ {
+ itemType = ITEM_UI_ELEMENT;
+
+ // Ctrl pressed: reorder
+ if (input.qualifierDown[QUAL_CTRL])
+ {
+ // Must be within the same parent
+ if (sourceElement.parent is null || sourceElement.parent !is targetElement.parent)
+ return false;
+ }
+ // No ctrl: reparent
+ else
+ {
+ if (sourceElement.parent is targetElement)
+ return false;
+ if (targetElement.parent is sourceElement)
+ return false;
+ }
+ }
+
+ return true;
+ }
+ else if (targetItemType == ITEM_COMPONENT)
+ {
+ Node@ sourceNode;
+ Component@ sourceComponent;
+ Component@ targetComponent;
+ Variant variant = source.GetVar(NODE_ID_VAR);
+ if (!variant.empty)
+ sourceNode = editorScene.GetNode(variant.GetUInt());
+ variant = target.GetVar(COMPONENT_ID_VAR);
+ if (!variant.empty)
+ targetComponent = editorScene.GetComponent(variant.GetUInt());
+ variant = source.GetVar(COMPONENT_ID_VAR);
+ if (!variant.empty)
+ sourceComponent = editorScene.GetComponent(variant.GetUInt());
+ Array sourceNodes = GetMultipleSourceNodes(source);
+
+ itemType = ITEM_COMPONENT;
+
+ if (input.qualifierDown[QUAL_CTRL] && sourceNodes.length == 1)
+ {
+ // Reorder components within node
+ if (sourceComponent !is null && targetComponent !is null && sourceComponent.node is targetComponent.node)
+ return true;
+ }
+ else
+ {
+ // Dragging of nodes to StaticModelGroup, SplinePath or Constraint
+ if (sourceNode !is null && targetComponent !is null && (targetComponent.type == STATICMODELGROUP_TYPE ||
+ targetComponent.type == CONSTRAINT_TYPE || targetComponent.type == SPLINEPATH_TYPE))
+ return true;
+
+ // Resource browser
+ int type = source.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt();
+ if (targetComponent.type == STATICMODEL_TYPE || targetComponent.type == ANIMATEDMODEL_TYPE)
+ return type == RESOURCE_TYPE_MATERIAL || type == RESOURCE_TYPE_MODEL;
+ }
+
+ return false;
+ }
+ else if (source.vars.Contains(TEXT_VAR_RESOURCE_TYPE)) // only testing resource browser ui elements
+ {
+ int type = source.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt();
+
+ // Test against resource pickers
+ LineEdit@ lineEdit = cast(target);
+ if (lineEdit !is null)
+ {
+ StringHash resourceType = GetResourceTypeFromPickerLineEdit(lineEdit);
+ if (resourceType == StringHash("Material") && type == RESOURCE_TYPE_MATERIAL)
+ return true;
+ else if (resourceType == StringHash("Model") && type == RESOURCE_TYPE_MODEL)
+ return true;
+ else if (resourceType == StringHash("Animation") && type == RESOURCE_TYPE_ANIMATION)
+ return true;
+ }
+ }
+ return true;
+}
+
+StringHash GetResourceTypeFromPickerLineEdit(UIElement@ lineEdit)
+{
+ Array@ targets = GetAttributeEditorTargets(lineEdit);
+ if (!targets.empty)
+ {
+ resourcePickIndex = lineEdit.vars["Index"].GetUInt();
+ resourcePickSubIndex = lineEdit.vars["SubIndex"].GetUInt();
+ AttributeInfo info = targets[0].attributeInfos[resourcePickIndex];
+ StringHash resourceType;
+ if (info.type == VAR_RESOURCEREF)
+ return targets[0].attributes[resourcePickIndex].GetResourceRef().type;
+ else if (info.type == VAR_RESOURCEREFLIST)
+ return targets[0].attributes[resourcePickIndex].GetResourceRefList().type;
+ else if (info.type == VAR_VARIANTVECTOR)
+ return targets[0].attributes[resourcePickIndex].GetVariantVector()[resourcePickSubIndex].GetResourceRef().type;
+ }
+ return StringHash();
+}
+
+void FocusNode(Node@ node)
+{
+ uint index = GetListIndex(node);
+ hierarchyList.selection = index;
+}
+
+void FocusComponent(Component@ component)
+{
+ uint index = GetComponentListIndex(component);
+ hierarchyList.selection = index;
+}
+
+void FocusUIElement(UIElement@ element)
+{
+ uint index = GetListIndex(element);
+ hierarchyList.selection = index;
+}
+
+void CreateBuiltinObject(const String& name)
+{
+ Node@ newNode = editorScene.CreateChild(name, REPLICATED);
+ // Set the new node a certain distance from the camera
+ newNode.position = GetNewNodePosition();
+
+ StaticModel@ object = newNode.CreateComponent("StaticModel");
+ object.model = cache.GetResource("Model", "Models/" + name + ".mdl");
+
+ // Create an undo action for the create
+ CreateNodeAction action;
+ action.Define(newNode);
+ SaveEditAction(action);
+ SetSceneModified();
+
+ FocusNode(newNode);
+}
+
+bool CheckHierarchyWindowFocus()
+{
+ // When we do edit operations based on key shortcuts, make sure the hierarchy list is focused
+ return ui.focusElement is hierarchyList || ui.focusElement is null;
+}
+
+bool CheckForExistingGlobalComponent(Node@ node, const String&in typeName)
+{
+ if (typeName != "Octree" && typeName != "PhysicsWorld" && typeName != "DebugRenderer")
+ return false;
+ else
+ return node.HasComponent(typeName);
+}
+
+void HandleNodeAdded(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressSceneChanges)
+ return;
+
+ Node@ node = eventData["Node"].GetPtr();
+ if (showTemporaryObject || !node.temporary)
+ UpdateHierarchyItem(node);
+}
+
+void HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressSceneChanges)
+ return;
+
+ Node@ node = eventData["Node"].GetPtr();
+ if (showTemporaryObject || !node.temporary)
+ UpdateHierarchyItem(GetListIndex(node), null, null);
+}
+
+void HandleComponentAdded(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressSceneChanges)
+ return;
+
+ // Insert the newly added component at last component position but before the first child node position of the parent node
+ Node@ node = eventData["Node"].GetPtr();
+ Component@ component = eventData["Component"].GetPtr();
+ if (showTemporaryObject || (!node.temporary && !component.temporary))
+ {
+ uint nodeIndex = GetListIndex(node);
+ if (nodeIndex != NO_ITEM)
+ {
+ uint index = node.numChildren > 0 ? GetListIndex(node.children[0]) : M_MAX_UNSIGNED;
+ UpdateHierarchyItem(index, component, hierarchyList.items[nodeIndex]);
+ }
+ }
+}
+
+void HandleComponentRemoved(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressSceneChanges)
+ return;
+
+ Node@ node = eventData["Node"].GetPtr();
+ Component@ component = eventData["Component"].GetPtr();
+ if (showTemporaryObject || (!node.temporary && !component.temporary))
+ {
+ uint index = GetComponentListIndex(component);
+ if (index != NO_ITEM)
+ hierarchyList.RemoveItem(index);
+ }
+}
+
+void HandleNodeNameChanged(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressSceneChanges)
+ return;
+
+ Node@ node = eventData["Node"].GetPtr();
+ if (showTemporaryObject || !node.temporary)
+ UpdateHierarchyItemText(GetListIndex(node), node.enabled, GetNodeTitle(node));
+}
+
+void HandleNodeEnabledChanged(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressSceneChanges)
+ return;
+
+ Node@ node = eventData["Node"].GetPtr();
+ if (showTemporaryObject || !node.temporary)
+ {
+ UpdateHierarchyItemText(GetListIndex(node), node.enabled);
+ attributesDirty = true;
+ }
+}
+
+void HandleComponentEnabledChanged(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressSceneChanges)
+ return;
+
+ Node@ node = eventData["Node"].GetPtr();
+ Component@ component = eventData["Component"].GetPtr();
+ if (showTemporaryObject || (!node.temporary && !component.temporary))
+ {
+ UpdateHierarchyItemText(GetComponentListIndex(component), component.enabledEffective);
+ attributesDirty = true;
+ }
+}
+
+void HandleUIElementAdded(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressUIElementChanges)
+ return;
+
+ UIElement@ element = eventData["Element"].GetPtr();
+ if ((showInternalUIElement || !element.internal) && (showTemporaryObject || !element.temporary))
+ UpdateHierarchyItem(element);
+}
+
+void HandleUIElementRemoved(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressUIElementChanges)
+ return;
+
+ UIElement@ element = eventData["Element"].GetPtr();
+ UpdateHierarchyItem(GetListIndex(element), null, null);
+}
+
+void HandleElementNameChanged(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressUIElementChanges)
+ return;
+
+ UIElement@ element = eventData["Element"].GetPtr();
+ UpdateHierarchyItemText(GetListIndex(element), element.visible, GetUIElementTitle(element));
+}
+
+void HandleElementVisibilityChanged(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressUIElementChanges)
+ return;
+
+ UIElement@ element = eventData["Element"].GetPtr();
+ UpdateHierarchyItemText(GetListIndex(element), element.visible);
+}
+
+void HandleElementAttributeChanged(StringHash eventType, VariantMap& eventData)
+{
+ // Do not refresh the attribute inspector while the attribute is being edited via the attribute-editors
+ if (suppressUIElementChanges || inEditAttribute)
+ return;
+
+ UIElement@ element = eventData["Element"].GetPtr();
+ for (uint i = 0; i < editUIElements.length; ++i)
+ {
+ if (editUIElements[i] is element)
+ attributesDirty = true;
+ }
+}
+
+void HandleTemporaryChanged(StringHash eventType, VariantMap& eventData)
+{
+ if (suppressSceneChanges || suppressUIElementChanges)
+ return;
+
+ Serializable@ serializable = cast(GetEventSender());
+
+ Node@ node = cast(serializable);
+ if (node !is null && node.scene is editorScene)
+ {
+ if (showTemporaryObject)
+ UpdateHierarchyItemText(GetListIndex(node), node.enabled);
+ else if (!node.temporary && GetListIndex(node) == NO_ITEM)
+ UpdateHierarchyItem(node);
+ else if (node.temporary)
+ UpdateHierarchyItem(GetListIndex(node), null, null);
+
+ return;
+ }
+
+ Component@ component = cast(serializable);
+ if (component !is null && component.node !is null && component.node.scene is editorScene)
+ {
+ node = component.node;
+ if (showTemporaryObject)
+ UpdateHierarchyItemText(GetComponentListIndex(component), node.enabled);
+ else if (!component.temporary && GetComponentListIndex(component) == NO_ITEM)
+ {
+ uint nodeIndex = GetListIndex(node);
+ if (nodeIndex != NO_ITEM)
+ {
+ uint index = node.numChildren > 0 ? GetListIndex(node.children[0]) : M_MAX_UNSIGNED;
+ UpdateHierarchyItem(index, component, hierarchyList.items[nodeIndex]);
+ }
+ }
+ else if (component.temporary)
+ {
+ uint index = GetComponentListIndex(component);
+ if (index != NO_ITEM)
+ hierarchyList.RemoveItem(index);
+ }
+
+ return;
+ }
+
+ UIElement@ element = cast(serializable);
+ if (element !is null)
+ {
+ if (showTemporaryObject)
+ UpdateHierarchyItemText(GetListIndex(element), element.visible);
+ else if (!element.temporary && GetListIndex(element) == NO_ITEM)
+ UpdateHierarchyItem(element);
+ else if (element.temporary)
+ UpdateHierarchyItem(GetListIndex(element), null, null);
+
+ return;
+ }
+}
+
+// Hierarchy window edit functions
+bool Undo()
+{
+ if (undoStackPos > 0)
+ {
+ --undoStackPos;
+ // Undo commands in reverse order
+ for (int i = int(undoStack[undoStackPos].actions.length - 1); i >= 0; --i)
+ undoStack[undoStackPos].actions[i].Undo();
+ }
+
+ return true;
+}
+
+bool Redo()
+{
+ if (undoStackPos < undoStack.length)
+ {
+ // Redo commands in same order as stored
+ for (uint i = 0; i < undoStack[undoStackPos].actions.length; ++i)
+ undoStack[undoStackPos].actions[i].Redo();
+ ++undoStackPos;
+ }
+
+ return true;
+}
+
+bool Cut()
+{
+ if (CheckHierarchyWindowFocus())
+ {
+ bool ret = true;
+ if (!selectedNodes.empty || !selectedComponents.empty)
+ ret = ret && SceneCut();
+ // Not mutually exclusive
+ if (!selectedUIElements.empty)
+ ret = ret && UIElementCut();
+ return ret;
+ }
+
+ return false;
+}
+
+bool Duplicate()
+{
+ if (CheckHierarchyWindowFocus())
+ {
+ bool ret = true;
+ if (!selectedNodes.empty || !selectedComponents.empty)
+ ret = ret && (selectedNodes.empty || selectedComponents.empty ? SceneDuplicate() : false); // Node and component is mutually exclusive for copy action
+ // Not mutually exclusive
+ if (!selectedUIElements.empty)
+ ret = ret && UIElementDuplicate();
+ return ret;
+ }
+
+ return false;
+}
+
+bool Copy()
+{
+ if (CheckHierarchyWindowFocus())
+ {
+ bool ret = true;
+ if (!selectedNodes.empty || !selectedComponents.empty)
+ ret = ret && (selectedNodes.empty || selectedComponents.empty ? SceneCopy() : false); // Node and component is mutually exclusive for copy action
+ // Not mutually exclusive
+ if (!selectedUIElements.empty)
+ ret = ret && UIElementCopy();
+ return ret;
+ }
+
+ return false;
+}
+
+bool Paste()
+{
+ if (CheckHierarchyWindowFocus())
+ {
+ bool ret = true;
+ if (editNode !is null && !sceneCopyBuffer.empty)
+ ret = ret && ScenePaste();
+ // Not mutually exclusive
+ if (editUIElement !is null && !uiElementCopyBuffer.empty)
+ ret = ret && UIElementPaste();
+ return ret;
+ }
+
+ return false;
+}
+
+bool BlenderModeDelete()
+{
+ if (ui.focusElement is null)
+ {
+ Array actions;
+ actions.Push(CreateContextMenuItem("Delete?", "HandleBlenderModeDelete"));
+ actions.Push(CreateContextMenuItem("Cancel", "HandleEmpty"));
+
+ if (actions.length > 0)
+ {
+ ActivateContextMenu(actions);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Delete()
+{
+ if (CheckHierarchyWindowFocus())
+ {
+ bool ret = true;
+ if (!selectedNodes.empty || !selectedComponents.empty)
+ ret = ret && SceneDelete();
+ // Not mutually exclusive
+ if (!selectedUIElements.empty)
+ ret = ret && UIElementDelete();
+ return ret;
+ }
+
+ return false;
+}
+
+bool SelectAll()
+{
+ if (CheckHierarchyWindowFocus())
+ {
+ if (!selectedNodes.empty || !selectedComponents.empty)
+ return SceneSelectAll();
+ else if (!selectedUIElements.empty || hierarchyList.items[GetListIndex(editorUIElement)].selected)
+ return UIElementSelectAll();
+ else
+ return SceneSelectAll(); // If nothing is selected yet, fall back to scene select all
+ }
+
+ return false;
+}
+
+bool DeselectAll()
+{
+ if (CheckHierarchyWindowFocus())
+ {
+ BeginSelectionModify();
+ hierarchyList.ClearSelection();
+ EndSelectionModify();
+ return true;
+ }
+ return false;
+}
+
+bool ResetToDefault()
+{
+ if (CheckHierarchyWindowFocus())
+ {
+ bool ret = true;
+ if (!selectedNodes.empty || !selectedComponents.empty)
+ ret = ret && (selectedNodes.empty || selectedComponents.empty ? SceneResetToDefault() : false); // Node and component is mutually exclusive for reset-to-default action
+ // Not mutually exclusive
+ if (!selectedUIElements.empty)
+ ret = ret && UIElementResetToDefault();
+ return ret;
+ }
+
+ return false;
+}
+
+void ClearEditActions()
+{
+ undoStack.Clear();
+ undoStackPos = 0;
+}
+
+void SaveEditAction(EditAction@ action)
+{
+ // Create a group with 1 action
+ EditActionGroup group;
+ group.actions.Push(action);
+ SaveEditActionGroup(group);
+}
+
+void SaveEditActionGroup(EditActionGroup@ group)
+{
+ if (group.actions.empty)
+ return;
+
+ // Truncate the stack first to current pos
+ undoStack.Resize(undoStackPos);
+ undoStack.Push(group);
+ ++undoStackPos;
+
+ // Limit maximum undo steps
+ if (undoStack.length > MAX_UNDOSTACK_SIZE)
+ {
+ undoStack.Erase(0);
+ --undoStackPos;
+ }
+}
+
+void BeginSelectionModify()
+{
+ // A large operation on selected nodes is about to begin. Disable intermediate selection updates
+ inSelectionModify = true;
+
+ // Cursor shape reverts back to normal automatically after the large operation is completed
+ ui.cursor.shape = CS_BUSY;
+}
+
+void EndSelectionModify()
+{
+ // The large operation on selected nodes has ended. Update node/component selection now
+ inSelectionModify = false;
+ HandleHierarchyListSelectionChange();
+}
+
+void HandleHierarchyContextCreateReplicatedNode()
+{
+ CreateNode(REPLICATED);
+}
+
+void HandleHierarchyContextCreateLocalNode()
+{
+ CreateNode(LOCAL);
+}
+
+void HandleHierarchyContextDuplicate()
+{
+ Duplicate();
+}
+
+void HandleHierarchyContextCopy()
+{
+ Copy();
+}
+
+void HandleHierarchyContextCut()
+{
+ Cut();
+}
+
+void HandleHierarchyContextDelete()
+{
+ Delete();
+}
+
+void HandleBlenderModeDelete()
+{
+ Delete();
+}
+
+void HandleEmpty()
+{
+ //just doing nothing
+}
+
+void HandleHierarchyContextPaste()
+{
+ Paste();
+}
+
+void HandleHierarchyContextResetToDefault()
+{
+ ResetToDefault();
+}
+
+void HandleHierarchyContextResetPosition()
+{
+ SceneResetPosition();
+}
+
+void HandleHierarchyContextResetRotation()
+{
+ SceneResetRotation();
+}
+
+void HandleHierarchyContextResetScale()
+{
+ SceneResetScale();
+}
+
+void HandleHierarchyContextEnableDisable()
+{
+ SceneToggleEnable();
+}
+
+void HandleHierarchyContextUnparent()
+{
+ SceneUnparent();
+}
+
+void HandleHierarchyContextUIElementCloseUILayout()
+{
+ CloseUILayout();
+}
+
+void HandleHierarchyContextUIElementCloseAllUILayouts()
+{
+ CloseAllUILayouts();
+}
+
+void CollapseHierarchy()
+{
+ Array oldSelections = hierarchyList.selections;
+ Array selections = {0};
+
+ hierarchyList.SetSelections(selections);
+
+ for (uint i = 0; i < selections.length; ++i)
+ hierarchyList.Expand(selections[i], false, true);
+
+ // only scene's scope expand by default
+ hierarchyList.Expand(0, true, false);
+
+ hierarchyList.SetSelections(oldSelections);
+}
+
+void CollapseHierarchy(StringHash eventType, VariantMap& eventData)
+{
+ CollapseHierarchy();
+}
+
+void HandleShowID(StringHash eventType, VariantMap& eventData)
+{
+ CheckBox@ checkBox = eventData["Element"].GetPtr();
+ showID = checkBox.checked;
+ UpdateHierarchyItem(editorScene, false);
+ CollapseHierarchy();
+}
diff --git a/bin/Data/Scripts/Editor/EditorImport.as b/bin/Data/Scripts/Editor/EditorImport.as
new file mode 100644
index 0000000..cafd07f
--- /dev/null
+++ b/bin/Data/Scripts/Editor/EditorImport.as
@@ -0,0 +1,576 @@
+// Urho3D editor import functions
+
+String importOptions = "-t";
+
+class ParentAssignment
+{
+ uint childID;
+ String parentName;
+}
+
+class AssetMapping
+{
+ String assetName;
+ String fullAssetName;
+}
+
+Array assetMappings;
+
+String assetImporterPath;
+
+int ExecuteAssetImporter(Array@ args)
+{
+ if (assetImporterPath.empty)
+ {
+ String exeSuffix = "";
+ if (GetPlatform() == "Windows")
+ exeSuffix = ".exe";
+ // Try both with and without the tool directory; a packaged build may not have the tool directory
+ assetImporterPath = fileSystem.programDir + "tool/AssetImporter" + exeSuffix;
+ if (!fileSystem.FileExists(assetImporterPath))
+ assetImporterPath = fileSystem.programDir + "AssetImporter" + exeSuffix;
+ }
+
+ return fileSystem.SystemRun(assetImporterPath, args);
+}
+
+void ImportAnimation(const String&in fileName)
+{
+ if (fileName.empty)
+ return;
+
+ ui.cursor.shape = CS_BUSY;
+
+ String modelName = "Models/" + GetFileName(fileName) + ".ani";
+ String outFileName = sceneResourcePath + modelName;
+ fileSystem.CreateDir(sceneResourcePath + "Models");
+
+ Array args;
+ args.Push("anim");
+ args.Push("\"" + fileName + "\"");
+ args.Push("\"" + outFileName + "\"");
+ args.Push("-p \"" + sceneResourcePath + "\"");
+ Array options = importOptions.Trimmed().Split(' ');
+ for (uint i = 0; i < options.length; ++i)
+ args.Push(options[i]);
+
+ if (ExecuteAssetImporter(args) == 0)
+ {
+
+ }
+ else
+ log.Error("Failed to execute AssetImporter to import model");
+}
+
+void ImportModel(const String&in fileName)
+{
+ if (fileName.empty)
+ return;
+
+ ui.cursor.shape = CS_BUSY;
+
+ String modelName = "Models/" + GetFileName(fileName) + ".mdl";
+ String outFileName = sceneResourcePath + modelName;
+ fileSystem.CreateDir(sceneResourcePath + "Models");
+
+ Array args;
+ args.Push("model");
+ args.Push("\"" + fileName + "\"");
+ args.Push("\"" + outFileName + "\"");
+ args.Push("-p \"" + sceneResourcePath + "\"");
+ Array options = importOptions.Trimmed().Split(' ');
+ for (uint i = 0; i < options.length; ++i)
+ args.Push(options[i]);
+ // If material lists are to be applied, make sure the option to create them exists
+ if (applyMaterialList)
+ args.Push("-l");
+
+ if (ExecuteAssetImporter(args) == 0)
+ {
+ Node@ newNode = editorScene.CreateChild(GetFileName(fileName));
+ StaticModel@ newModel = newNode.CreateComponent("StaticModel");
+ newNode.position = GetNewNodePosition();
+ newModel.model = cache.GetResource("Model", modelName);
+ newModel.ApplyMaterialList(); // Setup default materials if possible
+
+ // Create an undo action for the create
+ CreateNodeAction action;
+ action.Define(newNode);
+ SaveEditAction(action);
+ SetSceneModified();
+
+ FocusNode(newNode);
+ }
+ else
+ log.Error("Failed to execute AssetImporter to import model");
+}
+
+void ImportScene(const String&in fileName)
+{
+ if (fileName.empty)
+ return;
+
+ ui.cursor.shape = CS_BUSY;
+
+ // Handle Tundra scene files here in code, otherwise via AssetImporter
+ if (GetExtension(fileName) == ".txml")
+ ImportTundraScene(fileName);
+ else
+ {
+ // Export scene to a temp file, then load and delete it if successful
+ Array options = importOptions.Trimmed().Split(' ');
+ bool isBinary = false;
+ for (uint i = 0; i < options.length; ++i)
+ if (options[i] == "-b")
+ isBinary = true;
+ String tempSceneName = sceneResourcePath + (isBinary ? TEMP_BINARY_SCENE_NAME : TEMP_SCENE_NAME);
+
+ Array args;
+ args.Push("scene");
+ args.Push("\"" + fileName + "\"");
+ args.Push("\"" + tempSceneName + "\"");
+ args.Push("-p \"" + sceneResourcePath + "\"");
+ for (uint i = 0; i < options.length; ++i)
+ args.Push(options[i]);
+ if (applyMaterialList)
+ args.Push("-l");
+
+ if (ExecuteAssetImporter(args) == 0)
+ {
+ skipMruScene = true; // set to avoid adding tempscene to mru
+ LoadScene(tempSceneName);
+ fileSystem.Delete(tempSceneName);
+ UpdateWindowTitle();
+ }
+ else
+ log.Error("Failed to execute AssetImporter to import scene");
+ }
+}
+
+void ImportTundraScene(const String&in fileName)
+{
+ fileSystem.CreateDir(sceneResourcePath + "Materials");
+ fileSystem.CreateDir(sceneResourcePath + "Models");
+ fileSystem.CreateDir(sceneResourcePath + "Textures");
+
+ XMLFile source;
+ source.Load(File(fileName, FILE_READ));
+ String filePath = GetPath(fileName);
+
+ XMLElement sceneElem = source.root;
+ XMLElement entityElem = sceneElem.GetChild("entity");
+
+ Array convertedMaterials;
+ Array convertedMeshes;
+ Array parentAssignments;
+
+ // Read the scene directory structure recursively to get assetname to full assetname mappings
+ Array fileNames = fileSystem.ScanDir(filePath, "*.*", SCAN_FILES, true);
+ for (uint i = 0; i < fileNames.length; ++i)
+ {
+ AssetMapping mapping;
+ mapping.assetName = GetFileNameAndExtension(fileNames[i]);
+ mapping.fullAssetName = fileNames[i];
+ assetMappings.Push(mapping);
+ }
+
+ // Clear old scene, then create a zone and a directional light first
+ ResetScene();
+
+ // Set standard gravity
+ editorScene.CreateComponent("PhysicsWorld");
+ editorScene.physicsWorld.gravity = Vector3(0, -9.81, 0);
+
+ // Create zone & global light
+ Node@ zoneNode = editorScene.CreateChild("Zone");
+ Zone@ zone = zoneNode.CreateComponent("Zone");
+ zone.boundingBox = BoundingBox(-1000, 1000);
+ zone.ambientColor = Color(0.364, 0.364, 0.364);
+ zone.fogColor = Color(0.707792, 0.770537, 0.831373);
+ zone.fogStart = 100.0;
+ zone.fogEnd = 500.0;
+
+ Node@ lightNode = editorScene.CreateChild("GlobalLight");
+ Light@ light = lightNode.CreateComponent("Light");
+ lightNode.rotation = Quaternion(60, 30, 0);
+ light.lightType = LIGHT_DIRECTIONAL;
+ light.color = Color(0.639, 0.639, 0.639);
+ light.castShadows = true;
+ light.shadowCascade = CascadeParameters(5, 15.0, 50.0, 0.0, 0.9);
+
+ // Loop through scene entities
+ while (!entityElem.isNull)
+ {
+ String nodeName;
+ String meshName;
+ String parentName;
+ Vector3 meshPos;
+ Vector3 meshRot;
+ Vector3 meshScale(1, 1, 1);
+ Vector3 pos;
+ Vector3 rot;
+ Vector3 scale(1, 1, 1);
+ bool castShadows = false;
+ float drawDistance = 0;
+ Array materialNames;
+
+ int shapeType = -1;
+ float mass = 0.0f;
+ Vector3 bodySize;
+ bool trigger = false;
+ bool kinematic = false;
+ uint collisionLayer;
+ uint collisionMask;
+ String collisionMeshName;
+
+ XMLElement compElem = entityElem.GetChild("component");
+ while (!compElem.isNull)
+ {
+ String compType = compElem.GetAttribute("type");
+
+ if (compType == "EC_Mesh" || compType == "Mesh")
+ {
+ Array coords = GetComponentAttribute(compElem, "Transform").Split(',');
+ meshPos = GetVector3FromStrings(coords, 0);
+ meshPos.z = -meshPos.z; // Convert to lefthanded
+ meshRot = GetVector3FromStrings(coords, 3);
+ meshScale = GetVector3FromStrings(coords, 6);
+ meshName = GetComponentAttribute(compElem, "Mesh ref");
+ castShadows = GetComponentAttribute(compElem, "Cast shadows").ToBool();
+ drawDistance = GetComponentAttribute(compElem, "Draw distance").ToFloat();
+ materialNames = GetComponentAttribute(compElem, "Mesh materials").Split(';');
+ ProcessRef(meshName);
+ for (uint i = 0; i < materialNames.length; ++i)
+ ProcessRef(materialNames[i]);
+ }
+ if (compType == "EC_Name" || compType == "Name")
+ nodeName = GetComponentAttribute(compElem, "name");
+ if (compType == "EC_Placeable" || compType == "Placeable")
+ {
+ Array coords = GetComponentAttribute(compElem, "Transform").Split(',');
+ pos = GetVector3FromStrings(coords, 0);
+ pos.z = -pos.z; // Convert to lefthanded
+ rot = GetVector3FromStrings(coords, 3);
+ scale = GetVector3FromStrings(coords, 6);
+ parentName = GetComponentAttribute(compElem, "Parent entity ref");
+ }
+ if (compType == "EC_RigidBody" || compType == "RigidBody")
+ {
+ shapeType = GetComponentAttribute(compElem, "Shape type").ToInt();
+ mass = GetComponentAttribute(compElem, "Mass").ToFloat();
+ bodySize = GetComponentAttribute(compElem, "Size").ToVector3();
+ collisionMeshName = GetComponentAttribute(compElem, "Collision mesh ref");
+ trigger = GetComponentAttribute(compElem, "Phantom").ToBool();
+ kinematic = GetComponentAttribute(compElem, "Kinematic").ToBool();
+ collisionLayer = GetComponentAttribute(compElem, "Collision Layer").ToInt();
+ collisionMask = GetComponentAttribute(compElem, "Collision Mask").ToInt();
+ ProcessRef(collisionMeshName);
+ }
+
+ compElem = compElem.GetNext("component");
+ }
+
+ // If collision mesh not specified for the rigid body, assume same as the visible mesh
+ if ((shapeType == 4 || shapeType == 6) && collisionMeshName.Trimmed().empty)
+ collisionMeshName = meshName;
+
+ if (!meshName.empty || shapeType >= 0)
+ {
+ for (uint i = 0; i < materialNames.length; ++i)
+ ConvertMaterial(materialNames[i], filePath, convertedMaterials);
+
+ ConvertModel(meshName, filePath, convertedMeshes);
+ ConvertModel(collisionMeshName, filePath, convertedMeshes);
+
+ Node@ newNode = editorScene.CreateChild(nodeName);
+
+ // Calculate final transform in an Ogre-like fashion
+ Quaternion quat = GetTransformQuaternion(rot);
+ Quaternion meshQuat = GetTransformQuaternion(meshRot);
+ Quaternion finalQuat = quat * meshQuat;
+ Vector3 finalScale = scale * meshScale;
+ Vector3 finalPos = pos + quat * (scale * meshPos);
+
+ newNode.SetTransform(finalPos, finalQuat, finalScale);
+
+ // Create model
+ if (!meshName.empty)
+ {
+ StaticModel@ model = newNode.CreateComponent("StaticModel");
+ model.model = cache.GetResource("Model", GetOutModelName(meshName));
+ model.drawDistance = drawDistance;
+ model.castShadows = castShadows;
+ // Set default grey material to match Tundra defaults
+ model.material = cache.GetResource("Material", "Materials/DefaultGrey.xml");
+ // Then try to assign the actual materials
+ for (uint i = 0; i < materialNames.length; ++i)
+ {
+ Material@ mat = cache.GetResource("Material", GetOutMaterialName(materialNames[i]));
+ if (mat !is null)
+ model.materials[i] = mat;
+ }
+ }
+
+ // Create rigidbody & collision shape
+ if (shapeType >= 0)
+ {
+ RigidBody@ body = newNode.CreateComponent("RigidBody");
+
+ // If mesh has scaling, undo it for the collision shape
+ bodySize.x /= meshScale.x;
+ bodySize.y /= meshScale.y;
+ bodySize.z /= meshScale.z;
+
+ CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
+ switch (shapeType)
+ {
+ case 0:
+ shape.SetBox(bodySize);
+ break;
+
+ case 1:
+ shape.SetSphere(bodySize.x);
+ break;
+
+ case 2:
+ shape.SetCylinder(bodySize.x, bodySize.y);
+ break;
+
+ case 3:
+ shape.SetCapsule(bodySize.x, bodySize.y);
+ break;
+
+ case 4:
+ shape.SetTriangleMesh(cache.GetResource("Model", GetOutModelName(collisionMeshName)), 0, bodySize);
+ break;
+
+ case 6:
+ shape.SetConvexHull(cache.GetResource("Model", GetOutModelName(collisionMeshName)), 0, bodySize);
+ break;
+ }
+
+ body.collisionLayer = collisionLayer;
+ body.collisionMask = collisionMask;
+ body.trigger = trigger;
+ body.mass = mass;
+ }
+
+ // Store pending parent assignment if necessary
+ if (!parentName.empty)
+ {
+ ParentAssignment assignment;
+ assignment.childID = newNode.id;
+ assignment.parentName = parentName;
+ parentAssignments.Push(assignment);
+ }
+ }
+
+ entityElem = entityElem.GetNext("entity");
+ }
+
+ // Process any parent assignments now
+ for (uint i = 0; i < parentAssignments.length; ++i)
+ {
+ Node@ childNode = editorScene.GetNode(parentAssignments[i].childID);
+ Node@ parentNode = editorScene.GetChild(parentAssignments[i].parentName, true);
+ if (childNode !is null && parentNode !is null)
+ childNode.parent = parentNode;
+ }
+
+ UpdateHierarchyItem(editorScene, true);
+ UpdateWindowTitle();
+ assetMappings.Clear();
+}
+
+String GetFullAssetName(const String& assetName)
+{
+ for (uint i = 0; i < assetMappings.length; ++i)
+ {
+ if (assetMappings[i].assetName == assetName)
+ return assetMappings[i].fullAssetName;
+ }
+
+ return assetName;
+}
+
+Quaternion GetTransformQuaternion(Vector3 rotEuler)
+{
+ // Convert rotation to lefthanded
+ Quaternion rotateX(-rotEuler.x, Vector3(1, 0, 0));
+ Quaternion rotateY(-rotEuler.y, Vector3(0, 1, 0));
+ Quaternion rotateZ(-rotEuler.z, Vector3(0, 0, -1));
+ return rotateZ * rotateY * rotateX;
+}
+
+String GetComponentAttribute(XMLElement compElem, const String&in name)
+{
+ XMLElement attrElem = compElem.GetChild("attribute");
+ while (!attrElem.isNull)
+ {
+ if (attrElem.GetAttribute("name") == name)
+ return attrElem.GetAttribute("value");
+
+ attrElem = attrElem.GetNext("attribute");
+ }
+
+ return "";
+}
+
+Vector3 GetVector3FromStrings(Array@ coords, uint startIndex)
+{
+ return Vector3(coords[startIndex].ToFloat(), coords[startIndex + 1].ToFloat(), coords[startIndex + 2].ToFloat());
+}
+
+void ProcessRef(String& ref)
+{
+ if (ref.StartsWith("local://"))
+ ref = ref.Substring(8);
+ if (ref.StartsWith("file://"))
+ ref = ref.Substring(7);
+}
+
+String GetOutModelName(const String&in ref)
+{
+ return "Models/" + GetFullAssetName(ref).Replaced('/', '_').Replaced(".mesh", ".mdl");
+}
+
+String GetOutMaterialName(const String&in ref)
+{
+ return "Materials/" + GetFullAssetName(ref).Replaced('/', '_').Replaced(".material", ".xml");
+}
+
+String GetOutTextureName(const String&in ref)
+{
+ return "Textures/" + GetFullAssetName(ref).Replaced('/', '_');
+}
+
+void ConvertModel(const String&in modelName, const String&in filePath, Array@ convertedModels)
+{
+ if (modelName.Trimmed().empty)
+ return;
+
+ for (uint i = 0; i < convertedModels.length; ++i)
+ {
+ if (convertedModels[i] == modelName)
+ return;
+ }
+
+ String meshFileName = filePath + GetFullAssetName(modelName);
+ String xmlFileName = filePath + GetFullAssetName(modelName) + ".xml";
+ String outFileName = sceneResourcePath + GetOutModelName(modelName);
+
+ // Convert .mesh to .mesh.xml
+ String cmdLine = "ogrexmlconverter \"" + meshFileName + "\" \"" + xmlFileName + "\"";
+ if (!fileSystem.FileExists(xmlFileName))
+ fileSystem.SystemCommand(cmdLine.Replaced('/', '\\'));
+
+ if (!fileSystem.FileExists(outFileName))
+ {
+ // Convert .mesh.xml to .mdl
+ Array args;
+ args.Push("\"" + xmlFileName + "\"");
+ args.Push("\"" + outFileName + "\"");
+ args.Push("-a");
+ fileSystem.SystemRun(fileSystem.programDir + "tool/OgreImporter", args);
+ }
+
+ convertedModels.Push(modelName);
+}
+
+void ConvertMaterial(const String&in materialName, const String&in filePath, Array@ convertedMaterials)
+{
+ if (materialName.Trimmed().empty)
+ return;
+
+ for (uint i = 0; i < convertedMaterials.length; ++i)
+ {
+ if (convertedMaterials[i] == materialName)
+ return;
+ }
+
+ String fileName = filePath + GetFullAssetName(materialName);
+ String outFileName = sceneResourcePath + GetOutMaterialName(materialName);
+
+ if (!fileSystem.FileExists(fileName))
+ return;
+
+ bool mask = false;
+ bool twoSided = false;
+ bool uvScaleSet = false;
+ String textureName;
+ Vector2 uvScale(1, 1);
+ Color diffuse(1, 1, 1, 1);
+
+ File file(fileName, FILE_READ);
+ while (!file.eof)
+ {
+ String line = file.ReadLine().Trimmed();
+ if (line.StartsWith("alpha_rejection") || line.StartsWith("scene_blend alpha_blend"))
+ mask = true;
+ if (line.StartsWith("cull_hardware none"))
+ twoSided = true;
+ // Todo: handle multiple textures per material
+ if (textureName.empty && line.StartsWith("texture "))
+ {
+ textureName = line.Substring(8);
+ ProcessRef(textureName);
+ }
+ if (!uvScaleSet && line.StartsWith("scale "))
+ {
+ uvScale = line.Substring(6).ToVector2();
+ uvScaleSet = true;
+ }
+ if (line.StartsWith("diffuse "))
+ diffuse = line.Substring(8).ToColor();
+ }
+
+ XMLFile outMat;
+ XMLElement rootElem = outMat.CreateRoot("material");
+ XMLElement techniqueElem = rootElem.CreateChild("technique");
+
+ if (twoSided)
+ {
+ XMLElement cullElem = rootElem.CreateChild("cull");
+ cullElem.SetAttribute("value", "none");
+ XMLElement shadowCullElem = rootElem.CreateChild("shadowcull");
+ shadowCullElem.SetAttribute("value", "none");
+ }
+
+ if (!textureName.empty)
+ {
+ techniqueElem.SetAttribute("name", mask ? "Techniques/DiffAlphaMask.xml" : "Techniques/Diff.xml");
+
+ String outTextureName = GetOutTextureName(textureName);
+ XMLElement textureElem = rootElem.CreateChild("texture");
+ textureElem.SetAttribute("unit", "diffuse");
+ textureElem.SetAttribute("name", outTextureName);
+
+ fileSystem.Copy(filePath + GetFullAssetName(textureName), sceneResourcePath + outTextureName);
+ }
+ else
+ techniqueElem.SetAttribute("name", "NoTexture.xml");
+
+ if (uvScale != Vector2(1, 1))
+ {
+ XMLElement uScaleElem = rootElem.CreateChild("parameter");
+ uScaleElem.SetAttribute("name", "UOffset");
+ uScaleElem.SetVector3("value", Vector3(1 / uvScale.x, 0, 0));
+
+ XMLElement vScaleElem = rootElem.CreateChild("parameter");
+ vScaleElem.SetAttribute("name", "VOffset");
+ vScaleElem.SetVector3("value", Vector3(0, 1 / uvScale.y, 0));
+ }
+
+ if (diffuse != Color(1, 1, 1, 1))
+ {
+ XMLElement diffuseElem = rootElem.CreateChild("parameter");
+ diffuseElem.SetAttribute("name", "MatDiffColor");
+ diffuseElem.SetColor("value", diffuse);
+ }
+
+ File outFile(outFileName, FILE_WRITE);
+ outMat.Save(outFile);
+ outFile.Close();
+
+ convertedMaterials.Push(materialName);
+}
diff --git a/bin/Data/Scripts/Editor/EditorInspectorWindow.as b/bin/Data/Scripts/Editor/EditorInspectorWindow.as
new file mode 100644
index 0000000..26450cc
--- /dev/null
+++ b/bin/Data/Scripts/Editor/EditorInspectorWindow.as
@@ -0,0 +1,1032 @@
+// Urho3D editor attribute inspector window handling
+#include "Scripts/Editor/AttributeEditor.as"
+
+Window@ attributeInspectorWindow;
+UIElement@ parentContainer;
+UIElement@ inspectorLockButton;
+
+bool applyMaterialList = true;
+bool attributesDirty = false;
+bool attributesFullDirty = false;
+
+const String STRIKED_OUT = "——"; // Two unicode EM DASH (U+2014)
+const StringHash NODE_IDS_VAR("NodeIDs");
+const StringHash COMPONENT_IDS_VAR("ComponentIDs");
+const StringHash UI_ELEMENT_IDS_VAR("UIElementIDs");
+const int LABEL_WIDTH = 30;
+
+// Constants for accessing xmlResources
+Array xmlResources;
+const uint ATTRIBUTE_RES = 0;
+const uint VARIABLE_RES = 1;
+const uint STYLE_RES = 2;
+const uint TAGS_RES = 3;
+
+uint nodeContainerIndex = M_MAX_UNSIGNED;
+uint componentContainerStartIndex = 0;
+uint elementContainerIndex = M_MAX_UNSIGNED;
+
+// Node or UIElement hash-to-varname reverse mapping
+VariantMap globalVarNames;
+bool inspectorLocked = false;
+
+void InitXMLResources()
+{
+ String[] resources = { "UI/EditorInspector_Attribute.xml", "UI/EditorInspector_Variable.xml",
+ "UI/EditorInspector_Style.xml", "UI/EditorInspector_Tags.xml" };
+ for (uint i = 0; i < resources.length; ++i)
+ xmlResources.Push(cache.GetResource("XMLFile", resources[i]));
+}
+
+/// Delete all child containers in the inspector list.
+void DeleteAllContainers()
+{
+ parentContainer.RemoveAllChildren();
+ nodeContainerIndex = M_MAX_UNSIGNED;
+ componentContainerStartIndex = 0;
+ elementContainerIndex = M_MAX_UNSIGNED;
+}
+
+/// Get container at the specified index in the inspector list, the container must be created before.
+UIElement@ GetContainer(uint index)
+{
+ return parentContainer.children[index];
+}
+
+/// Get node container in the inspector list, create the container if it is not yet available.
+UIElement@ GetNodeContainer()
+{
+ if (nodeContainerIndex != M_MAX_UNSIGNED)
+ return GetContainer(nodeContainerIndex);
+
+ nodeContainerIndex = parentContainer.numChildren;
+ parentContainer.LoadChildXML(xmlResources[ATTRIBUTE_RES], uiStyle);
+ UIElement@ container = GetContainer(nodeContainerIndex);
+ container.LoadChildXML(xmlResources[VARIABLE_RES], uiStyle);
+ SubscribeToEvent(container.GetChild("ResetToDefault", true), "Released", "HandleResetToDefault");
+ SubscribeToEvent(container.GetChild("NewVarDropDown", true), "ItemSelected", "CreateNodeVariable");
+ SubscribeToEvent(container.GetChild("DeleteVarButton", true), "Released", "DeleteNodeVariable");
+ ++componentContainerStartIndex;
+
+ parentContainer.LoadChildXML(xmlResources[TAGS_RES], uiStyle);
+ parentContainer.GetChild("TagsLabel", true).SetFixedWidth(LABEL_WIDTH);
+ LineEdit@ tagEdit = parentContainer.GetChild("TagsEdit", true);
+ SubscribeToEvent(tagEdit, "TextChanged", "HandleTagsEdit");
+ UIElement@ tagSelect = parentContainer.GetChild("TagsSelect", true);
+ SubscribeToEvent(tagSelect, "Released", "HandleTagsSelect");
+ ++componentContainerStartIndex;
+
+ return container;
+}
+
+/// Get component container at the specified index, create the container if it is not yet available at the specified index.
+UIElement@ GetComponentContainer(uint index)
+{
+ if (componentContainerStartIndex + index < parentContainer.numChildren)
+ return GetContainer(componentContainerStartIndex + index);
+
+ UIElement@ container;
+ for (uint i = parentContainer.numChildren; i <= componentContainerStartIndex + index; ++i)
+ {
+ parentContainer.LoadChildXML(xmlResources[ATTRIBUTE_RES], uiStyle);
+ container = GetContainer(i);
+ SubscribeToEvent(container.GetChild("ResetToDefault", true), "Released", "HandleResetToDefault");
+ }
+ return container;
+}
+
+/// Get UI-element container, create the container if it is not yet available.
+UIElement@ GetUIElementContainer()
+{
+ if (elementContainerIndex != M_MAX_UNSIGNED)
+ return GetContainer(elementContainerIndex);
+
+ elementContainerIndex = parentContainer.numChildren;
+ parentContainer.LoadChildXML(xmlResources[ATTRIBUTE_RES], uiStyle);
+ parentContainer.LoadChildXML(xmlResources[TAGS_RES], uiStyle);
+ parentContainer.GetChild("TagsLabel", true).SetFixedWidth(LABEL_WIDTH);
+ UIElement@ container = GetContainer(elementContainerIndex);
+ container.LoadChildXML(xmlResources[VARIABLE_RES], uiStyle);
+ container.LoadChildXML(xmlResources[STYLE_RES], uiStyle);
+ DropDownList@ styleList = container.GetChild("StyleDropDown", true);
+ styleList.placeholderText = STRIKED_OUT;
+ styleList.parent.GetChild("StyleDropDownLabel").SetFixedWidth(LABEL_WIDTH);
+ PopulateStyleList(styleList);
+ SubscribeToEvent(container.GetChild("ResetToDefault", true), "Released", "HandleResetToDefault");
+ SubscribeToEvent(container.GetChild("NewVarDropDown", true), "ItemSelected", "CreateUIElementVariable");
+ SubscribeToEvent(container.GetChild("DeleteVarButton", true), "Released", "DeleteUIElementVariable");
+ SubscribeToEvent(styleList, "ItemSelected", "HandleStyleItemSelected");
+ LineEdit@ tagEdit = parentContainer.GetChild("TagsEdit", true);
+ SubscribeToEvent(tagEdit, "TextChanged", "HandleTagsEdit");
+ UIElement@ tagSelect = parentContainer.GetChild("TagsSelect", true);
+ SubscribeToEvent(tagSelect, "Released", "HandleTagsSelect");
+ return container;
+}
+
+void CreateAttributeInspectorWindow()
+{
+ if (attributeInspectorWindow !is null)
+ return;
+
+ InitResourcePicker();
+ InitVectorStructs();
+ InitXMLResources();
+
+ attributeInspectorWindow = LoadEditorUI("UI/EditorInspectorWindow.xml");
+ parentContainer = attributeInspectorWindow.GetChild("ParentContainer");
+ ui.root.AddChild(attributeInspectorWindow);
+ int height = Min(ui.root.height - 60, 500);
+ attributeInspectorWindow.SetSize(344, height);
+ attributeInspectorWindow.SetPosition(ui.root.width - 10 - attributeInspectorWindow.width, 100);
+ attributeInspectorWindow.opacity = uiMaxOpacity;
+ attributeInspectorWindow.BringToFront();
+ inspectorLockButton = attributeInspectorWindow.GetChild("LockButton", true);
+
+ UpdateAttributeInspector();
+
+ SubscribeToEvent(inspectorLockButton, "Pressed", "ToggleInspectorLock");
+ SubscribeToEvent(attributeInspectorWindow.GetChild("CloseButton", true), "Pressed", "HideAttributeInspectorWindow");
+ SubscribeToEvent(attributeInspectorWindow, "LayoutUpdated", "HandleWindowLayoutUpdated");
+}
+
+void DisableInspectorLock()
+{
+ inspectorLocked = false;
+ if (inspectorLockButton !is null)
+ inspectorLockButton.style = "Button";
+ UpdateAttributeInspector(true);
+}
+
+void EnableInspectorLock()
+{
+ inspectorLocked = true;
+ if (inspectorLockButton !is null)
+ inspectorLockButton.style = "ToggledButton";
+}
+
+void ToggleInspectorLock()
+{
+ if (inspectorLocked)
+ DisableInspectorLock();
+ else
+ EnableInspectorLock();
+}
+
+bool ToggleAttributeInspectorWindow()
+{
+ if (attributeInspectorWindow.visible == false)
+ ShowAttributeInspectorWindow();
+ else
+ HideAttributeInspectorWindow();
+ return true;
+}
+
+void ShowAttributeInspectorWindow()
+{
+ attributeInspectorWindow.visible = true;
+ attributeInspectorWindow.BringToFront();
+}
+
+void HideAttributeInspectorWindow()
+{
+ if(viewportMode == VIEWPORT_COMPACT)
+ return;
+ attributeInspectorWindow.visible = false;
+}
+
+
+/// Handle main window layout updated event by positioning elements that needs manually-positioning (elements that are children of UI-element container with "Free" layout-mode).
+void HandleWindowLayoutUpdated()
+{
+ // When window resize and so the list's width is changed, adjust the 'Is enabled' container width and icon panel width so that their children stay at the right most position
+ for (uint i = 0; i < parentContainer.numChildren; ++i)
+ {
+ UIElement@ container = GetContainer(i);
+ ListView@ list = container.GetChild("AttributeList");
+ if (list is null)
+ continue;
+
+ int width = list.width;
+
+ // Adjust the icon panel's width
+ UIElement@ panel = container.GetChild("IconsPanel", true);
+ if (panel !is null)
+ panel.width = width;
+
+ // At the moment, only 'Is Enabled' container (place-holder + check box) is being created as child of the list view instead of as list item
+ for (uint j = 0; j < list.numChildren; ++j)
+ {
+ UIElement@ element = list.children[j];
+ if (!element.internal)
+ {
+ element.SetFixedWidth(width);
+ UIElement@ title = container.GetChild("TitleText");
+ element.position = IntVector2(0, (title.screenPosition - list.screenPosition).y);
+
+ // Adjust icon panel's width one more time to cater for the space occupied by 'Is Enabled' check box
+ if (panel !is null)
+ panel.width = width - element.children[1].width - panel.layoutSpacing;
+
+ break;
+ }
+ }
+ }
+}
+
+Array ToSerializableArray(Array nodes)
+{
+ Array serializables;
+ for (uint i = 0; i < nodes.length; ++i)
+ serializables.Push(nodes[i]);
+ return serializables;
+}
+
+/// Update the whole attribute inspector window, when fullUpdate flag is set to true then first delete all the containers and repopulate them again from scratch.
+/// The fullUpdate flag is usually set to true when the structure of the attributes are different than the existing attributes in the list.
+void UpdateAttributeInspector(bool fullUpdate = true)
+{
+ if (inspectorLocked)
+ return;
+
+ attributesDirty = false;
+ if (fullUpdate)
+ attributesFullDirty = false;
+
+ // If full update delete all containers and add them back as necessary
+ if (fullUpdate)
+ DeleteAllContainers();
+
+ if (!editNodes.empty)
+ {
+ UIElement@ container = GetNodeContainer();
+
+ Text@ nodeTitle = container.GetChild("TitleText");
+ String nodeType;
+
+ if (editNode !is null)
+ {
+ String idStr;
+ if (editNode.replicated)
+ idStr = " (ID " + String(editNode.id) + ")";
+ else
+ idStr = " (Local ID " + String(editNode.id) + ")";
+ nodeType = editNode.typeName;
+ nodeTitle.text = nodeType + idStr;
+ LineEdit@ tagEdit = parentContainer.GetChild("TagsEdit", true);
+ tagEdit.text = Join(editNode.tags, ";");
+ }
+ else
+ {
+ nodeType = editNodes[0].typeName;
+ nodeTitle.text = nodeType + " (ID " + STRIKED_OUT + " : " + editNodes.length + "x)";
+ }
+ IconizeUIElement(nodeTitle, nodeType);
+
+ ListView@ list = container.GetChild("AttributeList");
+ Array nodes = ToSerializableArray(editNodes);
+ UpdateAttributes(nodes, list, fullUpdate);
+
+ if (fullUpdate)
+ {
+ //\todo Avoid hardcoding
+ // Resize the node editor according to the number of variables, up to a certain maximum
+ uint maxAttrs = Clamp(list.contentElement.numChildren, MIN_NODE_ATTRIBUTES, MAX_NODE_ATTRIBUTES);
+ list.SetFixedHeight(maxAttrs * ATTR_HEIGHT + 2);
+ container.SetFixedHeight(maxAttrs * ATTR_HEIGHT + 58);
+ }
+
+ // Set icon's target in the icon panel
+ SetAttributeEditorID(container.GetChild("ResetToDefault", true), nodes);
+ }
+
+ if (!editComponents.empty)
+ {
+ uint numEditableComponents = editComponents.length / numEditableComponentsPerNode;
+ String multiplierText;
+ if (numEditableComponents > 1)
+ multiplierText = " (" + numEditableComponents + "x)";
+
+ for (uint j = 0; j < numEditableComponentsPerNode; ++j)
+ {
+ UIElement@ container = GetComponentContainer(j);
+ Text@ componentTitle = container.GetChild("TitleText");
+ componentTitle.text = GetComponentTitle(editComponents[j * numEditableComponents]) + multiplierText;
+ IconizeUIElement(componentTitle, editComponents[j * numEditableComponents].typeName);
+ SetIconEnabledColor(componentTitle, editComponents[j * numEditableComponents].enabledEffective);
+
+ Array components;
+ for (uint i = 0; i < numEditableComponents; ++i)
+ {
+ Component@ component = editComponents[j * numEditableComponents + i];
+ components.Push(component);
+ }
+
+ UpdateAttributes(components, container.GetChild("AttributeList"), fullUpdate);
+ SetAttributeEditorID(container.GetChild("ResetToDefault", true), components);
+ }
+ }
+
+ if (!editUIElements.empty)
+ {
+ UIElement@ container = GetUIElementContainer();
+
+ Text@ titleText = container.GetChild("TitleText");
+ DropDownList@ styleList = container.GetChild("StyleDropDown", true);
+ String elementType;
+
+ if (editUIElement !is null)
+ {
+ elementType = editUIElement.typeName;
+ titleText.text = elementType + " [ID " + GetUIElementID(editUIElement).ToString() + "]";
+ SetStyleListSelection(styleList, editUIElement.style);
+ LineEdit@ tagEdit = parentContainer.GetChild("TagsEdit", true);
+ tagEdit.text = Join(editUIElement.tags, ";");
+ }
+ else
+ {
+ elementType = editUIElements[0].typeName;
+ String appliedStyle = cast(editUIElements[0]).style;
+
+ bool sameType = true;
+ bool sameStyle = true;
+ for (uint i = 1; i < editUIElements.length; ++i)
+ {
+ if (editUIElements[i].typeName != elementType)
+ {
+ sameType = false;
+ sameStyle = false;
+ break;
+ }
+
+ if (sameStyle && cast(editUIElements[i]).style != appliedStyle)
+ sameStyle = false;
+ }
+ titleText.text = (sameType ? elementType : "Mixed type") + " [ID " + STRIKED_OUT + " : " + editUIElements.length + "x]";
+ SetStyleListSelection(SetEditable(styleList, sameStyle), sameStyle ? appliedStyle : STRIKED_OUT);
+ if (!sameType)
+ elementType.Clear(); // No icon
+ }
+ IconizeUIElement(titleText, elementType);
+
+ UpdateAttributes(editUIElements, container.GetChild("AttributeList"), fullUpdate);
+ SetAttributeEditorID(container.GetChild("ResetToDefault", true), editUIElements);
+ }
+
+ if (parentContainer.numChildren > 0)
+ UpdateAttributeInspectorIcons();
+ else
+ {
+ // No editables, insert a dummy component container to show the information
+ Text@ titleText = GetComponentContainer(0).GetChild("TitleText");
+ titleText.text = "Select editable objects";
+ titleText.autoLocalizable = true;
+ UIElement@ panel = titleText.GetChild("IconsPanel");
+ panel.visible = false;
+ }
+
+ // Adjust size and position of manual-layout UI-elements, e.g. icons panel
+ if (fullUpdate)
+ HandleWindowLayoutUpdated();
+}
+
+/// Update the attribute list of the node container.
+void UpdateNodeAttributes()
+{
+ bool fullUpdate = false;
+ UpdateAttributes(ToSerializableArray(editNodes), GetNodeContainer().GetChild("AttributeList"), fullUpdate);
+ if (fullUpdate)
+ HandleWindowLayoutUpdated();
+}
+
+/// Update the icons enabled color based on the internal state of the objects.
+/// For node and component, based on "enabled" property.
+/// For ui-element, based on "visible" property.
+void UpdateAttributeInspectorIcons()
+{
+ if (!editNodes.empty)
+ {
+ Text@ nodeTitle = GetNodeContainer().GetChild("TitleText");
+ if (editNode !is null)
+ SetIconEnabledColor(nodeTitle, editNode.enabled);
+ else if (editNodes.length > 0)
+ {
+ bool hasSameEnabledState = true;
+
+ for (uint i = 1; i < editNodes.length; ++i)
+ {
+ if (editNodes[i].enabled != editNodes[0].enabled)
+ {
+ hasSameEnabledState = false;
+ break;
+ }
+ }
+
+ SetIconEnabledColor(nodeTitle, editNodes[0].enabled, !hasSameEnabledState);
+ }
+ }
+
+ if (!editComponents.empty)
+ {
+ uint numEditableComponents = editComponents.length / numEditableComponentsPerNode;
+
+ for (uint j = 0; j < numEditableComponentsPerNode; ++j)
+ {
+ Text@ componentTitle = GetComponentContainer(j).GetChild("TitleText");
+
+ bool enabledEffective = editComponents[j * numEditableComponents].enabledEffective;
+ bool hasSameEnabledState = true;
+ for (uint i = 1; i < numEditableComponents; ++i)
+ {
+ if (editComponents[j * numEditableComponents + i].enabledEffective != enabledEffective)
+ {
+ hasSameEnabledState = false;
+ break;
+ }
+ }
+
+ SetIconEnabledColor(componentTitle, enabledEffective, !hasSameEnabledState);
+ }
+ }
+
+ if (!editUIElements.empty)
+ {
+ Text@ elementTitle = GetUIElementContainer().GetChild("TitleText");
+ if (editUIElement !is null)
+ SetIconEnabledColor(elementTitle, editUIElement.visible);
+ else if (editUIElements.length > 0)
+ {
+ bool hasSameVisibleState = true;
+ bool visible = cast(editUIElements[0]).visible;
+
+ for (uint i = 1; i < editUIElements.length; ++i)
+ {
+ if (cast(editUIElements[i]).visible != visible)
+ {
+ hasSameVisibleState = false;
+ break;
+ }
+ }
+
+ SetIconEnabledColor(elementTitle, visible, !hasSameVisibleState);
+ }
+ }
+}
+
+/// Return true if the edit attribute action should continue.
+bool PreEditAttribute(Array@ serializables, uint index)
+{
+ return true;
+}
+
+/// Call after the attribute values in the target serializables have been edited.
+void PostEditAttribute(Array@ serializables, uint index, const Array& oldValues)
+{
+ // Create undo actions for the edits
+ EditActionGroup group;
+ for (uint i = 0; i < serializables.length; ++i)
+ {
+ EditAttributeAction action;
+ action.Define(serializables[i], index, oldValues[i]);
+ group.actions.Push(action);
+ }
+ SaveEditActionGroup(group);
+
+ // If a UI-element changing its 'Is Modal' attribute, clear the hierarchy list selection
+ int itemType = GetType(serializables[0]);
+ if (itemType == ITEM_UI_ELEMENT && serializables[0].attributeInfos[index].name == "Is Modal")
+ hierarchyList.ClearSelection();
+
+ for (uint i = 0; i < serializables.length; ++i)
+ {
+ PostEditAttribute(serializables[i], index);
+ if (itemType == ITEM_UI_ELEMENT)
+ SetUIElementModified(serializables[i]);
+ }
+
+ if (itemType != ITEM_UI_ELEMENT)
+ SetSceneModified();
+}
+
+/// Call after the attribute values in the target serializables have been edited.
+void PostEditAttribute(Serializable@ serializable, uint index)
+{
+ // If a StaticModel/AnimatedModel/Skybox model was changed, apply a possibly different material list
+ if (applyMaterialList && serializable.attributeInfos[index].name == "Model")
+ {
+ StaticModel@ staticModel = cast(serializable);
+ if (staticModel !is null)
+ staticModel.ApplyMaterialList();
+ }
+
+ // If a CollisionShape changed the shape type to trimesh or convex, and a collision model is not set,
+ // try to get it from a StaticModel in the same node
+ if (serializable.typeName == "CollisionShape" && serializable.attributeInfos[index].name == "Shape Type")
+ {
+ int shapeType = serializable.GetAttribute("Shape Type").GetInt();
+ if ((shapeType == 6 || shapeType == 7) && serializable.GetAttribute("CustomGeometry ComponentID").GetInt() == 0 &&
+ serializable.GetAttribute("Model").GetResourceRef().name.Trimmed().length == 0)
+ {
+ Node@ ownerNode = cast(serializable).node;
+ if (ownerNode !is null)
+ {
+ StaticModel@ staticModel = ownerNode.GetComponent("StaticModel");
+ if (staticModel !is null)
+ {
+ serializable.SetAttribute("Model", staticModel.GetAttribute("Model"));
+ serializable.ApplyAttributes();
+ }
+ }
+ }
+ }
+
+}
+
+/// Store the IDs of the actual serializable objects into user-defined variable of the 'attribute editor' (e.g. line-edit, drop-down-list, etc).
+void SetAttributeEditorID(UIElement@ attrEdit, Array@ serializables)
+{
+ if (serializables is null || serializables.length == 0)
+ return;
+
+ // All target serializables must be either nodes, ui-elements, or components
+ Array ids;
+ switch (GetType(serializables[0]))
+ {
+ case ITEM_NODE:
+ for (uint i = 0; i < serializables.length; ++i)
+ ids.Push(cast(serializables[i]).id);
+ attrEdit.vars[NODE_IDS_VAR] = ids;
+ break;
+
+ case ITEM_COMPONENT:
+ for (uint i = 0; i < serializables.length; ++i)
+ ids.Push(cast(serializables[i]).id);
+ attrEdit.vars[COMPONENT_IDS_VAR] = ids;
+ break;
+
+ case ITEM_UI_ELEMENT:
+ for (uint i = 0; i < serializables.length; ++i)
+ ids.Push(GetUIElementID(cast(serializables[i])));
+ attrEdit.vars[UI_ELEMENT_IDS_VAR] = ids;
+ break;
+
+ default:
+ break;
+ }
+}
+
+/// Return the actual serializable objects based on the IDs stored in the user-defined variable of the 'attribute editor'.
+Array@ GetAttributeEditorTargets(UIElement@ attrEdit)
+{
+ Array ret;
+ Variant variant = attrEdit.GetVar(NODE_IDS_VAR);
+ if (!variant.empty)
+ {
+ Array@ ids = variant.GetVariantVector();
+ for (uint i = 0; i < ids.length; ++i)
+ {
+ Node@ node = editorScene.GetNode(ids[i].GetUInt());
+ if (node !is null)
+ ret.Push(node);
+ }
+ }
+ else
+ {
+ variant = attrEdit.GetVar(COMPONENT_IDS_VAR);
+ if (!variant.empty)
+ {
+ Array@ ids = variant.GetVariantVector();
+ for (uint i = 0; i < ids.length; ++i)
+ {
+ Component@ component = editorScene.GetComponent(ids[i].GetUInt());
+ if (component !is null)
+ ret.Push(component);
+ }
+ }
+ else
+ {
+ variant = attrEdit.GetVar(UI_ELEMENT_IDS_VAR);
+ if (!variant.empty)
+ {
+ Array@ ids = variant.GetVariantVector();
+ for (uint i = 0; i < ids.length; ++i)
+ {
+ UIElement@ element = editorUIElement.GetChild(UI_ELEMENT_ID_VAR, ids[i], true);
+ if (element !is null)
+ ret.Push(element);
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+void HandleTagsEdit(StringHash eventType, VariantMap& eventData)
+{
+ LineEdit@ lineEdit = eventData["Element"].GetPtr();
+ Array tags = lineEdit.text.Split(';');
+
+ if (editUIElement !is null)
+ {
+ editUIElement.RemoveAllTags();
+ for (uint i = 0; i < tags.length; i++)
+ editUIElement.AddTag(tags[i].Trimmed());
+ }
+ else if (editNode !is null)
+ {
+ editNode.RemoveAllTags();
+ for (uint i = 0; i < tags.length; i++)
+ editNode.AddTag(tags[i].Trimmed());
+ }
+}
+
+void HandleTagsSelect(StringHash eventType, VariantMap& eventData)
+{
+ UIElement@ tagSelect = eventData["Element"].GetPtr();
+ Array