Adding IK library to Urho3D, enable/disable it with -DURHO3D_IK

This commit is contained in:
TheComet 2017-03-12 15:53:10 +01:00
parent eeb4eab788
commit cf30fb98c0
17 changed files with 1132 additions and 3 deletions

View File

@ -104,6 +104,7 @@ option (URHO3D_C++11 "Enable C++11 standard")
cmake_dependent_option (IOS "Setup build for iOS platform" FALSE "XCODE" FALSE)
cmake_dependent_option (URHO3D_64BIT "Enable 64-bit build, the default is set based on the native ABI of the chosen compiler toolchain" "${NATIVE_64BIT}" "NOT MSVC AND NOT ANDROID AND NOT (ARM AND NOT IOS) AND NOT WEB AND NOT POWERPC" "${NATIVE_64BIT}") # Intentionally only enable the option for iOS but not for tvOS as the latter is 64-bit only
option (URHO3D_ANGELSCRIPT "Enable AngelScript scripting support" TRUE)
option (URHO3D_IK "Enable inverse kinematics support" TRUE)
option (URHO3D_LUA "Enable additional Lua scripting support" TRUE)
option (URHO3D_NAVIGATION "Enable navigation support" TRUE)
# Urho's Network subsystem depends on kNet library which uses C++ exceptions feature
@ -345,6 +346,7 @@ if (URHO3D_CLANG_TOOLS)
URHO3D_ANGELSCRIPT
URHO3D_DATABASE_SQLITE
URHO3D_FILEWATCHER
URHO3D_IK
URHO3D_LOGGING
URHO3D_LUA
URHO3D_NAVIGATION
@ -395,6 +397,7 @@ foreach (OPT
URHO3D_ANGELSCRIPT
URHO3D_DATABASE
URHO3D_FILEWATCHER
URHO3D_IK
URHO3D_LOGGING
URHO3D_LUA
URHO3D_MINIDUMPS

View File

@ -88,6 +88,11 @@ if (URHO3D_DATABASE_SQLITE)
add_subdirectory (ThirdParty/SQLite)
endif ()
if (URHO3D_IK)
# IK lib provides its own Urho3D specific CMakeLists.txt
add_subdirectory (ThirdParty/ik/cmake/Urho3D)
endif ()
if (URHO3D_NAVIGATION)
add_subdirectory (ThirdParty/Detour)
add_subdirectory (ThirdParty/DetourCrowd)

View File

@ -97,7 +97,7 @@ if (WIN32)
endif ()
# Define source files
foreach (DIR Navigation Network Physics Urho2D)
foreach (DIR IK Navigation Network Physics Urho2D)
string (TOUPPER URHO3D_${DIR} OPT)
if (NOT ${OPT})
list (APPEND EXCLUDED_SOURCE_DIRS ${DIR})
@ -446,7 +446,7 @@ install_header_files (DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ DESTINATION ${DEST_
# Generate the include-all-headers header file even though we do not encourage Urho3D library users to use it
list (SORT URHO_HEADERS)
list (REVERSE URHO_HEADERS)
set (OPTIONAL_SUBS AngelScript Database Lua Navigation Network Physics Urho2D)
set (OPTIONAL_SUBS AngelScript Database IK Lua Navigation Network Physics Urho2D)
foreach (SUB ${OPTIONAL_SUBS})
if (URHO_HEADERS MATCHES "(include/Urho3D/${SUB}[^;]+)")
list (FIND URHO_HEADERS ${CMAKE_MATCH_1} FOUND_INDEX)

View File

@ -32,11 +32,20 @@
#include <SDL/SDL.h>
#endif
#ifdef URHO3D_IK
#include <ik/memory.h>
#include <ik/log.h>
#endif
namespace Urho3D
{
// Keeps track of how many times SDL was initialised so we know when to call SDL_Quit().
static int sdlInitCounter = 0;
// Keeps track of how many times IK was initialised
#ifdef URHO3D_IK
static int ikInitCounter = 0;
#endif
void EventReceiverGroup::BeginSendEvent()
{
@ -225,7 +234,7 @@ bool Context::RequireSDL(unsigned int sdlFlags)
++sdlInitCounter;
// Need to call SDL_Init() at least once before SDL_InitSubsystem()
if (sdlInitCounter == 0)
if (sdlInitCounter == 1)
{
URHO3D_LOGDEBUG("Initialising SDL");
if (SDL_Init(0) != 0)
@ -266,6 +275,37 @@ void Context::ReleaseSDL()
#endif
}
#ifdef URHO3D_IK
void Context::RequireIK()
{
// Always increment, the caller must match with ReleaseSDL(), regardless of
// what happens.
++ikInitCounter;
if (ikInitCounter == 1)
{
URHO3D_LOGDEBUG("Initialising Inverse Kinematics library");
ik_memory_init();
ik_log_init(IK_LOG_NONE);
}
}
void Context::ReleaseIK()
{
--ikInitCounter;
if (ikInitCounter == 0)
{
URHO3D_LOGDEBUG("De-initialising Inverse Kinematics library");
ik_log_deinit();
ik_memory_deinit();
}
if (ikInitCounter < 0)
URHO3D_LOGERROR("Too many calls to Context::ReleaseIK()");
}
#endif
void Context::CopyBaseAttributes(StringHash baseType, StringHash derivedType)
{
// Prevent endless loop if mistakenly copying attributes from same class as derived

View File

@ -100,6 +100,12 @@ public:
bool RequireSDL(unsigned int sdlFlags);
/// Indicate that you are done with using SDL. Must be called after using RequireSDL().
void ReleaseSDL();
#ifdef URHO3D_IK
/// Initialises the IK library, if not already. This call must be matched with ReleaseIK() when the IK library is no longer required.
void RequireIK();
/// Indicate that you are done with using the IK library.
void ReleaseIK();
#endif
/// Copy base class attributes to derived class.
void CopyBaseAttributes(StringHash baseType, StringHash derivedType);

View File

@ -38,6 +38,9 @@
#include "../IO/FileSystem.h"
#include "../IO/Log.h"
#include "../IO/PackageFile.h"
#ifdef URHO3D_IK
#include "../IK/IK.h"
#endif
#ifdef URHO3D_NAVIGATION
#include "../Navigation/NavigationMesh.h"
#endif
@ -139,6 +142,10 @@ Engine::Engine(Context* context) :
// Register object factories for libraries which are not automatically registered along with subsystem creation
RegisterSceneLibrary(context_);
#ifdef URHO3D_IK
RegisterIKLibrary(context_);
#endif
#ifdef URHO3D_PHYSICS
RegisterPhysicsLibrary(context_);
#endif

42
Source/Urho3D/IK/IK.cpp Normal file
View File

@ -0,0 +1,42 @@
//
// Copyright (c) 2008-2016 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 "IK.h"
#include "IKConstraint.h"
#include "IKEffector.h"
#include "IKSolver.h"
namespace Urho3D
{
const char* IK_CATEGORY = "Inverse Kinematics";
// ----------------------------------------------------------------------------
void RegisterIKLibrary(Context* context)
{
IKConstraint::RegisterObject(context);
IKEffector::RegisterObject(context);
IKSolver::RegisterObject(context);
}
} // namespace Urho3D

55
Source/Urho3D/IK/IK.h Normal file
View File

@ -0,0 +1,55 @@
//
// Copyright (c) 2008-2016 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.
//
/*
* TODO IK todo
* - Initial pose is not saved when saving scene in editor. Instead, the
* solved state of the chain(s) are saved.
* - Target angle in addition to target position -> use weighted angles
* approach
* - Add an IKEffector weight parameter, so the user can specify
* (between [0..1]) how much influence the solved chains have.
* - Apply angles from chain objects back to scene nodes (currently only
* translations are applied).
* - Add support for enabling/disabling initial pose to support animated
* models as well as scene nodes.
* - Pole targets?
* - Add support for manually updating initial pose.
* - Add support for having the initial pose update every time it's solved
* -> incremental solving so to speak.
* - Apply bullet constraints to joints.
* - Script bindings.
* - Optimise.
* - Profile.
*/
namespace Urho3D
{
class Context;
/*!
* Registers all IK systems to the specified context.
*/
void RegisterIKLibrary(Context* context);
} // namespace Urho3D

View File

@ -0,0 +1,51 @@
//
// Copyright (c) 2008-2016 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 "IK.h"
#include "IKConstraint.h"
#include "../Core/Context.h"
#include "../Scene/Node.h"
#include "../Scene/SceneEvents.h"
namespace Urho3D
{
extern const char* IK_CATEGORY;
// ----------------------------------------------------------------------------
IKConstraint::IKConstraint(Context* context) :
Component(context)
{
}
// ----------------------------------------------------------------------------
IKConstraint::~IKConstraint()
{
}
// ----------------------------------------------------------------------------
void IKConstraint::RegisterObject(Context* context)
{
context->RegisterFactory<IKConstraint>(IK_CATEGORY);
}
} // namespace Urho3D

View File

@ -0,0 +1,57 @@
//
// Copyright (c) 2008-2016 the Urho3D project.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#pragma once
#include "../Scene/Component.h"
namespace Urho3D
{
class Context;
class Node;
class IKConstraint : public Component
{
URHO3D_OBJECT(IKConstraint, Component)
public:
/*!
* Constructs a new IK constraint.
*/
IKConstraint(Context* context);
/*!
* Destructs he IK constraint.
*/
virtual ~IKConstraint();
/*!
* Registers this class as an object factory.
*/
static void RegisterObject(Context* context);
private:
};
} // namespace Urho3D

View File

@ -0,0 +1,201 @@
//
// Copyright (c) 2008-2016 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 "../IK/IK.h"
#include "../IK/IKEffector.h"
#include "../IK/IKSolver.h"
#include "../IK/IKEvents.h"
#include "../Core/Context.h"
#include "../Graphics/DebugRenderer.h"
#include "../Scene/Node.h"
#include "../Scene/Scene.h"
#include "../Scene/SceneEvents.h"
#include <ik/effector.h>
#include <ik/log.h>
namespace Urho3D
{
extern const char* IK_CATEGORY;
// ----------------------------------------------------------------------------
IKEffector::IKEffector(Context* context) :
Component(context),
ikEffector_(NULL),
chainLength_(0)
{
}
// ----------------------------------------------------------------------------
IKEffector::~IKEffector()
{
}
// ----------------------------------------------------------------------------
void IKEffector::RegisterObject(Context* context)
{
context->RegisterFactory<IKEffector>(IK_CATEGORY);
URHO3D_ACCESSOR_ATTRIBUTE("Chain Length", GetChainLength, SetChainLength, unsigned, true, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Target Node", GetTargetName, SetTargetName, String, String::EMPTY, AM_DEFAULT);
}
// ----------------------------------------------------------------------------
Node* IKEffector::GetTargetNode() const
{
return targetNode_;
}
// ----------------------------------------------------------------------------
void IKEffector::SetTargetNode(Node* targetNode)
{
using namespace IKEffectorTargetChanged;
VariantMap eventData;
eventData[P_EFFECTORNODE] = node_; // The effector node that has changed targets
eventData[P_TARGETNODE] = targetNode_; // The new target node. NOTE: Can be NULL (means no target)
SendEvent(E_IKEFFECTORTARGETCHANGED, eventData);
// Finally change the target node
targetNode_ = targetNode;
targetName_ = targetNode != NULL ? targetNode->GetName() : "";
}
// ----------------------------------------------------------------------------
const String& IKEffector::GetTargetName() const
{
return targetName_;
}
// ----------------------------------------------------------------------------
void IKEffector::SetTargetName(const String& nodeName)
{
targetName_ = nodeName;
targetNode_ = NULL;
}
// ----------------------------------------------------------------------------
void IKEffector::SetTargetPosition(const Vector3& targetPosition)
{
targetPosition_ = targetPosition;
if (ikEffector_ != NULL)
{
ikEffector_->target_position.v.x = targetPosition.x_;
ikEffector_->target_position.v.y = targetPosition.y_;
ikEffector_->target_position.v.z = targetPosition.z_;
}
}
// ----------------------------------------------------------------------------
void IKEffector::UpdateTargetNodePosition()
{
if (targetNode_ == NULL)
{
SetTargetNode(node_->GetScene()->GetChild(targetName_, true));
if (targetNode_ == NULL)
return;
}
SetTargetPosition(targetNode_->GetWorldPosition());
}
// ----------------------------------------------------------------------------
unsigned int IKEffector::GetChainLength() const
{
return chainLength_;
}
// ----------------------------------------------------------------------------
void IKEffector::SetChainLength(unsigned chainLength)
{
chainLength_ = chainLength;
if (ikEffector_ != NULL)
{
ikEffector_->chain_length = chainLength;
solver_->MarkSolverTreeDirty();
}
}
// ----------------------------------------------------------------------------
void IKEffector::SetEffector(ik_effector_t* effector)
{
ikEffector_ = effector;
if (effector)
{
effector->chain_length = chainLength_;
effector->target_position.v.x = targetPosition_.x_;
effector->target_position.v.y = targetPosition_.y_;
effector->target_position.v.z = targetPosition_.z_;
}
}
// ----------------------------------------------------------------------------
void IKEffector::SetSolver(IKSolver* solver)
{
solver_ = solver;
}
// ----------------------------------------------------------------------------
void IKEffector::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
{
// Calculate average length of all segments so we can determine the radius
// of the debug spheres to draw
int chainLength = chainLength_ == 0 ? -1 : chainLength_;
Node* a = node_;
float averageLength = 0.0f;
unsigned numberOfSegments = 0;
while (a && a != a->GetScene() && chainLength-- != 0)
{
averageLength += a->GetPosition().Length();
++numberOfSegments;
a = a->GetParent();
}
averageLength /= numberOfSegments;
// connect all chained nodes together with lines
chainLength = chainLength_ == 0 ? -1 : chainLength_;
a = node_;
Node* b = a->GetParent();
debug->AddSphere(
Sphere(a->GetWorldPosition(), averageLength * 0.1),
Color::YELLOW,
depthTest
);
while (b && b != b->GetScene() && chainLength-- != 0)
{
debug->AddLine(
a->GetWorldPosition(),
b->GetWorldPosition(),
Color::WHITE,
depthTest
);
debug->AddSphere(
Sphere(b->GetWorldPosition(), averageLength * 0.1),
Color::YELLOW,
depthTest
);
a = b;
b = b->GetParent();
}
}
} // namespace Urho3D

View File

@ -0,0 +1,104 @@
//
// Copyright (c) 2008-2016 the Urho3D project.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#pragma once
#include "../Scene/Component.h"
#include "../Scene/Scene.h"
struct ik_effector_t;
namespace Urho3D
{
class Context;
class IKSolver;
class IKEffector : public Component
{
URHO3D_OBJECT(IKEffector, Component)
public:
/// Constructs a new IK effector.
IKEffector(Context* context);
/// Destructs he IK effector.
virtual ~IKEffector();
/*!
* Registers this class as an object factory.
*/
static void RegisterObject(Context* context);
/// Retrieves the node that is being used as a target. Can be NULL.
Node* GetTargetNode() const;
/*!
* @brief The position of the target node provides the target position of
* the effector node.
*
* The IK chain is solved such that the node to which this component is
* attached to will try to move to the position of the target node.
* @param targetNode A scene node that acts as a target. Specifying NULL
* will erase the target and cause the solver to ignore this chain.
* @note You will get very strange behaviour if you specify a target node
* that is part the IK chain being solved for (circular dependency). Don't
* do that.
*/
void SetTargetNode(Node* targetNode);
/*!
* @brief Retrieves the name of the target node. The node doesn't
* necessarily have to exist in the scene graph.
*/
const String& GetTargetName() const;
/*!
* @brief Sets the name of the target node. The node doesn't necessarily
* have to exist in the scene graph. When a node is created that matches
* this name, it is selected as the target.
*/
void SetTargetName(const String& nodeName);
void SetTargetPosition(const Vector3& targetPosition);
void UpdateTargetNodePosition();
unsigned GetChainLength() const;
void SetChainLength(unsigned chainLength);
virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
void SetSolver(IKSolver* solver);
void SetEffector(ik_effector_t* effector);
private:
String targetName_;
Vector3 targetPosition_;
WeakPtr<Node> targetNode_;
WeakPtr<IKSolver> solver_;
ik_effector_t* ikEffector_;
unsigned chainLength_;
};
} // namespace Urho3D

View File

@ -0,0 +1,37 @@
//
// Copyright (c) 2008-2016 the Urho3D project.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#pragma once
#include "../Core/Object.h"
namespace Urho3D
{
URHO3D_EVENT(E_IKEFFECTORTARGETCHANGED, IKEffectorTargetChanged)
{
URHO3D_PARAM(P_EFFECTORNODE, EffectorNode); // (Node*) The effector node that has changed targets
URHO3D_PARAM(P_TARGETNODE, TargetNode); // (Node*) The new target node. NOTE: Can be NULL (means no target)
}
}

View File

@ -0,0 +1,375 @@
//
// Copyright (c) 2008-2016 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 "../IK/IKSolver.h"
#include "../IK/IK.h"
#include "../IK/IKEvents.h"
#include "../Core/Context.h"
#include "../Core/Profiler.h"
#include "../IO/Log.h"
#include "../Scene/Node.h"
#include "../Scene/Scene.h"
#include "../Scene/SceneEvents.h"
#include "../Graphics/Animation.h"
#include "../Graphics/AnimationState.h"
#include <ik/solver.h>
#include <ik/node.h>
#include <ik/effector.h>
#include <ik/log.h>
namespace Urho3D
{
extern const char* IK_CATEGORY;;
extern const char* SUBSYSTEM_CATEGORY;
static void HandleIKLog(const char* msg)
{
URHO3D_LOGINFOF("[IK] %s", msg);
}
static vec3_t Vec3Urho2IK(const Vector3& urho)
{
vec3_t ret;
ret.v.x = urho.x_;
ret.v.y = urho.y_;
ret.v.z = urho.z_;
return ret;
}
static Vector3 Vec3IK2Urho(const vec3_t* ik)
{
return Vector3(ik->v.x, ik->v.y, ik->v.z);
}
static ik_node_t* CreateIKNode(const Node* node)
{
ik_node_t* ikNode = ik_node_create(node->GetID());
ikNode->position = Vec3Urho2IK(node->GetWorldPosition());
ikNode->user_data = (void*)node;
return ikNode;
}
static bool ChildrenHaveEffector(const Node* node)
{
if (node->HasComponent<IKEffector>())
return true;
const Vector<SharedPtr<Node> >& children = node->GetChildren();
for (Vector<SharedPtr<Node> >::ConstIterator it = children.Begin(); it != children.End(); ++it)
{
if (ChildrenHaveEffector(it->Get()))
return true;
}
}
static void ApplyResultCallback(ik_node_t* ikNode, vec3_t position, quat_t rotation)
{
Node* node = (Node*)ikNode->user_data;
node->SetWorldPosition(Vec3IK2Urho(&ikNode->solved_position));
node->SetWorldRotation(Quaternion(
rotation.q.w,
rotation.q.x,
rotation.q.y,
rotation.q.z
));
}
// ----------------------------------------------------------------------------
IKSolver::IKSolver(Context* context) :
Component(context),
solver_(NULL),
solverTreeNeedsRebuild_(false)
{
context_->RequireIK();
solver_ = ik_solver_create(SOLVER_FABRIK);
solver_->apply_result = ApplyResultCallback;
solver_->build_mode = SOLVER_EXCLUDE_ROOT;
ik_log_register_listener(HandleIKLog);
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));
}
// ----------------------------------------------------------------------------
IKSolver::~IKSolver()
{
// Destroying the solver tree will destroy the effector objects, so remove
// any references any of the IKEffector objects could be holding
for(PODVector<IKEffector*>::ConstIterator it = effectorList_.Begin(); it != effectorList_.End(); ++it)
(*it)->SetEffector(NULL);
ik_log_unregister_listener(HandleIKLog);
ik_solver_destroy(solver_);
context_->ReleaseIK();
}
// ----------------------------------------------------------------------------
void IKSolver::RegisterObject(Context* context)
{
context->RegisterFactory<IKSolver>(SUBSYSTEM_CATEGORY);
static const char* algorithmNames[] = {
"FABRIK",
/* not implemented
"Jacobian Inverse",
"Jacobian Transpose",*/
NULL
};
URHO3D_ACCESSOR_ATTRIBUTE("Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
URHO3D_ENUM_ACCESSOR_ATTRIBUTE("Algorithm", GetAlgorithm, SetAlgorithm, Algorithm, algorithmNames, SOLVER_FABRIK, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Max Iterations", GetMaximumIterations, SetMaximumIterations, unsigned, 1000, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Convergence Tolerance", GetTolerance, SetTolerance, float, 0.1f, AM_DEFAULT);
}
// ----------------------------------------------------------------------------
void IKSolver::SetAlgorithm(IKSolver::Algorithm algorithm)
{
URHO3D_LOGERROR("Failed to set algorithm: Not implemented");
}
// ----------------------------------------------------------------------------
IKSolver::Algorithm IKSolver::GetAlgorithm() const
{
return FABRIK;
}
// ----------------------------------------------------------------------------
void IKSolver::SetMaximumIterations(unsigned iterations)
{
solver_->max_iterations = iterations;
}
// ----------------------------------------------------------------------------
unsigned IKSolver::GetMaximumIterations() const
{
return solver_->max_iterations;
}
// ----------------------------------------------------------------------------
void IKSolver::SetTolerance(float tolerance)
{
if(tolerance < M_EPSILON)
tolerance = M_EPSILON;
solver_->tolerance = tolerance;
}
// ----------------------------------------------------------------------------
float IKSolver::GetTolerance() const
{
return solver_->tolerance;
}
// ----------------------------------------------------------------------------
void IKSolver::MarkSolverTreeDirty()
{
solverTreeNeedsRebuild_ = true;
}
// ----------------------------------------------------------------------------
/*
* This next section maintains the internal list of effector nodes. Whenever
* nodes are deleted or added to the scene, or whenever components are added
* or removed from nodes, we must check to see which of those nodes are/were
* IK effector nodes and update our internal list accordingly.
*
* Unfortunately, E_COMPONENTREMOVED and E_COMPONENTADDED do not fire when a
* parent node is removed/added containing child effector nodes, so we must
* also monitor E_NODEREMOVED AND E_NODEADDED.
*/
// ----------------------------------------------------------------------------
void IKSolver::OnSceneSet(Scene* scene)
{
if (scene == NULL)
{
ik_solver_destroy_tree(solver_);
}
else
{
RebuildTree();
}
}
// ----------------------------------------------------------------------------
void IKSolver::RebuildTree()
{
Node* scene = GetScene();
assert(scene != NULL);
ik_node_t* ikRoot = ik_node_create(scene->GetID());
ik_solver_set_tree(solver_, ikRoot);
PODVector<Node*> effectorNodes;
scene->GetChildrenWithComponent<IKEffector>(effectorNodes, true);
for(PODVector<Node*>::ConstIterator it = effectorNodes.Begin(); it != effectorNodes.End(); ++it)
{
BuildTreeToEffector(*it);
}
}
// ----------------------------------------------------------------------------
void IKSolver::BuildTreeToEffector(const Node* node)
{
// Check if the component that was added is an IK effector. If not, then it
// does not concern us.
IKEffector* effector = static_cast<IKEffector*>(node->GetComponent<IKEffector>());
if(effector == NULL || effector->GetType() != IKEffector::GetTypeStatic())
return;
// May need to build tree up to the node where this effector was added. Do
// this by following the chain of parent nodes until we hit a node that
// exists in the solver's tree. Then iterate backwards again and add each
// missing node to the solver's tree.
PODVector<const Node*> missingNodes;
const Node* iterNode = node;
ik_node_t* ikNode = ik_node_find_child(solver_->tree, node->GetID());
while (ikNode == NULL)
{
missingNodes.Push(iterNode);
iterNode = iterNode->GetParent();
ikNode = ik_node_find_child(solver_->tree, iterNode->GetID());
}
while (missingNodes.Size() > 0)
{
iterNode = missingNodes.Back();
missingNodes.Pop();
ik_node_t* ikChildNode = CreateIKNode(iterNode);
ik_node_add_child(ikNode, ikChildNode);
ikNode = ikChildNode;
}
// The tip of the tree is the effector. The solver library has ownership of
// the effector object, but our IKEffector object also needs to know about
// it.
ik_effector_t* ikEffector = ik_effector_create();
ik_node_attach_effector(ikNode, ikEffector); // ownership of effector
effector->SetEffector(ikEffector); // "weak" reference to effector
effector->SetSolver(this);
effectorList_.Push(effector);
MarkSolverTreeDirty();
}
// ----------------------------------------------------------------------------
void IKSolver::HandleComponentAdded(StringHash eventType, VariantMap& eventData)
{
using namespace ComponentAdded;
(void)eventType;
if(solver_->tree == NULL)
return;
Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
BuildTreeToEffector(node);
}
// ----------------------------------------------------------------------------
void IKSolver::HandleComponentRemoved(StringHash eventType, VariantMap& eventData)
{
using namespace ComponentRemoved;
(void)eventType;
if(solver_->tree == NULL)
return;
// Check if the component that was removed was an IK effector. If not,
// then it does not concern us.
IKEffector* effector = static_cast<IKEffector*>(eventData[P_COMPONENT].GetPtr());
if(effector->GetType() != IKEffector::GetTypeStatic())
return;
Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
ik_node_t* ikNode = ik_node_find_child(solver_->tree, node->GetID());
ik_node_destroy_effector(ikNode);
effector->SetEffector(NULL);
effectorList_.Remove(effector); // TODO RemoveSwap()
MarkSolverTreeDirty();
}
// ----------------------------------------------------------------------------
void IKSolver::HandleNodeAdded(StringHash eventType, VariantMap& eventData)
{
using namespace NodeAdded;
(void)eventType;
if(solver_->tree == NULL)
return;
Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
PODVector<Node*> effectorNodes;
node->GetChildrenWithComponent<IKEffector>(effectorNodes, true);
for(PODVector<Node*>::ConstIterator it = effectorNodes.Begin(); it != effectorNodes.End(); ++it)
{
BuildTreeToEffector(*it);
effectorList_.Push((*it)->GetComponent<IKEffector>());
}
}
// ----------------------------------------------------------------------------
void IKSolver::HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
{
using namespace NodeRemoved;
(void)eventType;
if(solver_->tree == NULL)
return;
Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
PODVector<Node*> effectorNodes;
node->GetChildrenWithComponent<IKEffector>(effectorNodes, true);
for(PODVector<Node*>::ConstIterator it = effectorNodes.Begin(); it != effectorNodes.End(); ++it)
{
effectorList_.Remove((*it)->GetComponent<IKEffector>());
}
ik_node_t* ikNode = ik_node_find_child(solver_->tree, node->GetID());
if(ikNode != NULL)
ik_node_destroy(ikNode);
MarkSolverTreeDirty();
}
// ----------------------------------------------------------------------------
void IKSolver::HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData)
{
URHO3D_PROFILE(SolveIK);
if (solverTreeNeedsRebuild_)
{
ik_solver_rebuild_data(solver_);
solverTreeNeedsRebuild_ = false;
}
for(PODVector<IKEffector*>::ConstIterator it = effectorList_.Begin(); it != effectorList_.End(); ++it)
{
(*it)->UpdateTargetNodePosition();
}
ik_solver_solve(solver_);
}
} // namespace Urho3D

134
Source/Urho3D/IK/IKSolver.h Normal file
View File

@ -0,0 +1,134 @@
//
// Copyright (c) 2008-2016 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 "../IK/IKEffector.h"
#include "../Scene/Component.h"
struct ik_solver_t;
struct ik_node_t;
namespace Urho3D
{
class AnimationState;
/*!
* @brief Marks the root or "beginning" of an IK chain or multiple IK chains.
* The solving algorithm can be set along with other solver related parameters.
* The IK problem is solved starting from the node this component is attached
* to and ending at all nodes that have an IKEffector component attached.
*/
class IKSolver : public Component
{
URHO3D_OBJECT(IKSolver, Component)
public:
enum Algorithm
{
FABRIK,
JACOBIAN_INVERSE,
JACOBIAN_TRANSPOSE
};
/// Construct an IK root component.
IKSolver(Context* context);
/// Default destructor.
virtual ~IKSolver();
/// Registers this class to the context.
static void RegisterObject(Context* context);
/*!
* @brief Selects the solver algorithm. Default is FABRIK.
*
* The currently supported solvers are listed below.
* + **FABRIK**: This is a fairly new and highly efficient inverse
* kinematic solving algorithm. It requires the least iterations to
* reach its goal, it does not suffer from singularities (nearly no
* violent snapping about), and it always converges.
*/
void SetAlgorithm(Algorithm algorithm);
Algorithm GetAlgorithm() const;
/*!
* @brief Sets the maximum number of iterations the solver is allowed to
* perform before applying the result.
*
* Depending on the algorithm, you may want higher or lower values.
* FABRIK looks decent after only 10 iterations, whereas Jacobian based
* methods often require more than a 100.
*
* The default value is 50.
*
* @note Most algorithms have a convergence criteria at which the solver
* will stop iterating, so most of the time the maximum number of
* iterations isn't even reached.
*
* @param iterations Number of iterations. Must be greater than 0. Higher
* values yield more accurate results, but at the cost of performance.
*/
void SetMaximumIterations(unsigned iterations);
/*!
* @brief Returns the configured maximum number of iterations.
*/
unsigned GetMaximumIterations() const;
/*!
* @brief Sets the distance at which the effector is "close enough" to the
* target node, at which point the algorithm will stop iterating.
*
* @param tolerance The distance to set. Smaller values yield more accurate
* results, but at the cost of more iterations. Generally you'll want to
* specify a number that is about 1/100th to 1/1000th of the total size of
* the IK chain, e.g. if your human character has a leg that is 1 Urho3D
* unit long, a good starting tolerance would be 0.01.
*/
void SetTolerance(float tolerance);
/*!
* @brief Returns the configured tolerance.
*/
float GetTolerance() const;
void MarkSolverTreeDirty();
private:
virtual void OnSceneSet(Scene* scene);
/// Causes the entire tree to be rebuilt
void RebuildTree();
void BuildTreeToEffector(const Node* node);
void HandleComponentAdded(StringHash eventType, VariantMap& eventData);
void HandleComponentRemoved(StringHash eventType, VariantMap& eventData);
void HandleNodeAdded(StringHash eventType, VariantMap& eventData);
void HandleNodeRemoved(StringHash eventType, VariantMap& eventData);
/// Invokes the IK solver
void HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData);
PODVector<IKEffector*> effectorList_;
ik_solver_t* solver_;
bool solverTreeNeedsRebuild_;
};
} // namespace Urho3D

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -107,6 +107,18 @@
<attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
<attribute name="Image Rect" value="48 16 62 30" />
</element>
<element type="IKSolver">
<attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
<attribute name="Image Rect" value="160 0 174 14" />
</element>
<element type="IKConstraint">
<attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
<attribute name="Image Rect" value="160 0 174 14" />
</element>
<element type="IKEffector">
<attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
<attribute name="Image Rect" value="192 16 206 30" />
</element>
<element type="Light">
<attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
<attribute name="Image Rect" value="48 0 62 14" />