Professional Documents
Culture Documents
https://www.khanacademy.org/computing/computer-
programming/programming-natural-simulations/programming-
oscillations/a/trig-and-forces-the-pendulum
Log in or Sign up to save your progress.
Skip to main content
Courses
Search
DonateLoginSign up
Main content
Computing Computer programming Advanced JS: Natural
SimulationsOscillations
o
Oscillation amplitude and period
o
Challenge: Rainbow slinky
o
Oscillation with angular velocity
o
Challenge: Spaceship ride
o
Waves
o
Challenge: Many waves
o
Trig and forces: the pendulum
This is the currently selected item.
o
Challenge: Pendulum puppet
o
Spring forces
o
Project: Curling, crawling, circling creatures
Next lesson
Particle Systems
this.angle = PI/4;
this.aVelocity = 0.0;
this.aAcceleration = 0.0;
};
We’ll also need to write an update() method to update the pendulum’s angle
according to our formula…
Pendulum.prototype.update = function() {
// Arbitrary constant
var gravity = 0.4;
// Calculate acceleration
this.aAcceleration = 1 * gravity * sin(this.angle);
// Increment velocity
this.aVelocity += this.aAcceleration;
// Increment angle
this.angle += this.aVelocity;
};
…as well as a display() method to draw the pendulum in the window. This
begs the question: “Um, where do we draw the pendulum?” We know the
angle and the arm length, but how do we know the x,y (Cartesian!)
coordinates for both the pendulum’s pivot point (let’s call it origin) and bob
location (let’s call it position)? This may be getting a little tiring, but the
answer, yet again, is trigonometry. Let's reference the diagram to the left.
The origin is just something we make up, as is the arm length. Let’s say we
construct our pendulum like so:
var p = new Pendulum(new PVector(100, 10), 125);
We're storing the current angle on the angle property. So relative to the
origin, the pendulum’s position is a polar coordinate: (r,angle). And we need
it to be Cartesian. Luckily for us, we spent some time in the Angles section
deriving the formula for converting from polar to Cartesian. In that section,
our angle was relative to the horizontal axis, but here, it's relative to the
vertical axis, so we end up using sin() for the x position and cos() for the y
position, instead of cos() and sin(), respectively. And so, we can calculate
the position relative to the origin using that conversion formula, and then
add the origin position to it:
this.position = new PVector(
this.armLength * sin(this.angle),
this.armLength * cos(this.angle));
this.position.add(this.origin);
stroke(0, 0, 0);
fill(175, 175, 175);
line(this.origin.x, this.origin.y, this.position.x, this.position.y);
ellipse(this.position.x, this.position.y, 16, 16);
Before we put everything together, there’s one last little detail I neglected to
mention. Let’s think about the pendulum arm for a moment. Is it a metal
rod? A string? A rubber band? How is it attached to the pivot point? How long
is it? What is its mass? Is it a windy day? There are a lot of questions that we
could continue to ask that would affect the simulation. We’re living, of
course, in a fantasy world, one where the pendulum’s arm is some idealized
rod that never bends and the mass of the bob is concentrated in a single,
infinitesimally small point.
Nevertheless, even though we don’t want to worry ourselves with all of the
questions, we should add one more variable to our calculation of angular
acceleration. To keep things simple, in our derivation of the pendulum’s
acceleration, we assumed that the length of the pendulum’s arm is 1. In fact,
the length of the pendulum’s arm affects the acceleration greatly: the longer
the arm, the slower the acceleration. To simulate a pendulum more
accurately, we divide by that length, in this case armLength. For a more
involved explanation, visit The Simple Pendulum website.
this.aAcceleration = (1 * gravity / this.armLength) * sin(this.angle);
Finally, a real-world pendulum is going to experience some amount of friction
(at the pivot point) and air resistance. With our code as is, the pendulum
would swing forever, so to make it more realistic we can use a “damping”
trick. I saytrick because rather than model the resistance forces with some
degree of accuracy (as we did in the Forces section), we can achieve a
similar result by simply reducing the angular velocity during each cycle. The
following code reduces the velocity by 1% (or multiplies it by 99%) during
each frame of animation:
this.aVelocity *= this.damping;
Putting everything together, we have the following example. We've added a
bit of functionality to make it easy to drag the bob and drop it from different
heights, too. Try it out!
This "Natural Simulations" course is a derivative of "The Nature of Code" by
Daniel Shiffman, used under a Creative Commons Attribution-
NonCommercial 3.0 Unported License.
Sort by:
Top Voted
Questions
Tips & Thanks
Want to join the conversation?
Log in
awk888
5 years ago
This is my code, but it says "Looks like you're storing the current origin in a local variable. You'll want to
make that an object property so you can use it in other methods. " what should I do? Help!
angleMode = "radians";
this.aVelocity = 0.0;
this.aAcceleration = 0.0;
this.damping = 0.995;
this.ballRadius = 25.0;
this.dragging = false;
};
Pendulum.prototype.go = function() {
this.update();
this.display();
};
Pendulum.prototype.update = function() {
// As long as we aren't dragging the pendulum, let it swing!
if (!this.dragging) {
// Arbitrary constant
var gravity = 0.4;
// Calculate acceleration
this.aAcceleration = (-1 * gravity / this.armLength) * sin(this.angle);
// Increment velocity
this.aVelocity += this.aAcceleration;
// Arbitrary damping
this.aVelocity *= this.damping;
// Increment angle
this.angle += this.aVelocity;
}
};
Pendulum.prototype.display = function() {
if (this.origin instanceof PVector){
this.currehtOrigin = this.origin;
} else {
this.currentOrigin = this.origin.position;
}
this.position = new PVector(
this.armLength * sin(this.angle),
this.armLength * cos(this.angle));
this.position.add(this.origin);
stroke(0, 0, 0);
strokeWeight(3);
line(this.origin.x, this.origin.y, this.position.x, this.position.y);
fill(224, 194, 134);
if (this.dragging) {
fill(143, 110, 44);
}
ellipse(this.position.x, this.position.y, this.ballRadius, this.ballRadius);
};
Pendulum.prototype.stopDragging = function() {
this.aVelocity = 0;
this.dragging = false;
};
draw = function() {
background(255);
};
mousePressed = function() {
for (var i = 0; i < limbs.length; i++){
limbs[i].handleClick(mouseX, mouseY);
}
};
mouseDragged = function() {
for (var i = 0; i < limbs.length; i++){
limbs[i].handleDrag(mouseX, mouseY);
}
};
mouseReleased = function() {
for (var i = 0; i < limbs.length; i++){
limbs[i].stopDragging();
}
};
Reply
Button opens signup modal
•1 comment
(8 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
o
Emily Bartee
4 years ago
Not sure, but it may be because you have 'current' spelled wrong in your if function :
(6 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
See 1 more reply
5 years ago
Hi,
The article seems to jump from giving us the
sin(theta) = Fp/Fg formula to
this.aAcceleration = (-1 * gravity / this.armLength) * sin(this.angle);
I understand that there is a different article that explains why to add the division by this.armLength....
However, my question now is why add the part where u multiply by -1, when the foundation/basic formula is
sin(theta) = Fp/Fg formula to which works out to
Fg * sin(theta) = Fp or Fp = Fg * sin(theta)
And in the ProcessingJS world where 0 0 normally starts on the top left of the screen, Fg should be a positive
number so that the position will go down as the number gets incremented. By multiplying it by -1, isn't it the
reverse of Fg already?
Thank you....
Reply
Button opens signup modal
•Comment
Button opens signup modal
(5 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
Bob Lyon
5 years ago
You are very observant, but the -1 has nothing to do with the default grid layout of the Processing.js canvas.
This section makes my head explode every time I look at it. It is because the original author abandoned all
the previous sections and instead concentrated on angular displacements, angular velocities
and angularaccelerations as a "simplifying" move. (KA's only fault is republishing it... I vented about this
long ago and I agreed that there was no real choice but to keep the original text.)
A way to look at it goes all the way back to the first picture of this section. The angle is the pendulum's
angle relative to the vertical axis (and not the traditional horizontal axis). So angle zero is the resting
position. When the angle is positive, the sine of the angle is positive and we want the effects of gravity to
"pull" the bob back towards the zero angle, so the -1 is necessary. Similarly, when the angle is negative, its
sine is negative and we still want the effects of gravity to "pull" the bob back towards the zero angle, but
now that pulling must be positive, so the -1 is necessary. (Clear as mud, right? i tried...)
Note that the variable gravity is only expressing gravitational effects. With more time and possible
confusion, I could make the argument that the gravity variable should always be a negative number, thus
obviating the -1.
The other effects of basing the angle off of the vertical axis is reflected in the display method. There you
notice that the X coordinate is computed using the sine of the angle while the Y coordinate uses the cosine.
This is bass ackwards from what a casual observer (like you or me) would expect.
Bonus: Now explain the initialization of the pendulum's angle as computed by the handleDrag method. Do
not let your head explode.
5 comments
(10 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
See 1 more reply
CountOfTrouble
5 years ago
Anyone know the correct formatting to get it to accept the first step?
Reply
Button opens signup modal
•1 comment
(2 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
Kevin Looby
5 years ago
You need to use for loops to loop through the limb array. The grader won't accept it if you list out all of the
limbs one-by-one.
9 comments
(6 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
See 3 more replies
hideotsubouchi
5 years ago
I have been thinking of this for weeks, I still cannot find an answer.
Any help will be highly appreciated.
As pointed out below, by Bob Lyon, the program certainly works well by passing a PVector or a Pendulum
object to the origin argument of the Pendulum constructor function.
However, I think it should also work if either a PVector or a position of another instance (e.g.,
rightArm1.position) is passed as an origin argument when Pendulum instances are made. Then, in the case
that a position of another instance is used, a new position will be calculated based on the position of another
instance as the origin.
I tried this, but even when a position of another instance was used as the origin, the origin is set to x = 0, y
= 0, doesn't go anywhere else. I don't understand why.
I have monitored the positions of limbs (e.g., for rightArm1, text(rightArm1.position, ....) etc ).
rightArm1.position is certainly getting correct numbers (x = ~250, y = ~185).
Reply
Button opens signup modal
•4 comments
(6 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
o
seannam1218
3 years ago
I just want to say this is exactly what I tried, and could not get it to work. Thank you guys for clearing it up,
I'm leaving a comment here so I can refer to this discussion in the future.
Comment
Button opens signup modal
(1 vote)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
Vlad Winter
5 years ago
Reply
Button opens signup modal
•Comment
Button opens signup modal
(2 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
Bob Lyon
5 years ago
I made it so you pass either a PVector object or a Pendulum object as the origin argument to
the Pendulum constructor function. Then, within the display method I immediately set a new
property,currentOrigin which describes the pivot point of this pendulum using the
Javascript instanceof operator:
if (this.origin instanceof PVector) {
this.currentOrigin = this.origin;
} else {
this.currentOrigin = this.origin.position;
}
Then I can use the currentOrigin property knowing it's always the pivot point of the pendulum.
1 comment
(8 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
See 2 more replies
Kayla Fuhriman
3 years ago
I'm stuck on step two of pendulum puppet. When you pick up the bottom pendulums, they kind of jump up
and then they start swinging. I'm not sure what's wrong with it. Here is the code.
angleMode = "radians";
this.aVelocity = 0.0;
this.aAcceleration = 0.0;
this.damping = 0.995;
this.ballRadius = 25.0;
this.dragging = false;
};
Pendulum.prototype.go = function() {
this.update();
this.display();
};
Pendulum.prototype.update = function() {
// As long as we aren't dragging the pendulum, let it swing!
if (!this.dragging) {
// Arbitrary constant
var gravity = 0.4;
// Calculate acceleration
this.aAcceleration = (-1 * gravity / this.armLength) * sin(this.angle);
// Increment velocity
this.aVelocity += this.aAcceleration;
// Arbitrary damping
this.aVelocity *= this.damping;
// Increment angle
this.angle += this.aVelocity;
}
};
Pendulum.prototype.display = function() {
if(this.origin instanceof PVector){
this.currentOrigin = this.origin;
} else {
this.currentOrigin = this.origin.position;
}
this.position = new PVector(
this.armLength * sin(this.angle),
this.armLength * cos(this.angle));
this.position.add(this.currentOrigin);
stroke(0, 0, 0);
strokeWeight(3);
line(this.currentOrigin.x, this.currentOrigin.y, this.position.x, this.position.y);
fill(224, 194, 134);
if (this.dragging) {
fill(143, 110, 44);
}
ellipse(this.position.x, this.position.y, this.ballRadius, this.ballRadius);
};
Pendulum.prototype.stopDragging = function() {
this.aVelocity = 0;
this.dragging = false;
};
Pendulum.prototype.handleDrag = function(mx, my) {
if (this.dragging) {
var diff = PVector.sub(this.origin, new PVector(mx, my));
this.angle = atan2(-1*diff.y, diff.x) - radians(90);
}
};
draw = function() {
background(255);
};
mousePressed = function() {
mouseDragged = function() {
for (var i = 0; i < limbs.length; i++){
limbs[i].handleDrag(mouseX, mouseY);
}
};
mouseReleased = function() {
for (var i = 0; i < limbs.length; i++){
limbs[i].stopDragging();
}
};
Reply
Button opens signup modal
•Comment
Button opens signup modal
(3 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
3 years ago
yeah i had the same problem in the handleDrag method there is another origin you have to change to
currentOrigin
Comment
Button opens signup modal
(5 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
abhishek994sharma
4 years ago
Reply
Button opens signup modal
•Comment
Button opens signup modal
(4 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
Jared Thurber
4 years ago
Back in the display function, where it calls line, you have to change the first 2 parameters to
this.currentOrigin.x, this.currentOrigin.y, and back up a little bit more, you heve to change
this.position.add(this.currentOrigin);
2 comments
(3 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
octoberX
2 years ago
Please Help !!
Reply
Button opens signup modal
•Comment
Button opens signup modal
(2 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
Jim E
a year ago
Each pendulum (limb) have either a PVector as it's origin or another Pendulum. So we need to know if we just
use the origin which is a PVector, or the "parent" Pendulum.position (also a PVector).
1 comment
(4 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
See 1 more reply
mayazazoo
4 years ago
F=MA is really F(net)=MA, so why are we considering the net force to be the force of gravity, when the
tension force is also acting on the pendulum? Thanks :)
Reply
Button opens signup modal
•Comment
Button opens signup modal
(1 vote)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
Bob Lyon
4 years ago
This chapter tries to move quickly from forces to acceleration of the system due to gravity, and while the
sleight of hand is valid, it can be dissatisfying.
Consider the pendulum at rest. The force of gravity is equal to the opposite of the tension. So the net force is
zero, as is the acceleration. Nonetheless, the acceleration due to gravity can be defined asacceleration
= gravity * sin(theta) (also zero when theta is zero) which "magically" takes into account all the
forces. (After all, we know the *force*of gravity is constant on Planet Earth, and that the pendulum and the
planet know nothing about Trigonometry.) It is that tension that makes the bob move in an arc as opposed to
falling straight down.
(6 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
Richard Bustamante
3 years ago
I can't figure out what the grader is looking for in Step 3 of the challenge. Anybody know? Here is my code:
angleMode = "radians";
Pendulum.prototype.go = function() {
this.update();
this.display();
};
Pendulum.prototype.update = function() {
// As long as we aren't dragging the pendulum, let it swing!
if (!this.dragging) {
// Arbitrary constant
var gravity = 0.4;
// Calculate acceleration
this.aAcceleration = (-1 * gravity / this.armLength) * sin(this.angle);
// Increment velocity
this.aVelocity += this.aAcceleration;
// Arbitrary damping
this.aVelocity *= this.damping;
// Increment angle
this.angle += this.aVelocity;
}
};
Pendulum.prototype.display = function() {
if (this.origin instanceof PVector) {
this.currentOrigin = this.origin;
} else {
this.currentOrigin = this.origin.position;
}
Pendulum.prototype.stopDragging = function() {
this.aVelocity = 0;
this.dragging = false;
};
draw = function() {
background(255);
// Draw the body
strokeWeight(4);
line(width/2-50, 110, width/2+50, 110);
line(width/2, 110, width/2, 230);
line(width/2-40, 230, width/2+40, 230);
fill(224, 194, 134);
rect(width/2-25, 39, 50, 64, 30);
for (var i = 0; i < limbs.length; i++) {
limbs[i].go();
}
};
mousePressed = function() {
for (var i = 0; i < limbs.length; i++) {
limbs[i].handleClick(mouseX, mouseY);
}
};
mouseDragged = function() {
for (var i = 0; i < limbs.length; i++) {
limbs[i].handleDrag(mouseX, mouseY);
}
};
mouseReleased = function() {
for (var i = 0; i < limbs.length; i++) {
limbs[i].stopDragging();
}
};
Reply
Button opens signup modal
•Comment
Button opens signup modal
(1 vote)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
Bob Lyon
3 years ago
Their intent was to create still art until user action caused motion. Your program introduces motion from the
get go. revert back to a simpler constructor and wait for user mouse actions.
2 comments
(5 votes)
Upvote
Button opens signup modal
Downvote
Button opens signup modal
Flag
Button opens signup modal
more
Show more...