Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I render my lines in front of my rectangles in my code?

Tags:

java

javafx

I am new to javafx and I have a problem where I am making a grid of GridTiles16. This is how I make a Grid of tiles16:

private static void makeGrid(Group root, int gridSize, ArrayList<String> grid, GridTile16[][] gridTiles, Scene scene) {
    for (int i = 0; i < grid.size(); i++) {
        for (int j = 0; j < grid.get(i).length(); j++) {
            GridTile16 newGridTile = new GridTile16(i * gridSize, j * gridSize, gridSize, grid.get(i).charAt(j));
            root.getChildren().add(newGridTile);
            gridTiles[i][j] = newGridTile;
        }
    }
}

GridTiles classes:

import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

public class GridTile extends Group {

    protected int x;
    protected int y;
    protected int size;
    protected Rectangle rectangle;

    public GridTile(int x, int y, int size) {
        this.x = x;
        this.y = y;
        this.size = size;
        Rectangle rectangle = new Rectangle(x, y, size, size);
        rectangle.setFill(Color.BLACK);
        rectangle.setStroke(Color.WHITE);
        this.getChildren().add(rectangle);
        this.rectangle = rectangle;
    }

    public void resetColor() {
        this.rectangle.setFill(Color.BLACK);
    }

    public int getX() {
        return y/size;
    }

    public int getY() {
        return x/size;
    }
}
import com.programs.GridTile;

import javafx.scene.paint.Color;
import javafx.scene.shape.Line;

public class GridTile16 extends GridTile {

    private boolean[] isFromLeftRightTopBot = new boolean[4];

    public GridTile16(int y, int x, int size, char c) {
        super(x, y , size);
        if (c != '.') {
            Line line = null;
            switch (c) {
                case '-':
                    line = new Line(x, y + size / 2, x + size, y + size / 2);
                    break;
                case '|':
                    line = new Line(x + size / 2, y, x + size / 2, y + size);
                    break;
                case '/':
                    line = new Line(x, y + size, x + size, y);
                    break;
                case '\\':
                    line = new Line(x, y, x + size, y + size);
                    break;
            }
            line.setStroke(Color.WHITE);
            line.setStrokeWidth(5);
            this.getChildren().add(line);
        }
    }

    public boolean getIsFromDirection(int direction) {
        return this.isFromLeftRightTopBot[direction];
    }

    public void setIsFromDirection(int direction) {
        this.isFromLeftRightTopBot[direction] = true;
        this.rectangle.setFill(Color.BLUE);
        Line newLine = null;
        if (direction == 0) {
            newLine = new Line(this.x, this.y + this.size / 2, this.x + this.size / 2, this.y + this.size / 2);
        } else if (direction == 1) {
            newLine = new Line(this.x + this.size, this.y + this.size / 2, this.x + this.size / 2, this.y + this.size / 2);
        } else if (direction == 2) {
            newLine = new Line(this.x + this.size / 2, this.y, this.x + this.size / 2, this.y + this.size / 2);
        } else if (direction == 3) {
            newLine = new Line(this.x + this.size / 2, this.y + this.size, this.x + this.size / 2, this.y + this.size / 2);
        }
        newLine.setStrokeWidth(2);
        newLine.setStroke(Color.RED);
        this.getChildren().add(newLine);
    }
}

After all the tiles are made I get a grid like this:

grid

As I highlighted some areas I want the line part to be sticking out a bit but this is only possible if line is generated before the rectangles of the next tile. Is there a simpler way to just render all the lines in front of all the rectangles or is it just possible to first make a grid of rectangles (gridtiles without the lines) and after add the lines.

I tried to fix the problem with .toFront() and .toBack() functions which I assume only work for the current tile which is already fine because line is generated before the rectangle. I also tried to fix it with .viewOrderProperty().set(<0/1>) which has the same problem that it only works for the immediate parent and not for parent of the parent.

program input:

.|...\....
|.-.\.....
.....|-...
........|.
..........
.........\
..../.\\..
.-.-/..|..
.|....-|.\
..//.|....

main program:

public class App extends Application {

    private static final int gridSize = 40;
    private static double zoomFactor = 1;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws FileNotFoundException {

        Group root = new Group();
        Scene scene = new Scene(root, 400, 400, Color.BLACK);
        stage.setFullScreen(true);
        stage.setFullScreenExitHint("");
        stage.setTitle("Visualisation");
        stage.setScene(scene);
        stage.show();

        scene.widthProperty().addListener((obs, oldVal, newVal) -> {
            resetSize(scene, root);
        });
       
        scene.heightProperty().addListener((obs, oldVal, newVal) -> {
            resetSize(scene, root);
        });

        File file = new File(<INPUT>);
        Scanner scanner = new Scanner(file);
        ArrayList<String> grid = new ArrayList<>();
        while (scanner.hasNextLine()) {
            grid.add(scanner.nextLine());
        }
        scanner.close();
        GridTile16[][] gridTiles = new GridTile16[grid.size()][grid.get(0).length()];
        makeGrid(root, gridSize, grid, gridTiles, scene);

        resetSize(scene, root);
    }

    private static void resetSize(Scene scene, Group root) {
        root.setLayoutX((scene.getWidth() - root.prefWidth(-1)) / 2);
        root.setLayoutY((scene.getHeight() - root.prefHeight(-1)) / 2);

        double scaleX = scene.getWidth() / root.getBoundsInLocal().getWidth();
        double scaleY = scene.getHeight() / root.getBoundsInLocal().getHeight();
        zoomFactor = Math.min(scaleX, scaleY) - 0.01;
        updateTransforms(root);
    }

    private static void updateTransforms(Group root) {
        root.setScaleX(zoomFactor);
        root.setScaleY(zoomFactor);
    }
}

like image 327
Dimnik Avatar asked Nov 02 '25 19:11

Dimnik


1 Answers

If you only have two colors, you don't need to modify paint order

The initial example in your question only has two colors (black and white).

In this case, you don't need to worry about the order of stuff.

Everything in your scene is just white lines on a black background. The background of your scene is already black. When you create the rectangles in the grid, don't fill them with a black color. Currently, the black parts of the rectangles paint over your white lines, obscuring them. If the rectangles have no black parts, that won't happen. The white stroke on the rectangle borders will still paint over your white lines, but white on white is still white, so that doesn't matter.

Replacing this in your GridTile class:

rectangle.setFill(Color.BLACK);

With this:

rectangle.setFill(null);

Results in the output you seek:

lines

A note on painting order

It turned, out in this case, that the order of drawing was irrelevant, making the question title about how to set the drawing order an xy problem.

For another question, the order might actually be relevant. The asker notes that this is even the case with their actual full-scale application.

Conceptually, JavaFX uses a painter's algorithm. The discussion in the linked article talks about various ways to handle painting order issues that may arise when using such an algorithm (in a general way, without JavaFX implementation specifics).

Solution which modifies the paint order

How would I solve my problem if these rectangles would be different color than the background? Because after my program makes the grid, it is changing colors of rectangles and some other things and it covers the sticking out parts of lines.

Potential solutions:

  1. Place all items under a single parent, with the items to be painted last ordered last in the child list.
  2. Place all items under a single parent, with the items to be painted last assigned a higher view order than the other items.
  3. Use multiple parents, stacked on top of each other in layers, with the items to be painted last in the highest layer.
  4. Turn on 3D depth buffering in the scene and assign the items to be placed on top of other items a z order which is closer to the viewer.

Let's try the 3D depth buffering technique as an example. It is a different way of handling 3D ordering than the standard painters algorithm, it is similar to a painters algorithm in some ways, but a bit different. The nice thing is that it is just a switch that you set on, and the implementation detail is all handled by JavaFX and the graphics system, so you don't have to do much to use it.

The wikipedia article on the painters algorithm describes the situation like this:

The flaws of painter's algorithm led to the development of Z-buffer techniques, which can be viewed as a development of the painter's algorithm by resolving depth conflicts on a pixel-by-pixel basis, reducing the need for a depth-based rendering order.

When you create the scene, set the depth buffer to true:

Scene scene = new Scene(root, 400, 400, true);
scene.setFill(Color.BLACK);

When you draw the lines, translate them closer to the viewer:

line.setTranslateZ(-1);

For this example, I also paint the lines a different color (green) so it is clear they are completely covering the underlying grid squares and their borders.

green lines

Ahh, but now we still have a problem, if you look really closely. The image provided is a screenshot of a hi-DPI screen, so the actual program when running on the screen doesn't have any defects easily visible to the naked eye. But when screen captured and enlarged like the images here, you can see that sometimes there is a black gap where the depth buffering has been working and colors are mixed on an individual pixel. This is because anti-aliasing is switched off on the scene. So to get a better outcome, we can switch on anti-aliasing.

Scene scene = new Scene(root, 400, 400, true, SceneAntialiasing.BALANCED);

Then we get this outcome, which (perhaps) looks better, if you look really closely . . .

aliased green lines

But it still (to me) doesn't look quite as neat as it may be if you used one of the other techniques I suggested above (which surprised me). So depending on what you want, you could use this depth buffering technique (which is quite simple), or you could try one of the other techniques (which I won't try to implement or explain in more detail at this time).

FAQ

As this provides me with more lag when resizing window with bigger input I will try to implement the other suggestion you provided. Thank you for taking the time to answer my questions.

Depth buffering is more intensive than not. It is more efficient on higher performing graphics accelerators, so performance depends on your hardware. That said, even modest graphics hardware nowadays is capable of massive amounts of data processing.

If you have performance issues, the issues are likely with you writing inefficient code rather than anything else. Your performance issues are probably just exacerbated by the depth buffering switch, rather than the switch being the underlying cause of performance issues, because the switch will perform fine in other cases.

For further discussions on performance, see the write-up in the answer to this follow-up question:

  • Rendering in javaFX when resizing is very very slow / laggy. Why?

An interesting finding there is that, with a large number of arc-style nodes in a scene (e.g. tens of thousands), performance is slower (about a factor of 3x in the tested example), when you set the z translation value of any node to a non-zero value, even if you don't enable the depth buffer. I don't know why that is the case but it might be something to bear in mind when optimizing and analyzing performance. Enabling depth buffering itself didn't make any measurable difference on my system, but for it to function as you want, you need to adjust the z values of nodes, which does have a measurable performance impact when you have tens of thousands of arc-style nodes to be painted.

The answer to the follow-up question provides an example that renders using a couple of paths rather than many nodes. One path is placed on top of another to set the rendering order correctly, rather than adjusting the z translation value. The minimal node approach with paths performs much better than the approach that uses many nodes with adjusted z-translation values. The performance improves by a couple of orders of magnitude, once the node rendering-based solution is scaled up to tens of thousands of nodes. The layered path solution does not have the small visual anomalies exemplified in the z-translation-adjusted solution using a depth buffer.

like image 112
jewelsea Avatar answered Nov 04 '25 11:11

jewelsea