Skip to content

Instantly share code, notes, and snippets.

@Roland09
Last active March 30, 2016 02:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Roland09/4021335c1a0c38f4fdd47602b63885f1 to your computer and use it in GitHub Desktop.
Save Roland09/4021335c1a0c38f4fdd47602b63885f1 to your computer and use it in GitHub Desktop.
Bouncing Balls Example
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);
}
}
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;
}
}
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);
}
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