In this tutorial, we set up a simulation in which rectangles collide with each other and the walls, bouncing in physically realistic ways.
Note: The physics tutorials assume you are comfortable with using the basics of the MASON simulator.
This tutorial teaches:
In the sim/app/physicsTutorial1 directory, create a file called MobilePoly.java. In this file add:
package sim.app.physicsTutorial1; import java.awt.*; import sim.engine.*; import sim.physics2D.physicalObject.*; import sim.physics2D.util.*; public class MobilePoly extends MobileObject2D implements Steppable { public MobilePoly(Double2D pos, Double2D vel, double width, double height, Paint paint) { // initial position and velocity this.setVelocity(vel); this.setPose(pos, new Angle(0)); // shape - determines how the object will look and how collision detection is done this.setShape(new sim.physics2D.shape.Rectangle(width, height, paint), width * height); // physical properties this.setCoefficientOfFriction(0); this.setCoefficientOfRestitution(1); } }
The rectangles in this simulation are "mobile objects" (as opposed to "stationary objects," like walls). The physics simulator implements much of the logic needed to simulate mobile objects in the class sim.physics2D.physicalObject.MobileObject2D. Create a mobile object by inheriting from this class and then defining its specific attributes and behavior.
The first thing that gets set up in MobilePoly is its initial position and velocity by calling the setVelocity and setPose methods provided by MobileObject2D. This tells the physics engine where the objects are and how fast they are going when the simulation starts.
A MobileObject2D has no associated shape until one is provided. In this case, we use sim.physics2D.shape.Rectangle for the shape. The second parameter passed to setShape is the object's mass. To make larger rectangles act heavier, I assumed that the rectangles mass is given by its area (width * height).
The last two method calls set some physical parameters for the object. The "Coefficient of Friction" tells the simulator how easily the objects slides over the background. A value of "0" means that the object is perfectly slippery - kind of like a puck on ice. As the value for the friction coefficient increases the object gets less slippery. The "Coefficient of Restitution" determines how elastic the object is - how hard it rebounds from collisions. A value of 1 indicates perfect elasticity (like a super ball), while a value of 0 means the object is totally inelastic (like a flat basketball). After getting the simulation working, try playing around with different values for these two coefficients.
After setting up the constructor, finish the mobile poly by adding the code for the step method:
public void step(SimState state) { Double2D position = this.getPosition(); PhysicsTutorial1 simPhysicsTutorial1 = (PhysicsTutorial1)state; simPhysicsTutorial1.fieldEnvironment.setObjectLocation(this, new sim.util.Double2D(position.x, position.y)); }
As the simulation proceeds, the physics engine updates objects' positions based on their velocities, the forces and torques applied to them, and any collisions involving them. A mobile object can determine where it is at the current timestep with a call to MobileObject2D's getPosition method. One thing to note here is that the physics engine provides its own Double2D class (sim.app.physics2D.util.Double2D) that is separate from MASON's sim.util.Double2D class.
In the sim/app/physicsTutorial1 directory, create a file called Wall.java. In this file add:
package sim.app.physicsTutorial1; import java.awt.*; import sim.physics2D.physicalObject.*; import sim.physics2D.util.*; public class Wall extends StationaryObject2D { public double radius; public Wall(Double2D pos, int width, int height) { this.setCoefficientOfRestitution(1); this.setPose(pos, new Angle(0)); this.setShape(new sim.physics2D.shape.Rectangle(width, height, Color.white, true)); } }
A Wall, unlike a MobilePoly, is a "stationary object" - it has an infinite mass so it does not move at all as a result of collisions or forces. In this physics engine, this is represented by StationaryObject2D.
The rest of the code for the wall is similar to that of MobilePoly except for the final parameter passed to the Rectangle constructor. The value of true for this parameter tells the Rectangle that it is a stationary object. It is not necessary to provide the Rectangle with this information, but doing so increases efficiency by allowing the Rectangle to cache data about it's vertices' positions and to provide more accurate information about its endpoints to be used for collision detection.
The file sim/app/physicsTutorial1/PhysicsTutorial1.java already has much of the non-physics related MASON code needed to set up the simulation. The only code to add is in the start method. The following code blocks get added sequentially starting at the "Add physics specific code here" line.
// Create and schedule the physics engine PhysicsEngine2D objPE = new PhysicsEngine2D(); schedule.scheduleRepeating(objPE);
PhysicsEngine coordinates all the activities performed by the physics simulator. It needs to run at every times step, so it needs to be scheduled with the MASON scheduler.
Double2D pos; // WALLS // HORIZ Wall wall; pos = new Double2D(100, 0); wall = new Wall(pos, 200, 6); fieldEnvironment.setObjectLocation(wall, new sim.util.Double2D(pos.x, pos.y)); objPE.register(wall); pos = new Double2D(100,200); wall = new Wall(pos, 200, 6); fieldEnvironment.setObjectLocation(wall, new sim.util.Double2D(pos.x, pos.y)); objPE.register(wall); // VERT pos = new Double2D(0, 100); wall = new Wall(pos, 6, 200); fieldEnvironment.setObjectLocation(wall, new sim.util.Double2D(pos.x, pos.y)); objPE.register(wall); pos = new Double2D(200, 100); wall = new Wall(pos, 6, 200); fieldEnvironment.setObjectLocation(wall, new sim.util.Double2D(pos.x, pos.y)); objPE.register(wall);
This code creates four rectangles and places them around the simulation "floor." The horizontal walls are 200 units wide and 6 units tall. The position given for each wall specifies where its center should go. The walls are not scheduled with the MASON scheduler because they don't do anything except sit there. They do, however, need to be registered with the physics engine so it will know if other objects collide with them. In general, any object that needs to be considered by the physics engine needs to be registered at the beginning of the simulation.
Double2D vel; MobilePoly rec; for (int i = 0; i < 4; i++) { pos = new Double2D(Math.max(Math.min(random.nextDouble() * 200, 190), 10), Math.max(Math.min(random.nextDouble() * 200, 190), 10)); vel = new Double2D(random.nextDouble() * 1.5, random.nextDouble() * 1.5); rec = new MobilePoly(pos, vel, 15, 30, Color.red); fieldEnvironment.setObjectLocation(rec, new sim.util.Double2D(pos.x, pos.y)); objPE.register(rec); schedule.scheduleRepeating(rec); }
This creates four mobile polys in random locations and random velocities. The mobile polys need to be scheduled with the MASON scheduler and registered with the physics engine.
Compile MobilePoly.java, Wall.java, PhysicsTutorial1.java, and PhysicsTutorial1WithUI.java. Then run the program as java sim.app.physicsTutorial1.PhysicsTutorial1WithUI
After pressing play, you should see four red rectangles moving around and colliding with each other and the walls.