Last active
March 30, 2016 02:15
-
-
Save Roland09/4021335c1a0c38f4fdd47602b63885f1 to your computer and use it in GitHub Desktop.
Bouncing Balls Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Random; | |
import javafx.animation.AnimationTimer; | |
import javafx.application.Application; | |
import javafx.scene.Scene; | |
import javafx.scene.layout.BorderPane; | |
import javafx.scene.layout.Pane; | |
import javafx.scene.layout.StackPane; | |
import javafx.stage.Stage; | |
import common.PVector; | |
/** | |
* Bouncing balls example: Apply forces gravity and wind | |
*/ | |
public class Main extends Application { | |
static Random random = new Random(); | |
Pane playfield; | |
List<Sprite> allSprites = new ArrayList<>(); | |
AnimationTimer gameLoop; | |
Scene scene; | |
@Override | |
public void start(Stage primaryStage) { | |
// create containers | |
BorderPane root = new BorderPane(); | |
// entire game as layers | |
StackPane layerPane = new StackPane(); | |
// playfield for our Sprites | |
playfield = new Pane(); | |
playfield.setPrefSize(Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT); | |
layerPane.getChildren().addAll(playfield); | |
root.setCenter(layerPane); | |
scene = new Scene(root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT); | |
primaryStage.setScene(scene); | |
primaryStage.show(); | |
// add content | |
prepareGame(); | |
// run animation loop | |
startGame(); | |
} | |
private void prepareGame() { | |
// add sprites | |
for (int i = 0; i < Settings.SPRITE_COUNT; i++) { | |
addSprite(); | |
} | |
} | |
private void startGame() { | |
// start game | |
gameLoop = new AnimationTimer() { | |
@Override | |
public void handle(long now) { | |
// physics: apply forces | |
allSprites.forEach(s -> s.applyForce(Settings.FORCE_GRAVITY)); | |
allSprites.forEach(s -> s.applyForce(Settings.FORCE_WIND)); | |
// move | |
allSprites.forEach(Sprite::move); | |
// check boundaries | |
allSprites.forEach(Sprite::checkBounds); | |
// update in fx scene | |
allSprites.forEach(Sprite::display); | |
} | |
}; | |
gameLoop.start(); | |
} | |
private void addSprite() { | |
Pane layer = playfield; | |
// random location | |
double x = random.nextDouble() * layer.getWidth(); | |
double y = random.nextDouble() * layer.getHeight(); | |
// create sprite data | |
PVector location = new PVector(x, y); | |
PVector velocity = new PVector(0, 0); | |
PVector acceleration = new PVector(0, 0); | |
double mass = random.nextDouble() * 50 + 20; // at least 20 pixels, max | |
// 50 pixels | |
// create sprite and add to layer | |
Sprite sprite = new Sprite(layer, location, velocity, acceleration, mass); | |
// register sprite | |
allSprites.add(sprite); | |
} | |
public static void main(String[] args) { | |
launch(args); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package common; | |
/** | |
* Source: http://www.javased.com/?source_dir=SPaTo_Visual_Explorer/lib/src/core/src/processing/core/PVector.java | |
* Modification: converted all float to double | |
*/ | |
public class PVector { | |
/** The x component of the vector. */ | |
public double x; | |
/** The y component of the vector. */ | |
public double y; | |
/** The z component of the vector. */ | |
public double z; | |
/** Array so that this can be temporarily used in an array context */ | |
protected double[] array; | |
/** | |
* Constructor for an empty vector: x, y, and z are set to 0. | |
*/ | |
public PVector() { | |
} | |
/** | |
* Constructor for a 3D vector. | |
* | |
* @param x the x coordinate. | |
* @param y the y coordinate. | |
* @param z the y coordinate. | |
*/ | |
public PVector(double x, double y, double z) { | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
} | |
/** | |
* Constructor for a 2D vector: z coordinate is set to 0. | |
* | |
* @param x the x coordinate. | |
* @param y the y coordinate. | |
*/ | |
public PVector(double x, double y) { | |
this.x = x; | |
this.y = y; | |
this.z = 0; | |
} | |
/** | |
* Set x, y, and z coordinates. | |
* | |
* @param x the x coordinate. | |
* @param y the y coordinate. | |
* @param z the z coordinate. | |
*/ | |
public void set(double x, double y, double z) { | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
} | |
/** | |
* Set x, y, and z coordinates from a Vector3D object. | |
* | |
* @param v the PVector object to be copied | |
*/ | |
public void set(PVector v) { | |
x = v.x; | |
y = v.y; | |
z = v.z; | |
} | |
/** | |
* Set the x, y (and maybe z) coordinates using a double[] array as the source. | |
* @param source array to copy from | |
*/ | |
public void set(double[] source) { | |
if (source.length >= 2) { | |
x = source[0]; | |
y = source[1]; | |
} | |
if (source.length >= 3) { | |
z = source[2]; | |
} | |
} | |
/** | |
* Get a copy of this vector. | |
*/ | |
public PVector get() { | |
return new PVector(x, y, z); | |
} | |
public double[] get(double[] target) { | |
if (target == null) { | |
return new double[] { x, y, z }; | |
} | |
if (target.length >= 2) { | |
target[0] = x; | |
target[1] = y; | |
} | |
if (target.length >= 3) { | |
target[2] = z; | |
} | |
return target; | |
} | |
/** | |
* Calculate the magnitude (length) of the vector | |
* @return the magnitude of the vector | |
*/ | |
public double mag() { | |
return (double) Math.sqrt(x*x + y*y + z*z); | |
} | |
/** | |
* Add a vector to this vector | |
* @param v the vector to be added | |
*/ | |
public void add(PVector v) { | |
x += v.x; | |
y += v.y; | |
z += v.z; | |
} | |
public void add(double x, double y, double z) { | |
this.x += x; | |
this.y += y; | |
this.z += z; | |
} | |
/** | |
* Add two vectors | |
* @param v1 a vector | |
* @param v2 another vector | |
* @return a new vector that is the sum of v1 and v2 | |
*/ | |
static public PVector add(PVector v1, PVector v2) { | |
return add(v1, v2, null); | |
} | |
/** | |
* Add two vectors into a target vector | |
* @param v1 a vector | |
* @param v2 another vector | |
* @param target the target vector (if null, a new vector will be created) | |
* @return a new vector that is the sum of v1 and v2 | |
*/ | |
static public PVector add(PVector v1, PVector v2, PVector target) { | |
if (target == null) { | |
target = new PVector(v1.x + v2.x,v1.y + v2.y, v1.z + v2.z); | |
} else { | |
target.set(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); | |
} | |
return target; | |
} | |
/** | |
* Subtract a vector from this vector | |
* @param v the vector to be subtracted | |
*/ | |
public void sub(PVector v) { | |
x -= v.x; | |
y -= v.y; | |
z -= v.z; | |
} | |
public void sub(double x, double y, double z) { | |
this.x -= x; | |
this.y -= y; | |
this.z -= z; | |
} | |
/** | |
* Subtract one vector from another | |
* @param v1 a vector | |
* @param v2 another vector | |
* @return a new vector that is v1 - v2 | |
*/ | |
static public PVector sub(PVector v1, PVector v2) { | |
return sub(v1, v2, null); | |
} | |
static public PVector sub(PVector v1, PVector v2, PVector target) { | |
if (target == null) { | |
target = new PVector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); | |
} else { | |
target.set(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); | |
} | |
return target; | |
} | |
/** | |
* Multiply this vector by a scalar | |
* @param n the value to multiply by | |
*/ | |
public void mult(double n) { | |
x *= n; | |
y *= n; | |
z *= n; | |
} | |
/** | |
* Multiply a vector by a scalar | |
* @param v a vector | |
* @param n scalar | |
* @return a new vector that is v1 * n | |
*/ | |
static public PVector mult(PVector v, double n) { | |
return mult(v, n, null); | |
} | |
/** | |
* Multiply a vector by a scalar, and write the result into a target PVector. | |
* @param v a vector | |
* @param n scalar | |
* @param target PVector to store the result | |
* @return the target vector, now set to v1 * n | |
*/ | |
static public PVector mult(PVector v, double n, PVector target) { | |
if (target == null) { | |
target = new PVector(v.x*n, v.y*n, v.z*n); | |
} else { | |
target.set(v.x*n, v.y*n, v.z*n); | |
} | |
return target; | |
} | |
/** | |
* Multiply each element of one vector by the elements of another vector. | |
* @param v the vector to multiply by | |
*/ | |
public void mult(PVector v) { | |
x *= v.x; | |
y *= v.y; | |
z *= v.z; | |
} | |
/** | |
* Multiply each element of one vector by the individual elements of another | |
* vector, and return the result as a new PVector. | |
*/ | |
static public PVector mult(PVector v1, PVector v2) { | |
return mult(v1, v2, null); | |
} | |
/** | |
* Multiply each element of one vector by the individual elements of another | |
* vector, and write the result into a target vector. | |
* @param v1 the first vector | |
* @param v2 the second vector | |
* @param target PVector to store the result | |
*/ | |
static public PVector mult(PVector v1, PVector v2, PVector target) { | |
if (target == null) { | |
target = new PVector(v1.x*v2.x, v1.y*v2.y, v1.z*v2.z); | |
} else { | |
target.set(v1.x*v2.x, v1.y*v2.y, v1.z*v2.z); | |
} | |
return target; | |
} | |
/** | |
* Divide this vector by a scalar | |
* @param n the value to divide by | |
*/ | |
public void div(double n) { | |
x /= n; | |
y /= n; | |
z /= n; | |
} | |
/** | |
* Divide a vector by a scalar and return the result in a new vector. | |
* @param v a vector | |
* @param n scalar | |
* @return a new vector that is v1 / n | |
*/ | |
static public PVector div(PVector v, double n) { | |
return div(v, n, null); | |
} | |
static public PVector div(PVector v, double n, PVector target) { | |
if (target == null) { | |
target = new PVector(v.x/n, v.y/n, v.z/n); | |
} else { | |
target.set(v.x/n, v.y/n, v.z/n); | |
} | |
return target; | |
} | |
/** | |
* Divide each element of one vector by the elements of another vector. | |
*/ | |
public void div(PVector v) { | |
x /= v.x; | |
y /= v.y; | |
z /= v.z; | |
} | |
/** | |
* Multiply each element of one vector by the individual elements of another | |
* vector, and return the result as a new PVector. | |
*/ | |
static public PVector div(PVector v1, PVector v2) { | |
return div(v1, v2, null); | |
} | |
/** | |
* Divide each element of one vector by the individual elements of another | |
* vector, and write the result into a target vector. | |
* @param v1 the first vector | |
* @param v2 the second vector | |
* @param target PVector to store the result | |
*/ | |
static public PVector div(PVector v1, PVector v2, PVector target) { | |
if (target == null) { | |
target = new PVector(v1.x/v2.x, v1.y/v2.y, v1.z/v2.z); | |
} else { | |
target.set(v1.x/v2.x, v1.y/v2.y, v1.z/v2.z); | |
} | |
return target; | |
} | |
/** | |
* Calculate the Euclidean distance between two points (considering a point as a vector object) | |
* @param v another vector | |
* @return the Euclidean distance between | |
*/ | |
public double dist(PVector v) { | |
double dx = x - v.x; | |
double dy = y - v.y; | |
double dz = z - v.z; | |
return (double) Math.sqrt(dx*dx + dy*dy + dz*dz); | |
} | |
/** | |
* Calculate the Euclidean distance between two points (considering a point as a vector object) | |
* @param v1 a vector | |
* @param v2 another vector | |
* @return the Euclidean distance between v1 and v2 | |
*/ | |
static public double dist(PVector v1, PVector v2) { | |
double dx = v1.x - v2.x; | |
double dy = v1.y - v2.y; | |
double dz = v1.z - v2.z; | |
return (double) Math.sqrt(dx*dx + dy*dy + dz*dz); | |
} | |
/** | |
* Calculate the dot product with another vector | |
* @return the dot product | |
*/ | |
public double dot(PVector v) { | |
return x*v.x + y*v.y + z*v.z; | |
} | |
public double dot(double x, double y, double z) { | |
return this.x*x + this.y*y + this.z*z; | |
} | |
static public double dot(PVector v1, PVector v2) { | |
return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; | |
} | |
/** | |
* Return a vector composed of the cross product between this and another. | |
*/ | |
public PVector cross(PVector v) { | |
return cross(v, null); | |
} | |
/** | |
* Perform cross product between this and another vector, and store the | |
* result in 'target'. If target is null, a new vector is created. | |
*/ | |
public PVector cross(PVector v, PVector target) { | |
double crossX = y * v.z - v.y * z; | |
double crossY = z * v.x - v.z * x; | |
double crossZ = x * v.y - v.x * y; | |
if (target == null) { | |
target = new PVector(crossX, crossY, crossZ); | |
} else { | |
target.set(crossX, crossY, crossZ); | |
} | |
return target; | |
} | |
static public PVector cross(PVector v1, PVector v2, PVector target) { | |
double crossX = v1.y * v2.z - v2.y * v1.z; | |
double crossY = v1.z * v2.x - v2.z * v1.x; | |
double crossZ = v1.x * v2.y - v2.x * v1.y; | |
if (target == null) { | |
target = new PVector(crossX, crossY, crossZ); | |
} else { | |
target.set(crossX, crossY, crossZ); | |
} | |
return target; | |
} | |
/** | |
* Normalize the vector to length 1 (make it a unit vector) | |
*/ | |
public void normalize() { | |
double m = mag(); | |
if (m != 0 && m != 1) { | |
div(m); | |
} | |
} | |
/** | |
* Normalize this vector, storing the result in another vector. | |
* @param target Set to null to create a new vector | |
* @return a new vector (if target was null), or target | |
*/ | |
public PVector normalize(PVector target) { | |
if (target == null) { | |
target = new PVector(); | |
} | |
double m = mag(); | |
if (m > 0) { | |
target.set(x/m, y/m, z/m); | |
} else { | |
target.set(x, y, z); | |
} | |
return target; | |
} | |
/** | |
* Limit the magnitude of this vector | |
* @param max the maximum length to limit this vector | |
*/ | |
public void limit(double max) { | |
if (mag() > max) { | |
normalize(); | |
mult(max); | |
} | |
} | |
/** | |
* Calculate the angle of rotation for this vector (only 2D vectors) | |
* @return the angle of rotation | |
*/ | |
public double heading2D() { | |
double angle = (double) Math.atan2(-y, x); | |
return -1*angle; | |
} | |
/** | |
* Calculate the angle between two vectors, using the dot product | |
* @param v1 a vector | |
* @param v2 another vector | |
* @return the angle between the vectors | |
*/ | |
static public double angleBetween(PVector v1, PVector v2) { | |
double dot = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; | |
double v1mag = Math.sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z); | |
double v2mag = Math.sqrt(v2.x * v2.x + v2.y * v2.y + v2.z * v2.z); | |
return (double) Math.acos(dot / (v1mag * v2mag)); | |
} | |
public String toString() { | |
return "[ " + x + ", " + y + ", " + z + " ]"; | |
} | |
/** | |
* Return a representation of this vector as a double array. This is only for | |
* temporary use. If used in any other fashion, the contents should be copied | |
* by using the get() command to copy into your own array. | |
*/ | |
public double[] array() { | |
if (array == null) { | |
array = new double[3]; | |
} | |
array[0] = x; | |
array[1] = y; | |
array[2] = z; | |
return array; | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
import common.PVector; | |
public class Settings { | |
public static double SCENE_WIDTH = 800; | |
public static double SCENE_HEIGHT = 600; | |
public static int SPRITE_COUNT = 10; | |
public static double SPRITE_ACCELERATION_SCALE = 0.5; | |
public static double SPRITE_MAX_SPEED = 4; | |
public static PVector FORCE_WIND = new PVector(0.04,0); | |
public static PVector FORCE_GRAVITY = new PVector(0,3); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package application; | |
import javafx.scene.Node; | |
import javafx.scene.layout.Pane; | |
import javafx.scene.layout.Region; | |
import javafx.scene.paint.Color; | |
import javafx.scene.shape.Circle; | |
import common.PVector; | |
public class Sprite extends Region { | |
PVector location; | |
PVector velocity; | |
PVector acceleration; | |
double mass; | |
double maxSpeed = Settings.SPRITE_MAX_SPEED; | |
Node view; | |
// view dimensions | |
double width = 30; | |
double height = width; | |
double centerX = width / 2.0; | |
double centerY = height / 2.0; | |
double radius = width / 2.0; | |
Pane layer = null; | |
public Sprite(Pane layer, PVector location, PVector velocity, PVector acceleration, double mass) { | |
this.layer = layer; | |
this.location = location; | |
this.velocity = velocity; | |
this.acceleration = acceleration; | |
this.mass = mass; | |
// initialize view depending on mass | |
width = mass; | |
height = width; | |
centerX = width / 2.0; | |
centerY = height / 2.0; | |
radius = width / 2.0; | |
// create view | |
Circle circle = new Circle(radius); | |
circle.setCenterX(radius); | |
circle.setCenterY(radius); | |
circle.setStroke(Color.BLUE); | |
circle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.3)); | |
this.view = circle; | |
// add view to this node | |
getChildren().add(view); | |
// add this node to layer | |
layer.getChildren().add(this); | |
} | |
public void applyForce(PVector force) { | |
// Making a copy of the PVector before using it! | |
PVector f = PVector.div(force, mass); | |
acceleration.add(f); | |
} | |
public void move() { | |
// set velocity depending on acceleration | |
velocity.add(acceleration); | |
// limit velocity to max speed | |
velocity.limit(maxSpeed); | |
// change location depending on velocity | |
location.add(velocity); | |
// clear acceleration | |
acceleration.mult(0); | |
} | |
/** | |
* Ensure sprite can't go outside bounds | |
*/ | |
public void checkBounds() { | |
if (location.x > layer.getWidth() - radius) { | |
location.x = layer.getWidth() - radius; | |
velocity.x *= -1; | |
} else if (location.x < 0 + radius) { | |
velocity.x *= -1; | |
location.x = 0 + radius; | |
} | |
// reverse direction to bounce off floor | |
if (location.y > layer.getHeight() - radius) { | |
velocity.y *= -1; | |
location.y = layer.getHeight() - radius; | |
} | |
} | |
/** | |
* Update node position | |
*/ | |
public void display() { | |
relocate(location.x - centerX, location.y - centerY); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment