2

My program is one where you can place points of a field drawing from an FRC competition and then you can see the path that you have defined using those points.

The first step of the program is calibrating (finding the pixel to real distance scale) and I am currently adding the functionality of defining the field (the field walls, obstacles, etc...). I am first trying to define the field borders. To do this I was thinking about selecting points on the drawing and then add them to a polygon, then subtracting the polygon from a rectangle the same size as the field image.

The problem is that when testing this it shows my subtracted shape offset for some reason. I have looked at the StackOverflow question about Shape.intersect, however, it does not help. I have checked all the layout positions, scales, etc. of the AnchorPane and its children I place inside of it, but it all returns either 0 or 1s.

Here is a drawing of what it returns: enter image description here The red rectangle is the rectangle that is the size of the image.
The purple is the overlap between the rectangle and the polygon selected.
The grey is the Shape.subtract() resultant.

What I want is that the Shape.substract (the grey) has its empty spot on top of the purple and fit perfectly. But as you can see in this image the purple overlaps with the grey.

This is how I want it to look like: enter image description here

Here is a testing environment I have created to reproduce the phenomenon:

Main.java

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {


    public Main() {
    }

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

    @Override
    public final void start(Stage primaryStage) throws IOException {

        Parent root = FXMLLoader.load(getClass().getResource("recreationFieldSelection.fxml"));

        primaryStage.setTitle("Path Planner");
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
}

RecreationFieldSelection.java

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Path;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;

public class RecreationFieldSelection implements Initializable {

    public AnchorPane pointPlacement;
    public ImageView fieldImage;
    public TextField distanceViewer;
    public HBox infoPane;

    private Polygon polygon = new Polygon();
    private Color gray = Color.gray(.5, .6);
    private Color blue = Color.rgb(0, 0, 255, .5);
    private boolean now = true;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        fieldImage.setFitHeight(1000);
        fieldImage.setFitWidth(1000);

        polygon.setFill(Color.TRANSPARENT);
        polygon.setStroke(Color.RED);
        polygon.setStrokeWidth(1);

        pointPlacement.getChildren().add(polygon);
    }

    @FXML
    public void handleMouseClicked(MouseEvent mouseEvent) {
        outlineField(mouseEvent);
    }

    @FXML
    private void outlineField(MouseEvent mouseEvent) {

        polygon.getPoints().addAll(mouseEvent.getX(), mouseEvent.getY());

//      If the polygon has 8 defining points
        if (polygon.getPoints().size() / 2 >= 8 && now) {
            now = false;

            Rectangle rectangle = new Rectangle(fieldImage.getFitWidth(), fieldImage.getFitHeight());
            rectangle.setFill(Color.color(1, 0, 0, .3));

            Path subtract = (Path) Polygon.subtract(rectangle, polygon);
            subtract.setFill(gray);
            subtract.setStroke(gray);
            subtract.setStrokeWidth(1);

            polygon.setFill(blue);

            pointPlacement.getChildren().addAll(subtract);

        }
    }
}

recreationFieldSelection.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml/1" spacing="5.0" xmlns="http://javafx.com/javafx/8"
  fx:controller="testing.RecreationFieldSelection">
  <padding>
    <Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
  </padding>
  <ScrollPane VBox.vgrow="ALWAYS" prefViewportHeight="150.0" prefViewportWidth="200.0">
    <AnchorPane fx:id="pointPlacement">
      <ImageView onMouseClicked="#handleMouseClicked" pickOnBounds="true" preserveRatio="true" fx:id="fieldImage"/>
    </AnchorPane>
  </ScrollPane>
</VBox>

The rest of the code is here.

Nameless
  • 85
  • 2
  • 10
  • 1
    *Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself. Questions without a clear problem statement are not useful to other readers. See: How to create a [mcve].* – user1803551 Jul 30 '18 at 19:38
  • 1
    We should be able to cope-paste the posted code into our IDEs and run it and see the problem. Your posted code is not a [mcve], re-read the link content. – user1803551 Jul 30 '18 at 22:42
  • Then you are one step closer to finding your problem. But if you can't reproduce it, so can't we. – user1803551 Jul 31 '18 at 00:15
  • What are the menu, text field and button for? Remove anything which isn't needed. – user1803551 Jul 31 '18 at 06:16

1 Answers1

2

The problem is the result of a combination of 2 factors: padding and node hierarchy; and might be a bug. The important point to note is from subtract's doc:

Before the final operation the areas of the input shapes are transformed to the parent coordinate space of their respective topmost parent nodes.

Which means that it matters where the nodes are in the hierarchy when the subtraction is made. If the nodes are in a parent with padding, it is taken into account during the coordinate transforms, and that causes the shift you see.

Your simple workarounds are either to remove the padding on the problematic side(s) (depends on the alignment in the node), translate the shifted node by the padding amount (cancelling the padding shift), or to add the polygon to the hierarchy after the subtract operation where you add subtract:

pointPlacement.getChildren().addAll(subtract, polygon);

For a slightly more in-depth analysis, I created the following MCVE:

public class Main2 extends Application {

    private final double size = 400;

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

    @Override
    public final void start(Stage stage) throws IOException {
        AnchorPane pane = new AnchorPane();
        pane.setStyle("-fx-background-color: lightgray;");

        VBox root = new VBox(pane);
        VBox.setVgrow(pane, Priority.ALWAYS);
        root.setPadding(new Insets(10, 10, 10, 10));

        stage.setTitle("add none");
        stage.setScene(new Scene(root));
        stage.setWidth(size + 50);
        stage.setHeight(size + 70);

        draw(pane);
        stage.show();
//      stage.hide();
//      draw(pane);
//      stage.show();
    }

    private void draw(AnchorPane pane) {
        Rectangle big = new Rectangle(size, size);
        big.setFill(Color.RED.deriveColor(0, 1, 1, 0.3));
        big.setStroke(Color.RED);
        pane.getChildren().add(big);

        Rectangle small = new Rectangle(size/2, size/2);
        small.setFill(Color.BLUE.deriveColor(0, 1, 1, 0.3));
        small.setStroke(Color.BLUE);
        pane.getChildren().add(small);

        Shape subtract = Shape.subtract(big, small);
        subtract.setFill(Color.LIME.deriveColor(0, 1, 1, 0.3));
        subtract.setStroke(Color.LIME);

//      pane.getChildren().add(big);
//      pane.getChildren().add(small);
        pane.getChildren().add(subtract);
    }
}

There are several cases to note:

  • draw is called before the stage is made visible. In this case the padding is not taken into account and the result will always be correct.
  • draw is called after the stage is made visible for the first time (hiding it, calling draw, and showing it makes no difference). There we can make 4 distinctions:
    • None of the shapes is added to the hierarchy before subtraction. Neither is affected by the padding, so the result is correct:
      enter image description here
    • Only the small shape is added to the hierarchy before subtraction. This is akin to applying the padding to the small (blue) shape, clipping, and moving it back:
      enter image description here
    • Only the big shape is added to the hierarchy before subtraction. This is akin to applying the padding to the big (red) shape, clipping, and moving it back:
      enter image description here
    • Both shapes are added to the hierarchy before subtraction. Combination of the above 2:
      enter image description here
user1803551
  • 12,965
  • 5
  • 47
  • 74