I'm sorry, but I continue not understanding. My problem is I know nothing about physics but my teacher assigned to me this project.
private void shoot() {
Group group = new Group();
double angle = cannon.getRotate();
double speed = slider.getValue();
double x = cannon.getLayoutX();
double y = cannon.getLayoutY();
double v0X = Math.cos(angle)*speed;
double voY = Math.sin(angle)*speed;
Circle c = new Circle(x, y, 8, Color.BLACK);
/*t is the time, but I don't know its
value or has it the same value of the KeyFrame duration? */
double x2 = x + voX*t;
double y2 = y + v0Y * t - 0.5 * gravity * t * t;
Line l = new Line(x, y, x2, y2);
l.setStroke(Color.BLACK);
group.getChildren().addAll(c, l);
final Timeline timeline = new Timeline();
KeyValue xKV = new KeyValue(c.centerXProperty(), x2);
KeyValue yKV = new KeyValue(c.centerYProperty(), y2 , new Interpolator() {
#Override
//Maybe I need I splite, not a curve (?)
protected double curve(double t) {
//thisshould be trajectory's formula
return Math.tan(angle) * x*-(gravity/(2*speed*Math.cos(angle)))*x*x;
}
});
KeyFrame xKF = new KeyFrame(Duration.millis(2000), xKV);
KeyFrame yKF = new KeyFrame(Duration.millis(2000), yKV);
timeline.getKeyFrames().addAll(xKF, yKF);
timeline.play();
}
I'm at a standstill. Please, help meeee
In a KeyValue, the first parameter should be a WritableValue, e.g. circle.centerXProperty(), which represents the initial coordinate, say x. The second parameter should be a type compatible value, in this case the x coordinate toward which the projectile should move. As the timeline plays, the WritableValue will be updated accordingly. Add a second KeyValue to drive the y coordinate.
In the first example seen here, three instances of KeyValue move a figure from it's initial position to its destination position, which is size units away along each coordinate axis. In this related example, a figure moves form point p1 to p2.
In the example below, a Circle moves parallel to the x axis between 100 and 500. At the same time, that same Circle moves parallel to the y axis between 300 and 100 following the curve() defined by the parabola y = –4(x – ½)2 + 1, which has vertex (½, 1) and x intercepts at 0 and 1. This implementation of curve() models a parabolic path on a unit square, as required by the curve() API. You can change the angle of elevation by changing the ratio of height to width in the keys frames, e.g.
KeyValue xKV = new KeyValue(c.centerXProperty(), 200);
KeyValue yKV = new KeyValue(c.centerYProperty(), 0, new Interpolator() {…});
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* #see https://stackoverflow.com/a/38031826/230513
*/
public class Test extends Application {
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Test");
Group group = new Group();
Scene scene = new Scene(group, 600, 350);
scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
Circle c = new Circle(100, 300, 16, Color.AQUA);
Line l = new Line(100, 300, 500, 300);
l.setStroke(Color.AQUA);
group.getChildren().addAll(c, l);
final Timeline timeline = new Timeline();
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(false);
KeyValue xKV = new KeyValue(c.centerXProperty(), 500);
KeyValue yKV = new KeyValue(c.centerYProperty(), 100, new Interpolator() {
#Override
protected double curve(double t) {
return -4 * (t - .5) * (t - .5) + 1;
}
});
KeyFrame xKF = new KeyFrame(Duration.millis(2000), xKV);
KeyFrame yKF = new KeyFrame(Duration.millis(2000), yKV);
timeline.getKeyFrames().addAll(xKF, yKF);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
Related
I'm trying the create a 3D subscene with objects being labelled using Label objects in a 2D overlay. I've seen similar questions to mine on this subject, and they all point to using the Node.localToScene method on the node to be labelled in the 3D space. But this doesn't seem to work for my case. I've taken example code from the FXyz FloatingLabels example here:
FloatingLabels.java
The Label objects need to have their positions updated as the 3D scene in modified, which I've done but when I print out the coordinates returned by the Node.localToScene method, they're much too large to be within the application scene, and so the labels are never visible in the scene. I've written an example program that illustrates the issue, set up very similarly to the FXyz sample code but I've created an extra SubScene object to hold the 2D and 3D SubScene objects in order to plant them into a larger application window with slider controls. The 3D scene uses a perspective camera and shows a large sphere with coloured spheres along the x/y/z axes, and some extra little nubs on the surface for reference:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SubScene;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.AmbientLight;
import javafx.scene.PointLight;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.Priority;
import javafx.scene.shape.Sphere;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.geometry.Point3D;
import java.util.Map;
import java.util.HashMap;
public class LabelTest extends Application {
private Map<Node, Label> nodeToLabelMap;
#Override
public void start (Stage stage) {
// Create main scene graph
var objects3d = new Group();
Rotate xRotate = new Rotate (0, 0, 0, 0, Rotate.X_AXIS);
Rotate yRotate = new Rotate (0, 0, 0, 0, Rotate.Y_AXIS);
objects3d.getTransforms().addAll (
xRotate,
yRotate
);
var root3d = new Group();
root3d.getChildren().add (objects3d);
var camera = new PerspectiveCamera (true);
camera.setTranslateZ (-25);
var scene3d = new SubScene (root3d, 500, 500, true, SceneAntialiasing.BALANCED);
scene3d.setFill (Color.rgb (20, 20, 80));
scene3d.setCamera (camera);
var sceneRoot = new Group (scene3d);
var objects2d = new Group();
sceneRoot.getChildren().add (objects2d);
var viewScene = new SubScene (sceneRoot, 500, 500);
scene3d.widthProperty().bind (viewScene.widthProperty());
scene3d.heightProperty().bind (viewScene.heightProperty());
// Add lights and objects
var ambient = new AmbientLight (Color.color (0.7, 0.7, 0.7));
var point = new PointLight (Color.color (0.3, 0.3, 0.3));
point.setTranslateX (-25);
point.setTranslateY (-25);
point.setTranslateZ (-50);
root3d.getChildren().addAll (ambient, point);
var globe = new Sphere (5);
globe.setMaterial (new PhongMaterial (Color.color (0.3, 0.3, 0.3)));
var xSphere = new Sphere (0.5);
xSphere.setMaterial (new PhongMaterial (Color.RED));
xSphere.setTranslateX (5);
var ySphere = new Sphere (0.5);
ySphere.setMaterial (new PhongMaterial (Color.GREEN));
ySphere.setTranslateY (5);
var zSphere = new Sphere (0.5);
zSphere.setMaterial (new PhongMaterial (Color.BLUE));
zSphere.setTranslateZ (5);
objects3d.getChildren().addAll (globe, xSphere, ySphere, zSphere);
var nubMaterial = new PhongMaterial (Color.color (0.2, 0.2, 0.2));
for (int i = 0; i < 200; i++) {
var nub = new Sphere (0.125);
nub.setMaterial (nubMaterial);
var phi = 2*Math.PI*Math.random();
var theta = Math.acos (2*Math.random() - 1);
var z = -5 * Math.sin (theta) * Math.cos (phi);
var x = 5 * Math.sin (theta) * Math.sin (phi);
var y = -5 * Math.cos (theta);
nub.setTranslateX (x);
nub.setTranslateY (y);
nub.setTranslateZ (z);
objects3d.getChildren().add (nub);
} // for
// Add labels
var xLabel = new Label ("X axis");
xLabel.setTextFill (Color.RED);
var yLabel = new Label ("Y axis");
yLabel.setTextFill (Color.GREEN);
var zLabel = new Label ("Z axis");
zLabel.setTextFill (Color.BLUE);
objects2d.getChildren().addAll (xLabel, yLabel, zLabel);
nodeToLabelMap = new HashMap<>();
nodeToLabelMap.put (xSphere, xLabel);
nodeToLabelMap.put (ySphere, yLabel);
nodeToLabelMap.put (zSphere, zLabel);
xRotate.angleProperty().addListener ((obs, oldVal, newVal) -> updateLabels());
yRotate.angleProperty().addListener ((obs, oldVal, newVal) -> updateLabels());
camera.translateZProperty().addListener ((obs, oldVal, newVal) -> updateLabels());
Platform.runLater (() -> updateLabels());
// Create main pane
var gridPane = new GridPane();
var stackPane = new StackPane (viewScene);
viewScene.heightProperty().bind (stackPane.heightProperty());
viewScene.widthProperty().bind (stackPane.widthProperty());
viewScene.setManaged (false);
gridPane.add (stackPane, 0, 0);
gridPane.setVgrow (stackPane, Priority.ALWAYS);
gridPane.setHgrow (stackPane, Priority.ALWAYS);
// Add controls
var xSlider = new Slider (-90, 90, 0);
xRotate.angleProperty().bind (xSlider.valueProperty());
var ySlider = new Slider (-180, 180, 0);
yRotate.angleProperty().bind (ySlider.valueProperty());
var zSlider = new Slider (-60, -25, -25);
camera.translateZProperty().bind (zSlider.valueProperty());
ToolBar toolbar = new ToolBar (
new Label ("X rotation:"),
xSlider,
new Label ("Y rotation:"),
ySlider,
new Label ("Z position:"),
zSlider
);
gridPane.add (toolbar, 0, 1);
// Start the show
stage.setTitle ("Label Test");
stage.setScene (new Scene (gridPane, 800, 600));
stage.show();
} // start
private void updateLabels () {
nodeToLabelMap.forEach ((node, label) -> {
var coord = node.localToScene (Point3D.ZERO, true);
System.out.println ("label = " + label.getText() + ", coord = " + coord);
label.getTransforms().setAll (new Translate(coord.getX(), coord.getY()));
});
} // updateLabels
public static void main (String[] args) {
launch (args);
} // main
} // LabelTest class
You can compile and run the LabelTest.java program using this script (I'm using JavaFX 14 and JDK 14.0.2 on a Mac):
#!/bin/sh
set -x
export PATH_TO_FX=javafx-sdk-14/lib
javac --module-path $PATH_TO_FX --add-modules javafx.controls LabelTest.java
if [ $? -ne 0 ] ; then
exit 1
fi
java -cp . --module-path $PATH_TO_FX --add-modules javafx.controls LabelTest
My test program output contains very large label coordinates that don't represent the position of the coloured axis spheres, for example:
label = Y axis, coord = Point3D [x = 17448.00808897467, y = 21535.846392310217, z = 0.0]
label = X axis, coord = Point3D [x = 26530.33870777918, y = 12453.515773505665, z = 0.0]
label = Z axis, coord = Point3D [x = 17448.008088974653, y = 12453.515773505665, z = 0.0]
My display looks like this:
where as it should look more like the example from FXyz with labels next to the axis spheres:
If you follow what has been done in the link you have posted you'll make it work.
For starters, there is one subScene, not two.
So I've removed these lines:
- var viewScene = new SubScene (new Group (scene3d), 500, 500);
- scene3d.widthProperty().bind (viewScene.widthProperty());
- scene3d.heightProperty().bind (viewScene.heightProperty());
and replaced these:
- var stackPane = new StackPane (viewScene);
- viewScene.heightProperty().bind (stackPane.heightProperty());
- viewScene.widthProperty().bind (stackPane.widthProperty());
- viewScene.setManaged (false);
+ var stackPane = new StackPane (sceneRoot);
+ scene3d.heightProperty().bind (stackPane.heightProperty());
+ scene3d.widthProperty().bind (stackPane.widthProperty());
That works fine now for me:
label = Z axis, coord = Point3D [x = 613.2085772621016, y = 286.33580935946725, z = 0.0]
label = X axis, coord = Point3D [x = 401.67010722785966, y = 219.90328164976754, z = 0.0]
label = Y axis, coord = Point3D [x = 400.0, y = 503.57735384935296, z = 0.0]
I need to implement sort of a pathfinding algorithm, the context is the following:
I have a starting Point2D, and and objective (a Circle).
I draw a line between the starting point and the circle center.
I try to calculate a path that does not cross any other circles.
(The blue square is my object I want to move (at starting point)) and the red circle is my objective).
What I wanted to do first was to do something like this:
But the code I have seems to be buggy as sometimes, I've got negatives intersection coordonates (black points).
Is there any other way to solve this problem ? Am I seeing the problem from a correct point of view ? There is also a problem as I'm iterating over the circles to determines which intersects or not, but if the line intersect 2 or more circles, the order of which it intersect planets is different from the order I see the points on screen.
My goal is to create a PathTransition between starting point and objective following the correct path (no intersection).
I've not mentioned it, but the container is a Pane.
EDIT:
public static Point2D getMidPoint(Point2D p1, Point2D p2) {
return new Point2D((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2);
}
public static Circle createCircleFromPoint2D(Point2D p) {
return new Circle(p.getX(), p.getY(), 5);
}
public static Point2D createPoint2D(double x, double y) {
return new Point2D(x, y);
}
public static Pair<Point2D, Point2D> translate(int distance, Point2D p1, Point2D p2, double reference, double startX) {
double pente = (p2.getY() - p1.getY()) / (p2.getX() - p1.getX());
double newX1 = p1.getX() + (startX < reference ? -1 : 1) * (Math.sqrt(((distance*distance) / (1 + (pente*pente)))));
double newX2 = p2.getX() + (startX > reference ? -1 : 1) * (Math.sqrt(((distance*distance) / (1 + (pente*pente)))));
double newY1 = pente * (newX1 - p1.getX()) + p1.getY();
double newY2 = pente * (newX2 - p2.getX()) + p2.getY();
return new Pair<>(new Point2D(newX1, newY1), new Point2D(newX2, newY2));
}
public void start(Stage primaryStage) throws Exception{
Pane pane = new Pane();
Circle objective = new Circle(800, 250, 25);
Circle circle2 = new Circle(500, 250, 125);
Circle circle3 = new Circle(240, 400, 75);
Circle circle4 = new Circle(700, 500, 150, Color.VIOLET);
Circle circle5 = new Circle(1150, 300, 115, Color.ORANGE);
Rectangle myObject = new Rectangle(175, 175, 15, 15);
objective.setFill(Color.RED);
circle2.setFill(Color.BLUE);
circle3.setFill(Color.GREEN);
myObject.setFill(Color.BLUE);
ArrayList<Circle> circles = new ArrayList<>();
circles.add(objective);
circles.add(circle2);
circles.add(circle3);
circles.add(circle4);
circles.add(circle5);
Line straightLine = new Line();
pane.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
myObject.setX(event.getX());
myObject.setY(event.getY());
// My starting coordinates (at mouse position)
double fromX = myObject.getX();
double fromY = myObject.getY();
// Where I want to go
double toX = objective.getCenterX();
double toY = objective.getCenterY();
// Line style
straightLine.setStartX(event.getX());
straightLine.setStartY(event.getY());
straightLine.setEndX(toX);
straightLine.setEndY(toY);
straightLine.setStrokeWidth(2);
straightLine.setStroke(Color.GRAY.deriveColor(0, 1, 1, 0.5));
straightLine.setStrokeLineCap(StrokeLineCap.BUTT);
straightLine.getStrokeDashArray().setAll(10.0, 5.0);
straightLine.setMouseTransparent(true);
// Coordinates to Point2D
Point2D from = new Point2D(fromX, fromY);
Point2D to = new Point2D(toX, toY);
Path path = new Path();
path.getElements().add(new MoveTo(fromX, fromY));
for (Circle c : circles) {
if (straightLine.intersects(c.getLayoutBounds())) {
// I don't want to do anything if I'm intersecting the objective (for now)
if (c == objective)
continue;
Shape s = Shape.intersect(straightLine, c);
double xmin = s.getBoundsInLocal().getMinX();
double ymin = s.getBoundsInLocal().getMinY();
double xmax = s.getBoundsInLocal().getMaxX();
double ymax = s.getBoundsInLocal().getMaxY();
Point2D intersectionPt1 = createPoint2D((fromX < objective.getCenterX()) ? xmin : xmax , (fromY < objective.getCenterY()) ? ymin : ymax);
Point2D intersectionPt2 = createPoint2D((fromX > objective.getCenterX()) ? xmin : xmax , (fromY < objective.getCenterY()) ? ymax : ymin);
Point2D middlePt = getMidPoint(intersectionPt1, intersectionPt2);
Circle circlePt1 = new Circle(intersectionPt1.getX(), intersectionPt1.getY(), 5);
Circle circlePt2 = new Circle(intersectionPt2.getX(), intersectionPt2.getY(), 5);
Circle circleMiddle = new Circle(middlePt.getX(), middlePt.getY(), 5, Color.RED);
if (c != objective) {
// To calculate the points just before/after the first/second points (green points)
Pair<Point2D, Point2D> pts = translate(50, intersectionPt1, intersectionPt2, objective.getCenterX(), fromX);
Point2D beforePt1 = pts.getKey();
Point2D beforePt2 = pts.getValue();
Circle circleBeforePt1 = createCircleFromPoint2D(beforePt1);
Circle circleBeforePt2 = createCircleFromPoint2D(beforePt2);
circleBeforePt1.setFill(Color.GREEN);
circleBeforePt2.setFill(Color.GREEN);
pane.getChildren().addAll(circleBeforePt1, circleBeforePt2);
}
pane.getChildren().addAll(s, circlePt1, circlePt2, circleMiddle);
}
}
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.seconds(2));
pathTransition.setNode(myObject);
pathTransition.setPath(path);
pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pathTransition.play();
}
});
pane.getChildren().addAll(circles);
pane.getChildren().addAll(myObject, straightLine);
Scene scene = new Scene(pane, 1600, 900);
primaryStage.setScene(scene);
primaryStage.show();
}
I want to calculate a path (not necessarily a shortest path) from Point A to Point B, but can't figure it out how. Now I have the points where I would like to pass, I don't know how to link them togethers.
Solution strategy and implementation
I built a solution with the following strategy: On a given line from(X,Y) to to(X,Y) I compute the closest intersection with one of the obstacle shapes. From that shape I take the length of the intersection as a measure of how large the obstacle is, and take a look at the points left and right by 1/2 of that length from some point shortly before the intersection. The first of the left and right points that is not inside an obstacle is then used to sub-divide the task of finding a path around the obstacles.
protected void computeIntersections(double fromX, double fromY, double toX, double toY) {
// recursively test for obstacles and try moving around them by
// calling this same procedure on the segments to and from
// a suitable new point away from the line
Line testLine = new Line(fromX, fromY, toX, toY);
//compute the unit direction of the line
double dX = toX-fromX, dY = toY-fromY;
double ds = Math.hypot(dX,dY);
dX /= ds; dY /= ds;
// get the length from the initial point of the minimal intersection point
// and the opposite point of the same obstacle, remember also the closest obstacle
double t1=-1, t2=-1;
Shape obst = null;
for (Shape c : lstObstacles) {
if (testLine.intersects(c.getLayoutBounds())) {
Shape s = Shape.intersect(testLine, c);
if( s.getLayoutBounds().isEmpty() ) continue;
// intersection bounds of the current shape
double s1, s2;
if(Math.abs(dX) < Math.abs(dY) ) {
s1 = ( s.getBoundsInLocal().getMinY()-fromY ) / dY;
s2 = ( s.getBoundsInLocal().getMaxY()-fromY ) / dY;
} else {
s1 = ( s.getBoundsInLocal().getMinX()-fromX ) / dX;
s2 = ( s.getBoundsInLocal().getMaxX()-fromX ) / dX;
}
// ensure s1 < s2
if ( s2 < s1 ) { double h=s2; s2=s1; s1=h; }
// remember the closest intersection
if ( ( t1 < 0 ) || ( s1 < t1 ) ) { t1 = s1; t2 = s2; obst = c; }
}
}
// at least one intersection found
if( ( obst != null ) && ( t1 > 0 ) ) {
intersectionDecorations.getChildren().add(Shape.intersect(testLine, obst));
// coordinates for the vertex point of the path
double midX, midY;
// go to slightly before the intersection set
double intersectX = fromX + 0.8*t1*dX, intersectY = fromY + 0.8*t1*dY;
// orthogonal segment of half the length of the intersection, go left and right
double perpX = 0.5*(t2-t1)*dY, perpY = 0.5*(t1-t2)*dX;
Rectangle testRect = new Rectangle( 10, 10);
// go away from the line to hopefully have less obstacle from the new point
while( true ) {
// go "left", test if free
midX = intersectX + perpX; midY = intersectY + perpY;
testRect.setX(midX-5); testRect.setY(midY-5);
if( Shape.intersect(testRect, obst).getLayoutBounds().isEmpty() ) break;
// go "right"
midX = intersectX - perpX; midY = intersectY - perpY;
testRect.setX(midX-5); testRect.setY(midY-5);
if( Shape.intersect(testRect, obst).getLayoutBounds().isEmpty() ) break;
// if obstacles left and right, try closer points next
perpX *= 0.5; perpY *= 0.5;
}
intersectionDecorations.getChildren().add(new Line(intersectX, intersectY, midX, midY));
// test the first segment for intersections with obstacles
computeIntersections(fromX, fromY, midX, midY);
// add the middle vertex to the solution path
connectingPath.getElements().add(new LineTo(midX, midY));
// test the second segment for intersections with obstacles
computeIntersections(midX, midY, toX, toY);
}
}
This first chosen point might not be the most optimal one, as one can see, but it does the job. To do better one would have to construct some kind of decision tree of the left-right decisions and then chose the shortest path among the variants. All the usual strategies then apply, like starting a second tree from the target location, depth-first search etc.
The auxillary lines are the intersections that were used and the perpendicular lines to the new midpoints.
PathfinderApp.java
I used this problem to familiarize myself with the use of FXML, thus the main application has the usual boilerplate code.
package pathfinder;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class PathfinderApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("pathfinder.fxml"));
primaryStage.setTitle("Finding a path around obstacles");
primaryStage.setScene(new Scene(root, 1600, 900));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
pathfinder.fxml
The FXML file contains the "most" static (in the sense of always present for the given type of task) elements of the user interface. These are the cursor rectangle, the target circle and a line between them. Then groups for the obstacles and "decorations" from the path construction, and the path itself. This separation allows to clear and populate these groupings independent from each other with no other organizational effort.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.Group?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.shape.Line?>
<?import javafx.scene.shape.Path?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.paint.Color?>
<Pane xmlns:fx="http://javafx.com/fxml"
fx:controller="pathfinder.PathfinderController" onMouseClicked="#setCursor">
<Circle fx:id="target" centerX="800" centerY="250" radius="25" fill="red"/>
<Rectangle fx:id="cursor" x="175" y="175" width="15" height="15" fill="lightblue"/>
<Line fx:id="straightLine" startX="${cursor.X}" startY="${cursor.Y}" endX="${target.centerX}" endY="${target.centerY}"
strokeWidth="2" stroke="gray" strokeLineCap="butt" strokeDashArray="10.0, 5.0" mouseTransparent="true" />
<Group fx:id="obstacles" />
<Group fx:id="intersectionDecorations" />
<Path fx:id="connectingPath" strokeWidth="2" stroke="blue" />
</Pane>
PathfinderController.java
The main work is done in the controller. Some minimal initialization binding the target and cursor to their connecting line and the mouse event handler (with code that prevents the cursor to be placed inside some obstacle) and then the path finding procedures. One framing procedure and the recursive workhorse from above.
package pathfinder;
import javafx.fxml.FXML;
import javafx.geometry.Bounds;
import javafx.scene.layout.Pane;
import javafx.scene.Group;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.shape.Shape;
import javafx.scene.shape.Line;
import javafx.scene.shape.Path;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.paint.Color;
import javafx.scene.input.MouseEvent;
import java.util.*;
public class PathfinderController {
#FXML
private Circle target;
#FXML
private Rectangle cursor;
#FXML
private Line straightLine;
#FXML
private Path connectingPath;
#FXML
private Group obstacles, intersectionDecorations;
private static List<Shape> lstObstacles = Arrays.asList(
new Circle( 500, 250, 125, Color.BLUE ),
new Circle( 240, 400, 75, Color.GREEN ),
new Circle( 700, 500, 150, Color.VIOLET),
new Circle(1150, 300, 115, Color.ORANGE)
);
#FXML
public void initialize() {
straightLine.startXProperty().bind(cursor.xProperty());
straightLine.startYProperty().bind(cursor.yProperty());
obstacles.getChildren().addAll(lstObstacles);
findPath();
}
#FXML
protected void setCursor(MouseEvent e) {
Shape test = new Rectangle(e.getX()-5, e.getY()-5, 10, 10);
for (Shape c : lstObstacles) {
if( !Shape.intersect(c, test).getLayoutBounds().isEmpty() ) return;
}
cursor.setX(e.getX());
cursor.setY(e.getY());
findPath();
}
protected void findPath() {
double fromX = cursor.getX();
double fromY = cursor.getY();
double toX = target.getCenterX();
double toY = target.getCenterY();
intersectionDecorations.getChildren().clear();
connectingPath.getElements().clear();
// first point of path
connectingPath.getElements().add(new MoveTo(fromX, fromY));
// check path for intersections, move around if necessary
computeIntersections(fromX, fromY, toX, toY);
// last point of the path
connectingPath.getElements().add(new LineTo(toX, toY));
}
protected void computeIntersections(double fromX, double fromY, double toX, double toY) {
...
}
// end class
}
It may not be the desired answer, but did you think about unit testing your math code? It is easy to do for math code and then you can be sure the low level functions work correct.
If you still have the bug afterwards, you can write a unit test for easier reproducing it and post it here.
On Topic:
Your algorithm with the lines can get quite complex or even find no solution with more and/or overlapping circles.
Why not use the standard A* algorithm, where all non-white pixels are obstacles. Is that overkill?
I have a few rectangles that I assign rotation one by one ((javafx.scene.shape.Rectangle) shape).rotateProperty().bind(rotate);
For example, 45 degrees
My rectangle rotates around its center. Please tell me how to make the enemy a few right-angles around their common center.
To get something like this
this.rotate.addListener((obs, old, fresh) -> {
for (VObject vObject : children ) {
vObject.rotate.set(this.rotate.get());
}
});
This is how I add rotation. How can I specify the angle
Update: I used the advice below and now I set the rotation individually for each rectangle. (The selection is still a bit wrong)
this.rotate.addListener((obs, old, fresh) -> {
Rotate groupRotate = new Rotate(rotate.get(),
this.x.getValue().doubleValue() + this.width.getValue().doubleValue() / 2 ,
this.y.getValue().doubleValue() + this.height.getValue().doubleValue() / 2);
for (VObject vObject : children ) {
vObject.getShape().getTransforms().clear();
vObject.getShape().getTransforms().add(groupRotate);
}
});
But now the axis also rotates depending on the rotation.
Can I set the rotation to the rectangles without turning the coordinate axis?
If you put all rectangles into a common Group, you can rotate them at once:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class RotateAllApplication extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
// the common group
Group group = new Group();
group.getChildren().addAll(new Rectangle(10, 10, 80, 40), //
new Rectangle(110, 10, 80, 40), //
new Rectangle(10, 110, 80, 40), //
new Rectangle(110, 110, 80, 40));
// rotate the group instead of each rectangle
group.setRotate(45.0);
root.setCenter(group);
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
Update: If you don't want to create a parent Group object, you can apply the same rotation transformation to each child instead. While Node#setRotate(double) always rotates around the center of the node, adding a transformation to Node#getTransforms() is more general and not restricted to simple rotations.
The following statement will apply a rotation around the point (100.0/100.0) of the parent coordinate system to all children in the list:
childrenList.forEach(child -> child.getTransforms().add(Transform.rotate(45.0, 100.0, 100.0)));
Use a Rotate transform and specify the appropriate pivot point:
#Override
public void start(Stage primaryStage) throws IOException {
Pane pane = new Pane();
pane.setPrefSize(600, 600);
Rectangle[] rects = new Rectangle[4];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
Rectangle rect = new Rectangle(100 + i * 200, 100 + j * 100, 150, 50);
rects[i * 2 + j] = rect;
pane.getChildren().add(rect);
}
}
Slider slider = new Slider(0, 360, 0);
double minX = Double.POSITIVE_INFINITY;
double minY = Double.POSITIVE_INFINITY;
double maxX = Double.NEGATIVE_INFINITY;
double maxY = Double.NEGATIVE_INFINITY;
Rotate rotate = new Rotate();
// find pivot point
for (Rectangle rect : rects) {
double val = rect.getX();
if (minX > val) {
minX = val;
}
val += rect.getWidth();
if (maxX < val) {
maxX = val;
}
val = rect.getY();
if (minY > val) {
minY = val;
}
val += rect.getHeight();
if (maxY < val) {
maxY = val;
}
rect.getTransforms().add(rotate);
}
rotate.setPivotX(0.5 * (maxX + minX));
rotate.setPivotY(0.5 * (maxY + minY));
rotate.angleProperty().bind(slider.valueProperty());
Scene scene = new Scene(new VBox(10, pane, slider));
primaryStage.setScene(scene);
primaryStage.sizeToScene();
primaryStage.show();
}
If you're planing to apply multiple transformations, you may need to adjust the code for finding the pivot point to use transforms for calculating the bounds...
I am trying to recursively add ellipses to a pane for homework. I have written what code I believe should work, and while it both compiles and runs, it shows nothing on my pane.For a little background, the ellipses should all be centered in the pane, each should be 10px away from the next ellipse edge, and the outer ellipse should be 10px away from the edge of the pane.
Here is my code
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.*;
import javafx.scene.shape.*;
import java.util.Random;
import javafx.scene.paint.Color;
public class DisplayCircles extends Application {
private static Pane mainPane = new Pane();
public void start(Stage primaryStage) {
double horRadius = (mainPane.getWidth() / 2) - 10;
double vertRadius = (mainPane.getHeight() / 2) - 10;
addCircles(horRadius, vertRadius);
Scene scene = new Scene(mainPane, 500, 500);
primaryStage.setTitle("Circle Display");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* Recursively adds circles to the pane from largest to smallest.
*
* #param horizontal - The starting horizontal radius.
* #param vertical - The starting vertical radius.
*/
public static void addCircles(double horizontal, double vertical) {
if (horizontal <= 10 || vertical <= 10) {
createEllipse(horizontal, vertical);
} else {
createEllipse(horizontal, vertical);
addCircles(horizontal - 10, vertical - 10);
}
}
/**
* Creates an ellipse with the given horizontal and vertical radii.
*
* #param horizontal - The x based radius.
* #param vertical - the y based radius.
*/
private static void createEllipse(double horizontal, double vertical) {
Random rand = new Random();
Ellipse ellipse = new Ellipse(horizontal, vertical);
ellipse.centerXProperty().bind(
mainPane.widthProperty().divide(2.0));
ellipse.centerYProperty().bind(
mainPane.heightProperty().divide(2.0));
double r = rand.nextDouble();
double g = rand.nextDouble();
double b = rand.nextDouble();
double o = rand.nextDouble();
ellipse.setFill(Color.color(r, g, b, o));
mainPane.getChildren().add(ellipse);
}
}
The width and height of the Pane will be 0 until it has been added to a Scene and that Scene has undergone layout. Of course, in this case you know what the initial size of the pane is going to be, so you can do
double width = 500 ;
double height = 500 ;
double horRadius = (width / 2) - 10;
double vertRadius = (height / 2) - 10;
addCircles(horRadius, vertRadius);
Scene scene = new Scene(mainPane, width, height);
Another solution would be to re-compute the graphics when the size of the pane changes. In this solution, the circles are drawn when the pane is first placed in the scene, and then redrawn to fill the pane any time the window resizes. This probably isn't what you want for this application, but might be a useful idea in other cases:
mainPane.boundsInLocalProperty().addListener((obs, oldBounds, newBounds) -> {
mainPane.getChildren().clear();
double horRadius = (mainPane.getWidth() / 2) - 10;
double vertRadius = (mainPane.getHeight() / 2) - 10;
addCircles(horRadius, vertRadius);
});
Scene scene = new Scene(mainPane, 500, 500);
As an aside, why did you make everything static? It doesn't matter too much as only one instance of an Application subclass is ever created, but in general it's bad practice to use static when there's no good design reason to do so.
I've been trying to rotate a polygon around a specified center point but everything I've tried has failed miserably. I've googled for example and found many but not a single one seems to work.
The result I'm trying to replicate is similar with the first answer to this
How to rotate an image gradually in Swing?
The difference is that I need the polygon to actually rotate, just drawing it in an angle won't cut it. (it's for simple physics modelling)
Here's my code, with several different methods I've tried to implement
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JPanel;
public class rotationPanel extends JPanel {
private static final int SIZE = 500;
private static final Shape outline = makeShape();
Point p;
Point p2;
Point p3;
Point p4;
Point[] points;
Point[] npoints;
Point center;
Polygon poly;
double angle;
Timer timer;
long start;
long sleepTime;
static int runTime;
public rotationPanel(){
setSize(500,500);
setBackground(Color.DARK_GRAY);
setVisible(true);
runTime = 100; //ms
start = 0;
sleepTime = 0;
timer = new Timer();
center = new Point(250,250);
p = new Point(200,200);
p2 = new Point(150,150);
p3 = new Point(250,150);
p4 = new Point(200,100);
/*
points = new Point[4];
points[0]=p;
points[1]=p2;
points[2]=p3;
points[3]=p4;
npoints = new Point[4];
npoints[0]=p;
npoints[1]=p2;
npoints[2]=p3;
npoints[3]=p4;
poly = new Polygon();
*/
}
public void mainloop(){
start= System.currentTimeMillis();
//rotate(points,2);
p = rotatePoint(p,center);
p2 = rotatePoint(p2,center);
p3 = rotatePoint(p3,center);
p4 = rotatePoint(p4,center);
repaint();
sleepTime = runTime -(System.currentTimeMillis()-start);
System.out.println("Looped. Sleeping for:" +sleepTime+"ms");
if(sleepTime>0)
timer.schedule(new loop(), sleepTime);
else
mainloop();
}
private static Shape makeShape() {
AffineTransform at = new AffineTransform();
at.translate(SIZE/2, SIZE/2);
at.scale(20, 20);
at.rotate(Math.toRadians(35));
return at.createTransformedShape(initPoly());
}
/** Create a U shaped outline. */
private static Polygon initPoly() {
Polygon poly = new Polygon();
poly.addPoint( 1, 0);
poly.addPoint( 1, -2);
poly.addPoint( 2, -2);
poly.addPoint( 2, 1);
poly.addPoint(-2, 1);
poly.addPoint(-2, -2);
poly.addPoint(-1, -2);
poly.addPoint(-1, 0);
return poly;
}
public void rotatePoint(Point pt, double rotationAngle){
AffineTransform.getRotateInstance
(Math.toRadians(rotationAngle), center.x, center.y)
.transform(pt,pt);
}
public Point rotatePoint(Point pt, Point center)
{
angle = (Math.toRadians(150));
double cosAngle = Math.cos(angle);
double sinAngle = Math.sin(angle);
pt.x = center.x + (int) ((pt.x-center.x)*cosAngle-(pt.y-center.y)*sinAngle);
pt.y = center.y + (int) ((pt.x-center.x)*sinAngle+(pt.y-center.y)*cosAngle);
return pt;
}
public void rotate(Point[] pts, int angle){
AffineTransform.getRotateInstance
(Math.toRadians(angle), center.x, center.y)
.transform(pts,0,npoints,0,4);
points = new Point[4];
points[0]=npoints[0];
points[1]=npoints[1];
points[2]=npoints[2];
points[3]=npoints[3];
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillRect(center.x-4, center.y-4, 8, 8);
g.setColor(Color.YELLOW);
//g.fillRect(p.x-4, p.y-4, 8, 8);
//g.fillRect(p2.x-4, p2.y-4, 8, 8);
//g.fillRect(p3.x-4, p3.y-4, 8, 8);
//g.fillRect(p4.x-4, p4.y-4, 8, 8);
g.fillRect(p.x, p.y, 2, 2);
g.fillRect(p2.x, p2.y, 2, 2);
g.fillRect(p3.x, p3.y, 2, 2);
g.fillRect(p4.x, p4.y, 2, 2);
}
class loop extends TimerTask{
public void run() {
mainloop();
}
}
}
As you didn't help me vary much I was forced to figure this out by myself. Here we go:
The correct approach (or at least one of them) is to use affine transform to the points of the polygon you wish to rotate. The catch is that you cannot rotate the same polygon over and over again as it will severely deform due to the continuous rounding.
So the trick is to keep " an original version" of the polygon and always rotate that one.
Of course, this approach is only critical when rotating the polygon several times. If you want to only rotate it once you can simply use the values from the polygon you want to rotate.
Here's a little example I managed to put together:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class rotationPanel extends JPanel {
private static final long serialVersionUID = 117L;
private static final int SIZE = 500;
// point arrays which contain the points that are rotated around the center
Point[] points1;
Point[] points2;
Point[] points3;
// The center of rotation
Point center;
// the polygons being rotated
Polygon poly1;
Polygon poly2;
Polygon poly3;
// the angle of rotation
double angle;
Timer timer;
long start;
long sleepTime;
static int runTime;
public rotationPanel(){
setSize(500,500);
setBackground(Color.DARK_GRAY);
setVisible(true);
// time loop is set to run at fixed rate of 50 ms
runTime = 50;
start = 0;
sleepTime = 0;
timer = new Timer();
angle = 0;
// initializing the arrays (not neccesary)
points1 = getOriginalPoints(1);
points3 = getOriginalPoints(3);
points2 = getOriginalPoints(2);
// setting the rotation to the middle of the screen
center = new Point(250,250);
// start the looping
mainloop();
}
public void mainloop(){
start= System.currentTimeMillis();
// rotate the points the spcified angle and store the rotated
//points to the correct array
rotatePointMatrix(getOriginalPoints(1),angle,points1);
rotatePointMatrix(getOriginalPoints(2),angle,points2);
rotatePointMatrix(getOriginalPoints(3),angle,points3);
// Make the points into a polygon
poly1 = polygonize(points1);
poly2 = polygonize(points2);
poly3 = polygonize(points3);
// increase the angle by one degree, resulting to rotation in the longer run
angle++;
if (angle>=360){
angle=0;
}
// restatring the sequence
repaint();
sleepTime = runTime -(System.currentTimeMillis()-start);
System.out.println("Looped. Sleeping for:" +sleepTime+"ms");
if(sleepTime>0)
timer.schedule(new loop(), sleepTime);
else
mainloop();
}
public void rotatePointMatrix(Point[] origPoints, double angle, Point[] storeTo){
/* We ge the original points of the polygon we wish to rotate
* and rotate them with affine transform to the given angle.
* After the opeariont is complete the points are stored to the
* array given to the method.
*/
AffineTransform.getRotateInstance
(Math.toRadians(angle), center.x, center.y)
.transform(origPoints,0,storeTo,0,5);
}
public Polygon polygonize(Point[] polyPoints){
//a simple method that makes a new polygon out of the rotated points
Polygon tempPoly = new Polygon();
for(int i=0; i < polyPoints.length; i++){
tempPoly.addPoint(polyPoints[i].x, polyPoints[i].y);
}
return tempPoly;
}
public Point[] getOriginalPoints(int type){
/* In this example the rotated "polygon" are stored in this method.
* The Point is that if we want to rotate a polygon constatnly/frequently
* we cannot use the values of an already rotated polygon as this will
* lead to the polygon deforming severely after few translations due
* to the points being constantly rounded. So the trick is to save the
* original Points of the polygon and always rotate that one to the new
* angle instead of rotating the same one again and again.
*/
Point[] originalPoints = new Point[5];
if(type == 2){
originalPoints[0]= new Point(200, 100);
originalPoints[1]= new Point(250, 50);
originalPoints[2]= new Point(300, 100);
originalPoints[3]= new Point(300, 400);
originalPoints[4]= new Point(200, 400);
}
else if(type == 1){
originalPoints[0]= new Point(210, 150);
originalPoints[1]= new Point(250, 150);
originalPoints[2]= new Point(250, 190);
originalPoints[3]= new Point(230, 220);
originalPoints[4]= new Point(210, 190);
}
else{
originalPoints[0]= new Point(250, 300);
originalPoints[1]= new Point(290, 300);
originalPoints[2]= new Point(290, 340);
originalPoints[3]= new Point(270, 370);
originalPoints[4]= new Point(250, 340);
}
return originalPoints;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.GRAY);
g2d.fillPolygon(poly2);
g2d.setColor(Color.yellow);
g2d.fillPolygon(poly1);
g2d.setColor(Color.yellow);
g2d.fillPolygon(poly3);
g2d.setColor(Color.WHITE);
for(int i=0; i < points1.length; i++){
g2d.fillRect(points1[i].x-1, points1[i].y-1, 3, 3);
g2d.fillRect(points3[i].x-1, points3[i].y-1, 3, 3);
}
g2d.setColor(Color.BLUE);
g2d.fillOval(center.x-4, center.y-4, 8, 8);
g2d.setColor(Color.yellow);
g2d.drawString("Angle: "+angle, 10,450);
}
class loop extends TimerTask{
public void run() {
mainloop();
}
}
public static void main(String[] args){
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new rotationPanel());
f.setSize(500,500);
f.setVisible(true);
}
}
I hope this helps! Don't hesitate to contact me if you run into trouble!
Here is a simple method to build a polygon from a set of points, rotated around a center point, at a specified angle:
/**
* Builds a polygon from a set of points, rotated around a point, at the
* specified rotation angle.
*
* #param centerX the int center x coordinate around which to rotate
* #param centerY the int center y coordinate around which to rotate
* #param xp the int[] of x points which make up our polygon points. This
* array is parallel to the yp array where each index in this array
* corresponds to the same index in the yp array.
* #param yp the int[] of y points which make up our polygon points. This
* array is parallel to the xp array where each index in this array
* corresponds to the same index in the xp array.
* #param rotationAngle the double angle in which to rotate the provided
* coordinates (specified in degrees).
* #return a Polygon of the provided coordinates rotated around the center point
* at the specified angle.
* #throws IllegalArgumentException when the provided x points array is not the
* same length as the provided y points array
*/
private Polygon buildPolygon(int centerX, int centerY, int[] xp, int[] yp, double rotationAngle) throws IllegalArgumentException {
// copy the arrays so that we dont manipulate the originals, that way we can
// reuse them if necessary
int[] xpoints = Arrays.copyOf(xp,xp.length);
int[] ypoints = Arrays.copyOf(yp,yp.length);
if(xpoints.length != ypoints.length){
throw new IllegalArgumentException("The provided x points are not the same length as the provided y points.");
}
// create a list of Point2D pairs
ArrayList<Point2D> list = new ArrayList();
for(int i = 0; i < ypoints.length; i++){
list.add(new Point2D.Double(xpoints[i], ypoints[i]));
}
// create an array which will hold the rotated points
Point2D[] rotatedPoints = new Point2D[list.size()];
// rotate the points
AffineTransform transform = AffineTransform.getRotateInstance(Math.toRadians(rotationAngle), centerX, centerY);
transform.transform(list.toArray(new Point2D[0]), 0, rotatedPoints, 0, rotatedPoints.length);
// build the polygon from the rotated points and return it
int[] ixp = new int[list.size()];
int[] iyp = new int[list.size()];
for(int i = 0; i < ixp.length; i++){
ixp[i] = (int)rotatedPoints[i].getX();
iyp[i] = (int)rotatedPoints[i].getY();
}
return new Polygon(ixp, iyp, ixp.length);
}