瀏覽代碼

Wrote AngelScript bindings, I'm still considering renaming a few things here and there

TheComet 8 年之前
父節點
當前提交
2be9286d38

+ 33 - 0
Source/Samples/45_InverseKinematics/CMakeLists.txt

@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+# Define target name
+set (TARGET_NAME 45_InverseKinematics)
+
+# Define source files
+define_source_files (EXTRA_H_FILES ${COMMON_SAMPLE_H_FILES})
+
+# Setup target with resource copying
+setup_main_executable ()
+
+# Setup test cases
+setup_test ()

+ 225 - 0
Source/Samples/45_InverseKinematics/InverseKinematics.cpp

@@ -0,0 +1,225 @@
+//
+// Copyright (c) 2008-2017 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <Urho3D/Core/CoreEvents.h>
+#include <Urho3D/Engine/Engine.h>
+#include <Urho3D/Graphics/AnimatedModel.h>
+#include <Urho3D/Graphics/AnimationController.h>
+#include <Urho3D/Graphics/Camera.h>
+#include <Urho3D/Graphics/Graphics.h>
+#include <Urho3D/Graphics/Material.h>
+#include <Urho3D/Graphics/Octree.h>
+#include <Urho3D/Graphics/Renderer.h>
+#include <Urho3D/Graphics/RibbonTrail.h>
+#include <Urho3D/IK/IKEffector.h>
+#include <Urho3D/IK/IKSolver.h>
+#include <Urho3D/Input/Input.h>
+#include <Urho3D/Resource/ResourceCache.h>
+#include <Urho3D/Scene/Scene.h>
+#include <Urho3D/UI/Font.h>
+#include <Urho3D/UI/Text.h>
+#include <Urho3D/UI/UI.h>
+#include <Urho3D/UI/Text3D.h>
+
+#include "InverseKinematics.h"
+
+#include <Urho3D/DebugNew.h>
+
+URHO3D_DEFINE_APPLICATION_MAIN(InverseKinematics)
+
+InverseKinematics::InverseKinematics(Context* context) :
+    Sample(context),
+    timeStepSum_(0.0f)
+{
+}
+
+void InverseKinematics::Start()
+{
+    // Execute base class startup
+    Sample::Start();
+
+    // Create the scene content
+    CreateScene();
+
+    // Create the UI content
+    CreateInstructions();
+
+    // Setup the viewport for displaying the scene
+    SetupViewport();
+
+    // Hook up to the frame update events
+    SubscribeToEvents();
+
+    // Set the mouse mode to use in the sample
+    Sample::InitMouseMode(MM_RELATIVE);
+
+    GetSubsystem<Input>()->SetMouseVisible(true);
+}
+
+void InverseKinematics::CreateScene()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    scene_ = new Scene(context_);
+
+    // 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->SetScale(Vector3(100.0f, 1.0f, 100.0f));
+    StaticModel* planeObject = planeNode->CreateComponent<StaticModel>();
+    planeObject->SetModel(cache->GetResource<Model>("Models/Plane.mdl"));
+    planeObject->SetMaterial(cache->GetResource<Material>("Materials/StoneTiled.xml"));
+
+    // Create a directional light to the world.
+    Node* lightNode = scene_->CreateChild("DirectionalLight");
+    lightNode->SetDirection(Vector3(0.6f, -1.0f, 0.8f)); // The direction vector does not need to be normalized
+    Light* light = lightNode->CreateComponent<Light>();
+    light->SetLightType(LIGHT_DIRECTIONAL);
+    light->SetCastShadows(true);
+    light->SetShadowBias(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->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f));
+
+    // Load Jack model and walking animation.
+    Node* jackNode = scene_->CreateChild("Jack");
+    jackNode->SetPosition(Vector3(5.0f, 0.0f, 0.0f));
+    jackNode->SetRotation(Quaternion(0.0f, 180.0f, 0.0f));
+    AnimatedModel* ninja = jackNode->CreateComponent<AnimatedModel>();
+    ninja->SetModel(cache->GetResource<Model>("Models/Jack.mdl"));
+    ninja->SetMaterial(cache->GetResource<Material>("Materials/Jack.xml"));
+    ninja->SetCastShadows(true);
+
+    // We need to attach two inverse kinematic effectors to Jack's feet to
+    // control the grounding.
+    Node* leftToe  = jackNode->GetChild("Bip01_L_Toe0", true);
+    Node* rightToe = jackNode->GetChild("Bip01_R_Toe0", true);
+    leftEffector_  = leftToe->CreateComponent<IKEffector>();
+    rightEffector_ = rightToe->CreateComponent<IKEffector>();
+    // Control 3 segments up to the hips
+    leftEffector_->SetChainLength(3);
+    rightEffector_->SetChainLength(3);
+
+    // 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);
+    IKSolver* solver = spine->CreateComponent<IKSolver>();
+
+    // We want to solve for target rotations so the feet can be properly
+    // rotated to match the incline. Sometimes, if the feet are small, this
+    // isn't necessary. It does have a performance impact on the solver.
+    solver->EnableTargetRotation(true);
+
+    // Set the effectors so only the feet bones are affected by the target
+    // rotation. This is accomplished by setting the rotation weight to 1 and
+    // then decay by a factor of 0 so the next bone has weight 0.
+    leftEffector_->SetRotationWeight(1.0);
+    leftEffector_->SetRotationDecay(0.0);
+    rightEffector_->SetRotationWeight(1.0);
+    rightEffector_->SetRotationDecay(0.0);
+
+    // Create animation controller and play walk animation
+    jackAnimController_ = jackNode->CreateComponent<AnimationController>();
+    jackAnimController_->PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.0f);
+
+    // Create the camera.
+    cameraRotateNode_ = scene_->CreateChild("CameraAttach");
+    cameraNode_ = cameraRotateNode_->CreateChild("Camera");
+    cameraNode_->CreateComponent<Camera>();
+
+    // Set an initial position for the camera scene node above the plane
+    cameraNode_->SetPosition(Vector3(0.0f, 0.0f, -14.0f));
+    pitch_ = 5;
+}
+
+void InverseKinematics::CreateInstructions()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    UI* ui = GetSubsystem<UI>();
+
+    // Construct new Text object, set string to display and font to use
+    Text* instructionText = ui->GetRoot()->CreateChild<Text>();
+    instructionText->SetText("Use WASD keys and mouse/touch to move");
+    instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
+
+    // Position the text relative to the screen center
+    instructionText->SetHorizontalAlignment(HA_CENTER);
+    instructionText->SetVerticalAlignment(VA_CENTER);
+    instructionText->SetPosition(0, ui->GetRoot()->GetHeight() / 4);
+}
+
+void InverseKinematics::SetupViewport()
+{
+    Renderer* renderer = GetSubsystem<Renderer>();
+
+    // 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
+    SharedPtr<Viewport> viewport(new Viewport(context_, scene_, cameraNode_->GetComponent<Camera>()));
+    renderer->SetViewport(0, viewport);
+}
+
+void InverseKinematics::MoveCamera(float timeStep)
+{
+    // Do not move if the UI has a focused element (the console)
+    if (GetSubsystem<UI>()->GetFocusElement())
+        return;
+
+    Input* input = GetSubsystem<Input>();
+
+    // 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
+    if (input->GetMouseButtonDown(MOUSEB_LEFT))
+    {
+        IntVector2 mouseMove = input->GetMouseMove();
+        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
+    cameraRotateNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f));
+}
+
+void InverseKinematics::SubscribeToEvents()
+{
+    // Subscribe HandleUpdate() function for processing update events
+    SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(InverseKinematics, HandleUpdate));
+}
+
+void InverseKinematics::HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace Update;
+
+    // Take the frame time step, which is stored as a float
+    float timeStep = eventData[P_TIMESTEP].GetFloat();
+
+    // Move the camera, scale movement with time step
+    MoveCamera(timeStep);
+}

