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 FAQ

LARABIE 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 actions; + String Indicator = "* "; + // In first priority changes to UIElement + if (editUIElement !is null) + { + // 1. Add established tags from current editable UIElement to menu + Array elementTags = editUIElement.tags; + for (uint i = 0; i < elementTags.length; i++) + { + bool isHasTag = editUIElement.HasTag(elementTags[i]); + String taggedIndicator = (isHasTag ? Indicator : ""); + actions.Push(CreateContextMenuItem(taggedIndicator + elementTags[i], "HandleTagsMenuSelection", elementTags[i])); + } + + // 2. Add default tags + Array stdTags = defaultTags.Split(';'); + for (uint i= 0; i < stdTags.length; i++) + { + bool isHasTag = editUIElement.HasTag(stdTags[i]); + // Add this tag into menu if only Node not tadded with it yet, otherwise it showed on step 1. + if (!isHasTag) + { + String taggedIndicator = (isHasTag ? Indicator : ""); + actions.Push(CreateContextMenuItem(taggedIndicator + stdTags[i], "HandleTagsMenuSelection", stdTags[i])); + } + } + } + else if (editNode !is null) + { + // 1. Add established tags from Node to menu + Array nodeTags = editNode.tags; + for (uint i = 0; i < nodeTags.length; i++) + { + bool isHasTag = editNode.HasTag(nodeTags[i]); + String taggedIndicator = (isHasTag ? Indicator : ""); + actions.Push(CreateContextMenuItem(taggedIndicator + nodeTags[i], "HandleTagsMenuSelection", nodeTags[i])); + } + + Array sceneTags = editorScene.tags; + // 2. Add tags from Scene.tags (In this scenario Scene.tags used as storage for frequently used tags in current Scene only) + for (uint i = 0; i < sceneTags.length; i++) + { + bool isHasTag = editNode.HasTag(sceneTags[i]); + // Add this tag into menu if only Node not tadded with it yet, otherwise it showed on step 1. + if (!isHasTag) + { + String taggedIndicator = (isHasTag ? Indicator : ""); + actions.Push(CreateContextMenuItem(taggedIndicator + sceneTags[i], "HandleTagsMenuSelection", sceneTags[i])); + } + } + + // 3. Add default tags + Array stdTags = defaultTags.Split(';'); + for (uint i = 0; i < stdTags.length; i++) + { + bool isHasTag = editNode.HasTag(stdTags[i]); + // Add this tag into menu if only Node not tadded with it yet, otherwise it showed on step 1. + if (!isHasTag) + { + String taggedIndicator = (isHasTag ? Indicator : ""); + actions.Push(CreateContextMenuItem(taggedIndicator + stdTags[i], "HandleTagsMenuSelection", stdTags[i])); + } + } + } + + // if any action has been added, add also Reset and Cancel and show menu + if (actions.length > 0) + { + actions.Push(CreateContextMenuItem("Reset", "HandleTagsMenuSelection", "Reset")); + actions.Push(CreateContextMenuItem("Cancel", "HandleTagsMenuSelectionDivisor")); + ActivateContextMenu(actions); + } + +} +void HandleTagsMenuSelectionDivisor() +{ + //do nothing +} +void HandleTagsMenuSelection() +{ + Menu@ menu = GetEventSender(); + if (menu is null) + return; + + String menuSelectedTag = menu.name; + + // In first priority changes to UIElement + if (editUIElement !is null) + { + if (menuSelectedTag == "Reset") + { + editUIElement.RemoveAllTags(); + UpdateAttributeInspector(); + return; + } + + if (!editUIElement.HasTag(menuSelectedTag)) + { + editUIElement.AddTag(menuSelectedTag.Trimmed()); + } + else + { + editUIElement.RemoveTag(menuSelectedTag.Trimmed()); + } + } + else if (editNode !is null) + { + if (menuSelectedTag == "Reset") + { + editNode.RemoveAllTags(); + UpdateAttributeInspector(); + return; + } + + if (!editNode.HasTag(menuSelectedTag)) + { + editNode.AddTag(menuSelectedTag.Trimmed()); + } + else + { + editNode.RemoveTag(menuSelectedTag.Trimmed()); + } + } + + UpdateAttributeInspector(); +} + +/// Handle reset to default event, sent when reset icon in the icon-panel is clicked. +void HandleResetToDefault(StringHash eventType, VariantMap& eventData) +{ + ui.cursor.shape = CS_BUSY; + + UIElement@ button = eventData["Element"].GetPtr(); + Array@ serializables = GetAttributeEditorTargets(button); + if (serializables.empty) + return; + + // Group for storing undo actions + EditActionGroup group; + + // Reset target serializables to their default values + for (uint i = 0; i < serializables.length; ++i) + { + Serializable@ target = serializables[i]; + + ResetAttributesAction action; + action.Define(target); + group.actions.Push(action); + + target.ResetToDefault(); + if (action.targetType == ITEM_UI_ELEMENT) + { + action.SetInternalVars(target); + SetUIElementModified(target); + } + target.ApplyAttributes(); + for (uint j = 0; j < target.numAttributes; ++j) + PostEditAttribute(target, j); + } + + SaveEditActionGroup(group); + if (GetType(serializables[0]) != ITEM_UI_ELEMENT) + SetSceneModified(); + attributesFullDirty = true; +} + +/// Handle create new user-defined variable event for node target. +void CreateNodeVariable(StringHash eventType, VariantMap& eventData) +{ + if (editNodes.empty) + return; + + String newName = ExtractVariableName(eventData); + if (newName.empty) + return; + + // Create scene variable + editorScene.RegisterVar(newName); + globalVarNames[newName] = newName; + + Variant newValue = ExtractVariantType(eventData); + + // If we overwrite an existing variable, must recreate the attribute-editor(s) for the correct type + bool overwrite = false; + for (uint i = 0; i < editNodes.length; ++i) + { + overwrite = overwrite || editNodes[i].vars.Contains(newName); + editNodes[i].vars[newName] = newValue; + } + if (overwrite) + attributesFullDirty = true; + else + attributesDirty = true; +} + +/// Handle delete existing user-defined variable event for node target. +void DeleteNodeVariable(StringHash eventType, VariantMap& eventData) +{ + if (editNodes.empty) + return; + + String delName = ExtractVariableName(eventData); + if (delName.empty) + return; + + bool erased = false; + for (uint i = 0; i < editNodes.length; ++i) + { + // \todo Should first check whether var in question is editable + erased = editNodes[i].vars.Erase(delName) || erased; + } + if (erased) + { + attributesDirty = true; + // If the attribute is not defined in any other node, unregister from the scene + // to prevent it from being unnecessarily saved; the global var list will still hold it + // to keep the hash-name mapping known in case it's in use in other scenes + Array@ allChildren = editorScene.GetChildren(true); + StringHash delNameHash(delName); + bool inUse = false; + + for (uint i = 0; i < allChildren.length; ++i) + { + if (allChildren[i].vars.Contains(delNameHash)) + { + inUse = true; + break; + } + } + + if (!inUse) + editorScene.UnregisterVar(delName); + } +} + +/// Handle create new user-defined variable event for ui-element target. +void CreateUIElementVariable(StringHash eventType, VariantMap& eventData) +{ + if (editUIElements.empty) + return; + + String newName = ExtractVariableName(eventData); + if (newName.empty) + return; + + // Create UIElement variable + globalVarNames[newName] = newName; + + Variant newValue = ExtractVariantType(eventData); + + // If we overwrite an existing variable, must recreate the attribute-editor(s) for the correct type + bool overwrite = false; + for (uint i = 0; i < editUIElements.length; ++i) + { + UIElement@ element = cast(editUIElements[i]); + overwrite = overwrite || element.vars.Contains(newName); + element.vars[newName] = newValue; + } + if (overwrite) + attributesFullDirty = true; + else + attributesDirty = true; +} + +/// Handle delete existing user-defined variable event for ui-element target. +void DeleteUIElementVariable(StringHash eventType, VariantMap& eventData) +{ + if (editUIElements.empty) + return; + + String delName = ExtractVariableName(eventData); + if (delName.empty) + return; + + // Note: intentionally do not unregister the variable name here as the same variable name may still be used by other attribute list + + bool erased = false; + for (uint i = 0; i < editUIElements.length; ++i) + { + // \todo Should first check whether var in question is editable + erased = cast(editUIElements[i]).vars.Erase(delName) || erased; + } + if (erased) + attributesDirty = true; +} + +String ExtractVariableName(VariantMap& eventData) +{ + UIElement@ element = eventData["Element"].GetPtr(); + LineEdit@ nameEdit = element.parent.GetChild("VarNameEdit"); + return nameEdit.text.Trimmed(); +} + +Variant ExtractVariantType(VariantMap& eventData) +{ + DropDownList@ dropDown = eventData["Element"].GetPtr(); + switch (dropDown.selection) + { + case 0: + return int(0); + case 1: + return false; + case 2: + return float(0.0); + case 3: + return Variant(String()); + case 4: + return Variant(Vector3()); + case 5: + return Variant(Color()); + } + + return Variant(); // This should not happen +} + +/// Get back the human-readable variable name from the StringHash. +String GetVarName(StringHash hash) +{ + // First try to get it from scene + String name = editorScene.GetVarName(hash); + // Then from the global variable reverse mappings + if (name.empty && globalVarNames.Contains(hash)) + name = globalVarNames[hash].ToString(); + return name; +} + +bool inSetStyleListSelection = false; + +/// Select/highlight the matching style in the style drop-down-list based on specified style. +void SetStyleListSelection(DropDownList@ styleList, const String&in style) +{ + // Prevent infinite loop upon initial style selection + inSetStyleListSelection = true; + + uint selection = M_MAX_UNSIGNED; + String styleName = style.empty ? "auto" : style; + Array items = styleList.GetItems(); + for (uint i = 0; i < items.length; ++i) + { + Text@ element = cast(items[i]); + if (element is null) + continue; // It may be a divider + if (element.text == styleName) + { + selection = i; + break; + } + } + styleList.selection = selection; + + inSetStyleListSelection = false; +} + +/// Handle the style change of the target ui-elements event when a new style is picked from the style drop-down-list. +void HandleStyleItemSelected(StringHash eventType, VariantMap& eventData) +{ + if (inSetStyleListSelection || editUIElements.empty) + return; + + ui.cursor.shape = CS_BUSY; + + DropDownList@ styleList = eventData["Element"].GetPtr(); + Text@ text = cast(styleList.selectedItem); + if (text is null) + return; + String newStyle = text.text; + if (newStyle == "auto") + newStyle.Clear(); + + // Group for storing undo actions + EditActionGroup group; + + // Apply new style to selected UI-elements + for (uint i = 0; i < editUIElements.length; ++i) + { + UIElement@ element = editUIElements[i]; + + ApplyUIElementStyleAction action; + action.Define(element, newStyle); + group.actions.Push(action); + + // Use the Redo() to actually do the action + action.Redo(); + } + + SaveEditActionGroup(group); +} diff --git a/bin/Data/Scripts/Editor/EditorLayers.as b/bin/Data/Scripts/Editor/EditorLayers.as new file mode 100644 index 0000000..defe4e7 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorLayers.as @@ -0,0 +1,326 @@ +// Urho3D editor layer window + +enum EditMaskType +{ + EDIT_VIEW_MASK = 0, + EDIT_LIGHT_MASK, + EDIT_SHADOW_MASK, + EDIT_ZONE_MASK +} + +StringHash lineEditType = StringHash("LineEdit"); +StringHash eventTypeMouseButtonDown = StringHash("MouseButtonDown"); +StringHash eventTypeMouseMove = StringHash("MouseMove"); + +Array bits; +Window@ layerWindow; +IntVector2 layerWindowPosition; +Node@ patternMaskNode; +EditMode previousEdit ; +bool toggleBusy = false; +int editMaskType = 0; + +void CreateLayerEditor() +{ + if (layerWindow !is null) + return; + + layerWindow = LoadEditorUI("UI/EditorLayersWindow.xml"); + ui.root.AddChild(layerWindow); + layerWindow.opacity = uiMaxOpacity; + + HideLayerEditor(); + + bits.Resize(MAX_BITMASK_BITS); + + DropDownList@ EditMaskModeList = layerWindow.GetChild("LayerModeEdit", true); + SubscribeToEvent(EditMaskModeList, "ItemSelected", "HandleLayerModeEdit"); + + for (int i=0; i < MAX_BITMASK_BITS; i++) + { + bits[i] = layerWindow.GetChild("Bit" + String(i), true); + bits[i].vars["index"] = i; + SubscribeToEvent(bits[i], "Toggled", "ToggleBits"); + } +} + +bool ShowLayerEditor() +{ + // avoid to show layer window when we type text in LineEdit + if (ui.focusElement !is null && ui.focusElement.type == lineEditType && lastSelectedNode.Get() is null) + return false; + + // to avoid when we close dialog with selected other node + Node@ node = lastSelectedNode.Get(); + patternMaskNode = node; + + // just change position if already opened + if (layerWindow.visible == true) + { + HideLayerEditor(); + /* + layerWindowPosition = ui.cursorPosition; + layerWindow.position = layerWindowPosition; + layerWindowPosition.x += layerWindow.width / 2; + layerWindow.BringToFront(); + */ + return true; + } + + // to prevent manipulation until we change mask for one or group nodes + previousEdit = editMode; + editMode = EDIT_SELECT; + + // get mask type from pattern node + EstablishSelectedNodeBitMaskToPanel(); + + layerWindowPosition = ui.cursorPosition; + layerWindow.position = layerWindowPosition; + layerWindowPosition.x += layerWindow.width / 2; + layerWindow.visible = true; + layerWindow.BringToFront(); + + return true; +} + +void HideLayerEditor() +{ + layerWindow.visible = false; + editMode = previousEdit; +} + +void EstablishSelectedNodeBitMaskToPanel() +{ + if (selectedNodes.length < 1) return; + Node@ node; + node = patternMaskNode; + + if (node !is null) + { + // find first drawable to get mask + Array components = node.GetComponents(); + + Drawable@ firstDrawableInNode; + if (components.length > 0 ) + { + firstDrawableInNode = cast(components[0]); + } + + if (firstDrawableInNode !is null) + { + int showMask = 0; + + switch (editMaskType) + { + case EDIT_VIEW_MASK: + showMask = firstDrawableInNode.viewMask; + break; + case EDIT_LIGHT_MASK: + showMask = firstDrawableInNode.lightMask; + break; + case EDIT_SHADOW_MASK: + showMask = firstDrawableInNode.shadowMask; + break; + case EDIT_ZONE_MASK: + showMask = firstDrawableInNode.zoneMask; + break; + } + + SetupBitsPanel(showMask); + } + } +} + +void SetupBitsPanel(int mask) +{ + for (int i = 0; i < 8; i++) + { + if ((1 << i) & mask != 0) + { + bits[i].checked = true; + } + else + { + bits[i].checked = false; + } + } +} + +void ChangeNodeViewMask(Node@ node, EditActionGroup@ group, int mask) +{ + Array components = node.GetComponents(); + if (components.length > 0) + { + for (uint componentIndex = 0; componentIndex < components.length; componentIndex++) + { + Component@ component = components[componentIndex]; + Drawable@ drawable = cast(component); + if (drawable !is null) + { + // Save before modification + CreateDrawableMaskAction action; + action.Define(drawable, editMaskType); + group.actions.Push(action); + + switch (editMaskType) + { + case EDIT_VIEW_MASK: + drawable.viewMask = mask; + break; + case EDIT_LIGHT_MASK: + drawable.lightMask = mask; + break; + case EDIT_SHADOW_MASK: + drawable.shadowMask = mask; + break; + case EDIT_ZONE_MASK: + drawable.zoneMask = mask; + break; + } + } + } + } +} + +void EstablishBitMaskToSelectedNodes() +{ + if (selectedNodes.length < 1) return; + + int maskTypeSelected = 0; + + // Group for storing undo actions + EditActionGroup group; + + for (uint indexNode = 0; indexNode < selectedNodes.length; indexNode++) + { + Node@ node = selectedNodes[indexNode]; + if (node !is null) + { + int mask = 0; + for (int i = 0; i < MAX_BITMASK_BITS; i++) + { + mask = mask | (bits[i].checked ? 1 << i : 0); + } + + if (mask == MAX_BITMASK_VALUE) + mask = -1; + + ChangeNodeViewMask(node, group, mask); + Array children = node.GetChildren(true); + if (children.length > 0) + { + for (uint i = 0; i < children.length; i++) + { + ChangeNodeViewMask(children[i], group, mask); + } + } + } + } + + SaveEditActionGroup(group); + SetSceneModified(); +} + +void HandleLayerModeEdit(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ edit = eventData["Element"].GetPtr(); + editMaskType = edit.selection; + EstablishSelectedNodeBitMaskToPanel(); +} + +void HandleMaskTypeScroll(StringHash eventType, VariantMap& eventData) +{ + if (!layerWindow.IsInside(ui.cursorPosition, true)) return; + + DropDownList@ listView = layerWindow.GetChild("LayerModeEdit", true); + editMaskType = listView.selection; + + int wheel = eventData["Wheel"].GetInt(); + + if (wheel > 0) + { + if (editMaskType > 0) editMaskType--; + } + else if (wheel < 0) + { + if (editMaskType < 3) editMaskType++; + } + + listView.selection = editMaskType; + EstablishSelectedNodeBitMaskToPanel(); +} + +void HandleHideLayerEditor(StringHash eventType, VariantMap& eventData) +{ + if (layerWindow.visible == false) return; + + // if layer window not in focus and mouse folow away - close layer window + if ( eventType == eventTypeMouseMove) + { + IntVector2 mousePos; + mousePos.x = eventData["X"].GetInt(); + mousePos.y = eventData["Y"].GetInt(); + + Vector2 a = Vector2(layerWindowPosition.x, layerWindowPosition.y); + Vector2 b = Vector2(mousePos.x, mousePos.y); + Vector2 dir = a - b; + float distance = dir.length; + + if (distance > layerWindow.width) + HideLayerEditor(); + } + // if user click on scene - close layser window + else if (eventType == eventTypeMouseButtonDown) + { + if (ui.focusElement is null) + { + HideLayerEditor(); + } + } +} + +// Every time then we click on bits they are immediately established for all selected nodes for masks +void ToggleBits(StringHash eventType, VariantMap& eventData) +{ + if (toggleBusy) return; + toggleBusy = true; + + CheckBox@ cb = cast(eventData["Element"].GetPtr()); + int bitIndex = cb.vars["index"].GetInt(); + + + if (bitIndex < MAX_BITMASK_BITS) + { + // batch bits invert if pressed ctrl or alt + if (input.keyDown[KEY_CTRL]) + { + bool bit = true; + bits[bitIndex].checked = bit; + + for (int i = 0; i < MAX_BITMASK_BITS; i++) + { + if (i != bitIndex) + { + bits[i].checked = !bit; + }; + } + } + else if (input.keyDown[KEY_ALT]) + { + bool bit = false; + bits[bitIndex].checked = bit; + + for (int i = 0; i < MAX_BITMASK_BITS; i++) + { + if (i != bitIndex) + { + bits[i].checked = !bit; + }; + } + } + + EstablishBitMaskToSelectedNodes(); + } + + toggleBusy = false; +} diff --git a/bin/Data/Scripts/Editor/EditorMaterial.as b/bin/Data/Scripts/Editor/EditorMaterial.as new file mode 100644 index 0000000..873eec5 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorMaterial.as @@ -0,0 +1,1027 @@ +// Urho3D material editor + +Window@ materialWindow; +Material@ editMaterial; +XMLFile@ oldMaterialState; +bool inMaterialRefresh = true; +View3D@ materialPreview; +Scene@ previewScene; +Node@ previewCameraNode; +Node@ previewLightNode; +Light@ previewLight; +Node@ previewModelNode; +StaticModel@ previewModel; + +void CreateMaterialEditor() +{ + if (materialWindow !is null) + return; + + materialWindow = LoadEditorUI("UI/EditorMaterialWindow.xml"); + ui.root.AddChild(materialWindow); + materialWindow.opacity = uiMaxOpacity; + + InitMaterialPreview(); + InitModelPreviewList(); + RefreshMaterialEditor(); + + int height = Min(ui.root.height - 60, 600); + materialWindow.SetSize(400, height); + CenterDialog(materialWindow); + + HideMaterialEditor(); + + SubscribeToEvent(materialWindow.GetChild("NewButton", true), "Released", "NewMaterial"); + SubscribeToEvent(materialWindow.GetChild("RevertButton", true), "Released", "RevertMaterial"); + SubscribeToEvent(materialWindow.GetChild("SaveButton", true), "Released", "SaveMaterial"); + SubscribeToEvent(materialWindow.GetChild("SaveAsButton", true), "Released", "SaveMaterialAs"); + SubscribeToEvent(materialWindow.GetChild("CloseButton", true), "Released", "HideMaterialEditor"); + SubscribeToEvent(materialWindow.GetChild("NewParameterDropDown", true), "ItemSelected", "CreateShaderParameter"); + SubscribeToEvent(materialWindow.GetChild("DeleteParameterButton", true), "Released", "DeleteShaderParameter"); + SubscribeToEvent(materialWindow.GetChild("NewTechniqueButton", true), "Released", "NewTechnique"); + SubscribeToEvent(materialWindow.GetChild("DeleteTechniqueButton", true), "Released", "DeleteTechnique"); + SubscribeToEvent(materialWindow.GetChild("SortTechniquesButton", true), "Released", "SortTechniques"); + SubscribeToEvent(materialWindow.GetChild("VSDefinesEdit", true), "TextFinished", "EditVSDefines"); + SubscribeToEvent(materialWindow.GetChild("PSDefinesEdit", true), "TextFinished", "EditPSDefines"); + SubscribeToEvent(materialWindow.GetChild("ConstantBiasEdit", true), "TextChanged", "EditConstantBias"); + SubscribeToEvent(materialWindow.GetChild("ConstantBiasEdit", true), "TextFinished", "EditConstantBias"); + SubscribeToEvent(materialWindow.GetChild("SlopeBiasEdit", true), "TextChanged", "EditSlopeBias"); + SubscribeToEvent(materialWindow.GetChild("SlopeBiasEdit", true), "TextFinished", "EditSlopeBias"); + SubscribeToEvent(materialWindow.GetChild("RenderOrderEdit", true), "TextChanged", "EditRenderOrder"); + SubscribeToEvent(materialWindow.GetChild("RenderOrderEdit", true), "TextFinished", "EditRenderOrder"); + SubscribeToEvent(materialWindow.GetChild("CullModeEdit", true), "ItemSelected", "EditCullMode"); + SubscribeToEvent(materialWindow.GetChild("ShadowCullModeEdit", true), "ItemSelected", "EditShadowCullMode"); + SubscribeToEvent(materialWindow.GetChild("FillModeEdit", true), "ItemSelected", "EditFillMode"); + SubscribeToEvent(materialWindow.GetChild("OcclusionEdit", true), "Toggled", "EditOcclusion"); + SubscribeToEvent(materialWindow.GetChild("AlphaToCoverageEdit", true), "Toggled", "EditAlphaToCoverage"); + SubscribeToEvent(materialWindow.GetChild("LineAntiAliasEdit", true), "Toggled", "EditLineAntiAlias"); +} + +bool ToggleMaterialEditor() +{ + if (materialWindow.visible == false) + ShowMaterialEditor(); + else + HideMaterialEditor(); + return true; +} + +void ShowMaterialEditor() +{ + RefreshMaterialEditor(); + materialWindow.visible = true; + materialWindow.BringToFront(); +} + +void HideMaterialEditor() +{ + materialWindow.visible = false; +} + +void InitMaterialPreview() +{ + previewScene = Scene("PreviewScene"); + previewScene.CreateComponent("Octree"); + + Node@ zoneNode = previewScene.CreateChild("Zone"); + Zone@ zone = zoneNode.CreateComponent("Zone"); + zone.boundingBox = BoundingBox(-1000, 1000); + zone.ambientColor = Color(0.15, 0.15, 0.15); + zone.fogColor = Color(0, 0, 0); + zone.fogStart = 10.0; + zone.fogEnd = 100.0; + + previewCameraNode = previewScene.CreateChild("PreviewCamera"); + previewCameraNode.position = Vector3(0, 0, -1.5); + Camera@ camera = previewCameraNode.CreateComponent("Camera"); + camera.nearClip = 0.1f; + camera.farClip = 100.0f; + + previewLightNode = previewScene.CreateChild("PreviewLight"); + previewLightNode.direction = Vector3(0.5, -0.5, 0.5); + previewLight = previewLightNode.CreateComponent("Light"); + previewLight.lightType = LIGHT_DIRECTIONAL; + previewLight.specularIntensity = 0.5; + + previewModelNode = previewScene.CreateChild("PreviewModel"); + previewModelNode.rotation = Quaternion(0, 0, 0); + previewModel = previewModelNode.CreateComponent("StaticModel"); + previewModel.model = cache.GetResource("Model", "Models/Sphere.mdl"); + + materialPreview = materialWindow.GetChild("MaterialPreview", true); + materialPreview.SetFixedHeight(100); + materialPreview.SetView(previewScene, camera); + materialPreview.viewport.renderPath = renderPath; + materialPreview.autoUpdate = false; + + SubscribeToEvent(materialPreview, "DragMove", "RotateMaterialPreview"); +} + +void InitModelPreviewList() +{ + DropDownList@ modelPreview = materialWindow.GetChild("ModelPreview", true); + modelPreview.selection = 1; + SubscribeToEvent(materialWindow.GetChild("ModelPreview", true), "ItemSelected", "EditModelPreviewChange"); +} + +void EditMaterial(Material@ mat) +{ + if (editMaterial !is null) + UnsubscribeFromEvent(editMaterial, "ReloadFinished"); + + editMaterial = mat; + + if (editMaterial !is null) + SubscribeToEvent(editMaterial, "ReloadFinished", "RefreshMaterialEditor"); + + ShowMaterialEditor(); +} + +void RefreshMaterialEditor() +{ + RefreshMaterialPreview(); + RefreshMaterialName(); + RefreshMaterialTechniques(); + RefreshMaterialTextures(); + RefreshMaterialShaderParameters(); + RefreshMaterialMiscParameters(); +} + +void RefreshMaterialPreview() +{ + previewModel.material = editMaterial; + materialPreview.QueueUpdate(); +} + +void RefreshMaterialName() +{ + UIElement@ container = materialWindow.GetChild("NameContainer", true); + container.RemoveAllChildren(); + + LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, 0, 0); + if (editMaterial !is null) + nameEdit.text = editMaterial.name; + SubscribeToEvent(nameEdit, "TextFinished", "EditMaterialName"); + + Button@ pickButton = CreateResourcePickerButton(container, null, 0, 0, "smallButtonPick"); + SubscribeToEvent(pickButton, "Released", "PickEditMaterial"); +} + +void RefreshMaterialTechniques(bool fullUpdate = true) +{ + ListView@ list = materialWindow.GetChild("TechniqueList", true); + + if (editMaterial is null) + return; + + if (fullUpdate == true) + { + list.RemoveAllItems(); + + for (uint i = 0; i < editMaterial.numTechniques; ++i) + { + TechniqueEntry entry = editMaterial.techniqueEntries[i]; + + UIElement@ container = UIElement(); + container.SetLayout(LM_HORIZONTAL, 4); + container.SetFixedHeight(ATTR_HEIGHT); + list.AddItem(container); + + LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, i, 0); + nameEdit.name = "TechniqueNameEdit" + String(i); + + Button@ pickButton = CreateResourcePickerButton(container, null, i, 0, "smallButtonPick"); + SubscribeToEvent(pickButton, "Released", "PickMaterialTechnique"); + Button@ openButton = CreateResourcePickerButton(container, null, i, 0, "smallButtonOpen"); + SubscribeToEvent(openButton, "Released", "OpenResource"); + + if (entry.technique !is null) + nameEdit.text = entry.technique.name; + + SubscribeToEvent(nameEdit, "TextFinished", "EditMaterialTechnique"); + + UIElement@ container2 = UIElement(); + container2.SetLayout(LM_HORIZONTAL, 4); + container2.SetFixedHeight(ATTR_HEIGHT); + list.AddItem(container2); + + Text@ text = container2.CreateChild("Text"); + text.style = "EditorAttributeText"; + text.text = "Quality"; + LineEdit@ attrEdit = CreateAttributeLineEdit(container2, null, i, 0); + attrEdit.text = String(entry.qualityLevel); + SubscribeToEvent(attrEdit, "TextChanged", "EditTechniqueQuality"); + SubscribeToEvent(attrEdit, "TextFinished", "EditTechniqueQuality"); + + text = container2.CreateChild("Text"); + text.style = "EditorAttributeText"; + text.text = "LOD Distance"; + attrEdit = CreateAttributeLineEdit(container2, null, i, 0); + attrEdit.text = String(entry.lodDistance); + SubscribeToEvent(attrEdit, "TextChanged", "EditTechniqueLodDistance"); + SubscribeToEvent(attrEdit, "TextFinished", "EditTechniqueLodDistance"); + } + } + else + { + for (uint i = 0; i < editMaterial.numTechniques; ++i) + { + TechniqueEntry entry = editMaterial.techniqueEntries[i]; + + LineEdit@ nameEdit = materialWindow.GetChild("TechniqueNameEdit" + String(i), true); + if (nameEdit is null) + continue; + + nameEdit.text = entry.technique !is null ? entry.technique.name : ""; + } + } +} + +void RefreshMaterialTextures(bool fullUpdate = true) +{ + if (fullUpdate) + { + ListView@ list = materialWindow.GetChild("TextureList", true); + list.RemoveAllItems(); + + for (uint i = 0; i < MAX_MATERIAL_TEXTURE_UNITS; ++i) + { + String tuName = GetTextureUnitName(TextureUnit(i)); + tuName[0] = ToUpper(tuName[0]); + + UIElement@ parent = CreateAttributeEditorParentWithSeparatedLabel(list, "Unit " + i + " " + tuName, i, 0, false); + + UIElement@ container = UIElement(); + container.SetLayout(LM_HORIZONTAL, 4, IntRect(10, 0, 4, 0)); + container.SetFixedHeight(ATTR_HEIGHT); + parent.AddChild(container); + + LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, i, 0); + nameEdit.name = "TextureNameEdit" + String(i); + + Button@ pickButton = CreateResourcePickerButton(container, null, i, 0, "smallButtonPick"); + SubscribeToEvent(pickButton, "Released", "PickMaterialTexture"); + Button@ openButton = CreateResourcePickerButton(container, null, i, 0, "smallButtonOpen"); + SubscribeToEvent(openButton, "Released", "OpenResource"); + + if (editMaterial !is null) + { + Texture@ texture = editMaterial.textures[i]; + if (texture !is null) + nameEdit.text = texture.name; + } + + SubscribeToEvent(nameEdit, "TextFinished", "EditMaterialTexture"); + } + } + else + { + for (uint i = 0; i < MAX_MATERIAL_TEXTURE_UNITS; ++i) + { + LineEdit@ nameEdit = materialWindow.GetChild("TextureNameEdit" + String(i), true); + if (nameEdit is null) + continue; + + String textureName; + if (editMaterial !is null) + { + Texture@ texture = editMaterial.textures[i]; + if (texture !is null) + textureName = texture.name; + } + + nameEdit.text = textureName; + } + } +} + +void RefreshMaterialShaderParameters() +{ + ListView@ list = materialWindow.GetChild("ShaderParameterList", true); + list.RemoveAllItems(); + if (editMaterial is null) + return; + + Array@ parameterNames = editMaterial.shaderParameterNames; + + for (uint i = 0; i < parameterNames.length; ++i) + { + VariantType type = editMaterial.shaderParameters[parameterNames[i]].type; + Variant value = editMaterial.shaderParameters[parameterNames[i]]; + UIElement@ parent = CreateAttributeEditorParent(list, parameterNames[i], 0, 0); + uint numCoords = 1; + if (type >= VAR_VECTOR2 && type <= VAR_VECTOR4) + numCoords = type - VAR_FLOAT + 1; + + Array coordValues = value.ToString().Split(' '); + + for (uint j = 0; j < numCoords; ++j) + { + LineEdit@ attrEdit = CreateAttributeLineEdit(parent, null, 0, 0); + attrEdit.vars["Coordinate"] = j; + attrEdit.vars["Name"] = parameterNames[i]; + attrEdit.text = coordValues[j]; + + CreateDragSlider(attrEdit); + + SubscribeToEvent(attrEdit, "TextChanged", "EditShaderParameter"); + SubscribeToEvent(attrEdit, "TextFinished", "EditShaderParameter"); + } + } +} + +void RefreshMaterialMiscParameters() +{ + if (editMaterial is null) + return; + + inMaterialRefresh = true; + + BiasParameters bias = editMaterial.depthBias; + LineEdit@ attrEdit = materialWindow.GetChild("ConstantBiasEdit", true); + attrEdit.text = String(bias.constantBias); + attrEdit = materialWindow.GetChild("SlopeBiasEdit", true); + attrEdit.text = String(bias.slopeScaledBias); + attrEdit = materialWindow.GetChild("RenderOrderEdit", true); + attrEdit.text = String(uint(editMaterial.renderOrder)); + attrEdit = materialWindow.GetChild("VSDefinesEdit", true); + attrEdit.text = editMaterial.vertexShaderDefines; + attrEdit = materialWindow.GetChild("PSDefinesEdit", true); + attrEdit.text = editMaterial.pixelShaderDefines; + + DropDownList@ attrList = materialWindow.GetChild("CullModeEdit", true); + attrList.selection = editMaterial.cullMode; + attrList = materialWindow.GetChild("ShadowCullModeEdit", true); + attrList.selection = editMaterial.shadowCullMode; + attrList = materialWindow.GetChild("FillModeEdit", true); + attrList.selection = editMaterial.fillMode; + + CheckBox@ attrCheckBox = materialWindow.GetChild("OcclusionEdit", true); + attrCheckBox.checked = editMaterial.occlusion; + attrCheckBox = materialWindow.GetChild("AlphaToCoverageEdit", true); + attrCheckBox.checked = editMaterial.alphaToCoverage; + attrCheckBox = materialWindow.GetChild("LineAntiAliasEdit", true); + attrCheckBox.checked = editMaterial.lineAntiAlias; + + inMaterialRefresh = false; +} + +void RotateMaterialPreview(StringHash eventType, VariantMap& eventData) +{ + int elemX = eventData["ElementX"].GetInt(); + int elemY = eventData["ElementY"].GetInt(); + + if (materialPreview.height > 0 && materialPreview.width > 0) + { + float yaw = ((materialPreview.height / 2) - elemY) * (90.0 / materialPreview.height); + float pitch = ((materialPreview.width / 2) - elemX) * (90.0 / materialPreview.width); + + previewModelNode.rotation = previewModelNode.rotation.Slerp(Quaternion(yaw, pitch, 0), 0.1); + materialPreview.QueueUpdate(); + } +} + +void EditMaterialName(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ nameEdit = eventData["Element"].GetPtr(); + String newMaterialName = nameEdit.text.Trimmed(); + if (!newMaterialName.empty) + { + Material@ newMaterial = cache.GetResource("Material", newMaterialName); + if (newMaterial !is null) + EditMaterial(newMaterial); + } +} + +void PickEditMaterial() +{ + @resourcePicker = GetResourcePicker(StringHash("Material")); + if (resourcePicker is null) + return; + + 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", "PickEditMaterialDone"); +} + +void PickEditMaterialDone(StringHash eventType, VariantMap& eventData) +{ + StoreResourcePickerPath(); + CloseFileSelector(); + + if (!eventData["OK"].GetBool()) + { + @resourcePicker = null; + return; + } + + String resourceName = eventData["FileName"].GetString(); + Resource@ res = GetPickedResource(resourceName); + + if (res !is null) + EditMaterial(cast(res)); + + @resourcePicker = null; +} + +void NewMaterial() +{ + EditMaterial(Material()); +} + +void RevertMaterial() +{ + if (editMaterial is null) + return; + + BeginMaterialEdit(); + cache.ReloadResource(editMaterial); + EndMaterialEdit(); + + RefreshMaterialEditor(); +} + +void SaveMaterial() +{ + if (editMaterial is null || editMaterial.name.empty) + return; + + String fullName = cache.GetResourceFileName(editMaterial.name); + if (fullName.empty) + return; + + MakeBackup(fullName); + File saveFile(fullName, FILE_WRITE); + bool success; + if (GetExtension(fullName) == ".json") + { + JSONFile json; + editMaterial.Save(json.root); + success = json.Save(saveFile); + } + else + success = editMaterial.Save(saveFile); + RemoveBackup(success, fullName); +} + +void SaveMaterialAs() +{ + if (editMaterial is null) + return; + + @resourcePicker = GetResourcePicker(StringHash("Material")); + if (resourcePicker is null) + return; + + String lastPath = resourcePicker.lastPath; + if (lastPath.empty) + lastPath = sceneResourcePath; + CreateFileSelector("Save material as", "Save", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "SaveMaterialAsDone"); +} + +void SaveMaterialAsDone(StringHash eventType, VariantMap& eventData) +{ + StoreResourcePickerPath(); + CloseFileSelector(); + @resourcePicker = null; + + if (editMaterial is null) + return; + + if (!eventData["OK"].GetBool()) + { + @resourcePicker = null; + return; + } + + String fullName = eventData["FileName"].GetString(); + + // Add default extension for saving if not specified + String filter = eventData["Filter"].GetString(); + if (GetExtension(fullName).empty && filter != "*.*") + fullName = fullName + filter.Substring(1); + + MakeBackup(fullName); + File saveFile(fullName, FILE_WRITE); + bool success; + if (GetExtension(fullName) == ".json") + { + JSONFile json; + editMaterial.Save(json.root); + success = json.Save(saveFile); + } + else + success = editMaterial.Save(saveFile); + + if (success) + { + saveFile.Close(); + RemoveBackup(true, fullName); + + // Load the new resource to update the name in the editor + Material@ newMat = cache.GetResource("Material", GetResourceNameFromFullName(fullName)); + if (newMat !is null) + EditMaterial(newMat); + } +} + +void EditModelPreviewChange(StringHash eventType, VariantMap& eventData) +{ + if (materialPreview is null) + return; + + previewModelNode.scale = Vector3(1.0, 1.0, 1.0); + + DropDownList@ element = eventData["Element"].GetPtr(); + + switch (element.selection) + { + case 0: + previewModel.model = cache.GetResource("Model", "Models/Box.mdl"); + break; + case 1: + previewModel.model = cache.GetResource("Model", "Models/Sphere.mdl"); + break; + case 2: + previewModel.model = cache.GetResource("Model", "Models/Plane.mdl"); + break; + case 3: + previewModel.model = cache.GetResource("Model", "Models/Cylinder.mdl"); + previewModelNode.scale = Vector3(0.8, 0.8, 0.8); + break; + case 4: + previewModel.model = cache.GetResource("Model", "Models/Cone.mdl"); + break; + case 5: + previewModel.model = cache.GetResource("Model", "Models/TeaPot.mdl"); + break; + } + + materialPreview.QueueUpdate(); + +} + +void EditShaderParameter(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null) + return; + + LineEdit@ attrEdit = eventData["Element"].GetPtr(); + uint coordinate = attrEdit.vars["Coordinate"].GetUInt(); + + String name = attrEdit.vars["Name"].GetString(); + + Variant oldValue = editMaterial.shaderParameters[name]; + Array coordValues = oldValue.ToString().Split(' '); + if (oldValue.type != VAR_BOOL) + coordValues[coordinate] = String(attrEdit.text.ToFloat()); + else + coordValues[coordinate] = attrEdit.text; + + String valueString; + for (uint i = 0; i < coordValues.length; ++i) + { + valueString += coordValues[i]; + valueString += " "; + } + + Variant newValue; + newValue.FromString(oldValue.type, valueString); + + BeginMaterialEdit(); + editMaterial.shaderParameters[name] = newValue; + EndMaterialEdit(); +} + +void CreateShaderParameter(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null) + return; + + LineEdit@ nameEdit = materialWindow.GetChild("ParameterNameEdit", true); + String newName = nameEdit.text.Trimmed(); + if (newName.empty) + return; + + DropDownList@ dropDown = eventData["Element"].GetPtr(); + Variant newValue; + + switch (dropDown.selection) + { + case 0: + newValue = float(0); + break; + case 1: + newValue = Vector2(0, 0); + break; + case 2: + newValue = Vector3(0, 0, 0); + break; + case 3: + newValue = Vector4(0, 0, 0, 0); + break; + case 4: + newValue = int(0); + break; + case 5: + newValue = false; + break; + } + + BeginMaterialEdit(); + editMaterial.shaderParameters[newName] = newValue; + EndMaterialEdit(); + + RefreshMaterialShaderParameters(); +} + +void DeleteShaderParameter() +{ + if (editMaterial is null) + return; + + LineEdit@ nameEdit = materialWindow.GetChild("ParameterNameEdit", true); + String name = nameEdit.text.Trimmed(); + if (name.empty) + return; + + BeginMaterialEdit(); + editMaterial.RemoveShaderParameter(name); + EndMaterialEdit(); + + RefreshMaterialShaderParameters(); +} + +void PickMaterialTexture(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null) + return; + + UIElement@ button = eventData["Element"].GetPtr(); + resourcePickIndex = button.vars["Index"].GetUInt(); + + @resourcePicker = GetResourcePicker(StringHash("Texture2D")); + if (resourcePicker is null) + return; + + 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", "PickMaterialTextureDone"); +} + +void PickMaterialTextureDone(StringHash eventType, VariantMap& eventData) +{ + StoreResourcePickerPath(); + CloseFileSelector(); + + if (!eventData["OK"].GetBool()) + { + @resourcePicker = null; + return; + } + + String resourceName = eventData["FileName"].GetString(); + Resource@ res = GetPickedResource(resourceName); + + if (res !is null && editMaterial !is null) + { + BeginMaterialEdit(); + editMaterial.textures[resourcePickIndex] = res; + EndMaterialEdit(); + + RefreshMaterialTextures(false); + } + + @resourcePicker = null; +} + +void EditMaterialTexture(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null) + return; + + LineEdit@ attrEdit = eventData["Element"].GetPtr(); + String textureName = attrEdit.text.Trimmed(); + uint index = attrEdit.vars["Index"].GetUInt(); + + BeginMaterialEdit(); + + if (!textureName.empty) + { + Texture@ texture = cache.GetResource(GetExtension(textureName) == ".xml" ? "TextureCube" : "Texture2D", textureName); + editMaterial.textures[index] = texture; + } + else + editMaterial.textures[index] = null; + + EndMaterialEdit(); +} + +void NewTechnique() +{ + if (editMaterial is null) + return; + + BeginMaterialEdit(); + editMaterial.numTechniques = editMaterial.numTechniques + 1; + EndMaterialEdit(); + + RefreshMaterialTechniques(); +} + +void DeleteTechnique() +{ + if (editMaterial is null || editMaterial.numTechniques < 2) + return; + + BeginMaterialEdit(); + editMaterial.numTechniques = editMaterial.numTechniques - 1; + EndMaterialEdit(); + + RefreshMaterialTechniques(); +} + +void PickMaterialTechnique(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null) + return; + + UIElement@ button = eventData["Element"].GetPtr(); + resourcePickIndex = button.vars["Index"].GetUInt(); + + @resourcePicker = GetResourcePicker(StringHash("Technique")); + if (resourcePicker is null) + return; + + 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", "PickMaterialTechniqueDone"); +} + +void PickMaterialTechniqueDone(StringHash eventType, VariantMap& eventData) +{ + StoreResourcePickerPath(); + CloseFileSelector(); + + if (!eventData["OK"].GetBool()) + { + @resourcePicker = null; + return; + } + + String resourceName = eventData["FileName"].GetString(); + Resource@ res = GetPickedResource(resourceName); + + if (res !is null && editMaterial !is null) + { + BeginMaterialEdit(); + TechniqueEntry entry = editMaterial.techniqueEntries[resourcePickIndex]; + editMaterial.SetTechnique(resourcePickIndex, res, entry.qualityLevel, entry.lodDistance); + EndMaterialEdit(); + + RefreshMaterialTechniques(false); + } + + @resourcePicker = null; +} + +void EditMaterialTechnique(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null) + return; + + LineEdit@ attrEdit = eventData["Element"].GetPtr(); + String techniqueName = attrEdit.text.Trimmed(); + uint index = attrEdit.vars["Index"].GetUInt(); + + BeginMaterialEdit(); + + Technique@ newTech; + if (!techniqueName.empty) + newTech = cache.GetResource("Technique", techniqueName); + + TechniqueEntry entry = editMaterial.techniqueEntries[index]; + editMaterial.SetTechnique(index, newTech, entry.qualityLevel, entry.lodDistance); + + EndMaterialEdit(); +} + +void EditTechniqueQuality(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null) + return; + + LineEdit@ attrEdit = eventData["Element"].GetPtr(); + uint newQualityLevel = attrEdit.text.ToUInt(); + uint index = attrEdit.vars["Index"].GetUInt(); + + BeginMaterialEdit(); + TechniqueEntry entry = editMaterial.techniqueEntries[index]; + editMaterial.SetTechnique(index, entry.technique, newQualityLevel, entry.lodDistance); + EndMaterialEdit(); +} + +void EditTechniqueLodDistance(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null) + return; + + LineEdit@ attrEdit = eventData["Element"].GetPtr(); + float newLodDistance = attrEdit.text.ToFloat(); + uint index = attrEdit.vars["Index"].GetUInt(); + + BeginMaterialEdit(); + TechniqueEntry entry = editMaterial.techniqueEntries[index]; + editMaterial.SetTechnique(index, entry.technique, entry.qualityLevel, newLodDistance); + EndMaterialEdit(); +} + +void SortTechniques() +{ + if (editMaterial is null) + return; + + BeginMaterialEdit(); + editMaterial.SortTechniques(); + EndMaterialEdit(); + + RefreshMaterialTechniques(); +} + +void EditConstantBias(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + LineEdit@ attrEdit = eventData["Element"].GetPtr(); + BiasParameters bias = editMaterial.depthBias; + bias.constantBias = attrEdit.text.ToFloat(); + editMaterial.depthBias = bias; + + EndMaterialEdit(); +} + +void EditSlopeBias(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + LineEdit@ attrEdit = eventData["Element"].GetPtr(); + BiasParameters bias = editMaterial.depthBias; + bias.slopeScaledBias = attrEdit.text.ToFloat(); + editMaterial.depthBias = bias; + + EndMaterialEdit(); +} + +void EditRenderOrder(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + LineEdit@ attrEdit = eventData["Element"].GetPtr(); + editMaterial.renderOrder = attrEdit.text.ToUInt(); + + EndMaterialEdit(); +} + +void EditCullMode(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + DropDownList@ attrEdit = eventData["Element"].GetPtr(); + editMaterial.cullMode = CullMode(attrEdit.selection); + + EndMaterialEdit(); +} + +void EditShadowCullMode(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + DropDownList@ attrEdit = eventData["Element"].GetPtr(); + editMaterial.shadowCullMode = CullMode(attrEdit.selection); + + EndMaterialEdit(); +} + +void EditFillMode(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + DropDownList@ attrEdit = eventData["Element"].GetPtr(); + editMaterial.fillMode = FillMode(attrEdit.selection); + + EndMaterialEdit(); +} + +void EditOcclusion(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + CheckBox@ attrEdit = eventData["Element"].GetPtr(); + editMaterial.occlusion = attrEdit.checked; + + EndMaterialEdit(); +} + +void EditAlphaToCoverage(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + CheckBox@ attrEdit = eventData["Element"].GetPtr(); + editMaterial.alphaToCoverage = attrEdit.checked; + + EndMaterialEdit(); +} + +void EditLineAntiAlias(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + CheckBox@ attrEdit = eventData["Element"].GetPtr(); + editMaterial.lineAntiAlias = attrEdit.checked; + + EndMaterialEdit(); +} + +void EditVSDefines(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + LineEdit@ attrEdit = eventData["Element"].GetPtr(); + editMaterial.vertexShaderDefines = attrEdit.text.Trimmed(); + + EndMaterialEdit(); +} + +void EditPSDefines(StringHash eventType, VariantMap& eventData) +{ + if (editMaterial is null || inMaterialRefresh) + return; + + BeginMaterialEdit(); + + LineEdit@ attrEdit = eventData["Element"].GetPtr(); + editMaterial.pixelShaderDefines = attrEdit.text.Trimmed(); + + EndMaterialEdit(); +} + +void BeginMaterialEdit() +{ + if (editMaterial is null) + return; + + oldMaterialState = XMLFile(); + XMLElement materialElem = oldMaterialState.CreateRoot("material"); + editMaterial.Save(materialElem); +} + +void EndMaterialEdit() +{ + if (editMaterial is null) + return; + if (!dragEditAttribute) + { + EditMaterialAction@ action = EditMaterialAction(); + action.Define(editMaterial, oldMaterialState); + SaveEditAction(action); + } + + materialPreview.QueueUpdate(); +} diff --git a/bin/Data/Scripts/Editor/EditorParticleEffect.as b/bin/Data/Scripts/Editor/EditorParticleEffect.as new file mode 100644 index 0000000..8ac3c41 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorParticleEffect.as @@ -0,0 +1,1777 @@ +// Urho3D material editor + +Window@ particleEffectWindow; +ParticleEffect@ editParticleEffect; +XMLFile@ oldParticleEffectState; +bool inParticleEffectRefresh = false; +View3D@ particleEffectPreview; +Camera@ particlePreviewCamera; +Scene@ particlePreviewScene; +Node@ particleEffectPreviewNode; +Node@ particleEffectPreviewGizmoNode; +Node@ particleEffectPreviewGridNode; +CustomGeometry@ particleEffectPreviewGrid; +Node@ particlePreviewCameraNode; +Node@ particlePreviewLightNode; +Light@ particlePreviewLight; +ParticleEmitter@ particleEffectEmitter; +float particleResetTimer; +bool showParticlePreviewAxes = true; +Vector3 particleViewCamDir; +Vector3 particleViewCamRot; +float particleViewCamDist; + +float gizmoOffset = 0.1f; +float gizmoOffsetX; +float gizmoOffsetY; + +void CreateParticleEffectEditor() +{ + if (particleEffectWindow !is null) + return; + + particleEffectWindow = LoadEditorUI("UI/EditorParticleEffectWindow.xml"); + ui.root.AddChild(particleEffectWindow); + particleEffectWindow.opacity = uiMaxOpacity; + + InitParticleEffectPreview(); + InitParticleEffectBasicAttributes(); + RefreshParticleEffectEditor(); + + int width = Min(ui.root.width - 60, 800); + int height = Min(ui.root.height - 60, 600); + particleEffectWindow.SetSize(width, height); + CenterDialog(particleEffectWindow); + + HideParticleEffectEditor(); + + SubscribeToEvent(particleEffectWindow.GetChild("NewButton", true), "Released", "NewParticleEffect"); + SubscribeToEvent(particleEffectWindow.GetChild("RevertButton", true), "Released", "RevertParticleEffect"); + SubscribeToEvent(particleEffectWindow.GetChild("SaveButton", true), "Released", "SaveParticleEffect"); + SubscribeToEvent(particleEffectWindow.GetChild("SaveAsButton", true), "Released", "SaveParticleEffectAs"); + SubscribeToEvent(particleEffectWindow.GetChild("CloseButton", true), "Released", "HideParticleEffectEditor"); + SubscribeToEvent(particleEffectWindow.GetChild("NewColorFrame", true), "Released", "EditParticleEffectColorFrameNew"); + SubscribeToEvent(particleEffectWindow.GetChild("RemoveColorFrame", true), "Released", "EditParticleEffectColorFrameRemove"); + SubscribeToEvent(particleEffectWindow.GetChild("ColorFrameSort", true), "Released", "EditParticleEffectColorFrameSort"); + SubscribeToEvent(particleEffectWindow.GetChild("NewTextureFrame", true), "Released", "EditParticleEffectTextureFrameNew"); + SubscribeToEvent(particleEffectWindow.GetChild("RemoveTextureFrame", true), "Released", "EditParticleEffectTextureFrameRemove"); + SubscribeToEvent(particleEffectWindow.GetChild("TextureFrameSort", true), "Released", "EditParticleEffectTextureFrameSort"); + SubscribeToEvent(particleEffectWindow.GetChild("ConstantForceX", true), "TextChanged", "EditParticleEffectConstantForce"); + SubscribeToEvent(particleEffectWindow.GetChild("ConstantForceY", true), "TextChanged", "EditParticleEffectConstantForce"); + SubscribeToEvent(particleEffectWindow.GetChild("ConstantForceZ", true), "TextChanged", "EditParticleEffectConstantForce"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMinX", true), "TextChanged", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMinY", true), "TextChanged", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMinZ", true), "TextChanged", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMaxX", true), "TextChanged", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMaxY", true), "TextChanged", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMaxZ", true), "TextChanged", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DampingForce", true), "TextChanged", "EditParticleEffectDampingForce"); + SubscribeToEvent(particleEffectWindow.GetChild("ActiveTime", true), "TextChanged", "EditParticleEffectActiveTime"); + SubscribeToEvent(particleEffectWindow.GetChild("InactiveTime", true), "TextChanged", "EditParticleEffectInactiveTime"); + SubscribeToEvent(particleEffectWindow.GetChild("ParticleSizeMinX", true), "TextChanged", "EditParticleEffectParticleSize"); + SubscribeToEvent(particleEffectWindow.GetChild("ParticleSizeMinY", true), "TextChanged", "EditParticleEffectParticleSize"); + SubscribeToEvent(particleEffectWindow.GetChild("ParticleSizeMaxX", true), "TextChanged", "EditParticleEffectParticleSize"); + SubscribeToEvent(particleEffectWindow.GetChild("ParticleSizeMaxY", true), "TextChanged", "EditParticleEffectParticleSize"); + SubscribeToEvent(particleEffectWindow.GetChild("TimeToLiveMin", true), "TextChanged", "EditParticleEffectTimeToLive"); + SubscribeToEvent(particleEffectWindow.GetChild("TimeToLiveMax", true), "TextChanged", "EditParticleEffectTimeToLive"); + SubscribeToEvent(particleEffectWindow.GetChild("VelocityMin", true), "TextChanged", "EditParticleEffectVelocity"); + SubscribeToEvent(particleEffectWindow.GetChild("VelocityMax", true), "TextChanged", "EditParticleEffectVelocity"); + SubscribeToEvent(particleEffectWindow.GetChild("RotationMin", true), "TextChanged", "EditParticleEffectRotation"); + SubscribeToEvent(particleEffectWindow.GetChild("RotationMax", true), "TextChanged", "EditParticleEffectRotation"); + SubscribeToEvent(particleEffectWindow.GetChild("RotationSpeedMin", true), "TextChanged", "EditParticleEffectRotationSpeed"); + SubscribeToEvent(particleEffectWindow.GetChild("RotationSpeedMax", true), "TextChanged", "EditParticleEffectRotationSpeed"); + SubscribeToEvent(particleEffectWindow.GetChild("SizeAdd", true), "TextChanged", "EditParticleEffectSizeAdd"); + SubscribeToEvent(particleEffectWindow.GetChild("SizeMultiply", true), "TextChanged", "EditParticleEffectSizeMultiply"); + SubscribeToEvent(particleEffectWindow.GetChild("AnimationLodBias", true), "TextChanged", "EditParticleEffectAnimationLodBias"); + SubscribeToEvent(particleEffectWindow.GetChild("NumParticles", true), "TextChanged", "EditParticleEffectNumParticles"); + SubscribeToEvent(particleEffectWindow.GetChild("EmitterSizeX", true), "TextChanged", "EditParticleEffectEmitterSize"); + SubscribeToEvent(particleEffectWindow.GetChild("EmitterSizeY", true), "TextChanged", "EditParticleEffectEmitterSize"); + SubscribeToEvent(particleEffectWindow.GetChild("EmitterSizeZ", true), "TextChanged", "EditParticleEffectEmitterSize"); + SubscribeToEvent(particleEffectWindow.GetChild("EmissionRateMin", true), "TextChanged", "EditParticleEffectEmissionRate"); + SubscribeToEvent(particleEffectWindow.GetChild("EmissionRateMax", true), "TextChanged", "EditParticleEffectEmissionRate"); + + SubscribeToEvent(particleEffectWindow.GetChild("ConstantForceX", true), "TextFinished", "EditParticleEffectConstantForce"); + SubscribeToEvent(particleEffectWindow.GetChild("ConstantForceY", true), "TextFinished", "EditParticleEffectConstantForce"); + SubscribeToEvent(particleEffectWindow.GetChild("ConstantForceZ", true), "TextFinished", "EditParticleEffectConstantForce"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMinX", true), "TextFinished", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMinY", true), "TextFinished", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMinZ", true), "TextFinished", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMaxX", true), "TextFinished", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMaxY", true), "TextFinished", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DirectionMaxZ", true), "TextFinished", "EditParticleEffectDirection"); + SubscribeToEvent(particleEffectWindow.GetChild("DampingForce", true), "TextFinished", "EditParticleEffectDampingForce"); + SubscribeToEvent(particleEffectWindow.GetChild("ActiveTime", true), "TextFinished", "EditParticleEffectActiveTime"); + SubscribeToEvent(particleEffectWindow.GetChild("InactiveTime", true), "TextFinished", "EditParticleEffectInactiveTime"); + SubscribeToEvent(particleEffectWindow.GetChild("ParticleSizeMinX", true), "TextFinished", "EditParticleEffectParticleSize"); + SubscribeToEvent(particleEffectWindow.GetChild("ParticleSizeMinY", true), "TextFinished", "EditParticleEffectParticleSize"); + SubscribeToEvent(particleEffectWindow.GetChild("ParticleSizeMaxX", true), "TextFinished", "EditParticleEffectParticleSize"); + SubscribeToEvent(particleEffectWindow.GetChild("ParticleSizeMaxY", true), "TextFinished", "EditParticleEffectParticleSize"); + SubscribeToEvent(particleEffectWindow.GetChild("TimeToLiveMin", true), "TextFinished", "EditParticleEffectTimeToLive"); + SubscribeToEvent(particleEffectWindow.GetChild("TimeToLiveMax", true), "TextFinished", "EditParticleEffectTimeToLive"); + SubscribeToEvent(particleEffectWindow.GetChild("VelocityMin", true), "TextFinished", "EditParticleEffectVelocity"); + SubscribeToEvent(particleEffectWindow.GetChild("VelocityMax", true), "TextFinished", "EditParticleEffectVelocity"); + SubscribeToEvent(particleEffectWindow.GetChild("RotationMin", true), "TextFinished", "EditParticleEffectRotation"); + SubscribeToEvent(particleEffectWindow.GetChild("RotationMax", true), "TextFinished", "EditParticleEffectRotation"); + SubscribeToEvent(particleEffectWindow.GetChild("RotationSpeedMin", true), "TextFinished", "EditParticleEffectRotationSpeed"); + SubscribeToEvent(particleEffectWindow.GetChild("RotationSpeedMax", true), "TextFinished", "EditParticleEffectRotationSpeed"); + SubscribeToEvent(particleEffectWindow.GetChild("SizeAdd", true), "TextFinished", "EditParticleEffectSizeAdd"); + SubscribeToEvent(particleEffectWindow.GetChild("SizeMultiply", true), "TextFinished", "EditParticleEffectSizeMultiply"); + SubscribeToEvent(particleEffectWindow.GetChild("AnimationLodBias", true), "TextFinished", "EditParticleEffectAnimationLodBias"); + SubscribeToEvent(particleEffectWindow.GetChild("NumParticles", true), "TextFinished", "EditParticleEffectNumParticles"); + SubscribeToEvent(particleEffectWindow.GetChild("EmitterSizeX", true), "TextFinished", "EditParticleEffectEmitterSize"); + SubscribeToEvent(particleEffectWindow.GetChild("EmitterSizeY", true), "TextFinished", "EditParticleEffectEmitterSize"); + SubscribeToEvent(particleEffectWindow.GetChild("EmitterSizeZ", true), "TextFinished", "EditParticleEffectEmitterSize"); + SubscribeToEvent(particleEffectWindow.GetChild("EmissionRateMin", true), "TextFinished", "EditParticleEffectEmissionRate"); + SubscribeToEvent(particleEffectWindow.GetChild("EmissionRateMax", true), "TextFinished", "EditParticleEffectEmissionRate"); + + SubscribeToEvent(particleEffectWindow.GetChild("EmitterShape", true), "ItemSelected", "EditParticleEffectEmitterShape"); + SubscribeToEvent(particleEffectWindow.GetChild("Scaled", true), "Toggled", "EditParticleEffectScaled"); + SubscribeToEvent(particleEffectWindow.GetChild("Sorted", true), "Toggled", "EditParticleEffectSorted"); + SubscribeToEvent(particleEffectWindow.GetChild("Relative", true), "Toggled", "EditParticleEffectRelative"); + SubscribeToEvent(particleEffectWindow.GetChild("FixedScreenSize", true), "Toggled", "EditParticleEffectFixedScreenSize"); + SubscribeToEvent(particleEffectWindow.GetChild("FaceCameraMode", true), "ItemSelected", "EditParticleEffectFaceCameraMode"); + + SubscribeToEvent(particleEffectWindow.GetChild("ResetViewport", true), "Released", "ParticleEffectResetViewport"); + SubscribeToEvent(particleEffectWindow.GetChild("ShowGrid", true), "Toggled", "ParticleEffectShowGrid"); +} + +void SetGizmoPosition() +{ + Vector3 screenPos = Vector3(gizmoOffsetX, gizmoOffsetY, 25.0); + Vector3 newPos = particlePreviewCamera.ScreenToWorldPoint(screenPos); + particleEffectPreviewGizmoNode.position = newPos; +} + +void ResetCameraTransformation() +{ + particlePreviewCameraNode.position = Vector3(0, 0, -5); + particlePreviewCameraNode.LookAt(Vector3(0, 0, 0)); + particleViewCamDir = -particlePreviewCameraNode.position; + + // Manually set initial rotation because eulerAngle always return 0 on first frame + particleViewCamRot = Vector3(0.0, 180.0, 0.0); + + particleViewCamDist = particleViewCamDir.length; + particleViewCamDir.Normalize(); +} + +void ParticleEffectResetViewport(StringHash eventType, VariantMap& eventData) +{ + ResetCameraTransformation(); + SetGizmoPosition(); + particleEffectPreview.QueueUpdate(); +} + +void ParticleEffectShowGrid(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ element = eventData["Element"].GetPtr(); + showParticlePreviewAxes = element.checked; + particleEffectPreviewGridNode.enabled = showParticlePreviewAxes; + particleEffectPreview.QueueUpdate(); +} + +void EditParticleEffectColorFrameNew(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + uint num = editParticleEffect.numColorFrames; + editParticleEffect.numColorFrames = num + 1; + RefreshParticleEffectColorFrames(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectTextureFrameNew(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + uint num = editParticleEffect.numTextureFrames; + editParticleEffect.numTextureFrames = num + 1; + RefreshParticleEffectTextureFrames(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectColorFrameRemove(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + ListView@ lv = particleEffectWindow.GetChild("ColorFrameListView", true); + if (lv !is null && lv.selection != M_MAX_UNSIGNED) + { + BeginParticleEffectEdit(); + + editParticleEffect.RemoveColorFrame(lv.selection); + RefreshParticleEffectColorFrames(); + + EndParticleEffectEdit(); + } + +} + +void EditParticleEffectTextureFrameRemove(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + ListView@ lv = particleEffectWindow.GetChild("TextureFrameListView", true); + if (lv !is null && lv.selection != M_MAX_UNSIGNED) + { + BeginParticleEffectEdit(); + + editParticleEffect.RemoveTextureFrame(lv.selection); + RefreshParticleEffectTextureFrames(); + + EndParticleEffectEdit(); + } +} + +void EditParticleEffectColorFrameSort(StringHash eventType, VariantMap& eventData) +{ + RefreshParticleEffectColorFrames(); +} + +void EditParticleEffectTextureFrameSort(StringHash eventType, VariantMap& eventData) +{ + RefreshParticleEffectTextureFrames(); +} + +void InitParticleEffectBasicAttributes() +{ + CreateDragSlider(cast(particleEffectWindow.GetChild("ConstantForceX", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("ConstantForceY", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("ConstantForceZ", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("DirectionMinX", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("DirectionMinY", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("DirectionMinZ", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("DirectionMaxX", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("DirectionMaxY", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("DirectionMaxZ", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("DampingForce", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("ActiveTime", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("InactiveTime", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("ParticleSizeMinX", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("ParticleSizeMinY", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("ParticleSizeMaxX", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("ParticleSizeMaxY", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("TimeToLiveMin", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("TimeToLiveMax", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("VelocityMin", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("VelocityMax", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("RotationMin", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("RotationMax", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("RotationSpeedMin", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("RotationSpeedMax", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("SizeAdd", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("SizeMultiply", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("AnimationLodBias", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("NumParticles", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("EmitterSizeX", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("EmitterSizeY", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("EmitterSizeZ", true))); + + CreateDragSlider(cast(particleEffectWindow.GetChild("EmissionRateMin", true))); + CreateDragSlider(cast(particleEffectWindow.GetChild("EmissionRateMax", true))); + +} + +void EditParticleEffectConstantForce(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + Vector3 v = editParticleEffect.constantForce; + + if (element.name == "ConstantForceX") + editParticleEffect.constantForce = Vector3(element.text.ToFloat(), v.y, v.z); + + if (element.name == "ConstantForceY") + editParticleEffect.constantForce = Vector3(v.x, element.text.ToFloat(), v.z); + + if (element.name == "ConstantForceZ") + editParticleEffect.constantForce = Vector3(v.x, v.y, element.text.ToFloat()); + + EndParticleEffectEdit(); +} + +void EditParticleEffectDirection(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + Vector3 vMin = editParticleEffect.minDirection; + Vector3 vMax = editParticleEffect.maxDirection; + + if (element.name == "DirectionMinX") + editParticleEffect.minDirection = Vector3(element.text.ToFloat(), vMin.y, vMin.z); + + if (element.name == "DirectionMinY") + editParticleEffect.minDirection = Vector3(vMin.x, element.text.ToFloat(), vMin.z); + + if (element.name == "DirectionMinZ") + editParticleEffect.minDirection = Vector3(vMin.x, vMin.y, element.text.ToFloat()); + + if (element.name == "DirectionMaxX") + editParticleEffect.maxDirection = Vector3(element.text.ToFloat(), vMax.y, vMax.z); + + if (element.name == "DirectionMaxY") + editParticleEffect.maxDirection = Vector3(vMax.x, element.text.ToFloat(), vMax.z); + + if (element.name == "DirectionMaxZ") + editParticleEffect.maxDirection = Vector3(vMax.x, vMax.y, element.text.ToFloat()); + + EndParticleEffectEdit(); +} + +void EditParticleEffectDampingForce(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + editParticleEffect.dampingForce = element.text.ToFloat(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectActiveTime(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (particleEffectEmitter is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + editParticleEffect.activeTime = element.text.ToFloat(); + particleEffectEmitter.Reset(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectInactiveTime(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (particleEffectEmitter is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + editParticleEffect.inactiveTime = element.text.ToFloat(); + particleEffectEmitter.Reset(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectParticleSize(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + Vector2 vMin = editParticleEffect.minParticleSize; + Vector2 vMax = editParticleEffect.maxParticleSize; + + if (element.name == "ParticleSizeMinX") + editParticleEffect.minParticleSize = Vector2(element.text.ToFloat(), vMin.y); + + if (element.name == "ParticleSizeMinY") + editParticleEffect.minParticleSize = Vector2(vMin.x, element.text.ToFloat()); + + if (element.name == "ParticleSizeMaxX") + editParticleEffect.maxParticleSize = Vector2(element.text.ToFloat(), vMax.y); + + if (element.name == "ParticleSizeMaxY") + editParticleEffect.maxParticleSize = Vector2(vMax.x, element.text.ToFloat()); + + EndParticleEffectEdit(); +} + +void EditParticleEffectTimeToLive(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + float vMin = editParticleEffect.minTimeToLive; + float vMax = editParticleEffect.maxTimeToLive; + + if (element.name == "TimeToLiveMin") + editParticleEffect.minTimeToLive = element.text.ToFloat(); + + if (element.name == "TimeToLiveMax") + editParticleEffect.maxTimeToLive = element.text.ToFloat(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectVelocity(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + float vMin = editParticleEffect.minVelocity; + float vMax = editParticleEffect.maxVelocity; + + if (element.name == "VelocityMin") + editParticleEffect.minVelocity = element.text.ToFloat(); + + if (element.name == "VelocityMax") + editParticleEffect.maxVelocity = element.text.ToFloat(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectRotation(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + float vMin = editParticleEffect.minRotation; + float vMax = editParticleEffect.maxRotation; + + if (element.name == "RotationMin") + editParticleEffect.minRotation = element.text.ToFloat(); + + if (element.name == "RotationMax") + editParticleEffect.maxRotation = element.text.ToFloat(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectRotationSpeed(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + float vMin = editParticleEffect.minRotationSpeed; + float vMax = editParticleEffect.maxRotationSpeed; + + if (element.name == "RotationSpeedMin") + editParticleEffect.minRotationSpeed = element.text.ToFloat(); + + if (element.name == "RotationSpeedMax") + editParticleEffect.maxRotationSpeed = element.text.ToFloat(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectSizeAdd(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + editParticleEffect.sizeAdd = element.text.ToFloat(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectSizeMultiply(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + editParticleEffect.sizeMul = element.text.ToFloat(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectAnimationLodBias(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + editParticleEffect.animationLodBias = element.text.ToFloat(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectNumParticles(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (particleEffectEmitter is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + editParticleEffect.numParticles = element.text.ToInt(); + particleEffectEmitter.ApplyEffect(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectEmitterSize(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + Vector3 v = editParticleEffect.emitterSize; + + if (element.name == "EmitterSizeX") + editParticleEffect.emitterSize = Vector3(element.text.ToFloat(), v.y, v.z); + + if (element.name == "EmitterSizeY") + editParticleEffect.emitterSize = Vector3(v.x, element.text.ToFloat(), v.z); + + if (element.name == "EmitterSizeZ") + editParticleEffect.emitterSize = Vector3(v.x, v.y, element.text.ToFloat()); + + EndParticleEffectEdit(); +} + +void EditParticleEffectEmissionRate(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + + if (element.name == "EmissionRateMin") + editParticleEffect.minEmissionRate = element.text.ToFloat(); + + if (element.name == "EmissionRateMax") + editParticleEffect.maxEmissionRate = element.text.ToFloat(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectEmitterShape(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + DropDownList@ element = eventData["Element"].GetPtr(); + + switch (element.selection) + { + case 0: + editParticleEffect.emitterType = EMITTER_SPHERE; + break; + + case 1: + editParticleEffect.emitterType = EMITTER_BOX; + break; + + case 2: + editParticleEffect.emitterType = EMITTER_SPHEREVOLUME; + break; + + case 3: + editParticleEffect.emitterType = EMITTER_CYLINDER; + break; + + case 4: + editParticleEffect.emitterType = EMITTER_RING; + break; + } + + EndParticleEffectEdit(); +} + +void EditParticleEffectFaceCameraMode(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + BeginParticleEffectEdit(); + + DropDownList@ element = eventData["Element"].GetPtr(); + + switch (element.selection) + { + case 0: + editParticleEffect.faceCameraMode = FC_NONE; + break; + + case 1: + editParticleEffect.faceCameraMode = FC_ROTATE_XYZ; + break; + + case 2: + editParticleEffect.faceCameraMode = FC_ROTATE_Y; + break; + + case 3: + editParticleEffect.faceCameraMode = FC_LOOKAT_XYZ; + break; + + case 4: + editParticleEffect.faceCameraMode = FC_LOOKAT_Y; + break; + + case 5: + editParticleEffect.faceCameraMode = FC_DIRECTION; + break; + } + + particleEffectEmitter.ApplyEffect(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectMaterial(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (particleEffectEmitter is null) + return; + + LineEdit@ element = eventData["Element"].GetPtr(); + Material@ res = cache.GetResource("Material", element.text); + + if (res !is null) + { + BeginParticleEffectEdit(); + + editParticleEffect.material = res; + particleEffectEmitter.ApplyEffect(); + + EndParticleEffectEdit(); + } +} + +void PickEditParticleEffectMaterial(StringHash eventType, VariantMap& eventData) +{ + @resourcePicker = GetResourcePicker(StringHash("Material")); + if (resourcePicker is null) + return; + + 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", "PickEditParticleEffectMaterialDone"); +} + +void PickEditParticleEffectMaterialDone(StringHash eventType, VariantMap& eventData) +{ + StoreResourcePickerPath(); + CloseFileSelector(); + + if (!eventData["OK"].GetBool()) + { + @resourcePicker = null; + return; + } + + String resourceName = eventData["FileName"].GetString(); + Resource@ res = GetPickedResource(resourceName); + + if (res !is null && editParticleEffect !is null && particleEffectEmitter !is null) + { + editParticleEffect.material = res; + particleEffectEmitter.ApplyEffect(); + RefreshParticleEffectMaterial(); + } + + @resourcePicker = null; +} + +void EditParticleEffectScaled(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (particleEffectEmitter is null) + return; + + BeginParticleEffectEdit(); + + CheckBox@ element = eventData["Element"].GetPtr(); + + editParticleEffect.scaled = element.checked; + particleEffectEmitter.ApplyEffect(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectSorted(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (particleEffectEmitter is null) + return; + + BeginParticleEffectEdit(); + + CheckBox@ element = eventData["Element"].GetPtr(); + + editParticleEffect.sorted = element.checked; + particleEffectEmitter.ApplyEffect(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectRelative(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (particleEffectEmitter is null) + return; + + BeginParticleEffectEdit(); + + CheckBox@ element = eventData["Element"].GetPtr(); + + editParticleEffect.relative = element.checked; + particleEffectEmitter.ApplyEffect(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectFixedScreenSize(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (particleEffectEmitter is null) + return; + + BeginParticleEffectEdit(); + + CheckBox@ element = eventData["Element"].GetPtr(); + + editParticleEffect.fixedScreenSize = element.checked; + particleEffectEmitter.ApplyEffect(); + + EndParticleEffectEdit(); +} + +bool ToggleParticleEffectEditor() +{ + if (particleEffectWindow.visible == false) + ShowParticleEffectEditor(); + else + HideParticleEffectEditor(); + return true; +} + +void ShowParticleEffectEditor() +{ + RefreshParticleEffectEditor(); + particleEffectWindow.visible = true; + particleEffectWindow.BringToFront(); +} + +void HideParticleEffectEditor() +{ + if (particleEffectWindow !is null) + particleEffectWindow.visible = false; +} + +void UpdateParticleEffectPreviewGrid() +{ + uint gridSize = 8; + uint gridSubdivisions = 3; + + //particleEffectPreviewGridNode.scale = Vector3(gridScale, gridScale, gridScale); + uint size = uint(Floor(gridSize / 2) * 2); + float halfSizeScaled = size / 2; + float scale = 1.0; + uint subdivisionSize = uint(Pow(2.0f, float(gridSubdivisions))); + + if (subdivisionSize > 0) + { + size *= subdivisionSize; + scale /= subdivisionSize; + } + + uint halfSize = size / 2; + + particleEffectPreviewGrid.BeginGeometry(0, LINE_LIST); + float lineOffset = -halfSizeScaled; + for (uint i = 0; i <= size; ++i) + { + bool lineCenter = i == halfSize; + bool lineSubdiv = !Equals(Mod(i, subdivisionSize), 0.0); + + if (!grid2DMode) + { + particleEffectPreviewGrid.DefineVertex(Vector3(lineOffset, 0.0, halfSizeScaled)); + particleEffectPreviewGrid.DefineColor(lineCenter ? gridZColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + particleEffectPreviewGrid.DefineVertex(Vector3(lineOffset, 0.0, -halfSizeScaled)); + particleEffectPreviewGrid.DefineColor(lineCenter ? gridZColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + + particleEffectPreviewGrid.DefineVertex(Vector3(-halfSizeScaled, 0.0, lineOffset)); + particleEffectPreviewGrid.DefineColor(lineCenter ? gridXColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + particleEffectPreviewGrid.DefineVertex(Vector3(halfSizeScaled, 0.0, lineOffset)); + particleEffectPreviewGrid.DefineColor(lineCenter ? gridXColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + } + else + { + particleEffectPreviewGrid.DefineVertex(Vector3(lineOffset, halfSizeScaled, 0.0)); + particleEffectPreviewGrid.DefineColor(lineCenter ? gridYColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + particleEffectPreviewGrid.DefineVertex(Vector3(lineOffset, -halfSizeScaled, 0.0)); + particleEffectPreviewGrid.DefineColor(lineCenter ? gridYColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + + particleEffectPreviewGrid.DefineVertex(Vector3(-halfSizeScaled, lineOffset, 0.0)); + particleEffectPreviewGrid.DefineColor(lineCenter ? gridXColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + particleEffectPreviewGrid.DefineVertex(Vector3(halfSizeScaled, lineOffset, 0.0)); + particleEffectPreviewGrid.DefineColor(lineCenter ? gridXColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + } + + lineOffset += scale; + } + particleEffectPreviewGrid.Commit(); + +} + +void InitParticleEffectPreview() +{ + particlePreviewScene = Scene("particlePreviewScene"); + particlePreviewScene.CreateComponent("Octree"); + + Node@ zoneNode = particlePreviewScene.CreateChild("Zone"); + Zone@ zone = zoneNode.CreateComponent("Zone"); + zone.boundingBox = BoundingBox(-1000, 1000); + zone.ambientColor = Color(0.15, 0.15, 0.15); + zone.fogColor = Color(0, 0, 0); + zone.fogStart = 10.0; + zone.fogEnd = 1000.0; + + particlePreviewCameraNode = particlePreviewScene.CreateChild("PreviewCamera"); + particlePreviewCamera = particlePreviewCameraNode.CreateComponent("Camera"); + particlePreviewCamera.nearClip = 0.1f; + particlePreviewCamera.farClip = 1000.0f; + particlePreviewCamera.fov = 45.0f; + + particlePreviewLightNode = particlePreviewScene.CreateChild("particlePreviewLight"); + particlePreviewLightNode.direction = Vector3(0.5, -0.5, 0.5); + particlePreviewLight = particlePreviewLightNode.CreateComponent("Light"); + particlePreviewLight.lightType = LIGHT_DIRECTIONAL; + particlePreviewLight.specularIntensity = 0.5; + + particleEffectPreviewNode = particlePreviewScene.CreateChild("PreviewEmitter"); + particleEffectPreviewNode.rotation = Quaternion(0, 0, 0); + + ResetCameraTransformation(); + + particleEffectPreviewGizmoNode = particlePreviewScene.CreateChild("Gizmo"); + StaticModel@ gizmo = particleEffectPreviewGizmoNode.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.occludee = false; + + particleEffectPreviewGridNode = particlePreviewScene.CreateChild("Grid"); + particleEffectPreviewGrid = particleEffectPreviewGridNode.CreateComponent("CustomGeometry"); + particleEffectPreviewGrid.numGeometries = 1; + particleEffectPreviewGrid.material = cache.GetResource("Material", "Materials/VColUnlit.xml"); + particleEffectPreviewGrid.viewMask = 0x80000000; // Editor raycasts use viewmask 0x7fffffff + particleEffectPreviewGrid.occludee = false; + + UpdateParticleEffectPreviewGrid(); + + particleEffectEmitter = particleEffectPreviewNode.CreateComponent("ParticleEmitter"); + editParticleEffect = CreateNewParticleEffect(); + particleEffectEmitter.effect = editParticleEffect; + + particleEffectPreview = particleEffectWindow.GetChild("ParticleEffectPreview", true); + particleEffectPreview.SetView(particlePreviewScene, particlePreviewCamera); + particleEffectPreview.viewport.renderPath = renderPath; + + SubscribeToEvent(particleEffectPreview, "DragMove", "NavigateParticleEffectPreview"); + SubscribeToEvent(particleEffectPreview, "Resized", "ResizeParticleEffectPreview"); +} + +ParticleEffect@ CreateNewParticleEffect() +{ + ParticleEffect@ effect = ParticleEffect(); + Material@ res = cache.GetResource("Material", "Materials/Particle.xml"); + if (res is null) + log.Error("Could not load default material for new particle effect."); + effect.material = res; + effect.AddColorTime(Color(1,1,1,1), 0.0f); + return effect; +} + +void EditParticleEffect(ParticleEffect@ effect) +{ + if (effect is null) + return; + + if (editParticleEffect !is null) + UnsubscribeFromEvent(editParticleEffect, "ReloadFinished"); + + if (!effect.name.empty) + cache.ReloadResource(effect); + + editParticleEffect = effect; + particleEffectEmitter.effect = editParticleEffect; + + if (editParticleEffect !is null) + SubscribeToEvent(editParticleEffect, "ReloadFinished", "RefreshParticleEffectEditor"); + + ShowParticleEffectEditor(); +} + +void RefreshParticleEffectEditor() +{ + inParticleEffectRefresh = true; + + RefreshParticleEffectPreview(); + RefreshParticleEffectName(); + RefreshParticleEffectBasicAttributes(); + RefreshParticleEffectMaterial(); + RefreshParticleEffectColorFrames(); + RefreshParticleEffectTextureFrames(); + + inParticleEffectRefresh = false; +} + +void RefreshParticleEffectColorFrames() +{ + if (editParticleEffect is null) + return; + + editParticleEffect.SortColorFrames(); + + ListView@ lv = particleEffectWindow.GetChild("ColorFrameListView", true); + if (lv is null) + return; + + lv.RemoveAllItems(); + + for (uint i = 0; i < editParticleEffect.numColorFrames; i++) + { + ColorFrame@ colorFrame = editParticleEffect.GetColorFrame(i); + + Button@ container = Button(); + lv.AddItem(container); + container.style = "Button"; + container.imageRect = IntRect(18, 2, 30, 14); + container.minSize = IntVector2(0, 20); + container.maxSize = IntVector2(2147483647, 20); + container.layoutMode = LM_HORIZONTAL; + container.layoutBorder = IntRect(3,1,3,1); + container.layoutSpacing = 4; + + UIElement@ labelContainer = UIElement(); + container.AddChild(labelContainer); + labelContainer.style = "HorizontalPanel"; + labelContainer.minSize = IntVector2(0, 16); + labelContainer.maxSize = IntVector2(2147483647, 16); + labelContainer.verticalAlignment = VA_CENTER; + + { + LineEdit@ le = LineEdit(); + labelContainer.AddChild(le); + le.name = "ColorTime"; + le.vars["ColorFrame"] = i; + le.style = "LineEdit"; + le.minSize = IntVector2(0, 16); + le.maxSize = IntVector2(40, 16); + le.text = colorFrame.time; + le.cursorPosition = 0; + CreateDragSlider(le); + + SubscribeToEvent(le, "TextChanged", "EditParticleEffectColorFrame"); + } + + UIElement@ textContainer = UIElement(); + labelContainer.AddChild(textContainer); + textContainer.minSize = IntVector2(0, 16); + textContainer.maxSize = IntVector2(2147483647, 16); + textContainer.verticalAlignment = VA_CENTER; + + Text@ t = Text(); + textContainer.AddChild(t); + t.style = "Text"; + t.text = "Color"; + t.autoLocalizable = true; + + UIElement@ editContainer = UIElement(); + container.AddChild(editContainer); + editContainer.style = "HorizontalPanel"; + editContainer.minSize = IntVector2(0, 16); + editContainer.maxSize = IntVector2(2147483647, 16); + editContainer.verticalAlignment = VA_CENTER; + + { + LineEdit@ le = LineEdit(); + editContainer.AddChild(le); + le.name = "ColorR"; + le.vars["ColorFrame"] = i; + le.style = "LineEdit"; + le.text = colorFrame.color.r; + le.cursorPosition = 0; + CreateDragSlider(le); + + SubscribeToEvent(le, "TextChanged", "EditParticleEffectColorFrame"); + } + { + LineEdit@ le = LineEdit(); + editContainer.AddChild(le); + le.name = "ColorG"; + le.vars["ColorFrame"] = i; + le.style = "LineEdit"; + le.text = colorFrame.color.g; + le.cursorPosition = 0; + CreateDragSlider(le); + + SubscribeToEvent(le, "TextChanged", "EditParticleEffectColorFrame"); + } + { + LineEdit@ le = LineEdit(); + editContainer.AddChild(le); + le.name = "ColorB"; + le.vars["ColorFrame"] = i; + le.style = "LineEdit"; + le.text = colorFrame.color.b; + le.cursorPosition = 0; + CreateDragSlider(le); + + SubscribeToEvent(le, "TextChanged", "EditParticleEffectColorFrame"); + } + { + LineEdit@ le = LineEdit(); + editContainer.AddChild(le); + le.name = "ColorA"; + le.vars["ColorFrame"] = i; + le.style = "LineEdit"; + le.text = colorFrame.color.a; + le.cursorPosition = 0; + CreateDragSlider(le); + + SubscribeToEvent(le, "TextChanged", "EditParticleEffectColorFrame"); + } + + } +} + +void RefreshParticleEffectTextureFrames() +{ + if (editParticleEffect is null) + return; + + editParticleEffect.SortTextureFrames(); + + ListView@ lv = particleEffectWindow.GetChild("TextureFrameListView", true); + if (lv is null) + return; + + lv.RemoveAllItems(); + + for (uint i = 0; i < editParticleEffect.numTextureFrames; i++) + { + TextureFrame@ textureFrame = editParticleEffect.GetTextureFrame(i); + if (textureFrame is null) + continue; + + Button@ container = Button(); + lv.AddItem(container); + container.style = "Button"; + container.imageRect = IntRect(18, 2, 30, 14); + container.minSize = IntVector2(0, 20); + container.maxSize = IntVector2(2147483647, 20); + container.layoutMode = LM_HORIZONTAL; + container.layoutBorder = IntRect(1,1,1,1); + container.layoutSpacing = 4; + + UIElement@ labelContainer = UIElement(); + container.AddChild(labelContainer); + labelContainer.style = "HorizontalPanel"; + labelContainer.minSize = IntVector2(0, 16); + labelContainer.maxSize = IntVector2(2147483647, 16); + labelContainer.verticalAlignment = VA_CENTER; + + { + LineEdit@ le = LineEdit(); + labelContainer.AddChild(le); + le.name = "TextureTime"; + le.vars["TextureFrame"] = i; + le.style = "LineEdit"; + le.minSize = IntVector2(0, 16); + le.maxSize = IntVector2(40, 16); + le.text = textureFrame.time; + le.cursorPosition = 0; + CreateDragSlider(le); + + SubscribeToEvent(le, "TextChanged", "EditParticleEffectTextureFrame"); + } + + UIElement@ textContainer = UIElement(); + labelContainer.AddChild(textContainer); + textContainer.minSize = IntVector2(0, 16); + textContainer.maxSize = IntVector2(2147483647, 16); + textContainer.verticalAlignment = VA_CENTER; + + Text@ t = Text(); + textContainer.AddChild(t); + t.style = "Text"; + t.text = "Texture"; + t.autoLocalizable = true; + + UIElement@ editContainer = UIElement(); + container.AddChild(editContainer); + editContainer.style = "HorizontalPanel"; + editContainer.minSize = IntVector2(0, 16); + editContainer.maxSize = IntVector2(2147483647, 16); + editContainer.verticalAlignment = VA_CENTER; + + { + LineEdit@ le = LineEdit(); + editContainer.AddChild(le); + le.name = "TextureMinX"; + le.vars["TextureFrame"] = i; + le.style = "LineEdit"; + le.text = textureFrame.uv.min.x; + le.cursorPosition = 0; + CreateDragSlider(le); + + SubscribeToEvent(le, "TextChanged", "EditParticleEffectTextureFrame"); + } + { + LineEdit@ le = LineEdit(); + editContainer.AddChild(le); + le.name = "TextureMinY"; + le.vars["TextureFrame"] = i; + le.style = "LineEdit"; + le.text = textureFrame.uv.min.y; + le.cursorPosition = 0; + CreateDragSlider(le); + + SubscribeToEvent(le, "TextChanged", "EditParticleEffectTextureFrame"); + } + { + LineEdit@ le = LineEdit(); + editContainer.AddChild(le); + le.name = "TextureMaxX"; + le.vars["TextureFrame"] = i; + le.style = "LineEdit"; + le.text = textureFrame.uv.max.x; + le.cursorPosition = 0; + CreateDragSlider(le); + + SubscribeToEvent(le, "TextChanged", "EditParticleEffectTextureFrame"); + } + { + LineEdit@ le = LineEdit(); + editContainer.AddChild(le); + le.name = "TextureMaxY"; + le.vars["TextureFrame"] = i; + le.style = "LineEdit"; + le.text = textureFrame.uv.max.y; + le.cursorPosition = 0; + CreateDragSlider(le); + + SubscribeToEvent(le, "TextChanged", "EditParticleEffectTextureFrame"); + } + } +} + +void EditParticleEffectColorFrame(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (particleEffectEmitter is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + uint i = element.vars["ColorFrame"].GetInt(); + ColorFrame@ cf = editParticleEffect.GetColorFrame(i); + + if (element.name == "ColorTime") + cf.time = element.text.ToFloat(); + + if (element.name == "ColorR") + cf.color = Color(element.text.ToFloat(), cf.color.g, cf.color.b, cf.color.a); + + if (element.name == "ColorG") + cf.color = Color(cf.color.r, element.text.ToFloat(), cf.color.b, cf.color.a); + + if (element.name == "ColorB") + cf.color = Color(cf.color.r, cf.color.g, element.text.ToFloat(), cf.color.a); + + if (element.name == "ColorA") + cf.color = Color(cf.color.r, cf.color.g, cf.color.b, element.text.ToFloat()); + + editParticleEffect.SetColorFrame(i, cf); + particleEffectEmitter.Reset(); + + EndParticleEffectEdit(); +} + +void EditParticleEffectTextureFrame(StringHash eventType, VariantMap& eventData) +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (particleEffectEmitter is null) + return; + + BeginParticleEffectEdit(); + + LineEdit@ element = eventData["Element"].GetPtr(); + uint i = element.vars["TextureFrame"].GetInt(); + TextureFrame@ tf = editParticleEffect.GetTextureFrame(i); + + if (element.name == "TextureTime") + tf.time = element.text.ToFloat(); + + if (element.name == "TextureMinX") + tf.uv = Rect(element.text.ToFloat(), tf.uv.min.y, tf.uv.max.x, tf.uv.max.y); + + if (element.name == "TextureMinY") + tf.uv = Rect(tf.uv.min.x, element.text.ToFloat(), tf.uv.max.x, tf.uv.max.y); + + if (element.name == "TextureMaxX") + tf.uv = Rect(tf.uv.min.x, tf.uv.min.y, element.text.ToFloat(), tf.uv.max.y); + + if (element.name == "TextureMaxY") + tf.uv = Rect(tf.uv.min.x, tf.uv.min.y, tf.uv.max.x, element.text.ToFloat()); + + editParticleEffect.SetTextureFrame(i, tf); + particleEffectEmitter.Reset(); + + EndParticleEffectEdit(); +} + +void RefreshParticleEffectPreview() +{ + if (particleEffectEmitter is null || editParticleEffect is null) + return; + cast(particleEffectWindow.GetChild("ShowGrid", true)).checked = showParticlePreviewAxes; + particleEffectEmitter.effect = editParticleEffect; + particleEffectEmitter.Reset(); + particleEffectPreview.QueueUpdate(); +} + +void RefreshParticleEffectName() +{ + UIElement@ container = particleEffectWindow.GetChild("NameContainer", true); + if (container is null) + return; + + container.RemoveAllChildren(); + + LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, 0, 0); + if (editParticleEffect !is null) + nameEdit.text = editParticleEffect.name; + SubscribeToEvent(nameEdit, "TextFinished", "EditParticleEffectName"); + + Button@ pickButton = CreateResourcePickerButton(container, null, 0, 0, "smallButtonPick"); + SubscribeToEvent(pickButton, "Released", "PickEditParticleEffect"); +} + +void RefreshParticleEffectBasicAttributes() +{ + if (editParticleEffect is null) + return; + + cast(particleEffectWindow.GetChild("ConstantForceX", true)).text = editParticleEffect.constantForce.x; + cast(particleEffectWindow.GetChild("ConstantForceY", true)).text = editParticleEffect.constantForce.y; + cast(particleEffectWindow.GetChild("ConstantForceZ", true)).text = editParticleEffect.constantForce.z; + + cast(particleEffectWindow.GetChild("DirectionMinX", true)).text = editParticleEffect.minDirection.x; + cast(particleEffectWindow.GetChild("DirectionMinY", true)).text = editParticleEffect.minDirection.y; + cast(particleEffectWindow.GetChild("DirectionMinZ", true)).text = editParticleEffect.minDirection.z; + + cast(particleEffectWindow.GetChild("DirectionMaxX", true)).text = editParticleEffect.maxDirection.x; + cast(particleEffectWindow.GetChild("DirectionMaxY", true)).text = editParticleEffect.maxDirection.y; + cast(particleEffectWindow.GetChild("DirectionMaxZ", true)).text = editParticleEffect.maxDirection.z; + + cast(particleEffectWindow.GetChild("DampingForce", true)).text = editParticleEffect.dampingForce; + cast(particleEffectWindow.GetChild("ActiveTime", true)).text = editParticleEffect.activeTime; + cast(particleEffectWindow.GetChild("InactiveTime", true)).text = editParticleEffect.inactiveTime; + + cast(particleEffectWindow.GetChild("ParticleSizeMinX", true)).text = editParticleEffect.minParticleSize.x; + cast(particleEffectWindow.GetChild("ParticleSizeMinY", true)).text = editParticleEffect.minParticleSize.y; + + cast(particleEffectWindow.GetChild("ParticleSizeMaxX", true)).text = editParticleEffect.maxParticleSize.x; + cast(particleEffectWindow.GetChild("ParticleSizeMaxY", true)).text = editParticleEffect.maxParticleSize.y; + + cast(particleEffectWindow.GetChild("TimeToLiveMin", true)).text = editParticleEffect.minTimeToLive; + cast(particleEffectWindow.GetChild("TimeToLiveMax", true)).text = editParticleEffect.maxTimeToLive; + + cast(particleEffectWindow.GetChild("VelocityMin", true)).text = editParticleEffect.minVelocity; + cast(particleEffectWindow.GetChild("VelocityMax", true)).text = editParticleEffect.maxVelocity; + + cast(particleEffectWindow.GetChild("RotationMin", true)).text = editParticleEffect.minRotation; + cast(particleEffectWindow.GetChild("RotationMax", true)).text = editParticleEffect.maxRotation; + + cast(particleEffectWindow.GetChild("RotationSpeedMin", true)).text = editParticleEffect.minRotationSpeed; + cast(particleEffectWindow.GetChild("RotationSpeedMax", true)).text = editParticleEffect.maxRotationSpeed; + + cast(particleEffectWindow.GetChild("SizeAdd", true)).text = editParticleEffect.sizeAdd; + cast(particleEffectWindow.GetChild("SizeMultiply", true)).text = editParticleEffect.sizeMul; + cast(particleEffectWindow.GetChild("AnimationLodBias", true)).text = editParticleEffect.animationLodBias; + + cast(particleEffectWindow.GetChild("NumParticles", true)).text = editParticleEffect.numParticles; + + cast(particleEffectWindow.GetChild("EmitterSizeX", true)).text = editParticleEffect.emitterSize.x; + cast(particleEffectWindow.GetChild("EmitterSizeY", true)).text = editParticleEffect.emitterSize.y; + cast(particleEffectWindow.GetChild("EmitterSizeZ", true)).text = editParticleEffect.emitterSize.z; + + cast(particleEffectWindow.GetChild("EmissionRateMin", true)).text = editParticleEffect.minEmissionRate; + cast(particleEffectWindow.GetChild("EmissionRateMax", true)).text = editParticleEffect.maxEmissionRate; + + switch (editParticleEffect.emitterType) + { + case EMITTER_SPHERE: + cast(particleEffectWindow.GetChild("EmitterShape", true)).selection = 0; + break; + case EMITTER_BOX: + cast(particleEffectWindow.GetChild("EmitterShape", true)).selection = 1; + break; + case EMITTER_SPHEREVOLUME: + cast(particleEffectWindow.GetChild("EmitterShape", true)).selection = 2; + break; + case EMITTER_CYLINDER: + cast(particleEffectWindow.GetChild("EmitterShape", true)).selection = 3; + break; + case EMITTER_RING: + cast(particleEffectWindow.GetChild("EmitterShape", true)).selection = 4; + break; + } + + switch (editParticleEffect.faceCameraMode) + { + case FC_NONE: + cast(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 0; + break; + case FC_ROTATE_XYZ: + cast(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 1; + break; + case FC_ROTATE_Y: + cast(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 2; + break; + case FC_LOOKAT_XYZ: + cast(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 3; + break; + case FC_LOOKAT_Y: + cast(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 4; + break; + case FC_DIRECTION: + cast(particleEffectWindow.GetChild("FaceCameraMode", true)).selection = 5; + break; + } + + cast(particleEffectWindow.GetChild("Scaled", true)).checked = editParticleEffect.scaled; + cast(particleEffectWindow.GetChild("Sorted", true)).checked = editParticleEffect.sorted; + cast(particleEffectWindow.GetChild("Relative", true)).checked = editParticleEffect.relative; + cast(particleEffectWindow.GetChild("FixedScreenSize", true)).checked = editParticleEffect.fixedScreenSize; +} + +void RefreshParticleEffectMaterial() +{ + UIElement@ container = particleEffectWindow.GetChild("ParticleMaterialContainer", true); + if (container is null) + return; + + container.RemoveAllChildren(); + + LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, 0, 0); + if (editParticleEffect !is null) + { + if (editParticleEffect.material !is null) + nameEdit.text = editParticleEffect.material.name; + else + { + nameEdit.text = "Materials/Particle.xml"; + Material@ res = cache.GetResource("Material", "Materials/Particle.xml"); + if (res !is null) + editParticleEffect.material = res; + } + } + + SubscribeToEvent(nameEdit, "TextFinished", "EditParticleEffectMaterial"); + + Button@ pickButton = CreateResourcePickerButton(container, null, 0, 0, "smallButtonPick"); + SubscribeToEvent(pickButton, "Released", "PickEditParticleEffectMaterial"); +} + +void NavigateParticleEffectPreview(StringHash eventType, VariantMap& eventData) +{ + int dx = eventData["DX"].GetInt(); + int dy = eventData["DY"].GetInt(); + + if (particleEffectPreview.height > 0 && particleEffectPreview.width > 0) + { + if (!input.keyDown[KEY_LSHIFT]) + { + particleViewCamRot.x -= dy * 20 * time.timeStep; + particleViewCamRot.y += dx * 20 * time.timeStep; + particleViewCamRot.x = Clamp(particleViewCamRot.x, -89.5, 89.5); + } + else + { + particleViewCamDist += dy * 1.5 * time.timeStep; + particleViewCamDist -= dx * 1.5 * time.timeStep; + particleViewCamDist = Max(particleViewCamDist, 0.2); + } + particlePreviewCameraNode.position = particleEffectPreviewNode.position + + Quaternion(particleViewCamRot.x, particleViewCamRot.y, 0) * particleViewCamDir * particleViewCamDist; + particlePreviewCameraNode.LookAt(particleEffectPreviewNode.position); + + SetGizmoPosition(); + particleEffectPreview.QueueUpdate(); + } + +} + +void ResizeParticleEffectPreview(StringHash eventType, VariantMap& eventData) +{ + + float width = float(particleEffectPreview.width); + float height = float(particleEffectPreview.height); + + // Manually set aspect ratio because first frame is always returning aspect ratio of 1 + float aspectRatio = width / height; + particlePreviewCamera.aspectRatio = aspectRatio; + + gizmoOffsetX = gizmoOffset; + gizmoOffsetY = 1.0f - gizmoOffset * aspectRatio; + + if(width > height) + { + aspectRatio = height / width; + gizmoOffsetY = 1.0f - gizmoOffset; + gizmoOffsetX = gizmoOffset * aspectRatio; + } + + SetGizmoPosition(); + particleEffectPreview.QueueUpdate(); +} + +void EditParticleEffectName(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ nameEdit = eventData["Element"].GetPtr(); + String newParticleEffectName = nameEdit.text.Trimmed(); + if (!newParticleEffectName.empty && !(editParticleEffect !is null && newParticleEffectName == editParticleEffect.name)) + { + ParticleEffect@ newParticleEffect = cache.GetResource("ParticleEffect", newParticleEffectName); + if (newParticleEffect !is null) + EditParticleEffect(newParticleEffect); + } +} + +void PickEditParticleEffect() +{ + @resourcePicker = GetResourcePicker(StringHash("ParticleEffect")); + if (resourcePicker is null) + return; + + 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", "PickEditParticleEffectDone"); +} + +void PickEditParticleEffectDone(StringHash eventType, VariantMap& eventData) +{ + StoreResourcePickerPath(); + CloseFileSelector(); + + if (!eventData["OK"].GetBool()) + { + @resourcePicker = null; + return; + } + + String resourceName = eventData["FileName"].GetString(); + Resource@ res = GetPickedResource(resourceName); + + if (res !is null) + EditParticleEffect(cast(res)); + + @resourcePicker = null; +} + +void NewParticleEffect() +{ + BeginParticleEffectEdit(); + + EditParticleEffect(CreateNewParticleEffect()); + + EndParticleEffectEdit(); +} + +void RevertParticleEffect() +{ + if (inParticleEffectRefresh) + return; + + if (editParticleEffect is null) + return; + + if (editParticleEffect.name.empty) + { + NewParticleEffect(); + return; + } + + BeginParticleEffectEdit(); + + cache.ReloadResource(editParticleEffect); + + EndParticleEffectEdit(); + + RefreshParticleEffectEditor(); +} + +void SaveParticleEffect() +{ + if (editParticleEffect is null || editParticleEffect.name.empty) + return; + + String fullName = cache.GetResourceFileName(editParticleEffect.name); + if (fullName.empty) + return; + + File saveFile(fullName, FILE_WRITE); + editParticleEffect.Save(saveFile); +} + +void SaveParticleEffectAs() +{ + if (editParticleEffect is null) + return; + + @resourcePicker = GetResourcePicker(StringHash("ParticleEffect")); + if (resourcePicker is null) + return; + + String lastPath = resourcePicker.lastPath; + if (lastPath.empty) + lastPath = sceneResourcePath; + CreateFileSelector("Save particle effect as", "Save", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "SaveParticleEffectAsDone"); +} + +void SaveParticleEffectAsDone(StringHash eventType, VariantMap& eventData) +{ + StoreResourcePickerPath(); + CloseFileSelector(); + @resourcePicker = null; + + if (editParticleEffect is null) + return; + + if (!eventData["OK"].GetBool()) + { + @resourcePicker = null; + return; + } + + String fullName = eventData["FileName"].GetString(); + + // Add default extension for saving if not specified + String filter = eventData["Filter"].GetString(); + if (GetExtension(fullName).empty && filter != "*.*") + fullName = fullName + filter.Substring(1); + + File saveFile(fullName, FILE_WRITE); + if (editParticleEffect.Save(saveFile)) + { + saveFile.Close(); + + // Load the new resource to update the name in the editor + ParticleEffect@ newEffect = cache.GetResource("ParticleEffect", GetResourceNameFromFullName(fullName)); + if (newEffect !is null) + EditParticleEffect(newEffect); + } +} + +void BeginParticleEffectEdit() +{ + if (editParticleEffect is null) + return; + + inParticleEffectRefresh = true; + + oldParticleEffectState = XMLFile(); + XMLElement particleElem = oldParticleEffectState.CreateRoot("particleeffect"); + editParticleEffect.Save(particleElem); +} + +void EndParticleEffectEdit() +{ + if (editParticleEffect is null) + return; + + if (!dragEditAttribute) + { + EditParticleEffectAction@ action = EditParticleEffectAction(); + action.Define(particleEffectEmitter, editParticleEffect, oldParticleEffectState); + SaveEditAction(action); + } + + inParticleEffectRefresh = false; + + particleEffectPreview.QueueUpdate(); +} diff --git a/bin/Data/Scripts/Editor/EditorPreferences.as b/bin/Data/Scripts/Editor/EditorPreferences.as new file mode 100644 index 0000000..7e3a9c4 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorPreferences.as @@ -0,0 +1,444 @@ +// Urho3D editor preferences dialog + +bool subscribedToEditorPreferences = false; +Window@ preferencesDialog; + +LineEdit@ nodeItemTextColorEditR; +LineEdit@ nodeItemTextColorEditG; +LineEdit@ nodeItemTextColorEditB; +LineEdit@ componentItemTextColorEditR; +LineEdit@ componentItemTextColorEditG; +LineEdit@ componentItemTextColorEditB; + +LineEdit@ originalAttributeTextColorEditR; +LineEdit@ originalAttributeTextColorEditG; +LineEdit@ originalAttributeTextColorEditB; +LineEdit@ modifiedAttributeTextColorEditR; +LineEdit@ modifiedAttributeTextColorEditG; +LineEdit@ modifiedAttributeTextColorEditB; +LineEdit@ nonEditableAttributeTextColorEditR; +LineEdit@ nonEditableAttributeTextColorEditG; +LineEdit@ nonEditableAttributeTextColorEditB; + +LineEdit@ defaultZoneAmbientColorEditR; +LineEdit@ defaultZoneAmbientColorEditG; +LineEdit@ defaultZoneAmbientColorEditB; +LineEdit@ defaultZoneFogColorEditR; +LineEdit@ defaultZoneFogColorEditG; +LineEdit@ defaultZoneFogColorEditB; + +LineEdit@ gridColorEditR; +LineEdit@ gridColorEditG; +LineEdit@ gridColorEditB; +LineEdit@ gridSubdivisionColorEditR; +LineEdit@ gridSubdivisionColorEditG; +LineEdit@ gridSubdivisionColorEditB; + +void CreateEditorPreferencesDialog() +{ + if (preferencesDialog !is null) + return; + + preferencesDialog = LoadEditorUI("UI/EditorPreferencesDialog.xml"); + ui.root.AddChild(preferencesDialog); + preferencesDialog.opacity = uiMaxOpacity; + preferencesDialog.height = 440; + CenterDialog(preferencesDialog); + + DropDownList@ languageSelector = preferencesDialog.GetChild("LanguageSelector", true); + for (int i = 0; i < localization.numLanguages; i++) + { + Text@ choice = Text(); + languageSelector.AddItem(choice); + choice.style = "FileSelectorFilterText"; + choice.text = localization.GetLanguage(i); + } + + nodeItemTextColorEditR = preferencesDialog.GetChild("NodeItemTextColor.r", true); + nodeItemTextColorEditG = preferencesDialog.GetChild("NodeItemTextColor.g", true); + nodeItemTextColorEditB = preferencesDialog.GetChild("NodeItemTextColor.b", true); + componentItemTextColorEditR = preferencesDialog.GetChild("ComponentItemTextColor.r", true); + componentItemTextColorEditG = preferencesDialog.GetChild("ComponentItemTextColor.g", true); + componentItemTextColorEditB = preferencesDialog.GetChild("ComponentItemTextColor.b", true); + + originalAttributeTextColorEditR = preferencesDialog.GetChild("OriginalAttributeTextColor.r", true); + originalAttributeTextColorEditG = preferencesDialog.GetChild("OriginalAttributeTextColor.g", true); + originalAttributeTextColorEditB = preferencesDialog.GetChild("OriginalAttributeTextColor.b", true); + modifiedAttributeTextColorEditR = preferencesDialog.GetChild("ModifiedAttributeTextColor.r", true); + modifiedAttributeTextColorEditG = preferencesDialog.GetChild("ModifiedAttributeTextColor.g", true); + modifiedAttributeTextColorEditB = preferencesDialog.GetChild("ModifiedAttributeTextColor.b", true); + nonEditableAttributeTextColorEditR = preferencesDialog.GetChild("NonEditableAttributeTextColor.r", true); + nonEditableAttributeTextColorEditG = preferencesDialog.GetChild("NonEditableAttributeTextColor.g", true); + nonEditableAttributeTextColorEditB = preferencesDialog.GetChild("NonEditableAttributeTextColor.b", true); + + defaultZoneAmbientColorEditR = preferencesDialog.GetChild("DefaultZoneAmbientColor.r", true); + defaultZoneAmbientColorEditG = preferencesDialog.GetChild("DefaultZoneAmbientColor.g", true); + defaultZoneAmbientColorEditB = preferencesDialog.GetChild("DefaultZoneAmbientColor.b", true); + defaultZoneFogColorEditR = preferencesDialog.GetChild("DefaultZoneFogColor.r", true); + defaultZoneFogColorEditG = preferencesDialog.GetChild("DefaultZoneFogColor.g", true); + defaultZoneFogColorEditB = preferencesDialog.GetChild("DefaultZoneFogColor.b", true); + + gridColorEditR = preferencesDialog.GetChild("GridColor.r", true); + gridColorEditG = preferencesDialog.GetChild("GridColor.g", true); + gridColorEditB = preferencesDialog.GetChild("GridColor.b", true); + gridSubdivisionColorEditR = preferencesDialog.GetChild("GridSubdivisionColor.r", true); + gridSubdivisionColorEditG = preferencesDialog.GetChild("GridSubdivisionColor.g", true); + gridSubdivisionColorEditB = preferencesDialog.GetChild("GridSubdivisionColor.b", true); + + UpdateEditorPreferencesDialog(); + HideEditorPreferencesDialog(); +} + +void UpdateEditorPreferencesDialog() +{ + if (preferencesDialog is null) + return; + + DropDownList@ languageSelector = preferencesDialog.GetChild("LanguageSelector", true); + languageSelector.selection = localization.languageIndex; + + LineEdit@ uiMinOpacityEdit = preferencesDialog.GetChild("UIMinOpacity", true); + uiMinOpacityEdit.text = String(uiMinOpacity); + + LineEdit@ uiMaxOpacityEdit = preferencesDialog.GetChild("UIMaxOpacity", true); + uiMaxOpacityEdit.text = String(uiMaxOpacity); + + CheckBox@ showInternalUIElementToggle = preferencesDialog.GetChild("ShowInternalUIElement", true); + showInternalUIElementToggle.checked = showInternalUIElement; + + CheckBox@ showTemporaryObjectToggle = preferencesDialog.GetChild("ShowTemporaryObject", true); + showTemporaryObjectToggle.checked = showTemporaryObject; + + nodeItemTextColorEditR.text = String(nodeTextColor.r); + nodeItemTextColorEditG.text = String(nodeTextColor.g); + nodeItemTextColorEditB.text = String(nodeTextColor.b); + + componentItemTextColorEditR.text = String(componentTextColor.r); + componentItemTextColorEditG.text = String(componentTextColor.g); + componentItemTextColorEditB.text = String(componentTextColor.b); + + CheckBox@ showNonEditableAttributeToggle = preferencesDialog.GetChild("ShowNonEditableAttribute", true); + showNonEditableAttributeToggle.checked = showNonEditableAttribute; + + originalAttributeTextColorEditR.text = String(normalTextColor.r); + originalAttributeTextColorEditG.text = String(normalTextColor.g); + originalAttributeTextColorEditB.text = String(normalTextColor.b); + + modifiedAttributeTextColorEditR.text = String(modifiedTextColor.r); + modifiedAttributeTextColorEditG.text = String(modifiedTextColor.g); + modifiedAttributeTextColorEditB.text = String(modifiedTextColor.b); + + nonEditableAttributeTextColorEditR.text = String(nonEditableTextColor.r); + nonEditableAttributeTextColorEditG.text = String(nonEditableTextColor.g); + nonEditableAttributeTextColorEditB.text = String(nonEditableTextColor.b); + + defaultZoneAmbientColorEditR.text = String(renderer.defaultZone.ambientColor.r); + defaultZoneAmbientColorEditG.text = String(renderer.defaultZone.ambientColor.g); + defaultZoneAmbientColorEditB.text = String(renderer.defaultZone.ambientColor.b); + + defaultZoneFogColorEditR.text = String(renderer.defaultZone.fogColor.r); + defaultZoneFogColorEditG.text = String(renderer.defaultZone.fogColor.g); + defaultZoneFogColorEditB.text = String(renderer.defaultZone.fogColor.b); + + LineEdit@ defaultZoneFogStartEdit = preferencesDialog.GetChild("DefaultZoneFogStart", true); + defaultZoneFogStartEdit.text = String(renderer.defaultZone.fogStart); + LineEdit@ defaultZoneFogEndEdit = preferencesDialog.GetChild("DefaultZoneFogEnd", true); + defaultZoneFogEndEdit.text = String(renderer.defaultZone.fogEnd); + + CheckBox@ showGridToggle = preferencesDialog.GetChild("ShowGrid", true); + showGridToggle.checked = showGrid; + + CheckBox@ grid2DModeToggle = preferencesDialog.GetChild("Grid2DMode", true); + grid2DModeToggle.checked = grid2DMode; + + LineEdit@ gridSizeEdit = preferencesDialog.GetChild("GridSize", true); + gridSizeEdit.text = String(gridSize); + + LineEdit@ gridSubdivisionsEdit = preferencesDialog.GetChild("GridSubdivisions", true); + gridSubdivisionsEdit.text = String(gridSubdivisions); + + LineEdit@ gridScaleEdit = preferencesDialog.GetChild("GridScale", true); + gridScaleEdit.text = String(gridScale); + + gridColorEditR.text = String(gridColor.r); + gridColorEditG.text = String(gridColor.g); + gridColorEditB.text = String(gridColor.b); + gridSubdivisionColorEditR.text = String(gridSubdivisionColor.r); + gridSubdivisionColorEditG.text = String(gridSubdivisionColor.g); + gridSubdivisionColorEditB.text = String(gridSubdivisionColor.b); + + if (!subscribedToEditorPreferences) + { + SubscribeToEvent(uiMinOpacityEdit, "TextFinished", "EditUIMinOpacity"); + SubscribeToEvent(uiMaxOpacityEdit, "TextFinished", "EditUIMaxOpacity"); + SubscribeToEvent(showInternalUIElementToggle, "Toggled", "ToggleShowInternalUIElement"); + SubscribeToEvent(showTemporaryObjectToggle, "Toggled", "ToggleShowTemporaryObject"); + SubscribeToEvent(nodeItemTextColorEditR, "TextFinished", "EditNodeTextColor"); + SubscribeToEvent(nodeItemTextColorEditG, "TextFinished", "EditNodeTextColor"); + SubscribeToEvent(nodeItemTextColorEditB, "TextFinished", "EditNodeTextColor"); + SubscribeToEvent(componentItemTextColorEditR, "TextFinished", "EditComponentTextColor"); + SubscribeToEvent(componentItemTextColorEditG, "TextFinished", "EditComponentTextColor"); + SubscribeToEvent(componentItemTextColorEditB, "TextFinished", "EditComponentTextColor"); + SubscribeToEvent(showNonEditableAttributeToggle, "Toggled", "ToggleShowNonEditableAttribute"); + SubscribeToEvent(originalAttributeTextColorEditR, "TextFinished", "EditOriginalAttributeTextColor"); + SubscribeToEvent(originalAttributeTextColorEditG, "TextFinished", "EditOriginalAttributeTextColor"); + SubscribeToEvent(originalAttributeTextColorEditB, "TextFinished", "EditOriginalAttributeTextColor"); + SubscribeToEvent(modifiedAttributeTextColorEditR, "TextFinished", "EditModifiedAttributeTextColor"); + SubscribeToEvent(modifiedAttributeTextColorEditG, "TextFinished", "EditModifiedAttributeTextColor"); + SubscribeToEvent(modifiedAttributeTextColorEditB, "TextFinished", "EditModifiedAttributeTextColor"); + SubscribeToEvent(nonEditableAttributeTextColorEditR, "TextFinished", "EditNonEditableAttributeTextColor"); + SubscribeToEvent(nonEditableAttributeTextColorEditG, "TextFinished", "EditNonEditableAttributeTextColor"); + SubscribeToEvent(nonEditableAttributeTextColorEditB, "TextFinished", "EditNonEditableAttributeTextColor"); + SubscribeToEvent(defaultZoneAmbientColorEditR, "TextFinished", "EditDefaultZoneAmbientColor"); + SubscribeToEvent(defaultZoneAmbientColorEditG, "TextFinished", "EditDefaultZoneAmbientColor"); + SubscribeToEvent(defaultZoneAmbientColorEditB, "TextFinished", "EditDefaultZoneAmbientColor"); + SubscribeToEvent(defaultZoneFogColorEditR, "TextFinished", "EditDefaultZoneFogColor"); + SubscribeToEvent(defaultZoneFogColorEditG, "TextFinished", "EditDefaultZoneFogColor"); + SubscribeToEvent(defaultZoneFogColorEditB, "TextFinished", "EditDefaultZoneFogColor"); + SubscribeToEvent(defaultZoneFogStartEdit, "TextFinished", "EditDefaultZoneFogStart"); + SubscribeToEvent(defaultZoneFogEndEdit, "TextFinished", "EditDefaultZoneFogEnd"); + SubscribeToEvent(showGridToggle, "Toggled", "ToggleShowGrid"); + SubscribeToEvent(grid2DModeToggle, "Toggled", "ToggleGrid2DMode"); + SubscribeToEvent(gridSizeEdit, "TextFinished", "EditGridSize"); + SubscribeToEvent(gridSubdivisionsEdit, "TextFinished", "EditGridSubdivisions"); + SubscribeToEvent(gridScaleEdit, "TextFinished", "EditGridScale"); + SubscribeToEvent(gridColorEditR, "TextFinished", "EditGridColor"); + SubscribeToEvent(gridColorEditG, "TextFinished", "EditGridColor"); + SubscribeToEvent(gridColorEditB, "TextFinished", "EditGridColor"); + SubscribeToEvent(languageSelector, "ItemSelected", "EditLanguageSelector"); + SubscribeToEvent(gridSubdivisionColorEditR, "TextFinished", "EditGridSubdivisionColor"); + SubscribeToEvent(gridSubdivisionColorEditG, "TextFinished", "EditGridSubdivisionColor"); + SubscribeToEvent(gridSubdivisionColorEditB, "TextFinished", "EditGridSubdivisionColor"); + SubscribeToEvent(preferencesDialog.GetChild("CloseButton", true), "Released", "HideEditorPreferencesDialog"); + subscribedToEditorPreferences = true; + } +} + +void EditLanguageSelector(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ edit = eventData["Element"].GetPtr(); + localization.SetLanguage(edit.selection); +} + +bool ToggleEditorPreferencesDialog() +{ + if (preferencesDialog.visible == false) + ShowEditorPreferencesDialog(); + else + HideEditorPreferencesDialog(); + return true; +} + +void ShowEditorPreferencesDialog() +{ + UpdateEditorPreferencesDialog(); + preferencesDialog.visible = true; + preferencesDialog.BringToFront(); +} + +void HideEditorPreferencesDialog() +{ + preferencesDialog.visible = false; +} + +void EditUIMinOpacity(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + uiMinOpacity = edit.text.ToFloat(); + edit.text = String(uiMinOpacity); + FadeUI(); + UnfadeUI(); +} + +void EditUIMaxOpacity(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + uiMaxOpacity = edit.text.ToFloat(); + edit.text = String(uiMaxOpacity); + FadeUI(); + UnfadeUI(); +} + +void ToggleShowInternalUIElement(StringHash eventType, VariantMap& eventData) +{ + showInternalUIElement = cast(eventData["Element"].GetPtr()).checked; + UpdateHierarchyItem(editorUIElement, true); +} + +void ToggleShowTemporaryObject(StringHash eventType, VariantMap& eventData) +{ + showTemporaryObject = cast(eventData["Element"].GetPtr()).checked; + UpdateHierarchyItem(editorScene, true); + UpdateHierarchyItem(editorUIElement, true); +} + +void EditNodeTextColor(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + nodeTextColor = Color(nodeItemTextColorEditR.text.ToFloat(), nodeItemTextColorEditG.text.ToFloat(), nodeItemTextColorEditB.text.ToFloat()); + if (edit.name == "NodeItemTextColor.r") + edit.text = String(normalTextColor.r); + else if (edit.name == "NodeItemTextColor.g") + edit.text = String(normalTextColor.g); + else if (edit.name == "NodeItemTextColor.b") + edit.text = String(normalTextColor.b); + UpdateHierarchyItem(editorScene); +} + +void EditComponentTextColor(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + componentTextColor = Color(componentItemTextColorEditR.text.ToFloat(), componentItemTextColorEditG.text.ToFloat(), componentItemTextColorEditB.text.ToFloat()); + if (edit.name == "ComponentItemTextColor.r") + edit.text = String(normalTextColor.r); + else if (edit.name == "ComponentItemTextColor.g") + edit.text = String(normalTextColor.g); + else if (edit.name == "ComponentItemTextColor.b") + edit.text = String(normalTextColor.b); + UpdateHierarchyItem(editorScene); +} + +void ToggleShowNonEditableAttribute(StringHash eventType, VariantMap& eventData) +{ + showNonEditableAttribute = cast(eventData["Element"].GetPtr()).checked; + UpdateAttributeInspector(true); +} + +void EditOriginalAttributeTextColor(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + normalTextColor = Color(originalAttributeTextColorEditR.text.ToFloat(), originalAttributeTextColorEditG.text.ToFloat(), originalAttributeTextColorEditB.text.ToFloat()); + if (edit.name == "OriginalAttributeTextColor.r") + edit.text = String(normalTextColor.r); + else if (edit.name == "OriginalAttributeTextColor.g") + edit.text = String(normalTextColor.g); + else if (edit.name == "OriginalAttributeTextColor.b") + edit.text = String(normalTextColor.b); + UpdateAttributeInspector(false); +} + +void EditModifiedAttributeTextColor(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + modifiedTextColor = Color(modifiedAttributeTextColorEditR.text.ToFloat(), modifiedAttributeTextColorEditG.text.ToFloat(), modifiedAttributeTextColorEditB.text.ToFloat()); + if (edit.name == "ModifiedAttributeTextColor.r") + edit.text = String(modifiedTextColor.r); + else if (edit.name == "ModifiedAttributeTextColor.g") + edit.text = String(modifiedTextColor.g); + else if (edit.name == "ModifiedAttributeTextColor.b") + edit.text = String(modifiedTextColor.b); + UpdateAttributeInspector(false); +} + +void EditNonEditableAttributeTextColor(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + nonEditableTextColor = Color(nonEditableAttributeTextColorEditR.text.ToFloat(), nonEditableAttributeTextColorEditG.text.ToFloat(), nonEditableAttributeTextColorEditB.text.ToFloat()); + if (edit.name == "NonEditableAttributeTextColor.r") + edit.text = String(nonEditableTextColor.r); + else if (edit.name == "NonEditableAttributeTextColor.g") + edit.text = String(nonEditableTextColor.g); + else if (edit.name == "NonEditableAttributeTextColor.b") + edit.text = String(nonEditableTextColor.b); + UpdateAttributeInspector(false); +} + +void EditDefaultZoneAmbientColor(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + renderer.defaultZone.ambientColor = Color(defaultZoneAmbientColorEditR.text.ToFloat(), defaultZoneAmbientColorEditG.text.ToFloat(), defaultZoneAmbientColorEditB.text.ToFloat()); + if (edit.name == "DefaultZoneAmbientColor.r") + edit.text = String(renderer.defaultZone.ambientColor.r); + else if (edit.name == "DefaultZoneAmbientColor.g") + edit.text = String(renderer.defaultZone.ambientColor.g); + else if (edit.name == "DefaultZoneAmbientColor.b") + edit.text = String(renderer.defaultZone.ambientColor.b); +} + +void EditDefaultZoneFogColor(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + renderer.defaultZone.fogColor = Color(defaultZoneFogColorEditR.text.ToFloat(), defaultZoneFogColorEditG.text.ToFloat(), defaultZoneFogColorEditB.text.ToFloat()); + if (edit.name == "DefaultZoneFogColor.r") + edit.text = String(renderer.defaultZone.fogColor.r); + else if (edit.name == "DefaultZoneFogColor.g") + edit.text = String(renderer.defaultZone.fogColor.g); + else if (edit.name == "DefaultZoneFogColor.b") + edit.text = String(renderer.defaultZone.fogColor.b); +} + +void EditDefaultZoneFogStart(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + renderer.defaultZone.fogStart = edit.text.ToFloat(); + edit.text = String(renderer.defaultZone.fogStart); +} + +void EditDefaultZoneFogEnd(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + renderer.defaultZone.fogEnd = edit.text.ToFloat(); + edit.text = String(renderer.defaultZone.fogEnd); +} + +void ToggleShowGrid(StringHash eventType, VariantMap& eventData) +{ + showGrid = cast(eventData["Element"].GetPtr()).checked; + UpdateGrid(false); +} + +void ToggleGrid2DMode(StringHash eventType, VariantMap& eventData) +{ + grid2DMode = cast(eventData["Element"].GetPtr()).checked; + UpdateGrid(); +} + +void EditGridSize(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + gridSize = edit.text.ToInt(); + edit.text = String(gridSize); + UpdateGrid(); +} + +void EditGridSubdivisions(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + gridSubdivisions = edit.text.ToInt(); + edit.text = String(gridSubdivisions); + UpdateGrid(); +} + +void EditGridScale(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + gridScale = edit.text.ToFloat(); + edit.text = String(gridScale); + UpdateGrid(false); +} + +void EditGridColor(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + gridColor = Color(gridColorEditR.text.ToFloat(), gridColorEditG.text.ToFloat(), gridColorEditB.text.ToFloat()); + if (edit.name == "GridColor.r") + edit.text = String(gridColor.r); + else if (edit.name == "GridColor.g") + edit.text = String(gridColor.g); + else if (edit.name == "GridColor.b") + edit.text = String(gridColor.b); + UpdateGrid(); +} + +void EditGridSubdivisionColor(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + gridSubdivisionColor = Color(gridSubdivisionColorEditR.text.ToFloat(), gridSubdivisionColorEditG.text.ToFloat(), gridSubdivisionColorEditB.text.ToFloat()); + if (edit.name == "GridSubdivisionColor.r") + edit.text = String(gridSubdivisionColor.r); + else if (edit.name == "GridSubdivisionColor.g") + edit.text = String(gridSubdivisionColor.g); + else if (edit.name == "GridSubdivisionColor.b") + edit.text = String(gridSubdivisionColor.b); + UpdateGrid(); +} diff --git a/bin/Data/Scripts/Editor/EditorResourceBrowser.as b/bin/Data/Scripts/Editor/EditorResourceBrowser.as new file mode 100644 index 0000000..63ead73 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorResourceBrowser.as @@ -0,0 +1,1692 @@ +UIElement@ browserWindow; +Window@ browserFilterWindow; +ListView@ browserDirList; +ListView@ browserFileList; +LineEdit@ browserSearch; +BrowserFile@ browserDragFile; +Node@ browserDragNode; +Component@ browserDragComponent; +View3D@ resourceBrowserPreview; +Scene@ resourcePreviewScene; +Node@ resourcePreviewNode; +Node@ resourcePreviewCameraNode; +Node@ resourcePreviewLightNode; +Light@ resourcePreviewLight; +int browserSearchSortMode = 0; + +BrowserDir@ rootDir; +Array browserFiles; +Dictionary browserDirs; +Array activeResourceTypeFilters; +Array activeResourceDirFilters; + +Array browserFilesToScan; +const uint BROWSER_WORKER_ITEMS_PER_TICK = 10; +const uint BROWSER_SEARCH_LIMIT = 50; +const int BROWSER_SORT_MODE_ALPHA = 1; +const int BROWSER_SORT_MODE_SEARCH = 2; + +const int RESOURCE_TYPE_UNUSABLE = -2; +const int RESOURCE_TYPE_UNKNOWN = -1; +const int RESOURCE_TYPE_NOTSET = 0; +const int RESOURCE_TYPE_SCENE = 1; +const int RESOURCE_TYPE_SCRIPTFILE = 2; +const int RESOURCE_TYPE_MODEL = 3; +const int RESOURCE_TYPE_MATERIAL = 4; +const int RESOURCE_TYPE_ANIMATION = 5; +const int RESOURCE_TYPE_IMAGE = 6; +const int RESOURCE_TYPE_SOUND = 7; +const int RESOURCE_TYPE_TEXTURE = 8; +const int RESOURCE_TYPE_FONT = 9; +const int RESOURCE_TYPE_PREFAB = 10; +const int RESOURCE_TYPE_TECHNIQUE = 11; +const int RESOURCE_TYPE_PARTICLEEFFECT = 12; +const int RESOURCE_TYPE_UIELEMENT = 13; +const int RESOURCE_TYPE_UIELEMENTS = 14; +const int RESOURCE_TYPE_ANIMATION_SETTINGS = 15; +const int RESOURCE_TYPE_RENDERPATH = 16; +const int RESOURCE_TYPE_TEXTURE_ATLAS = 17; +const int RESOURCE_TYPE_2D_PARTICLE_EFFECT = 18; +const int RESOURCE_TYPE_TEXTURE_3D = 19; +const int RESOURCE_TYPE_CUBEMAP = 20; +const int RESOURCE_TYPE_PARTICLEEMITTER = 21; +const int RESOURCE_TYPE_2D_ANIMATION_SET = 22; +const int RESOURCE_TYPE_GENERIC_XML = 23; +const int RESOURCE_TYPE_GENERIC_JSON = 24; + +// any resource type > 0 is valid +const int NUMBER_OF_VALID_RESOURCE_TYPES = 24; + +const StringHash XML_TYPE_SCENE("scene"); +const StringHash XML_TYPE_NODE("node"); +const StringHash XML_TYPE_MATERIAL("material"); +const StringHash XML_TYPE_TECHNIQUE("technique"); +const StringHash XML_TYPE_PARTICLEEFFECT("particleeffect"); +const StringHash XML_TYPE_PARTICLEEMITTER("particleemitter"); +const StringHash XML_TYPE_TEXTURE("texture"); +const StringHash XML_TYPE_ELEMENT("element"); +const StringHash XML_TYPE_ELEMENTS("elements"); +const StringHash XML_TYPE_ANIMATION_SETTINGS("animation"); +const StringHash XML_TYPE_RENDERPATH("renderpath"); +const StringHash XML_TYPE_TEXTURE_ATLAS("TextureAtlas"); +const StringHash XML_TYPE_2D_PARTICLE_EFFECT("particleEmitterConfig"); +const StringHash XML_TYPE_TEXTURE_3D("texture3d"); +const StringHash XML_TYPE_CUBEMAP("cubemap"); +const StringHash XML_TYPE_SPRITER_DATA("spriter_data"); +const StringHash XML_TYPE_GENERIC("xml"); + +const StringHash JSON_TYPE_SCENE("scene"); +const StringHash JSON_TYPE_NODE("node"); +const StringHash JSON_TYPE_MATERIAL("material"); +const StringHash JSON_TYPE_TECHNIQUE("technique"); +const StringHash JSON_TYPE_PARTICLEEFFECT("particleeffect"); +const StringHash JSON_TYPE_PARTICLEEMITTER("particleemitter"); +const StringHash JSON_TYPE_TEXTURE("texture"); +const StringHash JSON_TYPE_ELEMENT("element"); +const StringHash JSON_TYPE_ELEMENTS("elements"); +const StringHash JSON_TYPE_ANIMATION_SETTINGS("animation"); +const StringHash JSON_TYPE_RENDERPATH("renderpath"); +const StringHash JSON_TYPE_TEXTURE_ATLAS("TextureAtlas"); +const StringHash JSON_TYPE_2D_PARTICLE_EFFECT("particleEmitterConfig"); +const StringHash JSON_TYPE_TEXTURE_3D("texture3d"); +const StringHash JSON_TYPE_CUBEMAP("cubemap"); +const StringHash JSON_TYPE_SPRITER_DATA("spriter_data"); +const StringHash JSON_TYPE_GENERIC("json"); + +const StringHash BINARY_TYPE_SCENE("USCN"); +const StringHash BINARY_TYPE_PACKAGE("UPAK"); +const StringHash BINARY_TYPE_COMPRESSED_PACKAGE("ULZ4"); +const StringHash BINARY_TYPE_ANGELSCRIPT("ASBC"); +const StringHash BINARY_TYPE_MODEL("UMDL"); +const StringHash BINARY_TYPE_MODEL2("UMD2"); +const StringHash BINARY_TYPE_SHADER("USHD"); +const StringHash BINARY_TYPE_ANIMATION("UANI"); + +const StringHash EXTENSION_TYPE_TTF(".ttf"); +const StringHash EXTENSION_TYPE_OTF(".otf"); +const StringHash EXTENSION_TYPE_OGG(".ogg"); +const StringHash EXTENSION_TYPE_WAV(".wav"); +const StringHash EXTENSION_TYPE_DDS(".dds"); +const StringHash EXTENSION_TYPE_PNG(".png"); +const StringHash EXTENSION_TYPE_JPG(".jpg"); +const StringHash EXTENSION_TYPE_JPEG(".jpeg"); +const StringHash EXTENSION_TYPE_HDR(".hdr"); +const StringHash EXTENSION_TYPE_BMP(".bmp"); +const StringHash EXTENSION_TYPE_TGA(".tga"); +const StringHash EXTENSION_TYPE_KTX(".ktx"); +const StringHash EXTENSION_TYPE_PVR(".pvr"); +const StringHash EXTENSION_TYPE_OBJ(".obj"); +const StringHash EXTENSION_TYPE_FBX(".fbx"); +const StringHash EXTENSION_TYPE_COLLADA(".dae"); +const StringHash EXTENSION_TYPE_BLEND(".blend"); +const StringHash EXTENSION_TYPE_ANGELSCRIPT(".as"); +const StringHash EXTENSION_TYPE_LUASCRIPT(".lua"); +const StringHash EXTENSION_TYPE_HLSL(".hlsl"); +const StringHash EXTENSION_TYPE_GLSL(".glsl"); +const StringHash EXTENSION_TYPE_FRAGMENTSHADER(".frag"); +const StringHash EXTENSION_TYPE_VERTEXSHADER(".vert"); +const StringHash EXTENSION_TYPE_HTML(".html"); + +const StringHash TEXT_VAR_FILE_ID("browser_file_id"); +const StringHash TEXT_VAR_DIR_ID("browser_dir_id"); +const StringHash TEXT_VAR_RESOURCE_TYPE("resource_type"); +const StringHash TEXT_VAR_RESOURCE_DIR_ID("resource_dir_id"); + +const int BROWSER_FILE_SOURCE_RESOURCE_DIR = 1; + +uint browserDirIndex = 1; +uint browserFileIndex = 1; +BrowserDir@ selectedBrowserDirectory; +BrowserFile@ selectedBrowserFile; +Text@ browserStatusMessage; +Text@ browserResultsMessage; +bool ignoreRefreshBrowserResults = false; +String resourceDirsCache; + +void CreateResourceBrowser() +{ + if (browserWindow !is null) return; + + CreateResourceBrowserUI(); + InitResourceBrowserPreview(); + RebuildResourceDatabase(); +} + +void RebuildResourceDatabase() +{ + if (browserWindow is null) + return; + + String newResourceDirsCache = Join(cache.resourceDirs, ';'); + ScanResourceDirectories(); + if (newResourceDirsCache != resourceDirsCache) + { + resourceDirsCache = newResourceDirsCache; + PopulateResourceDirFilters(); + } + PopulateBrowserDirectories(); + PopulateResourceBrowserFilesByDirectory(rootDir); +} + +void ScanResourceDirectories() +{ + browserDirs.Clear(); + browserFiles.Clear(); + browserFilesToScan.Clear(); + + rootDir = BrowserDir(""); + browserDirs.Set("", @rootDir); + + // collect all of the items and sort them afterwards + for(uint i=0; i < cache.resourceDirs.length; ++i) + { + if (activeResourceDirFilters.Find(i) > -1) + continue; + + ScanResourceDir(i); + } +} + +// used to stop ui from blocking while determining file types +void DoResourceBrowserWork() +{ + if (browserFilesToScan.length == 0) + return; + + int counter = 0; + bool updateBrowserUI = false; + BrowserFile@ scanItem = browserFilesToScan[0]; + while(counter < BROWSER_WORKER_ITEMS_PER_TICK) + { + scanItem.DetermainResourceType(); + + // next + browserFilesToScan.Erase(0); + if (browserFilesToScan.length > 0) + @scanItem = browserFilesToScan[0]; + else + break; + counter++; + } + + if (browserFilesToScan.length > 0) + browserStatusMessage.text = localization.Get("Files left to scan: " )+ browserFilesToScan.length; + else + browserStatusMessage.text = localization.Get("Scan complete"); + +} + +void CreateResourceBrowserUI() +{ + browserWindow = LoadEditorUI("UI/EditorResourceBrowser.xml"); + browserDirList = browserWindow.GetChild("DirectoryList", true); + browserFileList = browserWindow.GetChild("FileList", true); + browserSearch = browserWindow.GetChild("Search", true); + browserStatusMessage = browserWindow.GetChild("StatusMessage", true); + browserResultsMessage = browserWindow.GetChild("ResultsMessage", true); + // browserWindow.visible = false; + browserWindow.opacity = uiMaxOpacity; + + browserFilterWindow = LoadEditorUI("UI/EditorResourceFilterWindow.xml"); + CreateResourceFilterUI(); + HideResourceFilterWindow(); + + int height = Min(ui.root.height / 4, 300); + browserWindow.SetSize(900, height); + browserWindow.SetPosition(35, ui.root.height - height - 25); + + CloseContextMenu(); + ui.root.AddChild(browserWindow); + ui.root.AddChild(browserFilterWindow); + + SubscribeToEvent(browserWindow.GetChild("CloseButton", true), "Released", "HideResourceBrowserWindow"); + SubscribeToEvent(browserWindow.GetChild("RescanButton", true), "Released", "HandleRescanResourceBrowserClick"); + SubscribeToEvent(browserWindow.GetChild("FilterButton", true), "Released", "ToggleResourceFilterWindow"); + SubscribeToEvent(browserDirList, "SelectionChanged", "HandleResourceBrowserDirListSelectionChange"); + SubscribeToEvent(browserSearch, "TextChanged", "HandleResourceBrowserSearchTextChange"); + SubscribeToEvent(browserFileList, "ItemClicked", "HandleBrowserFileClick"); + SubscribeToEvent(browserFileList, "SelectionChanged", "HandleResourceBrowserFileListSelectionChange"); + SubscribeToEvent(cache, "FileChanged", "HandleFileChanged"); +} + +void CreateResourceFilterUI() +{ + UIElement@ options = browserFilterWindow.GetChild("TypeOptions", true); + CheckBox@ toggleAllTypes = browserFilterWindow.GetChild("ToggleAllTypes", true); + CheckBox@ toggleAllResourceDirs = browserFilterWindow.GetChild("ToggleAllResourceDirs", true); + SubscribeToEvent(toggleAllTypes, "Toggled", "HandleResourceTypeFilterToggleAllTypesToggled"); + SubscribeToEvent(toggleAllResourceDirs, "Toggled", "HandleResourceDirFilterToggleAllTypesToggled"); + SubscribeToEvent(browserFilterWindow.GetChild("CloseButton", true), "Released", "HideResourceFilterWindow"); + + int columns = 2; + UIElement@ col1 = browserFilterWindow.GetChild("TypeFilterColumn1", true); + UIElement@ col2 = browserFilterWindow.GetChild("TypeFilterColumn2", true); + + // use array to get sort of items + Array sorted; + for (int i=1; i <= NUMBER_OF_VALID_RESOURCE_TYPES; ++i) + sorted.Push(ResourceType(i, ResourceTypeName(i))); + + // 2 unknown types are reserved for the top, the rest are alphabetized + sorted.Sort(); + sorted.Insert(0, ResourceType(RESOURCE_TYPE_UNKNOWN, ResourceTypeName(RESOURCE_TYPE_UNKNOWN)) ); + sorted.Insert(0, ResourceType(RESOURCE_TYPE_UNUSABLE, ResourceTypeName(RESOURCE_TYPE_UNUSABLE)) ); + uint halfColumns = uint( Ceil( float(sorted.length) / float(columns) ) ); + + for (uint i = 0; i < sorted.length; ++i) + { + ResourceType@ type = sorted[i]; + UIElement@ resourceTypeHolder = UIElement(); + if (i < halfColumns) + col1.AddChild(resourceTypeHolder); + else + col2.AddChild(resourceTypeHolder); + + resourceTypeHolder.layoutMode = LM_HORIZONTAL; + resourceTypeHolder.layoutSpacing = 4; + + Text@ label = Text(); + label.style = "EditorAttributeText"; + label.text = type.name; + CheckBox@ checkbox = CheckBox(); + checkbox.name = type.id; + checkbox.SetStyleAuto(); + checkbox.vars[TEXT_VAR_RESOURCE_TYPE] = i; + checkbox.checked = true; + SubscribeToEvent(checkbox, "Toggled", "HandleResourceTypeFilterToggled"); + + resourceTypeHolder.AddChild(checkbox); + resourceTypeHolder.AddChild(label); + } +} + +void CreateDirList(BrowserDir@ dir, UIElement@ parentUI = null) +{ + Text@ dirText = Text(); + browserDirList.InsertItem(browserDirList.numItems, dirText, parentUI); + dirText.style = "FileSelectorListText"; + dirText.text = dir.resourceKey.empty ? localization.Get("Root") : dir.name; + dirText.name = dir.resourceKey; + dirText.vars[TEXT_VAR_DIR_ID] = dir.resourceKey; + + // Sort directories alphetically + browserSearchSortMode = BROWSER_SORT_MODE_ALPHA; + dir.children.Sort(); + + for(uint i=0; i 0) + fileText.dragDropMode = DD_SOURCE; + + { + Text@ text = Text(); + fileText.AddChild(text); + text.style = "FileSelectorListText"; + text.text = file.fullname; + text.name = file.resourceKey; + } + + { + Text@ text = Text(); + fileText.AddChild(text); + text.style = "FileSelectorListText"; + text.text = file.ResourceTypeName(); + } + + if (file.resourceType == RESOURCE_TYPE_MATERIAL || + file.resourceType == RESOURCE_TYPE_MODEL || + file.resourceType == RESOURCE_TYPE_PARTICLEEFFECT || + file.resourceType == RESOURCE_TYPE_PREFAB + ) + { + SubscribeToEvent(fileText, "DragBegin", "HandleBrowserFileDragBegin"); + SubscribeToEvent(fileText, "DragEnd", "HandleBrowserFileDragEnd"); + } + +} + +void InitResourceBrowserPreview() +{ + resourcePreviewScene = Scene("PreviewScene"); + resourcePreviewScene.CreateComponent("Octree"); + PhysicsWorld@ physicsWorld = resourcePreviewScene.CreateComponent("PhysicsWorld"); + physicsWorld.enabled = false; + physicsWorld.gravity = Vector3(0.0, 0.0, 0.0); + + Node@ zoneNode = resourcePreviewScene.CreateChild("Zone"); + Zone@ zone = zoneNode.CreateComponent("Zone"); + zone.boundingBox = BoundingBox(-1000, 1000); + zone.ambientColor = Color(0.15, 0.15, 0.15); + zone.fogColor = Color(0, 0, 0); + zone.fogStart = 10.0; + zone.fogEnd = 100.0; + + resourcePreviewCameraNode = resourcePreviewScene.CreateChild("PreviewCamera"); + resourcePreviewCameraNode.position = Vector3(0, 0, -1.5); + Camera@ camera = resourcePreviewCameraNode.CreateComponent("Camera"); + camera.nearClip = 0.1f; + camera.farClip = 100.0f; + + resourcePreviewLightNode = resourcePreviewScene.CreateChild("PreviewLight"); + resourcePreviewLightNode.direction = Vector3(0.5, -0.5, 0.5); + resourcePreviewLight = resourcePreviewLightNode.CreateComponent("Light"); + resourcePreviewLight.lightType = LIGHT_DIRECTIONAL; + resourcePreviewLight.specularIntensity = 0.5; + + resourceBrowserPreview = browserWindow.GetChild("ResourceBrowserPreview", true); + resourceBrowserPreview.SetFixedHeight(200); + resourceBrowserPreview.SetFixedWidth(266); + resourceBrowserPreview.SetView(resourcePreviewScene, camera); + resourceBrowserPreview.autoUpdate = false; + + resourcePreviewNode = resourcePreviewScene.CreateChild("PreviewNodeContainer"); + + SubscribeToEvent(resourceBrowserPreview, "DragMove", "RotateResourceBrowserPreview"); + + RefreshBrowserPreview(); +} + +// Opens a contextual menu based on what resource item was actioned +void HandleBrowserFileClick(StringHash eventType, VariantMap& eventData) +{ + if (eventData["Button"].GetInt() != MOUSEB_RIGHT) + return; + + UIElement@ uiElement = eventData["Item"].GetPtr(); + BrowserFile@ file = GetBrowserFileFromUIElement(uiElement); + + if (file is null) + return; + + Array actions; + if (file.resourceType == RESOURCE_TYPE_MATERIAL) + { + actions.Push(CreateBrowserFileActionMenu("Edit", "HandleBrowserEditResource", file)); + } + else if (file.resourceType == RESOURCE_TYPE_MODEL) + { + actions.Push(CreateBrowserFileActionMenu("Instance Animated Model", "HandleBrowserInstantiateAnimatedModel", file)); + actions.Push(CreateBrowserFileActionMenu("Instance Static Model", "HandleBrowserInstantiateStaticModel", file)); + } + else if (file.resourceType == RESOURCE_TYPE_PREFAB) + { + actions.Push(CreateBrowserFileActionMenu("Instance Prefab", "HandleBrowserInstantiatePrefab", file)); + actions.Push(CreateBrowserFileActionMenu("Instance in Spawner", "HandleBrowserInstantiateInSpawnEditor", file)); + } + else if (file.fileType == EXTENSION_TYPE_OBJ || + file.fileType == EXTENSION_TYPE_COLLADA || + file.fileType == EXTENSION_TYPE_FBX || + file.fileType == EXTENSION_TYPE_BLEND) + { + actions.Push(CreateBrowserFileActionMenu("Import Model", "HandleBrowserImportModel", file)); + actions.Push(CreateBrowserFileActionMenu("Import Scene", "HandleBrowserImportScene", file)); + } + else if (file.resourceType == RESOURCE_TYPE_UIELEMENT) + { + actions.Push(CreateBrowserFileActionMenu("Open UI Layout", "HandleBrowserOpenUILayout", file)); + } + else if (file.resourceType == RESOURCE_TYPE_SCENE) + { + actions.Push(CreateBrowserFileActionMenu("Load Scene", "HandleBrowserLoadScene", file)); + } + else if (file.resourceType == RESOURCE_TYPE_SCRIPTFILE) + { + actions.Push(CreateBrowserFileActionMenu("Execute Script", "HandleBrowserRunScript", file)); + } + else if (file.resourceType == RESOURCE_TYPE_PARTICLEEFFECT) + { + actions.Push(CreateBrowserFileActionMenu("Edit", "HandleBrowserEditResource", file)); + } + + actions.Push(CreateBrowserFileActionMenu("Open", "HandleBrowserOpenResource", file)); + + ActivateContextMenu(actions); +} + +BrowserDir@ GetBrowserDir(String path) +{ + BrowserDir@ browserDir; + browserDirs.Get(path, @browserDir); + return browserDir; +} + +// Makes sure the entire directory tree exists and new dir is linked to parent +BrowserDir@ InitBrowserDir(String path) +{ + BrowserDir@ browserDir; + if (browserDirs.Get(path, @browserDir)) + return browserDir; + + Array parts = path.Split('/'); + Array finishedParts; + if (parts.length > 0) + { + BrowserDir@ parent = rootDir; + for( uint i = 0; i < parts.length; ++i ) + { + finishedParts.Push(parts[i]); + String currentPath = Join(finishedParts, "/"); + if (!browserDirs.Get(currentPath, @browserDir)) + { + browserDir = BrowserDir(currentPath); + browserDirs.Set(currentPath, @browserDir); + parent.children.Push(browserDir); + } + @parent = browserDir; + } + return browserDir; + } + return null; +} + +void ScanResourceDir(uint resourceDirIndex) +{ + String resourceDir = cache.resourceDirs[resourceDirIndex]; + ScanResourceDirFiles("", resourceDirIndex); + Array dirs = fileSystem.ScanDir(resourceDir, "*", SCAN_DIRS, true); + for (uint i=0; i < dirs.length; ++i) + { + String path = dirs[i]; + if (path.EndsWith(".")) + continue; + + InitBrowserDir(path); + ScanResourceDirFiles(path, resourceDirIndex); + } +} + +void ScanResourceDirFiles(String path, uint resourceDirIndex) +{ + String fullPath = cache.resourceDirs[resourceDirIndex] + path; + if (!fileSystem.DirExists(fullPath)) + return; + + BrowserDir@ dir = GetBrowserDir(path); + + if (dir is null) + return; + + // get files in directory + Array dirFiles = fileSystem.ScanDir(fullPath, "*.*", SCAN_FILES, false); + + // add new files + for (uint x=0; x < dirFiles.length; x++) + { + String filename = dirFiles[x]; + BrowserFile@ browserFile = dir.AddFile(filename, resourceDirIndex, BROWSER_FILE_SOURCE_RESOURCE_DIR); + browserFiles.Push(browserFile); + browserFilesToScan.Push(browserFile); + } +} + +bool ToggleResourceBrowserWindow() +{ + if (browserWindow.visible == false) + ShowResourceBrowserWindow(); + else + HideResourceBrowserWindow(); + return true; +} + +void ShowResourceBrowserWindow() +{ + browserWindow.visible = true; + browserWindow.BringToFront(); + ui.focusElement = browserSearch; +} + +void HideResourceBrowserWindow() +{ + browserWindow.visible = false; +} + +void ToggleResourceFilterWindow() +{ + if (browserFilterWindow.visible) + HideResourceFilterWindow(); + else + ShowResourceFilterWindow(); +} +void HideResourceFilterWindow() +{ + browserFilterWindow.visible = false; +} + +void ShowResourceFilterWindow() +{ + int x = browserWindow.position.x + browserWindow.width - browserFilterWindow.width; + int y = browserWindow.position.y - browserFilterWindow.height - 1; + browserFilterWindow.position = IntVector2(x,y); + browserFilterWindow.visible = true; + browserFilterWindow.BringToFront(); +} + +void PopulateResourceDirFilters() +{ + UIElement@ resourceDirs = browserFilterWindow.GetChild("DirFilters", true); + resourceDirs.RemoveAllChildren(); + activeResourceDirFilters.Clear(); + for (uint i=0; i < cache.resourceDirs.length; ++i) + { + UIElement@ resourceDirHolder = UIElement(); + resourceDirs.AddChild(resourceDirHolder); + resourceDirHolder.layoutMode = LM_HORIZONTAL; + resourceDirHolder.layoutSpacing = 4; + resourceDirHolder.SetFixedHeight(16); + + Text@ label = Text(); + label.style = "EditorAttributeText"; + label.text = cache.resourceDirs[i].Replaced(fileSystem.programDir, ""); + CheckBox@ checkbox = CheckBox(); + checkbox.name = i; + checkbox.SetStyleAuto(); + checkbox.vars[TEXT_VAR_RESOURCE_DIR_ID] = i; + checkbox.checked = true; + SubscribeToEvent(checkbox, "Toggled", "HandleResourceDirFilterToggled"); + + + resourceDirHolder.AddChild(checkbox); + resourceDirHolder.AddChild(label); + } + +} + +void PopulateBrowserDirectories() +{ + browserDirList.RemoveAllItems(); + CreateDirList(rootDir); + browserDirList.selection = 0; +} + +void PopulateResourceBrowserFilesByDirectory(BrowserDir@ dir) +{ + @selectedBrowserDirectory = dir; + browserFileList.RemoveAllItems(); + if (dir is null) return; + + Array files; + for(uint x=0; x < dir.files.length; x++) + { + BrowserFile@ file = dir.files[x]; + + if (activeResourceTypeFilters.Find(file.resourceType) == -1) + files.Push(file); + } + + // Sort alphetically + browserSearchSortMode = BROWSER_SORT_MODE_ALPHA; + files.Sort(); + PopulateResourceBrowserResults(files); + browserResultsMessage.text = localization.Get("Showing files: ") + files.length; +} + + +void PopulateResourceBrowserBySearch() +{ + String query = browserSearch.text; + + Array scores; + Array scored; + Array filtered; + { + BrowserFile@ file; + for(uint x=0; x < browserFiles.length; x++) + { + @file = browserFiles[x]; + file.sortScore = -1; + if (activeResourceTypeFilters.Find(file.resourceType) > -1) + continue; + + if (activeResourceDirFilters.Find(file.resourceSourceIndex) > -1) + continue; + + int find = file.fullname.Find(query, 0, false); + if (find > -1) + { + int fudge = query.length - file.fullname.length; + int score = find * int(Abs(fudge*2)) + int(Abs(fudge)); + file.sortScore = score; + scored.Push(file); + scores.Push(score); + } + } + } + + // cut this down for a faster sort + if (scored.length > BROWSER_SEARCH_LIMIT) + { + scores.Sort(); + int scoreThreshold = scores[BROWSER_SEARCH_LIMIT]; + BrowserFile@ file; + for(uint x=0;x@ files) +{ + browserFileList.RemoveAllItems(); + for(uint i=0; i < files.length; ++i) + CreateFileList(files[i]); +} + +void RefreshBrowserResults() +{ + if (browserSearch.text.empty) + { + browserDirList.visible = true; + PopulateResourceBrowserFilesByDirectory(selectedBrowserDirectory); + } + else + { + browserDirList.visible = false; + PopulateResourceBrowserBySearch(); + } +} + +void HandleResourceTypeFilterToggleAllTypesToggled(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ checkbox = eventData["Element"].GetPtr(); + UIElement@ filterHolder = browserFilterWindow.GetChild("TypeFilters", true); + Array children = filterHolder.GetChildren(true); + + ignoreRefreshBrowserResults = true; + for(uint i=0; i < children.length; ++i) + { + CheckBox@ filter = children[i]; + if (filter !is null) + filter.checked = checkbox.checked; + } + ignoreRefreshBrowserResults = false; + RefreshBrowserResults(); +} + +void HandleResourceTypeFilterToggled(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ checkbox = eventData["Element"].GetPtr(); + if (!checkbox.vars.Contains(TEXT_VAR_RESOURCE_TYPE)) + return; + + int resourceType = checkbox.GetVar(TEXT_VAR_RESOURCE_TYPE).GetInt(); + int find = activeResourceTypeFilters.Find(resourceType); + + if (checkbox.checked && find != -1) + activeResourceTypeFilters.Erase(find); + else if (!checkbox.checked && find == -1) + activeResourceTypeFilters.Push(resourceType); + + if (ignoreRefreshBrowserResults == false) + RefreshBrowserResults(); +} + +void HandleResourceDirFilterToggleAllTypesToggled(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ checkbox = eventData["Element"].GetPtr(); + UIElement@ filterHolder = browserFilterWindow.GetChild("DirFilters", true); + Array children = filterHolder.GetChildren(true); + + ignoreRefreshBrowserResults = true; + for(uint i=0; i < children.length; ++i) + { + CheckBox@ filter = children[i]; + if (filter !is null) + filter.checked = checkbox.checked; + } + ignoreRefreshBrowserResults = false; + RebuildResourceDatabase(); +} + +void HandleResourceDirFilterToggled(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ checkbox = eventData["Element"].GetPtr(); + if (!checkbox.vars.Contains(TEXT_VAR_RESOURCE_DIR_ID)) + return; + + int resourceDir = checkbox.GetVar(TEXT_VAR_RESOURCE_DIR_ID).GetInt(); + int find = activeResourceDirFilters.Find(resourceDir); + + if (checkbox.checked && find != -1) + activeResourceDirFilters.Erase(find); + else if (!checkbox.checked && find == -1) + activeResourceDirFilters.Push(resourceDir); + + if (ignoreRefreshBrowserResults == false) + RebuildResourceDatabase(); +} + +void HandleRescanResourceBrowserClick(StringHash eventType, VariantMap& eventData) +{ + RebuildResourceDatabase(); +} + +void HandleResourceBrowserDirListSelectionChange(StringHash eventType, VariantMap& eventData) +{ + if (browserDirList.selection == M_MAX_UNSIGNED) + return; + + UIElement@ uiElement = browserDirList.GetItems()[browserDirList.selection]; + BrowserDir@ dir = GetBrowserDir(uiElement.vars[TEXT_VAR_DIR_ID].GetString()); + if (dir is null) + return; + + PopulateResourceBrowserFilesByDirectory(dir); +} + +void HandleResourceBrowserFileListSelectionChange(StringHash eventType, VariantMap& eventData) +{ + if (browserFileList.selection == M_MAX_UNSIGNED) + return; + + UIElement@ uiElement = browserFileList.GetItems()[browserFileList.selection]; + BrowserFile@ file = GetBrowserFileFromUIElement(uiElement); + if (file is null) + return; + + if (resourcePreviewNode !is null) + resourcePreviewNode.Remove(); + + resourcePreviewNode = resourcePreviewScene.CreateChild("PreviewNodeContainer"); + CreateResourcePreview(file.GetFullPath(), resourcePreviewNode); + + if (resourcePreviewNode !is null) + { + Array boxes; + Array staticModels = resourcePreviewNode.GetComponents("StaticModel", true); + Array animatedModels = resourcePreviewNode.GetComponents("AnimatedModel", true); + + for (uint i = 0; i < staticModels.length; ++i) + boxes.Push(cast(staticModels[i]).worldBoundingBox); + + for (uint i = 0; i < animatedModels.length; ++i) + boxes.Push(cast(animatedModels[i]).worldBoundingBox); + + if (boxes.length > 0) + { + Vector3 camPosition = Vector3(0.0, 0.0, -1.2); + BoundingBox biggestBox = boxes[0]; + for (uint i = 1; i < boxes.length; ++i) + { + if (boxes[i].size.length > biggestBox.size.length) + biggestBox = boxes[i]; + } + resourcePreviewCameraNode.position = biggestBox.center + camPosition * biggestBox.size.length; + } + + resourcePreviewScene.AddChild(resourcePreviewNode); + RefreshBrowserPreview(); + } +} + +void HandleResourceBrowserSearchTextChange(StringHash eventType, VariantMap& eventData) +{ + RefreshBrowserResults(); +} + +BrowserFile@ GetBrowserFileFromId(uint id) +{ + if (id == 0) + return null; + + BrowserFile@ file; + for(uint i=0; i(GetDrawableAtMousePostion()); + if (model !is null) + { + AssignMaterial(model, browserDragFile.resourceKey); + } + } + else if (browserDragFile.resourceType == RESOURCE_TYPE_PREFAB) + { + LoadNode(browserDragFile.GetFullPath(), null, true); + } + else if (browserDragFile.resourceType == RESOURCE_TYPE_MODEL) + { + Node@ createdNode = CreateNode(REPLICATED, true); + Model@ model = cache.GetResource("Model", browserDragFile.resourceKey); + if (model.skeleton.numBones > 0) + { + AnimatedModel@ am = createdNode.CreateComponent("AnimatedModel"); + am.model = model; + } + else + { + StaticModel@ sm = createdNode.CreateComponent("StaticModel"); + sm.model = model; + } + + AdjustNodePositionByAABB(createdNode); + } + + browserDragFile = null; + browserDragComponent = null; + browserDragNode = null; +} + +void HandleFileChanged(StringHash eventType, VariantMap& eventData) +{ + String filename = eventData["FileName"].GetString(); + BrowserFile@ file = GetBrowserFileFromPath(filename); + + if (file is null) + { + // TODO: new file logic when watchers are supported + return; + } + else + { + file.FileChanged(); + } +} + +Menu@ CreateBrowserFileActionMenu(String text, String handler, BrowserFile@ browserFile = null) +{ + Menu@ menu = CreateContextMenuItem(text, handler); + if (browserFile !is null) + menu.vars[TEXT_VAR_FILE_ID] = browserFile.id; + + return menu; +} + +int GetResourceType(String path) +{ + StringHash fileType; + return GetResourceType(path, fileType); +} + +int GetResourceType(String path, StringHash &out fileType, bool useCache = false) +{ + if (GetExtensionType(path, fileType) || GetBinaryType(path, fileType, useCache) || GetXmlType(path, fileType, useCache)) + return GetResourceType(fileType); + + return RESOURCE_TYPE_UNKNOWN; +} + +int GetResourceType(StringHash fileType) +{ + // Binary filetypes + if (fileType == BINARY_TYPE_SCENE) + return RESOURCE_TYPE_SCENE; + else if (fileType == BINARY_TYPE_PACKAGE) + return RESOURCE_TYPE_UNUSABLE; + else if (fileType == BINARY_TYPE_COMPRESSED_PACKAGE) + return RESOURCE_TYPE_UNUSABLE; + else if (fileType == BINARY_TYPE_ANGELSCRIPT) + return RESOURCE_TYPE_SCRIPTFILE; + else if (fileType == BINARY_TYPE_MODEL || fileType == BINARY_TYPE_MODEL2) + return RESOURCE_TYPE_MODEL; + else if (fileType == BINARY_TYPE_SHADER) + return RESOURCE_TYPE_UNUSABLE; + else if (fileType == BINARY_TYPE_ANIMATION) + return RESOURCE_TYPE_ANIMATION; + + // XML filetypes + else if (fileType == XML_TYPE_SCENE) + return RESOURCE_TYPE_SCENE; + else if (fileType == XML_TYPE_NODE) + return RESOURCE_TYPE_PREFAB; + else if(fileType == XML_TYPE_MATERIAL) + return RESOURCE_TYPE_MATERIAL; + else if(fileType == XML_TYPE_TECHNIQUE) + return RESOURCE_TYPE_TECHNIQUE; + else if(fileType == XML_TYPE_PARTICLEEFFECT) + return RESOURCE_TYPE_PARTICLEEFFECT; + else if(fileType == XML_TYPE_PARTICLEEMITTER) + return RESOURCE_TYPE_PARTICLEEMITTER; + else if(fileType == XML_TYPE_TEXTURE) + return RESOURCE_TYPE_TEXTURE; + else if(fileType == XML_TYPE_ELEMENT) + return RESOURCE_TYPE_UIELEMENT; + else if(fileType == XML_TYPE_ELEMENTS) + return RESOURCE_TYPE_UIELEMENTS; + else if (fileType == XML_TYPE_ANIMATION_SETTINGS) + return RESOURCE_TYPE_ANIMATION_SETTINGS; + else if (fileType == XML_TYPE_RENDERPATH) + return RESOURCE_TYPE_RENDERPATH; + else if (fileType == XML_TYPE_TEXTURE_ATLAS) + return RESOURCE_TYPE_TEXTURE_ATLAS; + else if (fileType == XML_TYPE_2D_PARTICLE_EFFECT) + return RESOURCE_TYPE_2D_PARTICLE_EFFECT; + else if (fileType == XML_TYPE_TEXTURE_3D) + return RESOURCE_TYPE_TEXTURE_3D; + else if (fileType == XML_TYPE_CUBEMAP) + return RESOURCE_TYPE_CUBEMAP; + else if (fileType == XML_TYPE_SPRITER_DATA) + return RESOURCE_TYPE_2D_ANIMATION_SET; + else if (fileType == XML_TYPE_GENERIC) + return RESOURCE_TYPE_GENERIC_XML; + + // JSON filetypes + else if (fileType == JSON_TYPE_SCENE) + return RESOURCE_TYPE_SCENE; + else if (fileType == JSON_TYPE_NODE) + return RESOURCE_TYPE_PREFAB; + else if(fileType == JSON_TYPE_MATERIAL) + return RESOURCE_TYPE_MATERIAL; + else if(fileType == JSON_TYPE_TECHNIQUE) + return RESOURCE_TYPE_TECHNIQUE; + else if(fileType == JSON_TYPE_PARTICLEEFFECT) + return RESOURCE_TYPE_PARTICLEEFFECT; + else if(fileType == JSON_TYPE_PARTICLEEMITTER) + return RESOURCE_TYPE_PARTICLEEMITTER; + else if(fileType == JSON_TYPE_TEXTURE) + return RESOURCE_TYPE_TEXTURE; + else if(fileType == JSON_TYPE_ELEMENT) + return RESOURCE_TYPE_UIELEMENT; + else if(fileType == JSON_TYPE_ELEMENTS) + return RESOURCE_TYPE_UIELEMENTS; + else if (fileType == JSON_TYPE_ANIMATION_SETTINGS) + return RESOURCE_TYPE_ANIMATION_SETTINGS; + else if (fileType == JSON_TYPE_RENDERPATH) + return RESOURCE_TYPE_RENDERPATH; + else if (fileType == JSON_TYPE_TEXTURE_ATLAS) + return RESOURCE_TYPE_TEXTURE_ATLAS; + else if (fileType == JSON_TYPE_2D_PARTICLE_EFFECT) + return RESOURCE_TYPE_2D_PARTICLE_EFFECT; + else if (fileType == JSON_TYPE_TEXTURE_3D) + return RESOURCE_TYPE_TEXTURE_3D; + else if (fileType == JSON_TYPE_CUBEMAP) + return RESOURCE_TYPE_CUBEMAP; + else if (fileType == JSON_TYPE_SPRITER_DATA) + return RESOURCE_TYPE_2D_ANIMATION_SET; + else if (fileType == JSON_TYPE_GENERIC) + return RESOURCE_TYPE_GENERIC_JSON; + + // Extension filetypes + else if (fileType == EXTENSION_TYPE_TTF) + return RESOURCE_TYPE_FONT; + else if (fileType == EXTENSION_TYPE_OTF) + return RESOURCE_TYPE_FONT; + else if (fileType == EXTENSION_TYPE_OGG) + return RESOURCE_TYPE_SOUND; + else if(fileType == EXTENSION_TYPE_WAV) + return RESOURCE_TYPE_SOUND; + else if(fileType == EXTENSION_TYPE_DDS) + return RESOURCE_TYPE_IMAGE; + else if(fileType == EXTENSION_TYPE_PNG) + return RESOURCE_TYPE_IMAGE; + else if(fileType == EXTENSION_TYPE_JPG) + return RESOURCE_TYPE_IMAGE; + else if(fileType == EXTENSION_TYPE_JPEG) + return RESOURCE_TYPE_IMAGE; + else if(fileType == EXTENSION_TYPE_HDR) + return RESOURCE_TYPE_IMAGE; + else if(fileType == EXTENSION_TYPE_BMP) + return RESOURCE_TYPE_IMAGE; + else if(fileType == EXTENSION_TYPE_TGA) + return RESOURCE_TYPE_IMAGE; + else if(fileType == EXTENSION_TYPE_KTX) + return RESOURCE_TYPE_IMAGE; + else if(fileType == EXTENSION_TYPE_PVR) + return RESOURCE_TYPE_IMAGE; + else if(fileType == EXTENSION_TYPE_OBJ) + return RESOURCE_TYPE_UNUSABLE; + else if(fileType == EXTENSION_TYPE_FBX) + return RESOURCE_TYPE_UNUSABLE; + else if(fileType == EXTENSION_TYPE_COLLADA) + return RESOURCE_TYPE_UNUSABLE; + else if(fileType == EXTENSION_TYPE_BLEND) + return RESOURCE_TYPE_UNUSABLE; + else if(fileType == EXTENSION_TYPE_ANGELSCRIPT) + return RESOURCE_TYPE_SCRIPTFILE; + else if(fileType == EXTENSION_TYPE_LUASCRIPT) + return RESOURCE_TYPE_SCRIPTFILE; + else if(fileType == EXTENSION_TYPE_HLSL) + return RESOURCE_TYPE_UNUSABLE; + else if(fileType == EXTENSION_TYPE_GLSL) + return RESOURCE_TYPE_UNUSABLE; + else if(fileType == EXTENSION_TYPE_FRAGMENTSHADER) + return RESOURCE_TYPE_UNUSABLE; + else if(fileType == EXTENSION_TYPE_VERTEXSHADER) + return RESOURCE_TYPE_UNUSABLE; + else if(fileType == EXTENSION_TYPE_HTML) + return RESOURCE_TYPE_UNUSABLE; + + return RESOURCE_TYPE_UNKNOWN; +} + +bool GetExtensionType(String path, StringHash &out fileType) +{ + StringHash type = StringHash(GetExtension(path)); + if (type == EXTENSION_TYPE_TTF) + fileType = EXTENSION_TYPE_TTF; + else if (type == EXTENSION_TYPE_OTF) + fileType = EXTENSION_TYPE_OTF; + else if (type == EXTENSION_TYPE_OGG) + fileType = EXTENSION_TYPE_OGG; + else if(type == EXTENSION_TYPE_WAV) + fileType = EXTENSION_TYPE_WAV; + else if(type == EXTENSION_TYPE_DDS) + fileType = EXTENSION_TYPE_DDS; + else if(type == EXTENSION_TYPE_PNG) + fileType = EXTENSION_TYPE_PNG; + else if(type == EXTENSION_TYPE_JPG) + fileType = EXTENSION_TYPE_JPG; + else if(type == EXTENSION_TYPE_JPEG) + fileType = EXTENSION_TYPE_JPEG; + else if(type == EXTENSION_TYPE_HDR) + fileType = EXTENSION_TYPE_HDR; + else if(type == EXTENSION_TYPE_BMP) + fileType = EXTENSION_TYPE_BMP; + else if(type == EXTENSION_TYPE_TGA) + fileType = EXTENSION_TYPE_TGA; + else if(type == EXTENSION_TYPE_KTX) + fileType = EXTENSION_TYPE_KTX; + else if(type == EXTENSION_TYPE_PVR) + fileType = EXTENSION_TYPE_PVR; + else if(type == EXTENSION_TYPE_OBJ) + fileType = EXTENSION_TYPE_OBJ; + else if(type == EXTENSION_TYPE_FBX) + fileType = EXTENSION_TYPE_FBX; + else if(type == EXTENSION_TYPE_COLLADA) + fileType = EXTENSION_TYPE_COLLADA; + else if(type == EXTENSION_TYPE_BLEND) + fileType = EXTENSION_TYPE_BLEND; + else if(type == EXTENSION_TYPE_ANGELSCRIPT) + fileType = EXTENSION_TYPE_ANGELSCRIPT; + else if(type == EXTENSION_TYPE_LUASCRIPT) + fileType = EXTENSION_TYPE_LUASCRIPT; + else if(type == EXTENSION_TYPE_HLSL) + fileType = EXTENSION_TYPE_HLSL; + else if(type == EXTENSION_TYPE_GLSL) + fileType = EXTENSION_TYPE_GLSL; + else if(type == EXTENSION_TYPE_FRAGMENTSHADER) + fileType = EXTENSION_TYPE_FRAGMENTSHADER; + else if(type == EXTENSION_TYPE_VERTEXSHADER) + fileType = EXTENSION_TYPE_VERTEXSHADER; + else if(type == EXTENSION_TYPE_HTML) + fileType = EXTENSION_TYPE_HTML; + else + return false; + + return true; +} + +bool GetBinaryType(String path, StringHash &out fileType, bool useCache = false) +{ + StringHash type; + if (useCache) + { + File@ file = cache.GetFile(path); + if (file is null) + return false; + + if (file.size == 0) + return false; + + type = StringHash(file.ReadFileID()); + } + else + { + File@ file = File(); + if (!file.Open(path)) + return false; + + if (file.size == 0) + return false; + + type = StringHash(file.ReadFileID()); + } + + if (type == BINARY_TYPE_SCENE) + fileType = BINARY_TYPE_SCENE; + else if (type == BINARY_TYPE_PACKAGE) + fileType = BINARY_TYPE_PACKAGE; + else if (type == BINARY_TYPE_COMPRESSED_PACKAGE) + fileType = BINARY_TYPE_COMPRESSED_PACKAGE; + else if (type == BINARY_TYPE_ANGELSCRIPT) + fileType = BINARY_TYPE_ANGELSCRIPT; + else if (type == BINARY_TYPE_MODEL || type == BINARY_TYPE_MODEL2) + fileType = BINARY_TYPE_MODEL; + else if (type == BINARY_TYPE_SHADER) + fileType = BINARY_TYPE_SHADER; + else if (type == BINARY_TYPE_ANIMATION) + fileType = BINARY_TYPE_ANIMATION; + else + return false; + + return true; +} + +bool GetXmlType(String path, StringHash &out fileType, bool useCache = false) +{ + if (GetFileName(path).length == 0) + return false; // .gitignore etc. + String extension = GetExtension(path); + if (extension == ".txt" || extension == ".json" || extension == ".icns" || extension == ".atlas") + return false; + + String name; + if (useCache) + { + XMLFile@ xml = cache.GetResource("XMLFile", path); + if (xml is null) + return false; + + name = xml.root.name; + } + else + { + File@ file = File(); + if (!file.Open(path)) + return false; + + if (file.size == 0) + return false; + + XMLFile@ xml = XMLFile(); + if (xml.Load(file)) + name = xml.root.name; + else + return false; + } + + bool found = false; + if (!name.empty) + { + found = true; + StringHash type = StringHash(name); + if (type == XML_TYPE_SCENE) + fileType = XML_TYPE_SCENE; + else if (type == XML_TYPE_NODE) + fileType = XML_TYPE_NODE; + else if(type == XML_TYPE_MATERIAL) + fileType = XML_TYPE_MATERIAL; + else if(type == XML_TYPE_TECHNIQUE) + fileType = XML_TYPE_TECHNIQUE; + else if(type == XML_TYPE_PARTICLEEFFECT) + fileType = XML_TYPE_PARTICLEEFFECT; + else if(type == XML_TYPE_PARTICLEEMITTER) + fileType = XML_TYPE_PARTICLEEMITTER; + else if(type == XML_TYPE_TEXTURE) + fileType = XML_TYPE_TEXTURE; + else if(type == XML_TYPE_ELEMENT) + fileType = XML_TYPE_ELEMENT; + else if(type == XML_TYPE_ELEMENTS) + fileType = XML_TYPE_ELEMENTS; + else if (type == XML_TYPE_ANIMATION_SETTINGS) + fileType = XML_TYPE_ANIMATION_SETTINGS; + else if (type == XML_TYPE_RENDERPATH) + fileType = XML_TYPE_RENDERPATH; + else if (type == XML_TYPE_TEXTURE_ATLAS) + fileType = XML_TYPE_TEXTURE_ATLAS; + else if (type == XML_TYPE_2D_PARTICLE_EFFECT) + fileType = XML_TYPE_2D_PARTICLE_EFFECT; + else if (type == XML_TYPE_TEXTURE_3D) + fileType = XML_TYPE_TEXTURE_3D; + else if (type == XML_TYPE_CUBEMAP) + fileType = XML_TYPE_CUBEMAP; + else if (type == XML_TYPE_SPRITER_DATA) + fileType = XML_TYPE_SPRITER_DATA; + else + fileType = XML_TYPE_GENERIC; + } + return found; +} + +String ResourceTypeName(int resourceType) +{ + if (resourceType == RESOURCE_TYPE_UNUSABLE) + return "Unusable"; + else if (resourceType == RESOURCE_TYPE_UNKNOWN) + return "Unknown"; + else if (resourceType == RESOURCE_TYPE_NOTSET) + return "Uninitialized"; + else if (resourceType == RESOURCE_TYPE_SCENE) + return "Scene"; + else if (resourceType == RESOURCE_TYPE_SCRIPTFILE) + return "Script File"; + else if (resourceType == RESOURCE_TYPE_MODEL) + return "Model"; + else if (resourceType == RESOURCE_TYPE_MATERIAL) + return "Material"; + else if (resourceType == RESOURCE_TYPE_ANIMATION) + return "Animation"; + else if (resourceType == RESOURCE_TYPE_IMAGE) + return "Image"; + else if (resourceType == RESOURCE_TYPE_SOUND) + return "Sound"; + else if (resourceType == RESOURCE_TYPE_TEXTURE) + return "Texture"; + else if (resourceType == RESOURCE_TYPE_FONT) + return "Font"; + else if (resourceType == RESOURCE_TYPE_PREFAB) + return "Prefab"; + else if (resourceType == RESOURCE_TYPE_TECHNIQUE) + return "Render Technique"; + else if (resourceType == RESOURCE_TYPE_PARTICLEEFFECT) + return "Particle Effect"; + else if (resourceType == RESOURCE_TYPE_PARTICLEEMITTER) + return "Particle Emitter"; + else if (resourceType == RESOURCE_TYPE_UIELEMENT) + return "UI Element"; + else if (resourceType == RESOURCE_TYPE_UIELEMENTS) + return "UI Elements"; + else if (resourceType == RESOURCE_TYPE_ANIMATION_SETTINGS) + return "Animation Settings"; + else if (resourceType == RESOURCE_TYPE_RENDERPATH) + return "Render Path"; + else if (resourceType == RESOURCE_TYPE_TEXTURE_ATLAS) + return "Texture Atlas"; + else if (resourceType == RESOURCE_TYPE_2D_PARTICLE_EFFECT) + return "2D Particle Effect"; + else if (resourceType == RESOURCE_TYPE_TEXTURE_3D) + return "Texture 3D"; + else if (resourceType == RESOURCE_TYPE_CUBEMAP) + return "Cubemap"; + else if (resourceType == RESOURCE_TYPE_2D_ANIMATION_SET) + return "2D Animation Set"; + else + return ""; +} + +class BrowserDir +{ + uint id; + String resourceKey; + String name; + Array children; + Array files; + + BrowserDir(String path_) + { + resourceKey = path_; + String parent = GetParentPath(path_); + name = path_; + name.Replace(parent, ""); + id = browserDirIndex++; + } + + int opCmp(BrowserDir@ b) + { + return name.opCmp(b.name); + } + + BrowserFile@ AddFile(String name, uint resourceSourceIndex, uint sourceType) + { + String path = resourceKey.length > 0 ? (resourceKey + "/" + name) : name; + + BrowserFile@ file = BrowserFile(path, resourceSourceIndex, sourceType); + files.Push(file); + return file; + } + +} + +class BrowserFile +{ + uint id; + uint resourceSourceIndex; + String resourceKey; + String name; + String fullname; + String extension; + StringHash fileType; + int resourceType = 0; + int sourceType = 0; + int sortScore = 0; + WeakHandle browserFileListRow; + + BrowserFile(String path_, uint resourceSourceIndex_, int sourceType_) + { + sourceType = sourceType_; + resourceSourceIndex = resourceSourceIndex_; + resourceKey = path_; + name = GetFileName(path_); + extension = GetExtension(path_); + fullname = GetFileNameAndExtension(path_); + id = browserFileIndex++; + } + + int opCmp(BrowserFile@ b) + { + if (browserSearchSortMode == 1) + return fullname.opCmp(b.fullname); + else + return sortScore - b.sortScore; + } + + String GetResourceSource() + { + if (sourceType == BROWSER_FILE_SOURCE_RESOURCE_DIR) + return cache.resourceDirs[resourceSourceIndex]; + else + return "Unknown"; + } + + String GetFullPath() + { + return String(cache.resourceDirs[resourceSourceIndex] + resourceKey); + } + + String GetPath() + { + return resourceKey; + } + + void DetermainResourceType() + { + resourceType = GetResourceType(GetFullPath(), fileType, false); + Text@ browserFileListRow_ = browserFileListRow.Get(); + if (browserFileListRow_ !is null) + { + InitializeBrowserFileListRow(browserFileListRow_, this); + } + } + + String ResourceTypeName() + { + return ::ResourceTypeName(resourceType); + } + + void FileChanged() + { + if (!fileSystem.FileExists(GetFullPath())) + { + } + else + { + } + } +} + +void CreateResourcePreview(String path, Node@ previewNode) +{ + resourceBrowserPreview.autoUpdate = false; + int resourceType = GetResourceType(path); + if (resourceType > 0) + { + File file; + file.Open(path); + + if (resourceType == RESOURCE_TYPE_MODEL) + { + Model@ model = Model(); + if (model.Load(file)) + { + StaticModel@ staticModel = previewNode.CreateComponent("StaticModel"); + staticModel.model = model; + return; + } + } + else if (resourceType == RESOURCE_TYPE_MATERIAL) + { + Material@ material = Material(); + if (material.Load(file)) + { + StaticModel@ staticModel = previewNode.CreateComponent("StaticModel"); + staticModel.model = cache.GetResource("Model", "Models/Sphere.mdl"); + staticModel.material = material; + return; + } + } + else if (resourceType == RESOURCE_TYPE_IMAGE) + { + Image@ image = Image(); + if (image.Load(file)) + { + StaticModel@ staticModel = previewNode.CreateComponent("StaticModel"); + staticModel.model = cache.GetResource("Model", "Models/Editor/ImagePlane.mdl"); + Material@ material = cache.GetResource("Material", "Materials/Editor/TexturedUnlit.xml"); + Texture2D@ texture = Texture2D(); + texture.SetData(@image, true); + material.textures[0] = texture; + staticModel.material = material; + return; + } + } + else if (resourceType == RESOURCE_TYPE_PREFAB) + { + if (GetExtension(path) == ".xml") + { + XMLFile xmlFile; + if(xmlFile.Load(file)) + if(previewNode.LoadXML(xmlFile.root) && (previewNode.GetComponents("StaticModel", true).length > 0 || previewNode.GetComponents("AnimatedModel", true).length > 0)) + { + return; + } + } + else if(previewNode.Load(file) && (previewNode.GetComponents("StaticModel", true).length > 0 || previewNode.GetComponents("AnimatedModel", true).length > 0)) + return; + + previewNode.RemoveAllChildren(); + previewNode.RemoveAllComponents(); + } + else if (resourceType == RESOURCE_TYPE_PARTICLEEFFECT) + { + ParticleEffect@ particleEffect = ParticleEffect(); + if (particleEffect.Load(file)) + { + ParticleEmitter@ particleEmitter = previewNode.CreateComponent("ParticleEmitter"); + particleEmitter.effect = particleEffect; + particleEffect.activeTime = 0.0; + particleEmitter.Reset(); + resourceBrowserPreview.autoUpdate = true; + return; + } + } + } + + StaticModel@ staticModel = previewNode.CreateComponent("StaticModel"); + staticModel.model = cache.GetResource("Model", "Models/Editor/ImagePlane.mdl"); + Material@ material = cache.GetResource("Material", "Materials/Editor/TexturedUnlit.xml"); + Texture2D@ texture = Texture2D(); + Image@ noPreviewImage = cache.GetResource("Image", "Textures/Editor/NoPreviewAvailable.png"); + texture.SetData(noPreviewImage, false); + material.textures[0] = texture; + staticModel.material = material; + + return; +} + +void RotateResourceBrowserPreview(StringHash eventType, VariantMap& eventData) +{ + int elemX = eventData["ElementX"].GetInt(); + int elemY = eventData["ElementY"].GetInt(); + + if (resourceBrowserPreview.height > 0 && resourceBrowserPreview.width > 0) + { + float yaw = ((resourceBrowserPreview.height / 2) - elemY) * (90.0 / resourceBrowserPreview.height); + float pitch = ((resourceBrowserPreview.width / 2) - elemX) * (90.0 / resourceBrowserPreview.width); + + resourcePreviewNode.rotation = resourcePreviewNode.rotation.Slerp(Quaternion(yaw, pitch, 0), 0.1); + RefreshBrowserPreview(); + } +} + +void RefreshBrowserPreview() +{ + resourceBrowserPreview.QueueUpdate(); +} + +class ResourceType +{ + int id; + String name; + ResourceType(int id_, String name_) + { + id = id_; + name = name_; + } + int opCmp(ResourceType@ b) + { + return name.opCmp(b.name); + } +} diff --git a/bin/Data/Scripts/Editor/EditorScene.as b/bin/Data/Scripts/Editor/EditorScene.as new file mode 100644 index 0000000..139dc8d --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorScene.as @@ -0,0 +1,1664 @@ +/// Urho3D editor scene handling + +#include "Scripts/Editor/EditorHierarchyWindow.as" +#include "Scripts/Editor/EditorInspectorWindow.as" +#include "Scripts/Editor/EditorCubeCapture.as" + +const int PICK_GEOMETRIES = 0; +const int PICK_LIGHTS = 1; +const int PICK_ZONES = 2; +const int PICK_RIGIDBODIES = 3; +const int PICK_UI_ELEMENTS = 4; +const int MAX_PICK_MODES = 5; +const int MAX_UNDOSTACK_SIZE = 256; + +Scene@ editorScene; + +String instantiateFileName; +CreateMode instantiateMode = REPLICATED; +bool sceneModified = false; +bool runUpdate = false; + +Array selectedNodes; +Array selectedComponents; +Node@ editNode; +Array editNodes; +Array editComponents; +uint numEditableComponentsPerNode = 1; + +Array sceneCopyBuffer; + +bool suppressSceneChanges = false; +bool inSelectionModify = false; +bool skipMruScene = false; + +Array undoStack; +uint undoStackPos = 0; + +bool revertOnPause = false; +XMLFile@ revertData; + +Vector3 lastOffsetForSmartDuplicate; + +void ClearSceneSelection() +{ + selectedNodes.Clear(); + selectedComponents.Clear(); + editNode = null; + editNodes.Clear(); + editComponents.Clear(); + numEditableComponentsPerNode = 1; + + HideGizmo(); +} + +void CreateScene() +{ + // Create a scene only once here + editorScene = Scene(); + + // Allow access to the scene from the console + script.defaultScene = editorScene; + + // Always pause the scene, and do updates manually + editorScene.updateEnabled = false; +} + +bool ResetScene() +{ + ui.cursor.shape = CS_BUSY; + + if (messageBoxCallback is null && sceneModified) + { + MessageBox@ messageBox = MessageBox("Scene has been modified.\nContinue to reset?", "Warning"); + if (messageBox.window !is null) + { + Button@ cancelButton = messageBox.window.GetChild("CancelButton", true); + cancelButton.visible = true; + cancelButton.focus = true; + SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement"); + messageBoxCallback = @ResetScene; + return false; + } + } + else + messageBoxCallback = null; + + suppressSceneChanges = true; + + // Create a scene with default values, these will be overridden when loading scenes + editorScene.Clear(); + editorScene.CreateComponent("Octree"); + editorScene.CreateComponent("DebugRenderer"); + + // Release resources that became unused after the scene clear + cache.ReleaseAllResources(false); + + sceneModified = false; + revertData = null; + StopSceneUpdate(); + + UpdateWindowTitle(); + DisableInspectorLock(); + UpdateHierarchyItem(editorScene, true); + ClearEditActions(); + + suppressSceneChanges = false; + + ResetCamera(); + CreateGizmo(); + CreateGrid(); + SetActiveViewport(viewports[0]); + + return true; +} + +void SetResourcePath(String newPath, bool usePreferredDir = true, bool additive = false) +{ + if (newPath.empty) + return; + if (!IsAbsolutePath(newPath)) + newPath = fileSystem.currentDir + newPath; + + if (usePreferredDir) + newPath = AddTrailingSlash(cache.GetPreferredResourceDir(newPath)); + else + newPath = AddTrailingSlash(newPath); + + if (newPath == sceneResourcePath) + return; + + // Remove the old scene resource path if any. However make sure that the default data paths do not get removed + if (!additive) + { + cache.ReleaseAllResources(false); + renderer.ReloadShaders(); + + String check = AddTrailingSlash(sceneResourcePath); + bool isDefaultResourcePath = check.Compare(fileSystem.programDir + "Data/", false) == 0 || + check.Compare(fileSystem.programDir + "CoreData/", false) == 0; + + if (!sceneResourcePath.empty && !isDefaultResourcePath) + cache.RemoveResourceDir(sceneResourcePath); + } + else + { + // If additive (path of a loaded prefab) check that the new path isn't already part of an old path + Array@ resourceDirs = cache.resourceDirs; + + for (uint i = 0; i < resourceDirs.length; ++i) + { + if (newPath.StartsWith(resourceDirs[i], false)) + return; + } + } + + // Add resource path as first priority so that it takes precedence over the default data paths + cache.AddResourceDir(newPath, 0); + RebuildResourceDatabase(); + + if (!additive) + { + sceneResourcePath = newPath; + uiScenePath = GetResourceSubPath(newPath, "Scenes"); + uiElementPath = GetResourceSubPath(newPath, "UI"); + uiNodePath = GetResourceSubPath(newPath, "Objects"); + uiScriptPath = GetResourceSubPath(newPath, "Scripts"); + uiParticlePath = GetResourceSubPath(newPath, "Particle"); + } +} + +String GetResourceSubPath(String basePath, const String&in subPath) +{ + basePath = AddTrailingSlash(basePath); + if (fileSystem.DirExists(basePath + subPath)) + return AddTrailingSlash(basePath + subPath); + else + return basePath; +} + +bool LoadScene(const String&in fileName) +{ + if (fileName.empty) + return false; + + ui.cursor.shape = CS_BUSY; + + // Always load the scene from the filesystem, not from resource paths + if (!fileSystem.FileExists(fileName)) + { + MessageBox("No such scene.\n" + fileName); + return false; + } + + File file(fileName, FILE_READ); + if (!file.open) + { + MessageBox("Could not open file.\n" + fileName); + return false; + } + + // Add the scene's resource path in case it's necessary + String newScenePath = GetPath(fileName); + if (!rememberResourcePath || !sceneResourcePath.StartsWith(newScenePath, false)) + SetResourcePath(newScenePath); + + suppressSceneChanges = true; + sceneModified = false; + revertData = null; + StopSceneUpdate(); + + String extension = GetExtension(fileName); + bool loaded; + if (extension == ".xml") + loaded = editorScene.LoadXML(file); + else if (extension == ".json") + loaded = editorScene.LoadJSON(file); + else + loaded = editorScene.Load(file); + + // Release resources which are not used by the new scene + cache.ReleaseAllResources(false); + + // Always pause the scene, and do updates manually + editorScene.updateEnabled = false; + + UpdateWindowTitle(); + DisableInspectorLock(); + UpdateHierarchyItem(editorScene, true); + CollapseHierarchy(); + ClearEditActions(); + + suppressSceneChanges = false; + + // global variable to mostly bypass adding mru upon importing tempscene + if (!skipMruScene) + UpdateSceneMru(fileName); + + skipMruScene = false; + + ResetCamera(); + CreateGizmo(); + CreateGrid(); + SetActiveViewport(viewports[0]); + + return loaded; +} + +bool SaveScene(const String&in fileName) +{ + if (fileName.empty) + return false; + + ui.cursor.shape = CS_BUSY; + + // Unpause when saving so that the scene will work properly when loaded outside the editor + editorScene.updateEnabled = true; + + MakeBackup(fileName); + File file(fileName, FILE_WRITE); + String extension = GetExtension(fileName); + bool success; + if (extension == ".xml") + success = editorScene.SaveXML(file); + else if (extension == ".json") + success = editorScene.SaveJSON(file); + else + success = editorScene.Save(file); + RemoveBackup(success, fileName); + + // Save all the terrains we've modified + terrainEditor.Save(); + + editorScene.updateEnabled = false; + + if (success) + { + UpdateSceneMru(fileName); + sceneModified = false; + UpdateWindowTitle(); + } + else + MessageBox("Could not save scene successfully!\nSee Urho3D.log for more detail."); + + return success; +} + +bool SaveSceneWithExistingName() +{ + if (editorScene.fileName.empty || editorScene.fileName == TEMP_SCENE_NAME) + return PickFile(); + else + return SaveScene(editorScene.fileName); +} + +Node@ CreateNode(CreateMode mode, bool raycastToMouse = false) +{ + Node@ newNode = null; + if (editNode !is null) + newNode = editNode.CreateChild("", mode); + else + newNode = editorScene.CreateChild("", mode); + newNode.worldPosition = GetNewNodePosition(raycastToMouse); + + // Create an undo action for the create + CreateNodeAction action; + action.Define(newNode); + SaveEditAction(action); + SetSceneModified(); + + FocusNode(newNode); + + return newNode; +} + +void CreateComponent(const String&in componentType) +{ + // If this is the root node, do not allow to create duplicate scene-global components + if (editNode is editorScene && CheckForExistingGlobalComponent(editNode, componentType)) + return; + + // Group for storing undo actions + EditActionGroup group; + + // For now, make a local node's all components local + /// \todo Allow to specify the createmode + for (uint i = 0; i < editNodes.length; ++i) + { + Component@ newComponent = editNodes[i].CreateComponent(componentType, editNodes[i].replicated ? REPLICATED : LOCAL); + if (newComponent !is null) + { + // Some components such as CollisionShape do not create their internal object before the first call to ApplyAttributes() + // to prevent unnecessary initialization with default values. Call now + newComponent.ApplyAttributes(); + + CreateComponentAction action; + action.Define(newComponent); + group.actions.Push(action); + } + } + + SaveEditActionGroup(group); + SetSceneModified(); + + // Although the edit nodes selection are not changed, call to ensure attribute inspector notices new components of the edit nodes + HandleHierarchyListSelectionChange(); +} + +void CreateLoadedComponent(Component@ component) +{ + if (component is null) return; + CreateComponentAction action; + action.Define(component); + SaveEditAction(action); + SetSceneModified(); + FocusComponent(component); +} + +Node@ LoadNode(const String&in fileName, Node@ parent = null, bool raycastToMouse = false) +{ + if (fileName.empty) + return null; + + if (!fileSystem.FileExists(fileName)) + { + MessageBox("No such node file.\n" + fileName); + return null; + } + + File file(fileName, FILE_READ); + if (!file.open) + { + MessageBox("Could not open file.\n" + fileName); + return null; + } + + ui.cursor.shape = CS_BUSY; + + // Before instantiating, add object's resource path if necessary + SetResourcePath(GetPath(fileName), true, true); + + Node@ newNode = InstantiateNodeFromFile(file, GetNewNodePosition(raycastToMouse), Quaternion(), 1, parent, instantiateMode); + if (newNode !is null) + { + FocusNode(newNode); + instantiateFileName = fileName; + } + return newNode; +} + +Node@ InstantiateNodeFromFile(File@ file, const Vector3& position, const Quaternion& rotation, float scaleMod = 1.0f, Node@ parent = null, CreateMode mode = REPLICATED) +{ + if (file is null) + return null; + + Node@ newNode; + uint numSceneComponent = editorScene.numComponents; + + suppressSceneChanges = true; + + String extension = GetExtension(file.name); + if (extension == ".xml") + newNode = editorScene.InstantiateXML(file, position, rotation, mode); + else if (extension == ".json") + newNode = editorScene.InstantiateJSON(file, position, rotation, mode); + else + newNode = editorScene.Instantiate(file, position, rotation, mode); + + suppressSceneChanges = false; + + if (parent !is null) + newNode.parent = parent; + + if (newNode !is null) + { + newNode.scale = newNode.scale * scaleMod; + + AdjustNodePositionByAABB(newNode); + + // Create an undo action for the load + CreateNodeAction action; + action.Define(newNode); + SaveEditAction(action); + SetSceneModified(); + + if (numSceneComponent != editorScene.numComponents) + UpdateHierarchyItem(editorScene); + else + UpdateHierarchyItem(newNode); + } + + return newNode; +} + +void AdjustNodePositionByAABB(Node@ newNode) +{ + if (alignToAABBBottom) + { + Drawable@ drawable = GetFirstDrawable(newNode); + if (drawable !is null) + { + BoundingBox aabb = drawable.worldBoundingBox; + Vector3 aabbBottomCenter(aabb.center.x, aabb.min.y, aabb.center.z); + Vector3 offset = aabbBottomCenter - newNode.worldPosition; + newNode.worldPosition = newNode.worldPosition - offset; + } + } +} + +bool SaveNode(const String&in fileName) +{ + if (fileName.empty) + return false; + + ui.cursor.shape = CS_BUSY; + + MakeBackup(fileName); + File file(fileName, FILE_WRITE); + if (!file.open) + { + MessageBox("Could not open file.\n" + fileName); + return false; + } + + String extension = GetExtension(fileName); + bool success; + if (extension == ".xml") + success = editNode.SaveXML(file); + else if (extension == ".json") + success = editNode.SaveJSON(file); + else + success = editNode.Save(file); + RemoveBackup(success, fileName); + + if (success) + instantiateFileName = fileName; + else + MessageBox("Could not save node successfully!\nSee Urho3D.log for more detail."); + + return success; +} + +void UpdateScene(float timeStep) +{ + if (runUpdate) + editorScene.Update(timeStep); +} + +void StopSceneUpdate() +{ + runUpdate = false; + audio.Stop(); + toolBarDirty = true; + + // If scene should revert on update stop, load saved data now + if (revertOnPause && revertData !is null) + { + suppressSceneChanges = true; + editorScene.Clear(); + editorScene.LoadXML(revertData.root); + CreateGrid(); + UpdateHierarchyItem(editorScene, true); + ClearEditActions(); + suppressSceneChanges = false; + } + + revertData = null; +} + +void StartSceneUpdate() +{ + runUpdate = true; + // Run audio playback only when scene is updating, so that audio components' time-dependent attributes stay constant when + // paused (similar to physics) + audio.Play(); + toolBarDirty = true; + + // Save scene data for reverting if enabled + if (revertOnPause) + { + revertData = XMLFile(); + XMLElement root = revertData.CreateRoot("scene"); + editorScene.SaveXML(root); + } + else + revertData = null; +} + +bool ToggleSceneUpdate() +{ + if (!runUpdate) + StartSceneUpdate(); + else + StopSceneUpdate(); + return true; +} + +bool ShowLayerMover() +{ + if (ui.focusElement is null) + return ShowLayerEditor(); + else + return false; +} + +void SetSceneModified() +{ + if (!sceneModified) + { + sceneModified = true; + UpdateWindowTitle(); + } +} + +bool SceneDelete() +{ + ui.cursor.shape = CS_BUSY; + + BeginSelectionModify(); + + // Clear the selection now to prevent repopulation of selectedNodes and selectedComponents combo + hierarchyList.ClearSelection(); + + // Group for storing undo actions + EditActionGroup group; + + // Remove nodes + for (uint i = 0; i < selectedNodes.length; ++i) + { + Node@ node = selectedNodes[i]; + if (node.parent is null || node.scene is null) + continue; // Root or already deleted + + uint nodeIndex = GetListIndex(node); + + // Create undo action + DeleteNodeAction action; + action.Define(node); + group.actions.Push(action); + + node.Remove(); + SetSceneModified(); + + // If deleting only one node, select the next item in the same index + if (selectedNodes.length == 1 && selectedComponents.empty) + hierarchyList.selection = nodeIndex; + } + + // Then remove components, if they still remain + for (uint i = 0; i < selectedComponents.length; ++i) + { + Component@ component = selectedComponents[i]; + Node@ node = component.node; + if (node is null) + continue; // Already deleted + + uint index = GetComponentListIndex(component); + uint nodeIndex = GetListIndex(node); + if (index == NO_ITEM || nodeIndex == NO_ITEM) + continue; + + // Do not allow to remove the Octree, DebugRenderer or MaterialCache2D or DrawableProxy2D from the root node + if (node is editorScene && (component.typeName == "Octree" || component.typeName == "DebugRenderer" || + component.typeName == "MaterialCache2D" || component.typeName == "DrawableProxy2D")) + continue; + + // Create undo action + DeleteComponentAction action; + action.Define(component); + group.actions.Push(action); + + node.RemoveComponent(component); + SetSceneModified(); + + // If deleting only one component, select the next item in the same index + if (selectedComponents.length == 1 && selectedNodes.empty) + hierarchyList.selection = index; + } + + SaveEditActionGroup(group); + + EndSelectionModify(); + return true; +} + +bool SceneCut() +{ + return SceneCopy() && SceneDelete(); +} + +bool SceneCopy() +{ + ui.cursor.shape = CS_BUSY; + + sceneCopyBuffer.Clear(); + + // Copy components + if (!selectedComponents.empty) + { + for (uint i = 0; i < selectedComponents.length; ++i) + { + XMLFile@ xml = XMLFile(); + XMLElement rootElem = xml.CreateRoot("component"); + selectedComponents[i].SaveXML(rootElem); + rootElem.SetBool("local", !selectedComponents[i].replicated); + sceneCopyBuffer.Push(xml); + } + } + // Copy nodes + else + { + for (uint i = 0; i < selectedNodes.length; ++i) + { + // Skip the root scene node as it cannot be copied + if (selectedNodes[i] is editorScene) + continue; + + XMLFile@ xml = XMLFile(); + XMLElement rootElem = xml.CreateRoot("node"); + selectedNodes[i].SaveXML(rootElem); + rootElem.SetBool("local", !selectedNodes[i].replicated); + sceneCopyBuffer.Push(xml); + } + } + + return true; +} + +bool ScenePaste(bool pasteRoot = false, bool duplication = false) +{ + ui.cursor.shape = CS_BUSY; + + // Group for storing undo actions + EditActionGroup group; + + for (uint i = 0; i < sceneCopyBuffer.length; ++i) + { + XMLElement rootElem = sceneCopyBuffer[i].root; + String mode = rootElem.name; + if (mode == "component" && editNode !is null) + { + // If this is the root node, do not allow to create duplicate scene-global components + if (editNode is editorScene && CheckForExistingGlobalComponent(editNode, rootElem.GetAttribute("type"))) + return false; + + // If copied component was local, make the new local too + Component@ newComponent = editNode.CreateComponent(rootElem.GetAttribute("type"), rootElem.GetBool("local") ? LOCAL : + REPLICATED); + if (newComponent is null) + return false; + + newComponent.LoadXML(rootElem); + newComponent.ApplyAttributes(); + + // Create an undo action + CreateComponentAction action; + action.Define(newComponent); + group.actions.Push(action); + } + else if (mode == "node") + { + // If copied node was local, make the new local too + Node@ newNode; + // Are we pasting into the root node? + if (pasteRoot) + newNode = editorScene.CreateChild("", rootElem.GetBool("local") ? LOCAL : REPLICATED); + else + { + // If we are duplicating or have the original node selected, paste into the selected nodes parent + if (duplication || editNode is null || editNode.id == rootElem.GetUInt("id")) + { + if (editNode !is null && editNode.parent !is null) + newNode = editNode.parent.CreateChild("", rootElem.GetBool("local") ? LOCAL : REPLICATED); + else + newNode = editorScene.CreateChild("", rootElem.GetBool("local") ? LOCAL : REPLICATED); + } + // If we aren't duplicating, paste into the selected node + else + { + newNode = editNode.CreateChild("", rootElem.GetBool("local") ? LOCAL : REPLICATED); + } + } + + newNode.LoadXML(rootElem); + + // Create an undo action + CreateNodeAction action; + action.Define(newNode); + group.actions.Push(action); + } + } + + SaveEditActionGroup(group); + SetSceneModified(); + return true; +} + +bool SceneDuplicate() +{ + Array copy = sceneCopyBuffer; + + if (!SceneCopy()) + { + sceneCopyBuffer = copy; + return false; + } + if (!ScenePaste(false, true)) + { + sceneCopyBuffer = copy; + return false; + } + + sceneCopyBuffer = copy; + return true; +} + +bool SceneUnparent() +{ + if (!CheckHierarchyWindowFocus() || !selectedComponents.empty || selectedNodes.empty) + return false; + + ui.cursor.shape = CS_BUSY; + + // Group for storing undo actions + EditActionGroup group; + + // Parent selected nodes to root + Array changedNodes; + for (uint i = 0; i < selectedNodes.length; ++i) + { + Node@ sourceNode = selectedNodes[i]; + if (sourceNode.parent is null || sourceNode.parent is editorScene) + continue; // Root or already parented to root + + // Perform the reparenting, continue loop even if action fails + ReparentNodeAction action; + action.Define(sourceNode, editorScene); + group.actions.Push(action); + + SceneChangeParent(sourceNode, editorScene, false); + changedNodes.Push(sourceNode); + } + + // Reselect the changed nodes at their new position in the list + for (uint i = 0; i < changedNodes.length; ++i) + hierarchyList.AddSelection(GetListIndex(changedNodes[i])); + + SaveEditActionGroup(group); + SetSceneModified(); + + return true; +} + +bool NodesParentToLastSelected() +{ + if (lastSelectedNode.Get() is null) + return false; + + if (!CheckHierarchyWindowFocus() || !selectedComponents.empty || selectedNodes.empty) + return false; + + ui.cursor.shape = CS_BUSY; + + // Group for storing undo actions + EditActionGroup group; + + // Parent selected nodes to root + Array changedNodes; + + // Find new parent node it selected last + Node@ lastNode = lastSelectedNode.Get(); //GetListNode(hierarchyList.selection); + + for (uint i = 0; i < selectedNodes.length; ++i) + { + + Node@ sourceNode = selectedNodes[i]; + if ( sourceNode.id == lastNode.id) + continue; // Skip last node it is parent + + if (sourceNode.parent.id == lastNode.id) + continue; // Root or already parented to root + + // Perform the reparenting, continue loop even if action fails + ReparentNodeAction action; + action.Define(sourceNode, lastNode); + group.actions.Push(action); + + SceneChangeParent(sourceNode, lastNode, false); + changedNodes.Push(sourceNode); + } + + // Reselect the changed nodes at their new position in the list + for (uint i = 0; i < changedNodes.length; ++i) + hierarchyList.AddSelection(GetListIndex(changedNodes[i])); + + SaveEditActionGroup(group); + SetSceneModified(); + + return true; +} + +bool SceneSmartDuplicateNode() +{ + const float minOffset = 0.1; + + if (!CheckHierarchyWindowFocus() || !selectedComponents.empty + || selectedNodes.empty || lastSelectedNode.Get() is null) + return false; + + + Node@ node = lastSelectedNode.Get(); + Node@ parent = node.parent; + Vector3 offset = Vector3(1,0,0); // default offset + + if (parent is editorScene) // if parent of selected node is Scene make empty parent for it and place in same position; + { + parent = CreateNode(LOCAL); + SceneChangeParent(parent, editorScene, false); + parent.worldPosition = node.worldPosition; + parent.name = node.name + "Group"; + node.name = parent.name + "Instance" + String(parent.numChildren); + SceneChangeParent(node, parent, false); + parent = node.parent; + SelectNode(node, false); + } + + Vector3 size; + BoundingBox bb; + + // get bb for offset + Drawable@ drawable = GetFirstDrawable(node); + if (drawable !is null) + { + bb = drawable.boundingBox; + size = bb.size * drawable.node.worldScale; + offset = Vector3(size.x, 0, 0); + } + + // make offset on axis that select user by mouse + if (gizmoAxisX.selected) + { + if (size.x < minOffset) size.x = minOffset; + offset = node.worldRotation * Vector3(size.x,0,0); + } + else if (gizmoAxisY.selected) + { + if (size.y < minOffset) size.y = minOffset; + offset = node.worldRotation * Vector3(0,size.y,0); + } + else if (gizmoAxisZ.selected) + { + if (size.z < minOffset) size.z = minOffset; + offset = node.worldRotation * Vector3(0,0,size.z); + } + else + offset = lastOffsetForSmartDuplicate; + + Vector3 lastInstancePosition = node.worldPosition; + + SelectNode(node, false); + SceneDuplicate(); + Node@ newInstance = parent.children[parent.numChildren-1]; + SelectNode(newInstance, false); + newInstance.worldPosition = lastInstancePosition; + newInstance.Translate(offset, TS_WORLD); + newInstance.name = parent.name + "Instance" + String(parent.numChildren-1); + + lastOffsetForSmartDuplicate = offset; + UpdateNodeAttributes(); + return true; +} + +bool ViewCloser() +{ + if (selectedNodes.length > 0 || selectedNodes.length > 0) + LocateNodesAndComponents(selectedNodes, selectedComponents); + + return true; +} + + +bool SceneToggleEnable() +{ + if (!CheckHierarchyWindowFocus()) + return false; + + ui.cursor.shape = CS_BUSY; + + EditActionGroup group; + + // Toggle enabled state of nodes recursively + for (uint i = 0; i < selectedNodes.length; ++i) + { + // Do not attempt to disable the Scene + if (selectedNodes[i].typeName == "Node") + { + bool oldEnabled = selectedNodes[i].enabled; + selectedNodes[i].SetEnabledRecursive(!oldEnabled); + + // Create undo action + ToggleNodeEnabledAction action; + action.Define(selectedNodes[i], oldEnabled); + group.actions.Push(action); + } + } + for (uint i = 0; i < selectedComponents.length; ++i) + { + // Some components purposefully do not expose the Enabled attribute, and it does not affect them in any way + // (Octree, PhysicsWorld). Check that the first attribute is in fact called "Is Enabled" + if (selectedComponents[i].numAttributes > 0 && selectedComponents[i].attributeInfos[0].name == "Is Enabled") + { + bool oldEnabled = selectedComponents[i].enabled; + selectedComponents[i].enabled = !oldEnabled; + + // Create undo action + EditAttributeAction action; + action.Define(selectedComponents[i], 0, Variant(oldEnabled)); + group.actions.Push(action); + } + } + + SaveEditActionGroup(group); + SetSceneModified(); + + return true; +} + +bool SceneEnableAllNodes() +{ + if (!CheckHierarchyWindowFocus()) + return false; + + ui.cursor.shape = CS_BUSY; + + EditActionGroup group; + + // Toggle enabled state of nodes recursively + Array allNodes; + allNodes = editorScene.GetChildren(true); + + for (uint i = 0; i < allNodes.length; ++i) + { + // Do not attempt to disable the Scene + if (allNodes[i].typeName == "Node") + { + bool oldEnabled = allNodes[i].enabled; + if (oldEnabled == false) + allNodes[i].SetEnabledRecursive(true); + + // Create undo action + ToggleNodeEnabledAction action; + action.Define(allNodes[i], oldEnabled); + group.actions.Push(action); + } + } + + Array allComponents; + allComponents = editorScene.GetComponents(); + + for (uint i = 0; i < allComponents.length; ++i) + { + // Some components purposefully do not expose the Enabled attribute, and it does not affect them in any way + // (Octree, PhysicsWorld). Check that the first attribute is in fact called "Is Enabled" + if (allComponents[i].numAttributes > 0 && allComponents[i].attributeInfos[0].name == "Is Enabled") + { + bool oldEnabled = allComponents[i].enabled; + allComponents[i].enabled = true; + + // Create undo action + EditAttributeAction action; + action.Define(allComponents[i], 0, Variant(oldEnabled)); + group.actions.Push(action); + } + } + + SaveEditActionGroup(group); + SetSceneModified(); + + return true; +} + +bool SceneChangeParent(Node@ sourceNode, Node@ targetNode, bool createUndoAction = true) +{ + // Create undo action if requested + if (createUndoAction) + { + ReparentNodeAction action; + action.Define(sourceNode, targetNode); + SaveEditAction(action); + } + + sourceNode.parent = targetNode; + SetSceneModified(); + + // Return true if success + if (sourceNode.parent is targetNode) + { + UpdateNodeAttributes(); // Parent change may have changed local transform + return true; + } + else + return false; +} + +bool SceneChangeParent(Node@ sourceNode, Array sourceNodes, Node@ targetNode, bool createUndoAction = true) +{ + // Create undo action if requested + if (createUndoAction) + { + ReparentNodeAction action; + action.Define(sourceNodes, targetNode); + SaveEditAction(action); + } + + for (uint i = 0; i < sourceNodes.length; ++i) + { + Node@ node = sourceNodes[i]; + node.parent = targetNode; + } + SetSceneModified(); + + // Return true if success + if (sourceNode.parent is targetNode) + { + UpdateNodeAttributes(); // Parent change may have changed local transform + return true; + } + else + return false; +} + +bool SceneReorder(Node@ sourceNode, Node@ targetNode) +{ + if (sourceNode is null || targetNode is null || sourceNode.parent is null || sourceNode.parent !is targetNode.parent) + return false; + if (sourceNode is targetNode) + return true; // No-op + + Node@ parent = sourceNode.parent; + uint destIndex = SceneFindChildIndex(parent, targetNode); + + ReorderNodeAction action; + action.Define(sourceNode, destIndex); + SaveEditAction(action); + PerformReorder(parent, sourceNode, destIndex); + return true; +} + +bool SceneReorder(Component@ sourceComponent, Component@ targetComponent) +{ + if (sourceComponent is null || targetComponent is null || sourceComponent.node !is targetComponent.node) + return false; + if (sourceComponent is targetComponent) + return true; // No-op + + Node@ node = sourceComponent.node; + uint destIndex = SceneFindComponentIndex(node, targetComponent); + + ReorderComponentAction action; + action.Define(sourceComponent, destIndex); + SaveEditAction(action); + PerformReorder(node, sourceComponent, destIndex); + return true; +} + +void PerformReorder(Node@ parent, Node@ child, uint destIndex) +{ + suppressSceneChanges = true; + + // Removal from scene zeroes the ID. Be prepared to restore it + uint oldId = child.id; + parent.RemoveChild(child); + child.id = oldId; + parent.AddChild(child, destIndex); + UpdateHierarchyItem(parent); // Force update to make sure the order is current + SetSceneModified(); + + suppressSceneChanges = false; +} + +void PerformReorder(Node@ node, Component@ component, uint destIndex) +{ + suppressSceneChanges = true; + + node.ReorderComponent(component, destIndex); + UpdateHierarchyItem(node); // Force update to make sure the order is current + SetSceneModified(); + + suppressSceneChanges = false; +} + +uint SceneFindChildIndex(Node@ parent, Node@ child) +{ + for (uint i = 0; i < parent.numChildren; ++i) + { + if (parent.children[i] is child) + return i; + } + + return -1; +} + +uint SceneFindComponentIndex(Node@ node, Component@ component) +{ + for (uint i = 0; i < node.numComponents; ++i) + { + if (node.components[i] is component) + return i; + } + + return -1; +} + +bool SceneResetPosition() +{ + if (editNode !is null) + { + Transform oldTransform; + oldTransform.Define(editNode); + + editNode.position = Vector3(0.0, 0.0, 0.0); + + // Create undo action + EditNodeTransformAction action; + action.Define(editNode, oldTransform); + SaveEditAction(action); + SetSceneModified(); + + UpdateNodeAttributes(); + return true; + } + else + return false; +} + +bool SceneResetRotation() +{ + if (editNode !is null) + { + Transform oldTransform; + oldTransform.Define(editNode); + + editNode.rotation = Quaternion(); + + // Create undo action + EditNodeTransformAction action; + action.Define(editNode, oldTransform); + SaveEditAction(action); + SetSceneModified(); + + UpdateNodeAttributes(); + return true; + } + else + return false; +} + +bool SceneResetScale() +{ + if (editNode !is null) + { + Transform oldTransform; + oldTransform.Define(editNode); + + editNode.scale = Vector3(1.0, 1.0, 1.0); + + // Create undo action + EditNodeTransformAction action; + action.Define(editNode, oldTransform); + SaveEditAction(action); + SetSceneModified(); + + UpdateNodeAttributes(); + return true; + } + else + return false; +} + +bool SceneResetTransform() +{ + if (editNode !is null) + { + Transform oldTransform; + oldTransform.Define(editNode); + + editNode.position = Vector3(0.0, 0.0, 0.0); + editNode.rotation = Quaternion(); + editNode.scale = Vector3(1.0, 1.0, 1.0); + + // Create undo action + EditNodeTransformAction action; + action.Define(editNode, oldTransform); + SaveEditAction(action); + SetSceneModified(); + + UpdateNodeAttributes(); + return true; + } + else + return false; +} + +bool SceneSelectAll() +{ + BeginSelectionModify(); + Array rootLevelNodes = editorScene.GetChildren(); + Array indices; + for (uint i = 0; i < rootLevelNodes.length; ++i) + indices.Push(GetListIndex(rootLevelNodes[i])); + hierarchyList.SetSelections(indices); + EndSelectionModify(); + + return true; +} + +bool SceneResetToDefault() +{ + ui.cursor.shape = CS_BUSY; + + // Group for storing undo actions + EditActionGroup group; + + // Reset selected component to their default + if (!selectedComponents.empty) + { + for (uint i = 0; i < selectedComponents.length; ++i) + { + Component@ component = selectedComponents[i]; + + ResetAttributesAction action; + action.Define(component); + group.actions.Push(action); + + component.ResetToDefault(); + component.ApplyAttributes(); + for (uint j = 0; j < component.numAttributes; ++j) + PostEditAttribute(component, j); + } + } + // OR reset selected nodes to their default + else + { + for (uint i = 0; i < selectedNodes.length; ++i) + { + Node@ node = selectedNodes[i]; + + ResetAttributesAction action; + action.Define(node); + group.actions.Push(action); + + node.ResetToDefault(); + node.ApplyAttributes(); + for (uint j = 0; j < node.numAttributes; ++j) + PostEditAttribute(node, j); + } + } + + SaveEditActionGroup(group); + SetSceneModified(); + attributesFullDirty = true; + + return true; +} + +bool SceneRebuildNavigation() +{ + ui.cursor.shape = CS_BUSY; + + Array@ navMeshes = editorScene.GetComponents("NavigationMesh", true); + if (navMeshes.empty) + { + @navMeshes = editorScene.GetComponents("DynamicNavigationMesh", true); + if (navMeshes.empty) + { + MessageBox("No NavigationMesh components in the scene, nothing to rebuild."); + return false; + } + } + + bool success = true; + for (uint i = 0; i < navMeshes.length; ++i) + { + NavigationMesh@ navMesh = navMeshes[i]; + if (!navMesh.Build()) + success = false; + } + + return success; +} + +bool SceneRenderZoneCubemaps() +{ + bool success = false; + Array capturedThisCall; + bool alreadyCapturing = activeCubeCapture.length > 0; // May have managed to quickly queue up a second round of zones to render cubemaps for + + for (uint i = 0; i < selectedNodes.length; ++i) + { + Array@ zones = selectedNodes[i].GetComponents("Zone", true); + for (uint z = 0; z < zones.length; ++z) + { + Zone@ zone = cast(zones[z]); + if (zone !is null) + { + activeCubeCapture.Push(EditorCubeCapture(zone)); + capturedThisCall.Push(zone); + } + } + } + + for (uint i = 0; i < selectedComponents.length; ++i) + { + Zone@ zone = cast(selectedComponents[i]); + if (zone !is null) + { + if (capturedThisCall.FindByRef(zone) < 0) + { + activeCubeCapture.Push(EditorCubeCapture(zone)); + capturedThisCall.Push(zone); + } + } + } + + // Start rendering cubemaps if there are any to render and the queue isn't already running + if (activeCubeCapture.length > 0 && !alreadyCapturing) + activeCubeCapture[0].Start(); + + if (capturedThisCall.length <= 0) + { + MessageBox("No zones selected to render cubemaps for/"); + } + return capturedThisCall.length > 0; +} + +bool SceneAddChildrenStaticModelGroup() +{ + StaticModelGroup@ smg = cast(editComponents.length > 0 ? editComponents[0] : null); + if (smg is null && editNode !is null) + smg = editNode.GetComponent("StaticModelGroup"); + + if (smg is null) + { + MessageBox("Must have a StaticModelGroup component selected."); + return false; + } + + uint attrIndex = GetAttributeIndex(smg, "Instance Nodes"); + Variant oldValue = smg.attributes[attrIndex]; + + Array children = smg.node.GetChildren(true); + for (uint i = 0; i < children.length; ++i) + smg.AddInstanceNode(children[i]); + + EditAttributeAction action; + action.Define(smg, attrIndex, oldValue); + SaveEditAction(action); + SetSceneModified(); + FocusComponent(smg); + + return true; +} + +bool SceneSetChildrenSplinePath(bool makeCycle) +{ + SplinePath@ sp = cast(editComponents.length > 0 ? editComponents[0] : null); + if (sp is null && editNode !is null) + sp = editNode.GetComponent("SplinePath"); + + if (sp is null) + { + MessageBox("Must have a SplinePath component selected."); + return false; + } + + uint attrIndex = GetAttributeIndex(sp, "Control Points"); + Variant oldValue = sp.attributes[attrIndex]; + + Array children = sp.node.GetChildren(true); + if (children.length >= 2) + { + sp.ClearControlPoints(); + for (uint i = 0; i < children.length; ++i) + sp.AddControlPoint(children[i]); + } + else + { + MessageBox("You must have a minimum two children Nodes in selected Node."); + return false; + } + + if (makeCycle) + sp.AddControlPoint(children[0]); + + EditAttributeAction action; + action.Define(sp, attrIndex, oldValue); + SaveEditAction(action); + SetSceneModified(); + FocusComponent(sp); + + return true; +} + +void AssignMaterial(StaticModel@ model, String materialPath) +{ + Material@ material = cache.GetResource("Material", materialPath); + if (material is null) + return; + + ResourceRefList materials = model.GetAttribute("Material").GetResourceRefList(); + Array oldMaterials; + for(uint i = 0; i < materials.length; ++i) + oldMaterials.Push(materials.names[i]); + + model.material = material; + + AssignMaterialAction action; + action.Define(model, oldMaterials, material); + SaveEditAction(action); + SetSceneModified(); + FocusComponent(model); +} + +void UpdateSceneMru(String filename) +{ + while (uiRecentScenes.Find(filename) > -1) + uiRecentScenes.Erase(uiRecentScenes.Find(filename)); + + uiRecentScenes.Insert(0, filename); + + for (uint i = uiRecentScenes.length - 1; i >= maxRecentSceneCount; i--) + uiRecentScenes.Erase(i); + + PopulateMruScenes(); +} + +Drawable@ GetFirstDrawable(Node@ node) +{ + Array nodes = node.GetChildren(true); + nodes.Insert(0, node); + + for (uint i = 0; i < nodes.length; ++i) + { + Array components = nodes[i].GetComponents(); + for (uint j = 0; j < components.length; ++j) + { + Drawable@ drawable = cast(components[j]); + if (drawable !is null) + return drawable; + } + } + + return null; +} + +void AssignModel(StaticModel@ assignee, String modelPath) +{ + Model@ model = cache.GetResource("Model", modelPath); + if (model is null) + return; + + Model@ oldModel = assignee.model; + assignee.model = model; + + AssignModelAction action; + action.Define(assignee, oldModel, model); + SaveEditAction(action); + SetSceneModified(); + FocusComponent(assignee); +} + +void CreateModelWithStaticModel(String filepath, Node@ parent) +{ + if (parent is null) + return; + /// \todo should be able to specify the createmode + if (parent is editorScene) + parent = CreateNode(REPLICATED); + + Model@ model = cache.GetResource("Model", filepath); + if (model is null) + return; + + StaticModel@ staticModel = parent.GetOrCreateComponent("StaticModel"); + staticModel.model = model; + if (applyMaterialList) + staticModel.ApplyMaterialList(); + CreateLoadedComponent(staticModel); +} + +void CreateModelWithAnimatedModel(String filepath, Node@ parent) +{ + if (parent is null) + return; + /// \todo should be able to specify the createmode + if (parent is editorScene) + parent = CreateNode(REPLICATED); + + Model@ model = cache.GetResource("Model", filepath); + if (model is null) + return; + + AnimatedModel@ animatedModel = parent.GetOrCreateComponent("AnimatedModel"); + animatedModel.model = model; + if (applyMaterialList) + animatedModel.ApplyMaterialList(); + CreateLoadedComponent(animatedModel); +} + +bool ColorWheelSetupBehaviorForColoring() +{ + Menu@ menu = GetEventSender(); + if (menu is null) + return false; + + coloringPropertyName = menu.name; + + if (coloringPropertyName == "menuCancel") return false; + + if (coloringComponent.typeName == "Light") + { + Light@ light = cast(coloringComponent); + if (light !is null) + { + if (coloringPropertyName == "menuLightColor") + { + coloringOldColor = light.color; + ShowColorWheelWithColor(coloringOldColor); + } + else if (coloringPropertyName == "menuSpecularIntensity") + { + // ColorWheel have only 0-1 range output of V-value(BW), and for huge-range values we divide in and multiply out + float scaledSpecular = light.specularIntensity * 0.1f; + coloringOldScalar = scaledSpecular; + ShowColorWheelWithColor(Color(scaledSpecular,scaledSpecular,scaledSpecular)); + + } + else if (coloringPropertyName == "menuBrightnessMultiplier") + { + float scaledBrightness = light.brightness * 0.1f; + coloringOldScalar = scaledBrightness; + ShowColorWheelWithColor(Color(scaledBrightness,scaledBrightness,scaledBrightness)); + } + } + } + else if (coloringComponent.typeName == "StaticModel") + { + StaticModel@ model = cast(coloringComponent); + if (model !is null) + { + Material@ mat = model.materials[0]; + if (mat !is null) + { + if (coloringPropertyName == "menuDiffuseColor") + { + Variant oldValue = mat.shaderParameters["MatDiffColor"]; + Array values = oldValue.ToString().Split(' '); + coloringOldColor = Color(values[0].ToFloat(),values[1].ToFloat(),values[2].ToFloat(),values[3].ToFloat()); //RGBA + ShowColorWheelWithColor(coloringOldColor); + } + else if (coloringPropertyName == "menuSpecularColor") + { + Variant oldValue = mat.shaderParameters["MatSpecColor"]; + Array values = oldValue.ToString().Split(' '); + coloringOldColor = Color(values[0].ToFloat(),values[1].ToFloat(),values[2].ToFloat()); + coloringOldScalar = values[3].ToFloat(); + ShowColorWheelWithColor(Color(coloringOldColor.r, coloringOldColor.g, coloringOldColor.b, coloringOldScalar/128.0f)); //RGB + shine + } + else if (coloringPropertyName == "menuEmissiveColor") + { + Variant oldValue = mat.shaderParameters["MatEmissiveColor"]; + Array values = oldValue.ToString().Split(' '); + coloringOldColor = Color(values[0].ToFloat(),values[1].ToFloat(),values[2].ToFloat()); // RGB + + + ShowColorWheelWithColor(coloringOldColor); + } + else if (coloringPropertyName == "menuEnvironmentMapColor") + { + Variant oldValue = mat.shaderParameters["MatEnvMapColor"]; + Array values = oldValue.ToString().Split(' '); + coloringOldColor = Color(values[0].ToFloat(),values[1].ToFloat(),values[2].ToFloat()); //RGB + + ShowColorWheelWithColor(coloringOldColor); + } + } + } + } + else if (coloringComponent.typeName == "Zone") + { + Zone@ zone = cast(coloringComponent); + if (zone !is null) + { + if (coloringPropertyName == "menuAmbientColor") + { + coloringOldColor = zone.ambientColor; + } + else if (coloringPropertyName == "menuFogColor") + { + coloringOldColor = zone.fogColor; + } + + ShowColorWheelWithColor(coloringOldColor); + } + } + else if (coloringComponent.typeName == "Text3D") + { + Text3D@ txt = cast(coloringComponent); + if (txt !is null) + { + if (coloringPropertyName == "c" || coloringPropertyName == "tl") + coloringOldColor = txt.colors[C_TOPLEFT]; + else if (coloringPropertyName == "tr") + coloringOldColor = txt.colors[C_TOPRIGHT]; + else if (coloringPropertyName == "bl") + coloringOldColor = txt.colors[C_BOTTOMLEFT]; + else if (coloringPropertyName == "br") + coloringOldColor = txt.colors[C_BOTTOMRIGHT]; + + ShowColorWheelWithColor(coloringOldColor); + } + } + return true; +} + diff --git a/bin/Data/Scripts/Editor/EditorSecondaryToolbar.as b/bin/Data/Scripts/Editor/EditorSecondaryToolbar.as new file mode 100644 index 0000000..163f982 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorSecondaryToolbar.as @@ -0,0 +1,175 @@ +UIElement@ secondaryToolBar; + +void CreateSecondaryToolBar() +{ + secondaryToolBar = BorderImage("secondaryToolBar"); + ui.root.AddChild(secondaryToolBar); + + secondaryToolBar.style = "EditorToolBar"; + secondaryToolBar.SetLayout(LM_VERTICAL); + secondaryToolBar.layoutSpacing = 4; + secondaryToolBar.layoutBorder = IntRect(4, 4, 4, 4); + secondaryToolBar.opacity = uiMaxOpacity; + secondaryToolBar.SetFixedSize(28, graphics.height); + secondaryToolBar.SetPosition(0, uiMenuBar.height+40); + secondaryToolBar.SetFixedHeight(graphics.height); + + Button@ b = CreateSmallToolBarButton("Node", "Replicated Node"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateReplNode"); + + b = CreateSmallToolBarButton("Node", "Local Node"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateLocalNode"); + + secondaryToolBar.AddChild(CreateSmallToolBarSpacer(3)); + + b = CreateSmallToolBarButton("Light"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("Camera"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("Zone"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("StaticModel"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("AnimatedModel"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("BillboardSet"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("ParticleEmitter"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("RibbonTrail"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("Skybox"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("Terrain"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("Text3D"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + secondaryToolBar.AddChild(CreateSmallToolBarSpacer(3)); + + b = CreateSmallToolBarButton("SoundListener"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("SoundSource3D"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("SoundSource"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + secondaryToolBar.AddChild(CreateSmallToolBarSpacer(3)); + + b = CreateSmallToolBarButton("RigidBody"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("CollisionShape"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("Constraint"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + secondaryToolBar.AddChild(CreateSmallToolBarSpacer(3)); + + b = CreateSmallToolBarButton("AnimationController"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("ScriptInstance"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + secondaryToolBar.AddChild(CreateSmallToolBarSpacer(3)); + + b = CreateSmallToolBarButton("Navigable"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("NavigationMesh"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + b = CreateSmallToolBarButton("OffMeshConnection"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); + + secondaryToolBar.AddChild(CreateSmallToolBarSpacer(3)); + + b = CreateSmallToolBarButton("NetworkPriority"); + secondaryToolBar.AddChild(b); + SubscribeToEvent(b, "Released", "SmallToolBarCreateComponent"); +} + +Button@ CreateSmallToolBarButton(const String&in title, String toolTipTitle = "") +{ + Button@ button = Button(title); + button.defaultStyle = uiStyle; + button.style = "ToolBarButton"; + button.SetFixedSize(20, 20); + CreateSmallToolBarIcon(button); + + if (toolTipTitle.empty) + toolTipTitle = title; + CreateToolTip(button, toolTipTitle, IntVector2(button.width + 10, button.height - 10)); + + return button; +} + +void CreateSmallToolBarIcon(UIElement@ element) +{ + BorderImage@ icon = BorderImage("Icon"); + icon.defaultStyle = iconStyle; + icon.style = element.name; + icon.SetFixedSize(14, 14); + element.AddChild(icon); +} + +UIElement@ CreateSmallToolBarSpacer(uint width) +{ + UIElement@ spacer = UIElement(); + spacer.SetFixedHeight(width); + return spacer; +} + +void SmallToolBarCreateReplNode(StringHash eventType, VariantMap& eventData) +{ + CreateNode(REPLICATED); +} + +void SmallToolBarCreateLocalNode(StringHash eventType, VariantMap& eventData) +{ + CreateNode(LOCAL); +} + +void SmallToolBarCreateComponent(StringHash eventType, VariantMap& eventData) +{ + Button@ b = GetEventSender(); + CreateComponent(b.name); +} diff --git a/bin/Data/Scripts/Editor/EditorSettings.as b/bin/Data/Scripts/Editor/EditorSettings.as new file mode 100644 index 0000000..5540483 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorSettings.as @@ -0,0 +1,452 @@ +// Urho3D editor settings dialog +bool subscribedToEditorSettings = false; +Window@ settingsDialog; +String defaultTags; + +void CreateEditorSettingsDialog() +{ + if (settingsDialog !is null) + return; + + settingsDialog = LoadEditorUI("UI/EditorSettingsDialog.xml"); + ui.root.AddChild(settingsDialog); + settingsDialog.opacity = uiMaxOpacity; + settingsDialog.height = 440; + CenterDialog(settingsDialog); + UpdateEditorSettingsDialog(); + HideEditorSettingsDialog(); +} + +void UpdateEditorSettingsDialog() +{ + if (settingsDialog is null) + return; + + LineEdit@ nearClipEdit = settingsDialog.GetChild("NearClipEdit", true); + nearClipEdit.text = String(viewNearClip); + + LineEdit@ farClipEdit = settingsDialog.GetChild("FarClipEdit", true); + farClipEdit.text = String(viewFarClip); + + LineEdit@ fovEdit = settingsDialog.GetChild("FOVEdit", true); + fovEdit.text = String(viewFov); + + LineEdit@ speedEdit = settingsDialog.GetChild("SpeedEdit", true); + speedEdit.text = String(cameraBaseSpeed); + + CheckBox@ limitRotationToggle = settingsDialog.GetChild("LimitRotationToggle", true); + limitRotationToggle.checked = limitRotation; + + + DropDownList@ mouseOrbitEdit = settingsDialog.GetChild("MouseOrbitEdit", true); + mouseOrbitEdit.selection = mouseOrbitMode; + + CheckBox@ middleMousePanToggle = settingsDialog.GetChild("MiddleMousePanToggle", true); + middleMousePanToggle.checked = mmbPanMode; + + CheckBox@ rotateAroundSelectToggle = settingsDialog.GetChild("RotateAroundSelectionToggle", true); + rotateAroundSelectToggle.checked = rotateAroundSelect; + + DropDownList@ hotKeysModeEdit = settingsDialog.GetChild("HotKeysModeEdit", true); + hotKeysModeEdit.selection = hotKeyMode; + + DropDownList@ newNodeModeEdit = settingsDialog.GetChild("NewNodeModeEdit", true); + newNodeModeEdit.selection = newNodeMode; + + LineEdit@ moveStepEdit = settingsDialog.GetChild("MoveStepEdit", true); + moveStepEdit.text = String(moveStep); + CheckBox@ moveSnapToggle = settingsDialog.GetChild("MoveSnapToggle", true); + moveSnapToggle.checked = moveSnap; + + LineEdit@ rotateStepEdit = settingsDialog.GetChild("RotateStepEdit", true); + rotateStepEdit.text = String(rotateStep); + CheckBox@ rotateSnapToggle = settingsDialog.GetChild("RotateSnapToggle", true); + rotateSnapToggle.checked = rotateSnap; + + LineEdit@ scaleStepEdit = settingsDialog.GetChild("ScaleStepEdit", true); + scaleStepEdit.text = String(scaleStep); + CheckBox@ scaleSnapToggle = settingsDialog.GetChild("ScaleSnapToggle", true); + scaleSnapToggle.checked = scaleSnap; + + CheckBox@ applyMaterialListToggle = settingsDialog.GetChild("ApplyMaterialListToggle", true); + applyMaterialListToggle.checked = applyMaterialList; + + CheckBox@ rememberResourcePathToggle = settingsDialog.GetChild("RememberResourcePathToggle", true); + rememberResourcePathToggle.checked = rememberResourcePath; + + LineEdit@ importOptionsEdit = settingsDialog.GetChild("ImportOptionsEdit", true); + importOptionsEdit.text = importOptions; + + DropDownList@ pickModeEdit = settingsDialog.GetChild("PickModeEdit", true); + pickModeEdit.selection = pickMode; + + LineEdit@ renderPathNameEdit = settingsDialog.GetChild("RenderPathNameEdit", true); + renderPathNameEdit.text = renderPathName; + + Button@ pickRenderPathButton = settingsDialog.GetChild("PickRenderPathButton", true); + + DropDownList@ textureQualityEdit = settingsDialog.GetChild("TextureQualityEdit", true); + textureQualityEdit.selection = renderer.textureQuality; + + DropDownList@ materialQualityEdit = settingsDialog.GetChild("MaterialQualityEdit", true); + materialQualityEdit.selection = renderer.materialQuality; + + DropDownList@ shadowResolutionEdit = settingsDialog.GetChild("ShadowResolutionEdit", true); + shadowResolutionEdit.selection = GetShadowResolution(); + + DropDownList@ shadowQualityEdit = settingsDialog.GetChild("ShadowQualityEdit", true); + shadowQualityEdit.selection = int(renderer.shadowQuality); + + LineEdit@ maxOccluderTrianglesEdit = settingsDialog.GetChild("MaxOccluderTrianglesEdit", true); + maxOccluderTrianglesEdit.text = String(renderer.maxOccluderTriangles); + + CheckBox@ specularLightingToggle = settingsDialog.GetChild("SpecularLightingToggle", true); + specularLightingToggle.checked = renderer.specularLighting; + + CheckBox@ dynamicInstancingToggle = settingsDialog.GetChild("DynamicInstancingToggle", true); + dynamicInstancingToggle.checked = renderer.dynamicInstancing; + + CheckBox@ frameLimiterToggle = settingsDialog.GetChild("FrameLimiterToggle", true); + frameLimiterToggle.checked = engine.maxFps > 0; + + CheckBox@ gammaCorrectionToggle = settingsDialog.GetChild("GammaCorrectionToggle", true); + gammaCorrectionToggle.checked = gammaCorrection; + + CheckBox@ HDRToggle = settingsDialog.GetChild("HDRToggle", true); + HDRToggle.checked = HDR; + + LineEdit@ cubemapPath = settingsDialog.GetChild("CubeMapGenPath", true); + cubemapPath.text = cubeMapGen_Path; + LineEdit@ cubemapName = settingsDialog.GetChild("CubeMapGenKey", true); + cubemapName.text = cubeMapGen_Name; + LineEdit@ cubemapSize = settingsDialog.GetChild("CubeMapGenSize", true); + cubemapSize.text = String(cubeMapGen_Size); + + LineEdit@ defaultTagsEdit = settingsDialog.GetChild("DefaultTagsEdit", true); + defaultTagsEdit.text = defaultTags.Trimmed(); + + + if (!subscribedToEditorSettings) + { + SubscribeToEvent(nearClipEdit, "TextChanged", "EditCameraNearClip"); + SubscribeToEvent(nearClipEdit, "TextFinished", "EditCameraNearClip"); + SubscribeToEvent(farClipEdit, "TextChanged", "EditCameraFarClip"); + SubscribeToEvent(farClipEdit, "TextFinished", "EditCameraFarClip"); + SubscribeToEvent(fovEdit, "TextChanged", "EditCameraFOV"); + SubscribeToEvent(fovEdit, "TextFinished", "EditCameraFOV"); + SubscribeToEvent(speedEdit, "TextChanged", "EditCameraSpeed"); + SubscribeToEvent(speedEdit, "TextFinished", "EditCameraSpeed"); + SubscribeToEvent(limitRotationToggle, "Toggled", "EditLimitRotation"); + SubscribeToEvent(middleMousePanToggle, "Toggled", "EditMiddleMousePan"); + SubscribeToEvent(rotateAroundSelectToggle, "Toggled", "EditRotateAroundSelect"); + SubscribeToEvent(mouseOrbitEdit, "ItemSelected", "EditMouseOrbitMode"); + SubscribeToEvent(hotKeysModeEdit, "ItemSelected", "EditHotKeyMode"); + SubscribeToEvent(newNodeModeEdit, "ItemSelected", "EditNewNodeMode"); + SubscribeToEvent(moveStepEdit, "TextChanged", "EditMoveStep"); + SubscribeToEvent(moveStepEdit, "TextFinished", "EditMoveStep"); + SubscribeToEvent(rotateStepEdit, "TextChanged", "EditRotateStep"); + SubscribeToEvent(rotateStepEdit, "TextFinished", "EditRotateStep"); + SubscribeToEvent(scaleStepEdit, "TextChanged", "EditScaleStep"); + SubscribeToEvent(scaleStepEdit, "TextFinished", "EditScaleStep"); + SubscribeToEvent(moveSnapToggle, "Toggled", "EditMoveSnap"); + SubscribeToEvent(rotateSnapToggle, "Toggled", "EditRotateSnap"); + SubscribeToEvent(scaleSnapToggle, "Toggled", "EditScaleSnap"); + SubscribeToEvent(rememberResourcePathToggle, "Toggled", "EditRememberResourcePath"); + SubscribeToEvent(applyMaterialListToggle, "Toggled", "EditApplyMaterialList"); + SubscribeToEvent(importOptionsEdit, "TextChanged", "EditImportOptions"); + SubscribeToEvent(importOptionsEdit, "TextFinished", "EditImportOptions"); + SubscribeToEvent(pickModeEdit, "ItemSelected", "EditPickMode"); + SubscribeToEvent(renderPathNameEdit, "TextFinished", "EditRenderPathName"); + SubscribeToEvent(pickRenderPathButton, "Released", "PickRenderPath"); + SubscribeToEvent(textureQualityEdit, "ItemSelected", "EditTextureQuality"); + SubscribeToEvent(materialQualityEdit, "ItemSelected", "EditMaterialQuality"); + SubscribeToEvent(shadowResolutionEdit, "ItemSelected", "EditShadowResolution"); + SubscribeToEvent(shadowQualityEdit, "ItemSelected", "EditShadowQuality"); + SubscribeToEvent(maxOccluderTrianglesEdit, "TextChanged", "EditMaxOccluderTriangles"); + SubscribeToEvent(maxOccluderTrianglesEdit, "TextFinished", "EditMaxOccluderTriangles"); + SubscribeToEvent(specularLightingToggle, "Toggled", "EditSpecularLighting"); + SubscribeToEvent(dynamicInstancingToggle, "Toggled", "EditDynamicInstancing"); + SubscribeToEvent(frameLimiterToggle, "Toggled", "EditFrameLimiter"); + SubscribeToEvent(gammaCorrectionToggle, "Toggled", "EditGammaCorrection"); + SubscribeToEvent(HDRToggle, "Toggled", "EditHDR"); + SubscribeToEvent(settingsDialog.GetChild("CloseButton", true), "Released", "HideEditorSettingsDialog"); + + SubscribeToEvent(cubemapPath, "TextChanged", "EditCubemapPath"); + SubscribeToEvent(cubemapPath, "TextFinished", "EditCubemapPath"); + SubscribeToEvent(cubemapName, "TextChanged", "EditCubemapName"); + SubscribeToEvent(cubemapName, "TextFinished", "EditCubemapName"); + SubscribeToEvent(cubemapSize, "TextChanged", "EditCubemapSize"); + SubscribeToEvent(cubemapSize, "TextFinished", "EditCubemapSize"); + + SubscribeToEvent(defaultTagsEdit, "TextFinished", "EditDefaultTags"); + + subscribedToEditorSettings = true; + } +} + +bool ToggleEditorSettingsDialog() +{ + if (settingsDialog.visible == false) + ShowEditorSettingsDialog(); + else + HideEditorSettingsDialog(); + return true; +} + +void ShowEditorSettingsDialog() +{ + UpdateEditorSettingsDialog(); + settingsDialog.visible = true; + settingsDialog.BringToFront(); +} + +void HideEditorSettingsDialog() +{ + settingsDialog.visible = false; +} + +void EditCameraNearClip(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + viewNearClip = edit.text.ToFloat(); + UpdateViewParameters(); + if (eventType == StringHash("TextFinished")) + edit.text = String(camera.nearClip); +} + +void EditCameraFarClip(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + viewFarClip = edit.text.ToFloat(); + UpdateViewParameters(); + if (eventType == StringHash("TextFinished")) + edit.text = String(camera.farClip); +} + +void EditCameraFOV(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + viewFov = edit.text.ToFloat(); + UpdateViewParameters(); + if (eventType == StringHash("TextFinished")) + edit.text = String(camera.fov); +} + +void EditCameraSpeed(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + cameraBaseSpeed = Max(edit.text.ToFloat(), 1.0); + if (eventType == StringHash("TextFinished")) + edit.text = String(cameraBaseSpeed); +} + +void EditLimitRotation(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + limitRotation = edit.checked; +} + +void EditMouseOrbitMode(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ edit = eventData["Element"].GetPtr(); + mouseOrbitMode = edit.selection; +} + +void EditMiddleMousePan(StringHash eventType, VariantMap& eventData) +{ + mmbPanMode = cast(eventData["Element"].GetPtr()).checked; +} + +void EditRotateAroundSelect(StringHash eventType, VariantMap& eventData) +{ + rotateAroundSelect = cast(eventData["Element"].GetPtr()).checked; +} + +void EditHotKeyMode(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ edit = eventData["Element"].GetPtr(); + hotKeyMode = edit.selection; + MessageBox("Please, restart Urho editor for applying changes.\n", " Notify "); + +} + +void EditNewNodeMode(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ edit = eventData["Element"].GetPtr(); + newNodeMode = edit.selection; +} + +void EditMoveStep(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + moveStep = Max(edit.text.ToFloat(), 0.0); + if (eventType == StringHash("TextFinished")) + edit.text = String(moveStep); +} + +void EditRotateStep(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + rotateStep = Max(edit.text.ToFloat(), 0.0); + if (eventType == StringHash("TextFinished")) + edit.text = String(rotateStep); +} + +void EditScaleStep(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + scaleStep = Max(edit.text.ToFloat(), 0.0); + if (eventType == StringHash("TextFinished")) + edit.text = String(scaleStep); +} + +void EditMoveSnap(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + moveSnap = edit.checked; + toolBarDirty = true; +} + +void EditRotateSnap(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + rotateSnap = edit.checked; + toolBarDirty = true; +} + +void EditScaleSnap(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + scaleSnap = edit.checked; + toolBarDirty = true; +} + +void EditRememberResourcePath(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + rememberResourcePath = edit.checked; +} + +void EditApplyMaterialList(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + applyMaterialList = edit.checked; +} + +void EditImportOptions(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + importOptions = edit.text.Trimmed(); +} + +void EditPickMode(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ edit = eventData["Element"].GetPtr(); + pickMode = edit.selection; +} + +void EditRenderPathName(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + SetRenderPath(edit.text); +} + +void PickRenderPath(StringHash eventType, VariantMap& eventData) +{ + CreateFileSelector("Load render path", "Load", "Cancel", uiRenderPathPath, uiRenderPathFilters, uiRenderPathFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadRenderPath"); +} + +void HandleLoadRenderPath(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiRenderPathFilter, uiRenderPathPath); + SetRenderPath(GetResourceNameFromFullName(ExtractFileName(eventData))); + LineEdit@ renderPathNameEdit = settingsDialog.GetChild("RenderPathNameEdit", true); + renderPathNameEdit.text = renderPathName; +} + +void EditTextureQuality(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ edit = eventData["Element"].GetPtr(); + renderer.textureQuality = edit.selection; +} + +void EditMaterialQuality(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ edit = eventData["Element"].GetPtr(); + renderer.materialQuality = edit.selection; +} + +void EditShadowResolution(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ edit = eventData["Element"].GetPtr(); + SetShadowResolution(edit.selection); +} + +void EditShadowQuality(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ edit = eventData["Element"].GetPtr(); + renderer.shadowQuality = ShadowQuality(edit.selection); +} + +void EditMaxOccluderTriangles(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + renderer.maxOccluderTriangles = edit.text.ToInt(); + if (eventType == StringHash("TextFinished")) + edit.text = String(renderer.maxOccluderTriangles); +} + +void EditSpecularLighting(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + renderer.specularLighting = edit.checked; +} + +void EditDynamicInstancing(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + renderer.dynamicInstancing = edit.checked; +} + +void EditFrameLimiter(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + engine.maxFps = edit.checked ? 200 : 0; +} + +void EditGammaCorrection(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + SetGammaCorrection(edit.checked); +} + +void EditHDR(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + SetHDR(edit.checked); +} + +void EditCubemapPath(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + cubeMapGen_Path = edit.text; +} + +void EditCubemapName(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + cubeMapGen_Name = edit.text; +} + +void EditCubemapSize(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + cubeMapGen_Size = edit.text.ToInt(); +} + +void EditDefaultTags(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + defaultTags = edit.text; +} diff --git a/bin/Data/Scripts/Editor/EditorSoundType.as b/bin/Data/Scripts/Editor/EditorSoundType.as new file mode 100644 index 0000000..7789be5 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorSoundType.as @@ -0,0 +1,226 @@ +// Urho3D Sound Type manager + +Window@ soundTypeEditorWindow; +Dictionary mappings; + +const uint DEFAULT_SOUND_TYPES_COUNT = 1; + +class SoundTypeMapping +{ + String key; + float value; + + SoundTypeMapping() + { + } + + SoundTypeMapping(const String&in key, const float&in value) + { + this.key = key; + this.value = Clamp(value, 0.0f, 1.0f); + } + + void Update(float value) + { + this.value = Clamp(value, 0.0f, 1.0f); + audio.masterGain[this.key] = this.value; + } +} + +void CreateSoundTypeEditor() +{ + if (soundTypeEditorWindow !is null) + return; + + soundTypeEditorWindow = ui.LoadLayout(cache.GetResource("XMLFile", "UI/EditorSoundTypeWindow.xml")); + ui.root.AddChild(soundTypeEditorWindow); + soundTypeEditorWindow.opacity = uiMaxOpacity; + + InitSoundTypeEditorWindow(); + RefreshSoundTypeEditorWindow(); + + int height = Min(ui.root.height - 60, 750); + soundTypeEditorWindow.SetSize(400, 0); + CenterDialog(soundTypeEditorWindow); + + HideSoundTypeEditor(); + + SubscribeToEvent(soundTypeEditorWindow.GetChild("CloseButton", true), "Released", "HideSoundTypeEditor"); + SubscribeToEvent(soundTypeEditorWindow.GetChild("AddButton", true), "Released", "AddSoundTypeMapping"); + + SubscribeToEvent(soundTypeEditorWindow.GetChild("MasterValue", true), "TextFinished", "EditGain"); +} + +void InitSoundTypeEditorWindow() +{ + if (!mappings.Exists(SOUND_MASTER)) + mappings[SOUND_MASTER] = SoundTypeMapping(SOUND_MASTER, audio.masterGain[SOUND_MASTER]); + + for (uint i = DEFAULT_SOUND_TYPES_COUNT; i < mappings.length; i++) + { + String key = mappings.keys[i]; + + SoundTypeMapping@ mapping; + if (mappings.Get(key, @mapping)) + AddUserUIElements(key, mapping.value); + } +} + +void RefreshSoundTypeEditorWindow() +{ + RefreshDefaults(soundTypeEditorWindow.GetChild("DefaultsContainer")); + RefreshUser(soundTypeEditorWindow.GetChild("UserContainer")); +} + +void RefreshDefaults(UIElement@ root) +{ + UpdateMappingValue(SOUND_MASTER, root.GetChild(SOUND_MASTER, true)); +} + +void RefreshUser(UIElement@ root) +{ + for (uint i = DEFAULT_SOUND_TYPES_COUNT; i < mappings.length; i++) + { + String key = mappings.keys[i]; + UpdateMappingValue(key, root.GetChild(key, true)); + } +} + +void UpdateMappingValue(const String&in key, UIElement@ root) +{ + if (root !is null) + { + LineEdit@ value = root.GetChild(key + "Value"); + SoundTypeMapping@ mapping; + + if (mappings.Get(key, @mapping) && value !is null) + { + value.text = mapping.value; + root.vars["DragDropContent"] = mapping.key; + } + } +} + +void AddUserUIElements(const String&in key, const String&in gain) +{ + ListView@ container = soundTypeEditorWindow.GetChild("UserContainer", true); + + UIElement@ itemParent = UIElement(); + container.AddItem(itemParent); + + itemParent.style = "ListRow"; + itemParent.name = key; + itemParent.layoutSpacing = 10; + + Text@ keyText = Text(); + LineEdit@ gainEdit = LineEdit(); + Button@ removeButton = Button(); + + itemParent.AddChild(keyText); + itemParent.AddChild(gainEdit); + itemParent.AddChild(removeButton); + itemParent.dragDropMode = DD_SOURCE; + + keyText.text = key; + keyText.textAlignment = HA_LEFT; + keyText.SetStyleAuto(); + + gainEdit.maxLength = 4; + gainEdit.maxWidth = 2147483647; + gainEdit.minWidth = 100; + gainEdit.name = key + "Value"; + gainEdit.text = gain; + gainEdit.SetStyleAuto(); + + removeButton.style = "CloseButton"; + + SubscribeToEvent(removeButton, "Released", "DeleteSoundTypeMapping"); + SubscribeToEvent(gainEdit, "TextFinished", "EditGain"); +} + +void AddSoundTypeMapping(StringHash eventType, VariantMap& eventData) +{ + UIElement@ button = eventData["Element"].GetPtr(); + LineEdit@ key = button.parent.GetChild("Key"); + LineEdit@ gain = button.parent.GetChild("Gain"); + + if (!key.text.empty && !gain.text.empty && !mappings.Exists(key.text)) + { + SoundTypeMapping@ mapping = SoundTypeMapping(key.text, gain.text.ToFloat()); + + mappings[key.text] = mapping; + AddUserUIElements(key.text, mapping.value); + } + + key.text = ""; + gain.text = ""; + + RefreshSoundTypeEditorWindow(); +} + +void DeleteSoundTypeMapping(StringHash eventType, VariantMap& eventData) +{ + UIElement@ button = eventData["Element"].GetPtr(); + UIElement@ parent = button.parent; + + mappings.Erase(parent.name); + parent.Remove(); +} + +void EditGain(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ input = eventData["Element"].GetPtr(); + String key = input.parent.name; + + SoundTypeMapping@ mapping; + + if (mappings.Get(key, @mapping)) + mapping.Update(input.text.ToFloat()); + + RefreshSoundTypeEditorWindow(); +} + +bool ToggleSoundTypeEditor() +{ + if (soundTypeEditorWindow.visible == false) + ShowSoundTypeEditor(); + else + HideSoundTypeEditor(); + return true; +} + +void ShowSoundTypeEditor() +{ + RefreshSoundTypeEditorWindow(); + soundTypeEditorWindow.visible = true; + soundTypeEditorWindow.BringToFront(); +} + +void HideSoundTypeEditor() +{ + soundTypeEditorWindow.visible = false; +} + +void SaveSoundTypes(XMLElement&in root) +{ + for (uint i = 0; i < mappings.length; i++) + { + String key = mappings.keys[i]; + + SoundTypeMapping@ mapping; + if (mappings.Get(key, @mapping)) + root.SetFloat(key, mapping.value); + } +} + +void LoadSoundTypes(const XMLElement&in root) +{ + for (uint i = 0; i < root.numAttributes ; i++) + { + String key = root.GetAttributeNames()[i]; + float gain = root.GetFloat(key); + + if (!key.empty && !mappings.Exists(key)) + mappings[key] = SoundTypeMapping(key, gain); + } +} \ No newline at end of file diff --git a/bin/Data/Scripts/Editor/EditorSpawn.as b/bin/Data/Scripts/Editor/EditorSpawn.as new file mode 100644 index 0000000..824a5f6 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorSpawn.as @@ -0,0 +1,441 @@ +// Urho3D spawn editor + +LineEdit@ positionOffsetX; +LineEdit@ positionOffsetY; +LineEdit@ positionOffsetZ; +LineEdit@ randomRotationX; +LineEdit@ randomRotationY; +LineEdit@ randomRotationZ; +LineEdit@ randomScaleMinEdit; +LineEdit@ randomScaleMaxEdit; +LineEdit@ numberSpawnedObjectsEdit; +LineEdit@ spawnRadiusEdit; +LineEdit@ spawnCountEdit; + +Window@ spawnWindow; +Vector3 positionOffset = Vector3(0, 0, 0); +Vector3 randomRotation = Vector3(0, 0, 0); +float randomScaleMin = 1; +float randomScaleMax = 1; +uint spawnCount = 1; +float spawnRadius = 0; +bool useNormal = true; +bool alignToAABBBottom = true; +bool spawnOnSelection = false; +uint numberSpawnedObjects = 1; +Array spawnedObjectsNames; + +void CreateSpawnEditor() +{ + if (spawnWindow !is null) + return; + + spawnWindow = LoadEditorUI("UI/EditorSpawnWindow.xml"); + ui.root.AddChild(spawnWindow); + spawnWindow.opacity = uiMaxOpacity; + + int height = Min(ui.root.height - 60, 500); + spawnWindow.SetSize(300, height); + CenterDialog(spawnWindow); + + HideSpawnEditor(); + SubscribeToEvent(spawnWindow.GetChild("CloseButton", true), "Released", "HideSpawnEditor"); + positionOffsetX = spawnWindow.GetChild("PositionOffset.x", true); + positionOffsetY = spawnWindow.GetChild("PositionOffset.y", true); + positionOffsetZ = spawnWindow.GetChild("PositionOffset.z", true); + positionOffsetX.text = String(positionOffset.x); + positionOffsetY.text = String(positionOffset.y); + positionOffsetZ.text = String(positionOffset.z); + randomRotationX = spawnWindow.GetChild("RandomRotation.x", true); + randomRotationY = spawnWindow.GetChild("RandomRotation.y", true); + randomRotationZ = spawnWindow.GetChild("RandomRotation.z", true); + randomRotationX.text = String(randomRotation.x); + randomRotationY.text = String(randomRotation.y); + randomRotationZ.text = String(randomRotation.z); + + randomScaleMinEdit = spawnWindow.GetChild("RandomScaleMin", true); + randomScaleMaxEdit = spawnWindow.GetChild("RandomScaleMax", true); + randomScaleMinEdit.text = String(randomScaleMin); + randomScaleMaxEdit.text = String(randomScaleMax); + CheckBox@ useNormalToggle = spawnWindow.GetChild("UseNormal", true); + useNormalToggle.checked = useNormal; + CheckBox@ alignToAABBBottomToggle = spawnWindow.GetChild("AlignToAABBBottom", true); + alignToAABBBottomToggle.checked = alignToAABBBottom; + CheckBox@ spawnOnSelectionToggle = spawnWindow.GetChild("SpawnOnSelected", true); + spawnOnSelectionToggle.checked = spawnOnSelection; + + numberSpawnedObjectsEdit = spawnWindow.GetChild("NumberSpawnedObjects", true); + numberSpawnedObjectsEdit.text = String(numberSpawnedObjects); + + spawnRadiusEdit = spawnWindow.GetChild("SpawnRadius", true); + spawnCountEdit = spawnWindow.GetChild("SpawnCount", true); + spawnRadiusEdit.text = String(spawnRadius); + spawnCountEdit.text = String(spawnCount); + + SubscribeToEvent(positionOffsetX, "TextChanged", "EditPositionOffset"); + SubscribeToEvent(positionOffsetY, "TextChanged", "EditPositionOffset"); + SubscribeToEvent(positionOffsetZ, "TextChanged", "EditPositionOffset"); + SubscribeToEvent(randomRotationX, "TextChanged", "EditRandomRotation"); + SubscribeToEvent(randomRotationY, "TextChanged", "EditRandomRotation"); + SubscribeToEvent(randomRotationZ, "TextChanged", "EditRandomRotation"); + SubscribeToEvent(randomScaleMinEdit, "TextChanged", "EditRandomScale"); + SubscribeToEvent(randomScaleMaxEdit, "TextChanged", "EditRandomScale"); + SubscribeToEvent(spawnRadiusEdit, "TextChanged", "EditSpawnRadius"); + SubscribeToEvent(spawnCountEdit, "TextChanged", "EditSpawnCount"); + SubscribeToEvent(useNormalToggle, "Toggled", "ToggleUseNormal"); + SubscribeToEvent(alignToAABBBottomToggle, "Toggled", "ToggleAlignToAABBBottom"); + SubscribeToEvent(spawnOnSelectionToggle, "Toggled", "ToggleSpawnOnSelected"); + SubscribeToEvent(numberSpawnedObjectsEdit, "TextFinished", "UpdateNumberSpawnedObjects"); + SubscribeToEvent(spawnWindow.GetChild("SetSpawnMode", true), "Released", "SetSpawnMode"); + RefreshPickedObjects(); +} + +bool ToggleSpawnEditor() +{ + if (spawnWindow.visible == false) + ShowSpawnEditor(); + else + HideSpawnEditor(); + return true; +} + +void ShowSpawnEditor() +{ + spawnWindow.visible = true; + spawnWindow.BringToFront(); +} + +void HideSpawnEditor() +{ + spawnWindow.visible = false; +} + +void PickSpawnObject() +{ + @resourcePicker = GetResourcePicker(StringHash("Node")); + if (resourcePicker is null) + return; + + 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", "PickSpawnObjectDone"); +} + +void EditPositionOffset(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + positionOffset = Vector3(positionOffsetX.text.ToFloat(), positionOffsetY.text.ToFloat(), positionOffsetZ.text.ToFloat()); + UpdateHierarchyItem(editorScene); +} + +void EditRandomRotation(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + randomRotation = Vector3(randomRotationX.text.ToFloat(), randomRotationY.text.ToFloat(), randomRotationZ.text.ToFloat()); + UpdateHierarchyItem(editorScene); +} + +void EditRandomScale(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + randomScaleMin = randomScaleMinEdit.text.ToFloat(); + randomScaleMax = randomScaleMaxEdit.text.ToFloat(); + UpdateHierarchyItem(editorScene); +} + +void ToggleUseNormal(StringHash eventType, VariantMap& eventData) +{ + useNormal = cast(eventData["Element"].GetPtr()).checked; +} + +void ToggleAlignToAABBBottom(StringHash eventType, VariantMap& eventData) +{ + alignToAABBBottom = cast(eventData["Element"].GetPtr()).checked; +} + +void ToggleSpawnOnSelected(StringHash eventType, VariantMap& eventData) +{ + spawnOnSelection = cast(eventData["Element"].GetPtr()).checked; +} + +void UpdateNumberSpawnedObjects(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + numberSpawnedObjects = edit.text.ToUInt(); + edit.text = String(numberSpawnedObjects); + RefreshPickedObjects(); +} + +void EditSpawnRadius(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + spawnRadius = edit.text.ToFloat(); +} + +void EditSpawnCount(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ edit = eventData["Element"].GetPtr(); + spawnCount = edit.text.ToUInt(); +} + +void RefreshPickedObjects() +{ + spawnedObjectsNames.Resize(numberSpawnedObjects); + ListView@ list = spawnWindow.GetChild("SpawnedObjects", true); + list.RemoveAllItems(); + + for (uint i = 0; i < numberSpawnedObjects; ++i) + { + UIElement@ parent = CreateAttributeEditorParentWithSeparatedLabel(list, "Object " +(i+1), i, 0, false); + + UIElement@ container = UIElement(); + container.SetLayout(LM_HORIZONTAL, 4, IntRect(10, 0, 4, 0)); + container.SetFixedHeight(ATTR_HEIGHT); + parent.AddChild(container); + + LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, i, 0); + nameEdit.name = "TextureNameEdit" + String(i); + + Button@ pickButton = CreateResourcePickerButton(container, null, i, 0, "smallButtonPick"); + SubscribeToEvent(pickButton, "Released", "PickSpawnedObject"); + nameEdit.text = spawnedObjectsNames[i]; + + SubscribeToEvent(nameEdit, "TextFinished", "EditSpawnedObjectName"); + } +} + +void EditSpawnedObjectName(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ nameEdit = eventData["Element"].GetPtr(); + int index = nameEdit.vars["Index"].GetUInt(); + String resourceName = VerifySpawnedObjectFile(nameEdit.text); + nameEdit.text = resourceName; + spawnedObjectsNames[index] = resourceName; +} + +String VerifySpawnedObjectFile(const String&in resourceName) +{ + File@ file = cache.GetFile(resourceName); + if(file !is null) + return resourceName; + else + return String(); +} + +void PickSpawnedObject(StringHash eventType, VariantMap& eventData) +{ + UIElement@ button = eventData["Element"].GetPtr(); + resourcePickIndex = button.vars["Index"].GetUInt(); + CreateFileSelector("Pick spawned object", "Pick", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter); + + SubscribeToEvent(uiFileSelector, "FileSelected", "PickSpawnedObjectNameDone"); +} + +void PickSpawnedObjectNameDone(StringHash eventType, VariantMap& eventData) +{ + StoreResourcePickerPath(); + CloseFileSelector(); + + if (!eventData["OK"].GetBool()) + { + @resourcePicker = null; + return; + } + + String resourceName = GetResourceNameFromFullName(eventData["FileName"].GetString()); + spawnedObjectsNames[resourcePickIndex] = VerifySpawnedObjectFile(resourceName); + @resourcePicker = null; + RefreshPickedObjects(); +} + +void SetSpawnMode(StringHash eventType, VariantMap& eventData) +{ + editMode = EDIT_SPAWN; +} + +void PlaceObject(Vector3 spawnPosition, Vector3 normal) +{ + Quaternion spawnRotation; + if (useNormal) + spawnRotation = Quaternion(Vector3(0, 1, 0), normal); + spawnRotation = Quaternion(Random(-randomRotation.x, randomRotation.x), + Random(-randomRotation.y, randomRotation.y), Random(-randomRotation.z, randomRotation.z)) * spawnRotation; + + int number = RandomInt(0, spawnedObjectsNames.length); + File@ file = cache.GetFile(spawnedObjectsNames[number]); + Node@ spawnedObject = InstantiateNodeFromFile(file, spawnPosition + (spawnRotation * positionOffset), spawnRotation, Random(randomScaleMin, randomScaleMax)); + if (spawnedObject is null) + { + spawnedObjectsNames[number] = spawnedObjectsNames[spawnedObjectsNames.length - 1]; + --numberSpawnedObjects; + RefreshPickedObjects(); + return; + } +} + +bool GetSpawnPosition(const Ray&in cameraRay, float maxDistance, Vector3&out position, Vector3&out normal, float randomRadius = 0.0, + bool allowNoHit = true) +{ + if (pickMode < PICK_RIGIDBODIES && editorScene.octree !is null) + { + RayQueryResult result = editorScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY, + 0x7fffffff); + if (result.drawable !is null) + { + if (randomRadius > 0) + { + Vector3 basePosition = RandomizeSpawnPosition(result.position, randomRadius); + basePosition.y += randomRadius; + result = editorScene.octree.RaycastSingle(Ray(basePosition, Vector3(0, -1, 0)), RAY_TRIANGLE, randomRadius * 2.0, + DRAWABLE_GEOMETRY, 0x7fffffff); + if (result.drawable !is null) + { + position = result.position; + normal = result.normal; + return true; + } + } + else + { + position = result.position; + normal = result.normal; + return true; + } + } + } + else if (editorScene.physicsWorld !is null) + { + // If we are not running the actual physics update, refresh collisions before raycasting + if (!runUpdate) + editorScene.physicsWorld.UpdateCollisions(); + + PhysicsRaycastResult result = editorScene.physicsWorld.RaycastSingle(cameraRay, maxDistance); + + if (result.body !is null) + { + if (randomRadius > 0) + { + Vector3 basePosition = RandomizeSpawnPosition(result.position, randomRadius); + basePosition.y += randomRadius; + result = editorScene.physicsWorld.RaycastSingle(Ray(basePosition, Vector3(0, -1, 0)), randomRadius * 2.0); + if (result.body !is null) + { + position = result.position; + normal = result.normal; + return true; + } + } + else + { + position = result.position; + normal = result.normal; + return true; + } + } + } + + position = cameraRay.origin + cameraRay.direction * maxDistance; + normal = Vector3(0, 1, 0); + return allowNoHit; +} + +bool GetSpawnPositionOnNode(const Ray&in cameraRay, float maxDistance, Vector3&out position, Vector3&out normal, Node@ node, float randomRadius = 0.0, + bool allowNoHit = true) +{ + if (pickMode < PICK_RIGIDBODIES && editorScene.octree !is null) + { + Array results = editorScene.octree.Raycast(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY, 0x7fffffff); + + if (!results.empty) + { + RayQueryResult result = results[0]; + + for(uint i = 0; i < results.length; i++) + { + if (results[i].node is node) + { + result = results[i]; + break; + } + } + + if (randomRadius > 0) + { + Vector3 basePosition = RandomizeSpawnPosition(result.position, randomRadius); + basePosition.y += randomRadius; + Array randomResults = editorScene.octree.Raycast(Ray(basePosition, Vector3(0, -1, 0)), RAY_TRIANGLE, randomRadius * 2.0, DRAWABLE_GEOMETRY, 0x7fffffff); + + if (randomResults.length == 0) + { + position = result.position; + normal = result.normal; + return true; + } + + result = randomResults[0]; + + // Find node in results + for(uint i = 0; i < randomResults.length; i++) + { + if (randomResults[i].node is node) + { + result = randomResults[i]; + break; + } + } + + position = result.position; + normal = result.normal; + return true; + } + else + { + position = result.position; + normal = result.normal; + return true; + } + } + } + + position = cameraRay.origin + cameraRay.direction * maxDistance; + normal = Vector3(0, 1, 0); + return allowNoHit; +} + +Vector3 RandomizeSpawnPosition(const Vector3&in position, float randomRadius) +{ + float angle = Random() * 360.0; + float distance = Random() * randomRadius; + return position + Quaternion(0, angle, 0) * Vector3(0, 0, distance); +} + +void SpawnObject() +{ + Node@ selectedNode = null; + + if (spawnOnSelection) + if (selectedNodes.length > 0) + selectedNode = selectedNodes[0]; + + if (spawnedObjectsNames.length == 0) + return; + + IntRect view = activeViewport.viewport.rect; + + for (uint i = 0; i < spawnCount; ++i) + { + Ray cameraRay = GetActiveViewportCameraRay(); + Vector3 position, normal; + bool result = false; + + if (spawnOnSelection && selectedNode !is null) + result = GetSpawnPositionOnNode(cameraRay, camera.farClip, position, normal, selectedNode, spawnRadius, false); + else + result = GetSpawnPosition(cameraRay, camera.farClip, position, normal, spawnRadius, false); + + if (result) + PlaceObject(position, normal); + } +} diff --git a/bin/Data/Scripts/Editor/EditorTerrain.as b/bin/Data/Scripts/Editor/EditorTerrain.as new file mode 100644 index 0000000..b2fc774 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorTerrain.as @@ -0,0 +1,657 @@ + +// Urho3D terrain editor + +const uint TERRAIN_EDITMODE_RAISELOWERHEIGHT = 0, TERRAIN_EDITMODE_SETHEIGHT = 1, TERRAIN_EDITMODE_SMOOTHHEIGHT = 3, + TERRAIN_EDITMODE_PAINTBRUSH = 4, TERRAIN_EDITMODE_PAINTTREES = 5, TERRAIN_EDITMODE_PAINTFOLIAGE = 6; + +funcdef bool TerrainEditorShowCallback(); + +class TerrainEditorUpdateChanges { + IntVector2 offset; + Image@ oldImage; + Image@ newImage; +} + +class TerrainEditorBrushVisualizer +{ + Node@ node; + CustomGeometry@ customGeometry; + private bool addedToOctree = false; + + void Create() + { + node = Node(); + customGeometry = node.CreateComponent("CustomGeometry"); + customGeometry.numGeometries = 1; + customGeometry.material = cache.GetResource("Material", "Materials/VColUnlit.xml"); + customGeometry.occludee = false; + customGeometry.enabled = true; + } + + void Hide() + { + node.enabled = false; + addedToOctree = false; + } + + void Update(Terrain@ terrainComponent, Vector3 position, float radius) + { + node.enabled = true; + node.position = Vector3(position.x, 0, position.z); + + // Generate the circle + customGeometry.BeginGeometry(0, LINE_STRIP); + for (uint i = 0; i < 364; i += 4) + { + float angle = i * M_PI / 180; + float x = radius * Cos(angle / 0.0174532925); + float z = radius * Sin(angle / 0.0174532925); + float y = terrainComponent.GetHeight(Vector3(position.x + x, 0, position.z + z)); + customGeometry.DefineVertex(Vector3(x, y + 0.25, z)); + customGeometry.DefineColor(Color(0, 1, 0)); + } + customGeometry.Commit(); + + if (editorScene.octree !is null && addedToOctree == false) + { + editorScene.octree.AddManualDrawable(customGeometry); + addedToOctree = true; + } + } +} + +class TerrainEditor +{ + private bool dirty = true; + private uint editMode = 0; + + private Window@ window; + private UIElement@ toolbar; + private Text@ currentToolDescText; + private Array brushes; + private CheckBox@ selectedBrush; + private Image@ selectedBrushImage; + private Image@ scaledSelectedBrushImage; + private Slider@ brushSizeSlider; + private Slider@ brushOpacitySlider; + private Slider@ brushHeightSlider; + private TerrainEditorBrushVisualizer brushVisualizer; + + private Array terrainsEdited; + + private Color targetColor; + bool targetColorSelected = false; + + // Create the Terrain Editor window and initialize it + void Create() + { + if (window !is null) + return; + + window = LoadEditorUI("UI/EditorTerrainWindow.xml"); + ui.root.AddChild(window); + window.opacity = uiMaxOpacity; + + BorderImage@ currentToolDesc = window.GetChild("CurrentToolDesc", true); + currentToolDesc.layoutBorder = IntRect(8, 8, 8, 8); + + currentToolDescText = window.GetChild("CurrentToolDescText", true); + + ListView@ brushesContainer = window.GetChild("BrushesContainer", true); + brushesContainer.SetScrollBarsVisible(true, false); + brushesContainer.contentElement.layoutMode = LM_HORIZONTAL; + brushesContainer.SetFixedHeight(84); + + BorderImage@ settingsArea = window.GetChild("SettingsArea", true); + settingsArea.layoutBorder = IntRect(8, 8, 8, 8); + + LineEdit@ createTerrainValue = window.GetChild("CreateTerrainValue", true); + createTerrainValue.text = "1024"; + + brushSizeSlider = window.GetChild("BrushSize", true); + brushOpacitySlider = window.GetChild("BrushOpacity", true); + brushHeightSlider = window.GetChild("BrushHeight", true); + + window.height = 300; + window.SetPosition(ui.root.width - 10 - window.width, attributeInspectorWindow.position.y + attributeInspectorWindow.height + 10); + + SubscribeToEvent(window.GetChild("RaiseLowerHeight", true), "Toggled", "OnEditModeSelected"); + SubscribeToEvent(window.GetChild("SetHeight", true), "Toggled", "OnEditModeSelected"); + SubscribeToEvent(window.GetChild("SmoothHeight", true), "Toggled", "OnEditModeSelected"); + //SubscribeToEvent(window.GetChild("PaintBrush", true), "Toggled", "OnEditModeSelected"); + //SubscribeToEvent(window.GetChild("PaintTrees", true), "Toggled", "OnEditModeSelected"); + //SubscribeToEvent(window.GetChild("PaintFoliage", true), "Toggled", "OnEditModeSelected"); + SubscribeToEvent(window.GetChild("CloseButton", true), "Released", "Hide"); + SubscribeToEvent(window.GetChild("CreateTerrainButton", true), "Released", "CreateTerrain"); + SubscribeToEvent(brushSizeSlider, "DragEnd", "UpdateScaledBrush"); + + LoadBrushes(); + Show(); + + brushVisualizer.Create(); + } + + // Hide the window + void Hide() + { + window.visible = false; + } + + void HideBrushVisualizer() + { + brushVisualizer.Hide(); + } + + void UpdateBrushVisualizer(Terrain@ terrainComponent, Vector3 position) + { + if (scaledSelectedBrushImage is null) + { + brushVisualizer.Hide(); + return; + } + brushVisualizer.Update(terrainComponent, position, scaledSelectedBrushImage.width / 2); + } + + // Save all the terrains we have edited + void Save() + { + for (uint i = 0; i < terrainsEdited.length; ++i) + { + // Make sure its not null (it may have been deleted since last save) + if (terrainsEdited[i] !is null) + { + String fileLocation = sceneResourcePath + terrainsEdited[i].GetAttribute("Height Map").GetResourceRef().name; + + Array chunks = fileLocation.Split('/'); + Array parts = chunks[chunks.length - 1].Split('.'); + String fileType = parts[1]; + + if (fileType == "png") + { + terrainsEdited[i].heightMap.SavePNG(fileLocation); + } + else if (fileType == "jpg") + { + // Save with the highest quality + terrainsEdited[i].heightMap.SaveJPG(fileLocation, 100); + } + else if (fileType == "bmp") + { + terrainsEdited[i].heightMap.SaveBMP(fileLocation); + } + else if (fileType == "tga") + { + terrainsEdited[i].heightMap.SaveTGA(fileLocation); + } + } + } + // Clean up terrainsEdited array by clearing it + // (this will remove any terrains that may have been deleted) + terrainsEdited.Clear(); + } + + // Show the window + bool Show() + { + window.visible = true; + window.BringToFront(); + return true; + } + + // Update the UI + void UpdateDirty() + { + if (!dirty) + return; + + CheckBox@ raiseLowerHeight = window.GetChild("RaiseLowerHeight", true); + CheckBox@ setHeight = window.GetChild("SetHeight", true); + CheckBox@ smoothHeight = window.GetChild("SmoothHeight", true); + //CheckBox@ paintBrush = window.GetChild("PaintBrush", true); + //CheckBox@ paintTrees = window.GetChild("PaintTrees", true); + //CheckBox@ paintFoliage = window.GetChild("PaintFoliage", true); + + raiseLowerHeight.checked = (editMode == TERRAIN_EDITMODE_RAISELOWERHEIGHT) ? true : false; + setHeight.checked = (editMode == TERRAIN_EDITMODE_SETHEIGHT) ? true : false; + smoothHeight.checked = (editMode == TERRAIN_EDITMODE_SMOOTHHEIGHT) ? true : false; + //paintBrush.checked = (editMode == TERRAIN_EDITMODE_PAINTBRUSH) ? true : false; + //paintTrees.checked = (editMode == TERRAIN_EDITMODE_PAINTTREES) ? true : false; + //paintFoliage.checked = (editMode == TERRAIN_EDITMODE_PAINTFOLIAGE) ? true : false; + + raiseLowerHeight.enabled = !raiseLowerHeight.checked; + setHeight.enabled = !setHeight.checked; + smoothHeight.enabled = !smoothHeight.checked; + + ListView@ terrainBrushes = window.GetChild("BrushesContainer", true); + + for (uint i = 0; i < terrainBrushes.numItems; ++i) + { + CheckBox@ checkbox = cast(terrainBrushes.items[i]); + checkbox.checked = terrainBrushes.items[i] is selectedBrush; + checkbox.enabled = !checkbox.checked; + } + + dirty = false; + } + + // This gets called when we want to do something to a terrain + void Work(Terrain@ terrainComponent, Vector3 position) + { + // Only work if a brush is selected + if (selectedBrushImage is null || scaledSelectedBrushImage is null) + return; + + SetSceneModified(); + + // Add that terrain to the terrainsEdited if its not already in there + if (terrainsEdited.FindByRef(terrainComponent) == -1) + terrainsEdited.Push(terrainComponent); + + TerrainEditorUpdateChanges@ updateChanges = TerrainEditorUpdateChanges(); + + IntVector2 pos = terrainComponent.WorldToHeightMap(position); + + switch (editMode) + { + case TERRAIN_EDITMODE_RAISELOWERHEIGHT: + UpdateTerrainRaiseLower(terrainComponent.heightMap, pos, updateChanges); + break; + case TERRAIN_EDITMODE_SETHEIGHT: + UpdateTerrainSetHeight(terrainComponent.heightMap, pos, updateChanges); + break; + case TERRAIN_EDITMODE_SMOOTHHEIGHT: + UpdateTerrainSmooth(terrainComponent.heightMap, pos, updateChanges); + break; + } + + terrainComponent.ApplyHeightMap(); + + UpdateBrushVisualizer(terrainComponent, position); + + ModifyTerrainAction action; + action.Define(terrainComponent, updateChanges.offset, updateChanges.oldImage, updateChanges.newImage); + SaveEditAction(action); + } + + private uint NearestPowerOf2(uint value) { + if (value < 2) + return 2; + + for (uint i = 1; i <= 2048; i *= 2) + { + if (value == i) + return i; + + if (value < i || value > i * 2) + continue; + + return value < (i + (i / 2)) ? i : i * 2; + } + + return 2048; + } + + private void CreateTerrain() + { + String fileName = "Textures/heightmap-" + time.timeSinceEpoch + ".png"; + String fileLocation = sceneResourcePath + fileName; + + Node@ node = CreateNode(LOCAL); + node.position = Vector3(0, 0, 0); + + LineEdit@ lineEdit = window.GetChild("CreateTerrainValue", true); + + uint lineEditLength = lineEdit.text.Trimmed().ToUInt(); + + // The parse failed, so use a decent default + if (lineEditLength == 0) + lineEditLength = 1024; + + Image@ image = Image(); + uint length = NearestPowerOf2(lineEditLength) + 1; + image.SetSize(length, length, 3); + + UpdateTerrainSetConstantHeight(image, 0); + + if (!fileSystem.DirExists(GetPath(fileLocation))) + fileSystem.CreateDir(GetPath(fileLocation)); + image.SavePNG(fileLocation); + + Terrain@ terrain = node.CreateComponent("Terrain"); + terrain.heightMap = image; + + Resource@ res = cache.GetResource("Image", fileLocation); + + ResourceRef ref = ResourceRef(); + ref.type = res.type; + ref.name = fileName; + terrain.SetAttribute("Height Map", Variant(ref)); + terrain.ApplyAttributes(); + + SelectComponent(terrain, false); + } + + // Utility function, returns the difference of the two numbers + private float Difference(float a, float b) + { + return (a > b) ? a - b : b - a; + } + + // Returns a brush based on its name (its filename minus the extension) + private Image@ GetBrushImage(String brushName) + { + for (uint i = 0; i < brushes.length; ++i) + { + if (brushes[i].name == brushName) + { + return brushes[i]; + } + } + + return null; + } + + // Creates a brush element + private UIElement@ LoadBrush(String fileLocation) + { + Array chunks = fileLocation.Split('/'); + Array parts = chunks[chunks.length - 1].Split('.'); + + // We use this when editing the terrain + Image@ image = cache.GetResource("Image", fileLocation); + if (image is null) + return null; + + image.name = parts[0]; + brushes.Push(image); + + // This is for the brush icon + Texture2D@ texture = cache.GetResource("Texture2D", fileLocation); + + CheckBox@ brush = CheckBox(parts[0]); + brush.defaultStyle = uiStyle; + brush.style = "TerrainEditorCheckbox"; + brush.SetFixedSize(64, 64); + SubscribeToEvent(brush, "Toggled", "OnBrushSelected"); + + BorderImage@ icon = BorderImage("Icon"); + icon.defaultStyle = iconStyle; + icon.texture = texture; + icon.imageRect = IntRect(0, 0, texture.width, texture.height); + icon.SetFixedSize(64, 64); + brush.AddChild(icon); + + return brush; + } + + // Loads all the brushes from a hard-coded folder + private void LoadBrushes() + { + ListView@ terrainBrushes = window.GetChild("BrushesContainer", true); + String brushPath = "Textures/Editor/TerrainBrushes/"; + + Array@ resourceDirs = cache.resourceDirs; + String brushesFileLocation; + + for (uint i = 0; i < resourceDirs.length; ++i) + { + brushesFileLocation = resourceDirs[i] + brushPath; + if (fileSystem.DirExists(brushesFileLocation)) + break; + } + + if (brushesFileLocation.empty) + return; + + Array files = fileSystem.ScanDir(brushesFileLocation, "*.*", SCAN_FILES, false); + + for (uint i = 0; i < files.length; ++i) + { + UIElement@ brush = LoadBrush(brushPath + files[i]); + if (brush !is null) + terrainBrushes.AddItem(brush); + } + } + + private void OnEditModeSelected(StringHash eventType, VariantMap& eventData) + { + CheckBox@ edit = eventData["Element"].GetPtr(); + + if (!edit.checked) + return; + + if (edit.name == "RaiseLowerHeight") + SetEditMode(TERRAIN_EDITMODE_RAISELOWERHEIGHT, "Raise or lower terrain"); + + else if (edit.name == "SetHeight") + SetEditMode(TERRAIN_EDITMODE_SETHEIGHT, "Set height to specified height"); + + else if (edit.name == "SmoothHeight") + SetEditMode(TERRAIN_EDITMODE_SMOOTHHEIGHT, "Smooth the terrain"); + + else if (edit.name == "PaintBrush") + SetEditMode(TERRAIN_EDITMODE_PAINTBRUSH, "Paint textures onto the terrain"); + + else if (edit.name == "PaintTrees") + SetEditMode(TERRAIN_EDITMODE_PAINTTREES, "Paint trees onto the terrain"); + + else if (edit.name == "PaintFoliage") + SetEditMode(TERRAIN_EDITMODE_PAINTFOLIAGE, "Paint foliage onto the terrain"); + } + + private void OnBrushSelected(StringHash eventType, VariantMap& eventData) + { + CheckBox@ checkbox = cast(eventData["Element"].GetPtr()); + if (checkbox.checked == false) + return; + selectedBrush = checkbox; + selectedBrushImage = GetBrushImage(selectedBrush.name); + UpdateScaledBrush(); + dirty = true; + } + + private void SetEditMode(uint mode, String description) + { + window.GetChild("BrushOpacityLabel", true).visible = (mode == TERRAIN_EDITMODE_RAISELOWERHEIGHT); + window.GetChild("BrushHeightLabel", true).visible = (mode == TERRAIN_EDITMODE_SETHEIGHT); + + window.GetChild("BrushOpacity", true).visible = (mode == TERRAIN_EDITMODE_RAISELOWERHEIGHT); + window.GetChild("BrushHeight", true).visible = (mode == TERRAIN_EDITMODE_SETHEIGHT); + + editMode = mode; + currentToolDescText.text = description; + dirty = true; + + // force the window to resize its height to fit its children + window.height = 0; + } + + // Utility function, returns the smaller of the two numbers + private float Smaller(float a, float b) + { + return (a > b) ? b : a; + } + + private void UpdateScaledBrush() + { + if (selectedBrushImage is null) + return; + float size = (brushSizeSlider.value / 25) + 0.5; + scaledSelectedBrushImage = selectedBrushImage.GetSubimage(IntRect(0, 0, selectedBrushImage.width, selectedBrushImage.height)); + scaledSelectedBrushImage.Resize(int(selectedBrushImage.width * size), int(selectedBrushImage.height * size)); + } + + private void UpdateTerrainRaiseLower(Image@ terrainImage, IntVector2 position, TerrainEditorUpdateChanges@ updateChanges) + { + uint brushImageWidth = scaledSelectedBrushImage.width; + uint brushImageHeight = scaledSelectedBrushImage.height; + + updateChanges.offset = IntVector2(position.x - (brushImageWidth / 2), position.y - (brushImageHeight / 2)); + if (updateChanges.offset.x < 0) updateChanges.offset.x = 0; + if (updateChanges.offset.y < 0) updateChanges.offset.y = 0; + + IntRect boundsRect = IntRect(updateChanges.offset.x, updateChanges.offset.y, updateChanges.offset.x + brushImageWidth, updateChanges.offset.y + brushImageHeight); + boundsRect = ClipIntRectToHeightmapBounds(terrainImage, boundsRect); + + updateChanges.oldImage = terrainImage.GetSubimage(boundsRect); + + // lower or raise (respectively), multiply this by the brush opacity + float opacity = brushOpacitySlider.value / 25; + float modifier = (input.keyDown[KEY_SHIFT] ? -opacity : opacity) * 0.05; + + // Iterate over the entire brush image + for (int y = 0; y < brushImageHeight; ++y) + { + for (int x = 0; x < brushImageWidth; ++x) + { + // Get the current terrain height at that position (centred to the brush's size) + IntVector2 pos = IntVector2(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2)); + Color newColor = terrainImage.GetPixel(pos.x, pos.y); + Color brushColor = scaledSelectedBrushImage.GetPixel(x, y); + // Now apply the brush to the terrain (we only use the alpha) + newColor.r += brushColor.a * modifier; + newColor.g += brushColor.a * modifier; + newColor.b += brushColor.a * modifier; + terrainImage.SetPixel(pos.x, pos.y, newColor); + } + } + + // Smooth the terrain a bit + TerrainEditorUpdateChanges@ smoothUpdateChanges = TerrainEditorUpdateChanges(); + UpdateTerrainSmooth(terrainImage, position, smoothUpdateChanges); + + updateChanges.newImage = smoothUpdateChanges.newImage; + } + + private void UpdateTerrainSmooth(Image@ terrainImage, IntVector2 position, TerrainEditorUpdateChanges@ updateChanges) + { + uint brushImageWidth = scaledSelectedBrushImage.width; + uint brushImageHeight = scaledSelectedBrushImage.height; + + updateChanges.offset = IntVector2(position.x - (brushImageWidth / 2), position.y - (brushImageHeight / 2)); + if (updateChanges.offset.x < 0) updateChanges.offset.x = 0; + if (updateChanges.offset.y < 0) updateChanges.offset.y = 0; + + IntRect boundsRect = IntRect(updateChanges.offset.x, updateChanges.offset.y, updateChanges.offset.x + brushImageWidth, updateChanges.offset.y + brushImageHeight); + boundsRect = ClipIntRectToHeightmapBounds(terrainImage, boundsRect); + + updateChanges.oldImage = terrainImage.GetSubimage(boundsRect); + + // Iterate over the entire brush image + for (int y = 0; y < brushImageHeight; ++y) + { + for (int x = 0; x < brushImageWidth; ++x) + { + Color brushColor = scaledSelectedBrushImage.GetPixel(x, y); + + // Only take opaque pixels into consideration for now + if (brushColor.a == 0) + continue; + + // Get the current terrain height at that position (centred to the brush's size) + IntVector2 pos = IntVector2(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2)); + + // Make sure the pixel we're working on is atleast one pixel inside the terrainImage + // as we need an adjacent pixel on all sides for the smoothing algorithm to work + if (pos.x < 0 || pos.y < 0 || pos.x > terrainImage.width - 1 || pos.y > terrainImage.height - 1) + continue; + + Color brp = terrainImage.GetPixel(pos.x + 1, pos.y + 1); // bottomRightPixel + Color rp = terrainImage.GetPixel(pos.x + 1, pos.y); // rightPixel + Color trp = terrainImage.GetPixel(pos.x + 1, pos.y - 1); // topRightPixel + Color blp = terrainImage.GetPixel(pos.x - 1, pos.y + 1); // bottomLeftPixel + Color lp = terrainImage.GetPixel(pos.x - 1, pos.y); // leftPixel + Color tlp = terrainImage.GetPixel(pos.x - 1, pos.y - 1); // topLeftPixel + Color bp = terrainImage.GetPixel(pos.x, pos.y + 1); // bottomPixel + Color cp = terrainImage.GetPixel(pos.x, pos.y); // centerPixel + Color tp = terrainImage.GetPixel(pos.x, pos.y - 1); // topPixel + + Color avgColor = Color( + ((brp.r + rp.r + trp.r + blp.r + lp.r + tlp.r + bp.r + cp.r + tp.r) / 9), + ((brp.g + rp.g + trp.g + blp.g + lp.g + tlp.g + bp.g + cp.g + tp.g) / 9), + ((brp.b + rp.b + trp.b + blp.b + lp.b + tlp.b + bp.b + cp.b + tp.b) / 9) + ); + + Color newColor = Color( + (Difference(cp.r, avgColor.r) * brushColor.a) + Smaller(cp.r, avgColor.r), + (Difference(cp.g, avgColor.g) * brushColor.a) + Smaller(cp.g, avgColor.g), + (Difference(cp.b, avgColor.b) * brushColor.a) + Smaller(cp.b, avgColor.b) + ); + + terrainImage.SetPixel(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2), avgColor); + } + } + + updateChanges.newImage = terrainImage.GetSubimage(boundsRect); + } + + private void UpdateTerrainSetHeight(Image@ terrainImage, IntVector2 position, TerrainEditorUpdateChanges@ updateChanges) + { + uint brushImageWidth = scaledSelectedBrushImage.width; + uint brushImageHeight = scaledSelectedBrushImage.height; + + updateChanges.offset = IntVector2(position.x - (brushImageWidth / 2), position.y - (brushImageHeight / 2)); + if (updateChanges.offset.x < 0) updateChanges.offset.x = 0; + if (updateChanges.offset.y < 0) updateChanges.offset.y = 0; + + IntRect boundsRect = IntRect(updateChanges.offset.x, updateChanges.offset.y, updateChanges.offset.x + brushImageWidth, updateChanges.offset.y + brushImageHeight); + boundsRect = ClipIntRectToHeightmapBounds(terrainImage, boundsRect); + + updateChanges.oldImage = terrainImage.GetSubimage(boundsRect); + + float targetHeight = brushHeightSlider.value / 25; + + // Iterate over the entire brush image + for (int y = 0; y < brushImageHeight; ++y) + { + for (int x = 0; x < brushImageWidth; ++x) + { + // Get the current terrain height at that position (centred to the brush's size) + IntVector2 pos = IntVector2(position.x + x - (brushImageWidth / 2), position.y + y - (brushImageHeight / 2)); + Color newColor = terrainImage.GetPixel(pos.x, pos.y); + Color brushColor = scaledSelectedBrushImage.GetPixel(x, y); + // Ease the height to the target height (using the brush alpha as the 'speed'), making sure the alpha isn't 0 + newColor.r += (targetHeight - newColor.r) * brushColor.a; + newColor.g += (targetHeight - newColor.g) * brushColor.a; + newColor.b += (targetHeight - newColor.b) * brushColor.a; + // Set it to target if its close enough + if (Difference(targetHeight, newColor.r) < 0.01f) newColor.r = targetHeight; + if (Difference(targetHeight, newColor.g) < 0.01f) newColor.g = targetHeight; + if (Difference(targetHeight, newColor.b) < 0.01f) newColor.b = targetHeight; + terrainImage.SetPixel(pos.x, pos.y, newColor); + } + } + + updateChanges.newImage = terrainImage.GetSubimage(boundsRect); + } + + private void UpdateTerrainSetConstantHeight(Image@ terrainImage, float height) + { + height = Clamp(height, 0.0, 1.0); + Color newColor = Color(height, height, height); + // Iterate over the entire brush image + for (int y = 0; y < terrainImage.height; ++y) + { + for (int x = 0; x < terrainImage.width; ++x) + { + terrainImage.SetPixel(x, y, newColor); + } + } + } + + private IntRect ClipIntRectToHeightmapBounds(Image@ terrainImage, IntRect intRect) { + if (intRect.left > terrainImage.width) + intRect.left = terrainImage.width; + + if (intRect.right > terrainImage.width) + intRect.right = terrainImage.width; + + if (intRect.top > terrainImage.height) + intRect.top = terrainImage.height; + + if (intRect.bottom > terrainImage.height) + intRect.bottom = terrainImage.height; + + return intRect; + } +} diff --git a/bin/Data/Scripts/Editor/EditorToolBar.as b/bin/Data/Scripts/Editor/EditorToolBar.as new file mode 100644 index 0000000..1266d57 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorToolBar.as @@ -0,0 +1,535 @@ + +bool subscribedToEditorToolBar = false; +bool toolBarDirty = true; +UIElement@ toolBar; + +const StringHash VIEW_MODE("VIEW_MODE"); + +void CreateToolBar() +{ + toolBar = BorderImage("ToolBar"); + toolBar.style = "EditorToolBar"; + toolBar.SetLayout(LM_HORIZONTAL); + toolBar.layoutSpacing = 4; + toolBar.layoutBorder = IntRect(8, 4, 4, 8); + toolBar.opacity = uiMaxOpacity; + toolBar.SetFixedSize(graphics.width, 42); + toolBar.SetPosition(0, uiMenuBar.height); + ui.root.AddChild(toolBar); + + UIElement@ runUpdateGroup = CreateGroup("RunUpdateGroup", LM_HORIZONTAL); + runUpdateGroup.AddChild(CreateToolBarToggle("RunUpdatePlay")); + runUpdateGroup.AddChild(CreateToolBarToggle("RunUpdatePause")); + runUpdateGroup.AddChild(CreateToolBarToggle("RevertOnPause")); + FinalizeGroupHorizontal(runUpdateGroup, "ToolBarToggle"); + toolBar.AddChild(runUpdateGroup); + + toolBar.AddChild(CreateToolBarSpacer(4)); + UIElement@ editModeGroup = CreateGroup("EditModeGroup", LM_HORIZONTAL); + editModeGroup.AddChild(CreateToolBarToggle("EditMove")); + editModeGroup.AddChild(CreateToolBarToggle("EditRotate")); + editModeGroup.AddChild(CreateToolBarToggle("EditScale")); + editModeGroup.AddChild(CreateToolBarToggle("EditSelect")); + FinalizeGroupHorizontal(editModeGroup, "ToolBarToggle"); + toolBar.AddChild(editModeGroup); + + UIElement@ axisModeGroup = CreateGroup("AxisModeGroup", LM_HORIZONTAL); + axisModeGroup.AddChild(CreateToolBarToggle("AxisWorld")); + axisModeGroup.AddChild(CreateToolBarToggle("AxisLocal")); + FinalizeGroupHorizontal(axisModeGroup, "ToolBarToggle"); + toolBar.AddChild(axisModeGroup); + + toolBar.AddChild(CreateToolBarSpacer(4)); + toolBar.AddChild(CreateToolBarToggle("MoveSnap")); + toolBar.AddChild(CreateToolBarToggle("RotateSnap")); + toolBar.AddChild(CreateToolBarToggle("ScaleSnap")); + + UIElement@ snapScaleModeGroup = CreateGroup("SnapScaleModeGroup", LM_HORIZONTAL); + snapScaleModeGroup.AddChild(CreateToolBarToggle("SnapScaleHalf")); + snapScaleModeGroup.AddChild(CreateToolBarToggle("SnapScaleQuarter")); + FinalizeGroupHorizontal(snapScaleModeGroup, "ToolBarToggle"); + toolBar.AddChild(snapScaleModeGroup); + + toolBar.AddChild(CreateToolBarSpacer(4)); + UIElement@ pickModeGroup = CreateGroup("PickModeGroup", LM_HORIZONTAL); + pickModeGroup.AddChild(CreateToolBarToggle("PickGeometries")); + pickModeGroup.AddChild(CreateToolBarToggle("PickLights")); + pickModeGroup.AddChild(CreateToolBarToggle("PickZones")); + pickModeGroup.AddChild(CreateToolBarToggle("PickRigidBodies")); + pickModeGroup.AddChild(CreateToolBarToggle("PickUIElements")); + FinalizeGroupHorizontal(pickModeGroup, "ToolBarToggle"); + toolBar.AddChild(pickModeGroup); + + toolBar.AddChild(CreateToolBarSpacer(4)); + UIElement@ fillModeGroup = CreateGroup("FillModeGroup", LM_HORIZONTAL); + fillModeGroup.AddChild(CreateToolBarToggle("FillPoint")); + fillModeGroup.AddChild(CreateToolBarToggle("FillWireFrame")); + fillModeGroup.AddChild(CreateToolBarToggle("FillSolid")); + FinalizeGroupHorizontal(fillModeGroup, "ToolBarToggle"); + toolBar.AddChild(fillModeGroup); + + toolBar.AddChild(CreateToolBarSpacer(4)); + UIElement@ originGroup = CreateGroup("OriginGroup", LM_HORIZONTAL); + originGroup.AddChild(CreateToolBarToggle("ShowOrigin")); + FinalizeGroupHorizontal(originGroup, "ToolBarToggle"); + toolBar.AddChild(originGroup); + + toolBar.AddChild(CreateToolBarSpacer(4)); + DropDownList@ viewportModeList = DropDownList(); + viewportModeList.style = AUTO_STYLE; + viewportModeList.SetMaxSize(100, 18); + viewportModeList.SetAlignment(HA_LEFT, VA_CENTER); + toolBar.AddChild(viewportModeList); + viewportModeList.AddItem(CreateViewPortModeText("Single", VIEWPORT_SINGLE)); + viewportModeList.AddItem(CreateViewPortModeText("Compact", VIEWPORT_COMPACT)); + viewportModeList.AddItem(CreateViewPortModeText("Vertical Split", VIEWPORT_LEFT|VIEWPORT_RIGHT)); + viewportModeList.AddItem(CreateViewPortModeText("Horizontal Split", VIEWPORT_TOP|VIEWPORT_BOTTOM)); + viewportModeList.AddItem(CreateViewPortModeText("Quad", VIEWPORT_TOP_LEFT|VIEWPORT_TOP_RIGHT|VIEWPORT_BOTTOM_LEFT|VIEWPORT_BOTTOM_RIGHT)); + viewportModeList.AddItem(CreateViewPortModeText("1 Top / 2 Bottom", VIEWPORT_TOP|VIEWPORT_BOTTOM_LEFT|VIEWPORT_BOTTOM_RIGHT)); + viewportModeList.AddItem(CreateViewPortModeText("2 Top / 1 Bottom", VIEWPORT_TOP_LEFT|VIEWPORT_TOP_RIGHT|VIEWPORT_BOTTOM)); + viewportModeList.AddItem(CreateViewPortModeText("1 Left / 2 Right", VIEWPORT_LEFT|VIEWPORT_TOP_RIGHT|VIEWPORT_BOTTOM_RIGHT)); + viewportModeList.AddItem(CreateViewPortModeText("2 Left / 1 Right", VIEWPORT_TOP_LEFT|VIEWPORT_BOTTOM_LEFT|VIEWPORT_RIGHT)); + for (uint i = 0; i < viewportModeList.numItems; ++i) + { + if (viewportModeList.items[i].vars[VIEW_MODE].GetUInt() == viewportMode) + { + viewportModeList.selection = i; + break; + } + } + SubscribeToEvent(viewportModeList, "ItemSelected", "ToolBarSetViewportMode"); +} + +Button@ CreateToolBarButton(const String&in title) +{ + Button@ button = Button(title); + button.defaultStyle = uiStyle; + button.style = "ToolBarButton"; + + CreateToolBarIcon(button); + CreateToolTip(button, title, IntVector2(button.width + 10, button.height - 10)); + + return button; +} + +CheckBox@ CreateToolBarToggle(const String&in title) +{ + CheckBox@ toggle = CheckBox(title); + toggle.defaultStyle = uiStyle; + toggle.style = "ToolBarToggle"; + + CreateToolBarIcon(toggle); + CreateToolTip(toggle, title, IntVector2(toggle.width + 10, toggle.height - 10)); + + return toggle; +} + +void CreateToolBarIcon(UIElement@ element) +{ + BorderImage@ icon = BorderImage("Icon"); + icon.defaultStyle = iconStyle; + icon.style = element.name; + icon.SetFixedSize(30, 30); + icon.blendMode = BLEND_ALPHA; + element.AddChild(icon); +} + +UIElement@ CreateGroup(const String&in title, LayoutMode layoutMode) +{ + UIElement@ group = UIElement(title); + group.defaultStyle = uiStyle; + group.layoutMode = layoutMode; + return group; +} + +void FinalizeGroupHorizontal(UIElement@ group, const String&in baseStyle) +{ + for (uint i = 0; i < group.numChildren; ++i) + { + UIElement@ child = group.children[i]; + + if (i == 0 && i < group.numChildren - 1) + child.style = baseStyle + "GroupLeft"; + else if (i < group.numChildren - 1) + child.style = baseStyle + "GroupMiddle"; + else + child.style = baseStyle + "GroupRight"; + } + + group.maxSize = group.size; +} + +UIElement@ CreateToolBarSpacer(uint width) +{ + UIElement@ spacer = UIElement(); + spacer.SetFixedWidth(width); + return spacer; +} + +UIElement@ CreateToolTip(UIElement@ parent, const String&in title, const IntVector2&in offset) +{ + ToolTip@ toolTip = parent.CreateChild("ToolTip"); + toolTip.position = offset; + + BorderImage@ textHolder = toolTip.CreateChild("BorderImage"); + textHolder.SetStyle("ToolTipBorderImage"); + + Text@ toolTipText = textHolder.CreateChild("Text"); + toolTipText.SetStyle("ToolTipText"); + toolTipText.autoLocalizable = true; + toolTipText.text = title; + + return toolTip; +} + +void ToolBarRunUpdatePlay(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + StartSceneUpdate(); + toolBarDirty = true; +} + +void ToolBarRunUpdatePause(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + StopSceneUpdate(); + toolBarDirty = true; +} + +void ToolBarRevertOnPause(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + revertOnPause = edit.checked; + toolBarDirty = true; +} + +void ToolBarEditModeMove(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + editMode = EDIT_MOVE; + toolBarDirty = true; +} + +void ToolBarEditModeRotate(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + editMode = EDIT_ROTATE; + toolBarDirty = true; +} + +void ToolBarEditModeScale(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + editMode = EDIT_SCALE; + toolBarDirty = true; +} + +void ToolBarEditModeSelect(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + editMode = EDIT_SELECT; + toolBarDirty = true; +} + +void ToolBarAxisModeWorld(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + axisMode = AXIS_WORLD; + toolBarDirty = true; +} + +void ToolBarAxisModeLocal(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + axisMode = AXIS_LOCAL; + toolBarDirty = true; +} + +void ToolBarMoveSnap(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + moveSnap = edit.checked; + toolBarDirty = true; +} + +void ToolBarRotateSnap(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + rotateSnap = edit.checked; + toolBarDirty = true; +} + +void ToolBarScaleSnap(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + scaleSnap = edit.checked; + toolBarDirty = true; +} + +void ToolBarSnapScaleModeHalf(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + { + snapScaleMode = SNAP_SCALE_HALF; + snapScale = 0.5; + } + else if (snapScaleMode == SNAP_SCALE_HALF) + { + snapScaleMode = SNAP_SCALE_FULL; + snapScale = 1.0; + } + toolBarDirty = true; +} + +void ToolBarSnapScaleModeQuarter(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + { + snapScaleMode = SNAP_SCALE_QUARTER; + snapScale = 0.25; + } + else if (snapScaleMode == SNAP_SCALE_QUARTER) + { + snapScaleMode = SNAP_SCALE_FULL; + snapScale = 1.0; + } + toolBarDirty = true; +} + +void ToolBarPickModeGeometries(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + pickMode = PICK_GEOMETRIES; + toolBarDirty = true; +} + +void ToolBarPickModeLights(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + pickMode = PICK_LIGHTS; + toolBarDirty = true; +} + +void ToolBarPickModeZones(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + pickMode = PICK_ZONES; + toolBarDirty = true; +} + +void ToolBarPickModeRigidBodies(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + pickMode = PICK_RIGIDBODIES; + toolBarDirty = true; +} + +void ToolBarPickModeUIElements(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + pickMode = PICK_UI_ELEMENTS; + toolBarDirty = true; +} + +void ToolBarFillModePoint(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + { + fillMode = FILL_POINT; + SetFillMode(fillMode); + } + toolBarDirty = true; +} + +void ToolBarFillModeWireFrame(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + { + fillMode = FILL_WIREFRAME; + SetFillMode(fillMode); + } + toolBarDirty = true; +} + +void ToolBarFillModeSolid(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + if (edit.checked) + { + fillMode = FILL_SOLID; + SetFillMode(fillMode); + } + toolBarDirty = true; +} + +void ToolBarSetViewportMode(StringHash eventType, VariantMap& eventData) +{ + DropDownList@ dropDown = eventData["Element"].GetPtr(); + UIElement@ selected = dropDown.selectedItem; + dropDown.focus = false; // Lose the focus so the RMB dragging, immediately followed after changing viewport setup, behaves as expected + uint mode = selected.vars[VIEW_MODE].GetUInt(); + SetViewportMode(mode); +} + +void ToolBarShowOrigin(StringHash eventType, VariantMap& eventData) +{ + CheckBox@ edit = eventData["Element"].GetPtr(); + + ShowOrigins (edit.checked); + + toolBarDirty = true; +} + +void UpdateDirtyToolBar() +{ + if (toolBar is null || !toolBarDirty) + return; + + CheckBox@ runUpdatePlayToggle = toolBar.GetChild("RunUpdatePlay", true); + if (runUpdatePlayToggle.checked != runUpdate) + runUpdatePlayToggle.checked = runUpdate; + + CheckBox@ runUpdatePauseToggle = toolBar.GetChild("RunUpdatePause", true); + if (runUpdatePauseToggle.checked != (runUpdate == false)) + runUpdatePauseToggle.checked = runUpdate == false; + + CheckBox@ revertOnPauseToggle = toolBar.GetChild("RevertOnPause", true); + if (revertOnPauseToggle.checked != revertOnPause) + revertOnPauseToggle.checked = revertOnPause; + + CheckBox@ editMoveToggle = toolBar.GetChild("EditMove", true); + if (editMoveToggle.checked != (editMode == EDIT_MOVE)) + editMoveToggle.checked = editMode == EDIT_MOVE; + + CheckBox@ editRotateToggle = toolBar.GetChild("EditRotate", true); + if (editRotateToggle.checked != (editMode == EDIT_ROTATE)) + editRotateToggle.checked = editMode == EDIT_ROTATE; + + CheckBox@ editScaleToggle = toolBar.GetChild("EditScale", true); + if (editScaleToggle.checked != (editMode == EDIT_SCALE)) + editScaleToggle.checked = editMode == EDIT_SCALE; + + CheckBox@ editSelectToggle = toolBar.GetChild("EditSelect", true); + if (editSelectToggle.checked != (editMode == EDIT_SELECT)) + editSelectToggle.checked = editMode == EDIT_SELECT; + + CheckBox@ axisWorldToggle = toolBar.GetChild("AxisWorld", true); + if (axisWorldToggle.checked != (axisMode == AXIS_WORLD)) + axisWorldToggle.checked = axisMode == AXIS_WORLD; + + CheckBox@ axisLocalToggle = toolBar.GetChild("AxisLocal", true); + if (axisLocalToggle.checked != (axisMode == AXIS_LOCAL)) + axisLocalToggle.checked = axisMode == AXIS_LOCAL; + + CheckBox@ moveSnapToggle = toolBar.GetChild("MoveSnap", true); + if (moveSnapToggle.checked != moveSnap) + moveSnapToggle.checked = moveSnap; + + CheckBox@ rotateSnapToggle = toolBar.GetChild("RotateSnap", true); + if (rotateSnapToggle.checked != rotateSnap) + rotateSnapToggle.checked = rotateSnap; + + CheckBox@ scaleSnapToggle = toolBar.GetChild("ScaleSnap", true); + if (scaleSnapToggle.checked != scaleSnap) + scaleSnapToggle.checked = scaleSnap; + + CheckBox@ snapStepHalfToggle = toolBar.GetChild("SnapScaleHalf", true); + if (snapStepHalfToggle.checked != (snapScaleMode == SNAP_SCALE_HALF)) + snapStepHalfToggle.checked = snapScaleMode == SNAP_SCALE_HALF; + + CheckBox@ snapStepQuarterToggle = toolBar.GetChild("SnapScaleQuarter", true); + if (snapStepQuarterToggle.checked != (snapScaleMode == SNAP_SCALE_QUARTER)) + snapStepQuarterToggle.checked = snapScaleMode == SNAP_SCALE_QUARTER; + + CheckBox@ pickGeometriesToggle = toolBar.GetChild("PickGeometries", true); + if (pickGeometriesToggle.checked != (pickMode == PICK_GEOMETRIES)) + pickGeometriesToggle.checked = pickMode == PICK_GEOMETRIES; + + CheckBox@ pickLightsToggle = toolBar.GetChild("PickLights", true); + if (pickLightsToggle.checked != (pickMode == PICK_LIGHTS)) + pickLightsToggle.checked = pickMode == PICK_LIGHTS; + + CheckBox@ pickZonesToggle = toolBar.GetChild("PickZones", true); + if (pickZonesToggle.checked != (pickMode == PICK_ZONES)) + pickZonesToggle.checked = pickMode == PICK_ZONES; + + CheckBox@ pickRigidBodiesToggle = toolBar.GetChild("PickRigidBodies", true); + if (pickRigidBodiesToggle.checked != (pickMode == PICK_RIGIDBODIES)) + pickRigidBodiesToggle.checked = pickMode == PICK_RIGIDBODIES; + + CheckBox@ pickUIElementsToggle = toolBar.GetChild("PickUIElements", true); + if (pickUIElementsToggle.checked != (pickMode == PICK_UI_ELEMENTS)) + pickUIElementsToggle.checked = pickMode == PICK_UI_ELEMENTS; + + CheckBox@ fillPointToggle = toolBar.GetChild("FillPoint", true); + if (fillPointToggle.checked != (fillMode == FILL_POINT)) + fillPointToggle.checked = fillMode == FILL_POINT; + + CheckBox@ fillWireFrameToggle = toolBar.GetChild("FillWireFrame", true); + if (fillWireFrameToggle.checked != (fillMode == FILL_WIREFRAME)) + fillWireFrameToggle.checked = fillMode == FILL_WIREFRAME; + + CheckBox@ fillSolidToggle = toolBar.GetChild("FillSolid", true); + if (fillSolidToggle.checked != (fillMode == FILL_SOLID)) + fillSolidToggle.checked = fillMode == FILL_SOLID; + + CheckBox@ showOriginToggle = toolBar.GetChild("ShowOrigin", true); + if (showOriginToggle.checked != (EditorOriginShow == true)) + showOriginToggle.checked = EditorOriginShow == true; + + if (!subscribedToEditorToolBar) + { + SubscribeToEvent(runUpdatePlayToggle, "Toggled", "ToolBarRunUpdatePlay"); + SubscribeToEvent(runUpdatePauseToggle, "Toggled", "ToolBarRunUpdatePause"); + SubscribeToEvent(revertOnPauseToggle, "Toggled", "ToolBarRevertOnPause"); + SubscribeToEvent(editMoveToggle, "Toggled", "ToolBarEditModeMove"); + SubscribeToEvent(editRotateToggle, "Toggled", "ToolBarEditModeRotate"); + SubscribeToEvent(editScaleToggle, "Toggled", "ToolBarEditModeScale"); + SubscribeToEvent(editSelectToggle, "Toggled", "ToolBarEditModeSelect"); + SubscribeToEvent(axisWorldToggle, "Toggled", "ToolBarAxisModeWorld"); + SubscribeToEvent(axisLocalToggle, "Toggled", "ToolBarAxisModeLocal"); + SubscribeToEvent(moveSnapToggle, "Toggled", "ToolBarMoveSnap"); + SubscribeToEvent(rotateSnapToggle, "Toggled", "ToolBarRotateSnap"); + SubscribeToEvent(scaleSnapToggle, "Toggled", "ToolBarScaleSnap"); + SubscribeToEvent(snapStepHalfToggle, "Toggled", "ToolBarSnapScaleModeHalf"); + SubscribeToEvent(snapStepQuarterToggle, "Toggled", "ToolBarSnapScaleModeQuarter"); + SubscribeToEvent(pickGeometriesToggle, "Toggled", "ToolBarPickModeGeometries"); + SubscribeToEvent(pickLightsToggle, "Toggled", "ToolBarPickModeLights"); + SubscribeToEvent(pickZonesToggle, "Toggled", "ToolBarPickModeZones"); + SubscribeToEvent(pickRigidBodiesToggle, "Toggled", "ToolBarPickModeRigidBodies"); + SubscribeToEvent(pickUIElementsToggle, "Toggled", "ToolBarPickModeUIElements"); + SubscribeToEvent(fillPointToggle, "Toggled", "ToolBarFillModePoint"); + SubscribeToEvent(fillWireFrameToggle, "Toggled", "ToolBarFillModeWireFrame"); + SubscribeToEvent(fillSolidToggle, "Toggled", "ToolBarFillModeSolid"); + SubscribeToEvent(showOriginToggle, "Toggled", "ToolBarShowOrigin"); + subscribedToEditorToolBar = true; + } + + toolBarDirty = false; +} + +Text@ CreateViewPortModeText(String text_, uint mode) +{ + Text@ text = Text(); + text.text = text_; + text.vars[VIEW_MODE] = mode; + text.style = "EditorEnumAttributeText"; + return text; +} diff --git a/bin/Data/Scripts/Editor/EditorUI.as b/bin/Data/Scripts/Editor/EditorUI.as new file mode 100644 index 0000000..1f0499d --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorUI.as @@ -0,0 +1,2266 @@ +// Urho3D editor user interface + +XMLFile@ uiStyle; +XMLFile@ iconStyle; +UIElement@ uiMenuBar; +UIElement@ quickMenu; +Menu@ recentSceneMenu; +Window@ mruScenesPopup; +Array quickMenuItems; +FileSelector@ uiFileSelector; +String consoleCommandInterpreter; +Window@ contextMenu; +uint stepColoringGroupUpdate = 100; // ms +uint timeToNextColoringGroupUpdate = 0; + +const StringHash UI_ELEMENT_TYPE("UIElement"); +const StringHash WINDOW_TYPE("Window"); +const StringHash MENU_TYPE("Menu"); +const StringHash TEXT_TYPE("Text"); +const StringHash CURSOR_TYPE("Cursor"); + +const String AUTO_STYLE(""); // Empty string means auto style, i.e. applying style according to UI-element's type automatically +const String TEMP_SCENE_NAME("_tempscene_.xml"); +const String TEMP_BINARY_SCENE_NAME("_tempscene_.bin"); +const StringHash CALLBACK_VAR("Callback"); +const StringHash INDENT_MODIFIED_BY_ICON_VAR("IconIndented"); + +const StringHash VAR_CONTEXT_MENU_HANDLER("ContextMenuHandler"); + +const int SHOW_POPUP_INDICATOR = -1; +const uint MAX_QUICK_MENU_ITEMS = 10; + +const uint maxRecentSceneCount = 5; + +Array uiSceneFilters = {"*.xml", "*.json", "*.bin", "*.*"}; +Array uiElementFilters = {"*.xml"}; +Array uiAllFilters = {"*.*"}; +Array uiScriptFilters = {"*.as", "*.*"}; +Array uiParticleFilters = {"*.xml"}; +Array uiRenderPathFilters = {"*.xml"}; +Array uiExportPathFilters = {"*.obj"}; +uint uiSceneFilter = 0; +uint uiElementFilter = 0; +uint uiNodeFilter = 0; +uint uiImportFilter = 0; +uint uiScriptFilter = 0; +uint uiParticleFilter = 0; +uint uiRenderPathFilter = 0; +uint uiExportFilter = 0; +String uiScenePath = fileSystem.programDir + "Data/Scenes"; +String uiElementPath = fileSystem.programDir + "Data/UI"; +String uiNodePath = fileSystem.programDir + "Data/Objects"; +String uiImportPath; +String uiExportPath; +String uiScriptPath = fileSystem.programDir + "Data/Scripts"; +String uiParticlePath = fileSystem.programDir + "Data/Particles"; +String uiRenderPathPath = fileSystem.programDir + "CoreData/RenderPaths"; +Array uiRecentScenes; +String screenshotDir = fileSystem.programDir + "Screenshots"; + +bool uiFaded = false; +float uiMinOpacity = 0.3; +float uiMaxOpacity = 0.7; +bool uiHidden = false; + +TerrainEditor terrainEditor; + +void CreateUI() +{ + // Remove all existing UI content in case we are reloading the editor script + /// \todo The console will not be properly recreated as it has already been created once + ui.root.RemoveAllChildren(); + + uiStyle = GetEditorUIXMLFile("UI/DefaultStyle.xml"); + ui.root.defaultStyle = uiStyle; + iconStyle = GetEditorUIXMLFile("UI/EditorIcons.xml"); + + graphics.windowIcon = cache.GetResource("Image", "Textures/UrhoIcon.png"); + + CreateCursor(); + CreateMenuBar(); + CreateToolBar(); + CreateSecondaryToolBar(); + CreateQuickMenu(); + CreateContextMenu(); + CreateHierarchyWindow(); + CreateAttributeInspectorWindow(); + CreateEditorSettingsDialog(); + CreateEditorPreferencesDialog(); + CreateMaterialEditor(); + CreateParticleEffectEditor(); + CreateSpawnEditor(); + CreateSoundTypeEditor(); + CreateStatsBar(); + CreateConsole(); + CreateDebugHud(); + CreateResourceBrowser(); + CreateCamera(); + CreateLayerEditor(); + CreateColorWheel(); + + terrainEditor.Create(); + + SubscribeToEvent("ScreenMode", "ResizeUI"); + SubscribeToEvent("MenuSelected", "HandleMenuSelected"); + SubscribeToEvent("ChangeLanguage", "HandleChangeLanguage"); + + SubscribeToEvent("WheelChangeColor", "HandleWheelChangeColor"); + SubscribeToEvent("WheelSelectColor", "HandleWheelSelectColor"); + SubscribeToEvent("WheelDiscardColor", "HandleWheelDiscardColor"); +} + +void ResizeUI() +{ + // Resize menu bar + uiMenuBar.SetFixedWidth(graphics.width); + + // Resize tool bar + toolBar.SetFixedWidth(graphics.width); + + // Resize secondary tool bar + secondaryToolBar.SetFixedHeight(graphics.height); + + // Relayout windows + Array children = ui.root.GetChildren(); + for (uint i = 0; i < children.length; ++i) + { + if (children[i].type == WINDOW_TYPE) + AdjustPosition(children[i]); + } + + // Relayout root UI element + editorUIElement.SetSize(graphics.width, graphics.height); + + // Set new viewport area and reset the viewport layout + viewportArea = IntRect(0, 0, graphics.width, graphics.height); + SetViewportMode(viewportMode); +} + +void AdjustPosition(Window@ window) +{ + IntVector2 position = window.position; + IntVector2 size = window.size; + IntVector2 extend = position + size; + if (extend.x > graphics.width) + position.x = Max(10, graphics.width - size.x - 10); + if (extend.y > graphics.height) + position.y = Max(100, graphics.height - size.y - 10); + window.position = position; +} + +void CreateCursor() +{ + Cursor@ cursor = Cursor("Cursor"); + cursor.SetStyleAuto(uiStyle); + cursor.SetPosition(graphics.width / 2, graphics.height / 2); + ui.cursor = cursor; + if (GetPlatform() == "Android" || GetPlatform() == "iOS") + ui.cursor.visible = false; +} + +// AngelScript does not support closures (yet), but funcdef should do just fine as a workaround for a few cases here for now +funcdef bool MENU_CALLBACK(); +Array menuCallbacks; +MENU_CALLBACK@ messageBoxCallback; + +void HandleQuickSearchChange(StringHash eventType, VariantMap& eventData) +{ + LineEdit@ search = eventData["Element"].GetPtr(); + if (search is null) + return; + + PerformQuickMenuSearch(search.text.ToLower().Trimmed()); +} + +void HandleQuickSearchFinish(StringHash eventType, VariantMap& eventData) +{ + Menu@ menu = quickMenu.GetChild("ResultsMenu", true); + if (menu is null) + return; + + String query = eventData["Text"].GetString(); + if (query.length <= 0) + return; + Array filtered; + { + QuickMenuItem@ qi; + for (uint x=0; x < quickMenuItems.length; x++) + { + @qi = quickMenuItems[x]; + int find = qi.action.Find(query, 0, false); + if (find > -1) + { + qi.sortScore = find; + filtered.Push(qi); + } + } + } + + filtered.Sort(); + if (!filtered.empty) + { + VariantMap data; + Menu@ item = CreateMenuItem(filtered[0].action, filtered[0].callback); + data["Element"] = item; + item.SendEvent("MenuSelected", data); + } +} + +void PerformQuickMenuSearch(const String&in query) +{ + Menu@ menu = quickMenu.GetChild("ResultsMenu", true); + if (menu is null) + return; + + menu.RemoveAllChildren(); + uint limit = 0; + + if (query.length > 0) + { + int lastIndex = 0; + uint score = 0; + int index = 0; + + Array filtered; + { + QuickMenuItem@ qi; + for (uint x=0; x < quickMenuItems.length; x++) + { + @qi = quickMenuItems[x]; + int find = qi.action.Find(query, 0, false); + if (find > -1) + { + qi.sortScore = find; + filtered.Push(qi); + } + } + } + + filtered.Sort(); + + { + QuickMenuItem@ qi; + limit = filtered.length > MAX_QUICK_MENU_ITEMS ? MAX_QUICK_MENU_ITEMS : filtered.length; + for (uint x=0; x < limit; x++) + { + @qi = filtered[x]; + Menu@ item = CreateMenuItem(qi.action, qi.callback); + item.SetMaxSize(1000,16); + menu.AddChild(item); + } + } + } + + menu.visible = limit > 0; + menu.SetFixedHeight(limit * 16); + quickMenu.BringToFront(); + quickMenu.SetFixedHeight(limit*16 + 62 + (menu.visible ? 6 : 0)); +} + +class QuickMenuItem +{ + String action; + MENU_CALLBACK@ callback; + uint sortScore = 0; + QuickMenuItem(){} + QuickMenuItem(String action, MENU_CALLBACK@ callback) + { + this.action = action; + this.callback = callback; + } + int opCmp(QuickMenuItem@ b) + { + return sortScore - b.sortScore; + } +} + +/// Create popup search menu. +void CreateQuickMenu() +{ + if (quickMenu !is null) + return; + + quickMenu = LoadEditorUI("UI/EditorQuickMenu.xml"); + quickMenu.enabled = false; + quickMenu.visible = false; + quickMenu.opacity = uiMaxOpacity; + + // Handle a dummy search in the quick menu to finalize its initial size to empty + PerformQuickMenuSearch(""); + + ui.root.AddChild(quickMenu); + LineEdit@ search = quickMenu.GetChild("Search", true); + SubscribeToEvent(search, "TextChanged", "HandleQuickSearchChange"); + SubscribeToEvent(search, "TextFinished", "HandleQuickSearchFinish"); + UIElement@ closeButton = quickMenu.GetChild("CloseButton", true); + SubscribeToEvent(closeButton, "Pressed", "ToggleQuickMenu"); +} + +void ToggleQuickMenu() +{ + quickMenu.enabled = !quickMenu.enabled && ui.cursor.visible; + quickMenu.visible = quickMenu.enabled; + if (quickMenu.enabled) + { + quickMenu.position = ui.cursorPosition - IntVector2(20,70); + LineEdit@ search = quickMenu.GetChild("Search", true); + search.text = ""; + search.focus = true; + } +} + +/// Create top menu bar. +void CreateMenuBar() +{ + uiMenuBar = BorderImage("MenuBar"); + ui.root.AddChild(uiMenuBar); + uiMenuBar.enabled = true; + uiMenuBar.style = "EditorMenuBar"; + uiMenuBar.SetLayout(LM_HORIZONTAL); + uiMenuBar.opacity = uiMaxOpacity; + uiMenuBar.SetFixedWidth(graphics.width); + + { + Menu@ menu = CreateMenu("File"); + Window@ popup = menu.popup; + popup.AddChild(CreateMenuItem("New scene", @ResetScene, KEY_N, QUAL_SHIFT | QUAL_CTRL)); + popup.AddChild(CreateMenuItem("Open scene...", @PickFile, KEY_O, QUAL_CTRL)); + popup.AddChild(CreateMenuItem("Save scene", @SaveSceneWithExistingName, KEY_S, QUAL_CTRL)); + popup.AddChild(CreateMenuItem("Save scene as...", @PickFile, KEY_S, QUAL_SHIFT | QUAL_CTRL)); + recentSceneMenu = CreateMenuItem("Open recent scene", null, SHOW_POPUP_INDICATOR); + popup.AddChild(recentSceneMenu); + mruScenesPopup = CreatePopup(recentSceneMenu); + PopulateMruScenes(); + CreateChildDivider(popup); + + Menu@ childMenu = CreateMenuItem("menu Load node", null, SHOW_POPUP_INDICATOR); + Window@ childPopup = CreatePopup(childMenu); + childPopup.AddChild(CreateMenuItem("As replicated...", @PickFile, 0, 0, true, "Load node as replicated...")); + childPopup.AddChild(CreateMenuItem("As local...", @PickFile, 0, 0, true, "Load node as local...")); + popup.AddChild(childMenu); + + popup.AddChild(CreateMenuItem("Save node as...", @PickFile)); + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Import model...", @PickFile)); + popup.AddChild(CreateMenuItem("Import scene...", @PickFile)); + popup.AddChild(CreateMenuItem("Import animation...", @PickFile)); + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Export scene to OBJ...", @PickFile)); + popup.AddChild(CreateMenuItem("Export selected to OBJ...", @PickFile)); + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Run script...", @PickFile)); + popup.AddChild(CreateMenuItem("Set resource path...", @PickFile)); + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Exit", @Exit)); + FinalizedPopupMenu(popup); + uiMenuBar.AddChild(menu); + } + + { + Menu@ menu = CreateMenu("Edit"); + Window@ popup = menu.popup; + popup.AddChild(CreateMenuItem("Undo", @Undo, KEY_Z, QUAL_CTRL)); + popup.AddChild(CreateMenuItem("Redo", @Redo, KEY_Y, QUAL_CTRL)); + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Cut", @Cut, KEY_X, QUAL_CTRL)); + + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + popup.AddChild(CreateMenuItem("Duplicate", @Duplicate, KEY_D, QUAL_CTRL)); + else if (hotKeyMode == HOTKEYS_MODE_BLENDER) + popup.AddChild(CreateMenuItem("Duplicate", @Duplicate, KEY_D, QUAL_SHIFT)); + + popup.AddChild(CreateMenuItem("Copy", @Copy, KEY_C, QUAL_CTRL)); + popup.AddChild(CreateMenuItem("Paste", @Paste, KEY_V, QUAL_CTRL)); + + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + popup.AddChild(CreateMenuItem("Delete", @Delete, KEY_DELETE, QUAL_ANY)); + else if (hotKeyMode == HOTKEYS_MODE_BLENDER) + popup.AddChild(CreateMenuItem("Delete", @BlenderModeDelete, KEY_X, QUAL_ANY)); + + popup.AddChild(CreateMenuItem("Select all", @SelectAll, KEY_A, QUAL_CTRL)); + popup.AddChild(CreateMenuItem("Deselect all", @DeselectAll, KEY_A, QUAL_SHIFT | QUAL_CTRL)); + + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Reset to default", @ResetToDefault)); + CreateChildDivider(popup); + + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + { + popup.AddChild(CreateMenuItem("Reset position", @SceneResetPosition, '1' , QUAL_ALT)); + popup.AddChild(CreateMenuItem("Reset rotation", @SceneResetRotation, '2' , QUAL_ALT)); + popup.AddChild(CreateMenuItem("Reset scale", @SceneResetScale, '3' , QUAL_ALT)); + popup.AddChild(CreateMenuItem("Reset transform", @SceneResetTransform, KEY_Q , QUAL_ALT)); + } + else if (hotKeyMode == HOTKEYS_MODE_BLENDER) + { + popup.AddChild(CreateMenuItem("Reset position", @SceneResetPosition, KEY_G , QUAL_ALT)); + popup.AddChild(CreateMenuItem("Reset rotation", @SceneResetRotation, KEY_R, QUAL_ALT)); + popup.AddChild(CreateMenuItem("Reset scale", @SceneResetScale, KEY_S, QUAL_ALT)); + popup.AddChild(CreateMenuItem("Reset transform", @SceneResetTransform, KEY_Q , QUAL_ALT)); + } + + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + { + popup.AddChild(CreateMenuItem("Enable/disable", @SceneToggleEnable, KEY_E, QUAL_CTRL)); + popup.AddChild(CreateMenuItem("Enable all", @SceneEnableAllNodes, KEY_E, QUAL_ALT)); + } + else if (hotKeyMode == HOTKEYS_MODE_BLENDER) + { + popup.AddChild(CreateMenuItem("Enable/disable", @SceneToggleEnable, KEY_H)); + popup.AddChild(CreateMenuItem("Enable all", @SceneEnableAllNodes, KEY_H, QUAL_ALT)); + } + + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + popup.AddChild(CreateMenuItem("Unparent", @SceneUnparent, KEY_U, QUAL_CTRL)); + else if (hotKeyMode == HOTKEYS_MODE_BLENDER) + popup.AddChild(CreateMenuItem("Unparent", @SceneUnparent, KEY_P, QUAL_ALT)); + + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + popup.AddChild(CreateMenuItem("Parent to last", @NodesParentToLastSelected, KEY_U)); + else if (hotKeyMode == HOTKEYS_MODE_BLENDER) + popup.AddChild(CreateMenuItem("Parent to last", @NodesParentToLastSelected, KEY_P, QUAL_CTRL)); + + CreateChildDivider(popup); + + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + popup.AddChild(CreateMenuItem("Toggle update", @ToggleSceneUpdate, KEY_P, QUAL_CTRL)); + //else if (hotKeyMode == HOT_KEYS_MODE_BLENDER) + // popup.AddChild(CreateMenuItem("Toggle update", @ToggleSceneUpdate, KEY_P, QUAL_CTRL)); + + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + { + popup.AddChild(CreateMenuItem("View closer", @ViewCloser, KEY_F)); + } + else if (hotKeyMode == HOTKEYS_MODE_BLENDER) + { + popup.AddChild(CreateMenuItem("Move to layer", @ShowLayerMover, KEY_M)); + popup.AddChild(CreateMenuItem("Smart Duplicate", @SceneSmartDuplicateNode, KEY_D, QUAL_ALT)); + popup.AddChild(CreateMenuItem("View closer", @ViewCloser, KEY_KP_PERIOD)); + } + popup.AddChild(CreateMenuItem("Color wheel", @ColorWheelBuildMenuSelectTypeColor, KEY_W, QUAL_ALT)); + popup.AddChild(CreateMenuItem("Show components icons", @ViewDebugIcons, KEY_I, QUAL_ALT)); + + CreateChildDivider(popup); + + popup.AddChild(CreateMenuItem("Stop test animation", @StopTestAnimation)); + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Rebuild navigation data", @SceneRebuildNavigation)); + popup.AddChild(CreateMenuItem("Render Zone Cubemap", @SceneRenderZoneCubemaps)); + popup.AddChild(CreateMenuItem("Add children to SM-group", @SceneAddChildrenStaticModelGroup)); + Menu@ childMenu = CreateMenuItem("Set children as spline path", null, SHOW_POPUP_INDICATOR); + Window@ childPopup = CreatePopup(childMenu); + childPopup.AddChild(CreateMenuItem("Non-cyclic", @SetSplinePath, 0, 0, true, "Set non-cyclic spline path")); + childPopup.AddChild(CreateMenuItem("Cyclic", @SetSplinePath, 0, 0, true, "Set cyclic spline path")); + popup.AddChild(childMenu); + FinalizedPopupMenu(popup); + uiMenuBar.AddChild(menu); + } + + { + Menu@ menu = CreateMenu("Create"); + Window@ popup = menu.popup; + popup.AddChild(CreateMenuItem("Replicated node", @PickNode, 0, 0, true, "Create Replicated node")); + popup.AddChild(CreateMenuItem("Local node", @PickNode, 0, 0, true, "Create Local node")); + CreateChildDivider(popup); + + Menu@ childMenu = CreateMenuItem("Component", null, SHOW_POPUP_INDICATOR); + Window@ childPopup = CreatePopup(childMenu); + String[] objectCategories = GetObjectCategories(); + for (uint i = 0; i < objectCategories.length; ++i) + { + // Skip the UI category for the component menus + if (objectCategories[i] == "UI") + continue; + + Menu@ m = CreateMenuItem(objectCategories[i], null, SHOW_POPUP_INDICATOR); + Window@ p = CreatePopup(m); + String[] componentTypes = GetObjectsByCategory(objectCategories[i]); + for (uint j = 0; j < componentTypes.length; ++j) + p.AddChild(CreateIconizedMenuItem(componentTypes[j], @PickComponent, 0, 0, "", true, "Create " + componentTypes[j])); + childPopup.AddChild(m); + } + FinalizedPopupMenu(childPopup); + popup.AddChild(childMenu); + + childMenu = CreateMenuItem("Builtin object", null, SHOW_POPUP_INDICATOR); + childPopup = CreatePopup(childMenu); + String[] objects = { "Box", "Cone", "Cylinder", "Plane", "Pyramid", "Sphere", "TeaPot", "Torus" }; + for (uint i = 0; i < objects.length; ++i) + childPopup.AddChild(CreateIconizedMenuItem(objects[i], @PickBuiltinObject, 0, 0, "Node", true, "Create " + objects[i])); + popup.AddChild(childMenu); + CreateChildDivider(popup); + + childMenu = CreateMenuItem("UI-element", null, SHOW_POPUP_INDICATOR); + childPopup = CreatePopup(childMenu); + String[] uiElementTypes = GetObjectsByCategory("UI"); + for (uint i = 0; i < uiElementTypes.length; ++i) + { + if (uiElementTypes[i] != "UIElement") + childPopup.AddChild(CreateIconizedMenuItem(uiElementTypes[i], @PickUIElement, 0, 0, "", true, "Create " + uiElementTypes[i])); + } + CreateChildDivider(childPopup); + childPopup.AddChild(CreateIconizedMenuItem("UIElement", @PickUIElement)); + popup.AddChild(childMenu); + + FinalizedPopupMenu(popup); + uiMenuBar.AddChild(menu); + } + + { + Menu@ menu = CreateMenu("UI-layout"); + Window@ popup = menu.popup; + popup.AddChild(CreateMenuItem("Open UI-layout...", @PickFile, KEY_O, QUAL_ALT)); + popup.AddChild(CreateMenuItem("Save UI-layout", @SaveUILayoutWithExistingName, KEY_S, QUAL_ALT)); + popup.AddChild(CreateMenuItem("Save UI-layout as...", @PickFile)); + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Close UI-layout", @CloseUILayout, KEY_C, QUAL_ALT)); + popup.AddChild(CreateMenuItem("Close all UI-layouts", @CloseAllUILayouts)); + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Load child element...", @PickFile)); + popup.AddChild(CreateMenuItem("Save child element as...", @PickFile)); + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Set default style...", @PickFile)); + FinalizedPopupMenu(popup); + uiMenuBar.AddChild(menu); + } + + { + Menu@ menu = CreateMenu("View"); + Window@ popup = menu.popup; + popup.AddChild(CreateMenuItem("Hierarchy", @ToggleHierarchyWindow, KEY_H, QUAL_CTRL)); + popup.AddChild(CreateMenuItem("Attribute inspector", @ToggleAttributeInspectorWindow, KEY_I, QUAL_CTRL)); + popup.AddChild(CreateMenuItem("Resource browser", @ToggleResourceBrowserWindow, KEY_B, QUAL_CTRL)); + popup.AddChild(CreateMenuItem("Material editor", @ToggleMaterialEditor)); + popup.AddChild(CreateMenuItem("Particle editor", @ToggleParticleEffectEditor)); + popup.AddChild(CreateMenuItem("Terrain editor", TerrainEditorShowCallback(terrainEditor.Show))); + popup.AddChild(CreateMenuItem("Spawn editor", @ToggleSpawnEditor)); + popup.AddChild(CreateMenuItem("Sound Type editor", @ToggleSoundTypeEditor)); + popup.AddChild(CreateMenuItem("Editor settings", @ToggleEditorSettingsDialog)); + popup.AddChild(CreateMenuItem("Editor preferences", @ToggleEditorPreferencesDialog)); + CreateChildDivider(popup); + popup.AddChild(CreateMenuItem("Hide editor", @ToggleUI, KEY_F12, QUAL_ANY)); + FinalizedPopupMenu(popup); + uiMenuBar.AddChild(menu); + } + + BorderImage@ spacer = BorderImage("MenuBarSpacer"); + uiMenuBar.AddChild(spacer); + spacer.style = "EditorMenuBar"; +} + +bool Exit() +{ + ui.cursor.shape = CS_BUSY; + + if (messageBoxCallback is null) + { + String message; + if (sceneModified) + message = "Scene has been modified.\n"; + + bool uiLayoutModified = false; + for (uint i = 0; i < editorUIElement.numChildren; ++i) + { + UIElement@ element = editorUIElement.children[i]; + if (element !is null && element.vars[MODIFIED_VAR].GetBool()) + { + uiLayoutModified = true; + message += "UI layout has been modified.\n"; + break; + } + } + + if (sceneModified || uiLayoutModified) + { + MessageBox@ messageBox = MessageBox(message + "Continue to exit?", "Warning"); + if (messageBox.window !is null) + { + Button@ cancelButton = messageBox.window.GetChild("CancelButton", true); + cancelButton.visible = true; + cancelButton.focus = true; + SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement"); + messageBoxCallback = @Exit; + return false; + } + } + } + else + messageBoxCallback = null; + + engine.Exit(); + return true; +} + +void HandleExitRequested() +{ + if (!ui.HasModalElement()) + Exit(); +} + +bool PickFile() +{ + Menu@ menu = GetEventSender(); + if (menu is null) + return false; + + String action = menu.name; + if (action.empty) + return false; + + // File (Scene related) + if (action == "Open scene...") + { + CreateFileSelector("Open scene", "Open", "Cancel", uiScenePath, uiSceneFilters, uiSceneFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleOpenSceneFile"); + } + else if (action == "Save scene as..." || action == "Save scene") + { + CreateFileSelector("Save scene as", "Save", "Cancel", uiScenePath, uiSceneFilters, uiSceneFilter); + uiFileSelector.fileName = GetFileNameAndExtension(editorScene.fileName); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveSceneFile"); + } + else if (action == "As replicated..." || action == "Load node as replicated...") + { + instantiateMode = REPLICATED; + CreateFileSelector("fileSelector Load node", "Load", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadNodeFile"); + } + else if (action == "As local..." || action == "Load node as local...") + { + instantiateMode = LOCAL; + CreateFileSelector("Load node", "Load", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadNodeFile"); + } + else if (action == "Save node as...") + { + if (editNode !is null && editNode !is editorScene) + { + CreateFileSelector("Save node", "Save", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter); + uiFileSelector.fileName = GetFileNameAndExtension(instantiateFileName); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveNodeFile"); + } + } + else if (action == "Import model...") + { + CreateFileSelector("Import model", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportModel"); + } + else if (action == "Import animation...") + { + CreateFileSelector("Import animation", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportAnimation"); + } + else if (action == "Import scene...") + { + CreateFileSelector("Import scene", "Import", "Cancel", uiImportPath, uiAllFilters, uiImportFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleImportScene"); + } + else if (action == "Export scene to OBJ..." || action == "Export selected to OBJ...") + { + // Set these up together to share the "export settings" options + if (action == "Export scene to OBJ...") + { + CreateFileSelector("Export scene to OBJ", "Save", "Cancel", uiExportPath, uiExportPathFilters, uiExportFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleExportSceneOBJ"); + } + else if (action == "Export selected to OBJ...") + { + CreateFileSelector("Export selected to OBJ", "Save", "Cancel", uiExportPath, uiExportPathFilters, uiExportFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleExportSelectedOBJ"); + } + + Window@ window = uiFileSelector.window; + + UIElement@ optionsGroup = UIElement(); + optionsGroup.maxHeight = 30; + optionsGroup.layoutMode = LM_HORIZONTAL; + window.defaultStyle = uiStyle; + window.style = AUTO_STYLE; + + CheckBox@ checkRightHanded = CheckBox(); + checkRightHanded.checked = objExportRightHanded_; + checkRightHanded.defaultStyle = uiStyle; + checkRightHanded.style = AUTO_STYLE; + SubscribeToEvent(checkRightHanded, "Toggled", "HandleOBJRightHandedChanged"); + optionsGroup.AddChild(checkRightHanded); + + Text@ lblRightHanded = Text(); + lblRightHanded.defaultStyle = uiStyle; + lblRightHanded.style = AUTO_STYLE; + lblRightHanded.text = " Right handed"; + optionsGroup.AddChild(lblRightHanded); + + CheckBox@ checkZUp = CheckBox(); + checkZUp.checked = objExportZUp_; + checkZUp.defaultStyle = uiStyle; + checkZUp.style = AUTO_STYLE; + SubscribeToEvent(checkZUp, "Toggled", "HandleOBJZUpChanged"); + optionsGroup.AddChild(checkZUp); + + Text@ lblZUp = Text(); + lblZUp.defaultStyle = uiStyle; + lblZUp.style = AUTO_STYLE; + lblZUp.text = " Z Axis Up"; + optionsGroup.AddChild(lblZUp); + + window.AddChild(optionsGroup); + } + else if (action == "Run script...") + { + CreateFileSelector("Run script", "Run", "Cancel", uiScriptPath, uiScriptFilters, uiScriptFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleRunScript"); + } + else if (action == "Set resource path...") + { + CreateFileSelector("Set resource path", "Set", "Cancel", sceneResourcePath, uiAllFilters, 0); + uiFileSelector.directoryMode = true; + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleResourcePath"); + } + // UI-element + else if (action == "Open UI-layout...") + { + CreateFileSelector("Open UI-layout", "Open", "Cancel", uiElementPath, uiElementFilters, uiElementFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleOpenUILayoutFile"); + } + else if (action == "Save UI-layout as..." || action == "Save UI-layout") + { + if (editUIElement !is null) + { + UIElement@ element = GetTopLevelUIElement(editUIElement); + if (element is null) + return false; + + CreateFileSelector("Save UI-layout as", "Save", "Cancel", uiElementPath, uiElementFilters, uiElementFilter); + uiFileSelector.fileName = GetFileNameAndExtension(element.GetVar(FILENAME_VAR).GetString()); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveUILayoutFile"); + } + } + else if (action == "Load child element...") + { + if (editUIElement !is null) + { + CreateFileSelector("Load child element", "Load", "Cancel", uiElementPath, uiElementFilters, uiElementFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleLoadChildUIElementFile"); + } + } + else if (action == "Save child element as...") + { + if (editUIElement !is null) + { + CreateFileSelector("Save child element", "Save", "Cancel", uiElementPath, uiElementFilters, uiElementFilter); + uiFileSelector.fileName = GetFileNameAndExtension(editUIElement.GetVar(CHILD_ELEMENT_FILENAME_VAR).GetString()); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleSaveChildUIElementFile"); + } + } + else if (action == "Set default style...") + { + CreateFileSelector("Set default style", "Set", "Cancel", uiElementPath, uiElementFilters, uiElementFilter); + SubscribeToEvent(uiFileSelector, "FileSelected", "HandleUIElementDefaultStyle"); + } + + return true; +} + +bool PickNode() +{ + Menu@ menu = GetEventSender(); + if (menu is null) + return false; + + String action = GetActionName(menu.name); + if (action.empty) + return false; + + CreateNode(action == "Replicated node" ? REPLICATED : LOCAL); + return true; +} + +bool PickComponent() +{ + if (editNodes.empty) + return false; + + Menu@ menu = GetEventSender(); + if (menu is null) + return false; + + String action = GetActionName(menu.name); + if (action.empty) + return false; + + CreateComponent(action); + return true; +} + +bool PickBuiltinObject() +{ + Menu@ menu = GetEventSender(); + if (menu is null) + return false; + + String action = GetActionName(menu.name); + if (action.empty) + return false; + + CreateBuiltinObject(action); + return true; +} + +bool PickUIElement() +{ + Menu@ menu = GetEventSender(); + if (menu is null) + return false; + + String action = GetActionName(menu.name); + if (action.empty) + return false; + + return NewUIElement(action); +} + +// When calling items from the quick menu, they have "Create" prepended for clarity. Strip that now to get the object name to create +String GetActionName(const String&in name) +{ + if (name.StartsWith("Create")) + return name.Substring(7); + else + return name; +} + +void HandleMenuSelected(StringHash eventType, VariantMap& eventData) +{ + Menu@ menu = eventData["Element"].GetPtr(); + if (menu is null) + return; + + HandlePopup(menu); + + quickMenu.visible = false; + quickMenu.enabled = false; + + // Execute the callback if available + Variant variant = menu.GetVar(CALLBACK_VAR); + if (!variant.empty) + menuCallbacks[variant.GetUInt()](); +} + +Menu@ CreateMenuItem(const String&in title, MENU_CALLBACK@ callback = null, int accelKey = 0, int accelQual = 0, bool addToQuickMenu = true, String quickMenuText="", bool autoLocalize = true) +{ + Menu@ menu = Menu(title); + menu.defaultStyle = uiStyle; + menu.style = AUTO_STYLE; + menu.SetLayout(LM_HORIZONTAL, 0, IntRect(8, 2, 8, 2)); + if (accelKey > 0) + menu.SetAccelerator(accelKey, accelQual); + if (callback !is null) + { + menu.vars[CALLBACK_VAR] = menuCallbacks.length; + menuCallbacks.Push(callback); + } + + Text@ menuText = Text(); + menu.AddChild(menuText); + menuText.style = "EditorMenuText"; + menuText.text = title; + menuText.autoLocalizable = autoLocalize; + + if (addToQuickMenu) + AddQuickMenuItem(callback, quickMenuText.empty ? title : quickMenuText); + + if (accelKey != 0) + { + UIElement@ spacer = UIElement(); + spacer.minWidth = menuText.indentSpacing; + spacer.height = menuText.height; + menu.AddChild(spacer); + menu.AddChild(CreateAccelKeyText(accelKey, accelQual)); + } + + return menu; +} + +void AddQuickMenuItem(MENU_CALLBACK@ callback, String text) +{ + if (callback is null) + return; + + bool exists = false; + for (uint i=0;i 0) + menu.SetAccelerator(accelKey, accelQual); + if (callback !is null) + { + menu.vars[CALLBACK_VAR] = menuCallbacks.length; + menuCallbacks.Push(callback); + } + + Text@ menuText = Text(); + menu.AddChild(menuText); + menuText.style = "EditorMenuText"; + menuText.text = title; + // If icon type is not provided, use the title instead + IconizeUIElement(menuText, iconType.empty ? title : iconType); + + if (addToQuickMenu) + AddQuickMenuItem(callback, quickMenuText.empty ? title : quickMenuText); + + if (accelKey != 0) + { + menuText.layoutMode = LM_HORIZONTAL; + menuText.AddChild(CreateAccelKeyText(accelKey, accelQual)); + } + + return menu; +} + +/// Create a child divider in parent with vertical layout mode. It works on other parent element as well, not just parent menu. +void CreateChildDivider(UIElement@ parent) +{ + BorderImage@ divider = parent.CreateChild("BorderImage", "Divider"); + divider.style = "EditorDivider"; +} + +Window@ CreatePopup(Menu@ baseMenu) +{ + Window@ popup = Window(); + popup.defaultStyle = uiStyle; + popup.style = AUTO_STYLE; + popup.SetLayout(LM_VERTICAL, 1, IntRect(2, 6, 2, 6)); + baseMenu.popup = popup; + baseMenu.popupOffset = IntVector2(0, baseMenu.height); + + return popup; +} + +Menu@ CreateMenu(const String&in title) +{ + Menu@ menu = CreateMenuItem(title); + Text@ text = menu.children[0]; + menu.maxWidth = text.width + 20; + CreatePopup(menu); + + return menu; +} + +void HandleChangeLanguage(StringHash eventType, VariantMap& eventData) +{ + Array children = uiMenuBar.GetChildren(); + + for (uint i = 0; i < children.length - 2; ++i) // last 2 elements is not menu + { + // dirty hack: force recalc text size + children[i].maxWidth = 1000; + Text@ text = children[i].children[0]; + text.minWidth = 0; + text.maxWidth = 1; + text.ApplyAttributes(); + children[i].maxWidth = text.width + 20; + } + + RebuildResourceDatabase(); +} + +Text@ CreateAccelKeyText(int accelKey, int accelQual) +{ + Text@ accelKeyText = Text(); + accelKeyText.defaultStyle = uiStyle; + accelKeyText.style = "EditorMenuText"; + accelKeyText.textAlignment = HA_RIGHT; + + String text; + if (accelKey == KEY_DELETE) + text = "Del"; + else if (accelKey == KEY_SPACE) + text = "Space"; + // Cannot use range as the key constants below do not appear to be in sequence + else if (accelKey == KEY_F1) + text = "F1"; + else if (accelKey == KEY_F2) + text = "F2"; + else if (accelKey == KEY_F3) + text = "F3"; + else if (accelKey == KEY_F4) + text = "F4"; + else if (accelKey == KEY_F5) + text = "F5"; + else if (accelKey == KEY_F6) + text = "F6"; + else if (accelKey == KEY_F7) + text = "F7"; + else if (accelKey == KEY_F8) + text = "F8"; + else if (accelKey == KEY_F9) + text = "F9"; + else if (accelKey == KEY_F10) + text = "F10"; + else if (accelKey == KEY_F11) + text = "F11"; + else if (accelKey == KEY_F12) + text = "F12"; + else if (accelKey == SHOW_POPUP_INDICATOR) + text = ">"; + else if (accelKey == KEY_KP_PERIOD) + text = "NumPad ."; + else + text.AppendUTF8(accelKey); + if (accelQual & QUAL_ALT > 0) + text = "Alt+" + text; + if (accelQual & QUAL_SHIFT > 0) + text = "Shift+" + text; + if (accelQual & QUAL_CTRL > 0) + text = "Ctrl+" + text; + accelKeyText.text = text; + + return accelKeyText; +} + +void FinalizedPopupMenu(Window@ popup) +{ + // Find the maximum menu text width + Array children = popup.GetChildren(); + int maxWidth = 0; + for (uint i = 0; i < children.length; ++i) + { + UIElement@ element = children[i]; + if (element.type != MENU_TYPE) // Skip if not menu item + continue; + + int width = element.children[0].width; + if (width > maxWidth) + maxWidth = width; + } + + // Adjust the indent spacing to slightly wider than the maximum width + maxWidth += 20; + for (uint i = 0; i < children.length; ++i) + { + UIElement@ element = children[i]; + if (element.type != MENU_TYPE) + continue; + Menu@ menu = element; + + Text@ menuText = menu.children[0]; + if (menuText.numChildren == 1) // Skip if menu text does not have accel + menuText.children[0].indentSpacing = maxWidth; + + // Adjust the popup offset taking the indentation into effect + if (menu.popup !is null) + menu.popupOffset = IntVector2(menu.width, 0); + } +} + +void CreateFileSelector(const String&in title, const String&in ok, const String&in cancel, const String&in initialPath, Array@ filters, + uint initialFilter, bool autoLocalizeTitle = true) +{ + // Within the editor UI, the file selector is a kind of a "singleton". When the previous one is overwritten, also + // the events subscribed from it are disconnected, so new ones are safe to subscribe. + uiFileSelector = FileSelector(); + uiFileSelector.defaultStyle = uiStyle; + uiFileSelector.title = title; + uiFileSelector.titleText.autoLocalizable = autoLocalizeTitle; + uiFileSelector.path = initialPath; + uiFileSelector.SetButtonTexts(ok, cancel); + Text@ okText = cast(uiFileSelector.okButton.children[0]); + okText.autoLocalizable = true; + Text@ cancelText = cast(uiFileSelector.cancelButton.children[0]); + cancelText.autoLocalizable = true; + uiFileSelector.SetFilters(filters, initialFilter); + CenterDialog(uiFileSelector.window); +} + +void CloseFileSelector(uint&out filterIndex, String&out path) +{ + // Save filter & path for next time + filterIndex = uiFileSelector.filterIndex; + path = uiFileSelector.path; + + uiFileSelector = null; +} + +void CloseFileSelector() +{ + uiFileSelector = null; +} + +void CreateConsole() +{ + Console@ console = engine.CreateConsole(); + console.defaultStyle = uiStyle; + console.commandInterpreter = consoleCommandInterpreter; + console.numBufferedRows = 100; + console.autoVisibleOnError = true; +} + +void CreateDebugHud() +{ + engine.CreateDebugHud(); + debugHud.defaultStyle = uiStyle; + debugHud.mode = DEBUGHUD_SHOW_NONE; +} + +void CenterDialog(UIElement@ element) +{ + IntVector2 size = element.size; + element.SetPosition((ui.root.width - size.x) / 2, (ui.root.height - size.y) / 2); +} + +void CreateContextMenu() +{ + contextMenu = LoadEditorUI("UI/EditorContextMenu.xml"); + ui.root.AddChild(contextMenu); +} + +void UpdateWindowTitle() +{ + String sceneName = GetFileNameAndExtension(editorScene.fileName); + if (sceneName.empty || sceneName == TEMP_SCENE_NAME || sceneName == TEMP_BINARY_SCENE_NAME) + sceneName = "Untitled"; + if (sceneModified) + sceneName += "*"; + graphics.windowTitle = "Urho3D editor - " + sceneName; +} + +void HandlePopup(Menu@ menu) +{ + // Close the top level menu now unless the selected menu item has another popup + if (menu.popup !is null) + return; + + for (;;) + { + UIElement@ menuParent = menu.parent; + if (menuParent is null) + break; + + Menu@ nextMenu = menuParent.vars["Origin"].GetPtr(); + if (nextMenu is null) + break; + else + menu = nextMenu; + } + + if (menu.parent is uiMenuBar) + menu.showPopup = false; +} + +String ExtractFileName(VariantMap& eventData, bool forSave = false) +{ + String fileName; + + // Check for OK + if (eventData["OK"].GetBool()) + { + String filter = eventData["Filter"].GetString(); + fileName = eventData["FileName"].GetString(); + // Add default extension for saving if not specified + if (GetExtension(fileName).empty && forSave && filter != "*.*") + fileName = fileName + filter.Substring(1); + } + return fileName; +} + +void HandleOpenSceneFile(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiSceneFilter, uiScenePath); + LoadScene(ExtractFileName(eventData)); + SendEvent(EDITOR_EVENT_SCENE_LOADED); +} + +void HandleSaveSceneFile(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiSceneFilter, uiScenePath); + SaveScene(ExtractFileName(eventData, true)); +} + +void HandleLoadNodeFile(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiNodeFilter, uiNodePath); + LoadNode(ExtractFileName(eventData)); +} + +void HandleSaveNodeFile(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiNodeFilter, uiNodePath); + SaveNode(ExtractFileName(eventData, true)); +} + +void HandleImportModel(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiImportFilter, uiImportPath); + ImportModel(ExtractFileName(eventData)); +} +void HandleImportAnimation(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiImportFilter, uiImportPath); + ImportAnimation(ExtractFileName(eventData)); +} + +void HandleImportScene(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiImportFilter, uiImportPath); + ImportScene(ExtractFileName(eventData)); +} + +void HandleExportSceneOBJ(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiExportFilter, uiExportPath); + ExportSceneToOBJ(ExtractFileName(eventData)); +} + +void HandleExportSelectedOBJ(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiExportFilter, uiExportPath); + ExportSelectedToOBJ(ExtractFileName(eventData)); +} + + +void ExecuteScript(const String&in fileName) +{ + if (fileName.empty) + return; + + File@ file = File(fileName, FILE_READ); + if (file.open) + { + String scriptCode; + while (!file.eof) + scriptCode += file.ReadLine() + "\n"; + file.Close(); + + if (script.Execute(scriptCode)) + log.Info("Script " + fileName + " ran successfully"); + } +} + +void HandleRunScript(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiScriptFilter, uiScriptPath); + + suppressSceneChanges = true; + ExecuteScript(ExtractFileName(eventData)); + suppressSceneChanges = false; + + UpdateHierarchyItem(editorScene, true); + UpdateHierarchyItem(editorUIElement, true); +} + +void HandleResourcePath(StringHash eventType, VariantMap& eventData) +{ + String pathName = uiFileSelector.path; + CloseFileSelector(); + if (eventData["OK"].GetBool()) + SetResourcePath(pathName, false); +} + +void HandleOpenUILayoutFile(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiElementFilter, uiElementPath); + OpenUILayout(ExtractFileName(eventData)); +} + +void HandleSaveUILayoutFile(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiElementFilter, uiElementPath); + SaveUILayout(ExtractFileName(eventData, true)); +} + +void HandleLoadChildUIElementFile(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiElementFilter, uiElementPath); + LoadChildUIElement(ExtractFileName(eventData)); +} + +void HandleSaveChildUIElementFile(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiElementFilter, uiElementPath); + SaveChildUIElement(ExtractFileName(eventData, true)); +} + +void HandleUIElementDefaultStyle(StringHash eventType, VariantMap& eventData) +{ + CloseFileSelector(uiElementFilter, uiElementPath); + SetUIElementDefaultStyle(ExtractFileName(eventData)); +} + +void HandleHotKeysBlender( VariantMap& eventData) +{ + int key = eventData["Key"].GetInt(); + int viewDirection = eventData["Qualifiers"].GetInt() == QUAL_CTRL ? -1 : 1; + + if (key == KEY_ESCAPE) + { + if (uiHidden) + UnhideUI(); + else if (console.visible) + console.visible = false; + else if (contextMenu.visible) + CloseContextMenu(); + else if (quickMenu.visible) + { + quickMenu.visible = false; + quickMenu.enabled = false; + } + else + { + UIElement@ front = ui.frontElement; + if (front is settingsDialog || front is preferencesDialog) + { + ui.focusElement = null; + front.visible = false; + } + } + } + // Ignore other keys when UI has a modal element + else if (ui.HasModalElement()) + return; + + else if (key == KEY_F1) + console.Toggle(); + else if (key == KEY_F2) + ToggleRenderingDebug(); + else if (key == KEY_F3) + TogglePhysicsDebug(); + else if (key == KEY_F4) + ToggleOctreeDebug(); + else if (key == KEY_F5) + ToggleNavigationDebug(); + else if (key == KEY_F11) + { + Image@ screenshot = Image(); + graphics.TakeScreenShot(screenshot); + if (!fileSystem.DirExists(screenshotDir)) + fileSystem.CreateDir(screenshotDir); + screenshot.SavePNG(screenshotDir + "/Screenshot_" + + time.timeStamp.Replaced(':', '_').Replaced('.', '_').Replaced(' ', '_') + ".png"); + } + // In Blender, HOME key is for locating the selected objects by pan and + // the PERIOD key of keypad is for moving the camera to focus the selected. + // Here we ignore the difference. + else if ((key == KEY_HOME || key == KEY_KP_PERIOD) && ui.focusElement is null) + { + if (selectedNodes.length > 0 || selectedComponents.length > 0) + { + LocateNodesAndComponents(selectedNodes, selectedComponents); + } + } + else if (key == KEY_KP_1 && ui.focusElement is null) // Front view + { + cameraSmoothInterpolate.Finish(); + + Vector3 pos = -Vector3(0.0, 0.0, cameraNode.position.length * viewDirection); + Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(0, 0, viewDirection)); + + cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos); + cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot); + cameraSmoothInterpolate.Start(0.5f); + } + else if ((key == KEY_KP_3 || key == KEY_KP_9) && ui.focusElement is null) // Side view + { + cameraSmoothInterpolate.Finish(); + + Vector3 pos = -Vector3(cameraNode.position.length * -viewDirection, 0.0, 0.0); + Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(-viewDirection, 0, 0)); + + cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos); + cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot); + cameraSmoothInterpolate.Start(0.5f); + } + else if (key == KEY_KP_7 && ui.focusElement is null) // Top view + { + cameraSmoothInterpolate.Finish(); + + Vector3 pos = -Vector3(0.0, cameraNode.position.length * -viewDirection, 0.0); + Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(0, -viewDirection, 0)); + + cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos); + cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot); + cameraSmoothInterpolate.Start(0.5f); + } + else if (key == KEY_KP_5 && ui.focusElement is null) + { + activeViewport.ToggleOrthographic(); + } + else if (key == '4' && ui.focusElement is null) + editMode = EDIT_SELECT; + else if (key == '5' && ui.focusElement is null) + axisMode = AxisMode(axisMode ^ AXIS_LOCAL); + else if (key == '6' && ui.focusElement is null) + { + --pickMode; + if (pickMode < PICK_GEOMETRIES) + pickMode = MAX_PICK_MODES - 1; + } + else if (key == '7' && ui.focusElement is null) + { + ++pickMode; + if (pickMode >= MAX_PICK_MODES) + pickMode = PICK_GEOMETRIES; + } + else if (key == KEY_Z && eventData["Qualifiers"].GetInt() != QUAL_CTRL) + { + if (ui.focusElement is null) + { + fillMode = FillMode(fillMode + 1); + if (fillMode > FILL_POINT) + fillMode = FILL_SOLID; + + // Update camera fill mode + SetFillMode(fillMode); + } + } + else if (key == KEY_SPACE) + { + if (ui.cursor.visible && ui.focusElement is null) + ToggleQuickMenu(); + } + else + { + SteppedObjectManipulation(key); + } + + if ((ui.focusElement is null) && (selectedNodes.length > 0) && !cameraFlyMode) + { + if (eventData["Qualifiers"].GetInt() == QUAL_ALT) // reset transformations + { + if (key == KEY_G) + SceneResetPosition(); + else if (key == KEY_R) + SceneResetRotation(); + else if (key == KEY_S) + SceneResetScale(); + } + else if (eventData["Qualifiers"].GetInt() != QUAL_CTRL) // set transformations + { + if (key == KEY_G) + { + editMode = EDIT_MOVE; + axisMode = AxisMode(axisMode ^ AXIS_LOCAL); + + } + else if (key == KEY_R) + { + editMode = EDIT_ROTATE; + axisMode = AxisMode(axisMode ^ AXIS_LOCAL); + + } + else if (key == KEY_S) + { + editMode = EDIT_SCALE; + axisMode = AxisMode(axisMode ^ AXIS_LOCAL); + } + } + } + + toolBarDirty = true; +} + +void HandleHotKeysStandard(VariantMap& eventData) +{ + int key = eventData["Key"].GetInt(); + int viewDirection = eventData["Qualifiers"].GetInt() == QUAL_CTRL ? -1 : 1; + + if (key == KEY_ESCAPE) + { + if (uiHidden) + UnhideUI(); + else if (console.visible) + console.visible = false; + else if (contextMenu.visible) + CloseContextMenu(); + else if (quickMenu.visible) + { + quickMenu.visible = false; + quickMenu.enabled = false; + } + else + { + UIElement@ front = ui.frontElement; + if (front is settingsDialog || front is preferencesDialog) + { + ui.focusElement = null; + front.visible = false; + } + } + } + + // Ignore other keys when UI has a modal element + else if (ui.HasModalElement()) + return; + + else if (key == KEY_F1) + console.Toggle(); + else if (key == KEY_F2) + ToggleRenderingDebug(); + else if (key == KEY_F3) + TogglePhysicsDebug(); + else if (key == KEY_F4) + ToggleOctreeDebug(); + else if (key == KEY_F5) + ToggleNavigationDebug(); + else if (key == KEY_F11) + { + Image@ screenshot = Image(); + graphics.TakeScreenShot(screenshot); + if (!fileSystem.DirExists(screenshotDir)) + fileSystem.CreateDir(screenshotDir); + screenshot.SavePNG(screenshotDir + "/Screenshot_" + + time.timeStamp.Replaced(':', '_').Replaced('.', '_').Replaced(' ', '_') + ".png"); + } + else if ((key == KEY_HOME || key == KEY_F) && ui.focusElement is null) + { + if (selectedNodes.length > 0 || selectedComponents.length > 0) + { + LocateNodesAndComponents(selectedNodes, selectedComponents); + } + } + else if (key == KEY_KP_1 && ui.focusElement is null) // Front view + { + cameraSmoothInterpolate.Finish(); + + Vector3 pos = -Vector3(0.0, 0.0, cameraNode.position.length * viewDirection); + Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(0, 0, viewDirection)); + + cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos); + cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot); + cameraSmoothInterpolate.Start(0.5f); + } + else if ((key == KEY_KP_3 || key == KEY_KP_9) && ui.focusElement is null) // Side view + { + cameraSmoothInterpolate.Finish(); + + Vector3 pos = -Vector3(cameraNode.position.length * -viewDirection, 0.0, 0.0); + Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(-viewDirection, 0, 0)); + + cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos); + cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot); + cameraSmoothInterpolate.Start(0.5f); + } + else if (key == KEY_KP_7 && ui.focusElement is null) // Top view + { + cameraSmoothInterpolate.Finish(); + + Vector3 pos = -Vector3(0.0, cameraNode.position.length * -viewDirection, 0.0); + Quaternion rot = Quaternion(Vector3::FORWARD, Vector3(0, -viewDirection, 0)); + + cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, pos); + cameraSmoothInterpolate.SetCameraNodeRotation(cameraNode.rotation, rot); + cameraSmoothInterpolate.Start(0.5f); + } + else if (key == KEY_KP_5 && ui.focusElement is null) + { + activeViewport.ToggleOrthographic(); + } + else if (eventData["Qualifiers"].GetInt() == QUAL_CTRL) + { + if (key == '1') + editMode = EDIT_MOVE; + else if (key == '2') + editMode = EDIT_ROTATE; + else if (key == '3') + editMode = EDIT_SCALE; + else if (key == '4') + editMode = EDIT_SELECT; + else if (key == '5') + axisMode = AxisMode(axisMode ^ AXIS_LOCAL); + else if (key == '6') + { + --pickMode; + if (pickMode < PICK_GEOMETRIES) + pickMode = MAX_PICK_MODES - 1; + } + else if (key == '7') + { + ++pickMode; + if (pickMode >= MAX_PICK_MODES) + pickMode = PICK_GEOMETRIES; + } + else if (key == KEY_W) + { + fillMode = FillMode(fillMode + 1); + if (fillMode > FILL_POINT) + fillMode = FILL_SOLID; + + // Update camera fill mode + SetFillMode(fillMode); + } + else if (key == KEY_SPACE) + { + if (ui.cursor.visible) + ToggleQuickMenu(); + } + else + SteppedObjectManipulation(key); + + toolBarDirty = true; + } +} + +void HandleKeyDown(StringHash eventType, VariantMap& eventData) +{ + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + { + HandleHotKeysStandard(eventData); + } + else if( hotKeyMode == HOTKEYS_MODE_BLENDER) + { + HandleHotKeysBlender(eventData); + } +} + +void UnfadeUI() +{ + FadeUI(false); +} + +void FadeUI(bool fade = true) +{ + if (uiHidden || uiFaded == fade) + return; + + float opacity = (uiFaded = fade) ? uiMinOpacity : uiMaxOpacity; + Array children = ui.root.GetChildren(); + for (uint i = 0; i < children.length; ++i) + { + // Texts, popup&modal windows (which are anyway only in ui.modalRoot), and editorUIElement are excluded + if (children[i].type != TEXT_TYPE && children[i] !is editorUIElement) + children[i].opacity = opacity; + } +} + +bool ToggleUI() +{ + HideUI(!uiHidden); + return true; +} + +void UnhideUI() +{ + HideUI(false); +} + +void HideUI(bool hide = true) +{ + if (uiHidden == hide) + return; + + // Note: we could set ui.root.visible = false and it would hide the whole hierarchy. + // However in this case we need the editorUIElement to stay visible + bool visible = !(uiHidden = hide); + Array children = ui.root.GetChildren(); + for (uint i = 0; i < children.length; ++i) + { + // Cursor and editorUIElement are excluded + if (children[i].type != CURSOR_TYPE && children[i] !is editorUIElement) + { + if (visible) + { + if (!children[i].visible) + children[i].visible = children[i].vars["HideUI"].GetBool(); + } + else + { + children[i].vars["HideUI"] = children[i].visible; + children[i].visible = false; + } + } + } +} + +void IconizeUIElement(UIElement@ element, const String&in iconType) +{ + // Check if the icon has been created before + BorderImage@ icon = element.GetChild("Icon"); + + // If iconType is empty, it is a request to remove the existing icon + if (iconType.empty) + { + // Remove the icon if it exists + if (icon !is null) + icon.Remove(); + + // Revert back the indent but only if it is indented by this function + if (element.vars[INDENT_MODIFIED_BY_ICON_VAR].GetBool()) + element.indent = 0; + + return; + } + + // The UI element must itself has been indented to reserve the space for the icon + if (element.indent == 0) + { + element.indent = 1; + element.vars[INDENT_MODIFIED_BY_ICON_VAR] = true; + } + + // If no icon yet then create one with the correct indent and size in respect to the UI element + if (icon is null) + { + // The icon is placed at one indent level less than the UI element + icon = BorderImage("Icon"); + icon.indent = element.indent - 1; + icon.SetFixedSize(element.indentWidth - 2, 14); + element.InsertChild(0, icon); // Ensure icon is added as the first child + } + + // Set the icon type + if (!icon.SetStyle(iconType, iconStyle)) + icon.SetStyle("Unknown", iconStyle); // If fails then use an 'unknown' icon type + icon.color = Color(1,1,1,1); // Reset to enabled color +} + +void SetIconEnabledColor(UIElement@ element, bool enabled, bool partial = false) +{ + BorderImage@ icon = element.GetChild("Icon"); + if (icon !is null) + { + if (partial) + { + icon.colors[C_TOPLEFT] = Color(1,1,1,1); + icon.colors[C_BOTTOMLEFT] = Color(1,1,1,1); + icon.colors[C_TOPRIGHT] = Color(1,0,0,1); + icon.colors[C_BOTTOMRIGHT] = Color(1,0,0,1); + } + else + icon.color = enabled ? Color(1,1,1,1) : Color(1,0,0,1); + } +} + +void UpdateDirtyUI() +{ + UpdateDirtyToolBar(); + terrainEditor.UpdateDirty(); + + // Perform hierarchy selection latently after the new selections are finalized (used in undo/redo action) + if (!hierarchyUpdateSelections.empty) + { + hierarchyList.SetSelections(hierarchyUpdateSelections); + hierarchyUpdateSelections.Clear(); + } + + // Perform some event-triggered updates latently in case a large hierarchy was changed + if (attributesFullDirty || attributesDirty) + UpdateAttributeInspector(attributesFullDirty); +} + +void HandleMessageAcknowledgement(StringHash eventType, VariantMap& eventData) +{ + if (eventData["OK"].GetBool()) + messageBoxCallback(); + else + messageBoxCallback = null; +} + +void PopulateMruScenes() +{ + mruScenesPopup.RemoveAllChildren(); + if (uiRecentScenes.length > 0) + { + recentSceneMenu.enabled = true; + for (uint i=0; i < uiRecentScenes.length; ++i) + mruScenesPopup.AddChild(CreateMenuItem(uiRecentScenes[i], @LoadMostRecentScene, 0, 0, false, "", false)); + } + else + recentSceneMenu.enabled = false; + +} + +bool LoadMostRecentScene() +{ + Menu@ menu = GetEventSender(); + if (menu is null) + return false; + + Text@ text = menu.GetChildren()[0]; + if (text is null) + return false; + + return LoadScene(text.text); +} + +// Set from click to false if opening menu procedurally. +void OpenContextMenu(bool fromClick=true) +{ + if (contextMenu is null) + return; + + contextMenu.enabled = true; + contextMenu.visible = true; + contextMenu.BringToFront(); + if (fromClick) + contextMenuActionWaitFrame=true; +} + +void CloseContextMenu() +{ + if (contextMenu is null) + return; + + contextMenu.enabled = false; + contextMenu.visible = false; +} + +void ActivateContextMenu(Array actions) +{ + contextMenu.RemoveAllChildren(); + for (uint i=0; i< actions.length; ++i) + { + contextMenu.AddChild(actions[i]); + } + contextMenu.SetFixedHeight(24*actions.length+6); + contextMenu.position = ui.cursor.screenPosition + IntVector2(10,-10); + OpenContextMenu(); +} + +Menu@ CreateContextMenuItem(String text, String handler, String menuName = "", bool autoLocalize = true) +{ + Menu@ menu = Menu(); + menu.defaultStyle = uiStyle; + menu.style = AUTO_STYLE; + menu.name = menuName; + menu.SetLayout(LM_HORIZONTAL, 0, IntRect(8, 2, 8, 2)); + Text@ menuText = Text(); + menuText.style = "EditorMenuText"; + menu.AddChild(menuText); + menuText.text = text; + menuText.autoLocalizable = autoLocalize; + menu.vars[VAR_CONTEXT_MENU_HANDLER] = handler; + SubscribeToEvent(menu, "Released", "ContextMenuEventWrapper"); + return menu; +} + +void ContextMenuEventWrapper(StringHash eventType, VariantMap& eventData) +{ + UIElement@ uiElement = eventData["Element"].GetPtr(); + if (uiElement is null) + return; + + String handler = uiElement.vars[VAR_CONTEXT_MENU_HANDLER].GetString(); + if (!handler.empty) + { + SubscribeToEvent(uiElement, "Released", handler); + uiElement.SendEvent("Released", eventData); + } + CloseContextMenu(); +} + +/// Load a UI XML file used by the editor +XMLFile@ GetEditorUIXMLFile(const String&in fileName) +{ + // Prefer the executable path to avoid using the user's resource path, which may point + // to an outdated Urho installation + String fullFileName = fileSystem.programDir + "Data/" + fileName; + if (fileSystem.FileExists(fullFileName)) + { + File@ file = File(fullFileName, FILE_READ); + XMLFile@ xml = XMLFile(); + xml.name = fileName; + if (xml.Load(file)) + return xml; + } + + // Fallback to resource system + return cache.GetResource("XMLFile", fileName); +} + + +/// Load an UI layout used by the editor +UIElement@ LoadEditorUI(const String&in fileName) +{ + return ui.LoadLayout(GetEditorUIXMLFile(fileName)); +} + +/// Set node children as a spline path, either cyclic or non-cyclic +bool SetSplinePath() +{ + Menu@ menu = GetEventSender(); + if (menu is null) + return false; + + return SceneSetChildrenSplinePath(menu.name == "Cyclic"); +} + +bool ColorWheelBuildMenuSelectTypeColor() +{ + if (selectedNodes.empty && selectedComponents.empty) return false; + editMode = EDIT_SELECT; + + // do coloring only for single selected object + // start with trying to find single component + if (selectedComponents.length == 1) + { + coloringComponent = selectedComponents[0]; + } + // else try to get first component from selected node + else if (selectedNodes.length == 1) + { + Array components = selectedNodes[0].GetComponents(); + if (components.length > 0) + { + coloringComponent = components[0]; + } + } + else + return false; + + if (coloringComponent is null) return false; + + Array actions; + + if (coloringComponent.typeName == "Light") + { + actions.Push(CreateContextMenuItem("Light color", "HandleColorWheelMenu", "menuLightColor")); + actions.Push(CreateContextMenuItem("Specular intensity", "HandleColorWheelMenu", "menuSpecularIntensity")); + actions.Push(CreateContextMenuItem("Brightness multiplier", "HandleColorWheelMenu", "menuBrightnessMultiplier")); + + actions.Push(CreateContextMenuItem("Cancel", "HandleColorWheelMenu", "menuCancel")); + + } + else if (coloringComponent.typeName == "StaticModel") + { + actions.Push(CreateContextMenuItem("Diffuse color", "HandleColorWheelMenu", "menuDiffuseColor")); + actions.Push(CreateContextMenuItem("Specular color", "HandleColorWheelMenu", "menuSpecularColor")); + actions.Push(CreateContextMenuItem("Emissive color", "HandleColorWheelMenu", "menuEmissiveColor")); + actions.Push(CreateContextMenuItem("Environment map color", "HandleColorWheelMenu", "menuEnvironmentMapColor")); + + actions.Push(CreateContextMenuItem("Cancel", "HandleColorWheelMenu", "menuCancel")); + } + else if (coloringComponent.typeName == "Zone") + { + actions.Push(CreateContextMenuItem("Ambient color", "HandleColorWheelMenu", "menuAmbientColor")); + actions.Push(CreateContextMenuItem("Fog color", "HandleColorWheelMenu", "menuFogColor")); + + actions.Push(CreateContextMenuItem("Cancel", "HandleColorWheelMenu", "menuCancel")); + } + else if (coloringComponent.typeName == "Text3D") + { + actions.Push(CreateContextMenuItem("Color", "HandleColorWheelMenu", "c")); + actions.Push(CreateContextMenuItem("Top left color", "HandleColorWheelMenu", "tl")); + actions.Push(CreateContextMenuItem("Top right color", "HandleColorWheelMenu", "tr")); + actions.Push(CreateContextMenuItem("Bottom left color", "HandleColorWheelMenu", "bl")); + actions.Push(CreateContextMenuItem("Bottom right color", "HandleColorWheelMenu", "br")); + actions.Push(CreateContextMenuItem("Cancel", "HandleColorWheelMenu", "menuCancel")); + } + if (actions.length > 0) { + ActivateContextMenu(actions); + return true; + } + + return false; +} + +void HandleColorWheelMenu() +{ + ColorWheelSetupBehaviorForColoring(); +} + +// color was changed, update color of all colorGroup for immediate preview; +void HandleWheelChangeColor(StringHash eventType, VariantMap& eventData) +{ + if (timeToNextColoringGroupUpdate > time.systemTime) return; + + if (coloringComponent !is null) + { + Color c = eventData["Color"].GetColor(); // current ColorWheel + // preview new color + if (coloringComponent.typeName == "Light") + { + Light@ light = cast(coloringComponent); + if (light !is null) + { + if (coloringPropertyName == "menuLightColor") + { + light.color = c; + } + else if (coloringPropertyName == "menuSpecularIntensity") + { + // multiply out + light.specularIntensity = c.Value() * 10.0f; + + } + else if (coloringPropertyName == "menuBrightnessMultiplier") + { + light.brightness = c.Value() * 10.0f; + + } + + attributesDirty = true; + } + } + else if (coloringComponent.typeName == "StaticModel") + { + StaticModel@ model = cast(coloringComponent); + if (model !is null) + { + Material@ mat = model.materials[0]; + if (mat !is null) + { + if (coloringPropertyName == "menuDiffuseColor") + { + Variant oldValue = mat.shaderParameters["MatDiffColor"]; + Variant newValue; + String valueString; + valueString += String(c.r).Substring(0,5); + valueString += " "; + valueString += String(c.g).Substring(0,5); + valueString += " "; + valueString += String(c.b).Substring(0,5); + valueString += " "; + valueString += String(c.a).Substring(0,5); + newValue.FromString(oldValue.type, valueString); + mat.shaderParameters["MatDiffColor"] = newValue; + } + else if (coloringPropertyName == "menuSpecularColor") + { + Variant oldValue = mat.shaderParameters["MatSpecColor"]; + Variant newValue; + String valueString; + valueString += String(c.r).Substring(0,5); + valueString += " "; + valueString += String(c.g).Substring(0,5); + valueString += " "; + valueString += String(c.b).Substring(0,5); + valueString += " "; + valueString += String(c.a * 128).Substring(0,5); + newValue.FromString(oldValue.type, valueString); + mat.shaderParameters["MatSpecColor"] = newValue; + } + else if (coloringPropertyName == "menuEmissiveColor") + { + Variant oldValue = mat.shaderParameters["MatEmissiveColor"]; + Variant newValue; + String valueString; + valueString += String(c.r).Substring(0,5); + valueString += " "; + valueString += String(c.g).Substring(0,5); + valueString += " "; + valueString += String(c.b).Substring(0,5); + valueString += " "; + valueString += String(c.a).Substring(0,5); + newValue.FromString(oldValue.type, valueString); + mat.shaderParameters["MatEmissiveColor"] = newValue; + } + else if (coloringPropertyName == "menuEnvironmentMapColor") + { + Variant oldValue = mat.shaderParameters["MatEnvMapColor"]; + Variant newValue; + String valueString; + valueString += String(c.r).Substring(0,5); + valueString += " "; + valueString += String(c.g).Substring(0,5); + valueString += " "; + valueString += String(c.b).Substring(0,5); + valueString += " "; + valueString += String(c.a).Substring(0,5); + newValue.FromString(oldValue.type, valueString); + mat.shaderParameters["MatEnvMapColor"] = newValue; + } + } + } + } + else if (coloringComponent.typeName == "Zone") + { + Zone@ zone = cast(coloringComponent); + if (zone !is null) + { + if (coloringPropertyName == "menuAmbientColor") + { + zone.ambientColor = c; + } + else if (coloringPropertyName == "menuFogColor") + { + zone.fogColor = c; + } + + attributesDirty = true; + } + } + else if (coloringComponent.typeName == "Text3D") + { + Text3D@ txt = cast(coloringComponent); + if (txt !is null) + { + if (coloringPropertyName == "c") + txt.color = c; + else if (coloringPropertyName == "tl") + txt.colors[C_TOPLEFT] = c; + else if (coloringPropertyName == "tr") + txt.colors[C_TOPRIGHT] = c; + else if (coloringPropertyName == "bl") + txt.colors[C_BOTTOMLEFT] = c; + else if (coloringPropertyName == "br") + txt.colors[C_BOTTOMRIGHT] = c; + attributesDirty = true; + } + } + } + + timeToNextColoringGroupUpdate = time.systemTime + stepColoringGroupUpdate; +} + +// Return old colors, wheel was closed or color discarded +void HandleWheelDiscardColor(StringHash eventType, VariantMap& eventData) +{ + if (coloringComponent !is null) + { + //Color oldColor = eventData["Color"].GetColor(); //Old color from ColorWheel from ShowColorWheelWithColor(old) + Color oldColor = coloringOldColor; + + // preview new color + if (coloringComponent.typeName == "Light") + { + Light@ light = cast(coloringComponent); + if (light !is null) + { + if (coloringPropertyName == "menuLightColor") + { + light.color = oldColor; + } + else if (coloringPropertyName == "menuSpecularIntensity") + { + light.specularIntensity = coloringOldScalar * 10.0f; + + } + else if (coloringPropertyName == "menuBrightnessMultiplier") + { + light.brightness = coloringOldScalar * 10.0f; + + } + + attributesDirty = true; + } + } + else if (coloringComponent.typeName == "StaticModel") + { + StaticModel@ model = cast(coloringComponent); + if (model !is null) + { + Material@ mat = model.materials[0]; + if (mat !is null) + { + if (coloringPropertyName == "menuDiffuseColor") + { + Variant oldValue = mat.shaderParameters["MatDiffColor"]; + Variant newValue; + String valueString; + valueString += String(oldColor.r).Substring(0,5); + valueString += " "; + valueString += String(oldColor.g).Substring(0,5); + valueString += " "; + valueString += String(oldColor.b).Substring(0,5); + valueString += " "; + valueString += String(oldColor.a).Substring(0,5); + newValue.FromString(oldValue.type, valueString); + mat.shaderParameters["MatDiffColor"] = newValue; + } + else if (coloringPropertyName == "menuSpecularColor") + { + Variant oldValue = mat.shaderParameters["MatSpecColor"]; + Variant newValue; + String valueString; + valueString += String(oldColor.r).Substring(0,5); + valueString += " "; + valueString += String(oldColor.g).Substring(0,5); + valueString += " "; + valueString += String(oldColor.b).Substring(0,5); + valueString += " "; + valueString += String(coloringOldScalar).Substring(0,5); + newValue.FromString(oldValue.type, valueString); + mat.shaderParameters["MatSpecColor"] = newValue; + } + else if (coloringPropertyName == "menuEmissiveColor") + { + Variant oldValue = mat.shaderParameters["MatEmissiveColor"]; + Variant newValue; + String valueString; + valueString += String(oldColor.r).Substring(0,5); + valueString += " "; + valueString += String(oldColor.g).Substring(0,5); + valueString += " "; + valueString += String(oldColor.b).Substring(0,5); + valueString += " "; + valueString += String(oldColor.a).Substring(0,5); + newValue.FromString(oldValue.type, valueString); + mat.shaderParameters["MatEmissiveColor"] = newValue; + } + else if (coloringPropertyName == "menuEnvironmentMapColor") + { + Variant oldValue = mat.shaderParameters["MatEnvMapColor"]; + Variant newValue; + String valueString; + valueString += String(oldColor.r).Substring(0,5); + valueString += " "; + valueString += String(oldColor.g).Substring(0,5); + valueString += " "; + valueString += String(oldColor.b).Substring(0,5); + valueString += " "; + valueString += String(oldColor.a).Substring(0,5); + newValue.FromString(oldValue.type, valueString); + mat.shaderParameters["MatEnvMapColor"] = newValue; + } + } + } + } + else if (coloringComponent.typeName == "Zone") + { + Zone@ zone = cast(coloringComponent); + if (zone !is null) + { + if (coloringPropertyName == "menuAmbientColor") + { + zone.ambientColor = oldColor; + } + else if (coloringPropertyName == "menuFogColor") + { + zone.fogColor = oldColor; + } + + attributesDirty = true; + } + } + } +} + +// Applying color wheel changes to material +void HandleWheelSelectColor(StringHash eventType, VariantMap& eventData) +{ + if (coloringComponent !is null) + if (coloringComponent.typeName == "StaticModel") + { + Color c = eventData["Color"].GetColor(); //Selected color from ColorWheel + StaticModel@ model = cast(coloringComponent); + if (model !is null) + { + Material@ mat = model.materials[0]; + if (mat !is null) + { + editMaterial = mat; + SaveMaterial(); + } + } + } +} + +bool ViewDebugIcons() +{ + debugIconsShow = !debugIconsShow; + return true; +} diff --git a/bin/Data/Scripts/Editor/EditorUIElement.as b/bin/Data/Scripts/Editor/EditorUIElement.as new file mode 100644 index 0000000..cd13475 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorUIElement.as @@ -0,0 +1,723 @@ +// Urho3D editor UI-element handling + +UIElement@ editorUIElement; +XMLFile@ uiElementDefaultStyle; +Array availableStyles; + +UIElement@ editUIElement; +Array selectedUIElements; +Array editUIElements; + +Array uiElementCopyBuffer; + +bool suppressUIElementChanges = false; + +const StringHash FILENAME_VAR("FileName"); +const StringHash MODIFIED_VAR("Modified"); +const StringHash CHILD_ELEMENT_FILENAME_VAR("ChildElemFileName"); + +void ClearUIElementSelection() +{ + editUIElement = null; + selectedUIElements.Clear(); + editUIElements.Clear(); +} + +void CreateRootUIElement() +{ + // Create a root UIElement only once here, do not confuse this with ui.root itself + editorUIElement = ui.root.CreateChild("UIElement"); + editorUIElement.name = "UI"; + editorUIElement.SetSize(graphics.width, graphics.height); + editorUIElement.traversalMode = TM_DEPTH_FIRST; // This is needed for root-like element to prevent artifacts + editorUIElement.priority = -1000; // All user-created UI elements have lowest priority so they do not cover editor's windows + + // This is needed to distinguish our own element events from Editor's UI element events + editorUIElement.elementEventSender = true; + SubscribeToEvent(editorUIElement, "ElementAdded", "HandleUIElementAdded"); + SubscribeToEvent(editorUIElement, "ElementRemoved", "HandleUIElementRemoved"); + + // Since this root UIElement is not being handled by above handlers, update it into hierarchy list manually as another list root item + UpdateHierarchyItem(M_MAX_UNSIGNED, editorUIElement, null); +} + +bool NewUIElement(const String&in typeName) +{ + // If no edit element then parented to root + UIElement@ parent = editUIElement !is null ? editUIElement : editorUIElement; + UIElement@ element = parent.CreateChild(typeName); + if (element !is null) + { + // Use the predefined UI style if set, otherwise use editor's own UI style + XMLFile@ defaultStyle = uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle; + + if (editUIElement is null) + { + // If parented to root, set the internal variables + element.vars[FILENAME_VAR] = ""; + element.vars[MODIFIED_VAR] = false; + // set a default UI style + element.defaultStyle = defaultStyle; + // and position the newly created element at center + CenterDialog(element); + } + // Apply the auto style + element.style = AUTO_STYLE; + // Do not allow UI subsystem to reorder children while editing the element in the editor + element.sortChildren = false; + + // Create an undo action for the create + CreateUIElementAction action; + action.Define(element); + SaveEditAction(action); + SetUIElementModified(element); + + FocusUIElement(element); + } + return true; +} + +void ResetSortChildren(UIElement@ element) +{ + element.sortChildren = false; + + // Perform the action recursively for child elements + for (uint i = 0; i < element.numChildren; ++i) + ResetSortChildren(element.children[i]); +} + +void OpenUILayout(const String&in fileName) +{ + if (fileName.empty) + return; + + ui.cursor.shape = CS_BUSY; + + // Check if the UI element has been opened before + if (editorUIElement.GetChild(FILENAME_VAR, Variant(fileName)) !is null) + { + MessageBox("UI element is already opened.\n" + fileName); + return; + } + + // Always load from the filesystem, not from resource paths + if (!fileSystem.FileExists(fileName)) + { + MessageBox("No such file.\n" + fileName); + return; + } + + File file(fileName, FILE_READ); + if (!file.open) + { + MessageBox("Could not open file.\n" + fileName); + return; + } + + // Add the UI layout's resource path in case it's necessary + SetResourcePath(GetPath(fileName), true, true); + + XMLFile@ xmlFile = XMLFile(); + xmlFile.Load(file); + + suppressUIElementChanges = true; + + // If uiElementDefaultStyle is not set then automatically fallback to use the editor's own default style + UIElement@ element = ui.LoadLayout(xmlFile, uiElementDefaultStyle); + if (element !is null) + { + element.vars[FILENAME_VAR] = fileName; + element.vars[MODIFIED_VAR] = false; + + // Do not allow UI subsystem to reorder children while editing the element in the editor + ResetSortChildren(element); + // Register variable names from the 'enriched' XMLElement, if any + RegisterUIElementVar(xmlFile.root); + + editorUIElement.AddChild(element); + + UpdateHierarchyItem(element); + FocusUIElement(element); + + ClearEditActions(); + } + else + MessageBox("Could not load UI layout successfully!\nSee Urho3D.log for more detail."); + + suppressUIElementChanges = false; +} + +bool CloseUILayout() +{ + ui.cursor.shape = CS_BUSY; + + if (messageBoxCallback is null) + { + for (uint i = 0; i < selectedUIElements.length; ++i) + { + UIElement@ element = GetTopLevelUIElement(selectedUIElements[i]); + if (element !is null && element.vars[MODIFIED_VAR].GetBool()) + { + MessageBox@ messageBox = MessageBox("UI layout has been modified.\nContinue to close?", "Warning"); + if (messageBox.window !is null) + { + Button@ cancelButton = messageBox.window.GetChild("CancelButton", true); + cancelButton.visible = true; + cancelButton.focus = true; + SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement"); + messageBoxCallback = @CloseUILayout; + return false; + } + } + } + } + else + messageBoxCallback = null; + + suppressUIElementChanges = true; + + for (uint i = 0; i < selectedUIElements.length; ++i) + { + UIElement@ element = GetTopLevelUIElement(selectedUIElements[i]); + if (element !is null) + { + element.Remove(); + UpdateHierarchyItem(GetListIndex(element), null, null); + } + } + hierarchyList.ClearSelection(); + ClearEditActions(); + + suppressUIElementChanges = false; + + return true; +} + +bool CloseAllUILayouts() +{ + ui.cursor.shape = CS_BUSY; + + if (messageBoxCallback is null) + { + for (uint i = 0; i < editorUIElement.numChildren; ++i) + { + UIElement@ element = editorUIElement.children[i]; + if (element !is null && element.vars[MODIFIED_VAR].GetBool()) + { + MessageBox@ messageBox = MessageBox("UI layout has been modified.\nContinue to close?", "Warning"); + if (messageBox.window !is null) + { + Button@ cancelButton = messageBox.window.GetChild("CancelButton", true); + cancelButton.visible = true; + cancelButton.focus = true; + SubscribeToEvent(messageBox, "MessageACK", "HandleMessageAcknowledgement"); + messageBoxCallback = @CloseAllUILayouts; + return false; + } + } + } + } + else + messageBoxCallback = null; + + suppressUIElementChanges = true; + + editorUIElement.RemoveAllChildren(); + UpdateHierarchyItem(editorUIElement, true); + + // Reset element ID number generator + uiElementNextID = UI_ELEMENT_BASE_ID + 1; + + hierarchyList.ClearSelection(); + ClearEditActions(); + + suppressUIElementChanges = false; + + return true; +} + +bool SaveUILayout(const String&in fileName) +{ + if (fileName.empty) + return false; + + ui.cursor.shape = CS_BUSY; + + MakeBackup(fileName); + File file(fileName, FILE_WRITE); + if (!file.open) + { + MessageBox("Could not open file.\n" + fileName); + return false; + } + + UIElement@ element = GetTopLevelUIElement(editUIElement); + if (element is null) + return false; + + XMLFile@ elementData = XMLFile(); + XMLElement rootElem = elementData.CreateRoot("element"); + bool success = element.SaveXML(rootElem); + RemoveBackup(success, fileName); + + if (success) + { + FilterInternalVars(rootElem); + success = elementData.Save(file); + if (success) + { + element.vars[FILENAME_VAR] = fileName; + SetUIElementModified(element, false); + } + } + if (!success) + MessageBox("Could not save UI layout successfully!\nSee Urho3D.log for more detail."); + + return success; +} + +bool SaveUILayoutWithExistingName() +{ + ui.cursor.shape = CS_BUSY; + + UIElement@ element = GetTopLevelUIElement(editUIElement); + if (element is null) + return false; + + String fileName = element.GetVar(FILENAME_VAR).GetString(); + if (fileName.empty) + return PickFile(); // No name yet, so pick one + else + return SaveUILayout(fileName); +} + +void LoadChildUIElement(const String&in fileName) +{ + if (fileName.empty) + return; + + ui.cursor.shape = CS_BUSY; + + if (!fileSystem.FileExists(fileName)) + { + MessageBox("No such file.\n" + fileName); + return; + } + + File file(fileName, FILE_READ); + if (!file.open) + { + MessageBox("Could not open file.\n" + fileName); + return; + } + + XMLFile@ xmlFile = XMLFile(); + xmlFile.Load(file); + + suppressUIElementChanges = true; + + if (editUIElement.LoadChildXML(xmlFile, uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle) !is null) + { + XMLElement rootElem = xmlFile.root; + uint index = rootElem.HasAttribute("index") ? rootElem.GetUInt("index") : editUIElement.numChildren - 1; + UIElement@ element = editUIElement.children[index]; + ResetSortChildren(element); + RegisterUIElementVar(xmlFile.root); + element.vars[CHILD_ELEMENT_FILENAME_VAR] = fileName; + if (index == editUIElement.numChildren - 1) + UpdateHierarchyItem(element); + else + // If not last child, find the list index of the next sibling as the insertion index + UpdateHierarchyItem(GetListIndex(editUIElement.children[index + 1]), element, hierarchyList.items[GetListIndex(editUIElement)]); + SetUIElementModified(element); + + // Create an undo action for the load + CreateUIElementAction action; + action.Define(element); + SaveEditAction(action); + + FocusUIElement(element); + } + + suppressUIElementChanges = false; +} + +bool SaveChildUIElement(const String&in fileName) +{ + if (fileName.empty) + return false; + + ui.cursor.shape = CS_BUSY; + + MakeBackup(fileName); + File file(fileName, FILE_WRITE); + if (!file.open) + { + MessageBox("Could not open file.\n" + fileName); + return false; + } + + XMLFile@ elementData = XMLFile(); + XMLElement rootElem = elementData.CreateRoot("element"); + bool success = editUIElement.SaveXML(rootElem); + RemoveBackup(success, fileName); + + if (success) + { + FilterInternalVars(rootElem); + success = elementData.Save(file); + if (success) + editUIElement.vars[CHILD_ELEMENT_FILENAME_VAR] = fileName; + } + if (!success) + MessageBox("Could not save child UI element successfully!\nSee Urho3D.log for more detail."); + + return success; +} + +void SetUIElementDefaultStyle(const String&in fileName) +{ + if (fileName.empty) + return; + + ui.cursor.shape = CS_BUSY; + + // Always load from the filesystem, not from resource paths + if (!fileSystem.FileExists(fileName)) + { + MessageBox("No such file.\n" + fileName); + return; + } + + File file(fileName, FILE_READ); + if (!file.open) + { + MessageBox("Could not open file.\n" + fileName); + return; + } + + uiElementDefaultStyle = XMLFile(); + uiElementDefaultStyle.Load(file); + + // Remove the existing style list to ensure it gets repopulated again with the new default style file + availableStyles.Clear(); + + // Refresh Attribute Inspector when it is currently showing attributes of UI-element item type as the existing styles in the style drop down list are not valid anymore + if (!editUIElements.empty) + attributesFullDirty = true; +} + +// Prepare XPath query object only once and use it multiple times +XPathQuery filterInternalVarsQuery("//attribute[@name='Variables']/variant"); + +void FilterInternalVars(XMLElement source) +{ + XPathResultSet resultSet = filterInternalVarsQuery.Evaluate(source); + XMLElement resultElem = resultSet.firstResult; + while (resultElem.notNull) + { + String name = GetVarName(resultElem.GetUInt("hash")); + if (name.empty) + { + XMLElement parent = resultElem.parent; + + // If variable name is empty (or unregistered) then it is an internal variable and should be removed + if (parent.RemoveChild(resultElem)) + { + // If parent does not have any children anymore then remove the parent also + if (!parent.HasChild("variant")) + parent.parent.RemoveChild(parent); + } + } + else + // If it is registered then it is a user-defined variable, so 'enrich' the XMLElement to store the variable name in plaintext + resultElem.SetAttribute("name", name); + resultElem = resultElem.nextResult; + } +} + +XPathQuery registerUIElemenVarsQuery("//attribute[@name='Variables']/variant/@name"); + +void RegisterUIElementVar(XMLElement source) +{ + XPathResultSet resultSet = registerUIElemenVarsQuery.Evaluate(source); + XMLElement resultAttr = resultSet.firstResult; // Since we are selecting attribute, the resultset is in attribute context + while (resultAttr.notNull) + { + String name = resultAttr.GetAttribute(); + globalVarNames[name] = name; + resultAttr = resultAttr.nextResult; + } +} + +UIElement@ GetTopLevelUIElement(UIElement@ element) +{ + // Only top level UI-element contains the FILENAME_VAR + while (element !is null && !element.vars.Contains(FILENAME_VAR)) + element = element.parent; + return element; +} + +void SetUIElementModified(UIElement@ element, bool flag = true) +{ + element = GetTopLevelUIElement(element); + if (element !is null && element.GetVar(MODIFIED_VAR).GetBool() != flag) + { + element.vars[MODIFIED_VAR] = flag; + UpdateHierarchyItemText(GetListIndex(element), element.visible, GetUIElementTitle(element)); + } +} + +XPathQuery availableStylesXPathQuery("/elements/element[@auto='false']/@type"); + +void GetAvailableStyles() +{ + // Use the predefined UI style if set, otherwise use editor's own UI style + XMLFile@ defaultStyle = uiElementDefaultStyle !is null ? uiElementDefaultStyle : uiStyle; + XMLElement rootElem = defaultStyle.root; + XPathResultSet resultSet = availableStylesXPathQuery.Evaluate(rootElem); + XMLElement resultElem = resultSet.firstResult; + while (resultElem.notNull) + { + availableStyles.Push(resultElem.GetAttribute()); + resultElem = resultElem.nextResult; + } + + availableStyles.Sort(); +} + +void PopulateStyleList(DropDownList@ styleList) +{ + if (availableStyles.empty) + GetAvailableStyles(); + + for (uint i = 0; i < availableStyles.length; ++i) + { + Text@ choice = Text(); + styleList.AddItem(choice); + choice.style = "EditorEnumAttributeText"; + choice.text = availableStyles[i]; + } +} + +bool UIElementCut() +{ + return UIElementCopy() && UIElementDelete(); +} + +bool UIElementCopy() +{ + ui.cursor.shape = CS_BUSY; + + uiElementCopyBuffer.Clear(); + + for (uint i = 0; i < selectedUIElements.length; ++i) + { + XMLFile@ xml = XMLFile(); + XMLElement rootElem = xml.CreateRoot("element"); + selectedUIElements[i].SaveXML(rootElem); + uiElementCopyBuffer.Push(xml); + } + + return true; +} + +void ResetDuplicateID(UIElement@ element) +{ + // If it is a duplicate copy then the element ID need to be regenerated by resetting it now to empty + if (GetListIndex(element) != NO_ITEM) + element.vars[UI_ELEMENT_ID_VAR] = Variant(); + + // Perform the action recursively for child elements + for (uint i = 0; i < element.numChildren; ++i) + ResetDuplicateID(element.children[i]); +} + +bool UIElementPaste(bool duplication = false) +{ + ui.cursor.shape = CS_BUSY; + + // Group for storing undo actions + EditActionGroup group; + + // Have to update manually because the element ID var is not set yet when the E_ELEMENTADDED event is sent + suppressUIElementChanges = true; + + for (uint i = 0; i < uiElementCopyBuffer.length; ++i) + { + XMLElement rootElem = uiElementCopyBuffer[i].root; + + UIElement@ pasteElement; + + if (!duplication) + pasteElement = editUIElement; + else + { + if (editUIElement.parent !is null) + pasteElement = editUIElement.parent; + else + pasteElement = editUIElement; + } + + if (pasteElement.LoadChildXML(rootElem, null) !is null) + { + UIElement@ element = pasteElement.children[pasteElement.numChildren - 1]; + + ResetDuplicateID(element); + UpdateHierarchyItem(element); + SetUIElementModified(pasteElement); + + // Create an undo action + CreateUIElementAction action; + action.Define(element); + group.actions.Push(action); + } + } + + SaveEditActionGroup(group); + + suppressUIElementChanges = false; + + return true; +} + +bool UIElementDuplicate() +{ + ui.cursor.shape = CS_BUSY; + + Array copy = uiElementCopyBuffer; + UIElementCopy(); + UIElementPaste(true); + uiElementCopyBuffer = copy; + + return true; +} + +bool UIElementDelete() +{ + ui.cursor.shape = CS_BUSY; + + BeginSelectionModify(); + + // Clear the selection now to prevent deleted elements from being reselected + hierarchyList.ClearSelection(); + + // Group for storing undo actions + EditActionGroup group; + + for (uint i = 0; i < selectedUIElements.length; ++i) + { + UIElement@ element = selectedUIElements[i]; + if (element.parent is null) + continue; // Already deleted + + uint index = GetListIndex(element); + + // Create undo action + DeleteUIElementAction action; + action.Define(element); + group.actions.Push(action); + + SetUIElementModified(element); + element.Remove(); + + // If deleting only one element, select the next item in the same index + if (selectedUIElements.length == 1) + hierarchyList.selection = index; + } + + SaveEditActionGroup(group); + + EndSelectionModify(); + return true; +} + +bool UIElementSelectAll() +{ + BeginSelectionModify(); + Array indices; + uint baseIndex = GetListIndex(editorUIElement); + indices.Push(baseIndex); + int baseIndent = hierarchyList.items[baseIndex].indent; + for (uint i = baseIndex + 1; i < hierarchyList.numItems; ++i) + { + if (hierarchyList.items[i].indent <= baseIndent) + break; + indices.Push(i); + } + hierarchyList.SetSelections(indices); + EndSelectionModify(); + + return true; +} + +bool UIElementResetToDefault() +{ + ui.cursor.shape = CS_BUSY; + + // Group for storing undo actions + EditActionGroup group; + + // Reset selected elements to their default values + for (uint i = 0; i < selectedUIElements.length; ++i) + { + UIElement@ element = selectedUIElements[i]; + + ResetAttributesAction action; + action.Define(element); + group.actions.Push(action); + + element.ResetToDefault(); + action.SetInternalVars(element); + element.ApplyAttributes(); + for (uint j = 0; j < element.numAttributes; ++j) + PostEditAttribute(element, j); + SetUIElementModified(element); + } + + SaveEditActionGroup(group); + attributesFullDirty = true; + + return true; +} + +bool UIElementChangeParent(UIElement@ sourceElement, UIElement@ targetElement) +{ + ReparentUIElementAction action; + action.Define(sourceElement, targetElement); + SaveEditAction(action); + + sourceElement.parent = targetElement; + SetUIElementModified(targetElement); + return sourceElement.parent is targetElement; +} + +bool UIElementReorder(UIElement@ sourceElement, UIElement@ targetElement) +{ + if (sourceElement is null || targetElement is null || sourceElement.parent is null || sourceElement.parent !is targetElement.parent) + return false; + if (sourceElement is targetElement) + return true; // No-op + UIElement@ parent = sourceElement.parent; + uint destIndex = parent.FindChild(targetElement); + Print("Reorder to dest index " + destIndex); + + ReorderUIElementAction action; + action.Define(sourceElement, destIndex); + SaveEditAction(action); + PerformReorder(parent, sourceElement, destIndex); + + return true; +} + +void PerformReorder(UIElement@ parent, UIElement@ child, uint destIndex) +{ + suppressSceneChanges = true; + + parent.RemoveChild(child); + parent.InsertChild(destIndex, child); + UpdateHierarchyItem(parent); // Force update to make sure the order is current + SetUIElementModified(parent); + + suppressSceneChanges = false; +} diff --git a/bin/Data/Scripts/Editor/EditorView.as b/bin/Data/Scripts/Editor/EditorView.as new file mode 100644 index 0000000..31e5a95 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorView.as @@ -0,0 +1,2797 @@ +// Urho3D editor view & camera functions + +WeakHandle previewCamera; + +Node@ cameraLookAtNode; +Node@ cameraNode; +Camera@ camera; + +float orthoCameraZoom = 1.0f; + +Node@ gridNode; +CustomGeometry@ grid; + +UIElement@ viewportUI; // holds the viewport ui, convienent for clearing and hiding +uint setViewportCursor = 0; // used to set cursor in post update +uint resizingBorder = 0; // current border that is dragging +uint viewportMode = VIEWPORT_SINGLE; +int viewportBorderOffset = 2; // used to center borders over viewport seams, should be half of width +int viewportBorderWidth = 4; // width of a viewport resize border +IntRect viewportArea; // the area where the editor viewport is. if we ever want to have the viewport not take up the whole screen this abstracts that +IntRect viewportUIClipBorder = IntRect(27, 60, 0, 0); // used to clip viewport borders, the borders are ugly when going behind the transparent toolbars +RenderPath@ renderPath; // Renderpath to use on all views +String renderPathName; +bool gammaCorrection = false; +bool HDR = false; +bool contextMenuActionWaitFrame = false; +bool cameraFlyMode = true; +int hotKeyMode = 0; // used for checking that kind of style manipulation user are prefer (see HotKeysMode) +Vector3 lastSelectedNodesCenterPoint = Vector3(0,0,0); // for Blender mode to avoid own origin rotation when no nodes are selected. preserve last center for this +WeakHandle lastSelectedNode = null; +WeakHandle lastSelectedDrawable = null; +WeakHandle lastSelectedComponent = null; +Component@ coloringComponent = null; +String coloringTypeName; +String coloringPropertyName; +Color coloringOldColor; +float coloringOldScalar; +bool debugRenderDisabled = false; +bool restoreViewport = false; +IntVector2 oldHierarchyWindowPosition; // used for restore hierarchy position when switch between viewport modes +int oldHierarchyWindowHeight; +IntVector2 oldInspectorWindowPosition; // used for restore inspector position when switch between viewport modes +int oldInspectorWindowHeight; + +const uint VIEWPORT_BORDER_H = 0x00000001; +const uint VIEWPORT_BORDER_H1 = 0x00000002; +const uint VIEWPORT_BORDER_H2 = 0x00000004; +const uint VIEWPORT_BORDER_V = 0x00000010; +const uint VIEWPORT_BORDER_V1 = 0x00000020; +const uint VIEWPORT_BORDER_V2 = 0x00000040; + +const uint VIEWPORT_SINGLE = 0x00000000; +const uint VIEWPORT_COMPACT = 0x00009000; +const uint VIEWPORT_TOP = 0x00000100; +const uint VIEWPORT_BOTTOM = 0x00000200; +const uint VIEWPORT_LEFT = 0x00000400; +const uint VIEWPORT_RIGHT = 0x00000800; +const uint VIEWPORT_TOP_LEFT = 0x00001000; +const uint VIEWPORT_TOP_RIGHT = 0x00002000; +const uint VIEWPORT_BOTTOM_LEFT = 0x00004000; +const uint VIEWPORT_BOTTOM_RIGHT = 0x00008000; + +// Combinations for easier testing +const uint VIEWPORT_BORDER_H_ANY = 0x00000007; +const uint VIEWPORT_BORDER_V_ANY = 0x00000070; +const uint VIEWPORT_SPLIT_H = 0x0000f300; +const uint VIEWPORT_SPLIT_V = 0x0000fc00; +const uint VIEWPORT_SPLIT_HV = 0x0000f000; +const uint VIEWPORT_TOP_ANY = 0x00003300; +const uint VIEWPORT_BOTTOM_ANY = 0x0000c200; +const uint VIEWPORT_LEFT_ANY = 0x00005400; +const uint VIEWPORT_RIGHT_ANY = 0x0000c800; +const uint VIEWPORT_QUAD = 0x0000f000; + +enum HotKeysMode +{ + HOTKEYS_MODE_STANDARD = 0, + HOTKEYS_MODE_BLENDER +} + +enum EditMode +{ + EDIT_MOVE = 0, + EDIT_ROTATE, + EDIT_SCALE, + EDIT_SELECT, + EDIT_SPAWN +} + +enum AxisMode +{ + AXIS_WORLD = 0, + AXIS_LOCAL +} + +enum SnapScaleMode +{ + SNAP_SCALE_FULL = 0, + SNAP_SCALE_HALF, + SNAP_SCALE_QUARTER +} + +// Holds info about a viewport such as camera settings and splits up shared resources +class ViewportContext +{ + float cameraYaw = 0; + float cameraPitch = 0; + Camera@ camera; + Node@ cameraLookAtNode; + Node@ cameraNode; + SoundListener@ soundListener; + Viewport@ viewport; + bool enabled = false; + uint index = 0; + uint viewportId = 0; + UIElement@ viewportContextUI; + UIElement@ statusBar; + Text@ cameraPosText; + + Window@ settingsWindow; + LineEdit@ cameraPosX; + LineEdit@ cameraPosY; + LineEdit@ cameraPosZ; + LineEdit@ cameraRotX; + LineEdit@ cameraRotY; + LineEdit@ cameraRotZ; + LineEdit@ cameraZoom; + LineEdit@ cameraOrthoSize; + CheckBox@ cameraOrthographic; + + ViewportContext(IntRect viewRect, uint index_, uint viewportId_) + { + cameraNode = Node(); + cameraLookAtNode = Node(); + cameraLookAtNode.AddChild(cameraNode); + camera = cameraNode.CreateComponent("Camera"); + orthoCameraZoom = camera.zoom; + camera.fillMode = fillMode; + soundListener = cameraNode.CreateComponent("SoundListener"); + viewport = Viewport(editorScene, camera, viewRect, renderPath); + index = index_; + viewportId = viewportId_; + camera.viewMask = 0xffffffff; // It's easier to only have 1 gizmo active this viewport is shared with the gizmo + } + + void ResetCamera() + { + cameraSmoothInterpolate.Stop(); + + cameraLookAtNode.position = Vector3(0, 0, 0); + cameraLookAtNode.rotation = Quaternion(); + + cameraNode.position = Vector3(0, 5, -10); + // Look at the origin so user can see the scene. + cameraNode.rotation = Quaternion(Vector3(0, 0, 1), -cameraNode.position); + ReacquireCameraYawPitch(); + UpdateSettingsUI(); + } + + void ReacquireCameraYawPitch() + { + cameraYaw = cameraNode.rotation.yaw; + cameraPitch = cameraNode.rotation.pitch; + } + + void CreateViewportContextUI() + { + Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"); + + viewportContextUI = UIElement(); + viewportUI.AddChild(viewportContextUI); + viewportContextUI.SetPosition(viewport.rect.left, viewport.rect.top); + viewportContextUI.SetFixedSize(viewport.rect.width, viewport.rect.height); + viewportContextUI.clipChildren = true; + + statusBar = BorderImage("ToolBar"); + statusBar.style = "EditorToolBar"; + viewportContextUI.AddChild(statusBar); + + statusBar.SetLayout(LM_HORIZONTAL); + statusBar.SetAlignment(HA_LEFT, VA_BOTTOM); + statusBar.layoutSpacing = 4; + statusBar.opacity = uiMaxOpacity; + + Button@ settingsButton = CreateSmallToolBarButton("Settings"); + statusBar.AddChild(settingsButton); + + cameraPosText = Text(); + statusBar.AddChild(cameraPosText); + + cameraPosText.SetFont(font, 11); + cameraPosText.color = Color(1, 1, 0); + cameraPosText.textEffect = TE_SHADOW; + cameraPosText.priority = -100; + + settingsWindow = LoadEditorUI("UI/EditorViewport.xml"); + settingsWindow.opacity = uiMaxOpacity; + settingsWindow.visible = false; + viewportContextUI.AddChild(settingsWindow); + + cameraPosX = settingsWindow.GetChild("PositionX", true); + cameraPosY = settingsWindow.GetChild("PositionY", true); + cameraPosZ = settingsWindow.GetChild("PositionZ", true); + cameraRotX = settingsWindow.GetChild("RotationX", true); + cameraRotY = settingsWindow.GetChild("RotationY", true); + cameraRotZ = settingsWindow.GetChild("RotationZ", true); + cameraOrthographic = settingsWindow.GetChild("Orthographic", true); + cameraZoom = settingsWindow.GetChild("Zoom", true); + cameraOrthoSize = settingsWindow.GetChild("OrthoSize", true); + + SubscribeToEvent(cameraPosX, "TextChanged", "HandleSettingsLineEditTextChange"); + SubscribeToEvent(cameraPosY, "TextChanged", "HandleSettingsLineEditTextChange"); + SubscribeToEvent(cameraPosZ, "TextChanged", "HandleSettingsLineEditTextChange"); + SubscribeToEvent(cameraRotX, "TextChanged", "HandleSettingsLineEditTextChange"); + SubscribeToEvent(cameraRotY, "TextChanged", "HandleSettingsLineEditTextChange"); + SubscribeToEvent(cameraRotZ, "TextChanged", "HandleSettingsLineEditTextChange"); + SubscribeToEvent(cameraZoom, "TextChanged", "HandleSettingsLineEditTextChange"); + SubscribeToEvent(cameraOrthoSize, "TextChanged", "HandleSettingsLineEditTextChange"); + SubscribeToEvent(cameraOrthographic, "Toggled", "HandleOrthographicToggled"); + + SubscribeToEvent(settingsButton, "Released", "ToggleViewportSettingsWindow"); + SubscribeToEvent(settingsWindow.GetChild("ResetCamera", true), "Released", "ResetCamera"); + SubscribeToEvent(settingsWindow.GetChild("CopyTransform", true), "Released", "HandleCopyTransformClicked"); + SubscribeToEvent(settingsWindow.GetChild("CloseButton", true), "Released", "CloseViewportSettingsWindow"); + SubscribeToEvent(settingsWindow.GetChild("Refresh", true), "Released", "UpdateSettingsUI"); + HandleResize(); + } + + void HandleResize() + { + viewportContextUI.SetPosition(viewport.rect.left, viewport.rect.top); + viewportContextUI.SetFixedSize(viewport.rect.width, viewport.rect.height); + if (viewport.rect.left < 34) + { + statusBar.layoutBorder = IntRect(34 - viewport.rect.left, 4, 4, 8); + IntVector2 pos = settingsWindow.position; + pos.x = 32 - viewport.rect.left; + settingsWindow.position = pos; + } + else + { + statusBar.layoutBorder = IntRect(8, 4, 4, 8); + IntVector2 pos = settingsWindow.position; + pos.x = 5; + settingsWindow.position = pos; + } + + statusBar.SetFixedSize(viewport.rect.width, 22); + } + + void ToggleOrthographic() + { + SetOrthographic(!camera.orthographic); + } + + void SetOrthographic(bool orthographic) + { + camera.orthographic = orthographic; + if (camera.orthographic) + camera.zoom = orthoCameraZoom; + else + camera.zoom = 1.0f; + + UpdateSettingsUI(); + } + + void Update(float timeStep) + { + // Update camera smooth move + if (cameraSmoothInterpolate.IsRunning()) + { + cameraSmoothInterpolate.Update(timeStep); + } + + Vector3 cameraPos = cameraNode.position; + String xText(cameraPos.x); + String yText(cameraPos.y); + String zText(cameraPos.z); + xText.Resize(8); + yText.Resize(8); + zText.Resize(8); + + cameraPosText.text = String( + "Pos: " + xText + " " + yText + " " + zText + + " Zoom: " + camera.zoom); + + cameraPosText.size = cameraPosText.minSize; + } + + void ToggleViewportSettingsWindow() + { + if (settingsWindow.visible) + CloseViewportSettingsWindow(); + else + OpenViewportSettingsWindow(); + } + + void OpenViewportSettingsWindow() + { + UpdateSettingsUI(); + /* settingsWindow.position = */ + settingsWindow.visible = true; + settingsWindow.BringToFront(); + } + + void CloseViewportSettingsWindow() + { + settingsWindow.visible = false; + } + + void UpdateSettingsUI() + { + cameraPosX.text = String(Floor(cameraNode.position.x * 1000) / 1000); + cameraPosY.text = String(Floor(cameraNode.position.y * 1000) / 1000); + cameraPosZ.text = String(Floor(cameraNode.position.z * 1000) / 1000); + cameraRotX.text = String(Floor(cameraNode.rotation.pitch * 1000) / 1000); + cameraRotY.text = String(Floor(cameraNode.rotation.yaw * 1000) / 1000); + cameraRotZ.text = String(Floor(cameraNode.rotation.roll * 1000) / 1000); + cameraZoom.text = String(Floor(camera.zoom * 1000) / 1000); + cameraOrthoSize.text = String(Floor(camera.orthoSize * 1000) / 1000); + cameraOrthographic.checked = camera.orthographic; + } + + void HandleOrthographicToggled(StringHash eventType, VariantMap& eventData) + { + SetOrthographic(cameraOrthographic.checked); + } + + void HandleSettingsLineEditTextChange(StringHash eventType, VariantMap& eventData) + { + LineEdit@ element = eventData["Element"].GetPtr(); + if (element.text == "") + return; + + if (element is cameraRotX || element is cameraRotY || element is cameraRotZ) + { + Vector3 euler = cameraNode.rotation.eulerAngles; + if (element is cameraRotX) + euler.x = element.text.ToFloat(); + else if (element is cameraRotY) + euler.y = element.text.ToFloat(); + else if (element is cameraRotZ) + euler.z = element.text.ToFloat(); + + cameraNode.rotation = Quaternion(euler); + } + else if (element is cameraPosX || element is cameraPosY || element is cameraPosZ) + { + Vector3 pos = cameraNode.position; + if (element is cameraPosX) + pos.x = element.text.ToFloat(); + else if (element is cameraPosY) + pos.y = element.text.ToFloat(); + else if (element is cameraPosZ) + pos.z = element.text.ToFloat(); + + cameraNode.position = pos; + } + else if (element is cameraZoom) + camera.zoom = element.text.ToFloat(); + else if (element is cameraOrthoSize) + camera.orthoSize = element.text.ToFloat(); + } + void HandleCopyTransformClicked(StringHash eventType, VariantMap& eventData) + { + if (editNode !is null) + { + editNode.position = cameraNode.position; + editNode.rotation = cameraNode.rotation; + } + } +} + +Array viewports; +ViewportContext@ activeViewport; + +Text@ editorModeText; +Text@ renderStatsText; +Text@ modelInfoText; + +EditMode editMode = EDIT_MOVE; +AxisMode axisMode = AXIS_WORLD; +FillMode fillMode = FILL_SOLID; +SnapScaleMode snapScaleMode = SNAP_SCALE_FULL; + +float viewNearClip = 0.1; +float viewFarClip = 1000.0; +float viewFov = 45.0; + + +float cameraBaseSpeed = 3; +float cameraBaseRotationSpeed = 0.2; +float cameraShiftSpeedMultiplier = 5; +float moveStep = 0.5; +float rotateStep = 5; +float scaleStep = 0.1; +float snapScale = 1.0; +bool limitRotation = false; +bool moveSnap = false; +bool rotateSnap = false; +bool scaleSnap = false; +bool renderingDebug = false; +bool physicsDebug = false; +bool octreeDebug = false; +bool navigationDebug = false; +int pickMode = PICK_GEOMETRIES; +bool orbiting = false; + +enum MouseOrbitMode +{ + ORBIT_RELATIVE = 0, + ORBIT_WRAP +} + +bool toggledMouseLock = false; +int mouseOrbitMode = ORBIT_RELATIVE; +bool mmbPanMode = false; +bool rotateAroundSelect = false; + +enum NewNodeMode +{ + NEW_NODE_CAMERA_LOOKAT = 0, + NEW_NODE_IN_CENTER, + NEW_NODE_RAYCAST +} + +int newNodeMode = NEW_NODE_CAMERA_LOOKAT; + +bool showGrid = true; +bool grid2DMode = false; +uint gridSize = 16; +uint gridSubdivisions = 3; +float gridScale = 8.0; +Color gridColor(0.1, 0.1, 0.1); +Color gridSubdivisionColor(0.05, 0.05, 0.05); +Color gridXColor(0.5, 0.1, 0.1); +Color gridYColor(0.1, 0.5, 0.1); +Color gridZColor(0.1, 0.1, 0.5); + +Array pickModeDrawableFlags = { + DRAWABLE_GEOMETRY, + DRAWABLE_LIGHT, + DRAWABLE_ZONE +}; + +Array editModeText = { + "Move", + "Rotate", + "Scale", + "Select", + "Spawn" +}; + +Array axisModeText = { + "World", + "Local" +}; + +Array pickModeText = { + "Geometries", + "Lights", + "Zones", + "Rigidbodies", + "UI-elements" +}; + +Array fillModeText = { + "Solid", + "Wire", + "Point" +}; + +// This class provides smooth translation/rotation/zoom interpolation for the editor camera +class CameraSmoothInterpolate +{ + Vector3 lookAtNodeBeginPos; + Vector3 cameraNodeBeginPos; + + Vector3 lookAtNodeEndPos; + Vector3 cameraNodeEndPos; + + Quaternion cameraNodeBeginRot; + Quaternion cameraNodeEndRot; + + float cameraBeginZoom; + float cameraEndZoom; + + bool isRunning = false; + float duration = 0.0f; + float elapsedTime = 0.0f; + + bool interpLookAtNodePos = false; + bool interpCameraNodePos = false; + bool interpCameraRot = false; + bool interpCameraZoom = false; + + CameraSmoothInterpolate() + { + } + + void SetLookAtNodePosition(Vector3 lookAtBeginPos, Vector3 lookAtEndPos) + { + lookAtNodeBeginPos = lookAtBeginPos; + lookAtNodeEndPos = lookAtEndPos; + interpLookAtNodePos = true; + } + + void SetCameraNodePosition(Vector3 cameraBeginPos, Vector3 cameraEndPos) + { + cameraNodeBeginPos = cameraBeginPos; + cameraNodeEndPos = cameraEndPos; + interpCameraNodePos = true; + } + + void SetCameraNodeRotation(Quaternion cameraBeginRot, Quaternion cameraEndRot) + { + cameraNodeBeginRot = cameraBeginRot; + cameraNodeEndRot = cameraEndRot; + interpCameraRot = true; + } + + void SetCameraZoom(float beginZoom, float endZoom) + { + cameraBeginZoom = beginZoom; + cameraEndZoom = endZoom; + interpCameraZoom = true; + } + + void Start(float duration_) + { + if (cameraLookAtNode is null || cameraNode is null || camera is null) + return; + + duration = duration_; + elapsedTime = 0.0f; + isRunning = true; + } + + void Stop() + { + interpLookAtNodePos = false; + interpCameraNodePos = false; + interpCameraRot = false; + interpCameraZoom = false; + + isRunning = false; + } + + void Finish() + { + if (!isRunning) + return; + + if (cameraLookAtNode is null || cameraNode is null || camera is null) + return; + + if (interpLookAtNodePos) + cameraLookAtNode.worldPosition = lookAtNodeEndPos; + + if (interpCameraNodePos) + cameraNode.position = cameraNodeEndPos; + + if (interpCameraRot) + { + cameraNode.rotation = cameraNodeEndRot; + ReacquireCameraYawPitch(); + } + + if (interpCameraZoom) + { + orthoCameraZoom = cameraEndZoom; + camera.zoom = cameraEndZoom; + } + + interpLookAtNodePos = false; + interpCameraNodePos = false; + interpCameraRot = false; + interpCameraZoom = false; + + isRunning = false; + } + + bool IsRunning() const + { + return isRunning; + } + + // Cubic easing out + // http://robertpenner.com/easing/ + float EaseOut(float t, float b , float c, float d) + { + return c * ((t = t / d - 1) * t * t + 1) + b; + } + + void Update(float timeStep) + { + if (!isRunning) + return; + + if (cameraLookAtNode is null || cameraNode is null || camera is null) + return; + + elapsedTime += timeStep; + + if (elapsedTime <= duration) + { + float factor = EaseOut(elapsedTime, 0.0f, 1.0f, duration); + + if (interpLookAtNodePos) + cameraLookAtNode.worldPosition = lookAtNodeBeginPos + (lookAtNodeEndPos - lookAtNodeBeginPos) * factor; + + if (interpCameraNodePos) + cameraNode.position = cameraNodeBeginPos + (cameraNodeEndPos - cameraNodeBeginPos) * factor; + + if (interpCameraRot) + { + cameraNode.rotation = cameraNodeBeginRot.Slerp(cameraNodeEndRot, factor); + ReacquireCameraYawPitch(); + } + + if (interpCameraZoom) + { + orthoCameraZoom = cameraBeginZoom + (cameraEndZoom - cameraBeginZoom) * factor; + camera.zoom = orthoCameraZoom; + } + } + else + { + Finish(); + } + } +} + + +CameraSmoothInterpolate cameraSmoothInterpolate; // Camera smooth interpolation control + +void SetRenderPath(const String&in newRenderPathName) +{ + renderPath = null; + renderPathName = newRenderPathName.Trimmed(); + + if (renderPathName.length > 0) + { + File@ file = cache.GetFile(renderPathName); + if (file !is null) + { + XMLFile@ xml = XMLFile(); + if (xml.Load(file)) + { + renderPath = RenderPath(); + if (!renderPath.Load(xml)) + renderPath = null; + } + } + } + + if (renderPath is null) + renderPath = renderer.defaultRenderPath.Clone(); + + // Append gamma correction postprocess and disable/enable it as requested + renderPath.Append(cache.GetResource("XMLFile", "PostProcess/GammaCorrection.xml")); + renderPath.SetEnabled("GammaCorrection", gammaCorrection); + + renderer.hdrRendering = HDR; + + for (uint i = 0; i < renderer.numViewports; ++i) + renderer.viewports[i].renderPath = renderPath; + + if (materialPreview !is null && materialPreview.viewport !is null) + materialPreview.viewport.renderPath = renderPath; + + if (particleEffectPreview !is null && particleEffectPreview.viewport !is null) + particleEffectPreview.viewport.renderPath = renderPath; +} + +void SetGammaCorrection(bool enable) +{ + gammaCorrection = enable; + if (renderPath !is null) + renderPath.SetEnabled("GammaCorrection", gammaCorrection); +} + +void SetHDR(bool enable) +{ + HDR = enable; + if (renderer !is null) + renderer.hdrRendering = HDR; +} + +void CreateCamera() +{ + // Set the initial viewport rect + viewportArea = IntRect(0, 0, graphics.width, graphics.height); + + // Set viewport single to store default hierarchy/inspector height/positions + if(viewportMode == VIEWPORT_COMPACT) + { + SetViewportMode(VIEWPORT_SINGLE); + SetViewportMode(VIEWPORT_COMPACT); + } + else + { + SetViewportMode(viewportMode); + } + + SetActiveViewport(viewports[0]); + + // Note: the camera is not inside the scene, so that it is not listed, and does not get deleted + ResetCamera(); + + // Set initial renderpath if defined + SetRenderPath(renderPathName); +} + +// Create any UI associated with changing the editor viewports +void CreateViewportUI() +{ + if (viewportUI is null) + { + viewportUI = UIElement(); + ui.root.AddChild(viewportUI); + } + + viewportUI.SetFixedSize(viewportArea.width, viewportArea.height); + viewportUI.position = IntVector2(viewportArea.top, viewportArea.left); + viewportUI.clipChildren = true; + viewportUI.clipBorder = viewportUIClipBorder; + viewportUI.RemoveAllChildren(); + viewportUI.priority = -2000; + + Array borders; + + IntRect top; + IntRect bottom; + IntRect left; + IntRect right; + IntRect topLeft; + IntRect topRight; + IntRect bottomLeft; + IntRect bottomRight; + + for (uint i = 0; i < viewports.length; ++i) + { + ViewportContext@ vc = viewports[i]; + vc.CreateViewportContextUI(); + + if (vc.viewportId & VIEWPORT_TOP > 0) + top = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_BOTTOM > 0) + bottom = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_LEFT > 0) + left = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_RIGHT > 0) + right = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_TOP_LEFT > 0) + topLeft = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_TOP_RIGHT > 0) + topRight = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_BOTTOM_LEFT > 0) + bottomLeft = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_BOTTOM_RIGHT > 0) + bottomRight = vc.viewport.rect; + } + + // Creates resize borders based on the mode set + if (viewportMode == VIEWPORT_QUAD) // independent borders for quad isn't easy + { + borders.Push(CreateViewportDragBorder(VIEWPORT_BORDER_V, topLeft.right - viewportBorderOffset, topLeft.top, viewportBorderWidth, viewportArea.height)); + borders.Push(CreateViewportDragBorder(VIEWPORT_BORDER_H, topLeft.left, topLeft.bottom-viewportBorderOffset, viewportArea.width, viewportBorderWidth)); + } + else + { + // Figures what borders to create based on mode + if (viewportMode & (VIEWPORT_LEFT|VIEWPORT_RIGHT) > 0) + { + borders.Push( + viewportMode & VIEWPORT_LEFT > 0 ? + CreateViewportDragBorder(VIEWPORT_BORDER_V, left.right-viewportBorderOffset, left.top, viewportBorderWidth, left.height) : + CreateViewportDragBorder(VIEWPORT_BORDER_V, right.left-viewportBorderOffset, right.top, viewportBorderWidth, right.height) + ); + } + else + { + if (viewportMode & (VIEWPORT_TOP_LEFT|VIEWPORT_TOP_RIGHT) > 0) + borders.Push(CreateViewportDragBorder(VIEWPORT_BORDER_V1, topLeft.right-viewportBorderOffset, topLeft.top, viewportBorderWidth, topLeft.height)); + if (viewportMode & (VIEWPORT_BOTTOM_LEFT|VIEWPORT_BOTTOM_RIGHT) > 0) + borders.Push(CreateViewportDragBorder(VIEWPORT_BORDER_V2, bottomLeft.right-viewportBorderOffset, bottomLeft.top, viewportBorderWidth, bottomLeft.height)); + } + + if (viewportMode & (VIEWPORT_TOP|VIEWPORT_BOTTOM) > 0) + { + borders.Push( + viewportMode & VIEWPORT_TOP > 0 ? + CreateViewportDragBorder(VIEWPORT_BORDER_H, top.left, top.bottom-viewportBorderOffset, top.width, viewportBorderWidth) : + CreateViewportDragBorder(VIEWPORT_BORDER_H, bottom.left, bottom.top-viewportBorderOffset, bottom.width, viewportBorderWidth) + ); + } + else + { + if (viewportMode & (VIEWPORT_TOP_LEFT|VIEWPORT_BOTTOM_LEFT) > 0) + borders.Push(CreateViewportDragBorder(VIEWPORT_BORDER_H1, topLeft.left, topLeft.bottom-viewportBorderOffset, topLeft.width, viewportBorderWidth)); + if (viewportMode & (VIEWPORT_TOP_RIGHT|VIEWPORT_BOTTOM_RIGHT) > 0) + borders.Push(CreateViewportDragBorder(VIEWPORT_BORDER_H2, topRight.left, topRight.bottom-viewportBorderOffset, topRight.width, viewportBorderWidth)); + } + } +} + +BorderImage@ CreateViewportDragBorder(uint value, int posX, int posY, int sizeX, int sizeY) +{ + BorderImage@ border = BorderImage(); + viewportUI.AddChild(border); + border.name = "border"; + border.style = "ViewportBorder"; + border.vars["VIEWMODE"] = value; + border.SetFixedSize(sizeX, sizeY); // relevant size gets set by viewport later + border.position = IntVector2(posX, posY); + border.opacity = uiMaxOpacity; + SubscribeToEvent(border, "DragMove", "HandleViewportBorderDragMove"); + SubscribeToEvent(border, "DragEnd", "HandleViewportBorderDragEnd"); + return border; +} + +void SetFillMode(FillMode fillMode_) +{ + fillMode = fillMode_; + for (uint i = 0; i < viewports.length; ++i) + viewports[i].camera.fillMode = fillMode_; +} + +// Sets the viewport mode +void SetViewportMode(uint mode = VIEWPORT_SINGLE) +{ + // Remember old viewport positions + Array cameralookAtPositions; + Array cameraLookAtRotations; + Array cameraPositions; + Array cameraRotations; + for (uint i = 0; i < viewports.length; ++i) + { + cameralookAtPositions.Push(viewports[i].cameraLookAtNode.position); + cameraLookAtRotations.Push(viewports[i].cameraLookAtNode.rotation); + + cameraPositions.Push(viewports[i].cameraNode.position); + cameraRotations.Push(viewports[i].cameraNode.rotation); + } + + viewports.Clear(); + + if(mode == VIEWPORT_COMPACT) + { + // Remember old hierarchy/inspector height/positions + if(viewportMode != VIEWPORT_COMPACT){ + restoreViewport = true; + oldHierarchyWindowPosition = hierarchyWindow.position; + oldHierarchyWindowHeight = hierarchyWindow.height; + oldInspectorWindowPosition = attributeInspectorWindow.position; + oldInspectorWindowHeight = attributeInspectorWindow.height; + } + + // Move and scale hierarchy window to left of screen + ShowHierarchyWindow(); + hierarchyWindow.position = IntVector2(secondaryToolBar.width,toolBar.height + uiMenuBar.height); + hierarchyWindow.height = viewportArea.height-(toolBar.height + uiMenuBar.height); + + // Move and scale inspector window to left of screen + ShowAttributeInspectorWindow(); + attributeInspectorWindow.position = IntVector2(viewportArea.width-attributeInspectorWindow.width,toolBar.height + uiMenuBar.height); + attributeInspectorWindow.height = viewportArea.height-(toolBar.height + uiMenuBar.height); + + // Hide close button and disable resize/movement inspector/hierarchy of windows + attributeInspectorWindow.GetChild("CloseButton",true).visible = false; + attributeInspectorWindow.resizable = false; + attributeInspectorWindow.movable = false; + hierarchyWindow.GetChild("CloseButton",true).visible = false; + hierarchyWindow.resizable = false; + hierarchyWindow.movable = false; + + // Create viewport on center of window + { + uint viewport = 0; + ViewportContext@ vc = ViewportContext( + IntRect( + secondaryToolBar.width + hierarchyWindow.width, + toolBar.height + uiMenuBar.height, + viewportArea.width-attributeInspectorWindow.width, + viewportArea.height), + viewports.length + 1, + viewportMode & (VIEWPORT_TOP|VIEWPORT_LEFT|VIEWPORT_TOP_LEFT) + ); + viewports.Push(vc); + } + viewportMode = mode; + + } + else + { + if(viewportMode == VIEWPORT_COMPACT) + { + // Restore hierarchy/inspector windows height/positions + if(restoreViewport) + { + hierarchyWindow.position = oldHierarchyWindowPosition; + hierarchyWindow.height = oldHierarchyWindowHeight; + attributeInspectorWindow.position = oldInspectorWindowPosition; + attributeInspectorWindow.height = oldInspectorWindowHeight; + } + + // Show close button and enable resize/movement of inspector/hierarchy windows + attributeInspectorWindow.GetChild("CloseButton",true).visible = true; + attributeInspectorWindow.resizable = true; + attributeInspectorWindow.movable = true; + hierarchyWindow.GetChild("CloseButton",true).visible = true; + hierarchyWindow.resizable = true; + hierarchyWindow.movable = true; + } + + viewportMode = mode; + + // Always have quad a + { + uint viewport = 0; + ViewportContext@ vc = ViewportContext( + IntRect( + 0, + 0, + mode & (VIEWPORT_LEFT|VIEWPORT_TOP_LEFT) > 0 ? viewportArea.width / 2 : viewportArea.width, + mode & (VIEWPORT_TOP|VIEWPORT_TOP_LEFT) > 0 ? viewportArea.height / 2 : viewportArea.height), + viewports.length + 1, + viewportMode & (VIEWPORT_TOP|VIEWPORT_LEFT|VIEWPORT_TOP_LEFT) + ); + viewports.Push(vc); + } + + uint topRight = viewportMode & (VIEWPORT_RIGHT|VIEWPORT_TOP_RIGHT); + if (topRight > 0) + { + ViewportContext@ vc = ViewportContext( + IntRect( + viewportArea.width/2, + 0, + viewportArea.width, + mode & VIEWPORT_TOP_RIGHT > 0 ? viewportArea.height / 2 : viewportArea.height), + viewports.length + 1, + topRight + ); + viewports.Push(vc); + } + + uint bottomLeft = viewportMode & (VIEWPORT_BOTTOM|VIEWPORT_BOTTOM_LEFT); + if (bottomLeft > 0) + { + ViewportContext@ vc = ViewportContext( + IntRect( + 0, + viewportArea.height / 2, + mode & (VIEWPORT_BOTTOM_LEFT) > 0 ? viewportArea.width / 2 : viewportArea.width, + viewportArea.height), + viewports.length + 1, + bottomLeft + ); + viewports.Push(vc); + } + + uint bottomRight = viewportMode & (VIEWPORT_BOTTOM_RIGHT); + if (bottomRight > 0) + { + ViewportContext@ vc = ViewportContext( + IntRect( + viewportArea.width / 2, + viewportArea.height / 2, + viewportArea.width, + viewportArea.height), + viewports.length + 1, + bottomRight + ); + viewports.Push(vc); + } + + } + + renderer.numViewports = viewports.length; + for (uint i = 0; i < viewports.length; ++i) + renderer.viewports[i] = viewports[i].viewport; + + // Restore camera positions as applicable. Default new viewports to the last camera position + if (cameraPositions.length > 0) + { + for (uint i = 0; i < viewports.length; ++i) + { + uint src = i; + if (src >= cameraPositions.length) + src = cameraPositions.length - 1; + + viewports[i].cameraLookAtNode.position = cameralookAtPositions[src]; + viewports[i].cameraLookAtNode.rotation = cameraLookAtRotations[src]; + + viewports[i].cameraNode.position = cameraPositions[src]; + viewports[i].cameraNode.rotation = cameraRotations[src]; + } + } + + ReacquireCameraYawPitch(); + UpdateViewParameters(); + UpdateCameraPreview(); + CreateViewportUI(); +} + +// Create a preview viewport if a camera component is selected +void UpdateCameraPreview() +{ + previewCamera = null; + StringHash cameraType("Camera"); + + for (uint i = 0; i < selectedComponents.length; ++i) + { + if (selectedComponents[i].type == cameraType) + { + // Take the first encountered camera + previewCamera = selectedComponents[i]; + break; + } + } + // Also try nodes if not found from components + if (previewCamera.Get() is null) + { + for (uint i = 0; i < selectedNodes.length; ++i) + { + previewCamera = selectedNodes[i].GetComponent("Camera"); + if (previewCamera.Get() !is null) + break; + } + } + + // Remove extra viewport if it exists and no camera is selected + if (previewCamera.Get() is null) + { + if (renderer.numViewports > viewports.length) + renderer.numViewports = viewports.length; + } + else + { + if (renderer.numViewports < viewports.length + 1) + renderer.numViewports = viewports.length + 1; + + int previewWidth = graphics.width / 4; + int previewHeight = previewWidth * 9 / 16; + int previewX = graphics.width - 10 - previewWidth; + int previewY = graphics.height - 30 - previewHeight; + + Viewport@ previewView = Viewport(); + previewView.scene = editorScene; + previewView.camera = previewCamera.Get(); + previewView.rect = IntRect(previewX, previewY, previewX + previewWidth, previewY + previewHeight); + previewView.renderPath = renderPath; + renderer.viewports[viewports.length] = previewView; + } +} + +void HandleViewportBorderDragMove(StringHash eventType, VariantMap& eventData) +{ + UIElement@ dragBorder = eventData["Element"].GetPtr(); + if (dragBorder is null) + return; + + uint hPos; + uint vPos; + + // Moves border to new cursor position, restricts motion to 1 axis, and keeps borders within view area + if (resizingBorder & VIEWPORT_BORDER_V_ANY > 0) + { + hPos = Clamp(ui.cursorPosition.x, 150, viewportArea.width-150); + vPos = dragBorder.position.y; + dragBorder.position = IntVector2(hPos, vPos); + } + if (resizingBorder & VIEWPORT_BORDER_H_ANY > 0) + { + vPos = Clamp(ui.cursorPosition.y, 150, viewportArea.height-150); + hPos = dragBorder.position.x; + dragBorder.position = IntVector2(hPos, vPos); + } + + // Move all dependent borders + Array borders = viewportUI.GetChildren(); + for (uint i = 0; i < borders.length; ++i) + { + BorderImage@ border = borders[i]; + if (border is null || border is dragBorder || border.name != "border") + continue; + + uint borderViewMode = border.vars["VIEWMODE"].GetUInt(); + if (resizingBorder == VIEWPORT_BORDER_H) + { + if (borderViewMode == VIEWPORT_BORDER_V1) + { + border.SetFixedHeight(vPos); + } + else if (borderViewMode == VIEWPORT_BORDER_V2) + { + border.position = IntVector2(border.position.x, vPos); + border.SetFixedHeight(viewportArea.height - vPos); + } + } + else if (resizingBorder == VIEWPORT_BORDER_V) + { + if (borderViewMode == VIEWPORT_BORDER_H1) + { + border.SetFixedWidth(hPos); + } + else if (borderViewMode == VIEWPORT_BORDER_H2) + { + border.position = IntVector2(hPos, border.position.y); + border.SetFixedWidth(viewportArea.width - hPos); + } + } + } +} + +void HandleViewportBorderDragEnd(StringHash eventType, VariantMap& eventData) +{ + // Sets the new viewports by checking all the dependencies + Array children = viewportUI.GetChildren(); + Array borders; + + BorderImage@ borderV; + BorderImage@ borderV1; + BorderImage@ borderV2; + BorderImage@ borderH; + BorderImage@ borderH1; + BorderImage@ borderH2; + + for (uint i = 0; i < children.length; ++i) + { + if (children[i].name == "border") + { + BorderImage@ border = children[i]; + uint mode = border.vars["VIEWMODE"].GetUInt(); + if (mode == VIEWPORT_BORDER_V) + borderV = border; + else if (mode == VIEWPORT_BORDER_V1) + borderV1 = border; + else if (mode == VIEWPORT_BORDER_V2) + borderV2 = border; + else if (mode == VIEWPORT_BORDER_H) + borderH = border; + else if (mode == VIEWPORT_BORDER_H1) + borderH1 = border; + else if (mode == VIEWPORT_BORDER_H2) + borderH2 = border; + } + } + + IntRect top; + IntRect bottom; + IntRect left; + IntRect right; + IntRect topLeft; + IntRect topRight; + IntRect bottomLeft; + IntRect bottomRight; + + for (uint i = 0; i < viewports.length; ++i) + { + ViewportContext@ vc = viewports[i]; + if (vc.viewportId & VIEWPORT_TOP > 0) + top = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_BOTTOM > 0) + bottom = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_LEFT > 0) + left = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_RIGHT > 0) + right = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_TOP_LEFT > 0) + topLeft = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_TOP_RIGHT > 0) + topRight = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_BOTTOM_LEFT > 0) + bottomLeft = vc.viewport.rect; + else if (vc.viewportId & VIEWPORT_BOTTOM_RIGHT > 0) + bottomRight = vc.viewport.rect; + } + + if (borderV !is null) + { + if (viewportMode & VIEWPORT_LEFT > 0) + left.right = borderV.position.x + viewportBorderOffset; + if (viewportMode & VIEWPORT_TOP_LEFT > 0) + topLeft.right = borderV.position.x + viewportBorderOffset; + if (viewportMode & VIEWPORT_TOP_RIGHT > 0) + topRight.left = borderV.position.x + viewportBorderOffset; + if (viewportMode & VIEWPORT_RIGHT > 0) + right.left = borderV.position.x + viewportBorderOffset; + if (viewportMode & VIEWPORT_BOTTOM_LEFT > 0) + bottomLeft.right = borderV.position.x + viewportBorderOffset; + if (viewportMode & VIEWPORT_BOTTOM_RIGHT > 0) + bottomRight.left = borderV.position.x + viewportBorderOffset; + } + else + { + if (borderV1 !is null) + { + if (viewportMode & VIEWPORT_TOP_LEFT > 0) + topLeft.right = borderV1.position.x + viewportBorderOffset; + if (viewportMode & VIEWPORT_TOP_RIGHT > 0) + topRight.left = borderV1.position.x + viewportBorderOffset; + } + if (borderV2 !is null) + { + if (viewportMode & VIEWPORT_BOTTOM_LEFT > 0) + bottomLeft.right = borderV2.position.x + viewportBorderOffset; + if (viewportMode & VIEWPORT_BOTTOM_RIGHT > 0) + bottomRight.left = borderV2.position.x + viewportBorderOffset; + } + } + + if (borderH !is null) + { + if (viewportMode & VIEWPORT_TOP > 0) + top.bottom = borderH.position.y + viewportBorderOffset; + if (viewportMode & VIEWPORT_TOP_LEFT > 0) + topLeft.bottom = borderH.position.y + viewportBorderOffset; + if (viewportMode & VIEWPORT_BOTTOM_LEFT > 0) + bottomLeft.top = borderH.position.y + viewportBorderOffset; + if (viewportMode & VIEWPORT_BOTTOM > 0) + bottom.top = borderH.position.y + viewportBorderOffset; + if (viewportMode & VIEWPORT_TOP_RIGHT > 0) + topRight.bottom = borderH.position.y + viewportBorderOffset; + if (viewportMode & VIEWPORT_BOTTOM_RIGHT > 0) + bottomRight.top = borderH.position.y + viewportBorderOffset; + } + else + { + if (borderH1 !is null) + { + if (viewportMode & VIEWPORT_TOP_LEFT > 0) + topLeft.bottom = borderH1.position.y+viewportBorderOffset; + if (viewportMode & VIEWPORT_BOTTOM_LEFT > 0) + bottomLeft.top = borderH1.position.y+viewportBorderOffset; + } + if (borderH2 !is null) + { + if (viewportMode & VIEWPORT_TOP_RIGHT > 0) + topRight.bottom = borderH2.position.y+viewportBorderOffset; + if (viewportMode & VIEWPORT_BOTTOM_RIGHT > 0) + bottomRight.top = borderH2.position.y+viewportBorderOffset; + } + } + + // Applies the calculated changes + for (uint i = 0; i < viewports.length; ++i) + { + ViewportContext@ vc = viewports[i]; + if (vc.viewportId & VIEWPORT_TOP > 0) + vc.viewport.rect = top; + else if (vc.viewportId & VIEWPORT_BOTTOM > 0) + vc.viewport.rect = bottom; + else if (vc.viewportId & VIEWPORT_LEFT > 0) + vc.viewport.rect = left; + else if (vc.viewportId & VIEWPORT_RIGHT > 0) + vc.viewport.rect = right; + else if (vc.viewportId & VIEWPORT_TOP_LEFT > 0) + vc.viewport.rect = topLeft; + else if (vc.viewportId & VIEWPORT_TOP_RIGHT > 0) + vc.viewport.rect = topRight; + else if (vc.viewportId & VIEWPORT_BOTTOM_LEFT > 0) + vc.viewport.rect = bottomLeft; + else if (vc.viewportId & VIEWPORT_BOTTOM_RIGHT > 0) + vc.viewport.rect = bottomRight; + vc.HandleResize(); + } + + // End drag state + resizingBorder = 0; + setViewportCursor = 0; +} + +void SetViewportCursor() +{ + if (setViewportCursor & VIEWPORT_BORDER_V_ANY > 0) + ui.cursor.shape = CS_RESIZEHORIZONTAL; + else if (setViewportCursor & VIEWPORT_BORDER_H_ANY > 0) + ui.cursor.shape = CS_RESIZEVERTICAL; +} + +void SetActiveViewport(ViewportContext@ context) +{ + // Sets the global variables to the current context + @cameraLookAtNode = context.cameraLookAtNode; + @cameraNode = context.cameraNode; + @camera = context.camera; + @audio.listener = context.soundListener; + + // Camera is created before gizmo, this gets called again after UI is created + if (gizmo !is null) + gizmo.viewMask = camera.viewMask; + + @activeViewport = context; + + // If a mode is changed while in a drag or hovering over a border these can get out of sync + resizingBorder = 0; + setViewportCursor = 0; +} + +void ResetCamera() +{ + for (uint i = 0; i < viewports.length; ++i) + viewports[i].ResetCamera(); +} + +void ReacquireCameraYawPitch() +{ + for (uint i = 0; i < viewports.length; ++i) + viewports[i].ReacquireCameraYawPitch(); +} + +void UpdateViewParameters() +{ + for (uint i = 0; i < viewports.length; ++i) + { + viewports[i].camera.nearClip = viewNearClip; + viewports[i].camera.farClip = viewFarClip; + viewports[i].camera.fov = viewFov; + } +} + +void CreateGrid() +{ + if (gridNode !is null) + gridNode.Remove(); + + gridNode = Node(); + gridNode.name = "EditorGrid"; + grid = gridNode.CreateComponent("CustomGeometry"); + grid.numGeometries = 1; + grid.material = cache.GetResource("Material", "Materials/VColUnlit.xml"); + grid.viewMask = 0x80000000; // Editor raycasts use viewmask 0x7fffffff + grid.occludee = false; + + UpdateGrid(); +} + +void HideGrid() +{ + if (grid !is null) + grid.enabled = false; +} + +void ShowGrid() +{ + if (grid !is null) + { + grid.enabled = true; + + if (editorScene.octree !is null) + editorScene.octree.AddManualDrawable(grid); + } +} + +void UpdateGrid(bool updateGridGeometry = true) +{ + showGrid ? ShowGrid() : HideGrid(); + gridNode.scale = Vector3(gridScale, gridScale, gridScale); + + if (!updateGridGeometry) + return; + + uint size = uint(Floor(gridSize / 2) * 2); + float halfSizeScaled = size / 2; + float scale = 1.0; + uint subdivisionSize = uint(Pow(2.0f, float(gridSubdivisions))); + + if (subdivisionSize > 0) + { + size *= subdivisionSize; + scale /= subdivisionSize; + } + + uint halfSize = size / 2; + + grid.BeginGeometry(0, LINE_LIST); + float lineOffset = -halfSizeScaled; + for (uint i = 0; i <= size; ++i) + { + bool lineCenter = i == halfSize; + bool lineSubdiv = !Equals(Mod(i, subdivisionSize), 0.0); + + if (!grid2DMode) + { + grid.DefineVertex(Vector3(lineOffset, 0.0, halfSizeScaled)); + grid.DefineColor(lineCenter ? gridZColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + grid.DefineVertex(Vector3(lineOffset, 0.0, -halfSizeScaled)); + grid.DefineColor(lineCenter ? gridZColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + + grid.DefineVertex(Vector3(-halfSizeScaled, 0.0, lineOffset)); + grid.DefineColor(lineCenter ? gridXColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + grid.DefineVertex(Vector3(halfSizeScaled, 0.0, lineOffset)); + grid.DefineColor(lineCenter ? gridXColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + } + else + { + grid.DefineVertex(Vector3(lineOffset, halfSizeScaled, 0.0)); + grid.DefineColor(lineCenter ? gridYColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + grid.DefineVertex(Vector3(lineOffset, -halfSizeScaled, 0.0)); + grid.DefineColor(lineCenter ? gridYColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + + grid.DefineVertex(Vector3(-halfSizeScaled, lineOffset, 0.0)); + grid.DefineColor(lineCenter ? gridXColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + grid.DefineVertex(Vector3(halfSizeScaled, lineOffset, 0.0)); + grid.DefineColor(lineCenter ? gridXColor : (lineSubdiv ? gridSubdivisionColor : gridColor)); + } + + lineOffset += scale; + } + grid.Commit(); +} + +void CreateStatsBar() +{ + editorModeText = Text(); + ui.root.AddChild(editorModeText); + renderStatsText = Text(); + ui.root.AddChild(renderStatsText); + modelInfoText = Text(); + ui.root.AddChild(modelInfoText); +} + +void SetupStatsBarText(Text@ text, Font@ font, int x, int y, HorizontalAlignment hAlign, VerticalAlignment vAlign) +{ + text.position = IntVector2(x, y); + text.horizontalAlignment = hAlign; + text.verticalAlignment = vAlign; + text.SetFont(font, 11); + text.color = Color(1, 1, 0); + text.textEffect = TE_SHADOW; + text.priority = -100; +} + +void UpdateStats(float timeStep) +{ + String adding = ""; + // Todo: add localization + if (hotKeyMode == HOTKEYS_MODE_BLENDER) + adding = localization.Get(" CameraFlyMode: ") + (cameraFlyMode ? "True" : "False"); + + editorModeText.text = String( + localization.Get("Mode: ") + localization.Get(editModeText[editMode]) + + localization.Get(" Axis: ") + localization.Get(axisModeText[axisMode]) + + localization.Get(" Pick: ") + localization.Get(pickModeText[pickMode]) + + localization.Get(" Fill: ") + localization.Get(fillModeText[fillMode]) + + localization.Get(" Updates: ") + (runUpdate ? localization.Get("Running") : localization.Get("Paused") + adding)); + + renderStatsText.text = String( + localization.Get("Tris: ") + renderer.numPrimitives + + localization.Get(" Batches: ") + renderer.numBatches + + localization.Get(" Lights: ") + renderer.numLights[true] + + localization.Get(" Shadowmaps: ") + renderer.numShadowMaps[true] + + localization.Get(" Occluders: ") + renderer.numOccluders[true]); + + editorModeText.size = editorModeText.minSize; + renderStatsText.size = renderStatsText.minSize; + + // Relayout stats bar + Font@ font = cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"); + + if(viewportMode != VIEWPORT_COMPACT) + { + if (ui.root.width >= editorModeText.size.x + renderStatsText.size.x + 45) + { + SetupStatsBarText(editorModeText, font, 35, 64, HA_LEFT, VA_TOP); + SetupStatsBarText(renderStatsText, font, -4, 64, HA_RIGHT, VA_TOP); + SetupStatsBarText(modelInfoText, font, 35, 88, HA_LEFT, VA_TOP); + } + else + { + SetupStatsBarText(editorModeText, font, 35, 64, HA_LEFT, VA_TOP); + SetupStatsBarText(renderStatsText, font, 35, 78, HA_LEFT, VA_TOP); + SetupStatsBarText(modelInfoText, font, 35, 102, HA_LEFT, VA_TOP); + } + } + else + { + SetupStatsBarText(editorModeText, font, secondaryToolBar.width + hierarchyWindow.width + 10 , 64, HA_LEFT, VA_TOP); + SetupStatsBarText(renderStatsText, font, secondaryToolBar.width + hierarchyWindow.width + 10 , 84, HA_LEFT, VA_TOP); + SetupStatsBarText(modelInfoText, font, secondaryToolBar.width + hierarchyWindow.width + 10, 104, HA_LEFT, VA_TOP); + } +} + +void UpdateViewports(float timeStep) +{ + for(uint i = 0; i < viewports.length; ++i) + { + ViewportContext@ viewportContext = viewports[i]; + viewportContext.Update(timeStep); + } +} + +void SetMouseMode(bool enable) +{ + if (enable) + { + if (mouseOrbitMode == ORBIT_RELATIVE) + { + input.mouseMode = MM_RELATIVE; + ui.cursor.visible = false; + } + else if (mouseOrbitMode == ORBIT_WRAP) + input.mouseMode = MM_WRAP; + } + else + { + input.mouseMode = MM_ABSOLUTE; + ui.cursor.visible = true; + } +} + +void SetMouseLock() +{ + toggledMouseLock = true; + SetMouseMode(true); + FadeUI(); +} + +void ReleaseMouseLock() +{ + if (toggledMouseLock) + { + toggledMouseLock = false; + SetMouseMode(false); + } +} + +void CameraPan(Vector3 trans) +{ + cameraSmoothInterpolate.Stop(); + + cameraLookAtNode.Translate(trans); +} + +void CameraMoveForward(Vector3 trans) +{ + cameraSmoothInterpolate.Stop(); + + cameraNode.Translate(trans, TS_PARENT); +} + +void CameraRotateAroundLookAt(Quaternion rot) +{ + cameraSmoothInterpolate.Stop(); + + cameraNode.rotation = rot; + + Vector3 dir = cameraNode.direction; + dir.Normalize(); + + float dist = cameraNode.position.length; + + cameraNode.position = -dir * dist; +} + +void CameraRotateAroundCenter(Quaternion rot) +{ + cameraSmoothInterpolate.Stop(); + + cameraNode.rotation = rot; + + Vector3 oldPos = cameraNode.worldPosition; + + Vector3 dir = cameraNode.worldDirection; + dir.Normalize(); + + float dist = cameraNode.position.length; + + cameraLookAtNode.worldPosition = cameraNode.worldPosition + dir * dist; + cameraNode.worldPosition = oldPos; +} + +void CameraRotateAroundSelect(Quaternion rot) +{ + cameraSmoothInterpolate.Stop(); + + cameraNode.rotation = rot; + + Vector3 dir = cameraNode.direction; + dir.Normalize(); + + float dist = cameraNode.position.length; + + cameraNode.position = -dir * dist; + + Vector3 centerPoint; + if ((selectedNodes.length > 0 || selectedComponents.length > 0)) + centerPoint = SelectedNodesCenterPoint(); + else + centerPoint = lastSelectedNodesCenterPoint; + + // legacy way, camera look-at will jump to the selection + cameraLookAtNode.worldPosition = centerPoint; +} + +void CameraZoom(float zoom) +{ + cameraSmoothInterpolate.Stop(); + + camera.zoom = Clamp(zoom, .1, 30); +} + +void HandleStandardUserInput(float timeStep) +{ + // Speedup camera move if Shift key is down + float speedMultiplier = 1.0; + if (input.keyDown[KEY_LSHIFT]) + speedMultiplier = cameraShiftSpeedMultiplier; + + // Handle FPS mode + if (!input.keyDown[KEY_LCTRL] && !input.keyDown[KEY_LALT]) + { + if (input.keyDown[KEY_W] || input.keyDown[KEY_UP]) + { + Vector3 dir = cameraNode.direction; + dir.Normalize(); + + CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + if (input.keyDown[KEY_S] || input.keyDown[KEY_DOWN]) + { + Vector3 dir = cameraNode.direction; + dir.Normalize(); + + CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + if (input.keyDown[KEY_A] || input.keyDown[KEY_LEFT]) + { + Vector3 dir = cameraNode.right; + dir.Normalize(); + + CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + if (input.keyDown[KEY_D] || input.keyDown[KEY_RIGHT]) + { + Vector3 dir = cameraNode.right; + dir.Normalize(); + + CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + if (input.keyDown[KEY_E] || input.keyDown[KEY_PAGEUP]) + { + Vector3 dir = cameraNode.up; + dir.Normalize(); + + CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + if (input.keyDown[KEY_Q] || input.keyDown[KEY_PAGEDOWN]) + { + Vector3 dir = cameraNode.up; + dir.Normalize(); + + CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + } + + // Zoom in/out + if (input.mouseMoveWheel != 0 && ui.GetElementAt(ui.cursor.position) is null) + { + float distance = cameraNode.position.length; + float ratio = distance / 40.0f; + float factor = ratio < 1.0f ? ratio : 1.0f; + + if (!camera.orthographic) + { + Vector3 dir = cameraNode.direction; + dir.Normalize(); + dir *= input.mouseMoveWheel * 40 * timeStep * cameraBaseSpeed * speedMultiplier * factor; + + CameraMoveForward(dir); + } + else + { + float zoom = camera.zoom + input.mouseMoveWheel * speedMultiplier * factor; + + CameraZoom(zoom); + } + } + + + // Rotate/orbit/pan camera + bool changeCamViewButton = false; + + changeCamViewButton = input.mouseButtonDown[MOUSEB_RIGHT] || input.mouseButtonDown[MOUSEB_MIDDLE]; + + if (changeCamViewButton) + { + SetMouseLock(); + + IntVector2 mouseMove = input.mouseMove; + if (mouseMove.x != 0 || mouseMove.y != 0) + { + bool panTheCamera = false; + + if (input.mouseButtonDown[MOUSEB_MIDDLE]) + { + if (mmbPanMode) + panTheCamera = !input.keyDown[KEY_LSHIFT]; + else + panTheCamera = input.keyDown[KEY_LSHIFT]; + } + + // Pan the camera + if (panTheCamera) + { + Vector3 right = -cameraNode.worldRight; + right.Normalize(); + right *= mouseMove.x; + Vector3 up = cameraNode.worldUp; + up.Normalize(); + up *= mouseMove.y; + + Vector3 trans = (right + up) * timeStep * cameraBaseSpeed * 0.5; + + CameraPan(trans); + } + else // Rotate the camera + { + activeViewport.cameraYaw += mouseMove.x * cameraBaseRotationSpeed; + activeViewport.cameraPitch += mouseMove.y * cameraBaseRotationSpeed; + + if (limitRotation) + activeViewport.cameraPitch = Clamp(activeViewport.cameraPitch, -90.0, 90.0); + + Quaternion rot = Quaternion(activeViewport.cameraPitch, activeViewport.cameraYaw, 0); + + if (input.mouseButtonDown[MOUSEB_MIDDLE]) // Rotate around the camera center + { + if (rotateAroundSelect) + CameraRotateAroundSelect(rot); + else + CameraRotateAroundLookAt(rot); + + orbiting = true; + } + else // Rotate around the look-at + { + CameraRotateAroundCenter(rot); + + orbiting = true; + } + } + } + } + else + ReleaseMouseLock(); + + if (orbiting && !input.mouseButtonDown[MOUSEB_MIDDLE]) + orbiting = false; +} + +void HandleBlenderUserInput(float timeStep) +{ + if (ui.HasModalElement() || ui.focusElement !is null) + { + ReleaseMouseLock(); + return; + } + + // Check for camara fly mode + if (input.keyDown[KEY_LSHIFT] && input.keyPress[KEY_F]) + { + cameraFlyMode = !cameraFlyMode; + } + + // Speedup camera move if Shift key is down + float speedMultiplier = 1.0; + if (input.keyDown[KEY_LSHIFT]) + speedMultiplier = cameraShiftSpeedMultiplier; + + // Handle FPS mode + if (!input.keyDown[KEY_LCTRL] && !input.keyDown[KEY_LALT]) + { + if (cameraFlyMode /*&& !input.keyDown[KEY_LSHIFT]*/) + { + if (input.keyDown[KEY_W] || input.keyDown[KEY_UP]) + { + Vector3 dir = cameraNode.direction; + dir.Normalize(); + + CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + if (input.keyDown[KEY_S] || input.keyDown[KEY_DOWN]) + { + Vector3 dir = cameraNode.direction; + dir.Normalize(); + + CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + if (input.keyDown[KEY_A] || input.keyDown[KEY_LEFT]) + { + Vector3 dir = cameraNode.right; + dir.Normalize(); + + CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + if (input.keyDown[KEY_D] || input.keyDown[KEY_RIGHT]) + { + Vector3 dir = cameraNode.right; + dir.Normalize(); + + CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + if (input.keyDown[KEY_E] || input.keyDown[KEY_PAGEUP]) + { + Vector3 dir = cameraNode.up; + dir.Normalize(); + + CameraPan(dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + if (input.keyDown[KEY_Q] || input.keyDown[KEY_PAGEDOWN]) + { + Vector3 dir = cameraNode.up; + dir.Normalize(); + + CameraPan(-dir * timeStep * cameraBaseSpeed * speedMultiplier); + FadeUI(); + } + } + } + + if (input.mouseMoveWheel != 0 && ui.GetElementAt(ui.cursor.position) is null) + { + if (!camera.orthographic) + { + if (input.keyDown[KEY_LSHIFT]) + { + Vector3 dir = cameraNode.up; + dir.Normalize(); + + CameraPan(dir * input.mouseMoveWheel * 5 * timeStep * cameraBaseSpeed * speedMultiplier); + } + else if (input.keyDown[KEY_LCTRL]) + { + Vector3 dir = cameraNode.right; + dir.Normalize(); + + CameraPan(dir * input.mouseMoveWheel * 5 * timeStep * cameraBaseSpeed * speedMultiplier); + } + else // Zoom in/out + { + float distance = cameraNode.position.length; + float ratio = distance / 40.0f; + float factor = ratio < 1.0f ? ratio : 1.0f; + + Vector3 dir = cameraNode.direction; + dir.Normalize(); + dir *= input.mouseMoveWheel * 40 * timeStep * cameraBaseSpeed * speedMultiplier * factor; + + CameraMoveForward(dir); + } + } + else + { + if (input.keyDown[KEY_LSHIFT]) + { + Vector3 dir = cameraNode.up; + dir.Normalize(); + + CameraPan(dir * input.mouseMoveWheel * timeStep * cameraBaseSpeed * speedMultiplier * 4.0f); + } + else if (input.keyDown[KEY_LCTRL]) + { + Vector3 dir = cameraNode.right; + dir.Normalize(); + + CameraPan(dir * input.mouseMoveWheel * timeStep * cameraBaseSpeed * speedMultiplier * 4.0f); + } + else + { + float zoom = camera.zoom + input.mouseMoveWheel * speedMultiplier * 0.5f; + + CameraZoom(zoom); + } + } + } + + // Rotate/orbit/pan camera + bool changeCamViewButton = input.mouseButtonDown[MOUSEB_MIDDLE] || cameraFlyMode; + + if (input.mouseButtonPress[MOUSEB_RIGHT] || input.keyDown[KEY_ESCAPE]) + cameraFlyMode = false; + + if (changeCamViewButton) + { + SetMouseLock(); + + IntVector2 mouseMove = input.mouseMove; + if (mouseMove.x != 0 || mouseMove.y != 0) + { + bool panTheCamera = false; + + if (!cameraFlyMode) + panTheCamera = input.keyDown[KEY_LSHIFT]; + + if (panTheCamera) + { + Vector3 right = -cameraNode.worldRight; + right.Normalize(); + right *= mouseMove.x; + Vector3 up = cameraNode.worldUp; + up.Normalize(); + up *= mouseMove.y; + + Vector3 trans = (right + up) * timeStep * cameraBaseSpeed * 0.5; + + CameraPan(trans); + } + else + { + activeViewport.cameraYaw += mouseMove.x * cameraBaseRotationSpeed; + activeViewport.cameraPitch += mouseMove.y * cameraBaseRotationSpeed; + + if (limitRotation) + activeViewport.cameraPitch = Clamp(activeViewport.cameraPitch, -90.0, 90.0); + + Quaternion rot = Quaternion(activeViewport.cameraPitch, activeViewport.cameraYaw, 0); + + if (cameraFlyMode) + { + CameraRotateAroundCenter(rot); + orbiting = true; + } + else if (input.mouseButtonDown[MOUSEB_MIDDLE]) + { + if (rotateAroundSelect) + CameraRotateAroundSelect(rot); + else + CameraRotateAroundLookAt(rot); + + orbiting = true; + } + } + } + } + else + ReleaseMouseLock(); + + if (orbiting && !input.mouseButtonDown[MOUSEB_MIDDLE]) + orbiting = false; + + // force to select component node for manipulation if selected only component and not his node + if ((editMode != EDIT_SELECT && editNodes.empty) && lastSelectedComponent.Get() !is null) + { + if (lastSelectedComponent.Get() !is null) + { + Component@ component = lastSelectedComponent.Get(); + SelectNode(component.node, false); + } + } +} + +void UpdateView(float timeStep) +{ + if (ui.HasModalElement() || ui.focusElement !is null) + { + ReleaseMouseLock(); + return; + } + + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + { + HandleStandardUserInput(timeStep); + } + else if (hotKeyMode == HOTKEYS_MODE_BLENDER) + { + HandleBlenderUserInput(timeStep); + } + + if (!editNodes.empty && editMode != EDIT_SELECT && input.keyDown[KEY_LCTRL]) + { + Vector3 adjust(0, 0, 0); + if (input.keyDown[KEY_UP]) + adjust.z = 1; + if (input.keyDown[KEY_DOWN]) + adjust.z = -1; + if (input.keyDown[KEY_LEFT]) + adjust.x = -1; + if (input.keyDown[KEY_RIGHT]) + adjust.x = 1; + if (input.keyDown[KEY_PAGEUP]) + adjust.y = 1; + if (input.keyDown[KEY_PAGEDOWN]) + adjust.y = -1; + if (editMode == EDIT_SCALE) + { + if (input.keyDown[KEY_KP_PLUS]) + adjust = Vector3(1, 1, 1); + if (input.keyDown[KEY_KP_MINUS]) + adjust = Vector3(-1, -1, -1); + } + + if (adjust == Vector3(0, 0, 0)) + return; + + bool moved = false; + adjust *= timeStep * 10; + + switch (editMode) + { + case EDIT_MOVE: + if (!moveSnap) + moved = MoveNodes(adjust * moveStep); + break; + + case EDIT_ROTATE: + if (!rotateSnap) + moved = RotateNodes(adjust * rotateStep); + break; + + case EDIT_SCALE: + if (!scaleSnap) + moved = ScaleNodes(adjust * scaleStep); + break; + } + + if (moved) + UpdateNodeAttributes(); + } + + // If not dragging + if (resizingBorder == 0) + { + UIElement@ uiElement = ui.GetElementAt(ui.cursorPosition); + if (uiElement !is null && uiElement.vars.Contains("VIEWMODE")) + { + setViewportCursor = uiElement.vars["VIEWMODE"].GetUInt(); + if (input.mouseButtonDown[MOUSEB_LEFT]) + resizingBorder = setViewportCursor; + } + } +} + +void SteppedObjectManipulation(int key) +{ + if (editNodes.empty || editMode == EDIT_SELECT) + return; + + // Do not react in non-snapped mode, because that is handled in frame update + if (editMode == EDIT_MOVE && !moveSnap) + return; + if (editMode == EDIT_ROTATE && !rotateSnap) + return; + if (editMode == EDIT_SCALE && !scaleSnap) + return; + + Vector3 adjust(0, 0, 0); + if (key == KEY_UP) + adjust.z = 1; + if (key == KEY_DOWN) + adjust.z = -1; + if (key == KEY_LEFT) + adjust.x = -1; + if (key == KEY_RIGHT) + adjust.x = 1; + if (key == KEY_PAGEUP) + adjust.y = 1; + if (key == KEY_PAGEDOWN) + adjust.y = -1; + if (editMode == EDIT_SCALE) + { + if (key == KEY_KP_PLUS) + adjust = Vector3(1, 1, 1); + if (key == KEY_KP_MINUS) + adjust = Vector3(-1, -1, -1); + } + + if (adjust == Vector3(0, 0, 0)) + return; + + bool moved = false; + + switch (editMode) + { + case EDIT_MOVE: + moved = MoveNodes(adjust); + break; + + case EDIT_ROTATE: + { + float rotateStepScaled = rotateStep * snapScale; + moved = RotateNodes(adjust * rotateStepScaled); + } + break; + + case EDIT_SCALE: + { + float scaleStepScaled = scaleStep * snapScale; + moved = ScaleNodes(adjust * scaleStepScaled); + } + break; + } + + if (moved) + UpdateNodeAttributes(); +} + +void HandlePostRenderUpdate() +{ + DebugRenderer@ debug = editorScene.debugRenderer; + if (debug is null || orbiting || debugRenderDisabled) + return; + + // Visualize the currently selected nodes + for (uint i = 0; i < selectedNodes.length; ++i) + DrawNodeDebug(selectedNodes[i], debug); + + // Visualize the currently selected components + for (uint i = 0; i < selectedComponents.length; ++i) + selectedComponents[i].DrawDebugGeometry(debug, false); + + // Visualize the currently selected UI-elements + for (uint i = 0; i < selectedUIElements.length; ++i) + ui.DebugDraw(selectedUIElements[i]); + + if (renderingDebug) + renderer.DrawDebugGeometry(false); + + if (physicsDebug && editorScene.physicsWorld !is null) + editorScene.physicsWorld.DrawDebugGeometry(true); + + if (physicsDebug && editorScene.physicsWorld2D !is null) + { + bool needDraw = true; + for (uint i = 0; i < selectedComponents.length; ++i) + { + if (cast(selectedComponents[i]) !is null) + { + needDraw = false; // Already drawed + break; + } + } + + if (needDraw) + physicsWorld2D.DrawDebugGeometry(); + } + + if (octreeDebug && editorScene.octree !is null) + editorScene.octree.DrawDebugGeometry(true); + + if (navigationDebug) + { + CrowdManager@ crowdManager = editorScene.GetComponent("CrowdManager"); + if (crowdManager !is null) + crowdManager.DrawDebugGeometry(true); + + Array@ navMeshes = editorScene.GetComponents("NavigationMesh", true); + for (uint i = 0; i < navMeshes.length; ++i) + cast(navMeshes[i]).DrawDebugGeometry(true); + + Array@ dynNavMeshes = editorScene.GetComponents("DynamicNavigationMesh", true); + for (uint i = 0; i < dynNavMeshes.length; ++i) + cast(dynNavMeshes[i]).DrawDebugGeometry(true); + } + + if (setViewportCursor | resizingBorder > 0) + { + SetViewportCursor(); + if (resizingBorder == 0) + setViewportCursor = 0; + } + + ViewRaycast(false); +} + +void DrawNodeDebug(Node@ node, DebugRenderer@ debug, bool drawNode = true) +{ + if (drawNode) + debug.AddNode(node, 1.0, false); + + // Exception for the scene to avoid bringing the editor to its knees: drawing either the whole hierarchy or the subsystem- + // components can have a large performance hit. Also do not draw terrain child nodes due to their large amount + // (TerrainPatch component itself draws nothing as debug geometry) + if (node !is editorScene && node.GetComponent("Terrain") is null) + { + for (uint j = 0; j < node.numComponents; ++j) + node.components[j].DrawDebugGeometry(debug, false); + + // To avoid cluttering the view, do not draw the node axes for child nodes + for (uint k = 0; k < node.numChildren; ++k) + DrawNodeDebug(node.children[k], debug, false); + } +} + +void ViewMouseMove() +{ + Ray cameraRay = GetActiveViewportCameraRay(); + Component@ selectedComponent; + + if (pickMode < PICK_RIGIDBODIES && editorScene.octree !is null) + { + RayQueryResult result = editorScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, camera.farClip, + pickModeDrawableFlags[pickMode], 0x7fffffff); + + if (result.drawable !is null && result.drawable.typeName == "TerrainPatch" && result.drawable.node.parent !is null) + { + Terrain@ terrainComponent = result.drawable.node.parent.GetComponent("Terrain"); + terrainEditor.UpdateBrushVisualizer(terrainComponent, result.position); + } + else { + terrainEditor.HideBrushVisualizer(); + } + } + + // setting mouse position based on mouse position + if (ui.IsDragging()) { } + else if (ui.focusElement !is null || input.mouseButtonDown[MOUSEB_LEFT|MOUSEB_MIDDLE|MOUSEB_RIGHT]) + return; + + IntVector2 pos = ui.cursor.position; + for (uint i = 0; i < viewports.length; ++i) + { + ViewportContext@ vc = viewports[i]; + if (vc !is activeViewport && vc.viewport.rect.IsInside(pos) == INSIDE) + SetActiveViewport(vc); + } +} + +void ViewMouseClick() +{ + ViewRaycast(true); +} + +Ray GetActiveViewportCameraRay() +{ + IntRect view = activeViewport.viewport.rect; + return camera.GetScreenRay( + float(ui.cursorPosition.x - view.left) / view.width, + float(ui.cursorPosition.y - view.top) / view.height + ); +} + +void ViewMouseClickEnd() +{ + // checks to close open popup windows + IntVector2 pos = ui.cursorPosition; + if (contextMenu !is null && contextMenu.enabled) + { + if (contextMenuActionWaitFrame) + contextMenuActionWaitFrame = false; + else + { + if (!contextMenu.IsInside(pos, true)) + CloseContextMenu(); + } + } + if (quickMenu !is null && quickMenu.enabled) + { + bool enabled = quickMenu.IsInside(pos, true); + quickMenu.enabled = enabled; + quickMenu.visible = enabled; + } +} + +void ViewRaycast(bool mouseClick) +{ + // Ignore if UI has modal element + if (ui.HasModalElement()) + return; + + // Ignore if mouse is grabbed by other operation + if (input.mouseGrabbed) + return; + + IntVector2 pos = ui.cursorPosition; + UIElement@ elementAtPos = ui.GetElementAt(pos, pickMode != PICK_UI_ELEMENTS); + if (editMode == EDIT_SPAWN) + { + if(mouseClick && input.mouseButtonPress[MOUSEB_LEFT] && elementAtPos is null) + SpawnObject(); + return; + } + + // Do not raycast / change selection if hovering over the gizmo + if (IsGizmoSelected()) + return; + + DebugRenderer@ debug = editorScene.debugRenderer; + + if (pickMode == PICK_UI_ELEMENTS) + { + bool leftClick = mouseClick && input.mouseButtonPress[MOUSEB_LEFT]; + bool multiselect = input.qualifierDown[QUAL_CTRL]; + + // Only interested in user-created UI elements + if (elementAtPos !is null && elementAtPos !is editorUIElement && elementAtPos.GetElementEventSender() is editorUIElement) + { + ui.DebugDraw(elementAtPos); + + if (leftClick) + SelectUIElement(elementAtPos, multiselect); + } + // If clicked on emptiness in non-multiselect mode, clear the selection + else if (leftClick && !multiselect && ui.GetElementAt(pos) is null) + hierarchyList.ClearSelection(); + + return; + } + + // Do not raycast / change selection if hovering over a UI element when not in PICK_UI_ELEMENTS Mode + if (elementAtPos !is null) + return; + + Ray cameraRay = GetActiveViewportCameraRay(); + Component@ selectedComponent; + + if (pickMode < PICK_RIGIDBODIES) + { + if (editorScene.octree is null) + return; + + RayQueryResult result = editorScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, camera.farClip, + pickModeDrawableFlags[pickMode], 0x7fffffff); + + if (result.drawable !is null) + { + Drawable@ drawable = result.drawable; + + // for actual last selected node or component in both modes + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + { + if (input.mouseButtonDown[MOUSEB_LEFT]) + { + lastSelectedNode = drawable.node; + lastSelectedDrawable = drawable; + lastSelectedComponent = drawable; + } + } + else if (hotKeyMode == HOTKEYS_MODE_BLENDER) + { + if (input.mouseButtonDown[MOUSEB_RIGHT]) + { + lastSelectedNode = drawable.node; + lastSelectedDrawable = drawable; + lastSelectedComponent = drawable; + } + } + + // If selecting a terrain patch, select the parent terrain instead + if (drawable.typeName != "TerrainPatch") + { + selectedComponent = drawable; + if (debug !is null) + { + debug.AddNode(drawable.node, 1.0, false); + drawable.DrawDebugGeometry(debug, false); + } + } + else if (drawable.node.parent !is null){ + Terrain@ terrainComponent = drawable.node.parent.GetComponent("Terrain"); + selectedComponent = terrainComponent; + if (selectedComponent is terrainComponent && input.mouseButtonDown[MOUSEB_LEFT]) + { + selectedComponent = terrainComponent; + terrainEditor.Work(terrainComponent, result.position); + } + else + { + terrainEditor.targetColorSelected = false; + } + } + } + } + else + { + if (editorScene.physicsWorld is null) + return; + + // If we are not running the actual physics update, refresh collisions before raycasting + if (!runUpdate) + editorScene.physicsWorld.UpdateCollisions(); + + PhysicsRaycastResult result = editorScene.physicsWorld.RaycastSingle(cameraRay, camera.farClip); + if (result.body !is null) + { + RigidBody@ body = result.body; + if (debug !is null) + { + debug.AddNode(body.node, 1.0, false); + body.DrawDebugGeometry(debug, false); + } + selectedComponent = body; + } + } + + bool multiselect = false; + bool componentSelectQualifier = false; + bool mouseButtonPressRL = false; + + if (hotKeyMode == HOTKEYS_MODE_STANDARD) + { + mouseButtonPressRL = input.mouseButtonPress[MOUSEB_LEFT]; + componentSelectQualifier = input.qualifierDown[QUAL_SHIFT]; + multiselect = input.qualifierDown[QUAL_CTRL]; + } + else if (hotKeyMode == HOTKEYS_MODE_BLENDER) + { + mouseButtonPressRL = input.mouseButtonPress[MOUSEB_RIGHT]; + componentSelectQualifier = input.qualifierDown[QUAL_CTRL]; + multiselect = input.qualifierDown[QUAL_SHIFT]; + } + + if (mouseClick && mouseButtonPressRL) + { + if (selectedComponent !is null) + { + if (componentSelectQualifier) + { + // If we are selecting components, but have nodes in existing selection, do not multiselect to prevent confusion + if (!selectedNodes.empty) + multiselect = false; + SelectComponent(selectedComponent, multiselect); + } + else + { + // If we are selecting nodes, but have components in existing selection, do not multiselect to prevent confusion + if (!selectedComponents.empty) + multiselect = false; + SelectNode(selectedComponent.node, multiselect); + } + } + else + { + // If clicked on emptiness in non-multiselect mode, clear the selection + if (!multiselect) + SelectComponent(null, false); + } + } +} + +Vector3 GetNewNodePosition(bool raycastToMouse = false) +{ + if (newNodeMode == NEW_NODE_IN_CENTER) + return Vector3(0, 0, 0); + if (newNodeMode == NEW_NODE_RAYCAST) + { + Ray cameraRay = raycastToMouse ? GetActiveViewportCameraRay() : camera.GetScreenRay(0.5, 0.5); + Vector3 position, normal; + if (GetSpawnPosition(cameraRay, camera.farClip, position, normal, 0, false)) + return position; + } + return cameraLookAtNode.worldPosition; +} + +int GetShadowResolution() +{ + if (!renderer.drawShadows) + return 0; + int level = 1; + int res = renderer.shadowMapSize; + while (res > 512) + { + res >>= 1; + ++level; + } + if (level > 3) + level = 3; + + return level; +} + +void SetShadowResolution(int level) +{ + if (level <= 0) + { + renderer.drawShadows = false; + return; + } + else + { + renderer.drawShadows = true; + renderer.shadowMapSize = 256 << level; + } +} + +void ToggleRenderingDebug() +{ + renderingDebug = !renderingDebug; +} + +void TogglePhysicsDebug() +{ + physicsDebug = !physicsDebug; +} + +void ToggleOctreeDebug() +{ + octreeDebug = !octreeDebug; +} + +void ToggleNavigationDebug() +{ + navigationDebug = !navigationDebug; +} + +bool StopTestAnimation() +{ + testAnimState = null; + return true; +} + +void MergeNodeBoundingBox(BoundingBox &inout box, Array&inout visitedComponents, Node@ node) +{ + if (node is null || node is editorScene) + return; + + // if node has no component, merge its world position + if (node.numComponents == 0) + { + box.Merge(node.worldPosition); + } + + // Merge components bounding box of this node + for (uint i = 0; i < node.numComponents; ++i) + { + MergeComponentBoundingBox(box, visitedComponents, node.components[i]); + } + + // Merge bounding boxes of child nodes recursively + for (uint i = 0; i < node.numChildren; ++i) + { + Node@ child = node.children[i]; + MergeNodeBoundingBox(box, visitedComponents, child); + } +} + +void MergeComponentBoundingBox(BoundingBox &inout box, Array&inout visitedComponents, Component@ component) +{ + if (component is null || visitedComponents.FindByRef(component) != -1) + return; + + Drawable@ drawable = cast(component); + + // Merge drawable component's bounding box. Skip skybox, as its box is very large, as well as lights + if (drawable !is null && cast(drawable) is null && cast(drawable) is null) + { + box.Merge(drawable.worldBoundingBox); + visitedComponents.Push(component); + return; + } + + // If the component is not a drawable, merge the world position of its node + if (component.node !is editorScene) + box.Merge(component.node.worldPosition); + + visitedComponents.Push(component); +} + +void LocateNodes(Array nodes) +{ + if (nodes.empty || (nodes.length == 1 && nodes[0] is editorScene)) + return; + + // Calculate bounding box of all nodes + BoundingBox box; + Array visitedComponents; + + for (uint i = 0; i < nodes.length; ++i) + { + MergeNodeBoundingBox(box, visitedComponents, nodes[i]); + } + + FitCamera(box, true); +} + +void LocateComponents(Array components) +{ + if (components.empty || components.length == 1 && components[0].node is editorScene) + return; + + // Calculate bounding box of all nodes + BoundingBox box; + Array visitedComponents; + + for (uint i = 0; i < components.length; ++i) + { + MergeComponentBoundingBox(box, visitedComponents, components[i]); + } + + FitCamera(box, true); +} + +void LocateNodesAndComponents(Array nodes, Array components) +{ + if (nodes.length == 0 && components.length == 0) + return; + + // Calculate bounding box of all nodes + BoundingBox box; + Array visitedComponents; + + if (!nodes.empty && !(nodes.length == 1 && nodes[0] is editorScene)) + { + for (uint i = 0; i < nodes.length; ++i) + { + MergeNodeBoundingBox(box, visitedComponents, nodes[i]); + } + } + + if (!components.empty) + { + for (uint i = 0; i < components.length; ++i) + { + MergeComponentBoundingBox(box, visitedComponents, components[i]); + } + } + + FitCamera(box, true); +} + +void FitCamera(BoundingBox box, bool smooth) +{ + // Calculate proper camera distance - fit the bounding sphere into the camera frustum + Sphere sphere = Sphere(box); + + float aspect = camera.aspectRatio; + float fov = 0.0f; + + // Choose the small one from vertical and horizontal fovs + if (aspect > 1.0f) + fov = camera.fov; + else + fov = camera.fov * aspect; + + fov *= 0.5f; + + if (sphere.radius < 1.0f) + sphere.radius = 1.0f; + + float distance = sphere.radius / Sin(fov); + + if (distance > viewFarClip) + distance = viewFarClip; + + Vector3 dir = cameraNode.direction; + dir.Normalize(); + + // Make the distance a little farther + distance *= 1.1f; + + // Set zoom value a little bigger + float zoom = camera.orthoSize / (sphere.radius * 2.0f); + zoom *= 1.1f; + + // We put the pivot node to the center of the bounding sphere + // and put the camera node to the opposite of view direction + Vector3 lookAtPos = sphere.center; + Vector3 cameraPos = -dir * distance; + + cameraSmoothInterpolate.Stop(); + + if (smooth) + { + cameraSmoothInterpolate.SetLookAtNodePosition(cameraLookAtNode.worldPosition, lookAtPos); + cameraSmoothInterpolate.SetCameraNodePosition(cameraNode.position, cameraPos); + + if (camera.orthographic) + cameraSmoothInterpolate.SetCameraZoom(camera.zoom, zoom); + + cameraSmoothInterpolate.Start(0.5f); + } + else + { + cameraLookAtNode.worldPosition = lookAtPos; + cameraNode.position = cameraPos; + + if (camera.orthographic) + camera.zoom = zoom; + } +} + +Vector3 SelectedNodesCenterPoint() +{ + Vector3 centerPoint; + uint count = selectedNodes.length; + for (uint i = 0; i < selectedNodes.length; ++i) + centerPoint += selectedNodes[i].worldPosition; + + for (uint i = 0; i < selectedComponents.length; ++i) + { + Drawable@ drawable = cast(selectedComponents[i]); + count++; + if (drawable !is null) + centerPoint += drawable.node.LocalToWorld(drawable.boundingBox.center); + else + centerPoint += selectedComponents[i].node.worldPosition; + } + + if (count > 0) + { + lastSelectedNodesCenterPoint = centerPoint / count; + return centerPoint / count; + } + else + { + lastSelectedNodesCenterPoint = centerPoint; + return centerPoint; + } +} + +Drawable@ GetDrawableAtMousePostion() +{ + IntVector2 pos = ui.cursorPosition; + Ray cameraRay = camera.GetScreenRay(float(pos.x) / activeViewport.viewport.rect.width, float(pos.y) / activeViewport.viewport.rect.height); + + if (editorScene.octree is null) + return null; + + RayQueryResult result = editorScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, camera.farClip, DRAWABLE_GEOMETRY, 0x7fffffff); + + return result.drawable; +} + +void HandleBeginViewUpdate(StringHash eventType, VariantMap& eventData) +{ + // Hide gizmo, grid and debug icons from any camera other then active viewport + if (eventData["Camera"].GetPtr() !is camera) + { + if (gizmo !is null) + gizmo.viewMask = 0; + } + if (eventData["Camera"].GetPtr() is previewCamera.Get()) + { + suppressSceneChanges = true; + if (grid !is null) + grid.viewMask = 0; + if (debugIconsNode !is null) + debugIconsNode.enabled = false; + suppressSceneChanges = false; + } +} + +void HandleEndViewUpdate(StringHash eventType, VariantMap& eventData) +{ + // Restore gizmo and grid after camera view update + if (eventData["Camera"].GetPtr() !is camera) + { + if (gizmo !is null) + gizmo.viewMask = 0x80000000; + } + if (eventData["Camera"].GetPtr() is previewCamera.Get()) + { + suppressSceneChanges = true; + if (grid !is null) + grid.viewMask = 0x80000000; + if (debugIconsNode !is null) + debugIconsNode.enabled = true; + suppressSceneChanges = false; + } +} + +bool debugWasEnabled = true; + +void HandleBeginViewRender(StringHash eventType, VariantMap& eventData) +{ + // Hide debug geometry from preview camera + if (eventData["Camera"].GetPtr() is previewCamera.Get()) + { + DebugRenderer@ debug = editorScene.GetComponent("DebugRenderer"); + if (debug !is null) + { + suppressSceneChanges = true; // Do not want UI update now + debugWasEnabled = debug.enabled; + debug.enabled = false; + suppressSceneChanges = false; + } + } +} + +void HandleEndViewRender(StringHash eventType, VariantMap& eventData) +{ + // Restore debug geometry after preview camera render + if (eventData["Camera"].GetPtr() is previewCamera.Get()) + { + DebugRenderer@ debug = editorScene.GetComponent("DebugRenderer"); + if (debug !is null) + { + suppressSceneChanges = true; // Do not want UI update now + debug.enabled = debugWasEnabled; + suppressSceneChanges = false; + } + } +} diff --git a/bin/Data/Scripts/Editor/EditorViewDebugIcons.as b/bin/Data/Scripts/Editor/EditorViewDebugIcons.as new file mode 100644 index 0000000..0d6ba19 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorViewDebugIcons.as @@ -0,0 +1,275 @@ +// Editor debug icons +// to add new std debug icons just add IconType, IconsTypesMaterials and ComponentTypes items + +enum IconsTypes +{ + ICON_POINT_LIGHT = 0, + ICON_SPOT_LIGHT, + ICON_DIRECTIONAL_LIGHT, + ICON_CAMERA, + ICON_SOUND_SOURCE, + ICON_SOUND_SOURCE_3D, + ICON_SOUND_LISTENERS, + ICON_ZONE, + ICON_SPLINE_PATH, + ICON_TRIGGER, + ICON_CUSTOM_GEOMETRY, + ICON_PARTICLE_EMITTER, + ICON_COUNT +} + +enum IconsColorType +{ + ICON_COLOR_DEFAULT = 0, + ICON_COLOR_SPLINE_PATH_BEGIN, + ICON_COLOR_SPLINE_PATH_END +} + +Array debugIconsColors = { Color(1,1,1) , Color(1,1,0), Color(0,1,0) }; + +Array iconsTypesMaterials = {"DebugIconPointLight.xml", + "DebugIconSpotLight.xml", + "DebugIconLight.xml", + "DebugIconCamera.xml", + "DebugIconSoundSource.xml", + "DebugIconSoundSource.xml", + "DebugIconSoundListener.xml", + "DebugIconZone.xml", + "DebugIconSplinePathPoint.xml", + "DebugIconCollisionTrigger.xml", + "DebugIconCustomGeometry.xml", + "DebugIconParticleEmitter.xml"}; + +Array componentTypes = {"Light", + "Light", + "Light", + "Camera", + "SoundSource", + "SoundSource3D", + "SoundListener", + "Zone", + "SplinePath", + "RigidBody", + "CustomGeometry", + "ParticleEmitter"}; + +Array debugIconsSet(ICON_COUNT); +Node@ debugIconsNode = null; +int stepDebugIconsUpdate = 100; //ms +uint timeToNextDebugIconsUpdate = 0; +int stepDebugIconsUpdateSplinePath = 1000; //ms +uint timeToNextDebugIconsUpdateSplinePath = 0; +const int splinePathResolution = 16; +const float splineStep = 1.0f / splinePathResolution; +bool debugIconsShow = true; +Vector2 debugIconsSize = Vector2(64, 64); +Vector2 debugIconsSizeSmall = debugIconsSize / 1.5; +VariantMap debugIconsPlacement; +const int debugIconsPlacementIndent = 1.0; +const float debugIconsOrthoDistance = 15.0; +const float debugIconAlphaThreshold = 0.1; +const float maxDistance = 50.0; + +void CreateDebugIcons(Node@ tempNode) +{ + if (editorScene is null) return; + debugIconsSet.Resize(ICON_COUNT); + for (int i = 0; i < ICON_COUNT; ++i) + { + debugIconsSet[i] = tempNode.CreateComponent("BillboardSet"); + debugIconsSet[i].material = cache.GetResource("Material", "Materials/Editor/" + iconsTypesMaterials[i]); + debugIconsSet[i].sorted = true; + debugIconsSet[i].temporary = true; + debugIconsSet[i].fixedScreenSize = true; + debugIconsSet[i].viewMask = 0x80000000; + } +} + +void UpdateViewDebugIcons() +{ + if (editorScene is null || timeToNextDebugIconsUpdate > time.systemTime) return; + + debugIconsNode = editorScene.GetChild("DebugIconsContainer", true); + + if (debugIconsNode is null) + { + debugIconsNode = editorScene.CreateChild("DebugIconsContainer", LOCAL); + debugIconsNode.temporary = true; + } + // Checkout if debugIconsNode do not have any BBS component, add all at once + BillboardSet@ isBSExist = debugIconsNode.GetComponent("BillboardSet"); + if (isBSExist is null) + CreateDebugIcons(debugIconsNode); + + if (debugIconsSet[ICON_POINT_LIGHT] !is null) + { + for (int i = 0; i < ICON_COUNT; ++i) + debugIconsSet[i].enabled = debugIconsShow; + } + + if (debugIconsShow == false) return; + + Vector3 camPos = activeViewport.cameraNode.worldPosition; + bool isOrthographic = activeViewport.camera.orthographic; + debugIconsPlacement.Clear(); + + for (int iconType = 0; iconType < ICON_COUNT; ++iconType) + { + if (debugIconsSet[iconType] !is null) + { + // SplinePath update resolution + if (iconType == ICON_SPLINE_PATH && timeToNextDebugIconsUpdateSplinePath > time.systemTime) + continue; + + Array nodes = editorScene.GetChildrenWithComponent(componentTypes[iconType], true); + + // Clear old data + if (iconType == ICON_SPLINE_PATH) + ClearCommit(ICON_SPLINE_PATH, ICON_SPLINE_PATH + 1, nodes.length * splinePathResolution); + else if (iconType == ICON_POINT_LIGHT || iconType == ICON_SPOT_LIGHT || iconType == ICON_DIRECTIONAL_LIGHT) + ClearCommit(ICON_POINT_LIGHT, ICON_DIRECTIONAL_LIGHT + 1, nodes.length); + else + ClearCommit(iconType, iconType + 1, nodes.length); + + if (nodes.length > 0) + { + // Fill with new data + for (uint i = 0; i < nodes.length; ++i) + { + Component@ component = nodes[i].GetComponent(componentTypes[iconType]); + if (component is null) continue; + + Color finalIconColor = debugIconsColors[ICON_COLOR_DEFAULT]; + float distance = (camPos - nodes[i].worldPosition).length; + if (isOrthographic) + distance = debugIconsOrthoDistance; + int iconsOffset = debugIconsPlacement[StringHash(nodes[i].id)].GetInt(); + float iconsYPos = 0; + + if (iconType == ICON_SPLINE_PATH) + { + SplinePath@ sp = cast(component); + if (sp !is null) + { + if (sp.length > 0.01f) + { + for (int step = 0; step < splinePathResolution; step++) + { + int index = (i * splinePathResolution) + step; + Vector3 splinePoint = sp.GetPoint(splineStep * step); + Billboard@ bb = debugIconsSet[ICON_SPLINE_PATH].billboards[index]; + float stepDistance = (camPos - splinePoint).length; + if (isOrthographic) + stepDistance = debugIconsOrthoDistance; + + if (step == 0) // SplinePath start + { + bb.color = debugIconsColors[ICON_COLOR_SPLINE_PATH_BEGIN]; + bb.size = debugIconsSize; + bb.position = splinePoint; + } + else if ((step+1) >= (splinePathResolution - splineStep)) // SplinePath end + { + bb.color = debugIconsColors[ICON_COLOR_SPLINE_PATH_END]; + bb.size = debugIconsSize; + bb.position = splinePoint; + } + else // SplinePath middle points + { + bb.color = finalIconColor; + bb.size = debugIconsSizeSmall; + bb.position = splinePoint; + } + bb.enabled = sp.enabled; + // Blend Icon relatively by distance to it + bb.color = Color(bb.color.r, bb.color.g, bb.color.b, 1.2f - 1.0f / (maxDistance / stepDistance)); + if (bb.color.a < debugIconAlphaThreshold) + bb.enabled = false; + } + } + } + } + else + { + Billboard@ bb = debugIconsSet[iconType].billboards[i]; + + if (iconType == ICON_TRIGGER) + { + RigidBody@ rigidbody = cast(component); + if (rigidbody !is null) + { + if (rigidbody.trigger == false) + continue; + } + } + else if (iconType == ICON_POINT_LIGHT || iconType == ICON_SPOT_LIGHT || iconType == ICON_DIRECTIONAL_LIGHT) + { + Light@ light = cast(component); + if (light !is null) + { + if (light.lightType == LIGHT_POINT) + bb = debugIconsSet[ICON_POINT_LIGHT].billboards[i]; + else if (light.lightType == LIGHT_DIRECTIONAL) + bb = debugIconsSet[ICON_DIRECTIONAL_LIGHT].billboards[i]; + else if (light.lightType == LIGHT_SPOT) + bb = debugIconsSet[ICON_SPOT_LIGHT].billboards[i]; + + finalIconColor = light.effectiveColor; + } + } + + bb.position = nodes[i].worldPosition; + bb.size = debugIconsSize; + + // Blend Icon relatively by distance to it + bb.color = Color(finalIconColor.r, finalIconColor.g, finalIconColor.b, 1.2f - 1.0f / (maxDistance / distance)); + bb.enabled = component.enabled; + // Discard billboard if it almost transparent + if (bb.color.a < debugIconAlphaThreshold) + bb.enabled = false; + IncrementIconPlacement(bb.enabled, nodes[i], 1); + } + } + Commit(iconType, iconType+1); + // SplinePath update resolution + if (iconType == ICON_SPLINE_PATH) + timeToNextDebugIconsUpdateSplinePath = time.systemTime + stepDebugIconsUpdateSplinePath; + } + } + } + timeToNextDebugIconsUpdate = time.systemTime + stepDebugIconsUpdate; +} + +void ClearCommit(int begin, int end, int newLength) +{ + for (int i = begin; i < end; ++i) + { + BillboardSet@ iconSet = debugIconsSet[i]; + iconSet.numBillboards = newLength; + + for (int j = 0; j < newLength; ++j) + { + Billboard@ bb = iconSet.billboards[j]; + bb.enabled = false; + } + + iconSet.Commit(); + } +} + +void Commit(int begin, int end) +{ + for (int i = begin; i < end; ++i) + { + debugIconsSet[i].Commit(); + } +} + +void IncrementIconPlacement(bool componentEnabled, Node@ node, int offset) +{ + if (componentEnabled == true) + { + int oldPlacement = debugIconsPlacement[StringHash(node.id)].GetInt(); + debugIconsPlacement[StringHash(node.id)] = Variant(oldPlacement + offset); + } +} \ No newline at end of file diff --git a/bin/Data/Scripts/Editor/EditorViewPaintSelection.as b/bin/Data/Scripts/Editor/EditorViewPaintSelection.as new file mode 100644 index 0000000..1d7ce55 --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorViewPaintSelection.as @@ -0,0 +1,194 @@ +const int PAINT_STEP_UPDATE = 16; +const int PAINT_SELECTION_KEY = KEY_C; + +bool EditorPaintSelectionShow = false; +int EditorPaintSelectionUITimeToUpdate = 0; + +UIElement@ EditorPaintSelectionUIContainer = null; +BorderImage@ paintSelectionImage = null; + +IntVector2 paintSelectionBrushDefaultSize(96,96); +IntVector2 paintSelectionBrushCurrentSize = paintSelectionBrushDefaultSize; +IntVector2 paintSelectionBrushMinSize(64,64); +IntVector2 paintSelectionBrushMaxSize(512,512); +IntVector2 paintSelectionBrushStepSizeChange(16,16); + +void CreatePaintSelectionContainer() +{ + if (editorScene is null) return; + EditorPaintSelectionUIContainer = UIElement(); + EditorPaintSelectionUIContainer.position = IntVector2(0,0); + EditorPaintSelectionUIContainer.size = IntVector2(graphics.width,graphics.height); + EditorPaintSelectionUIContainer.priority = -5; + EditorPaintSelectionUIContainer.focusMode = FM_NOTFOCUSABLE; + EditorPaintSelectionUIContainer.bringToBack = true; + EditorPaintSelectionUIContainer.name ="DebugPaintSelectionContainer"; + EditorPaintSelectionUIContainer.temporary = true; + ui.root.AddChild(EditorPaintSelectionUIContainer); +} + +void CreatePaintSelectionTool() +{ + paintSelectionImage = BorderImage("Icon"); + paintSelectionImage.temporary = true; + paintSelectionImage.SetFixedSize(paintSelectionBrushDefaultSize.x,paintSelectionBrushDefaultSize.y); + paintSelectionImage.texture = cache.GetResource("Texture2D", "Textures/Editor/SelectionCircle.png"); + paintSelectionImage.imageRect = IntRect(0,0,512,512); + paintSelectionImage.priority = -5; + paintSelectionImage.color = Color(1,1,1); + paintSelectionImage.bringToBack = true; + paintSelectionImage.enabled = false; + paintSelectionImage.selected = false; + paintSelectionImage.visible = true; + EditorPaintSelectionUIContainer.AddChild(paintSelectionImage); +} + +void UpdatePaintSelection() +{ + PaintSelectionCheckKeyboard(); + + // Early out if disabled + if (!EditorPaintSelectionShow) return; + + if (editorScene is null || EditorPaintSelectionUITimeToUpdate > time.systemTime) return; + + EditorPaintSelectionUIContainer = ui.root.GetChild("DebugPaintSelectionContainer"); + + if (EditorPaintSelectionUIContainer is null) + { + CreatePaintSelectionContainer(); + CreatePaintSelectionTool(); + } + + if (EditorPaintSelectionUIContainer !is null) + { + // Set visibility for all origins + EditorPaintSelectionUIContainer.visible = EditorPaintSelectionShow; + + if (viewportMode!=VIEWPORT_SINGLE) + EditorPaintSelectionUIContainer.visible = false; + + if (EditorPaintSelectionShow) + { + IntVector2 mp = input.mousePosition; + paintSelectionImage.position = IntVector2(mp.x - (paintSelectionBrushCurrentSize.x * 0.5f), mp.y - (paintSelectionBrushCurrentSize.y * 0.5f)); + } + } + + EditorPaintSelectionUITimeToUpdate = time.systemTime + PAINT_STEP_UPDATE; +} + +void PaintSelectionCheckKeyboard() +{ + bool key = input.keyPress[PAINT_SELECTION_KEY]; + + if (key && ui.focusElement is null) + { + EditorPaintSelectionShow = !EditorPaintSelectionShow; + if (EditorPaintSelectionUIContainer !is null) + EditorPaintSelectionUIContainer.visible = EditorPaintSelectionShow; + + if (EditorPaintSelectionShow) + { + // When we start paint selection we change editmode to select + editMode = EDIT_SELECT; + //selectedNodes.Clear(); + // and also we show origins for proper origins update + ShowOrigins(true); + toolBarDirty = true; + } + } + else if (EditorPaintSelectionShow && ui.focusElement is null) + { + if (editMode != EDIT_SELECT) + { + EditorPaintSelectionShow = false; + EditorPaintSelectionUIContainer.visible = false; + } + } + + if (input.mouseButtonDown[MOUSEB_RIGHT]) + { + EditorPaintSelectionShow = false; + if (EditorPaintSelectionUIContainer !is null) + EditorPaintSelectionUIContainer.visible = false; + } +} + +void SelectOriginsByPaintSelection(IntVector2 curPos, float brushRadius, bool isOperationAddToSelection = true) +{ + if (!EditorPaintSelectionShow || EditorPaintSelectionUIContainer is null) return; + + for (int i=0; i < originsNodes.length; i++) + { + Vector3 v1(originsIcons[i].position.x, originsIcons[i].position.y, 0); + Vector3 v2(curPos.x - ORIGINOFFSETICON.x, curPos.y - ORIGINOFFSETICON.y, 0); + + float distance = (v1 - v2).length; + bool isThisOriginInCircle = distance < brushRadius ? true : false; + int nodeID = originsIcons[i].vars[ORIGIN_NODEID_VAR].GetInt(); + + if (isThisOriginInCircle) + { + WeakHandle handle = editorScene.GetNode(nodeID); + if (handle.Get() !is null) + { + Node@ node = handle.Get(); + if (isOperationAddToSelection) + { + if (node !is null && isThisNodeOneOfSelected(node) == false) + SelectNode(node, true); + } + else // Deselect origins operation + { + if (node !is null && isThisNodeOneOfSelected(node) == true) + DeselectNode(node); + } + } + } + } +} + +void HandlePaintSelectionMouseMove(StringHash eventType, VariantMap& eventData) +{ + if (!EditorPaintSelectionShow || EditorPaintSelectionUIContainer is null) return; + + int x = eventData["X"].GetInt(); + int y = eventData["Y"].GetInt(); + float r = (paintSelectionBrushCurrentSize.x * 0.5); + + IntVector2 mousePos(x,y); + + // Select by mouse + if (input.mouseButtonDown[MOUSEB_LEFT] && input.qualifierDown[QUAL_CTRL] != true) + { + SelectOriginsByPaintSelection(mousePos, r, true); + } + // Deselect by mouse + else if (input.mouseButtonDown[MOUSEB_LEFT] && input.qualifierDown[QUAL_CTRL] == true) + { + SelectOriginsByPaintSelection(mousePos, r, false); + } +} + +void HandlePaintSelectionWheel(StringHash eventType, VariantMap& eventData) +{ + if (!EditorPaintSelectionShow || EditorPaintSelectionUIContainer is null) return; + + int wheelValue = eventData["Wheel"].GetInt(); + + if (wheelValue != 0) + { + if (wheelValue > 0) + { + paintSelectionBrushCurrentSize = paintSelectionBrushCurrentSize - paintSelectionBrushStepSizeChange; + paintSelectionBrushCurrentSize = IntVector2(Max(paintSelectionBrushCurrentSize.x, paintSelectionBrushMinSize.x), Max(paintSelectionBrushCurrentSize.y, paintSelectionBrushMinSize.y)); + } + else if (wheelValue < 0) + { + paintSelectionBrushCurrentSize = paintSelectionBrushCurrentSize + paintSelectionBrushStepSizeChange; + paintSelectionBrushCurrentSize = IntVector2(Min(paintSelectionBrushCurrentSize.x, paintSelectionBrushMaxSize.x), Min(paintSelectionBrushCurrentSize.y, paintSelectionBrushMaxSize.y)); + } + paintSelectionImage.SetFixedSize(paintSelectionBrushCurrentSize.x, paintSelectionBrushCurrentSize.y); + } +} diff --git a/bin/Data/Scripts/Editor/EditorViewSelectableOrigins.as b/bin/Data/Scripts/Editor/EditorViewSelectableOrigins.as new file mode 100644 index 0000000..7b87eca --- /dev/null +++ b/bin/Data/Scripts/Editor/EditorViewSelectableOrigins.as @@ -0,0 +1,435 @@ +const bool DEFAULT_SHOW_NAMES_FOR_ALL = false; +const int ORIGIN_STEP_UPDATE = 10; +const int NAMES_SIZE = 11; +const StringHash ORIGIN_NODEID_VAR("OriginNodeID"); +const Color ORIGIN_COLOR(1.0f,1.0f,1.0f,1.0f); +const Color ORIGIN_COLOR_SELECTED(0.0f,1.0f,1.0f,1.0f); +const Color ORIGIN_COLOR_DISABLED(1.0f,0.0f,0.0f,1.0f); +const Color ORIGIN_COLOR_TEXT(1.0f,1.0f,1.0f,0.3f); +const Color ORIGIN_COLOR_SELECTED_TEXT(1.0f,1.0f,1.0f,1.0f); +const IntVector2 ORIGIN_ICON_SIZE(14,14); +const IntVector2 ORIGIN_ICON_SIZE_SELECTED(18,18); +const float ORIGINS_VISIBLITY_RANGE = 32.0f; +const IntVector2 ORIGINOFFSETICON(8,8); +const IntVector2 ORIGINOFFSETICONSELECTED(10,8); + +bool showNamesForAll = DEFAULT_SHOW_NAMES_FOR_ALL; +bool EditorOriginShow = false; +bool rebuildSceneOrigins = true; +bool isOriginsHovered = false; + +int EditorOriginUITimeToUpdate = 0; +int EditorOriginUITimeToSceneNodeRead = 0; +int prevSelectedID; +int selectedNodeInfoState = 0; +int originHoveredIndex = -1; + +UIElement@ EditorOriginUIContainer = null; +Text@ selectedNodeName = null; +BorderImage@ selectedNodeOrigin = null; + +Array selectedNodeOriginChilds; +Array selectedNodeNameChilds; +Array originsNodes; +Array originsIcons; +Array originsNames; + +void CreateOriginsContainer() +{ + if (editorScene is null) return; + EditorOriginUIContainer = UIElement(); + EditorOriginUIContainer.position = IntVector2(0,0); + EditorOriginUIContainer.size = IntVector2(graphics.width,graphics.height); + EditorOriginUIContainer.priority = -1000; + EditorOriginUIContainer.focusMode = FM_NOTFOCUSABLE; + EditorOriginUIContainer.bringToBack = true; + EditorOriginUIContainer.name ="DebugOriginsContainer"; + EditorOriginUIContainer.temporary = true; + ui.root.AddChild(EditorOriginUIContainer); +} + +void HandleOriginToggled(StringHash eventType, VariantMap& eventData) +{ + UIElement@ origin = eventData["Element"].GetPtr(); + if (origin is null) return; + + if (EditorPaintSelectionShow) return; + + if (IsSceneOrigin(origin)) + { + int nodeID = origin.vars[ORIGIN_NODEID_VAR].GetInt(); + if (editorScene !is null) + { + bool goBackAndSelectNodeParent = input.qualifierDown[QUAL_CTRL]; + bool multiSelect = input.qualifierDown[QUAL_SHIFT]; + + WeakHandle handle = editorScene.GetNode(nodeID); + if (handle.Get() !is null) { + Node@ selectedNodeByOrigin = handle.Get(); + if (selectedNodeByOrigin !is null) + { + if (goBackAndSelectNodeParent) + SelectNode(selectedNodeByOrigin.parent, false); + else + SelectNode(selectedNodeByOrigin, multiSelect); + } + } + } + } +} + +void ShowOrigins(bool isVisible = true) +{ + EditorOriginShow = isVisible; + + if (EditorOriginUIContainer is null) + CreateOriginsContainer(); + + EditorOriginUIContainer.visible = isVisible; +} + +void UpdateOrigins() +{ + // Early out if Origins are disabled + if (!EditorOriginShow) return; + + CheckKeyboardQualifers(); + + if (editorScene is null || EditorOriginUITimeToUpdate > time.systemTime) return; + + EditorOriginUIContainer = ui.root.GetChild("DebugOriginsContainer"); + + // Since editor not clear UIs then do loading new scenes, this creation called once per Editor's starting event + // for other scenes we use the same container + if (EditorOriginUIContainer is null) + { + CreateOriginsContainer(); + } + + if (EditorOriginUIContainer !is null) + { + // Set visibility for all origins + EditorOriginUIContainer.visible = EditorOriginShow; + + if (viewportMode!=VIEWPORT_SINGLE) + EditorOriginUIContainer.visible = false; + + // Forced read nodes for some reason: + if ((originsNodes.length < 1) || rebuildSceneOrigins) + { + originsNodes = editorScene.GetChildren(true); + // If we are hot have free origins icons in arrays, resize x 2 + if (originsIcons.length < originsNodes.length) + { + EditorOriginUIContainer.RemoveAllChildren(); + originsIcons.Clear(); + originsNames.Clear(); + + originsIcons.Resize(originsNodes.length * 2); + originsNames.Resize(originsNodes.length * 2); + + if (originsIcons.length > 0) + { + for (int i=0; i < originsIcons.length; i++) + { + CreateOrigin(i, false); + } + } + } + // If this rebuild pass after new scene loading or add/delete node - reset flag to default + if (rebuildSceneOrigins) + rebuildSceneOrigins = false; + } + + if (originsNodes.length > 0) + { + // Get selected node for feeding proper arrray's UIElements with slyte colorig and additional info on ALT + Node@ selectedNode = null; + if (selectedNodes.length > 0) + { + selectedNode = selectedNodes[0]; + } + else if (selectedComponents.length > 0) + { + selectedNode = selectedComponents[0].node; + } + + // Update existed origins (every 10 ms) + if (originsNodes.length > 0 ) + { + for (int i=0; i < originsNodes.length; i++) + { + Vector3 eyeDir = originsNodes[i].worldPosition - cameraNode.worldPosition; + float distance = (eyeDir).length; + eyeDir.Normalize(); + Vector3 cameraDir = (cameraNode.worldRotation * Vector3(0.0f, 0.0f, 1.0f)).Normalized(); + float angleCameraDirVsDirToNode = eyeDir.DotProduct(cameraDir); + + // if node in range and in camera view (clip back sibe) + if (distance < ORIGINS_VISIBLITY_RANGE && angleCameraDirVsDirToNode > 0.7f) + { + // turn on origin and move + MoveOrigin(i, true); + + if (isThisNodeOneOfSelected(originsNodes[i])) + { + ShowSelectedNodeOrigin(originsNodes[i], i); + originsNames[i].visible = true; + } + else + { + if (showNamesForAll || (isOriginsHovered && originHoveredIndex == i)) + originsNames[i].text = NodeInfo(originsNodes[i], selectedNodeInfoState); + } + } + else + { + // turn-off origin + VisibilityOrigin(i, false); + } + } + + // Hide non used origins + for (int j=originsNodes.length; j < originsIcons.length; j++) + { + VisibilityOrigin(j, false); + } + } + } + } + + EditorOriginUITimeToUpdate = time.systemTime + ORIGIN_STEP_UPDATE; +} + +bool isThisNodeOneOfSelected(Node@ node) +{ + if (selectedNodes.length < 1) return false; + + for (int i = 0; i < selectedNodes.length; i++) + { + if (node is selectedNodes[i]) + return true; + } + + return false; +} + +void ShowSelectedNodeOrigin(Node@ node, int index) +{ + if (node !is null) + { + // just keep node's text and node's origin icon position in actual view + Viewport@ vp = activeViewport.viewport; + Vector2 sp = activeViewport.camera.WorldToScreenPoint(node.worldPosition); + //originsIcons[index].position = IntVector2(10+int(vp.rect.left + sp.x * vp.rect.right), -5 + int(vp.rect.top + sp.y* vp.rect.bottom)); + originsIcons[index].position = IntVector2(int(vp.rect.left + sp.x * vp.rect.right) - ORIGINOFFSETICONSELECTED.x, int(vp.rect.top + sp.y* vp.rect.bottom) - ORIGINOFFSETICONSELECTED.y); + originsNames[index].color = ORIGIN_COLOR_SELECTED_TEXT; + + if (originsNodes[index].enabled) + originsIcons[index].color = ORIGIN_COLOR_SELECTED; + else + originsIcons[index].color = ORIGIN_COLOR_DISABLED; + + originsIcons[index].SetFixedSize(ORIGIN_ICON_SIZE_SELECTED.x,ORIGIN_ICON_SIZE_SELECTED.y); + + // if selected node chaged, reset some vars + if (prevSelectedID != node.id) + { + prevSelectedID = node.id; + selectedNodeInfoState = 0; + originsIcons[index].vars[ORIGIN_NODEID_VAR] = node.id; + } + + // We always update to keep and feed alt-info with actual info about node components + Array components = node.GetComponents(); + Array componentsShortInfo; + Array componentsDetailInfo; + componentsShortInfo.Resize(components.length); + componentsDetailInfo.Resize(components.length); + // Add std info node name + tags + originsNames[index].text = NodeInfo(node, selectedNodeInfoState) + "\n"; + } +} + +void CreateOrigin(int index, bool isVisible = false) +{ + if (originsIcons.length < index) return; + + originsIcons[index] = BorderImage("Icon"); + originsIcons[index].temporary = true; + originsIcons[index].SetFixedSize(ORIGIN_ICON_SIZE.x,ORIGIN_ICON_SIZE.y); + originsIcons[index].texture = cache.GetResource("Texture2D", "Textures/Editor/EditorIcons.png"); + originsIcons[index].imageRect = IntRect(0,0,14,14); + originsIcons[index].priority = -1000; + originsIcons[index].color = ORIGIN_COLOR; + originsIcons[index].bringToBack = true; + originsIcons[index].enabled = true; + originsIcons[index].selected = true; + originsIcons[index].visible = isVisible; + EditorOriginUIContainer.AddChild(originsIcons[index]); + + originsNames[index] = Text(); + originsNames[index].visible = false; + originsNames[index].SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), NAMES_SIZE); + originsNames[index].color = ORIGIN_COLOR_TEXT; + //originsNames[index].textEffect = TE_STROKE; + originsNames[index].temporary = true; + originsNames[index].bringToBack = true; + originsNames[index].priority = -1000; + originsNames[index].enabled = false; + + EditorOriginUIContainer.AddChild(originsNames[index]); +} + +void MoveOrigin(int index, bool isVisible = false) +{ + if (originsIcons.length < index) return; + if (originsIcons[index] is null) return; + if (originsNodes[index].temporary) + { + originsIcons[index].visible = false; + originsNames[index].visible = false; + return; + } + + Viewport@ vp = activeViewport.viewport; + Vector2 sp = activeViewport.camera.WorldToScreenPoint(originsNodes[index].worldPosition); + + originsIcons[index].SetFixedSize(ORIGIN_ICON_SIZE.x,ORIGIN_ICON_SIZE.y); + + if (originsNodes[index].enabled) + originsIcons[index].color = ORIGIN_COLOR; + else + originsIcons[index].color = ORIGIN_COLOR_DISABLED; + + originsIcons[index].position = IntVector2(int(vp.rect.left + sp.x * vp.rect.right) - ORIGINOFFSETICON.x, int(vp.rect.top + sp.y* vp.rect.bottom) - ORIGINOFFSETICON.y); + originsIcons[index].visible = isVisible; + originsIcons[index].vars[ORIGIN_NODEID_VAR] = originsNodes[index].id; + + originsNames[index].position = IntVector2(10+int(vp.rect.left + sp.x * vp.rect.right), -5 + int(vp.rect.top + sp.y* vp.rect.bottom)); + + if (isOriginsHovered && originHoveredIndex == index) + { + originsNames[index].visible = true; + originsNames[index].color = ORIGIN_COLOR_SELECTED_TEXT; + } + else + { + originsNames[index].visible = showNamesForAll ? isVisible : false; + originsNames[index].color = ORIGIN_COLOR_TEXT; + } +} + +void VisibilityOrigin(int index, bool isVisible = false) +{ + originsIcons[index].visible = isVisible; + originsNames[index].visible = isVisible; +} + +bool IsSceneOrigin(UIElement@ element) +{ + if (originsIcons.length < 1) return false; + + for (int i=0; i < originsIcons.length; i++) + { + if (element is originsIcons[i]) + { + originHoveredIndex = i; + return true; + } + } + + originHoveredIndex = -1; + return false; +} + +void CheckKeyboardQualifers() +{ + // if pressed alt we inc state for info + bool showAltInfo = input.keyPress[KEY_ALT]; + if (showAltInfo) + if (selectedNodeInfoState < 3) selectedNodeInfoState += 1; + + // if pressed ctrl we reset info state + bool hideAltInfo = input.qualifierDown[QUAL_CTRL]; + if (hideAltInfo) + selectedNodeInfoState = 0; + + bool showNameForOther = false; + + // In-B.mode Key_Space are busy by quick menu, so we use other key for B.mode + if (hotKeyMode == HOTKEYS_MODE_BLENDER) + showNameForOther = (input.keyPress[KEY_TAB] && ui.focusElement is null); + else + showNameForOther = (input.keyPress[KEY_SPACE] && ui.focusElement is null); + + if (showNameForOther) + showNamesForAll =!showNamesForAll; + +} + +String NodeInfo(Node& node, int st) +{ + String result = ""; + if (node !is editorScene) + { + if (node.name.empty) + result = "Node"; + else + result = node.name; + + // Add node's tags if wey are exist + if (st > 0 && node.tags.length > 0) + { + result = result + "\n["; + for (int i=0;i players; +Array hiscores; + +void Start() +{ + if (engine.headless) + OpenConsoleWindow(); + + ParseNetworkArguments(); + if (runServer || runClient) + singlePlayer = false; + + InitAudio(); + InitConsole(); + InitScene(); + InitNetworking(); + CreateCamera(); + CreateOverlays(); + + SubscribeToEvent(gameScene, "SceneUpdate", "HandleUpdate"); + if (gameScene.physicsWorld !is null) + SubscribeToEvent(gameScene.physicsWorld, "PhysicsPreStep", "HandleFixedUpdate"); + SubscribeToEvent(gameScene, "ScenePostUpdate", "HandlePostUpdate"); + SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate"); + SubscribeToEvent("KeyDown", "HandleKeyDown"); + SubscribeToEvent("Points", "HandlePoints"); + SubscribeToEvent("Kill", "HandleKill"); + SubscribeToEvent("ScreenMode", "HandleScreenMode"); + + if (singlePlayer) + { + StartGame(null); + engine.pauseMinimized = true; + } +} + +void InitAudio() +{ + if (engine.headless) + return; + + // Lower mastervolumes slightly. + audio.masterGain[SOUND_MASTER] = 0.75; + audio.masterGain[SOUND_MUSIC] = 0.9; + + if (!nobgm) + { + Sound@ musicFile = cache.GetResource("Sound", "Music/Ninja Gods.ogg"); + musicFile.looped = true; + + // Note: the non-positional sound source component need to be attached to a node to become effective + // Due to networked mode clearing the scene on connect, do not attach to the scene itself + musicNode = Node(); + musicSource = musicNode.CreateComponent("SoundSource"); + musicSource.soundType = SOUND_MUSIC; + musicSource.Play(musicFile); + } +} + +void InitConsole() +{ + if (engine.headless) + return; + + XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml"); + ui.root.defaultStyle = uiStyle; + + Console@ console = engine.CreateConsole(); + console.defaultStyle = uiStyle; + console.background.opacity = 0.8; + + engine.CreateDebugHud(); + debugHud.defaultStyle = uiStyle; +} + +void InitScene() +{ + gameScene = Scene("NinjaSnowWar"); + + // Enable access to this script file & scene from the console + script.defaultScene = gameScene; + script.defaultScriptFile = scriptFile; + + // For the multiplayer client, do not load the scene, let it load from the server + if (runClient) + return; + + gameScene.LoadXML(cache.GetFile("Scenes/NinjaSnowWar.xml")); + + // On mobile devices render the shadowmap first for better performance, adjust the cascaded shadows + String platform = GetPlatform(); + if (platform == "Android" || platform == "iOS" || platform == "Raspberry Pi") + { + renderer.reuseShadowMaps = false; + // Adjust the directional light shadow range slightly further, as only the first + // cascade is supported + Node@ dirLightNode = gameScene.GetChild("GlobalLight", true); + if (dirLightNode !is null) + { + Light@ dirLight = dirLightNode.GetComponent("Light"); + dirLight.shadowCascade = CascadeParameters(15.0f, 0.0f, 0.0f, 0.0f, 0.9f); + } + } + + // Precache shaders if possible + if (!engine.headless && cache.Exists("NinjaSnowWarShaders.xml")) + graphics.PrecacheShaders(cache.GetFile("NinjaSnowWarShaders.xml")); +} + +void InitNetworking() +{ + network.updateFps = 25; // 1/4 of physics FPS + // Remote events sent between client & server must be explicitly registered or else they are not allowed to be received + network.RegisterRemoteEvent("PlayerSpawned"); + network.RegisterRemoteEvent("UpdateScore"); + network.RegisterRemoteEvent("UpdateHiscores"); + network.RegisterRemoteEvent("ParticleEffect"); + + if (runServer) + { + network.StartServer(serverPort); + + // Disable physics interpolation to ensure clients get sent physically correct transforms + gameScene.physicsWorld.interpolation = false; + + SubscribeToEvent("ClientIdentity", "HandleClientIdentity"); + SubscribeToEvent("ClientSceneLoaded", "HandleClientSceneLoaded"); + SubscribeToEvent("ClientDisconnected", "HandleClientDisconnected"); + } + if (runClient) + { + VariantMap identity; + identity["UserName"] = userName; + network.updateFps = 50; // Increase controls send rate for better responsiveness + network.Connect(serverAddress, serverPort, gameScene, identity); + + SubscribeToEvent("PlayerSpawned", "HandlePlayerSpawned"); + SubscribeToEvent("UpdateScore", "HandleUpdateScore"); + SubscribeToEvent("UpdateHiscores", "HandleUpdateHiscores"); + SubscribeToEvent("NetworkUpdateSent", "HandleNetworkUpdateSent"); + } +} + +void InitTouchInput() +{ + touchEnabled = true; + screenJoystickID = input.AddScreenJoystick(cache.GetResource("XMLFile", "UI/ScreenJoystick_NinjaSnowWar.xml")); +} + +void CreateCamera() +{ + // Note: the camera is not in the scene + gameCameraNode = Node(); + gameCameraNode.position = Vector3(0, 2, -10); + + gameCamera = gameCameraNode.CreateComponent("Camera"); + gameCamera.nearClip = 0.5; + gameCamera.farClip = 160; + + if (!engine.headless) + { + renderer.viewports[0] = Viewport(gameScene, gameCamera); + audio.listener = gameCameraNode.CreateComponent("SoundListener"); + } +} + +void CreateOverlays() +{ + if (engine.headless || runServer) + return; + + int height = graphics.height / 22; + if (height > 64) + height = 64; + + sight = BorderImage(); + sight.texture = cache.GetResource("Texture2D", "Textures/NinjaSnowWar/Sight.png"); + sight.SetAlignment(HA_CENTER, VA_CENTER); + sight.SetSize(height, height); + ui.root.AddChild(sight); + + Font@ font = cache.GetResource("Font", "Fonts/BlueHighway.ttf"); + + scoreText = Text(); + scoreText.SetFont(font, 13); + scoreText.SetAlignment(HA_LEFT, VA_TOP); + scoreText.SetPosition(5, 5); + scoreText.colors[C_BOTTOMLEFT] = Color(1, 1, 0.25); + scoreText.colors[C_BOTTOMRIGHT] = Color(1, 1, 0.25); + ui.root.AddChild(scoreText); + + @hiscoreText = Text(); + hiscoreText.SetFont(font, 13); + hiscoreText.SetAlignment(HA_RIGHT, VA_TOP); + hiscoreText.SetPosition(-5, 5); + hiscoreText.colors[C_BOTTOMLEFT] = Color(1, 1, 0.25); + hiscoreText.colors[C_BOTTOMRIGHT] = Color(1, 1, 0.25); + ui.root.AddChild(hiscoreText); + + @messageText = Text(); + messageText.SetFont(font, 13); + messageText.SetAlignment(HA_CENTER, VA_CENTER); + messageText.SetPosition(0, -height * 2); + messageText.color = Color(1, 0, 0); + ui.root.AddChild(messageText); + + BorderImage@ healthBorder = BorderImage(); + healthBorder.texture = cache.GetResource("Texture2D", "Textures/NinjaSnowWar/HealthBarBorder.png"); + healthBorder.SetAlignment(HA_CENTER, VA_TOP); + healthBorder.SetPosition(0, 8); + healthBorder.SetSize(120, 20); + ui.root.AddChild(healthBorder); + + healthBar = BorderImage(); + healthBar.texture = cache.GetResource("Texture2D", "Textures/NinjaSnowWar/HealthBarInside.png"); + healthBar.SetPosition(2, 2); + healthBar.SetSize(116, 16); + healthBorder.AddChild(healthBar); + + if (GetPlatform() == "Android" || GetPlatform() == "iOS") + // On mobile platform, enable touch by adding a screen joystick + InitTouchInput(); + else if (input.numJoysticks == 0) + // On desktop platform, do not detect touch when we already got a joystick + SubscribeToEvent("TouchBegin", "HandleTouchBegin"); +} + +void SetMessage(const String&in message) +{ + if (messageText !is null) + messageText.text = message; +} + +void StartGame(Connection@ connection) +{ + // Clear the scene of all existing scripted objects + { + Array scriptedNodes = gameScene.GetChildrenWithScript(true); + for (uint i = 0; i < scriptedNodes.length; ++i) + scriptedNodes[i].Remove(); + } + + players.Clear(); + SpawnPlayer(connection); + + ResetAI(); + + gameOn = true; + maxEnemies = initialMaxEnemies; + incrementCounter = 0; + enemySpawnTimer = 0; + powerupSpawnTimer = 0; + + if (singlePlayer) + { + playerControls.yaw = 0; + playerControls.pitch = 0; + SetMessage(""); + } +} + +void SpawnPlayer(Connection@ connection) +{ + Vector3 spawnPosition; + if (singlePlayer) + spawnPosition = Vector3(0, 0.97, 0); + else + spawnPosition = Vector3(Random(spawnAreaSize) - spawnAreaSize * 0.5, 0.97, Random(spawnAreaSize) - spawnAreaSize); + + Node@ playerNode = SpawnObject(spawnPosition, Quaternion(), "Ninja"); + // Set owner connection. Owned nodes are always updated to the owner at full frequency + playerNode.owner = connection; + playerNode.name = "Player"; + + // Initialize variables + Ninja@ playerNinja = cast(playerNode.scriptObject); + playerNinja.health = playerNinja.maxHealth = playerHealth; + playerNinja.side = SIDE_PLAYER; + // Make sure the player can not shoot on first frame by holding the button down + if (connection is null) + playerNinja.controls = playerNinja.prevControls = playerControls; + else + playerNinja.controls = playerNinja.prevControls = connection.controls; + + // Check if player entry already exists + int playerIndex = -1; + for (uint i = 0; i < players.length; ++i) + { + if (players[i].connection is connection) + { + playerIndex = i; + break; + } + } + + // Does not exist, create new + if (playerIndex < 0) + { + playerIndex = players.length; + players.Resize(players.length + 1); + players[playerIndex].connection = connection; + + if (connection !is null) + { + players[playerIndex].name = connection.identity["UserName"].GetString(); + // In multiplayer, send current hiscores to the new player + SendHiscores(playerIndex); + } + else + { + players[playerIndex].name = "Player"; + // In singleplayer, create also the default hiscore entry immediately + HiscoreEntry newHiscore; + newHiscore.name = players[playerIndex].name; + newHiscore.score = 0; + hiscores.Push(newHiscore); + } + } + + players[playerIndex].nodeID = playerNode.id; + players[playerIndex].score = 0; + + if (connection !is null) + { + // In multiplayer, send initial score, then send a remote event that tells the spawned node's ID + // It is important for the event to be in-order so that the node has been replicated first + SendScore(playerIndex); + VariantMap eventData; + eventData["NodeID"] = playerNode.id; + connection.SendRemoteEvent("PlayerSpawned", true, eventData); + + // Create name tag (Text3D component) for players in multiplayer + Node@ textNode = playerNode.CreateChild("NameTag"); + textNode.position = Vector3(0, 1.2, 0); + Text3D@ text3D = textNode.CreateComponent("Text3D"); + Font@ font = cache.GetResource("Font", "Fonts/BlueHighway.ttf"); + text3D.SetFont(font, 19); + text3D.color = Color(1, 1, 0); + text3D.text = players[playerIndex].name; + text3D.horizontalAlignment = HA_CENTER; + text3D.verticalAlignment = VA_CENTER; + text3D.faceCameraMode = FC_ROTATE_XYZ; + } +} + +void HandleUpdate(StringHash eventType, VariantMap& eventData) +{ + float timeStep = eventData["TimeStep"].GetFloat(); + + UpdateControls(); + CheckEndAndRestart(); + + if (engine.headless) + { + String command = GetConsoleInput(); + if (command.length > 0) + script.Execute(command); + } + else + { + if (debugHud.mode != DEBUGHUD_SHOW_NONE) + { + Node@ playerNode = FindOwnNode(); + if (playerNode !is null) + { + debugHud.SetAppStats("Player Pos", playerNode.worldPosition.ToString()); + debugHud.SetAppStats("Player Yaw", Variant(playerNode.worldRotation.yaw)); + } + else + debugHud.ClearAppStats(); + } + } +} + +void HandleFixedUpdate(StringHash eventType, VariantMap& eventData) +{ + float timeStep = eventData["TimeStep"].GetFloat(); + + // Spawn new objects, singleplayer or server only + if (singlePlayer || runServer) + SpawnObjects(timeStep); +} + +void HandlePostUpdate() +{ + UpdateCamera(); + UpdateStatus(); +} + +void HandlePostRenderUpdate() +{ + if (engine.headless) + return; + + if (drawDebug) + gameScene.physicsWorld.DrawDebugGeometry(true); + if (drawOctreeDebug) + gameScene.octree.DrawDebugGeometry(true); +} + +void HandleTouchBegin(StringHash eventType, VariantMap& eventData) +{ + // On some platforms like Windows the presence of touch input can only be detected dynamically + InitTouchInput(); + UnsubscribeFromEvent("TouchBegin"); +} + +void HandleKeyDown(StringHash eventType, VariantMap& eventData) +{ + int key = eventData["Key"].GetInt(); + + if (key == KEY_ESCAPE) + { + if (!console.visible) + engine.Exit(); + else + console.visible = false; + } + + if (key == KEY_F1) + console.Toggle(); + + if (key == KEY_F2) + debugHud.ToggleAll(); + + if (key == KEY_F3) + drawDebug = !drawDebug; + + if (key == KEY_F4) + drawOctreeDebug = !drawOctreeDebug; + + if (key == KEY_F5) + debugHud.Toggle(DEBUGHUD_SHOW_EVENTPROFILER); + + // Take screenshot + if (key == KEY_F6) + { + Image@ screenshot = Image(); + graphics.TakeScreenShot(screenshot); + // Here we save in the Data folder with date and time appended + screenshot.SavePNG(fileSystem.programDir + "Data/Screenshot_" + + time.timeStamp.Replaced(':', '_').Replaced('.', '_').Replaced(' ', '_') + ".png"); + } + // Allow pause only in singleplayer + if (key == KEY_P && singlePlayer && !console.visible && gameOn) + { + gameScene.updateEnabled = !gameScene.updateEnabled; + if (!gameScene.updateEnabled) + { + SetMessage("PAUSED"); + audio.PauseSoundType(SOUND_EFFECT); + + // Open the settings joystick only if the controls screen joystick was already open + if (screenJoystickID >= 0) + { + // Lazy initialization + if (screenJoystickSettingsID < 0) + screenJoystickSettingsID = input.AddScreenJoystick(cache.GetResource("XMLFile", "UI/ScreenJoystickSettings_NinjaSnowWar.xml")); + else + input.screenJoystickVisible[screenJoystickSettingsID] = true; + } + } + else + { + SetMessage(""); + audio.ResumeSoundType(SOUND_EFFECT); + + // Hide the settings joystick + if (screenJoystickSettingsID >= 0) + input.screenJoystickVisible[screenJoystickSettingsID] = false; + } + } +} + +void HandlePoints(StringHash eventType, VariantMap& eventData) +{ + if (eventData["DamageSide"].GetInt() == SIDE_PLAYER) + { + // Get node ID of the object that should receive points -> use it to find player index + int playerIndex = FindPlayerIndex(eventData["Receiver"].GetInt()); + if (playerIndex >= 0) + { + players[playerIndex].score += eventData["Points"].GetInt(); + SendScore(playerIndex); + + bool newHiscore = CheckHiscore(playerIndex); + if (newHiscore) + SendHiscores(-1); + } + } +} + +void HandleKill(StringHash eventType, VariantMap& eventData) +{ + if (eventData["DamageSide"].GetInt() == SIDE_PLAYER) + { + MakeAIHarder(); + + // Increment amount of simultaneous enemies after enough kills + incrementCounter++; + if (incrementCounter >= incrementEach) + { + incrementCounter = 0; + if (maxEnemies < finalMaxEnemies) + maxEnemies++; + } + } +} + +void HandleClientIdentity(StringHash eventType, VariantMap& eventData) +{ + Connection@ connection = GetEventSender(); + // If user has empty name, invent one + if (connection.identity["UserName"].GetString().Trimmed().empty) + connection.identity["UserName"] = "user" + RandomInt(1000); + // Assign scene to begin replicating it to the client + connection.scene = gameScene; +} + +void HandleClientSceneLoaded(StringHash eventType, VariantMap& eventData) +{ + // Now client is actually ready to begin. If first player, clear the scene and restart the game + Connection@ connection = GetEventSender(); + if (players.empty) + StartGame(connection); + else + SpawnPlayer(connection); +} + +void HandleClientDisconnected(StringHash eventType, VariantMap& eventData) +{ + Connection@ connection = GetEventSender(); + // Erase the player entry, and make the player's ninja commit seppuku (if exists) + for (uint i = 0; i < players.length; ++i) + { + if (players[i].connection is connection) + { + players[i].connection = null; + Node@ playerNode = FindPlayerNode(i); + if (playerNode !is null) + { + Ninja@ playerNinja = cast(playerNode.scriptObject); + playerNinja.health = 0; + playerNinja.lastDamageSide = SIDE_NEUTRAL; // No-one scores from this + } + players.Erase(i); + return; + } + } +} + +void HandlePlayerSpawned(StringHash eventType, VariantMap& eventData) +{ + // Store our node ID and mark the game as started + clientNodeID = eventData["NodeID"].GetInt(); + gameOn = true; + SetMessage(""); + + // Copy initial yaw from the player node (we should have it replicated now) + Node@ playerNode = FindOwnNode(); + if (playerNode !is null) + { + playerControls.yaw = playerNode.rotation.yaw; + playerControls.pitch = 0; + + // Disable the nametag from own character + Node@ nameTag = playerNode.GetChild("NameTag"); + nameTag.enabled = false; + } +} + +void HandleUpdateScore(StringHash eventType, VariantMap& eventData) +{ + clientScore = eventData["Score"].GetInt(); + scoreText.text = "Score " + clientScore; +} + +void HandleUpdateHiscores(StringHash eventType, VariantMap& eventData) +{ + VectorBuffer data = eventData["Hiscores"].GetBuffer(); + hiscores.Resize(data.ReadVLE()); + for (uint i = 0; i < hiscores.length; ++i) + { + hiscores[i].name = data.ReadString(); + hiscores[i].score = data.ReadInt(); + } + + String allHiscores; + for (uint i = 0; i < hiscores.length; ++i) + allHiscores += hiscores[i].name + " " + hiscores[i].score + "\n"; + hiscoreText.text = allHiscores; +} + +void HandleNetworkUpdateSent() +{ + // Clear accumulated buttons from the network controls + if (network.serverConnection !is null) + network.serverConnection.controls.Set(CTRL_ALL, false); +} + +int FindPlayerIndex(uint nodeID) +{ + for (uint i = 0; i < players.length; ++i) + { + if (players[i].nodeID == nodeID) + return i; + } + return -1; +} + +Node@ FindPlayerNode(int playerIndex) +{ + if (playerIndex >= 0 && playerIndex < int(players.length)) + return gameScene.GetNode(players[playerIndex].nodeID); + else + return null; +} + +Node@ FindOwnNode() +{ + if (singlePlayer) + return gameScene.GetChild("Player", true); + else + return gameScene.GetNode(clientNodeID); +} + +bool CheckHiscore(int playerIndex) +{ + for (uint i = 0; i < hiscores.length; ++i) + { + if (hiscores[i].name == players[playerIndex].name) + { + if (players[playerIndex].score > hiscores[i].score) + { + hiscores[i].score = players[playerIndex].score; + SortHiscores(); + return true; + } + else + return false; // No update to individual hiscore + } + } + + // Not found, create new hiscore entry + HiscoreEntry newHiscore; + newHiscore.name = players[playerIndex].name; + newHiscore.score = players[playerIndex].score; + hiscores.Push(newHiscore); + SortHiscores(); + return true; +} + +void SortHiscores() +{ + for (int i = 1; i < int(hiscores.length); ++i) + { + HiscoreEntry temp = hiscores[i]; + int j = i; + while (j > 0 && temp.score > hiscores[j - 1].score) + { + hiscores[j] = hiscores[j - 1]; + --j; + } + hiscores[j] = temp; + } +} + +void SendScore(int playerIndex) +{ + if (!runServer || playerIndex < 0 || playerIndex >= int(players.length)) + return; + + VariantMap eventData; + eventData["Score"] = players[playerIndex].score; + players[playerIndex].connection.SendRemoteEvent("UpdateScore", true, eventData); +} + +void SendHiscores(int playerIndex) +{ + if (!runServer) + return; + + VectorBuffer data; + data.WriteVLE(hiscores.length); + for (uint i = 0; i < hiscores.length; ++i) + { + data.WriteString(hiscores[i].name); + data.WriteInt(hiscores[i].score); + } + + VariantMap eventData; + eventData["Hiscores"] = data; + + if (playerIndex >= 0 && playerIndex < int(players.length)) + players[playerIndex].connection.SendRemoteEvent("UpdateHiscores", true, eventData); + else + network.BroadcastRemoteEvent(gameScene, "UpdateHiscores", true, eventData); // Send to all in scene +} + +Node@ SpawnObject(const Vector3&in position, const Quaternion&in rotation, const String&in className) +{ + XMLFile@ xml = cache.GetResource("XMLFile", "Objects/" + className + ".xml"); + return scene.InstantiateXML(xml, position, rotation); +} + +Node@ SpawnParticleEffect(const Vector3&in position, const String&in effectName, float duration, CreateMode mode = REPLICATED) +{ + Node@ newNode = scene.CreateChild("Effect", mode); + newNode.position = position; + + // Create the particle emitter + ParticleEmitter@ emitter = newNode.CreateComponent("ParticleEmitter"); + emitter.effect = cache.GetResource("ParticleEffect", effectName); + + // Create a GameObject for managing the effect lifetime. This is always local, so for server-controlled effects it + // exists only on the server + GameObject@ object = cast(newNode.CreateScriptObject(scriptFile, "GameObject", LOCAL)); + object.duration = duration; + + return newNode; +} + +Node@ SpawnSound(const Vector3&in position, const String&in soundName, float duration) +{ + Node@ newNode = scene.CreateChild(); + newNode.position = position; + + // Create the sound source + SoundSource3D@ source = newNode.CreateComponent("SoundSource3D"); + Sound@ sound = cache.GetResource("Sound", soundName); + source.SetDistanceAttenuation(200, 5000, 1); + source.Play(sound); + + // Create a GameObject for managing the sound lifetime + GameObject@ object = cast(newNode.CreateScriptObject(scriptFile, "GameObject", LOCAL)); + object.duration = duration; + + return newNode; +} + +void SpawnObjects(float timeStep) +{ + // If game not running, run only the random generator + if (!gameOn) + { + Random(); + return; + } + + // Spawn powerups + powerupSpawnTimer += timeStep; + if (powerupSpawnTimer >= powerupSpawnRate) + { + powerupSpawnTimer = 0; + int numPowerups = gameScene.GetChildrenWithScript("SnowCrate", true).length + gameScene.GetChildrenWithScript("Potion", true).length; + + if (numPowerups < maxPowerups) + { + const float maxOffset = 40; + float xOffset = Random(maxOffset * 2.0) - maxOffset; + float zOffset = Random(maxOffset * 2.0) - maxOffset; + + SpawnObject(Vector3(xOffset, 50, zOffset), Quaternion(), "SnowCrate"); + } + } + + // Spawn enemies + enemySpawnTimer += timeStep; + if (enemySpawnTimer > enemySpawnRate) + { + enemySpawnTimer = 0; + int numEnemies = 0; + Array ninjaNodes = gameScene.GetChildrenWithScript("Ninja", true); + for (uint i = 0; i < ninjaNodes.length; ++i) + { + Ninja@ ninja = cast(ninjaNodes[i].scriptObject); + if (ninja.side == SIDE_ENEMY) + ++numEnemies; + } + + if (numEnemies < maxEnemies) + { + const float maxOffset = 40; + float offset = Random(maxOffset * 2.0) - maxOffset; + // Random north/east/south/west direction + int dir = RandomInt() & 3; + dir *= 90; + Quaternion rotation(0, dir, 0); + + Node@ enemyNode = SpawnObject(rotation * Vector3(offset, 10, -120), rotation, "Ninja"); + + // Initialize variables + Ninja@ enemyNinja = cast(enemyNode.scriptObject); + enemyNinja.side = SIDE_ENEMY; + @enemyNinja.controller = AIController(); + RigidBody@ enemyBody = enemyNode.GetComponent("RigidBody"); + enemyBody.linearVelocity = rotation * Vector3(0, 10, 30); + } + } +} + +void CheckEndAndRestart() +{ + // Only check end of game if singleplayer or client + if (runServer) + return; + + // Check if player node has vanished + Node@ playerNode = FindOwnNode(); + if (gameOn && playerNode is null) + { + gameOn = false; + SetMessage("Press Fire or Jump to restart!"); + return; + } + + // Check for restart (singleplayer only) + if (!gameOn && singlePlayer && playerControls.IsPressed(CTRL_FIRE | CTRL_JUMP, prevPlayerControls)) + StartGame(null); +} + +void UpdateControls() +{ + if (singlePlayer || runClient) + { + prevPlayerControls = playerControls; + playerControls.Set(CTRL_ALL, false); + + if (touchEnabled) + { + for (uint i = 0; i < input.numTouches; ++i) + { + TouchState@ touch = input.touches[i]; + if (touch.touchedElement is null) + { + // Touch on empty space + playerControls.yaw += touchSensitivity * gameCamera.fov / graphics.height * touch.delta.x; + playerControls.pitch += touchSensitivity * gameCamera.fov / graphics.height * touch.delta.y; + } + } + } + + if (input.numJoysticks > 0) + { + JoystickState@ joystick = touchEnabled ? input.joysticks[screenJoystickID] : input.joysticksByIndex[0]; + if (joystick.numButtons > 0) + { + if (joystick.buttonDown[0]) + playerControls.Set(CTRL_JUMP, true); + if (joystick.buttonDown[1]) + playerControls.Set(CTRL_FIRE, true); + if (joystick.numButtons >= 6) + { + if (joystick.buttonDown[4]) + playerControls.Set(CTRL_JUMP, true); + if (joystick.buttonDown[5]) + playerControls.Set(CTRL_FIRE, true); + } + if (joystick.numHats > 0) + { + if (joystick.hatPosition[0] & HAT_LEFT != 0) + playerControls.Set(CTRL_LEFT, true); + if (joystick.hatPosition[0] & HAT_RIGHT != 0) + playerControls.Set(CTRL_RIGHT, true); + if (joystick.hatPosition[0] & HAT_UP != 0) + playerControls.Set(CTRL_UP, true); + if (joystick.hatPosition[0] & HAT_DOWN != 0) + playerControls.Set(CTRL_DOWN, true); + } + if (joystick.numAxes >= 2) + { + if (joystick.axisPosition[0] < -joyMoveDeadZone) + playerControls.Set(CTRL_LEFT, true); + if (joystick.axisPosition[0] > joyMoveDeadZone) + playerControls.Set(CTRL_RIGHT, true); + if (joystick.axisPosition[1] < -joyMoveDeadZone) + playerControls.Set(CTRL_UP, true); + if (joystick.axisPosition[1] > joyMoveDeadZone) + playerControls.Set(CTRL_DOWN, true); + } + if (joystick.numAxes >= 4) + { + float lookX = joystick.axisPosition[2]; + float lookY = joystick.axisPosition[3]; + + if (lookX < -joyLookDeadZone) + playerControls.yaw -= joySensitivity * lookX * lookX; + if (lookX > joyLookDeadZone) + playerControls.yaw += joySensitivity * lookX * lookX; + if (lookY < -joyLookDeadZone) + playerControls.pitch -= joySensitivity * lookY * lookY; + if (lookY > joyLookDeadZone) + playerControls.pitch += joySensitivity * lookY * lookY; + } + } + } + + // For the triggered actions (fire & jump) check also for press, in case the FPS is low + // and the key was already released + if (console is null || !console.visible) + { + if (input.keyDown[KEY_W]) + playerControls.Set(CTRL_UP, true); + if (input.keyDown[KEY_S]) + playerControls.Set(CTRL_DOWN, true); + if (input.keyDown[KEY_A]) + playerControls.Set(CTRL_LEFT, true); + if (input.keyDown[KEY_D]) + playerControls.Set(CTRL_RIGHT, true); + if (input.keyDown[KEY_LCTRL] || input.keyPress[KEY_LCTRL]) + playerControls.Set(CTRL_FIRE, true); + if (input.keyDown[' '] || input.keyPress[' ']) + playerControls.Set(CTRL_JUMP, true); + + if (input.mouseButtonDown[MOUSEB_LEFT] || input.mouseButtonPress[MOUSEB_LEFT]) + playerControls.Set(CTRL_FIRE, true); + if (input.mouseButtonDown[MOUSEB_RIGHT] || input.mouseButtonPress[MOUSEB_RIGHT]) + playerControls.Set(CTRL_JUMP, true); + + playerControls.yaw += mouseSensitivity * input.mouseMoveX; + playerControls.pitch += mouseSensitivity * input.mouseMoveY; + playerControls.pitch = Clamp(playerControls.pitch, -60.0, 60.0); + } + + // In singleplayer, set controls directly on the player's ninja. In multiplayer, transmit to server + if (singlePlayer) + { + Node@ playerNode = gameScene.GetChild("Player", true); + if (playerNode !is null) + { + Ninja@ playerNinja = cast(playerNode.scriptObject); + playerNinja.controls = playerControls; + } + } + else if (network.serverConnection !is null) + { + // Set the latest yaw & pitch to server controls, and accumulate the buttons so that we do not miss any presses + network.serverConnection.controls.yaw = playerControls.yaw; + network.serverConnection.controls.pitch = playerControls.pitch; + network.serverConnection.controls.buttons |= playerControls.buttons; + + // Tell the camera position to server for interest management + network.serverConnection.position = gameCameraNode.worldPosition; + } + } + + if (runServer) + { + // Apply each connection's controls to the ninja they control + for (uint i = 0; i < players.length; ++i) + { + Node@ playerNode = FindPlayerNode(i); + if (playerNode !is null) + { + Ninja@ playerNinja = cast(playerNode.scriptObject); + playerNinja.controls = players[i].connection.controls; + } + else + { + // If player has no ninja, respawn if fire/jump is pressed + if (players[i].connection.controls.IsPressed(CTRL_FIRE | CTRL_JUMP, players[i].lastControls)) + SpawnPlayer(players[i].connection); + } + players[i].lastControls = players[i].connection.controls; + } + } +} + +void UpdateCamera() +{ + if (engine.headless) + return; + + // On the server, use a simple freelook camera + if (runServer) + { + UpdateFreelookCamera(); + return; + } + + Node@ playerNode = FindOwnNode(); + if (playerNode is null) + return; + + Vector3 pos = playerNode.position; + Quaternion dir; + + // Make controls seem more immediate by forcing the current mouse yaw to player ninja's Y-axis rotation + if (playerNode.vars["Health"].GetInt() > 0) + playerNode.rotation = Quaternion(0, playerControls.yaw, 0); + + dir = dir * Quaternion(playerNode.rotation.yaw, Vector3(0, 1, 0)); + dir = dir * Quaternion(playerControls.pitch, Vector3(1, 0, 0)); + + Vector3 aimPoint = pos + Vector3(0, 1, 0); + Vector3 minDist = aimPoint + dir * Vector3(0, 0, -cameraMinDist); + Vector3 maxDist = aimPoint + dir * Vector3(0, 0, -cameraMaxDist); + + // Collide camera ray with static objects (collision mask 2) + Vector3 rayDir = (maxDist - minDist).Normalized(); + float rayDistance = cameraMaxDist - cameraMinDist + cameraSafetyDist; + PhysicsRaycastResult result = gameScene.physicsWorld.RaycastSingle(Ray(minDist, rayDir), rayDistance, 2); + if (result.body !is null) + rayDistance = Min(rayDistance, result.distance - cameraSafetyDist); + + gameCameraNode.position = minDist + rayDir * rayDistance; + gameCameraNode.rotation = dir; +} + +void UpdateFreelookCamera() +{ + if (console is null || !console.visible) + { + float timeStep = time.timeStep; + float speedMultiplier = 1.0; + if (input.keyDown[KEY_LSHIFT]) + speedMultiplier = 5.0; + if (input.keyDown[KEY_LCTRL]) + speedMultiplier = 0.1; + + if (input.keyDown[KEY_W]) + gameCameraNode.Translate(Vector3(0, 0, 10) * timeStep * speedMultiplier); + if (input.keyDown[KEY_S]) + gameCameraNode.Translate(Vector3(0, 0, -10) * timeStep * speedMultiplier); + if (input.keyDown[KEY_A]) + gameCameraNode.Translate(Vector3(-10, 0, 0) * timeStep * speedMultiplier); + if (input.keyDown[KEY_D]) + gameCameraNode.Translate(Vector3(10, 0, 0) * timeStep * speedMultiplier); + + playerControls.yaw += mouseSensitivity * input.mouseMoveX; + playerControls.pitch += mouseSensitivity * input.mouseMoveY; + playerControls.pitch = Clamp(playerControls.pitch, -90.0, 90.0); + gameCameraNode.rotation = Quaternion(playerControls.pitch, playerControls.yaw, 0); + } +} + +void UpdateStatus() +{ + if (engine.headless || runServer) + return; + + if (singlePlayer) + { + if (players.length > 0) + scoreText.text = "Score " + players[0].score; + if (hiscores.length > 0) + hiscoreText.text = "Hiscore " + hiscores[0].score; + } + + Node@ playerNode = FindOwnNode(); + if (playerNode !is null) + { + int health = 0; + if (singlePlayer) + { + GameObject@ object = cast(playerNode.scriptObject); + health = object.health; + } + else + { + // In multiplayer the client does not have script logic components, but health is replicated via node user variables + health = playerNode.vars["Health"].GetInt(); + } + healthBar.width = 116 * health / playerHealth; + } +} + +void HandleScreenMode() +{ + int height = graphics.height / 22; + if (height > 64) + height = 64; + sight.SetSize(height, height); + messageText.SetPosition(0, -height * 2); +} diff --git a/bin/Data/Scripts/NinjaSnowWar/AIController.as b/bin/Data/Scripts/NinjaSnowWar/AIController.as new file mode 100644 index 0000000..68beb3e --- /dev/null +++ b/bin/Data/Scripts/NinjaSnowWar/AIController.as @@ -0,0 +1,195 @@ +const float initialAggression = 0.0020; +const float initialPrediction = 30; +const float initialAimSpeed = 10; +const float deltaAggression = 0.000025; +const float deltaPrediction = -0.15; +const float deltaAimSpeed = 0.30; +const float maxAggression = 0.01; +const float maxPrediction = 20; +const float maxAimSpeed = 40; + +float aiAggression = initialAggression; +float aiPrediction = initialPrediction; +float aiAimSpeed = initialAimSpeed; + +void ResetAI() +{ + aiAggression = initialAggression; + aiPrediction = initialPrediction; + aiAimSpeed = initialAimSpeed; +} + +void MakeAIHarder() +{ + aiAggression += deltaAggression; + if (aiAggression > maxAggression) + aiAggression = maxAggression; + + aiPrediction += deltaPrediction; + if (aiPrediction < maxPrediction) + aiPrediction = maxPrediction; + + aiAimSpeed += deltaAimSpeed; + if (aiAimSpeed > maxAimSpeed) + aiAimSpeed = maxAimSpeed; +} + +class AIController +{ + // Use a weak handle instead of a normal handle to point to the current target + // so that we don't mistakenly keep it alive. + WeakHandle currentTarget; + float newTargetTimer = 0; + + void Control(Ninja@ ownNinja, Node@ ownNode, float timeStep) + { + // Get new target if none. Do not constantly scan for new targets to conserve CPU time + if (currentTarget.Get() is null) + { + newTargetTimer += timeStep; + if (newTargetTimer > 0.5) + GetNewTarget(ownNode); + } + + Node@ targetNode = currentTarget.Get(); + + if (targetNode !is null) + { + // Check that current target is still alive. Otherwise choose new + Ninja@ targetNinja = cast(targetNode.scriptObject); + if (targetNinja is null || targetNinja.health <= 0) + { + currentTarget = null; + return; + } + + RigidBody@ targetBody = targetNode.GetComponent("RigidBody"); + + ownNinja.controls.Set(CTRL_FIRE, false); + ownNinja.controls.Set(CTRL_JUMP, false); + + float deltaX = 0.0f; + float deltaY = 0.0f; + + // Aim from own head to target's feet + Vector3 ownPos(ownNode.position + Vector3(0, 0.9, 0)); + Vector3 targetPos(targetNode.position + Vector3(0, -0.9, 0)); + float distance = (targetPos - ownPos).length; + + // Use prediction according to target distance & estimated snowball speed + Vector3 currentAim(ownNinja.GetAim() * Vector3(0, 0, 1)); + float predictDistance = distance; + if (predictDistance > 50) predictDistance = 50; + Vector3 predictedPos = targetPos + targetBody.linearVelocity * predictDistance / aiPrediction; + Vector3 targetAim = (predictedPos - ownPos); + + // Add distance/height compensation + float compensation = Max(targetAim.length - 15, 0.0); + targetAim += Vector3(0, 0.6, 0) * compensation; + + // X-aiming + targetAim.Normalize(); + Vector3 currentYaw(currentAim.x, 0, currentAim.z); + Vector3 targetYaw(targetAim.x, 0, targetAim.z); + currentYaw.Normalize(); + targetYaw.Normalize(); + deltaX = Clamp(Quaternion(currentYaw, targetYaw).yaw, -aiAimSpeed, aiAimSpeed); + + // Y-aiming + Vector3 currentPitch(0, currentAim.y, 1); + Vector3 targetPitch(0, targetAim.y, 1); + currentPitch.Normalize(); + targetPitch.Normalize(); + deltaY = Clamp(Quaternion(currentPitch, targetPitch).pitch, -aiAimSpeed, aiAimSpeed); + + ownNinja.controls.yaw += 0.1 * deltaX; + ownNinja.controls.pitch += 0.1 * deltaY; + + // Firing? if close enough and relatively correct aim + if ((distance < 25) && (currentAim.DotProduct(targetAim) > 0.75)) + { + if (Random(1.0) < aiAggression) + ownNinja.controls.Set(CTRL_FIRE, true); + } + + // Movement + ownNinja.dirChangeTime -= timeStep; + if (ownNinja.dirChangeTime <= 0) + { + ownNinja.dirChangeTime = 0.5 + Random(1.0); + ownNinja.controls.Set(CTRL_UP|CTRL_DOWN|CTRL_LEFT|CTRL_RIGHT, false); + + // Far distance: go forward + if (distance > 30) + ownNinja.controls.Set(CTRL_UP, true); + else if (distance > 6) + { + // Medium distance: random strafing, predominantly forward + float v = Random(1.0); + if (v < 0.8) + ownNinja.controls.Set(CTRL_UP, true); + float h = Random(1.0); + if (h < 0.3) + ownNinja.controls.Set(CTRL_LEFT, true); + if (h > 0.7) + ownNinja.controls.Set(CTRL_RIGHT, true); + } + else + { + // Close distance: random strafing backwards + float v = Random(1.0); + if (v < 0.8) + ownNinja.controls.Set(CTRL_DOWN, true); + float h = Random(1.0); + if (h < 0.4) + ownNinja.controls.Set(CTRL_LEFT, true); + if (h > 0.6) + ownNinja.controls.Set(CTRL_RIGHT, true); + } + } + + // Random jump, if going forward + if ((ownNinja.controls.IsDown(CTRL_UP)) && (distance < 1000)) + { + if (Random(1.0) < (aiAggression / 5.0)) + ownNinja.controls.Set(CTRL_JUMP, true); + } + } + else + { + // If no target, walk idly + ownNinja.controls.Set(CTRL_ALL, false); + ownNinja.controls.Set(CTRL_UP, true); + ownNinja.dirChangeTime -= timeStep; + if (ownNinja.dirChangeTime <= 0) + { + ownNinja.dirChangeTime = 1 + Random(2); + ownNinja.controls.yaw += 0.1 * (Random(600) - 300); + } + if (ownNinja.isSliding) + ownNinja.controls.yaw += 0.2; + } + } + + void GetNewTarget(Node@ ownNode) + { + newTargetTimer = 0; + + Array nodes = ownNode.scene.GetChildrenWithScript("Ninja", true); + float closestDistance = M_INFINITY; + for (uint i = 0; i < nodes.length; ++i) + { + Node@ otherNode = nodes[i]; + Ninja@ otherNinja = cast(otherNode.scriptObject); + if (otherNinja.side == SIDE_PLAYER && otherNinja.health > 0) + { + float distance = (ownNode.position - otherNode.position).lengthSquared; + if (distance < closestDistance) + { + currentTarget = otherNode; + closestDistance = distance; + } + } + } + } +} diff --git a/bin/Data/Scripts/NinjaSnowWar/FootSteps.as b/bin/Data/Scripts/NinjaSnowWar/FootSteps.as new file mode 100644 index 0000000..b3f823d --- /dev/null +++ b/bin/Data/Scripts/NinjaSnowWar/FootSteps.as @@ -0,0 +1,25 @@ +class FootSteps : ScriptObject +{ + void Start() + { + // Subscribe to animation triggers, which are sent by the AnimatedModel's node (same as our node) + SubscribeToEvent(node, "AnimationTrigger", "HandleAnimationTrigger"); + } + + void HandleAnimationTrigger(StringHash eventType, VariantMap& eventData) + { + AnimatedModel@ model = node.GetComponent("AnimatedModel"); + AnimationState@ state = model.animationStates[eventData["Name"].GetString()]; + if (state is null) + return; + + // If the animation is blended with sufficient weight, instantiate a local particle effect for the footstep. + // The trigger data (string) tells the bone scenenode to use. Note: called on both client and server + if (state.weight > 0.5f) + { + Node@ bone = node.GetChild(eventData["Data"].GetString(), true); + if (bone !is null) + SpawnParticleEffect(bone.worldPosition, "Particle/SnowExplosionFade.xml", 1, LOCAL); + } + } +} \ No newline at end of file diff --git a/bin/Data/Scripts/NinjaSnowWar/GameObject.as b/bin/Data/Scripts/NinjaSnowWar/GameObject.as new file mode 100644 index 0000000..36a9b09 --- /dev/null +++ b/bin/Data/Scripts/NinjaSnowWar/GameObject.as @@ -0,0 +1,152 @@ +const int CTRL_UP = 1; +const int CTRL_DOWN = 2; +const int CTRL_LEFT = 4; +const int CTRL_RIGHT = 8; +const int CTRL_FIRE = 16; +const int CTRL_JUMP = 32; +const int CTRL_ALL = 63; + +const int SIDE_NEUTRAL = 0; +const int SIDE_PLAYER = 1; +const int SIDE_ENEMY = 2; + +class GameObject : ScriptObject +{ + bool onGround; + bool isSliding; + float duration; + int health; + int maxHealth; + int side; + int lastDamageSide; + uint lastDamageCreatorID; + uint creatorID; + + GameObject() + { + onGround = false; + isSliding = false; + duration = -1; // Infinite + health = 0; + maxHealth = 0; + side = SIDE_NEUTRAL; + lastDamageSide = SIDE_NEUTRAL; + lastDamageCreatorID = 0; + creatorID = 0; + + // if (runClient) + // Print("Warning! Logic object created on client!"); + } + + void FixedUpdate(float timeStep) + { + // Disappear when duration expired + if (duration >= 0) + { + duration -= timeStep; + if (duration <= 0) + node.Remove(); + } + } + + bool Damage(GameObject@ origin, int amount) + { + if ((origin.side == side) || (health == 0)) + return false; + + lastDamageSide = origin.side; + lastDamageCreatorID = origin.creatorID; + health -= amount; + if (health < 0) + health = 0; + return true; + } + + bool Heal(int amount) + { + // By default do not heal + return false; + } + + void PlaySound(const String&in soundName) + { + SoundSource3D@ source = node.CreateComponent("SoundSource3D"); + Sound@ sound = cache.GetResource("Sound", soundName); + // Subscribe to sound finished for cleaning up the source + SubscribeToEvent(node, "SoundFinished", "HandleSoundFinished"); + + source.SetDistanceAttenuation(2, 50, 1); + source.Play(sound); + } + + void HandleSoundFinished(StringHash eventType, VariantMap& eventData) + { + SoundSource3D@ source = eventData["SoundSource"].GetPtr(); + source.Remove(); + } + + void HandleNodeCollision(StringHash eventType, VariantMap& eventData) + { + Node@ otherNode = eventData["OtherNode"].GetPtr(); + RigidBody@ otherBody = eventData["OtherBody"].GetPtr(); + + // If the other collision shape belongs to static geometry, perform world collision + if (otherBody.collisionLayer == 2) + WorldCollision(eventData); + + // If the other node is scripted, perform object-to-object collision + GameObject@ otherObject = cast(otherNode.scriptObject); + if (otherObject !is null) + ObjectCollision(otherObject, eventData); + } + + void WorldCollision(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 ground contact + if (contactPosition.y < node.position.y) + { + float level = contactNormal.y; + if (level > 0.75) + onGround = true; + else + { + // If contact is somewhere between vertical/horizontal, is sliding a slope + if (level > 0.1) + isSliding = true; + } + } + } + + // Ground contact has priority over sliding contact + if (onGround == true) + isSliding = false; + } + + void ObjectCollision(GameObject@ otherObject, VariantMap& eventData) + { + } + + void ResetWorldCollision() + { + RigidBody@ body = node.GetComponent("RigidBody"); + if (body.active) + { + onGround = false; + isSliding = false; + } + else + { + // If body is not active, assume it rests on the ground + onGround = true; + isSliding = false; + } + } +} diff --git a/bin/Data/Scripts/NinjaSnowWar/LightFlash.as b/bin/Data/Scripts/NinjaSnowWar/LightFlash.as new file mode 100644 index 0000000..28a7d8e --- /dev/null +++ b/bin/Data/Scripts/NinjaSnowWar/LightFlash.as @@ -0,0 +1,18 @@ +#include "Scripts/NinjaSnowWar/GameObject.as" + +class LightFlash : GameObject +{ + LightFlash() + { + duration = 2.0; + } + + void FixedUpdate(float timeStep) + { + Light@ light = node.GetComponent("Light"); + light.brightness = light.brightness * Max(1.0 - timeStep * 10.0, 0.0); + + // Call superclass to handle lifetime + GameObject::FixedUpdate(timeStep); + } +} \ No newline at end of file diff --git a/bin/Data/Scripts/NinjaSnowWar/Ninja.as b/bin/Data/Scripts/NinjaSnowWar/Ninja.as new file mode 100644 index 0000000..a9465b0 --- /dev/null +++ b/bin/Data/Scripts/NinjaSnowWar/Ninja.as @@ -0,0 +1,335 @@ +#include "Scripts/NinjaSnowWar/GameObject.as" +#include "Scripts/NinjaSnowWar/AIController.as" + +const int LAYER_MOVE = 0; +const int LAYER_ATTACK = 1; + +const float ninjaMoveForce = 25; +const float ninjaAirMoveForce = 1; +const float ninjaDampingForce = 5; +const float ninjaJumpForce = 450; +const Vector3 ninjaThrowVelocity(0, 4.25, 20); +const Vector3 ninjaThrowPosition(0, 0.2, 1); +const float ninjaThrowDelay = 0.1; +const float ninjaCorpseDuration = 3; +const int ninjaPoints = 250; + +class Ninja : GameObject +{ + Controls controls; + Controls prevControls; + AIController@ controller; + bool okToJump; + bool smoke; + float inAirTime; + float onGroundTime; + float throwTime; + float deathTime; + float deathDir; + float dirChangeTime; + float aimX; + float aimY; + + Ninja() + { + health = maxHealth = 2; + okToJump = false; + smoke = false; + onGround = false; + isSliding = false; + inAirTime = 1; + onGroundTime = 0; + throwTime = 0; + deathTime = 0; + deathDir = 0; + dirChangeTime = 0; + aimX = 0; + aimY = 0; + } + + void DelayedStart() + { + SubscribeToEvent(node, "NodeCollision", "HandleNodeCollision"); + + // Get horizontal aim from initial rotation + aimX = controls.yaw = node.rotation.yaw; + + // Start playing the idle animation immediately, even before the first physics update + AnimationController@ animCtrl = node.children[0].GetComponent("AnimationController"); + animCtrl.PlayExclusive("Models/NinjaSnowWar/Ninja_Idle3.ani", LAYER_MOVE, true); + } + + void SetControls(const Controls&in newControls) + { + controls = newControls; + } + + Quaternion GetAim() + { + Quaternion q = Quaternion(aimX, Vector3(0, 1, 0)); + q = q * Quaternion(aimY, Vector3(1, 0, 0)); + return q; + } + + void FixedUpdate(float timeStep) + { + // For multiplayer, replicate the health into the node user variables + node.vars["Health"] = health; + + if (health <= 0) + { + DeathUpdate(timeStep); + return; + } + + // AI control if controller exists + if (controller !is null) + controller.Control(this, node, timeStep); + + RigidBody@ body = node.GetComponent("RigidBody"); + AnimationController@ animCtrl = node.children[0].GetComponent("AnimationController"); + + // Turning / horizontal aiming + if (aimX != controls.yaw) + aimX = controls.yaw; + + // Vertical aiming + if (aimY != controls.pitch) + aimY = controls.pitch; + + // Force the physics rotation + Quaternion q(aimX, Vector3(0, 1, 0)); + body.rotation = q; + + // Movement ground/air + Vector3 vel = body.linearVelocity; + if (onGround) + { + // If landed, play a particle effect at feet (use the AnimatedModel node) + if (inAirTime > 0.5) + SpawnParticleEffect(node.children[0].worldPosition, "Particle/SnowExplosion.xml", 1); + + inAirTime = 0; + onGroundTime += timeStep; + } + else + { + onGroundTime = 0; + inAirTime += timeStep; + } + + if (inAirTime < 0.3f && !isSliding) + { + bool sideMove = false; + + // Movement in four directions + if (controls.IsDown(CTRL_UP|CTRL_DOWN|CTRL_LEFT|CTRL_RIGHT)) + { + float animDir = 1.0f; + Vector3 force(0, 0, 0); + if (controls.IsDown(CTRL_UP)) + force += q * Vector3(0, 0, 1); + if (controls.IsDown(CTRL_DOWN)) + { + animDir = -1.0f; + force += q * Vector3(0, 0, -1); + } + if (controls.IsDown(CTRL_LEFT)) + { + sideMove = true; + force += q * Vector3(-1, 0, 0); + } + if (controls.IsDown(CTRL_RIGHT)) + { + sideMove = true; + force += q * Vector3(1, 0, 0); + } + // Normalize so that diagonal strafing isn't faster + force.Normalize(); + force *= ninjaMoveForce; + body.ApplyImpulse(force); + + // Walk or sidestep animation + if (sideMove) + { + animCtrl.PlayExclusive("Models/NinjaSnowWar/Ninja_Stealth.ani", LAYER_MOVE, true, 0.2); + animCtrl.SetSpeed("Models/NinjaSnowWar/Ninja_Stealth.ani", animDir * 2.2); + } + else + { + animCtrl.PlayExclusive("Models/NinjaSnowWar/Ninja_Walk.ani", LAYER_MOVE, true, 0.2); + animCtrl.SetSpeed("Models/NinjaSnowWar/Ninja_Walk.ani", animDir * 1.6); + } + } + else + { + // Idle animation + animCtrl.PlayExclusive("Models/NinjaSnowWar/Ninja_Idle3.ani", LAYER_MOVE, true, 0.2); + } + + // Overall damping to cap maximum speed + body.ApplyImpulse(Vector3(-ninjaDampingForce * vel.x, 0, -ninjaDampingForce * vel.z)); + + // Jumping + if (controls.IsDown(CTRL_JUMP)) + { + if (okToJump && inAirTime < 0.1f) + { + // Lift slightly off the ground for better animation + body.position = body.position + Vector3(0, 0.03, 0); + body.ApplyImpulse(Vector3(0, ninjaJumpForce, 0)); + inAirTime = 1.0f; + animCtrl.PlayExclusive("Models/NinjaSnowWar/Ninja_JumpNoHeight.ani", LAYER_MOVE, false, 0.1); + animCtrl.SetTime("Models/NinjaSnowWar/Ninja_JumpNoHeight.ani", 0.0); // Always play from beginning + okToJump = false; + } + } + else okToJump = true; + } + else + { + // Motion in the air + // Note: when sliding a steep slope, control (or damping) isn't allowed! + if (inAirTime > 0.3f && !isSliding) + { + if (controls.IsDown(CTRL_UP|CTRL_DOWN|CTRL_LEFT|CTRL_RIGHT)) + { + Vector3 force(0, 0, 0); + if (controls.IsDown(CTRL_UP)) + force += q * Vector3(0, 0, 1); + if (controls.IsDown(CTRL_DOWN)) + force += q * Vector3(0, 0, -1); + if (controls.IsDown(CTRL_LEFT)) + force += q * Vector3(-1, 0, 0); + if (controls.IsDown(CTRL_RIGHT)) + force += q * Vector3(1, 0, 0); + // Normalize so that diagonal strafing isn't faster + force.Normalize(); + force *= ninjaAirMoveForce; + body.ApplyImpulse(force); + } + } + + // Falling/jumping/sliding animation + if (inAirTime > 0.1f) + animCtrl.PlayExclusive("Models/NinjaSnowWar/Ninja_JumpNoHeight.ani", LAYER_MOVE, false, 0.1); + } + + // Shooting + if (throwTime >= 0) + throwTime -= timeStep; + + // Start fading the attack animation after it has progressed past a certain point + if (animCtrl.GetTime("Models/NinjaSnowWar/Ninja_Attack1.ani") > 0.1) + animCtrl.Fade("Models/NinjaSnowWar/Ninja_Attack1.ani", 0.0, 0.5); + + if ((controls.IsPressed(CTRL_FIRE, prevControls)) && (throwTime <= 0)) + { + Vector3 projectileVel = GetAim() * ninjaThrowVelocity; + + animCtrl.Play("Models/NinjaSnowWar/Ninja_Attack1.ani", LAYER_ATTACK, false, 0.0); + animCtrl.SetTime("Models/NinjaSnowWar/Ninja_Attack1.ani", 0.0); // Always play from beginning + + Node@ snowball = SpawnObject(node.position + vel * timeStep + q * ninjaThrowPosition, GetAim(), "SnowBall"); + RigidBody@ snowballBody = snowball.GetComponent("RigidBody"); + snowballBody.linearVelocity = projectileVel; + GameObject@ snowballObject = cast(snowball.scriptObject); + snowballObject.side = side; + snowballObject.creatorID = node.id; + + PlaySound("Sounds/NutThrow.wav"); + + throwTime = ninjaThrowDelay; + } + + prevControls = controls; + + ResetWorldCollision(); + } + + void DeathUpdate(float timeStep) + { + RigidBody@ body = node.GetComponent("RigidBody"); + CollisionShape@ shape = node.GetComponent("CollisionShape"); + Node@ modelNode = node.children[0]; + AnimationController@ animCtrl = modelNode.GetComponent("AnimationController"); + AnimatedModel@ model = modelNode.GetComponent("AnimatedModel"); + + Vector3 vel = body.linearVelocity; + + // Overall damping to cap maximum speed + body.ApplyImpulse(Vector3(-ninjaDampingForce * vel.x, 0, -ninjaDampingForce * vel.z)); + + // Collide only to world geometry + body.collisionMask = 2; + + // Pick death animation on first death update + if (deathDir == 0) + { + if (Random(1.0) < 0.5) + deathDir = -1; + else + deathDir = 1; + + PlaySound("Sounds/SmallExplosion.wav"); + + VariantMap eventData; + eventData["Points"] = ninjaPoints; + eventData["Receiver"] = lastDamageCreatorID; + eventData["DamageSide"] = lastDamageSide; + SendEvent("Points", eventData); + SendEvent("Kill", eventData); + } + + deathTime += timeStep; + + // Move the model node to center the corpse mostly within the physics cylinder + // (because of the animation) + if (deathDir < 0) + { + // Backward death + animCtrl.StopLayer(LAYER_ATTACK, 0.1); + animCtrl.PlayExclusive("Models/NinjaSnowWar/Ninja_Death1.ani", LAYER_MOVE, false, 0.2); + animCtrl.SetSpeed("Models/NinjaSnowWar/Ninja_Death1.ani", 0.5); + if ((deathTime >= 0.3) && (deathTime < 0.8)) + modelNode.Translate(Vector3(0, 0, 4.25 * timeStep)); + } + else if (deathDir > 0) + { + // Forward death + animCtrl.StopLayer(LAYER_ATTACK, 0.1); + animCtrl.PlayExclusive("Models/NinjaSnowWar/Ninja_Death2.ani", LAYER_MOVE, false, 0.2); + animCtrl.SetSpeed("Models/NinjaSnowWar/Ninja_Death2.ani", 0.5); + if ((deathTime >= 0.4) && (deathTime < 0.8)) + modelNode.Translate(Vector3(0, 0, -4.25 * timeStep)); + } + + // Create smokecloud just before vanishing + if ((deathTime > (ninjaCorpseDuration - 1)) && (!smoke)) + { + SpawnParticleEffect(node.position + Vector3(0, -0.4, 0), "Particle/Smoke.xml", 8); + smoke = true; + } + + if (deathTime > ninjaCorpseDuration) + { + SpawnObject(node.position + Vector3(0, -0.5, 0), Quaternion(), "LightFlash"); + SpawnSound(node.position + Vector3(0, -0.5, 0), "Sounds/BigExplosion.wav", 2); + node.Remove(); + } + } + + bool Heal(int amount) + { + if (health == maxHealth) + return false; + + health += amount; + if (health > maxHealth) + health = maxHealth; + // If player, play the "powerup" sound + if (side == SIDE_PLAYER) + PlaySound("Sounds/Powerup.wav"); + return true; + } +} \ No newline at end of file diff --git a/bin/Data/Scripts/NinjaSnowWar/Player.as b/bin/Data/Scripts/NinjaSnowWar/Player.as new file mode 100644 index 0000000..c3161a0 --- /dev/null +++ b/bin/Data/Scripts/NinjaSnowWar/Player.as @@ -0,0 +1,20 @@ +class Player +{ + int score; + String name; + uint nodeID; + Connection@ connection; + Controls lastControls; + + Player() + { + score = 0; + nodeID = 0; + } +} + +class HiscoreEntry +{ + int score; + String name; +} diff --git a/bin/Data/Scripts/NinjaSnowWar/Potion.as b/bin/Data/Scripts/NinjaSnowWar/Potion.as new file mode 100644 index 0000000..bb5208f --- /dev/null +++ b/bin/Data/Scripts/NinjaSnowWar/Potion.as @@ -0,0 +1,31 @@ +#include "Scripts/NinjaSnowWar/GameObject.as" + +const int potionHealAmount = 5; + +class Potion : GameObject +{ + int healAmount; + + Potion() + { + healAmount = potionHealAmount; + } + + void Start() + { + SubscribeToEvent(node, "NodeCollision", "HandleNodeCollision"); + } + + void ObjectCollision(GameObject@ otherObject, VariantMap& eventData) + { + if (healAmount > 0) + { + if (otherObject.Heal(healAmount)) + { + // Could also remove the potion directly, but this way it gets removed on next update + healAmount = 0; + duration = 0; + } + } + } +} \ No newline at end of file diff --git a/bin/Data/Scripts/NinjaSnowWar/SnowBall.as b/bin/Data/Scripts/NinjaSnowWar/SnowBall.as new file mode 100644 index 0000000..75c02ff --- /dev/null +++ b/bin/Data/Scripts/NinjaSnowWar/SnowBall.as @@ -0,0 +1,76 @@ +#include "Scripts/NinjaSnowWar/GameObject.as" + +const float snowballMinHitSpeed = 1; +const float snowballDampingForce = 20; +const float snowballDuration = 5; +const float snowballGroundHitDuration = 1; +const float snowballObjectHitDuration = 0; +const int snowballDamage = 1; + +class SnowBall : GameObject +{ + int hitDamage; + + SnowBall() + { + duration = snowballDuration; + hitDamage = snowballDamage; + } + + void Start() + { + SubscribeToEvent(node, "NodeCollision", "HandleNodeCollision"); + } + + void FixedUpdate(float timeStep) + { + // Apply damping when rolling on the ground, or near disappearing + RigidBody@ body = node.GetComponent("RigidBody"); + if ((onGround) || (duration < snowballGroundHitDuration)) + { + Vector3 vel = body.linearVelocity; + body.ApplyForce(Vector3(-snowballDampingForce * vel.x, 0, -snowballDampingForce * vel.z)); + } + + // Disappear when duration expired + if (duration >= 0) + { + duration -= timeStep; + if (duration <= 0) + { + SpawnParticleEffect(node.position, "Particle/SnowExplosion.xml", 1); + node.Remove(); + } + } + } + + void WorldCollision(VariantMap& eventData) + { + GameObject::WorldCollision(eventData); + + // If hit the ground, disappear after a short while + if (duration > snowballGroundHitDuration) + duration = snowballGroundHitDuration; + } + + void ObjectCollision(GameObject@ otherObject, VariantMap& eventData) + { + if (hitDamage > 0) + { + RigidBody@ body = node.GetComponent("RigidBody"); + if ((body.linearVelocity.length >= snowballMinHitSpeed)) + { + if (side != otherObject.side) + { + otherObject.Damage(this, hitDamage); + // Create a temporary node for the hit sound + SpawnSound(node.position, "Sounds/PlayerFistHit.wav", 0.2); + } + + hitDamage = 0; + } + } + if (duration > snowballObjectHitDuration) + duration = snowballObjectHitDuration; + } +} diff --git a/bin/Data/Scripts/NinjaSnowWar/SnowCrate.as b/bin/Data/Scripts/NinjaSnowWar/SnowCrate.as new file mode 100644 index 0000000..345acce --- /dev/null +++ b/bin/Data/Scripts/NinjaSnowWar/SnowCrate.as @@ -0,0 +1,35 @@ +#include "Scripts/NinjaSnowWar/GameObject.as" + + +const int snowcrateHealth = 5; +const int snowcratePoints = 250; + +class SnowCrate : GameObject +{ + SnowCrate() + { + health = maxHealth = snowcrateHealth; + } + + void Start() + { + SubscribeToEvent(node, "NodeCollision", "HandleNodeCollision"); + } + + void FixedUpdate(float timeStep) + { + if (health <= 0) + { + SpawnParticleEffect(node.position, "Particle/SnowExplosionBig.xml", 2); + SpawnObject(node.position, Quaternion(), "Potion"); + + VariantMap eventData; + eventData["Points"] = snowcratePoints; + eventData["Receiver"] = lastDamageCreatorID; + eventData["DamageSide"] = lastDamageSide; + SendEvent("Points", eventData); + + node.Remove(); + } + } +} \ No newline at end of file diff --git a/bin/Data/Scripts/Utilities/2D/Mover.as b/bin/Data/Scripts/Utilities/2D/Mover.as new file mode 100644 index 0000000..18207c8 --- /dev/null +++ b/bin/Data/Scripts/Utilities/2D/Mover.as @@ -0,0 +1,134 @@ +// 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 + +class Mover : ScriptObject +{ + float speed = 0.8f; + Array path; + int currentPathID = 1; + float emitTime = 0.0f; + float fightTimer = 0.0f; + float flip = 0.0f; + uint bufferSize = 0; + + void Load(Deserializer& deserializer) + { + bufferSize = deserializer.ReadUInt(); // Get buffer size + SetPathAttr(deserializer.ReadVectorBuffer(bufferSize)); + } + + void Save(Serializer& serializer) + { + serializer.WriteVectorBuffer(GetPathAttr(serializer)); + } + + void SetPathAttr(VectorBuffer buffer) + { + if (buffer.size == 0) + return; + + while (!buffer.eof) + path.Push(buffer.ReadVector2()); + } + + VectorBuffer GetPathAttr(Serializer& serializer) + { + VectorBuffer buffer = VectorBuffer(); + + for (uint i=0; i < path.length; ++i) + buffer.WriteVector2(path[i]); + + bufferSize = buffer.size; + serializer.WriteUInt(bufferSize); + + return buffer; + } + + void Update(float timeStep) + { + if (path.length < 2) + return; + + // Handle Orc states (idle/wounded/fighting) + if (node.name == "Orc") + { + AnimatedSprite2D@ animatedSprite = node.GetComponent("AnimatedSprite2D"); + String anim = "run"; + + // Handle wounded state + if (emitTime > 0.0f) + { + emitTime += timeStep; + anim = "dead"; + + // Handle dead + if (emitTime >= 3.0f) + { + node.Remove(); + return; + } + } + else + { + // Handle fighting state + if (fightTimer > 0.0f) + { + anim = "attack"; + flip = character2DNode.position.x - node.position.x; + fightTimer += timeStep; + if (fightTimer >= 3.0f) + fightTimer = 0.0f; // Reset + } + // Flip Orc animation according to speed, or player position when fighting + animatedSprite.flipX = flip >= 0.0f; + } + // Animate + if (animatedSprite.animation != anim) + animatedSprite.SetAnimation(anim); + } + + // Don't move if fighting or wounded + if (fightTimer > 0.0f || emitTime > 0.0f) + return; + + // Set direction and move to target + Vector2 dir = path[currentPathID] - node.position2D; + Vector2 dirNormal = dir.Normalized(); + node.Translate(Vector3(dirNormal.x, dirNormal.y, 0.0f) * Abs(speed) * timeStep); + flip = dir.x; + + // Check for new target to reach + if (Abs(dir.length) < 0.1f) + { + if (speed > 0.0f) + { + if (currentPathID + 1 < path.length) + currentPathID = currentPathID + 1; + else + { + // If loop, go to first waypoint, which equates to last one (and never reverse) + if (path[currentPathID] == path[0]) + { + currentPathID = 1; + return; + } + // Reverse path if not looping + currentPathID = currentPathID - 1; + speed = -speed; + } + } + else + { + if (currentPathID - 1 >= 0) + currentPathID = currentPathID - 1; + else + { + currentPathID = 1; + speed = -speed; + } + } + } + } +} \ No newline at end of file diff --git a/bin/Data/Scripts/Utilities/2D/Sample2D.as b/bin/Data/Scripts/Utilities/2D/Sample2D.as new file mode 100644 index 0000000..4ed569f --- /dev/null +++ b/bin/Data/Scripts/Utilities/2D/Sample2D.as @@ -0,0 +1,569 @@ +// 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 +// - Load/Save the scene +// - Set global variables +// - Set XML patch instructions for screen joystick + +#include "Scripts/Utilities/2D/Mover.as" + +float CAMERA_MIN_DIST = 0.1f; +float CAMERA_MAX_DIST = 6.0f; + +const float MOVE_SPEED = 23.0f; // Movement speed as world units per second +const float MOVE_SPEED_X = 1.5f; // Movement speed as world units per second +float MOVE_SPEED_SCALE = 1.0f; // Scaling factor based on tiles' aspect ratio + +const int LIFES = 3; +float zoom = 2.0f; // Speed is scaled according to zoom +String demoFilename = ""; +Node@ character2DNode; + + +void CreateCollisionShapesFromTMXObjects(Node@ tileMapNode, TileMapLayer2D@ tileMapLayer, TileMapInfo2D@ info) +{ + // Create rigid body to the root node + RigidBody2D@ body = tileMapNode.CreateComponent("RigidBody2D"); + body.bodyType = BT_STATIC; + + // Generate physics collision shapes and rigid bodies from the tmx file's objects located in "Physics" layer + for (uint i = 0; i < tileMapLayer.numObjects; ++i) + { + TileMapObject2D@ tileMapObject = tileMapLayer.GetObject(i); // Get physics objects + + // Create collision shape from tmx object + switch (tileMapObject.objectType) + { + case OT_RECTANGLE: + CreateRectangleShape(tileMapNode, tileMapObject, tileMapObject.size, info); + continue; + case OT_ELLIPSE: + CreateCircleShape(tileMapNode, tileMapObject, tileMapObject.size.x / 2, info); // Ellipse is built as a Circle shape as it doesn't exist in Box2D + continue; + case OT_POLYGON: + CreatePolygonShape(tileMapNode, tileMapObject); + continue; + case OT_POLYLINE: + CreatePolyLineShape(tileMapNode, tileMapObject); + continue; + default: + continue; + } + } +} + +CollisionBox2D@ CreateRectangleShape(Node@ node, TileMapObject2D@ object, Vector2 size, TileMapInfo2D@ info) +{ + CollisionBox2D@ shape = node.CreateComponent("CollisionBox2D"); + shape.size = size; + if (info.orientation == O_ORTHOGONAL) + shape.center = object.position + size / 2; + else + { + shape.center = object.position + Vector2(info.tileWidth / 2, 0.0f); + shape.angle = 45.0f; // If our tile map is isometric then shape is losange + } + shape.friction = 0.8f; + if (object.HasProperty("Friction")) + shape.friction = object.GetProperty("Friction").ToFloat(); + return shape; +} + +CollisionCircle2D@ CreateCircleShape(Node@ node, TileMapObject2D@ object, float radius, TileMapInfo2D@ info) +{ + CollisionCircle2D@ shape = node.CreateComponent("CollisionCircle2D"); + Vector2 size = object.size; + shape.radius = radius; + if (info.orientation == O_ORTHOGONAL) + shape.center = object.position + size / 2; + else + { + shape.center = object.position + Vector2(info.tileWidth / 2, 0.0f); + } + shape.friction = 0.8f; + if (object.HasProperty("Friction")) + shape.friction = object.GetProperty("Friction").ToFloat(); + return shape; +} + +CollisionPolygon2D@ CreatePolygonShape(Node@ node, TileMapObject2D@ object) +{ + CollisionPolygon2D@ shape = node.CreateComponent("CollisionPolygon2D"); + uint numVertices = object.numPoints; + shape.vertexCount = numVertices; + for (uint i = 0; i < numVertices; ++i) + shape.SetVertex(i, object.GetPoint(i)); + shape.friction = 0.8f; + if (object.HasProperty("Friction")) + shape.friction = object.GetProperty("Friction").ToFloat(); + return shape; +} + +CollisionChain2D@ CreatePolyLineShape(Node@ node, TileMapObject2D@ object) +{ + CollisionChain2D@ shape = node.CreateComponent("CollisionChain2D"); + uint numVertices = object.numPoints; + shape.vertexCount = numVertices; + for (uint i = 0; i < numVertices; ++i) + shape.SetVertex(i, object.GetPoint(i)); + shape.friction = 0.8f; + if (object.HasProperty("Friction")) + shape.friction = object.GetProperty("Friction").ToFloat(); + return shape; +} + +void CreateCharacter(TileMapInfo2D@ info, bool createObject, float friction, Vector3 position, float scale) +{ + character2DNode = scene_.CreateChild("Imp"); + character2DNode.position = position; + character2DNode.SetScale(scale); + AnimatedSprite2D@ animatedSprite = character2DNode.CreateComponent("AnimatedSprite2D"); + + AnimationSet2D@ spriterAnimationSet = cache.GetResource("AnimationSet2D", "Urho2D/imp/imp.scml"); + if (spriterAnimationSet is null) + return; + + animatedSprite.animationSet = spriterAnimationSet; + animatedSprite.SetAnimation("idle"); // Get scml file and Play "idle" anim + animatedSprite.layer = 3; // Put character over tile map (which is on layer 0) and over Orcs (which are on layer 2) + RigidBody2D@ characterBody = character2DNode.CreateComponent("RigidBody2D"); + characterBody.bodyType = BT_DYNAMIC; + characterBody.allowSleep = false; + CollisionCircle2D@ shape = character2DNode.CreateComponent("CollisionCircle2D"); + shape.radius = 1.1f; // Set shape size + shape.friction = friction; // Set friction + shape.restitution = 0.1f; // Bounce + if (createObject) + character2DNode.CreateScriptObject(scriptFile, "Character2D"); // Create a ScriptObject to handle character behavior + + // Scale character's speed on the Y axis according to tiles' aspect ratio (for isometric only) + MOVE_SPEED_SCALE = info.tileHeight / info.tileWidth; +} + +Node@ CreateTrigger() +{ + Node@ node = scene_.CreateChild(); // Clones will be renamed according to object type + RigidBody2D@ body = node.CreateComponent("RigidBody2D"); + body.bodyType = BT_STATIC; + CollisionBox2D@ shape = node.CreateComponent("CollisionBox2D"); // Create box shape + shape.trigger = true; + return node; +} + +Node@ CreateEnemy() +{ + Node@ node = scene_.CreateChild("Enemy"); + StaticSprite2D@ staticSprite = node.CreateComponent("StaticSprite2D"); + staticSprite.sprite = cache.GetResource("Sprite2D", "Urho2D/Aster.png"); + RigidBody2D@ body = node.CreateComponent("RigidBody2D"); + body.bodyType = BT_STATIC; + CollisionCircle2D@ shape = node.CreateComponent("CollisionCircle2D"); // Create circle shape + shape.radius = 0.25f; // Set radius + return node; +} + +Node@ CreateOrc() +{ + Node@ node = scene_.CreateChild("Orc"); + node.scale = character2DNode.scale; // Use same scale as player character + AnimatedSprite2D@ animatedSprite = node.CreateComponent("AnimatedSprite2D"); + + AnimationSet2D@ spriterAnimationSet = cache.GetResource("AnimationSet2D", "Urho2D/Orc/Orc.scml"); + if (spriterAnimationSet is null) + return null; + + animatedSprite.animationSet = spriterAnimationSet; + animatedSprite.SetAnimation("run"); // Get scml file and Play "run" anim + animatedSprite.layer = 2; // Make orc always visible + RigidBody2D@ body = node.CreateComponent("RigidBody2D"); + CollisionCircle2D@ shape = node.CreateComponent("CollisionCircle2D"); // Create circle shape + shape.radius = 1.3f; // Set shape size + shape.trigger = true; + return node; +} + +Node@ CreateCoin() +{ + Node@ node = scene_.CreateChild("Coin"); + node.SetScale(0.5); + AnimatedSprite2D@ animatedSprite = node.CreateComponent("AnimatedSprite2D"); + animatedSprite.layer = 4; + AnimationSet2D@ spriterAnimationSet = cache.GetResource("AnimationSet2D", "Urho2D/GoldIcon.scml"); + if (spriterAnimationSet is null) + return null; + + animatedSprite.animationSet = spriterAnimationSet; + animatedSprite.SetAnimation("idle"); // Get scml file and Play "idle" anim + RigidBody2D@ body = node.CreateComponent("RigidBody2D"); + body.bodyType = BT_STATIC; + CollisionCircle2D@ shape = node.CreateComponent("CollisionCircle2D"); // Create circle shape + shape.radius = 0.32f; // Set radius + shape.trigger = true; + return node; +} + +Node@ CreateMovingPlatform() +{ + Node@ node = scene_.CreateChild("MovingPlatform"); + node.scale = Vector3(3.0f, 1.0f, 0.0f); + StaticSprite2D@ staticSprite = node.CreateComponent("StaticSprite2D"); + staticSprite.sprite = cache.GetResource("Sprite2D", "Urho2D/Box.png"); + RigidBody2D@ body = node.CreateComponent("RigidBody2D"); + body.bodyType = BT_STATIC; + CollisionBox2D@ shape = node.CreateComponent("CollisionBox2D"); // Create box shape + shape.size = Vector2(0.32f, 0.32f); // Set box size + shape.friction = 0.8f; // Set friction + return node; +} + +void PopulateMovingEntities(TileMapLayer2D@ movingEntitiesLayer) +{ + // Create enemy, Orc and moving platform nodes (will be cloned at each placeholder) + Node@ enemyNode = CreateEnemy(); + Node@ orcNode = CreateOrc(); + Node@ platformNode = CreateMovingPlatform(); + + // Instantiate enemies and moving platforms at each placeholder (placeholders are Poly Line objects defining a path from points) + for (uint i=0; i < movingEntitiesLayer.numObjects; ++i) + { + // Get placeholder object + TileMapObject2D@ movingObject = movingEntitiesLayer.GetObject(i); // Get placeholder object + if (movingObject.objectType == OT_POLYLINE) + { + // Clone the moving entity node and position it at placeholder point + Node@ movingClone; + Vector2 offset = Vector2(0.0f, 0.0f); + if (movingObject.type == "Enemy") + { + movingClone = enemyNode.Clone(); + offset = Vector2(0.0f, -0.32f); + } + else if (movingObject.type == "Orc") + movingClone = orcNode.Clone(); + else if (movingObject.type == "MovingPlatform") + movingClone = platformNode.Clone(); + else + continue; + movingClone.position2D = movingObject.GetPoint(0) + offset; + + // Create script object that handles entity translation along its path (load from file included) + Mover@ mover = cast(movingClone.CreateScriptObject(scriptFile, "Mover")); + + // Set path from points + mover.path = CreatePathFromPoints(movingObject, offset); + + // Override default speed + if (movingObject.HasProperty("Speed")) + mover.speed = movingObject.GetProperty("Speed").ToFloat(); + } + } + + // Remove nodes used for cloning purpose + enemyNode.Remove(); + orcNode.Remove(); + platformNode.Remove(); +} + +void PopulateCoins(TileMapLayer2D@ coinsLayer) +{ + // Create coin (will be cloned at each placeholder) + Node@ coinNode = CreateCoin(); + + // Instantiate coins to pick at each placeholder + for (uint i=0; i < coinsLayer.numObjects; ++i) + { + TileMapObject2D@ coinObject = coinsLayer.GetObject(i); // Get placeholder object + Node@ coinClone = coinNode.Clone(); + coinClone.position2D = coinObject.position + coinObject.size / 2 + Vector2(0.0f, 0.16f); + } + + // Init coins counters + Character2D@ character = cast(character2DNode.scriptObject); + character.remainingCoins = coinsLayer.numObjects; + character.maxCoins = coinsLayer.numObjects; + + // Remove node used for cloning purpose + coinNode.Remove(); +} + +void PopulateTriggers(TileMapLayer2D@ triggersLayer) +{ + // Create trigger node (will be cloned at each placeholder) + Node@ triggerNode = CreateTrigger(); + + // Instantiate triggers at each placeholder (Rectangle objects) + for (uint i=0; i < triggersLayer.numObjects; ++i) + { + TileMapObject2D@ triggerObject = triggersLayer.GetObject(i); // Get placeholder object + if (triggerObject.objectType == OT_RECTANGLE) + { + Node@ triggerClone = triggerNode.Clone(); + triggerClone.name = triggerObject.type; + CollisionBox2D@ shape = triggerClone.GetComponent("CollisionBox2D"); + shape.size = triggerObject.size; + triggerClone.position2D = triggerObject.position + triggerObject.size / 2; + } + } +} + +void Zoom(Camera@ camera) +{ + if (input.mouseMoveWheel != 0) + camera.zoom = Clamp(camera.zoom + input.mouseMoveWheel * 0.1, CAMERA_MIN_DIST, CAMERA_MAX_DIST); + + if (input.keyDown[KEY_PAGEUP]) + { + zoom = Clamp(camera.zoom * 1.01f, CAMERA_MIN_DIST, CAMERA_MAX_DIST); + camera.zoom = zoom; + } + + if (input.keyDown[KEY_PAGEDOWN]) + { + zoom = Clamp(camera.zoom * 0.99f, CAMERA_MIN_DIST, CAMERA_MAX_DIST); + camera.zoom = zoom; + } +} + +Vector2[] CreatePathFromPoints(TileMapObject2D@ object, Vector2 offset) +{ + Array path; + for (uint i=0; i < object.numPoints; ++i) + path.Push(object.GetPoint(i) + offset); + return path; +} + +void CreateUIContent(String demoTitle) +{ + // Set the default UI style and font + ui.root.defaultStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml"); + Font@ 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 + BorderImage@ 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); + Text@ coinsText = coinsUI.CreateChild("Text", "CoinsText"); + coinsText.SetAlignment(HA_CENTER, VA_CENTER); + coinsText.SetFont(font, 24); + coinsText.textEffect = TE_SHADOW; + coinsText.text = cast(character2DNode.scriptObject).remainingCoins; + + // Create the UI for displaying the remaining lifes + BorderImage@ 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); + Text@ 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 + Window@ 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 + BorderImage@ 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); + Text@ titleText = title.CreateChild("Text", "TitleText"); + titleText.SetAlignment(HA_CENTER, VA_CENTER); + titleText.SetFont(font, 24); + titleText.text = demoTitle; + + // Create the image + BorderImage@ 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 + Button@ 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); + Text@ 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 + Button@ 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); + Text@ 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 + Text@ 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; +} + +void HandleExitButton() +{ + engine.Exit(); +} + +void HandlePlayButton() +{ + // Remove fullscreen UI and unfreeze the scene + if (ui.root.GetChild("FullUI", true) !is null) + { + ui.root.GetChild("FullUI", true).Remove(); + scene_.updateEnabled = true; + } + else + { + // Reload scene + ReloadScene(true); + } + + // Hide Instructions and Play/Exit buttons + Text@ instructionText = ui.root.GetChild("Instructions", true); + instructionText.text = ""; + Button@ exitButton = ui.root.GetChild("ExitButton", true); + exitButton.visible = false; + Button@ playButton = ui.root.GetChild("PlayButton", true); + playButton.visible = false; + + // Hide mouse cursor + input.mouseVisible = false; +} + +void SaveScene(bool initial) +{ + String filename = demoFilename; + if (!initial) + filename = demoFilename + "InGame"; + + File saveFile(fileSystem.programDir + "Data/Scenes/" + filename + ".xml", FILE_WRITE); + scene_.SaveXML(saveFile); +} + +void ReloadScene(bool reInit) +{ + String filename = demoFilename; + if (!reInit) + filename = demoFilename + "InGame"; + + File loadFile(fileSystem.programDir + "Data/Scenes/" + filename + ".xml", FILE_READ); + scene_.LoadXML(loadFile); + // After loading we have to reacquire the character2D 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 is null) + return; + + // Set what value to use depending whether reload is requested from 'PLAY' button (reInit=true) or 'F7' key (reInit=false) + Character2D@ character = cast(character2DNode.scriptObject); + int lifes = character.remainingLifes; + int coins = character.remainingCoins; + if (reInit) + { + lifes = LIFES; + coins = character.maxCoins; + } + + // Update lifes UI and variable + Text@ lifeText = ui.root.GetChild("LifeText", true); + lifeText.text = lifes; + character.remainingLifes = lifes; + + // Update coins UI and variable + Text@ coinsText = ui.root.GetChild("CoinsText", true); + coinsText.text = coins; + character.remainingCoins = coins; +} + +void SpawnEffect(Node@ node) +{ + Node@ particleNode = node.CreateChild("Emitter"); + particleNode.SetScale(0.5 / node.scale.x); + ParticleEmitter2D@ particleEmitter = particleNode.CreateComponent("ParticleEmitter2D"); + particleEmitter.effect = cache.GetResource("ParticleEffect2D", "Urho2D/sun.pex"); + particleEmitter.layer = 2; +} + +void PlaySound(String soundName) +{ + Node@ soundNode = scene_.CreateChild("Sound"); + SoundSource@ source = soundNode.CreateComponent("SoundSource"); + source.Play(cache.GetResource("Sound", "Sounds/" + soundName)); +} + +void CreateBackgroundSprite(TileMapInfo2D@ info, float scale, String texture, bool animate) +{ + Node@ node = scene_.CreateChild("Background"); + node.position = Vector3(info.mapWidth, info.mapHeight, 0) / 2; + node.SetScale(scale); + StaticSprite2D@ sprite = node.CreateComponent("StaticSprite2D"); + sprite.sprite = cache.GetResource("Sprite2D", texture); + SetRandomSeed(time.systemTime); // Randomize from system clock + sprite.color = Color(Random(0.0f, 1.0f), Random(0.0f, 1.0f), Random(0.0f, 1.0f), 1.0f); + + // Create rotation animation + if (animate) + { + ValueAnimation@ animation = ValueAnimation(); + animation.SetKeyFrame(0, Variant(Quaternion(0.0f, 0.0f, 0.0f))); + animation.SetKeyFrame(1, Variant(Quaternion(0.0f, 0.0f, 180.0f))); + animation.SetKeyFrame(2, Variant(Quaternion(0.0f, 0.0f, 0.0f))); + node.SetAttributeAnimation("Rotation", animation, WM_LOOP, 0.05f); + } +} + + +// Create XML patch instructions for screen joystick layout specific to this sample app +String patchInstructions = + "" + + " " + + " Fight" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " Jump" + + " " + + " " + + " " + + " " + + " " + + " " + + ""; diff --git a/bin/Data/Scripts/Utilities/Network.as b/bin/Data/Scripts/Utilities/Network.as new file mode 100644 index 0000000..a14bfd1 --- /dev/null +++ b/bin/Data/Scripts/Utilities/Network.as @@ -0,0 +1,39 @@ +bool runServer = false; +bool runClient = false; +String serverAddress; +uint16 serverPort = 1234; +String userName; +bool nobgm = false; + +void ParseNetworkArguments() +{ + Array@ arguments = GetArguments(); + + for (uint i = 0; i < arguments.length; ++i) + { + String argument = arguments[i].ToLower(); + if (argument[0] == '-') + { + argument = argument.Substring(1); + if (argument == "server") + { + runServer = true; + runClient = false; + } + else if (argument == "address") + { + runClient = true; + runServer = false; + serverAddress = arguments[i + 1]; + ++i; + } + else if (argument == "username") + { + userName = arguments[i + 1]; + ++i; + } + else if (argument == "nobgm") + nobgm = true; + } + } +} diff --git a/bin/Data/Scripts/Utilities/Rotator.as b/bin/Data/Scripts/Utilities/Rotator.as new file mode 100644 index 0000000..9c091f0 --- /dev/null +++ b/bin/Data/Scripts/Utilities/Rotator.as @@ -0,0 +1,16 @@ +// Rotator script object class. Script objects to be added to a scene node must implement the empty ScriptObject interface +class Rotator : ScriptObject +{ + Vector3 rotationSpeed; + + void SetRotationSpeed(const Vector3&in speed) + { + rotationSpeed = speed; + } + + // 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)); + } +} diff --git a/bin/Data/Scripts/Utilities/Sample.as b/bin/Data/Scripts/Utilities/Sample.as new file mode 100644 index 0000000..ed86b71 --- /dev/null +++ b/bin/Data/Scripts/Utilities/Sample.as @@ -0,0 +1,339 @@ +// 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) + +Sprite@ logoSprite; +Scene@ scene_; +uint screenJoystickIndex = M_MAX_UNSIGNED; // Screen joystick index for navigational controls (mobile platforms only) +uint screenJoystickSettingsIndex = M_MAX_UNSIGNED; // Screen joystick index for settings (mobile platforms only) +bool touchEnabled = false; // Flag to indicate whether touch input has been enabled +bool paused = false; // Pause flag +bool drawDebug = false; // Draw debug geometry flag +Node@ cameraNode; // Camera scene node +float yaw = 0.0f; // Camera yaw angle +float pitch = 0.0f; // Camera pitch angle +const float TOUCH_SENSITIVITY = 2; +MouseMode useMouseMode_ = MM_ABSOLUTE; + +void SampleStart() +{ + if (GetPlatform() == "Android" || GetPlatform() == "iOS" || input.touchEmulation) + // On mobile platform, enable touch by adding a screen joystick + InitTouchInput(); + else if (input.numJoysticks == 0) + // On desktop platform, do not detect touch when we already got a joystick + SubscribeToEvent("TouchBegin", "HandleTouchBegin"); + + // 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"); +} + +void InitTouchInput() +{ + touchEnabled = true; + + XMLFile@ layout = cache.GetResource("XMLFile", "UI/ScreenJoystick_Samples.xml"); + if (!patchInstructions.empty) + { + // Patch the screen joystick layout further on demand + XMLFile@ patchFile = XMLFile(); + if (patchFile.FromString(patchInstructions)) + layout.Patch(patchFile); + } + screenJoystickIndex = input.AddScreenJoystick(layout, cache.GetResource("XMLFile", "UI/DefaultStyle.xml")); + input.screenJoystickVisible[0] = true; +} + +void SampleInitMouseMode(MouseMode mode) +{ + useMouseMode_ = mode; + + if (GetPlatform() != "Web") + { + if (useMouseMode_ == MM_FREE) + input.mouseVisible = true; + + if (useMouseMode_ != MM_ABSOLUTE) + { + input.mouseMode = useMouseMode_; + if (console.visible) + input.SetMouseMode(MM_ABSOLUTE, true); + } + } + else + { + input.mouseVisible = true; + SubscribeToEvent("MouseButtonDown", "HandleMouseModeRequest"); + SubscribeToEvent("MouseModeChanged", "HandleMouseModeChange"); + } +} + +void SetLogoVisible(bool enable) +{ + if (logoSprite !is null) + logoSprite.visible = enable; +} + +void CreateLogo() +{ + // Get logo texture + Texture2D@ logoTexture = cache.GetResource("Texture2D", "Textures/FishBoneLogo.png"); + if (logoTexture is null) + return; + + // Create logo sprite and add to the UI layout + logoSprite = ui.root.CreateChild("Sprite"); + + // Set logo sprite texture + logoSprite.texture = logoTexture; + + int textureWidth = logoTexture.width; + int textureHeight = logoTexture.height; + + // Set logo sprite scale + logoSprite.SetScale(256.0f / textureWidth); + + // Set logo sprite size + logoSprite.SetSize(textureWidth, textureHeight); + + // Set logo sprite hot spot + logoSprite.SetHotSpot(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.9f; + + // Set a low priority for the logo so that other UI elements can be drawn on top + logoSprite.priority = -100; +} + +void SetWindowTitleAndIcon() +{ + Image@ icon = cache.GetResource("Image", "Textures/UrhoIcon.png"); + graphics.windowIcon = icon; + graphics.windowTitle = "Urho3D Sample"; +} + +void CreateConsoleAndDebugHud() +{ + // Get default style + XMLFile@ xmlFile = cache.GetResource("XMLFile", "UI/DefaultStyle.xml"); + if (xmlFile is null) + return; + + // Create console + Console@ console = engine.CreateConsole(); + console.defaultStyle = xmlFile; + console.background.opacity = 0.8f; + + // Create debug HUD + DebugHud@ debugHud = engine.CreateDebugHud(); + debugHud.defaultStyle = xmlFile; +} + +void HandleKeyUp(StringHash eventType, VariantMap& eventData) +{ + int key = eventData["Key"].GetInt(); + + // Close console (if open) or exit when ESC is pressed + if (key == KEY_ESCAPE) + { + if (console.visible) + console.visible = false; + else + { + if (GetPlatform() == "Web") + { + input.mouseVisible = true; + if (useMouseMode_ != MM_ABSOLUTE) + input.mouseMode = MM_FREE; + } + else + engine.Exit(); + } + } +} + +void HandleKeyDown(StringHash eventType, VariantMap& eventData) +{ + int key = eventData["Key"].GetInt(); + + // Toggle console with F1 + if (key == KEY_F1) + console.Toggle(); + + // Toggle debug HUD with F2 + else if (key == KEY_F2) + debugHud.ToggleAll(); + + // Common rendering quality controls, only when UI has no focused element + else if (ui.focusElement is null) + { + // Preferences / Pause + if (key == KEY_SELECT && touchEnabled) + { + paused = !paused; + + if (screenJoystickSettingsIndex == M_MAX_UNSIGNED) + { + // Lazy initialization + screenJoystickSettingsIndex = input.AddScreenJoystick(cache.GetResource("XMLFile", "UI/ScreenJoystickSettings_Samples.xml"), cache.GetResource("XMLFile", "UI/DefaultStyle.xml")); + } + else + input.screenJoystickVisible[screenJoystickSettingsIndex] = paused; + } + + // Texture quality + else if (key == '1') + { + int quality = renderer.textureQuality; + ++quality; + if (quality > QUALITY_HIGH) + quality = QUALITY_LOW; + renderer.textureQuality = quality; + } + + // Material quality + else if (key == '2') + { + int quality = renderer.materialQuality; + ++quality; + if (quality > QUALITY_HIGH) + quality = QUALITY_LOW; + renderer.materialQuality = quality; + } + + // Specular lighting + else if (key == '3') + renderer.specularLighting = !renderer.specularLighting; + + // Shadow rendering + else if (key == '4') + renderer.drawShadows = !renderer.drawShadows; + + // Shadow map resolution + else if (key == '5') + { + int shadowMapSize = renderer.shadowMapSize; + shadowMapSize *= 2; + if (shadowMapSize > 2048) + shadowMapSize = 512; + renderer.shadowMapSize = shadowMapSize; + } + + // Shadow depth and filtering quality + else if (key == '6') + { + ShadowQuality quality = renderer.shadowQuality; + quality = ShadowQuality(quality + 1); + if (quality > SHADOWQUALITY_BLUR_VSM) + quality = SHADOWQUALITY_SIMPLE_16BIT; + renderer.shadowQuality = quality; + } + + // Occlusion culling + else if (key == '7') + { + bool occlusion = renderer.maxOccluderTriangles > 0; + occlusion = !occlusion; + renderer.maxOccluderTriangles = occlusion ? 5000 : 0; + } + + // Instancing + else if (key == '8') + renderer.dynamicInstancing = !renderer.dynamicInstancing; + + // Take screenshot + else if (key == '9') + { + Image@ screenshot = Image(); + graphics.TakeScreenShot(screenshot); + // Here we save in the Data folder with date and time appended + screenshot.SavePNG(fileSystem.programDir + "Data/Screenshot_" + + time.timeStamp.Replaced(':', '_').Replaced('.', '_').Replaced(' ', '_') + ".png"); + } + } +} + +void HandleSceneUpdate(StringHash eventType, VariantMap& eventData) +{ + // Move the camera by touch, if the camera node is initialized by descendant sample class + if (touchEnabled && cameraNode !is null) + { + for (uint i = 0; i < input.numTouches; ++i) + { + TouchState@ state = input.touches[i]; + if (state.touchedElement is null) // Touch on empty space + { + if (state.delta.x !=0 || state.delta.y !=0) + { + Camera@ camera = cameraNode.GetComponent("Camera"); + if (camera is null) + return; + + yaw += TOUCH_SENSITIVITY * camera.fov / graphics.height * state.delta.x; + 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.rotation = Quaternion(pitch, yaw, 0.0f); + } + else + { + // Move the cursor to the touch position + Cursor@ cursor = ui.cursor; + if (cursor !is null && cursor.visible) + cursor.position = state.position; + } + } + } + } +} + +void HandleTouchBegin(StringHash eventType, VariantMap& eventData) +{ + // On some platforms like Windows the presence of touch input can only be detected dynamically + InitTouchInput(); + UnsubscribeFromEvent("TouchBegin"); +} + +// If the user clicks the canvas, attempt to switch to relative mouse mode on web platform +void HandleMouseModeRequest(StringHash eventType, VariantMap& eventData) +{ + if (console !is null && console.visible) + return; + + if (useMouseMode_ == MM_ABSOLUTE) + input.mouseVisible = false; + else if (useMouseMode_ == MM_FREE) + input.mouseVisible = true; + + input.mouseMode = useMouseMode_; +} + +void HandleMouseModeChange(StringHash eventType, VariantMap& eventData) +{ + bool mouseLocked = eventData["MouseLocked"].GetBool(); + input.SetMouseVisible(!mouseLocked); +} diff --git a/bin/Data/Scripts/Utilities/Touch.as b/bin/Data/Scripts/Utilities/Touch.as new file mode 100644 index 0000000..e0233a7 --- /dev/null +++ b/bin/Data/Scripts/Utilities/Touch.as @@ -0,0 +1,63 @@ +// Mobile framework for Android/iOS +// Gamepad from NinjaSnowWar +// 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 + +const float GYROSCOPE_THRESHOLD = 0.1; +const float CAMERA_MIN_DIST = 1.0f; +const float CAMERA_MAX_DIST = 20.0f; + +bool zoom = false; +bool useGyroscope = false; +float cameraDistance = 5.0; + +void UpdateTouches(Controls& controls) // Called from HandleUpdate +{ + zoom = false; // reset bool + + // Zoom in/out + if (input.numTouches == 2) + { + TouchState@ touch1 = input.touches[0]; + TouchState@ touch2 = input.touches[1]; + + // Check for zoom pattern (touches moving in opposite directions) + if (touch1.touchedElement is null && touch2.touchedElement is null && ((touch1.delta.y > 0 && touch2.delta.y < 0) || (touch1.delta.y < 0 && touch2.delta.y > 0))) + zoom = true; + else + zoom = false; + + if (zoom) + { + int sens = 0; + // Check for zoom direction (in/out) + if (Abs(touch1.position.y - touch2.position.y) > Abs(touch1.lastPosition.y - touch2.lastPosition.y)) + sens = -1; + else + sens = 1; + 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] + } + } + + // Gyroscope (emulated by SDL through a virtual joystick) + if (input.numJoysticks > 0) // numJoysticks = 1 on iOS & Android + { + JoystickState@ joystick = input.joysticksByIndex[0]; + if (joystick.numAxes >= 2) + { + if (joystick.axisPosition[0] < -GYROSCOPE_THRESHOLD) + controls.Set(CTRL_LEFT, true); + if (joystick.axisPosition[0] > GYROSCOPE_THRESHOLD) + controls.Set(CTRL_RIGHT, true); + if (joystick.axisPosition[1] < -GYROSCOPE_THRESHOLD) + controls.Set(CTRL_FORWARD, true); + if (joystick.axisPosition[1] > GYROSCOPE_THRESHOLD) + controls.Set(CTRL_BACK, true); + } + } +} diff --git a/bin/Data/Sounds/BigExplosion.wav b/bin/Data/Sounds/BigExplosion.wav new file mode 100644 index 0000000..dd43f6a Binary files /dev/null and b/bin/Data/Sounds/BigExplosion.wav differ diff --git a/bin/Data/Sounds/NutThrow.wav b/bin/Data/Sounds/NutThrow.wav new file mode 100644 index 0000000..1f407fa Binary files /dev/null and b/bin/Data/Sounds/NutThrow.wav differ diff --git a/bin/Data/Sounds/PlayerFist.wav b/bin/Data/Sounds/PlayerFist.wav new file mode 100644 index 0000000..89d8c4f Binary files /dev/null and b/bin/Data/Sounds/PlayerFist.wav differ diff --git a/bin/Data/Sounds/PlayerFistHit.wav b/bin/Data/Sounds/PlayerFistHit.wav new file mode 100644 index 0000000..d50d7ee Binary files /dev/null and b/bin/Data/Sounds/PlayerFistHit.wav differ diff --git a/bin/Data/Sounds/PlayerLand.wav b/bin/Data/Sounds/PlayerLand.wav new file mode 100644 index 0000000..2e4e411 Binary files /dev/null and b/bin/Data/Sounds/PlayerLand.wav differ diff --git a/bin/Data/Sounds/Powerup.wav b/bin/Data/Sounds/Powerup.wav new file mode 100644 index 0000000..0513b4a Binary files /dev/null and b/bin/Data/Sounds/Powerup.wav differ diff --git a/bin/Data/Sounds/SmallExplosion.wav b/bin/Data/Sounds/SmallExplosion.wav new file mode 100644 index 0000000..4868ce9 Binary files /dev/null and b/bin/Data/Sounds/SmallExplosion.wav differ diff --git a/bin/Data/Spark/Effects/Bubbles.xml b/bin/Data/Spark/Effects/Bubbles.xml new file mode 100644 index 0000000..24ab1be --- /dev/null +++ b/bin/Data/Spark/Effects/Bubbles.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Spark/Effects/Fire1.xml b/bin/Data/Spark/Effects/Fire1.xml new file mode 100644 index 0000000..84853ce --- /dev/null +++ b/bin/Data/Spark/Effects/Fire1.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Spark/Effects/FireAtlas.xml b/bin/Data/Spark/Effects/FireAtlas.xml new file mode 100644 index 0000000..7016f34 --- /dev/null +++ b/bin/Data/Spark/Effects/FireAtlas.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Spark/Effects/MagicBall.xml b/bin/Data/Spark/Effects/MagicBall.xml new file mode 100644 index 0000000..cf61547 --- /dev/null +++ b/bin/Data/Spark/Effects/MagicBall.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Spark/Effects/Spark1.xml b/bin/Data/Spark/Effects/Spark1.xml new file mode 100644 index 0000000..37c367f --- /dev/null +++ b/bin/Data/Spark/Effects/Spark1.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Spark/Effects/Vortex.xml b/bin/Data/Spark/Effects/Vortex.xml new file mode 100644 index 0000000..a979511 --- /dev/null +++ b/bin/Data/Spark/Effects/Vortex.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Spark/Materials/SparkParticleAddAlpha.xml b/bin/Data/Spark/Materials/SparkParticleAddAlpha.xml new file mode 100644 index 0000000..1036b41 --- /dev/null +++ b/bin/Data/Spark/Materials/SparkParticleAddAlpha.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/bin/Data/Spark/Materials/SparkParticleAlpha.xml b/bin/Data/Spark/Materials/SparkParticleAlpha.xml new file mode 100644 index 0000000..9fd35f1 --- /dev/null +++ b/bin/Data/Spark/Materials/SparkParticleAlpha.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/bin/Data/Spark/Techniques/DiffUnlitParticleAddAlpha.xml b/bin/Data/Spark/Techniques/DiffUnlitParticleAddAlpha.xml new file mode 100644 index 0000000..b70e550 --- /dev/null +++ b/bin/Data/Spark/Techniques/DiffUnlitParticleAddAlpha.xml @@ -0,0 +1,3 @@ + + + diff --git a/bin/Data/Spark/Techniques/DiffUnlitParticleAlpha.xml b/bin/Data/Spark/Techniques/DiffUnlitParticleAlpha.xml new file mode 100644 index 0000000..2780261 --- /dev/null +++ b/bin/Data/Spark/Techniques/DiffUnlitParticleAlpha.xml @@ -0,0 +1,3 @@ + + + diff --git a/bin/Data/Spark/Textures/Arrow.png b/bin/Data/Spark/Textures/Arrow.png new file mode 100644 index 0000000..e0f66e3 Binary files /dev/null and b/bin/Data/Spark/Textures/Arrow.png differ diff --git a/bin/Data/Spark/Textures/FlameStill6.png b/bin/Data/Spark/Textures/FlameStill6.png new file mode 100644 index 0000000..a8d6de7 Binary files /dev/null and b/bin/Data/Spark/Textures/FlameStill6.png differ diff --git a/bin/Data/Spark/Textures/Flare1.png b/bin/Data/Spark/Textures/Flare1.png new file mode 100644 index 0000000..d1fccc4 Binary files /dev/null and b/bin/Data/Spark/Textures/Flare1.png differ diff --git a/bin/Data/Spark/Textures/Fog-Group.png b/bin/Data/Spark/Textures/Fog-Group.png new file mode 100644 index 0000000..5c6e9f7 Binary files /dev/null and b/bin/Data/Spark/Textures/Fog-Group.png differ diff --git a/bin/Data/Spark/Textures/bubble_1_256.png b/bin/Data/Spark/Textures/bubble_1_256.png new file mode 100644 index 0000000..6f8a67f Binary files /dev/null and b/bin/Data/Spark/Textures/bubble_1_256.png differ diff --git a/bin/Data/Spark/Textures/explosion.bmp b/bin/Data/Spark/Textures/explosion.bmp new file mode 100644 index 0000000..401b192 Binary files /dev/null and b/bin/Data/Spark/Textures/explosion.bmp differ diff --git a/bin/Data/Spark/Textures/flash.bmp b/bin/Data/Spark/Textures/flash.bmp new file mode 100644 index 0000000..5aaf5b0 Binary files /dev/null and b/bin/Data/Spark/Textures/flash.bmp differ diff --git a/bin/Data/Spark/Textures/point.bmp b/bin/Data/Spark/Textures/point.bmp new file mode 100644 index 0000000..7c49f8c Binary files /dev/null and b/bin/Data/Spark/Textures/point.bmp differ diff --git a/bin/Data/Spark/Textures/sprite-flame.jpg b/bin/Data/Spark/Textures/sprite-flame.jpg new file mode 100644 index 0000000..d357ce9 Binary files /dev/null and b/bin/Data/Spark/Textures/sprite-flame.jpg differ diff --git a/bin/Data/Spark/Textures/wave.bmp b/bin/Data/Spark/Textures/wave.bmp new file mode 100644 index 0000000..b9519af Binary files /dev/null and b/bin/Data/Spark/Textures/wave.bmp differ diff --git a/bin/Data/StringsDe.json b/bin/Data/StringsDe.json new file mode 100644 index 0000000..a5b103d --- /dev/null +++ b/bin/Data/StringsDe.json @@ -0,0 +1,14 @@ +{ + "lang":{ + "de":"Sprache: Deutsch" + }, + "quit":{ + "de":"Verlassen" + }, + "Press this button":{ + "de":"Drücken Sie diese Taste" + }, + "title":{ + "de":"Titel" + } +} diff --git a/bin/Data/StringsEnRu.json b/bin/Data/StringsEnRu.json new file mode 100644 index 0000000..b71453a --- /dev/null +++ b/bin/Data/StringsEnRu.json @@ -0,0 +1,18 @@ +{ + "lang":{ + "en":"Language: English", + "ru":"Язык: РуÑÑкий" + }, + "quit":{ + "en":"Quit", + "ru":"Выйти" + }, + "Press this button":{ + "en":"Press this button", + "ru":"Ðажмите Ñту кнопку" + }, + "title":{ + "en":"Title", + "ru":"Заголовок" + } +} diff --git a/bin/Data/Textures/BrightDay1_NegX.dds b/bin/Data/Textures/BrightDay1_NegX.dds new file mode 100644 index 0000000..349c5a1 Binary files /dev/null and b/bin/Data/Textures/BrightDay1_NegX.dds differ diff --git a/bin/Data/Textures/BrightDay1_NegY.dds b/bin/Data/Textures/BrightDay1_NegY.dds new file mode 100644 index 0000000..66481b9 Binary files /dev/null and b/bin/Data/Textures/BrightDay1_NegY.dds differ diff --git a/bin/Data/Textures/BrightDay1_NegZ.dds b/bin/Data/Textures/BrightDay1_NegZ.dds new file mode 100644 index 0000000..a779152 Binary files /dev/null and b/bin/Data/Textures/BrightDay1_NegZ.dds differ diff --git a/bin/Data/Textures/BrightDay1_PosX.dds b/bin/Data/Textures/BrightDay1_PosX.dds new file mode 100644 index 0000000..ac84ddc Binary files /dev/null and b/bin/Data/Textures/BrightDay1_PosX.dds differ diff --git a/bin/Data/Textures/BrightDay1_PosY.dds b/bin/Data/Textures/BrightDay1_PosY.dds new file mode 100644 index 0000000..fcd3816 Binary files /dev/null and b/bin/Data/Textures/BrightDay1_PosY.dds differ diff --git a/bin/Data/Textures/BrightDay1_PosZ.dds b/bin/Data/Textures/BrightDay1_PosZ.dds new file mode 100644 index 0000000..8b0d1db Binary files /dev/null and b/bin/Data/Textures/BrightDay1_PosZ.dds differ diff --git a/bin/Data/Textures/Editor/BW.png b/bin/Data/Textures/Editor/BW.png new file mode 100644 index 0000000..8fcaed6 Binary files /dev/null and b/bin/Data/Textures/Editor/BW.png differ diff --git a/bin/Data/Textures/Editor/EditorIcons.png b/bin/Data/Textures/Editor/EditorIcons.png new file mode 100644 index 0000000..0cbb496 Binary files /dev/null and b/bin/Data/Textures/Editor/EditorIcons.png differ diff --git a/bin/Data/Textures/Editor/EditorIcons.xml b/bin/Data/Textures/Editor/EditorIcons.xml new file mode 100644 index 0000000..5ac998e --- /dev/null +++ b/bin/Data/Textures/Editor/EditorIcons.xml @@ -0,0 +1,4 @@ + + + + diff --git a/bin/Data/Textures/Editor/HSV20.png b/bin/Data/Textures/Editor/HSV20.png new file mode 100644 index 0000000..28ad7f8 Binary files /dev/null and b/bin/Data/Textures/Editor/HSV20.png differ diff --git a/bin/Data/Textures/Editor/IconCamera.png b/bin/Data/Textures/Editor/IconCamera.png new file mode 100644 index 0000000..fdb27ef Binary files /dev/null and b/bin/Data/Textures/Editor/IconCamera.png differ diff --git a/bin/Data/Textures/Editor/IconCollisionTrigger.png b/bin/Data/Textures/Editor/IconCollisionTrigger.png new file mode 100644 index 0000000..6a41463 Binary files /dev/null and b/bin/Data/Textures/Editor/IconCollisionTrigger.png differ diff --git a/bin/Data/Textures/Editor/IconCustomGeometry.png b/bin/Data/Textures/Editor/IconCustomGeometry.png new file mode 100644 index 0000000..5213ff5 Binary files /dev/null and b/bin/Data/Textures/Editor/IconCustomGeometry.png differ diff --git a/bin/Data/Textures/Editor/IconLight.png b/bin/Data/Textures/Editor/IconLight.png new file mode 100644 index 0000000..b058715 Binary files /dev/null and b/bin/Data/Textures/Editor/IconLight.png differ diff --git a/bin/Data/Textures/Editor/IconParticleEmitter.png b/bin/Data/Textures/Editor/IconParticleEmitter.png new file mode 100644 index 0000000..747b190 Binary files /dev/null and b/bin/Data/Textures/Editor/IconParticleEmitter.png differ diff --git a/bin/Data/Textures/Editor/IconPointLight.png b/bin/Data/Textures/Editor/IconPointLight.png new file mode 100644 index 0000000..8a312bb Binary files /dev/null and b/bin/Data/Textures/Editor/IconPointLight.png differ diff --git a/bin/Data/Textures/Editor/IconSoundListener.png b/bin/Data/Textures/Editor/IconSoundListener.png new file mode 100644 index 0000000..e7234fe Binary files /dev/null and b/bin/Data/Textures/Editor/IconSoundListener.png differ diff --git a/bin/Data/Textures/Editor/IconSoundSource.png b/bin/Data/Textures/Editor/IconSoundSource.png new file mode 100644 index 0000000..e97ec28 Binary files /dev/null and b/bin/Data/Textures/Editor/IconSoundSource.png differ diff --git a/bin/Data/Textures/Editor/IconSplinePathPoint.png b/bin/Data/Textures/Editor/IconSplinePathPoint.png new file mode 100644 index 0000000..dd00601 Binary files /dev/null and b/bin/Data/Textures/Editor/IconSplinePathPoint.png differ diff --git a/bin/Data/Textures/Editor/IconSpotLight.png b/bin/Data/Textures/Editor/IconSpotLight.png new file mode 100644 index 0000000..1666165 Binary files /dev/null and b/bin/Data/Textures/Editor/IconSpotLight.png differ diff --git a/bin/Data/Textures/Editor/IconZone.png b/bin/Data/Textures/Editor/IconZone.png new file mode 100644 index 0000000..5f294bd Binary files /dev/null and b/bin/Data/Textures/Editor/IconZone.png differ diff --git a/bin/Data/Textures/Editor/NoPreviewAvailable.png b/bin/Data/Textures/Editor/NoPreviewAvailable.png new file mode 100644 index 0000000..df68768 Binary files /dev/null and b/bin/Data/Textures/Editor/NoPreviewAvailable.png differ diff --git a/bin/Data/Textures/Editor/SelectionCircle.png b/bin/Data/Textures/Editor/SelectionCircle.png new file mode 100644 index 0000000..c02583f Binary files /dev/null and b/bin/Data/Textures/Editor/SelectionCircle.png differ diff --git a/bin/Data/Textures/Editor/TerrainBrushes/large_dots.png b/bin/Data/Textures/Editor/TerrainBrushes/large_dots.png new file mode 100644 index 0000000..f09b4cd Binary files /dev/null and b/bin/Data/Textures/Editor/TerrainBrushes/large_dots.png differ diff --git a/bin/Data/Textures/Editor/TerrainBrushes/large_hard.png b/bin/Data/Textures/Editor/TerrainBrushes/large_hard.png new file mode 100644 index 0000000..7abafd1 Binary files /dev/null and b/bin/Data/Textures/Editor/TerrainBrushes/large_hard.png differ diff --git a/bin/Data/Textures/Editor/TerrainBrushes/large_med.png b/bin/Data/Textures/Editor/TerrainBrushes/large_med.png new file mode 100644 index 0000000..4ca66fa Binary files /dev/null and b/bin/Data/Textures/Editor/TerrainBrushes/large_med.png differ diff --git a/bin/Data/Textures/Editor/TerrainBrushes/large_soft.png b/bin/Data/Textures/Editor/TerrainBrushes/large_soft.png new file mode 100644 index 0000000..b39c93a Binary files /dev/null and b/bin/Data/Textures/Editor/TerrainBrushes/large_soft.png differ diff --git a/bin/Data/Textures/Editor/TerrainBrushes/tiny_hard.png b/bin/Data/Textures/Editor/TerrainBrushes/tiny_hard.png new file mode 100644 index 0000000..beb86e0 Binary files /dev/null and b/bin/Data/Textures/Editor/TerrainBrushes/tiny_hard.png differ diff --git a/bin/Data/Textures/Editor/TerrainBrushes/tiny_med.png b/bin/Data/Textures/Editor/TerrainBrushes/tiny_med.png new file mode 100644 index 0000000..1510a06 Binary files /dev/null and b/bin/Data/Textures/Editor/TerrainBrushes/tiny_med.png differ diff --git a/bin/Data/Textures/Editor/TerrainBrushes/tiny_soft.png b/bin/Data/Textures/Editor/TerrainBrushes/tiny_soft.png new file mode 100644 index 0000000..df8dc7b Binary files /dev/null and b/bin/Data/Textures/Editor/TerrainBrushes/tiny_soft.png differ diff --git a/bin/Data/Textures/FishBoneLogo.png b/bin/Data/Textures/FishBoneLogo.png new file mode 100644 index 0000000..92f69bd Binary files /dev/null and b/bin/Data/Textures/FishBoneLogo.png differ diff --git a/bin/Data/Textures/FishBoneLogo.xml b/bin/Data/Textures/FishBoneLogo.xml new file mode 100644 index 0000000..c95afd5 --- /dev/null +++ b/bin/Data/Textures/FishBoneLogo.xml @@ -0,0 +1,4 @@ + + + + diff --git a/bin/Data/Textures/Flare.dds b/bin/Data/Textures/Flare.dds new file mode 100644 index 0000000..59419de Binary files /dev/null and b/bin/Data/Textures/Flare.dds differ diff --git a/bin/Data/Textures/HeightMap.png b/bin/Data/Textures/HeightMap.png new file mode 100644 index 0000000..df7daf6 Binary files /dev/null and b/bin/Data/Textures/HeightMap.png differ diff --git a/bin/Data/Textures/Jack_body_color.jpg b/bin/Data/Textures/Jack_body_color.jpg new file mode 100644 index 0000000..264a73b Binary files /dev/null and b/bin/Data/Textures/Jack_body_color.jpg differ diff --git a/bin/Data/Textures/Jack_face.jpg b/bin/Data/Textures/Jack_face.jpg new file mode 100644 index 0000000..9040949 Binary files /dev/null and b/bin/Data/Textures/Jack_face.jpg differ diff --git a/bin/Data/Textures/Logo.png b/bin/Data/Textures/Logo.png new file mode 100644 index 0000000..151c859 Binary files /dev/null and b/bin/Data/Textures/Logo.png differ diff --git a/bin/Data/Textures/Logo.xml b/bin/Data/Textures/Logo.xml new file mode 100644 index 0000000..c95afd5 --- /dev/null +++ b/bin/Data/Textures/Logo.xml @@ -0,0 +1,4 @@ + + + + diff --git a/bin/Data/Textures/LogoLarge.png b/bin/Data/Textures/LogoLarge.png new file mode 100644 index 0000000..d61b5d8 Binary files /dev/null and b/bin/Data/Textures/LogoLarge.png differ diff --git a/bin/Data/Textures/LogoLarge.xml b/bin/Data/Textures/LogoLarge.xml new file mode 100644 index 0000000..c95afd5 --- /dev/null +++ b/bin/Data/Textures/LogoLarge.xml @@ -0,0 +1,4 @@ + + + + diff --git a/bin/Data/Textures/Mushroom.dds b/bin/Data/Textures/Mushroom.dds new file mode 100644 index 0000000..af87c2b Binary files /dev/null and b/bin/Data/Textures/Mushroom.dds differ diff --git a/bin/Data/Textures/NinjaSnowWar/CloudPlane.dds b/bin/Data/Textures/NinjaSnowWar/CloudPlane.dds new file mode 100644 index 0000000..e0af895 Binary files /dev/null and b/bin/Data/Textures/NinjaSnowWar/CloudPlane.dds differ diff --git a/bin/Data/Textures/NinjaSnowWar/HealthBarBorder.png b/bin/Data/Textures/NinjaSnowWar/HealthBarBorder.png new file mode 100644 index 0000000..1a77b51 Binary files /dev/null and b/bin/Data/Textures/NinjaSnowWar/HealthBarBorder.png differ diff --git a/bin/Data/Textures/NinjaSnowWar/HealthBarInside.png b/bin/Data/Textures/NinjaSnowWar/HealthBarInside.png new file mode 100644 index 0000000..1372a36 Binary files /dev/null and b/bin/Data/Textures/NinjaSnowWar/HealthBarInside.png differ diff --git a/bin/Data/Textures/NinjaSnowWar/Ninja.dds b/bin/Data/Textures/NinjaSnowWar/Ninja.dds new file mode 100644 index 0000000..7eaf3fa Binary files /dev/null and b/bin/Data/Textures/NinjaSnowWar/Ninja.dds differ diff --git a/bin/Data/Textures/NinjaSnowWar/Sight.png b/bin/Data/Textures/NinjaSnowWar/Sight.png new file mode 100644 index 0000000..64805ca Binary files /dev/null and b/bin/Data/Textures/NinjaSnowWar/Sight.png differ diff --git a/bin/Data/Textures/NinjaSnowWar/Sight.xml b/bin/Data/Textures/NinjaSnowWar/Sight.xml new file mode 100644 index 0000000..b59d2a0 --- /dev/null +++ b/bin/Data/Textures/NinjaSnowWar/Sight.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/bin/Data/Textures/NinjaSnowWar/Snow.dds b/bin/Data/Textures/NinjaSnowWar/Snow.dds new file mode 100644 index 0000000..958f7ed Binary files /dev/null and b/bin/Data/Textures/NinjaSnowWar/Snow.dds differ diff --git a/bin/Data/Textures/NinjaSnowWar/SnowCrate.dds b/bin/Data/Textures/NinjaSnowWar/SnowCrate.dds new file mode 100644 index 0000000..8a51777 Binary files /dev/null and b/bin/Data/Textures/NinjaSnowWar/SnowCrate.dds differ diff --git a/bin/Data/Textures/RibbonTrail.png b/bin/Data/Textures/RibbonTrail.png new file mode 100644 index 0000000..63fa683 Binary files /dev/null and b/bin/Data/Textures/RibbonTrail.png differ diff --git a/bin/Data/Textures/Skybox.xml b/bin/Data/Textures/Skybox.xml new file mode 100644 index 0000000..3a310fa --- /dev/null +++ b/bin/Data/Textures/Skybox.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/bin/Data/Textures/SlashTrail.png b/bin/Data/Textures/SlashTrail.png new file mode 100644 index 0000000..c826d13 Binary files /dev/null and b/bin/Data/Textures/SlashTrail.png differ diff --git a/bin/Data/Textures/Smoke.dds b/bin/Data/Textures/Smoke.dds new file mode 100644 index 0000000..df7459e Binary files /dev/null and b/bin/Data/Textures/Smoke.dds differ diff --git a/bin/Data/Textures/StoneDiffuse.dds b/bin/Data/Textures/StoneDiffuse.dds new file mode 100644 index 0000000..cba0609 Binary files /dev/null and b/bin/Data/Textures/StoneDiffuse.dds differ diff --git a/bin/Data/Textures/StoneNormal.dds b/bin/Data/Textures/StoneNormal.dds new file mode 100644 index 0000000..09ac497 Binary files /dev/null and b/bin/Data/Textures/StoneNormal.dds differ diff --git a/bin/Data/Textures/TerrainDetail1.dds b/bin/Data/Textures/TerrainDetail1.dds new file mode 100644 index 0000000..2e9da04 Binary files /dev/null and b/bin/Data/Textures/TerrainDetail1.dds differ diff --git a/bin/Data/Textures/TerrainDetail2.dds b/bin/Data/Textures/TerrainDetail2.dds new file mode 100644 index 0000000..a478d50 Binary files /dev/null and b/bin/Data/Textures/TerrainDetail2.dds differ diff --git a/bin/Data/Textures/TerrainDetail3.dds b/bin/Data/Textures/TerrainDetail3.dds new file mode 100644 index 0000000..a3fb5ea Binary files /dev/null and b/bin/Data/Textures/TerrainDetail3.dds differ diff --git a/bin/Data/Textures/TerrainWeights.dds b/bin/Data/Textures/TerrainWeights.dds new file mode 100644 index 0000000..f0b0d0c Binary files /dev/null and b/bin/Data/Textures/TerrainWeights.dds differ diff --git a/bin/Data/Textures/TouchInput.png b/bin/Data/Textures/TouchInput.png new file mode 100644 index 0000000..fc97548 Binary files /dev/null and b/bin/Data/Textures/TouchInput.png differ diff --git a/bin/Data/Textures/UI.png b/bin/Data/Textures/UI.png new file mode 100644 index 0000000..b197e77 Binary files /dev/null and b/bin/Data/Textures/UI.png differ diff --git a/bin/Data/Textures/UI.xml b/bin/Data/Textures/UI.xml new file mode 100644 index 0000000..b59d2a0 --- /dev/null +++ b/bin/Data/Textures/UI.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/bin/Data/Textures/UrhoDecal.dds b/bin/Data/Textures/UrhoDecal.dds new file mode 100644 index 0000000..4e3d2ae Binary files /dev/null and b/bin/Data/Textures/UrhoDecal.dds differ diff --git a/bin/Data/Textures/UrhoDecalAlpha.dds b/bin/Data/Textures/UrhoDecalAlpha.dds new file mode 100644 index 0000000..0a0fbd5 Binary files /dev/null and b/bin/Data/Textures/UrhoDecalAlpha.dds differ diff --git a/bin/Data/Textures/UrhoIcon.icns b/bin/Data/Textures/UrhoIcon.icns new file mode 100644 index 0000000..f6531c1 Binary files /dev/null and b/bin/Data/Textures/UrhoIcon.icns differ diff --git a/bin/Data/Textures/UrhoIcon.png b/bin/Data/Textures/UrhoIcon.png new file mode 100644 index 0000000..f133f7f Binary files /dev/null and b/bin/Data/Textures/UrhoIcon.png differ diff --git a/bin/Data/Textures/WaterNoise.dds b/bin/Data/Textures/WaterNoise.dds new file mode 100644 index 0000000..0f3ada4 Binary files /dev/null and b/bin/Data/Textures/WaterNoise.dds differ diff --git a/bin/Data/UI/DefaultStyle.xml b/bin/Data/UI/DefaultStyle.xml new file mode 100644 index 0000000..b90d121 --- /dev/null +++ b/bin/Data/UI/DefaultStyle.xml @@ -0,0 +1,410 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorColorWheel.xml b/bin/Data/UI/EditorColorWheel.xml new file mode 100644 index 0000000..51e3e19 --- /dev/null +++ b/bin/Data/UI/EditorColorWheel.xml @@ -0,0 +1,387 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorContextMenu.xml b/bin/Data/UI/EditorContextMenu.xml new file mode 100644 index 0000000..68be6a2 --- /dev/null +++ b/bin/Data/UI/EditorContextMenu.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/bin/Data/UI/EditorHierarchyWindow.xml b/bin/Data/UI/EditorHierarchyWindow.xml new file mode 100644 index 0000000..3040fe9 --- /dev/null +++ b/bin/Data/UI/EditorHierarchyWindow.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorIcons.xml b/bin/Data/UI/EditorIcons.xml new file mode 100644 index 0000000..32a3b18 --- /dev/null +++ b/bin/Data/UI/EditorIcons.xml @@ -0,0 +1,458 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorInspectorWindow.xml b/bin/Data/UI/EditorInspectorWindow.xml new file mode 100644 index 0000000..1909dde --- /dev/null +++ b/bin/Data/UI/EditorInspectorWindow.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorInspector_Attribute.xml b/bin/Data/UI/EditorInspector_Attribute.xml new file mode 100644 index 0000000..76cfc85 --- /dev/null +++ b/bin/Data/UI/EditorInspector_Attribute.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorInspector_Style.xml b/bin/Data/UI/EditorInspector_Style.xml new file mode 100644 index 0000000..5df4839 --- /dev/null +++ b/bin/Data/UI/EditorInspector_Style.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorInspector_Tags.xml b/bin/Data/UI/EditorInspector_Tags.xml new file mode 100644 index 0000000..064e81f --- /dev/null +++ b/bin/Data/UI/EditorInspector_Tags.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorInspector_Variable.xml b/bin/Data/UI/EditorInspector_Variable.xml new file mode 100644 index 0000000..a848210 --- /dev/null +++ b/bin/Data/UI/EditorInspector_Variable.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorLayersWindow.xml b/bin/Data/UI/EditorLayersWindow.xml new file mode 100644 index 0000000..c73a4ff --- /dev/null +++ b/bin/Data/UI/EditorLayersWindow.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorMaterialWindow.xml b/bin/Data/UI/EditorMaterialWindow.xml new file mode 100644 index 0000000..85f090f --- /dev/null +++ b/bin/Data/UI/EditorMaterialWindow.xml @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorParticleEffectWindow.xml b/bin/Data/UI/EditorParticleEffectWindow.xml new file mode 100644 index 0000000..b48d2ed --- /dev/null +++ b/bin/Data/UI/EditorParticleEffectWindow.xml @@ -0,0 +1,842 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorPreferencesDialog.xml b/bin/Data/UI/EditorPreferencesDialog.xml new file mode 100644 index 0000000..6bdcc25 --- /dev/null +++ b/bin/Data/UI/EditorPreferencesDialog.xml @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorQuickMenu.xml b/bin/Data/UI/EditorQuickMenu.xml new file mode 100644 index 0000000..8a9f048 --- /dev/null +++ b/bin/Data/UI/EditorQuickMenu.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorResourceBrowser.xml b/bin/Data/UI/EditorResourceBrowser.xml new file mode 100644 index 0000000..c5e52a1 --- /dev/null +++ b/bin/Data/UI/EditorResourceBrowser.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorResourceFilterWindow.xml b/bin/Data/UI/EditorResourceFilterWindow.xml new file mode 100644 index 0000000..f567680 --- /dev/null +++ b/bin/Data/UI/EditorResourceFilterWindow.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorSettingsDialog.xml b/bin/Data/UI/EditorSettingsDialog.xml new file mode 100644 index 0000000..be0e99b --- /dev/null +++ b/bin/Data/UI/EditorSettingsDialog.xml @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorSoundTypeWindow.xml b/bin/Data/UI/EditorSoundTypeWindow.xml new file mode 100644 index 0000000..97bbaf3 --- /dev/null +++ b/bin/Data/UI/EditorSoundTypeWindow.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorSpawnWindow.xml b/bin/Data/UI/EditorSpawnWindow.xml new file mode 100644 index 0000000..2176bc5 --- /dev/null +++ b/bin/Data/UI/EditorSpawnWindow.xml @@ -0,0 +1,364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorTerrainWindow.xml b/bin/Data/UI/EditorTerrainWindow.xml new file mode 100644 index 0000000..6523ab1 --- /dev/null +++ b/bin/Data/UI/EditorTerrainWindow.xml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/EditorViewport.xml b/bin/Data/UI/EditorViewport.xml new file mode 100644 index 0000000..807c05b --- /dev/null +++ b/bin/Data/UI/EditorViewport.xml @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/MessageBox.xml b/bin/Data/UI/MessageBox.xml new file mode 100644 index 0000000..6a43175 --- /dev/null +++ b/bin/Data/UI/MessageBox.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/ScreenJoystick.xml b/bin/Data/UI/ScreenJoystick.xml new file mode 100644 index 0000000..b9f37c3 --- /dev/null +++ b/bin/Data/UI/ScreenJoystick.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/ScreenJoystickSettings.xml b/bin/Data/UI/ScreenJoystickSettings.xml new file mode 100644 index 0000000..943a39b --- /dev/null +++ b/bin/Data/UI/ScreenJoystickSettings.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/ScreenJoystickSettings_NinjaSnowWar.xml b/bin/Data/UI/ScreenJoystickSettings_NinjaSnowWar.xml new file mode 100644 index 0000000..9d424f6 --- /dev/null +++ b/bin/Data/UI/ScreenJoystickSettings_NinjaSnowWar.xml @@ -0,0 +1,34 @@ + + + + + + Octree + + + + + + + Debug + + + + + + + HUD + + + + + + + Console + + + + + + + diff --git a/bin/Data/UI/ScreenJoystickSettings_Samples.xml b/bin/Data/UI/ScreenJoystickSettings_Samples.xml new file mode 100644 index 0000000..fb3f601 --- /dev/null +++ b/bin/Data/UI/ScreenJoystickSettings_Samples.xml @@ -0,0 +1,117 @@ + + + S-Quality + + + + + + + S-Size + + + + + + + Shadow + + + + + + + Console + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/UI/ScreenJoystick_NinjaSnowWar.xml b/bin/Data/UI/ScreenJoystick_NinjaSnowWar.xml new file mode 100644 index 0000000..d126043 --- /dev/null +++ b/bin/Data/UI/ScreenJoystick_NinjaSnowWar.xml @@ -0,0 +1,12 @@ + + + Jump + Fire + -12 36 + + + + + + + diff --git a/bin/Data/UI/ScreenJoystick_Samples.xml b/bin/Data/UI/ScreenJoystick_Samples.xml new file mode 100644 index 0000000..ddebf93 --- /dev/null +++ b/bin/Data/UI/ScreenJoystick_Samples.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + 12 -76 + + + + + + + diff --git a/bin/Data/UI/UILoadExample.xml b/bin/Data/UI/UILoadExample.xml new file mode 100644 index 0000000..5bdb03a --- /dev/null +++ b/bin/Data/UI/UILoadExample.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Urho2D/Aster.png b/bin/Data/Urho2D/Aster.png new file mode 100644 index 0000000..cc7239f Binary files /dev/null and b/bin/Data/Urho2D/Aster.png differ diff --git a/bin/Data/Urho2D/Ball.png b/bin/Data/Urho2D/Ball.png new file mode 100644 index 0000000..9a52a6c Binary files /dev/null and b/bin/Data/Urho2D/Ball.png differ diff --git a/bin/Data/Urho2D/Box.png b/bin/Data/Urho2D/Box.png new file mode 100644 index 0000000..46996c4 Binary files /dev/null and b/bin/Data/Urho2D/Box.png differ diff --git a/bin/Data/Urho2D/GoldIcon.png b/bin/Data/Urho2D/GoldIcon.png new file mode 100644 index 0000000..f8b0a6b Binary files /dev/null and b/bin/Data/Urho2D/GoldIcon.png differ diff --git a/bin/Data/Urho2D/GoldIcon.scml b/bin/Data/Urho2D/GoldIcon.scml new file mode 100644 index 0000000..b10d8c5 --- /dev/null +++ b/bin/Data/Urho2D/GoldIcon.scml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Urho2D/GoldIcon/1.png b/bin/Data/Urho2D/GoldIcon/1.png new file mode 100644 index 0000000..f8b0a6b Binary files /dev/null and b/bin/Data/Urho2D/GoldIcon/1.png differ diff --git a/bin/Data/Urho2D/GoldIcon/2.png b/bin/Data/Urho2D/GoldIcon/2.png new file mode 100644 index 0000000..0fcceb1 Binary files /dev/null and b/bin/Data/Urho2D/GoldIcon/2.png differ diff --git a/bin/Data/Urho2D/GoldIcon/3.png b/bin/Data/Urho2D/GoldIcon/3.png new file mode 100644 index 0000000..7d0b371 Binary files /dev/null and b/bin/Data/Urho2D/GoldIcon/3.png differ diff --git a/bin/Data/Urho2D/GoldIcon/4.png b/bin/Data/Urho2D/GoldIcon/4.png new file mode 100644 index 0000000..4377596 Binary files /dev/null and b/bin/Data/Urho2D/GoldIcon/4.png differ diff --git a/bin/Data/Urho2D/GoldIcon/5.png b/bin/Data/Urho2D/GoldIcon/5.png new file mode 100644 index 0000000..6e55b68 Binary files /dev/null and b/bin/Data/Urho2D/GoldIcon/5.png differ diff --git a/bin/Data/Urho2D/Orc/License.txt b/bin/Data/Urho2D/Orc/License.txt new file mode 100644 index 0000000..df6eaa7 --- /dev/null +++ b/bin/Data/Urho2D/Orc/License.txt @@ -0,0 +1,7 @@ +Copyright (C) 2013 by TreeFortress (https://github.com/treefortress/SpriterAS/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bin/Data/Urho2D/Orc/Orc.scml b/bin/Data/Urho2D/Orc/Orc.scml new file mode 100644 index 0000000..1fc2af7 --- /dev/null +++ b/bin/Data/Urho2D/Orc/Orc.scml @@ -0,0 +1,1536 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Urho2D/Orc/Orc.xml b/bin/Data/Urho2D/Orc/Orc.xml new file mode 100644 index 0000000..c618f0f --- /dev/null +++ b/bin/Data/Urho2D/Orc/Orc.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/bin/Data/Urho2D/Orc/Orc_Atlas.png b/bin/Data/Urho2D/Orc/Orc_Atlas.png new file mode 100644 index 0000000..a22f148 Binary files /dev/null and b/bin/Data/Urho2D/Orc/Orc_Atlas.png differ diff --git a/bin/Data/Urho2D/Orc/Orc_Atlas.xml b/bin/Data/Urho2D/Orc/Orc_Atlas.xml new file mode 100644 index 0000000..5ac998e --- /dev/null +++ b/bin/Data/Urho2D/Orc/Orc_Atlas.xml @@ -0,0 +1,4 @@ + + + + diff --git a/bin/Data/Urho2D/Stretchable.png b/bin/Data/Urho2D/Stretchable.png new file mode 100644 index 0000000..0c20307 Binary files /dev/null and b/bin/Data/Urho2D/Stretchable.png differ diff --git a/bin/Data/Urho2D/Stretchable.xml b/bin/Data/Urho2D/Stretchable.xml new file mode 100644 index 0000000..30a5b1c --- /dev/null +++ b/bin/Data/Urho2D/Stretchable.xml @@ -0,0 +1,3 @@ + + + diff --git a/bin/Data/Urho2D/Tilesets/Licenses.txt b/bin/Data/Urho2D/Tilesets/Licenses.txt new file mode 100644 index 0000000..70d05fe --- /dev/null +++ b/bin/Data/Urho2D/Tilesets/Licenses.txt @@ -0,0 +1,15 @@ +============================= +Isometric Dungeon (atrium): + +Copyright Clint Bellanger +https://github.com/clintbellanger/flare-game + +All of Flare's art and data files are released under CC-BY-SA 3.0. Later versions are permitted. + +============================= +Orthographic tileset: + +Copyright Kenney +www.kenney.nl + +Licensed under CC-0. \ No newline at end of file diff --git a/bin/Data/Urho2D/Tilesets/Ortho.png b/bin/Data/Urho2D/Tilesets/Ortho.png new file mode 100644 index 0000000..e3dfd50 Binary files /dev/null and b/bin/Data/Urho2D/Tilesets/Ortho.png differ diff --git a/bin/Data/Urho2D/Tilesets/Ortho.tmx b/bin/Data/Urho2D/Tilesets/Ortho.tmx new file mode 100644 index 0000000..d35f4f8 --- /dev/null +++ b/bin/Data/Urho2D/Tilesets/Ortho.tmx @@ -0,0 +1,338 @@ + + + + + + + + FAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAAAAAAAgAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABMAAAAAAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAAAAAAEwAAAAAAAAAAAAAAFAAAABQAAAAAAAAAAAAAABkAAAAUAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAAAAAAAAAeAAAAGwAAACcAAAAcAAAAHwAAAB8AAAAbAAAAJwAAABwAAAAfAAAAHwAAABsAAAAnAAAAHAAAAB8AAAAAAAAAFAAAABoAAAAAAAAAAAAAABQAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAUAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAZAAAAFAAAAAAAAAAAAAAAGwAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAAAAAANgAAADcAAAAUAAAANwAAADcAAAA3AAAANwAAABQAAAA3AAAANwAAADcAAAA3AAAAFAAAADcAAAA3AAAAAAAAABQAAAAaAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAAAAAAAAAWAAAAFgAAABQAAAAWAAAAFgAAABYAAAAWAAAAFAAAABYAAAAWAAAAFgAAABYAAAAUAAAAFgAAABYAAAAAAAAAFAAAAAAAAAAAAAAAGQAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAUAAAAAAAAADMAAAA/AAAANAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAGgAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAADYAAAA3AAAANwAAADcAAAA3AAAANwAAADcAAAA3AAAANwAAADcAAAA3AAAAFAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAABkAAAAUAAAAAAAAAAAAAAAXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAAAAAAAAAUAAAAFAAAABQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAAABoAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAUAAAAAAAAABQAAAAAAAAAAAAAABsAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAAAAAAAAAAAAZAAAAFAAAAAAAAAAAAAAAAAAAADUAAAAAAAAANQAAAAAAAAAAAAAAAAAAADUAAAAAAAAANQAAAAAAAAA2AAAAFAAAABQAAAAAAAAAFAAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAaAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEwAAAAAAAAATAAAAAAAAABMAAAAAAAAAEwAAAAAAAAATAAAAAAAAABMAAAAAAAAAAAAAAAAAAAAAAAAAGQAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANQAAAAAAAAA1AAAAAAAAABQAAAAUAAAAFAAAoBQAAMAUAABgFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAAAAAAFAAAACsAAAArAAAAFAAAADcAAAA3AAAANwAAADgAAAAAAAAAOQAAAD8AAAA3AAAAPwAAAD0AAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbAAAAJwAAACcAAAAcAAAAAAAAAAAAAAAAAAAAAAAAADkAAAA6AAAAOwAAAAAAAAA7AAAAPAAAAD0AAAAAAAAAAAAAADUAAAAUAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAPwAAAD8AAAA/AAAAOgAAAAAAAAA7AAAAAAAAADsAAAAAAAAAPAAAAD0AAAAAAAAAAAAAABQAAAAUAAAANwAAADcAAAA4AAAAAAAAAAAAAAA2AAAANwAAADgAAAAAAAAAAAAAADYAAAA4AAAAAAAAAA4AAAALAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQAAADoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAAAAAAAOwAAAAAAAAAzAAAANAAAAAAAAAAAAAAAFAAAABQAAAAAAAAAAAAAAAAAAAAAAAAABQAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAAD8AAAAyAAAAAAAAADkAAAA/AAAAPwAAAD8AAAA6AAAAOwAAADsAAAA7AAAAOwAAADsAAAA7AAAAOwAAAAAAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5AAAAOgAAADQAAAAAAAAABQAAADMAAAA/AAAANAAAAAUAAAAzAAAAPwAAADQAAAA2AAAANwAAADcAAAA3AAAANwAAADcAAAA3AAAAOAAAABQAAAAUAAAAPwAAAD8AAAA/AAAAMgAAAAAAAAAAAAAAMQAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAADoAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAFAAAAMwAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAADcAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2AAAAPwAAAAAAAAA2AAAANwAAADgAAAAAAAAAAAAAADEAAAA/AAAAQAAAABQAAAAUAAAAJwAAACcAAAAnAAAAJwAAACcAAAAnAAAAJwAAABwAAAAAAAAAAAAAABkAAAAnAAAAJwAAABoAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAD8AAAAAAAAAFAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwAAAAAAAAAUAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAADQAAAAAAAAAAAAAADYAAAA7AAAAOAAAAAAAAAA2AAAAOwAAADgAAAAAAAAAAAAAADYAAAA3AAAAOAAAAAAAAAA7AAAANQAAABQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAADsAAAAAAAAAAAAAAAAAAAA7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADsAAAAAAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAA + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAHwAAAAAAAAAfAAAAAAAAAAAAAAAfAAAAAAAAAB8AAAAAAAAAAAAAAB8AAAAAAAAAHwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAABYAAAAAAAAAAAAAAAAAAAAAAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAFAAAAAAAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAgAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAACgAAAAAAAAAKAAAAAAAAAAoAAAAAAAAACgAAAAAAAAAKAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsAAAAAAAAAKwAAAAAAAAArAAAAAAAAACsAAAAAAAAAKwAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2AAAANwAAADgAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAAAAAAAAAAAAAAAAAA3AAAAAAAAAAAAAAAAAAAANwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACsAAAAAAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Urho2D/Tilesets/Ortho.xml b/bin/Data/Urho2D/Tilesets/Ortho.xml new file mode 100644 index 0000000..6998d5a --- /dev/null +++ b/bin/Data/Urho2D/Tilesets/Ortho.xml @@ -0,0 +1,3 @@ + + + diff --git a/bin/Data/Urho2D/Tilesets/atrium.tmx b/bin/Data/Urho2D/Tilesets/atrium.tmx new file mode 100644 index 0000000..f53dd93 --- /dev/null +++ b/bin/Data/Urho2D/Tilesets/atrium.tmx @@ -0,0 +1,138 @@ + + + + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAACAAAAAQAAAAEAAAABAAAAAQAAAAEAAAACAAAAAQAAAAIAAAACAAAAAwAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAADAAAAAQAAAAMAAAACAAAAAgAAAAEAAAABAAAABAAAAAIAAAAEAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAMAAAACAAAABAAAAAIAAAADAAAAAwAAAAIAAAACAAAAAgAAAAEAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAABAAAAAEAAAAEAAAAAgAAAAMAAAAEAAAAAQAAAAEAAAACAAAAAQAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAADAAAABAAAAAEAAAABAAAAAwAAAAIAAAAEAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAQAAAADAAAABAAAAAQAAAADAAAAAQAAAAQAAAAEAAAAAQAAAAIAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAABAAAAAIAAAABAAAAAQAAAAIAAAABAAAAAQAAAAIAAAACAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAEAAAAAQAAAAQAAAABAAAAAQAAAAMAAAABAAAAAwAAAAIAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAEAAAABAAAAAQAAAAIAAAABAAAAAgAAAAEAAAABAAAAAQAAAAQAAAABAAAAAQAAAAMAAAABAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAABAAAABAAAAAMAAAAEAAAABAAAAAMAAAABAAAABAAAAAQAAAABAAAAAQAAAAIAAAABAAAAAgAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAMAAAAEAAAAAgAAAAEAAAABAAAAAgAAAAEAAAABAAAAAgAAAAIAAAABAAAAAQAAAAEAAAACAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAgAAAAQAAAABAAAABAAAAAEAAAABAAAAAwAAAAEAAAADAAAAAgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAAgAAAAEAAAACAAAAAQAAAAEAAAADAAAAAgAAAAAAAAAJAAAABgAAAAYAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAABAAAAAQAAAAEAAAABAAAAAQAAAAIAAAABAAAAAgAAAAIAAAADAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAMAAAABAAAAAwAAAAIAAAACAAAAAQAAAAEAAAAEAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAwAAAAIAAAAEAAAAAgAAAAMAAAADAAAAAgAAAAIAAAACAAAAAQAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAEAAAAAQAAAAQAAAACAAAAAwAAAAQAAAABAAAAAQAAAAIAAAABAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAABAAAAAQAAAAMAAAAEAAAAAQAAAAEAAAADAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAABAAAAAMAAAAEAAAABAAAAAMAAAABAAAABAAAAAQAAAABAAAAAgAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAMAAAABAAAAAwAAAAIAAAACAAAAAQAAAAEAAAAEAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAwAAAAIAAAAEAAAAJAAAACQAAAAkAAAAJAAAAAIAAAACAAAAAQAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAEAAAAAQAAAAQAAAAkAAAAJAAAACQAAAAkAAAAAQAAAAIAAAABAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAABAAAAAQAAACQAAAAkAAAAJAAAACQAAAADAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAABAAAAAMAAAAEAAAAJAAAACQAAAAkAAAAJAAAAAQAAAABAAAAAgAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAEAAAAAgAAAAEAAAAkAAAAJAAAACQAAAAkAAAAAgAAAAIAAAABAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAQAAAABAAAABAAAACQAAAAkAAAAJAAAACQAAAADAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAJAAAACQAAAAkAAAAJAAAAAEAAAADAAAAAgAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAABAAAAAQAAAAEAAAAkAAAAJAAAACQAAAAkAAAAAgAAAAIAAAADAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAMAAAABAAAAAwAAACQAAAAkAAAAJAAAACQAAAAEAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAwAAAAIAAAAEAAAAJAAAACQAAAAkAAAAJAAAAAIAAAACAAAAAQAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAEAAAAAQAAAAQAAAAkAAAAJAAAACQAAAAkAAAAAQAAAAIAAAABAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAABAAAAAQAAACQAAAAkAAAAJAAAACQAAAADAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAABAAAAAMAAAAEAAAAJAAAACQAAAAkAAAAJAAAAAQAAAABAAAAAgAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAEAAAAAgAAAAEAAAAkAAAAJAAAACQAAAAkAAAAAgAAAAIAAAABAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAQAAAABAAAABAAAACQAAAAkAAAAJAAAACQAAAADAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAJAAAACQAAAAkAAAAJAAAAAEAAAADAAAAAgAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAEAAAAAgAAAAEAAAAkAAAAJAAAACQAAAAkAAAAAgAAAAIAAAABAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAQAAAABAAAABAAAACQAAAAkAAAAJAAAACQAAAADAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAJAAAACQAAAAkAAAAJAAAAAEAAAADAAAAAgAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAEAAAAAgAAAAEAAAAkAAAAJAAAACQAAAAkAAAAAgAAAAIAAAABAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAQAAAABAAAABAAAACQAAAAkAAAAJAAAACQAAAADAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAQAAAAEAAAABAAAAJAAAACQAAAAkAAAAJAAAAAEAAAADAAAAAgAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAABAAAAAQAAAAEAAAAkAAAAJAAAACQAAAAkAAAAAgAAAAIAAAADAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAMAAAABAAAAAwAAAAIAAAACAAAAAQAAAAEAAAAEAAAAAgAAAAQAAAAAAAAABQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAwAAAAIAAAAEAAAAAgAAAAMAAAADAAAAAgAAAAIAAAACAAAAAQAAAAAAAAABAAAAAQAAAAEAAAABAAAAAAAAAAEAAAAEAAAAAQAAAAEAAAADAAAAAQAAAAQAAAACAAAAAwAAAAQAAAABAAAAAQAAAAIAAAAEAAAAAQAAAAEAAAADAAAAAQAAAAEAAAAAAAAAAQAAAAEAAAACAAAAAQAAAAIAAAABAAAAAQAAAAMAAAAEAAAAAQAAAAEAAAADAAAAAgAAAAEAAAACAAAAAQAAAAIAAAABAAAAAQAAAAAAAAABAAAAAQAAAAEAAAACAAAAAQAAAAMAAAAEAAAABAAAAAEAAAABAAAABAAAAAQAAAABAAAAAQAAAAEAAAACAAAAAQAAAAEAAAABAAAAAAAAAAEAAAAAAAAAAAAAAAMAAAAEAAAAAgAAAAEAAAABAAAAAgAAAAEAAAABAAAAAgAAAAIAAAABAAAAAAAAAAAAAAACAAAAAgAAAAEAAAAAAAAAAQAAAAAAAAAAAAAAAgAAAAQAAAABAAAABAAAAAEAAAABAAAAAwAAAAEAAAADAAAAAgAAAAQAAAAAAAAABQAAAAEAAAACAAAAAQAAAAAAAAABAAAAAQAAAAAAAAADAAAAAQAAAAEAAAABAAAAAgAAAAIAAAABAAAAAQAAAAEAAAADAAAAAgAAAAAAAAAFAAAAAQAAAAEAAAABAAAAAAAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAwAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAABAAAAAQAAAAEAAAAAAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAAAAAAIAAAADAAAABAAAAAAAAAAJAAAABgAAAAYAAAAGAAAADQAAAAEAAAABAAAAAQAAAAAAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAwAAAAQAAAABAAAAAgAAAAUAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA + + + + + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsAAAAbAAAAGwAAABsAAAAbAAAAF8AAAAAAAAAAAAAAAAAAAA+AAAAMgAAADIAAABcAAAAOgAAAAAAAAAAAAAAOwAAAFwAAAAyAAAAMgAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAADUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAANQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDAAAAAAAAAEwAAABJAAAAAAAAAGsAAAAAAAAAAAAAAAAAAABZAAAAAAAAAAAAAABfAAAAAAAAAAAAAAAAAAAAAAAAAF8AAAAAAAAAAAAAAEMAAAAAAAAASwAAAEoAAAAAAAAAawAAAAAAAAAAAAAAAAAAADUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAAAxAAAAAAAAAAAAAAAXAAAAAAAAAAAAAAAAAAAAAAAAABUAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAADEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLAAAAQgAAADIAAABYAAAAMgAAADoAAAAAAAAAAAAAAAAAAAA1AAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAAAARAAAAEQAAABEAAAARAAAAF8AAAAAAAAAAAAAAAAAAABTAAAAAAAAAAAAAAAXAAAAAAAAAAAAAAAAAAAAAAAAABUAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAADUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAAA1AAAAAAAAAAAAAABfAAAAAAAAAAAAAAAAAAAAAAAAAF8AAAAAAAAAAAAAAEMAAAAAAAAAFQAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAAGMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAADUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAVwAAADwAAAA0AAAANAAAADkAAAAAAAAAAAAAADwAAAA0AAAANAAAADkAAABDAAAAAAAAABUAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAAAxAAAAOwAAAFoAAABUAAAAOgAAAAAAAAAAAAAAOwAAAFQAAABaAAAAOgAAAEMAAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAADEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAANQAAACUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAAA1AAAAJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAADEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAVAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAABdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAAGMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLAAAATwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAABXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMAAAAAAAAAFQAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAADUAAAAAAAAAAAAAADkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAPgAAAFoAAABUAAAAOgAAAAAAAAAAAAAAAAAAADsAAABUAAAAWgAAADoAAABQAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAABTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAAF0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAVwAAACkAAAAqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKwAAACwAAABDAAAAAAAAABUAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAAD0AAAA5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMAAAAUAAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAPgAAADoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEsAAABPAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAAFcAAAApAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsAAAAsAAAAQwAAAAAAAAAVAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAANQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAAA9AAAAOQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAAD4AAAA6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLAAAATwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAABXAAAAKQAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArAAAALAAAAEMAAAAAAAAAFQAAAAAAAAAAAAAAawAAAAAAAAAAAAAAAAAAAF0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAPQAAADkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEwAAABQAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAAA+AAAAOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASwAAAE8AAAAAAAAAAAAAAAAAAAAAAAAAawAAADsAAAAyAAAAUgAAADoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASwAAAEIAAAA6AAAAAAAAAAAAAABrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAawAAAGsAAABEAAAARAAAAEkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAAEQAAABEAAAAAAAAAAAAAABrAAAAawAAAAAAAAAAAAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDAAAAAAAAAAAAAAAAAAAAAAAAAGsAAABrAAAAAAAAAAAAAAAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAAAAAAAAAAAAawAAAGsAAAAAAAAAAAAAAD0AAAA4AAAARAAAAEQAAABJAAAAAAAAAAAAAABMAAAARAAAAEQAAABEAAAAUAAAAAAAAAAAAAAAAAAAAAAAAABrAAAAawAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEEAAAAAAAAAAAAAAEMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGsAAABrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANQAAAAAAAAAAAAAAQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAawAAAF8AAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAAA6AAAAAAAAAAAAAABfAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABfAAAA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Urho2D/Tilesets/tiled_dungeon.png b/bin/Data/Urho2D/Tilesets/tiled_dungeon.png new file mode 100644 index 0000000..ec3be04 Binary files /dev/null and b/bin/Data/Urho2D/Tilesets/tiled_dungeon.png differ diff --git a/bin/Data/Urho2D/Tilesets/tiled_dungeon.xml b/bin/Data/Urho2D/Tilesets/tiled_dungeon.xml new file mode 100644 index 0000000..6998d5a --- /dev/null +++ b/bin/Data/Urho2D/Tilesets/tiled_dungeon.xml @@ -0,0 +1,3 @@ + + + diff --git a/bin/Data/Urho2D/greenspiral.pex b/bin/Data/Urho2D/greenspiral.pex new file mode 100644 index 0000000..fcd8e0f --- /dev/null +++ b/bin/Data/Urho2D/greenspiral.pex @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Urho2D/greenspiral.png b/bin/Data/Urho2D/greenspiral.png new file mode 100644 index 0000000..1138241 Binary files /dev/null and b/bin/Data/Urho2D/greenspiral.png differ diff --git a/bin/Data/Urho2D/imp/imp.scml b/bin/Data/Urho2D/imp/imp.scml new file mode 100644 index 0000000..95836f6 --- /dev/null +++ b/bin/Data/Urho2D/imp/imp.scml @@ -0,0 +1,1303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Urho2D/imp/imp.txt b/bin/Data/Urho2D/imp/imp.txt new file mode 100644 index 0000000..df6eaa7 --- /dev/null +++ b/bin/Data/Urho2D/imp/imp.txt @@ -0,0 +1,7 @@ +Copyright (C) 2013 by TreeFortress (https://github.com/treefortress/SpriterAS/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bin/Data/Urho2D/imp/imp_all.png b/bin/Data/Urho2D/imp/imp_all.png new file mode 100644 index 0000000..6d66bf0 Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_all.png differ diff --git a/bin/Data/Urho2D/imp/imp_blood.png b/bin/Data/Urho2D/imp/imp_blood.png new file mode 100644 index 0000000..dc76091 Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_blood.png differ diff --git a/bin/Data/Urho2D/imp/imp_body.png b/bin/Data/Urho2D/imp/imp_body.png new file mode 100644 index 0000000..5960378 Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_body.png differ diff --git a/bin/Data/Urho2D/imp/imp_footbig.png b/bin/Data/Urho2D/imp/imp_footbig.png new file mode 100644 index 0000000..1fba766 Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_footbig.png differ diff --git a/bin/Data/Urho2D/imp/imp_footsmall.png b/bin/Data/Urho2D/imp/imp_footsmall.png new file mode 100644 index 0000000..544bcfa Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_footsmall.png differ diff --git a/bin/Data/Urho2D/imp/imp_handbig.png b/bin/Data/Urho2D/imp/imp_handbig.png new file mode 100644 index 0000000..d2b0ec1 Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_handbig.png differ diff --git a/bin/Data/Urho2D/imp/imp_handsmall.png b/bin/Data/Urho2D/imp/imp_handsmall.png new file mode 100644 index 0000000..f1287a6 Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_handsmall.png differ diff --git a/bin/Data/Urho2D/imp/imp_handthrow.png b/bin/Data/Urho2D/imp/imp_handthrow.png new file mode 100644 index 0000000..8e82588 Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_handthrow.png differ diff --git a/bin/Data/Urho2D/imp/imp_head.png b/bin/Data/Urho2D/imp/imp_head.png new file mode 100644 index 0000000..03db1f9 Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_head.png differ diff --git a/bin/Data/Urho2D/imp/imp_headangry.png b/bin/Data/Urho2D/imp/imp_headangry.png new file mode 100644 index 0000000..de8ea43 Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_headangry.png differ diff --git a/bin/Data/Urho2D/imp/imp_headblink.png b/bin/Data/Urho2D/imp/imp_headblink.png new file mode 100644 index 0000000..792b96b Binary files /dev/null and b/bin/Data/Urho2D/imp/imp_headblink.png differ diff --git a/bin/Data/Urho2D/isometric_grass_and_water.png b/bin/Data/Urho2D/isometric_grass_and_water.png new file mode 100644 index 0000000..68e5e67 Binary files /dev/null and b/bin/Data/Urho2D/isometric_grass_and_water.png differ diff --git a/bin/Data/Urho2D/isometric_grass_and_water.tmx b/bin/Data/Urho2D/isometric_grass_and_water.tmx new file mode 100644 index 0000000..4b32a26 --- /dev/null +++ b/bin/Data/Urho2D/isometric_grass_and_water.tmx @@ -0,0 +1,664 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Urho2D/sun.pex b/bin/Data/Urho2D/sun.pex new file mode 100644 index 0000000..cd7d114 --- /dev/null +++ b/bin/Data/Urho2D/sun.pex @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bin/Data/Urho2D/sun.png b/bin/Data/Urho2D/sun.png new file mode 100644 index 0000000..8da898c Binary files /dev/null and b/bin/Data/Urho2D/sun.png differ diff --git a/bin/Editor.bat b/bin/Editor.bat new file mode 100644 index 0000000..38d6fb2 --- /dev/null +++ b/bin/Editor.bat @@ -0,0 +1,7 @@ +@echo off +setlocal +set "dirname=%~dp0" +if %0 == "%~dpnx0" where /q "%cd%:%~nx0" && set "dirname=%cd%\" +if exist "%dirname%Urho3DPlayer.exe" (set "DEBUG=") else (set "DEBUG=_d") +if [%1] == [] (set "OPT1=-w -s") else (set "OPT1=") +start "" "%dirname%Urho3DPlayer%DEBUG%" Scripts/Editor.as %OPT1% %* diff --git a/bin/Editor.sh b/bin/Editor.sh new file mode 100644 index 0000000..b558940 --- /dev/null +++ b/bin/Editor.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +if [[ $# -eq 0 ]]; then OPT1="-w -s"; fi +$(dirname $0)/Urho3DPlayer Scripts/Editor.as $OPT1 $@ diff --git a/bin/NinjaSnowWar.bat b/bin/NinjaSnowWar.bat new file mode 100644 index 0000000..0ddbb8c --- /dev/null +++ b/bin/NinjaSnowWar.bat @@ -0,0 +1,17 @@ +:: To avoid cacophony of multiple background music when testing with multiple connections on the same test machine, +:: start all the other non-headless connections with '-nobgm' argument. +:: E.g. 1 - headless server +:: Start the server with "NinjaSnowWar -headless -server" +:: Start the first client with "NinjaSnowWar -w -address " +:: Start the subsequent clients on the same host with "NinjaSnowWar -w -nobgm -address " +:: +:: E.g. 2 - non-headless server +:: Start the server with "NinjaSnowWar -w -server" +:: Start the client on the same host with "NinjaSnowWar -w -nobgm -address " +:: +@echo off +setlocal +set "dirname=%~dp0" +if %0 == "%~dpnx0" where /q "%cd%:%~nx0" && set "dirname=%cd%\" +if exist "%dirname%Urho3DPlayer.exe" (set "DEBUG=") else (set "DEBUG=_d") +"%dirname%Urho3DPlayer%DEBUG%" Scripts/NinjaSnowWar.as %* diff --git a/bin/NinjaSnowWar.sh b/bin/NinjaSnowWar.sh new file mode 100644 index 0000000..78830ac --- /dev/null +++ b/bin/NinjaSnowWar.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# To avoid cacophony of multiple background music when testing with multiple connections on the same test machine, +# start all the other non-headless connections with '-nobgm' argument. +# E.g. 1 - headless server +# Start the server with "./NinjaSnowWar.sh -headless -server" +# Start the first client with "./NinjaSnowWar.sh -w -address `hostname`" +# Start the subsequent clients on the same host with "./NinjaSnowWar.sh -w -nobgm -address `hostname`" +# +# E.g. 2 - non-headless server +# Start the server with "./NinjaSnowWar.sh -w -server" +# Start the client on the same host with "./NinjaSnowWar.sh -w -nobgm -address `hostname`" +# +$(dirname $0)/Urho3DPlayer Scripts/NinjaSnowWar.as $@ diff --git a/bin/PBRDemo.bat b/bin/PBRDemo.bat new file mode 100644 index 0000000..02be95f --- /dev/null +++ b/bin/PBRDemo.bat @@ -0,0 +1,6 @@ +@echo off +setlocal +set "dirname=%~dp0" +if %0 == "%~dpnx0" where /q "%cd%:%~nx0" && set "dirname=%cd%\" +if exist "%dirname%Urho3DPlayer.exe" (set "DEBUG=") else (set "DEBUG=_d") +"%dirname%Urho3DPlayer%DEBUG%" Scripts/42_PBRMaterials.as %* diff --git a/bin/PBRDemo.sh b/bin/PBRDemo.sh new file mode 100644 index 0000000..660f9b3 --- /dev/null +++ b/bin/PBRDemo.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +$(dirname $0)/Urho3DPlayer Scripts/42_PBRMaterials.as $@ diff --git a/bin/PBRDemoDeferred.bat b/bin/PBRDemoDeferred.bat new file mode 100644 index 0000000..d553abf --- /dev/null +++ b/bin/PBRDemoDeferred.bat @@ -0,0 +1,6 @@ +@echo off +setlocal +set "dirname=%~dp0" +if %0 == "%~dpnx0" where /q "%cd%:%~nx0" && set "dirname=%cd%\" +if exist "%dirname%Urho3DPlayer.exe" (set "DEBUG=") else (set "DEBUG=_d") +"%dirname%Urho3DPlayer%DEBUG%" Scripts/42_PBRMaterials.as -renderpath CoreData/RenderPaths/PBRDeferred.xml %* diff --git a/bin/PBRDemoDeferredHWDepth.bat b/bin/PBRDemoDeferredHWDepth.bat new file mode 100644 index 0000000..e88fd80 --- /dev/null +++ b/bin/PBRDemoDeferredHWDepth.bat @@ -0,0 +1,6 @@ +@echo off +setlocal +set "dirname=%~dp0" +if %0 == "%~dpnx0" where /q "%cd%:%~nx0" && set "dirname=%cd%\" +if exist "%dirname%Urho3DPlayer.exe" (set "DEBUG=") else (set "DEBUG=_d") +"%dirname%Urho3DPlayer%DEBUG%" Scripts/42_PBRMaterials.as -renderpath CoreData/RenderPaths/PBRDeferredHWDepth.xml %* diff --git a/cmake_emscripten.sh b/cmake_emscripten.sh new file mode 100644 index 0000000..1520456 --- /dev/null +++ b/cmake_emscripten.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2008-2017 the Urho3D project. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +$(dirname $0)/cmake_generic.sh "$@" -DWEB=1 + +# vi: set ts=4 sw=4 expandtab: diff --git a/cmake_generic.sh b/cmake_generic.sh new file mode 100644 index 0000000..1bbe8b8 --- /dev/null +++ b/cmake_generic.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2008-2017 the Urho3D project. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +# Determine source tree and build tree +if [[ "$1" ]] && [[ ! "$1" =~ ^- ]]; then BUILD=$1; shift; elif [[ -f $(pwd)/CMakeCache.txt ]]; then BUILD=$(pwd); else caller=$(ps -o args= $PPID |cut -d' ' -f2); if [[ ! "$caller" =~ cmake_.*\.sh$ ]]; then caller=$0; fi; echo "Usage: ${caller##*/} /path/to/build-tree [build-options]"; exit 1; fi +SOURCE=$(cd ${0%/*}; pwd) +if [[ "$BUILD" == "." ]]; then BUILD=$(pwd); fi + +# Define helpers +. "$SOURCE"/.bash_helpers.sh + +# Detect CMake toolchains directory if it is not provided explicitly +[ "$TOOLCHAINS" == "" ] && TOOLCHAINS="$SOURCE"/CMake/Toolchains +[ ! -d "$TOOLCHAINS" -a -d "$URHO3D_HOME"/share/Urho3D/CMake/Toolchains ] && TOOLCHAINS="$URHO3D_HOME"/share/Urho3D/CMake/Toolchains + +# Default to native generator and toolchain if none is specified explicitly +IFS=# +OPTS= +for a in $@; do + case $a in + --fix-scm) + FIX_SCM=1 + ;; + Eclipse\ CDT4\ -\ Unix\ Makefiles) + ECLIPSE=1 + ;; + -DIOS=1) + IOS=1 + ;; + -DTVOS=1) + TVOS=1 + ;; + -DANDROID=1) + ANDROID=1 && OPTS="-DCMAKE_TOOLCHAIN_FILE=$TOOLCHAINS/Android.cmake" + ;; + -DRPI=1) + if [[ ! $(uname -m) =~ ^arm ]]; then OPTS="-DCMAKE_TOOLCHAIN_FILE=$TOOLCHAINS/RaspberryPi.cmake"; fi + ;; + -DARM=1) + if [[ ! $(uname -m) =~ ^(arm|aarch64) ]]; then OPTS="-DCMAKE_TOOLCHAIN_FILE=$TOOLCHAINS/Arm.cmake"; fi + ;; + -DWIN32=1) + OPTS="-DCMAKE_TOOLCHAIN_FILE=$TOOLCHAINS/MinGW.cmake" + ;; + -DWEB=1) + OPTS="-DCMAKE_TOOLCHAIN_FILE=$TOOLCHAINS/Emscripten.cmake" + ;; + esac +done + +# Create project with the chosen CMake generator and toolchain +cmake -E make_directory "$BUILD" && cmake -E chdir "$BUILD" cmake $OPTS $@ "$SOURCE" && post_cmake + +# vi: set ts=4 sw=4 expandtab: diff --git a/example.gif b/example.gif new file mode 100644 index 0000000..9ee2f0a Binary files /dev/null and b/example.gif differ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1a996d4 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include + +using namespace Urho3D; + +class MyApp: public Application +{ +public: + MyApp(Context *context) + : Application(context) + { + } + + virtual void Setup() + { + engineParameters_["FullScreen"] = false; + engineParameters_["WindowWidth"] = 600; + engineParameters_["WindowHeight"] = 600; + engineParameters_["WindowResizable"] = false; + GetSubsystem()->SetMouseVisible(true); + GetSubsystem()->SetMouseGrabbed(false); + } + + virtual void Start() + { + XMLFile *style = GetSubsystem()->GetResource("UI/DefaultStyle.xml"); + GetSubsystem()->SetUseSystemClipboard(true); + + MultiLineEdit *multiLineEdit = new MultiLineEdit(GetContext()); + multiLineEdit->SetStyle("MultiLineEdit", style); + GetSubsystem()->GetRoot()->AddChild(multiLineEdit); + multiLineEdit->SetPosition(50, 50); + multiLineEdit->SetWidth(500); + multiLineEdit->SetHeight(500); + multiLineEdit->SetDragDropMode(DragAndDropMode::DD_SOURCE_AND_TARGET); + multiLineEdit->SetText("Text\n123\n123"); + } +}; + +URHO3D_DEFINE_APPLICATION_MAIN(MyApp)