I am using JavaFX and AnimationTimer to get a circle to move to a random location triggered by clicking the circle. The issue I am seeing is that the circle will not go toward the the top-right or bottom-right of the screen, I am not sure why.
Here is my code:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.layout.*;
import javafx.scene.shape.*;
import javafx.stage.*;
import javafx.scene.paint.*;
import java.util.*;
import javafx.scene.*;
import javafx.animation.AnimationTimer;
public class MovingCircle extends Application {
double WIDTH = 800;
double HEIGHT = WIDTH;
class TheCircle extends Circle {
public TheCircle(){
super();
setFill(Color.GREEN);
setCenterX(WIDTH / 2);setCenterY(HEIGHT / 2);
setRadius(50);
setOnMouseClicked(event -> {
double newX = random(50, HEIGHT - 50); double newY = random(50, WIDTH - 50);
move(newX, newY);
});
}
void move(double newX, double newY){
double xVelocity;
double yVelocity;
if (newX == getCenterX()){
xVelocity = 0;
}else {
xVelocity = getCenterX() < newX ? 1 : -1;
}
if (newY == getCenterY()){
yVelocity = 0;
}else {
yVelocity = getCenterY() < newY ? 1 : -1;
}
AnimationTimer h = new AnimationTimer() {
#Override
public void handle(long l) {
setCenterY(getCenterX() + xVelocity);
setCenterX(getCenterY() + yVelocity);
//System.out.println(getCenterX() + ", " + getCenterY() + " ; " + newX + ", " + newY);
if (getCenterX() == newX && getCenterY() == newY){
this.stop();
}
}
};
System.out.println("begin!");
h.start();
}
}
public void start(Stage stage){
Pane root = new Pane();
TheCircle mover = new TheCircle();
root.getChildren().add(mover);
stage.setScene(new Scene(root, WIDTH, HEIGHT));
stage.show();
}
public static void main(String[] args){
launch(args);
}
double random(double min, double max){
return Math.floor(Math.random() * (max - min + 1) + min);
}
}
It seems that if I force the xVelocity and yVelocity to animate the circle toward those areas the circle either just stalls or the window freezes up. How can I fix this issue?
Related
im triying to create a simple game, where my player, falls into different platforms. However, rigth now, im only able to detect the first collision.
I've tried making a loop, where i call HandleEvent(), but only works for the first platform.
How could i make it to detect every time the player collides with a platform? Thank you!
Player class:
package com.mygdx.juegoreto;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Rectangle;
import java.util.ArrayList;
public class Personaje {
int x;
int y;
int width;
int height;
int xSpeed;
int ySpeed;
Color color;
public Personaje(int x, int y, int width, int height, int xSpeed, int ySpeed) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.color = Color.WHITE;
}
public void setxSpeed(int xSpeed) {
this.xSpeed = xSpeed;
}
public void setySpeed(int ySpeed) {
this.ySpeed = ySpeed;
}
public void update() {
x += xSpeed;
y += ySpeed;
if (x < 0 || x > Gdx.graphics.getWidth() - 70) {
xSpeed = -xSpeed;
}
if (y > Gdx.graphics.getHeight()) {
System.out.println("tas muerto");
}
}
public void draw(ShapeRenderer shape) {
shape.rect(x, y, width, height);
}
public void checkCollision(ArrayList<Plataforma> plats) {
if(collidesWith(plats)){
color = Color.GREEN;
}
else{
color = Color.WHITE;
}
}
private boolean collidesWith(ArrayList<Plataforma> plats) {
System.out.println("collides");
for (Plataforma plat : plats) {
if(x < plat.x + plat.width && y < plat.y + plat.height && x + width > plat.x && y + height > plat.y){
this.setySpeed(plat.ySpeed);
System.out.println("si");
return true;
}else{
this.setySpeed(-3);
System.out.println("no");
return false;
}
}
return true;
}
}
Main class:
package com.mygdx.juegoreto;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.graphics.GL20;
import java.util.ArrayList;
public class MyGame extends ApplicationAdapter {
ShapeRenderer shape;
Personaje player;
Plataforma plat;
ArrayList<Plataforma> plats;
private float timeSeconds = 0f;
private float period = 0f;
#Override
public void create () {
int x = Gdx.graphics.getWidth() / 2;
int y = Gdx.graphics.getHeight() ;
shape = new ShapeRenderer();
player = new Personaje(x, y - 200, 70, 70, 0, 0);
plat = new Plataforma(x - 70, y - 800, 200, 20,2);
plats = new ArrayList<>(1);
int altura = 0;
for(int i = 0; i <= 1 ; i++){
altura -= 160;
plats.add(new Plataforma((int)(Math.random() * Gdx.graphics.getWidth()), altura, 200, 20, 2));
}
}
#Override
public void render () {
ScreenUtils.clear(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
player.update();
//plat.update();
shape.begin(ShapeRenderer.ShapeType.Filled);
player.draw(shape);
//plat.draw(shape);
timeSeconds +=Gdx.graphics.getDeltaTime();
if(timeSeconds > period){
timeSeconds-=period;
handleEvent();
}
shape.end();
if (Gdx.input.isTouched()) {
if (Gdx.input.getX() < Gdx.graphics.getWidth() / 2){
player.setxSpeed(-2); //IZQUIERDA
} else {
player.setxSpeed(2); //DERECHA
}
}
if(player.y < -40){
System.out.println("tas muerto je");
}
/*for (Plataforma plat : plats ) {
int x = (int)(Math.random() * Gdx.graphics.getWidth());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(x);
plat.draw(shape);
}*/
}
#Override
public void dispose () {
}
public void handleEvent() {
for (Plataforma plataforma : plats) {
plataforma.update();
plataforma.draw(shape);
}
player.checkCollision(plats);
}
}
I'm new to programming graphic applications in java and I'm having some trouble with this one in particular, what it should do is that it should create two rectangles moving right to left. my issue is that for some reason the program does move the rectangles but doesn't show the rectangles and when it does they are for some unknown reason glitched with only two sides visible. here's the code:
Rectangle's class:
import javax.swing.JComponent;
import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class TubeComponent extends JComponent
{
private final static int WIDTH = 20;
private Rectangle up, down;
private int windowLength;
private int windowWidth;
public TubeComponent(int aWidth, int aHeight)
{
windowLength = aWidth;
windowWidth = aHeight;
up = new Rectangle();
down = new Rectangle();
up.setLocation(windowWidth, 0);
int newHeight = (int)((windowLength - WIDTH) * Math.random());
up.setSize(WIDTH, newHeight);
down.setSize(WIDTH, windowLength - newHeight - WIDTH);
down.setLocation(windowWidth, (int) (up.getHeight() + WIDTH));
}
public void randomize()
{
up.setLocation(windowWidth, 0);
int newHeight = (int)((windowLength - WIDTH) * Math.random());
up.setSize(WIDTH, newHeight);
down.setSize(WIDTH, windowLength - newHeight - WIDTH);
down.setLocation(windowWidth, (int) (up.getHeight() + WIDTH));
repaint();
}
#Override
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.draw(up);
g2.draw(down);
System.out.println("ascissa " + getX() + " ordinata " + getY() + " altezza " + getHeight() + " larghezza " + getWidth());
}
public void moveBy(int dx, int dy)
{
up.translate(dx, dy);
down.translate(dx, dy);
repaint();
}
public int getX()
{
return (int) up.getX();
}
public int getY()
{
return (int) up.getY();
}
public int getHeight()
{
return (int) up.getHeight();
}
public int getWidth()
{
return (int) up.getWidth();
}
}
Frame class:
import javax.swing.JFrame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
public class Scene extends JFrame{
private final static int HEIGHT = 400;
private final static int WIDTH = 500;
private final static int SPEED = 4;
private final static int PING = 1;
private TubeComponent tube;
public Scene()
{
tube = new TubeComponent(WIDTH, HEIGHT);
add(tube);
setSize(HEIGHT, WIDTH);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("flappy");
ActionListener a = new TimeListener();
Timer t = new Timer(PING, a);
t.start();
}
private class TimeListener implements ActionListener
{
#Override
public void actionPerformed(ActionEvent e) {
tube.moveBy(-1, 0);
if(tube.getX() < -1*tube.getWidth())
{
tube.randomize();
}
System.out.println("ascissa " + tube.getX() + " ordinata " + tube.getY() + " altezza " + tube.getHeight() + " larghezza " + tube.getWidth());
}
}
}
Executable class:
import javax.swing.JFrame;
public class FrameViewer {
public static void main(String[] args)
{
Scene s = new Scene();
}
}
I've left the print command to show that the rectangle is actually there and isn't out of bounds. I've tried simplifying the class (by making it print a still rectangle to see if it was my computer but even though it still didn't work, by programming another project with just a still rectangle and that works.
Okay so I have a custom pane class that I want to reference from inside FXML. When I try to change the node class to BallPane, it says it doesn't exist.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
public class BallPane extends Pane {
public final double radius = 20;
private double x = radius, y = radius;
private double dx = 1, dy = 1;
private Circle circle = new Circle(x, y, radius);
private Timeline animation;
public BallPane() {
circle.setFill(Color.GREEN);
getChildren().add(circle);
animation = new Timeline(
new KeyFrame(Duration.millis(50), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play(); // Start animation
}
public void play() {
animation.play();
}
public void pause() {
animation.pause();
}
public void increaseSpeed() {
animation.setRate(animation.getRate() + 0.1);
}
public void decreaseSpeed() {
animation.setRate(
animation.getRate() > 0 ? animation.getRate() - 0.1 : 0);
}
public DoubleProperty rateProperty() {
return animation.rateProperty();
}
protected void moveBall() {
// Check boundaries
if (x < radius || x > getWidth() - radius) {
dx *= -1; // Change ball move direction
}
if (y < radius || y > getHeight() - radius) {
dy *= -1; // Change ball move direction
}
// Adjust ball position
x += dx;
y += dy;
circle.setCenterX(x);
circle.setCenterY(y);
}
}
I have tried this nested inside my FXML
<BallPane layoutX="14.0" layoutY="14.0" prefHeight="334.0" prefWidth="462.0" />
and then this in my Controller
#FXML
BallPane bpane;
What am I missing?
I am trying to build a game myself using JavaFX. It features a circle that must follow my cursor (using a smooth translation animation, so it will not go directly to the location of my cursor)
For now I have written this piece of code
root.setOnMouseMoved(event -> {
TranslateTransition tt = new TranslateTransition(Duration.millis(2000), circle);
x[0] = event.getSceneX();
y[0] = event.getSceneY();
location.setText(x[0] + ", " + y[0]);
if (x[0] != oldX[0] || y[0] != oldY[0]) {
tt.stop();
tt.setToX(event.getSceneX());
tt.setToY(event.getSceneY());
oldX[0] = x[0];
oldY[0] = y[0];
}
tt.play();
});
The location.setText(..) is just a label to see whether the coords are recognized by the program. And in fact they are: for each pixel my cursor moves it updated these numbers instantly.
However, my circle will only go to the location of my cursor when it does not move. I want the shape to follow my cursor on the go as well but it just won't.
So my problem is this: how can I make my circle follow my mouse, even when it is moving?
Use an AnimationTimer for the movement. You may also want to check Mike's Blog about a usage example.
Read The Nature of Code by Daniel Shiffman, especially the Chapter Vectors, 1.10 Interactivity with Acceleration. The webpage has a running example, easily to convert to JavaFX.
Here's the code from the book implemented in JavaFX:
Walker.java
import java.util.Random;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class Walker extends Pane {
private static Random random = new Random();
PVector location;
PVector velocity;
PVector acceleration;
float topspeed;
double width = 30;
double height = width;
double centerX = width / 2.0;
double centerY = height / 2.0;
double radius = width / 2.0;
Circle circle;
public Walker() {
location = new PVector(random.nextDouble() * width, random.nextDouble() * height, 0);
velocity = new PVector(0, 0, 0);
topspeed = 4;
circle = new Circle(radius);
circle.setCenterX(radius);
circle.setCenterY(radius);
circle.setStroke(Color.BLUE);
circle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.3));
getChildren().add(circle);
}
public void step(PVector mouse) {
PVector dir = PVector.sub(mouse, location);
dir.normalize();
dir.mult(0.5);
acceleration = dir;
velocity.add(acceleration);
velocity.limit(topspeed);
location.add(velocity);
}
public void checkBoundaries() {
if (location.x > Settings.SCENE_WIDTH) {
location.x = 0;
} else if (location.x < 0) {
location.x = Settings.SCENE_WIDTH;
}
if (location.y > Settings.SCENE_HEIGHT) {
location.y = 0;
} else if (location.y < 0) {
location.y = Settings.SCENE_HEIGHT;
}
}
public void display() {
relocate(location.x - centerX, location.y - centerY);
}
}
Main.java
import java.util.ArrayList;
import java.util.List;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
Pane playfield;
List<Walker> allWalkers = new ArrayList<>();
PVector mouse = new PVector(0,0,0);
#Override
public void start(Stage primaryStage) {
// create containers
BorderPane root = new BorderPane();
StackPane layerPane = new StackPane();
// playfield for our walkers
playfield = new Pane();
layerPane.getChildren().addAll(playfield);
root.setCenter(layerPane);
Scene scene = new Scene(root, Settings.SCENE_WIDTH, Settings.SCENE_HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
// add 1 walker
addWalker();
// capture mouse position
scene.addEventFilter(MouseEvent.ANY, e -> {
mouse.set(e.getX(), e.getY(), 0);
});
// process all walkers
AnimationTimer loop = new AnimationTimer() {
#Override
public void handle(long now) {
// move
allWalkers.forEach((walker) -> walker.step(mouse));
// check border
allWalkers.forEach(Walker::checkBoundaries);
// update in fx scene
allWalkers.forEach(Walker::display);
}
};
loop.start();
}
/**
* Add single walker to list of walkers and to the playfield
*/
private void addWalker() {
Walker walker = new Walker();
allWalkers.add(walker);
playfield.getChildren().add(walker);
}
public static void main(String[] args) {
launch(args);
}
}
Settings.java
public class Settings {
public static double SCENE_WIDTH = 800;
public static double SCENE_HEIGHT = 600;
}
PVector.java (you can get the full source from the Processing source code)
public class PVector {
public double x;
public double y;
public double z;
public PVector(double x, double y, double z) {
super();
this.x = x;
this.y = y;
this.z = z;
}
public void normalize() {
double m = mag();
if (m != 0 && m != 1) {
div(m);
}
}
public void div(double value) {
x /= value;
y /= value;
z /= value;
}
public void mult(double value) {
x *= value;
y *= value;
z *= value;
}
public void add(PVector v) {
x += v.x;
y += v.y;
z += v.z;
}
public void sub(PVector v) {
x -= v.x;
y -= v.y;
z -= v.z;
}
public void limit(float max) {
if (mag() > max) {
normalize();
mult(max);
}
}
public double mag() {
return Math.sqrt(x * x + y * y + z * z);
}
public static PVector sub(PVector v1, PVector v2) {
return sub(v1, v2, null);
}
public static 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;
}
public void set(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
}
So using the tips Roland gave me I have created this piece of code which works just the way I want to be (like I described in my question)
AnimationTimer timer = new AnimationTimer() {
#Override
public void handle(long now) {
TranslateTransition tt = new TranslateTransition(Duration.millis(250), circle);
Point mouse = MouseInfo.getPointerInfo().getLocation();
x[0] = mouse.getX();
y[0] = mouse.getY();
location.setText(x[0] + ", " + y[0]);
tt.setToX(x[0]);
tt.setToY(y[0]);
tt.play();
}
};
timer.start();
The major change is the use of the AnimationTimer instead of an event. This caused me to change the way I retrieve the location of the mouse, now I use the awt.MouseInfo to get the X and Y of my cursor.
I've written a java implementation of Craig Reynolds Boids. I recently updated each object to be represented by a .png image. Ever since I've been having the display issue in the image.
What's the best way to fix the issue?
I've tried using a Polygon but when one of my coordinates is a negative the triangle doesn't display properly.
Main Class:
public void paint(final GraphicsContext g) {
new AnimationTimer() {
#Override
public void handle(long now) {
flock.updateBoidsPostion();
g.clearRect(0, 0, width, height);
flock.drawBoids(g);
}
}.start();
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Boids Flocking Algorithm");
Group root = new Group();
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D();
root.getChildren().add(canvas);
primaryStage.setScene(new Scene(root));
primaryStage.show();
paint(gc);
}
Flock:
/**
* Paint each boid comprising the flock the canvas.
* #param g
*/
public void drawBoids(GraphicsContext g) {
for(Boid aBoid : boids) {
aBoid.draw(g);
}
}
Boid:
public void draw(GraphicsContext g) {
//coordinates for the tip of the boid
int x = (int)this.position.xPos;
int y = (int)this.position.yPos;
//Calculate a angle representing the direction of travel.
Rotate r = new Rotate(angle, x, y);
g.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy());
g.drawImage(image, x, y);
}
The problem with your code is that you rotate the GraphicsContext, not the image. Or at least you don't rotate the GraphicsContext back after you rotated it.
I was curious about the link you mentioned, i. e. the Boids Pseudocode.
Here's a quick implementation. Drag the rectangle to have the flock follow it.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
// Boids implementation in JavaFX
// Pseudo code by Conrad Parker: http://www.kfish.org/boids/pseudocode.html
public class Main extends Application {
int numBoids = 50;
double boidRadius = 10d;
double boidMinDistance = boidRadius * 2d + 5; // +5 = arbitrary value
double initialBaseVelocity = 1d;
double velocityLimit = 3d;
double movementToCenter = 0.01; // 1% towards center
List<Boid> boids;
static Random rnd = new Random();
double sceneWidth = 1024;
double sceneHeight = 768;
Pane playfield;
Rectangle rectangle;
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
playfield = new Pane();
playfield.setPrefSize(sceneWidth, sceneHeight);
Text infoText = new Text( "Drag the rectangle and have the flock follow it");
root.setTop(infoText);
root.setCenter(playfield);
Scene scene = new Scene(root, sceneWidth, sceneHeight, Color.WHITE);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
// create boids
createBoids();
// add boids to scene
playfield.getChildren().addAll(boids);
double w = 20;
double h = 20;
rectangle = new Rectangle( w, h);
rectangle.relocate(sceneWidth / 2 - w/2, sceneHeight / 4 - h/2);
playfield.getChildren().add(rectangle);
MouseGestures mg = new MouseGestures();
mg.makeDraggable(rectangle);
// animation loop
AnimationTimer loop = new AnimationTimer() {
#Override
public void handle(long now) {
boids.forEach(Boid::move);
boids.forEach(Boid::updateUI);
}
};
loop.start();
}
private void createBoids() {
boids = new ArrayList<>();
// margin from top/left/bottom/right, so we have the boids initially more in the center
double marginX = sceneWidth / 4;
double marginY = sceneHeight / 4;
for (int i = 0; i < numBoids; i++) {
// random position around the center
double x = rnd.nextDouble() * (sceneWidth - marginX * 2) + marginX;
double y = rnd.nextDouble() * (sceneHeight - marginY * 2) + marginY;
// initial random velocity depending on speed
double v = Math.random() * 4 + initialBaseVelocity;
Boid boid = new Boid(i, x, y, v);
boids.add(boid);
}
}
// Rule 1: Boids try to fly towards the centre of mass of neighbouring boids.
public Point2D rule1(Boid boid) {
Point2D pcj = new Point2D(0, 0);
for( Boid neighbor: boids) {
if( boid == neighbor)
continue;
pcj = pcj.add( neighbor.position);
}
if( boids.size() > 1) {
double div = 1d / (boids.size() - 1);
pcj = pcj.multiply( div);
}
pcj = (pcj.subtract(boid.position)).multiply( movementToCenter);
return pcj;
}
// Rule 2: Boids try to keep a small distance away from other objects (including other boids).
public Point2D rule2(Boid boid) {
Point2D c = new Point2D(0, 0);
for( Boid neighbor: boids) {
if( boid == neighbor)
continue;
double distance = (neighbor.position.subtract(boid.position)).magnitude();
if( distance < boidMinDistance) {
c = c.subtract(neighbor.position.subtract(boid.position));
}
}
return c;
}
// Rule 3: Boids try to match velocity with near boids.
public Point2D rule3(Boid boid) {
Point2D pvj = new Point2D(0, 0);
for( Boid neighbor: boids) {
if( boid == neighbor)
continue;
pvj = pvj.add( neighbor.velocity);
}
if( boids.size() > 1) {
double div = 1d / (boids.size() - 1);
pvj = pvj.multiply( div);
}
pvj = (pvj.subtract(boid.velocity)).multiply(0.125); // 0.125 = 1/8
return pvj;
}
// tend towards the rectangle
public Point2D tendToPlace( Boid boid) {
Point2D place = new Point2D( rectangle.getBoundsInParent().getMinX() + rectangle.getBoundsInParent().getWidth() / 2, rectangle.getBoundsInParent().getMinY() + rectangle.getBoundsInParent().getHeight() / 2);
return (place.subtract(boid.position)).multiply( 0.01);
}
public class Boid extends Circle {
int id;
Point2D position;
Point2D velocity;
double v;
// random color
Color color = randomColor();
public Boid(int id, double x, double y, double v) {
this.id = id;
this.v = v;
position = new Point2D( x, y);
velocity = new Point2D( v, v);
setRadius( boidRadius);
setStroke(color);
setFill(color.deriveColor(1, 1, 1, 0.2));
}
public void move() {
Point2D v1 = rule1(this);
Point2D v2 = rule2(this);
Point2D v3 = rule3(this);
Point2D v4 = tendToPlace(this);
velocity = velocity
.add(v1)
.add(v2)
.add(v3)
.add(v4)
;
limitVelocity();
position = position.add(velocity);
constrainPosition();
}
private void limitVelocity() {
double vlim = velocityLimit;
if( velocity.magnitude() > vlim) {
velocity = (velocity.multiply(1d/velocity.magnitude())).multiply( vlim);
}
}
// limit position to screen dimensions
public void constrainPosition() {
double xMin = boidRadius;
double xMax = sceneWidth - boidRadius;
double yMin = boidRadius;
double yMax = sceneHeight - boidRadius;
double x = position.getX();
double y = position.getY();
double vx = velocity.getX();
double vy = velocity.getY();
if( x < xMin) {
x = xMin;
vx = v;
}
else if( x > xMax) {
x = xMax;
vx = -v;
}
if( y < yMin) {
y = yMin;
vy = v;
}
else if( y > yMax) {
y = yMax;
vy = -v;
}
// TODO: modification would be less performance consuming => find out how to modify the vector directly or create own Poin2D class
position = new Point2D( x, y);
velocity = new Point2D( vx, vy);
}
public void updateUI() {
setCenterX(position.getX());
setCenterY(position.getY());
}
}
public static Color randomColor() {
int range = 220;
return Color.rgb((int) (rnd.nextDouble() * range), (int) (rnd.nextDouble() * range), (int) (rnd.nextDouble() * range));
}
public static class MouseGestures {
class DragContext {
double x;
double y;
}
DragContext dragContext = new DragContext();
public void makeDraggable( Node node) {
node.setOnMousePressed( onMousePressedEventHandler);
node.setOnMouseDragged( onMouseDraggedEventHandler);
node.setOnMouseReleased( onMouseReleasedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if( event.getSource() instanceof Circle) {
Circle circle = ((Circle) (event.getSource()));
dragContext.x = circle.getCenterX() - event.getSceneX();
dragContext.y = circle.getCenterY() - event.getSceneY();
} else {
Node node = ((Node) (event.getSource()));
dragContext.x = node.getTranslateX() - event.getSceneX();
dragContext.y = node.getTranslateY() - event.getSceneY();
}
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if( event.getSource() instanceof Circle) {
Circle circle = ((Circle) (event.getSource()));
circle.setCenterX( dragContext.x + event.getSceneX());
circle.setCenterY( dragContext.y + event.getSceneY());
} else {
Node node = ((Node) (event.getSource()));
node.setTranslateX( dragContext.x + event.getSceneX());
node.setTranslateY( dragContext.y + event.getSceneY());
}
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
}
};
}
public static void main(String[] args) {
launch(args);
}
}
A 3D version is just a matter of using Points3D instead of Points2D and Spheres and Boxes instead of Circles and Rectangles.
I also suggest you read the excellent book The Nature of Code by Daniel Shiffman, especially the chapter Autonomous Agents. It deals in detail with the Boids.