eu.mihosoft.vrl.v3d.ext.openjfx.shape3d.PolygonMeshView Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of JavaCad Show documentation
Show all versions of JavaCad Show documentation
A Java based CSG Cad library
/*
* 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.vrl.v3d.ext.openjfx.shape3d;
import eu.mihosoft.vrl.v3d.ext.openjfx.shape3d.SubdivisionMesh.BoundaryMode;
import eu.mihosoft.vrl.v3d.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;
import static javafx.scene.shape.TriangleMesh.*;
// TODO: Auto-generated Javadoc
/**
* A MeshView node for Polygon Meshes.
*/
public class PolygonMeshView extends Parent {
/** The Constant DEBUG. */
private static final boolean DEBUG = false;
/** The mesh view. */
private final MeshView meshView = new MeshView();
/** The triangle mesh. */
private TriangleMesh triangleMesh = new TriangleMesh();
/** The subdivision mesh. */
// this is null if no subdivision is happening (i.e. subdivisionLevel = 0);
private SubdivisionMesh subdivisionMesh;
/** The mesh points listener. */
private final ArrayChangeListener meshPointsListener = (t, bln, i, i1) -> {
pointsDirty = true;
updateMesh();
};
/** The mesh tex coord listener. */
private final ArrayChangeListener meshTexCoordListener = (t, bln, i, i1) -> {
texCoordsDirty = true;
updateMesh();
};
/** The points dirty. */
private boolean pointsDirty = true;
/** The points size dirty. */
private boolean pointsSizeDirty = true;
/** The tex coords dirty. */
private boolean texCoordsDirty = true;
/** The faces dirty. */
private boolean facesDirty = true;
// =========================================================================
// PROPERTIES
/**
* Specifies the 3D mesh data of this {@code MeshView}.
*
* @defaultValue null
*/
private ObjectProperty meshProperty;
/**
* Gets the mesh.
*
* @return the mesh
*/
public PolygonMesh getMesh() { return meshProperty().get(); }
/**
* Sets the mesh.
*
* @param mesh the new mesh
*/
public void setMesh(PolygonMesh mesh) { meshProperty().set(mesh);}
/**
* Mesh property.
*
* @return the object property
*/
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;
/**
* Sets the draw mode.
*
* @param value the new draw mode
*/
public final void setDrawMode(DrawMode value) { drawModeProperty().set(value); }
/**
* Gets the draw mode.
*
* @return the draw mode
*/
public final DrawMode getDrawMode() { return drawMode == null ? DrawMode.FILL : drawMode.get(); }
/**
* Draw mode property.
*
* @return the object property
*/
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;
/**
* Sets the cull face.
*
* @param value the new cull face
*/
public final void setCullFace(CullFace value) { cullFaceProperty().set(value); }
/**
* Gets the cull face.
*
* @return the cull face
*/
public final CullFace getCullFace() { return cullFace == null ? CullFace.BACK : cullFace.get(); }
/**
* Cull face property.
*
* @return the object property
*/
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();
/**
* Gets the material.
*
* @return the material
*/
public Material getMaterial() { return materialProperty.get(); }
/**
* Sets the material.
*
* @param material the new material
*/
public void setMaterial(Material material) { materialProperty.set(material); }
/**
* Material property.
*
* @return the object property
*/
public ObjectProperty materialProperty() { return materialProperty; }
/**
* Number of iterations of Catmull Clark subdivision to apply to the mesh.
*
* @defaultValue 0
*/
private SimpleIntegerProperty subdivisionLevelProperty;
/**
* Sets the subdivision level.
*
* @param subdivisionLevel the new subdivision level
*/
public void setSubdivisionLevel(int subdivisionLevel) { subdivisionLevelProperty().set(subdivisionLevel); }
/**
* Gets the subdivision level.
*
* @return the subdivision level
*/
public int getSubdivisionLevel() { return subdivisionLevelProperty == null ? 0 : subdivisionLevelProperty.get(); }
/**
* Subdivision level property.
*
* @return the simple integer property
*/
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;
/**
* Sets the boundary mode.
*
* @param boundaryMode the new boundary mode
*/
public void setBoundaryMode(BoundaryMode boundaryMode) { boundaryModeProperty().set(boundaryMode); }
/**
* Gets the boundary mode.
*
* @return the boundary mode
*/
public BoundaryMode getBoundaryMode() { return boundaryMode == null ? BoundaryMode.CREASE_EDGES : boundaryMode.get(); }
/**
* Boundary mode property.
*
* @return the simple object property
*/
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;
/**
* Sets the map border mode.
*
* @param mapBorderMode the new map border mode
*/
public void setMapBorderMode(MapBorderMode mapBorderMode) { mapBorderModeProperty().set(mapBorderMode); }
/**
* Gets the map border mode.
*
* @return the map border mode
*/
public MapBorderMode getMapBorderMode() { return mapBorderMode == null ? MapBorderMode.NOT_SMOOTH : mapBorderMode.get(); }
/**
* Map border mode property.
*
* @return the simple object property
*/
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
/**
* Instantiates a new polygon mesh view.
*/
public PolygonMeshView() {
meshView.materialProperty().bind(materialProperty());
getChildren().add(meshView);
}
/**
* Instantiates a new polygon mesh view.
*
* @param mesh the mesh
*/
public PolygonMeshView(PolygonMesh mesh) {
this();
setMesh(mesh);
}
// =========================================================================
// PRIVATE METHODS
/**
* Update mesh.
*/
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