Reflect Geometric Node around arbitrary axis in JavaFX - javafx

Is there a way in javafx to reflect/mirror a geometric node (line for example) around an arbitrary axis-- one that is user-defined?
I see rotation, scale, shear, and translation methods, but no reflect(mirror).
I stumpled upon a post that suggests using alignment Left to right , or Right to left, but it is not quit what I am looking for.
I didn't build the application yet to post sample code, as I am still looking for quite a few libraries before starting.
Any tips would be great from the community!
EDIT: Not sure how to add images to comments. I tried the code by James below, I get the result in the image below, but I am trying to get it where noted in read.

If you have a line segment defined by two points p1 and p2, then reflecting in that line segment is equivalent to
Translate p1 to the origin (T)
Rotate p2 to the positive x-axis (R)
Reflect in the x-axis (which you can do with a scale with x=1 and y=-1)
Inverse of R
Inverse of T
So this looks something like
private Transform createReflection(Point2D p1, Point2D p2) {
Translate translation = new Translate(-p1.getX(), -p1.getY());
double deltaX = p2.getX() - p1.getX();
double deltaY = p2.getY() - p1.getY();
Rotate rotate = new Rotate(-Math.toDegrees(Math.atan2(deltaY, deltaX)));
Scale scale = new Scale(1, -1);
Affine reflection = new Affine();
reflection.append(translation.createInverse());
try {
// not sure how a rotation could possibly be non-invertible:
reflection.append(rotate.createInverse());
} catch (NonInvertibleTransformException e) {
throw new RuntimeException(e);
}
reflection.append(scale);
reflection.append(rotate);
reflection.append(translation);
return reflection ;
}
SSCCE:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.scene.transform.Affine;
import javafx.scene.transform.NonInvertibleTransformException;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class ArbitraryReflection extends Application {
#Override
public void start(Stage primaryStage) {
Text original = new Text("Through the looking glass");
original.relocate(200, 350);
Text reflected = new Text("Through the looking glass");
reflected.relocate(200, 350);
Group reflectedGp = new Group(reflected);
Point2D start = new Point2D(0, 400);
Point2D end = new Point2D(400, 150);
Line axis = new Line(start.getX(), start.getY(), end.getX(), end.getY());
Pane root = new Pane(original, reflectedGp, axis);
reflectedGp.getTransforms().add(createReflection(start, end));
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private Transform createReflection(Point2D p1, Point2D p2) {
Translate translation = new Translate(-p1.getX(), -p1.getY());
double deltaX = p2.getX() - p1.getX();
double deltaY = p2.getY() - p1.getY();
Rotate rotate = new Rotate(-Math.toDegrees(Math.atan2(deltaY, deltaX)));
Scale scale = new Scale(1, -1);
Affine reflection = new Affine();
reflection.append(translation.createInverse());
try {
// not sure how a rotation could possibly be non-invertible:
reflection.append(rotate.createInverse());
} catch (NonInvertibleTransformException e) {
throw new RuntimeException(e);
}
reflection.append(scale);
reflection.append(rotate);
reflection.append(translation);
return reflection ;
}
public static void main(String[] args) {
launch(args);
}
}

Related

Javafx 3D: 3DShape material lost translucency and white colouring surrounding sphere

I have the following piece of code that displays a sphere inside a box.
package com.example.animation3d;
import javafx.application.Application;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class Camera3D extends Application {
public static final int WIDTH = 1280;
public static final int HEIGHT = 720;
public static final int ROTATE_SENS = 20;
public static final int ZOOM_SENS = 20;
public Rotate yRotate;
public double cursorX;
#Override
public void start(Stage stage) throws Exception {
Box box = new Box(5, 10, 5);
PhongMaterial material = new PhongMaterial();
material.setDiffuseColor(Color.web("rgba(34, 139, 34, 0.5)"));
box.setMaterial(material);
Sphere sphere = new Sphere(0.5);
sphere.setMaterial(new PhongMaterial(Color.RED));
Group group = new Group();
group.getChildren().addAll(box, sphere);
Translate pivot = new Translate();
yRotate = new Rotate(0, Rotate.Y_AXIS);
// Create and position camera
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.getTransforms().addAll (
pivot,
yRotate,
new Rotate(-20, Rotate.X_AXIS),
new Translate(0, 0, -50)
);
Scene scene = new Scene(group, WIDTH, HEIGHT);
scene.setFill(Color.ALICEBLUE);
scene.setCamera(camera);
stage.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
switch (event.getCode()) {
case W:
camera.translateZProperty().set(camera.getTranslateZ() + 10);
break;
case S:
camera.translateZProperty().set(camera.getTranslateZ() - 10);
break;
case A:
yRotate.angleProperty().set(yRotate.getAngle() + 10);
break;
case D:
yRotate.angleProperty().set(yRotate.getAngle() - 10);
break;
case UP:
sphere.setTranslateY(sphere.getTranslateY() - 1);
break;
case DOWN:
sphere.setTranslateY(sphere.getTranslateY() + 1);
break;
case LEFT:
sphere.setTranslateX(sphere.getTranslateX() - 1);
break;
case RIGHT:
sphere.setTranslateX(sphere.getTranslateX() + 1);
break;
}
});
stage.setTitle("Camera");
stage.setScene(scene);
stage.show();
}
}
However, when I run the program,
Translucent effect gradually lost as I move the sphere. I have added a material to the box to make it look translucent. However, when I move the sphere with arrow keys, the box seems to become opaque again, which I do not want.
Whitish background surrounding the sphere as I move it. When the sphere is moved with arrow keys, a whitish colouring surrounds the sphere and trails behind it, which I do not want.
How do these problems arise, and how can they be fixed? Thanks in advance!
I was able to reproduce your finding, although it appeared to be a trail of altered transparency following the sphere as it moved; the trail was comprised of rectangles, each approximating the sphere's boundary.
With reference to this answer, I changed the order in which the objects were added to the group:
group.getChildren().addAll(sphere, box);
Empirically, like you, I noticed that the trails disappeared if I rotated the box slightly between each move of the sphere:
yRotate.angleProperty().set(yRotate.getAngle() + 0.1);
yRotate.angleProperty().set(yRotate.getAngle() - 0.1);
A variation of your example to work around the problem is shown below. I'm guessing that it forces a depth recalculation, as suggested here, but I'd welcome any addition insight.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class Camera3D extends Application {
public static final int WIDTH = 500;
public static final int HEIGHT = 500;
public Rotate yRotate;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) throws Exception {
var box = new Box(5, 10, 5);
var material = new PhongMaterial(Color.web("#20902080"));
box.setMaterial(material);
var sphere = new Sphere(3);
sphere.setMaterial(new PhongMaterial(Color.RED));
Group group = new Group();
group.getChildren().addAll(sphere, box);
Translate pivot = new Translate();
yRotate = new Rotate(0, Rotate.Y_AXIS);
// Create and position camera
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.getTransforms().addAll(
pivot,
yRotate,
new Rotate(-20, Rotate.X_AXIS),
new Translate(0, 0, -30)
);
Scene scene = new Scene(group, WIDTH, HEIGHT);
scene.setFill(Color.ALICEBLUE);
scene.setCamera(camera);
stage.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
switch (event.getCode()) {
case W ->
camera.translateZProperty().set(camera.getTranslateZ() + 10);
case S ->
camera.translateZProperty().set(camera.getTranslateZ() - 10);
case A ->
yRotate.angleProperty().set(yRotate.getAngle() + 10);
case D ->
yRotate.angleProperty().set(yRotate.getAngle() - 10);
case UP -> {
sphere.setTranslateY(sphere.getTranslateY() - 1);
adjust();
}
case DOWN -> {
sphere.setTranslateY(sphere.getTranslateY() + 1);
adjust();
}
case LEFT -> {
sphere.setTranslateX(sphere.getTranslateX() - 1);
adjust();
}
case RIGHT -> {
sphere.setTranslateX(sphere.getTranslateX() + 1);
adjust();
}
}
});
stage.setTitle("Camera");
stage.setScene(scene);
stage.show();
}
private void adjust() {
yRotate.angleProperty().set(yRotate.getAngle() + 0.1);
yRotate.angleProperty().set(yRotate.getAngle() - 0.1);
}
}

