All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.mihosoft.jcsg.ext.openjfx.shape3d.PolygonMeshView Maven / Gradle / Ivy

There is a newer version: 0.5.7
Show newest version
/*
 * Copyright (c) 2010, 2014, Oracle and/or its affiliates.
 * All rights reserved. Use is subject to license terms.
 *
 * This file is available and licensed under the following license:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  - Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the distribution.
 *  - Neither the name of Oracle Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package eu.mihosoft.jcsg.ext.openjfx.shape3d;

import eu.mihosoft.jcsg.ext.openjfx.shape3d.SubdivisionMesh.BoundaryMode;
import eu.mihosoft.jcsg.ext.openjfx.shape3d.SubdivisionMesh.MapBorderMode;
import java.util.Arrays;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ArrayChangeListener;
import javafx.collections.ObservableFloatArray;
import javafx.scene.Parent;
import javafx.scene.paint.Material;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;


/**
 * A MeshView node for Polygon Meshes
 */
public class PolygonMeshView extends Parent {
    private static final boolean DEBUG = false;
    private final MeshView meshView = new MeshView();

    private TriangleMesh triangleMesh = new TriangleMesh();
    
    // this is null if no subdivision is happening (i.e. subdivisionLevel = 0);
    private SubdivisionMesh subdivisionMesh;
    
    private final ArrayChangeListener meshPointsListener = (t, bln, i, i1) -> {
        pointsDirty = true;
        updateMesh();
    };
    private final ArrayChangeListener meshTexCoordListener = (t, bln, i, i1) -> {
        texCoordsDirty = true;
        updateMesh();
    };
    
    private boolean pointsDirty = true;
    private boolean pointsSizeDirty = true;
    private boolean texCoordsDirty = true;
    private boolean facesDirty = true;
    
    // =========================================================================
    // PROPERTIES

    /**
     * Specifies the 3D mesh data of this {@code MeshView}.
     *
     * @defaultValue null
     */
    private ObjectProperty meshProperty;
    public PolygonMesh getMesh() { return meshProperty().get(); }
    public void setMesh(PolygonMesh mesh) { meshProperty().set(mesh);}
    public ObjectProperty meshProperty() { 
        if (meshProperty == null) {
            meshProperty = new SimpleObjectProperty();
            meshProperty.addListener((observable, oldValue, newValue) -> {
                if (oldValue != null) {
                    oldValue.getPoints().removeListener(meshPointsListener);
                    oldValue.getPoints().removeListener(meshTexCoordListener);
                }

                meshProperty.set(newValue);

                pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true;
                updateMesh();
                
                if (newValue != null) {
                    newValue.getPoints().addListener(meshPointsListener);
                    newValue.getTexCoords().addListener(meshTexCoordListener);
                }
            });
        }
        return meshProperty;
    }
    
    /**
     * Defines the drawMode this {@code Shape3D}.
     *
     * @defaultValue DrawMode.FILL
     */
    private ObjectProperty drawMode;
    public final void setDrawMode(DrawMode value) { drawModeProperty().set(value); }
    public final DrawMode getDrawMode() { return drawMode == null ? DrawMode.FILL : drawMode.get(); }
    public final ObjectProperty drawModeProperty() {
        if (drawMode == null) {
            drawMode = new SimpleObjectProperty(PolygonMeshView.this, "drawMode", DrawMode.FILL) {
                @Override protected void invalidated() {
                    meshView.setDrawMode(get());
                    pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true;
                    updateMesh();
                }
            };
        }
        return drawMode;
    }

    /**
     * Defines the drawMode this {@code Shape3D}.
     *
     * @defaultValue CullFace.BACK
     */
    private ObjectProperty cullFace;
    public final void setCullFace(CullFace value) { cullFaceProperty().set(value); }
    public final CullFace getCullFace() { return cullFace == null ? CullFace.BACK : cullFace.get(); }
    public final ObjectProperty cullFaceProperty() {
        if (cullFace == null) {
            cullFace = new SimpleObjectProperty(PolygonMeshView.this, "cullFace", CullFace.BACK) {
                @Override protected void invalidated() {
                    meshView.setCullFace(get());
                }
            };
        }
        return cullFace;
    }

    /**
     * Defines the material this {@code Shape3D}.
     * The default material is null. If {@code Material} is null, a PhongMaterial
     * with a diffuse color of Color.LIGHTGRAY is used for rendering.
     *
     * @defaultValue null
     */
    private ObjectProperty materialProperty = new SimpleObjectProperty();
    public Material getMaterial() { return materialProperty.get(); }
    public void setMaterial(Material material) { materialProperty.set(material); }
    public ObjectProperty materialProperty() { return materialProperty; }

