Skip to content

Instantly share code, notes, and snippets.

@JSandusky
Created March 10, 2025 21:32
Show Gist options
  • Save JSandusky/8bdb2466a76819ca329ccd4627054621 to your computer and use it in GitHub Desktop.
Save JSandusky/8bdb2466a76819ca329ccd4627054621 to your computer and use it in GitHub Desktop.
Urho3D JiggleBone
#include "../Precompiled.h"
#include "../Physics/JiggleBone.h"
#include "../Core/Context.h"
#include "../Graphics/DebugRenderer.h"
#include "../Scene/Node.h"
/*
Port of http://wiki.unity3d.com/index.php?title=JiggleBone
to Urho3D
*/
namespace Urho3D
{
extern const char* PHYSICS_CATEGORY;
JiggleBone::JiggleBone(Context* context) :
LogicComponent(context)
{
SetUpdateEventMask(USE_POSTUPDATE);
// Bone settings
boneAxis_ = Vector3(0, 0, 1);
targetDistance_ = 2.0f;
// Dynamics settings
stiffness_ = 0.1f;
mass_ = 0.9f;
damping_ = 0.75f;
gravity_ = 0.75f;
// Squash and stretch variables
squashAndStretch_ = true;
sideStretch_ = 0.15f;
frontStretch_ = 0.2f;
}
JiggleBone::~JiggleBone()
{
}
void JiggleBone::RegisterObject(Context* context)
{
context->RegisterFactory<JiggleBone>(PHYSICS_CATEGORY);
URHO3D_COPY_BASE_ATTRIBUTES(LogicComponent);
URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
URHO3D_ATTRIBUTE("Bone Axis", Vector3, boneAxis_, Vector3(0, 0, 1), AM_DEFAULT);
URHO3D_ATTRIBUTE("Stiffness", float, stiffness_, 0.1f, AM_DEFAULT);
URHO3D_ATTRIBUTE("Mass", float, mass_, 0.9f, AM_DEFAULT);
URHO3D_ATTRIBUTE("Damping", float, damping_, 0.75f, AM_DEFAULT);
URHO3D_ATTRIBUTE("Gravity", float, gravity_, 0.75f, AM_DEFAULT);
URHO3D_ATTRIBUTE("Target Distance", float, targetDistance_, 2.0f, AM_DEFAULT);
URHO3D_ATTRIBUTE("Allow Stretch", bool, squashAndStretch_, true, AM_DEFAULT);
URHO3D_ATTRIBUTE("Side Stretch", float, sideStretch_, 0.15f, AM_DEFAULT);
URHO3D_ATTRIBUTE("Front Stretch", float, frontStretch_, 0.15f, AM_DEFAULT);
URHO3D_ATTRIBUTE("Target Pos", Vector3, targetPos_, Vector3::ZERO, AM_DEFAULT | AM_NOEDIT);
URHO3D_ATTRIBUTE("Dynamic Pos", Vector3, dynamicPos_, Vector3::ZERO, AM_DEFAULT | AM_NOEDIT);
}
void JiggleBone::DelayedStart()
{
targetPos_ = GetNode()->GetWorldPosition() + GetNode()->GetWorldDirection() * targetDistance_;
dynamicPos_ = targetPos_;
}
void JiggleBone::PostUpdate(float timeStep)
{
if (!IsEnabledEffective())
return;
// Update forwardVector and upVector
Vector3 forwardVector = GetNode()->GetWorldDirection() * targetDistance_;
Vector3 upVector = GetNode()->GetWorldUp();
// Calculate target position
Vector3 targetPos = GetNode()->GetWorldPosition() + forwardVector;
// Calculate force, acceleration, and velocity per X, Y and Z
force_.x_ = (targetPos.x_ - dynamicPos_.x_) * stiffness_;
acc_.x_ = force_.x_ / mass_;
vel_.x_ += acc_.x_ * (1 - damping_);
force_.y_ = (targetPos.y_ - dynamicPos_.y_) * stiffness_;
force_.y_ -= gravity_ / 10; // Add some gravity
acc_.y_ = force_.y_ / mass_;
vel_.y_ += acc_.y_ * (1 - damping_);
force_.z_ = (targetPos.z_ - dynamicPos_.z_) * stiffness_;
acc_.z_ = force_.z_ / mass_;
vel_.z_ += acc_.z_ * (1 - damping_);
// Update dynamic postion
dynamicPos_ += vel_ + force_;
// Set bone rotation to look at dynamicPos
GetNode()->LookAt(dynamicPos_, upVector);
// ==================================================
// Squash and Stretch section
// ==================================================
if (squashAndStretch_) {
// Create a vector from target position to dynamic position
// We will measure the magnitude of the vector to determine
// how much squash and stretch we will apply
Vector3 dynamicVec = dynamicPos_ - targetPos;
// Get the magnitude of the vector
float stretchMag = dynamicVec.Length();
// Here we determine the amount of squash and stretch based on stretchMag
// and the direction the Bone Axis is pointed in. Ideally there should be
// a vector with two values at 0 and one at 1. Like Vector3(0,0,1)
// for the 0 values, we assume those are the sides, and 1 is the direction
// the bone is facing
float xStretch;
if (boneAxis_.x_ == 0)
xStretch = 1 + (-stretchMag * sideStretch_);
else
xStretch = 1 + (stretchMag * frontStretch_);
float yStretch;
if (boneAxis_.y_ == 0)
yStretch = 1 + (-stretchMag * sideStretch_);
else
yStretch = 1 + (stretchMag * frontStretch_);
float zStretch;
if (boneAxis_.z_ == 0)
zStretch = 1 + (-stretchMag * sideStretch_);
else
zStretch = 1 + (stretchMag * frontStretch_);
// Set the bone scale
GetNode()->SetScale(Vector3(xStretch, yStretch, zStretch));
}
}
void JiggleBone::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
{
if (IsEnabledEffective())
{
// draw forward line
debug->AddLine(GetNode()->GetWorldPosition(),
GetNode()->GetWorldPosition() + GetNode()->GetWorldDirection() * targetDistance_,
Color(0, 0, 1), false);
// draw the up vector
debug->AddLine(GetNode()->GetWorldPosition(),
GetNode()->GetWorldPosition() + GetNode()->GetWorldUp() * (targetDistance_ * 0.5f),
Color(0, 1, 0), false);
// draw the target position
debug->AddLine(targetPos_,
Vector3::UP * (targetDistance_ * 0.2f),
Color(1, 1, 0), false);
// draw the dynamic position
debug->AddLine(dynamicPos_,
Vector3::UP * (targetDistance_ * 0.2f),
Color(1, 0, 0), false);
}
}
}
#pragma once
#include <Urho3D/Scene/LogicComponent.h>
#include <Urho3D/Math/Vector3.h>
namespace Urho3D
{
class Context;
class URHO3D_API JiggleBone : public LogicComponent
{
URHO3D_OBJECT(JiggleBone, LogicComponent)
public:
JiggleBone(Context*);
virtual ~JiggleBone();
static void RegisterObject(Context*);
virtual void DelayedStart() override;
virtual void PostUpdate(float timeStep) override;
virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest) override;
float GetStiffness() const { return stiffness_; }
float GetMass() const { return mass_; }
float GetDamping() const { return damping_; }
float GetGravity() const { return gravity_; }
void SetStiffness(float aValue) { stiffness_ = aValue; }
void SetMass(float aValue) { mass_ = aValue; }
void SetDamping(float aValue) { damping_ = aValue; }
void SetGravity(float aValue) { gravity_ = aValue; }
bool GetSquashAndStretch() const { return squashAndStretch_; }
float GetSideStretch() const { return sideStretch_; }
float GetFrontStretch() const { return frontStretch_; }
void SetSquashAndStretch(bool aValue) { squashAndStretch_ = aValue; }
void SetSideStretch(float aValue) { sideStretch_ = aValue; }
void SetFrontStretch(float aValue) { frontStretch_ = aValue; }
private:
Vector3 targetPos_;
Vector3 dynamicPos_;
Vector3 boneAxis_;
float targetDistance_;
// dynamics settings
float stiffness_;
float mass_;
float damping_;
float gravity_;
// dynamics values
Vector3 force_;
Vector3 acc_;
Vector3 vel_;
// squashing
bool squashAndStretch_;
float sideStretch_;
float frontStretch_;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment