1

Is there any possible way to convert JavaFX Node, Group, or Shape3D to Mesh?

I need this to export my scene to '.obj' and the only exporter I found (in FXyz library) requires Mesh.

kcpr
  • 1,055
  • 1
  • 12
  • 28
  • Every 3D node in your scene could be transformed into equivalent objects with a known mesh, and these could be exported. Is this what you are looking for? What kind of objects do you have? – José Pereda Jan 03 '16 at 01:06
  • @JoséPereda, I think so. It at least seems like a step in a good direction. I use `Sphere` and `Cylinder` objects. – kcpr Jan 03 '16 at 01:27
  • Can you post a sample of what you have? – José Pereda Jan 03 '16 at 01:29
  • @JoséPereda, this is part of bigger project, so it may be hard to understand at first, but let's consider this class: http://shoutkey.com/east . There's `Group root` which contains all 3D objects I use. To be specific `VisualizedConnection` and `VisualizedNode` objects, which extends `Cylinder` and `Sphere`. If I knew how to convert each of this objects to `TriangleMesh` I would manage I think. – kcpr Jan 03 '16 at 01:54

1 Answers1

2

The main problem when using built-in Shape3D nodes like Sphere, Cylinder or Box is those don't expose their TriangleMesh.

If you need that mesh, there are several options:

  1. Create your own implementation of that shapes, where you provide the mesh
  2. Use FXyz library equivalent shapes instead, so you will have already the mesh in all of them
  3. Stick to built-in Shape3D nodes if this is a requirement, but before exporting them, transform them to equivalent shapes from the FXyz library, so you can access their meshes.

In terms of exporting the model, the current ObjWriter in FXyz only exports one mesh at a time, so you can:

  1. Export all the shapes to their own .obj file
  2. Modify ObjWriter in order to export a list of meshes to a single file (you are welcome to create a pull request to FXyz if you do)
  3. use MeshHelper to create one single mesh (only one material could be applied), and export it to a single .obj file.

I'll assume the third case in both situations, creating a new TexturedMesh object, based in all the 3D shapes found on the scene. This will have a single mesh that can be exported to an obj file. Notice you need FXyzLib.jar.

class EquivalentMesh extends TexturedMesh {

    private MeshHelper mh = null;

    public EquivalentMesh(Parent root) {
        ArrayList<Node> nodes = new ArrayList<>();
        addShapes3D(root, nodes);

        transformAndMerge(nodes);
    }

    // Transform Built-in Shape3Ds to a single TexturedMesh
    private void transformAndMerge(ArrayList<Node> nodes) {

        nodes.stream().forEach(shape -> {
            TriangleMesh tm = null;
            if (shape instanceof Sphere) {
                Sphere sphere = (Sphere)shape;
                SegmentedSphereMesh sm = new SegmentedSphereMesh(sphere.getRadius());
                sm.setCenter(new Point3D((float)sphere.getTranslateX(), (float)sphere.getTranslateY(), (float)sphere.getTranslateZ()));
                tm = (TriangleMesh)sm.getMesh();
            } else if (shape instanceof Cylinder) {
                Cylinder cylinder = (Cylinder)shape;
                FrustumMesh fm = new FrustumMesh(cylinder.getRadius(), cylinder.getRadius(), cylinder.getHeight());
                Affine affine = new Affine();
                cylinder.getTransforms().forEach(affine::append);
                javafx.geometry.Point3D ini = affine.transform(new javafx.geometry.Point3D(0, cylinder.getHeight()/2, 0));
                javafx.geometry.Point3D end = affine.transform(new javafx.geometry.Point3D(0, - cylinder.getHeight()/2, 0));
                fm.setAxisOrigin(new Point3D((float)ini.getX(), (float)ini.getY(), (float)ini.getZ()));
                fm.setAxisEnd(new Point3D((float)end.getX(), (float)end.getY(), (float)end.getZ()));
                fm.setSectionType(TriangleMeshHelper.SectionType.CIRCLE);
                tm = (TriangleMesh)fm.getMesh();
            } else if (shape instanceof Box) {
                Box box = (Box)shape;
                CuboidMesh cm = new CuboidMesh(box.getWidth(), box.getHeight(), box.getDepth());
                cm.setCenter(new Point3D((float)box.getTranslateX(), (float)box.getTranslateY(), (float)box.getTranslateZ()));
                // TODO: apply rotations
                tm = (TriangleMesh)cm.getMesh();
            } else if (shape instanceof MeshView) {
               tm = (TriangleMesh)((MeshView)shape).getMesh();
               // TODO: apply transformations 
            }

            if (mh == null) {
                mh = new MeshHelper(tm);
            } else {
                mh.addMesh(new MeshHelper(tm));
            }
        });

        // create single mesh
        updateMesh(mh);
    }

    private void addShapes3D(Parent parent, ArrayList<Node> nodes) {
        for (Node node : parent.getChildrenUnmodifiable()) {
            if (node instanceof Shape3D) {
                nodes.add(node);
            }
            if (node instanceof Parent) {
                addShapes3D((Parent)node, nodes);
            }
        }
    }

    @Override
    protected void updateMesh() {
        // no-op
    }

    // export to obj and mtl
    public void export(String nameFile) {
        OBJWriter writer=new OBJWriter((TriangleMesh) getMesh(), nameFile);
        writer.setMaterialColor(Color.RED);
        writer.exportMesh();
    }

}

In order to test it, let's create a simple scene:

@Override
public void start(Stage primaryStage) {
    Sphere sphere = new Sphere(100);
    sphere.setMaterial(new PhongMaterial(Color.BLUE));
    Box box = new Box(50,50,50);
    box.setMaterial(new PhongMaterial(Color.RED));
    box.setTranslateX(300);
    Cylinder cylinder = new Cylinder(2, 300);
    cylinder.setMaterial(new PhongMaterial(Color.GREEN));
    // Transformations applied:
    cylinder.getTransforms().addAll(new Translate(150, 0, 0), new Rotate(90, Rotate.Z_AXIS));

    Group group = new Group(cylinder, sphere, box);
    StackPane root = new StackPane(group);

    Scene scene = new Scene(root, 600, 400);

    primaryStage.setScene(scene);
    primaryStage.show();

    // export as single mesh
    EquivalentMesh equivalentMesh = new EquivalentMesh(root);
    equivalentMesh.export("group");
}

Initial scene

Notice that by creating a new instance of EquivalentMesh, now we have a single mesh and we can export it, generating group.obj and group.mtl files.

Finally, using 3DViewer to import the obj file, this is what you'll get:

3DViewer obj file

José Pereda
  • 44,311
  • 7
  • 104
  • 132