    /**
     * Number of iterations of Catmull Clark subdivision to apply to the mesh
     *
     * @defaultValue 0
     */
    private SimpleIntegerProperty subdivisionLevelProperty;
    public void setSubdivisionLevel(int subdivisionLevel) { subdivisionLevelProperty().set(subdivisionLevel); }
    public int getSubdivisionLevel() { return subdivisionLevelProperty == null ? 0 : subdivisionLevelProperty.get(); }
    public SimpleIntegerProperty subdivisionLevelProperty() { 
        if (subdivisionLevelProperty == null) {
            subdivisionLevelProperty = new SimpleIntegerProperty(getSubdivisionLevel()) {
                @Override protected void invalidated() {
                    // create SubdivisionMesh if subdivisionLevel is greater than 0
                    if ((getSubdivisionLevel() > 0) && (subdivisionMesh == null)) {
                        subdivisionMesh = new SubdivisionMesh(getMesh(), getSubdivisionLevel(), getBoundaryMode(), getMapBorderMode());
                        subdivisionMesh.getOriginalMesh().getPoints().addListener((t, bln, i, i1) -> subdivisionMesh.update());
                        setMesh(subdivisionMesh);
                    }
                    if (subdivisionMesh != null) {
                        subdivisionMesh.setSubdivisionLevel(getSubdivisionLevel());
                        subdivisionMesh.update();
                    }
                    pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true;
                    updateMesh();
                }
            };
        }
        return subdivisionLevelProperty;
    }
    
    /**
     * Texture mapping boundary rule for Catmull Clark subdivision applied to the mesh
     *
     * @defaultValue BoundaryMode.CREASE_EDGES
     */
    private SimpleObjectProperty boundaryMode;
    public void setBoundaryMode(BoundaryMode boundaryMode) { boundaryModeProperty().set(boundaryMode); }
    public BoundaryMode getBoundaryMode() { return boundaryMode == null ? BoundaryMode.CREASE_EDGES : boundaryMode.get(); }
    public SimpleObjectProperty boundaryModeProperty() {
        if (boundaryMode == null) {
            boundaryMode = new SimpleObjectProperty(getBoundaryMode()) {
                @Override protected void invalidated() {
                    if (subdivisionMesh != null) {
                        subdivisionMesh.setBoundaryMode(getBoundaryMode());
                        subdivisionMesh.update();
                    }
                    pointsDirty = true;
                    updateMesh();
                }
            };
        }
        return boundaryMode;
    }
    
    /**
     * Texture mapping smoothness option for Catmull Clark subdivision applied to the mesh
     *
     * @defaultValue MapBorderMode.NOT_SMOOTH
     */
    private SimpleObjectProperty mapBorderMode;
    public void setMapBorderMode(MapBorderMode mapBorderMode) { mapBorderModeProperty().set(mapBorderMode); }
    public MapBorderMode getMapBorderMode() { return mapBorderMode == null ? MapBorderMode.NOT_SMOOTH : mapBorderMode.get(); }
    public SimpleObjectProperty mapBorderModeProperty() { 
        if (mapBorderMode == null) {
            mapBorderMode = new SimpleObjectProperty(getMapBorderMode()) {
                @Override protected void invalidated() {
                    if (subdivisionMesh != null) {
                        subdivisionMesh.setMapBorderMode(getMapBorderMode());
                        subdivisionMesh.update();
                    }
                    texCoordsDirty = true;
                    updateMesh();
                }
            };
        }
        return mapBorderMode;
    }

    // =========================================================================
    // CONSTRUCTORS

    public PolygonMeshView() {
        meshView.materialProperty().bind(materialProperty());
        getChildren().add(meshView);
    }

    public PolygonMeshView(PolygonMesh mesh) {
        this();
        setMesh(mesh);
    }

    // =========================================================================
    // PRIVATE METHODS

    private void updateMesh() {
        PolygonMesh pmesh = getMesh();
        if (pmesh == null || pmesh.faces == null) {
            triangleMesh = new TriangleMesh();
            meshView.setMesh(triangleMesh);
            return;
        }

        final int pointElementSize = triangleMesh.getPointElementSize();
        final int faceElementSize = triangleMesh.getFaceElementSize();
        final boolean isWireframe = getDrawMode() == DrawMode.LINE;
        if (DEBUG) System.out.println("UPDATE MESH -- "+(isWireframe?"WIREFRAME":"SOLID"));
        final int numOfPoints = pmesh.getPoints().size() / pointElementSize;
        if (DEBUG) System.out.println("numOfPoints = " + numOfPoints);
        
        if(isWireframe) {
            // The current triangleMesh implementation gives buggy behavior when the size of faces are shrunken
            // Create a new TriangleMesh as a work around
            // [JIRA] (RT-31178)
            if (texCoordsDirty || facesDirty || pointsSizeDirty) {
                triangleMesh = new TriangleMesh();
                pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true; // to fill in the new triangle mesh
            }
            if (facesDirty) {
                facesDirty = false;
                // create faces for each edge
                int [] facesArray = new int [pmesh.getNumEdgesInFaces() * faceElementSize];
                int facesInd = 0;
                int pointsInd = pmesh.getPoints().size();
                for(int[] face: pmesh.faces) {
                    if (DEBUG) System.out.println("face.length = " + (face.length/2)+"  -- "+Arrays.toString(face));
                    int lastPointIndex = face[face.length-2];
                    if (DEBUG) System.out.println("    lastPointIndex = " + lastPointIndex);
                    for (int p=0;p
            


© 2015 - 2024 Weber Informatics LLC | Privacy Policy