I have an object, like a box in this example.
I want this box to move in a sine to the left on the sphere when the Z-axis is rotated. But after the box has made a curve, i.e. the rotation of the Z-axis is back to 0. The sphere no longer rotates downwards as it did at the start, but upwards to the right.
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.*;
import javafx.stage.Stage;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import java.util.ArrayList;
public class TestFX extends Application {
public static final float WIDTH = 1400;
public static final float HEIGHT = 1000;
private final ArrayList<String> input = new ArrayList<>();
private final Sphere sphere = new Sphere(500);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Flugzeug flieger = new Flugzeug(30, 30, 50);
flieger.setMaterial(new PhongMaterial(Color.GREEN));
flieger.setTranslateZ(330);
flieger.getTransforms().add(new Rotate(20, Rotate.X_AXIS));
sphere.translateZProperty().set(710);
sphere.translateYProperty().set(420);
PhongMaterial phongMaterial = new PhongMaterial();
phongMaterial.setDiffuseMap(new Image(getClass().getResourceAsStream("Earth_diffuse_8k.png")));
sphere.setMaterial(phongMaterial);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(40000);
Group root = new Group(flieger, sphere);
Scene scene = new Scene(root, WIDTH, HEIGHT, true);
scene.setCamera(camera);
primaryStage.setTitle("FliegenFX");
primaryStage.setScene(scene);
primaryStage.show();
new AnimationTimer() {
#Override
public void handle(long now) {
initGameLogic(flieger);
dreheErde(flieger, sphere);
}
}.start();
scene.setOnKeyPressed(event -> {
String code = event.getCode().toString();
if (!input.contains(code))
input.add(code);
});
scene.setOnKeyReleased(event -> {
String code = event.getCode().toString();
input.remove(code);
});
}
// TODO Erddrehung nach Drehung beibehalten
private void dreheErde(Flugzeug flieger, Sphere erde) {
double rotationFactor = 0.005;
double drehung = flieger.getDrehung() * rotationFactor;
double amplitude = 2;
double frequency = 0.001;
float angle = (float) (Math.sin(drehung * frequency) * amplitude);
Quaternionf quat = new Quaternionf();
Matrix4f matrix4f = new Matrix4f();
Affine affine = new Affine();
quat.rotateY(angle);
quat.rotateZ(angle);
quat.rotateX(-0.001f);
quat.normalize();
quat.get(matrix4f);
float[] matrixArray = new float[16];
matrix4f.get(matrixArray);
double[] matrix = new double[16];
for (int i = 0 ; i < matrixArray.length; i++)
{
matrix[i] = matrixArray[i];
}
affine.append(matrix, MatrixType.MT_3D_4x4, 0);
erde.getTransforms().add(affine);
}
private void initGameLogic(Flugzeug flieger) {
if (input.contains("LEFT")) {
flieger.rotateByZ(-0.5);
}
if (input.contains("RIGHT")) {
flieger.rotateByZ(0.5);
}
}
private class Flugzeug extends Box{
private double drehung = 0;
private Rotate r;
private Transform t = new Rotate();
public Flugzeug(double width, double height, double depth){
super(width, height, depth);
}
public void rotateByZ(double ang){
drehung += ang;
r = new Rotate(ang, Rotate.Z_AXIS);
t = t.createConcatenation(r);
getTransforms().clear();
getTransforms().addAll(t);
}
public double getDrehung() {
return drehung;
}
}
}
I have tried many different ways with trigonometry but they have not been successful. This was for example to move the X-axis of the sphere also according to the sine or to subtract the movements of the Y and Z axis of the sphere as cosine.
I think it has to be transformed in a Rotation Matrix, but i‘ve never had matrix calculation.
Rotating a Group inside another Group in 3d scene
In this approach Box instance doesn't seem to move 'cause it's moving with the camera . Camera and box are moving together because is it's Group parent who is rotating with keyboard event . The Sphere node is in another group and is not affected . Sphere itself is rotating in Y axis
App.java
public class App extends Application {
#Override
public void start(Stage stage) {
Shape3D sphere = new Sphere(10);
PhongMaterial mat = new PhongMaterial();
mat.setDiffuseMap(new Image("https://www.h-schmidt.net/map/map.jpg"));
sphere.setMaterial(mat);
RotateTransition sphereRotation = new RotateTransition(Duration.seconds(40), sphere);
sphereRotation.setAxis(Rotate.Y_AXIS);
sphereRotation.setToAngle(360);
sphereRotation.setInterpolator(Interpolator.LINEAR);
sphereRotation.setCycleCount(Animation.INDEFINITE);
sphereRotation.play();
Shape3D box = new Box(2, 2, 2);
box.setMaterial(new PhongMaterial(Color.ORANGE));
box.setTranslateZ(-11);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setTranslateZ(-30);
var planeGroup = new Group(box, camera);
Group sphereGroup = new Group(sphere);
Group group3d = new Group(planeGroup, sphereGroup);
Scene scene = new Scene(group3d, 640, 480, true, SceneAntialiasing.BALANCED);
scene.setOnKeyPressed((t) -> {
if (t.getCode() == KeyCode.UP) {
Rotate r = new Rotate(-2);
r.setAxis(Rotate.X_AXIS);
planeGroup.getTransforms().add(r);
}
if (t.getCode() == KeyCode.LEFT) {
Rotate r = new Rotate(2);
r.setAxis(Rotate.Y_AXIS);
planeGroup.getTransforms().add(r);
}
if (t.getCode() == KeyCode.RIGHT) {
Rotate r = new Rotate(-2);
r.setAxis(Rotate.Y_AXIS);
planeGroup.getTransforms().add(r);
}
if (t.getCode() == KeyCode.DOWN) {
Rotate r = new Rotate(2);
r.setAxis(Rotate.X_AXIS);
planeGroup.getTransforms().add(r);
}
});
scene.setCamera(camera);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Related
I want to create a hexagon of hexagons as buttons in JavaFX, I use an image and try to position some buttons to the position of each hexagon but I cannot change the position of them in a grid pane. Here is my code:
package sample;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
public class GameBoard extends GridPane {
public GameBoard(){
this.setAlignment(Pos.CENTER);
ImageView image = new ImageView();
Image hexagonImg = new Image("hexagon.jpg");
image.setFitWidth(500);
image.setFitHeight(500);
image.setImage(hexagonImg);
this.add(image,0,0);
GridPane button1Pane = new GridPane();
this.add(button1Pane,0,0);
Button button1 = new Button();
button1Pane.add(button1,1,0);
}
}
polygon hexagons
Since you need to use as buttons ; I added mouse click event wich changes stage's tittle . If you want to place hexagons side to side you may need to concider radius for x axis and apothem for y axis .
this is afunctional single class javafx app you can try
App.java
public class App extends Application {
#Override
public void start(Stage stage) {
double HexagonRadius = 100;
Hexagon hexagon1 = new Hexagon(HexagonRadius, Color.CADETBLUE);
Hexagon hexagon2 = new Hexagon(HexagonRadius, Color.MEDIUMPURPLE);
hexagon2.setTranslateY(hexagon1.getOffsetY() * 2);
Hexagon hexagon3 = new Hexagon(HexagonRadius, Color.MEDIUMSEAGREEN);
hexagon3.setTranslateY(-hexagon1.getOffsetY() * 2);
Hexagon hexagon4 = new Hexagon(HexagonRadius, Color.CORNFLOWERBLUE);
hexagon4.setTranslateY(-hexagon1.getOffsetY());
hexagon4.setTranslateX(hexagon1.getOffsetX());
Hexagon hexagon5 = new Hexagon(HexagonRadius, Color.YELLOW);
hexagon5.setTranslateY(-hexagon1.getOffsetY());
hexagon5.setTranslateX(-hexagon1.getOffsetX());
Hexagon hexagon6 = new Hexagon(HexagonRadius, Color.ORANGE);
hexagon6.setTranslateY(hexagon1.getOffsetY());
hexagon6.setTranslateX(-hexagon1.getOffsetX());
Hexagon hexagon7 = new Hexagon(HexagonRadius, Color.SKYBLUE);
hexagon7.setTranslateY(hexagon1.getOffsetY());
hexagon7.setTranslateX(hexagon1.getOffsetX());
Group hexagonsGroup = new Group(hexagon1, hexagon2, hexagon3, hexagon4, hexagon5, hexagon6, hexagon7);
StackPane stackPane = new StackPane(hexagonsGroup);
var scene = new Scene(stackPane, 640, 480);
scene.setFill(Color.ANTIQUEWHITE);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
public class Hexagon extends Group {
private Polygon polygon;
private double radius;
private double radianStep = (2 * Math.PI) / 6;
private double offsetY;
private double offsetX;
public Hexagon(double radius, Paint color) {
this.radius = radius;
makeHexagon(radius, color);
offsetY = calculateApothem();
offsetX = radius * 1.5;
changeTittle();
}
private void makeHexagon(double radius, Paint color) {
polygon = new Polygon();
this.getChildren().add(polygon);
polygon.setFill(color);
polygon.setStroke(Color.WHITESMOKE);
polygon.setEffect(new DropShadow(10, Color.BLACK));
polygon.setStrokeWidth(10);
polygon.setStrokeType(StrokeType.INSIDE);
for (int i = 0; i < 6; i++) {
double angle = radianStep * i;
polygon.getPoints().add(Math.cos(angle) * radius / 1.1);
polygon.getPoints().add(Math.sin(angle) * radius / 1.1);
}
}
public void changeTittle() {
polygon.setOnMouseClicked(e -> {
Stage stage = (Stage) this.getScene().getWindow();
stage.setTitle(polygon.getFill().toString());
});
}
public double getOffsetY() {
return offsetY;
}
public double getOffsetX() {
return offsetX;
}
private double calculateApothem() {
return (Math.tan(radianStep) * radius) / 2;
}
}
}
I would just create a Path for each Hexagon and put them in Group which you can then place in the middle of some Pane. Dealing with images and a GridPane only complicates things. Just doing some graphics directly is much easier.
I am new to JavaFX and I am creating a simple program. What I'm trying to achieve is to create a ball every 5 seconds that bounces off the walls and all balls should move every tens times per second (10 milliseconds). Also, feel free to leave other suggestions about my code.
Here's the source code:
public class Main extends Application {
#Override
public void start(Stage stage) {
//Sets the title, adds a group, and background color
BorderPane canvas = new BorderPane();
Scene scene = new Scene(canvas, 640, 480, Color.WHITE);
Circle ball = new Circle(10, Color.RED);
ball.relocate(0, 10);
canvas.getChildren().add(ball);
stage.setTitle("Title");
stage.setScene(scene);
stage.show();
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(20), new EventHandler<ActionEvent>() {
double dx = 5; //Step on x or velocity
double dy = 3; //Step on y
#Override
public void handle(ActionEvent t) {
//move the ball
ball.setLayoutX(ball.getLayoutX() + dx);
ball.setLayoutY(ball.getLayoutY() + dy);
Bounds bounds = canvas.getBoundsInLocal();
//If the ball reaches the left or right border make the step negative
if(ball.getLayoutX() <= (bounds.getMinX() + ball.getRadius()) ||
ball.getLayoutX() >= (bounds.getMaxX() - ball.getRadius()) ){
dx = -dx;
}
//If the ball reaches the bottom or top border make the step negative
if((ball.getLayoutY() >= (bounds.getMaxY() - ball.getRadius())) ||
(ball.getLayoutY() <= (bounds.getMinY() + ball.getRadius()))){
dy = -dy;
}
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
Here is one way to do it as suggested in comments:
Create a custom object that holds the ball and its orientation information.
Add the newly created object in the list and the ball in canvas.
Loop through all balls and position them based on their orientation information.
When your desired time is reached add a new ball.
To keep it simple, you don't need any concurrent related stuff. Maybe using them will improve the implementation (but I am not touching that now). :-)
Here is a demo of using the points I mentioned.
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
public class CanvasBallCreation_Demo extends Application {
List<Ball> balls = new ArrayList<>();
BorderPane canvas;
double dx = 5; //Step on x or velocity
double dy = 3; //Step on y
double refresh = 20;//ms
double addBallDuration = 5000;//ms
double temp = 0;
SecureRandom rnd = new SecureRandom();
#Override
public void start(Stage stage) {
canvas = new BorderPane();
Scene scene = new Scene(canvas, 640, 480, Color.WHITE);
addBall();
stage.setTitle("Title");
stage.setScene(scene);
stage.show();
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(refresh), e->moveBalls()));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
private void addBall(){
Ball ball = new Ball();
balls.add(ball);
canvas.getChildren().add(ball.getBall());
}
private void moveBalls() {
temp = temp + refresh;
if(temp>addBallDuration){
temp = 0;
addBall();
}
Bounds bounds = canvas.getBoundsInLocal();
balls.forEach(obj -> {
Circle ball = obj.getBall();
double tx = obj.getTx();
double ty = obj.getTy();
ball.setLayoutX(ball.getLayoutX() + dx*tx);
ball.setLayoutY(ball.getLayoutY() + dy*ty);
//If the ball reaches the left or right border make the step negative
if (ball.getLayoutX() <= (bounds.getMinX() + ball.getRadius()) ||
ball.getLayoutX() >= (bounds.getMaxX() - ball.getRadius())) {
obj.setTx(-tx);
}
//If the ball reaches the bottom or top border make the step negative
if ((ball.getLayoutY() >= (bounds.getMaxY() - ball.getRadius())) ||
(ball.getLayoutY() <= (bounds.getMinY() + ball.getRadius()))) {
obj.setTy(-ty);
}
});
}
class Ball {
Circle ball = new Circle(10, Color.RED);
double tx = 1;
double ty = 1;
public Ball(){
// Placing the ball at a random location between 0,0 and 10,10 to generate random paths
ball.relocate(rnd.nextInt(10), rnd.nextInt(10));
}
public Circle getBall() {
return ball;
}
public double getTx() {
return tx;
}
public void setTx(double tx) {
this.tx = tx;
}
public double getTy() {
return ty;
}
public void setTy(double ty) {
this.ty = ty;
}
}
}
There are a few options, java.util.concurrent.TimeUnit or Thread.sleep.
You may want to look at both, and if you need two operations going at once (creating new balls, balls moving) I would look into using a new thread for each ball.
You can read more about using threads by using these articles, and your simple program actually seems like a great use case for learning about them.
https://www.geeksforgeeks.org/multithreading-in-java/
https://www.tutorialspoint.com/java/java_multithreading.htm
https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html
I have 2 rectangles representing the paddles for my Pong game. I use W/S for one rectangle and UP/DOWN for the second rectangle. When I press W to move one rectangle and then press UP to move the second rectangle, the first rectangle will stop moving and then the second rectangle will move. How do I make it so both rectangles can move simultaneously?
GraphicsContext gc;
Rectangle player11;
Rectangle player22;
Circle ball;
private int y1;
private int p1y = 381;
private int y2;
private int p2y = 381;
AnimateObjects animate;
Canvas canvas;
private AnimationTimer timer = new AnimationTimer() {
public void handle(long now) {
// update paddle positions
p1y += y1;
p2y += y2;
if (p1y < 0) {
p1y = 0;
}
if (p2y < 0) {
p2y = 0;
}
player11.setY(p1y);
player22.setY(p2y);
}
};
public EventHandler<KeyEvent> keyReleased = new EventHandler<KeyEvent>() {
public void handle(KeyEvent event) {
// set movement to 0, if the released key was responsible for the paddle
switch (event.getCode()) {
case W:
case S:
y1 = 0;
break;
case UP:
case DOWN:
y2 = 0;
break;
}
}
};
private EventHandler<KeyEvent> keyPressed = new EventHandler<KeyEvent>() {
public void handle(KeyEvent event) {
// start movement according to key pressed
switch (event.getCode()) {
case W:
y1 = -6;
break;
case S:
y1 = 6;
break;
case UP:
y2 = -6;
break;
case DOWN:
y2 = 6;
break;
}
}
};
public static void main(String[] args) {
launch();
}//main
public void start(Stage stage) {
stage.setTitle("Pong");
Group root = new Group();
canvas = new Canvas(1000, 800);
root.getChildren().add(canvas);
Scene scene = new Scene(root, Color.GREEN);
stage.setScene(scene);
player11 = new Rectangle(30, p1y, 20, 70);
player11.setFill(Color.RED);
player22 = new Rectangle(750, p2y, 20, 70);
player22.setFill(Color.BLUE);
root.getChildren().add(player11);
root.getChildren().add(player22);
scene.setOnKeyPressed(keyPressed);
scene.setOnKeyReleased(keyReleased);
ball = new Circle(10, Color.DARKSLATEBLUE);
root.getChildren().add(ball);
ball.relocate(500,350);
gc = canvas.getGraphicsContext2D();
animate = new AnimateObjects();
animate.start();
stage.show();
}//start
public class AnimateObjects extends AnimationTimer {
public void handle(long now) {
}//handle method in AnimateObjects class
}//AnimateObjects class
}//pong class
You need to capture KeyCodes. Use up, down, z, and x to control paddles. The right paddle will not move beyond the upper and lower bounds of the gameboard. The left paddle will. More comments in the code! Here is a working example.
import java.util.HashSet;
import java.util.Set;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class App extends Application
{
Rectangle leftPaddle, rightPaddle;
AnimationTimer gameLoop;
Set<KeyCode> input;
Pane gameBoard;
#Override
public void start(Stage primaryStage)
{
leftPaddle = new Rectangle(7, 100, Color.BLACK);
leftPaddle.setX(3);
leftPaddle.setY(0);
rightPaddle = new Rectangle(7, 100, Color.BLACK);
rightPaddle.setX(500 - 10);
rightPaddle.setY(0);
input = new HashSet(); //This set is used to keep up with keys that are currently being pressed.
gameBoard = new Pane(leftPaddle, rightPaddle);
VBox.setVgrow(gameBoard, Priority.ALWAYS);
gameBoard.setOnKeyPressed(event -> input.add(event.getCode()));//add keys that are currently being pressed to the set
gameBoard.setOnKeyReleased(event -> input.remove(event.getCode()));//remove keys from the set after they are released
gameLoop = new AnimationTimer()
{
#Override
public void handle(long l)
{
movePaddle();//Call method to move paddle based on certain keys in the set
//System.out.println("playing");
}
};
Button btnStartGame = new Button("Play");
btnStartGame.setMaxWidth(Double.MAX_VALUE);
btnStartGame.setOnAction((event) -> {
gameBoard.requestFocus();//Request gameboard focus to capture keyevents
gameLoop.start();
btnStartGame.setDisable(true);
});
VBox root = new VBox(gameBoard, btnStartGame);
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
//Use to move paddles based on keycodes in set
private void movePaddle()
{
if (input.contains(KeyCode.UP)) {
rightPaddle.setY(rightPaddle.getY() - 10);
if (rightPaddle.getY() < 0) {
rightPaddle.setY(0);
}
}
else if (input.contains(KeyCode.DOWN)) {
rightPaddle.setY(rightPaddle.getY() + 10);
if (rightPaddle.getY() + rightPaddle.getHeight() > gameBoard.getHeight()) {
rightPaddle.setY(gameBoard.getHeight() - rightPaddle.getHeight());
}
}
if (input.contains(KeyCode.Z)) {
leftPaddle.setY(leftPaddle.getY() - 10);
}
else if (input.contains(KeyCode.X)) {
leftPaddle.setY(leftPaddle.getY() + 10);
}
}
}
I want to make a program in javafx in which when a button is pressed rectangle or circle is created with random dimension and color. I have written code but facing some problem. In it when I'm clicking button, figure is appearing but when i click it again figure stays same. After 3-4 clicks figure is changing.
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.text.*;
import javafx.scene.image.*;
import javafx.collections.*;
import javafx.geometry.*;
import javafx.event.*;
import java.util.*;
public class ranshap extends Application
{
double x1,y1,x2,y2;
Rectangle rectangle;
Circle circle;
Color rectangleColor;
Color circleColor;
Button b;
Pane pane;
Scene scene;
Stage stage;
public void start (Stage stage)
{
stage.setTitle("Drawing Rectangle and Circle");
CreateRect c= new CreateRect();
b= new Button("Click me");
b.setOnAction(c);
rectangle = new Rectangle();
circle = new Circle();
/*rectangle.setX(50);
rectangle.setY(50);
rectangle.setWidth(200);
rectangle.setHeight(50);
rectangleColor= new Color(0.0,0.8,0.2,0.6);
rectangle.setFill(rectangleColor);*/
pane= new Pane();
pane.getChildren().add(b);
scene= new Scene(pane,500,300);
stage.setScene(scene);
stage.show();
}
private class CreateRect implements EventHandler <ActionEvent>
{
public void handle(ActionEvent e)
{
double width,height,r,g,b,o,s,radius;
Random generator= new Random();
s=generator.nextDouble();
if(s>0.5)
{
pane.getChildren().remove(circle);
pane.getChildren().add(rectangle);
width= generator.nextDouble()*100;
height= generator.nextDouble()*100;
r= generator.nextDouble();
g= generator.nextDouble();
b= generator.nextDouble();
o= generator.nextDouble();
rectangle.setX(50);
rectangle.setY(50);
rectangle.setWidth(width);
rectangle.setHeight(height);
rectangleColor= new Color(r,g,b,o);
rectangle.setFill(rectangleColor);
}
else
{
pane.getChildren().remove(rectangle);
pane.getChildren().add(circle);
radius=generator.nextDouble()*100;
circle.setCenterX (100);
circle.setCenterY (100);
circle.setRadius (radius);
r= generator.nextDouble();
g= generator.nextDouble();
b= generator.nextDouble();
o= generator.nextDouble();
circleColor= new Color(r,g,b,o);
circle.setFill(circleColor);
}
}
}
}
Actrully, you add the same children twice. Using the following code
public class ranshap extends Application {
double x1, y1, x2, y2;
Rectangle rectangle;
Circle circle;
Color rectangleColor;
Color circleColor;
Button b;
Pane pane;
Scene scene;
Stage stage;
#Override
public void start(Stage stage) {
stage.setTitle("Drawing Rectangle and Circle");
CreateRect c = new CreateRect();
b = new Button("Click me");
b.setOnAction(c);
rectangle = new Rectangle();
circle = new Circle();
/*
* rectangle.setX(50); rectangle.setY(50); rectangle.setWidth(200); rectangle.setHeight(50); rectangleColor= new
* Color(0.0,0.8,0.2,0.6); rectangle.setFill(rectangleColor);
*/
pane = new Pane();
pane.getChildren().add(b);
scene = new Scene(pane, 500, 300);
stage.setScene(scene);
stage.show();
}
private class CreateRect implements EventHandler<ActionEvent> {
#Override
public void handle(ActionEvent e) {
double width, height, r, g, b, o, s, radius;
Random generator = new Random();
s = generator.nextDouble();
pane.getChildren().remove(rectangle);
pane.getChildren().remove(circle);
if (s > 0.5) {
pane.getChildren().add(rectangle);
width = generator.nextDouble() * 100;
height = generator.nextDouble() * 100;
r = generator.nextDouble();
g = generator.nextDouble();
b = generator.nextDouble();
o = generator.nextDouble();
rectangle.setX(50);
rectangle.setY(50);
rectangle.setWidth(width);
rectangle.setHeight(height);
rectangleColor = new Color(r, g, b, o);
rectangle.setFill(rectangleColor);
} else {
pane.getChildren().add(circle);
radius = generator.nextDouble() * 100;
circle.setCenterX(100);
circle.setCenterY(100);
circle.setRadius(radius);
r = generator.nextDouble();
g = generator.nextDouble();
b = generator.nextDouble();
o = generator.nextDouble();
circleColor = new Color(r, g, b, o);
circle.setFill(circleColor);
}
}
}
public static void main(String[] args) {
launch(args);
}
}
My problem is that I have a rectangle presented with a small perspective, and I would like to stretch it back to be presented as a rectangle again.
To represent it visually, I currently have within my image something like the red shape, and I have 4 Points (each corner of this shape). As result I would like to have something like the blue shape, and I already have the Rectangle object for it.
I was wondering if there is a method to copy a polygon and draw it as another polygon stretched. I found something for Android (setPolyToPoly), but I couldn't find something like this for java.
Is there some reference or code sample that performs this operation, or maybe some idea how can I solve this problem?
I think I understand what you need: a so-called perspective transformation that can be applied to an image. Java has the built-in AffineTransform, but an affine transform always preserves the "parallelness" of lines, so you cannot use that.
Now if you search the web for "java perspective transformation", you will find lots of options like the JavaFX PerspectiveTransform, the JAI PerspectiveTransform. If you only need to stretch images, you can also use the JHLabs PerspectiveFilter and there are other options as well.
Here is some code that will turn stretch a polygon with four points to a rectangle.
public static Rectangle2D polyToRect(Polygon polygon) {
if (polygon.xpoints.length != 4 || polygon.ypoints.length != 4)
throw new IllegalArgumentException(
"More than four points, this cannot be fitted to a rectangle");
Rectangle2D rect = new Rectangle2D.Double();
for (int i = 0; i < 4; i++) {
Point2D point = new Point2D.Double(polygon.xpoints[i],
polygon.ypoints[i]);
rect.add(point);
}
return rect;
}
public static Polygon rectangleToPolygon(Rectangle2D rect) {
Polygon poly = new Polygon();
poly.addPoint((int) rect.getX(), (int) rect.getY());
poly.addPoint((int) (rect.getX() + rect.getWidth()), (int) rect.getY());
poly.addPoint((int) (rect.getX() + rect.getWidth()),
(int) (rect.getY() + rect.getHeight()));
poly.addPoint((int) rect.getX(), (int) (rect.getY() + rect.getHeight()));
return poly;
}
public static class drawPolyAndRect extends JPanel {
Polygon poly = new Polygon();
public drawPolyAndRect() {
poly.addPoint(0, 0);
poly.addPoint(400, 40);
poly.addPoint(400, 250);
poly.addPoint(0, 400);
}
#Override
#Transient
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.green);
g2d.fill(poly);
Composite c = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
0.5f);
g2d.setColor(Color.blue);
g2d.setComposite(c);
Rectangle2D polyToRect = polyToRect(poly);
g2d.fill(polyToRect);
// displace for drawing
polyToRect.setFrame(polyToRect.getX() + 100,
polyToRect.getY() + 100, polyToRect.getWidth(),
polyToRect.getHeight());
Polygon polyToRectToPoly = rectangleToPolygon(polyToRect);
g2d.fill(polyToRectToPoly);
g2d.dispose();
}
}
public static void main(String[] args) {
JFrame frame = new JFrame("Poly to rect");
frame.getContentPane().add(new drawPolyAndRect());
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
Essentially this uses how adding points to an empty rectangle2d works, it constructs the smallest possible rectangle containing all four points, stretching the polygon. Check the second static method if you want the returned rectangle as a polygon. Picture:
A JavaFX based solution using a PerspectiveTransform as suggested by #lbalazscs answer.
Toggle Perspective switches on and off the perspective effect on the content.
Morph Perspective smoothly animates a transition between the perspective transformed and non-perspective transformed content.
import javafx.animation.*;
import javafx.application.*;
import javafx.beans.value.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.ToggleButton;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.image.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.*;
import javafx.stage.Stage;
import javafx.util.Duration;
public class PerspectiveMovement extends Application {
// perspective transformed group width and height.
private final int W = 280;
private final int H = 96;
// upper right and lower right co-ordinates of perspective transformed group.
private final int URY = 35;
private final int LRY = 65;
#Override public void start(Stage stage) {
final PerspectiveTransform perspectiveTransform = createPerspectiveTransform();
final Group group = new Group();
group.setCache(true);
setContent(group);
final ToggleButton perspectiveToggle = createToggle(
group,
perspectiveTransform
);
VBox layout = new VBox(10);
layout.setAlignment(Pos.CENTER);
layout.getChildren().setAll(
perspectiveToggle,
createMorph(perspectiveToggle, group, perspectiveTransform),
group
);
layout.setStyle("-fx-padding: 10px; -fx-background-color: rgb(17, 20, 25);");
stage.setScene(new Scene(layout));
stage.show();
}
private void setContent(Group group) {
Rectangle rect = new Rectangle(0, 5, W, 80);
rect.setFill(Color.web("0x3b596d"));
Text text = new Text();
text.setX(4.0);
text.setY(60.0);
text.setText("A long time ago");
text.setFill(Color.ALICEBLUE);
text.setFont(Font.font(null, FontWeight.BOLD, 36));
Image image = new Image(
"http://icons.iconarchive.com/icons/danrabbit/elementary/96/Star-icon.png"
);
ImageView imageView = new ImageView(image);
imageView.setX(50);
group.getChildren().addAll(rect, imageView, text);
}
private PerspectiveTransform createPerspectiveTransform() {
PerspectiveTransform perspectiveTransform = new PerspectiveTransform();
perspectiveTransform.setUlx(0.0);
perspectiveTransform.setUly(0.0);
perspectiveTransform.setUrx(W);
perspectiveTransform.setUry(URY);
perspectiveTransform.setLrx(W);
perspectiveTransform.setLry(LRY);
perspectiveTransform.setLlx(0.0);
perspectiveTransform.setLly(H);
return perspectiveTransform;
}
private ToggleButton createToggle(final Group group, final PerspectiveTransform perspectiveTransform) {
final ToggleButton toggle = new ToggleButton("Toggle Perspective");
toggle.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override public void changed(ObservableValue<? extends Boolean> observable, Boolean wasSelected, Boolean selected) {
if (selected) {
perspectiveTransform.setUry(URY);
perspectiveTransform.setLry(LRY);
group.setEffect(perspectiveTransform);
} else {
group.setEffect(null);
}
}
});
return toggle;
}
private ToggleButton createMorph(final ToggleButton perspectiveToggle, final Group group, final PerspectiveTransform perspectiveTransform) {
final Timeline distorter = new Timeline(
new KeyFrame(
Duration.seconds(0),
new KeyValue(perspectiveTransform.uryProperty(), 0, Interpolator.LINEAR),
new KeyValue(perspectiveTransform.lryProperty(), H, Interpolator.LINEAR)
),
new KeyFrame(
Duration.seconds(3),
new KeyValue(perspectiveTransform.uryProperty(), URY, Interpolator.LINEAR),
new KeyValue(perspectiveTransform.lryProperty(), LRY, Interpolator.LINEAR)
)
);
final ToggleButton morphToggle = new ToggleButton("Morph Perspective");
morphToggle.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override public void changed(ObservableValue<? extends Boolean> observable, Boolean wasSelected, Boolean selected) {
if (!perspectiveToggle.isSelected()) {
perspectiveToggle.fire();
}
if (selected) {
distorter.setRate(1);
distorter.play();
} else {
distorter.setRate(-1);
distorter.play();
}
}
});
return morphToggle;
}
}
I don't know if this could help you but let me give you what i made with what i understand:
import javax.swing.*;
import java.awt.*;
public class PolyToRectangle extends JPanel {
public static final int SPEED = 50; //less = more fast.
private int ax = 0, bx = 800, cx = 800, dx = 0,
ay = 0, by = 40, cy = 250, dy = 400;
private Polygon poly;
public PolyToRectangle() {
setPreferredSize(new Dimension(1200, 720));
poly = new Polygon(new int[]{ax, bx, cx, dx}, new int[]{ay, by, cy, dy}, 4);
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.draw(poly);
g2d.fill(poly);
}
public void polyToRectangle() throws InterruptedException {
int flag = 0;
for (int i = 0; i < 150; i++) {
flag++;
poly.addPoint(ax, ay);
poly.addPoint(bx, (by = flag % 3 == 0 ? --by : by));
poly.addPoint(cx, cy++);
poly.addPoint(dx, dy);
Thread.sleep(SPEED);
repaint();
}
}
protected void clear(Graphics g) {
super.paintComponent(g);
}
public static void main(String[] args) throws InterruptedException {
Frame frame = new JFrame();
PolyToRectangle se = new PolyToRectangle();
frame.add(se);
frame.pack();
frame.setVisible(true);
se.polyToRectangle();
}
}
Ok as you can see this code is more a "PolyToSquare" more than PolyToRectangle, but the main was to show the "effect" of repainting with a Thread.sleep in a for, maybe that's the "visual stretch" you are talking about, note that the numbers of iterations on the for depends on numbers of pixels from point 1 and 2 to "stretch" from polygon to rectangle, this is a "hand made" effect, maybe what #lbalazscs is suggesting is the best solution, hope to be helpful, regards.
EDIT: edited the code to be more clean and to follow in a more specific way your goal
(now is more a PolyToRectangle, fixed the bx and cx values).