How to create fog in JavaFX 3D? - javafx

I am developping a game, in JavaFX 3D, in which the player is running down a long road. There are items and obstacles coming his way. The point is: now the objects just appear out of nowhere if they enter my clipping distance. Therefore I want to create some kind of fog (or anything else that "hides" the initialization of the objects)
The problem is that I cannot find any example in which this is done. I am looking for an example piece of code/link to source/any other advice.

Having said it's too broad a question, and there must be many, many ways to do this, here's one possible implementation. This is 2D but could easily be adapted for a 3D app (I think). The idea is just to let a few light gray circles drift around on a white background, and apply an enormous blur to the whole thing.
You could then let your objects appear with this as a background, and fade them in from gray to their real color (or some such). The colors and velocities of the circles, and the blur radius, probably need some tuning...
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.effect.GaussianBlur;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class FogExample extends Application {
private static final int WIDTH = 600 ;
private static final int HEIGHT = 600 ;
#Override
public void start(Stage primaryStage) {
Fog fog = new Fog(WIDTH, HEIGHT);
Scene scene = new Scene(new StackPane(fog.getView()), WIDTH, HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Fog {
private final int width ;
private final int height ;
private final Pane fog ;
private final Random RNG = new Random();
public Fog(int width, int height) {
this.width = width ;
this.height = height ;
this.fog = new Pane();
Rectangle rect = new Rectangle(0, 0, width, height);
rect.setFill(Color.rgb(0xe0, 0xe0, 0xe0));
fog.getChildren().add(rect);
for (int i = 0; i < 50; i++) {
fog.getChildren().add(createFogElement());
}
fog.setEffect(new GaussianBlur((width + height) / 2.5));
}
private Circle createFogElement() {
Circle circle = new Circle(RNG.nextInt(width - 50) + 25, RNG.nextInt(height - 50) + 25, 15 + RNG.nextInt(50));
int shade = 0xcf + RNG.nextInt(0x20);
circle.setFill(Color.rgb(shade, shade, shade));
AnimationTimer anim = new AnimationTimer() {
double xVel = RNG.nextDouble()*40 - 20 ;
double yVel = RNG.nextDouble()*40 - 20 ;
long lastUpdate = 0 ;
#Override
public void handle(long now) {
if (lastUpdate > 0) {
double elapsedSeconds = (now - lastUpdate) / 1_000_000_000.0 ;
double x = circle.getCenterX() ;
double y = circle.getCenterY() ;
if ( x + elapsedSeconds * xVel > width || x + elapsedSeconds * xVel < 0) {
xVel = - xVel ;
}
if ( y + elapsedSeconds * yVel > height || y + elapsedSeconds * yVel < 0) {
yVel = - yVel ;
}
circle.setCenterX(x + elapsedSeconds*xVel);
circle.setCenterY(y + elapsedSeconds * yVel);
}
lastUpdate = now ;
}
};
anim.start();
return circle ;
}
public Node getView() {
return fog ;
}
}
public static void main(String[] args) {
launch(args);
}
}

Typical 3D systems use a Particle system to do this which involves a combination of transparency, alpha fluctuations, and billboard textures. Believe it or not Fog, smoke and flames... particle effects... are not actually 3D at all but 2D images that have been positioned, sized, and colored to appear in 3D. They are then animated rapidly enough so that the human viewer can't make out the nuances of the 2D images.
The problem with JavaFX 3D is that it does not support transparency "yet".(it is unofficially available). Also there is no particle type object that will let you overlay shapes and textures as James_D suggests without manually managing the positioning with respect to the camera.
However there is hope... the F(X)yz project provides a BillBoard class which will allow you to place an image perpendicular to the camera. You can rapidly update this image using a Timer and some Random.next() type Circle creations for the fog using the approach James_D suggested. An example on using the BillBoard in a manner like this is in BillBoardBehaviourTest and CameraViewTest. Performance is demonstrated in CameraViewTest.
What I would do if I were you is setup a large frustrum wide BillBoard object at the back -Z position of your frustrum (where the objects enter the scene) and then setup a Timer/Random circle generator in a pane off screen. Then using the approach in the CameraViewTest, snapshot the off screen pane (which has the circles) and then set that image to the BillBoard. You will have to have the SnapShot call be on some timer itself to achieve an effect of animation. The CameraViewTest demonstrates how to do that. The effect to the user will be a bunch of moving blurry tiny circles.
Fog on the cheap!

Related

Desaturation effect washes away contrast

I have a pretty specific problem with javaFx's ColorAdjust effect, I'm trying to apply a grayscale filter on an image, I'm using a ColorAdjust effect and setting the saturation
Here is a reproducible example of what I'm trying to do
public class App extends Application {
#Override
public void start(Stage ps) {
Pane root = new Pane();
root.setMinSize(300, 300);
root.setStyle("-fx-background-color: #40444b;");
ImageView view = new ImageView(new Image("https://res.cloudinary.com/mesa-clone/image/upload/v1642936429/1f914_tydc44.png"));
view.setTranslateX(5);
view.setTranslateY(5);
view.setEffect(new ColorAdjust(0, -1, 0, 0));
root.getChildren().add(view);
ps.setScene(new Scene(root));
ps.show();
}
}
now this piece of code does exactly what it's supposed to do, but I'm not satisfied with the result, I want a grayscale filter that behaves similarly to the web css grayscale filter, which produces much better results for my use case :
<html>
<body style="background-color: #40444b;">
<img src="https://res.cloudinary.com/mesa-clone/image/upload/v1642936429/1f914_tydc44.png" style="filter: grayscale(100);">
</body>
</html>
[ Left is javafx, Right is Web (firefox) ]
I know the difference isn't a lot but it's crucial for my use case and I would appreciate if anyone has better ideas to get similar results to the web version of the grayscale filter
IMO, the update you provided with the PixelWriter solution is probably the best you can do, and you should edit the question to remove the update and place the update as an answer.
I realize that the process in the update is slightly more involved than using a ColorAdjust effect.
But I don't think you can do what you want using ColorAdjust.
The grayscale function adjusts colors via RGB multiplication:
double gray = 0.21 * red + 0.71 * green + 0.07 * blue;
return Color.color(gray, gray, gray, opacity);
ColorAdjust adjusts via HSB.
I could be wrong, but I don't think you can provide the HSB params to ColorAdjust to perform the equivalent RGB multiplication.
Potential higher performance solutions
Only spend time on this if you have a performance issue and must have better performance.
You could create your own GrayscaleEffect using the effect pipeline that uses the hardware acceleration and that might be something to look into if you need high performance. But that process is really complicated, it is not easy to create a hardware accelerated effect, there is no documentation for it and to do so you would need to study the existing effect code in the openjfx repository and adapt that for your purposes.
If the hardware accelerated implementation is too tricky, you could use the fork join pool to do the adjustments in parallel for speed up, operating on the byte buffer directly rather than using color objects. The algorithm you need to apply for each pixel is given by the grayscale function implementation. There is a partial example in the answers to:
javafx argb to grayscale conversion
int pixel = pixelReader.getArgb(x, y);
int alpha = ((pixel >> 24) & 0xff);
int red = ((pixel >> 16) & 0xff);
int green = ((pixel >> 8) & 0xff);
int blue = (pixel & 0xff);
int grayLevel = (int) (0.2162 * red + 0.7152 * green + 0.0722 * blue);
int gray = (alpha << 24) + (grayLevel << 16) + (grayLevel << 8) + grayLevel;
grayImage.getPixelWriter().setArgb(x, y, gray);
But that doesn't use the byte buffer and it may be more efficient to work directly on the bytebuffer from the pixel reader/writer, though that is a little trickier.
I might have suggested ColorConvertOp, shown here, but it's marginally slower than PixelWriter and not especially more versatile.
As an illustration of #jewelsea's key insight, note the low contrast between the hand and face in the unadjusted image. Manipulation via ColorAdjust can alter the properties of the image as a whole. Unfortunately, it can't alter the relative contrast of image areas, as shown in your preferred result.
Nevertheless, the example will let you adjust the properties empirically in case you find an acceptable result.
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.control.Tooltip;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/** #see https://stackoverflow.com/q/70821638/230513 */
public class GrayApp extends Application {
private final Insets insets = new Insets(10);
private Slider createSlider(DoubleProperty dp) {
Slider s = new Slider(-1, 1, dp.get());
s.setBlockIncrement(0.1);
s.setTooltip(new Tooltip(dp.getName()));
dp.bind(s.valueProperty());
VBox.setMargin(s, insets);
return s;
}
#Override
public void start(Stage stage) {
ImageView view = new ImageView(new Image(
"https://res.cloudinary.com/mesa-clone/image/upload/v1642936429/1f914_tydc44.png"));
ColorAdjust adjust = new ColorAdjust(0, -1, 0, 0);
view.setEffect(adjust);
Slider hSlider = createSlider(adjust.hueProperty());
Slider sSlider = createSlider(adjust.saturationProperty());
Slider bSlider = createSlider(adjust.brightnessProperty());
Slider cSlider = createSlider(adjust.contrastProperty());
VBox root = new VBox();
root.setPadding(insets);
VBox.setMargin(view, insets);
root.setAlignment(Pos.CENTER);
root.setStyle("-fx-background-color: #808080;");
root.getChildren().addAll(view, hSlider, sSlider, bSlider, cSlider);
stage.setScene(new Scene(root));
stage.show();
}
}
manually converting the image to grayscale using a WritableImage and Color.grayscale() gives better results but it would complicate the process of switching between color and grayscale :
public class App extends Application {
#Override
public void start(Stage ps) {
Pane root = new Pane();
root.setMinSize(300, 300);
root.setStyle("-fx-background-color: #40444b;");
Image image = new Image("https://res.cloudinary.com/mesa-clone/image/upload/v1642936429/1f914_tydc44.png");
ImageView view = new ImageView(grayScale(image));
view.setTranslateX(5);
view.setTranslateY(5);
root.getChildren().add(view);
ps.setScene(new Scene(root));
ps.setTitle("javafx grayscale test");
ps.show();
}
private static Image grayScale(Image img) {
WritableImage res = new WritableImage((int) img.getWidth(), (int) img.getHeight());
PixelReader pr = img.getPixelReader();
PixelWriter pw = res.getPixelWriter();
for (int y = 0; y < img.getHeight(); y++) {
for (int x = 0; x < img.getWidth(); x++) {
pw.setColor(x, y, pr.getColor(x, y).grayscale());
}
}
return res;
}
}
you have the choice of saving the filtered image or generating it every time, depending on whether that trade-off (increased memory usage for increased performance) is worthwhile.

Fixed size JavaFX component

Creating new components in JavaFX is still a but muddy to me compared to "Everything is a JPanel" in Swing.
I'm trying to make a fixed size component. I hesitate to call it a control, it's a pane of activity, not a button.
But here's my problem.
The fixed size I want is smaller than the contents of the element.
The grid is, in truth, 200x200. I'm shifting it up and left 25x25, and I'm trying to make the fixed size of 150x150. You can see in my example I've tried assorted ways of forcing it to 150, but in my tests, the size never sticks. Also, to be clear, I would expect the lines to clip at the boundary of the component.
This is, roughly, what I'm shooting for in my contrived case (note this looks bigger than 150x150 because of the retina display on my Mac, which doubles everything):
I've put some in to a FlowPane, and they stack right up, but ignore the 150x150 dimensions.
FlowPane fp = new FlowPane(new TestPane(), new TestPane(), new TestPane());
var scene = new Scene(fp, 640, 480);
stage.setScene(scene);
I tried sticking one in a ScrollPane, and the scroll bars never appear, even after resizing the window.
TestPane pane = new TestPane();
ScrollPane sp = new ScrollPane(pane);
var scene = new Scene(sp, 640, 480);
stage.setScene(scene);
And I struggle to discern whether I should be extending Region or Control in these cases.
I am missing something fundamental.
package pkg;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.shape.Line;
import javafx.scene.transform.Translate;
public class TestPane extends Control {
public TestPane() {
setMinHeight(150);
setMaxHeight(150);
setMinWidth(150);
setMaxWidth(150);
setPrefHeight(150);
setPrefWidth(150);
populate();
}
#Override
protected double computePrefHeight(double width) {
return 150;
}
#Override
protected double computePrefWidth(double height) {
return 150;
}
#Override
protected double computeMaxHeight(double width) {
return 150;
}
#Override
protected double computeMaxWidth(double height) {
return 150;
}
#Override
protected double computeMinHeight(double width) {
return 150;
}
#Override
protected double computeMinWidth(double height) {
return 150;
}
#Override
public boolean isResizable() {
return false;
}
private void populate() {
Translate translate = new Translate();
translate.setX(-25);
translate.setY(-25);
getTransforms().clear();
getTransforms().addAll(translate);
ObservableList<Node> children = getChildren();
for (int i = 0; i < 4; i++) {
Line line = new Line(0, i * 50, 200, i * 50);
children.add(line);
line = new Line(i * 50, 0, i * 50, 200);
children.add(line);
}
}
}
Addenda, to clarify.
I want a fixed sized component. It's a rectangle. I want it X x Y big.
I want to draw things in my box. Lines, circles, text.
I want the things I draw to clip to the boundaries of the component.
I don't want to use Canvas.
More addenda.
What I'm looking for is not much different from what a ScrollPane does, save I don't want any scroll bars, and I don't want the size of the outlying pane to grow or shrink.
TLDR:
Subclass Region,
make isResizable() return true to respect pref, min, and max sizes,
explicitly set a clip to avoid painting outside the local bounds.
Most of the documentation for this is in the package documentation for javafx.scene.layout
First, note the distinction between resizable and non-resizable nodes. Resizable nodes (for which isResizable() returns true) are resized by their parent during layout, and the parent will make a best-effort to respect their preferred, minimum, and maximum sizes.
Non-resizable nodes are not resized by their parent. If isResizable() returns false, then resize() is a no-op and the preferred, minimum, and maximum sizes are effectively ignored. Their sizes are computed internally and reported to the parent via its visual bounds. Ultimately, all JavaFX nodes have a peer node in the underlying graphical system, and AFAIK the only way a non-resizable node can determine its size is by directly setting the size of the peer. (I'm happy to be corrected on this.)
So unless you want to get your hands really dirty with custom peer nodes (and I don't even know if the API has mechanisms for this), I think the preferred way to create a "fixed size node" is by creating a resizable node with preferred, minimum, and maximum sizes all set to the same value. This is likely by design: as noted in a comment to your question, fixed-size nodes in layout-driven UI toolkits are generally discouraged, other than very low-level components (Text, Shape, etc).
Transformations applied to resizable nodes are generally applied after layout (i.e. they don't affect the layout bounds). Therefore using a translation to manage the internal positioning of the child nodes is not a good approach; it will have effects on the layout of the custom node in the parent which you probably don't intend.
As you note, you are not really defining a control here; it has no behavior or skin. Thus subclassing Control is not really the rigth approach. The most appropriate hook in the API is to subclass Region. Override the layoutChildren() method to position the child nodes (for Shapes and Text nodes, set their coordinates, for resizable children call resizeRelocate(...)).
Finally, to prevent the node spilling out of its intended bounds (150x150 in your example), either ensure no child nodes are positioned outside those bounds, or explicitly set the clip.
Here's a refactoring of your example:
import javafx.scene.layout.Region;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
public class TestPane extends Region {
private Line[] verticalLines ;
private Line[] horizontalLines ;
private static final int WIDTH = 150 ;
private static final int HEIGHT = 150 ;
private static final int LINE_GAP = 50 ;
public TestPane() {
populate();
}
#Override
protected double computePrefHeight(double width) {
return HEIGHT;
}
#Override
protected double computePrefWidth(double height) {
return HEIGHT;
}
#Override
protected double computeMaxHeight(double width) {
return HEIGHT;
}
#Override
protected double computeMaxWidth(double height) {
return WIDTH;
}
#Override
protected double computeMinHeight(double width) {
return WIDTH;
}
#Override
protected double computeMinWidth(double height) {
return WIDTH;
}
#Override
public boolean isResizable() {
return true;
}
#Override
public void layoutChildren() {
double w = getWidth();
double h = getHeight() ;
double actualWidth = verticalLines.length * LINE_GAP ;
double actualHeight = horizontalLines.length * LINE_GAP ;
double hOffset = (actualWidth - w) / 2 ;
double vOffset = (actualHeight - h) / 2 ;
for (int i = 0 ; i < verticalLines.length ; i++) {
double x = i * LINE_GAP - hOffset;
verticalLines[i].setStartX(x);
verticalLines[i].setEndX(x);
verticalLines[i].setStartY(0);
verticalLines[i].setEndY(h);
}
for (int i = 0 ; i < horizontalLines.length ; i++) {
double y = i * LINE_GAP - vOffset;
horizontalLines[i].setStartY(y);
horizontalLines[i].setEndY(y);
horizontalLines[i].setStartX(0);
horizontalLines[i].setEndX(w);
}
setClip(new Rectangle(0, 0, w, h));
}
private void populate() {
verticalLines = new Line[4] ;
horizontalLines = new Line[4] ;
for (int i = 0 ; i <verticalLines.length ; i++) {
verticalLines[i] = new Line();
getChildren().add(verticalLines[i]);
}
for (int i = 0 ; i <horizontalLines.length ; i++) {
horizontalLines[i] = new Line();
getChildren().add(horizontalLines[i]);
}
}
}
A more sophisticated example might have, for example, LINE_GAP as a property. When that property changes you would call requestLayout() to mark the component as "dirty", so its layoutChildren() method would be called again on the next frame rendered.
Here's a quick test case:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage stage) {
FlowPane root = new FlowPane();
root.setAlignment(Pos.TOP_LEFT);
root.setPadding(new Insets(10));
root.setHgap(5);
root.setVgap(5);
for (int i = 0; i < 6 ; i++) {
root.getChildren().add(new TestPane());
}
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Which results in:
This plays nicely with the layout pane; resizing the window gives

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. :)

Prevent dragged circle from overlapping

As mentioned in the title, i have two Circle 's the first is draggable and the second is fixed, I would rotate (with the drag) the first one around the second without overlapping them but my Circle reacts oddly, I'm sure the error comes from the drag condition but I don't know how to solve it, that's why I need your help, here is a minimal and testable code :
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Collision extends Application{
private Pane root = new Pane();
private Scene scene;
private Circle CA = new Circle(20);
private Circle CB = new Circle(20);
private double xOffset = 0;
private double yOffset = 0;
#Override
public void start(Stage stage) throws Exception{
initCircles();
scene = new Scene(root,500,500);
stage.setScene(scene);
stage.show();
}
private void initCircles(){
CA.setCenterX(100);
CA.setCenterY(100);
CA.setFill(Color.rgb(255, 0, 0,0.2));
CA.setStroke(Color.BLACK);
CB.setCenterX(250);
CB.setCenterY(200);
CB.setFill(Color.rgb(255, 0, 0,0.2));
CB.setStroke(Color.BLACK);
CA.setOnMousePressed(evt->{
xOffset = CA.getCenterX() - evt.getSceneX();
yOffset = CA.getCenterY() - evt.getSceneY();
});
CA.setOnMouseDragged(evt->{
//get Scene coordinate from MouseEvent
drag(evt.getSceneX(),evt.getSceneY());
});
root.getChildren().addAll(CA,CB);
}
private void drag(double x, double y){
/* calculate the distance between
* the center of the first and the second circle
*/
double distance = Math.sqrt (Math.pow(CA.getCenterX() - CB.getCenterX(),2) + Math.pow(CA.getCenterY() - CB.getCenterY(),2));
if (!(distance < (CA.getRadius() + CB.getRadius()))){
CA.setCenterX(x + xOffset);
CA.setCenterY(y + yOffset);
}else{
/**************THE PROBLEM :Condition to drag************/
CA.setCenterX(CA.getCenterX() - (CB.getCenterX()-CA.getCenterX()));
CA.setCenterY(CA.getCenterY() - (CB.getCenterY()-CA.getCenterY()));
/*What condition must be established for the
* circle to behave correctly
*/
/********************************************************/
}
}
public static void main(String[] args) {
launch(args);
}
}
Here is a brief overview :
Note:
for my defense, i searched and found several subject close to mine but which have no precise or exact solution, among which:
-The circle remains blocked at the time of the collision
-Two circle that push each other
-JavaScript, Difficult to understand and convert to java
Thank you for your help !
Point2D can be interpreted as a 2D vector, and has useful methods for creating new vectors from it, etc. You can do:
private void drag(double x, double y){
// place drag wants to move circle to:
Point2D newCenter = new Point2D(x + xOffset, y+yOffset);
// center of fixed circle:
Point2D fixedCenter = new Point2D(CB.getCenterX(), CB.getCenterY());
// minimum distance between circles:
double minDistance = CA.getRadius() + CB.getRadius() ;
// if they overlap, adjust newCenter:
if (newCenter.distance(fixedCenter) < minDistance) {
// vector between fixedCenter and newCenter:
Point2D newDelta = newCenter.subtract(fixedCenter);
// adjust so that length of delta is distance between two centers:
Point2D adjustedDelta = newDelta.normalize().multiply(minDistance);
// move newCenter to match adjusted delta:
newCenter = fixedCenter.add(adjustedDelta);
}
CA.setCenterX(newCenter.getX());
CA.setCenterY(newCenter.getY());
}
Obviously, you could do all this without using Point2D and just doing the computation, but I think the API calls make the code easier to understand.

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