title: In JavaFX, how to colour the reverse side of a mesh?
body: <p>I am creating a 3D plot in JavaFX using the same technique discussed in <a href="https://stackoverflow.com/a/31125736/58755">this answer</a>, with a MeshView and a PhongMaterial to provide the colours. However, only the top side of the material is coloured, and if the user rotates the camera to view from below, it's impossible to determine the shape of the plot because it is all black.</p>
  
  <p>My questions:</p>
  
  <ol>
  <li>Is there any way to set the material of the reverse side of the mesh?</li>
  <li>If not, is there a good approach to "faking" it? I would imagine creating a new mesh upside-down in exactly the same position would cause rendering issues; is the best approach to do that but apply a very small offset so that the two meshes are not exactly on top of each other?</li>
  </ol>
  
  <p><strong>Edit:</strong> I have included some example code below, which is cut down from my real code but contains enough to illustrate the problem. By default it displays the top of the mesh, which is coloured red in this example. If you change the line that reads <code>new Rotate(-30, Rotate.X_AXIS)</code> so that the angle becomes <code>+30</code> rather than <code>-30</code>, it will rotate the camera to show the underside of the mesh, which you will see appears black.</p>
  
  <pre class="lang-java prettyprint-override"><code>package test;
  
  import javafx.application.Application;
  import javafx.scene.DepthTest;
  import javafx.scene.PerspectiveCamera;
  import javafx.scene.Scene;
  import javafx.scene.SceneAntialiasing;
  import javafx.scene.SubScene;
  import javafx.scene.image.Image;
  import javafx.scene.image.PixelWriter;
  import javafx.scene.image.WritableImage;
  import javafx.scene.layout.Region;
  import javafx.scene.layout.StackPane;
  import javafx.scene.paint.Color;
  import javafx.scene.paint.PhongMaterial;
  import javafx.scene.shape.CullFace;
  import javafx.scene.shape.DrawMode;
  import javafx.scene.shape.MeshView;
  import javafx.scene.shape.TriangleMesh;
  import javafx.scene.transform.Rotate;
  import javafx.scene.transform.Translate;
  import javafx.stage.Stage;
  
  public class TestApp extends Application {
  
    public static void main(String[] args) {
      launch(args);
    }
  
    @Override
    public void start(Stage stage) throws Exception {
      TestPlot tp = new TestPlot();
      tp.setPrefSize(600, 400);
      Scene scene = new Scene(tp);
      stage.setScene(scene);
      stage.show();
    }
  
    class TestPlot extends Region {
      private final PerspectiveCamera camera = new PerspectiveCamera(true);
      private double[][] data = new double[500][500];
      private final StackPane root = new StackPane();
      private final SubScene subscene;
  
      public TestPlot() {
        subscene = new SubScene(root, 1, 1, true, SceneAntialiasing.BALANCED);
        subscene.setCamera(camera);
        getChildren().add(subscene);
  
        widthProperty().addListener((obs, oldVal, newVal) -&gt; refreshPlot());
        heightProperty().addListener((obs, oldVal, newVal) -&gt; refreshPlot());
        refreshPlot();
      }
  
      private void refreshPlot() {
        // Set the subscene bounds to match the plot bounds, in case the plot was
        // resized
        subscene.setHeight(this.getHeight());
        subscene.setWidth(this.getWidth());
  
        // Clear any existing stuff
        root.getChildren().clear();
        root.setStyle("-fx-background-color: rgba(0, 0, 0, 0);");
        root.getChildren().add(camera);
        camera.getTransforms().clear();
  
        int xDataPoints = data.length;
        int zDataPoints = data[0].length;
  
        // Create data mesh
        TriangleMesh mesh = new TriangleMesh();
        for (int x = 0; x &lt; xDataPoints; x++) {
          for (int z = 0; z &lt; zDataPoints; z++) {
            // Invert the data as JavaFX meshes are positive-down, whereas we expect
            // the plot to be positive-up
            mesh.getPoints().addAll(x, (float) (-data[x][z]), z);
          }
        }
  
        // Create faces from data mesh
        for (int x = 0; x &lt; xDataPoints - 1; x++) {
          for (int z = 0; z &lt; zDataPoints - 1; z++) {
            int tl = x * zDataPoints + z; // top-left
            int bl = x * zDataPoints + z + 1; // bottom-left
            int tr = (x + 1) * zDataPoints + z; // top-right
            int br = (x + 1) * zDataPoints + z + 1; // bottom-right
  
            int offset = (x * (zDataPoints - 1) + z) * 8 / 2; // div 2 because we have u AND v in the list
  
            // working
            mesh.getFaces().addAll(bl, offset + 1, tl, offset + 0, tr, offset + 2);
            mesh.getFaces().addAll(tr, offset + 2, br, offset + 3, bl, offset + 1);
          }
        }
  
        // Create data mesh texture map
        for (float x = 0; x &lt; xDataPoints - 1; x++) {
          for (float z = 0; z &lt; zDataPoints - 1; z++) {
            float x0 = x / xDataPoints;
            float z0 = z / zDataPoints;
            float x1 = (x + 1) / xDataPoints;
            float z1 = (z + 1) / zDataPoints;
  
            mesh.getTexCoords().addAll( //
                x0, z0, // 0, top-left
                x0, z1, // 1, bottom-left
                x1, z1, // 2, top-right
                x1, z1 // 3, bottom-right
            );
          }
        }
  
        // Create texture material
        Image diffuseMap = createTexture(data);
        PhongMaterial material = new PhongMaterial();
        material.setDiffuseMap(diffuseMap);
  
        // Create &amp; add mesh view
        MeshView meshView = new MeshView(mesh);
        meshView.setTranslateZ(-zDataPoints);
        meshView.setMaterial(material);
        meshView.setCullFace(CullFace.NONE);
        meshView.setDrawMode(DrawMode.FILL);
        meshView.setDepthTest(DepthTest.ENABLE);
        root.getChildren().addAll(meshView);
  
        double biggestAxisSize = xDataPoints;
        double z = -(0.5 * biggestAxisSize) / Math.tan(0.5 * Math.toRadians(camera.getFieldOfView()));
        camera.getTransforms().addAll(
            new Translate(0, 0, -zDataPoints / 3.0),
            new Rotate(-30, Rotate.X_AXIS),
            new Translate(0, 0.5, z)
        );
        camera.setFarClip(biggestAxisSize * 200.0);
      }
  
      private Image createTexture(double[][] data) {
        int width = data.length;
        int height = data[0].length;
  
        WritableImage wr = new WritableImage(width, height);
        PixelWriter pw = wr.getPixelWriter();
        for (int x = 0; x &lt; width; x++) {
          for (int y = 0; y &lt; height; y++) {
            pw.setColor(x, y, Color.RED);
          }
        }
        return wr;
      }
    }
  }
  </code></pre>