BowlerKernel
PolygonMeshView.java
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2010, 2014, Oracle and/or its affiliates.
3  * All rights reserved. Use is subject to license terms.
4  *
5  * This file is available and licensed under the following license:
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * - Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * - Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in
15  * the documentation and/or other materials provided with the distribution.
16  * - Neither the name of Oracle Corporation nor the names of its
17  * contributors may be used to endorse or promote products derived
18  * from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package eu.mihosoft.vrl.v3d.ext.openjfx.shape3d;
33 
34 import eu.mihosoft.vrl.v3d.ext.openjfx.shape3d.SubdivisionMesh.BoundaryMode;
35 import eu.mihosoft.vrl.v3d.ext.openjfx.shape3d.SubdivisionMesh.MapBorderMode;
36 import java.util.Arrays;
37 import javafx.beans.property.ObjectProperty;
38 import javafx.beans.property.SimpleIntegerProperty;
39 import javafx.beans.property.SimpleObjectProperty;
40 import javafx.collections.ArrayChangeListener;
41 import javafx.collections.ObservableFloatArray;
42 import javafx.scene.Parent;
43 import javafx.scene.paint.Material;
44 import javafx.scene.shape.CullFace;
45 import javafx.scene.shape.DrawMode;
46 import javafx.scene.shape.MeshView;
47 import javafx.scene.shape.TriangleMesh;
48 import static javafx.scene.shape.TriangleMesh.*;
49 
50 
51 // TODO: Auto-generated Javadoc
55 public class PolygonMeshView extends Parent {
56 
58  private static final boolean DEBUG = false;
59 
61  private final MeshView meshView = new MeshView();
62 
64  private TriangleMesh triangleMesh = new TriangleMesh();
65 
67  // this is null if no subdivision is happening (i.e. subdivisionLevel = 0);
69 
71  private final ArrayChangeListener<ObservableFloatArray> meshPointsListener = (t, bln, i, i1) -> {
72  pointsDirty = true;
73  updateMesh();
74  };
75 
77  private final ArrayChangeListener<ObservableFloatArray> meshTexCoordListener = (t, bln, i, i1) -> {
78  texCoordsDirty = true;
79  updateMesh();
80  };
81 
83  private boolean pointsDirty = true;
84 
86  private boolean pointsSizeDirty = true;
87 
89  private boolean texCoordsDirty = true;
90 
92  private boolean facesDirty = true;
93 
94  // =========================================================================
95  // PROPERTIES
96 
102  private ObjectProperty<PolygonMesh> meshProperty;
103 
109  public PolygonMesh getMesh() { return meshProperty().get(); }
110 
116  public void setMesh(PolygonMesh mesh) { meshProperty().set(mesh);}
117 
123  public ObjectProperty<PolygonMesh> meshProperty() {
124  if (meshProperty == null) {
125  meshProperty = new SimpleObjectProperty<PolygonMesh>();
126  meshProperty.addListener((observable, oldValue, newValue) -> {
127  if (oldValue != null) {
128  oldValue.getPoints().removeListener(meshPointsListener);
129  oldValue.getPoints().removeListener(meshTexCoordListener);
130  }
131 
132  meshProperty.set(newValue);
133 
135  updateMesh();
136 
137  if (newValue != null) {
138  newValue.getPoints().addListener(meshPointsListener);
139  newValue.getTexCoords().addListener(meshTexCoordListener);
140  }
141  });
142  }
143  return meshProperty;
144  }
145 
151  private ObjectProperty<DrawMode> drawMode;
152 
158  public final void setDrawMode(DrawMode value) { drawModeProperty().set(value); }
159 
165  public final DrawMode getDrawMode() { return drawMode == null ? DrawMode.FILL : drawMode.get(); }
166 
172  public final ObjectProperty<DrawMode> drawModeProperty() {
173  if (drawMode == null) {
174  drawMode = new SimpleObjectProperty<DrawMode>(PolygonMeshView.this, "drawMode", DrawMode.FILL) {
175  @Override protected void invalidated() {
176  meshView.setDrawMode(get());
178  updateMesh();
179  }
180  };
181  }
182  return drawMode;
183  }
184 
190  private ObjectProperty<CullFace> cullFace;
191 
197  public final void setCullFace(CullFace value) { cullFaceProperty().set(value); }
198 
204  public final CullFace getCullFace() { return cullFace == null ? CullFace.BACK : cullFace.get(); }
205 
211  public final ObjectProperty<CullFace> cullFaceProperty() {
212  if (cullFace == null) {
213  cullFace = new SimpleObjectProperty<CullFace>(PolygonMeshView.this, "cullFace", CullFace.BACK) {
214  @Override protected void invalidated() {
215  meshView.setCullFace(get());
216  }
217  };
218  }
219  return cullFace;
220  }
221 
229  private ObjectProperty<Material> materialProperty = new SimpleObjectProperty<Material>();
230 
236  public Material getMaterial() { return materialProperty.get(); }
237 
243  public void setMaterial(Material material) { materialProperty.set(material); }
244 
250  public ObjectProperty<Material> materialProperty() { return materialProperty; }
251 
257  private SimpleIntegerProperty subdivisionLevelProperty;
258 
264  public void setSubdivisionLevel(int subdivisionLevel) { subdivisionLevelProperty().set(subdivisionLevel); }
265 
271  public int getSubdivisionLevel() { return subdivisionLevelProperty == null ? 0 : subdivisionLevelProperty.get(); }
272 
278  public SimpleIntegerProperty subdivisionLevelProperty() {
279  if (subdivisionLevelProperty == null) {
280  subdivisionLevelProperty = new SimpleIntegerProperty(getSubdivisionLevel()) {
281  @Override protected void invalidated() {
282  // create SubdivisionMesh if subdivisionLevel is greater than 0
283  if ((getSubdivisionLevel() > 0) && (subdivisionMesh == null)) {
285  subdivisionMesh.getOriginalMesh().getPoints().addListener((t, bln, i, i1) -> subdivisionMesh.update());
287  }
288  if (subdivisionMesh != null) {
291  }
293  updateMesh();
294  }
295  };
296  }
298  }
299 
305  private SimpleObjectProperty<BoundaryMode> boundaryMode;
306 
313 
320 
326  public SimpleObjectProperty<BoundaryMode> boundaryModeProperty() {
327  if (boundaryMode == null) {
328  boundaryMode = new SimpleObjectProperty<BoundaryMode>(getBoundaryMode()) {
329  @Override protected void invalidated() {
330  if (subdivisionMesh != null) {
333  }
334  pointsDirty = true;
335  updateMesh();
336  }
337  };
338  }
339  return boundaryMode;
340  }
341 
347  private SimpleObjectProperty<MapBorderMode> mapBorderMode;
348 
355 
362 
368  public SimpleObjectProperty<MapBorderMode> mapBorderModeProperty() {
369  if (mapBorderMode == null) {
370  mapBorderMode = new SimpleObjectProperty<MapBorderMode>(getMapBorderMode()) {
371  @Override protected void invalidated() {
372  if (subdivisionMesh != null) {
375  }
376  texCoordsDirty = true;
377  updateMesh();
378  }
379  };
380  }
381  return mapBorderMode;
382  }
383 
384  // =========================================================================
385  // CONSTRUCTORS
386 
390  public PolygonMeshView() {
391  meshView.materialProperty().bind(materialProperty());
392  getChildren().add(meshView);
393  }
394 
401  this();
402  setMesh(mesh);
403  }
404 
405  // =========================================================================
406  // PRIVATE METHODS
407 
411  private void updateMesh() {
412  PolygonMesh pmesh = getMesh();
413  if (pmesh == null || pmesh.faces == null) {
414  triangleMesh = new TriangleMesh();
415  meshView.setMesh(triangleMesh);
416  return;
417  }
418 
419  final int pointElementSize = triangleMesh.getPointElementSize();
420  final int faceElementSize = triangleMesh.getFaceElementSize();
421  final boolean isWireframe = getDrawMode() == DrawMode.LINE;
422  if (DEBUG) System.out.println("UPDATE MESH -- "+(isWireframe?"WIREFRAME":"SOLID"));
423  final int numOfPoints = pmesh.getPoints().size() / pointElementSize;
424  if (DEBUG) System.out.println("numOfPoints = " + numOfPoints);
425 
426  if(isWireframe) {
427  // The current triangleMesh implementation gives buggy behavior when the size of faces are shrunken
428  // Create a new TriangleMesh as a work around
429  // [JIRA] (RT-31178)
431  triangleMesh = new TriangleMesh();
432  pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true; // to fill in the new triangle mesh
433  }
434  if (facesDirty) {
435  facesDirty = false;
436  // create faces for each edge
437  int [] facesArray = new int [pmesh.getNumEdgesInFaces() * faceElementSize];
438  int facesInd = 0;
439  int pointsInd = pmesh.getPoints().size();
440  for(int[] face: pmesh.faces) {
441  if (DEBUG) System.out.println("face.length = " + (face.length/2)+" -- "+Arrays.toString(face));
442  int lastPointIndex = face[face.length-2];
443  if (DEBUG) System.out.println(" lastPointIndex = " + lastPointIndex);
444  for (int p=0;p<face.length;p+=2) {
445  int pointIndex = face[p];
446  if (DEBUG) System.out.println(" connecting point["+lastPointIndex+"] to point[" + pointIndex+"]");
447  facesArray[facesInd++] = lastPointIndex;
448  facesArray[facesInd++] = 0;
449  facesArray[facesInd++] = pointIndex;
450  facesArray[facesInd++] = 0;
451  facesArray[facesInd++] = pointsInd / pointElementSize;
452  facesArray[facesInd++] = 0;
453  if (DEBUG) System.out.println(" facesInd = " + facesInd);
454  pointsInd += pointElementSize;
455  lastPointIndex = pointIndex;
456  }
457  }
458  triangleMesh.getFaces().setAll(facesArray);
459  triangleMesh.getFaceSmoothingGroups().clear();
460  }
461  if (texCoordsDirty) {
462  texCoordsDirty = false;
463  // set simple texCoords for wireframe
464  triangleMesh.getTexCoords().setAll(0,0);
465  }
466  if (pointsDirty) {
467  pointsDirty = false;
468  // create points and copy over points to the first part of the array
469  float [] pointsArray = new float [pmesh.getPoints().size() + pmesh.getNumEdgesInFaces()*3];
470  pmesh.getPoints().copyTo(0, pointsArray, 0, pmesh.getPoints().size());
471 
472  // add point for each edge
473  int pointsInd = pmesh.getPoints().size();
474  for(int[] face: pmesh.faces) {
475  int lastPointIndex = face[face.length-2];
476  for (int p=0;p<face.length;p+=2) {
477  int pointIndex = face[p];
478  // get start and end point
479  final float x1 = pointsArray[lastPointIndex * pointElementSize];
480  final float y1 = pointsArray[lastPointIndex * pointElementSize + 1];
481  final float z1 = pointsArray[lastPointIndex * pointElementSize + 2];
482  final float x2 = pointsArray[pointIndex * pointElementSize];
483  final float y2 = pointsArray[pointIndex * pointElementSize + 1];
484  final float z2 = pointsArray[pointIndex * pointElementSize + 2];
485  final float distance = Math.abs(distanceBetweenPoints(x1,y1,z1,x2,y2,z2));
486  final float offset = distance/1000;
487  // add new point
488  pointsArray[pointsInd++] = x2 + offset;
489  pointsArray[pointsInd++] = y2 + offset;
490  pointsArray[pointsInd++] = z2 + offset;
491  lastPointIndex = pointIndex;
492  }
493  }
494  triangleMesh.getPoints().setAll(pointsArray);
495  }
496  } else {
497  // The current triangleMesh implementation gives buggy behavior when the size of faces are shrunken
498  // Create a new TriangleMesh as a work around
499  // [JIRA] (RT-31178)
501  triangleMesh = new TriangleMesh();
502  pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true; // to fill in the new triangle mesh
503  }
504  if (facesDirty) {
505  facesDirty = false;
506  // create faces and break into triangles
507  final int numOfFacesBefore = pmesh.faces.length;
508  final int numOfFacesAfter = pmesh.getNumEdgesInFaces() - 2*numOfFacesBefore;
509  int [] facesArray = new int [numOfFacesAfter * faceElementSize];
510  int [] smoothingGroupsArray = new int [numOfFacesAfter];
511  int facesInd = 0;
512  for(int f = 0; f < pmesh.faces.length; f++) {
513  int[] face = pmesh.faces[f];
514  int currentSmoothGroup = pmesh.getFaceSmoothingGroups().get(f);
515  if (DEBUG) System.out.println("face.length = " + face.length+" -- "+Arrays.toString(face));
516  int firstPointIndex = face[0];
517  int firstTexIndex = face[1];
518  int lastPointIndex = face[2];
519  int lastTexIndex = face[3];
520  for (int p=4;p<face.length;p+=2) {
521  int pointIndex = face[p];
522  int texIndex = face[p+1];
523  facesArray[facesInd * faceElementSize] = firstPointIndex;
524  facesArray[facesInd * faceElementSize + 1] = firstTexIndex;
525  facesArray[facesInd * faceElementSize + 2] = lastPointIndex;
526  facesArray[facesInd * faceElementSize + 3] = lastTexIndex;
527  facesArray[facesInd * faceElementSize + 4] = pointIndex;
528  facesArray[facesInd * faceElementSize + 5] = texIndex;
529  smoothingGroupsArray[facesInd] = currentSmoothGroup;
530  facesInd++;
531  lastPointIndex = pointIndex;
532  lastTexIndex = texIndex;
533  }
534  }
535  triangleMesh.getFaces().setAll(facesArray);
536  triangleMesh.getFaceSmoothingGroups().setAll(smoothingGroupsArray);
537  }
538  if (texCoordsDirty) {
539  texCoordsDirty = false;
540  triangleMesh.getTexCoords().setAll(pmesh.getTexCoords());
541  }
542  if (pointsDirty) {
543  pointsDirty = false;
544  triangleMesh.getPoints().setAll(pmesh.getPoints());
545  }
546  }
547 
548  if (DEBUG) System.out.println("CREATING TRIANGLE MESH");
549  if (DEBUG) System.out.println(" points = "+Arrays.toString(((TriangleMesh) meshView.getMesh()).getPoints().toArray(null)));
550  if (DEBUG) System.out.println(" texCoords = "+Arrays.toString(((TriangleMesh) meshView.getMesh()).getTexCoords().toArray(null)));
551  if (DEBUG) System.out.println(" faces = "+Arrays.toString(((TriangleMesh) meshView.getMesh()).getFaces().toArray(null)));
552 
553  if (meshView.getMesh() != triangleMesh) {
554  meshView.setMesh(triangleMesh);
555  }
557  }
558 
570  private float distanceBetweenPoints(float x1, float y1, float z1, float x2, float y2, float z2) {
571  return (float)Math.sqrt(
572  Math.pow(z2 - z1,2) +
573  Math.pow(x2 - x1,2) +
574  Math.pow(y2 - y1,2));
575  }
576 }
float distanceBetweenPoints(float x1, float y1, float z1, float x2, float y2, float z2)
final ArrayChangeListener< ObservableFloatArray > meshPointsListener
SimpleObjectProperty< MapBorderMode > mapBorderMode
SimpleObjectProperty< MapBorderMode > mapBorderModeProperty()
SimpleObjectProperty< BoundaryMode > boundaryModeProperty()
final ArrayChangeListener< ObservableFloatArray > meshTexCoordListener
void setMapBorderMode(SubdivisionMesh.MapBorderMode mapBorderMode)
void setBoundaryMode(SubdivisionMesh.BoundaryMode boundaryMode)