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.