+ 75 - 0
Source/Samples/45_InverseKinematics/InverseKinematics.h

@@ -0,0 +1,75 @@
+//
+// Copyright (c) 2008-2017 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "Sample.h"
+
+namespace Urho3D
+{
+
+class AnimationController;
+class Node;
+class IKEffector;
+class Scene;
+class RibbonTrail;
+
+}
+
+/// Ribbon trail demo.
+/// This sample demonstrates how to use both trail types of RibbonTrail component.
+class InverseKinematics : public Sample
+{
+    URHO3D_OBJECT(InverseKinematics, Sample);
+
+public:
+    /// Construct.
+    InverseKinematics(Context* context);
+
+    /// Setup after engine initialization and before running the main loop.
+    virtual void Start();
+
+protected:
+    /// Animation controller of the ninja.
+    SharedPtr<Urho3D::AnimationController> jackAnimController_;
+    /// Inverse kinematic effectors
+    SharedPtr<Urho3D::IKEffector> leftEffector_;
+    SharedPtr<Urho3D::IKEffector> rightEffector_;
+    /// Sum of timestep.
+    float timeStepSum_;
+
+private:
+    /// Construct the scene content.
+    void CreateScene();
+    /// Construct an instruction text to the UI.
+    void CreateInstructions();
+    /// Set up a viewport for displaying the scene.
+    void SetupViewport();
+    /// Read input and moves the camera.
+    void MoveCamera(float timeStep);
+    /// Subscribe to application-wide logic update events.
+    void SubscribeToEvents();
+    /// Handle the logic update event.
+    void HandleUpdate(StringHash eventType, VariantMap& eventData);
+
+    SharedPtr<Node> cameraRotateNode_;
+};

