Overview
This lab builds a small SFML program, Newtons.cpp, where three separate per-frame update functions each demonstrate one of Newton's three laws of motion using sf::CircleShape objects moving inside a single window. updateFirstLaw moves a ball at a constant velocity and reverses it off the window's edges, updateSecondLaw accelerates a ball under a constant force divided by its mass, and updateThirdLaw exchanges velocity between two balls of different mass through an elastic collision formula the instant they touch. Only one update function is wired into the main loop at a time, so each law can be built and observed on its own before the next one replaces it.
Learning Objectives
By the end of this lab you will be able to:
- Explain Newton's first law of motion and identify why a per-frame update needs an external event, such as a wall, to change an object's velocity under that law
- Implement a per-frame update that moves an object at a constant velocity and reverses the correct velocity component when the object reaches a window edge
- Explain how Newton's second law, force equals mass times acceleration, produces a constant acceleration from a fixed force and mass, and how that acceleration is integrated into velocity and position each frame
- Implement Euler integration that accumulates a constant acceleration into velocity and velocity into position for an object with no boundary response
- Explain Newton's third law and implement a one-dimensional elastic collision that exchanges velocity between two objects of different mass using conservation of momentum
Concept
A game object's velocity has to change for some reason, or every ball in a scene would either sit still forever or drift in a straight line without ever slowing, speeding up, or bouncing off anything. Newton's three laws of motion give the rule for exactly when and how a velocity is allowed to change, and this lab turns each law into a small, separate per-frame update.
The first law says an object in motion stays in motion at a constant velocity unless an external force acts on it. In code, that means a moving sf::CircleShape keeps the same velocity every frame until something, here the edge of the window, forces a change by reversing one component of it.
The second law gives the rule for how a force does change velocity. it states force = mass * acceleration, so a constant force divided by a mass gives a constant acceleration, added into velocity scaled by deltaTime every frame, and that velocity in turn added into position. this is the same Euler integration pattern used to fall under gravity elsewhere in this project.
The third law says every force comes paired with an equal and opposite reaction. when two objects collide, this shows up as conservation of momentum: an elastic collision formula built from both objects' masses and velocities computes new velocities for both at once, so what one object loses in speed the other gains, tested with sf::FloatRect::intersects().
Tasks
Task 1 - First law, constant velocity and a wall bounce
Create a new Console App (C++) project in Visual Studio the same way as in the Environment Setup lab, naming it Newtons. Use Add > New Item... to add a new C++ source file named Newtons.cpp, and write the skeleton below. Complete updateFirstLaw, which moves object1 and reverses its velocity when it reaches an edge of the window.
- Move
object1byvelocity1scaled bydeltaTime. - If
object1's left edge has gone past0, or its right edge,getPosition().x + object1.getRadius() * 2.0f, has gone pastwindow.getSize().x, print"There is a collision here"and reversevelocity1.x. - Do the same check on the y axis using
velocity1.yandwindow.getSize().y.
Success condition: build and run the project. object1 drifts diagonally across the window at a constant speed and bounces off all four edges, each bounce reversing only the axis it hit, printing "There is a collision here" to the console on every bounce.
#include <SFML/Graphics.hpp>
#include <iostream>
sf::CircleShape object1(20.0f);
sf::Vector2f velocity1(200.0f, 150.0f);
void updateFirstLaw(float deltaTime, sf::RenderWindow& window)
{
// TODO: move object1 by velocity1 scaled by deltaTime
// TODO: if object1's left edge is less than 0, or its right edge
// (getPosition().x + object1.getRadius() * 2.0f) has gone past
// window.getSize().x, print "There is a collision here" and
// reverse velocity1.x
// TODO: same check on the y axis, using velocity1.y and
// window.getSize().y
}
int main()
{
sf::RenderWindow window(sf::VideoMode({800, 600}), "Newton's laws");
object1.setPosition({300.0f, 200.0f});
sf::Clock clock;
while (window.isOpen())
{
float deltaTime = clock.restart().asSeconds();
while (const std::optional event = window.pollEvent())
{
if (event->is<sf::Event::Closed>())
window.close();
}
updateFirstLaw(deltaTime, window);
window.clear();
window.draw(object1);
window.display();
}
}
Task 2 - Second law, a constant force integrated into motion
Open Newtons.cpp. force, mass and acceleration are declared above updateSecondLaw, with acceleration already set to force / mass, Newton's second law rearranged to solve for acceleration. Complete updateSecondLaw, then switch main's single update call from updateFirstLaw to updateSecondLaw so this law can be observed on its own.
- Add
accelerationscaled bydeltaTimetovelocity2. - Move
object1byvelocity2scaled bydeltaTime. - In
main, replace the call toupdateFirstLaw(deltaTime, window)withupdateSecondLaw(deltaTime).
Success condition: build and run the project. object1 starts still and picks up downward speed every frame, moving further each frame than the one before, until it drifts off the bottom of the window. Unlike Task 1, nothing bounces it back, since updateSecondLaw has no edge check.
#include <SFML/Graphics.hpp>
#include <iostream>
sf::CircleShape object1(20.0f);
sf::Vector2f velocity1(200.0f, 150.0f);
sf::Vector2f velocity2(0.0f, 0.0f);
sf::Vector2f force(0.0f, 100.0f);
float mass = 5.0f;
sf::Vector2f acceleration = force / mass;
void updateFirstLaw(float deltaTime, sf::RenderWindow& window)
{
object1.move(velocity1 * deltaTime);
if (object1.getPosition().x < 0.0f ||
object1.getPosition().x + object1.getRadius() * 2.0f > window.getSize().x)
{
std::cout << "There is a collision here" << std::endl;
velocity1.x *= -1.0f;
}
if (object1.getPosition().y < 0.0f ||
object1.getPosition().y + object1.getRadius() * 2.0f > window.getSize().y)
{
std::cout << "There is a collision here" << std::endl;
velocity1.y *= -1.0f;
}
}
void updateSecondLaw(float deltaTime)
{
// TODO: add acceleration scaled by deltaTime to velocity2
// TODO: move object1 by velocity2 scaled by deltaTime
}
int main()
{
sf::RenderWindow window(sf::VideoMode({800, 600}), "Newton's laws");
object1.setPosition({300.0f, 200.0f});
sf::Clock clock;
while (window.isOpen())
{
float deltaTime = clock.restart().asSeconds();
while (const std::optional event = window.pollEvent())
{
if (event->is<sf::Event::Closed>())
window.close();
}
updateFirstLaw(deltaTime, window);
window.clear();
window.draw(object1);
window.display();
}
}
Task 3 - Third law, an elastic collision between two masses
Open Newtons.cpp. A second shape, object2, and a second mass, mass2, are declared above updateThirdLaw, along with velocity3 for object1 and velocity4 for object2. Complete updateThirdLaw, then switch main's update call to updateThirdLaw and give object2 a starting position.
- Inside the
if (object2.getGlobalBounds().intersects(object1.getGlobalBounds()))block, compute a new velocity forobject1using the elastic collision formula below, withmassandvelocity3as this object's own mass and velocity, andmass2andvelocity4as the other object's mass and velocity. - Compute a new velocity for
object2the same way, with the two objects' masses and velocities swapped. - Assign both new velocities to
velocity3andvelocity4. - Outside the
ifblock, moveobject1byvelocity3scaled bydeltaTimeandobject2byvelocity4scaled bydeltaTime. - In
main, replace the call toupdateSecondLaw(deltaTime)withupdateThirdLaw(deltaTime), drawobject2alongsideobject1, and setobject2's position to500.0f, 200.0f.
Success condition: build and run the project. object1 moves right toward object2, which starts still, while object2 already drifts left toward object1. The instant their bounds overlap, both velocities change at once, the heavier object2 continuing on more slowly than object1 bounced back, exactly the equal and opposite exchange the third law predicts.
#include <SFML/Graphics.hpp>
#include <iostream>
sf::CircleShape object1(20.0f);
sf::Vector2f velocity1(200.0f, 150.0f);
sf::Vector2f velocity2(0.0f, 0.0f);
sf::Vector2f force(0.0f, 100.0f);
float mass = 5.0f;
sf::Vector2f acceleration = force / mass;
sf::CircleShape object2(20.0f);
float mass2 = 10.0f;
sf::Vector2f velocity3(200.0f, 0.0f);
sf::Vector2f velocity4(-100.0f, 0.0f);
void updateFirstLaw(float deltaTime, sf::RenderWindow& window)
{
object1.move(velocity1 * deltaTime);
if (object1.getPosition().x < 0.0f ||
object1.getPosition().x + object1.getRadius() * 2.0f > window.getSize().x)
{
std::cout << "There is a collision here" << std::endl;
velocity1.x *= -1.0f;
}
if (object1.getPosition().y < 0.0f ||
object1.getPosition().y + object1.getRadius() * 2.0f > window.getSize().y)
{
std::cout << "There is a collision here" << std::endl;
velocity1.y *= -1.0f;
}
}
void updateSecondLaw(float deltaTime)
{
velocity2 = velocity2 + acceleration * deltaTime;
object1.move(velocity2 * deltaTime);
}
void updateThirdLaw(float deltaTime)
{
if (object2.getGlobalBounds().intersects(object1.getGlobalBounds()))
{
// TODO: compute object1's new velocity from velocity3, velocity4,
// mass and mass2 using the elastic collision formula above
// TODO: compute object2's new velocity the same way, with the
// two objects' masses and velocities swapped
// TODO: assign both new velocities to velocity3 and velocity4
}
// TODO: move object1 by velocity3 scaled by deltaTime
// TODO: move object2 by velocity4 scaled by deltaTime
}
int main()
{
sf::RenderWindow window(sf::VideoMode({800, 600}), "Newton's laws");
object1.setPosition({300.0f, 200.0f});
sf::Clock clock;
while (window.isOpen())
{
float deltaTime = clock.restart().asSeconds();
while (const std::optional event = window.pollEvent())
{
if (event->is<sf::Event::Closed>())
window.close();
}
updateSecondLaw(deltaTime);
window.clear();
window.draw(object1);
window.display();
}
}
Solution
Try the tasks above before revealing the solution.