Erasing Antialiased Shapes from a JavaFX Canvas

I have inherited a simulation program to extend with new features. The original was written as an Applet using the AWT library for graphics. Before adding the new features I want to adapt the program to the desktop and use JavaFX instead of AWT.
The simulation paints hundreds or thousands of objects dozens of times per second, then erases them and repaints them at new locations, effectively animating them. I am using a Canvas object for that part of the UI. Erasing is done by repainting the object with the background color. What I am seeing though is that erasing objects is incomplete. A kind of "halo" gets left behind though.
The following program illustrates the problem. Clicking the "Draw" button causes it to draw a few hundred circles on the Canvas using the foreground color. After drawing, clicking the button again will erase the circles by re-drawing them in the background color. Multiple cycles of draw/erase will build up a visible background of "ghost" images.
package com.clartaq.antialiasingghosts;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
static final int NUM_CIRCLES = 500;
static final int CIRCLE_DIAMETER = 10;
static final double PANEL_WIDTH = 75.0;
static final double PANEL_HEIGHT = 40.0;
static final Color FG_COLOR = Color.rgb(10, 0, 200);
static final Color BG_COLOR = Color.rgb(255, 255, 255);
static final double BUTTON_WIDTH = 50.0;
GraphicsContext gc;
Random rand = new Random();
double[] px = new double[NUM_CIRCLES];
double[] py = new double[NUM_CIRCLES];
void randomizeParticlePositions() {
for (int i = 0; i < NUM_CIRCLES; i++) {
px[i] = rand.nextDouble() * PANEL_WIDTH;
py[i] = rand.nextDouble() * PANEL_HEIGHT;
}
}
void drawCircles(Color color) {
gc.setFill(color);
for (int i = 0; i < NUM_CIRCLES; i++) {
var screenX = px[i] * CIRCLE_DIAMETER;
var screenY = py[i] * CIRCLE_DIAMETER;
gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
}
#Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");
Label versionLabel = new Label("JavaFX " + javafxVersion
+ ", running on Java " + javaVersion + ".");
double canvasWidth = (PANEL_WIDTH * CIRCLE_DIAMETER);
double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
Canvas canvasRef = new Canvas(canvasWidth, canvasHeight);
gc = canvasRef.getGraphicsContext2D();
Button deBtn = new Button("Draw");
deBtn.setPrefWidth(BUTTON_WIDTH);
deBtn.setOnAction(e -> {
String txt = deBtn.getText();
switch (txt) {
case "Draw" -> {
randomizeParticlePositions();
drawCircles(FG_COLOR);
deBtn.setText("Erase");
}
case "Erase" -> {
drawCircles(BG_COLOR);
deBtn.setText("Draw");
}
default -> Platform.exit();
}
});
Button exBtn = new Button("Exit");
exBtn.setPrefWidth(BUTTON_WIDTH);
exBtn.setOnAction(e -> Platform.exit());
TilePane tp = new TilePane();
tp.setAlignment(Pos.CENTER);
tp.setHgap(10);
tp.getChildren().addAll(deBtn, exBtn);
VBox root = new VBox();
root.setPadding(new Insets(7));
root.setSpacing(10);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(versionLabel, canvasRef, tp);
StackPane sp = new StackPane(root);
BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
Background bg = new Background(bf);
sp.setBackground(bg);
Scene scene = new Scene(sp, 640.0, 480.0);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
I can get good erasure by expanding the diameter of the circles by 2 pixels when erasing. Of course, that can affect nearby shapes too.
Also, using the fillRect method to erase the entire Canvas seems reasonable, but that means everything has to be re-drawn if anything has to be re-drawn. I suppose it is possible to optimize the re-draw by erasing and re-drawing a smaller section of the Canvas but I don't want to do that if it isn't necessary.
Magnifying sections of the program display shows that it is really an antialiasing effect. Constructing the Scene with the SceneAntialiasing.DISABLED parameter does not seem to have any effect.
Attempting to turn off image smoothing as suggested in this question does not help.
Is possible to erase a single shape drawn on a Canvas by re-drawing it in the background color?
I am using Java 17.0.1, JavaFX 17.0.1, and a 5K Mac display if that is relevant.
For expedience, note the difference between fillOval and strokeOval() in the GraphicsContext. You can conditionally erase the outline in drawCircles() as a function of a suitable boolean value:
if (stroke) {
gc.setStroke(BG_COLOR);
gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
Try a few representative shapes, e.g. fillRect, to verify the desired result.
A better alternative, IMO, is to pursue the erase -> render strategy. Complete examples seen here and here may help you establish whether the approach is scalable to your use-case. See also this related examination of resampling artifact.
Expedient approach, as tested:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Random;
public class Main extends Application {
static final int NUM_CIRCLES = 500;
static final int CIRCLE_DIAMETER = 10;
static final double PANEL_WIDTH = 75.0;
static final double PANEL_HEIGHT = 40.0;
static final Color FG_COLOR = Color.rgb(10, 0, 200);
static final Color BG_COLOR = Color.rgb(255, 255, 255);
static final double BUTTON_WIDTH = 50.0;
GraphicsContext gc;
Random rand = new Random();
private boolean stroke;
double[] px = new double[NUM_CIRCLES];
double[] py = new double[NUM_CIRCLES];
void randomizeParticlePositions() {
for (int i = 0; i < NUM_CIRCLES; i++) {
px[i] = rand.nextDouble() * PANEL_WIDTH;
py[i] = rand.nextDouble() * PANEL_HEIGHT;
}
}
void drawCircles(Color color) {
gc.setFill(color);
for (int i = 0; i < NUM_CIRCLES; i++) {
var screenX = px[i] * CIRCLE_DIAMETER;
var screenY = py[i] * CIRCLE_DIAMETER;
gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
if (stroke) {
gc.setStroke(BG_COLOR);
gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}
}
}
#Override
public void start(Stage stage) {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");
Label versionLabel = new Label("JavaFX " + javafxVersion
+ ", running on Java " + javaVersion + ".");
double canvasWidth = (PANEL_WIDTH * CIRCLE_DIAMETER);
double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
Canvas canvasRef = new Canvas(canvasWidth, canvasHeight);
gc = canvasRef.getGraphicsContext2D();
Button deBtn = new Button("Draw");
deBtn.setPrefWidth(BUTTON_WIDTH);
deBtn.setOnAction(e -> {
String txt = deBtn.getText();
switch (txt) {
case "Draw" -> {
randomizeParticlePositions();
drawCircles(FG_COLOR);
deBtn.setText("Erase");
stroke = true;
}
case "Erase" -> {
drawCircles(BG_COLOR);
deBtn.setText("Draw");
stroke = false;
}
default ->
Platform.exit();
}
});
Button exBtn = new Button("Exit");
exBtn.setPrefWidth(BUTTON_WIDTH);
exBtn.setOnAction(e -> Platform.exit());
TilePane tp = new TilePane();
tp.setAlignment(Pos.CENTER);
tp.setHgap(10);
tp.getChildren().addAll(deBtn, exBtn);
VBox root = new VBox();
root.setPadding(new Insets(7));
root.setSpacing(10);
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(versionLabel, canvasRef, tp);
StackPane sp = new StackPane(root);
BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
Background bg = new Background(bf);
sp.setBackground(bg);
Scene scene = new Scene(sp, 640.0, 480.0);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}

lost in 3D space - tilt values (euler?) from rotation matrix (javafx affine) only works partially

it is a while ago that I asked this question:
javafx - How to apply yaw, pitch and roll deltas (not euler) to a node in respect to the nodes rotation axes instead of the scene rotation axes?
Today I want to ask, how I can get the tilt (fore-back and sideways) relative to the body (not to the room) from the rotation matrix. To make the problem understandable, I took the final code from the fantastic answer of José Pereda and basicly added a method "getEulersFromRotationMatrix". This is working a bit, but at some point freaks out.
Attached find the whole working example. The problem becomes clear with the following click path:
// right after start
tilt fore
tilt left // all right
tilt right
tilt back // all right
// right after start
turn right
turn right
turn right
tilt fore
tilt back // all right
tilt left // bang, tilt values are completely off
While the buttons move the torso as expected, the tilt values (printed out under the buttons) behave wrong at some point.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
public class PuppetTestApp extends Application {
private final int width = 800;
private final int height = 500;
private XGroup torsoGroup;
private final double torsoX = 50;
private final double torsoY = 80;
private Label output = new Label();
public Parent createRobot() {
Box torso = new Box(torsoX, torsoY, 20);
torso.setMaterial(new PhongMaterial(Color.RED));
Box head = new Box(20, 20, 20);
head.setMaterial(new PhongMaterial(Color.YELLOW.darker()));
head.setTranslateY(-torsoY / 2 -10);
Box x = new Box(200, 2, 2);
x.setMaterial(new PhongMaterial(Color.BLUE));
Box y = new Box(2, 200, 2);
y.setMaterial(new PhongMaterial(Color.BLUEVIOLET));
Box z = new Box(2, 2, 200);
z.setMaterial(new PhongMaterial(Color.BURLYWOOD));
torsoGroup = new XGroup();
torsoGroup.getChildren().addAll(torso, head, x, y, z);
return torsoGroup;
}
public Parent createUI() {
HBox buttonBox = new HBox();
Button b;
buttonBox.getChildren().add(b = new Button("Exit"));
b.setOnAction( (ActionEvent arg0) -> { Platform.exit(); } );
buttonBox.getChildren().add(b = new Button("tilt fore"));
b.setOnAction(new TurnAction(torsoGroup.rx, 15) );
buttonBox.getChildren().add(b = new Button("tilt back"));
b.setOnAction(new TurnAction(torsoGroup.rx, -15) );
buttonBox.getChildren().add(b = new Button("tilt left"));
b.setOnAction(new TurnAction(torsoGroup.rz, 15) );
buttonBox.getChildren().add(b = new Button("tilt right"));
b.setOnAction(new TurnAction(torsoGroup.rz, -15) );
buttonBox.getChildren().add(b = new Button("turn left"));
b.setOnAction(new TurnAction(torsoGroup.ry, -28) ); // not 30 degree to avoid any gymbal lock problems
buttonBox.getChildren().add(b = new Button("turn right"));
b.setOnAction(new TurnAction(torsoGroup.ry, 28) ); // not 30 degree to avoid any gymbal lock problems
VBox vbox = new VBox();
vbox.getChildren().add(buttonBox);
vbox.getChildren().add(output);
return vbox;
}
class TurnAction implements EventHandler<ActionEvent> {
final Rotate rotate;
double deltaAngle;
public TurnAction(Rotate rotate, double targetAngle) {
this.rotate = rotate;
this.deltaAngle = targetAngle;
}
#Override
public void handle(ActionEvent arg0) {
addRotate(torsoGroup, rotate, deltaAngle);
}
}
private void addRotate(XGroup node, Rotate rotate, double angle) {
Affine affine = node.getTransforms().isEmpty() ? new Affine() : new Affine(node.getTransforms().get(0));
double A11 = affine.getMxx(), A12 = affine.getMxy(), A13 = affine.getMxz();
double A21 = affine.getMyx(), A22 = affine.getMyy(), A23 = affine.getMyz();
double A31 = affine.getMzx(), A32 = affine.getMzy(), A33 = affine.getMzz();
Rotate newRotateX = new Rotate(angle, new Point3D(A11, A21, A31));
Rotate newRotateY = new Rotate(angle, new Point3D(A12, A22, A32));
Rotate newRotateZ = new Rotate(angle, new Point3D(A13, A23, A33));
affine.prepend(rotate.getAxis() == Rotate.X_AXIS ? newRotateX :
rotate.getAxis() == Rotate.Y_AXIS ? newRotateY : newRotateZ);
EulerValues euler= getEulersFromRotationMatrix(affine);
output.setText(String.format("tilt fore/back=%3.0f tilt sideways=%3.0f", euler.forward, euler.leftSide));
node.getTransforms().setAll(affine);
}
public class XGroup extends Group {
public Rotate rx = new Rotate(0, Rotate.X_AXIS);
public Rotate ry = new Rotate(0, Rotate.Y_AXIS);
public Rotate rz = new Rotate(0, Rotate.Z_AXIS);
}
#Override
public void start(Stage stage) throws Exception {
Parent robot = createRobot();
SubScene subScene = new SubScene(robot, width, height, true, SceneAntialiasing.BALANCED);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setNearClip(0.01);
camera.setFarClip(100000);
camera.setTranslateZ(-400);
subScene.setCamera(camera);
Parent ui = createUI();
StackPane combined = new StackPane(ui, subScene);
combined.setStyle("-fx-background-color: linear-gradient(to bottom, cornsilk, midnightblue);");
Scene scene = new Scene(combined, width, height);
stage.setScene(scene);
stage.show();
}
/**
* Shall return the tilt values relative to the body (not relative to the room)
* (Maybe euler angles are not the right term here, but anyway)
*/
private EulerValues getEulersFromRotationMatrix(Affine rot) {
double eulerX; // turn left/right
double eulerY; // tilt fore/back
double eulerZ; // tilt sideways
double r11 = rot.getMxx();
double r12 = rot.getMxy();
double r13 = rot.getMxz();
double r21 = rot.getMyx();
double r31 = rot.getMzx();
double r32 = rot.getMzy();
double r33 = rot.getMzz();
// used instructions from https://www.gregslabaugh.net/publications/euler.pdf
if (r31 != 1.0 && r31 != -1.0) {
eulerX = -Math.asin(r31); // already tried with the 2nd solution as well
double cosX = Math.cos(eulerX);
eulerY = Math.atan2(r32/cosX, r33/cosX);
eulerZ = Math.atan2(r21/cosX, r11/cosX);
}
else {
eulerZ = 0;
if (r31 == -1) {
eulerX = Math.PI / 2;
eulerY = Math.atan2(r12, r13);
}
else {
eulerX = -Math.PI / 2;
eulerY = Math.atan2(-r12, -r13);
}
}
return new EulerValues(
eulerY / Math.PI * 180.0,
eulerZ / Math.PI * 180.0,
-eulerX / Math.PI * 180.0);
}
public class EulerValues {
public double leftTurn;
public double forward;
public double leftSide;
public EulerValues(double forward, double leftSide, double leftTurn) {
this.forward = forward;
this.leftSide = leftSide;
this.leftTurn = leftTurn;
}
}
public static void main(String[] args) {
launch(args);
}
}
PS: This may look like I have close to no progress, but this is only because I try to reduce the question to the possible minimum. If you want to see how this stuff is embedded in my main project, you can watch this little video I just uploaded (but does not add anything to the question): https://www.youtube.com/watch?v=R3t8BIHeo7k
I think I got it by myself now: What I computed was the "default" euler angles, sometimes refered to as z x' z'', where the 1st and 3th rotation is around the same axis. But what I am looking for are the angles that can be applied to the z, y' and x'' achses (in that order) to reach the position presented by the rotation matrix. (and then ignore the z rotation).
Or even better compute the z y' x'' eulers and the z x' y'' eulers and
only use the x' and y' values.
Added:
No, that was wrong. I indeed calculated the Tait-Bryan x y z rotations. So this was not the solution.
Ok, new explanation:
The rotation axes wthat I calculate are room relative rotations (not object relative rotations), and the 2nd rotation is at the vertical axe (which I am not interested in). But because it is "in the middle", it can cancel out the 1st and 3th rotation, and this is what happens.
So the solution should be the change the rotation order, that comes out of my matrix-to-euler algorithm. But how to do this?
I just exchanged all "y" and "z":
r11 = rot.getMxx();
r12 = rot.getMxz();
r13 = rot.getMxy();
r21 = rot.getMzx();
r31 = rot.getMyx();
r32 = rot.getMyz();
r33 = rot.getMyy();
and now it really does what I want. :)

Javafx RotateTransition rendering smooth

I’m making some trials with JavaFX RotateTransition, applying a simple model found on a jfx itself documentation file:
rotateTransition = RotateTransitionBuilder.create()
.node(elements)
.duration(Duration.seconds(4))
.fromAngle(0)
.toAngle(720)
.cycleCount(3)
.autoReverse(true)
.build();
Above, elements is a Group of bare Arc primitives.
When this group has a limited number of nodes, say 20, the animation goes smooth but when I increase the number of nodes to 500 (nested actually, Group of Group) the animation still works but does not result any more fluid.
The question is: does this nodes limit can be considered too much for this task? How to speed up the rendering?
I have found the thread below that in a similar context asserts that could be a matter of using the right Animation class, but I’m not sure that the proposed AnimationTimer does apply well to a rotation.
http://mail.openjdk.java.net/pipermail/openjfx-dev/2013-June/008104.html
I have also tried to use setCache(true) to every node with no visible improvements.
Thank you!
Edit: Arc generation. No strange things but a binder and a EventHandler.
Arc arc = new Arc();
arc.centerXProperty().bind(plotRadiusBinding);
arc.centerYProperty().bind(plotRadiusBinding);
arc.radiusXProperty().bind(plotRadiusBinding);
arc.radiusYProperty().bind(plotRadiusBinding);
arc.setStartAngle(startAngle * 180 / PI);
arc.setLength(radiansLength * 180 / PI);
arc.setType(ArcType.ROUND);
arc.setStroke(defaultArcColor);
arc.setStrokeType(StrokeType.INSIDE);
arc.setFill(null);
arc.setOnMouseClicked(arcEventHandler);
I had similar issues when moving images around when I used KeyFrames (KeyFrames)
I could improve it by using
I had success with implementing the stuff by myself:
Animation animation = new Transition() {
{
setCycleDuration(Duration.millis(1000));
}
#Override
protected void interpolate(double frac) {
// your rotation code here
}
}
If you could provide your arc generation source code it might be helpful.
for me this works flawlessly for 500 arcs:
package application;
import java.util.Random;
import javafx.animation.RotateTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Arc;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Pane root = new Pane();
Group group = new Group();
Random rand = new Random();
for(int i=0; i<1000; i++){
Arc c = new Arc(200 + rand.nextInt(400), 200 + rand.nextInt(400), 10, 10, 0, 360);
group.getChildren().add(c);
}
root.getChildren().add(group);
RotateTransition rotateTransition = new RotateTransition(Duration.millis(5000), group);
rotateTransition.setFromAngle(0);
rotateTransition.setToAngle(720);
rotateTransition.setCycleCount(3);
rotateTransition.setAutoReverse(true);
rotateTransition.play();
Scene scene = new Scene(root,800,800);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
if this is still to laggy and you only want to draw arcs you could directly invoke the graphics-context of a canvas.

The pain with the pane in JavaFX. How can you scale Nodes with fixed Top-Left Corner?

It seems to be a simple problem. But I found no simple solution. If you scale Nodes, the new form will be in the center of the parent. But I would like that the new form has the same Top-Left Corner as the old one.
The expample code is:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class TestScale extends Application{
Group root;
Pane pane;
Scene scene;
Rectangle rect0;
#Override
public void start(Stage stage) {
root = new Group();
scene = new Scene(root, 200, 160);
rect0=new Rectangle(0, 0, 200, 160);
rect0.setFill(Color.BLUE);
pane = new Pane();
pane.getChildren().add(rect0);
Button btnForward = new Button();
btnForward.setText(">");
btnForward.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
transform(pane);
}
});
root.getChildren().add(pane);
root.getChildren().add(btnForward);
stage.setScene(scene);
stage.show();
}
void transform (Node node){
node.setScaleX(0.5);
node.setScaleY(0.5);
}
public static void main(String[] args) {
launch(args);
}
}
All tests with Stackpane, Borderpane, Anchorpane, Groups delivers no easy solution. The only way seems to be with setTransformX and setTransformY. But I need for this a complex calculation of the arguments.
When you use ScaleX/ScaleY, scaling occurs from the center of the node.
From JavaDocs
The pivot point about which the scale occurs is the center of the untransformed layoutBounds.
So, if you want to translate the scaling co-ordinates, you need to take the scaling compression into account when you set the required translation values.
As your current pivot is center, you need to set Translate to a negative value. Since the compression of X and Y is half, so you need to translate to 1/4 of total size of the scene.
node.setScaleX(0.5);
node.setScaleY(0.5);
node.setTranslateX(0 - node.getScene().getWidth()/4);
node.setTranslateY(0 - node.getScene().getHeight()/4);
Here ist the code to transform an rectangle within an image:
The procedure deliver a scalefaktor for setScaleX and setScaleY (scale) and set value tx for setTransformX and ty for setTransformY.
public Scaler(double sceneWidth, double sceneHeight, double imgWidth, double imgHeight,
int x, int y, int width, int height) {
double scrnRatio = sceneHeight / sceneWidth;
double offsetX = 0.;
double offsetY = 0.;
if (height / (double)width > scrnRatio) {
offsetX = (height / scrnRatio - width) / 2.;
scale = sceneHeight/height;
} else {
offsetY = (width * scrnRatio - height) / 2.;
scale = sceneWidth/width;
}
double dh = (1. - scale) / 2.;
tx = -(x - offsetX) * scale - dh * imgWidth;
ty = -(y - offsetY) * scale - dh * imgHeight;
}
There is no way for an easier code?

Resources