Box 3D JavaFX Rotate - javafx

I have two Boxes (Group), and when I rotate, the image displays like this:
Display Boxes
Rotate Boxes
When rotating, the Box (JANELA_MEIO_BOX) is distorted:
public class Demo1 extends Application {
private PhongMaterial texturedMaterial = new PhongMaterial();
private Image texture = new Image("/T3D/mapfooter.JPG");
private final PhongMaterial redMaterial = new PhongMaterial();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(final Stage stage) {
redMaterial.setSpecularColor(Color.ORANGE);
redMaterial.setDiffuseColor(Color.RED);
texturedMaterial.setDiffuseMap(texture);
javafx.scene.shape.Box JANELA_MEIO_BOX = new javafx.scene.shape.Box();
/* rotate */
JANELA_MEIO_BOX.setWidth(600.0);
JANELA_MEIO_BOX.setHeight(340.0);
JANELA_MEIO_BOX.setDepth(100.0);
JANELA_MEIO_BOX.setMaterial(texturedMaterial);
Group JANELA_001 = new Group();
stage.setTitle("Cube");
final CameraView cameraView = new CameraView();
final Scene scene = new Scene(cameraView, 1000, 800, true);
scene.setFill(new RadialGradient(225, 0.85, 300, 300, 500, false,
CycleMethod.NO_CYCLE, new Stop[]{new Stop(0f, Color.BLUE),
new Stop(1f, Color.LIGHTBLUE)}));
PerspectiveCamera camera = new PerspectiveCamera();
scene.setCamera(camera);
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
javafx.scene.shape.Box JAN_MAIN = new javafx.scene.shape.Box();
JAN_MAIN.setMaterial(redMaterial);
JAN_MAIN.setWidth(1000.0);
JAN_MAIN.setHeight(600.0);
JAN_MAIN.setDepth(100.0);
JAN_MAIN.getTransforms().add(new Translate(1, 1, 1));
JANELA_MEIO_BOX.getTransforms().add(new Translate(1, 1, 1));
JANELA_001.getChildren().addAll(JAN_MAIN, JANELA_MEIO_BOX);
cameraView.add(JANELA_001);
/* mouse events */
cameraView.frameCam(stage, scene);
MouseHandler mouseHandler = new MouseHandler(scene, cameraView);
KeyHandler keyHandler = new KeyHandler(stage, scene, cameraView);
/* scene */
stage.setScene(scene);
stage.show();
}
When rotating, the Box (JANELA_MEIO_BOX) is distorted

You have two boxes: a 1000x600x100 cube, and a 600x340x100 cube.
When you put both of them in a group, they are placed in the center: the bigger one goes from -500 to 500 in X, -300 to 300 in Y, -50 to 50 in Z, and the same goes for the smaller one, also in Z from -50 to 50.
When you render two shapes with their faces in the same exact Z coordinate you will always get these artifacts.
One quick solution, if you want to see both shapes, is just making the smaller one a little bit deeper:
JANELA_MEIO_BOX.setDepth(100.1);
And it is also convenient that you set Scene Antialiasing to Balanced:
final Scene scene = new Scene(cameraView, 1000, 800, true, SceneAntialiasing.BALANCED);

Related

JavaFX ColorPicker show uncolor option

I want to show un-color option in my ColorPicker.
How i can show it?
Thanks.
The solution is a bit of a hack, but it avoids using private API.
These are the required steps:
Get the Popup control that shows up when you click on the ColorPicker.
You can find it here or here.
Get the square colors on that popup, so we can change one of them. I'll use the last one.
Once we have the popup, we'll get the set of square colors by using lookups: Set<Node> squares = popup.lookupAll(".color-rect");
Let's use the last color to add our customized 'un-color'.
Find out how to draw that red diagonal line.
I've come up with a LinearGradient:
final LinearGradient redLine = new LinearGradient(0, 0, 1, 1, true, CycleMethod.NO_CYCLE,
new Stop(0, Color.WHITE), new Stop(0.45, Color.WHITE),
new Stop(0.46, Color.RED), new Stop(0.54, Color.RED),
new Stop(0.55, Color.WHITE), new Stop(1, Color.WHITE));
That works fine, but sadly the gradient breaks the ColorPicker control, that is an extension of ComboBoxBase<Color>, and all the fills used for the rectangles will be casted to Color instead of Paint. That means we'll have to use a color (for instance Color.TRANSPARENT) during the transitions.
Solve other issues like the square color that will be seen when the popup closes, or the square color that shows up on hovering.
For this, we need to lookup for both the square color in the color picker and hovered square, and when those match our transparent one, replace the color with the gradient.
This is the code:
public class UnColorPicker extends Application {
private final LinearGradient redLine = new LinearGradient(0, 0, 1, 1, true, CycleMethod.NO_CYCLE,
new Stop(0, Color.WHITE), new Stop(0.45, Color.WHITE), new Stop(0.46, Color.RED),
new Stop(0.54, Color.RED), new Stop(0.55, Color.WHITE), new Stop(1, Color.WHITE));
#Override
public void start(Stage primaryStage) {
ColorPicker picker = new ColorPicker();
StackPane root = new StackPane(picker);
Scene scene = new Scene(root, 500, 400);
primaryStage.setScene(scene);
primaryStage.show();
Rectangle rect = (Rectangle) root.lookup(".picker-color-rect");
Label label = (Label) root.lookup(".color-picker-label");
picker.showingProperty().addListener((obs, b, b1) -> {
if (b1) {
PopupWindow popupWindow = getPopupWindow();
Node popup = popupWindow.getScene().getRoot().getChildrenUnmodifiable().get(0);
StackPane hover = (StackPane) popup.lookup(".hover-square");
Rectangle rectH = (Rectangle) hover.getChildren().get(0);
Set<Node> squares = popup.lookupAll(".color-rect");
squares.stream()
.skip(squares.size()-2)
.map(Rectangle.class::cast)
.findFirst()
.ifPresent(r -> {
r.getParent().setOnMousePressed(e -> {
// avoid CastException
r.setFill(Color.TRANSPARENT);
e.consume();
});
r.getParent().setOnMouseReleased(e -> {
Platform.runLater(() -> {
rect.setFill(redLine);
label.setText("Un-color");
});
});
r.setFill(redLine);
Tooltip.install(r.getParent(), new Tooltip("Un-color"));
});
hover.visibleProperty().addListener((obs2, ov, nv) -> {
if (nv && rectH.getFill().equals(Color.TRANSPARENT)) {
Platform.runLater(() -> rectH.setFill(redLine));
}
});
}
});
}
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation")
final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof PopupWindow) {
return (PopupWindow)window;
}
}
return null;
}
public static void main(String[] args) {
launch(args);
}
}
The approach posted above didn't work for me anymore so I came up with a slightly different solution, though the idea is the same. It also avoids using deprecated functions.
I subclassed the ColorPicker class to build my own CustomColorPicker which can be used instead.
Here is my code:
#SuppressWarnings("restriction")
public class CustomColorPicker extends ColorPicker {
final static LinearGradient RED_LINE = new LinearGradient(0, 0, 1, 1, true, CycleMethod.NO_CYCLE,
new Stop(0, Color.WHITE), new Stop(0.45, Color.WHITE),
new Stop(0.46, Color.RED), new Stop(0.54, Color.RED),
new Stop(0.55, Color.WHITE), new Stop(1, Color.WHITE));
#Override
protected Skin<?> createDefaultSkin() {
final CustomColorPickerSkin skin = new CustomColorPickerSkin(this);
final Label lbl = (Label)skin.getDisplayNode();
final StackPane pane = (StackPane)lbl.getGraphic();
final Rectangle rect = (Rectangle)pane.getChildren().get(0);
// set initial color to red line if transparent is shown
if (getValue().equals(Color.TRANSPARENT))
rect.setFill(RED_LINE);
// set color to red line when transparent is selected
rect.fillProperty().addListener((o, oldVal, newVal) -> {
if (newVal != null && newVal.equals(Color.TRANSPARENT))
rect.setFill(RED_LINE);
});
return skin;
}
private class CustomColorPickerSkin extends ColorPickerSkin {
private boolean initialized = false;
public CustomColorPickerSkin(ColorPicker colorPicker) {
super(colorPicker);
}
#Override
protected Node getPopupContent() {
final ColorPalette popupContent = (ColorPalette)super.getPopupContent();
// make sure listeners and geometry are only created once
if (!initialized) {
final VBox paletteBox = (VBox)popupContent.getChildrenUnmodifiable().get(0);
final StackPane hoverSquare = (StackPane)popupContent.getChildrenUnmodifiable().get(1); // ColorSquare
final Rectangle hoverRect = (Rectangle)hoverSquare.getChildren().get(0); // ColorSquare
final GridPane grid = (GridPane)paletteBox.getChildren().get(0); // ColorPalette
final StackPane colorSquare = (StackPane)grid.getChildren().get(grid.getChildren().size()-1); // ColorSquare
final Rectangle colorRect = (Rectangle)colorSquare.getChildren().get(0);
// set fill color of original color rectangle to transparent
// (can't be set to red line gradient because ComboBoxBase<Color> tries to cast it to Color)
colorRect.setFill(Color.TRANSPARENT);
// put another rectangle with red line on top of it
colorSquare.getChildren().add(new Rectangle(colorRect.getWidth(), colorRect.getHeight(), RED_LINE));
// show red line gradient also in hover rectangle when the transparent color is selected
hoverRect.fillProperty().addListener((o, oldVal, newVal) -> {
if (newVal.equals(Color.TRANSPARENT))
hoverRect.setFill(RED_LINE);
});
initialized = true;
}
return popupContent;
}
}
}

