# Unity falling body pendulum behaviour

I wonder if someone could provide some guidance.

Im attempting to create a pendulum like behaviour in 2D space in Unity without using a hinge joint.

Essentially I want to affect a falling body to act as though it were restrained at the radius of a point, and to be subject to gravity and friction etc.

Ive tried many modifications of this code, and have come up with some cool 'strange-attractor' like behaviour but i cannot for the life of me create a realistic pendulum like action.

This is what I have so far:

    startingposition = transform.position;          //Get start position
newposition = startingposition + velocity;      //add old velocity
newposition.y -= gravity * Time.deltaTime;      //add gravity
velocity = newposition-startingposition;        //Get new velocity
transform.Translate (velocity * Time.deltaTime, Space.World);   //apply to transform


So im working out the new position based on the old velocity + gravity, then constraining it to a distance from a point, which is the element in the code i cannot get correct. Is this a logical way to go about it?

• Why don't you want to use a hinge joint? – NauticalMile May 27 '14 at 2:11
• Production decision for flexibility, and itd be pretty nice to know how to do it, im experimenting with various techniques for a little physics sandbox for my own education. Im sure its a fairly easy operation to perform its just my understanding of maths/physics is lacking. – user3447980 May 27 '14 at 8:58
• Ok I can see an error now, I think i should be working out gravity and velocity independently from each other and then normalising the results. Not able to test if thats the problem currently. – user3447980 May 27 '14 at 13:36
• I'm afraid your pendulum may lose energy too fast with this approach. Let us know if you have such problem. – kolenda May 28 '14 at 15:15

I thought this would be a relatively simple problem to solve but I spent a couple days trying to figure out how the heck to simulate pendulum movement. I didn't want to cheat and just change the x,y position based on sin(theta) and cos(theta) curves. Instead I wanted to deal with the two forces that are applied in real life, Gravity and Tension. The main piece I was missing was centripetal force.

The Pendulum (mathematics) wikipedia page has a great animation(below, on left) explaining the pendulum motion. You can see my result(on right) strikingly similar to that diagram

The "bob" is the swinging object and the "pivot" is the origin/root.

Theta equals the angle between the rope and the direction of gravity.

When the bob is on the left or right the tension equals:

The reason the tension force is greater as the bob approaches equilibrium point(middle) is because of centripetal force:

So the overrall tension formula looks like as the bob swings is:

There are two forces in the pendulum system:

• Gravity
• GravityForce = mass * gravity.magnitude
• GravityDirection = gravity.normalized
• Tension
• TensionForce = (mass * gravity * Cos(theta)) + ((mass * velocityTangent^2)/ropeLength)
• TensionDirection = ropeDirection = bob to pivot

Just apply gravity to your object like you would for a normal object and then apply the tension. When you apply the forces, just multiply the force by the direction and deltaTime.

