diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 26c94e10..0f25977a 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -10,7 +10,9 @@ import javafx.scene.transform.Rotate; import seng302.models.parsers.StreamParser; /** - * Created by CJIRWIN on 25/04/2017. + * BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 dimensional boat. + * It contains a single polygon for the boat, a group of lines to show it's path, a wake object and two text labels to + * annotate the boat teams name and the boats velocity. */ public class BoatGroup extends RaceObject{ @@ -18,36 +20,50 @@ public class BoatGroup extends RaceObject{ private static final double TEAMNAME_Y_OFFSET = -15d; private static final double VELOCITY_X_OFFSET = 10d; private static final double VELOCITY_Y_OFFSET = -5d; - private static final double VELOCITY_WAKE_RATIO = 2d; private static final double BOAT_HEIGHT = 15d; private static final double BOAT_WIDTH = 10d; private static final int LINE_INTERVAL = 180; private static double expectedUpdateInterval = 200; - private static int WAKE_FRAME_INTERVAL = 30; private double framesForNewLine = 0; private boolean destinationSet; private Point2D lastPoint; private int wakeGenerationDelay; private Boat boat; - private int wakeCounter = WAKE_FRAME_INTERVAL; private Group lineGroup = new Group(); - private Group wakeGroup = new Group(); private Polygon boatPoly; private Text teamNameObject; private Text velocityObject; private Wake wake; + /** + * Creates a BoatGroup with the default triangular boat polygon. + * @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which + * BoatGroup to update. + * @param color The colour of the boat polygon and the trailing line. + */ public BoatGroup (Boat boat, Color color){ this.boat = boat; initChildren(color); } + /** + * Creates a BoatGroup with the boat being the default polygon. The head of the boat should be at point (0,0). + * @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which + * BoatGroup to update. + * @param color The colour of the boat polygon and the trailing line. + * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat polygon. + */ public BoatGroup (Boat boat, Color color, double... points) { initChildren(color, points); } + /** + * Creates the javafx objects that will be the in the group by default. + * @param color The colour of the boat polygon and the trailing line. + * @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat polygon. + */ private void initChildren (Color color, double... points) { boatPoly = new Polygon(points); boatPoly.setFill(color); @@ -64,16 +80,20 @@ public class BoatGroup extends RaceObject{ velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); destinationSet = false; - wake = new Wake(0, 0); + wake = new Wake(0, -BOAT_HEIGHT); wakeGenerationDelay = wake.numWakes; super.getChildren().addAll(teamNameObject, velocityObject, boatPoly); } + /** + * Creates the javafx objects that will be the in the group by default. + * @param color The colour of the boat polygon and the trailing line. + */ private void initChildren (Color color) { - initChildren(color, - -BOAT_WIDTH / 2, BOAT_HEIGHT, - 0.0, 0.0, - BOAT_WIDTH / 2, BOAT_HEIGHT); + initChildren(color, + -BOAT_WIDTH / 2, BOAT_HEIGHT / 2, + 0.0, -BOAT_HEIGHT / 2, + BOAT_WIDTH / 2, BOAT_HEIGHT / 2); } /** @@ -97,12 +117,18 @@ public class BoatGroup extends RaceObject{ * Moves the boat and its children annotations to coordinates specified * @param x The X coordinate to move the boat to * @param y The Y coordinate to move the boat to + * @param rotation The heading in degrees from north the boat should rotate to. */ public void moveTo (double x, double y, double rotation) { rotateTo(rotation); moveTo(x, y); } + /** + * Moves the boat and its children annotations to coordinates specified + * @param x The X coordinate to move the boat to + * @param y The Y coordinate to move the boat to + */ public void moveTo (double x, double y) { boatPoly.setLayoutX(x); boatPoly.setLayoutY(y); @@ -115,10 +141,15 @@ public class BoatGroup extends RaceObject{ wake.rotate(currentRotation); } + /** + * Updates the position of all graphics in the BoatGroup based off of the given time interval. + * @param timeInterval The interval, in microseconds, the boat should update it's position based on. + */ public void updatePosition (long timeInterval) { + //Calculate the movement of the boat. double dx = pixelVelocityX * timeInterval; double dy = pixelVelocityY * timeInterval; - double rotation = 0d; + double rotation = rotationalVelocity * timeInterval; moveGroupBy(dx, dy, rotation); @@ -138,7 +169,7 @@ public class BoatGroup extends RaceObject{ if (destinationSet){ lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); } - if (lineGroup.getChildren().size() > 100) + if (lineGroup.getChildren().size() > 60) lineGroup.getChildren().remove(0); } wake.updatePosition(timeInterval); @@ -148,11 +179,25 @@ public class BoatGroup extends RaceObject{ destinationSet = true; boat.setVelocity(StreamParser.boatSpeeds.get((long)boat.getId())); if (hasRaceId(raceIds)) { - this.pixelVelocityX = (newXValue - boatPoly.getLayoutX()) / expectedUpdateInterval; - this.pixelVelocityY = (newYValue - boatPoly.getLayoutY()) / expectedUpdateInterval; - this.rotationalGoal = rotation; + double dx = newXValue - boatPoly.getLayoutX(); + if ((dx > 0 && pixelVelocityX < 0) || (dx < 0 && pixelVelocityX > 0)) { + pixelVelocityX = 0; + } else { + pixelVelocityX = dx / expectedUpdateInterval; + } + double dy = newYValue - boatPoly.getLayoutY(); + if ((dy > 0 && pixelVelocityY < 0) || (dy < 0 && pixelVelocityY > 0)) { + pixelVelocityY = 0; + } else { + pixelVelocityY = dy / expectedUpdateInterval; + } +// this.pixelVelocityX = (newXValue - boatPoly.getLayoutX()) / expectedUpdateInterval; +// this.pixelVelocityY = (newYValue - boatPoly.getLayoutY()) / expectedUpdateInterval; + rotationalGoal = rotation; calculateRotationalVelocity(); - rotateTo(rotation); + if (Math.abs(rotationalVelocity) > 0.00003) + rotationalVelocity = 0; + if (wakeGenerationDelay > 0) { wake.rotate(rotationalGoal); wakeGenerationDelay--; @@ -177,29 +222,12 @@ public class BoatGroup extends RaceObject{ } } - void resizeWake(){ - velocityObject.setText(String.valueOf(boat.getVelocity())); - super.getChildren().remove(wakePoly); - wakePoly = new Polygon( - 5.0,0.0, - 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, - 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO - ); - wakePoly.setLayoutX(boatPoly.getLayoutX()); - wakePoly.setLayoutY(boatPoly.getLayoutY()); - wakePoly.setFill(Color.DARKBLUE); - super.getChildren().add(wakePoly); - - } - public void rotateTo (double rotation) { currentRotation = rotation; boatPoly.getTransforms().clear(); boatPoly.getTransforms().add(new Rotate(rotation)); } - - public void forceRotation () { rotateTo (rotationalGoal); wake.rotate(rotationalGoal); diff --git a/src/main/java/seng302/models/Wake.java b/src/main/java/seng302/models/Wake.java index 09b037ba..0300cd8b 100644 --- a/src/main/java/seng302/models/Wake.java +++ b/src/main/java/seng302/models/Wake.java @@ -1,51 +1,97 @@ package seng302.models; +import javafx.scene.Group; import javafx.scene.paint.Color; import javafx.scene.shape.Arc; import javafx.scene.shape.ArcType; import javafx.scene.transform.Rotate; -import javafx.scene.transform.Translate; /** - * Created by CJIRWIN on 27/04/2017. + * By default wake is a group containing 5 arcs. Each arc starts from the same point. Each arc is larger and more + * transparent than the last. On calling updatePositions() arcs rotate at velocities given by setRotationalVelocity(). + * The larger and more transparent an arc is the longer the delay before it rotates at the latest velocity. It is + * assumed that rotationalVelocities() are set regularly as wakes do not stop rotating and an array of velocities needs + * to be populated for the class to work as expected. */ -class Wake extends Arc { +class Wake extends Group { - private static int VELOCITY_SCALE_FACTOR = 3; - private static int MAX_LIFESPAN = 210; - private static double LIFESPAN_PER_FRAME = 1.0 / MAX_LIFESPAN; - //private static double LENGTH_PER_FRAME = 120 / MAX_LIFESPAN; - private static double LENGTH_PER_FRAME = 0.25; + final int numWakes = 5; + private double[] velocities = new double[13]; + private Arc[] arcs = new Arc[numWakes]; + private double[] rotations = new double[numWakes]; + private int[] velocityIndices = new int[numWakes]; + private double sum = 0; - private double velocityX; - private double velocityY; - private double opacity; - private int lifespan = MAX_LIFESPAN; - - Wake (double startingX, double startingY, double velocityX, double velocityY, double rotation) { - super(startingX, startingY, 20, 30, 180, 0); - //super.setFill(Color.BLUE); - super.setStroke(Color.DEEPSKYBLUE); - super.setType(ArcType.OPEN); - super.setFill(new Color(0, 0, 0 ,0)); - super.setStrokeWidth(2.0); - super.getTransforms().add(new Rotate(rotation - 270, startingX + 20, startingY + 20)); -// this.velocityX = -velocityX; -// this.velocityY = -velocityY; - this.velocityX = 0; - this.velocityY = 0; + /** + * Create a wake at the given location. + * @param startingX x location where the tip of wake arcs will be. + * @param startingY y location where the tip of wake arcs will be. + */ + Wake(double startingX, double startingY) { + super.setLayoutX(startingX); + super.setLayoutY(startingY); + Arc arc; + for (int i = 0; i < numWakes; i++) { + //Default triangle is -110 deg out of phase with a default wake and has angle of 40 deg. + arc = new Arc(0,0,0,0,-110,40); + //Opacity increases from 0.5 -> 0 evenly over the 5 wake arcs. + arc.setFill(new Color(0.18, 0.7, 1.0, 0.50 + -0.1 * i)); + arc.setType(ArcType.ROUND); + arcs[i] = arc; + } + super.getChildren().addAll(arcs); } - boolean updatePosition (double timeInterval) { - lifespan--; - //super.setOpacity(LIFESPAN_PER_FRAME * lifespan * super.getOpacity()); - //opacity = LIFESPAN_PER_FRAME * lifespan * opacity; - //super.setFill(new Color(0.0f, 0.0f, 1.0f, opacity)); - super.setLayoutX(super.getLayoutX() + velocityX * timeInterval); - super.setLayoutY(super.getLayoutY() + velocityY * timeInterval); - super.setStartAngle(super.getStartAngle() - LENGTH_PER_FRAME); - super.setLength(super.getLength() + LENGTH_PER_FRAME * 2); - return lifespan < 0; + /** + * Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses + * the latest given velocity. + * @param rotationalVelocity The rotationalVelocity the wake should move at. + */ + void setRotationalVelocity (double rotationalVelocity, double rotationGoal, double velocityX, double velocityY) { + sum -= Math.abs(velocities[velocityIndices[0]]); + sum += Math.abs(rotationalVelocity); + if (sum < 0.00003) + rotate (rotationGoal); //In relatively straight segments the wake snaps to match the boats current position. + //This stops the wake from eventually becoming out of sync with the boat. + + //Update the index of the array of recent velocities that each wake uses. Each wake is 3 velocities behind the + //next smallest wake. + velocityIndices[0] = (13 + (velocityIndices[0] - 1) % 13) % 13; + velocities[velocityIndices[0]] = rotationalVelocity; + for (int i = 1; i < numWakes; i++) + velocityIndices[i] = (velocityIndices[0] + 3 * i) % 13; + + //Scale wakes based on velocity. Assumes boats are always moving at a decent pace. + double scaleFactor = Math.abs(Math.log10(Math.abs(velocityX) + Math.abs(velocityY))); + double baseRad = 25; + for (Arc arc :arcs) { + double rad = Math.min(baseRad + 5 * scaleFactor, baseRad + 15); + arc.setRadiusX(rad); + arc.setRadiusY(rad); + baseRad += 10; + } + } + + /** + * Arcs rotate based on the distance they would have travelled over the supplied time interval. + * @param timeInterval the time interval, in microseconds, that the wake should move. + */ + void updatePosition (long timeInterval) { + for (int i = 0; i < numWakes; i++) { + rotations[i] = rotations[i] + velocities[velocityIndices[i]] * timeInterval; + arcs[i].getTransforms().setAll(new Rotate(rotations[i])); + } + } + + /** + * Rotate all wakes to the given rotation. + * @param rotation the from north angle in degrees to rotate to. + */ + void rotate (double rotation) { + for (int i = 0; i < arcs.length; i++) { + rotations[i] = rotation; + arcs[i].getTransforms().setAll(new Rotate(rotation)); + } } }