3

I'm developing a GUI for a game and I want to mix a 3D SubScene with a 2D Pane in JavaFX. I have a group called root3D that contains all my 3d objects already set correctly, and then I'm creating a Pane using an FXML file set through JavaFX Scene Builder. But nothing shows up and I can only see my 3D Objects.

       PerspectiveCamera camera = new PerspectiveCamera(true);
       camera.setTranslateZ(-30);
       Group root3D = new Group(model1,model2,model3); //various 3d models I imported
       SubScene subScene = new SubScene(root3D, 1280, 700, true,SceneAntialiasing.BALANCED);
       subScene.setCamera(camera);
       FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/file.fxml"));
       AnchorPane pane = loader.load();
       pane.getChildren().add(subScene);
       Scene scene = new Scene(pane);
       primaryStage.setScene(scene);
       primaryStage.setResizable(false);
       primaryStage.show();

FXML file:

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

<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.text.Font?>

<AnchorPane prefHeight="700.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <StackPane prefHeight="150.0" prefWidth="200.0">
         <children>
            <ImageView fitHeight="214.0" fitWidth="169.0" pickOnBounds="true" preserveRatio="true">
               <image>
                  <Image url="@../Sprite/Cards/Small/podium-characters-Poseidon.png" />
               </image>
            </ImageView>
            <TextField prefHeight="62.0" prefWidth="274.0" text="APOLLO">
               <font>
                  <Font name="Trebuchet MS Bold Italic" size="22.0" />
               </font>
            </TextField>
         </children>
      </StackPane>
   </children>
</AnchorPane>

No sign of the FXML Layout

EDIT: I found out that resizing the subscene, I can see the FXML elements. Apparently they are being "covered" by the subscene. Does anyone know how to lay the FXML elements on top of the subscene and not the opposite like it's happening now? The Subscene covers the FXML Layout I created

gouessej
  • 3,640
  • 3
  • 33
  • 67
