Physics Simulator

A Discrete Time Simulator

The questions below are due on Monday February 17, 2020; 11:59:00 PM.
 
You are not logged in.

If you are a current student, please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.csail.mit.edu) to authenticate, and then you will be redirected back to this page.
A Python Error Occurred:

Error on line 7 of Python tag (line 8 of source):
    kerberos = cs_user_info['username']

KeyError: 'username'

Back to Exercise 02

Music for this Problem

In the next series of exercises we're going to learn how to think about and write a simple physics simulator to make a "rolling ball" on our LCD.

The result will be a system that looks like the following:

1) Physics

We're going to represent a ball on our LCD using the tft.fillCircle and/or tft.drawCircle functions that you may have already started messing with in Lab 01A. We'll then update its drawn position many times per second to make it look like it is moving. However, we want to make sure that it looks "real" so we need to build a simple physics engine and to do that we need to come up with a physics-based model that is code-compatible in order to code up this ball's motion and behavior. The first part of this is coming up with a consistent way of thinking about our coordinate space:

1.1) Frame of Reference

Generally when doing graphics, the origin (0,0) is in the top left of the screen with positive X pointing right and positive Y pointing downward. We'll follow that convention here so your screen has a coordinate field like the following:

Screen Orientation for our LCD as we've laid it out on the board (note this is valid when tft.setRotation(2); has been asserted!

We'll be simulating a moving ball so we'll need to also maintain consistency with this coordinate system when calculating the ball's acceleration, velocity, and position. For example, the ball's velocity vectors will be defined as following:

Ball Orientation

As you can see, positive y velocity is defined as down-pointing and positive x velocity defined as right-pointing.

2) Code and Modeling

To get us started, download the skeleton code file for our ball simulator HERE. We also have it printed out below for reference. Take a few minutes to read through it and start to digest it. There is a lot to take in.

#include <TFT_eSPI.h> // Graphics and font library for ST7735 driver chip
#include <SPI.h> //Used in support of TFT Display
#include <string.h>  //used for some string handling and processing.

TFT_eSPI tft = TFT_eSPI();  // Invoke library, pins defined in User_Setup.h

#define BACKGROUND TFT_GREEN
#define BALL_COLOR TFT_BLUE

const int DT = 40; //milliseconds
const int EXCITEMENT = 10000; //how much force to apply to ball
const uint8_t BUTTON_PIN = 16; //CHANGE YOUR WIRING TO PIN 16!!! (FROM 19)

uint32_t primary_timer; //main loop timer

//state variables:
float x_pos = 64; //x position
float y_pos = 32; //y position
float x_vel = 0; //x velocity
float y_vel = 0; //y velocity
float x_accel = 0; //x acceleration
float y_accel = 0; //y acceleration

//physics constants:
const float MASS = 1; //for starters
const int RADIUS = 5; //radius of ball
const float K_FRICTION = 0.15;  //friction coefficient
const float K_SPRING = 0.9;  //spring coefficient

//boundary constants:
const int LEFT_LIMIT = RADIUS; //left side of screen limit
const int RIGHT_LIMIT = 127-RADIUS; //right side of screen limit
const int TOP_LIMIT = RADIUS; //top of screen limit
const int BOTTOM_LIMIT = 159-RADIUS; //bottom of screen limit
 
bool pushed_last_time; //for finding change of button (using bool type...same as uint8_t)
 
//will eventually replace with your improved step function!
void step(float x_force=0, float y_force=0 ){
  //update acceleration (from f=ma)
  x_accel = x_force/MASS;
  y_accel = y_force/MASS;
  //integrate to get velocity from current acceleration
  x_vel = x_vel + 0.001*DT*x_accel; //integrate, 0.001 is conversion from milliseconds to seconds
  y_vel = y_vel + 0.001*DT*y_accel; //integrate!!
  //
  moveBall(); //you'll write this from scratch!
}
 
void moveBall(){
  //your code here
}
 
 
void setup() {
  Serial.begin(115200); //for debugging if needed.
  pinMode(BUTTON_PIN,INPUT_PULLUP);
  tft.init();
  tft.setRotation(2);
  tft.setTextSize(1);
  tft.fillScreen(BACKGROUND);
  randomSeed(analogRead(0));  //initialize random numbers
  step(random(-EXCITEMENT,EXCITEMENT),random(-EXCITEMENT,EXCITEMENT)); //apply initial force to lower right
  pushed_last_time = false;
  primary_timer = millis();
}
 
