Main content
Computer programming
Course: Computer programming > Unit 5
Lesson 6: Angular Movement- Angles and units
- Challenge: Spinning baton
- Angular velocity
- Challenge: Falling boulder
- Trigonometry
- Trigonometric ratios in right triangles
- Pointing towards movement
- Challenge: Turning car
- Polar coordinates
- Challenge: Spiral drawer
- Project: Asteroids spaceship
© 2023 Khan AcademyTerms of usePrivacy PolicyCookie Notice
Pointing towards movement
Let’s go all the way back to one of our first examples, the one where a
Mover
object accelerates towards the mouse.You might notice that almost all of the shapes we’ve been drawing so far are circles. This is convenient for a number of reasons, one of which is that we don’t have to consider the question of rotation. Rotate a circle and, well, it looks exactly the same. However, there comes a time in all motion programmers’ lives when they want to draw something on the screen that points in the direction of movement. Perhaps you are drawing an ant, or a car, or a spaceship. And when we say "point in the direction of movement," what we are really saying is “rotate according to the velocity vector.” Velocity is a vector, with an x and a y component, but to rotate in ProcessingJS we need an angle. Let’s draw our trigonometry diagram one more time, with an object’s velocity vector:
OK. We know that the definition of tangent is:
The problem with the above is that we know velocity, but we don’t know the angle. We have to solve for the angle. This is where a special function known as inverse tangent comes in, sometimes referred to as arctangent or tan-1. (There is also an inverse sine and an inverse cosine.)
If the tangent of some value a equals some value b, then the inverse tangent of b equals a. For example:
if | t, a, n, g, e, n, t, left parenthesis, a, right parenthesis, equals, b |
then | a, equals, a, r, c, t, a, n, g, e, n, t, left parenthesis, b, right parenthesis |
See how that is the inverse? The above now allows us to solve for the angle:
if | t, a, n, g, e, n, t, left parenthesis, a, n, g, l, e, right parenthesis, equals, v, e, l, o, c, i, t, y, start subscript, y, end subscript, slash, v, e, l, o, c, i, t, y, start subscript, x, end subscript |
then | a, n, g, l, e, equals, a, r, c, t, a, n, g, e, n, t, left parenthesis, v, e, l, o, c, i, t, y, start subscript, y, end subscript, slash, v, e, l, o, c, i, t, y, start subscript, x, end subscript) |
Now that we have the formula, let’s see where it should go in our mover’s
display()
method. Notice that in ProcessingJS, the function for arctangent is called atan()
. JavaScript also provides Math.atan()
natively (as well as all the basic trig functions), but we'll stick with the ProcessingJS provided functions.Mover.prototype.display = function () {
var angle = atan(this.velocity.y / this.velocity.x);
stroke(0, 0, 0);
fill(127, 127, 127);
pushMatrix();
rectMode(CENTER);
translate(this.position.x, this.position.y);
rotate(angle);
rect(0, 0, 30, 10);
popMatrix();
};
Now the above code is pretty darn close, and almost works. We still have a big problem, though. Let’s consider the two velocity vectors depicted below.
Though superficially similar, the two vectors point in quite different directions—opposite directions, in fact! However, if we were to apply our formula to solve for the angle to each vector…
V1 ⇒ angle = atan(3/-4) = atan(-0.75) = -0.644 radians = -57 degrees
V2 ⇒ angle = atan(-3/4) = atan(-0.75) = -0.644 radians = -57 degrees
V2 ⇒ angle = atan(-3/4) = atan(-0.75) = -0.644 radians = -57 degrees
…we get the same angle for each vector. This can’t be right for both; the vectors point in opposite directions! The thing is, this is a pretty common problem in computer graphics. Rather than simply using
atan()
along with a bunch of conditional statements to account for positive/negative scenarios, ProcessingJS (along with JavaScript and pretty much all programming environments) has a nice function called atan2()
that does it for you.Mover.prototype.display = function () {
var angle = atan2(this.velocity.y, this.velocity.x);
stroke(0, 0, 0);
fill(127, 127, 127);
pushMatrix();
rectMode(CENTER);
translate(this.position.x, this.position.y);
rotate(angle);
rect(0, 0, 30, 10);
popMatrix();
};
To simplify this even further, the
PVector
object itself provides a function called heading()
, which takes care of calling atan2()
for you so you can get the 2D direction angle, in radians, for any PVector
.Here's what the program looks like, all together. Move your mouse over it and see how it rotates!
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.
Want to join the conversation?
- So heading is basically atan2?(6 votes)
- Basically, yes. But without the correct arguments, that does not get us very far.(6 votes)
- What are the variables xoff, yoff & r doing here? I see that's they are set to values of 1000, 0 & 16 respectively but then neither those variables, or the numbers are used anywhere in the program.(5 votes)
- You are right. They are not used in this project.
That happens often when using object oriented coding. It is so easy to re-use the code, so you just copy from your ready made objects, but you don't need all parts of the objects in all projects.(3 votes)
- In the turning car challenge pressing the keys to turn the car results in a stutter before it starts turning. Any idea on how I can make it smoother? Also how can I implement multiple keys at the same time?(3 votes)
- Here is an example of a typical way of multiple key checking.
https://www.khanacademy.org/computer-programming/keyskeycode-spin-off-of-keyreleased/5557812305788928(4 votes)
- Here's my last part of code:
Car.prototype.turnLeft = function() {
//println("turning left!");
var force = this.velocity.get();
force.rotate(-PI/2);
this.applyForce(force);
};
Car.prototype.turnRight = function() {
//println("turning right!");
var force = this.velocity.get();
force.rotate(PI/2);
this.applyForce(force);
};
var car = new Car();
var keyPressed = function() {
if (keyIsPressed && keyCode === LEFT) {
car.turnLeft();
} else if (keyIsPressed && keyCode === RIGHT) {
car.turnRight();
}
};
draw = function() {
background(102, 209, 104);
keyPressed();
car.update();
car.checkEdges();
car.display();
};(4 votes) - Took me quite a while to figure out the next challenge solution.
I wonder if there is any other ways to achieve the task?Car.prototype.turnLeft = function() {
var f = this.velocity.get();
f.rotate(-PI/2);
this.applyForce(f);
};(3 votes) - It seems to have an anomaly inside. First, we measure the angle. Then we rotate all the way to that angle. That process should make the rectangle to move not very smooth. For example, we move the mouse to the upper boundary, it will move up forever and we slowly introduce the mouse from outside of the screen to the sides bounadry. The angle should calculated to 90 and rotate immediately to the mouse. And that is not what i've seen(2 votes)
- Since it is a force, it adds the force to the acceleration, which in turn slowly changes the velocity. The angle is not directly pointing to the mouse(2 votes)
- For some reason, in the next challenge when I press the left or right arrow key, the car teleports to the top left. I don't understand what I'm doing wrong but programme seems to accept the code. Here is my code:
angleMode = "radians";
var Car = function() {
this.position = new PVector(width/2, height/2);
this.velocity = new PVector(3, 0);
this.acceleration = new PVector(0, 0);
this.topspeed = 4;
this.xoff = 1000;
this.yoff = 0;
this.r = 16;
};
Car.prototype.update = function () {
this.velocity.add(this.acceleration);
this.velocity.limit(this.topspeed);
this.position.add(this.velocity);
this.acceleration.mult(0);
};
Car.prototype.applyForce = function(force) {
this.acceleration.add(force);
};
Car.prototype.turnLeft = function() {
println("turning left!");
var rotate = this.velocity.get;
this.velocity.rotate(-0.1);
this.applyForce(rotate);
};
Car.prototype.turnRight = function() {
println("turning right!");
this.velocity.rotate(0.1);
this.applyForce(rotate);
};
Car.prototype.display = function () {
// Step 3:
var angle = this.velocity.heading();
stroke(0, 0, 0);
strokeWeight(2);
fill(127, 127, 127);
pushMatrix();
rectMode(CENTER);
translate(this.position.x, this.position.y);
// Step 3:
rotate(angle);
// draw the car
fill(255, 0, 0);
rect(0, 0, 70, 30);
rect(0, 0, 29, 30);
fill(79, 79, 79);
ellipse(-15, -18, 20, 8);
ellipse(-15, 18, 20, 8);
ellipse(15, 18, 20, 8);
ellipse(15, -18, 20, 8);
rect(21, 0, 11, 26);
popMatrix();
};
Car.prototype.checkEdges = function () {
if (this.position.x > width) {
this.position.x = 0;
} else if (this.position.x < 0) {
this.position.x = width;
}
if (this.position.y > height) {
this.position.y = 0;
} else if (this.position.y < 0) {
this.position.y = height;
}
};
var car = new Car();
draw = function() {
background(102, 209, 104);
car.update();
car.checkEdges();
car.display();
};
keyPressed = function() {
if (keyCode === LEFT) {
car.turnLeft();
} else if (keyCode === RIGHT) {
car.turnRight();
}
};(2 votes)this.applyForce(rotate);
I do not believe that a function is a force. Disappointment ensues.(1 vote)
- What is the difference between ProcessingJS and JavaScript?(2 votes)
- Processing.js is a JS drawing library while Javascript is a programming language.(2 votes)
- I've done "Challenge: Turning car", it says I've completed all the steps, but when I press the left and right arrow keys, my car won't turn. Why is that?
Here's my code:angleMode = "radians";
var Car = function() {
this.position = new PVector(width/2, height/2);
this.velocity = new PVector(3, 0);
this.acceleration = new PVector(0, 0);
this.topspeed = 4;
this.xoff = 1000;
this.yoff = 0;
this.r = 16;
};
Car.prototype.update = function () {
this.velocity.add(this.acceleration);
this.velocity.limit(this.topspeed);
this.position.add(this.velocity);
this.acceleration.mult(0);
};
Car.prototype.applyForce = function(force) {
this.acceleration.add(force);
};
Car.prototype.turnLeft = function() {
println("turning left!");
var force = PVector.get(this.velocity);
force.rotate((-PI/2));
this.applyForce(force);
};
Car.prototype.turnRight = function() {
println("turning right!");
var force = PVector.get(this.velocity);
force.rotate(PI/2);
this.applyForce(force);
};
Car.prototype.display = function () {
// Step 3:
var angle = this.velocity.heading();
stroke(0, 0, 0);
strokeWeight(2);
fill(127, 127, 127);
pushMatrix();
rectMode(CENTER);
translate(this.position.x, this.position.y);
// Step 3:
rotate(angle);
// draw the car
fill(255, 0, 0);
rect(0, 0, 70, 30);
rect(0, 0, 29, 30);
fill(79, 79, 79);
ellipse(-15, -18, 20, 8);
ellipse(-15, 18, 20, 8);
ellipse(15, 18, 20, 8);
ellipse(15, -18, 20, 8);
rect(21, 0, 11, 26);
popMatrix();
};
Car.prototype.checkEdges = function () {
if (this.position.x > width) {
this.position.x = 0;
} else if (this.position.x < 0) {
this.position.x = width;
}
if (this.position.y > height) {
this.position.y = 0;
} else if (this.position.y < 0) {
this.position.y = height;
}
};
var car = new Car();
draw = function() {
background(102, 209, 104);
car.update();
car.checkEdges();
car.display();
var keyPressed = function() {
if (keyIsPressed && keyCode === LEFT) {
car.turnLeft();
} else if (keyIsPressed && keyCode === RIGHT) {
car.turnRight();
}
};
};(2 votes) - I'm so confused with the next challenge, here's my code...
angleMode = "radians";
var Car = function() {
this.position = new PVector(width/2, height/2);
this.velocity = new PVector(3, 0);
this.acceleration = new PVector(0, 0);
this.topspeed = 4;
this.xoff = 1000;
this.yoff = 0;
this.r = 16;
};
Car.prototype.update = function () {
this.velocity.add(this.acceleration);
this.velocity.limit(this.topspeed);
this.position.add(this.velocity);
this.acceleration.mult(0);
};
Car.prototype.applyForce = function(force) {
this.acceleration.add(force);
};
Car.prototype.turnLeft = function() {
println("turning left!");
};
Car.prototype.turnRight = function() {
println("turning right!");
};
Car.prototype.display = function () {
// Step 3:
var angle = this.velocity.heading();
stroke(0, 0, 0);
strokeWeight(2);
fill(127, 127, 127);
pushMatrix();
rectMode(CENTER);
translate(this.position.x, this.position.y);
// Step 3:
rotate(angle);
// draw the car
fill(255, 0, 0);
rect(0, 0, 70, 30);
rect(0, 0, 29, 30);
fill(79, 79, 79);
ellipse(-15, -18, 20, 8);
ellipse(-15, 18, 20, 8);
ellipse(15, 18, 20, 8);
ellipse(15, -18, 20, 8);
rect(21, 0, 11, 26);
popMatrix();
};
Car.prototype.checkEdges = function () {
if (this.position.x > width) {
this.position.x = 0;
} else if (this.position.x < 0) {
this.position.x = width;
}
if (this.position.y > height) {
this.position.y = 0;
} else if (this.position.y < 0) {
this.position.y = height;
}
};
var car = new Car();
var keypresed = function() {
if (keyIsPressed === LEFT) {
car.turnLeft();}
else if (keyIsPressed === RIGHT) {
car.turnRight();}
};
draw = function() {
background(102, 209, 104);
car.update();
car.checkEdges();
car.display();
};
But then oh noes pops up and says "Which function are you using to process input? You should be using the processingJS function that is called whenever you press a key."
but everything i used is processingJS...isn't it?(2 votes)- keyIsPressed is a boolean variable, not a number. "keypresed" was never called, and shall do nothing until then. Shall i refer you to the Intro to JS?(1 vote)