package seng302.fxObjects; import javafx.event.EventHandler; import javafx.geometry.Point2D; import javafx.scene.CacheHint; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.input.MouseDragEvent; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.Line; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; import javafx.scene.transform.Rotate; import seng302.models.Yacht; import seng302.models.stream.StreamParser; import java.text.DateFormat; import java.text.SimpleDateFormat; /** * 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. The * boat will update it's position onscreen everytime UpdatePosition is called unless the window is * minimized in which case it attempts to store animations and apply them when the window is * maximised. */ public class BoatGroup extends Group { //Constants for drawing private static final double BOAT_HEIGHT = 15d; private static final double BOAT_WIDTH = 10d; //Variables for boat logic. private boolean isStopped = true; private double xIncrement; private double yIncrement; private long lastTimeValid = 0; private Double lastRotation = 0.0; private long framesToMove; //Graphical objects private Yacht boat; private Group lineGroup = new Group(); private Polygon boatPoly; private Wake wake; private Double distanceTravelled = 0.0; private Point2D lastPoint; private boolean destinationSet; private Color textColor = Color.RED; private BoatAnnotations boatAnnotations; private Boolean isSelected = true; //All boats are initalised as selected /** * 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(Yacht boat, Color color) { this.boat = boat; initChildren(color); this.textColor = 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(Yacht boat, Color color, double... points) { this.boat = boat; initChildren(color, points); } // /** // * Return a text object with caching and a color applied // * // * @param defaultText The default text to display // * @param fill The text fill color // * @return The text object // */ // private Text getTextObject(String defaultText, Color fill) { // Text text = new Text(defaultText); // // text.setFill(fill); // text.setCacheHint(CacheHint.SPEED); // text.setCache(true); // // return text; // } /** * 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) { textColor = color; destinationSet = false; boatPoly = new Polygon(points); boatPoly.setFill(color); boatPoly.setOnMouseEntered(event -> { boatPoly.setFill(Color.FLORALWHITE); boatPoly.setStroke(Color.RED); }); boatPoly.setOnMouseExited(event -> { boatPoly.setFill(color); boatPoly.setStroke(Color.BLACK); }); boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected)); boatPoly.setCache(true); boatPoly.setCacheHint(CacheHint.SPEED); // teamNameObject = getTextObject(boat.getShortName(), textColor); // velocityObject = getTextObject(boat.getVelocity().toString(), textColor); // // teamNameObject.setX(TEAMNAME_X_OFFSET); // teamNameObject.setY(TEAMNAME_Y_OFFSET); // teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); // // velocityObject.setX(VELOCITY_X_OFFSET); // velocityObject.setY(VELOCITY_Y_OFFSET); // velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); //// //// updateLastMarkRoundingTime(); //// updateTimeTillNextMark(); // // if (estTimeToNextMarkObject != null) { // estTimeToNextMarkObject.setX(ESTTIMETONEXTMARK_X_OFFSET); // estTimeToNextMarkObject.setY(ESTTIMETONEXTMARK_Y_OFFSET); // estTimeToNextMarkObject // .relocate(estTimeToNextMarkObject.getX(), estTimeToNextMarkObject.getY()); // } // // if (legTimeObject != null) { // legTimeObject.setX(LEGTIME_X_OFFSET); // legTimeObject.setY(LEGTIME_Y_OFFSET); // legTimeObject.relocate(legTimeObject.getX(), legTimeObject.getY()); // // } boatAnnotations = new BoatAnnotations(boat, color); wake = new Wake(0, -BOAT_HEIGHT); super.getChildren().addAll(boatPoly, boatAnnotations); } /** * 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 / 2, 0.0, -BOAT_HEIGHT / 2, BOAT_WIDTH / 2, BOAT_HEIGHT / 2); } /** * Moves the boat and its children annotations from its current coordinates by specified * amounts. * * @param dx The amount to move the X coordinate by * @param dy The amount to move the Y coordinate by */ private void moveGroupBy(double dx, double dy) { boatPoly.setLayoutX(boatPoly.getLayoutX() + dx); boatPoly.setLayoutY(boatPoly.getLayoutY() + dy); boatAnnotations.setLayoutX(boatAnnotations.getLayoutX() + dx); boatAnnotations.setLayoutY(boatAnnotations.getLayoutY() + dy); // for (Node node : boatAnnotations.getkiddies()) { // node.setLayoutX(node.getLayoutX() + dx); // node.setLayoutY(node.getLayoutY() + dy); // } wake.setLayoutX(wake.getLayoutX() + dx); wake.setLayoutY(wake.getLayoutY() + dy); } /** * 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 */ private void moveTo(double x, double y, double rotation) { rotateTo(rotation); boatPoly.setLayoutX(x); boatPoly.setLayoutY(y); boatAnnotations.setLayoutX(x); boatAnnotations.setLayoutY(y); // int i = 0; // for (Node n : boatAnnotations.getkiddies()) { // n.setLayoutX(x + 10 + i); // n.setLayoutY(y + 10 + i); // i += 10; // } wake.setLayoutX(x); wake.setLayoutY(y); wake.rotate(rotation); } private void rotateTo(double rotation) { boatPoly.getTransforms().setAll(new Rotate(rotation)); } // /** // * Updates the time until next mark label, will create a label if one doesn't exist // */ // private void updateTimeTillNextMark() { // if (estTimeToNextMarkObject == null) { // estTimeToNextMarkObject = getTextObject("Next mark: -", textColor); // } // if (boat.getEstimateTimeAtNextMark() != null) { // DateFormat format = new SimpleDateFormat("mm:ss"); // String timeToNextMark = format // .format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong()); // estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark); // } else { // estTimeToNextMarkObject.setText("Next mark: -"); // } // } // /** // * Updates the time since last mark rounding, will create a label if one doesn't exist // */ // private void updateLastMarkRoundingTime() { // if (legTimeObject == null) { // legTimeObject = getTextObject("Last mark: -", textColor); // } // // if (boat.getMarkRoundingTime() != null) { // DateFormat format = new SimpleDateFormat("mm:ss"); // String elapsedTime = format // .format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime()); // legTimeObject.setText("Last mark: " + elapsedTime); // } else { // legTimeObject.setText("Last mark: -"); // // } // } public void move() { double dx = xIncrement * framesToMove; double dy = yIncrement * framesToMove; distanceTravelled += Math.abs(dx) + Math.abs(dy); moveGroupBy(xIncrement, yIncrement); framesToMove = framesToMove - 1; if (framesToMove <= 0) { isStopped = true; } if (distanceTravelled > 70) { distanceTravelled = 0d; if (lastPoint != null) { Line l = new Line( lastPoint.getX(), lastPoint.getY(), boatPoly.getLayoutX(), boatPoly.getLayoutY() ); l.getStrokeDashArray().setAll(3d, 7d); l.setStroke(boat.getColour()); l.setCache(true); l.setCacheHint(CacheHint.SPEED); lineGroup.getChildren().add(l); } if (destinationSet) { lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); } } wake.updatePosition(1000 / 60); } /** * Calculates the rotational velocity required to reach the rotationalGoal from the * currentRotation. */ private Double calculateRotationalVelocity(Double rotationalGoal) { Double rotationalVelocity = 0.0; if (Math.abs(rotationalGoal - lastRotation) > 180) { if (rotationalGoal - lastRotation >= 0.0) { rotationalVelocity = ((rotationalGoal - lastRotation) - 360) / 200; } else { rotationalVelocity = (360 + (rotationalGoal - lastRotation)) / 200; } } else { rotationalVelocity = (rotationalGoal - lastRotation) / 200; } //Sometimes the rotation is too large to be realistic. In that case just do it instantly. if (Math.abs(rotationalVelocity) > 1) { rotationalVelocity = 0.0; } return rotationalVelocity; } /** * Sets the destination of the boat and the headng it should have once it reaches * * @param newXValue The X co-ordinate the boat needs to move to. * @param newYValue The Y co-ordinate the boat needs to move to. * @param rotation Rotation to move graphics to. * @param timeValid the time the position values are valid for */ public void setDestination(double newXValue, double newYValue, double rotation, double groundSpeed, long timeValid, double frameRate, long id) { if (lastTimeValid == 0) { lastTimeValid = timeValid - 200; moveTo(newXValue, newYValue, rotation); } framesToMove = Math.round((frameRate / (1000.0f / (timeValid - lastTimeValid)))); double dx = newXValue - boatPoly.getLayoutX(); double dy = newYValue - boatPoly.getLayoutY(); xIncrement = dx / framesToMove; yIncrement = dy / framesToMove; destinationSet = true; Double rotationalVelocity = calculateRotationalVelocity(rotation); // updateTimeTillNextMark(); // updateLastMarkRoundingTime(); if (Math.abs(rotationalVelocity) > 0.075) { rotationalVelocity = 0.0; wake.rotate(rotation); } rotateTo(rotation); boat.setVelocity(groundSpeed); wake.setRotationalVelocity(rotationalVelocity, groundSpeed); lastTimeValid = timeValid; isStopped = false; lastRotation = rotation; boatAnnotations.update(); } public void setIsSelected(Boolean isSelected) { this.isSelected = isSelected; setTeamNameObjectVisible(isSelected); setVelocityObjectVisible(isSelected); setLineGroupVisible(isSelected); setWakeVisible(isSelected); setEstTimeToNextMarkObjectVisible(isSelected); setLegTimeObjectVisible(isSelected); boatAnnotations.setVisible(isSelected); } public void setTeamNameObjectVisible(Boolean visible) { boatAnnotations.setTeamNameObjectVisible(visible); } public void setVelocityObjectVisible(Boolean visible) { boatAnnotations.setVelocityObjectVisible(visible); } public void setEstTimeToNextMarkObjectVisible(Boolean visible) { boatAnnotations.setEstTimeToNextMarkObjectVisible(visible); } public void setLegTimeObjectVisible(Boolean visible) { boatAnnotations.setLegTimeObjectVisible(visible); } public void setLineGroupVisible(Boolean visible) { lineGroup.setVisible(visible); } public void setWakeVisible(Boolean visible) { wake.setVisible(visible); } public Yacht getBoat() { return boat; } /** * Returns all raceIds associated with this group. For BoatGroups the ID's are for the boat. * * @return An array containing all ID's associated with this RaceObject. */ public long getRaceId() { return boat.getSourceID(); } /** * Due to javaFX limitations annotations associated with a boat that you want to appear below * all boats in the Z-axis need to be pulled out of the BoatGroup and added to the parent group * of the BoatGroups. This function returns these annotations as a group. * * @return A group containing low priority annotations. */ public Group getLowPriorityAnnotations() { Group group = new Group(); group.getChildren().addAll(wake, lineGroup); return group; } public boolean isStopped() { return isStopped; } @Override public String toString() { return boat.toString(); } }