void loop() {
  //draw circle in previous location of ball in color background (redraws minimal num of pixels, therefore is quick!)
  tft.fillCircle(x_pos,y_pos,RADIUS,BACKGROUND);
  //if button pushed *just* pushed down, inject random force into system
  //else, just run out naturally
  if (!digitalRead(BUTTON_PIN)){ //if pushed
    if(!pushed_last_time){ //if not previously pushed
      pushed_last_time = true;
      step(random(-EXCITEMENT,EXCITEMENT),random(-EXCITEMENT,EXCITEMENT)); //apply initial force to lower right
    }else{
      step();
    }
  }else{ //else not pushed
    pushed_last_time = false; //mark that we were not pushed
    step(); //step as usual
  }
  tft.fillCircle(x_pos,y_pos,RADIUS,BALL_COLOR); //draw new ball location

  while (millis()-primary_timer < DT); //wait for primary timer to increment
  primary_timer = millis();
}

The basic flow of this piece of code is:

  • Check if there are any forces applied to the ball. In this code, we use button pushes to stimulate the ball. Upon pushing your button (hooked up to IO pin 16 **which is different than what we've been using1, so let's not use it..), a random force is applied to the ball, the magnitude of which is dictated by the EXCITEMENT variable.
  • Calculate acceleration if any forces are applied, and update velocity accordingly (integrate). This is done within the step function.
  • Update position based on current position and velocity. This is not yet implemented, but will be done within the moveBall function.
  • draw new position
  • wait until next time step

In this system, forces can only be applied to the ball through the step function. If you look at how that is declared and defined in the code you'll notice the two input arguments are set =0. This is a way to make the arguments take on a default value if they are not specified. As a result if we wanted to apply a force of 1 and 3 Newtons in the x and y dimensions, respectively you could do:

step(1,3)

and if you just wanted to let the ball run (and apply no force) you would just do:

step()

Let's look a bit more into what step is doing We can make our life a bit easier by assuming like we should have learned in 8.01 or AP Physics or whatever you took that we can handle each axis of motion separately. We first take in the force applied to the ball and divide it by the mass we've assigned (for simplicity it is 1 here, but feel free to change it later). Diagramatically we're doing the following, which is coming right from f = ma from 8.01.

Force and Mass relation

Once we've determined our acceleration in a given axis we will then integrate it to determine our velocity for that axis. 8.01 tells us that assuming we're starting from rest:

v(t) = \int_0^ta(\tau)d\tau
but as we went through on the previous page we can't just type that in...instead we must integrate in discrete time which means we'll be implementing a lineof code such as the following which integrates for us:
v[n] = v[n-1] + \Delta T a[n]
which diagrammatically looks like:

Acceleration to Velocity

Finally we'll determine our position in each axis by integrating a second time:

p[n] = p[n-1] + \Delta T v[n]
This last part has not been done yet! You'll need to do it!!!

Velocity to Position

2.1) States of Our System

When all put together, each dimension of freedom that our ball has is made up of three states represented by the red boxes below: acceleration, velocity, and position. These six values uniquely describe the state of a ball at each time step and are needed to fully describe its operation.

The term "state" here is somewhat related to the idea of "state" in our Finite State Machines that we've been building. Both describe the state of a system. There are differences, however. In this ball simulator we're building there are infinite potential values for each state, whereas in the case of our finite state machine there are, well...a finite amount.

State Identification

These states will be held by the following six variables (three for each axis, with one for acceleration, one for velocity, and one for position) and we are making them global right now.

float x_pos; //x pos state
float y_pos; //y pos state
float x_vel; //x vel state
float y_vel; //y pos state
float x_accel; //x accel state
float y_accel; //y accel state

It is no coincidence that we're using the term "state" here to describe the variables that describe the ball. The ball that we are creating is another example of a stateful system, although of a different type than what we've dealt with previously. If you apply a force to it, its output (how the ball moves) will be based not only that force but also on the the state of the system (its current position and velocity). Because the number of states is not finite, however, we won't refer to this as a finite state machine (the states of the system are essentially unlimited....we can have any speed)

3) moveBall

It is now your job to write function called moveBall that updates the position state of the ball based on current velocity and previous position. Use the system diagrams drawn out above as well as what we learned when discussing difference equations in the discrete time part of this week's exercises to determine what should go into moveBall. First just get moveBall to properly integrate velocity with position. Submit that working version into the checker below to make sure you're on the right track. Don't forget to scale time appropriately!!

3.1) moveBall v1

Submit a moveBall implementation that does ONLY the velocity to position integration! You have access to all the global variables in the starter code. For now, do NOT take into account the boundaries of the screen.

Functions that you write should make use of all relevant constant variables defined at the top of the skeleton code. Do not assume things like mass, boundaries, etc.

void moveBall(){ //Your code here! }

When you pass the check, add this working function into the code skeleton, upload and see what happens. It will be awesome for a second...and then sadness...and then not sadness....and then sadness again...the ball should appear to be teleporting from one side of the screen to the other after flying across.

3.2) moveBall v2