Connecting Circles with a Polyline JavaFX

I have created circles on a graph in JavaFX, and I want to connect those circles with a polyline. Does anyone know the syntax for doing this? Thanks!
A Polyline may work, but you can do this easier, if you use the Path class, since this allows you to access to the individual elements of the path (PathElements). You can use bindings to bind the position of the line points to the positions of the circles. This way the lines will stay at the appropriate positions, even if you move the circles later.
Example
private static void bindLinePosTo(Circle circle, LineTo lineTo) {
lineTo.xProperty().bind(circle.centerXProperty());
lineTo.yProperty().bind(circle.centerYProperty());
}
private static void animate(Circle circle, Duration duration, double dy) {
Timeline animation = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(circle.centerYProperty(), circle.getCenterY())),
new KeyFrame(duration, new KeyValue(circle.centerYProperty(), circle.getCenterY()+dy)));
animation.setAutoReverse(true);
animation.setCycleCount(Animation.INDEFINITE);
animation.play();
}
#Override
public void start(Stage primaryStage) {
MoveTo start = new MoveTo();
LineTo line1 = new LineTo();
LineTo line2 = new LineTo();
Circle c1 = new Circle(10, 100, 5);
Circle c2 = new Circle(50, 100, 5);
Circle c3 = new Circle(100, 100, 5);
c1.setFill(Color.RED);
c2.setFill(Color.RED);
c3.setFill(Color.RED);
start.xProperty().bind(c1.centerXProperty());
start.yProperty().bind(c1.centerYProperty());
bindLinePosTo(c2, line1);
bindLinePosTo(c3, line2);
Path path = new Path(start, line1, line2);
Pane root = new Pane(path, c1, c2, c3);
animate(c1, Duration.seconds(1), 100);
animate(c2, Duration.seconds(2), 50);
animate(c3, Duration.seconds(0.5), 150);
Scene scene = new Scene(root, 110, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
Use the Binding API, e. g. like this.

How to keep angle of DropShadow when node is rotated

When you apply a DropShadow on a node which is rotated, then the DropShadow rotates with it. Is there a simple way to keep the DropShadow angle where it is, e. g. bottom right even when the node is rotated?
I know that it would work if I put all the nodes into a group and apply the shadow on the group, but that's unfortunately not an option in my case.
Sample image:
left rectangle with drop shadow
right rectangle with same drop shadow, but rotated by 180 degrees
You see, it looks wrong with the shadows being in opposite directions.
Code
public class HelloEffects extends Application {
Stage stage;
Scene scene;
#Override
public void start(Stage stage) {
Group group = new Group();
DropShadow ds1 = new DropShadow();
ds1.setOffsetY(4.0f);
ds1.setOffsetX(4.0f);
ds1.setColor(Color.BLACK);
Rectangle rect1 = new Rectangle( 100, 200);
rect1.relocate(100, 100);
rect1.setEffect(ds1);
rect1.setFill(Color.RED);
Rectangle rect2 = new Rectangle( 100, 200);
rect2.relocate(300, 100);
rect2.setEffect(ds1);
rect2.setFill(Color.RED);
rect2.setRotate(180);
group.getChildren().addAll(rect1, rect2);
scene = new Scene( group, 840, 680);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
You should add a container to rect2 and apply the effect on the container, the container can be either Pane or Group:
Group rect2Container = new Group(rect2);
rect2Container.setEffect(ds1);
group.getChildren().addAll(rect1, rect2Container);

JavaFx 2D part in 3D application

I have a small problem with an Application I write.
I want to have to have a 3D field and on the right side a toolbar that contains 2D-Components like buttons.
I tried to simply add these Components to my root-Group, but then it is impossible to read the text and they move with all the others.
So, how can I seperate these two areas? Possibly with two scenes?
Thank you for every hint you can give :)
The best approach is using a SubScene for the 3D nodes. You can keep all the 2D content on top of it without rendering problems.
You can read about the SubScene API here.
This is a small sample of what you can do with this node:
private double mousePosX, mousePosY;
private double mouseOldX, mouseOldY;
private final Rotate rotateX = new Rotate(-20, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(-20, Rotate.Y_AXIS);
#Override
public void start(Stage primaryStage) throws Exception {
// 3D
Box box = new Box(5, 5, 5);
box.setMaterial(new PhongMaterial(Color.GREENYELLOW));
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.getTransforms().addAll (rotateX, rotateY, new Translate(0, 0, -20));
Group root3D = new Group(camera,box);
SubScene subScene = new SubScene(root3D, 300, 300, true, SceneAntialiasing.BALANCED);
subScene.setFill(Color.AQUAMARINE);
subScene.setCamera(camera);
// 2D
BorderPane pane = new BorderPane();
pane.setCenter(subScene);
Button button = new Button("Reset");
button.setOnAction(e->{
rotateX.setAngle(-20);
rotateY.setAngle(-20);
});
CheckBox checkBox = new CheckBox("Line");
checkBox.setOnAction(e->{
box.setDrawMode(checkBox.isSelected()?DrawMode.LINE:DrawMode.FILL);
});
ToolBar toolBar = new ToolBar(button, checkBox);
toolBar.setOrientation(Orientation.VERTICAL);
pane.setRight(toolBar);
pane.setPrefSize(300,300);
Scene scene = new Scene(pane);
scene.setOnMousePressed((MouseEvent me) -> {
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
});
scene.setOnMouseDragged((MouseEvent me) -> {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
rotateX.setAngle(rotateX.getAngle()-(mousePosY - mouseOldY));
rotateY.setAngle(rotateY.getAngle()+(mousePosX - mouseOldX));
mouseOldX = mousePosX;
mouseOldY = mousePosY;
});
primaryStage.setScene(scene);
primaryStage.setTitle("3D SubScene");
primaryStage.show();
}
For more complex scenarios, have a look at the 3DViewer application under the OpenJFX project.

Javafx - How to set drag range for components added in a pane

i have tried a below sample, in which left area of border Pane will have list of components and center of the border pane will act as a canvas area and here i have added a rectangle on run time as children to a Pane which is set to Center portion of BorderPane. But when drag the rectangle it moving outof the area allocated for the center, so how could i make this drag around only inside the Center Pane.
#Override
public void start(Stage stage) throws Exception {
stage.setTitle("BPM");
BorderPane border = new BorderPane();
Pane canvas = new Pane();
canvas.setStyle("-fx-background-color: #F0F0F0;");
border.setLeft(compList());
border.setCenter(canvas);
//
Anchor start = new Anchor(null, "Start", Color.PALEGREEN, new SimpleDoubleProperty(170), new SimpleDoubleProperty(170));
final Rect rect=new Rect(100, 70,new SimpleDoubleProperty(10), new SimpleDoubleProperty(100));
rect.setX(100);
rect.setY(100);
canvas.getChildren().add(rect);
canvas.getChildren().add(start);
Scene scene = new Scene(border, 800, 600);
stage.setScene(scene);
stage.show();
}
Actually, by default, the Pane class does not ensure that all its children are clipping hence there are possibility that the children might go out of the boundary of the Pane. To ensure that all children (in your case, the rectangle) are dragged within specify boundary, you have to manually check the boundary as you dragging the children. Below are example of my implementation:
#Override
public void start(Stage stage){
stage.setTitle("BPM");
BorderPane mainPanel = new BorderPane();
VBox nameList = new VBox();
nameList.getChildren().add(new Label("Data"));
nameList.setPrefWidth(150);
Pane canvas = new Pane();
canvas.setStyle("-fx-background-color: #ffe3c3;");
canvas.setPrefSize(400,300);
Circle anchor = new Circle(10);
double rectWidth = 50, rectHeight = 50;
Rectangle rect = new Rectangle(50,50);
rect.setX(100);
rect.setY(100);
canvas.getChildren().addAll(rect, anchor);
// set the clip boundary
Rectangle bound = new Rectangle(400,300);
canvas.setClip(bound);
rect.setOnMouseDragged(event -> {
Point2D currentPointer = new Point2D(event.getX(), event.getY());
if(bound.getBoundsInLocal().contains(currentPointer)){
if(currentPointer.getX() > 0 &&
(currentPointer.getX() + rectWidth) < bound.getWidth()){
rect.setX(currentPointer.getX());
}
if(currentPointer.getY() > 0 &&
(currentPointer.getY() + rectHeight) < bound.getHeight()){
rect.setY(currentPointer.getY());
}
}
});
mainPanel.setLeft(nameList);
mainPanel.setCenter(canvas);
Scene scene = new Scene(mainPanel, 800, 600);
stage.setScene(scene);
stage.show();
}

Resources