+ 103 - 0
Source/Urho3D/AngelScript/IKAPI.cpp

@@ -0,0 +1,103 @@
+//
+// 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.
+//
+
+#ifdef URHO3D_PHYSICS
+
+#include "../Precompiled.h"
+
+#include "../AngelScript/APITemplates.h"
+#include "../IK/IKSolver.h"
+#include "../IK/IKEffector.h"
+#include "../IK/IKConstraint.h"
+
+namespace Urho3D
+{
+
+static void RegisterIKSolver(asIScriptEngine* engine)
+{
+    engine->RegisterEnum("IKAlgorithm");
+    engine->RegisterEnumValue("IKAlgorithm", "FABRIK", IKSolver::FABRIK);
+
+    RegisterComponent<IKSolver>(engine, "IKSolver");
+    engine->RegisterObjectMethod("IKSolver", "IKAlgorithm get_algorithm() const", asMETHOD(IKSolver, GetAlgorithm), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void set_algorithm(IKAlgorithm)", asMETHOD(IKSolver, SetAlgorithm), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "uint get_maximumIterations() const", asMETHOD(IKSolver, GetMaximumIterations), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void set_maximumIterations(uint)", asMETHOD(IKSolver, SetMaximumIterations), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "float get_tolerance() const", asMETHOD(IKSolver, GetTolerance), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void set_tolerance(float)", asMETHOD(IKSolver, SetTolerance), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "bool get_boneRotations() const", asMETHOD(IKSolver, BoneRotationsEnabled), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void set_boneRotations(bool)", asMETHOD(IKSolver, EnableBoneRotations), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "bool get_targetRotation() const", asMETHOD(IKSolver, TargetRotationEnabled), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void set_targetRotation(bool)", asMETHOD(IKSolver, EnableTargetRotation), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "bool get_continuousSolving() const", asMETHOD(IKSolver, ContinuousSolvingEnabled), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void set_continuousSolving(bool)", asMETHOD(IKSolver, EnableContinuousSolving), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "bool get_updatePose() const", asMETHOD(IKSolver, UpdatePoseEnabled), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void set_updatePose(bool)", asMETHOD(IKSolver, EnableUpdatePose), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "bool get_autoSolve() const", asMETHOD(IKSolver, AutoSolveEnabled), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void set_autoSolve(bool)", asMETHOD(IKSolver, EnableAutoSolve), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void Solve()", asMETHOD(IKSolver, Solve), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void ResetToInitialPose()", asMETHOD(IKSolver, ResetToInitialPose), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void UpdateInitialPose()", asMETHOD(IKSolver, UpdateInitialPose), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKSolver", "void DrawDebugGeometry(bool)", asMETHODPR(IKSolver, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
+}
+
+static void RegisterIKEffector(asIScriptEngine* engine)
+{
+    RegisterComponent<IKEffector>(engine, "IKEffector");
+    engine->RegisterObjectMethod("IKEffector", "Node@+ get_targetNode() const", asMETHOD(IKEffector, GetTargetNode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void set_targetNode(Node@+)", asMETHOD(IKEffector, SetTargetNode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "String& get_targetName() const", asMETHOD(IKEffector, GetTargetName), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void set_targetName(const String&in)", asMETHOD(IKEffector, SetTargetName), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "Vector3& get_targetPosition() const", asMETHOD(IKEffector, GetTargetPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void set_targetPosition(Vector3&in)", asMETHOD(IKEffector, SetTargetPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "Quaternion& get_targetRotation() const", asMETHOD(IKEffector, GetTargetRotation), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void set_targetRotation(Quaternion&in)", asMETHOD(IKEffector, SetTargetRotation), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "uint get_chainLength() const", asMETHOD(IKEffector, GetChainLength), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void set_chainLength(uint)", asMETHOD(IKEffector, SetChainLength), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "float get_weight() const", asMETHOD(IKEffector, GetWeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void set_weight(float)", asMETHOD(IKEffector, SetWeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "float get_rotationWeight() const", asMETHOD(IKEffector, GetRotationWeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void set_rotationWeight(float)", asMETHOD(IKEffector, SetRotationWeight), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "float get_rotationDecay() const", asMETHOD(IKEffector, GetRotationDecay), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void set_rotationDecay(float)", asMETHOD(IKEffector, SetRotationDecay), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "bool get_weightedNlerp() const", asMETHOD(IKEffector, WeightedNlerpEnabled), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void set_weightedNlerp(bool)", asMETHOD(IKEffector, EnableWeightedNlerp), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "bool get_inheritParentRotation() const", asMETHOD(IKEffector, InheritParentRotationEnabled), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void set_inheritParentRotation(bool)", asMETHOD(IKEffector, EnableInheritParentRotation), asCALL_THISCALL);
+    engine->RegisterObjectMethod("IKEffector", "void DrawDebugGeometry(bool)", asMETHODPR(IKEffector, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
+}
+
+static void RegisterIKConstraint(asIScriptEngine* engine)
+{
+    RegisterComponent<IKConstraint>(engine, "IKConstraint");
+}
+
+void RegisterIKAPI(asIScriptEngine* engine)
+{
+    RegisterIKSolver(engine);
+    RegisterIKEffector(engine);
+    RegisterIKConstraint(engine);
+}
+
+}
+
+#endif

+ 3 - 0
Source/Urho3D/AngelScript/Script.cpp

@@ -126,6 +126,9 @@ Script::Script(Context* context) :
 #ifdef URHO3D_DATABASE
     RegisterDatabaseAPI(scriptEngine_);
 #endif
+#ifdef URHO3D_IK
+    RegisterIKAPI(scriptEngine_);
+#endif
 #ifdef URHO3D_PHYSICS
     RegisterPhysicsAPI(scriptEngine_);
 #endif

+ 4 - 0
Source/Urho3D/AngelScript/ScriptAPI.h

@@ -55,6 +55,10 @@ void RegisterNetworkAPI(asIScriptEngine* engine);
 /// Register the Database library to script.
 void RegisterDatabaseAPI(asIScriptEngine* engine);
 #endif
+#ifdef URHO3D_IK
+/// Register the inverse kinematics library to script
+void RegisterIKAPI(asIScriptEngine* engine);
+#endif
 #ifdef URHO3D_PHYSICS
 /// Register the Physics library to script.
 void RegisterPhysicsAPI(asIScriptEngine* engine);

+ 1 - 1
Source/Urho3D/CMakeLists.txt

@@ -210,7 +210,7 @@ if (URHO3D_LUA)
     # Use the host tool to generate source files for tolua++ API binding
     file (MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/LuaScript/generated)
     file (GLOB API_PKG_FILES LuaScript/pkgs/*.pkg)
-    foreach (DIR Navigation Network Database Physics Urho2D)
+    foreach (DIR IK Navigation Network Database Physics Urho2D)
         string (TOUPPER URHO3D_${DIR} OPT)
         if (NOT ${OPT})
             list (REMOVE_ITEM API_PKG_FILES ${CMAKE_CURRENT_SOURCE_DIR}/LuaScript/pkgs/${DIR}LuaAPI.pkg)

+ 7 - 10
Source/Urho3D/IK/IK.h

@@ -21,20 +21,17 @@
 //
 
 /*
- * TODO IK todo
- *  - Actually implement tolerance.
- *  - Target angle in addition to target position -> use weighted angles
- *    approach
- *  - Fix rotation issue with shared sub-base nodes -> rotations need to be
- *    averaged.
+ * TODO
  *  - Add support for manually updating initial pose.
- *  - Pole targets?
- *  - Support for "stretchiness" with min/max lengths.
- *  - Support for "stiffness" factor, describes how well a bone rotates.
- *  - Apply bullet constraints to joints.
  *  - Script bindings.
  *  - Optimise.
  *  - Profile.
+ *  - Documentation.
+ *
+ * FUTURE
+ *  - Support for "stretchiness" with min/max lengths.
+ *  - Support for "stiffness" factor, describes how well a bone rotates.
+ *  - Apply bullet constraints to joints.
  */
 
 namespace Urho3D

+ 20 - 7
Source/Urho3D/IK/IKEffector.cpp

@@ -67,8 +67,8 @@ void IKEffector::RegisterObject(Context* context)
     URHO3D_ACCESSOR_ATTRIBUTE("Weight", GetWeight, SetWeight, float, 1.0, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Rotation Weight", GetRotationWeight, SetRotationWeight, float, 1.0, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Rotation Decay", GetRotationDecay, SetRotationDecay, float, 0.25, AM_DEFAULT);
-    URHO3D_ACCESSOR_ATTRIBUTE("Nlerp Weight", DoWeightedNlerp, SetWeightedNlerp, bool, false, AM_DEFAULT);
-    URHO3D_ACCESSOR_ATTRIBUTE("Inherit Parent Rotation", DoInheritParentRotation, SetInheritParentRotation, bool, false, AM_DEFAULT);
+    URHO3D_ACCESSOR_ATTRIBUTE("Nlerp Weight", WeightedNlerpEnabled, EnableWeightedNlerp, bool, false, AM_DEFAULT);
+    URHO3D_ACCESSOR_ATTRIBUTE("Inherit Parent Rotation", InheritParentRotationEnabled, EnableInheritParentRotation, bool, false, AM_DEFAULT);
 }
 
 // ----------------------------------------------------------------------------
@@ -205,13 +205,13 @@ void IKEffector::SetRotationDecay(float decay)
 }
 
 // ----------------------------------------------------------------------------
-bool IKEffector::DoWeightedNlerp() const
+bool IKEffector::WeightedNlerpEnabled() const
 {
     return weightedNlerp_;
 }
 
 // ----------------------------------------------------------------------------
-void IKEffector::SetWeightedNlerp(bool enable)
+void IKEffector::EnableWeightedNlerp(bool enable)
 {
     weightedNlerp_ = enable;
     if (ikEffector_ != NULL)
@@ -223,13 +223,13 @@ void IKEffector::SetWeightedNlerp(bool enable)
 }
 
 // ----------------------------------------------------------------------------
-bool IKEffector::DoInheritParentRotation() const
+bool IKEffector::InheritParentRotationEnabled() const
 {
     return inheritParentRotation_;
 }
 
 // ----------------------------------------------------------------------------
-void IKEffector::SetInheritParentRotation(bool enable)
+void IKEffector::EnableInheritParentRotation(bool enable)
 {
     inheritParentRotation_ = enable;
     if(ikEffector_ != NULL)
@@ -254,6 +254,14 @@ void IKEffector::UpdateTargetNodePosition()
     SetTargetRotation(targetNode_->GetWorldRotation());
 }
 
+// ----------------------------------------------------------------------------
+void IKEffector::DrawDebugGeometry(bool depthTest)
+{
+    DebugRenderer* debug = GetScene()->GetComponent<DebugRenderer>();
+    if (debug)
+        DrawDebugGeometry(debug, depthTest);
+}
+
 // ----------------------------------------------------------------------------
 void IKEffector::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
@@ -302,6 +310,11 @@ void IKEffector::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
         a = b;
         b = b->GetParent();
     }
+
+    Vector3 direction = targetRotation_ * Vector3::FORWARD;
+    direction = direction * averageLength + targetPosition_;
+    debug->AddSphere(Sphere(targetPosition_, averageLength * 0.2), Color(255, 128, 0), depthTest);
+    debug->AddLine(targetPosition_, direction, Color(255, 128, 0), depthTest);
 }
 
 // ----------------------------------------------------------------------------
@@ -316,7 +329,7 @@ void IKEffector::SetEffector(ik_effector_t* effector)
         effector->rotation_weight = rotationWeight_;
         effector->rotation_decay = rotationDecay_;
         effector->chain_length = chainLength_;
-        SetWeightedNlerp(weightedNlerp_);
+        EnableWeightedNlerp(weightedNlerp_);
     }
 }
 

+ 5 - 4
Source/Urho3D/IK/IKEffector.h

@@ -101,14 +101,15 @@ public:
     float GetRotationDecay() const;
     void SetRotationDecay(float decay);
 
-    bool DoWeightedNlerp() const;
-    void SetWeightedNlerp(bool enable);
+    bool WeightedNlerpEnabled() const;
+    void EnableWeightedNlerp(bool enable);
 
-    bool DoInheritParentRotation() const;
-    void SetInheritParentRotation(bool enable);
+    bool InheritParentRotationEnabled() const;
+    void EnableInheritParentRotation(bool enable);
 
     void UpdateTargetNodePosition();
 
+    void DrawDebugGeometry(bool depthTest);
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
 
     void SetSolver(IKSolver* solver);

+ 49 - 15
Source/Urho3D/IK/IKSolver.cpp

@@ -74,17 +74,17 @@ IKSolver::IKSolver(Context* context) :
     Component(context),
     solver_(NULL),
     solverTreeNeedsRebuild_(false),
-    updateInitialPose_(false)
+    updateInitialPose_(false),
+    autoSolveEnabled_(true)
 {
     context_->RequireIK();
 
     SetAlgorithm(FABRIK);
 
-    SubscribeToEvent(E_COMPONENTADDED,              URHO3D_HANDLER(IKSolver, HandleComponentAdded));
-    SubscribeToEvent(E_COMPONENTREMOVED,            URHO3D_HANDLER(IKSolver, HandleComponentRemoved));
-    SubscribeToEvent(E_NODEADDED,                   URHO3D_HANDLER(IKSolver, HandleNodeAdded));
-    SubscribeToEvent(E_NODEREMOVED,                 URHO3D_HANDLER(IKSolver, HandleNodeRemoved));
-    SubscribeToEvent(E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
+    SubscribeToEvent(GetScene(), E_COMPONENTADDED,              URHO3D_HANDLER(IKSolver, HandleComponentAdded));
+    SubscribeToEvent(GetScene(), E_COMPONENTREMOVED,            URHO3D_HANDLER(IKSolver, HandleComponentRemoved));
+    SubscribeToEvent(GetScene(), E_NODEADDED,                   URHO3D_HANDLER(IKSolver, HandleNodeAdded));
+    SubscribeToEvent(GetScene(), E_NODEREMOVED,                 URHO3D_HANDLER(IKSolver, HandleNodeRemoved));
 }
 
 // ----------------------------------------------------------------------------
@@ -116,10 +116,10 @@ void IKSolver::RegisterObject(Context* context)
     URHO3D_ENUM_ACCESSOR_ATTRIBUTE("Algorithm", GetAlgorithm, SetAlgorithm, Algorithm, algorithmNames, SOLVER_FABRIK, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Max Iterations", GetMaximumIterations, SetMaximumIterations, unsigned, 20, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Convergence Tolerance", GetTolerance, SetTolerance, float, 0.001, AM_DEFAULT);
-    URHO3D_ACCESSOR_ATTRIBUTE("Bone Rotations", BoneRotationsEnabled, SetBoneRotations, bool, true, AM_DEFAULT);
-    URHO3D_ACCESSOR_ATTRIBUTE("Target Rotation", TargetRotationEnabled, SetTargetRotation, bool, false, AM_DEFAULT);
-    URHO3D_ACCESSOR_ATTRIBUTE("Continuous Solving", ContinuousSolvingEnabled, SetContinuousSolving, bool, false, AM_DEFAULT);
-    URHO3D_ACCESSOR_ATTRIBUTE("Update Pose", UpdatePoseEnabled, SetUpdatePose, bool, false, AM_DEFAULT);
+    URHO3D_ACCESSOR_ATTRIBUTE("Bone Rotations", BoneRotationsEnabled, EnableBoneRotations, bool, true, AM_DEFAULT);
+    URHO3D_ACCESSOR_ATTRIBUTE("Target Rotation", TargetRotationEnabled, EnableTargetRotation, bool, false, AM_DEFAULT);
+    URHO3D_ACCESSOR_ATTRIBUTE("Continuous Solving", ContinuousSolvingEnabled, EnableContinuousSolving, bool, false, AM_DEFAULT);
+    URHO3D_ACCESSOR_ATTRIBUTE("Update Pose", UpdatePoseEnabled, EnableUpdatePose, bool, false, AM_DEFAULT);
 }
 
 // ----------------------------------------------------------------------------
@@ -179,7 +179,7 @@ bool IKSolver::BoneRotationsEnabled() const
 }
 
 // ----------------------------------------------------------------------------
-void IKSolver::SetBoneRotations(bool enable)
+void IKSolver::EnableBoneRotations(bool enable)
 {
     solver_->flags &= ~SOLVER_CALCULATE_FINAL_ROTATIONS;
     if (enable)
@@ -193,7 +193,7 @@ bool IKSolver::TargetRotationEnabled() const
 }
 
 // ----------------------------------------------------------------------------
-void IKSolver::SetTargetRotation(bool enable)
+void IKSolver::EnableTargetRotation(bool enable)
 {
     solver_->flags &= ~SOLVER_CALCULATE_TARGET_ROTATIONS;
     if (enable)
@@ -207,7 +207,7 @@ bool IKSolver::ContinuousSolvingEnabled() const
 }
 
 // ----------------------------------------------------------------------------
-void IKSolver::SetContinuousSolving(bool enable)
+void IKSolver::EnableContinuousSolving(bool enable)
 {
     solver_->flags &= ~SOLVER_SKIP_RESET;
     if (enable)
@@ -221,7 +221,7 @@ bool IKSolver::UpdatePoseEnabled() const
 }
 
 // ----------------------------------------------------------------------------
-void IKSolver::SetUpdatePose(bool enable)
+void IKSolver::EnableUpdatePose(bool enable)
 {
     updateInitialPose_ = enable;
 }
@@ -232,6 +232,26 @@ void IKSolver::MarkSolverTreeDirty()
     solverTreeNeedsRebuild_ = true;
 }
 
+// ----------------------------------------------------------------------------
+bool IKSolver::AutoSolveEnabled() const
+{
+    return autoSolveEnabled_;
+}
+
+// ----------------------------------------------------------------------------
+void IKSolver::EnableAutoSolve(bool enable)
+{
+    if (autoSolveEnabled_ == enable)
+        return;
+
+    if (enable)
+        SubscribeToEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
+    else
+        UnsubscribeFromEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED);
+
+    autoSolveEnabled_ = enable;
+}
+
 // ----------------------------------------------------------------------------
 static void ApplySolvedDataCallback(ik_node_t* ikNode)
 {
@@ -299,10 +319,16 @@ void IKSolver::UpdateInitialPose()
  * also monitor E_NODEREMOVED AND E_NODEADDED.
  */
 
+// ----------------------------------------------------------------------------
+void IKSolver::OnSceneSet(Scene* scene)
+{
+    if (autoSolveEnabled_)
+        SubscribeToEvent(scene, E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
+}
+
 // ----------------------------------------------------------------------------
 void IKSolver::OnNodeSet(Node* node)
 {
-    ResetToInitialPose();
     if (node == NULL)
         ik_solver_destroy_tree(solver_);
     else
@@ -463,6 +489,14 @@ void IKSolver::HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMa
     Solve();
 }
 
+// ----------------------------------------------------------------------------
+void IKSolver::DrawDebugGeometry(bool depthTest)
+{
+    DebugRenderer* debug = GetScene()->GetComponent<DebugRenderer>();
+    if (debug)
+        DrawDebugGeometry(debug, depthTest);
+}
+
 // ----------------------------------------------------------------------------
 void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {

+ 12 - 6
Source/Urho3D/IK/IKSolver.h

@@ -111,27 +111,32 @@ public:
     void SetTolerance(float tolerance);
 
     bool BoneRotationsEnabled() const;
-    void SetBoneRotations(bool enable);
+    void EnableBoneRotations(bool enable);
 
     bool TargetRotationEnabled() const;
-    void SetTargetRotation(bool enable);
+    void EnableTargetRotation(bool enable);
 
     bool ContinuousSolvingEnabled() const;
-    void SetContinuousSolving(bool enable);
+    void EnableContinuousSolving(bool enable);
 
     bool UpdatePoseEnabled() const;
-    void SetUpdatePose(bool enable);
+    void EnableUpdatePose(bool enable);
 
-    /// Causes the solver tree to be rebuilt before solving the next time.
-    void MarkSolverTreeDirty();
+    bool AutoSolveEnabled() const;
+    void EnableAutoSolve(bool enable);
 
     void Solve();
     void ResetToInitialPose();
     void UpdateInitialPose();
 
+    /// Causes the solver tree to be rebuilt before solving the next time.
+    void MarkSolverTreeDirty();
+
+    void DrawDebugGeometry(bool depthTest);
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
 
 private:
+    virtual void OnSceneSet(Scene* scene);
     virtual void OnNodeSet(Node* scene);
     /// Causes the entire tree to be rebuilt
     void RebuildTree();
@@ -149,6 +154,7 @@ private:
     Algorithm algorithm_;
     bool solverTreeNeedsRebuild_;
     bool updateInitialPose_;
+    bool autoSolveEnabled_;
 };
 
 } // namespace Urho3D

+ 5 - 0
Source/Urho3D/LuaScript/pkgs/IK/IKConstraint.pkg

@@ -0,0 +1,5 @@
+#include "Scene/Component.h"
+
+class IKConstraint : public Component
+{
+};

+ 31 - 0
Source/Urho3D/LuaScript/pkgs/IK/IKEffector.pkg

@@ -0,0 +1,31 @@
+#include "Scene/Component.h"
+#include "Scene/Scene.h"
+
+class IKEffector : public Component
+{
+    Node* GetTargetNode() const;
+    void SetTargetNode(Node* targetNode);
+    const String& GetTargetName() const;
+    void SetTargetName(const String& nodeName);
+    const Vector3& GetTargetPosition() const;
+    void SetTargetPosition(const Vector3& targetPosition);
+    const Quaternion& GetTargetRotation() const;
+    void SetTargetRotation(const Quaternion& targetRotation);
+    Vector3 GetTargetRotationEuler() const;
+    void SetTargetRotationEuler(const Vector3& targetRotation);
+    unsigned GetChainLength() const;
+    void SetChainLength(unsigned chainLength);
+    float GetWeight() const;
+    void SetWeight(float weight);
+    float GetRotationWeight() const;
+    void SetRotationWeight(float weight);
+    float GetRotationDecay() const;
+    void SetRotationDecay(float decay);
+    bool DoWeightedNlerp() const;
+    void SetWeightedNlerp(bool enable);
+    bool DoInheritParentRotation() const;
+    void SetInheritParentRotation(bool enable);
+    void UpdateTargetNodePosition();
+};
+
+} // namespace Urho3D