What happened? Well our physics engine worked well, but we never bothered to code up wall collissions. So you just simulated yourself right off the edge of the screen which is no good. Now you need to make an even better version of moveBall that handles wall collissions.

This new better moveBall method should also handle wall collisions in a completely elastic manner meaning that the normal velocity is perfectly negated as shown in the figure below when a wall is encountered:

Reflection Mechanism

We can describe this operation as:

v_{\perp}[n] = -v_{\perp}[n']
where n' is the value at the nth timestep prior to correction and n is the value after this correction.

Test your code out locally and be sure to paste in a fully functioning (from the previous section) in order to get everything working correctly! When you feel like your code is good, paste it in below for checking/grading purposes. Paste only the moveBall method in below. We will test it by dropping it into a simulated game in our code.

YOU MUST fully reflect the velocity with this ball. Be very careful with x_pos and y_pos during a bounce.

For example, let's say the ball is a single pixel at (32,158) and is moving 3 pixels in the +y direction per time step (really large) and it cannot move beyond y=159 (i.e. BOTTOM_LIMIT is 159). At the next time step it must be at (32,157) because it moved down +1 pixel towards the wall, then -2 pixel away from the wall (for full 3 pixel reflection)

Do NOT hardcode the boundary values (0, 63, 127, or whatever). Instead use LEFT_LIMIT, RIGHT_LIMIT, etc.

void moveBall(){ //Your code here! }

When you have passed the tutor check above, you should have a really nice perpetual motion machine of a rolling ball. This is cool, but we're going to use this in game development in the future and this might cause some issues (in something like pong it wouldn't, but in a simulated soccer or billiards game it would). So what's happening? The answer is that our physics model right now has no loss mechanism. The ball is initially accelerated and it bounces around a bunch, but it never loses any of the simulated kinetic energy imparted on it at the start of the simulation. An energy loss mechanism such as friction or non-ideal elastic collisions with walls will make the system more realistic. So let's do that. Let's pull some energy out of our system (and make it "lossy") by adding in dynamic friction. In general friction manifests as a force in the opposite direction of the velocity

f_{fric} = -k_{fric}v

We can implement this mechanism by changing our system block diagram. A modified system diagram for this new simulation is shown below. Notice how the location where the velocity state exists is now also "fed back", multiplied by the friction coefficient, and then subtracted from the force being applied by gravity. The subtraction part is necessary to account for the fact that the force exerted by dynamic friction is opposing the sign of the velocity.

Adding in some Friction

We also want to add a lossy bounce characteristic. Where before 100% of normal velocity would reflect upon collision with a wall in real life that will never happen...in the case of a rubber ball bouncing off a wall, the compression and then expansion of the rubber is not 100% efficient...instead some of that energy is given off as heat, sound, enjoyment, etc... So we're going to now have some loss, which will be represented by the following:

v_{\perp}[n] = -k_sv_{\perp}[n']
where v_{\perp}[n'] is the value at the nth timestep prior to correction and v_{\perp}[n] is the value after this correction. k_s is the spring coefficient discussed above and k_s\leq1 (otherwise the ball would be drawing energy from its collisions with the stationary wall, which doesn't make sense.)

Spring Mechanism

You can assume two additional variables are defined and should be included in the code: K_SPRING is a float that represents the spring coefficient of the ball and K_FRICTION is a float that represents the friction coefficient.

3.3) step v2

Modify the step and moveBall functions so that they now take into account friction. With wall bounces, be particularly careful with x_pos and y_pos. Remember that K_SPRING will decrease the distance of a wall bounce. From the previous example, we had a ball at (32,158), which was trying to move 3 units in the +y direcion. We had BOTTOM_LIMIT = 159. Let's now say K_SPRING is 0.5. The ball will move +1 unit, hit the wall, and then move -2 * 0.5 = -1 unit back off the wall, ending at (32,158). Here, the inelastic collision has decreased the "bounce back" distance from 2 units in the -y direction to 1 unit in the -y direction.

In the box below implement an improved version of step only! (you'll implement an new version of moveBall in the next section.)

void step(float x_force=0, float y_force=0){ //Your code here! }

3.3.1) moveBall v3

Now implement an improved version of moveBall

void moveBall(){ //Your code here! }

Once you've got these two bits of code, put them on your microcontroller and enjoy a bouncing ball. It should jump approximately every time you push your button! If it doesn't make sure you switch is hooked up, and that you have the right versions of the code in your file! Feel free to change friction or the initial "kick" in the setup to get different behaviors! We'll build on some of this ball physics in future exercises.

Back to Exercise 02

 
Footnotes

1Pin 19 is turning out to be buggy since it shares some internal resources with the I2C bus and the WiFi (click to return to text)