Below is the Pendulum.cs script(also as a GitHub Gist). It works quite well but there is some rounding error drift if you leave it for a while (doesn't return to exactly same position).

The script works in 3D but of course a pendulum only swings in a 2D plane. It also works with gravity in any direction. So for example, if you invert the gravity the pendulum works upside down. Edit->Project Settings->Physics->Gravity

It is very important to have a consistent relatively small deltaTime when updating the pendulum so that you do not bounce around the curve. I am using the technique found in this article, FIX YOUR TIMESTEP! by Glenn Fiedler to accomplish this. Check the Update() function below to see how I implemented it.

## Also as a GitHub Gist

using UnityEngine;
using System.Collections;

// Author: Eric Eastwood (ericeastwood.com)
//
// Description:
//      Written for this gd.se question: http://gamedev.stackexchange.com/a/75748/16587
//      Simulates/Emulates pendulum motion in code
//      Works in any 3D direction and with any force/direciton of gravity
//
// Demonstration: https://i.imgur.com/vOQgFMe.gif
//
// Usage: https://i.imgur.com/BM52dbT.png
public class Pendulum : MonoBehaviour {

public GameObject Pivot;
public GameObject Bob;

public float mass = 1f;

float ropeLength = 2f;

Vector3 bobStartingPosition;
bool bobStartingPositionSet = false;

// You could define these in the PendulumUpdate() loop
// But we want them in the class scope so we can draw gizmos OnDrawGizmos()
private Vector3 gravityDirection;
private Vector3 tensionDirection;

private Vector3 tangentDirection;
private Vector3 pendulumSideDirection;

private float tensionForce = 0f;
private float gravityForce = 0f;

// Keep track of the current velocity
Vector3 currentVelocity = new Vector3();

// We use these to smooth between values in certain framerate situations in the Update() loop
Vector3 currentStatePosition;
Vector3 previousStatePosition;

// Use this for initialization
void Start () {
// Set the starting position for later use in the context menu reset methods
this.bobStartingPosition = this.Bob.transform.position;
this.bobStartingPositionSet = true;

this.PendulumInit();
}

float t = 0f;
float dt = 0.01f;
float currentTime = 0f;
float accumulator = 0f;

void Update()
{
/* */
// Fixed deltaTime rendering at any speed with smoothing
// Technique: http://gafferongames.com/game-physics/fix-your-timestep/
float frameTime = Time.time - currentTime;
this.currentTime = Time.time;

this.accumulator += frameTime;

while (this.accumulator >= this.dt)
{
this.previousStatePosition = this.currentStatePosition;
this.currentStatePosition = this.PendulumUpdate(this.currentStatePosition, this.dt);
//integrate(state, this.t, this.dt);
accumulator -= this.dt;
this.t += this.dt;
}

float alpha = this.accumulator/this.dt;

Vector3 newPosition = this.currentStatePosition*alpha + this.previousStatePosition*(1f-alpha);

this.Bob.transform.position = newPosition; //this.currentStatePosition;
/* */

//this.Bob.transform.position = this.PendulumUpdate(this.Bob.transform.position, Time.deltaTime);
}

// Use this to reset forces and go back to the starting position
void ResetPendulumPosition()
{
if(this.bobStartingPositionSet)
this.MoveBob(this.bobStartingPosition);
else
this.PendulumInit();
}

// Use this to reset any built up forces
void ResetPendulumForces()
{
this.currentVelocity = Vector3.zero;

// Set the transition state
this.currentStatePosition = this.Bob.transform.position;
}

void PendulumInit()
{
// Get the initial rope length from how far away the bob is now
this.ropeLength = Vector3.Distance(Pivot.transform.position, Bob.transform.position);
this.ResetPendulumForces();
}

void MoveBob(Vector3 resetBobPosition)
{
// Put the bob back in the place we first saw it at in Start()
this.Bob.transform.position = resetBobPosition;

// Set the transition state
this.currentStatePosition = resetBobPosition;
}

Vector3 PendulumUpdate(Vector3 currentStatePosition, float deltaTime)
{
this.gravityForce = this.mass * Physics.gravity.magnitude;
this.gravityDirection = Physics.gravity.normalized;
this.currentVelocity += this.gravityDirection * this.gravityForce * deltaTime;

Vector3 pivot_p = this.Pivot.transform.position;
Vector3 bob_p = this.currentStatePosition;

Vector3 auxiliaryMovementDelta = this.currentVelocity * deltaTime;
float distanceAfterGravity = Vector3.Distance(pivot_p, bob_p + auxiliaryMovementDelta);

// If at the end of the rope
if(distanceAfterGravity > this.ropeLength || Mathf.Approximately(distanceAfterGravity, this.ropeLength))
{

this.tensionDirection = (pivot_p - bob_p).normalized;

this.pendulumSideDirection = (Quaternion.Euler(0f, 90f, 0f) * this.tensionDirection);
this.pendulumSideDirection.Scale(new Vector3(1f, 0f, 1f));
this.pendulumSideDirection.Normalize();

this.tangentDirection = (-1f * Vector3.Cross(this.tensionDirection, this.pendulumSideDirection)).normalized;

float inclinationAngle = Vector3.Angle(bob_p-pivot_p, this.gravityDirection);

this.tensionForce = this.mass * Physics.gravity.magnitude * Mathf.Cos(Mathf.Deg2Rad * inclinationAngle);
float centripetalForce = ((this.mass * Mathf.Pow(this.currentVelocity.magnitude, 2))/this.ropeLength);
this.tensionForce += centripetalForce;

this.currentVelocity += this.tensionDirection * this.tensionForce * deltaTime;
}

// Get the movement delta
Vector3 movementDelta = Vector3.zero;
movementDelta += this.currentVelocity * deltaTime;

//return currentStatePosition + movementDelta;

float distance = Vector3.Distance(pivot_p, currentStatePosition + movementDelta);
return this.GetPointOnLine(pivot_p, currentStatePosition + movementDelta, distance <= this.ropeLength ? distance : this.ropeLength);
}

Vector3 GetPointOnLine(Vector3 start, Vector3 end, float distanceFromStart)
{
return start + (distanceFromStart * Vector3.Normalize(end - start));
}

void OnDrawGizmos()
{
// purple
Gizmos.color = new Color(.5f, 0f, .5f);
Gizmos.DrawWireSphere(this.Pivot.transform.position, this.ropeLength);

Gizmos.DrawWireCube(this.bobStartingPosition, new Vector3(.5f, .5f, .5f));

// Blue: Auxilary
Gizmos.color = new Color(.3f, .3f, 1f); // blue
Vector3 auxVel = .3f * this.currentVelocity;
Gizmos.DrawRay(this.Bob.transform.position, auxVel);
Gizmos.DrawSphere(this.Bob.transform.position + auxVel, .2f);

// Yellow: Gravity
Gizmos.color = new Color(1f, 1f, .2f);
Vector3 gravity = .3f * this.gravityForce*this.gravityDirection;
Gizmos.DrawRay(this.Bob.transform.position, gravity);
Gizmos.DrawSphere(this.Bob.transform.position + gravity, .2f);

// Orange: Tension
Gizmos.color = new Color(1f, .5f, .2f); // Orange
Vector3 tension = .3f * this.tensionForce*this.tensionDirection;
Gizmos.DrawRay(this.Bob.transform.position, tension);
Gizmos.DrawSphere(this.Bob.transform.position + tension, .2f);

// Red: Resultant
Gizmos.color = new Color(1f, .3f, .3f); // red
Vector3 resultant = gravity + tension;
Gizmos.DrawRay(this.Bob.transform.position, resultant);
Gizmos.DrawSphere(this.Bob.transform.position + resultant, .2f);

/* * /
// Green: Pendulum side direction
Gizmos.color = new Color(.3f, 1f, .3f);
Gizmos.DrawRay(this.Bob.transform.position, 3f*this.pendulumSideDirection);
Gizmos.DrawSphere(this.Bob.transform.position + 3f*this.pendulumSideDirection, .2f);
/* */

/* * /
// Cyan: tangent direction
Gizmos.color = new Color(.2f, 1f, 1f); // cyan
Gizmos.DrawRay(this.Bob.transform.position, 3f*this.tangentDirection);
Gizmos.DrawSphere(this.Bob.transform.position + 3f*this.tangentDirection, .2f);
/* */
}
}


More glamour shots:

• This is utterly phenomenal. Really incredible. Been playing around with this and you can really see just how realistic this when you push it into a turbulent state and see it come apart. My one query would be that it appears to be mildly chaotic in increasing and decreasing amplitude, is there a way to explain what is happening there? Still trying pick this apart and digest it all... – user3447980 May 28 '14 at 18:57
• @user3447980 I get this slightly "chaotic" effect as well. Part of it has to do with floating point imprecision in the decimal range. Increasing dt may help as deltaTime will be smaller making it interpolate less but also means more imprecision in the decimal range. I think the real issue is that since the rope can't stretch, I limit the distance away from the pivot(see the bottom of PendulumUpdate). Also keep in mind that this is a rope and not a rigid rod. So if you put the bob in the middle it will free fall, before hitting the end of the rope and starting to swing. – MLM May 28 '14 at 19:12
• @user3447980 I updated the code with a fix for drift in side to side motion when there shouldn't be any movement. I noticed this some setups. I also tried a double precision version of the Pendulum script but there is no difference. There is still a problem where the pendulum slowly doesn't make it as high as when it started which I will try to figure out. – MLM May 29 '14 at 2:32
• @MLM sorry for resurrecting this, but knowing this problem to be a good benchmark for illustrating the properties of numerical integration methods, I noticed your remark where you stated you did not want to cheat by using the analytical (theta-based) solution. As far as I know, the numerical integration (in your case Symplectic Euler) cannot completely solve the issue of not being able to keep a reasonably constant swing radius. Then I saw this line: return this.GetPointOnLine(pivot_p, currentStatePosition + movementDelta, distance <= this.ropeLength ? distance : this.ropeLength);. – teodron May 20 '16 at 15:38