JavaFX get corner coordinates of rotated imageView? - java

I am making a simple simulation, and have had a good amount of trouble finding the X and Y coordinates of a rotated, weirdly sized, imageView node. (The blue bit is the front)
The goal is to find out an XY coordinate relative to the direction that the imageView is pointing, after it has been rotated. I can find the angle that the imageView is at relative to its starting position but I cannot figure out how to get an XY coordinate of the imageView relative to this angle. Since the .setRotate(angle) method does not change the X and Y location of the imageView, how should I go about finding a point that the imageView is facing?
Minimal example of the rotation and imageView I am using:
Main.java
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root, 500, 500));
Image robotImage = null;
try {
robotImage = new Image(new FileInputStream("res\\robot.png"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
ImageView robot = new ImageView(robotImage);
robot.setLayoutX(125);
robot.setLayoutY(125);
System.out.println("PreRotate X: " + robot.getLayoutX() + "PreRotate Y: " + robot.getLayoutY());
robot.setRotate(45);
System.out.println("PostRotate X: " + robot.getLayoutX() + "PostRotate Y: " + robot.getLayoutY());
root.getChildren().add(robot);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have already tried using the bounds of the imageView along with lines that lay on top of the imageView, but that requires me to find the new max/min x/y every time that the imageView changes its max/min x/y.
For example:
if (turnAngle < 35) {
directionLine.setStartX(robotLeftRightAngle.getBoundsInParent().getMaxX());
directionLine.setStartY(robotLeftRightAngle.getBoundsInParent().getMinY());
directionLine.setEndX(robotRightLeftAngle.getBoundsInParent().getMinX() + ((robotLeftRightAngle.getBoundsInParent().getMaxX() - robotRightLeftAngle.getBoundsInParent().getMinX()) / 2));
directionLine.setEndY(robotRightLeftAngle.getBoundsInParent().getMinY() + ((robotLeftRightAngle.getBoundsInParent().getMinY() - robotRightLeftAngle.getBoundsInParent().getMinY()) / 2));
}
else if (turnAngle < 55) {
directionLine.setStartX(robotLeftRightAngle.getBoundsInParent().getMaxX());
directionLine.setStartY(robotLeftRightAngle.getBoundsInParent().getMaxY());
directionLine.setEndX(robotRightLeftAngle.getBoundsInParent().getMinX() + ((robotLeftRightAngle.getBoundsInParent().getMaxX() - robotRightLeftAngle.getBoundsInParent().getMinX()) / 2));
directionLine.setEndY(robotRightLeftAngle.getBoundsInParent().getMinY() + ((robotLeftRightAngle.getBoundsInParent().getMaxY() - robotRightLeftAngle.getBoundsInParent().getMinY()) / 2));
}
And so on all the way to 360. DRY yikes.
How should I approach this? Am I using the wrong transformation? Did I not see a method that can be used for this? I know that there must be a better approach. Thanks for reading.

I'm not sure I 100% understand the question. The transformations between coordinate systems but it's hard to tell the coordinate systems you need to convert between from your description, so I assume you want to convert between the coordinate system of robot to the coordinate system of group.
It's possible to use localToParent to convert from the coordinate system of a node to that of the parent which accomodates for all transforms. (parentToLocal would achieve the inverse transformation, but this does not seem to be the required transformation in this case.)
The following example modifies the start and end points of a line to the coordinates of the top left and a point 100 px above of the Rectangle in the Rectangle's coordinate system:
#Override
public void start(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root, 500, 500));
Rectangle robot = new Rectangle(100, 20, Color.RED);
robot.setLayoutX(125);
robot.setLayoutY(125);
Line line = new Line(125, 125, 125, 25);
robot.rotateProperty().addListener(o -> {
Point2D start = robot.localToParent(0, 0);
Point2D end = robot.localToParent(0, -100);
line.setStartX(start.getX());
line.setStartY(start.getY());
line.setEndX(end.getX());
line.setEndY(end.getY());
});
RotateTransition rotateTransition = new RotateTransition(Duration.seconds(5), robot);
rotateTransition.setCycleCount(Animation.INDEFINITE);
rotateTransition.setFromAngle(0);
rotateTransition.setToAngle(360);
rotateTransition.setInterpolator(Interpolator.LINEAR);
rotateTransition.play();
root.getChildren().addAll(robot, line);
primaryStage.show();
}

Related

javafx 3d sphere partial texture

I am trying to draw a texture on a sphere with JavaFX (16). I add the material with the texture but the texture is stretched to the whole surface. It is possible to set the texture on only a portion of the surface? Like the image below (not mine, taken from SO):
My code so far (very trivial):
public void start(Stage stage) throws Exception {
Sphere sphere = new Sphere(200);
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(new Image(new File("picture.png").toURI().toURL().toExternalForm()));
sphere.setMaterial(material);
Group group = new Group(sphere);
Scene scene = new Scene( new StackPane(group), 640, 480, true, SceneAntialiasing.BALANCED);
scene.setCamera(new PerspectiveCamera());
stage.setScene(scene);
stage.show();
}
The reason why the texture you apply is stretched to the whole sphere is that the texture coordinates that define the Sphere mesh are mapping the whole sphere surface, and therefore, when you apply a diffuse image, it is translated 1-1 to that surface.
You could create a custom mesh, with custom texture coordinates values, but that can be more complex.
Another option is to create the diffuse image "on demand", based on your needs.
For a sphere, a 2D image that can be wrapped around the 3D sphere can be defined by a 2*r*PI x 2*r rectangular container (a JavaFX Pane for our purposes).
Then, you can draw inside your images, scaling and translating them accordingly.
Finally, you need a way to convert that drawing into an image, and for that you can use Scene::snapshot.
Just to play around with this idea, I'll create a rectangular grid that will be wrapped around the sphere, in order to have some kind of a coordinate system.
private Image getTexture(double r) {
double h = 2 * r;
double w = 2 * r * 3.125; // 3.125 is ~ PI, rounded to get perfect squares.
Pane pane = new Pane();
pane.setPrefSize(w, h);
pane.getStyleClass().add("pane-grid");
Group rootAux = new Group(pane);
Scene sceneAux = new Scene(rootAux, rootAux.getBoundsInLocal().getWidth(), rootAux.getBoundsInLocal().getHeight());
sceneAux.getStylesheets().add(getClass().getResource("/style.css").toExternalForm());
SnapshotParameters sp = new SnapshotParameters();
return rootAux.snapshot(sp, null);
}
where style.css has:
.pane-grid {
-fx-background-color: #D3D3D333,
linear-gradient(from 0.5px 0.0px to 50.5px 0.0px, repeat, black 5%, transparent 5%),
linear-gradient(from 0.0px 0.5px to 0.0px 50.5px, repeat, black 5%, transparent 5%);
}
.pane-solid {
-fx-background-color: black;
}
(based on this answer)
With a radius of 400, you get this image:
each square is 50x50, and there are 50x16 squares.
If you apply this diffuse map to an Sphere:
#Override
public void start(Stage stage) {
PhongMaterial earthMaterial = new PhongMaterial();
earthMaterial.setDiffuseMap(getTexture(400));
final Sphere earth = new Sphere(400);
earth.setMaterial(earthMaterial);
Scene scene = new Scene(root, 800, 600, true);
scene.setFill(Color.WHITESMOKE);
stage.setScene(scene);
stage.show();
}
you get:
In theory, now you could fill any of the grid squares, like:
private Image getTexture(double r) throws IOException {
double h = 2 * r;
double w = 2 * r * 3.125;
Pane pane = new Pane();
pane.setPrefSize(w, h);
pane.getStyleClass().add("pane-grid");
Rectangle rectangle = new Rectangle(50, 50, Color.RED);
rectangle.setStroke(Color.WHITE);
rectangle.setStrokeWidth(2);
// fill rectangle at 20 x 10
rectangle.setTranslateX(20 * 50 + 1);
rectangle.setTranslateY(10 * 50 + 1);
Group rootAux = new Group(pane, rectangle);
...
with the result:
Now that you have a well positioned image (for now just a red rectangle), you can get rid of the grid, and simply use a black color for the texture image:
private Image getTexture(double r) throws IOException {
double h = 2 * r;
double w = 2 * r * 3.125;
Pane pane = new Pane();
pane.setPrefSize(w, h);
// pane.getStyleClass().add("pane-grid");
pane.getStyleClass().add("pane-solid");
resulting in:
Now it is up to you to apply this idea to your needs. Note that you can use an ImageView with size 50x50, or 100x100, ... instead of the red rectangle, so you can use a more complex image.

Issues dragging a JavaFX 3D Node

I have been having this issue for a long time, and no matter what answers on here I try I keep having an issue.
This is a previous question of mine that had no resolution to it JavaFX 3D PerspectiveCamera affects drag position of a node
(nor did the answers/links provided in the question)
Essentially I am trying to drag a node while keeping the mouse position at to the clicked position of the node while dragging.
The original thought was to do an event.getScreenX() or event.getSceneX() to get the initial position on mouseClicked, and then compare/update in the mouse dragged.
The issue is that when I zoom the camera in and out(camera.setTranslateZ()), for some reason the values will increase/decrease depending on the zoom, i.e., the node drags slower/stays with the mouse when the camera is zoomed out.
For what it's worth I also have scaled the main node by 10, which I think might have something to do with this as one of the examples did seem to break when the scale and/or camera were changed; however the example also doesn't work, with no scale.
Does anyone have any idea? It's extremely frustrating with how simple this task is, yet hard to actually accomplish. I would think that as the mouse would drag, regardless if it dragged 1 pixel with the mouse zoomed in, or 100 pixels with the mouse zoomed out that it wouldn't cause this issue, so I'm wondering if there is some sort of bug with this? Any thoughts are appreciated, thank you.
public class Move extends Application {
double x0,xDiff;
double y0,yDiff;
#Override
public void start(Stage primaryStage) {
Box b = new Box(100,100,1);
b.setLayoutX(0);
b.setLayoutY(0);
// b.setTranslateZ(20000);
Pane root = new Pane();
root.getChildren().add(b);
PhongMaterial p = new PhongMaterial();
p.setDiffuseColor(Color.RED);
b.setMaterial(p);
Scene scene = new Scene(root, 2000, 1250,true);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setTranslateZ(-1000);
camera.setFarClip(2000);
scene.setCamera(camera);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
b.setOnMousePressed(event
->{
x0 = event.getSceneX();
y0 = event.getSceneY();
event.consume();
});
b.setOnMouseDragged(event
->{
xDiff = event.getSceneX() - x0;
yDiff = event.getSceneY() - y0;
b.setLayoutX(b.getLayoutX() + xDiff);
b.setLayoutY(b.getLayoutY() + yDiff);
x0 = event.getSceneX();
y0 = event.getSceneY();
});
primaryStage.setOnScroll(event
->{
if (event.getDeltaY() > 0)
{
camera.setTranslateZ(camera.getTranslateZ() + 45);
}
else
{
camera.setTranslateZ(camera.getTranslateZ() - 45);
}
});
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

JavaFX: Creating a board grid with Permanin twist

I am creating a board game (first ported to JavaFX) in which the player must kill the opponent's piece going through a loop.
The above is provided on the Wikipedia page for Surakurta (another name for Permanin). But, I have been only able to build a grid of this sort:
How can I create those roundabouts in the corners?
Implementation details for already built grid: GridPane filled with 36 BoardInput extends javafx.scene.control.Button objects. These objects are special because they automatically create a Background with three BackgroundFill objects - horizontal line, vertical line, and the pebble circular fill.
Use a Path. ArcTo elements allow you to create the circular parts. HLineTo, VLineTo and ClosePath can be used for the straight sections:
Furthermore I don't recommend using BackgroundFills. I'd prefer overlaying invisible buttons on top of the board visuals or handling MouseEvents for the GridPane itself.
Example
private static ArcTo createArc(double radius, double dx, double dy) {
ArcTo result = new ArcTo(radius, radius, 0, dx, dy, true, true);
result.setAbsolute(false);
return result;
}
private static HLineTo createHLine(double length) {
HLineTo result = new HLineTo(length);
result.setAbsolute(false);
return result;
}
private static VLineTo createVLine(double length) {
VLineTo result = new VLineTo(length);
result.setAbsolute(false);
return result;
}
private static Path createPath(double radius, double midSize, Color storke) {
final double lineLength = 2 * radius + midSize;
Path result = new Path(
new MoveTo(radius, 2 * radius), // start at left end of top horizontal line
createArc(radius, radius, -radius), // top left loop
createVLine(lineLength), // down
createArc(radius, -radius, -radius), // bottom left loop
createHLine(lineLength), // right
createArc(radius, -radius, radius), // bottom right loop
createVLine(-lineLength), // up
createArc(radius, radius, radius),
new ClosePath() // left
);
result.setStroke(storke);
result.setStrokeWidth(10);
return result;
}
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(new StackPane(
createPath(100, 50, Color.GREEN),
createPath(50, 150, Color.AQUA)
));
primaryStage.setScene(scene);
primaryStage.show();
}
Output

JavaFX GraphicsContext clearRect doesn't work with clip mask

The documentation for the clearRect method of GraphicsContext states that it uses the current clip, but this isn't currently working for me. Consider:
GraphicsContext context = canvas.getGraphicsContext2D();
context.beginPath();
context.rect(0,0,100,100); //Set the current path to a rectangle
context.stroke(); //Highlights where the current path is
context.clip(); //Intersect current clip with rectangle
context.fillOval(80, 80, 40, 40); //This correctly draws the oval clipped
context.clearRect(0,0,100,100); //This does nothing at all
The above code sets the clip mask correctly, as evidenced by the fact that fillOval works correctly, however clearRect does nothing (although it works normally without the context.clip()). Why is this?
(Note that I specifically need the clip mask to be working, as later I plan on setting it to specific shapes to erase in non-rectangular shapes.)
-- Edit --
To be clear, clearRect does literally nothing, not even erase the oval. I realise that it won't erase the stroked rectangle but that's not what I'm concerned about.
-- Edit 2 --
Updating to the latest JDK has partially fixed the issue. The above code now works correctly. However, using a non-rectangular clip mask still has the same problem. e.g.
GraphicsContext context = canvas.getGraphicsContext2D();
context.beginPath();
context.arc(50, 50, 40, 40, 0, 360); // Make a circular clip mask
context.closePath();
context.clip();
context.fillRect(0, 0, 200, 200); //Draw a circle clipped correctly, shows clip mask is working
context.clearRect(0, 0, 200, 200); //Does nothing
I realise I could use save and restore to get a rectangular clip mask back, and then clearRect would work. However I want to be able to erase in non-rectangular shapes.
Full code for reproducing this is (created by making a new JavaFX project in eclipse and adding the above lines):
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
Canvas canvas = new Canvas(500, 500);
root.getChildren().add(canvas);
GraphicsContext context = canvas.getGraphicsContext2D();
context.beginPath();
context.arc(50, 50, 40, 40, 0, 360);
context.closePath();
context.clip();
context.fillRect(0, 0, 200, 200);
context.clearRect(0, 0, 200, 200);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
This should show a blank screen, but the circle is not being cleared.
As per Edit 2, this behaviour seems to be a bug. In summary, clearRect has no effect when a non-rectangular clip is set. (If it isn't working for you even with a rectangular clip, update to the latest JDK.) I've filed a bug report for this.
While i agree that this should be fixed, but am facing the fact that it just is not, i have come up with a solution that meets my specific needs at least.
It is a method that clears an area based on an SVGPath using the GraphicsContext.
private void clearPath(GraphicsContext gc, SVGPath path) {
int xstart = (int) path.getLayoutX();
int xend = (int) (xstart + path.getLayoutBounds().getMaxX());
int ystart = (int) path.getLayoutY();
int yend = (int) (ystart + path.getLayoutBounds().getMaxY());
PixelWriter pw = gc.getPixelWriter();
for (int x = xstart; x <= xend; x++) {
for (int y = ystart; y <= yend; y++) {
if(path.contains(new Point2D(x, y))) {
pw.setColor(x, y, Color.TRANSPARENT);
}
}
}
}
The code works just fine on Windows 7 with JavaFX 8u40.
What you should do is to provide a MCVE. Nobody can possibly guess what else you've done with the GraphicsContext. There may be a save and restore missing and what not. By the way, your code lacks a closePath().
Answer before your edit:
The problem you are facing is the way JavaFX draws the lines. Check out the documentation of the Node class:
At the device pixel level, integer coordinates map onto the corners
and cracks between the pixels and the centers of the pixels appear at
the midpoints between integer pixel locations. Because all coordinate
values are specified with floating point numbers, coordinates can
precisely point to these corners (when the floating point values have
exact integer values) or to any location on the pixel. For example, a
coordinate of (0.5, 0.5) would point to the center of the upper left
pixel on the Stage. Similarly, a rectangle at (0, 0) with dimensions
of 10 by 10 would span from the upper left corner of the upper left
pixel on the Stage to the lower right corner of the 10th pixel on the
10th scanline. The pixel center of the last pixel inside that
rectangle would be at the coordinates (9.5, 9.5).
That's why you have a thin line on the right and at the bottom. The lines aren't crisp.
I suggest you move the rectangle to the center and also use the fill method to make this more visible for you.
Your code without clearRect:
Your code with clearRect:

JavaFx: How to make a cube adjust to window resize

I'm having problems with a javafx exercise I'm doing. Basically the problem states to make a rectangular cube and make it so that it automatically expands or contracts as you adjust the window size. I'm done with the first part of the part of the program but having difficulty with the second. Here's my code:
public void start(Stage primaryStage) {
Pane pane = new Pane();
//Draw two rectangles
Rectangle upper = new Rectangle(140, 100, 120, 100);
upper.setFill(null);
upper.setStroke(Color.BLACK);
Rectangle lower = new Rectangle(100, 140, 120, 100);
lower.setFill(null);
lower.setStroke(Color.BLACK);
//Draw the line connecting them
Line ul = new Line(140, 100, 100, 140);
Line ur = new Line(260, 100, 220, 140);
Line ll = new Line(140, 200, 100, 240);
Line lr = new Line(260, 200, 220, 240);
pane.getChildren().addAll(upper, lower, ul, ur, ll, lr);
Scene scene = new Scene(pane, 200, 200);
primaryStage.setTitle("Exercise14");
primaryStage.setScene(scene);
primaryStage.show();
}
The rectangular cube appears as thus, but I can't figure out how to make it expand or contract when I resize the window:
I'm thinking I need to bind each individual shape to the pane or something, but I'm not sure where to begin. Would appreciate a point in the right direction. Thanks.
You need to add change listeners to the scene and calculate the dimensions of the cube relative to the width and height of the scene. Something like this:
ChangeListener<Number> listenerX = new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println( "X: observable: " + observable + ", oldValue: " + oldValue.doubleValue() + ", newValue: " + newValue.doubleValue());
// TODO:
// Lines:
// line.setStartX(...)
// line.setStartY(...)
// line.setEndX(...)
// line.setEndY(...)
//
// Rectangles:
// rect.setTranslateX(...)
// rect.setTranslateY(...)
// rect.setWidth(...)
// rect.setHeight(...)
// hint: instead of translate you can use rect.relocate(..., ...). translate is relative, relocate applies layoutX/Y and translateX/Y
}
};
ChangeListener<Number> listenerY = new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println( "Y: observable: " + observable + ", oldValue: " + oldValue.doubleValue() + ", newValue: " + newValue.doubleValue());
// similare to listenerX
}
};
scene.widthProperty().addListener( listenerX);
scene.heightProperty().addListener( listenerY);
I would recommend the approach explained over here: http://fxexperience.com/2014/05/resizable-grid-using-canvas/
Basically you create your custom Pane and override the 'layoutChildren()' method to draw your cube. You "just" have to replace the drawing code with your cube.
This is much faster than using the listener approach: When the node is resized it will draw twice at a minimum when using listeners (when width and height are set). 'layoutChildren()' will at most do one draw per frame.

Categories

Resources