|
|
BouncingBallsExample
Shows a simple example of Heron, using the examples of bouncing balls.
Bouncing Balls Example
The following code animates four circles that bounce off the walls of a box but ignore each other. Using the Heron to Java compiler an applet was created which you can see at http://www.heron-language.com/demo.html.
The class and state diagram can be found at http://code.google.com/p/heron-language/wiki/BouncingBallDiagrams
domain Demo
{
imports {
Math;
Graphics;
}
attributes {
painter : Painter;
walls : Collection<Wall>;
balls : Collection<Ball>;
}
operations {
initialize() {
var x0 : int = 100;
var y0 : int = 100;
var x1 : int = 300;
var y1 : int = 300;
walls = new Collection<Wall>();
balls = new Collection<Ball>();
walls.add(new Wall(x0,y0,x1,y0));
walls.add(new Wall(x1,y0,x1,y1));
walls.add(new Wall(x1,y1,x0,y1));
walls.add(new Wall(x0,y1,x0,y0));
balls.add(new Ball(new Point(200,200), new Vector(200,120), 10));
balls.add(new Ball(new Point(150,150), new Vector(100,60), 15));
balls.add(new Ball(new Point(250,150), new Vector(-50,-5), 20));
balls.add(new Ball(new Point(250,250), new Vector(-100,0), 25));
balls.add(new Ball(new Point(150,250), new Vector(0,100), 30));
painter = new Painter();
painter.generateNextEvent();
}
distance(first : Point, second : Point) : Real {
return sqrt(sqr(first.x - second.x) + sqr(first.y - second.y));
}
sqrt(x : Real) : Real {
return Math.sqrt(x);
}
sqr(x : Real) : Real {
return x * x;
}
acos(x : Real) : Real {
return Math.acos(x);
}
min(x : Real, y : Real) : Real {
return Math.min(x, y);
}
floor(x : Real) : int {
return (int)Math.floor(x);
}
updateBallPositions(elapsedMSec : Real) {
foreach (ball : Ball in Demo.balls) {
ball.updatePosition(elapsedMSec);
}
}
}
classes {
class Point {
attributes {
x : Real;
y : Real;
}
operations {
constructor(x : Real, y : Real) {
this.x = x;
this.y = y;
}
translate(v : Vector) : Point {
Point result = new Point(x + v.x, y + v.y);
return result;
}
difference(pt : Point) : Vector {
return new Vector(pt.x - x, pt.y - y);
}
distance(pt : Point) : Real {
return difference(pt).length();
}
asVector() : Vector {
return new Vector(x, y);
}
}
}
class Vector {
attributes {
x : Real;
y : Real;
}
operations {
constructor(x : Real, y : Real) {
this.x = x;
this.y = y;
}
constructor(line : Line) {
this.x = line.begin.x - line.end.x;
this.y = line.begin.y - line.end.y;
}
add(v : Vector) : Vector {
return new Vector(x + v.x, y + v.y);
}
sub(v : Vector) : Vector {
return new Vector(v.x - x, v.y - y);
}
scale(s : Real) : Vector {
return new Vector(x * s, y * s);
}
dot(v : Vector) : Real {
return x * v.x + y * v.y;
}
normal() : Vector {
return new Vector(-y, x);
}
normalize() : Vector {
return new Vector(x / length(), y / length());
}
length() : Real {
return Demo.sqrt(Demo.sqr(x) + Demo.sqr(y));
}
theta(v : Vector) : Real {
return Demo.acos(dot(v) / (length() * v.length()));
}
tangent() : Vector {
return new Vector(y, -x);
}
proj(v : Vector) : Vector {
return v.scale(dot(v) / Demo.sqr(v.length()));
}
}
}
class Line {
attributes {
begin : Point;
end : Point;
}
operations {
constructor(x0 : Real, y0 : Real, x1 : Real, y1 : Real) {
begin = new Point(x0, y0);
end = new Point(x1, y1);
}
length() : Real {
return Demo.distance(begin, end);
}
}
}
class Wall {
attributes {
line : Line;
}
operations {
constructor(x0 : Real, y0 : Real, x1 : Real, y1 : Real) {
line = new Line(x0, y0, x1, y1);
}
}
}
class Ball {
attributes {
pos : Point;
vec : Vector;
radius : Real;
}
operations {
constructor(pos : Point, vec : Vector, radius : Real) {
this.pos = pos;
this.vec = vec;
this.radius = radius;
}
computeCollisionEvent(wall : Wall) : CollisionEvent
{
var x0 : Real = pos.x;
var y0 : Real = pos.y;
var xt : Real = vec.x;
var yt : Real = vec.y;
var x1 : Real = wall.line.begin.x;
var y1 : Real = wall.line.begin.y;
var x2 : Real = wall.line.end.x;
var y2 : Real = wall.line.end.y;
var den : Real = Demo.sqrt(Demo.sqr(x2 - x1) + Demo.sqr(y2 - y1));
var dx : Real = x2 - x1;
var dy : Real = y2 - y1;
var t1 : Real = ((radius * den) - (dx * y1) + (dx * y0) + (dy * x1) - (dy * x0)) / (-dx * yt + dy * xt);
var t2 : Real = (-(radius * den) - (dx * y1) + (dx * y0) + (dy * x1) - (dy * x0)) / (-dx * yt + dy * xt);
var t : Real = Demo.min(t1, t2);
if (t1 <= 0.0) {
if (t2 <= 0.0) {
return null;
}
else {
t = t2;
}
}
else {
if (t2 < 0) {
t = t1;
}
else {
t = Demo .min (t1 , t2 ) ;
}
}
CollisionEvent ret = new CollisionEvent(Demo.floor(t * 1000), this, wall);
return ret;
}
computeCollisionEvents() : Collection<CollisionEvent> {
Collection<CollisionEvent> result = new Collection<CollisionEvent>();
foreach (wall : Wall in Demo.walls) {
CollisionEvent collision = computeCollisionEvent(wall);
if (collision != null) {
result.add(collision);
}
}
return result;
}
bounceOffWall(wall : Wall) {
var w : Vector = new Vector(wall.line);
var n : Vector = w.normal();
var pn : Vector = vec.proj(n);
var pw : Vector = vec.proj(w);
var r : Vector = pn.scale(-1.0).add(pw);
vec.x = r.x;
vec.y = r.y;
}
updatePosition(elapsedMSec : Real) {
pos = pos.translate(vec.scale(elapsedMSec / 1000.0));
}
}
}
class CollisionEvent {
attributes {
timeElapsed : Real;
ball : Ball;
wall : Wall;
}
operations {
constructor(timeElapsed : Real, ball : Ball, wall : Wall) {
this.timeElapsed = timeElapsed;
this.ball = ball;
this.wall = wall;
}
}
}
class PaintEvent {
attributes {
timeElapsed : Real;
}
operations {
constructor(timeElapsed : Real) {
this.timeElapsed = timeElapsed;
}
}
}
class Painter {
attributes {
paintFrequency : Real;
timeToNextPaint : Real;
}
operations {
constructor() {
paintFrequency = 50.0;
timeToNextPaint = paintFrequency;
}
generateNextEvent() {
generateNextEvent(null, null);
}
generateNextEvent(lastBall : Ball, lastWall : Wall) {
var next : CollisionEvent;
var q : Collection<CollisionEvent> = new Collection<CollisionEvent>();
// Computer all possible CollisionEvents
q.clear();
foreach (ball : Ball in Demo.balls) {
q.concat(ball.computeCollisionEvents());
}
// Exclude last ball/wall CollisionEvent
q.filter((x : CollisionEvent)
=> { return (x.ball != lastBall) || (x.wall != lastWall); });
// Get the next earliest CollisionEvent
next = q.min((x : CollisionEvent) => { return x.timeElapsed; });
if (next != null) {
if (next.timeElapsed < timeToNextPaint) {
timeToNextPaint = timeToNextPaint - next.timeElapsed;
this.sendIn(next, (int)next.timeElapsed);
}
else {
this.sendIn(new PaintEvent(timeToNextPaint), (int)timeToNextPaint);
}
}
else {
Demo.error("could not compute next collision");
}
}
}
states {
initial() {
transitions {
CollisionEvent -> onCollision;
PaintEvent -> onPaint;
}
}
onCollision(col : CollisionEvent) {
entry {
Demo.updateBallPositions(col.timeElapsed);
col.ball.bounceOffWall(col.wall);
generateNextEvent(col.ball, col.wall);
}
transitions {
CollisionEvent -> onCollision;
PaintEvent -> onPaint;
}
}
onPaint(evt : PaintEvent) {
entry {
timeToNextPaint = paintFrequency;
Demo.clear();
Demo.updateBallPositions(evt.timeElapsed);
foreach (wall : Wall in Demo.walls) {
Demo.drawLine(wall.line.begin.x, wall.line.begin.y, wall.line.end.x, wall.line.end.y);
}
foreach (ball : Ball in Demo.balls) {
Demo.drawCircle(ball.pos.x, ball.pos.y, ball.radius);
}
Demo.render();
generateNextEvent();
}
transitions {
CollisionEvent -> onCollision;
PaintEvent -> onPaint;
}
}
}
}
}
}
Sign in to add a comment