+ 31 - 0
Source/Urho3D/LuaScript/pkgs/IK/IKSolver.pkg

@@ -0,0 +1,31 @@
+#include "IK/IKEffector.h"
+#include "Scene/Component.h"
+
+class IKSolver : public Component
+{
+    enum Algorithm
+    {
+        FABRIK
+    };
+
+    Algorithm GetAlgorithm() const;
+    void SetAlgorithm(Algorithm algorithm);
+    unsigned GetMaximumIterations() const;
+    void SetMaximumIterations(unsigned iterations);
+    float GetTolerance() const;
+    void SetTolerance(float tolerance);
+    bool BoneRotationsEnabled() const;
+    void EnableBoneRotations(bool enable);
+    bool TargetRotationEnabled() const;
+    void EnableTargetRotation(bool enable);
+    bool ContinuousSolvingEnabled() const;
+    void EnableContinuousSolving(bool enable);
+    bool UpdatePoseEnabled() const;
+    void EnableUpdatePose(bool enable);
+
+    void Solve();
+    void ResetToInitialPose();
+    void UpdateInitialPose();
+};
+
+} // namespace Urho3D

+ 250 - 0
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(100.0f, 1.0f, 100.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("Ninja");
+    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");
+
+    // Disable auto-solving, which means we can call Solve() manually.
+    solver_.autoSolve = false;
+
+    // When this is enabled, the solver will use the current positions of the
+    // nodes in the skeleton as its basis. If you disable this, then the solver
+    // will store the initial positions of the nodes once and always use those
+    // positions.
+    // With animated characters you generally want to continuously update the
+    // initial positions.
+    solver_.updatePose = 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;
+
+    // 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
+    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;
+        Vector3 mouseMove = Matrix3(
+            -Cos(yaw), Sin(yaw), 0,
+            Sin(yaw),  Cos(yaw), 0,
+            0,         0,        1
+        ) * Vector3(mouseMoveInt.y, -mouseMoveInt.x, 0);
+        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 leftTarget = leftFoot_.worldPosition;
+    Vector3 rightTarget = rightFoot_.worldPosition;
+
+    PhysicsRaycastResult result = scene_.physicsWorld.RaycastSingle(Ray(leftTarget + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2);
+    if (result.body !is null)
+    {
+        result = scene_.physicsWorld.RaycastSingle(Ray(leftTarget + result.normal, -result.normal), 2);
+        leftTarget = result.position;
+        leftFoot_.Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD);
+    }
+
+    result = scene_.physicsWorld.RaycastSingle(Ray(rightTarget + Vector3(0, 1, 0), Vector3(0, -1, 0)), 2);
+    if (result.body !is null)
+    {
+        result = scene_.physicsWorld.RaycastSingle(Ray(rightTarget + result.normal, -result.normal), 2);
+        rightTarget = result.position;
+        rightFoot_.Rotate(Quaternion(Vector3(0, 1, 0), result.normal), TS_WORLD);
+    }
+
+    Vector3 leftFootOffset = Vector3(0, jackNode_.worldPosition.y + leftFoot_.worldPosition.y, 0);
+    Vector3 rightFootOffset = Vector3(0, jackNode_.worldPosition.y + rightFoot_.worldPosition.y, 0);
+
+    leftEffector_.targetPosition = leftTarget + leftFootOffset;
+    rightEffector_.targetPosition = rightTarget + rightFootOffset;
+
+    solver_.Solve();
+}
+
+// Create XML patch instructions for screen joystick layout specific to this sample app
+String patchInstructions = "";