anonymflaco
  • 158
  • 1
  • 10
  • 1
    See [Z-Order in JavaFX](https://stackoverflow.com/questions/2988196/z-order-in-javafx). Unfortunately, you don't appear to provide enough information for me to be more specific. Consider providing a [mre] if you need more help. You may want to check out `StackPane` as well. Also, from your call to `pane.setCenter(subScene)` I assume that `pane` references a `BorderPane`. If that's correct then you should remove the call to `pane.getChildren().add(subScene)`. When it comes to layouts such as `BorderPane` you should not interact with the children list directly. – Slaw May 27 '20 at 02:36
  • Make sure your FXML root node is either an `HBox` or a `BorderPane`. `BorderPane` is probably preferred. Set the `Subcene to the `center` or `right`. – SedJ601 May 27 '20 at 06:07
  • apparently I edited the question but some edits didn't show up so the code was a mix between the two and got me confused as well. I fixed it. My FXML root is an Anchor Pane at the moment; is that the problem Sedrick? I tried to make it a StackPane but the result seems the same. – anonymflaco May 27 '20 at 09:29
  • BorderPane would not have worked. From your image, I thought you had a panel on the left. – SedJ601 May 28 '20 at 05:34

2 Answers2

5

Although you've found a solution, I'm posting an answer with the hope of making things a little clearer with regards to why calling toBack() on your SubScene works.

Z-Order

Each Parent, which all layouts inherit from, can have one or more children. If two (or more) children occupy the same space within the Parent then one will be rendered on top of the other. Which child is drawn over the other is determined by two things:

  1. The order of the children in the Parent's children list.
  2. (Since JavaFX 9) The value of each child's viewOrder property relative to the other children in the same Parent.

The Z-Order in JavaFX Q&A goes into more detail.

The Problem in Your Code

Your FXML file describes an AnchorPane which has a StackPane as its only child. This means that the following:

AnchorPane pane = loader.load();

Gives you an AnchorPane with a StackPane in its children list, at the zeroth index. Then you immediately add your SubScene using:

pane.getChildren().add(subScene);

The add method you use appends the element to the end of the list. So that means the AnchorPane's children list's order is:

  1. StackPane
  2. SubScene

Since the SubScene comes after the StackPane the former is rendered on top of the latter.

Your Solution

The solution you chose is to call toBack() on your SubScene after adding it to the AnchorPane. Here's the documentation of that method:

Moves this Node to the back of its sibling nodes in terms of z-order. This is accomplished by moving this Node to the first position in its parent's content ObservableList. This function has no effect if this Node is not part of a group.

In other words, after you call that method the AnchorPane's children list's order becomes:

  1. SubScene
  2. StackPane

That's why the SubScene is now rendered underneath the StackPane, because you changed the order of the children.

Note that the toBack() method is part of the Node class, not SubScene. The latter inherits from the former. I bring this up to point out that the problem you were having is not specific to SubScene or even mixing 2D and 3D scene graphs together. Both your StackPane and your SubScene are part of a 2D Scene (i.e. no depth buffering) which means their z-order is solely governed by what's discussed above*. The fact that the SubScene is 3D (i.e. depth buffering enabled) is irrelevant to the problem at hand; that fact only affects the descendants of said SubScene.

* In a 3D scene the z-coordinate of a node becomes relevant.

Other Solutions

Here are some other approaches that could be used to solve your problem:

  • Add the SubScene to the start of the AnchorPane's children list:

    pane.getChildren().add(0, subScene);
    
  • If you're using JavaFX 9+, set the viewOrder property of the SubScene to something less than that of the AnchorPane's property (by default, that property's value is 0):

    subScene.setViewOrder(-1);
    
  • Define your SubScene in the FXML file before the StackPane (note this approach requires the use of an FXML controller):

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.TextField?>
    <?import javafx.scene.Group?>
    <?import javafx.scene.image.Image?>
    <?import javafx.scene.image.ImageView?>
    <?import javafx.scene.layout.AnchorPane?>
    <?import javafx.scene.layout.StackPane?>
    <?import javafx.scene.SceneAntialiasing?>
    <?import javafx.scene.SubScene?>
    <?import javafx.scene.text.Font?>
    
    <AnchorPane prefHeight="700.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/11.0.1"
                xmlns:fx="http://javafx.com/fxml/1">
      <children>
    
        <!-- Define the SubScene before the StackPane -->
        <SubScene fx:id="subScene" width="1280" height="700" depthBuffer="true">
          <antiAliasing>
            <SceneAntialiasing fx:constant="BALANCED"/>
          </antiAliasing>
    
          <!-- 
            Unfortunately, as far as I can tell, you can't set your PerspectiveCamera in FXML because you
            want 'fixedEyeAtCameraZero' to be true. That property can only be set during construction but
            the constructor with that parameter does not annotate said parameter with @NamedArg, thus the
            FXMLLoader can't see it. And the no-arg constructor sets the value to false, not true. This
            means you have to inject the SubScene into your controller and add the PerspectiveCamera in
            code.
           -->
    
          <root>
            <!-- Inject the root into the controller in order to add your models to it in code -->
            <Group fx:id="root3D"/>
          </root>
        </SubScene>
    
        <StackPane prefHeight="150.0" prefWidth="200.0">
          <children>
            <ImageView fitHeight="214.0" fitWidth="169.0" pickOnBounds="true" preserveRatio="true">
              <image>
                <Image url="@../Sprite/Cards/Small/podium-characters-Poseidon.png"/>
              </image>
            </ImageView>
            <TextField prefHeight="62.0" prefWidth="274.0" text="APOLLO">
              <font>
                <Font name="Trebuchet MS Bold Italic" size="22.0"/>
              </font>
            </TextField>
          </children>
        </StackPane>
    
      </children>
    </AnchorPane>
    
Slaw
  • 37,820
  • 8
  • 53
  • 80
3

As @Slaw indirectly pointed out, what I needed was to set Subscene.toBack() to set a correct Z-order in my scene. Thank you very much! While reading about Subscenes I didn't find anything about it so I hope this can help some 3d beginner in the future. final result

anonymflaco
  • 158
  • 1
  • 10