University of Wales Trinity Saint David
ACCB5017 - Games Architecture and Mechanics

Newton's Laws

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:

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.

  1. Move object1 by velocity1 scaled by deltaTime.
  2. If object1's left edge has gone past 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.
  3. Do the same check on the y axis using velocity1.y and window.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.

Newtons.cpp
#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.

  1. Add acceleration scaled by deltaTime to velocity2.
  2. Move object1 by velocity2 scaled by deltaTime.
  3. In main, replace the call to updateFirstLaw(deltaTime, window) with updateSecondLaw(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.

Newtons.cpp
#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.

  1. Inside the if (object2.getGlobalBounds().intersects(object1.getGlobalBounds())) block, compute a new velocity for object1 using the elastic collision formula below, with mass and velocity3 as this object's own mass and velocity, and mass2 and velocity4 as the other object's mass and velocity.
  2. Compute a new velocity for object2 the same way, with the two objects' masses and velocities swapped.
  3. Assign both new velocities to velocity3 and velocity4.
  4. Outside the if block, move object1 by velocity3 scaled by deltaTime and object2 by velocity4 scaled by deltaTime.
  5. In main, replace the call to updateSecondLaw(deltaTime) with updateThirdLaw(deltaTime), draw object2 alongside object1, and set object2's position to 500.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.

v1′=v1(m1−m2)+2m2v2m1+m2v_1' = \frac{v_1 (m_1 - m_2) + 2 m_2 v_2}{m_1 + m_2}
v2′=v2(m2−m1)+2m1v1m1+m2v_2' = \frac{v_2 (m_2 - m_1) + 2 m_1 v_1}{m_1 + m_2}
Newtons.cpp
#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.