javaswingjavafxjavafx-8java-3d

How to embed a Canvas3D in a JavaFX application?


I'm developing a JavaFX desktop application. In it I want to display some java 3D objects.

The way to do this, as far as I know, is using JavaFX's SwingNode. Java 3d has to be integrated in a Swing component too. So it's a two-step process.

Integrating Java 3D objects with Java Swing

Complete and minimal example composed of the panel I intend to reuse in the FX app and a JFrame that shows it works:

import java.awt.BorderLayout;

import javax.swing.JFrame;

public class JFrameWithCanvas3D extends JFrame {
   public JFrameWithCanvas3D() {
      super("Swing JFrame Wraps Canvas3D");
      setLayout(new BorderLayout());

      BallPanel panel = new BallPanel();
      add(panel, BorderLayout.CENTER);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      pack();
      setVisible(true);
   }

   public static void main(String[] args) {
      System.setProperty("sun.awt.noerasebackground", "true");
      javax.swing.SwingUtilities.invokeLater(new Runnable() {
         @SuppressWarnings("unused")
         @Override
         public void run() {
            new JFrameWithCanvas3D();
         }
      });
   }  
}

public class BallPanel extends JPanel {

   public BallPanel() {
      setLayout(new BorderLayout());
      setPreferredSize(new Dimension(500, 500));
      add(makeCanvas());

   }

   private static Canvas3D makeCanvas() {
      BranchGroup group = new BranchGroup();
      group.addChild(makeLight());
      group.addChild(new Sphere(5));

      Canvas3D canvas3D = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
      SimpleUniverse universe = new SimpleUniverse(canvas3D);
      Transform3D viewTransform = new Transform3D();
      viewTransform.setTranslation(new Vector3d(0, 0, 20)); //move "back" a little
      universe.getViewingPlatform().getViewPlatformTransform().setTransform(viewTransform);
      universe.addBranchGraph(group);
      return canvas3D;
   }

   private static DirectionalLight makeLight() {
      DirectionalLight light = new DirectionalLight(new Color3f(Color.WHITE), new Vector3f(-1.0f, -1.0f, -1.0f));
      light.setInfluencingBounds(new BoundingSphere(new Point3d(0, 0, 0), 100));
      return light;
   }

}

Integrating the BallPanel in the JavaFX app

public class FXAppWithSwingPanel extends Application {
   @Override
   public void start(Stage stage) {
       final SwingNode swingNode = new SwingNode();

       JPanel panel = new JPanel();       
       panel.setLayout(new BorderLayout());
       panel.setPreferredSize(new Dimension(450, 450));
       panel.add(new BallPanel(), BorderLayout.CENTER);
       swingNode.setContent(panel);

       Pane pane = new Pane();
       pane.getChildren().add(swingNode);

       stage.setScene(new Scene(pane, 500, 500));
       stage.show();
   }

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

}

All I see is a gray background where the sphere should be. I've tried a few different variations with no luck. Couldn't find anything online about this particular integration between Java 3D classes and JavaFX.

I know JavaFX has 3D graphics but I don't like them (at all).

Related, unresolved, question:

How Can I embed java3d's Canvas3d in javafx layout?


Solution

  • I finally managed to make it work using JCanvas3D. Credit for the suggestion goes to @gouessej. Many thanks.

    Here is the new makeCanvas method:

       private void makeCanvas() {
          GraphicsConfigTemplate3D gCT = new GraphicsConfigTemplate3D();
          JCanvas3D jCanvas3D = new JCanvas3D(gCT);
          Dimension canvasDim = new Dimension(400, 400);
          jCanvas3D.setPreferredSize(canvasDim);
          jCanvas3D.setSize(canvasDim);
          add(jCanvas3D, BorderLayout.CENTER);          
          Canvas3D canvas3D =  jCanvas3D.getOffscreenCanvas3D(); 
    
          View view = new View();
          view.setPhysicalBody(new PhysicalBody());
          view.setPhysicalEnvironment(new PhysicalEnvironment());
          view.addCanvas3D(canvas3D);
    
          ViewPlatform vp = new ViewPlatform();
          view.attachViewPlatform(vp);
    
    
          Transform3D viewTransform = new Transform3D();
          viewTransform.setTranslation(new Vector3d(0, 0, 20)); //move "back" a little
    
          TransformGroup viewTG = new TransformGroup();
          viewTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
          viewTG.setTransform(viewTransform);
    
          viewTG.addChild(vp);
    
    
          viewTG.addChild(makeLight());
          viewTG.addChild(new Sphere(5));
    
          BranchGroup group = new BranchGroup();
          group.addChild(viewTG);
          group.addChild(makeLight());
          group.addChild(new Sphere(5));
    
          VirtualUniverse vu = new VirtualUniverse();
          Locale locale = new Locale(vu);  
          locale.addBranchGraph(group);
       }
    

    Also, as James_D points out in the comments, Canvas3D was not the way to go as it cannot be correctly rendered in a JavaFX SwingNode because it is a heavyweight component.

    The SwingNode javadoc confirms it:

    The hierarchy of components contained in the JComponent instance should not contain any heavyweight components, otherwise SwingNode may fail to paint it.